feat(sdk): add --ws flag for workstream-aware execution (#1884)

Add a --ws <name> CLI flag that routes all .planning/ paths to
.planning/workstreams/<name>/, enabling multi-workstream projects
without directory conflicts.

Changes:
- workstream-utils.ts: validateWorkstreamName() and relPlanningPath() helpers
- cli.ts: Parse --ws flag with input validation
- types.ts: Add workstream? to GSDOptions
- gsd-tools.ts: Inject --ws <name> into all gsd-tools.cjs invocations
- config.ts: Resolve workstream-aware config path with root fallback
- context-engine.ts: Constructor accepts workstream via positional param
- index.ts: GSD class propagates workstream to all subsystems
- ws-flag.test.ts: 22 tests covering all workstream functionality

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-04-11 09:33:34 -04:00
parent 72e789432e
commit 868e3d488f
8 changed files with 369 additions and 11 deletions

View File

@@ -15,6 +15,7 @@ import { GSD } from './index.js';
import { CLITransport } from './cli-transport.js';
import { WSTransport } from './ws-transport.js';
import { InitRunner } from './init-runner.js';
import { validateWorkstreamName } from './workstream-utils.js';
// ─── Parsed CLI args ─────────────────────────────────────────────────────────
@@ -29,6 +30,8 @@ export interface ParsedCliArgs {
wsPort: number | undefined;
model: string | undefined;
maxBudget: number | undefined;
/** Workstream name for multi-workstream projects. Routes .planning/ to .planning/workstreams/<name>/. */
ws: string | undefined;
help: boolean;
version: boolean;
}
@@ -43,6 +46,7 @@ export function parseCliArgs(argv: string[]): ParsedCliArgs {
options: {
'project-dir': { type: 'string', default: process.cwd() },
'ws-port': { type: 'string' },
ws: { type: 'string' },
model: { type: 'string' },
'max-budget': { type: 'string' },
init: { type: 'string' },
@@ -69,6 +73,7 @@ export function parseCliArgs(argv: string[]): ParsedCliArgs {
wsPort: values['ws-port'] ? Number(values['ws-port']) : undefined,
model: values.model as string | undefined,
maxBudget: values['max-budget'] ? Number(values['max-budget']) : undefined,
ws: values.ws as string | undefined,
help: values.help as boolean,
version: values.version as boolean,
};
@@ -92,6 +97,7 @@ Options:
--init <input> Bootstrap from a PRD before running (auto only)
Accepts @path/to/prd.md or "description text"
--project-dir <dir> Project directory (default: cwd)
--ws <name> Route .planning/ to .planning/workstreams/<name>/
--ws-port <port> Enable WebSocket transport on <port>
--model <model> Override LLM model
--max-budget <n> Max budget per step in USD
@@ -194,6 +200,13 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
return;
}
// Validate --ws flag if provided
if (args.ws !== undefined && !validateWorkstreamName(args.ws)) {
console.error(`Error: Invalid workstream name "${args.ws}". Use alphanumeric, hyphens, underscores, or dots only.`);
process.exitCode = 1;
return;
}
if (args.command !== 'run' && args.command !== 'init' && args.command !== 'auto') {
console.error('Error: Expected "gsd-sdk run <prompt>", "gsd-sdk auto", or "gsd-sdk init [input]"');
console.error(USAGE);
@@ -226,6 +239,7 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
projectDir: args.projectDir,
model: args.model,
maxBudgetUsd: args.maxBudget,
workstream: args.ws,
});
// Wire CLI transport
@@ -296,6 +310,7 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
model: args.model,
maxBudgetUsd: args.maxBudget,
autoMode: true,
workstream: args.ws,
});
// Wire CLI transport (always active)
@@ -384,6 +399,7 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
projectDir: args.projectDir,
model: args.model,
maxBudgetUsd: args.maxBudget,
workstream: args.ws,
});
// Wire CLI transport (always active)