fix: align run-uat artifact path to ASSESSMENT, preventing false stuck retries (#3053)

The run-uat prompt instructs the agent to save results via gsd_summary_save
with artifact_type: "ASSESSMENT", which writes S##-ASSESSMENT.md. But
resolveExpectedArtifactPath and diagnoseExpectedArtifact expected S##-UAT.md,
causing artifact verification to fail and auto-mode to retry indefinitely.

Align all three contract points (prompt uatResultPath, artifact resolution,
and diagnostic message) to use ASSESSMENT as the canonical artifact type.

Closes #2873

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher 2026-03-30 16:45:57 -04:00 committed by GitHub
parent fb0fb5582e
commit dfb18c6e62
4 changed files with 49 additions and 5 deletions

View file

@ -56,7 +56,7 @@ export function resolveExpectedArtifactPath(
}
case "run-uat": {
const dir = resolveSlicePath(base, mid, sid!);
return dir ? join(dir, buildSliceFileName(sid!, "UAT")) : null;
return dir ? join(dir, buildSliceFileName(sid!, "ASSESSMENT")) : null;
}
case "execute-task": {
const dir = resolveSlicePath(base, mid, sid!);
@ -124,7 +124,7 @@ export function diagnoseExpectedArtifact(
case "reassess-roadmap":
return `${relSliceFile(base, mid, sid!, "ASSESSMENT")} (roadmap reassessment)`;
case "run-uat":
return `${relSliceFile(base, mid, sid!, "UAT")} (UAT result)`;
return `${relSliceFile(base, mid, sid!, "ASSESSMENT")} (UAT assessment result)`;
case "validate-milestone":
return `${relMilestoneFile(base, mid, "VALIDATION")} (milestone validation report)`;
case "complete-milestone":

View file

@ -1568,7 +1568,7 @@ export async function buildRunUatPrompt(
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT"));
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "ASSESSMENT"));
const uatType = getUatType(uatContent);
return loadPrompt("run-uat", {

View file

@ -111,7 +111,51 @@ test("resolveExpectedArtifactPath returns correct path for all slice-level types
const uatResult = resolveExpectedArtifactPath("run-uat", "M001/S01", base);
assert.ok(uatResult);
assert.ok(uatResult!.includes("UAT"));
assert.ok(uatResult!.includes("ASSESSMENT"));
});
// ─── run-uat artifact path contract (#2873) ──────────────────────────────
test("resolveExpectedArtifactPath for run-uat returns ASSESSMENT path, not UAT (#2873)", (t) => {
// The run-uat prompt instructs the agent to call gsd_summary_save with
// artifact_type: "ASSESSMENT", which writes S##-ASSESSMENT.md. The artifact
// verification path must match — otherwise verification fails and auto-mode
// retries the unit in an infinite loop.
const base = makeTmpBase();
t.after(() => cleanup(base));
const result = resolveExpectedArtifactPath("run-uat", "M001/S01", base);
assert.ok(result, "run-uat should resolve to a non-null artifact path");
assert.ok(
result!.endsWith("S01-ASSESSMENT.md"),
`run-uat artifact path should end with S01-ASSESSMENT.md, got: ${result}`,
);
});
test("diagnoseExpectedArtifact for run-uat references ASSESSMENT (#2873)", (t) => {
const base = makeTmpBase();
t.after(() => cleanup(base));
const diag = diagnoseExpectedArtifact("run-uat", "M001/S01", base);
assert.ok(diag, "run-uat should have a diagnostic message");
assert.ok(
diag!.includes("ASSESSMENT"),
`run-uat diagnostic should reference ASSESSMENT, got: ${diag}`,
);
});
test("verifyExpectedArtifact passes for run-uat when ASSESSMENT file exists (#2873)", (t) => {
// Regression test: run-uat writes S##-ASSESSMENT.md via gsd_summary_save,
// but verification looked for S##-UAT.md, causing false stuck retries.
const base = makeTmpBase();
t.after(() => cleanup(base));
// Write the ASSESSMENT file (what gsd_summary_save actually produces)
const assessPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-ASSESSMENT.md");
writeFileSync(assessPath, "---\nverdict: PASS\n---\n# UAT Assessment\n");
const verified = verifyExpectedArtifact("run-uat", "M001/S01", base);
assert.ok(verified, "verifyExpectedArtifact should pass when ASSESSMENT file exists");
});
// ─── diagnoseExpectedArtifact ─────────────────────────────────────────────

View file

@ -171,7 +171,7 @@ test('(k) run-uat prompt template', () => {
const milestoneId = 'M001';
const sliceId = 'S01';
const uatPath = '.gsd/milestones/M001/slices/S01/S01-UAT.md';
const uatResultPath = '.gsd/milestones/M001/slices/S01/S01-UAT.md';
const uatResultPath = '.gsd/milestones/M001/slices/S01/S01-ASSESSMENT.md';
const uatType = 'live-runtime';
const inlinedContext = '<!-- no context -->';
let promptResult: string | undefined;