mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
fix: normalize phase numbers in stats Map to prevent duplicate rows (#2220)
When ROADMAP.md uses unpadded phase numbers (e.g. "Phase 1:") and the phases/ directory uses zero-padded names (e.g. "01-auth"), the phasesByNumber Map held two separate entries — one keyed "1" from the ROADMAP heading scan and one keyed "01" from the directory scan — doubling phases_total in /gsd-stats output. Apply normalizePhaseName() to all Map keys in both the ROADMAP heading scan and the directory scan so the two code paths always produce the same canonical key and merge into a single entry. Closes #2195 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -831,8 +831,9 @@ function cmdStats(cwd, format, raw) {
|
|||||||
const headingPattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
const headingPattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
||||||
let match;
|
let match;
|
||||||
while ((match = headingPattern.exec(roadmapContent)) !== null) {
|
while ((match = headingPattern.exec(roadmapContent)) !== null) {
|
||||||
phasesByNumber.set(match[1], {
|
const key = normalizePhaseName(match[1]);
|
||||||
number: match[1],
|
phasesByNumber.set(key, {
|
||||||
|
number: key,
|
||||||
name: match[2].replace(/\(INSERTED\)/i, '').trim(),
|
name: match[2].replace(/\(INSERTED\)/i, '').trim(),
|
||||||
plans: 0,
|
plans: 0,
|
||||||
summaries: 0,
|
summaries: 0,
|
||||||
@@ -862,9 +863,10 @@ function cmdStats(cwd, format, raw) {
|
|||||||
|
|
||||||
const status = determinePhaseStatus(plans, summaries, path.join(phasesDir, dir), 'Not Started');
|
const status = determinePhaseStatus(plans, summaries, path.join(phasesDir, dir), 'Not Started');
|
||||||
|
|
||||||
const existing = phasesByNumber.get(phaseNum);
|
const normalizedNum = normalizePhaseName(phaseNum);
|
||||||
phasesByNumber.set(phaseNum, {
|
const existing = phasesByNumber.get(normalizedNum);
|
||||||
number: phaseNum,
|
phasesByNumber.set(normalizedNum, {
|
||||||
|
number: normalizedNum,
|
||||||
name: existing?.name || phaseName,
|
name: existing?.name || phaseName,
|
||||||
plans: (existing?.plans || 0) + plans,
|
plans: (existing?.plans || 0) + plans,
|
||||||
summaries: (existing?.summaries || 0) + summaries,
|
summaries: (existing?.summaries || 0) + summaries,
|
||||||
|
|||||||
@@ -1693,6 +1693,36 @@ describe('stats command', () => {
|
|||||||
const output = JSON.parse(result.output);
|
const output = JSON.parse(result.output);
|
||||||
assert.strictEqual(output.phases[0].status, 'Executed', 'progress should show Executed without verification');
|
assert.strictEqual(output.phases[0].status, 'Executed', 'progress should show Executed without verification');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('does not duplicate phases when ROADMAP uses unpadded numbers and dirs use padded numbers', () => {
|
||||||
|
// ROADMAP.md uses "Phase 1:" (unpadded) but directory is "01-auth" (padded).
|
||||||
|
// Without normalization, the Map holds two entries: "1" and "01", doubling phases_total.
|
||||||
|
const p1 = path.join(tmpDir, '.planning', 'phases', '01-auth');
|
||||||
|
fs.mkdirSync(p1, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||||
|
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
||||||
|
fs.writeFileSync(path.join(p1, 'VERIFICATION.md'), '---\nstatus: passed\n---\n# Verified');
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||||
|
[
|
||||||
|
'# Roadmap',
|
||||||
|
'',
|
||||||
|
'## Milestone v1',
|
||||||
|
'',
|
||||||
|
'### Phase 1: Auth',
|
||||||
|
'**Goal:** Authentication',
|
||||||
|
].join('\n')
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = runGsdTools('stats', tmpDir);
|
||||||
|
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||||
|
|
||||||
|
const stats = JSON.parse(result.output);
|
||||||
|
assert.strictEqual(stats.phases_total, 1, 'unpadded ROADMAP heading and padded dir should merge into one phase');
|
||||||
|
assert.strictEqual(stats.phases_completed, 1);
|
||||||
|
assert.strictEqual(stats.phases.length, 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user