Saltar al contenido
Changelog.es

Changelog

What changed in each release, for the people who install multivac. The reasoning behind each decision lives in .multivac/changes/archive/ — that is the brain’s own ledger, written for the people changing the tool.

Every entry names the invariant it made true, because a rule quoted without its ID does not bind.

This file is the only copy. The documentation site mounts it rather than keeping a second one (MV-78).

0.15.0 — 2026-10-02

multivac keeps no code graph: a brain that declares grapher: still loads, and doors takes back what multivac itself wrote, but nothing builds, refreshes, gates, commits or names a graph any more, and graphify’s or codegraph’s files stay until you remove them. Three more behaviours changed in a way that can newly refuse what used to pass: a brain that was loading, an opsx brain’s slugs and archives, and a change close whose claims cite no stated row. And change new now writes a claim an older multivac cannot read. Read the first four items before upgrading.

Changed — read before upgrading

  • Graphers are gone (MV-153). multivac no longer declares, installs, builds, refreshes, gates, commits or names a code graph, and no longer renders .multivac/ecosystem.json.
    • An old config loads. grapher, grapher_auto, graphers and repos.<key>.grapher are read only to say they are ignored: verify and doctor print one line naming them, whatever they hold. Deleting them is a config edit, so it needs an open change (MV-97).
    • init --grapher and change close --no-grapher are unknown flags, refused with exit 2.
    • doors takes back what multivac wrote: every post-edit refresh hook it put in .claude/settings.json (and nothing else there), and .multivac/ecosystem.json. Commit the removal.
    • What the vendors wrote stays until you remove it. doctor names, per repo it may write in, graphify’s graphify-out/, .graphifyignore, skills, door section and hooks, and codegraph’s .codegraph/ and codegraph.json, each with the vendor’s own removal; and the door warns, where graphify’s skill or hooks remain, that its graph is no longer refreshed. Those paths are not code, so the removal commits on any branch. graphify’s hook-guard keeps telling agents to ask a graph that only goes staler until you run its uninstall.
    • Doors are shorter: 747–1,156 bytes on a consumer door, 1,308–2,276 on a brain door, and each edit runs one hook where it ran two.
    • What you lose: one-call transitive reach (codegraph impact, graphify path); one-hop questions cost the same or less by grep.
    • Fourteen rows retired — twelve that were law and two never enacted: MV-50, MV-52, MV-58, MV-59, MV-61, MV-62, MV-90, MV-103, MV-131, MV-134, MV-139, MV-140, MV-148 and MV-149 (the last two proposed). Thirty-four rows amended.
  • The SDD lives in the brain alone (MV-146). A top-level sdd: used to reach every declared repo: repos sync installed the vendor into each code repo (spec-kit: 30 files, about 240 KB), each code repo’s door carried the SDD’s step block into every session, change plan refused until every code repo had written its own constitution, and the first apply committed the vendor’s files onto each code repo’s branch — while the specs of every change were written in the brain. The SDD is now installed, gated and printed in the brain only. A code repo’s door carries one line saying where the brain’s SDD runs and where that repo’s code belongs.
    • A config can now refuse to load. A tool named in repos.<key>.sdd, a top-level sdd: the brain’s own entry contradicts, and an SDD name multivac does not know make every command in the brain exit 2, naming the key and the fix — except doctor, doors and init, which report it and exit 1, as they do for any config they cannot load. repos.<key>.sdd takes only none, which exempts that repo’s code from the change gate. The config edit itself needs an open change (MV-97). A consumer whose mounted brain carries such a config is not refused: verify and count print the refusal as one line, which gates only under verify --strict.
    • An SDD declared only on the brain==code entry now governs its siblings' code. The code gate asks which SDD governs a repo’s code — the brain’s, unless the repo says sdd: none — so a sibling that resolved no SDD of its own, and so was never gated, is gated now (MV-137).
    • Code repos equipped by an earlier release keep their install, and the carry committed .specify/** on their change branches. Nothing removes it for you: while the brain declares an SDD, doctor names it as a leftover with its removal, repos check appends it to that repo’s line, and neither fails over it. For spec-kit, git rm -r .specify and specify integration uninstall <key> for its skills; for OpenSpec, delete openspec/ and the openspec-* skills and opsx commands its init wrote. Every known SDD’s install paths are not code in any repo, so the removal commits on any branch.
    • A change open across the upgrade whose specs sit in a code repo is gated in the brain alone: plan and apply name the file they found in the code repo and do not read it, and close refuses rather than pass over a task list it cannot read. Move the slug’s directory into the brain, or pass --no-sdd.
    • An older multivac keeps cascading the SDD against this config. Set requires: to the release carrying this change to have it say so (MV-86).
  • opsx runs through openspec’s own CLI (MV-147). Its steps printed chat commands spelled as only two harnesses spell them, and the scaffold installed a command body per workflow per door — 19 files, about 270 KB, for doors: [agents, claude] — whose propose body told the agent to stop after planning and whose archive body merged by hand. The steps are now openspec’s own terminal verbs, which every harness runs alike: openspec new change <slug> --json and the status / instructions loop at new and plan, openspec instructions apply --change <slug> --json at apply, and openspec archive <slug> --json after the merge. They need openspec 1.5.0 or later, the scaffold 1.7.0.
    • No command body is installed. The scaffold is openspec init --tools none --no-animation ., which writes openspec/config.yaml and two .gitkeeps whatever the doors. In a brain scaffolded earlier, doctor names the bodies that init left, with the git rm -r that removes them; that removal is not code, so it commits with no change open.
    • The archive’s question goes to you. The printed archive carries no --yes, --skip-specs or --no-validate, so on a change with spec deltas openspec answers archive_confirmation_required and writes nothing; the guide printed under the step has the agent show you the deltas and offer openspec’s own answers. Each step’s line names the question the removed bodies asked on it, and a guide under it at its point carries the rest, never in the door.
    • A slug openspec refuses is refused first. change new (exit 1) and roadmap add (exit 2) refuse a slug outside openspec’s grammar — lowercase letters and digits in runs joined by single hyphens — or the reserved archive, whatever sdd_auto and --no-sdd say. Fix_Auth used to open a change whose first printed command failed.
    • A land step’s proof is read in the brain checkout alone. An archive run in the change’s worktree after the merge used to pass change close and never reach the brain; it is now refused by name.
    • Close stages a merged main spec only when it carries the merge. A spec under openspec/specs/ is staged when it holds, whole and under the same name, every ADDED and MODIFIED requirement block of the archived delta, both read as openspec reads them, fenced examples included; one that does not — after an archive made without merging, or a draft of yours — is named dirty and left out of close’s commit. Only spec.md is merged, so a file kept beside a delta never stages yours.
    • The ledger’s reason is true under --json: --yes archives over the tool’s own refusal, and under --json says nothing. change apply prints openspec’s “Archive would refuse this delta” note from a passing validator, and still passes.
    • What the agent’s own openspec calls send and write is disclosed by version. The entry’s opt-outs reach only the two commands multivac runs, the validator and the scaffold; the calls the agent makes follow the agent’s own environment, where OPENSPEC_TELEMETRY=0 or DO_NOT_TRACK=1 stops them sending.
  • A claim is its row’s ID, and close checks what it cites (MV-150). change new and the scaffold now write claims: [<ID>]; the row states the rule. A legacy { id, statement } keeps working and is written back unchanged — do not convert a change in flight.
    • An older multivac reads an ID-only change as absent: its change commands refuse the file, and its verify, roadmap and doors skip it without a word, so the change’s claims stop pending and a brain==code hook running it blocks the change’s own commits. Upgrade every consumer’s pinned and global multivac before the brain writes one, and set requires: in the brain’s .multivac/config.yml to the release carrying this change (MV-86), with or after that release.
    • change close refuses a claim that cites no stated row the change adds, touches or retires, a claim anchored only in its change file, an added row already in the law, and a proposed row it anchors but never claims — all in one run, before anything is written. --abandon refuses a change whose own proposed row states a rule. verify’s finished line, land and plan say what close will refuse, and a row the brain’s channel states and this checkout lacks is named as a pull.
    • A key inside a claim other than id and statement is refused by every command that rewrites the change file, and named by every other reader.
    • Close’s reservation release reads the anchors verify parses; a fresh claude-door brain’s first unused reservation is now released.

Fixed

  • A run reads the checkout that holds where it was asked (MV-151). verify took its starting directory for the root. From a subdirectory of a brain or a consumer it exited 2 advising multivac init ., which would git-init a second brain there; from a brain change worktree’s subdirectory it judged the main checkout’s law and passed a row the worktree added; a monorepo subproject holding its own mount lost its verdict below its own directory, and a brain that declares mount: docs/brain was never found. verify now resolves the root first — the nearest .multivac/config.yml up to the git toplevel, asked with the ambient GIT_* dropped, else the consumer its change worktree or mount names, else the stale pin or the door — and a full report printed away from that root names it in one root line. count, doctor, doors and roadmap root the same way, so doctor no longer reports “config invalid” from src/ and roadmap no longer lists “empty” there.

    • A brain change worktree reads the change’s own siblings. Each sibling is read in the change’s own worktree for it, else where the main checkout reads it; one missing both ways names the main checkout for repos sync, which typed in the worktree cloned the ecosystem into .multivac/worktrees/. doctor reads them the same way.
    • A mount is judged as the brain it is. Every brain gate holds inside a consumer’s mount; a sibling missing beside it names the host once and never advises repos sync, which typed there clones the ecosystem into the consumer.
    • Nothing is advised about another directory. Outside a work tree nothing is walked; below a toplevel no brain governs the run says so instead of advising init; a submodule of a governed repository names it; a git refusal such as dubious ownership is quoted, exit 2, not read as “no repository”; and every command refused below a brain names that brain: no .multivac/config.yml in <dir> — it is inside the brain at <brain>; run this there.
    • The version floor reaches every run that reads the brain. The notice reads the brain of the root the command reads, so a requires: floor is heard from a subdirectory and from a consumer; a consumer hears only the floor, since the record’s fix, doors --adopt, runs in the brain.
  • change close lands the brain’s specs whatever the flags say (MV-146, amending MV-144). Under --no-sdd or sdd_auto: false the spec directory was left untracked; after openspec archive the moved-from openspec/changes/<slug>/ and the merged openspec/specs/<cap>/ were left out of the commit and named dirty; --abandon dropped the specs entirely. Close and abandon now stage the slug’s directories, deletions included, and the main specs an archive merged into.

  • A slug inside a parent directory’s name no longer owns the parent (MV-146, amending MV-144). Slug spec took specs/ and slug change took openspec/changes/, so the carry and close staged every change’s directory. Close stages the file an archive merged into, never its capability’s whole directory, so an unrelated edit there stays named dirty.

  • Two open changes no longer share spec-kit’s feature pointer (MV-146). /speckit.plan run for one change wrote into the other’s directory, whose gate then passed. change plan and change apply point .specify/feature.json at the slug’s directory and say so when it named another.

  • A brain==code entry keyed other than brain gates its close (MV-146). Its proofs were looked for in a worktree named after the root’s scope, never its key, so a task list carried onto the branch was never found and close passed over an open task.

  • A code repo’s specs/ is code (MV-146). The SDD’s artifact directories were not code in every repo that resolved it; they are not code in the brain alone now. Every known SDD’s vendor state stays not code everywhere.

  • Under sdd_auto: false, nothing claims a gate (MV-146). The door, flow.md and doctor said the lifecycle refuses without the step artifacts, which it does not do with the automation off.

  • A consumer door names the law at the path that repo can open (MV-143). It printed .multivac/invariants.md in repos where the law lives under the mounted brain.

  • What the SDD writes in the brain lands with the change (MV-144). The commit change close prints staged the change file, the law, the graphs and the governance graph, and left the spec, the plan and the task list untracked — the very artifacts every gate in the lifecycle had demanded. It now also stages what the brain’s own status reports under the directories the closing slug owns, deletions included, so an archive that moves a directory lands in one commit. A dirty file the change did not write is named, never staged.

  • change apply equips a repo it just made before carrying its artifacts (MV-144). The vendor’s init ran over a checkout whose files the carry had just taken out of it, and wrote a second copy. Cloning or creating, equipping, then branching and carrying are now three steps in that order.

  • A fresh brain’s first commit passes its own gate whatever it declares (MV-142). The directories that are not code came from the doors multivac projects, so a brain declaring openspec and no grapher was refused over .agents/**, a directory openspec’s own init had just been told to create. They now come from the declared scaffold’s integrations, each recording what it writes, measured per tool version.

Changed

  • One line when nothing is off (MV-151). verify --quiet, or MULTIVAC_QUIET=1, prints one line — the summary first, then the header, each plain read with its ref, sha and age, the enact answer and the change the code lands in — and the whole report, byte for byte and both streams in their order, the moment anything is off: a leg that is not ok, a staged law, any config line, a warning, an unparsable open change file, an anchor naming no row. A read that is not plain and a pin that does not gate print beneath the line. The git hooks doors writes export MULTIVAC_QUIET=1, so a clean commit says one line; re-run doors to regenerate them. A binary older than this ignores the variable and prints in full. The claude harness hooks keep their commands byte for byte: verify reads the harness’s own hook payload, so a session start is quiet and a post-edit run follows the edited file into the checkout that governs it — an edit in a change worktree from a session at the main checkout is now judged there. Those two harness behaviours were read from the harness binary with a simulated payload and await a live session.

  • Cursor reads AGENTS.md, so multivac projects nothing else for it (MV-143). The .cursor/rules/multivac.mdc stub was a second door that could disagree with the canonical one. One doors run removes multivac’s block from it and deletes the file when nothing but multivac’s own frontmatter is left; lines you added there survive, and the file with them stays.

  • Fresh spec-kit installs get skeleton templates (MV-146). When the lifecycle’s scaffold installs spec-kit in the brain, multivac writes three short skeletons to .specify/templates/overrides/, which every spec-kit template resolver reads first, so the specify, plan and tasks steps read about 4 KB of template per change instead of about 18 KB. Only into an absent directory, only from spec-kit 0.9.4 on, only where the installed core template still carries every heading the skeleton keeps, and never again after that run. doctor names an enabled preset those files outrank, with the override to delete. An existing install gets none.

  • The constitution’s revisit says to commit no Sync Impact Report (MV-146). It said to prepend one, which spec-kit itself stopped saying at 1.0.6: from there the report is review scratch, removed before the amended constitution is committed. The amendment record is git and the change that amended it.

  • The instruction to run the chain through is printed once per lifecycle point (MV-146, amending MV-95), after the last step, instead of after every step. The brain door ends each step with its proof path or [ungateable] and leaves the reason to the lifecycle, doctor and flow.md.

  • The change body cites its spec (MV-146). change new says the why, the design and the tasks go into the SDD’s files. change close appends one line, Specified in `<dir>/` (<sdd>)., unless the body already names the directory; nothing else in a body is written.

  • change plan says where code goes (MV-146). For a change naming a code repo, it says the steps run from the brain checkout and code is written only in the change’s worktrees.

  • The skill carries what no command prints (MV-152). Its body states the rhythm, where you are, seven rules and where to read what; each reference keeps the judgement no command prints and names what prints the rest — the brain door, the lifecycle’s lines and refusals, verify’s lines, mvac help anchor, mvac help verify, the seed report. A change session loads 9,897 bytes of skill instead of 31,138, and the pack is 25,453 bytes instead of 56,542. A session in a code repo is told to write code in the worktree change apply printed and to run change, roadmap and seed in the brain’s own checkout, never in the mount. The skill, the root documents and the pages no longer list two git shims of three, name a Cursor rules file as written, promise no install step to forget, say close commits a code-less brain’s specs, or call the pre-commit hook the earlier and stricter check. Run multivac doors to re-project the skill.

0.14.1 — 2026-09-17

Fixed

  • A change closed on its own branch passes verify --range. Landing a change through one merge request puts its archive commit on the same branch, so at the head of the range the change file is archived, and 0.14.0 refused the whole request as code outside a change. A release is exactly that. The range reader now accepts a change archived at the head and not at the base. A change already archived at the base still admits no code. If you upgrade: a merge request refused this way passes once its pipeline runs 0.14.1. (MV-142)
  • A fresh brain’s first commit passes the code-in-change check. In a brain initialised with an SDD and graphify, the commit init tells you to make was refused over .gitignore and .agents/. .gitignore, and the harness directories that spec-kit, OpenSpec and graphify install into (.claude/, .agents/, .cursor/ and the rest), are no longer code. In .github/, only the door file is exempt; workflows are still code. (MV-142)

Documentation

  • The merge request job in the code-in-change reference now covers a branch that closes its own change. multivac’s own pipeline runs that job. (MV-142)

0.14.0 — 2026-09-16

Added

  • multivac repos check. It reports, per declared repo and offline, whether the path is the declared clone, and whether the repo’s declared SDD and graph are installed and committed, its project document written, and its mount in place. It exits 1 when any repo is not. It runs git and reads files, and never a vendor tool, so CI can run repos sync --shallow && repos check with no tool installed. (MV-132)
  • Code reaches a repo only through a change. Where a repo declares an SDD with automation on, verify refuses a commit or a local merge that changes code outside the branch of an open change declaring that repo. multivac’s own files, the doors, and the SDD’s and grapher’s files are not code. A new pre-merge-commit hook runs the same check on a local merge, and verify --strict --range <base>..<head> --branch <name> runs it over a merge request in CI, catching a commit made with --no-verify. doctor says it binds only where the forge requires that pipeline and protects the default branch. --no-sdd is recorded in the change file as sdd_skipped. If you upgrade with an SDD declared: run multivac doors to install the new hook, then commit code on a change’s branch, not on main. A release is a change too: CHANGELOG.md and package.json count as code. (MV-137)
  • .multivac/ecosystem.json. The brain keeps a graph of its own declarations: repos, law rows (without their text), anchors and changes, and how they link. Nothing machine-dependent goes in it, so the same declarations render the same bytes. init, doors and every lifecycle commit in the brain write it; verify says when it is stale, without gating. graphify reads it with --graph .multivac/ecosystem.json, and the doors name it. (MV-139, MV-141)

Changed

  • repos sync and the lifecycle install what they declare. repos sync runs each repo’s SDD init and first graph build, and exits 1 naming a tool it would run and cannot find. change plan and change apply do the same after they clone or create a repo, and change new refuses a missing SDD before writing anything. If you upgrade: a repos sync in CI where a declared tool is not installed exited 0 and now exits 1; use repos sync --shallow, which writes nothing. (MV-129)
  • The SDD’s init follows your doors. spec-kit is initialised with the integration for each declared harness door (specify integration install for the rest), and OpenSpec with openspec init --tools for all of them. graphify’s own project install runs for each declared door, and an absolute path it writes into a hook is rewritten to the bare graphify. (MV-130, MV-131)
  • change apply carries the SDD files onto the change branch. Files your SDD wrote before apply, such as specs/<n>-<slug>/ or an uncommitted .specify/, are copied into the worktree, committed there and removed from the checkout, so the merge no longer stops on an untracked copy. A modified tracked file, or an ignored one, is refused before anything moves. (MV-133)
  • change land commits the graph on the change branch. Before its push line, land refreshes each ready repo’s shared graph in the change’s checkout and commits it there. change close refreshes before it prints the archive commit and puts a changed brain graph in it. Building, installing, gating and refreshing graphs during a change now covers only the brain and the repos the change names; repos sync still covers every repo. (MV-134)
  • A project document is judged against its tool’s own template. A constitution is still the template when it matches the sha256 spec-kit recorded, or still carries one of the template’s own tokens outside an HTML comment; the refusal names the token. A written document that cites [1] or [API] no longer fails. change new asks for an unwritten document, saying the principles come from you. OpenSpec’s context: in openspec/config.yaml is reported, never gated. The door says an active row outranks a project document. (MV-135)
  • Session zero, in order. init ends with the whole of session zero: declare repos: before the first commit, sync, both flows with the one that fits marked, the project document, then the law, doors and verify. seed reports each repo’s graph and project document. The skill runs sync, seed, the open questions, the project document, the law and then doors. (MV-136)
  • verify in a consumer’s change worktree takes the brain, the change and the repo from the worktree’s path, instead of exiting 2 over an empty mount. (MV-138)
  • The graph’s promises match what happens. The door says “refreshed after your edits” only where a declared harness has a post-edit hook. That refresh now runs in the repository of the file you edited, when it holds a graph. flow.md and doctor say that asking the graph cannot be checked. Where graphify’s own install covers a declared door, the door points to graphify’s section instead of repeating its verbs. (MV-140)
  • repos and doctor report clone state. A repo is cloned, missing or invalid (a plain directory, a directory inside another repository, a repository with no commit, or a different remote), not present because its path exists. If you upgrade: a script that parsed present from multivac repos or doctor needs cloned. (MV-141)
  • The documentation describes this flow end to end. (MV-141)

0.13.0 — 2026-09-16

Changed

  • init installs the SDD and grapher it declares. init --sdd speckit --grapher graphify used to write both names into the config and the door and install neither: a fresh repo got no .specify/ and no graphify-out/. init now runs the tool’s own init and the graph’s first build in the brain, the same way change does, and runs neither where the tool is already installed. (MV-128)
  • init refuses a tool it would run and cannot find, before writing anything. It exits 1, before git init, naming the binary, the install line and the vendor’s repository. Nothing is required for a tool it would not run: one already installed, opsx, which has no init on record, or an SDD under sdd_auto: false. If you upgrade: a script or CI job that runs init --sdd speckit or init --grapher graphify where the tool is not installed exited 0 and now exits 1. (MV-128)
  • A graph’s first build adds its ignore lines first. For graphify that is .graphifyignore, keeping .claude/, .multivac/, .specify/, specs/ and openspec/ out of the graph, and graphify-out/* with !graphify-out/graph.json in .gitignore. Missing lines are appended and yours are left alone; a rule of yours that still ignores graph.json is named. This applies wherever a first build runs, init or change. Measured on graphify 0.9.29, a fresh brain’s first graph went from 223 KB, most of it spec-kit’s own files, to under 3 KB. (MV-128)
  • Step 0 of init commits only what init wrote. It printed git add -A, which in a repo with code also committed your uncommitted work as “multivac init”. It now lists the paths init created or changed, leaving out the tools’ per-checkout outputs. (MV-128)

0.12.0 — 2026-09-16

Changed

  • repos sync mounts the brain in every repo it gates. doors installs hooks that read the law through the brain mount, and until now nothing made that mount: it was a manual git submodule add in each repo. repos sync now checks the mount in every declared repo on disk, on every run. A repo with no mount gets the brain added as a submodule, and a directory that already holds a copy of the brain is adopted without downloading it. An empty mount is filled. A mount that holds something other than a brain, or records a different url, is reported and left alone. The mount is left staged: multivac never commits in your repos. A read-only repo is skipped, and a mount git refuses is quoted by its cause and exits 1 without stopping the rest. doctor, doors and the consumer door now name multivac repos sync as the fix; doctor says when a mount is staged but not committed, and doors lists the repos it gave hooks with no mount to read. (MV-127)
  • brain_url is a new config key: the address everyone clones the brain from. repos sync writes it into each repo’s .gitmodules, so multivac never takes it from your origin, which may be an ssh alias only your machine knows. init writes it commented out, with the origin it found as a suggestion. Without it, repos sync makes no mount, and names the key once when a repo needs one. If you upgrade: add brain_url, then run repos sync; it stages a mount in every writable repo on disk that lacks one, for you to review and commit there. multivac 0.10.0 and 0.11.0 refuse a config that uses the key, and 0.9.0 and older ignore it. (MV-127)
  • verify no longer locks the commits of a repo whose brain it cannot reach. In a repo where multivac installed hooks and no brain is mounted, it exited 2 on every commit and advised multivac init ., which would create a second brain. It now warns that nothing was verified, names multivac repos sync, and exits 0, as the hooks already do on a machine with no multivac. It recognises the hooks by the header line multivac writes, including hooks it installed into another tool’s hooks directory. A repo without those hooks still exits 2, as does a stale .brain mount. If you upgrade: repos locked this way commit again as soon as the multivac their hooks run is 0.12.0; nothing needs re-projecting. (MV-127)
  • The documentation site names no law IDs. Its pages explain behaviour in plain words; IDs stay in the law table, the brain’s records and this changelog. The pages changed in 0.11.0, and this release makes it a rule. (MV-126)

0.11.0 — 2026-09-14

Changed

  • A repo you do not own is read, never written. repos.<key>.managed: false is a new key, and a clone git reports shallow is treated the same way: multivac scaffolds, builds, refreshes and projects nothing there, and no gate looks for a file there; repos sync still clones and fetches it. doors, doctor and repos mark it read-only, and repos sync --shallow says so as it clones. change plan and change apply refuse a change that names one, with exit 1, before cloning, branching or bumping anything. managed: must be a boolean, and the brain’s own entry cannot say false. If you upgrade: a shallow clone you already have is read-only now (git fetch --unshallow makes it writable), a spec kept in one no longer proves a step, and a door or hook projected there earlier stays. multivac 0.10.0 refuses a config that uses the key, and 0.9.0 and older ignore it and write there. (MV-125)
  • sdd: and grapher: resolve the same way everywhere, and none means none for both. A repo’s own value wins, then the top-level one, and the brain’s own repos: entry counts, so a grapher: there is honoured. If you upgrade: a repo’s own sdd: now gates change plan, apply and close with no top-level sdd:, where they passed silently. Each adapter is judged in the repos that resolve to it and all must pass, including repos the change does not name (--no-sdd skips one run); one whose repos are all uncloned refuses until repos sync. A spec in an sdd: none repo no longer proves a step, grapher: none is no longer reported as unverified, and graphers.none is refused. (MV-122)
  • A vendor counts as initialised only when its own state file says so. spec-kit needs .specify/integration.json with integration_state_schema 1 and a non-empty installed_integrations, OpenSpec openspec/config.yaml or .yml, graphify a graph.json that parses as JSON, codegraph .codegraph/codegraph.db. A hand-made .specify or a 0-byte graph no longer counts. doctor reports installed, missing, partial or unevaluable, with the reason, where it said artifact ok. A partial graph is rebuilt, and change close refuses while it stays partial; the SDD init is never run over a partial install, because a re-run can revert edited files. If you upgrade: a .specify with no valid integration.json (spec-kit before 0.16.4 was never measured), or an openspec/ with no config file, reads partial and is warned about, not refused; commit before you run the vendor’s init there yourself. (MV-124)
  • change close wants a shared graph committed, and never codegraph’s. If you upgrade: a graph.json that is staged but not in HEAD no longer passes; the refusal names the add and the commit, and doctor says NOT COMMITTED where it said UNTRACKED. codegraph’s database is local: built in each checkout, never asked for in HEAD, and the door says not to commit it. (MV-124)
  • init --grapher is checked. A name that is neither a verified grapher nor declared under graphers: in a readable config already at the target exits 2, listing the known names, before anything is created. --grapher none is refused too: leave the flag out. (MV-122)
  • Vendor opt-outs are set on every run multivac makes. OpenSpec gets DO_NOT_TRACK=1 OPENSPEC_TELEMETRY=0 and codegraph DO_NOT_TRACK=1 CODEGRAPH_TELEMETRY=0 CODEGRAPH_NO_DOWNLOAD=1, in the scaffold, the validator, the build, the refresh and the post-edit hook; OpenSpec 1.13.0 otherwise sends telemetry from every command, the gates’ openspec validate included. They override your shell, so a DO_NOT_TRACK=0 you export does not reach them, and a codegraph missing its platform bundle is told not to download it; a run you type yourself is still yours. Re-run doors so Claude Code’s post-edit hook exports codegraph’s and reaches node_modules/.bin, and so the projected skill describes this release’s gates. (MV-121, MV-123, MV-124)

Fixed

  • An adapter’s binary is found the same way everywhere. doctor, doors and change close’s graph checks looked on PATH alone, so a tool installed with npm i -D read as missing. Every surface now looks on PATH, then in that repo’s node_modules/.bin, and the post-edit hook reaches it too. On Windows it tries PATHEXT’s extensions, untested there. A missing binary is named per repo with its adapter, install line and vendor repository, where it said is not on PATH. (MV-123)
  • The spec-kit scaffold no longer needs the claude CLI. It passes --ignore-agent-tools; without it and without claude, spec-kit 1.0.6 exits 1 and writes nothing. (MV-123)
  • A failed vendor command is quoted by its cause. The scaffold, the validator and the refresh quoted the first three lines, which were spec-kit’s logo or a Python traceback header. They drop banner lines now and quote a traceback’s closing exception, else the lines naming an error, a refusal, a denial or something not found, else the last lines, at most three. (MV-123)
  • doctor no longer says the SDD init reaches the network. With the network denied, spec-kit 1.0.6’s and OpenSpec 1.13.0’s inits exit 0. The init stays out of verify, doctor and doors because it writes the vendor’s files into the tree. (MV-121)
  • 0.10.0’s changelog entry and two site pages are corrected. The entry now names MV-105 to MV-119, the rows that release made law, and the install guide and hooks reference give the runner order MV-92 set: this repository’s own build, then the multivac it declares, then mvac on PATH. (MV-120)

0.10.0 — 2026-08-24

Changed

  • A red verify now reaches the agent in Claude Code, and after an edit the agent has to read it. doors now writes mvac verify 2>&1 || true at session start, where nothing may block, and mvac verify >&2 || exit 2 after an edit, where a red law, a config the edit broke and a missing binary all come back to the model. This is a forced read in the same turn, not a revert. Re-running doors rewrites an existing bare mvac verify entry in place, and a mvac verify --strict you wrote stays yours. Only the claude target carries a hook config, and a brain that never re-runs doors keeps the old, mute command. (MV-112)
  • A commit that deletes the law is refused. A row that is active at HEAD and absent from the index is now refused with exit 1, and so is an index that removes the law file. Retiring a row stays allowed, and a proposed row may still disappear. Where the question cannot be answered — no HEAD, an unreadable index, a consumer checkout — the check says so and does not gate. (MV-107) Retired rows are covered too: deleting one is refused. (MV-117)
  • An unknown config key or --sdd name is refused by name. A key multivac does not know is refused, naming the key it is near when only case or separators differ and listing the known keys otherwise; --sdd= exits 2 instead of 1. If you run more than one version: from this release on, a key a newer multivac knows is refused by an older one. And init --grapher is still not checked: an unknown name is written to the config, and doors and doctor report it as not verified. (MV-114)
  • Exit codes keep the documented contract in four more commands. seed, repos, repos sync and roadmap sync exited 1 on a config that will not load; they exit 2 now. doors and doctor remain the documented exceptions; init, which now refuses a broken config, exits 1 too, though the reference does not list it; and bare roadmap still exits 0 on a broken config because it never reads one. Bare doctor now exits 1 when an anchor in the law does not parse, and prints a law line naming them. (MV-118, amending MV-85)

Fixed

  • --flag=value is accepted again. A long flag written with = is matched by its name now. A declared valued flag whose value is missing, or is itself a flag, is refused naming the flag. And change and count read the same refusal as every other command, so change land <slug> api no longer exits 0 having recorded nothing. (MV-105)
  • The commit gates read the commit being made, git commit -a included. Under -a they saw fewer paths than the commit contained, which walked the enactment and config checks straight past the most common way to commit; under a pathspec commit they saw more. The index git hands the hook is honoured now, for the repository the hook runs in and no other. (MV-106)
  • multivac no longer takes “it is there” to mean “it is ours”. doors wrote a stub door such as .github/copilot-instructions.md whole; it writes only its managed block there now, and frontmatter only when it creates the file. The commit hook’s first choice of runner — the repository’s own build — now requires the repository’s package.json to name multivac. A hook that mentions multivac only on a comment line no longer reads as wired; a mention inside a quoted string on a real line still reads as wired, by design. multivac’s own shim beside husky is recognised and rewritten, so strict_pre_push arms there. And init . no longer downgrades a strict pre-push shim to a plain one. A hand-edit to a hook multivac wrote is still lost when the hook is regenerated. (MV-108)
  • Hooks and projections survive the environments they land in. In a linked worktree the repository’s own lefthook or hand-written gate never ran; the shim looks under --git-common-dir now. A declared grapher refresh ran through a shell after an edit but was split on spaces at change close; both run it through a shell. One mangled managed file no longer ends a multi-repo run: that file gets a notice naming it, and a file with two marker pairs is refused rather than half updated. doctor reads a shim instead of checking that it exists. (MV-115)
  • A broken config no longer disarms a strict gate through init, and a commented requires: floor is read. init now refuses a config it cannot load, with exit 1, though it may already have run git init in a directory that was not a repository. And requires: ">=0.4.0" # floor for CI was ignored; a trailing comment no longer hides the floor, so a binary below it gets its notice, and a malformed floor with a comment is named in its own notice. Neither notice changes an exit code. (MV-114)
  • The anchor engine reads one way. A bare [:digit:] outside a bracket expression is refused now — an absent leg written that way was green forever. The dialect gate also refuses (?…), lazy quantifiers, backreferences, \0 and alphabetic escapes with no ERE meaning. A line in a CRLF file is a line — a $-anchored or exact-line leg never matched there; a lone \r is still text. count reads the bytes verify reads, so a count=N pinned from it agrees with the gate. (MV-109)
  • Self-heal stays inside the file kind a leg names, and a symlink is not file text. A present leg whose pattern moved is healed by rewriting its glob, and a candidate must now share the include’s trailing extension; an include with no trailing extension, or ending in a brace group, keeps only the .multivac/ fence. Tracked symlinks and gitlinks are listed by neither reader. A leg whose glob names only a symlink now matches no tracked file; point it at the link’s target. (MV-116)
  • An SDD proof names exactly one feature. The spec-kit proof was specs/*<slug>*/spec.md. The artifact language has no wildcard now: <n> is one run of digits, so the proof is specs/<n>-<slug>/spec.md and an openspec archive is <n>-<n>-<n>-<slug>. When more than one directory proves one step, the gate refuses and names them. A directory of the right shape written by hand still proves the step. (MV-113)
  • The lifecycle commits what it wrote and reports what happened. change land --landed commits its status bump now. --abandon names repos that already landed instead of printing nothing landed. change new refuses a slug whose archive already exists. roadmap sync passed --label to gh issue edit, so every GitHub update failed; it passes --add-label now, and a failed update or close is printed as NOT updated or NOT closed, followed by the command that failed, and a failed update still prints the not found in the tracker line after it. A GitHub issue still accumulates status labels, and close’s spec-kit ledger still passes when the artifact never existed. (MV-110)
  • change close loses nothing on the way to the archive. It verified claims against every anchor it could see, including anchors written inside the change file, then archived that file — which the parser never reads. It refuses now, naming the claim. Two branches closing the same slug could overwrite an archived record; the archive write itself refuses now. And a frontmatter key the lifecycle does not know, which its next rewrite of the file drops, is named whenever a command reads the change file, as a notice rather than a refusal. --abandon still reports rather than refuses over landed repos, on purpose. (MV-117)
  • A law row whose statement contains a pipe is read correctly. The trailing columns are read from the end of the row now, and a row missing them claims nothing rather than borrowing a neighbour’s cell. (MV-119)
  • init tells you to commit what it wrote, and the skill stops teaching what the tool does not do. A fresh brain’s first change new is refused until the scaffold is committed; init’s closing steps now start with that commit. The shipped skill taught an arrow-edge landing_order the parser refuses, told the interview to write into the brain door’s managed block, which doors regenerates whole on every run, and said change apply re-projects doors, which it does not. All three are corrected. (MV-111) The site’s session-zero guide gave the same managed-block advice and is corrected too. (MV-118)
  • The brain door no longer implies the graph stays uncommitted. Its line for a declared grapher said “never staged or committed by multivac”; it now reads “refreshed after your edits; multivac never stages it, but you can track and commit it”. (MV-50)

