mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-05-05 06:42:14 +02:00
* fix(roadmap): fall back to full ROADMAP.md for backlog and planned phases Closes #1634 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(roadmap): prevent checklist-only match from blocking full header fallback When the current milestone had a checklist reference to a phase (e.g. `- [ ] **Phase 50: Cleanup**`) but the full `### Phase 50:` header existed in a different milestone, the malformed_roadmap result from the first searchPhaseInContent call short-circuited the `||` operator and prevented the fallback to the full roadmap content. Now a malformed_roadmap result is deferred so the full content search can find the actual header match. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
246 lines
7.1 KiB
JavaScript
246 lines
7.1 KiB
JavaScript
/**
|
|
* GSD Tools Tests - roadmap get-phase fallback to full ROADMAP.md
|
|
*
|
|
* Covers issue #1634: phases outside the current milestone slice should still
|
|
* resolve by falling back to the full ROADMAP.md content.
|
|
*/
|
|
|
|
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
|
|
|
/**
|
|
* Helper: write STATE.md with a milestone version so extractCurrentMilestone
|
|
* will slice the roadmap to only that milestone's section.
|
|
*/
|
|
function writeState(tmpDir, version) {
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'STATE.md'),
|
|
`---\nmilestone: ${version}\n---\n`
|
|
);
|
|
}
|
|
|
|
describe('roadmap get-phase fallback to full ROADMAP.md (#1634)', () => {
|
|
let tmpDir;
|
|
|
|
beforeEach(() => {
|
|
tmpDir = createTempProject();
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup(tmpDir);
|
|
});
|
|
|
|
test('active milestone phase still resolves correctly', () => {
|
|
writeState(tmpDir, 'v1.0');
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
`# Roadmap
|
|
|
|
## v1.0 Current Release
|
|
|
|
### Phase 1: Foundation
|
|
**Goal:** Set up project infrastructure
|
|
|
|
### Phase 2: API
|
|
**Goal:** Build REST API
|
|
|
|
## v2.0 Next Release
|
|
|
|
### Phase 3: Frontend
|
|
**Goal:** Build UI layer
|
|
`
|
|
);
|
|
|
|
const result = runGsdTools('roadmap get-phase 1', tmpDir);
|
|
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
|
|
const output = JSON.parse(result.output);
|
|
assert.equal(output.found, true, 'active milestone phase should be found');
|
|
assert.equal(output.phase_number, '1');
|
|
assert.equal(output.phase_name, 'Foundation');
|
|
assert.equal(output.goal, 'Set up project infrastructure');
|
|
});
|
|
|
|
test('backlog phase outside current milestone resolves via fallback', () => {
|
|
writeState(tmpDir, 'v1.0');
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
`# Roadmap
|
|
|
|
## v1.0 Current Release
|
|
|
|
### Phase 1: Foundation
|
|
**Goal:** Set up project infrastructure
|
|
|
|
## v2.0 Future Release
|
|
|
|
### Phase 999.60: Backlog Cleanup
|
|
**Goal:** Clean up technical debt from backlog
|
|
`
|
|
);
|
|
|
|
const result = runGsdTools('roadmap get-phase 999.60', tmpDir);
|
|
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
|
|
const output = JSON.parse(result.output);
|
|
assert.equal(output.found, true, 'backlog phase should be found via fallback');
|
|
assert.equal(output.phase_number, '999.60');
|
|
assert.equal(output.phase_name, 'Backlog Cleanup');
|
|
assert.equal(output.goal, 'Clean up technical debt from backlog');
|
|
});
|
|
|
|
test('future planned milestone phase resolves via fallback', () => {
|
|
writeState(tmpDir, 'v1.0');
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
`# Roadmap
|
|
|
|
## v1.0 Current Release
|
|
|
|
### Phase 1: Foundation
|
|
**Goal:** Set up project infrastructure
|
|
|
|
## v3.0 Planned Milestone
|
|
|
|
### Phase 1025: Advanced Analytics
|
|
**Goal:** Build analytics dashboard for enterprise customers
|
|
|
|
**Success Criteria** (what must be TRUE):
|
|
1. Dashboard renders in under 2s
|
|
2. Supports 10k concurrent users
|
|
`
|
|
);
|
|
|
|
const result = runGsdTools('roadmap get-phase 1025', tmpDir);
|
|
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
|
|
const output = JSON.parse(result.output);
|
|
assert.equal(output.found, true, 'future milestone phase should be found via fallback');
|
|
assert.equal(output.phase_number, '1025');
|
|
assert.equal(output.phase_name, 'Advanced Analytics');
|
|
assert.equal(output.goal, 'Build analytics dashboard for enterprise customers');
|
|
assert.ok(Array.isArray(output.success_criteria), 'success_criteria should be extracted');
|
|
assert.equal(output.success_criteria.length, 2, 'should have 2 criteria');
|
|
});
|
|
|
|
test('truly missing phase still returns found: false', () => {
|
|
writeState(tmpDir, 'v1.0');
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
`# Roadmap
|
|
|
|
## v1.0 Current Release
|
|
|
|
### Phase 1: Foundation
|
|
**Goal:** Set up project infrastructure
|
|
|
|
## v2.0 Future Release
|
|
|
|
### Phase 5: Mobile
|
|
**Goal:** Build mobile app
|
|
`
|
|
);
|
|
|
|
const result = runGsdTools('roadmap get-phase 9999', tmpDir);
|
|
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
|
|
const output = JSON.parse(result.output);
|
|
assert.equal(output.found, false, 'truly missing phase should return found: false');
|
|
assert.equal(output.phase_number, '9999');
|
|
});
|
|
|
|
test('backlog checklist-only phase triggers malformed_roadmap via fallback', () => {
|
|
writeState(tmpDir, 'v1.0');
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
`# Roadmap
|
|
|
|
## v1.0 Current Release
|
|
|
|
### Phase 1: Foundation
|
|
**Goal:** Set up project infrastructure
|
|
|
|
## v2.0 Backlog
|
|
|
|
- [ ] **Phase 50: Cleanup** - Remove old code
|
|
`
|
|
);
|
|
|
|
const result = runGsdTools('roadmap get-phase 50', tmpDir);
|
|
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
|
|
const output = JSON.parse(result.output);
|
|
assert.equal(output.found, false, 'checklist-only phase should not be "found"');
|
|
assert.equal(output.error, 'malformed_roadmap', 'should identify malformed roadmap via fallback');
|
|
assert.ok(output.message.includes('missing'), 'should explain the issue');
|
|
});
|
|
|
|
test('checklist in milestone does not block full header match in wider roadmap', () => {
|
|
writeState(tmpDir, 'v1.0');
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
`# Roadmap
|
|
|
|
## v1.0 Current Release
|
|
|
|
### Phase 1: Foundation
|
|
**Goal:** Set up project infrastructure
|
|
|
|
- [ ] **Phase 50: Cleanup** - referenced in checklist
|
|
|
|
## v2.0 Future Release
|
|
|
|
### Phase 50: Cleanup
|
|
**Goal:** Remove deprecated modules
|
|
`
|
|
);
|
|
|
|
const result = runGsdTools('roadmap get-phase 50', tmpDir);
|
|
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
|
|
const output = JSON.parse(result.output);
|
|
assert.equal(output.found, true, 'full header in v2.0 should win over checklist in v1.0');
|
|
assert.equal(output.phase_name, 'Cleanup');
|
|
assert.equal(output.goal, 'Remove deprecated modules');
|
|
});
|
|
|
|
test('section extraction from fallback includes correct content boundaries', () => {
|
|
writeState(tmpDir, 'v1.0');
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
`# Roadmap
|
|
|
|
## v1.0 Current Release
|
|
|
|
### Phase 1: Foundation
|
|
**Goal:** Set up project infrastructure
|
|
|
|
## v2.0 Future Release
|
|
|
|
### Phase 10: Database
|
|
**Goal:** Schema design and migrations
|
|
|
|
This phase covers:
|
|
- Schema modeling
|
|
- Migration tooling
|
|
- Seed data
|
|
|
|
### Phase 11: Caching
|
|
**Goal:** Add Redis caching layer
|
|
`
|
|
);
|
|
|
|
const result = runGsdTools('roadmap get-phase 10', tmpDir);
|
|
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
|
|
const output = JSON.parse(result.output);
|
|
assert.equal(output.found, true, 'phase 10 should be found via fallback');
|
|
assert.ok(output.section.includes('Schema modeling'), 'section includes description');
|
|
assert.ok(output.section.includes('Seed data'), 'section includes all bullets');
|
|
assert.ok(!output.section.includes('Phase 11'), 'section does not include next phase');
|
|
});
|
|
});
|