Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
5.6 KiB
Workstream Flag (--ws)
Overview
The --ws <name> flag scopes MSD operations to a specific workstream, enabling
parallel milestone work by multiple Claude Code instances on the same codebase.
Resolution Priority
--ws <name>flag (explicit, highest priority)MSD_WORKSTREAMenvironment variable (per-instance)- Session-scoped active workstream pointer in temp storage (per runtime session / terminal), when that pointer exists and is non-blank
.planning/active-workstreamfile — consulted whenever step 3 has nothing to say: either there is no session identity at all, or there is one but it has never pointed at a workstream. A session that already has its own pointer (step 3) is never overridden by this step, even if that pointer is stale.null— flat mode (no workstreams)
Why session-scoped pointers exist
The shared .planning/active-workstream file is fundamentally unsafe when multiple
Claude/Codex instances are active on the same repo at the same time. One session can
silently repoint another session's STATE.md, ROADMAP.md, and phase paths.
MSD now prefers a session-scoped pointer keyed by runtime/session identity
(MSD_SESSION_KEY, CODEX_THREAD_ID, CLAUDE_CODE_SESSION_ID,
CLAUDE_CODE_SSE_PORT, terminal session IDs,
or the controlling TTY). This keeps concurrent sessions isolated while preserving
legacy compatibility for runtimes that do not expose a stable session key.
A session that has never set its own pointer inherits .planning/active-workstream
(step 4) rather than silently falling back to flat mode — this does not weaken the
isolation guarantee above: inheritance only fires when a session's own pointer is
absent, and a session that has ever set one is never repointed by the shared file.
Session Identity Resolution
When MSD resolves the session-scoped pointer in step 3 above, it uses this order:
- Explicit runtime/session env vars such as
MSD_SESSION_KEY,CODEX_THREAD_ID,CLAUDE_SESSION_ID,CLAUDE_CODE_SESSION_ID,CLAUDE_CODE_SSE_PORT,OPENCODE_SESSION_ID,GEMINI_SESSION_ID,CURSOR_SESSION_ID,WINDSURF_SESSION_ID,TERM_SESSION_ID,WT_SESSION,TMUX_PANE, andZELLIJ_SESSION_NAME TTYorSSH_TTYif the shell/runtime already exposes the terminal path- A single best-effort
ttyprobe, but only when stdin is interactive
If none of those produce a stable identity, MSD does not keep probing. It falls
back directly to the legacy shared .planning/active-workstream file.
This matters in headless or stripped environments: when stdin is already
non-interactive, MSD intentionally skips shelling out to tty because that path
cannot discover a stable session identity and only adds avoidable failures on the
routing hot path.
Pointer Lifecycle
Session-scoped pointers are intentionally lightweight and best-effort:
- Clearing a workstream for one session removes only that session's pointer file.
This returns that session to step 4 of Resolution Priority above — it goes back
to inheriting
.planning/active-workstream(if a marker exists there), not to flat mode. A cleared session with no marker present resolves tonull; a cleared session with a marker present resolves to whatever that marker names. To force flat mode for a cleared session, remove the shared marker file, or use an explicit override such as--ws/MSD_WORKSTREAMon the command in question. - If that was the last pointer for the repo, MSD also removes the now-empty per-project temp directory
- If sibling session pointers still exist, the temp directory is left in place
- When a pointer refers to a workstream directory that no longer exists, MSD
treats it as stale state: it removes that pointer file and resolves to
nulluntil the session explicitly sets a new active workstream again
MSD does not currently run a background garbage collector for historical temp directories. Cleanup is opportunistic at the pointer being cleared or self-healed, and broader temp hygiene is left to OS temp cleanup or future maintenance work.
Routing Propagation
All workflow routing commands include ${MSD_WS} which:
- Expands to
--ws <name>when a workstream is active - Expands to empty string in flat mode (backward compatible)
This ensures workstream scope chains automatically through the workflow:
new-milestone → discuss-phase → plan-phase → execute-phase → transition
Directory Structure
.planning/
├── PROJECT.md # Shared
├── config.json # Shared
├── milestones/ # Shared
├── codebase/ # Shared
├── active-workstream # Shared marker; inherited when a session has no pointer of its own
└── workstreams/
├── feature-a/ # Workstream A
│ ├── STATE.md
│ ├── ROADMAP.md
│ ├── REQUIREMENTS.md
│ └── phases/
└── feature-b/ # Workstream B
├── STATE.md
├── ROADMAP.md
├── REQUIREMENTS.md
└── phases/
CLI Usage
# All msd_run query commands accept --ws
msd_run query state.json --ws feature-a
msd_run query find-phase 3 --ws feature-b
# Session-local switching without --ws on every command
MSD_SESSION_KEY=my-terminal-a msd_run query workstream.set feature-a
MSD_SESSION_KEY=my-terminal-a msd_run query state.json
MSD_SESSION_KEY=my-terminal-b msd_run query workstream.set feature-b
MSD_SESSION_KEY=my-terminal-b msd_run query state.json
# Workstream CRUD
msd_run query workstream.create <name>
msd_run query workstream.list
msd_run query workstream.status <name>
msd_run query workstream.complete <name>