0.9.0 — 2026-08-18

Changed

  • The CLI is parsed by citty. Every command declares what it takes once, as data; citty parses that declaration and the refusal reads the same one, so adding a flag is one edit instead of two that can drift. Nothing a user types behaves differently: same flags, same positionals, same refusals, same exit codes, and the existing suite passes unedited. The refusal is not delegated — measured, citty parses an undeclared flag into a key nobody declared and hands it to the command, which is the silence MV-85 exists to end, so the check still runs before the parser. --help stays this tool’s own. Third runtime dependency, named in the law and in the constitution before the package was added: citty is one package with zero dependencies of its own. (MV-104, amending MV-02 and MV-85)

0.8.0 — 2026-08-18

Added

  • A declared grapher’s graph has to be in the repository, not just on disk. change close refuses while a declared, present root keeps its artifact untracked, naming each root, the path and the git add that ends it. An artifact matched by an ignore rule gets its own message, because git add does not fix that one. Existence was half the question: a graph that lives in one working tree passes the old gate and helps nobody who clones the repo, where the door still tells every agent to ask it. multivac stages nothing — the gate says, you commit — and doctor reports the same state per root without gating on it. (MV-103, amending MV-90)
  • multivac roadmap sync projects the change files to a declared tracker. tracker: gitlab or tracker: github, using that vendor’s own CLI so multivac holds no credentials and adds no dependency. One way, always: the change files are the source, and nothing the tracker says ever reaches them — closing an issue by hand closes nothing, and the next sync restores it. The identity is the issue number, recorded in the change file, so a title edit cannot duplicate anything. Only labels in multivac’s own namespace are written, and none it does not own is ever removed. An absent CLI refuses rather than reporting success, and it never runs from verify, doctor or doors. One issue per change for now. (MV-99)
  • The ritual now arrives with candidates, all commented out. init wrote it as a bare comment, and facing a blank page most people write nothing — so the closing step printed nothing forever. Candidates are drawn from what you declared and nothing is asserted on your behalf: a commented line never prints, so a fresh brain still prints nothing at close. Only things no check could decide are seeded; a declared grapher contributes none, because its work is automatic and already gated. (MV-98, amending MV-34)
  • Changing .multivac/config.yml now needs an open change. It decides which repos exist, which adapters bind and which gates run — all as load-bearing as a law row, and all editable in a commit with no explanation and nobody noticing. A staged modification is refused while no change is open; creating one is free, because a brain has to start somewhere and init is the only thing that writes it. Any open change satisfies it, including one opened for that edit. (MV-97)
  • multivac doors now writes .multivac/flow.md — one page sorting what your declarations oblige into what happens unasked, what refuses, and what no tool can check. A fresh brain had no place answering “what did I just sign up for”: the law is dozens of rows, the ritual starts empty, and everything else scrolled past. Every row is rendered from the adapter registry and your config, so it cannot describe behaviour the tool does not have, and each unprovable step carries the adapter’s own reason verbatim. It cites no invariant identifier — those are per-brain, so a generated one would be wrong in every other ecosystem. Derived and rewritten whole; anything you write outside the markers survives; it binds nothing and says so. (MV-96)
  • change apply now says what can be worked at once. Repos in one stage of landing_order have no ordering dependency by the declaration’s own meaning, and apply hands back an isolated checkout for each — it knew both and said neither. The line carries its boundaries: never the same file twice at once, and never the law. And each printed SDD step now says to run the chain through rather than waiting to be asked, with the opt-out on the same line; stopping is for a question the tool itself raises, not for permission. Both are printed and never checked — no artifact proves either happened. (MV-95)
  • change new and change apply now say when the brain is behind. The pin each repo records for the mounted brain was already compared to its channel, offline, and reported by verify — but nothing said it at the moment work starts, which is the only moment “refresh before you start” is an instruction rather than a fact about the past. It reports and never refuses: offline a pin behind its channel means somebody landed work or nobody fetched, and refusing on the second reading would fail an ordinary morning. staleness: block still gates in verify, unchanged. (MV-94)
  • A door in a code repo now names the ecosystem it belongs to. It carried the law and the mount and nothing else, while the brain’s door listed the repos and every adapter — and the consumer door is the one most sessions start from. It now carries the list of declared repos with the one you are in marked, brain named as the handle anchors use, the adapters that apply to that repo, and an optional one-line role per repo. The mount refresh is now the first instruction rather than the second of four bullets, and carries its reason: the pin stays where the last commit left it, so a present mount is not a current one. (MV-93, amending MV-61 and MV-87)

