// The git verbs against a local bare remote, without pi and without the // network. Every row is a way a commit, pull or push from Discord could go // wrong, and the fixed refusal or the honest result it gets instead. The // vault protocol is exercised through small stand-ins for the // shared-signals scripts that speak the same command line. import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdirSync, writeFileSync, readFileSync, chmodSync, existsSync, readdirSync } from "node:fs"; import { spawnSync } from "node:child_process"; import { join } from "node:path"; import { loadToolsConfig, createToolSet, enabledToolNames, TOOL_NAMES, WRITE_TOOL_NAMES } from "../src/tools.mjs"; import { GIT_TOOL_NAMES, RESERVE_TOOL_NAME, GIT_REFUSAL, GitRefusal, GIT_TOKEN_FILE_ENV, GIT_USERNAME_ENV, CREDENTIAL_HELPER, VAULT_REGISTRY, gitEnv, gitStatus, gitCommit, gitPull, gitPush, reserveId, maskSecrets, parseStatus, loadGitConfig } from "../src/git.mjs"; import { makeRoot } from "./helpers.mjs"; // Built at run time so the suite's grep for token shapes never finds one // in the source tree. const FAKE_TOKEN = ["ghp", "_", "A".repeat(36)].join(""); function sh(cwd, args, env = {}) { const r = spawnSync("git", args, { cwd, encoding: "utf8", env: { ...process.env, GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_NOSYSTEM: "1", GIT_AUTHOR_NAME: "Jason", GIT_AUTHOR_EMAIL: "j@example.test", GIT_COMMITTER_NAME: "Jason", GIT_COMMITTER_EMAIL: "j@example.test", ...env } }); if (r.status !== 0) throw new Error(`git ${args.join(" ")} failed: ${r.stderr}`); return r.stdout.trim(); } const FAKE_LOCK_TOOL = `#!/usr/bin/env python3 import os, sys, json args = sys.argv[1:] cmd = args.pop(0) owner = os.environ.get("VAULT_LOCK_OWNER") title = None pos = [] while args: a = args.pop(0) if a == "--owner": owner = args.pop(0) elif a == "--title": title = args.pop(0) elif a == "--ttl": args.pop(0) elif a == "--": pos.extend(args); args = [] else: pos.append(a) root = os.getcwd() locks = os.path.join(root, ".vault-locks") os.makedirs(locks, exist_ok=True) def lockfile(p): return os.path.join(locks, p.replace("/", "__") + ".json") def holder(p): try: return json.load(open(lockfile(p))) except Exception: return None if cmd == "check": bad = False for p in pos: h = holder(p) if h and h["owner"] != owner: print(f"{p} is locked by {h['owner']} until later", file=sys.stderr); bad = True sys.exit(1 if bad else 0) if cmd == "lock": p = pos[0]; h = holder(p) if h and h["owner"] != owner: sys.exit(f"vault_lock: {p} is held by {h['owner']} until later") json.dump({"owner": owner, "path": p}, open(lockfile(p), "w")); print(f"locked {p} for {owner}"); sys.exit(0) if cmd == "unlock": p = pos[0]; h = holder(p) if h and h["owner"] == owner: os.remove(lockfile(p)) print(f"released {p}"); sys.exit(0) if cmd == "reserve": prefix = pos[0] reg = os.path.join(root, "docs", "ID-REGISTRY.txt") lines = open(reg).read().splitlines() if os.path.exists(reg) else ["# id\\tdate\\towner\\ttitle"] nums = [int(l.split("\\t")[0].split("-")[1]) for l in lines if l.startswith(prefix + "-")] rid = f"{prefix}-{(max(nums) + 1 if nums else 1):03d}" lines.append(f"{rid}\\t2026-09-18T00:00:00+00:00\\t{owner}\\t{title}") open(reg, "w").write("\\n".join(lines) + "\\n") print(rid) if os.environ.get("FAKE_RESERVE_NOTE"): print("note: origin/main not consulted; commit and push the registry line soon", file=sys.stderr) sys.exit(0) sys.exit(f"vault_lock: unknown {cmd}") `; const FAKE_VALIDATOR = `#!/usr/bin/env python3 import os, sys bad = [] for dp, dn, fn in os.walk("vault"): for f in fn: p = os.path.join(dp, f) if "INVALID" in open(p).read(): bad.append(p) if bad: for p in bad: print(f"{p}: id not registered", file=sys.stderr) sys.exit(1) print("PASS: structured records") `; // A bare remote, a clone for Sage with one commit, a second clone for // "someone else", and a private token file. With `vault`, the stand-in // scripts and a registry are committed in the clone. function fixture({ vault = false } = {}) { const base = makeRoot(); const remote = join(base, "remote.git"); const clone = join(base, "clone"); const other = join(base, "other"); mkdirSync(remote); sh(remote, ["init", "--bare", "-q", "-b", "main"]); sh(base, ["clone", "-q", remote, clone]); writeFileSync(join(clone, "README.md"), "# Signals\n"); mkdirSync(join(clone, "vault")); writeFileSync(join(clone, "vault", "SS-001.md"), "id: SS-001\n"); if (vault) { mkdirSync(join(clone, "tools")); mkdirSync(join(clone, "docs")); writeFileSync(join(clone, "tools", "vault_lock.py"), FAKE_LOCK_TOOL); writeFileSync(join(clone, "tools", "validate_vault.py"), FAKE_VALIDATOR); writeFileSync(join(clone, "docs", "ID-REGISTRY.txt"), "# id\tdate\towner\ttitle\nSS-001\t2026-09-01T00:00:00+00:00\tjason\tfirst\n"); writeFileSync(join(clone, ".gitignore"), ".vault-locks/\n"); } sh(clone, ["add", "-A"]); sh(clone, ["commit", "-q", "-m", "first"]); sh(clone, ["push", "-q", "-u", "origin", "main"]); sh(base, ["clone", "-q", remote, other]); const tokenFile = join(base, "token"); writeFileSync(tokenFile, `${FAKE_TOKEN}\n`, { mode: 0o600 }); const git = { branch: "main", identity: "sage", tokenFile, author: "Sage ", ...(vault ? { protocol: "vault" } : {}) }; const config = loadToolsConfig({ roots: [{ name: "ss", path: clone, write: true, git }], maxFileBytes: 4096, maxCallsPerTurn: 30 }); return { base, remote, clone, other, tokenFile, git, config, root: config.roots[0] }; } const refusal = (reason) => (e) => e instanceof GitRefusal && e.reason === reason; test("git: config validation is strict, needs write: true, a work tree and a private token file", () => { const { clone, tokenFile, git } = fixture(); const cfg = (g, extra = {}) => loadToolsConfig({ roots: [{ name: "ss", path: clone, write: true, ...extra, git: g }], maxFileBytes: 4096, maxCallsPerTurn: 3 }); assert.equal(cfg(git).roots[0].git.author.name, "Sage"); assert.equal(cfg(git).roots[0].git.protocol, null); assert.equal(cfg({ ...git, protocol: "vault" }).roots[0].git.protocol, "vault"); assert.throws(() => cfg(git, { write: false }), /git needs write: true/); assert.throws(() => cfg({ ...git, extra: 1 }), /unknown key/); assert.throws(() => cfg({ ...git, branch: "-x" }), /branch/); assert.throws(() => cfg({ ...git, branch: "a..b" }), /branch/); assert.throws(() => cfg({ ...git, identity: "Sage" }), /identity/); assert.throws(() => cfg({ ...git, author: "sage@mosaicstack.dev" }), /author/); assert.throws(() => cfg({ ...git, protocol: "svn" }), /protocol/); assert.throws(() => cfg({ ...git, tokenFile: "relative" }), /absolute/); assert.throws(() => cfg({ ...git, tokenFile: join(clone, "nope") }), /not found/); chmodSync(tokenFile, 0o644); assert.throws(() => cfg(git), /mode 0600/); chmodSync(tokenFile, 0o600); const notRepo = join(clone, "vault"); assert.throws(() => loadToolsConfig({ roots: [{ name: "v", path: notRepo, write: true, git }], maxFileBytes: 4096, maxCallsPerTurn: 3 }), /not a git work tree/); assert.throws(() => loadGitConfig(git, "x", "/nonexistent"), /not a git work tree/); assert.deepEqual(enabledToolNames(cfg(git)), [...TOOL_NAMES, ...WRITE_TOOL_NAMES, ...GIT_TOOL_NAMES]); assert.deepEqual(enabledToolNames(cfg({ ...git, protocol: "vault" })), [...TOOL_NAMES, ...WRITE_TOOL_NAMES, ...GIT_TOOL_NAMES, RESERVE_TOOL_NAME]); }); test("git: the child environment drops every host git config, names one helper, and carries the token path only for origin", () => { const { git: raw, root } = fixture(); const env = gitEnv(root.git); assert.equal(env.GIT_CONFIG_GLOBAL, "/dev/null"); assert.equal(env.GIT_CONFIG_NOSYSTEM, "1"); assert.equal(env.GIT_TERMINAL_PROMPT, "0"); const keys = Object.entries(env).filter(([k]) => /^GIT_CONFIG_KEY_/.test(k)).map(([k, v]) => [v, env[k.replace("KEY", "VALUE")]]); const helper = keys.find(([k]) => k === "credential.helper")[1]; assert.equal(helper, `!'${process.execPath}' '${CREDENTIAL_HELPER}'`); assert.ok(existsSync(CREDENTIAL_HELPER)); assert.deepEqual(keys.find(([k]) => k === "user.name"), ["user.name", "Sage"]); assert.deepEqual(keys.find(([k]) => k === "user.email"), ["user.email", "sage@mosaicstack.dev"]); assert.equal(env.MOSAIC_AGENT_NAME, "sage"); assert.equal(env.VAULT_LOCK_OWNER, "sage"); assert.equal(env[GIT_USERNAME_ENV], "sage"); assert.equal(env[GIT_TOKEN_FILE_ENV], undefined, "no token path for a local verb"); assert.equal(gitEnv(root.git, { remote: true })[GIT_TOKEN_FILE_ENV], raw.tokenFile); for (const k of Object.keys(env)) assert.ok(!/TOKEN$|SECRET|PASSWORD/.test(k) || k === GIT_TOKEN_FILE_ENV, k); }); test("git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge", () => { const { clone, root } = fixture(); const s = gitStatus(root); assert.equal(s.branch, "main"); assert.equal(s.upstream, "origin/main"); assert.equal(s.ahead, 0); assert.equal(s.behind, 0); assert.deepEqual(s.changed, []); writeFileSync(join(clone, "README.md"), "# Signals\nmore\n"); writeFileSync(join(clone, "new.md"), "new\n"); const s2 = gitStatus(root); assert.deepEqual(s2.changed.map((c) => c.path), ["README.md"]); assert.deepEqual(s2.untracked, ["new.md"]); sh(clone, ["checkout", "-q", "-b", "side"]); assert.throws(() => gitStatus(root), refusal(GIT_REFUSAL.BRANCH)); sh(clone, ["checkout", "-q", "main"]); sh(clone, ["checkout", "-q", "--detach"]); assert.throws(() => gitStatus(root), refusal(GIT_REFUSAL.DETACHED)); sh(clone, ["checkout", "-q", "main"]); writeFileSync(join(clone, ".git", "MERGE_HEAD"), `${sh(clone, ["rev-parse", "HEAD"])}\n`); assert.throws(() => gitStatus(root), refusal(GIT_REFUSAL.IN_PROGRESS)); }); test("git: parseStatus reads porcelain v2 including renames and conflicts", () => { const p = parseStatus([ "# branch.oid abc", "# branch.head main", "# branch.upstream origin/main", "# branch.ab +2 -1", "1 .M N... 100644 100644 100644 abc def docs/a b.md", "2 R. N... 100644 100644 100644 abc def R100 new name.md\told.md", "u UU N... 100644 100644 100644 100644 a b c fight.md", "? loose.md", ].join("\n")); assert.equal(p.ahead, 2); assert.equal(p.behind, 1); assert.deepEqual(p.changed, [{ path: "docs/a b.md", state: ".M" }, { path: "new name.md", state: "R." }]); assert.deepEqual(p.conflicts, ["fight.md"]); assert.deepEqual(p.untracked, ["loose.md"]); }); test("git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once", () => { const { clone, remote, other, root } = fixture(); writeFileSync(join(clone, "vault", "SS-002.md"), "id: SS-002\n"); writeFileSync(join(clone, "vault", "SS-001.md"), "id: SS-001\nchanged\n"); writeFileSync(join(clone, "jason-draft.md"), "not sage's\n"); const r = gitCommit(root, { message: "Open SS-002 from Discord", rels: ["vault/SS-002.md", "vault/SS-001.md"], requester: "Jason" }); assert.match(r.hash, /^[0-9a-f]{7,}$/); assert.equal(r.pushed, true); assert.equal(r.pushError, null); assert.deepEqual(r.paths.sort(), ["vault/SS-001.md", "vault/SS-002.md"]); const log = sh(clone, ["log", "-1", "--format=%an%n%ae%n%cn%n%B"]); assert.match(log, /^Sage\nsage@mosaicstack\.dev\nSage\nOpen SS-002 from Discord\n\nRequested-by: Jason/); assert.equal(sh(remote, ["rev-parse", "main"]), sh(clone, ["rev-parse", "HEAD"]), "origin has the commit"); assert.deepEqual(sh(clone, ["status", "--porcelain"]).split("\n"), ["?? jason-draft.md"], "Jason's own file is untouched and not committed"); sh(other, ["pull", "-q", "--ff-only"]); assert.ok(existsSync(join(other, "vault", "SS-002.md"))); }); test("git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work", () => { const { clone, root, config } = fixture(); const ok = { message: "m", rels: ["README.md"], requester: "Jason" }; assert.throws(() => gitCommit(root, { ...ok, message: "" }), refusal(GIT_REFUSAL.BAD_MESSAGE)); assert.throws(() => gitCommit(root, { ...ok, message: "x".repeat(501) }), refusal(GIT_REFUSAL.BAD_MESSAGE)); assert.throws(() => gitCommit(root, { ...ok, rels: [] }), refusal(GIT_REFUSAL.BAD_PATHS)); assert.throws(() => gitCommit(root, { ...ok, rels: ["--all"] }), refusal(GIT_REFUSAL.BAD_PATHS)); assert.throws(() => gitCommit(root, { ...ok, requester: null }), refusal(GIT_REFUSAL.NO_REQUESTER)); assert.throws(() => gitCommit(root, { ...ok, requester: "a\nb" }), refusal(GIT_REFUSAL.NO_REQUESTER)); assert.throws(() => gitCommit(root, ok), refusal(GIT_REFUSAL.NOTHING), "unchanged file"); assert.equal(sh(clone, ["diff", "--cached", "--name-only"]), "", "nothing left staged after the refusal"); writeFileSync(join(clone, "README.md"), "# Signals\nmore\n"); writeFileSync(join(clone, "staged-by-jason.md"), "x\n"); sh(clone, ["add", "staged-by-jason.md"]); assert.throws(() => gitCommit(root, ok), refusal(GIT_REFUSAL.INDEX_DIRTY)); sh(clone, ["reset", "-q"]); // Through the tool set: the path fence and the requester come from state. const tools = createToolSet(config); const r1 = tools.call("git_commit", { root: "ss", message: "m", paths: ["README.md"] }); assert.equal(r1.ok, false); assert.match(r1.text, /requester of this message is unknown/); tools.setRequester("Jason"); for (const paths of [["../x"], [".git/config"], ["vault"], ["missing.md"], [42]]) { const r = tools.call("git_commit", { root: "ss", message: "m", paths }); assert.equal(r.ok, false, JSON.stringify(paths)); } assert.equal(sh(clone, ["rev-list", "--count", "HEAD"]), "1", "no commit happened"); const r2 = tools.call("git_commit", { root: "ss", message: "Explain the signals", paths: ["README.md", "README.md"] }); assert.equal(r2.ok, true, r2.text); assert.match(r2.text, /committed [0-9a-f]+ on main for Jason: README.md; pushed to origin/); assert.equal(r2.details.pushed, true); assert.equal(r2.details.requester, "Jason"); assert.deepEqual(r2.details.paths, ["README.md"]); assert.equal(sh(clone, ["rev-list", "--count", "HEAD"]), "2"); const r3 = tools.call("git_commit", { root: "stack-docs", message: "m", paths: ["README.md"] }); assert.match(r3.text, /unknown root/); }); test("git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6)", () => { const { clone, remote, root } = fixture(); sh(clone, ["remote", "set-url", "origin", join(clone, "..", "nowhere.git")]); writeFileSync(join(clone, "a.md"), "a\n"); const r1 = gitCommit(root, { message: "a", rels: ["a.md"], requester: "Carmen" }); assert.equal(r1.pushed, false); assert.match(r1.pushError, /could not push/); assert.equal(sh(clone, ["rev-list", "--count", "HEAD"]), "2"); sh(clone, ["remote", "set-url", "origin", remote]); writeFileSync(join(clone, "b.md"), "b\n"); const r2 = gitCommit(root, { message: "b", rels: ["b.md"], requester: "Carmen" }); assert.equal(r2.pushed, true); assert.equal(sh(remote, ["rev-parse", "main"]), sh(clone, ["rev-parse", "HEAD"])); assert.equal(sh(remote, ["rev-list", "--count", "main"]), "3"); }); test("git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged", () => { const { clone, other, root } = fixture(); assert.equal(gitPull(root).updated, false); writeFileSync(join(other, "from-other.md"), "o\n"); sh(other, ["add", "from-other.md"]); sh(other, ["commit", "-q", "-m", "other"]); sh(other, ["push", "-q", "origin", "main"]); const p = gitPull(root); assert.equal(p.updated, true); assert.notEqual(p.from, p.to); assert.ok(existsSync(join(clone, "from-other.md"))); // Diverged: a local commit and a different remote commit. writeFileSync(join(clone, "local.md"), "l\n"); sh(clone, ["add", "local.md"]); sh(clone, ["commit", "-q", "-m", "local"]); writeFileSync(join(other, "from-other.md"), "o2\n"); sh(other, ["commit", "-q", "-am", "other2"]); sh(other, ["push", "-q", "origin", "main"]); assert.throws(() => gitPull(root), refusal(GIT_REFUSAL.NON_FF)); assert.equal(sh(clone, ["rev-list", "--count", "HEAD"]), "3", "nothing merged"); assert.throws(() => gitPush(root), refusal(GIT_REFUSAL.PUSH_FAILED), "a diverged push is rejected, never forced"); sh(clone, ["reset", "-q", "--hard", "origin/main"]); sh(clone, ["pull", "-q", "--ff-only"]); // Dirty: origin changes a file that has local edits. writeFileSync(join(other, "from-other.md"), "o3\n"); sh(other, ["commit", "-q", "-am", "other3"]); sh(other, ["push", "-q", "origin", "main"]); writeFileSync(join(clone, "from-other.md"), "mine\n"); assert.throws(() => gitPull(root), refusal(GIT_REFUSAL.DIRTY)); assert.equal(readFileSync(join(clone, "from-other.md"), "utf8"), "mine\n", "the local edit survives"); }); test("git: push pushes the named branch only and reports up to date", () => { const { clone, remote, root } = fixture(); assert.equal(gitPush(root).upToDate, true); writeFileSync(join(clone, "c.md"), "c\n"); sh(clone, ["add", "c.md"]); sh(clone, ["commit", "-q", "-m", "c"]); const r = gitPush(root); assert.equal(r.upToDate, false); assert.equal(r.pushed, true); assert.equal(sh(remote, ["rev-parse", "main"]), sh(clone, ["rev-parse", "HEAD"])); }); test("git: no token value or token path ever reaches a git argument list; outputs are masked and capped", () => { const { clone, root, tokenFile } = fixture(); const argvs = []; const spy = (cmd, args, opts) => { argvs.push([cmd, ...args]); return spawnSync(cmd, args, opts); }; writeFileSync(join(clone, "d.md"), "d\n"); gitCommit(root, { message: "d", rels: ["d.md"], requester: "Jason" }, { spawn: spy }); gitPull(root, { spawn: spy }); gitPush(root, { spawn: spy }); gitStatus(root, { spawn: spy }); assert.ok(argvs.length > 5); for (const argv of argvs) { const joined = argv.join(" "); assert.ok(!joined.includes(FAKE_TOKEN), joined); assert.ok(!joined.includes(tokenFile), joined); assert.ok(!/--force|-f\b|--tags|--all/.test(joined), joined); } assert.equal(maskSecrets(`fatal: unable to access 'https://sage:${FAKE_TOKEN}@github.com/x/y': 401`), "fatal: unable to access 'https://@github.com/x/y': 401"); assert.equal(maskSecrets(`remote: ${FAKE_TOKEN} was used`), "remote: was used"); assert.equal(maskSecrets(`github_pat_${"B".repeat(40)} x`), " x"); }); test("git: the credential helper answers get over https from a private file and nothing else", () => { const { tokenFile } = fixture(); const call = (action, input, env) => spawnSync(process.execPath, [CREDENTIAL_HELPER, action], { input, encoding: "utf8", env: { PATH: process.env.PATH, [GIT_TOKEN_FILE_ENV]: tokenFile, [GIT_USERNAME_ENV]: "sage", ...env } }); const ok = call("get", "protocol=https\nhost=github.com\n\n"); assert.equal(ok.status, 0, ok.stderr); assert.equal(ok.stdout, `username=sage\npassword=${FAKE_TOKEN}\n`); assert.equal(call("store", "protocol=https\nhost=github.com\n\n").stdout, ""); assert.equal(call("erase", "protocol=https\nhost=github.com\n\n").status, 0); const http = call("get", "protocol=http\nhost=github.com\n\n"); assert.equal(http.status, 1); assert.match(http.stderr, /only https/); assert.ok(!http.stderr.includes(FAKE_TOKEN)); const noFile = call("get", "protocol=https\nhost=github.com\n\n", { [GIT_TOKEN_FILE_ENV]: "" }); assert.equal(noFile.status, 1); chmodSync(tokenFile, 0o644); const loose = call("get", "protocol=https\nhost=github.com\n\n"); assert.equal(loose.status, 1); assert.match(loose.stderr, /mode 0600/); assert.equal(loose.stdout, ""); chmodSync(tokenFile, 0o600); writeFileSync(tokenFile, "short\n", { mode: 0o600 }); const bad = call("get", "protocol=https\nhost=github.com\n\n"); assert.equal(bad.status, 1); assert.equal(bad.stdout, ""); }); test("git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes", () => { const { clone, root, config } = fixture({ vault: true }); const tools = createToolSet(config); tools.setRequester("Jason"); assert.deepEqual(enabledToolNames(config).slice(-5), [...GIT_TOOL_NAMES, RESERVE_TOOL_NAME]); // reserve_id appends the registry line and returns the id. const bad = tools.call("reserve_id", { root: "ss", prefix: "XX", title: "t" }); assert.match(bad.text, /prefix must be one of/); assert.match(tools.call("reserve_id", { root: "ss", prefix: "SS", title: "a\nb" }).text, /title must be/); const res = tools.call("reserve_id", { root: "ss", prefix: "SS", title: "Maestro naming" }); assert.equal(res.ok, true, res.text); assert.equal(res.details.id, "SS-002"); assert.match(res.text, /reserved SS-002; the registry line is in docs\/ID-REGISTRY.txt/); assert.match(readFileSync(join(clone, VAULT_REGISTRY), "utf8"), /SS-002\t[^\t]+\tsage\tMaestro naming\n$/); // A record that fails the validator refuses the commit; nothing staged. const w = tools.call("write_file", { root: "ss", path: "vault/SS-002.md", text: "id: SS-002\nINVALID\n" }); assert.equal(w.ok, true, w.text); assert.match(w.text, /commit it with git_commit/); assert.deepEqual(readdirSync(join(clone, ".vault-locks")), [], "the write's lock was released"); const c1 = tools.call("git_commit", { root: "ss", message: "Add SS-002", paths: ["vault/SS-002.md", VAULT_REGISTRY] }); assert.equal(c1.ok, false); assert.match(c1.text, /record validator failed/); assert.match(c1.text, /SS-002.md: id not registered/); assert.equal(sh(clone, ["diff", "--cached", "--name-only"]), ""); // Fixed record: the commit goes through with both paths and pushes. tools.call("write_file", { root: "ss", path: "vault/SS-002.md", text: "id: SS-002\ntitle: Maestro naming\n" }); const c2 = tools.call("git_commit", { root: "ss", message: "Add SS-002", paths: ["vault/SS-002.md", VAULT_REGISTRY] }); assert.equal(c2.ok, true, c2.text); assert.equal(c2.details.pushed, true); assert.deepEqual(sh(clone, ["show", "--name-only", "--format=", "HEAD"]).split("\n").sort(), [VAULT_REGISTRY, "vault/SS-002.md"]); // Another owner's lock refuses a write and a commit that touches the path. mkdirSync(join(clone, ".vault-locks"), { recursive: true }); writeFileSync(join(clone, ".vault-locks", "vault__SS-002.md.json"), JSON.stringify({ owner: "jason", path: "vault/SS-002.md" })); const w2 = tools.call("edit_file", { root: "ss", path: "vault/SS-002.md", old: "naming", new: "name" }); assert.equal(w2.ok, false); assert.match(w2.text, /locked by another contributor: vault_lock: vault\/SS-002.md is held by jason/); assert.match(readFileSync(join(clone, "vault", "SS-002.md"), "utf8"), /naming/, "the file is untouched"); writeFileSync(join(clone, "vault", "SS-002.md"), "id: SS-002\ntitle: Maestro name\n"); const c3 = tools.call("git_commit", { root: "ss", message: "rename", paths: ["vault/SS-002.md"] }); assert.equal(c3.ok, false); assert.match(c3.text, /locked by another contributor: vault\/SS-002.md is locked by jason/); assert.equal(sh(clone, ["diff", "--cached", "--name-only"]), ""); // Without the protocol, reserve_id is not offered and writes take no lock. const plain = fixture(); assert.ok(!enabledToolNames(plain.config).includes(RESERVE_TOOL_NAME)); assert.throws(() => reserveId(plain.root, { prefix: "SS", title: "t" }), refusal(GIT_REFUSAL.NO_PROTOCOL)); });