From b8aaded95e4be95efc45145d92d1cb9dd0743bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Mon, 23 Mar 2026 11:37:37 -0600 Subject: [PATCH] chore(M001/S05): auto-commit after plan-slice --- .gsd/milestones/M001/slices/S05/S05-PLAN.md | 93 +++++++++++++ .../M001/slices/S05/tasks/T01-PLAN.md | 98 ++++++++++++++ .../M001/slices/S05/tasks/T02-PLAN.md | 67 ++++++++++ .../M001/slices/S05/tasks/T03-PLAN.md | 123 +++++++++++++++++ .../M001/slices/S05/tasks/T04-PLAN.md | 125 ++++++++++++++++++ 5 files changed, 506 insertions(+) create mode 100644 .gsd/milestones/M001/slices/S05/S05-PLAN.md create mode 100644 .gsd/milestones/M001/slices/S05/tasks/T01-PLAN.md create mode 100644 .gsd/milestones/M001/slices/S05/tasks/T02-PLAN.md create mode 100644 .gsd/milestones/M001/slices/S05/tasks/T03-PLAN.md create mode 100644 .gsd/milestones/M001/slices/S05/tasks/T04-PLAN.md diff --git a/.gsd/milestones/M001/slices/S05/S05-PLAN.md b/.gsd/milestones/M001/slices/S05/S05-PLAN.md new file mode 100644 index 000000000..93ba92d58 --- /dev/null +++ b/.gsd/milestones/M001/slices/S05/S05-PLAN.md @@ -0,0 +1,93 @@ +# S05: Warm/cold callers + flag files + pre-M002 migration + +**Goal:** All non-hot-path parseRoadmap/parsePlan callers migrated to DB queries with lazy parser fallback. REPLAN.md and REPLAN-TRIGGER.md flag-file detection in deriveStateFromDb() replaced with DB table/column queries. migrateHierarchyToDb() populates v8 planning columns from parsed markdown. +**Demo:** `grep -rn 'import.*parseRoadmap\|import.*parsePlan' src/resources/extensions/gsd/*.ts | grep -v '/tests/' | grep -v 'md-importer' | grep -v 'files.ts'` returns only lazy `createRequire` references and markdown-renderer.ts lazy imports. Flag-file phase detection works without disk files when DB is seeded. + +## Must-Haves + +- Schema v10 adds `replan_triggered_at TEXT` column to slices table (both CREATE TABLE DDL and migration block) +- `deriveStateFromDb()` uses `getReplanHistory()` for REPLAN detection and `replan_triggered_at` column for REPLAN-TRIGGER detection instead of `resolveSliceFile()` disk checks +- `triage-resolution.ts` `executeReplan()` writes `replan_triggered_at` column in addition to disk file +- `migrateHierarchyToDb()` passes `planning: { vision, successCriteria, boundaryMapMarkdown }` to `insertMilestone()`, `planning: { goal }` to `insertSlice()`, and `files`/`verify` to `insertTask()` +- All 13 warm/cold caller files have module-level `parseRoadmap`/`parsePlan` imports replaced with `isDbAvailable()` gate + lazy `createRequire` fallback (or dynamic import for async callers) +- `markdown-renderer.ts` validation moves parser import from module-level to lazy `createRequire` (keeps parser calls — they're intentional disk-vs-DB comparison) +- CONTINUE.md and CONTEXT-DRAFT.md migration NOT touched per D003 (locked, non-revisable) +- All existing tests pass (no regressions) + +## Proof Level + +- This slice proves: integration (DB queries replace parser calls across 13+ files) +- Real runtime required: no (unit tests with seeded DBs prove behavior) +- Human/UAT required: no + +## Verification + +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/flag-file-db.test.ts` — flag-file DB migration tests pass +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/gsd-recover.test.ts` — extended recovery tests pass (v8 column population) +- `grep -rn 'import.*parseRoadmap\|import.*parsePlan\|import.*parseRoadmapSlices' src/resources/extensions/gsd/*.ts | grep -v '/tests/' | grep -v 'md-importer' | grep -v 'files.ts'` — returns zero module-level imports (only lazy createRequire references) +- Regression suites: doctor.test.ts, auto-recovery.test.ts, auto-dashboard.test.ts, derive-state-db.test.ts, derive-state-crossval.test.ts, planning-crossval.test.ts, markdown-renderer.test.ts all pass + +## Observability / Diagnostics + +- Runtime signals: `replan_triggered_at` column on slices table records when triage writes a replan trigger; `replan_history` table rows indicate completed replans — both queryable via SQL +- Inspection surfaces: `SELECT id, replan_triggered_at FROM slices WHERE milestone_id = :mid` shows trigger state; `SELECT * FROM replan_history WHERE milestone_id = :mid AND slice_id = :sid` shows replan completion +- Failure visibility: `isDbAvailable()` gate in all migrated callers writes to stderr when falling back to parser — detectable in logs +- Redaction constraints: none + +## Integration Closure + +- Upstream surfaces consumed: `getReplanHistory()` from S03, `getMilestoneSlices()`/`getSliceTasks()`/`getTask()` from S01/S02, `isDbAvailable()` + lazy `createRequire` pattern from S04 +- New wiring introduced: `replan_triggered_at` column writer in `triage-resolution.ts`, v8 column population in `migrateHierarchyToDb()` +- What remains before the milestone is truly usable end-to-end: S06 (parser deprecation + cleanup — removes dead parser code from hot paths) + +## Tasks + +- [ ] **T01: Schema v10 + flag-file DB migration in deriveStateFromDb** `est:45m` + - Why: The architecturally novel piece — REPLAN.md and REPLAN-TRIGGER.md detection in `deriveStateFromDb()` must use DB queries instead of disk-file checks. Schema v10 adds the `replan_triggered_at` column. Triage-resolution must also write the column. + - Files: `src/resources/extensions/gsd/gsd-db.ts`, `src/resources/extensions/gsd/state.ts`, `src/resources/extensions/gsd/triage-resolution.ts`, `src/resources/extensions/gsd/tests/flag-file-db.test.ts` + - Do: (1) Bump SCHEMA_VERSION to 10, add `replan_triggered_at TEXT DEFAULT NULL` to slices CREATE TABLE DDL and v10 migration block. (2) Update `SliceRow` interface and `rowToSlice()`. (3) In `deriveStateFromDb()`, replace `resolveSliceFile(... "REPLAN")` with `getReplanHistory(mid, sid).length > 0` check, replace `resolveSliceFile(... "REPLAN-TRIGGER")` with checking `getSlice(mid, sid)?.replan_triggered_at`. (4) In `triage-resolution.ts` `executeReplan()`, after writing the disk file, also write the `replan_triggered_at` column via `UPDATE slices SET replan_triggered_at = :ts`. (5) Write `flag-file-db.test.ts` testing: blocker→replan detection via DB (no disk file), REPLAN-TRIGGER via DB column (no disk file), loop protection (replan_history exists = no replanning phase). + - Verify: `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/flag-file-db.test.ts` + - Done when: deriveStateFromDb returns phase='replanning-slice' from DB-only data (no REPLAN.md or REPLAN-TRIGGER.md on disk) and returns phase='executing' when replan_history exists (loop protection). SCHEMA_VERSION=10. + +- [ ] **T02: Extend migrateHierarchyToDb with v8 column population** `est:30m` + - Why: Existing projects migrating to the DB need their parsed ROADMAP/PLAN data written into the v8 planning columns so DB queries return meaningful data. The `gsd recover` test must verify this. + - Files: `src/resources/extensions/gsd/md-importer.ts`, `src/resources/extensions/gsd/tests/gsd-recover.test.ts` + - Do: (1) In `migrateHierarchyToDb()`, extend the `insertMilestone()` call to pass `planning: { vision: roadmap.vision, successCriteria: roadmap.successCriteria, boundaryMapMarkdown: boundaryMapSection }` where `boundaryMapMarkdown` is the raw "## Boundary Map" section extracted from the roadmap content. (2) Extend `insertSlice()` calls to pass `planning: { goal: plan.goal }` from the parsed plan (when plan exists). (3) Extend `insertTask()` calls to pass `planning: { files: task.files, verify: task.verify }` from TaskPlanEntry. (4) Extend `gsd-recover.test.ts` to assert: after recover, milestone has non-empty `vision`; slice has non-empty `goal`; task has populated `files` array and `verify` string. + - Verify: `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/gsd-recover.test.ts` + - Done when: migrateHierarchyToDb populates vision, successCriteria, boundaryMapMarkdown on milestones; goal on slices; files and verify on tasks. Recovery test proves it. + +- [ ] **T03: Migrate warm/cold callers batch 1 — doctor, visualizer, workspace, dashboard, guided-flow** `est:40m` + - Why: Seven files with straightforward parseRoadmap/parsePlan usage need the S04 isDbAvailable + lazy createRequire pattern applied. + - Files: `src/resources/extensions/gsd/doctor.ts`, `src/resources/extensions/gsd/doctor-checks.ts`, `src/resources/extensions/gsd/visualizer-data.ts`, `src/resources/extensions/gsd/workspace-index.ts`, `src/resources/extensions/gsd/dashboard-overlay.ts`, `src/resources/extensions/gsd/auto-dashboard.ts`, `src/resources/extensions/gsd/guided-flow.ts` + - Do: For each file: (1) Remove module-level `parseRoadmap`/`parsePlan` from the import statement. (2) At each call site, add `isDbAvailable()` gate calling `getMilestoneSlices()`/`getSliceTasks()` for the DB path. (3) Add lazy `createRequire`-based fallback loading the parser for non-DB path. (4) For `parsePlan().filesLikelyTouched` aggregation in callers: collect `.files` arrays from `getSliceTasks()` results. (5) Keep other non-parser imports (loadFile, parseSummary, etc.) as module-level. Note: these files are async or synchronous — check each. For async callers, dynamic `import()` is also acceptable. Follow the exact pattern from `dispatch-guard.ts` (S04). + - Verify: `grep -n 'import.*parseRoadmap\|import.*parsePlan' src/resources/extensions/gsd/doctor.ts src/resources/extensions/gsd/doctor-checks.ts src/resources/extensions/gsd/visualizer-data.ts src/resources/extensions/gsd/workspace-index.ts src/resources/extensions/gsd/dashboard-overlay.ts src/resources/extensions/gsd/auto-dashboard.ts src/resources/extensions/gsd/guided-flow.ts` returns zero results. Existing test suites pass. + - Done when: Zero module-level parseRoadmap/parsePlan imports in these 7 files. All existing tests for these files pass. + +- [ ] **T04: Migrate warm/cold callers batch 2 — auto-prompts, auto-recovery, auto-direct-dispatch, auto-worktree, reactive-graph, markdown-renderer + final verification** `est:50m` + - Why: The remaining 6 files include auto-prompts.ts (6 parser calls, 1649 lines, highest complexity) and markdown-renderer.ts (intentional parser usage → lazy import only). Final grep verification confirms zero module-level parser imports remain. + - Files: `src/resources/extensions/gsd/auto-prompts.ts`, `src/resources/extensions/gsd/auto-recovery.ts`, `src/resources/extensions/gsd/auto-direct-dispatch.ts`, `src/resources/extensions/gsd/auto-worktree.ts`, `src/resources/extensions/gsd/reactive-graph.ts`, `src/resources/extensions/gsd/markdown-renderer.ts` + - Do: (1) **auto-prompts.ts** — all functions are async, so use dynamic `import("./gsd-db.js")` pattern (already used in this file for decisions/requirements). For `inlineDependencySummaries`: replace `parseRoadmap(roadmapContent).slices.find(s => s.id === sid)?.depends` with `getSlice(mid, sid)?.depends`. For `checkNeedsReassessment`/`checkNeedsRunUat`: replace `parseRoadmap().slices` with `getMilestoneSlices(mid)`, map `s.done` to `s.status === 'complete'`. For `buildCompleteMilestonePrompt`/`buildValidateMilestonePrompt`: replace slice iteration with `getMilestoneSlices()`. For `buildResumeContextListing` parsePlan: replace with `getSliceTasks()` to find incomplete tasks. Keep `parseSummary`, `parseContinue`, `loadFile`, `parseTaskPlanFile` imports — those aren't in scope. (2) **auto-recovery.ts** — the `parsePlan` at line 370 replaces with `getSliceTasks()` to check task plan files exist. The `parseRoadmap` at line 407 is already inside an `!isDbAvailable()` block — leave it, just move to lazy import. (3) **auto-direct-dispatch.ts** — replace 2 `parseRoadmap` calls with `getMilestoneSlices()` behind `isDbAvailable()` gate. (4) **auto-worktree.ts** — replace 1 `parseRoadmap` call with `getMilestoneSlices()`. (5) **reactive-graph.ts** — replace 1 `parsePlan` call with `getSliceTasks()`. Also uses `parseTaskPlanIO` — keep that as-is (not a planning parser). (6) **markdown-renderer.ts** — move `parseRoadmap`/`parsePlan` from module-level import to lazy `createRequire` (the parser calls are intentional disk-vs-DB comparison in `findStaleArtifacts()`). (7) Run final grep to confirm zero module-level parser imports remain across all non-test, non-md-importer, non-files.ts source files. + - Verify: `grep -rn 'import.*parseRoadmap\|import.*parsePlan\|import.*parseRoadmapSlices' src/resources/extensions/gsd/*.ts | grep -v '/tests/' | grep -v 'md-importer' | grep -v 'files.ts'` returns zero results. `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/auto-recovery.test.ts` passes. + - Done when: Zero module-level parseRoadmap/parsePlan/parseRoadmapSlices imports in any non-test, non-md-importer, non-files.ts source file. All existing test suites pass. + +## Files Likely Touched + +- `src/resources/extensions/gsd/gsd-db.ts` +- `src/resources/extensions/gsd/state.ts` +- `src/resources/extensions/gsd/triage-resolution.ts` +- `src/resources/extensions/gsd/md-importer.ts` +- `src/resources/extensions/gsd/doctor.ts` +- `src/resources/extensions/gsd/doctor-checks.ts` +- `src/resources/extensions/gsd/visualizer-data.ts` +- `src/resources/extensions/gsd/workspace-index.ts` +- `src/resources/extensions/gsd/dashboard-overlay.ts` +- `src/resources/extensions/gsd/auto-dashboard.ts` +- `src/resources/extensions/gsd/guided-flow.ts` +- `src/resources/extensions/gsd/reactive-graph.ts` +- `src/resources/extensions/gsd/auto-direct-dispatch.ts` +- `src/resources/extensions/gsd/auto-worktree.ts` +- `src/resources/extensions/gsd/auto-recovery.ts` +- `src/resources/extensions/gsd/auto-prompts.ts` +- `src/resources/extensions/gsd/markdown-renderer.ts` +- `src/resources/extensions/gsd/tests/flag-file-db.test.ts` +- `src/resources/extensions/gsd/tests/gsd-recover.test.ts` diff --git a/.gsd/milestones/M001/slices/S05/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S05/tasks/T01-PLAN.md new file mode 100644 index 000000000..f9b70e930 --- /dev/null +++ b/.gsd/milestones/M001/slices/S05/tasks/T01-PLAN.md @@ -0,0 +1,98 @@ +--- +estimated_steps: 5 +estimated_files: 4 +skills_used: [] +--- + +# T01: Schema v10 + flag-file DB migration in deriveStateFromDb + +**Slice:** S05 — Warm/cold callers + flag files + pre-M002 migration +**Milestone:** M001 + +## Description + +Add `replan_triggered_at TEXT DEFAULT NULL` column to the slices table (schema v10), then replace the disk-based REPLAN.md and REPLAN-TRIGGER.md detection in `deriveStateFromDb()` with DB queries. Update `triage-resolution.ts` to write the new column when creating a replan trigger. Write a test file proving flag-file phase detection works from DB-only data. + +**Critical semantic note:** In `deriveStateFromDb()`, REPLAN.md detection is **loop protection** — if a replan has already been done (REPLAN.md exists / replan_history has entries), the system should NOT re-enter replanning phase. REPLAN-TRIGGER.md detection triggers replanning when triage creates it. These are distinct checks with different semantics: +- `resolveSliceFile(... "REPLAN")` → checks if replan was already completed → DB equivalent: `getReplanHistory(mid, sid).length > 0` +- `resolveSliceFile(... "REPLAN-TRIGGER")` → checks if triage triggered a replan → DB equivalent: `getSlice(mid, sid)?.replan_triggered_at` is non-null + +**D003 constraint:** Do NOT touch CONTINUE.md detection. It stays as disk-based per locked decision D003. + +## Steps + +1. **Schema v10 migration + DDL update in `gsd-db.ts`:** + - Bump `SCHEMA_VERSION` from 9 to 10 + - Add `replan_triggered_at TEXT DEFAULT NULL` to the CREATE TABLE DDL for `slices` (after the `sequence` column) + - Add a `if (currentVersion < 10)` migration block using `ensureColumn()` to add the column to existing DBs + - Update `SliceRow` interface to include `replan_triggered_at: string | null` + - Update `rowToSlice()` to read the column: `replan_triggered_at: (row["replan_triggered_at"] as string) ?? null` + +2. **Update `deriveStateFromDb()` in `state.ts`:** + - The blocker detection block (around line 640) checks `resolveSliceFile(basePath, activeMilestone.id, activeSlice.id, "REPLAN")` for loop protection. Replace with: import and call `getReplanHistory` from `gsd-db.js`, check if `getReplanHistory(activeMilestone.id, activeSlice.id).length > 0`. If replan history exists, it means replan was already done — don't return `replanning-slice`. + - The REPLAN-TRIGGER detection block (around line 659) checks `resolveSliceFile(basePath, activeMilestone.id, activeSlice.id, "REPLAN-TRIGGER")`. Replace with: import `getSlice` from `gsd-db.js`, check if `getSlice(activeMilestone.id, activeSlice.id)?.replan_triggered_at` is non-null. If set, check loop protection (replan_history) before returning `replanning-slice`. + - Do NOT touch the `_deriveStateImpl()` fallback path (line ~1266+) — that's the disk-based fallback and stays as-is. + - Do NOT touch CONTINUE.md detection (line ~679) — per D003. + +3. **Update `triage-resolution.ts` `executeReplan()`:** + - After writing the disk file (keep the disk write for `_deriveStateImpl()` fallback), also write the DB column: + ```typescript + try { + const { isDbAvailable, _getAdapter } = await import("./gsd-db.js"); + // ... or use a synchronous approach since executeReplan is sync + } + ``` + - Since `executeReplan` is synchronous and `gsd-db.ts` exports are module-level, use a direct import if possible, or use `createRequire` for lazy loading. Check if `gsd-db.ts` is already imported in the file. If not, use the lazy pattern. Write: `UPDATE slices SET replan_triggered_at = :ts WHERE milestone_id = :mid AND id = :sid` + - Note: `_getAdapter()` returns the raw adapter. Or use `isDbAvailable()` check + direct SQL. Follow the pattern used by other callers. + +4. **Write `flag-file-db.test.ts`:** + Test cases: + - "blocker_discovered + no replan_history → phase is replanning-slice" — seed DB with a completed task that has `blocker_discovered=1`, no replan_history entries. Confirm `deriveStateFromDb()` returns `phase: 'replanning-slice'`. + - "blocker_discovered + replan_history exists → loop protection, phase is executing" — seed DB with blocker task AND a replan_history entry for that slice. Confirm `deriveStateFromDb()` returns `phase: 'executing'` (loop protection). + - "replan_triggered_at set + no replan_history → phase is replanning-slice" — seed DB with `replan_triggered_at` on the active slice, no replan_history. Confirm replanning phase. + - "replan_triggered_at set + replan_history exists → loop protection" — seed with both. Confirm executing phase. + - "no blocker, no trigger → phase is executing" — baseline test confirming normal execution. + - Use the test harness pattern from `derive-state-db.test.ts` — create temp dirs, seed DB, call `deriveStateFromDb()`. + +5. **Run verification:** + - Run `flag-file-db.test.ts` + - Run `derive-state-db.test.ts` and `derive-state-crossval.test.ts` for regressions + - Run `schema-v9-sequence.test.ts` (now schema v10 — confirm v9 migration still works) + +## Must-Haves + +- [ ] SCHEMA_VERSION bumped to 10 +- [ ] `replan_triggered_at` column in both CREATE TABLE DDL and v10 migration block +- [ ] `SliceRow` interface and `rowToSlice()` updated +- [ ] `deriveStateFromDb()` uses `getReplanHistory()` for REPLAN loop protection +- [ ] `deriveStateFromDb()` uses `getSlice().replan_triggered_at` for REPLAN-TRIGGER detection +- [ ] `triage-resolution.ts` `executeReplan()` writes `replan_triggered_at` column +- [ ] CONTINUE.md detection untouched per D003 +- [ ] `_deriveStateImpl()` fallback path untouched +- [ ] `flag-file-db.test.ts` with 5 test cases passing + +## Verification + +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/flag-file-db.test.ts` — all 5 tests pass +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/derive-state-db.test.ts` — no regressions +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/derive-state-crossval.test.ts` — no regressions + +## Observability Impact + +- Signals added: `replan_triggered_at` column on slices — queryable indicator of triage-initiated replan triggers +- How a future agent inspects this: `SELECT id, replan_triggered_at FROM slices WHERE milestone_id = :mid` +- Failure state exposed: If `deriveStateFromDb()` returns wrong phase, inspect `replan_history` table and `replan_triggered_at` column to diagnose + +## Inputs + +- `src/resources/extensions/gsd/gsd-db.ts` — schema, SliceRow interface, getReplanHistory(), getSlice(), _getAdapter() +- `src/resources/extensions/gsd/state.ts` — deriveStateFromDb() with existing REPLAN/REPLAN-TRIGGER disk checks +- `src/resources/extensions/gsd/triage-resolution.ts` — executeReplan() that writes REPLAN-TRIGGER.md +- `src/resources/extensions/gsd/tests/derive-state-db.test.ts` — test pattern reference for DB-seeded state tests + +## Expected Output + +- `src/resources/extensions/gsd/gsd-db.ts` — schema v10, updated SliceRow, rowToSlice +- `src/resources/extensions/gsd/state.ts` — deriveStateFromDb() using DB queries for flag-file detection +- `src/resources/extensions/gsd/triage-resolution.ts` — executeReplan() also writing replan_triggered_at column +- `src/resources/extensions/gsd/tests/flag-file-db.test.ts` — new test file with 5 flag-file DB migration tests diff --git a/.gsd/milestones/M001/slices/S05/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S05/tasks/T02-PLAN.md new file mode 100644 index 000000000..26bfab3f7 --- /dev/null +++ b/.gsd/milestones/M001/slices/S05/tasks/T02-PLAN.md @@ -0,0 +1,67 @@ +--- +estimated_steps: 4 +estimated_files: 2 +skills_used: [] +--- + +# T02: Extend migrateHierarchyToDb with v8 column population + +**Slice:** S05 — Warm/cold callers + flag files + pre-M002 migration +**Milestone:** M001 + +## Description + +Extend `migrateHierarchyToDb()` in `md-importer.ts` to populate v8 planning columns from parsed ROADMAP and PLAN files. This ensures pre-M002 projects get meaningful data in the DB planning columns when migrating. Per D004, tool-only fields (risks, requirementCoverage, proofLevel) are not populated — only fields the parsers can extract. Extend `gsd-recover.test.ts` to verify the v8 columns are populated after recovery. + +## Steps + +1. **Extend milestone insertion in `migrateHierarchyToDb()`:** + - The `parseRoadmap(roadmapContent)` call already returns `{ title, vision, successCriteria, slices, boundaryMap }`. + - The `insertMilestone()` call (around line 558) currently passes only `id`, `title`, `status`, `depends_on`. + - Add `planning: { vision: roadmap.vision, successCriteria: roadmap.successCriteria, boundaryMapMarkdown: boundaryMapSection }`. + - For `boundaryMapMarkdown`: extract the raw `## Boundary Map` section from `roadmapContent` using string operations (find `## Boundary Map` heading, take content until next `##` or EOF). The `extractSection()` function from `files.ts` can do this but is not exported — use a simple inline extraction: `const bmIdx = roadmapContent.indexOf('## Boundary Map'); const bmSection = bmIdx >= 0 ? roadmapContent.slice(bmIdx) ... : ''`. + - Note: `successCriteria` from `parseRoadmap()` is already a `string[]` — `insertMilestone()` expects it as `string[]` in the planning object and `JSON.stringify`s it internally. Verify this matches the `MilestonePlanningRecord.successCriteria` type. + +2. **Extend slice insertion:** + - The `insertSlice()` call (around line 574) currently passes `id`, `milestoneId`, `title`, `status`, `risk`, `depends`, `demo`. + - Parse the plan content (which already happens at line ~592: `parsePlan(planContent)`) and add `planning: { goal: plan.goal }` to the `insertSlice()` call. + - The plan parsing happens AFTER slice insertion currently. Restructure: read and parse the plan file BEFORE `insertSlice()`, so the goal is available. Or call `upsertSlicePlanning()` after parsing. The simpler approach: move the plan parse earlier, pass goal into insertSlice. If no plan exists, goal stays empty (the default). + +3. **Extend task insertion:** + - The `insertTask()` call (around line 612) currently passes `id`, `sliceId`, `milestoneId`, `title`, `status`. + - Add `planning: { files: taskEntry.files ?? [], verify: taskEntry.verify ?? '' }`. + - `TaskPlanEntry` from `parsePlan()` has optional `files?: string[]` and `verify?: string` fields. These are populated when the plan markdown has `- Files:` and `- Verify:` lines in task entries. + +4. **Extend `gsd-recover.test.ts`:** + - The existing test writes a ROADMAP.md and PLAN.md, runs `migrateHierarchyToDb()`, then checks counts and status. + - Add assertions after recovery: + - `getMilestonePlanning(mid)` returns non-empty `vision` matching what was in the fixture ROADMAP + - Slice row has non-empty `goal` matching what was in the fixture PLAN + - Task row has populated `files` array and non-empty `verify` string matching fixture data + - The fixture ROADMAP.md must include a `**Vision:**` field and `## Success Criteria` section for this to work. Check the existing fixture — if it doesn't have these, add them. + - The fixture PLAN.md must include `- Files:` and `- Verify:` in task entries. Check and extend if needed. + +## Must-Haves + +- [ ] `insertMilestone()` call in migrateHierarchyToDb passes `planning: { vision, successCriteria, boundaryMapMarkdown }` +- [ ] `insertSlice()` call passes `planning: { goal }` from parsed plan +- [ ] `insertTask()` call passes `planning: { files, verify }` from TaskPlanEntry +- [ ] `gsd-recover.test.ts` asserts v8 columns are populated after recovery +- [ ] Tool-only fields (risks, requirementCoverage, proofLevel) left empty per D004 + +## Verification + +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/gsd-recover.test.ts` — all tests pass including new v8 column assertions +- No regressions in other tests that use migrateHierarchyToDb (check `integration-mixed-milestones.test.ts`) + +## Inputs + +- `src/resources/extensions/gsd/md-importer.ts` — migrateHierarchyToDb() with existing insertMilestone/insertSlice/insertTask calls +- `src/resources/extensions/gsd/gsd-db.ts` — insertMilestone(planning), insertSlice(planning), insertTask(planning) signatures, getMilestonePlanning(), SliceRow, TaskRow interfaces +- `src/resources/extensions/gsd/tests/gsd-recover.test.ts` — existing recovery test to extend +- `src/resources/extensions/gsd/files.ts` — parseRoadmap() return type (vision, successCriteria, boundaryMap), parsePlan() return type (goal, tasks with files/verify) + +## Expected Output + +- `src/resources/extensions/gsd/md-importer.ts` — migrateHierarchyToDb() populates v8 planning columns +- `src/resources/extensions/gsd/tests/gsd-recover.test.ts` — extended with v8 column population assertions diff --git a/.gsd/milestones/M001/slices/S05/tasks/T03-PLAN.md b/.gsd/milestones/M001/slices/S05/tasks/T03-PLAN.md new file mode 100644 index 000000000..a55625668 --- /dev/null +++ b/.gsd/milestones/M001/slices/S05/tasks/T03-PLAN.md @@ -0,0 +1,123 @@ +--- +estimated_steps: 4 +estimated_files: 7 +skills_used: [] +--- + +# T03: Migrate warm/cold callers batch 1 — doctor, visualizer, workspace, dashboard, guided-flow + +**Slice:** S05 — Warm/cold callers + flag files + pre-M002 migration +**Milestone:** M001 + +## Description + +Apply the established S04 migration pattern (`isDbAvailable()` gate + lazy `createRequire` fallback) to 7 warm/cold caller files: `doctor.ts`, `doctor-checks.ts`, `visualizer-data.ts`, `workspace-index.ts`, `dashboard-overlay.ts`, `auto-dashboard.ts`, `guided-flow.ts`. These files have straightforward parseRoadmap/parsePlan usage that can be mechanically replaced with DB queries. + +**Pattern reference (from S04 dispatch-guard.ts):** +```typescript +// Remove from module-level import: +// import { parseRoadmap } from "./files.js"; + +// Add to module-level import: +import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js"; + +// At each call site, replace: +// const roadmap = parseRoadmap(content); +// for (const slice of roadmap.slices) { ... } +// With: +if (isDbAvailable()) { + const slices = getMilestoneSlices(mid); + // use slices directly — SliceRow has .id, .title, .status, .risk, .depends, .demo + // .done equivalent: slice.status === 'complete' +} else { + // Lazy fallback + const { createRequire } = await import("node:module"); + const _require = createRequire(import.meta.url); + let parseRoadmap: (c: string) => { slices: Array<{ id: string; done: boolean; title: string; risk: string; depends: string[]; demo: string }> }; + try { + parseRoadmap = _require("./files.ts").parseRoadmap; + } catch { + parseRoadmap = _require("./files.js").parseRoadmap; + } + const roadmap = parseRoadmap(content); + // ... use roadmap.slices +} +``` + +**Key mapping from parsed types to DB types:** +- `roadmap.slices[].done` → `slice.status === 'complete'` +- `roadmap.slices[].id/title/risk/depends/demo` → same field names on `SliceRow` +- `plan.tasks[].done` → `task.status === 'complete' || task.status === 'done'` +- `plan.tasks[].id/title` → same on `TaskRow` +- `plan.tasks[].files` → `task.files` (already parsed as `string[]` by `rowToTask()`) +- `plan.tasks[].verify` → `task.verify` +- `plan.filesLikelyTouched` → aggregate: `sliceTasks.flatMap(t => t.files)` + +**Important:** Some of these files have async functions (doctor.ts, visualizer-data.ts, workspace-index.ts, dashboard-overlay.ts, auto-dashboard.ts). For async callers, `await import("./gsd-db.js")` is cleaner than `createRequire`. For synchronous callers, use `createRequire`. Check each file. + +## Steps + +1. **doctor.ts** (3 parseRoadmap + 1 parsePlan): + - Remove `parseRoadmap`, `parsePlan` from the module-level import from `./files.js`. Keep `loadFile`, `parseSummary`, `saveFile`, `parseTaskPlanMustHaves`, `countMustHavesMentionedInSummary`. + - Add `import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";` + - At line ~216: replace `parseRoadmap(roadmapContent).slices` with `isDbAvailable() ? getMilestoneSlices(mid) : lazyParseRoadmap(roadmapContent).slices`. Map `.done` to `.status === 'complete'`. + - At line ~463: same pattern. + - At line ~582: replace `parsePlan(planContent)` with `isDbAvailable() ? { tasks: getSliceTasks(mid, sid) } : lazyParsePlan(planContent)`. Map task fields accordingly. + - Create a local lazy-parser helper function at the top of the file to avoid repeating the createRequire boilerplate. + +2. **doctor-checks.ts** (2 parseRoadmap): + - Remove `parseRoadmap` from import. Keep `loadFile`. + - Add DB imports. Replace 2 call sites with `getMilestoneSlices()` + fallback. + +3. **visualizer-data.ts** (1 parseRoadmap + 1 parsePlan): + - Remove parser imports. Add DB imports. Replace call sites. + +4. **workspace-index.ts** (2 parseRoadmap + 1 parsePlan): + - Remove parser imports. Add DB imports. Replace 3 call sites. + +5. **dashboard-overlay.ts** (1 parseRoadmap + 1 parsePlan): + - Remove parser imports. Add DB imports. Replace call sites. + +6. **auto-dashboard.ts** (1 parseRoadmap + 1 parsePlan): + - Remove parser imports. Add DB imports. Replace call sites. + +7. **guided-flow.ts** (2 parseRoadmap): + - Remove `parseRoadmap` from import. Keep `loadFile`. Add DB imports. Replace 2 call sites. + +After all changes, run verification grep and existing test suites. + +## Must-Haves + +- [ ] Zero module-level `parseRoadmap`/`parsePlan` imports in all 7 files +- [ ] Each file uses `isDbAvailable()` gate with DB query as primary path +- [ ] Each file has lazy `createRequire` (or dynamic import for async) fallback for parser +- [ ] `SliceRow.status === 'complete'` used instead of `.done` for all DB-path code +- [ ] Existing tests pass for all modified files + +## Verification + +- `grep -n 'import.*parseRoadmap\|import.*parsePlan' src/resources/extensions/gsd/doctor.ts src/resources/extensions/gsd/doctor-checks.ts src/resources/extensions/gsd/visualizer-data.ts src/resources/extensions/gsd/workspace-index.ts src/resources/extensions/gsd/dashboard-overlay.ts src/resources/extensions/gsd/auto-dashboard.ts src/resources/extensions/gsd/guided-flow.ts` — returns zero results +- Run available test suites: `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/doctor.test.ts` +- Run `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/auto-dashboard.test.ts` (if exists) + +## Inputs + +- `src/resources/extensions/gsd/doctor.ts` — 3 parseRoadmap + 1 parsePlan calls to migrate +- `src/resources/extensions/gsd/doctor-checks.ts` — 2 parseRoadmap calls +- `src/resources/extensions/gsd/visualizer-data.ts` — 1 parseRoadmap + 1 parsePlan +- `src/resources/extensions/gsd/workspace-index.ts` — 2 parseRoadmap + 1 parsePlan +- `src/resources/extensions/gsd/dashboard-overlay.ts` — 1 parseRoadmap + 1 parsePlan +- `src/resources/extensions/gsd/auto-dashboard.ts` — 1 parseRoadmap + 1 parsePlan +- `src/resources/extensions/gsd/guided-flow.ts` — 2 parseRoadmap +- `src/resources/extensions/gsd/gsd-db.ts` — isDbAvailable(), getMilestoneSlices(), getSliceTasks(), SliceRow, TaskRow interfaces +- `src/resources/extensions/gsd/dispatch-guard.ts` — reference implementation of the migration pattern from S04 + +## Expected Output + +- `src/resources/extensions/gsd/doctor.ts` — module-level parser imports removed, DB queries + lazy fallback +- `src/resources/extensions/gsd/doctor-checks.ts` — same migration +- `src/resources/extensions/gsd/visualizer-data.ts` — same migration +- `src/resources/extensions/gsd/workspace-index.ts` — same migration +- `src/resources/extensions/gsd/dashboard-overlay.ts` — same migration +- `src/resources/extensions/gsd/auto-dashboard.ts` — same migration +- `src/resources/extensions/gsd/guided-flow.ts` — same migration diff --git a/.gsd/milestones/M001/slices/S05/tasks/T04-PLAN.md b/.gsd/milestones/M001/slices/S05/tasks/T04-PLAN.md new file mode 100644 index 000000000..627ba3457 --- /dev/null +++ b/.gsd/milestones/M001/slices/S05/tasks/T04-PLAN.md @@ -0,0 +1,125 @@ +--- +estimated_steps: 4 +estimated_files: 6 +skills_used: [] +--- + +# T04: Migrate warm/cold callers batch 2 — auto-prompts, auto-recovery, auto-direct-dispatch, auto-worktree, reactive-graph, markdown-renderer + final verification + +**Slice:** S05 — Warm/cold callers + flag files + pre-M002 migration +**Milestone:** M001 + +## Description + +Migrate the remaining 6 files with parseRoadmap/parsePlan imports. `auto-prompts.ts` is the most complex (6 parser calls across 1649 lines, all async functions — use dynamic `import()` pattern already established in that file). `markdown-renderer.ts` is special: its parser calls are intentional disk-vs-DB comparisons in `findStaleArtifacts()` — only move the import from module-level to lazy `createRequire`, don't replace parser usage. Final step: run the comprehensive grep to confirm zero module-level parser imports remain anywhere in the codebase (excluding tests, md-importer, files.ts). + +**Pattern for async callers (already used in auto-prompts.ts for decisions/requirements):** +```typescript +try { + const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js"); + if (isDbAvailable()) { + const slices = getMilestoneSlices(mid); + // ... use DB data + return result; + } +} catch { /* fall through */ } +// Filesystem fallback +const roadmapContent = await loadFile(roadmapFile); +if (!roadmapContent) return null; +// lazy-load parser +const { createRequire } = await import("node:module"); +const _require = createRequire(import.meta.url); +let parseRoadmap: Function; +try { parseRoadmap = _require("./files.ts").parseRoadmap; } +catch { parseRoadmap = _require("./files.js").parseRoadmap; } +const roadmap = parseRoadmap(roadmapContent); +``` + +**Key field mappings:** +- `roadmap.slices[].done` → `slice.status === 'complete'` +- `plan.tasks[].done` → `task.status === 'complete' || task.status === 'done'` +- `plan.tasks[].files` → `task.files` (already parsed `string[]` per KNOWLEDGE.md) +- `plan.filesLikelyTouched` → `tasks.flatMap(t => t.files)` +- Slice `depends` field: same on `SliceRow` (already parsed as `string[]`) + +## Steps + +1. **auto-prompts.ts** (5 parseRoadmap + 1 parsePlan — all in async functions): + - Remove `parsePlan`, `parseRoadmap` from the module-level import on line 9. Keep `loadFile`, `parseContinue`, `parseSummary`, `extractUatType`, `loadActiveOverrides`, `formatOverridesSection`, `parseTaskPlanFile`. + - **`inlineDependencySummaries()` (line ~184):** Uses `parseRoadmap(roadmapContent).slices.find(s => s.id === sid)?.depends`. Replace with DB: `const { isDbAvailable, getSlice } = await import("./gsd-db.js"); if (isDbAvailable()) { const slice = getSlice(mid, sid); if (!slice || slice.depends.length === 0) return "- (no dependencies)"; /* use slice.depends */ }`. Fallback: lazy-load parseRoadmap. + - **`checkNeedsReassessment()` (line ~691):** Uses `parseRoadmap().slices` to find completed/incomplete slices. Replace with: `getMilestoneSlices(mid)`, filter by `s.status === 'complete'` vs not. + - **`checkNeedsRunUat()` (line ~732):** Same pattern as checkNeedsReassessment — replace with `getMilestoneSlices(mid)`. + - **`buildCompleteMilestonePrompt()` (line ~1221):** Iterates `roadmap.slices` to inline slice summaries. Replace with `getMilestoneSlices(mid)` to get slice IDs. + - **`buildValidateMilestonePrompt()` (line ~1277):** Same as buildCompleteMilestonePrompt — iterate `getMilestoneSlices(mid)` for slice summary inlining. + - **`buildResumeContextListing()` (line ~1603):** Uses `parsePlan(planContent).tasks` to find incomplete tasks for listing. Replace with `getSliceTasks(mid, sid)`, filter by `task.status !== 'complete' && task.status !== 'done'`. + - Create a local helper `async function lazyParseRoadmap(content: string)` and `async function lazyParsePlan(content: string)` at top of file to centralize the createRequire fallback pattern. + +2. **auto-recovery.ts** (1 parsePlan at line 370, 1 parseRoadmap at line 407): + - Remove `parseRoadmap`, `parsePlan` from module-level import on line 14. Keep `clearParseCache`. + - Line 370 `parsePlan`: Used in plan-slice completion check — gets task list to verify task plan files exist. Replace with `getSliceTasks(mid, sid)` to get task IDs, then check if task plan files exist on disk. Fallback: lazy-load parsePlan. + - Line 407 `parseRoadmap`: Already inside `!isDbAvailable()` block — this IS the fallback path. Just move the import from module-level to lazy `createRequire` at that call site. + - Add `import { isDbAvailable, getSliceTasks } from "./gsd-db.js";` to module-level imports. + +3. **auto-direct-dispatch.ts, auto-worktree.ts, reactive-graph.ts:** + - **auto-direct-dispatch.ts** (2 parseRoadmap at lines 160, 185): Remove `parseRoadmap` from import (keep `loadFile`). Add `isDbAvailable, getMilestoneSlices`. Replace both call sites with `getMilestoneSlices()` + fallback. + - **auto-worktree.ts** (1 parseRoadmap at line 1002): Remove `parseRoadmap` from import. Add DB imports. Replace call site. + - **reactive-graph.ts** (1 parsePlan at line 191): Remove `parsePlan` from import (keep `loadFile`, `parseTaskPlanIO`). Add `isDbAvailable, getSliceTasks`. Replace with `getSliceTasks()` + fallback. Note: `parseTaskPlanIO` is NOT a planning parser — it parses Inputs/Expected Output from task plan files for dependency graphing. Keep it as module-level import. + +4. **markdown-renderer.ts** (2 parseRoadmap + 2 parsePlan in `findStaleArtifacts()`): + - These parser calls are **intentional** — they compare disk content against DB state to detect staleness. Do NOT replace parser usage with DB queries. + - Move `parseRoadmap`, `parsePlan` from module-level import (line 33) to lazy `createRequire` inside `findStaleArtifacts()`. Keep `saveFile`, `clearParseCache` as module-level. + - At the top of `findStaleArtifacts()` (around line 775), add lazy loading: + ```typescript + const { createRequire } = await import("node:module"); + const _require = createRequire(import.meta.url); + let parseRoadmap: Function, parsePlan: Function; + try { + const m = _require("./files.ts"); + parseRoadmap = m.parseRoadmap; parsePlan = m.parsePlan; + } catch { + const m = _require("./files.js"); + parseRoadmap = m.parseRoadmap; parsePlan = m.parsePlan; + } + ``` + - Note: `findStaleArtifacts()` is async, so dynamic import works too. Use whichever is simpler. + +5. **Final verification grep:** + - `grep -rn 'import.*parseRoadmap\|import.*parsePlan\|import.*parseRoadmapSlices' src/resources/extensions/gsd/*.ts | grep -v '/tests/' | grep -v 'md-importer' | grep -v 'files.ts'` + - Expected: ZERO results. No module-level parser imports remain. + - Run `auto-recovery.test.ts` and any other available test suites for modified files. + +## Must-Haves + +- [ ] Zero module-level `parseRoadmap`/`parsePlan` imports in all 6 files +- [ ] `auto-prompts.ts` uses DB queries as primary path for all 6 parser call sites +- [ ] `auto-recovery.ts` parsePlan at line 370 replaced with getSliceTasks() + fallback +- [ ] `markdown-renderer.ts` parser imports moved to lazy loading (parser usage kept) +- [ ] Final grep returns zero module-level parser imports across all non-test source files +- [ ] All existing test suites pass + +## Verification + +- `grep -rn 'import.*parseRoadmap\|import.*parsePlan\|import.*parseRoadmapSlices' src/resources/extensions/gsd/*.ts | grep -v '/tests/' | grep -v 'md-importer' | grep -v 'files.ts'` — returns zero results +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/auto-recovery.test.ts` — passes +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/markdown-renderer.test.ts` — passes +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/planning-crossval.test.ts` — passes + +## Inputs + +- `src/resources/extensions/gsd/auto-prompts.ts` — 5 parseRoadmap + 1 parsePlan calls to migrate (all async functions) +- `src/resources/extensions/gsd/auto-recovery.ts` — 1 parsePlan + 1 parseRoadmap (latter already in !isDbAvailable block) +- `src/resources/extensions/gsd/auto-direct-dispatch.ts` — 2 parseRoadmap calls +- `src/resources/extensions/gsd/auto-worktree.ts` — 1 parseRoadmap call +- `src/resources/extensions/gsd/reactive-graph.ts` — 1 parsePlan call +- `src/resources/extensions/gsd/markdown-renderer.ts` — 2 parseRoadmap + 2 parsePlan (intentional disk-vs-DB comparison) +- `src/resources/extensions/gsd/gsd-db.ts` — isDbAvailable(), getMilestoneSlices(), getSliceTasks(), getSlice(), getTask() +- `src/resources/extensions/gsd/dispatch-guard.ts` — reference for lazy createRequire pattern + +## Expected Output + +- `src/resources/extensions/gsd/auto-prompts.ts` — module-level parser imports removed, 6 call sites use DB queries with lazy fallback +- `src/resources/extensions/gsd/auto-recovery.ts` — module-level parser imports removed, DB + lazy fallback +- `src/resources/extensions/gsd/auto-direct-dispatch.ts` — module-level parseRoadmap removed, DB + fallback +- `src/resources/extensions/gsd/auto-worktree.ts` — module-level parseRoadmap removed, DB + fallback +- `src/resources/extensions/gsd/reactive-graph.ts` — module-level parsePlan removed, DB + fallback +- `src/resources/extensions/gsd/markdown-renderer.ts` — module-level parser imports moved to lazy loading inside findStaleArtifacts()