Fixed

  • The door a fresh brain gets is the door the tool maintains. init carried its own copy of the brain door and the copy had drifted: the projected door gained the graph block and the ecosystem’s repo list, the copy gained neither. So the first file an agent reads — the only one it has before anybody runs a second command — never named the graph it tells you to ask before reading the tree. There is one rendering now: init writes the bytes doors writes, and running doors straight after init changes nothing. A config that will not load leaves the door alone rather than being written from nothing. (MV-102, amending MV-101 and MV-91)
  • The door no longer names an SDD the config does not declare. On a brain whose config exists but declares no sdd:, init --sdd speckit reported the flag as not in the config — correctly — and then wrote a door gating through speckit anyway, because the door resolved the flag as a fallback. doors, which reads the config alone, deleted that block on its next run: two commands, one repo, two different doors, with nothing on disk recording the choice or explaining the revert. The door now takes the config’s answer whenever a config exists, and a flag is the declaration exactly once — on the first run, which is what writes the config. The report, the refusal for a disagreeing flag and first-run behaviour are all unchanged. (MV-101, amending MV-91)
  • The commit hook now runs the multivac that governs the repository, not whatever is installed on the machine. The shim tried mvac on PATH first and the repository’s own build last; the order is now the exact inverse — this repository’s build, then the multivac it declares, then PATH. A global install a year behind was enforcing an older law table against a repo that pinned something else, silently. Nothing runnable still never blocks a commit, and the repository’s own gates still run first. (MV-92)
  • pnpm test no longer runs tests whose sources are gone. tsc does not delete output for a deleted source, so the compiled suite accumulated whatever any branch ever built: measured after a rebase, five failures from a file absent from that branch — and silently, a deleted test that keeps passing. The build clears its output before compiling, and a test asserts the property rather than the script, so it fails whatever the cause. (MV-92)
  • A pasted link to the site now renders as a card. The head served an empty description, a whitespace og:type, and no og:image on any page — and a scraper that finds no image does not fall back to a favicon, it renders a bare URL. Documentation pages carried twitter:title and no og: tags at all. Every page now carries a description and a 1200×630 card image, the Twitter card is the large format, and robots.txt names the sitemap it already had. No template was written: the machinery existed and had never been given anything to render. (MV-100, amending MV-77)
  • And the rest of the discoverability surface. Sitemap entries now carry a lastmod derived from git rather than from a date field nobody would maintain; the empty /categories/ and /tags/ pages are no longer generated or indexed; languageCode replaces a locale: key Hugo never read; and the head gains og:site_name, og:locale, the card’s dimensions and alt text, and JSON-LD saying what kind of thing each page is — a developer tool, not a shop — which the theme’s microdata could not express. Every added tag is one the theme does not emit, because a duplicate is two sources for one fact.
  • The site’s discoverability is now checked on every commit, not fixed once. The check walks the content tree recursively, so a section added later is covered without anybody remembering — a listed set would pass forever while the next section shipped without a description. It states its ceilings: it cannot judge whether a description is good, and cannot decide whether a given network renders the card.

