sf snapshot: pre-dispatch, uncommitted changes after 30m inactivity

This commit is contained in:
Mikael Hugo 2026-05-10 09:19:51 +02:00
parent e58e138457
commit 7e8e3aa846
10 changed files with 132 additions and 5 deletions

View file

@ -1,3 +1,3 @@
{
"lastFullVacuumAt": "2026-05-09T23:40:22.903Z"
"lastFullVacuumAt": "2026-05-10T05:57:58.807Z"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -9,6 +9,16 @@
"lastUsed": "2026-05-08T13:36:05.865Z",
"successRate": 1,
"total": 4
},
"minimax/MiniMax-M2.7": {
"successes": 1,
"failures": 0,
"timeouts": 0,
"totalTokens": 1101124,
"totalCost": 0.6158798199999999,
"lastUsed": "2026-05-10T07:19:50.702Z",
"successRate": 1,
"total": 1
}
},
"plan-slice": {

View file

@ -31,6 +31,15 @@ Then:
6. If this slice produced evidence that a requirement changed status (Active → Validated, Active → Deferred, etc.), call `update_requirement` with the requirement ID, updated `status`, and `validation` evidence. Do NOT write `.sf/REQUIREMENTS.md` directly — the engine renders it from the database.
7. Prepare the slice completion content you will pass to `complete_slice` using the camelCase fields `milestoneId`, `sliceId`, `sliceTitle`, `oneLiner`, `narrative`, `verification`, and `uatContent`. Do **not** manually write `{{sliceSummaryPath}}`. Do **not** manually write `{{sliceUatPath}}` — the DB-backed tool is the canonical write path for both artifacts.
8. Draft the UAT content you will pass as `uatContent` — a concrete UAT script with real test cases derived from the slice plan and task summaries. Include preconditions, numbered steps with expected outcomes, and edge cases. This must NOT be a placeholder or generic template — tailor every test case to what this slice actually built.
**Whenever the slice uses a mode other than `artifact-driven`, start `uatContent` with a parsable `## UAT Type` section** (`extractUatType` must recognise the bullet). Typical shape:
```
## UAT Type
- UAT mode: artifact-driven | browser-executable | runtime-executable | live-runtime | human-experience | mixed
- Why this mode is sufficient: <one sentence>
```
The mode determines how the run-uat agent executes checks. For slices verified only by build commands, grep checks, and automated tests you may omit this block — `complete_slice` then injects a default `artifact-driven` section ahead of your body so parsers still classify the artifact.
9. Review task summaries for `key_decisions`. Append any significant decisions to `.sf/DECISIONS.md` if missing.
10. Review task summaries for patterns, gotchas, or non-obvious lessons learned. If any would save future agents from repeating investigation or hitting the same issues, append them to `.sf/KNOWLEDGE.md`. Only add entries that are genuinely useful — don't pad with obvious observations.
10b. Scan task summaries and the slice's activity log for sf-internal anomalies that the per-task agents may not have reported individually — repeated `Git stage failed`, `Verification failed … advisory`, `Safety: N unexpected file change(s)`, brittle gate predicates, etc. For any genuine sf-the-tool defect that surfaced during this slice but was NOT already filed via `report_issue`, file it now via `report_issue` with appropriate severity. This is the slice-level sweep — task-level agents file individual reports during execution; the slice-close agent catches systemic issues only visible across multiple tasks.

View file

@ -82,3 +82,92 @@ test("handleCompleteSlice_when_successful_records_completion_summary_evidence",
assert.match(trail[0].content, /Slice finished with evidence/);
assert.match(trail[0].content, /Keep slice evidence in DB/);
});
test("handleCompleteSlice_writes_uat_with_UAT_Type_header_so_downstream_can_parse_mode", async () => {
const project = makeProject();
const uatPath = join(
project,
".sf",
"milestones",
"M001",
"slices",
"S01",
"S01-UAT.md",
);
const result = await handleCompleteSlice(
{
milestoneId: "M001",
sliceId: "S01",
sliceTitle: "Slice",
verification: "Verification passed.",
uatContent: "UAT passed.",
oneLiner: "Slice finished with evidence",
narrative: "All tasks are closed.",
keyDecisions: [],
keyFiles: [],
},
project,
);
assert.equal(result.error, undefined);
assert.equal(result.uatPath, uatPath);
const { readFileSync } = await import("node:fs");
const diskUat = readFileSync(uatPath, "utf8");
assert.match(
diskUat,
/^## UAT Type$/m,
"UAT.md must contain ## UAT Type section",
);
assert.match(diskUat, /- UAT mode: artifact-driven/m);
assert.match(diskUat, /## UAT Type\n\n- UAT mode: artifact-driven/m);
const { extractUatType } = await import("../files.js");
assert.equal(extractUatType(diskUat), "artifact-driven");
});
test("handleCompleteSlice_when_uat_declares_non_artifact_mode_does_not_duplicate_UAT_Type", async () => {
const project = makeProject();
const uatPath = join(
project,
".sf",
"milestones",
"M001",
"slices",
"S01",
"S01-UAT.md",
);
const agentBody = `## UAT Type
- UAT mode: browser-executable
- Why this mode is sufficient: Playwright validates the UX flow end-to-end
## Steps
1. Run the headed Playwright smoke and confirm the dashboard renders.`;
const result = await handleCompleteSlice(
{
milestoneId: "M001",
sliceId: "S01",
sliceTitle: "Slice",
verification: "Verification passed.",
uatContent: agentBody,
oneLiner: "Slice finished with evidence",
narrative: "All tasks are closed.",
keyDecisions: [],
keyFiles: [],
},
project,
);
assert.equal(result.error, undefined);
const { readFileSync } = await import("node:fs");
const diskUat = readFileSync(uatPath, "utf8");
assert.equal(
(diskUat.match(/^## UAT Type$/gm) ?? []).length,
1,
"handler must not prepend a second ## UAT Type when the agent already declared one",
);
const { extractUatType } = await import("../files.js");
assert.equal(extractUatType(diskUat), "browser-executable");
});

View file

@ -8,7 +8,7 @@
import { promises as fs, constants as fsConstants, mkdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { atomicWriteAsync } from "../atomic-write.js";
import { clearParseCache } from "../files.js";
import { clearParseCache, extractUatType } from "../files.js";
import { getGatesForTurn } from "../gate-registry.js";
import { renderRoadmapCheckboxes } from "../markdown-renderer.js";
import { clearPathCache, resolveSlicePath } from "../paths.js";
@ -341,15 +341,34 @@ ${filesMod}
}
/**
* Render UAT markdown matching the template format.
*
* When `uatContent` already contains a parsable `## UAT Type` block (validated
* via `extractUatType`), the handler does **not** inject a second section so
* the agent-chosen mode is what downstream tools observe. Otherwise the
* handler prepends canonical `artifact-driven` defaults before `uatContent`.
*
* Purpose: preserve single-source truth for run-uat / verdict parsers without
* forcing every caller to duplicate boilerplate when `artifact-driven` applies.
*/
function renderUatMarkdown(params) {
const now = new Date().toISOString();
const uatBodyRaw =
typeof params.uatContent === "string" ? params.uatContent : "";
const injectDefaultType = extractUatType(uatBodyRaw.trim()) === undefined;
const typePreface = injectDefaultType
? `## UAT Type
- UAT mode: artifact-driven
- Why this mode is sufficient: automated build + test verification
`
: "";
return `# ${params.sliceId}: ${params.sliceTitle} — UAT
**Milestone:** ${params.milestoneId}
**Written:** ${new Date().toISOString()}
**Written:** ${now}
${params.uatContent}
`;
${typePreface}${uatBodyRaw}`;
}
/**
* Handle the complete_slice operation end-to-end.