* test(#3865): init.* phase queries accept --phase as positional alias (failing first) * fix(#3865): init.* phase queries accept --phase <N> as the positional alias The phase-taking init.* queries read their phase token at args[2] blindly: '--phase 60' made the literal '--phase' the phase, which the locator can never resolve — the reported incident answered a well-formed phase_found:false, plan_count:0 with exit 0 for a phase holding seven committed plans (ADR-3473 §8.4's later strict validation turned the paired form into a usage error instead, still not the alias). normalizePhaseAlias (shared by all eight phase-taking handlers — the issue's four plus phase-op, review, discuss-phase-assumptions, todos, the same class) rewrites '--phase N'/'--phase=N' into the caller-owned positional slot before flag parsing, so handlers and strict validation see exactly the argv the positional form produces. A valueless --phase is a usage error naming the flag. Any other flag-shaped args[2] now resolves to undefined (the commands' designed use-the-current-phase input) instead of passing flag text down as a phase name. isFlagToken exported from command-arg-projection (single owner of the flag-shape predicate). * fix(#3865): review fold-ins — honest no-position-given comment, fail-closed throws The helper's comment claimed flag-shaped args[2] resolves to 'the commands' designed use-the-current-phase input' — no such cross-module behavior exists: execute-phase/plan-phase/verify-work usage-error 'phase required', the find-based queries answer phase_found:false, and todos drops its area filter. Reworded to what actually happens, so the contract-grade comment cannot mislead a future edit. Adds the parseNamedArgsOrExit-style throw after each error() call as a fail-closed backstop against a returning fail(). * chore(#3865): changeset fragment (pr number backfilled after PR creation) * chore(#3865): backfill changeset PR number (4054) --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -918,6 +918,69 @@ describe('init commands', () => {
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_found, true);
|
||||
assert.strictEqual(output.phase_name, 'Details Block Regression');
|
||||
|
||||
});
|
||||
// ─── #3865: --phase alias for the positional phase token ──────────────────
|
||||
|
||||
test('#3865: init execute-phase accepts --phase <N> as the positional alias', () => {
|
||||
seedPhase(tmpDir, '03-api', { '03-01-PLAN.md': '# Plan' });
|
||||
writePlanningDocs(tmpDir);
|
||||
const result = runGsdTools(['init', 'execute-phase', '--phase', '03'], tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_found, true, '--phase 03 must resolve the phase, not answer phase_found:false');
|
||||
assert.strictEqual(output.plan_count, 1, 'the on-disk plan must be counted — the reported incident had 7 plans read as 0');
|
||||
});
|
||||
|
||||
test('#3865: init execute-phase accepts the --phase=N form', () => {
|
||||
seedPhase(tmpDir, '03-api', { '03-01-PLAN.md': '# Plan' });
|
||||
writePlanningDocs(tmpDir);
|
||||
const result = runGsdTools(['init', 'execute-phase', '--phase=03'], tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.strictEqual(JSON.parse(result.output).phase_found, true);
|
||||
});
|
||||
|
||||
test('#3865: the positional form still works (control)', () => {
|
||||
seedPhase(tmpDir, '03-api', { '03-01-PLAN.md': '# Plan' });
|
||||
writePlanningDocs(tmpDir);
|
||||
const result = runGsdTools(['init', 'execute-phase', '03'], tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.strictEqual(JSON.parse(result.output).phase_found, true);
|
||||
});
|
||||
|
||||
test('#3865: init plan-phase accepts --phase <N>', () => {
|
||||
seedPhase(tmpDir, '03-api', { '03-01-PLAN.md': '# Plan' });
|
||||
writePlanningDocs(tmpDir);
|
||||
const result = runGsdTools(['init', 'plan-phase', '--phase', '03'], tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.strictEqual(JSON.parse(result.output).phase_found, true);
|
||||
});
|
||||
|
||||
test('#3865: init verify-work accepts --phase <N>', () => {
|
||||
seedPhase(tmpDir, '03-api', { '03-01-PLAN.md': '# Plan' });
|
||||
writePlanningDocs(tmpDir);
|
||||
const result = runGsdTools(['init', 'verify-work', '--phase', '03'], tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.strictEqual(JSON.parse(result.output).phase_found, true);
|
||||
});
|
||||
|
||||
test('#3865: init code-review accepts --phase <N>', () => {
|
||||
seedPhase(tmpDir, '03-api', { '03-01-PLAN.md': '# Plan' });
|
||||
writePlanningDocs(tmpDir);
|
||||
const result = runGsdTools(['init', 'code-review', '--phase', '03'], tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.strictEqual(JSON.parse(result.output).phase_found, true);
|
||||
});
|
||||
|
||||
test('#3865: --phase with no value is a usage error, never a silent phase_found:false', () => {
|
||||
seedPhase(tmpDir, '03-api', { '03-01-PLAN.md': '# Plan' });
|
||||
writePlanningDocs(tmpDir);
|
||||
const result = runGsdTools(['init', 'execute-phase', '--phase'], tmpDir);
|
||||
assert.strictEqual(result.success, false, 'a valueless --phase must exit non-zero with a diagnostic');
|
||||
assert.ok(
|
||||
(result.error || '').includes('--phase'),
|
||||
`the diagnostic must name the flag; got: ${result.error}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4877,3 +4940,5 @@ describe('init — GSD_PROJECT scoping (#3964)', () => {
|
||||
assert.equal(out['codebase_dir_exists'], true, 'unscoped probe of the root codebase dir');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user