0.7.0 — 2026-08-18

Fixed

  • Re-running init no longer writes a door that disagrees with the config it kept. init --sdd speckit followed by init --sdd opsx left sdd: speckit in .multivac/config.yml while AGENTS.md said the features gate through the opsx SDD, with nothing saying they disagreed — and the door is the first file an agent reads. The config is authoritative once it exists: a flag naming a different adapter is now refused, with both values named and both ways forward stated, and the refusal writes nothing at all. A flag that agrees is reported as redundant; a flag naming an adapter the config declares none of is reported with how to make it stick. Everything else about a re-run was already correct and is unchanged. (MV-91, amending MV-70)

Added

  • A declared grapher now obliges a graph. change close refuses while any declared, present repo has no graph, naming every one of them in a single message with the command that builds one there. Declaring grapher: used to oblige nothing — the tool ran where it could and every failure was a notice that kept going, so a change could close with four repos ungraphed and say nothing. The cost was invisible by design: the door tells agents to ask the graph before reading the tree, so a missing graph never failed, it degraded into agents grepping. A root whose binary is not on PATH refuses too, because a gate that cannot be evaluated must not pass. The gate asks whether a graph exists and never whether it is current. --no-grapher skips it for one run, grapher_auto: false turns it off for good, and --abandon is exempt. (MV-90, amending MV-87)
  • The refresh at close reaches every declared repo, not only the ones the change named. A repo moved by another change, a merge or a sync was left describing a tree that was gone.
  • The door projected into each declared repo now names the graph — the tool, its artifact and its own query verbs — resolved with the grapher that applies to that repo. Only the brain’s door carried this before, so an operator entering the ecosystem through a code repo got an agent that never learned a graph existed.
  • A change can now exist before it starts. planned is a new change state in front of open, carried by the same .multivac/changes/<slug>.md file with the same schema, plus a horizon of now, next or later. That is the whole ordering model — no dates, no estimates, no rank, no dependencies between items. Keeping the roadmap in the change files rather than in a second list is the point: two lists describing the same work drift apart, and whichever one the tool does not read becomes fiction. (MV-89)
  • multivac roadmap lists planned changes grouped by horizon, nearest first, and reports how many changes are actually in flight under its own label so intention is never read as progress. multivac roadmap add <slug> "<title>" [--horizon now|next|later] records one. It reserves no invariant id, opens no branch and creates no worktree.
  • change new on a planned slug promotes the file that is already there — status flips to open, the invariant id is reserved at that moment, and the body is carried across byte for byte, so the prose written when the idea was young survives into the change that implements it. change new on a slug nobody planned behaves exactly as before: the roadmap is never a precondition, and no command anywhere refuses an operation because its subject was not recorded first.
  • plan, apply, land and close refuse a change that has not started, and name change new as the step that comes first.

