fix(gsd): migrate completion/validation prompts to DB-backed tools (#2449)
* fix(gsd): migrate completion/validation prompts to DB-backed tools and fix pipeline inconsistencies (#2444) - Create gsd_validate_milestone tool (handler + DB registration) using assessments table - Update complete-milestone.md to use gsd_complete_milestone instead of manual file writes - Update validate-milestone.md to use gsd_validate_milestone + gsd_reassess_roadmap for remediation - Add buildSkillActivationBlock() to 4 missing prompt builders (complete-milestone, validate-milestone, run-uat, reassess-roadmap) - Remove dead completedSliceSummaryPath variable from reassess-roadmap builder - Remove dead "degraded fallback" sections from replan-slice.md and reassess-roadmap.md - Fix plan-slice.md double-tool instruction (gsd_plan_slice already persists tasks) - Fix inconsistent commit/write instructions in complete-milestone.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update tests for new tool registration and prompt changes - Add gsd_validate_milestone to tool-naming RENAME_MAP (24→26 tools) - Update prompt-contracts assertions for removed fallback text and singular DB tool phrasing - Restore {{roadmapPath}}, {{assessmentPath}}, {{planPath}}, {{replanPath}} template vars in prompts for context Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: restore {{milestoneSummaryPath}} template var in complete-milestone prompt Test expects the milestone summary path reference in the prompt content. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9a6a341b57
commit
b9ff5d5052
10 changed files with 251 additions and 71 deletions
|
|
@ -1307,6 +1307,12 @@ export async function buildCompleteMilestonePrompt(
|
|||
roadmapPath: roadmapRel,
|
||||
inlinedContext,
|
||||
milestoneSummaryPath,
|
||||
skillActivation: buildSkillActivationBlock({
|
||||
base,
|
||||
milestoneId: mid,
|
||||
milestoneTitle: midTitle,
|
||||
extraContext: [inlinedContext],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1390,6 +1396,12 @@ export async function buildValidateMilestonePrompt(
|
|||
inlinedContext,
|
||||
validationPath: validationOutputPath,
|
||||
remediationRound: String(remediationRound),
|
||||
skillActivation: buildSkillActivationBlock({
|
||||
base,
|
||||
milestoneId: mid,
|
||||
milestoneTitle: midTitle,
|
||||
extraContext: [inlinedContext],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1500,6 +1512,12 @@ export async function buildRunUatPrompt(
|
|||
uatResultPath,
|
||||
uatType,
|
||||
inlinedContext,
|
||||
skillActivation: buildSkillActivationBlock({
|
||||
base,
|
||||
milestoneId: mid,
|
||||
sliceId,
|
||||
extraContext: [inlinedContext],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1552,11 +1570,16 @@ export async function buildReassessRoadmapPrompt(
|
|||
milestoneTitle: midTitle,
|
||||
completedSliceId,
|
||||
roadmapPath: roadmapRel,
|
||||
completedSliceSummaryPath: summaryRel,
|
||||
assessmentPath,
|
||||
inlinedContext,
|
||||
deferredCaptures,
|
||||
commitInstruction: reassessCommitInstruction,
|
||||
skillActivation: buildSkillActivationBlock({
|
||||
base,
|
||||
milestoneId: mid,
|
||||
milestoneTitle: midTitle,
|
||||
extraContext: [inlinedContext, deferredCaptures],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -813,6 +813,74 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|||
pi.registerTool(milestoneCompleteTool);
|
||||
registerAlias(pi, milestoneCompleteTool, "gsd_milestone_complete", "gsd_complete_milestone");
|
||||
|
||||
// ─── gsd_validate_milestone (gsd_milestone_validate alias) ─────────────
|
||||
|
||||
const milestoneValidateExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
|
||||
const dbAvailable = await ensureDbOpen();
|
||||
if (!dbAvailable) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot validate milestone." }],
|
||||
details: { operation: "validate_milestone", error: "db_unavailable" } as any,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const { handleValidateMilestone } = await import("../tools/validate-milestone.js");
|
||||
const result = await handleValidateMilestone(params, process.cwd());
|
||||
if ("error" in result) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Error validating milestone: ${result.error}` }],
|
||||
details: { operation: "validate_milestone", error: result.error } as any,
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Validated milestone ${result.milestoneId} — verdict: ${result.verdict}. Written to ${result.validationPath}` }],
|
||||
details: {
|
||||
operation: "validate_milestone",
|
||||
milestoneId: result.milestoneId,
|
||||
verdict: result.verdict,
|
||||
validationPath: result.validationPath,
|
||||
} as any,
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
process.stderr.write(`gsd-db: validate_milestone tool failed: ${msg}\n`);
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Error validating milestone: ${msg}` }],
|
||||
details: { operation: "validate_milestone", error: msg } as any,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const milestoneValidateTool = {
|
||||
name: "gsd_validate_milestone",
|
||||
label: "Validate Milestone",
|
||||
description:
|
||||
"Validate a milestone before completion — persist validation results to the DB, render VALIDATION.md to disk. " +
|
||||
"Records verdict (pass/needs-attention/needs-remediation) and rationale.",
|
||||
promptSnippet: "Validate a GSD milestone (DB write + VALIDATION.md render)",
|
||||
promptGuidelines: [
|
||||
"Use gsd_validate_milestone when all slices are done and the milestone needs validation before completion.",
|
||||
"Parameters: milestoneId, verdict, remediationRound, successCriteriaChecklist, sliceDeliveryAudit, crossSliceIntegration, requirementCoverage, verdictRationale, remediationPlan (optional).",
|
||||
"If verdict is 'needs-remediation', also provide remediationPlan and use gsd_reassess_roadmap to add remediation slices to the roadmap.",
|
||||
"On success, returns validationPath where VALIDATION.md was written.",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
||||
verdict: StringEnum(["pass", "needs-attention", "needs-remediation"], { description: "Validation verdict" }),
|
||||
remediationRound: Type.Number({ description: "Remediation round (0 for first validation)" }),
|
||||
successCriteriaChecklist: Type.String({ description: "Markdown checklist of success criteria with pass/fail and evidence" }),
|
||||
sliceDeliveryAudit: Type.String({ description: "Markdown table auditing each slice's claimed vs delivered output" }),
|
||||
crossSliceIntegration: Type.String({ description: "Markdown describing any cross-slice boundary mismatches" }),
|
||||
requirementCoverage: Type.String({ description: "Markdown describing any unaddressed requirements" }),
|
||||
verdictRationale: Type.String({ description: "Why this verdict was chosen" }),
|
||||
remediationPlan: Type.Optional(Type.String({ description: "Remediation plan (required if verdict is needs-remediation)" })),
|
||||
}),
|
||||
execute: milestoneValidateExecute,
|
||||
};
|
||||
|
||||
pi.registerTool(milestoneValidateTool);
|
||||
registerAlias(pi, milestoneValidateTool, "gsd_milestone_validate", "gsd_validate_milestone");
|
||||
|
||||
// ─── gsd_replan_slice (gsd_slice_replan alias) ─────────────────────────
|
||||
|
||||
const replanSliceExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ Then:
|
|||
4. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior. List any criterion that was NOT met.
|
||||
5. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly.
|
||||
6. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
|
||||
7. Write `{{milestoneSummaryPath}}` using the milestone-summary template. Fill all frontmatter fields and narrative sections. The `requirement_outcomes` field must list every requirement that changed status with `from_status`, `to_status`, and `proof`.
|
||||
8. Update `.gsd/REQUIREMENTS.md` if any requirement status transitions were validated in step 5.
|
||||
7. **Persist completion through `gsd_complete_milestone`.** Call it with: `milestoneId`, `title`, `oneLiner`, `narrative`, `successCriteriaResults`, `definitionOfDoneResults`, `requirementOutcomes`, `keyDecisions`, `keyFiles`, `lessonsLearned`, `followUps`, `deviations`. The tool updates the milestone status in the DB, renders `{{milestoneSummaryPath}}`, and validates all slices are complete before proceeding.
|
||||
8. Update `.gsd/REQUIREMENTS.md` if any requirement status transitions were validated in step 6.
|
||||
9. Update `.gsd/PROJECT.md` to reflect milestone completion and current project state.
|
||||
10. Review all slice summaries for cross-cutting lessons, patterns, or gotchas that emerged during this milestone. Append any non-obvious, reusable insights to `.gsd/KNOWLEDGE.md`.
|
||||
11. Do not commit manually — the system auto-commits your changes after this unit completes.
|
||||
|
|
@ -31,6 +31,4 @@ Then:
|
|||
|
||||
**File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
|
||||
|
||||
**You MUST write `{{milestoneSummaryPath}}` AND update PROJECT.md before finishing.**
|
||||
|
||||
When done, say: "Milestone {{milestoneId}} complete."
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ Then:
|
|||
- a matching task plan file with description, steps, must-haves, verification, inputs, and expected output
|
||||
- **Inputs and Expected Output must list concrete backtick-wrapped file paths** (e.g. `` `src/types.ts` ``). These are machine-parsed to derive task dependencies — vague prose without paths breaks parallel execution. Every task must have at least one output file path.
|
||||
- Observability Impact section **only if the task touches runtime boundaries, async flows, or error paths** — omit it otherwise
|
||||
6. **Persist planning state through DB-backed tools.** Call `gsd_plan_slice` with the full slice planning payload (goal, demo, must-haves, verification, tasks, and metadata). Then call `gsd_plan_task` for each task to persist its planning fields. These tools write to the DB and render `{{outputPath}}` and `{{slicePath}}/tasks/T##-PLAN.md` files automatically. Do **not** rely on direct `PLAN.md` writes as the source of truth; the DB-backed tools are the canonical write path for slice and task planning state.
|
||||
6. **Persist planning state through `gsd_plan_slice`.** Call it with the full slice planning payload (goal, demo, must-haves, verification, tasks, and metadata). The tool inserts all tasks in the same transaction, writes to the DB, and renders `{{outputPath}}` and `{{slicePath}}/tasks/T##-PLAN.md` files automatically. Do **not** call `gsd_plan_task` separately — `gsd_plan_slice` handles task persistence. Do **not** rely on direct `PLAN.md` writes as the source of truth; the DB-backed tool is the canonical write path for slice and task planning state.
|
||||
7. **Self-audit the plan.** Walk through each check — if any fail, fix the plan files before moving on:
|
||||
- **Completion semantics:** If every task were completed exactly as written, the slice goal/demo should actually be true.
|
||||
- **Requirement coverage:** Every must-have in the slice maps to at least one task. No must-have is orphaned. If `REQUIREMENTS.md` exists, every Active requirement this slice owns maps to at least one task.
|
||||
|
|
|
|||
|
|
@ -50,14 +50,14 @@ If all criteria have at least one remaining owning slice, the coverage check pas
|
|||
|
||||
**If the roadmap is still good:**
|
||||
|
||||
Write `{{assessmentPath}}` with a brief confirmation that roadmap coverage still holds after {{completedSliceId}}. If requirements exist, explicitly note whether requirement coverage remains sound. If `gsd_reassess_roadmap` is available, use it with `verdict: "roadmap-confirmed"`, an empty `sliceChanges` object, and the assessment text — the tool writes the assessment to the DB and renders ASSESSMENT.md.
|
||||
Use `gsd_reassess_roadmap` with `verdict: "roadmap-confirmed"`, an empty `sliceChanges` object, and the assessment text — the tool writes the assessment to the DB and renders `{{assessmentPath}}`. If requirements exist, explicitly note whether requirement coverage remains sound.
|
||||
|
||||
**If changes are needed:**
|
||||
|
||||
1. **Persist changes through `gsd_reassess_roadmap`.** Pass: `milestoneId`, `completedSliceId`, `verdict` (e.g. "roadmap-adjusted"), `assessment` (text explaining the decision), and `sliceChanges` with `modified` (array of sliceId, title, risk, depends, demo), `added` (same shape), `removed` (array of slice ID strings). The tool structurally enforces preservation of completed slices, writes the assessment to the DB, re-renders ROADMAP.md, and renders ASSESSMENT.md. Skip step 2 when this tool succeeds.
|
||||
2. **Degraded fallback — direct file writes:** If `gsd_reassess_roadmap` is not available, rewrite the remaining (unchecked) slices in `{{roadmapPath}}` directly. Keep completed slices exactly as they are (`[x]`). Update the boundary map for changed slices. Update the proof strategy if risks changed. Update requirement coverage if ownership or scope changed.
|
||||
3. Write `{{assessmentPath}}` explaining what changed and why — keep it brief and concrete.
|
||||
4. If `.gsd/REQUIREMENTS.md` exists and requirement ownership or status changed, update it.
|
||||
5. {{commitInstruction}}
|
||||
**Persist changes through `gsd_reassess_roadmap`.** Pass: `milestoneId`, `completedSliceId`, `verdict` (e.g. "roadmap-adjusted"), `assessment` (text explaining the decision), and `sliceChanges` with `modified` (array of sliceId, title, risk, depends, demo), `added` (same shape), `removed` (array of slice ID strings). The tool structurally enforces preservation of completed slices, writes the assessment to the DB, re-renders `{{roadmapPath}}`, and renders `{{assessmentPath}}`.
|
||||
|
||||
If `.gsd/REQUIREMENTS.md` exists and requirement ownership or status changed, update it.
|
||||
|
||||
{{commitInstruction}}
|
||||
|
||||
When done, say: "Roadmap reassessed."
|
||||
|
|
|
|||
|
|
@ -32,19 +32,8 @@ Consider these captures when rewriting the remaining tasks — they represent th
|
|||
|
||||
1. Read the blocker task summary carefully. Understand exactly what was discovered and why it blocks the current plan.
|
||||
2. Analyze the remaining `[ ]` tasks in the slice plan. Determine which are still valid, which need modification, and which should be replaced.
|
||||
3. **Persist replan state through `gsd_replan_slice`.** Call it with the following parameters: `milestoneId`, `sliceId`, `blockerTaskId`, `blockerDescription`, `whatChanged`, `updatedTasks` (array of task objects with taskId, title, description, estimate, files, verify, inputs, expectedOutput), `removedTaskIds` (array of task ID strings). The tool structurally enforces preservation of completed tasks, writes replan history to the DB, re-renders PLAN.md, and renders REPLAN.md. Skip steps 4–5 when this tool succeeds.
|
||||
4. **Degraded fallback — direct file writes:** If `gsd_replan_slice` is not available, fall back to writing files directly. Write `{{replanPath}}` documenting:
|
||||
- What blocker was discovered and in which task
|
||||
- What changed in the plan and why
|
||||
- Which incomplete tasks were modified, added, or removed
|
||||
- Any new risks or considerations introduced by the replan
|
||||
5. If using the degraded fallback, rewrite `{{planPath}}` with the updated slice plan:
|
||||
- Keep all `[x]` tasks exactly as they were (same IDs, same descriptions, same checkmarks)
|
||||
- Update the `[ ]` tasks to address the blocker
|
||||
- Ensure the slice Goal and Demo sections are still achievable with the new tasks, or update them if the blocker fundamentally changes what the slice can deliver
|
||||
- Update the Files Likely Touched section if the replan changes which files are affected
|
||||
- If a DB-backed planning tool exists for this phase, use it as the source of truth and make any rewritten `PLAN.md` reflect that persisted state rather than bypassing it
|
||||
6. If any incomplete task had a `T0x-PLAN.md`, remove or rewrite it to match the new task description.
|
||||
7. Do not commit manually — the system auto-commits your changes after this unit completes.
|
||||
3. **Persist replan state through `gsd_replan_slice`.** Call it with: `milestoneId`, `sliceId`, `blockerTaskId`, `blockerDescription`, `whatChanged`, `updatedTasks` (array of task objects with taskId, title, description, estimate, files, verify, inputs, expectedOutput), `removedTaskIds` (array of task ID strings). The tool structurally enforces preservation of completed tasks, writes replan history to the DB, re-renders `{{planPath}}`, and renders `{{replanPath}}`.
|
||||
4. If any incomplete task had a `T0x-PLAN.md`, remove or rewrite it to match the new task description.
|
||||
5. Do not commit manually — the system auto-commits your changes after this unit completes.
|
||||
|
||||
When done, say: "Slice {{sliceId}} replanned."
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ All relevant context has been preloaded below — the roadmap, all slice summari
|
|||
|
||||
{{inlinedContext}}
|
||||
|
||||
{{skillActivation}}
|
||||
|
||||
## Validation Steps
|
||||
|
||||
1. For each **success criterion** in `{{roadmapPath}}`, check whether slice summaries and UAT results provide evidence that it was met. Record pass/fail per criterion.
|
||||
|
|
@ -25,47 +27,15 @@ All relevant context has been preloaded below — the roadmap, all slice summari
|
|||
5. Determine a verdict:
|
||||
- `pass` — all criteria met, all slices delivered, no gaps
|
||||
- `needs-attention` — minor gaps that do not block completion (document them)
|
||||
- `needs-remediation` — material gaps found; add remediation slices to the roadmap
|
||||
- `needs-remediation` — material gaps found; remediation slices must be added to the roadmap
|
||||
|
||||
## Output
|
||||
## Persist Validation
|
||||
|
||||
Write `{{validationPath}}` with this structure:
|
||||
|
||||
```markdown
|
||||
---
|
||||
verdict: <pass|needs-attention|needs-remediation>
|
||||
remediation_round: {{remediationRound}}
|
||||
---
|
||||
|
||||
# Milestone Validation: {{milestoneId}}
|
||||
|
||||
## Success Criteria Checklist
|
||||
- [x] Criterion 1 — evidence: ...
|
||||
- [ ] Criterion 2 — gap: ...
|
||||
|
||||
## Slice Delivery Audit
|
||||
| Slice | Claimed | Delivered | Status |
|
||||
|-------|---------|-----------|--------|
|
||||
| S01 | ... | ... | pass |
|
||||
|
||||
## Cross-Slice Integration
|
||||
(any boundary mismatches)
|
||||
|
||||
## Requirement Coverage
|
||||
(any unaddressed requirements)
|
||||
|
||||
## Verdict Rationale
|
||||
(why this verdict was chosen)
|
||||
|
||||
## Remediation Plan
|
||||
(only if verdict is needs-remediation — list new slices to add to the roadmap)
|
||||
```
|
||||
**Persist validation results through `gsd_validate_milestone`.** Call it with: `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verdictRationale`, and `remediationPlan` (if verdict is `needs-remediation`). The tool writes the validation to the DB and renders VALIDATION.md to disk.
|
||||
|
||||
If verdict is `needs-remediation`:
|
||||
- Add new slices to `{{roadmapPath}}` with unchecked `[ ]` status
|
||||
- These slices will be planned and executed before validation re-runs
|
||||
|
||||
**You MUST write `{{validationPath}}` before finishing.**
|
||||
- After calling `gsd_validate_milestone`, use `gsd_reassess_roadmap` to add remediation slices. Pass `milestoneId`, a synthetic `completedSliceId` (e.g. "VALIDATION"), `verdict: "roadmap-adjusted"`, `assessment` text, and `sliceChanges` with the new slices in the `added` array. The tool persists the changes to the DB and re-renders ROADMAP.md.
|
||||
- These remediation slices will be planned and executed before validation re-runs.
|
||||
|
||||
**File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
|
||||
|
||||
|
|
|
|||
|
|
@ -147,12 +147,12 @@ test("plan-slice prompt no longer frames direct PLAN writes as the source of tru
|
|||
assert.match(prompt, /Do \*\*not\*\* rely on direct `PLAN\.md` writes as the source of truth/i);
|
||||
});
|
||||
|
||||
test("plan-slice prompt explicitly names gsd_plan_slice and gsd_plan_task as DB-backed planning tools", () => {
|
||||
test("plan-slice prompt explicitly names gsd_plan_slice as DB-backed planning tool", () => {
|
||||
const prompt = readPrompt("plan-slice");
|
||||
assert.match(prompt, /gsd_plan_slice/);
|
||||
assert.match(prompt, /gsd_plan_task/);
|
||||
// The prompt should describe these as the canonical write path
|
||||
assert.match(prompt, /DB-backed tools are the canonical write path/i);
|
||||
// The prompt should describe the DB-backed tool as the canonical write path
|
||||
assert.match(prompt, /DB-backed tool is the canonical write path/i);
|
||||
});
|
||||
|
||||
test("plan-slice prompt does not instruct direct file writes as a primary step", () => {
|
||||
|
|
@ -161,14 +161,18 @@ test("plan-slice prompt does not instruct direct file writes as a primary step",
|
|||
assert.doesNotMatch(prompt, /^\d+\.\s+Write `?\{\{outputPath\}\}`?\s*$/m);
|
||||
});
|
||||
|
||||
test("plan-slice prompt instructs calling gsd_plan_task for each task", () => {
|
||||
test("plan-slice prompt clarifies gsd_plan_slice handles task persistence", () => {
|
||||
const prompt = readPrompt("plan-slice");
|
||||
assert.match(prompt, /call `gsd_plan_task` for each task/i);
|
||||
// gsd_plan_slice persists tasks in its transaction — no separate gsd_plan_task calls needed
|
||||
assert.match(prompt, /gsd_plan_task/);
|
||||
assert.match(prompt, /gsd_plan_slice` handles task persistence/i);
|
||||
});
|
||||
|
||||
test("replan-slice prompt requires DB-backed planning state when available", () => {
|
||||
test("replan-slice prompt uses gsd_replan_slice as canonical DB-backed tool", () => {
|
||||
const prompt = readPrompt("replan-slice");
|
||||
assert.match(prompt, /DB-backed planning tool exists for this phase, use it as the source of truth/i);
|
||||
assert.match(prompt, /gsd_replan_slice/);
|
||||
// Degraded fallback (direct file writes) was removed — DB tools are always available
|
||||
assert.doesNotMatch(prompt, /Degraded fallback/i);
|
||||
});
|
||||
|
||||
test("reassess-roadmap prompt references gsd_reassess_roadmap tool", () => {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const RENAME_MAP: Array<{ canonical: string; alias: string }> = [
|
|||
{ canonical: "gsd_replan_slice", alias: "gsd_slice_replan" },
|
||||
{ canonical: "gsd_reassess_roadmap", alias: "gsd_roadmap_reassess" },
|
||||
{ canonical: "gsd_complete_milestone", alias: "gsd_milestone_complete" },
|
||||
{ canonical: "gsd_validate_milestone", alias: "gsd_milestone_validate" },
|
||||
];
|
||||
|
||||
// ─── Registration count ──────────────────────────────────────────────────────
|
||||
|
|
@ -43,7 +44,7 @@ console.log('\n── Tool naming: registration count ──');
|
|||
const pi = makeMockPi();
|
||||
registerDbTools(pi);
|
||||
|
||||
assert.deepStrictEqual(pi.tools.length, 24, 'Should register exactly 24 tools (12 canonical + 12 aliases)');
|
||||
assert.deepStrictEqual(pi.tools.length, 26, 'Should register exactly 26 tools (13 canonical + 13 aliases)');
|
||||
|
||||
// ─── Both names exist for each pair ──────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
127
src/resources/extensions/gsd/tools/validate-milestone.ts
Normal file
127
src/resources/extensions/gsd/tools/validate-milestone.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* validate-milestone handler — the core operation behind gsd_validate_milestone.
|
||||
*
|
||||
* Persists milestone validation results to the assessments table,
|
||||
* renders VALIDATION.md to disk, and invalidates caches.
|
||||
*/
|
||||
|
||||
import { join } from "node:path";
|
||||
|
||||
import {
|
||||
transaction,
|
||||
_getAdapter,
|
||||
} from "../gsd-db.js";
|
||||
import { resolveMilestonePath, clearPathCache } from "../paths.js";
|
||||
import { saveFile, clearParseCache } from "../files.js";
|
||||
import { invalidateStateCache } from "../state.js";
|
||||
|
||||
export interface ValidateMilestoneParams {
|
||||
milestoneId: string;
|
||||
verdict: "pass" | "needs-attention" | "needs-remediation";
|
||||
remediationRound: number;
|
||||
successCriteriaChecklist: string;
|
||||
sliceDeliveryAudit: string;
|
||||
crossSliceIntegration: string;
|
||||
requirementCoverage: string;
|
||||
verdictRationale: string;
|
||||
remediationPlan?: string;
|
||||
}
|
||||
|
||||
export interface ValidateMilestoneResult {
|
||||
milestoneId: string;
|
||||
verdict: string;
|
||||
validationPath: string;
|
||||
}
|
||||
|
||||
function renderValidationMarkdown(params: ValidateMilestoneParams): string {
|
||||
let md = `---
|
||||
verdict: ${params.verdict}
|
||||
remediation_round: ${params.remediationRound}
|
||||
---
|
||||
|
||||
# Milestone Validation: ${params.milestoneId}
|
||||
|
||||
## Success Criteria Checklist
|
||||
${params.successCriteriaChecklist}
|
||||
|
||||
## Slice Delivery Audit
|
||||
${params.sliceDeliveryAudit}
|
||||
|
||||
## Cross-Slice Integration
|
||||
${params.crossSliceIntegration}
|
||||
|
||||
## Requirement Coverage
|
||||
${params.requirementCoverage}
|
||||
|
||||
## Verdict Rationale
|
||||
${params.verdictRationale}
|
||||
`;
|
||||
|
||||
if (params.verdict === "needs-remediation" && params.remediationPlan) {
|
||||
md += `\n## Remediation Plan\n${params.remediationPlan}\n`;
|
||||
}
|
||||
|
||||
return md;
|
||||
}
|
||||
|
||||
export async function handleValidateMilestone(
|
||||
params: ValidateMilestoneParams,
|
||||
basePath: string,
|
||||
): Promise<ValidateMilestoneResult | { error: string }> {
|
||||
if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
|
||||
return { error: "milestoneId is required and must be a non-empty string" };
|
||||
}
|
||||
const validVerdicts = ["pass", "needs-attention", "needs-remediation"];
|
||||
if (!validVerdicts.includes(params.verdict)) {
|
||||
return { error: `verdict must be one of: ${validVerdicts.join(", ")}` };
|
||||
}
|
||||
|
||||
// ── Filesystem render ──────────────────────────────────────────────────
|
||||
const validationMd = renderValidationMarkdown(params);
|
||||
|
||||
let validationPath: string;
|
||||
const milestoneDir = resolveMilestonePath(basePath, params.milestoneId);
|
||||
if (milestoneDir) {
|
||||
validationPath = join(milestoneDir, `${params.milestoneId}-VALIDATION.md`);
|
||||
} else {
|
||||
const gsdDir = join(basePath, ".gsd");
|
||||
const manualDir = join(gsdDir, "milestones", params.milestoneId);
|
||||
validationPath = join(manualDir, `${params.milestoneId}-VALIDATION.md`);
|
||||
}
|
||||
|
||||
try {
|
||||
await saveFile(validationPath, validationMd);
|
||||
} catch (renderErr) {
|
||||
process.stderr.write(
|
||||
`gsd-db: validate_milestone — disk render failed: ${(renderErr as Error).message}\n`,
|
||||
);
|
||||
return { error: `disk render failed: ${(renderErr as Error).message}` };
|
||||
}
|
||||
|
||||
// ── DB write — store in assessments table ──────────────────────────────
|
||||
const validatedAt = new Date().toISOString();
|
||||
|
||||
transaction(() => {
|
||||
const adapter = _getAdapter()!;
|
||||
adapter.prepare(
|
||||
`INSERT OR REPLACE INTO assessments (path, milestone_id, slice_id, task_id, status, scope, full_content, created_at)
|
||||
VALUES (:path, :mid, NULL, NULL, :verdict, 'milestone-validation', :content, :created_at)`,
|
||||
).run({
|
||||
":path": validationPath,
|
||||
":mid": params.milestoneId,
|
||||
":verdict": params.verdict,
|
||||
":content": validationMd,
|
||||
":created_at": validatedAt,
|
||||
});
|
||||
});
|
||||
|
||||
invalidateStateCache();
|
||||
clearPathCache();
|
||||
clearParseCache();
|
||||
|
||||
return {
|
||||
milestoneId: params.milestoneId,
|
||||
verdict: params.verdict,
|
||||
validationPath,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue