fix: keep skipped tasks out of slice verification

This commit is contained in:
Mikael Hugo 2026-04-29 20:37:56 +02:00
parent d6fc1211b7
commit ab57548f2b
3 changed files with 116 additions and 2 deletions

View file

@ -63,7 +63,11 @@ import {
resolveSkillDiscoveryMode,
} from "./preferences.js";
import { inlineTemplate, loadPrompt } from "./prompt-loader.js";
import { getPendingGatesForTurn } from "./sf-db.js";
import {
getPendingGatesForTurn,
getSliceTasks,
isDbAvailable,
} from "./sf-db.js";
import { warnIfManifestHasMissingSkills } from "./skill-manifest.js";
import {
formatDecisionsCompact,
@ -2422,6 +2426,28 @@ export async function buildCompleteSlicePrompt(
): Promise<string> {
const inlineLevel = level ?? resolveInlineLevel();
const skippedTaskBlock = (() => {
try {
if (!isDbAvailable()) return null;
const skippedTasks = getSliceTasks(mid, sid).filter(
(t) => t.status === "skipped",
);
if (skippedTasks.length === 0) return null;
const rows = skippedTasks.map(
(t) =>
`- ${t.id}: ${t.title || "(untitled)"} — skipped by SF state; do not execute its task-level verification during slice closeout.`,
);
return [
"### Skipped Tasks",
"These tasks are closed as skipped. Treat their original verification commands as non-applicable for this closeout and record the gap in the slice summary/UAT instead of running them.",
"",
...rows,
].join("\n");
} catch {
return null;
}
})();
// #4782 phase 3: complete-slice migrated through composer. Manifest
// declares [roadmap, slice-context, slice-plan, requirements,
// prior-task-summaries, templates]. Overrides prepend and knowledge
@ -2468,6 +2494,7 @@ export async function buildCompleteSlicePrompt(
}),
);
const blocks = entries.filter((b): b is string => b !== null);
if (skippedTaskBlock) blocks.push(skippedTaskBlock);
return blocks.length > 0 ? blocks.join("\n\n---\n\n") : null;
}
case "templates": {

View file

@ -23,7 +23,7 @@ All relevant context has been preloaded below — the slice plan, all task summa
Then:
1. Use the **Slice Summary** and **UAT** output templates from the inlined context above
2. {{skillActivation}}
3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first. Task artifacts use a **flat file layout** directly inside `tasks/` (for example `T01-SUMMARY.md`, `T02-SUMMARY.md`) rather than per-task subdirectories. If you need to count or re-read task summaries during verification, use `find .sf/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` or `ls .sf/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks/*-SUMMARY.md`. Never use `tasks/*/SUMMARY.md` — that glob expects subdirectories that do not exist.
3. Run all applicable slice-level verification checks defined in the slice plan. All applicable checks must pass before marking the slice done. If any fail, fix them first. If the inlined context includes **Skipped Tasks**, do not execute verification that belongs only to those skipped tasks; record the evidence gap in the slice summary and UAT instead. Task artifacts use a **flat file layout** directly inside `tasks/` (for example `T01-SUMMARY.md`, `T02-SUMMARY.md`) rather than per-task subdirectories. If you need to count or re-read task summaries during verification, use `find .sf/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` or `ls .sf/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks/*-SUMMARY.md`. Never use `tasks/*/SUMMARY.md` — that glob expects subdirectories that do not exist.
4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
5. Address every gate listed in the **Gates to Close** section above — each gate maps to a specific slice-summary section the handler inspects (for example, Q8 maps to **Operational Readiness**: health signal, failure signal, recovery procedure, and monitoring gaps). Leaving a section empty records the gate as `omitted`.
6. If this slice produced evidence that a requirement changed status (Active → Validated, Active → Deferred, etc.), call `sf_requirement_update` with the requirement ID, updated `status`, and `validation` evidence. Do NOT write `.sf/REQUIREMENTS.md` directly — the engine renders it from the database.

View file

@ -0,0 +1,87 @@
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { buildCompleteSlicePrompt } from "../auto-prompts.ts";
import {
closeDatabase,
insertMilestone,
insertSlice,
insertTask,
openDatabase,
} from "../sf-db.ts";
test("complete-slice prompt treats skipped tasks as non-executable gaps", async (t) => {
const tmp = mkdtempSync(join(tmpdir(), "sf-complete-skip-"));
t.after(() => {
closeDatabase();
rmSync(tmp, { recursive: true, force: true });
});
mkdirSync(join(tmp, ".sf"), { recursive: true });
openDatabase(join(tmp, ".sf", "sf.db"));
const sliceDir = join(tmp, ".sf", "milestones", "M001", "slices", "S05");
mkdirSync(join(sliceDir, "tasks"), { recursive: true });
writeFileSync(
join(tmp, ".sf", "milestones", "M001", "M001-ROADMAP.md"),
"# M001\n",
);
writeFileSync(
join(sliceDir, "S05-PLAN.md"),
[
"# S05",
"",
"## Verification",
"Run docker compose up -d and poll staging health.",
"",
].join("\n"),
);
writeFileSync(
join(sliceDir, "tasks", "T01-SUMMARY.md"),
["---", "id: T01", "---", "", "# T01", "Existing tests passed.", ""].join(
"\n",
),
);
insertMilestone({ id: "M001", title: "Test Milestone" });
insertSlice({
id: "S05",
milestoneId: "M001",
title: "Final Verification",
status: "active",
});
insertTask({
id: "T01",
sliceId: "S05",
milestoneId: "M001",
title: "Run focused tests",
status: "complete",
});
insertTask({
id: "T02",
sliceId: "S05",
milestoneId: "M001",
title: "Validate docker-compose staging stack starts cleanly",
status: "skipped",
});
const prompt = await buildCompleteSlicePrompt(
"M001",
"Test Milestone",
"S05",
"Final Verification",
tmp,
);
assert.match(prompt, /### Skipped Tasks/);
assert.match(
prompt,
/T02: Validate docker-compose staging stack starts cleanly/,
);
assert.match(
prompt,
/do not execute verification that belongs only to those skipped tasks/,
);
});