diff --git a/get-shit-done/bin/lib/phase.cjs b/get-shit-done/bin/lib/phase.cjs index e8170f32b..46e19eed1 100644 --- a/get-shit-done/bin/lib/phase.cjs +++ b/get-shit-done/bin/lib/phase.cjs @@ -226,7 +226,7 @@ function getRoadmapModeForPhase(cwd, phaseNum) { const sectionStart = headerMatch.index; const rest = content.slice(sectionStart); - const nextHeader = rest.slice(headerMatch[0].length).match(/\n#{2,4}\s+Phase\s+\d/i); + const nextHeader = rest.slice(headerMatch[0].length).match(/\n#{2,4}\s+Phase\s+\S/i); const sectionEnd = nextHeader ? sectionStart + headerMatch[0].length + nextHeader.index : content.length; const section = content.slice(sectionStart, sectionEnd); const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i); diff --git a/tests/feat-3251-command-aliases-manifest-coverage.test.cjs b/tests/feat-3251-command-aliases-manifest-coverage.test.cjs index 1003b0fc4..09e95daca 100644 --- a/tests/feat-3251-command-aliases-manifest-coverage.test.cjs +++ b/tests/feat-3251-command-aliases-manifest-coverage.test.cjs @@ -10,6 +10,7 @@ const { describe, test } = require('node:test'); const assert = require('node:assert/strict'); +const crypto = require('node:crypto'); const fs = require('node:fs'); const os = require('node:os'); const path = require('path'); @@ -141,6 +142,8 @@ function runGsdTools(args, projectDir) { return spawnSync(process.execPath, [GSD_TOOLS, ...args], { cwd: projectDir, encoding: 'utf8', + timeout: 30000, + killSignal: 'SIGKILL', }); } @@ -152,11 +155,16 @@ function listProjectFiles(projectDir) { const full = path.join(dir, entry.name); const rel = path.relative(projectDir, full); if (entry.isDirectory()) walk(full); - else files.push(rel); + else { + files.push({ + path: rel, + sha256: crypto.createHash('sha256').update(fs.readFileSync(full)).digest('hex'), + }); + } } } walk(projectDir); - return files.sort(); + return files.sort((a, b) => a.path.localeCompare(b.path)); } describe('feat-3251: generated aliases dispatch through real gsd-tools behavior', () => { @@ -212,6 +220,40 @@ describe('feat-3251: generated aliases dispatch through real gsd-tools behavior' } }); + test('phase.mvp-mode ROADMAP lookup stops before custom-id next phase', () => { + const projectDir = createProject(); + try { + fs.writeFileSync( + path.join(projectDir, '.planning', 'ROADMAP.md'), + [ + '# Roadmap', + '', + '## v1.0.0', + '', + '### Phase 1: Numeric Phase', + '**Goal:** Users can sign in.', + '', + '### Phase custom-alpha: Custom Phase', + '**Goal:** Custom work.', + '**Mode:** mvp', + '', + ].join('\n'), + ); + const beforeFiles = listProjectFiles(projectDir); + + const result = runGsdTools(['phase', 'mvp-mode', '1'], projectDir); + assert.equal(result.status, 0, result.stderr); + + const output = JSON.parse(result.stdout); + assert.equal(output.active, false); + assert.equal(output.source, 'none'); + assert.equal(output.roadmap_mode, null); + assert.deepEqual(listProjectFiles(projectDir), beforeFiles); + } finally { + fs.rmSync(projectDir, { recursive: true, force: true }); + } + }); + test('phase.mvp-mode JSON error is typed and leaves project files untouched', () => { const projectDir = createProject(); try {