High-level architecture map for commands, workflows, services, and JS integration.
This page describes how the WSM codebase is organized. It is aimed at contributors who want to understand where behavior lives, how layers connect, and where to add new functionality.
WSM follows a strict four-layer architecture. Each layer has a clear responsibility, and dependencies flow downward:
┌──────────────────────────────────────────────────┐
│ cmd/wsm/cmds/ CLI adapters │
│ registry/ workspace/ git/ js/ │
├──────────────────────────────────────────────────┤
│ pkg/wsm/workflows/ Orchestration layer │
│ create, status, commit, rebase, fork, ... │
├──────────────────────────────────────────────────┤
│ pkg/wsm/ Core domain services │
│ workspace, discovery, git_operations, branch/ │
├──────────────────────────────────────────────────┤
│ pkg/wsm/gitclient/ Git abstraction │
│ cli backend (system git) │
└──────────────────────────────────────────────────┘
cmd/wsm/cmds/)Each Cobra command lives in a subpackage. Command files are thin: they decode flags into a settings struct, call a workflow, and format output. Business logic does not belong here.
Subpackages:
registry/ -- discover, list repos, list workspacesworkspace/ -- create, info, status, add, remove, fork, merge, deletegit/ -- commit, diff, log, branch (create/switch/list), rebase (start/status/continue/abort)js/ -- runnerShared infrastructure lives in cmd/wsm/cmds/common/:
build.go -- Glazed command description builder with standard sectionsruntime.go -- --output-mode resolution and structured row emissionpkg/wsm/workflows/)Workflows are the orchestration layer. Each workflow encapsulates a complete user-facing operation and defines its own request/result types:
| Workflow | Purpose |
|---|---|
DiscoverWorkflow | Scan directories, update registry |
CreateWorkflow | Resolve branches, create worktrees, write workspace config |
StatusWorkflow | Gather git status across repos (parallelized with --jobs) |
CommitWorkflow | Two-phase: Prepare() then Execute() for multi-repo commits |
RebaseWorkflow | Rebase/status/continue/abort with conflict detection |
ListWorkflow | List repositories and workspaces |
InfoWorkflow | Resolve workspace, extract field values |
ForkWorkflow | Two-phase: Plan() then Fork() |
DeleteWorkflow | Preview then delete with worktree cleanup |
MergeWorkflow | Merge branches back and optionally clean up |
Workflows own the sequencing of operations. They call into core domain services but never call CLI commands.
pkg/wsm/)The domain layer contains the foundational types and services:
types.go -- Repository, Workspace, RepositoryStatus, WorkspaceStatus, WorkspaceConfigworkspace.go -- WorkspaceManager with create, load, delete, detect operationsworkspace_context.go -- Workspace detection from working directorydiscovery.go -- Repository scanning logicstatus.go -- StatusChecker for per-repo git statusgit_operations.go -- Diff and other git operationshistory_operations.go -- Log and commit history queriesbranch_operations.go -- Create, switch, list branches across reposrebase_operations.go -- Low-level rebase: start, continue, abort, detect state, list conflictspkg/wsm/gitclient/)The git client provides an abstraction over git operations. WSM uses the system git CLI backend:
client.go -- Interface definitioncli_client.go -- Git operations via git CLIworktree_cli.go -- Worktree operations via git worktreeBranch policy is centralized in pkg/wsm/branch/ to avoid duplicating branch
decisions across commands. The system uses typed enums:
ResolutionMode -- what operation triggered the branch decision:
CreateWorktree -- creating a new workspace worktreeAddRepository -- adding a repo to an existing workspaceSync -- syncing branches across a workspaceResolutionStrategy -- how to obtain the target branch:
UseLocal -- use the existing local branch as-isTrackRemote -- create a local tracking branch from remoteCreateFromBase -- create a new branch from the specified baseCreateFromHead -- create a new branch from HEADRemoteRefKind -- how to interpret remote references:
None -- no remote referenceRemoteTrackingBranch -- reference is remote/branch formatA BranchResolutionRequest goes in, a BranchResolutionPlan comes out. The
plan is deterministic: same inputs always produce the same strategy. Commands
and workflows use the branch service rather than implementing their own logic.
The JavaScript layer lives in pkg/wsmjs/ and mirrors the CLI/workflow
architecture:
pkg/wsmjs/
├── service/ Manager facade wrapping workflows
├── module/ goja native module (require("wsm"))
└── runner/ Script file loader and executor
service/manager.go -- The Manager struct wraps workflow instances
and provides a flat Go API: Discover, CreateWorkspace, Status,
ListWorkspaces, ListRepositories.module/module.go -- Registers a goja native module that maps JS calls
to service methods. Handles type conversion between Go and JS via JSON
round-trip.runner/runner.go -- RunFile() bootstraps a goja runtime, registers
the wsm module, loads and executes a script file, and returns its result.The key design decision is that the JS API calls the same workflow layer as the
CLI. There are no separate implementations -- require("wsm").createWorkspace()
and wsm create both go through CreateWorkflow.
All commands use the shared output mode system defined in
cmd/wsm/cmds/common/runtime.go:
ShouldOutputHuman(mode) -- true for human and both modesShouldOutputData(mode) -- true for data and both modesEmitRows(ctx, vals, rows) -- emits structured data through GlazedCommands typically have two output blocks: one for human-readable text (headers,
tables, status messages) and one for structured rows. The both mode runs them
sequentially.
cmd/wsm/cmds/ subpackage.pkg/wsm/workflows/ with request/result types.pkg/wsm/, not implement git logic directly.cmd/wsm/root.go.pkg/wsmjs/service/manager.go and expose it in pkg/wsmjs/module/module.go.| Problem | Cause | Solution |
|---|---|---|
| New command has lots of business logic | Logic added in cmd/ instead of workflow | Move orchestration into pkg/wsm/workflows/ |
| Duplicate branch decisions | Branch policy bypassed | Route through pkg/wsm/branch/ service |
| JS API diverges from CLI behavior | Separate code paths | Both must call the same workflow |
| Rebase state detection fails | Missing .git/rebase-merge check | See rebase_operations.go for state detection logic |
wsm help wsm-command-referencewsm help wsm-persistence-and-statewsm help wsm-js-api-and-runner