fix(#1912): init.progress fails safe in workstream mode with no active ws (#1918)

cmdInitProgress used planningDir(cwd), which resolves to root .planning
when no active workstream and no --ws/GSD_WORKSTREAM is set — regardless
of mode:workstream. So /gsd-progress confidently reported a stale root
milestone with no signal it was stale.

Fail safe: when .planning/workstreams/ has workstreams AND no active
workstream is resolved, error with an actionable hint naming the available
workstreams and the --ws / `workstream set` fix. Flat mode (no workstreams
dir) and --ws <name> are unchanged.

Regression in tests/init.test.cjs: errors when workstreams exist but none
active (no stale root report); succeeds with --ws; flat mode unchanged.

Closes #1912
This commit is contained in:
Tom Boucher
2026-07-02 10:24:53 -04:00
committed by GitHub
parent 05cd55d43b
commit 8084f626ba
3 changed files with 79 additions and 0 deletions

View File

@@ -71,6 +71,7 @@ const {
planningPaths,
planningDir,
planningRoot,
getActiveWorkstream,
findContextMdIn,
} = planningWorkspace;
@@ -1683,6 +1684,31 @@ function cmdInitProgress(cwd: string, raw: boolean): void {
const milestone = getMilestoneInfo(cwd) as unknown as Record<string, unknown>;
const _slashRuntime = resolveRuntime(cwd);
// #1912: fail safe in workstream mode with no active workstream. With no active
// workstream and no --ws, planningDir(cwd) resolves to root .planning — silently
// reporting a stale root milestone. Require an explicit workstream instead.
// Mirror planningDir's resolution (GSD_WORKSTREAM env > stored active pointer) so
// an explicit --ws (which sets GSD_WORKSTREAM) satisfies the check.
const _wsRoot = path.join(planningRoot(cwd), 'workstreams');
let _availableWorkstreams: string[] = [];
try {
_availableWorkstreams = fs
.readdirSync(_wsRoot, { withFileTypes: true })
.filter((e) => e.isDirectory())
.map((e) => e.name)
.sort();
} catch {
/* no workstreams dir → flat mode */
}
const _resolvedWorkstream = process.env['GSD_WORKSTREAM'] || getActiveWorkstream(cwd);
if (_availableWorkstreams.length > 0 && !_resolvedWorkstream) {
error(
`init.progress requires a workstream in workstream mode — no active workstream is set, so root STATE.md (likely stale) would be reported. ` +
`Pass --ws <name> or run ${formatGsdSlash('workstream set', _slashRuntime) as string} first. ` +
`Available workstreams: ${_availableWorkstreams.join(', ')}`,
);
}
const phasesDir = path.join(planningDir(cwd), 'phases');
const phases: Record<string, unknown>[] = [];
let currentPhase: Record<string, unknown> | null = null;