chore(M001/S05): auto-commit after plan-slice

This commit is contained in:
TÂCHES 2026-03-23 11:37:37 -06:00
parent 4f829131f6
commit b8aaded95e
5 changed files with 506 additions and 0 deletions

View file

@ -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`

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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()