A roadmap of any length never delays a release: a planned change contributes no pending claim and no landed repo, so verify --strict can never name it as unclosed. Had it counted, the first entry recorded would have blocked every release for as long as the roadmap was not empty.

0.6.0 — 2026-08-17

Packaging — read if you verify what you install

  • This release and the ones after it carry no provenance attestation. npm generates one by default under trusted publishing, and its registry verifies a bundle only from a gitlab.com shared runner; this project’s shared-runner minutes are exhausted and it publishes from a self-hosted one, so the release stopped on that check with a 422 after the tarball was already built and signed. Publishing is still trusted publishing over OIDC — the credential is short-lived and no long-lived token exists in the pipeline — but a tarball from here has no verifiable link back to the commit that built it. It comes back the day npm supports self-hosted runners, or the day this project runs on a shared runner again. (MV-88, amending MV-68)

Fixed

  • A declared adapter reached whichever repo answered first, and stopped there. Measured in an ecosystem of six: one sibling repo that somebody had run specify init in by hand months earlier made the scaffold return before it touched anything — the brain included — because presence was asked of the whole repo list and answered by the first hit. doctor printed speckit: artifact ok over five unequipped repos for the same reason, and the project-document check accepted that one repo’s constitution as the ecosystem’s, so the gate passed on a document five repos had never seen.

    One rule replaces the three reads: a root is the brain plus every declared repo present on disk, and one root’s artifact never answers for another’s. The scaffold runs the tool’s own init in every root that lacks it and stays silent in every root that has it, a root whose init fails is reported in the tool’s own words while the roots after it are still attempted, doctor prints one line per root, and the project-document gate asks each root where the tool is installed and names every one that fails. (MV-87, amending MV-75 and MV-76)

  • The mark showed the old colour in the terminal. The lamp in flight was still amber while the site had long since moved to the acid accent, and lockup.svg still carried the old hex — three copies of one identity disagreeing about its own colour. The accent is now defined once and gated twice: by the TTY check for ordinary output, by its own argument for the drawing init prints. (MV-33)

