From b44896e187419aee9c02494bbf96b76e7714265a Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 17 Mar 2026 09:47:57 -0400 Subject: [PATCH] fix: generate validation and summary files for completed milestones during migration (#819) (#838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .planning → .gsd migration creates roadmaps and summaries but not VALIDATION files. deriveState() requires a terminal validation file (verdict: pass) to consider a milestone complete. Without it, every migrated milestone enters validating-milestone phase, blocking progress to the actual current milestone. For milestones where all slices are done, write a pass-through VALIDATION.md (verdict: pass, migrated: true) and SUMMARY.md so deriveState() skips them correctly. Updated integration test to verify VALIDATION/SUMMARY files are written and deriveState returns 'complete' phase with activeMilestone pointing to the last completed entry (expected behavior). --- .../extensions/gsd/migrate/writer.ts | 39 +++++++++++++++++++ .../tests/migrate-writer-integration.test.ts | 18 ++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/resources/extensions/gsd/migrate/writer.ts b/src/resources/extensions/gsd/migrate/writer.ts index 27b627ba9..4fa12eaf9 100644 --- a/src/resources/extensions/gsd/migrate/writer.ts +++ b/src/resources/extensions/gsd/migrate/writer.ts @@ -483,6 +483,45 @@ export async function writeGSDDirectory( counts.research++; } + // For fully-completed milestones (all slices done), write a pass-through + // validation file so deriveState() doesn't enter validating-milestone + // phase for historical milestones that predate the validation gate (#819). + const allSlicesDone = milestone.slices.length > 0 && milestone.slices.every(s => s.done); + if (allSlicesDone) { + const validationPath = join(mDir, `${milestone.id}-VALIDATION.md`); + const validationContent = [ + `---`, + `verdict: pass`, + `migrated: true`, + `---`, + ``, + `# ${milestone.id} Validation`, + ``, + `Migrated milestone — all slices were completed in the original project.`, + ``, + ].join('\n'); + await saveFile(validationPath, validationContent); + paths.push(validationPath); + counts.other++; + + // Also write a milestone summary if one doesn't exist + const summaryPath = join(mDir, `${milestone.id}-SUMMARY.md`); + const summaryContent = [ + `---`, + `status: done`, + `migrated: true`, + `---`, + ``, + `# ${milestone.id}: ${milestone.title}`, + ``, + `Migrated from .planning — ${milestone.slices.length} slices completed.`, + ``, + ].join('\n'); + await saveFile(summaryPath, summaryContent); + paths.push(summaryPath); + counts.other++; + } + // Slices for (const slice of milestone.slices) { const sDir = join(mDir, 'slices', slice.id); diff --git a/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts b/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts index f86dae777..fca6a533b 100644 --- a/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +++ b/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts @@ -11,6 +11,7 @@ import { writeGSDDirectory } from '../migrate/writer.ts'; import { generatePreview } from '../migrate/preview.ts'; import { parseRoadmap, parsePlan, parseSummary } from '../files.ts'; import { deriveState } from '../state.ts'; +import { invalidateAllCaches } from '../cache.ts'; import type { GSDProject, GSDMilestone, @@ -207,6 +208,7 @@ async function main(): Promise { // (e) deriveState console.log(' --- deriveState ---'); + invalidateAllCaches(); const state = await deriveState(base); assertEq(state.phase, 'executing', 'incomplete: deriveState phase is executing'); assertTrue(state.activeMilestone !== null, 'incomplete: deriveState has activeMilestone'); @@ -262,14 +264,18 @@ async function main(): Promise { assertTrue(!existsSync(join(m, 'M001-RESEARCH.md')), 'complete: M001-RESEARCH.md NOT written (null)'); // No REQUIREMENTS.md since empty requirements assertTrue(!existsSync(join(base, '.gsd', 'REQUIREMENTS.md')), 'complete: REQUIREMENTS.md NOT written (empty)'); + // Completed milestone should have VALIDATION and SUMMARY from migration (#819) + assertTrue(existsSync(join(m, 'M001-VALIDATION.md')), 'complete: M001-VALIDATION.md written for completed milestone'); + assertTrue(existsSync(join(m, 'M001-SUMMARY.md')), 'complete: M001-SUMMARY.md written for completed milestone'); - // deriveState: all slices done, all tasks done — needs validation then milestone summary - // Without VALIDATION file, it should be 'validating-milestone' + // deriveState: all slices done, all tasks done — migration now writes + // VALIDATION.md and SUMMARY.md for completed milestones (#819), + // so the milestone should be fully complete. + invalidateAllCaches(); const state = await deriveState(base); - // All slices are done in roadmap. No VALIDATION or SUMMARY exists. - // deriveState should return 'validating-milestone' since validation gate precedes completion. - assertEq(state.phase, 'validating-milestone', 'complete: deriveState phase is validating-milestone'); - assertTrue(state.activeMilestone !== null, 'complete: deriveState has activeMilestone'); + assertEq(state.phase, 'complete', 'complete: deriveState phase is complete (validation + summary written by migration)'); + // When all milestones are complete, activeMilestone points to the last entry (for display) + assertTrue(state.activeMilestone !== null, 'complete: deriveState has activeMilestone (last entry)'); assertEq(state.activeMilestone!.id, 'M001', 'complete: deriveState activeMilestone is M001'); // generatePreview for complete project