refactor(state): drop unused args + lift currentPhase in cmdStateCompletePhase (#2761)
* refactor(state): drop unused args param and lift currentPhase in cmdStateCompletePhase Two cleanup items surfaced by CodeRabbit review of PR #2759: 1. cmdStateCompletePhase(cwd, args, raw) — args is never read inside the function. All sibling state subcommands use the leaner (cwd, raw) shape. Remove the unused parameter and update the dispatch call in gsd-tools.cjs. 2. output() at line 1754 called fs.readFileSync(statePath) after readModifyWriteStateMd had already released the lock, re-extracting Current Phase via an extra fs read. The closure already computed currentPhase at line 1704; lifting resolvedPhase into outer scope and capturing it in the callback eliminates the post-lock read and closes the small race window. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(#2761): apply CodeRabbit nitpicks with regression tests Two CodeRabbit nitpicks from PR #2761 review, each landed with a regression test so a future refactor can't unwind them. 1. tests/dispatcher.test.cjs — pin the enumerated subcommand list: the 'state unknown subcommand errors' test now also asserts that the dispatcher's error string includes 'complete-phase'. Without this, a future reformat of the available-subcommands enumeration could silently drop entries and the existing 'Unknown state subcommand' substring check would still pass. 2. get-shit-done/bin/lib/state.cjs — tighten the Phase fallback in cmdStateCompletePhase: when STATE.md is missing the canonical '**Current Phase:**' field and the only phase signal is the decorated body line under '## Current Position' (e.g. 'Phase: 01 (Foo) — EXECUTING'), the previous fallback returned the entire decorated string, producing messy downstream output: Status: Phase 01 (Foo) — EXECUTING complete Phase: 01 (Foo) — EXECUTING — COMPLETE The fallback now strips everything past the leading numeric/decimal token via /^\\s*([\\w.-]+)/ so degraded inputs produce clean output identical to the canonical path. 3. tests/state.test.cjs — two new tests in a dedicated describe block: - decorated Phase line writes clean Phase identifier - canonical Current Phase wins over Current Position decoration Both run real `gsd state complete-phase` against synthetic STATE.md fixtures and assert on the rendered Status field. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,14 @@ describe('dispatcher error paths', () => {
|
||||
const result = runGsdTools('state bogus', tmpDir);
|
||||
assert.strictEqual(result.success, false, 'Should exit non-zero');
|
||||
assert.ok(result.error.includes('Unknown state subcommand'), `Expected "Unknown state subcommand" in stderr, got: ${result.error}`);
|
||||
// Pin the enumerated subcommand list. If a future refactor reformats the
|
||||
// error string and silently drops 'complete-phase' from the available list,
|
||||
// this test fails loudly rather than passing on the substring above.
|
||||
// CodeRabbit nitpick on PR #2761.
|
||||
assert.ok(
|
||||
result.error.includes('complete-phase'),
|
||||
`Expected enumerated subcommands to include "complete-phase", got: ${result.error}`,
|
||||
);
|
||||
});
|
||||
|
||||
// Unknown subcommand: template
|
||||
|
||||
@@ -2370,6 +2370,105 @@ describe('stale phase dirs do not corrupt phase counts (bug #2445)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// state complete-phase: Phase-fallback decoration handling (PR #2761 nitpick)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// When STATE.md is missing the canonical `**Current Phase:**` field but
|
||||
// includes a decorated `## Current Position` body line, the fallback path used
|
||||
// to leak the decoration into downstream Status/Phase strings — producing
|
||||
// `**Status:** Phase 01 (Foo) — EXECUTING complete` instead of the expected
|
||||
// `**Status:** Phase 01 complete`. CodeRabbit flagged this on PR #2761 and the
|
||||
// Phase fallback now strips everything past the leading numeric/decimal token.
|
||||
describe('state complete-phase: decorated Phase fallback (#2761 nitpick)', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('writes clean Phase identifier when only Current Position decoration is present', () => {
|
||||
// STATE.md without the canonical `**Current Phase:**` field — the only
|
||||
// phase signal lives inside the `## Current Position` block as a decorated
|
||||
// line. This is the regression fixture.
|
||||
const stateMd = [
|
||||
'---',
|
||||
'milestone: v1.0',
|
||||
'---',
|
||||
'',
|
||||
'# State',
|
||||
'',
|
||||
'**Status:** Executing',
|
||||
'**Last Activity:** 2024-01-15',
|
||||
'',
|
||||
'## Current Position',
|
||||
'',
|
||||
'Phase: 01 (Foo) — EXECUTING',
|
||||
'Plan: bootstrap',
|
||||
'',
|
||||
].join('\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), stateMd);
|
||||
|
||||
const result = runGsdTools('state complete-phase', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const updated = fs.readFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
// Status should reference the bare phase identifier (`01`), not the
|
||||
// decorated string. The negative assertion catches the regression
|
||||
// shape directly.
|
||||
assert.ok(
|
||||
updated.includes('**Status:** Phase 01 complete'),
|
||||
`Status should be "Phase 01 complete", got STATE.md:\n${updated}`,
|
||||
);
|
||||
assert.ok(
|
||||
!updated.includes('Phase 01 (Foo) — EXECUTING complete'),
|
||||
`Status must not embed Current Position decoration: ${updated}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('canonical Current Phase field is preferred over Current Position decoration', () => {
|
||||
// When both are present, Current Phase wins — same outcome as before, but
|
||||
// pinned here so a future refactor that flips precedence is caught.
|
||||
const stateMd = [
|
||||
'---',
|
||||
'milestone: v1.0',
|
||||
'---',
|
||||
'',
|
||||
'# State',
|
||||
'',
|
||||
'**Status:** Executing',
|
||||
'**Current Phase:** 03',
|
||||
'**Last Activity:** 2024-01-15',
|
||||
'',
|
||||
'## Current Position',
|
||||
'',
|
||||
'Phase: 01 (Foo) — EXECUTING',
|
||||
'',
|
||||
].join('\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), stateMd);
|
||||
|
||||
const result = runGsdTools('state complete-phase', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const updated = fs.readFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
'utf-8',
|
||||
);
|
||||
assert.ok(
|
||||
updated.includes('**Status:** Phase 03 complete'),
|
||||
`Status should reference canonical Current Phase (03), got: ${updated}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// summary-extract command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user