Added

  • A declared repo that no change has touched gets its graph. The code graph was only ever built for repos a change explicitly touched, so a repo had to be worked on before it could be navigated — backwards for an agent that reads the graph in order to do the work. The lifecycle now builds it once in every declared, present repo that has none, using the adapter’s own create where it declares one; refreshing an existing graph is unchanged. (MV-87)

  • A repo can declare its own sdd:, mirroring the per-repo grapher: the config already carried. The literal none means that repo has no spec-driven flow: never scaffolded, never gated, never reported as lacking anything — out of scope, not a gap. (MV-87)

  • init closes on a call to action instead of a full stop. It used to end with “load the multivac skill”, leaving the reader to discover session zero — that there are two flows and which one is theirs — from the door. It now prints three numbered steps with the branch already decided: tracked source in the repo means discovery (multivac seed), an empty repo means the interview. On a terminal the scaffolding lines are dim and that closing line is acid; piped output and NO_COLOR are byte-identical to before.

0.5.0 — 2026-08-17

Fixed

  • The site advertised a version nobody could install. It deploys on every merge to main and its badge was held equal to package.json — but both of those sit at HEAD, so the pair agreed with each other while neither knew what the registry serves. The release sequence is bump → merge → the site deploys → tag → publish, so every release advertised an unpublished version for as long as that took; and a release abandoned after the bump merged would have advertised it forever, with the rule calling it correct.

    The site now states no version at all: the badge renders a parameter the release pipeline sets from the last git tag, which is a published version because publishing refuses a tag that disagrees with the manifest. What is not written cannot drift.

    Deployment is unchanged — site-only corrections still reach readers on merge, without a release being cut — and the pipeline’s stages were reordered so a release’s site follows the publication it describes and does not run if that fails. (MV-77, tightening MV-84 to forbid version literals on the site outright)

  • change close --abandon no longer releases a reserved invariant ID that an anchor names. It archived the change first and then released against an empty anchor set, so the condition the law states — release only when no anchor names the ID — was never evaluated on that path. The ID could return to the pool with a live reference pointing at it, and the next change new would hand it out: MV-26’s collision by another road. Both close paths now read the anchor set before archiving. (MV-45)

  • No lifecycle command sweeps a tree with git add -A. change apply did, when creating a greenfield repo — harmless in itself, a repo made seconds earlier holding one file, but the law said nowhere, and the leg meant to hold that claim matched only a comment saying “never add -A” while the real call spelled the flag differently and stayed invisible. (MV-46)

