// Scratch repositories for the queue tests. Every test works under // os.tmpdir(): a fresh repository holding a copy of packages/queue/src and // the two discord helpers it imports, so the code-under-root check (8.3) // passes there and nothing touches this checkout's .git. import { spawnSync } from "node:child_process"; import { cpSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; const HERE = dirname(fileURLToPath(import.meta.url)); export const PKG_SRC = join(HERE, "..", "src"); export const DISCORD_SRC = join(HERE, "..", "..", "discord", "src"); export const SCRIPTS = join(HERE, "..", "..", "..", "scripts"); export const BEGIN = ""; export const END = ""; // Git reads no user or system config here: HOME is a scratch directory. export function gitEnv(home, extra = {}) { const env = { ...process.env, HOME: home, XDG_CONFIG_HOME: join(home, ".config"), GIT_CONFIG_NOSYSTEM: "1", GIT_AUTHOR_NAME: "t", GIT_AUTHOR_EMAIL: "t@example.invalid", GIT_COMMITTER_NAME: "t", GIT_COMMITTER_EMAIL: "t@example.invalid", ...extra }; for (const k of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE", "MOSAIC_AGENT_NAME"]) if (!(k in extra)) delete env[k]; return env; } export function sh(cmd, args, { cwd, env, input, allowFail = false } = {}) { const r = spawnSync(cmd, args, { cwd, env, input, encoding: "utf8", maxBuffer: 64 << 20 }); if (r.error) throw r.error; if (r.status !== 0 && !allowFail) throw new Error(`${cmd} ${args.join(" ")} exited ${r.status}: ${r.stderr}`); return r; } export const MAP_ROWS = [ { id: 1, piece: "Finished thing", owner: "darkwing", issues: [1503], closes: [1503], state: "done", previousState: null, required: false, requiredSince: null, gate: "B passed", gateOwner: "jason", brief: null, after: [], reviewers: [], note: null, blockedReason: null, createdAt: "unknown", }, { id: 6, piece: "Row six", owner: "darkwing", issues: [1511], closes: [1511], state: "in-progress", previousState: null, required: false, requiredSince: null, gate: "pilot accepted", gateOwner: "jason", brief: { path: "docs/plans/brief-a.md", anchor: "Row six" }, after: [], reviewers: ["filbert"], note: "legacy note", blockedReason: null, createdAt: "2026-09-13", }, { id: 8, piece: "Parked thing", owner: "unassigned", issues: [], closes: [], state: "parked", previousState: null, required: false, requiredSince: null, gate: "Jason says", gateOwner: "jason", brief: { path: "docs/plans/brief-a.md", anchor: "Parked" }, after: [], reviewers: [], note: null, blockedReason: null, createdAt: "unknown", }, { id: 9, piece: "Queue as data", owner: "darkwing", issues: [1508], closes: [1508], state: "briefed", previousState: null, required: true, requiredSince: "unknown", gate: "filbert approves", gateOwner: "filbert", brief: { path: "docs/plans/brief-b.md", anchor: "Queue" }, after: [{ id: 6, when: "settled" }], reviewers: ["filbert"], note: null, blockedReason: null, createdAt: "2026-09-13", }, { id: 11, piece: "Brief template", owner: "dewey", issues: [1508], closes: [1508], state: "briefed", previousState: null, required: false, requiredSince: null, gate: "two briefs accepted", gateOwner: "jason", brief: { path: "docs/plans/brief-b.md", anchor: "Template" }, after: [], reviewers: [], note: null, blockedReason: null, createdAt: "unknown", }, ]; export function mapText(rows = MAP_ROWS, { retired = [7], highWater = 11 } = {}) { return `# Migration map\n\nReviewed.\n\n\`\`\`json queue-map\n${JSON.stringify({ rows, retired, highWater }, null, 2)}\n\`\`\`\n`; } export const LEGACY = "| # | Piece |\n|---|---|\n| 1 | old row |\n"; export function queueMd(body = LEGACY) { return `# QUEUE\n\nHeader prose.\n\n${BEGIN}\n${body}${END}\n\nParked entries below.\n`; } // A committed scratch checkout on branch `refactor` with the queue code, // seats, briefs, a map and QUEUE.md. Returns paths and runners. With // `scripts`, it also commits queue-commit.sh, the guard and a one-test // packages/queue/tests, so the commit procedure has an archive to run. export function scratchRepo(t, { map = mapText(), commitMap = true, name = "repo", scripts = false } = {}) { const base = realpathSync(mkdtempSync(join(tmpdir(), "mosaic-queue-test-"))); if (t) t.after(() => rmSync(base, { recursive: true, force: true })); const home = join(base, "home"); mkdirSync(join(home, ".config"), { recursive: true }); const root = join(base, name); mkdirSync(root); const env = gitEnv(home); const g = (...args) => sh("git", ["-C", root, ...args], { env }).stdout; g("init", "-q", "-b", "refactor"); cpSync(PKG_SRC, join(root, "packages/queue/src"), { recursive: true }); mkdirSync(join(root, "packages/discord/src"), { recursive: true }); for (const f of ["journal.mjs", "errors.mjs"]) cpSync(join(DISCORD_SRC, f), join(root, "packages/discord/src", f)); for (const s of ["darkwing", "dewey", "filbert", "rocko", "sage"]) { mkdirSync(join(root, "agents", s, "work"), { recursive: true }); writeFileSync(join(root, "agents", s, ".keep"), ""); } mkdirSync(join(root, "docs/plans"), { recursive: true }); writeFileSync(join(root, "docs/plans/brief-a.md"), "# Briefs A\n\n## Row six\n\nText.\n\n## Parked\n\nStub.\n"); writeFileSync(join(root, "docs/plans/brief-b.md"), "# Briefs B\n\n## Queue\n\nText.\n\n## Template\n\nText.\n\n```\n## Queue\n```\n"); writeFileSync(join(root, "docs/plans/QUEUE.md"), queueMd()); if (map !== null) writeFileSync(join(root, "agents/sage/work/queue-migration-map.md"), map); if (scripts) { mkdirSync(join(root, "scripts/git-hooks"), { recursive: true }); cpSync(join(SCRIPTS, "queue-commit.sh"), join(root, "scripts/queue-commit.sh")); cpSync(join(SCRIPTS, "git-hooks/pre-commit"), join(root, "scripts/git-hooks/pre-commit")); mkdirSync(join(root, "packages/queue/tests"), { recursive: true }); writeFileSync(join(root, "packages/queue/tests/stub.test.mjs"), 'import { test } from "node:test";\ntest("archive stub", () => {});\n'); } if (!commitMap && map !== null) { g("add", "-A", "--", ".", ":!agents/sage/work/queue-migration-map.md"); } else { g("add", "-A"); } g("commit", "-q", "-m", "scratch"); return { base, home, root, env, g, gitDir: join(root, ".git"), queuePath: join(root, "docs/plans/queue.json"), viewPath: join(root, "docs/plans/QUEUE.md") }; } // The copied code, imported from inside the scratch repository. export async function load(repo) { const src = join(repo.root, "packages/queue/src"); const store = await import(pathToFileURL(join(src, "store.mjs")).href); const cli = await import(pathToFileURL(join(src, "cli.mjs")).href); const queue = await import(pathToFileURL(join(src, "queue.mjs")).href); const io = await import(pathToFileURL(join(src, "io.mjs")).href); const lock = await import(pathToFileURL(join(src, "lock.mjs")).href); const errors = await import(pathToFileURL(join(src, "errors.mjs")).href); return { store, cli, queue, io, lock, errors }; } // Runs the copied CLI as a child process in `cwd`. export function cli(repo, args, { cwd = repo.root, by = null, env = {} } = {}) { const e = { ...repo.env, ...env }; if (by !== null) e.MOSAIC_AGENT_NAME = by; const r = spawnSync(process.execPath, [join(repo.root, "packages/queue/src/cli.mjs"), ...args], { cwd, env: e, encoding: "utf8" }); if (r.error) throw r.error; return { code: r.status, signal: r.signal, out: r.stdout, err: r.stderr }; } // Genesis in the working tree, then the genesis commit by plain git (the // scratch repo has no guard installed), so later ops pass the F3 check. export function genesisCommitted(repo, { by = "sage", op = "genesis-2026-09-26" } = {}) { const r = cli(repo, ["genesis", "--op", op, "--root", repo.root, "--branch", "refactor", "--map", "agents/sage/work/queue-migration-map.md"], { by }); if (r.code !== 0) throw new Error(`genesis failed: ${r.err}`); repo.g("add", "docs/plans/queue.json", "docs/plans/QUEUE.md"); repo.g("commit", "-q", "-m", "queue genesis"); return r; } export function opts(repo, extra = {}) { return { cwd: repo.root, env: repo.env, ...extra }; }