// The CLI against scratch repositories: genesis, the canonical checks (8.3), // op ids (8.6), claims, the view (8.5 outcomes), brief checks (8.13), // verify, render and snapshot. import assert from "node:assert/strict"; import { mkdirSync, readFileSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { test } from "node:test"; import { LEGACY, MAP_ROWS, PKG_SRC, cli, genesisCommitted, mapText, queueMd, scratchRepo, sh } from "./helpers.mjs"; const MAP = "agents/sage/work/queue-migration-map.md"; function ok(r, re) { assert.equal(r.code, 0, `exit ${r.code}: ${r.err}`); if (re) assert.match(r.out, re); return r; } function no(r, code, re) { assert.equal(r.code, code, `expected exit ${code}, got ${r.code}: ${r.out}${r.err}`); if (re) assert.match(r.err, re); return r; } const genesisArgs = (repo, over = {}) => [ "genesis", "--op", over.op ?? "genesis-2026-09-26", "--root", over.root ?? repo.root, "--branch", over.branch ?? "refactor", "--map", over.map ?? MAP, ]; function ready(t, o = {}) { const repo = scratchRepo(t, o); genesisCommitted(repo); return repo; } const doc = (repo) => JSON.parse(readFileSync(repo.queuePath, "utf8")); const row = (repo, id) => doc(repo).rows.find((r) => r.id === id); test("genesis: refusals before anything is written", (t) => { const repo = scratchRepo(t); no(cli(repo, ["list"]), 2, /no docs\/plans\/queue.json; genesis has not run/); no(cli(repo, genesisArgs(repo), { by: "darkwing" }), 2, /only a privileged actor/); no(cli(repo, genesisArgs(repo)), 2, /no actor: pass --by NAME or set MOSAIC_AGENT_NAME/); no(cli(repo, genesisArgs(repo, { root: repo.base }), { by: "sage" }), 2, /is not this checkout's toplevel/); no(cli(repo, genesisArgs(repo, { branch: "main" }), { by: "sage" }), 2, /HEAD is refs\/heads\/refactor; the queue runs only on branch main/); no(cli(repo, genesisArgs(repo, { map: "agents/sage/work/nope.md" }), { by: "sage" }), 2, /not a committed file in HEAD/); no(cli(repo, genesisArgs(repo, { op: "short" }), { by: "sage" }), 2, /must match/); const missingOp = genesisArgs(repo).filter((a, i, all) => a !== "--op" && all[i - 1] !== "--op"); no(cli(repo, missingOp, { by: "sage" }), 4, /--op ID is required/); assert.throws(() => readFileSync(repo.queuePath)); }); test("genesis: the map must be committed, well formed, with committed briefs and seats", (t) => { const uncommitted = scratchRepo(t, { commitMap: false }); no(cli(uncommitted, genesisArgs(uncommitted), { by: "sage" }), 2, /not a committed file in HEAD/); const twice = scratchRepo(t, { map: mapText() + mapText() }); no(cli(twice, genesisArgs(twice), { by: "sage" }), 2, /exactly one ```json queue-map block/); const anchor = scratchRepo(t, { map: mapText(MAP_ROWS.map((r) => (r.id === 11 ? { ...r, brief: { path: "docs/plans/brief-b.md", anchor: "Nope" } } : r))) }); no(cli(anchor, genesisArgs(anchor), { by: "sage" }), 2, /has 0 headings "Nope" in HEAD/); const staged = scratchRepo(t, { map: mapText(MAP_ROWS.map((r) => (r.id === 11 ? { ...r, brief: { path: "docs/plans/new.md", anchor: "New" } } : r))) }); writeFileSync(join(staged.root, "docs/plans/new.md"), "## New\n"); staged.g("add", "docs/plans/new.md"); no(cli(staged, genesisArgs(staged), { by: "sage" }), 2, /new.md is not committed in HEAD; commit it first \(a staged brief is refused\)/); const ghost = scratchRepo(t, { map: mapText(MAP_ROWS.map((r) => (r.id === 11 ? { ...r, owner: "ghost" } : r))) }); no(cli(ghost, genesisArgs(ghost), { by: "sage" }), 2, /row 11 owner ghost is not jason, coordinator, unassigned or a seat/); const noBrief = scratchRepo(t, { map: mapText(MAP_ROWS.map((r) => (r.id === 11 ? { ...r, brief: null } : r))) }); no(cli(noBrief, genesisArgs(noBrief), { by: "sage" }), 2, /only a done row may lack one/); }); test("genesis: markers, a stray witness, once only; a retry returns the receipt", (t) => { const repo = scratchRepo(t); writeFileSync(repo.viewPath, "# QUEUE\n\nno markers\n"); no(cli(repo, genesisArgs(repo), { by: "sage" }), 2, /needs the two queue markers/); writeFileSync(repo.viewPath, queueMd()); writeFileSync(join(repo.gitDir, "mosaic-queue.head"), "{}\n"); no(cli(repo, genesisArgs(repo), { by: "sage" }), 2, /the witness .* exists without docs\/plans\/queue.json/); unlinkSync(join(repo.gitDir, "mosaic-queue.head")); const g = ok(cli(repo, genesisArgs(repo), { by: "sage" }), /^ok genesis-2026-09-26 rev 0 genesis 5 rows$/m); const d = doc(repo); assert.equal(d.canonicalRoot, repo.root); assert.equal(d.log[0].result.legacyView, LEGACY); assert.equal(d.log[0].result.mapBlob, repo.g("rev-parse", `HEAD:${MAP}`).trim()); assert.match(readFileSync(repo.viewPath, "utf8"), /^# QUEUE\n\nHeader prose\.\n\n\n\nGenerated from `docs\/plans\/queue.json` revision 0/); assert.match(readFileSync(repo.viewPath, "utf8"), /\n\nParked entries below\.\n$/); ok(cli(repo, genesisArgs(repo), { by: "sage" }), new RegExp(`^${g.out.trim()} \\(already recorded at rev 0\\)`)); no(cli(repo, genesisArgs(repo, { op: "genesis-other-01" }), { by: "sage" }), 2, /exists; genesis runs once/); // Before the genesis commit, reads work and ops refuse. ok(cli(repo, ["list"]), /^1\tdone\tdarkwing\tFinished thing\n6\tin-progress/); no(cli(repo, ["note", "9", "x", "--op", "note-row9-001"], { by: "darkwing" }), 2, /genesis not committed: HEAD has no docs\/plans\/queue.json/); repo.g("add", "docs/plans/queue.json", "docs/plans/QUEUE.md"); repo.g("commit", "-q", "-m", "genesis"); rmSync(repo.queuePath); no(cli(repo, genesisArgs(repo, { op: "genesis-again-1" }), { by: "sage" }), 2, /HEAD already has docs\/plans\/queue.json|witness .* exists/); }); test("genesis: a file holding genesis alone with no witness is confirmed by sync or a retry", (t) => { const repo = ready(t); unlinkSync(join(repo.gitDir, "mosaic-queue.head")); no(cli(repo, ["list"]), 2, /witness .* is missing; .*run `scripts\/mosaic queue sync` \(the file holds genesis alone\)/); ok(cli(repo, genesisArgs(repo), { by: "sage" }), /already recorded at rev 0/); unlinkSync(join(repo.gitDir, "mosaic-queue.head")); ok(cli(repo, ["sync"]), /durable now, never acknowledged: genesis-2026-09-26 by sage/); ok(cli(repo, ["sync"]), /nothing to confirm: rev 0 is durable and witnessed/); }); test("canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works", (t) => { const repo = ready(t); ok(cli(repo, ["verify"]), /^ok verify rev 0/); const wt = join(repo.base, "wt"); repo.g("worktree", "add", "-q", "--detach", wt); no(cli(repo, ["list"], { cwd: wt }), 2, /is a linked worktree; the queue runs only in the canonical checkout/); const clone = join(repo.base, "clone"); sh("git", ["clone", "-q", "--branch", "refactor", repo.root, clone], { env: repo.env }); no(cli(repo, ["list"], { cwd: clone }), 2, new RegExp(`this checkout is ${clone}; the queue's canonical root is ${repo.root}`)); const cloneCli = { ...repo, root: clone }; no(cli(cloneCli, ["list"], { cwd: clone }), 2, /the queue's canonical root is/); repo.g("checkout", "-q", "--detach"); no(cli(repo, ["list"]), 2, /HEAD is detached; the queue runs only on branch refactor/); repo.g("checkout", "-q", "-b", "other"); no(cli(repo, ["list"]), 2, /HEAD is refs\/heads\/other; the queue runs only on branch refactor/); repo.g("checkout", "-q", "refactor"); no(cli(repo, ["list"], { env: { GIT_DIR: repo.gitDir } }), 2, /GIT_DIR is set/); no(cli(repo, ["list"], { env: { GIT_WORK_TREE: repo.root } }), 2, /GIT_WORK_TREE is set/); no(cli(repo, ["list"], { cwd: repo.base }), 2, /is not inside a git checkout/); const foreign = sh(process.execPath, [join(PKG_SRC, "cli.mjs"), "list"], { cwd: repo.root, env: repo.env, allowFail: true }); assert.equal(foreign.status, 2); assert.match(foreign.stderr, /this queue code .* is not under the canonical root/); const link = join(repo.base, "link"); symlinkSync(repo.root, link); ok(cli(repo, ["list"], { cwd: join(link, "docs") }), /^1\tdone/); }); test("op ids: missing, too long, reserved; a retry answers; another payload refuses", (t) => { const repo = ready(t); no(cli(repo, ["note", "9", "hello"], { by: "darkwing" }), 4, /--op ID is required/); no(cli(repo, ["note", "9", "hello", "--op", "note-by-ghost"], { by: "ghost" }), 2, /actor ghost is not jason or a seat under agents/); no(cli(repo, ["note", "9", "hello", "--op", "note-by-nobody"]), 2, /no actor/); no(cli(repo, ["note", "9", "hello", "--op", "a".repeat(73)], { by: "darkwing" }), 2, /8 to 72 characters/); ok(cli(repo, ["note", "9", "hello", "--op", "a".repeat(72)], { by: "darkwing" })); no(cli(repo, ["note", "9", "hello", "--op", "note-row9.outcome"], { by: "darkwing" }), 2, /reserved/); const first = ok(cli(repo, ["note", "9", "hi", "--op", "note-row9-0001"], { by: "darkwing" }), /^ok note-row9-0001 rev 2 row 9 note$/m); ok(cli(repo, ["note", "9", "hi", "--op", "note-row9-0001"], { by: "darkwing" }), new RegExp(`^${first.out.trim()} \\(already recorded at rev 2\\)$`, "m")); no(cli(repo, ["note", "9", "other", "--op", "note-row9-0001"], { by: "darkwing" }), 2, /recorded at rev 2 as note with other arguments; a new operation needs a new op id/); no(cli(repo, ["release", "9", "--op", "note-row9-0001"], { by: "darkwing" }), 2, /as note with other arguments/); no(cli(repo, ["move", "6", "in-progress", "--op", "move-row6-again"], { by: "darkwing" }), 2, /not a transition/); assert.equal(doc(repo).revision, 2); }); test("a retried add returns the id it first allocated, after reassignment and after done", (t) => { const repo = ready(t); const add = ["add", "--op", "add-new-row-01", "--piece", "New thing", "--gate", "Jason says", "--brief", "docs/plans/brief-b.md#Template", "--owner", "rocko", "--issue", "#1508"]; const first = ok(cli(repo, add, { by: "sage" }), /row 12 none→queued$/m).out.trim(); ok(cli(repo, ["assign", "12", "dewey", "--op", "assign-12-dewey"], { by: "sage" }), /owner: rocko→dewey/); ok(cli(repo, add, { by: "sage" }), new RegExp(`^${first} \\(already recorded at rev 1\\)$`, "m")); ok(cli(repo, ["move", "12", "briefed", "--op", "brief-12-0001"], { by: "sage" })); ok(cli(repo, ["move", "12", "in-progress", "--op", "start-12-0001"], { by: "dewey" })); ok(cli(repo, ["move", "12", "in-review", "--candidate", "HEAD", "--op", "review-12-0001"], { by: "dewey" }), /in-progress→in-review round 1/); ok(cli(repo, ["move", "12", "waiting-on-jason", "--op", "wait-12-00001"], { by: "sage" })); ok(cli(repo, ["move", "12", "done", "--op", "done-12-00001"], { by: "jason" })); ok(cli(repo, add, { by: "sage" }), new RegExp(`^${first} \\(already recorded at rev 1\\)$`, "m")); assert.equal(doc(repo).rows.filter((r) => r.piece === "New thing").length, 1); }); test("Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round", (t) => { const repo = ready(t); ok(cli(repo, ["move", "6", "blocked", "--reason", "paused", "--op", "block-6-00001"], { by: "darkwing" })); ok(cli(repo, ["move", "9", "in-progress", "--op", "start-9-00001"], { by: "darkwing" })); const review = ["move", "9", "in-review", "--candidate", "HEAD", "--op", "review-9-00001"]; cli(repo, review, { by: "darkwing" }); // result lost ok(cli(repo, ["note", "9", "looking now", "--op", "note-9-000001"], { by: "filbert" })); ok(cli(repo, review, { by: "darkwing" }), /round 1 on #1508 \(already recorded at rev 3\)/); assert.equal(row(repo, 9).review.rounds.length, 1); }); test("the review issue and the evidence round through the CLI (lead decision 23, 8.7)", (t) => { const repo = ready(t); ok(cli(repo, ["move", "6", "blocked", "--reason", "paused", "--op", "block-6-00001"], { by: "darkwing" })); ok(cli(repo, ["set", "9", "issues", "1495,1508", "--op", "issues-9-0001"], { by: "sage" })); ok(cli(repo, ["move", "9", "in-progress", "--op", "start-9-00001"], { by: "darkwing" })); const review = (op, ...extra) => ["move", "9", "in-review", "--candidate", "HEAD", "--op", op, ...extra]; no(cli(repo, review("review-9-00001"), { by: "darkwing" }), 2, /lists several issues \(#1495, #1508\); name the review's issue with --issue/); no(cli(repo, review("review-9-00001", "--issue", "1495", "--issue", "1508"), { by: "darkwing" }), 4, /one --issue/); no(cli(repo, review("review-9-00001", "--issue", "#1600"), { by: "darkwing" }), 2, /--issue #1600 is not one of row 9's issues/); ok(cli(repo, review("review-9-00001", "--issue", "#1508"), { by: "darkwing" }), /in-progress→in-review round 1 on #1508$/m); const head = repo.g("rev-parse", "HEAD").trim(); no(cli(repo, ["move", "9", "done", "--evidence", `comment=7,candidate=${head}`, "--op", "done-9-000001"], { by: "filbert" }), 2, /round=/); ok(cli(repo, ["move", "9", "in-progress", "--op", "changes-9-0001"], { by: "darkwing" })); ok(cli(repo, review("review-9-00002"), { by: "darkwing" }), /round 2 on #1508$/m); no(cli(repo, ["move", "9", "done", "--evidence", `comment=7,round=1,candidate=${head}`, "--op", "done-9-000001"], { by: "filbert" }), 2, /evidence names round 1; row 9 is in round 2/); ok(cli(repo, ["move", "9", "done", "--evidence", `comment=8,round=2,candidate=${head}`, "--op", "done-9-000002"], { by: "filbert" }), /in-review→done round 2$/m); }); test("claims and add defaults through the CLI; candidates are manifests or reachable commits", (t) => { const repo = ready(t); ok(cli(repo, ["add", "--op", "add-by-dewey-1", "--piece", "Mine", "--gate", "tests", "--brief", "docs/plans/brief-b.md#Template"], { by: "dewey" })); const r = row(repo, 12); assert.deepEqual([r.owner, r.gateOwner, r.state, r.claim, r.brief.blob], ["dewey", "jason", "queued", null, repo.g("rev-parse", "HEAD:docs/plans/brief-b.md").trim()]); no(cli(repo, ["add", "--op", "add-by-dewey-2", "--piece", "x", "--gate", "g", "--brief", "docs/plans/brief-b.md#Template", "--reviewer", "ghost"], { by: "sage" }), 2, /reviewer ghost is not jason or a seat/); no(cli(repo, ["add", "--op", "add-by-dewey-3", "--piece", "x", "--gate", "g", "--brief", "docs/plans/brief-b.md"], { by: "dewey" }), 2, /PATH#ANCHOR/); no(cli(repo, ["add", "--op", "add-by-dewey-4", "--piece", "x", "--gate", "g", "--brief", "docs/plans/brief-b.md#Queue", "--issue", "x"], { by: "dewey" }), 4); ok(cli(repo, ["move", "11", "in-progress", "--op", "start-11-0001"], { by: "dewey" })); assert.deepEqual(row(repo, 11).claim, { seat: "dewey", op: "start-11-0001" }); no(cli(repo, ["move", "11", "in-review", "--candidate", "no-such-thing", "--op", "review-11-001"], { by: "dewey" }), 2, /neither a manifest file nor a commit/); const dangling = repo.g("commit-tree", "-m", "x", `${repo.g("rev-parse", "HEAD^{tree}").trim()}`).trim(); no(cli(repo, ["move", "11", "in-review", "--candidate", dangling, "--op", "review-11-002"], { by: "dewey" }), 2, /not reachable from any local branch or tag/); const manifest = join(repo.base, "cand.sha256"); writeFileSync(manifest, `${"e".repeat(64)} packages/x.mjs\n`); ok(cli(repo, ["move", "11", "in-review", "--candidate", manifest, "--op", "review-11-003"], { by: "dewey" })); assert.equal(row(repo, 11).review.rounds[0].candidate.kind, "manifest"); ok(cli(repo, ["release", "6", "--op", "release-6-001"], { by: "sage" }), /in-progress→briefed/); assert.equal(row(repo, 6).claim, null); }); test("the working-brief check: a changed working copy refuses the start and flags next", (t) => { const repo = ready(t); writeFileSync(join(repo.root, "docs/plans/brief-b.md"), "# Briefs B\n\n## Queue\n\nEdited.\n\n## Template\n\nEdited.\n"); ok(cli(repo, ["next", "dewey"]), /^start row 11: Brief template; brief docs\/plans\/brief-b.md § Template; brief differs from pinned blob; ask the lead to re-pin$/m); no(cli(repo, ["move", "11", "in-progress", "--op", "start-11-0001"], { by: "dewey" }), 2, /row 11: brief differs from pinned blob; ask the lead to re-pin/); repo.g("commit", "-q", "-am", "edit brief"); no(cli(repo, ["move", "11", "in-progress", "--op", "start-11-0001"], { by: "dewey" }), 2, /brief differs from pinned blob/); no(cli(repo, ["verify", "--current"]), 2, /brief drift against HEAD:\nrow 9: brief docs\/plans\/brief-b.md changed in HEAD .*\nrow 11:/); ok(cli(repo, ["verify"])); ok(cli(repo, ["set", "11", "brief", "docs/plans/brief-b.md#Template", "--op", "repin-11-0001"], { by: "sage" }), /row 11 brief: docs\/plans\/brief-b.md § Template @\w{12}→docs\/plans\/brief-b.md § Template @\w{12}/); ok(cli(repo, ["next", "dewey"]), /^start row 11: Brief template; brief docs\/plans\/brief-b.md § Template$/m); ok(cli(repo, ["move", "11", "in-progress", "--op", "start-11-0001"], { by: "dewey" })); }); test("next: resume first, then nothing for an idle seat; needs a seat", (t) => { const repo = ready(t); ok(cli(repo, ["next", "darkwing"]), /^resume row 6: Row six/m); ok(cli(repo, ["next"], { by: "dewey" }), /^start row 11/m); ok(cli(repo, ["next", "rocko"]), /^nothing$/m); no(cli(repo, ["next"]), 2, /next needs a seat/); }); test("view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes", (t) => { const repo = ready(t); const first = ok(cli(repo, ["note", "9", "one", "--op", "note-9-000001"], { by: "darkwing" })).out.trim(); repo.g("checkout", "--", "docs/plans/QUEUE.md"); no(cli(repo, ["note", "9", "two", "--op", "note-9-000002"], { by: "darkwing" }), 2, /view stale: QUEUE.md shows rev 0; rev 1 \(op note-9-000001 by darkwing at .*\) is recorded .*Tell darkwing, then run `scripts\/mosaic queue render`/); no(cli(repo, ["verify"]), 2, /view stale/); const retry = ok(cli(repo, ["note", "9", "one", "--op", "note-9-000001"], { by: "darkwing" })); assert.equal(retry.out.trim(), `${first} (already recorded at rev 1)`); assert.match(retry.err, /warning: view stale/); const read = ok(cli(repo, ["list"])); assert.match(read.err, /warning: view stale/); no(cli(repo, ["render", "--check"]), 2, /view stale/); ok(cli(repo, ["render"]), /rendered rev 1 over rev 0; newly shown: rev 1 \(op note-9-000001 by darkwing/); ok(cli(repo, ["render"]), /view current at rev 1; nothing written/); ok(cli(repo, ["verify"])); }); test("view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers", (t) => { const repo = ready(t); ok(cli(repo, ["note", "9", "one", "--op", "note-9-000001"], { by: "darkwing" })); const good = readFileSync(repo.viewPath, "utf8"); const cases = [ good.replace("| one |", "| two |"), repo.g("show", "HEAD:docs/plans/QUEUE.md").replace("Row six", "Row 6"), good.replace("\n", ""), good + "\n", ]; for (const text of cases) { writeFileSync(repo.viewPath, text); no(cli(repo, ["note", "9", "two", "--op", "note-9-000002"], { by: "darkwing" }), 2, /view unknown: .*restore the table with git or re-apply the edit as queue ops/); no(cli(repo, ["render"]), 2, /view unknown/); no(cli(repo, ["verify"]), 2, /view unknown/); assert.match(ok(cli(repo, ["show", "9"])).err, /warning: view unknown/); assert.equal(readFileSync(repo.viewPath, "utf8"), text, "nothing rewrote an unknown view"); } writeFileSync(repo.viewPath, good.replace("Header prose.", "Header prose, edited by hand.")); ok(cli(repo, ["note", "9", "two", "--op", "note-9-000002"], { by: "darkwing" })); assert.match(readFileSync(repo.viewPath, "utf8"), /edited by hand/); }); test("a hand edit to queue.json refuses every verb, reads included", (t) => { const repo = ready(t); const text = readFileSync(repo.queuePath, "utf8"); writeFileSync(repo.queuePath, text.replace('"piece": "Row six"', '"piece": "Row 6"')); for (const args of [["list"], ["show", "6"], ["next", "darkwing"], ["verify"], ["render"], ["sync"]]) no(cli(repo, args), 2, /do not equal the replay/); no(cli(repo, ["note", "9", "x", "--op", "note-9-000001"], { by: "darkwing" }), 2, /do not equal the replay/); writeFileSync(repo.queuePath, text.replace("\n", "\n\n")); no(cli(repo, ["list"]), 2, /re-serialize byte for byte/); writeFileSync(repo.queuePath, "{ not json"); no(cli(repo, ["list"]), 2, /not valid JSON|is not JSON/); }); test("verify and render --check leave bytes and mtimes unchanged", (t) => { const repo = ready(t); const snap = () => [repo.queuePath, repo.viewPath, join(repo.gitDir, "mosaic-queue.head")].map((p) => [readFileSync(p, "hex"), statSync(p, { bigint: true }).mtimeNs]); const before = snap(); ok(cli(repo, ["verify"])); ok(cli(repo, ["verify", "--current"])); ok(cli(repo, ["render", "--check"])); ok(cli(repo, ["render"])); ok(cli(repo, ["list"])); ok(cli(repo, ["next", "darkwing"])); assert.deepEqual(snap(), before); }); test("render is byte-stable across runs and repositories", (t) => { const a = ready(t); const b = ready(t); const body = (repo) => readFileSync(repo.viewPath, "utf8").replace(repo.root, "ROOT"); assert.equal(body(a), body(b)); const bytes = readFileSync(a.viewPath); writeFileSync(join(a.base, "x"), ""); ok(cli(a, ["render"])); assert.ok(readFileSync(a.viewPath).equals(bytes)); }); test("snapshot and verify --snapshot", (t) => { const repo = ready(t); const out = join(repo.base, "snap"); mkdirSync(out); no(cli(repo, ["snapshot", "--out", join(repo.root, "docs")]), 2, /is not empty/); mkdirSync(join(repo.root, "empty-inside")); no(cli(repo, ["snapshot", "--out", "empty-inside"]), 2, /is inside the repository/); no(cli(repo, ["snapshot", "--out", join(repo.base, "missing")]), 2, /does not exist/); ok(cli(repo, ["snapshot", "--out", out]), /^snapshot rev 0: queue.json [0-9a-f]{64}, QUEUE.md [0-9a-f]{64}$/m); ok(cli(repo, ["verify", "--snapshot", out, "--base-absent"], { cwd: repo.base }), /genesis alone/); no(cli(repo, ["verify", "--snapshot", out]), 4, /exactly one of/); const base = join(repo.base, "base.json"); writeFileSync(base, readFileSync(repo.queuePath)); ok(cli(repo, ["note", "9", "one", "--op", "note-9-000001"], { by: "darkwing" })); const out2 = join(repo.base, "snap2"); mkdirSync(out2); ok(cli(repo, ["snapshot", "--out", out2])); ok(cli(repo, ["verify", "--snapshot", out2, "--base-file", base], { cwd: repo.base }), /extends the base/); no(cli(repo, ["verify", "--snapshot", out2, "--base-absent"]), 2, /must hold genesis alone/); no(cli(repo, ["verify", "--snapshot", out, "--base-file", join(out2, "queue.json")]), 2, /does not extend the base/); writeFileSync(join(out2, "QUEUE.md"), readFileSync(join(out, "QUEUE.md"))); no(cli(repo, ["verify", "--snapshot", out2, "--base-file", base]), 2, /is not the render of rev 1 \(stale\)/); repo.g("checkout", "--", "docs/plans/QUEUE.md"); const out3 = join(repo.base, "snap3"); mkdirSync(out3); no(cli(repo, ["snapshot", "--out", out3]), 2, /view stale/); }); test("usage errors exit 4", (t) => { const repo = ready(t); no(cli(repo, []), 4); no(cli(repo, ["frobnicate"]), 4, /unknown verb/); no(cli(repo, ["list", "--bogus"]), 4, /unknown option --bogus/); no(cli(repo, ["show"]), 4, /expected show ID/); no(cli(repo, ["show", "0"]), 4, /positive integer/); no(cli(repo, ["move", "9", "done", "--op", "x-00000001", "--op", "y-00000001"], { by: "sage" }), 4, /--op given twice/); no(cli(repo, ["set", "9", "owner", "x", "--op", "set-9-000001"], { by: "sage" }), 4, /set fields/); no(cli(repo, ["set", "9", "required", "yes", "--op", "set-9-000001"], { by: "sage" }), 4, /true or false/); no(cli(repo, ["add", "--op", "add-0000001", "--piece", "x"], { by: "sage" }), 4, /add needs --gate/); no(cli(repo, ["list", "--check"]), 4, /does not apply here/); ok(cli(repo, ["--help"]), /^usage: queue list/); });