diff --git a/.gsd/milestones/M001/slices/S03/S03-PLAN.md b/.gsd/milestones/M001/slices/S03/S03-PLAN.md new file mode 100644 index 000000000..66c280c4d --- /dev/null +++ b/.gsd/milestones/M001/slices/S03/S03-PLAN.md @@ -0,0 +1,87 @@ +# S03: replan_slice + reassess_roadmap with structural enforcement + +**Goal:** `gsd_replan_slice` rejects mutations to completed tasks, `gsd_reassess_roadmap` rejects mutations to completed slices. Both write to DB tables (replan_history, assessments), render REPLAN.md/ASSESSMENT.md from DB, and re-render PLAN.md/ROADMAP.md after mutations. +**Demo:** Tests prove that calling replan with a completed task ID returns a structural rejection error, while modifying only incomplete tasks succeeds. Similarly, calling reassess with a completed slice ID returns a rejection error, while modifying only pending slices succeeds. Rendered REPLAN.md and ASSESSMENT.md artifacts exist on disk. Prompts name `gsd_replan_slice` and `gsd_reassess_roadmap` as the canonical tool paths. + +## Must-Haves + +- `handleReplanSlice` structurally rejects mutations (update or remove) to completed tasks +- `handleReplanSlice` writes `replan_history` row, applies task mutations, re-renders PLAN.md + task plans, renders REPLAN.md +- `handleReassessRoadmap` structurally rejects mutations (modify or remove) to completed slices +- `handleReassessRoadmap` writes `assessments` row, applies slice mutations, re-renders ROADMAP.md, renders ASSESSMENT.md +- Both handlers follow validate → enforce → transaction → render → invalidate pattern +- Both handlers invalidate state cache and parse cache after success +- `replan-slice.md` and `reassess-roadmap.md` prompts name the new tools as canonical write path +- Prompt contract tests assert tool name presence in both prompts +- DB helper functions: `insertReplanHistory()`, `insertAssessment()`, `deleteTask()`, `deleteSlice()` +- Renderers: `renderReplanFromDb()`, `renderAssessmentFromDb()` + +## Proof Level + +- This slice proves: contract +- Real runtime required: no +- Human/UAT required: no + +## Verification + +```bash +# Primary proof — replan handler: validation, structural enforcement, DB writes, rendering +node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/replan-handler.test.ts + +# Primary proof — reassess handler: validation, structural enforcement, DB writes, rendering +node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/reassess-handler.test.ts + +# Prompt contracts — verify prompts reference new tool names +node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/prompt-contracts.test.ts + +# Full regression — existing tests still pass +node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/plan-milestone.test.ts src/resources/extensions/gsd/tests/plan-slice.test.ts src/resources/extensions/gsd/tests/plan-task.test.ts src/resources/extensions/gsd/tests/markdown-renderer.test.ts src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +``` + +## Observability / Diagnostics + +- Runtime signals: Handler error payloads include structured rejection messages naming the specific completed task/slice IDs that blocked the mutation +- Inspection surfaces: `replan_history` and `assessments` DB tables can be queried directly; rendered REPLAN.md and ASSESSMENT.md artifacts on disk +- Failure visibility: Validation errors, structural rejection errors, render failures all return distinct `{ error: string }` payloads with actionable messages + +## Integration Closure + +- Upstream surfaces consumed: `gsd-db.ts` query functions (`getSliceTasks`, `getTask`, `getSlice`, `getMilestoneSlices`, `getMilestone`), `gsd-db.ts` mutation functions (`upsertTaskPlanning`, `upsertSlicePlanning`, `insertTask`, `insertSlice`, `transaction`), `markdown-renderer.ts` renderers (`renderPlanFromDb`, `renderRoadmapFromDb`, `writeAndStore` pattern), `files.ts` (`clearParseCache`), `state.ts` (`invalidateStateCache`) +- New wiring introduced in this slice: `tools/replan-slice.ts` and `tools/reassess-roadmap.ts` handler modules, tool registrations in `db-tools.ts`, prompt template references to `gsd_replan_slice` and `gsd_reassess_roadmap` +- What remains before the milestone is truly usable end-to-end: S04 hot-path caller migration, S05 flag file migration, S06 parser deprecation + +## Tasks + +- [ ] **T01: Implement replan_slice handler with structural enforcement** `est:1h` + - Why: Delivers R005 — the core replan handler that queries DB for completed tasks and structurally rejects mutations to them. Also adds required DB helpers (`insertReplanHistory`, `deleteTask`, `deleteSlice`) and the REPLAN.md renderer that all downstream work depends on. + - Files: `src/resources/extensions/gsd/gsd-db.ts`, `src/resources/extensions/gsd/tools/replan-slice.ts`, `src/resources/extensions/gsd/markdown-renderer.ts`, `src/resources/extensions/gsd/tests/replan-handler.test.ts` + - Do: (1) Add `insertReplanHistory()`, `insertAssessment()`, `deleteTask()`, `deleteSlice()` to `gsd-db.ts`. `deleteTask` must first delete from `verification_evidence` (FK constraint) before deleting the task row. `deleteSlice` must delete all child tasks' evidence, then child tasks, then the slice. (2) Add `renderReplanFromDb()` and `renderAssessmentFromDb()` to `markdown-renderer.ts` — both use `writeAndStore()` pattern. REPLAN.md should contain the blocker description, what changed, and the updated task list. ASSESSMENT.md should contain the verdict, assessment text, and slice changes. (3) Create `tools/replan-slice.ts` with `handleReplanSlice()`. Params: milestoneId, sliceId, blockerTaskId, blockerDescription, whatChanged, updatedTasks array (taskId, title, description, estimate, files, verify, inputs, expectedOutput), removedTaskIds array. Validate flat params. Query `getSliceTasks()` for completed tasks (status === 'complete' or 'done'). Reject if any updatedTasks[].taskId or removedTaskIds element matches a completed task. In transaction: write replan_history row, apply task mutations (upsert updated tasks via insertTask+upsertTaskPlanning, delete removed tasks), insert new tasks. After transaction: re-render PLAN.md via `renderPlanFromDb()`, render REPLAN.md via `renderReplanFromDb()`, invalidate caches. (4) Write `tests/replan-handler.test.ts` using `node:test` and the same pattern as `plan-slice.test.ts`. Tests must prove: validation failures, structural rejection of completed task update, structural rejection of completed task removal, successful replan modifying only incomplete tasks, replan_history row persistence, re-rendered PLAN.md correctness, REPLAN.md existence, cache invalidation via parse-visible state. + - Verify: `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/replan-handler.test.ts` + - Done when: All replan handler tests pass, including structural rejection of completed-task mutations and successful replan of incomplete tasks with DB persistence and rendered artifacts. + +- [ ] **T02: Implement reassess_roadmap handler with structural enforcement** `est:45m` + - Why: Delivers R006 — the reassess handler that queries DB for completed slices and structurally rejects mutations to them. Reuses DB helpers from T01 and the ASSESSMENT.md renderer. + - Files: `src/resources/extensions/gsd/tools/reassess-roadmap.ts`, `src/resources/extensions/gsd/tests/reassess-handler.test.ts` + - Do: (1) Create `tools/reassess-roadmap.ts` with `handleReassessRoadmap()`. Params: milestoneId, completedSliceId (the slice that just finished), verdict, assessment (text), sliceChanges object with: modified array (sliceId, title, risk, depends, demo), added array (same shape), removed array (sliceId strings). Validate flat params. Query `getMilestoneSlices()` for completed slices (status === 'complete' or 'done'). Reject if any modified[].sliceId or removed[] element matches a completed slice. In transaction: write assessments row (path as PK = ASSESSMENT.md artifact path, milestone_id, status=verdict, scope='roadmap', full_content=assessment text), apply slice mutations (upsert modified via `upsertSlicePlanning`, insert added via `insertSlice`, delete removed via `deleteSlice`). After transaction: re-render ROADMAP.md via `renderRoadmapFromDb()`, render ASSESSMENT.md via `renderAssessmentFromDb()`, invalidate caches. (2) Write `tests/reassess-handler.test.ts` using `node:test`. Tests must prove: validation failures, structural rejection of completed slice modification, structural rejection of completed slice removal, successful reassess modifying only pending slices, assessments row persistence, re-rendered ROADMAP.md correctness, ASSESSMENT.md existence, cache invalidation. + - Verify: `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/reassess-handler.test.ts` + - Done when: All reassess handler tests pass, including structural rejection of completed-slice mutations and successful reassess with DB persistence and rendered artifacts. + +- [ ] **T03: Register tools in db-tools.ts + update prompts + prompt contract tests** `est:30m` + - Why: Connects the handlers to the tool system so auto-mode dispatch can invoke them, and updates prompts to name the tools as canonical write paths. Extends prompt contract tests to catch regressions. + - Files: `src/resources/extensions/gsd/bootstrap/db-tools.ts`, `src/resources/extensions/gsd/prompts/replan-slice.md`, `src/resources/extensions/gsd/prompts/reassess-roadmap.md`, `src/resources/extensions/gsd/tests/prompt-contracts.test.ts` + - Do: (1) Register `gsd_replan_slice` in `db-tools.ts` following the exact pattern of `gsd_plan_slice` — ensureDbOpen check, dynamic import of `../tools/replan-slice.js`, call `handleReplanSlice(params, process.cwd())`, return structured content/details. TypeBox schema matches handler params. Register alias `gsd_slice_replan`. (2) Register `gsd_reassess_roadmap` with alias `gsd_roadmap_reassess` — same pattern, dynamic import of `../tools/reassess-roadmap.js`, call `handleReassessRoadmap(params, process.cwd())`. (3) Update `replan-slice.md` prompt: add a step before the existing file-write instructions that says to use `gsd_replan_slice` tool as the canonical write path when DB-backed tools are available. Position the existing file-write instructions as degraded fallback. Name the specific tool and its parameters. (4) Update `reassess-roadmap.md` prompt: similarly add `gsd_reassess_roadmap` as canonical path. The prompt already has "Do not bypass state with manual roadmap-only edits" — strengthen by naming the specific tool. (5) Add prompt contract tests in `prompt-contracts.test.ts`: assert `replan-slice.md` contains `gsd_replan_slice`, assert `reassess-roadmap.md` contains `gsd_reassess_roadmap`. + - Verify: `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/prompt-contracts.test.ts` + - Done when: Both tools are registered with aliases, both prompts name the canonical tools, and prompt contract tests pass. + +## Files Likely Touched + +- `src/resources/extensions/gsd/gsd-db.ts` +- `src/resources/extensions/gsd/markdown-renderer.ts` +- `src/resources/extensions/gsd/tools/replan-slice.ts` (new) +- `src/resources/extensions/gsd/tools/reassess-roadmap.ts` (new) +- `src/resources/extensions/gsd/bootstrap/db-tools.ts` +- `src/resources/extensions/gsd/prompts/replan-slice.md` +- `src/resources/extensions/gsd/prompts/reassess-roadmap.md` +- `src/resources/extensions/gsd/tests/replan-handler.test.ts` (new) +- `src/resources/extensions/gsd/tests/reassess-handler.test.ts` (new) +- `src/resources/extensions/gsd/tests/prompt-contracts.test.ts` diff --git a/.gsd/milestones/M001/slices/S03/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S03/tasks/T01-PLAN.md new file mode 100644 index 000000000..ec588ee0b --- /dev/null +++ b/.gsd/milestones/M001/slices/S03/tasks/T01-PLAN.md @@ -0,0 +1,88 @@ +--- +estimated_steps: 4 +estimated_files: 4 +skills_used: [] +--- + +# T01: Implement replan_slice handler with structural enforcement + +**Slice:** S03 — replan_slice + reassess_roadmap with structural enforcement +**Milestone:** M001 + +## Description + +Build the `handleReplanSlice()` handler that structurally enforces preservation of completed tasks during replanning. This task also adds required DB helper functions (`insertReplanHistory`, `insertAssessment`, `deleteTask`, `deleteSlice`) and markdown renderers (`renderReplanFromDb`, `renderAssessmentFromDb`) that both the replan and reassess handlers use. + +The handler follows the established validate → enforce → transaction → render → invalidate pattern from `plan-slice.ts`. The novel addition is the structural enforcement step: before writing any mutations, query `getSliceTasks()` and reject the operation if any `updatedTasks[].taskId` or `removedTaskIds` element matches a task with status `complete` or `done`. + +## Steps + +1. **Add DB helper functions to `gsd-db.ts`:** + - `insertReplanHistory(entry)` — INSERT into `replan_history` table. Columns: milestone_id, slice_id, task_id (nullable, the blocker task), summary, previous_artifact_path, replacement_artifact_path, created_at. + - `insertAssessment(entry)` — INSERT OR REPLACE into `assessments` table (path is PK). Columns: path, milestone_id, slice_id, task_id, status, scope, full_content, created_at. + - `deleteTask(milestoneId, sliceId, taskId)` — Must first DELETE from `verification_evidence WHERE task_id = :tid AND slice_id = :sid AND milestone_id = :mid`, then DELETE from `tasks WHERE ...`. The `verification_evidence` table has a FK referencing tasks — deleting evidence first avoids FK constraint violations. + - `deleteSlice(milestoneId, sliceId)` — Must delete all child verification_evidence rows, then all child task rows, then the slice row. Use cascade-style manual deletion. + +2. **Add renderers to `markdown-renderer.ts`:** + - `renderReplanFromDb(basePath, milestoneId, sliceId, replanData)` — Generates REPLAN.md with blocker description, what changed, and summary. Uses `writeAndStore()` with artifact_type `"REPLAN"`. The `replanData` param includes blockerTaskId, blockerDescription, whatChanged. Path: `{sliceDir}/{sliceId}-REPLAN.md`. + - `renderAssessmentFromDb(basePath, milestoneId, sliceId, assessmentData)` — Generates ASSESSMENT.md with verdict, assessment text. Uses `writeAndStore()` with artifact_type `"ASSESSMENT"`. Path: `{sliceDir}/{sliceId}-ASSESSMENT.md`. + +3. **Create `tools/replan-slice.ts` with `handleReplanSlice()`:** + - Interface `ReplanSliceParams`: milestoneId, sliceId, blockerTaskId, blockerDescription, whatChanged, updatedTasks (array of {taskId, title, description, estimate, files, verify, inputs, expectedOutput}), removedTaskIds (string array). + - Validate all required fields (same `isNonEmptyString` pattern as plan-slice.ts). + - Query `getSlice()` to verify parent slice exists. + - Query `getSliceTasks()` to get all tasks. Build a Set of completed task IDs (status === 'complete' || status === 'done'). + - **Structural enforcement**: Check if any `updatedTasks[].taskId` is in the completed set → return `{ error: "cannot modify completed task T0X" }`. Check if any `removedTaskIds` element is in the completed set → return `{ error: "cannot remove completed task T0X" }`. + - In `transaction()`: call `insertReplanHistory()` with the replan metadata. For each updatedTask: if task exists, use `upsertTaskPlanning()` to update planning fields; if new, use `insertTask()` then `upsertTaskPlanning()`. For each removedTaskId: call `deleteTask()`. + - After transaction: call `renderPlanFromDb()` to re-render PLAN.md and task plans. Call `renderReplanFromDb()` to write REPLAN.md. Call `invalidateStateCache()` and `clearParseCache()`. + - Return `{ milestoneId, sliceId, replanPath, planPath }` on success. + +4. **Write `tests/replan-handler.test.ts`:** + - Use `node:test` (import test from 'node:test') and `node:assert/strict`. Follow the exact test setup pattern from `plan-slice.test.ts`: `makeTmpBase()`, `openDatabase()`, `cleanup()`, seed parent milestone+slice+tasks. + - Test cases: + - Validation failure (missing milestoneId) → returns `{ error }` containing "validation failed" + - Structural rejection: seed T01 as complete, T02 as pending. Call replan with updatedTasks targeting T01. Assert error contains "completed task" and "T01". + - Structural rejection: seed T01 as complete. Call replan with removedTaskIds containing T01. Assert error contains "completed task". + - Successful replan: seed T01 complete, T02 pending, T03 pending. Call replan updating T02 and removing T03 and adding T04. Assert success. Verify replan_history row exists in DB. Verify T02 updated in DB. Verify T03 deleted from DB. Verify T04 exists in DB. Verify rendered PLAN.md exists on disk. Verify REPLAN.md exists on disk. + - Cache invalidation: verify that re-parsing the PLAN.md after replan reflects the mutations (parse-visible state assertion). + - Idempotent rerun: call replan twice with same params, assert second call also succeeds. + +## Must-Haves + +- [ ] `insertReplanHistory()`, `insertAssessment()`, `deleteTask()`, `deleteSlice()` exported from `gsd-db.ts` +- [ ] `deleteTask()` handles FK constraint by deleting verification_evidence first +- [ ] `renderReplanFromDb()` and `renderAssessmentFromDb()` exported from `markdown-renderer.ts` +- [ ] `handleReplanSlice()` exported from `tools/replan-slice.ts` +- [ ] Structural rejection returns error naming the specific completed task ID +- [ ] Successful replan writes `replan_history` row with blocker metadata +- [ ] Successful replan re-renders PLAN.md and writes REPLAN.md via `writeAndStore()` +- [ ] Cache invalidation via `invalidateStateCache()` + `clearParseCache()` after render +- [ ] All tests in `replan-handler.test.ts` pass + +## Verification + +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/replan-handler.test.ts` — all tests pass +- Structural rejection tests prove completed tasks cannot be mutated +- DB persistence tests prove replan_history row exists after successful replan + +## Observability Impact + +- Signals added/changed: Replan handler error payloads include the specific completed task IDs that blocked the mutation +- How a future agent inspects this: Query `replan_history` table, read rendered REPLAN.md, check PLAN.md for updated task list +- Failure state exposed: Validation errors, structural rejection errors, render failures return distinct `{ error: string }` payloads + +## Inputs + +- `src/resources/extensions/gsd/gsd-db.ts` — existing DB functions: `getSliceTasks()`, `getTask()`, `getSlice()`, `insertTask()`, `upsertTaskPlanning()`, `transaction()`, `insertArtifact()` +- `src/resources/extensions/gsd/markdown-renderer.ts` — existing `writeAndStore()` pattern, `renderPlanFromDb()` for PLAN.md re-rendering +- `src/resources/extensions/gsd/tools/plan-slice.ts` — reference handler pattern (validate → transaction → render → invalidate) +- `src/resources/extensions/gsd/tests/plan-slice.test.ts` — reference test pattern (setup, seed, assert) +- `src/resources/extensions/gsd/state.ts` — `invalidateStateCache()` import +- `src/resources/extensions/gsd/files.ts` — `clearParseCache()` import + +## Expected Output + +- `src/resources/extensions/gsd/gsd-db.ts` — modified with 4 new exported functions +- `src/resources/extensions/gsd/markdown-renderer.ts` — modified with 2 new renderer functions +- `src/resources/extensions/gsd/tools/replan-slice.ts` — new handler file +- `src/resources/extensions/gsd/tests/replan-handler.test.ts` — new test file diff --git a/.gsd/milestones/M001/slices/S03/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S03/tasks/T02-PLAN.md new file mode 100644 index 000000000..da4326acd --- /dev/null +++ b/.gsd/milestones/M001/slices/S03/tasks/T02-PLAN.md @@ -0,0 +1,75 @@ +--- +estimated_steps: 2 +estimated_files: 2 +skills_used: [] +--- + +# T02: Implement reassess_roadmap handler with structural enforcement + +**Slice:** S03 — replan_slice + reassess_roadmap with structural enforcement +**Milestone:** M001 + +## Description + +Build the `handleReassessRoadmap()` handler that structurally enforces preservation of completed slices during roadmap reassessment. This handler follows the identical control flow pattern as `handleReplanSlice()` from T01 but operates at the milestone/slice level instead of the slice/task level. It reuses the DB helpers (`insertAssessment`, `deleteSlice`) and the `renderAssessmentFromDb()` renderer from T01. + +The structural enforcement logic: before writing any mutations, query `getMilestoneSlices()` and reject if any modified or removed slice has status `complete` or `done`. + +## Steps + +1. **Create `tools/reassess-roadmap.ts` with `handleReassessRoadmap()`:** + - Interface `ReassessRoadmapParams`: milestoneId, completedSliceId (the slice that just finished), verdict (string — e.g. "confirmed", "adjusted"), assessment (text body), sliceChanges object with: modified (array of {sliceId, title, risk, depends, demo}), added (array of {sliceId, title, risk, depends, demo}), removed (array of sliceId strings). + - Validate all required fields. `sliceChanges` must be an object with modified, added, removed arrays (can be empty arrays but must exist). + - Query `getMilestone()` to verify milestone exists. + - Query `getMilestoneSlices()` to get all slices. Build a Set of completed slice IDs (status === 'complete' || status === 'done'). + - **Structural enforcement**: Check if any `sliceChanges.modified[].sliceId` is in the completed set → return `{ error: "cannot modify completed slice S0X" }`. Check if any `sliceChanges.removed[]` element is in the completed set → return `{ error: "cannot remove completed slice S0X" }`. + - Compute assessment artifact path: `{sliceDir}/{completedSliceId}-ASSESSMENT.md` (the assessment lives in the completed slice's directory). + - In `transaction()`: call `insertAssessment()` with path (PK), milestone_id, status=verdict, scope='roadmap', full_content=assessment text, created_at. For each modified slice: call `upsertSlicePlanning()` to update title/risk/depends/demo. For each added slice: call `insertSlice()` with id, milestoneId, title, status='pending', demo. For each removed sliceId: call `deleteSlice()`. + - After transaction: call `renderRoadmapFromDb()` to re-render ROADMAP.md. Call `renderAssessmentFromDb()` to write ASSESSMENT.md. Call `invalidateStateCache()` and `clearParseCache()`. + - Return `{ milestoneId, completedSliceId, assessmentPath, roadmapPath }` on success. + +2. **Write `tests/reassess-handler.test.ts`:** + - Use `node:test` and `node:assert/strict`. Follow the setup pattern from `plan-slice.test.ts`: temp directory with `.gsd/milestones/M001/` structure, `openDatabase()`, seed milestone with S01 (complete), S02 (pending), S03 (pending). + - Test cases: + - Validation failure (missing milestoneId) → returns `{ error }` containing "validation failed" + - Missing milestone → returns `{ error }` containing "not found" + - Structural rejection: call reassess with modified containing S01 (complete). Assert error contains "completed slice" and "S01". + - Structural rejection: call reassess with removed containing S01 (complete). Assert error contains "completed slice". + - Successful reassess: modify S02 title/demo, add S04, remove S03. Assert success. Verify assessments row exists in DB (query by path). Verify S02 updated in DB. Verify S03 deleted from DB. Verify S04 exists in DB. Verify ROADMAP.md re-rendered on disk. Verify ASSESSMENT.md exists on disk. + - Cache invalidation: verify parse-visible state reflects mutations. + - Idempotent rerun: call reassess twice, second also succeeds (INSERT OR REPLACE on assessments path PK). + +## Must-Haves + +- [ ] `handleReassessRoadmap()` exported from `tools/reassess-roadmap.ts` +- [ ] Structural rejection returns error naming the specific completed slice ID +- [ ] Successful reassess writes `assessments` row with path PK and assessment content +- [ ] Successful reassess re-renders ROADMAP.md and writes ASSESSMENT.md via renderers +- [ ] Cache invalidation via `invalidateStateCache()` + `clearParseCache()` after render +- [ ] All tests in `reassess-handler.test.ts` pass + +## Verification + +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/reassess-handler.test.ts` — all tests pass +- Structural rejection tests prove completed slices cannot be mutated +- DB persistence tests prove assessments row exists after successful reassess + +## Observability Impact + +- Signals added/changed: Reassess handler error payloads include the specific completed slice IDs that blocked the mutation +- How a future agent inspects this: Query `assessments` table by path, read rendered ASSESSMENT.md, check ROADMAP.md for updated slice list +- Failure state exposed: Validation errors, structural rejection errors, render failures return distinct `{ error: string }` payloads + +## Inputs + +- `src/resources/extensions/gsd/gsd-db.ts` — `getMilestoneSlices()`, `getMilestone()`, `insertSlice()`, `upsertSlicePlanning()`, `insertAssessment()`, `deleteSlice()`, `transaction()` (the last two added by T01) +- `src/resources/extensions/gsd/markdown-renderer.ts` — `renderRoadmapFromDb()`, `renderAssessmentFromDb()` (the latter added by T01) +- `src/resources/extensions/gsd/tools/replan-slice.ts` — reference handler pattern from T01 +- `src/resources/extensions/gsd/tests/replan-handler.test.ts` — reference test pattern from T01 +- `src/resources/extensions/gsd/state.ts` — `invalidateStateCache()` +- `src/resources/extensions/gsd/files.ts` — `clearParseCache()` + +## Expected Output + +- `src/resources/extensions/gsd/tools/reassess-roadmap.ts` — new handler file +- `src/resources/extensions/gsd/tests/reassess-handler.test.ts` — new test file diff --git a/.gsd/milestones/M001/slices/S03/tasks/T03-PLAN.md b/.gsd/milestones/M001/slices/S03/tasks/T03-PLAN.md new file mode 100644 index 000000000..1029473a8 --- /dev/null +++ b/.gsd/milestones/M001/slices/S03/tasks/T03-PLAN.md @@ -0,0 +1,78 @@ +--- +estimated_steps: 5 +estimated_files: 4 +skills_used: [] +--- + +# T03: Register tools in db-tools.ts + update prompts + prompt contract tests + +**Slice:** S03 — replan_slice + reassess_roadmap with structural enforcement +**Milestone:** M001 + +## Description + +Wire the two new handlers into the tool system by registering them in `db-tools.ts`, update the prompt templates to name the specific tools as canonical write paths, and extend prompt contract tests to catch regressions. This is the integration closure task that makes the handlers callable by auto-mode dispatch. + +## Steps + +1. **Register `gsd_replan_slice` in `db-tools.ts`:** + - Add after the `gsd_plan_task` registration block (around line 531). + - Follow the exact pattern of `gsd_plan_slice`: `ensureDbOpen()` guard, dynamic `import("../tools/replan-slice.js")`, call `handleReplanSlice(params, process.cwd())`, check for `error` in result, return structured `content`/`details`. + - TypeBox schema mirrors `ReplanSliceParams`: milestoneId, sliceId, blockerTaskId, blockerDescription, whatChanged as `Type.String()`, updatedTasks as `Type.Array(Type.Object({...}))`, removedTaskIds as `Type.Array(Type.String())`. + - Name: `gsd_replan_slice`, label: `"Replan Slice"`, description mentioning structural enforcement of completed tasks. + - promptGuidelines: mention canonical name and alias. + - Register alias: `gsd_slice_replan` → `gsd_replan_slice`. + +2. **Register `gsd_reassess_roadmap` in `db-tools.ts`:** + - Same pattern. Dynamic `import("../tools/reassess-roadmap.js")`, call `handleReassessRoadmap(params, process.cwd())`. + - TypeBox schema mirrors `ReassessRoadmapParams`: milestoneId, completedSliceId, verdict, assessment as `Type.String()`, sliceChanges as `Type.Object({ modified: Type.Array(...), added: Type.Array(...), removed: Type.Array(Type.String()) })`. + - Name: `gsd_reassess_roadmap`, label: `"Reassess Roadmap"`. + - Register alias: `gsd_roadmap_reassess` → `gsd_reassess_roadmap`. + +3. **Update `replan-slice.md` prompt:** + - Add a new step before the existing file-write instructions (before step 3). The new step should say: "If a DB-backed planning tool is available, use `gsd_replan_slice` with the following parameters: milestoneId, sliceId, blockerTaskId, blockerDescription, whatChanged, updatedTasks, removedTaskIds. This is the canonical write path — it structurally enforces preservation of completed tasks and writes replan history to the DB." + - Reposition the existing file-write steps (writing `{{replanPath}}` and `{{planPath}}`) as the degraded fallback: "If the `gsd_replan_slice` tool is not available, fall back to writing files directly..." + - Keep all existing hard constraints about completed tasks intact — they remain as documentation even though the tool enforces them structurally. + +4. **Update `reassess-roadmap.md` prompt:** + - Add a new instruction before the "If changes are needed" section: "Use `gsd_reassess_roadmap` to persist the assessment and any roadmap changes. Pass: milestoneId, completedSliceId, verdict, assessment text, and sliceChanges with modified/added/removed arrays." + - The prompt already has "Do not bypass state with manual roadmap-only edits" — augment it with: "when `gsd_reassess_roadmap` is available". + - Keep the existing file-write instructions as degraded fallback. + +5. **Extend `prompt-contracts.test.ts`:** + - Add test: `replan-slice prompt names gsd_replan_slice as canonical tool` — assert `replan-slice.md` contains `gsd_replan_slice`. + - Add test: `reassess-roadmap prompt names gsd_reassess_roadmap as canonical tool` — assert `reassess-roadmap.md` contains `gsd_reassess_roadmap`. + - Update the existing test at line 170 (`"replan-slice prompt requires DB-backed planning state when available"`) if the new prompt content makes the old assertion redundant — the existing test checks for generic "DB-backed planning tool" language, the new test checks for the specific tool name. + +## Must-Haves + +- [ ] `gsd_replan_slice` registered in db-tools.ts with TypeBox schema and alias `gsd_slice_replan` +- [ ] `gsd_reassess_roadmap` registered in db-tools.ts with TypeBox schema and alias `gsd_roadmap_reassess` +- [ ] `replan-slice.md` contains `gsd_replan_slice` as canonical tool name +- [ ] `reassess-roadmap.md` contains `gsd_reassess_roadmap` as canonical tool name +- [ ] Prompt contract tests pass asserting tool name presence in both prompts +- [ ] Existing prompt contract tests still pass (no regressions) + +## Verification + +- `node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/prompt-contracts.test.ts` — all tests pass including new assertions +- `grep -q 'gsd_replan_slice' src/resources/extensions/gsd/prompts/replan-slice.md` — exits 0 +- `grep -q 'gsd_reassess_roadmap' src/resources/extensions/gsd/prompts/reassess-roadmap.md` — exits 0 +- `grep -q 'gsd_replan_slice' src/resources/extensions/gsd/bootstrap/db-tools.ts` — exits 0 +- `grep -q 'gsd_reassess_roadmap' src/resources/extensions/gsd/bootstrap/db-tools.ts` — exits 0 + +## Inputs + +- `src/resources/extensions/gsd/tools/replan-slice.ts` — handler created in T01 +- `src/resources/extensions/gsd/tools/reassess-roadmap.ts` — handler created in T02 +- `src/resources/extensions/gsd/bootstrap/db-tools.ts` — existing registration patterns for plan_slice, plan_task +- `src/resources/extensions/gsd/prompts/replan-slice.md` — existing prompt template +- `src/resources/extensions/gsd/prompts/reassess-roadmap.md` — existing prompt template +- `src/resources/extensions/gsd/tests/prompt-contracts.test.ts` — existing prompt contract tests + +## Expected Output + +- `src/resources/extensions/gsd/bootstrap/db-tools.ts` — modified with two new tool registrations +- `src/resources/extensions/gsd/prompts/replan-slice.md` — modified to name `gsd_replan_slice` +- `src/resources/extensions/gsd/prompts/reassess-roadmap.md` — modified to name `gsd_reassess_roadmap` +- `src/resources/extensions/gsd/tests/prompt-contracts.test.ts` — modified with new tool name assertions