fix(gsd): persist verification classes in milestone validation (#2820)

This commit is contained in:
mastertyko 2026-03-28 01:09:39 +01:00 committed by GitHub
parent 6dd02bb3ce
commit c88a9d2d4f
5 changed files with 39 additions and 4 deletions

View file

@ -924,7 +924,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
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).",
"Parameters: milestoneId, verdict, remediationRound, successCriteriaChecklist, sliceDeliveryAudit, crossSliceIntegration, requirementCoverage, verificationClasses (optional), 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.",
],
@ -936,6 +936,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
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" }),
verificationClasses: Type.Optional(Type.String({ description: "Markdown describing verification class compliance and gaps" })),
verdictRationale: Type.String({ description: "Why this verdict was chosen" }),
remediationPlan: Type.Optional(Type.String({ description: "Remediation plan (required if verdict is needs-remediation)" })),
}),

View file

@ -26,7 +26,7 @@ All relevant context has been preloaded below — the roadmap, all slice summari
4. Check **requirement coverage** — are all active requirements addressed by at least one slice?
5. If **Verification Classes** are provided in the inlined context above, check each non-empty class:
- For each verification class (Contract, Integration, Operational, UAT), determine whether slice summaries, UAT results, or observable behavior provide evidence that this verification tier was addressed.
- Document the compliance status of each class in your verdict rationale.
- Document the compliance status of each class in a dedicated verification classes section.
- If `Operational` verification is non-empty and no evidence of operational verification exists, flag this explicitly — it means planned operational checks (migrations, deployments, runtime verification) were not proven.
- A milestone with unaddressed verification classes may still pass if the gaps are minor, but the gaps MUST be documented in the Deferred Work Inventory.
6. Determine a verdict:
@ -36,7 +36,7 @@ All relevant context has been preloaded below — the roadmap, all slice summari
## Persist Validation
**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.
**Persist validation results through `gsd_validate_milestone`.** Call it with: `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verificationClasses` (when non-empty), `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`:
- 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.

View file

@ -181,6 +181,13 @@ test("reassess-roadmap prompt references gsd_reassess_roadmap tool", () => {
assert.match(prompt, /gsd_reassess_roadmap/);
});
test("validate-milestone prompt persists verification classes through gsd_validate_milestone", () => {
const prompt = readPrompt("validate-milestone");
assert.match(prompt, /verification classes section/i);
assert.match(prompt, /verificationClasses/);
assert.match(prompt, /gsd_validate_milestone/);
});
// ─── Prompt migration: replan-slice → gsd_replan_slice ────────────────
test("replan-slice prompt names gsd_replan_slice as the tool to use", () => {

View file

@ -1,6 +1,6 @@
import { describe, it, afterEach } from "node:test";
import assert from "node:assert/strict";
import { mkdirSync, existsSync, rmSync, writeFileSync } from "node:fs";
import { mkdirSync, existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { randomUUID } from "node:crypto";
@ -24,6 +24,7 @@ const VALID_PARAMS = {
sliceDeliveryAudit: "| S01 | delivered |",
crossSliceIntegration: "No issues",
requirementCoverage: "All covered",
verificationClasses: "- Contract: covered\n- Integration: covered\n- Operational: gap noted",
verdictRationale: "Everything checks out",
};
@ -59,6 +60,27 @@ describe("handleValidateMilestone write ordering (#2725)", () => {
// Disk file exists
const filePath = join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md");
assert.ok(existsSync(filePath), "VALIDATION.md should exist on disk");
const validationMd = readFileSync(filePath, "utf-8");
assert.match(validationMd, /## Verification Class Compliance/);
assert.match(validationMd, /- Contract: covered/);
assert.match(validationMd, /## Verdict Rationale/);
});
it("omits verification class section when no verification classes are supplied", async () => {
base = makeTmpBase();
const dbPath = join(base, ".gsd", "gsd.db");
openDatabase(dbPath);
insertMilestone({ id: "M001" });
const result = await handleValidateMilestone(
{ ...VALID_PARAMS, verificationClasses: undefined },
base,
);
assert.ok(!("error" in result), `unexpected error: ${"error" in result ? result.error : ""}`);
const filePath = join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md");
const validationMd = readFileSync(filePath, "utf-8");
assert.doesNotMatch(validationMd, /## Verification Class Compliance/);
});
it("rolls back DB row when disk write fails", async () => {

View file

@ -25,6 +25,7 @@ export interface ValidateMilestoneParams {
sliceDeliveryAudit: string;
crossSliceIntegration: string;
requirementCoverage: string;
verificationClasses?: string;
verdictRationale: string;
remediationPlan?: string;
}
@ -55,6 +56,10 @@ ${params.crossSliceIntegration}
## Requirement Coverage
${params.requirementCoverage}
${params.verificationClasses ? `## Verification Class Compliance
${params.verificationClasses}
` : ""}
## Verdict Rationale
${params.verdictRationale}
`;