Documentation

  • Eight claims an external audit found overstating their code are corrected, and the ninth is recorded as examined and accurate. Two moved the code, three withdrew a clause that described something removed, two gained the ceiling they were missing, and two documents stopped contradicting the law: CONTRIBUTING.md told contributors to add an entry marked unsupported, which MV-28 forbids, and DESIGN.md described a ripgrep matching engine and a commit-sha-keyed cache that were designed and never built. (MV-21, MV-31, MV-51, MV-56, MV-57)

0.4.0 — 2026-08-17

Added

  • A brain now records the version it was deliberately brought to, in .multivac/projected.yml, and every command tells you when your binary disagrees — in colour, with the command that closes the gap. Upgrading the binary never upgraded a brain: npm i -g multivac@latest replaces the projector, not the doors, skills, hook shims and harness settings it already wrote. Nothing is refused over a version; enforcement degrades, it never locks you out.
    • mvac doors --adopt re-projects and records. Bare mvac doors re-projects and leaves the record alone on purpose, so the notice survives a run made for an unrelated reason rather than going quiet without the upgrade having been taken.
    • requires: ">=X.Y.Z" in .multivac/config.yml, hand-authored, declares the minimum your team will trust. A binary below it gets the loudest notice. The tool never writes this field.
    • Existing brains have no record, which reads as an absence and not as an ancient version: you get the mildest notice, once, with the command to fix it. (MV-86, amending MV-29)

Changed — read before upgrading

  • A command now refuses an argument it does not declare and exits 2, where four of the nine used to accept one and carry on. This can refuse a command line that worked before, so read the list:

    • mvac doctor --sttrict used to run the report without the strict assertion and exit 0 — a pipeline going green over a gate nobody had asserted was armed. It now refuses and names the flag.
    • mvac doctor <dir> and mvac doors <dir> used to discard the directory and act on the working one. Neither command declares a directory. They now refuse rather than answering about somewhere else.
    • mvac seed --anything used to ignore the flag; mvac init --badflag exited 1 where the reference documents 2.
    • mvac count --anything already exited 2 but printed only its usage; it now names the argument it did not understand.

    What each command takes is its --help (MV-69), and the refusal is measured against exactly that. If a script of yours passes an argument a command never read, it was already having no effect — the difference is that you are told. (MV-85, strengthening MV-29)

