Added TBD guard test before merge.
This commit is contained in:
@@ -16,6 +16,13 @@ function cmdInitExecutePhase(cwd, phase, raw) {
|
||||
const phaseInfo = findPhaseInternal(cwd, phase);
|
||||
const milestone = getMilestoneInfo(cwd);
|
||||
|
||||
const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
||||
const reqMatch = roadmapPhase?.section?.match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m);
|
||||
const reqExtracted = reqMatch
|
||||
? reqMatch[1].replace(/[\[\]]/g, '').split(',').map(s => s.trim()).filter(Boolean).join(', ')
|
||||
: null;
|
||||
const phase_req_ids = (reqExtracted && reqExtracted !== 'TBD') ? reqExtracted : null;
|
||||
|
||||
const result = {
|
||||
// Models
|
||||
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
||||
@@ -35,6 +42,7 @@ function cmdInitExecutePhase(cwd, phase, raw) {
|
||||
phase_number: phaseInfo?.phase_number || null,
|
||||
phase_name: phaseInfo?.phase_name || null,
|
||||
phase_slug: phaseInfo?.phase_slug || null,
|
||||
phase_req_ids,
|
||||
|
||||
// Plan inventory
|
||||
plans: phaseInfo?.plans || [],
|
||||
@@ -80,6 +88,13 @@ function cmdInitPlanPhase(cwd, phase, raw) {
|
||||
const config = loadConfig(cwd);
|
||||
const phaseInfo = findPhaseInternal(cwd, phase);
|
||||
|
||||
const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
||||
const reqMatch = roadmapPhase?.section?.match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m);
|
||||
const reqExtracted = reqMatch
|
||||
? reqMatch[1].replace(/[\[\]]/g, '').split(',').map(s => s.trim()).filter(Boolean).join(', ')
|
||||
: null;
|
||||
const phase_req_ids = (reqExtracted && reqExtracted !== 'TBD') ? reqExtracted : null;
|
||||
|
||||
const result = {
|
||||
// Models
|
||||
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
|
||||
@@ -99,6 +114,7 @@ function cmdInitPlanPhase(cwd, phase, raw) {
|
||||
phase_name: phaseInfo?.phase_name || null,
|
||||
phase_slug: phaseInfo?.phase_slug || null,
|
||||
padded_phase: phaseInfo?.phase_number?.padStart(2, '0') || null,
|
||||
phase_req_ids,
|
||||
|
||||
// Existing artifacts
|
||||
has_research: phaseInfo?.has_research || false,
|
||||
|
||||
@@ -336,7 +336,7 @@ function cmdPhaseAdd(cwd, description, raw) {
|
||||
fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
|
||||
|
||||
// Build phase entry
|
||||
const phaseEntry = `\n### Phase ${newPhaseNum}: ${description}\n\n**Goal:** [To be planned]\n**Depends on:** Phase ${maxPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${newPhaseNum} to break down)\n`;
|
||||
const phaseEntry = `\n### Phase ${newPhaseNum}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${maxPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${newPhaseNum} to break down)\n`;
|
||||
|
||||
// Find insertion point: before last "---" or at end
|
||||
let updatedContent;
|
||||
@@ -407,7 +407,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
||||
fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
|
||||
|
||||
// Build phase entry
|
||||
const phaseEntry = `\n### Phase ${decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${decimalPhase} to break down)\n`;
|
||||
const phaseEntry = `\n### Phase ${decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${decimalPhase} to break down)\n`;
|
||||
|
||||
// Insert after the target phase section
|
||||
const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
|
||||
|
||||
@@ -19,7 +19,7 @@ Load all context in one call:
|
||||
INIT=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init execute-phase "${PHASE_ARG}")
|
||||
```
|
||||
|
||||
Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`.
|
||||
Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`, `phase_req_ids`.
|
||||
|
||||
**If `phase_found` is false:** Error — phase directory not found.
|
||||
**If `plan_count` is 0:** Error — no plans found in phase.
|
||||
@@ -294,10 +294,6 @@ node ~/.claude/get-shit-done/bin/gsd-tools.cjs commit "docs(phase-${PARENT_PHASE
|
||||
<step name="verify_phase_goal">
|
||||
Verify phase achieved its GOAL, not just completed tasks.
|
||||
|
||||
```bash
|
||||
PHASE_REQ_IDS=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs roadmap get-phase "${PHASE_NUMBER}" | jq -r '.section' | grep -i "Requirements:" | sed 's/.*Requirements:\*\*\s*//' | sed 's/[\[\]]//g')
|
||||
```
|
||||
|
||||
```
|
||||
Task(
|
||||
prompt="Verify phase {phase_number} goal achievement.
|
||||
|
||||
@@ -18,7 +18,7 @@ Load all context in one call (paths only to minimize orchestrator context):
|
||||
INIT=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init plan-phase "$PHASE")
|
||||
```
|
||||
|
||||
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_plans`, `plan_count`, `planning_exists`, `roadmap_exists`.
|
||||
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_plans`, `plan_count`, `planning_exists`, `roadmap_exists`, `phase_req_ids`.
|
||||
|
||||
**File paths (for <files_to_read> blocks):** `state_path`, `roadmap_path`, `requirements_path`, `context_path`, `research_path`, `verification_path`, `uat_path`. These are null if files don't exist.
|
||||
|
||||
@@ -84,7 +84,6 @@ Display banner:
|
||||
|
||||
```bash
|
||||
PHASE_DESC=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs roadmap get-phase "${PHASE}" | jq -r '.section')
|
||||
PHASE_REQ_IDS=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs roadmap get-phase "${PHASE}" | jq -r '.section // empty' | grep -i "Requirements:" | head -1 | sed 's/.*Requirements:\*\*\s*//' | sed 's/[\[\]]//g' | tr ',' '\n' | sed 's/^ *//;s/ *$//' | grep -v '^$' | tr '\n' ',' | sed 's/,$//')
|
||||
```
|
||||
|
||||
Research prompt:
|
||||
|
||||
@@ -97,6 +97,92 @@ describe('init commands', () => {
|
||||
assert.strictEqual(output.context_path, undefined);
|
||||
assert.strictEqual(output.research_path, undefined);
|
||||
});
|
||||
|
||||
// ── phase_req_ids extraction (fix for #684) ──────────────────────────────
|
||||
|
||||
test('init plan-phase extracts phase_req_ids from ROADMAP', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Requirements**: CP-01, CP-02, CP-03\n**Plans:** 0 plans\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('init plan-phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_req_ids, 'CP-01, CP-02, CP-03');
|
||||
});
|
||||
|
||||
test('init plan-phase strips brackets from phase_req_ids', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Requirements**: [CP-01, CP-02]\n**Plans:** 0 plans\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('init plan-phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_req_ids, 'CP-01, CP-02');
|
||||
});
|
||||
|
||||
test('init plan-phase returns null phase_req_ids when Requirements line is absent', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Plans:** 0 plans\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('init plan-phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_req_ids, null);
|
||||
});
|
||||
|
||||
test('init plan-phase returns null phase_req_ids when ROADMAP is absent', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('init plan-phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_req_ids, null);
|
||||
});
|
||||
|
||||
test('init execute-phase extracts phase_req_ids from ROADMAP', () => {
|
||||
const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api');
|
||||
fs.mkdirSync(phaseDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(phaseDir, '03-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Requirements**: EX-01, EX-02\n**Plans:** 1 plans\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('init execute-phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_req_ids, 'EX-01, EX-02');
|
||||
});
|
||||
|
||||
test('init execute-phase returns null phase_req_ids when Requirements line is absent', () => {
|
||||
const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api');
|
||||
fs.mkdirSync(phaseDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(phaseDir, '03-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Plans:** 1 plans\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('init execute-phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_req_ids, null);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -420,6 +420,19 @@ describe('phase add command', () => {
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_number, 1, 'should be phase 1');
|
||||
});
|
||||
|
||||
test('phase add includes **Requirements**: TBD in new ROADMAP entry', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap v1.0\n\n### Phase 1: Foundation\n**Goal:** Setup\n\n---\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('phase add User Dashboard', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
|
||||
assert.ok(roadmap.includes('**Requirements**: TBD'), 'new phase entry should include Requirements TBD');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -528,6 +541,20 @@ describe('phase insert command', () => {
|
||||
assert.ok(roadmap.includes('(INSERTED)'), 'roadmap should include inserted phase');
|
||||
});
|
||||
|
||||
test('phase insert includes **Requirements**: TBD in new ROADMAP entry', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n### Phase 1: Foundation\n**Goal:** Setup\n\n### Phase 2: API\n**Goal:** Build API\n`
|
||||
);
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-foundation'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('phase insert 1 Fix Critical Bug', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
|
||||
assert.ok(roadmap.includes('**Requirements**: TBD'), 'inserted phase entry should include Requirements TBD');
|
||||
});
|
||||
|
||||
test('handles #### heading depth from multi-milestone roadmaps', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
|
||||
Reference in New Issue
Block a user