fix: use bare slice directories for record promotion

This commit is contained in:
Mikael Hugo 2026-05-05 13:37:25 +02:00
parent ee836142ed
commit 00c9a1e0b5
2 changed files with 64 additions and 3 deletions

View file

@ -1,6 +1,6 @@
// SF Extension — Record Promoter
// Scans docs/records/*.md for frontmatter-tagged actionable records and
// promotes each one to a .sf/milestones/M<NNN>-<slug>/ structure.
// promotes each one to a .sf/milestones/M<NNN>/ structure.
// Called from ensureAgenticDocsScaffold (ADR-021 Phase C hook) and from
// the post-milestone-completion path in auto-post-unit.ts / auto.ts:stopAuto.
//
@ -249,9 +249,10 @@ export function promoteActionableRecords(basePath) {
sliceTable,
"",
].join("\n"), "utf-8");
// slices/S<NN>-<slug>/ directories
// slices/S<NN>/ directories. Slice titles live in roadmap content,
// not path names; resolvers keep supporting legacy slug dirs.
for (const slice of sliceRows) {
const sliceDir = join(milestoneDir, "slices", `${slice.id}-${slice.slug}`);
const sliceDir = join(milestoneDir, "slices", slice.id);
mkdirSync(sliceDir, { recursive: true });
}
// Append to QUEUE.md

View file

@ -0,0 +1,60 @@
import assert from "node:assert/strict";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";
import { promoteActionableRecords } from "../record-promoter.js";
const tmpRoots = [];
afterEach(() => {
for (const dir of tmpRoots.splice(0)) {
rmSync(dir, { recursive: true, force: true });
}
});
function makeProject() {
const root = mkdtempSync(join(tmpdir(), "sf-record-promoter-"));
tmpRoots.push(root);
mkdirSync(join(root, "docs", "records"), { recursive: true });
return root;
}
test("promoteActionableRecords_creates_bare_slice_id_directories", () => {
const projectRoot = makeProject();
const recordPath = join(projectRoot, "docs", "records", "2026-05-05-path-shape.md");
writeFileSync(
recordPath,
[
"---",
"actionable: true",
"promoted: false",
"---",
"",
"## Gap One Needs Long Title",
"",
"Finding one.",
"",
"## Gap Two Also Needs Long Title",
"",
"Finding two.",
"",
].join("\n"),
);
const result = promoteActionableRecords(projectRoot);
assert.equal(result.promoted.length, 1);
assert.ok(existsSync(join(projectRoot, ".sf", "milestones", "M001", "slices", "S01")));
assert.ok(existsSync(join(projectRoot, ".sf", "milestones", "M001", "slices", "S02")));
assert.equal(
existsSync(join(projectRoot, ".sf", "milestones", "M001", "slices", "S01-gap-one-needs-long-title")),
false,
);
assert.equal(
existsSync(join(projectRoot, ".sf", "milestones", "M001", "slices", "S02-gap-two-also-needs-long-title")),
false,
);
assert.match(readFileSync(recordPath, "utf-8"), /promoted_to: M001/);
});