0.3.0 — 2026-08-17

One behaviour changed in a way that can newly refuse a repository that was passing. Read the first item before upgrading.

Changed — read before upgrading

  • The anchor scanner skips a line only when that line carries a complete anchor comment — the opener the grammar defines, and the --> that closes it. Until now it skipped any line containing the substring @anchor anywhere, so const evade = "user.name"; // @anchor in your source was invisible to every leg: an absent tombstone over that pattern reported green at exit 0, and the same line without the seven-character suffix broke it. Those lines are now scanned. If your repository has source, fixtures or docs mentioning @anchor outside a real anchor comment, a tombstone or a count=N ratchet over them can start refusing — that is the defect being fixed, not a regression. mvac count '<the leg>' shows you the new match set before you decide.

    The ceiling, stated rather than implied: a line carrying both the opener and --> still hides, anywhere on the line, in any file, whether or not it is a well-formed anchor. The scanner tests shape and never grammar, because the fixtures that quote whole anchors inside string literals must keep hiding and a forgery is byte-identical to them in shape. Closing that needs something which is not a test on one line’s shape. (MV-82)

Documentation

  • The site sets its own type, and serves it from its own origin. Two variable faces — one for human language, one for machine output — with the width axis carrying the distinction, both stored in the repository with their licences. No typeface is fetched from a third party at page load, which is the same promise verify makes about the network. Nothing an installed tool does changes. (MV-83)
  • Twenty-one statements across the site, DESIGN.md and the shipped skill were found to contradict the code and were corrected. The install page told readers the binary prints 1.0.0, that the package is private: true and that it is unreleased — none of which has ever been true of a published multivac. Others miscounted the door registry, the command list, the door kinds and the leg states, or quoted output the tool does not print. (MV-84)
  • The site’s pages now carry exactly one version string, and a test holds it equal to the manifest. The three pinned version sites — tag, manifest, badge — were already held equal by MV-68 and MV-77; nothing covered a version somebody typed into prose, which is how 1.0.0 survived on the install page under a law table with 83 anchored rows. (MV-84)

0.2.0 — 2026-08-17

Five of these change behaviour for anyone already running the tool. Read this section before upgrading.

Changed — read before upgrading

  • verify --strict now refuses a change that is finished but not closed — every declared claim resolving and every declared repo recorded landed — and names it with the command that fixes it. Until now such a change was indistinguishable from one opened seconds ago: both reported pending, and pending never blocks. That grace hid fourteen claims in this repo for weeks. If you carry finished-but-unclosed changes, --strict will start refusing them, in CI too. change close <slug> is the whole fix. A change declaring no claims is never finished, and staleness of any kind is untouched. (MV-80)

  • verify now refuses a commit that flips a law row to active alongside the code that row anchors. A rule and its evidence arriving under one hand is a rule nobody reviewed on its own. Commit the law file alone, then the code. The check reads the index against HEAD, so it answers only while a commit is being composed, and says so when it cannot answer rather than passing silently. (MV-81)

  • change land now reads whether the work landed from the channel ref instead of commit containment, which a squashing forge defeats every time, and reports the ref, its sha and how long ago it was fetched. It offers the conclusion; recording it stays yours. (MV-80)

  • change plan now refuses while the SDD’s project-level document is missing, empty, or still the unfilled template its own tool shipped. A repo that declares sdd: and has never written its constitution will start being refused where it used to pass. Staleness stays a report: a document older than the law’s newest row is named, never gated. (MV-76, amending MV-57)

  • The lifecycle now runs the SDD’s own init when its scaffold is absent — for spec-kit, specify init --here --integration <harness> --force, which reaches the network. It runs only from change, never from verify, doctor or doors, which stay offline (MV-01). This closes a deadlock: declaring an SDD in a repo where that tool has never run used to make the change that installs it unplannable. (MV-75)

  • doors now deletes files under the skill directory it projects when the source no longer ships them — including one you put there yourself, because nothing on disk records who wrote it. The prune is bounded to multivac’s own skill directory and never its parent, so a sibling tool’s skills are untouched. (MV-73)

Fixed

  • core.hooksPath is read the way git reads it. An absolute or ~-spelled value was joined onto the repo root, so the hook shims landed in a directory tree named after the machine’s filesystem while init printed the real path as the place they went and doctor called them missing from the directory they were sitting in. The enforcement gate was disarmed in silence for any repo with an absolute hooksPath — every git worktree inherits one. Every read now goes through git config --path, and directory identity is decided after resolution rather than by comparing text. (MV-79, amending MV-37)
  • The managed .claude/settings.json merge no longer replaces a hook entry it did not write. It claimed any entry whose command merely contained mvac verify, then overwrote that entry’s whole hooks array and its matcher — deleting a user’s own commands and flags. Ownership is now the individual hook, matched exactly; where that leaves an event ungated, multivac adds its own entry beside yours and says so. (MV-74, amending MV-52)

Added

  • A changelog, on both surfaces. This file is the only copy; the documentation site mounts it rather than keeping a second one. (MV-78)
  • The version the site advertises is pinned to the version the package declares. (MV-77)
  • In multivac’s own repository, the committed copy of the skill its harness reads, .claude/skills/multivac/, is held to the skills/multivac/ the package ships by a test — same files, same bytes. (MV-72)
  • doctor now states, in its own report, that who enacts a law row cannot be checked by this tool — identity is not a fact on disk — and names where it is enforced instead. Declared ungateable with its reason rather than left absent from the law. (MV-81)
  • Adoption and Composition in the docs: the arc from init to steady state and which phase buys what, and why spec-driven tools and code graphers are built on rather than competed with.

0.1.1 — 2026-08-16

Fixed

  • Enumeration counts each tracked file once. A tree mid-merge keeps three index entries per conflicted path, so every match inside such a file was counted three times: a count=2 leg reported found 6 and advised ratcheting to 6 — advice that, followed, would have written a corrupted number into the law over an unrelated merge. Runs taken mid-merge now say so instead of judging a tree nobody will commit. (MV-71)

Added

  • --help on any command prints that command’s own flags and arguments. Five of nine commands previously printed only a one-line description; init --help said nothing about [dir], --provider, --sdd, --grapher or --quiet. Where a flag’s legal values come from the adapter registry they are rendered from it, so a new adapter cannot leave the help behind. (MV-69)
  • init --provider <name> now writes that harness’s door, skill and hooks in the same run, instead of recording a name and leaving a second command to be discovered. (MV-70)

Removed

  • aider is no longer an adapter entry. It sat there marked unsupported, carrying a note explaining at length why none of it applied, and read as support to everyone who did not open it. An unknown name already gets the list of what is supported, which is the answer that helps. (MV-28)

Documentation

  • The integrations reference says why doors is a list of doors to project and not a list of providers to choose from.

0.1.0 — 2026-08-16

Added

  • First release. npx multivac init scaffolds a brain, arms the git hooks, and writes the canonical AGENTS.md door.
  • Published by trusted publishing (OIDC) on a v<semver> tag only, and the job refuses unless the tag equals the version in package.json. No long-lived publish token exists to leak or rotate, and a release is a decision somebody makes rather than a side effect of a merge. The published tarball carries dist and skills by allowlist — this repo’s own brain, site and tests never ship. (MV-68)