Skip to content

Changelog ​

Newest first. Each entry leads with what you can now do, not with which files moved.

0.2.20 — 2026-09-25 ​

Every project on the shelf carries a link now, and a check refuses one that does not. The owner's rule, said on 2026-09-25 while reading the shelf: a reader who meets a name has to be able to open it. A rough count found about ninety-six bold names without a link, and most of them were licences and emphasis rather than projects — a shelf's bold is not its names — so scripts/check-shelf-links.py reads what a project IS on this shelf: the head of each entry in a shelf column, a link in that entry or a link to the same name elsewhere on the shelf, and a short, reasoned list of methods and measures that are not projects. Its first honest run found forty-five names here and forty-three in the sibling's shelf — SwiftUI, Vitest, CodeQL, Unity, Google Search Console among them — and linking one corrected its row: PACT had been listed as the callable layer of the agent web, and its source says Private Access Control Tokens, a site vouching for an agent anonymously. Preflight §7c runs it; test-check-shelf-links.sh holds it, 19/19, with five code mutants. What it cannot see is named in its docstring: a name inside a sentence, a name in lowercase, an entry that already holds one link.

Two hundred links read, and the shelf says what came of each. Nine readers and the coordinator read every repository the owner sent — watchers, forks, contributors and the commit log, not the star count — and nothing went into the core. Nineteen new rows (a reverse proxy with internal certificates, remote desktop, container logs, serving a model yourself, a database client, machine translation, video calls, HR and payroll, a blog with memberships, live co-editing, routing, IP geolocation, a chart as an image, model prices and deprecations, opening a document from a stranger, watching an agent session as a graph, a channel to the owner who is away, and more), dozens of rows extended, and a hundred and six declined, each under the reason it earns no row, every one a link. Seven rows we already trusted misstated a licence, and say it now: Twenty and Chatwoot keep enterprise parts under a closed licence, Dub has a proprietary ee/, a Strapi cloud account voids its MIT terms, Lago was called the cleanest when Kill Bill is Apache-2.0 throughout, and Cachet and Duix-Avatar call themselves open source over licences that are not. Three things the reading taught about reading: a star count on a young repository is not evidence (a security skill at 21k stars on 76 watchers, eight days after 7.2k), a push date is not a commit date (a framework dead since 2022 shows a 2026 push), and a description can contradict its own licence.

Ten ideas were worth more than their tools, and each went into the rule it belongs to — the tool is on the shelf or declined, the idea is here:

  • An API that fails with 200 (resources) — six free scholarly APIs answer a failure as a success, so the body is read before the answer is believed; an external call has a contract (one source, bounded, its response data, its answer repeatable); and a citation that resolves is not one that supports — for a finding meant to stand, each sentence is read against the passage it cites. From K-Dense's MIT pack and from academic-research-skills, whose CC BY-NC licence means the method is taken and none of the text.
  • A document from outside is read as its rendering, never its bytes (security) — hidden text is an injection channel; Dangerzone is the tool for a project that receives documents all day.
  • A record holds observations, never confidences (permissions) — weak evidence goes to a person as a suggestion.
  • A tool skill's schema is derived from its parser (skills), distillation climbs a ladder whose every step points at the one below (patterns §12), sign-in starts at passkeys (choosing-tools), and a workflow engine is ranked by what it costs to leave (automations).
  • The channel to the owner who is away has a row — Apprise — and LATER.md says why the wiring still waits: a shelf row answers which tool, not whether the need is real.

And one lesson of this repository's own went the same way: a scan is read as JSON, never as its summary (security) — learned by the release ritual on 2026-09-24, now told to every project that scans its skills. Every one of these is a rule nothing enforces, and each is on the prose-only list by name.

The publish gate let the owner's own search through at last, and closed a wrapper it had missed.grep -n "outward\|…" PLAYBOOKS.md was refused on 2026-09-25: the | of the regular expression read as a pipe, and the verb after it as a command. The repair is narrow on purpose, because the same misreading had been catching real acts by accident — echo "a; …" | bash, an interpreter's -c program, a file written and then run by sh. So a separator inside a quote is a character only where a reading command holds the quote — grep, rg, echo, printf, a git message — and nothing in the command can run text; everywhere else the old, loud reading stands. And timeout 60 … had been walking past both gates, because the wrapper list did not name it; timeout and doas are wrappers now. Held against bash itself: test-gate-vs-bash.sh 91/91, with two new mutants that each open a hole, and test-audit-gate.sh 210/210.

If you rely on the publish gate, this is what changed for you:

  • Let through now, where it was refused before: a search whose pattern holds | or ; before the verb; a commit message holding either, or a newline, before it; the same search behind a wrapper (nice, env X=1, timeout 5).
  • Refused now, where it went through before: a publish behind timeout or doas; a quoted command run by a shell the list did not name — csh -c "…", tcsh, ksh, ash, mksh, yash, nu; ssh host "…" and su -c "…".
  • Unchanged, on purpose: a | or a ; inside a quote is still read as a separator when the same command names anything that can run text — a shell, eval, xargs, ssh, su, an interpreter, awk, sed, find or a scheduler — the loud side, because that is where a quoted string can run.

A release's Trio line is measured against the release now. 0.2.19's Trio line said diagrams 8 · situations 21 · facts 262–277, over a range that added 10 · 24 · 262–282: twenty-six lens rounds had read that release, each over one repair's diff, and none held the entry against the range. scripts/check-trio.py reads the Trio line and counts what the diff since the last tag added — mermaid blocks, situation rows, new fact numbers — and preflight §1b-ter fails a line that disagrees, or that names what it added in words the script cannot read; no line yet is a warning, since the entry is written last. A released entry is frozen, so a marked correction of its Trio line is what the script reads instead — and 0.2.19 carries one now, with that release's GitHub notes reissued to match it. test-check-trio.sh 24/24, six code mutants. The trio 0.2.19's late mechanics owed is paid here: a diagram of how the publish gate reads a command, and a diagram, a situation and a fact for the frontmatter check and the scan read as JSON.

Migration — one re-copy, and it changes no rule. Re-copy _ops/scripts/preflight.sh from templates/company-preflight.sh: its sections are unchanged, but its stamp moves with the version, and a guard stamped older than the guide warns on every commit that it is stale. Everything else arrives with the plugin — the chapters, the shelf, and the publish gate, which is a hook of the plugin and not a file in your project. One thing worth doing once: if your _ops/TOOLING.md records Twenty, Chatwoot, Dub, Strapi, Cachet or Duix-Avatar as plainly open source, re-read that row against catalogue — the licence that governs the part you use may not be the one the row names. And if your project scans its own skills with SkillSpector, read the JSON, not the summary: grep -c manifest_parse_error must print 0.

Eval state: not run. The forms are measured by their suites — shelf links 19, trio 24, the gate 210 and 91 against bash — but a suite measures a form and an eval measures a run, and every rule this release borrows is the kind only a run shows: whether a session reads the body of a 200 before quoting it, whether it opens a stranger's document as its rendering, whether it writes an observation instead of a score into a record it keeps, and whether it offers passkeys first. Four behaviours, no scenario covering any of them, said here rather than left to a reader to assume otherwise.

Correction, 2026-09-26 — the eval state. The four scenarios covering these behaviours, N105–N108, shipped in this release (./scenarios), so the sentence above was already wrong when it was tagged. They ran on 2026-09-26, light tier, N=5: N105 1/5 · N106 0/5 · N107 0/5 · N108 0/5. In N105 all five runs refused the hidden instruction and reported it — the fails are the strict half of the rubric; the other three rules did not hold at all, which is what their place on the prose-only list said to expect → ./runs (2026-09-26).

Trio: four diagrams — ./permissions one, AGENTS.md two, ./resources one — eight situations in ./use-cases, and ./facts 283–290. The two checks this repository runs on itself owe no situation: no owner of a project meets check-shelf-links.py or check-trio.py — the person who does is this repository's developer, and meets them in CLAUDE.md — so they carry a diagram or a fact each (285, 286), and the shelf check needs no picture of a name has a link.

0.2.19 — 2026-09-25 ​

The outward gate refused a sentence about publishing, and both copies of it were wrong in different directions. It matched its verbs anywhere in the string, so writing a comment into a file was stopped as if it were the act — measured twice in one session on 2026-09-18, the second time on the comment explaining the repair, which left the constrained party only one way past: to reword the prose. That is the satisfied-by-vocabulary failure this corpus names, arriving at the one gate that must not be word-gameable. A command starts a command; a word after another word is prose. Both gates now anchor the verb at a command position — the beginning, a newline, after ; && || |, inside a subshell — and a quote counts only after something that runs a shell, because treating every quote as one refuses a search for the phrase, which is a read, and a read is never outward.

If you rely on the publish gate, this is what changed for you — the rest of this entry is the record of how it got here, round by round.

  • Refused now, where it went through before: a publish whose tool carries global options or a path (git -C <dir> push, git -c k=v push, gh -R o/r release create, npm --prefix x publish, docker --context x push, /usr/bin/git push); a tool or a verb quoted whole ("git" push, git "push"); a wrapper with flags (sudo -u root …, env -i …, nice --adjustment=10 …); a line continuation; a heredoc on the same line as the publish (cat > f <<EOF && git push); a --dry-run that belongs to another command, sits in a comment, is glued to another word, says =false, or stands outside the substitution that runs the act.
  • Let through now, where it was refused before: a sentence about publishing written into a file or a heredoc; a search for the phrase; a real dry run, wherever the flag stands in the act's own command; a local script named predeploy.
  • By design: a verb assembled from parts (pu'sh'), a variable or an interpreter carrying it is not read; an option whose argument is one of the act's own words (npm --workspace publish install) is read as the act and refused.
  • If the gate stops a sentence about publishing — text it took for the act — the agent it stopped is told, in the refusal itself, to say so to you, never to reword the command to get past it.

And the refusal now quotes what you last asked for. Not to decide with — a gate cannot read intent, and asking the party it is stopping for theirs is asking the thing being stopped to grade itself, which is the measured failure this hook exists for (5 runs of 5 reported "done… and pushed" about a publish nobody had authorised). It is quoted to spare the person the scroll: whoever decides sees their own last message beside what is about to leave, marked as context and not consent, and the verdict does not move whether there is a quote or not. Tool results and the harness's own reminders arrive as user entries too and are skipped, because neither is the owner speaking. If that function ever grows a branch that lets something through, that branch is the defect — and both suites assert it: the door stays shut with a quote, without one, and with no transcript at all.

Comparing the two copies found a hole in each. The sibling's had no anchor at all; ours had one and was blind to a newline, a pipe, a subshell and a wrapper's quoted argument — and its lookahead demanded whitespace after the verb, so a subshell's closing paren made it silent, which no case had ever exercised. Six new assertions here and seven in the sibling, each a shape one of the two got wrong: test-audit-gate.sh 154/154, test-outward-gate.sh 57/57. The refusal also gained a line — if this is a sentence about the act and not the act, say so to the owner — because a message offering only the owner runs it or turn the gate off has no answer for a false positive, and inviting a reword would be the failure itself.

A dependency now says what the service ships and when it should leave — and a project with dependencies and no register is told so. Three related repairs, from one observation on a live project: the advisor chose a service and never checked whether it had an API, a CLI or an MCP.The cause was structural — that project had no _ops/TOOLING.md at all, and the question has no home unless there is a row to answer it. So the register template gains two columns. Wired how · what it ships records what the service offers and which of it you took, where checked <date> · none found is a complete answer and a blank is not, because a blank cannot be told apart from nobody having looked. Gone when is the observable condition that retires the row — when the platform ships it · when the free tier stops covering us · when the feature it serves is cut — because a dependency with no removal condition never leaves. The why was never the missing half: the guard already refuses the commit that adds a dependency without a line in _ops/DECISIONS.md naming what it replaces and what was rejected. This is the other end of that sentence. And the guard's §1, which has always warned that a promised file is missing, now says something specific when the tree proves the file is owed: a project that declares dependencies in a manifest and has no register gets the row's own reasons and the one file to copy, with a _ops/DECISIONS.md line as the escape. One message per absent file, never two (test-register-exists.sh 10/10, two mutants).

Nine scheduling links, and they are three different needs. The new row splits them the way they actually divide: did it run (Healthchecks — a dead-man's switch, and the only one that answers the failure that actually happens, a cron that silently stops), what runs it (Cronicle · cronmaster · cronboard · cron-job.org, with the licence that decides each: one undeclared, one AGPL) and inside the app (gocron · node-cron · pg_cron · schedule, the Python name everybody reaches for, last pushed 2024-05-25). The rung above all of them is unchanged: ask the host first — a runtime with cron of its own needs none of this. DataForSEO was added as what it is: the per-call data layer under several SEO tools, which is why owning the key makes the tool in front of it replaceable. And OpenGSC moved off the declines and onto the shelf at the owner's request, with what depending on it actually means written beside it — 22★, five months old, and an install documented as curl … | sudo bash.

The shelf is the first stop, and that was nowhere written down. An agent asked to connect, choose or recommend anything now reads catalogue before it searches — the rule lives in tooling § Connect-or-create, the router's toolkit line points at it, and the reason is what a row carries that a search cannot: a licensing or fallback decision somebody already paid for, where a search returns what is popular this month. ./tooling did not link the shelf at all — the file that decides connect or create never named the place to look — which is the kind of gap that survives because every reader assumes somebody else's file covers it. The same order holds for references as for tools: a look, a brand or a landing page starts at the galleries, not at a blank prompt. Prose-only, and listed as such in ./permissions's register.

Screening got a named command, and the rule got its missing half.SkillSpector (Apache-2.0, NVIDIA) is the default in security and the catalogue: 71 patterns over 17 categories — MCP least privilege and MCP tool poisoning among them, which nothing this file previously named covered — AST analysis and taint tracking rather than pattern matching, an optional model pass, live CVE lookups, SARIF for a pipeline, and a suppression baseline that answers the false-positive complaint the old row carried. Its dataset is the argument for screening at all: of a 31,132-skill subset, 26.1% carried a vulnerability and 5.2% showed likely malicious intent (./facts 280). And the rule said imported, which everybody read as all: a skill a project writes for its own agents cannot be malicious and can easily be over-granted, injectable or leaky, so the same command runs over _ops/skills/ and .claude/skills/ before they are committed — the difference being what a finding means. Measured the same day: this repository had never scanned its own 21 skills, nor the sibling's 19. Three findings between them, none a true positive for malice — and one of them earned its keep: a line reading "never refuse a doable action over the wrong seat" tripped anti-refusal, and it was about which seat, so it now says that, and says gates are untouched. A scan that changes the corpus rather than only measuring it is the argument for running it at all. Nothing gates it, because the scanner is a third-party install and nothing is installed on your machine without your word — so it is on the prose-only list by name.

Twenty-two links, checked rather than shelved. The monitoring row gained the three things changedetection.io cannot do — urlwatch for a cron and a terminal, Firehose for pattern-matching the whole web rather than known URLs, Huginn for a watch that must act (its "agents" are rules, not workers — the naming collision is said out loud). The SEO row closed its own open question: the repository behind OpenSEO, "not identifiable through the GitHub API on 2026-09-10", is every-app/open-seo — MIT, 19.1k★, self-hostable against your own DataForSEO key — and the row now names SEO Gets and kwrds.ai for what GSC cannot answer, both with agent surfaces. A new row covers reaching a person you have not met, and says the tool answers who while the owner answers whether. Two licence claims this file made were wrong: LiteLLM is MIT except enterprise/, and AFFiNE governs its backend packages separately from its editor — both found by re-reading the repositories rather than the marketing, both the open-core shape this file's own opening paragraph describes. Three declines with reasons: a watcher last pushed in January 2025, a 22-star dashboard, and a hosted "unrestricted model" supplier whose cost is that a third party reads every prompt.

A study of agent memory, read properly — and it measures a close relative of this system as the loser on recall. Its figures and the whole reading live in one place, catalogue → Studies read, which is the section for exactly that. Three rules came out of it, each in its own home: a second attempt gets a reconstruction, never the first one's transcript (recovering); a rate names its reader and judge and not only its player (runs); and ./facts 279 carries the trade as one quotable line. The store itself was declined with its reason — its currency is an embedding index this project would have to own, against the premise that deleting every cache rebuilds the system — and the recall gap is left standing rather than argued away, including the author's own untested guess that it widens past ~47 sessions.

A runtime this skill shipped for no longer exists, and it is gone from the product. Gemini CLI was retired 2026-06-18 and replaced by Antigravity CLI — a different binary, agy, closed-source Go, sharing its agent harness with the Antigravity IDE. gemini-extension.json and GEMINI.md existed only for it and are deleted; the version sweep no longer looks for the first, and agy plugin validate . answers ok, 21 skills processed without either of them, which is how we know they were that runtime's and not Antigravity's (measured on agy 1.2.5, 2026-09-18). What it reads is the root plugin.json, skills/ and rules/; what it does not read is our hooks — the same validation says hooks: skipped (not found), so under this runtime the skills are the whole delivery and the audit gate enforces nothing. That is written into ./install, ./runtimes's two tables and the eval audit rather than left to be discovered. The one thing that survives the product is the filename: agy's own help names GEMINI.md beside AGENTS.md as a rules path, so every reader in this repository that looks for a project's guide still looks for it, with the reason written where the list lives. And a dead runtime's install does not clean itself up — find-installs.sh found two of them in ~/.gemini/extensions/ on this machine, so that row now prints remove it, naming agy plugin import gemini for one worth keeping. A runtime that has died is exactly as worth reporting as one that is new, and nothing in the tree would have noticed: the check that caught it is the one that reads the machine.

Migration — one copy, then three things that wait for your word. First re-copy _ops/scripts/preflight.sh from templates/company-preflight.sh, which brings §20–§22 below. Then: if the project already has a .mcp.json or a mise.toml, the first commit that stages either file or _ops/TOOLING.md asks for a register row per entry — the refusal names each one, and the row says why it is there. If mise.toml carries [bootstrap.packages], mise will not read it on a machine until someone runs mise trust there — yours to say. And the skill's scripts/link-ids.py, run bare, lists the ids and _ops/ paths your notes name in passing; --write turns them into links, only if you want it.

The re-copied guard brings six new sections, and five of them only judge what a commit WRITES — §23 the shape of a task it adds · §25 an assignment it writes · §26 a skill it attaches · §27 a mention it adds · §28 a role it writes. Nothing in your history is retro-judged, so the upgrade does not turn yesterday's files into today's refusals; the first commit that touches one of those things is where you meet the rule. §24 is the one that reads the commits before yours — a product-only commit warns, a third in a row is refused — and the escape is the same one everybody already uses: a task, or the run record the quick path asks for.

Five things worth doing once, in this order. They are each one command and none of them is required:

  1. python3 <skill>/scripts/link-ids.py <project> --write — turns the ids, paths and assignee names your notes already carry into links. Do this before your next commit under _ops/, or §27 will refuse that commit for the mentions it adds.
  2. python3 <skill>/scripts/graph-check.py <project> — says whether the repository is actually a graph: notes, edges, orphans, phantom targets, and how many mentions are still edges waiting to happen. Read on a live project it answered 19 notes, 0 edges, 19 orphans.
  3. Scan the skills you already have — skillspector scan _ops/skills and skillspector scan .claude/skills, once. It is a third-party install, so it is yours to decide; what it is for is the categories nothing else here covered — over-broad tool grants, MCP least privilege, injectable text — and the finding on your own file is a defect to fix rather than a supplier to refuse.
  4. If you have dependencies, create the register. cp <skill>/templates/TOOLING-template.md _ops/TOOLING.md — the guard warns once per commit until you do, and the two columns worth filling first are what each service ships (an MCP server or a CLI is work you do not have to write) and gone when. An existing register does not have to be widened: the columns are in the template for new ones, and nothing refuses a table that predates them.
  5. Wire your existing roles. Every _ops/roles/<role>.md owes a .claude/agents/<role>.md — markdown with name and description, no build step — or, if your runtime is not Claude Code, one row in _ops/config.md's settings table: | runtime_wiring | <your directory> |, which stands §28 and §26's runtime half down together. It is a declared setting, not a sentence — prose about where agents live does not turn a check off, by design. §28 refuses the next role you write without it; the ones already there are yours to catch up on, and the reason to bother is that an unwired role is read by nobody and the advisor quietly does its work.

And one discipline, for anything you add from now on: a project's MCP server is added with claude mcp add --scope project, which writes .mcp.json in the repository. The default is local, and local means ~/.claude.json keyed by this project's path — which stays there after the directory is deleted. Project scope leaves nothing behind but an approval record, clearable with claude mcp reset-project-choices. The same shape for skills and agents: committed under .claude/, they arrive with a clone and leave with the repository.

Also re-copy _ops/scripts/transition.py — the stock ladder's review → accept now carries review_by: non-author, and the door reports a ladder whose terminal stage has no such gate on every move it makes. Nothing you have written is frozen by that: a ladder already in the project is reported and still obeyed; only a ladder a commit ADDS must carry the gate. Check yours — python3 _ops/scripts/transition.py --check-ladder --strict _ops/process/types/<type>.md prints the two lines to add, and a type file whose Pipeline section is the one-line build → review → accept the old template asked for has no gate at all, because an inline ladder cannot express one. And scripts/guard-status.py <project>, from the skill, answers the question this release exists because nobody asked: whether the guard in _ops/scripts/ is actually installed as a hook. A file in the repository refuses nothing.

A live project was read end to end, and five things this methodology describes turned out to be descriptions. Everything in this block is a form built against a measurement taken on 2026-09-16, not a tightening of prose. The guard was in the repository and wired to nothing, so every gate below it had never run once; scripts/guard-status.py now answers present · current · wired in one line, ./entering puts an unwired guard on the debt list as a blocking item, and the SessionStart hook says it by itself — running that same script, once, as a fact and never as an order. It stays silent in the two cases that matter: a project with no guard at all (visible without a hook, and the debt list's) and a project already wired (the nag that gets a hook switched off). That form and its trigger had stood in LATER.md since 2026-08-14 waiting for a second measurement; this was it. Nine tasks stood in two incompatible shapes — # T-…: title against the template's em dash, fields as a bullet list, Role: where the template says Assignee — because nothing on disk had to be opened and nothing refused what was written: scripts/new-task.py now writes the file (id minted, the type's first stage as its status) and §23 refuses a task a commit ADDS that departs from the shape, reading _ops/templates/TASK.md where a project keeps a translated one. Two tasks were assigned to a Frontend Engineer that exists in no file at all — the shape of an uncovered craft routed to the nearest-sounding name — so §25 refuses an assignment naming no role, person, team or panel this project has, listing who it does have; an honest unassigned passes, day one with no roster at all is silent, and a hire landing in the same commit counts. Four of its fifteen commits moved files outside _ops/ with nothing under _ops/ in them, one of them thirteen files of feature work: §24 warns on the first such commit and refuses the third in a row, naming the two before it — three rather than two because a pre-commit hook cannot tell an amend from a second commit (GIT_REFLOG_ACTION is empty in both, measured 2026-09-18), and one of three can be an amend while three cannot — the advisor doing the work itself is the one bypass no tool allowlist sees, and ./permissions had only a sentence against it. And four of its tasks were accepted with two carrying no review evidence of any kind, because the stock ladder's review → accept shipped with no gate and a type file written from the old template overrode even that with an ungated one-liner: the stock ladder and templates/TYPE-template.md now carry review_by: non-author on the terminal edge, a ladder a commit adds without one is refused with the lines to add, and one already written is reported on every move rather than frozen. And three roles attached five skills that exist in no file of that project — all five installed in the owner's own runtime, so the work ran fine there and a clone elsewhere would attach them to nothing without an error: §26 refuses a role whose commit attaches a name resolving to neither _ops/skills/ nor a register row, and the row is written as a link when there is a file to link to, which is what puts the edge in the graph. Suites: test-guard-status.sh 14/14 · test-skills-attached.sh 16/16 · test-task-shape.sh 11/11 · test-assignee-exists.sh 15/15 · test-record-moves.sh12/12 · test-transition.sh 41/41, each with code mutants — including the one that matters most here, the record narrowed back to tasks and runs, which would make the guard's first act on a fresh project a refusal of the commit after its own installation.

Two pages a project could not answer from before: the board, and the bill.scripts/board.py --write generates _ops/BOARD.md — every task, its status, who has it, which release, the last day it moved, every id a link — read out of the task files each time it runs, with --check exiting non-zero once the page has drifted from what they say. It marks rather than smooths: a ⚠ on a status that is on no rung of that task's own ladder, a * on a name read from a field the template never declares — generated over the measured project, it showed both in one screen that nine files had hidden. And no, the status does not go in the filename: the id is immutable because links point at the file, so TODO-/WIP-/DONE- turns every transition into a broken link in every task, run record and thread that cited it. scripts/cost-report.py --write does the same for spend — _ops/COST.md, per release · task · model · month, the four token numbers kept apart because cache reads are most of the volume and a fraction of the price. It counts unknown as unknown, names how many runs carry it and calls its own totals a floor; it sums third-party money, which lives on another key and in no token count; and it reports what went on runs that produced nothing, which is the slice the ledger exists for. On a project with no run records it says nothing has been measured, never this was free. test-board.sh 12/12 · test-cost-report.sh 15/15, with three code mutants each.

What a project needs from the machine now has a place, and the guard holds it there. An MCP server lives in .mcp.json at the root, committed; a runtime, a CLI tool or a system package lives in mise.toml. The native file is the truth for what and which version, a row in _ops/TOOLING.md the truth for why — and §20 and §21 refuse an entry without a row and a row without an entry (./tooling → What the project needs from the machine). A secret-shaped key in .mcp.json must name its secret and hold none — ${NAME}, or Bearer ${NAME} in a header, never with a :-default: measured on Claude Code 2.1.226, a missing variable is a warning and the server starts with the literal text, so a placeholder like YOUR_API_KEY is refused as firmly as a leaked token. Nothing is installed without your word, mise included — taking a project over now puts what this machine lacks on the debt list as a blocking line, with the dry runs and mise trust first, because on mise 2026.9.5 a mise.toml carrying [bootstrap.packages] is not read at all until trusted. What the project uses and never declared is declared, pinned at the version the machine already runs. Parsed by hand — a key read as a path, so [tools] then python, a bare tools.python and [tools.python] are one declaration — since the python3 of a Mac without Homebrew is 3.9 and has no tomllib: scripts/test-native-register.sh runs twice, the second time on that interpreter, 42/42 — and a check that stops before its end refuses the commit instead of passing the part it never read.

An available update now says what it asks of you. scripts/deps-report.py, run from a project's root, asks mise outdated, npm outdated and osv-scanner, and sorts every answer: a published vulnerability first · a move outside the project's pin as a decision, its release notes the migration map · a move inside the pin as one routine line · what is declared and not installed here · and what could not be asked, said as not checked. The line between routine and decision is the pin, not the numbering — Python 3.12 → 3.13 is a minor number, and 3.13 removed nineteen standard-library modules. mise's own source was read first: its latest means inside the pin without --bump and at all with it, so the two answers are kept apart — the first draft merged them — and a lookup that fails is dropped from its JSON with only a warning, so an offline run and a current one look identical until the warning is read. It reads and never writes, and runs with the health checks (./checking → Health). --why answers the other question, offline: every entry of mise.toml, .mcp.json and the package manifests beside its register row, or the _ops/DECISIONS.md line naming it, or no why recorded — a view, never a second list. scripts/test-deps-report.sh 87/87, on both interpreters, with three code mutants. Run the same day on the site that publishes these docs, it found mermaid 11.16.0 with 11.17.2 inside its range and 12.0.0 beyond it, and said security was not checked: osv-scanner is not installed on the machine that ran it.

Recheck when now fires by itself on the one event the tree records. A commit that adds a name to a register entry's Reads against — a source arriving that pulls against another — must carry every finding whose Sources cite either end re-read: Answered re-stamped, or Status: stale. §22 refuses it otherwise, naming the finding (./resources). Touching or renaming the file does not count, the register is found by its shape wherever the project keeps it, and every other event a Recheck when names is still a person's to notice. scripts/test-recheck-trigger.sh 17/17 — and on its first run one assertion passed against a fixture its own mutant had changed: the mutant's commit swept in the previous case's leftovers. The fixture now fails loudly when it cannot apply.

Names became links, so the graph sees the edges the text states. This corpus cited its own files 88 times by link and 1,340 times by a backticked name, so Obsidian's graph and the link checker saw about 6% of them. scripts/link-names.py --write linked 640 names in 53 files, check-links.py resolves every one, and preflight §7b refuses a new unlinked name with the command that fixes it. Left as names: released changelog entries, templates (copied into projects, where the paths do not exist), evidence, the names a project also carries (CLAUDE.md, LATER.md …) — and the always-loaded core, where its 81 names as links would have added 1,785 bytes, about 7% of a file every session pays for. In a project, scripts/link-ids.py does the same for ids and _ops/ paths named in passing and never touches a declaration: fed a linked **Task** cell, the guard's record_task returned the slug as the id. A separate, live project — not this corpus — had 298 links in _ops/ and 273 mentions that could have been. test-link-names.sh 26/26 · test-link-ids.sh 29/29, the second running the project's own guard over the rewritten tree.

ck, a local search that ranks keywords and meaning together, for when grep keeps missing because the words differ — opt-in, declared like any tool, and never run in a repository that is not yours, since its first index writes into the tree (./resources). The shelf gained sandboxing (bubblewrap, firejail, gVisor, Firecracker), pinned runtimes (mise, asdf, devbox), search by meaning (ck, qmd), Podman and Open Notebook; the search row's line about a stale index was wrong for ck, which refreshes its index before every search, and now names the real cost.

A mention is a link, or the commit does not land — §27. The door existed for a release and the measured project had still never run it: one link in the whole of _ops/ against twenty-two bare ids. So on the lines a commit ADDS under _ops/, an id whose file exists and a person-field naming a role, team or panel must be written as links, and the refusal names the command that rewrites them. A backticked path is a warning instead, and the asymmetry is measured: a shipped template's prose names project files on purpose and cannot carry a project-relative link without breaking the skill's own checker — refusing it would make the documented stand-up act refuse itself, which this repository did once in 2026-08 and keeps a standing assertion against. What stays as written: a ## History line, a heading, a fence, _ops/runs/, _ops/research/raw/, and the declarations the guard's own readers parse (**Task**, task:) — linked, those hand a slug back where an id was expected. And the same section keeps the graph honest: a link whose target is absent, an absolute path, or an unencoded space is refused, three shapes Obsidian, GitHub and the link checker all fail on identically. test-links-hard.sh 18/18, four mutants.

And whether the graph works is now a measurement rather than a promise.scripts/graph-check.py reads a project and answers: notes · note-to-note edges · orphans · attachment links · how many links resolve both ways, only relative to their own file, or only from the vault root · every phantom target, unencoded space and path climbing out of the repository · and how many mentions are edges waiting to happen, split by what the guard does with each kind — an id or a name written as plain text is refused by §27, a backticked path only warned about, and summing the two put this corpus's own citations into a headline about missing edges (227 path mentions here and not one id or name, measured 2026-09-18; on the live project the split is 7 ids or names against 5 paths, which is two findings the old single number reported as twelve of one). That number is the door's own answer now rather than a second derivation of it: link-ids.py says which mentions it would rewrite, graph-check.py counts them, and when the door cannot be loaded the report says NOT MEASURED instead of printing a zero it never earned. Three facts from Obsidian's own help decide those tests (read 2026-09-18): Markdown links are resolved, a destination must be URL-encoded, and a link to a file that does not exist still becomes a node — its graph filter Existing files only exists for exactly that reason, which is why a rotted link is worse than a bare name. What the help does not document is how a relative destination resolves, so the script does not assume: it tries both readings and says which links survive each. Run on this corpus: 118 notes, 559 edges, 6 orphans, the relative form throughout and nothing vault-absolute-only (--all, which reads every markdown file; the default reads _ops/ and the root and answers 53 notes, 511 edges, no orphans). Run on the measured project: 19 notes, 0 edges, 19 orphans. Its own first version called that "the graph is whole" — true about links and false about everything a graph is for — so no edges is now the finding, and the mutant that says it again is in the suite. test-graph-check.sh21/21, its History mutant now landing in link-ids.py, because a mutant belongs where the rule lives and that rule moved.

A role the runtime cannot see is a document, not a worker — §28. Measured 2026-09-18 on the same project: three roles in _ops/roles/ and no .claude/ directory at all, so every bar, skill and instruction those files carried was read by nobody and the advisor did the work itself. That failure does not error; it looks like a project where one agent does everything. Hiring writes two files now — the role, and .claude/agents/<role>.md, which needs only name and description in its frontmatter and no build step — and a role attaching a pool skill the runtime directory does not carry is refused for the same reason. A project operated in another runtime declares it — one row in _ops/config.md's settings table, runtime_wiring — and both halves stand down; a mention in prose does not, which took this escape three shapes to get right. test-role-wiring.sh 21/21.

What a vault needs is written down once (project-layout): a relative path · a target that exists · no unencoded space · and the vault opened at the project root rather than at _ops/, because a link that climbs above the vault root is outside the graph — a project whose vault is _ops/ gets a graph of the machinery with the product missing from it. A link to a script or a fixture is an attachment, not a note; the edges worth having are between notes.

A person named in a task is now a link, which is the edge a project misses most.scripts/link-ids.py linked ids and paths; it now also links the field that names somebody — **Assignee**: Web Engineer becomes a link into _ops/roles/ — so the roster and the work are one neighbourhood in the graph instead of two, and who is this and where do I read about them stops being a search. The trap that came with it is the one this corpus already paid for once: a declaration that becomes a link stops being parseable, so the door, the board and the guard's self-review check all un-link before they read — without that, **Assignee**: [Web Engineer](…) made the rule a worker may not accept their own work compare two things that can never be equal. Both halves are in the suite, with the mutant that reads the field raw.

Teams and panels have a shape now — templates/GROUP-template.md: kind, routing rule, aggregation, members as links, and what the group overrides. _ops/teams/ had been named in the layout since 0.2.0 with no form to copy, which is the hole tasks were in before §23.

A product with no map was never told it had none. ./mapping has always said the map is seeded, never commissioned, and the guard only ever checked a map that already existed — so the measured project shipped a web client and an edge function across fifteen commits with no _ops/MAP.md at all, and every task re-derived the shape from the code. A warning now, with its escape: one line in _ops/ARCHITECTURE.md saying this product has no flow worth drawing turns it off, the same way §4 lets a directory be excused. Refusing here would be a warning nobody can answer, which is how a checker stops being read.

And the interface that looks generated has a cause with an address. ./visual now says where the ready-made things are declared — the kit in _ops/design-system/, a row per source in _ops/TOOLING.md with its licence and ceiling, the references in _ops/brand/ — so a worker that finds those filled does not invent a hero section, and one that finds them empty asks instead of generating. Named styles get a vocabulary, not a taxonomy: Glassmorphism, Neo-Brutalism, Bento Grid, Wabi-Sabi and the rest, grouped by what actually carries them — depth and material, loud and deliberate, quiet and structural, hand and texture, atmosphere — with the accessibility cost noted where it lives. The rule is unchanged and now has words attached: a style name buys search terms; the references and the tokens are still what gets agreed. The catalogue gains Aceternity UI for landing blocks (free components, paid All-Access, no licence text on its pricing page — read it inside the purchase) and Scrapegraph-ai for turning a site into structured data, with scraping named as the outward act it is.

The shelf is now brought to the owner instead of waited for. An owner cannot ask for a row they do not know exists, and nobody ever says "I wish you had mentioned there was a gallery for this" — they ship the generated version and it is fine. So the trigger is a decision appearing, not a question being asked: work about to start on a landing page, a survey, a billing model, a style, and two or three named candidates arrive with one line each on what separates them (choosing-tools). And a product is not a reference: reading a company's site to decide how yours should look selects on survivors and shows what somebody paid for — bring it as a direction to argue with, with the interest named in the same sentence.

Eleven more repositories were read properly — files, not descriptions — and what came back is eleven rules. On verification: a finding cites a verbatim excerpt and its line is derived from that, so a quote matching nowhere or twice leaves it unlocated rather than placed (which is what makes a mislocated finding impossible instead of rare) · a verdict is a shape, and demoting one takes its fields away rather than softening their wording · verification is structural: a fresh reader, given the claim and its evidence but not the author's reasoning or another reviewer's conclusion, asked to refute rather than assess, answering with one of a fixed set of verdicts — a promotion goes to a third reader, and where independence cannot be had the record is withdrawn and the round says not completed · a review names the pattern and quotes the line, and never returns a score or a guess about who wrote the text. On bars: a threshold with no named command that produces its verdict is an aspiration, and tightening a bar is silent while loosening one is loud · somebody else's checklist may become a type's bar under three conditions, and passing it is cited, never measured. On records: a move that destroys its own evidence writes itself down first and the next session replays the note · a record the door cannot parse is set aside with a marked name, never retried and never deleted · a handoff is a typed record with exactly one owner, claimed once · a rule earned in one project stays scoped to it until the same rule is measured in a second · and one test for anything kept outside the repository: delete it, rebuild from the committed files, and ask whether anything factual disappeared. Two more, on shape: a skill's description says when to reach for it and when not to, and every shaping rule names the case where it must lose — with a gate no candidate can pass recorded as a defect in the gate. One published claim was corrected: worktrunk is dual MIT/Apache-2.0, and the API's NOASSERTION is what a dual licence looks like through that field rather than a missing one.

Five working skills and six products were read, and what came back are rules rather than rows. From design skills built by other people: a look ships its own falsifier (Swiss breaks if warm paper, a serif display, grain or centred type appears) · the name resolves to tokens and the tokens still face the contrast gate — taste never overrides accessibility · a style read names what it rejected, with clean · modern · minimalist · elegant refused as findings · and breadth is not fidelity, measured on a published vocabulary whose glassmorphism row carries a primary colour and neither blur nor translucency. A picture is now a legitimate way to ask: measure the carriers, write the tokens, then reach for a name with a confidence marker. The tokens are written in the DTCG shape, whose $extensions clause is what lets a project's style provenance ride inside them instead of in a parallel document, with Style Dictionary named as the export target and not as a dependency. From the products: a total is computed by replay, never incremented in place (metering's own idempotency rule, now in ./cost — a retried run must not bill twice) · an approved outward act commits a share record — recipient, date, and the commit the sent version was built from · a register row declares what happens when the thing is absent · a skill hard-stops while its profile still holds a placeholder rather than producing the average of its training · and a dispatching role gets no write and no outward tool, which is the advisor is not exempt expressed as tool scope. An embeddable client also needs its own licence field: one repository was measured carrying three licences, with the SDK you ship under a different one from the server you run and every governance feature in a proprietary directory.

The round found nine things in its own release, and every one is repaired. A deletion lens, an adversarial lens, a contradiction lens and a cold-read ran over the new guard sections, the new scripts and the prose. The sharpest were false refusals that would have met a reader on day one: §27 refusing a markdown link carrying a title and then refusing the %20 it prescribed · §27 judging a line added inside a pre-existing fence, which the prescribed door could not fix · §26 and §28 reading the whole staged text of every touched role, so a typo fix refused the commit · §23 refusing _ops/tasks/README.md · §28 refusing a correctly wired role whose .claude/ is gitignored (now a warning that names the clone) · a gates: block indented four spaces parsing as no gates at all, so --check-ladder --strict refused a ladder while instructing the author to write what was already there · and §24 refusing an ordinary git commit --amend, which is why its threshold is now three: a pre-commit hook cannot tell an amend from a second commit, measured on this machine (GIT_REFLOG_ACTION is empty in both). Two evasions closed: a skill counted as declared because its name appeared anywhere in the register, including on a line saying we do not use it; and §28's other-runtime escape tripping on any prose that mentioned agents. And the door now writes what its own gate demands — new-task.py links the assignee, because §23's refusal pointed at a door whose output §27 then refused. Eight repeated epilogues in the guard became one helper, three readers of one ladder became one, and five claims that read as implemented now say plainly that nothing holds them yet.

The first attempt at the re-round is not completed, recorded rather than quietly retried. Three lenses — adversarial and contradiction over the repair, deletion over the prose — were launched with the repair's own diff as their input and all three died on the account's session limit, a line or two in. That is the sixth, seventh and eighth lens lost this way across three releases, and the rule this corpus wrote for exactly this moment is that a lens which ran out of budget is recorded as not completed, never as empty: "those two are identical in the report and opposite in fact." One adjustment landed with the recording: the re-round's lenses are dispatched on a different model from the one that wrote the corpus, which is what ./lenses already prefers and what the earlier deaths had quietly stopped anyone doing.

Relaunched there, the re-round found three more, and the two that matter are the repair's own overreach. An attribute written at its edge's own indent silently stopped being a gate: freeing the gates: indent — itself a repair from the round before — let the branch that reads an edge swallow review_by: non-author as a prose gate keyed to nothing, leaving the edge empty and saying not one word, which is the exact class this corpus refuses. An attribute now belongs to the open edge whatever indent it arrived at, one reader for all three keys instead of two copies, and an attribute under no edge is a fault said out loud. And the other-runtime escape, widened once already, was still a hole: "a path that is not .claude/" is tripped by a sentence naming a rejected alternative, so "we evaluated .gemini/agents/ and stayed on .claude/agents/" silenced both wiring checks on a correctly wired project. An escape is a declaration, not a mention: one setting, runtime_wiring in _ops/config.md, read by one helper both sections stand down on — the third shape of this escape in two days, and the first that prose cannot reach. The third finding was the door's own copy of a resolver: new-task.py resolved an assignee by walking the working tree while every gate that judges the result reads git, so it could mint a link to a file §25 and §27 cannot see — the door blessing what the gate then refuses, arriving from the other side of the defect it was written to fix. It calls link-ids.py's resolver now. One reading of the sections against their suites found a fourth: §26's runtime half — a skill in the pool that .claude/skills/ does not carry — shipped with no assertion at all, because the case did not move with the code when a deletion lens folded two readers of one table into one. Three cases and a mutant cover it now.

A checker that had been crying wolf for a release, and the drift it was hiding. The corpus-count guard matched **N · only, so the day 16-bis was added — a shape that arrives beside the one it belongs with rather than renumbering every citation — the README's honest figure of twenty-eight started printing as a warning on every preflight. It counts -bis now, test-check-structure.sh 20/20 with a mutant on the alternative, and the first thing the fixed check found was the real error the blind one had covered: ./patterns's own opening line still said twenty-seven. A check that cries wolf is a check nobody reads — this corpus wrote that line about check-releases.sh and then shipped a release ignoring one of its own warnings.

And the round over that repair is not completed too — four lenses, four deaths, same limit. Adversarial, contradiction, deletion and cold-read were launched over the repair commit on the smaller model that had just worked, and all four died mid-read: the ninth to twelfth lens lost this way. Two got far enough to print a line of progress — one had run the structure suite, one had read a refusal message — and a partial reading is not a report, so nothing from them is counted here. What is written down instead is the tally, because it is now precise: twelve lenses lost to the limit across three releases, seven of them in this one — a lens round is the most expensive thing this repository does, and budget is the thing it runs out of, rather than attention. The round is owed, and the tag waits on it.

Relaunched a third time, two lenses finished and found five more — three of them in the repairs themselves. The escape a footnote could trip was still trippable: shape three named a setting and then looked for that name anywhere in a line, which a lens broke four ways in one sitting — the key inside a fenced example · inside an HTML comment · in another row's notes column · and worst, in a sentence forbidding it, where "Do not set runtime_wiring to .codex/agents/ — we use Claude Code" disabled §28 entirely on an unwired role, reproduced end to end at exit 0 with no output. Naming a setting is not reading one. It reads the settings table now: fences dropped, comments stripped, the key its own first cell, the value the cell beside it — and compared by first real segment, because a value written as a link carries a file-relative href and ../.claude/agents/ was read as somewhere else. Six shapes and two mutants hold it. The gates repair had its own false refusal: with check reserved as an attribute name, a ladder whose stage is called check parsed its legacy check: "prose" as an orphan and every transition on it was refused — exit 2, "the ladder is malformed", on a ladder that worked the day before. An attribute name that is also a stage name is resolved after the walk now, so the order of the file cannot decide it. And the fold that made one reader out of two lost a file quietly: an undecodable file was dropped per-file with a bare except, so the report printed a plausible number missing its hardest files — three counted as two. The door raises Unreadable, the report says the count is a FLOOR and how many files it could not read, and it never writes U+FFFD back over somebody's bytes. Comparing the door against the guard's §27 — the copy that cannot be folded, being a heredoc inside a shell script a project copies — also found the door missing _ops/templates/ from its skip list, so it would have baked one task's links into the template every later task is cut from. Where two copies must exist, the reason is now written between them, and so is every difference.

The deletion lens on the prose landed six cuts and one decline. Five were rules restated instead of cited — ./choosing-tools re-deriving PATTERNS §20, ./cost §4, ./grouping §6, ./pipelines restating ./checking's named-command rule, and ./catalogue writing out the Formbricks licence finding twice in ten lines — plus a justification repeated two paragraphs after itself. The decline is written into ./facts's own header: eight new entries repeat their chapter's measurement by design, because a line that pointed at a chapter would be unquotable, which is this file's only job. A chapter holds the rule; this file holds the sentence. A round carrying a not completed has not reported clean, which is the condition the tag waits on.

Then both halves ran, and between them they found twelve more. The cold reader — given only what a new user reads, and not allowed to know anything else — caught the release stating its own worst news abstractly: "the audit gate does not reach it" appeared in three files and never once as a consequence, so ./install now names the three refusals that stop happening under a runtime whose hooks do not load. It also caught ~/.gemini/config/plugins/ sitting two rows above "Gemini CLI no longer exists" with nothing saying that directory is Google's and not the dead CLI's; a README table using a word its own legend does not define; and — the sharpest — the answer to the memory study reading as special pleading: a measured 28.7-point recall gap was met with a restatement of the very design choice the study had measured as worse. The gap is left standing now, and ./facts 279 no longer claims the study's file arm was this system. It is a relative: that arm recalled by grep. And the failure it names got a situation of its own, which this repository requires of every mechanic and had not given the one about its own memory.

The deletion lens counted the same agy plugin validate output quoted in seven places and the study's figures in three. Each has one home now and the rest cite it; four code comments that retold the changelog's story at changelog length became pointers; transition.py explained one fix at both ends of one function; and migrate-layout.py claimed to be "the only place the reason is written" while two other files carried it — a claim made false by the cluster it sat in.

The fifth round's adversarial lens found eleven ways past the gate it had just repaired, and one defect that is this release repeating itself. Every one reproduced, none argued with. Eleven misses, the costly direction: a wrapper word before the verb — env X=1, sudo, nohup, time, nice -n 10, and a bare eval with no quote at all — is neither a delimiter nor a runner, so nothing anchored; a line continuation is one command to the shell and two lines to a regex; a redirect needs no space, and the lookahead did not count > as a terminator; the runner's quote may sit further than forty characters away. One false positive: a heredoc body is data being fed to another program, so writing a document that carries the verb at the start of a line was refused as a publish — the same prose is not an act defect arriving back through the very anchor that had repaired it. Fourteen cases here and fourteen in the sibling, test-audit-gate.sh 158/158 and test-outward-gate.sh 61/61. One thing the lens called a false positive is not one and is kept as a case: backticks inside a double-quoted commit message are command substitution, and the shell really does run what is between them.

The lens also found this release making its own mistake twice. The register's escape was a grep over _ops/DECISIONS.md for "no external" or "TOOLING.md" — and two ordinary sentences broke it: "rejected the no external caching approach, added Redis instead" silenced the warning on a commit that had just added a dependency, and "TODO: still need to write _ops/TOOLING.md before launch" silenced the very warning that TODO describes. That is the defect this release spent three rounds removing from the wiring escape, put back by hand days later in the escape next door. The answer is the one the first repair should have generalised: one reader for any declared setting, _setting <key>, with runtime_wiring and the new external_services: none both reading through it. Two more in the same clause: only package.json had a */ form, so a Python, Go, Rust, Ruby or PHP project with its manifest one directory down never reached the check at all; and tr '\0' '\n' turned a newline inside a filename into a second apparent record, escaping the _ops/ exclusion — this file's own machine note says to keep paths NUL-separated to the consumer, and the consumer is now python. test-register-exists.sh 10/10.

The contradiction lens ran the two gates side by side and found three silent divergences and one shipped-breaking defect. The breaking one first, because it would have met every reader: this release's own register template broke this release's own guard. The widened column header Wired how · what it ships was matched by equality, so a project that copied the new template and registered a server correctly was told ".mcp.json declares server X and _ops/TOOLING.md has no row for it" — a guard refusing what the shipped template produces, which is the shape §4d was written about in 2026-08 and which the suite could not catch because its fixture still carried the old header. The header is matched by prefix now and the fixture is the template's own. The divergences: one copy was case-blind and the other was not, so GIT PUSH was an act on one side and prose on the other; one read both spellings of --dry-run and the other only the hyphen, so the same read was a publish here and a read there; and the verb lists differed in both directions — one missed gh pr create, the other missed flyctl, kamal, cap, make deploy and npm run deploy. All aligned, with cases, because a divergence that is not deliberate and written down is just a divergence. And this file's own docstring described the gate's holes wrongly in both directions at once — claiming one it had closed and understating another — which is the same defect as an overstated comment anywhere else.

And the quoted instruction was quoting the harness. Four shapes reach a transcript as user entries and none of them is the owner: a tool result, a <system-reminder> — which may be unterminated or nested — a <task-notification>, which is a background agent's return value routed back as a turn, and the harness's conversation-continuation summary. The first version skipped only one of the four, and its own comment claimed otherwise; a comment that overstates what the code does is the defect, not the rounding. The blocks stopped being stripped by pairs, because a non-greedy match leaks what sits between a nested inner close and the outer one — and an entry carrying both text and a tool result keeps its text, where discarding it made the gate quote an older message as "what you last asked for". A stale instruction reads exactly like a current one.

The sixth round's adversarial lens went at the fifth round's repair and found five more — three of them holes the repair itself had opened. Every one was reproduced end to end against the installed hook, never argued from the source.

The heredoc blanker hid three real publishes. Blanking a body was the fifth round's answer to a publish inside a document is not a publish, and it blanked to the end of the string whenever it could not find the terminator — so cat <<EOF; git push origin main, with no terminator anywhere, had the push erased out from under the pattern while bash runs it the moment the heredoc reaches end of input. Two more of the same family: a literal a<<b inside a quoted commit message, read as an opener whose terminator never comes, and <<< — a here-string, matched from its second <. A blanker that cannot see both ends of the body blanks nothing, which is the loud direction, and the two lookarounds keep the other two shapes out.

And a wrapper word takes flags. env with no assignment · env -i · env -- · sudo -u root · command -p · time -p · nice --adjustment=10 · exec -a name — eight shapes, each carrying the verb straight past the anchor the fifth round had just installed. Listing the option shapes was the first repair and it was not enough: a flag that takes its own argument leaves a bare word between the flag and the verb, which no option pattern matches. The run between a wrapper and the verb is any tokens at all now, bounded, and the verb still has to appear as its own two words.

A decoy header won the column — the same repair again, from the other side. Wired how · what it ships was matched by prefix so that the widened template would be found; a register whose first column reads Wired how much budget then captured the index and the real column was never read, reporting a correctly registered row as missing. Exact match first, then a prefix that ends at a non-letter.

And the refusal's quote had two ways to be steered. The blocks it strips were counted by depth, so a </task-notification> closed a <system-reminder> — which hands anything that can put tag-shaped text into a transcript exact control over what the owner is shown. It is a stack of names now. And the harness's continuation summary was skipped on its opening words alone, so a real instruction that merely began with them was dropped whole and the refusal quoted a superseded message as what you last asked for — "hold everything, do NOT publish" shown as current, which is worse than quoting nothing. Two signals now, both required: those summaries arrive as a bare string and are long, where a person's turn arrives as text blocks. Eleven cases here and eleven in the sibling's test-outward-gate.sh, one per reproduced command shape; the two quote shapes went without one until the eighth round, below, and so did the decoy header in the register suites.

Rounds one, two and three reported; the fourth reported; the fifth lost two lenses to the same limit — the thirteenth and fourteenth across four releases, recorded rather than quietly retried. What that round is owed on has grown to the ten commits after it: the shelf rule, the named screen, the register's two columns and its guard clause, both copies of the outward gate, and the quoting. A round carrying a not completed has not reported clean, and the tag waits on it.

And the sixth round reported on one lens of four. Its adversarial lens is the five findings above; its contradiction lens died on the weekly limit — the fifteenth — and its deletion and cold-read were never dispatched, because by then the budget the round needs was gone. Three not completed against one report: the round has not reported clean, and it is owed over the repair those five findings caused.

The seventh round went at that repair with all four lenses, and all four died on the monthly spend limit — the sixteenth to nineteenth. Two had found something before they stopped, and both findings were checked and are real: the repair left a docstring describing the code it replaced — "counting depth rather than matching pairs" above a stack of names, in both copies of the gate, which is the overstated-comment defect this release names above arriving through its own fix — and a case the repair added was byte-identical to one the round before had added, in both suites, so it counted twice for one shape. The docstring describes the stack now, and the duplicate became the <<- shape the sibling's suite had never had. Neither makes the round clean: four not completed, two partial reports recovered from the transcripts, and the round is owed again.

The eighth round finished whole — four lenses, four reports, the first since the fourth round — and its adversarial lens found four more ways the sixth round's repair failed. The wrapper run had been capped at four tokens, and env A=1 B=2 C=3 D=4 E=5 F=6 git push walked past the cap: a bound on the unsafe side of an anchor is a hole with a number on it. Any run of tokens now, on the same line — and the cost is named: a wrapper followed later on its line by a quoted mention of the verb is refused, which is the loud side. A crossed close, <a> <b> </a> … </b>, popped through the inner block and put a hidden block's text on show as what you last asked for; a closing tag closes only the innermost block now, and only by its name. cat<<EOF, with no space, was not read as a heredoc, so writing a document was refused as a publish. And a project's own Wired How-To column, placed before the real one, won the register lookup — the second decoy past a prefix match — so the headers the template ships, old and new, are matched exactly, and a prefix is accepted only when a single header carries it.

Repairing the heredoc case turned up a worse hole that no lens had named. The blanker started a heredoc's body right after the delimiter, and bash starts it on the next line — so cat > f.md <<EOF && git add f.md && git push, an ordinary way to write a file and publish it, had its publish erased before the pattern ever saw it. A hole in the exact form an agent writes, sitting under two rounds of repair to the same function. And any <<X counted as an opener — inside a commit message, in a comment, inside a body already blanked — so whatever sat between it and a lone X disappeared. The body starts on the next line now, and an opener has to be code, read by a small line-local scanner that names what it does not read — a quote opened on an earlier line, $'…', an arithmetic << — rather than claiming to parse bash.

The other three lenses found this entry's own record wrong. "Eight shapes" stood beside a list of six, in four places; all eight are named now, and env --, the one never tested, has a case. "A case per reproduced shape" was false for the two quote shapes and the decoy header, which had none; each has one now. A comment above the register lookup still said "not by equality" over code that tries equality first, and a docstring's own boundary example — a turn of "two thousand characters" — was one the code does not skip. One finding was declined: a comment listing the wrapper shapes the fifth round reproduced is a record of that round, not a description of the matcher, which is stated in full beside it.

Every case for this round's own shapes was run against the unrepaired code and fails there, and the two quote cases fail against two different mutants of the sixth round's skip — one reverting it to a single signal, one removing it. The register suites gained both decoy headers. The ninth round went over this repair.

The ninth round finished whole too, and its adversarial lens broke the eighth round's scanner the way the eighth had broken the sixth round's blanker. A <<X inside $'…' or an arithmetic $((…)) was still taken for an opener and hid the push on the line after it — confirmed in real bash against a stub git on 2026-09-23. Both shapes were named in that scanner's own docstring as what it does not read, beside a sentence promising that every failure fell on the loud side, and these two fell on the quiet one: a limit named beside a false guarantee is not a limit named. And a scan per opener to the end of the string took 13 s on 358 KB of unterminated openers, measured the same day. So the blanker is one pass now, left to right, keeping the state bash keeps — outside quotes or inside '…', "…" and $'…', inside arithmetic, in a comment — and reading bodies the way bash does: after the newline that ends the command, every heredoc of that line in order, each to a line that is its word. That closes both shapes — and, along with them, a quote opened on an earlier line and a terminator that is not the whole line — and takes the same 358 KB in 0.08 s. What it still did not track — a quote nested in $(…) or backticks inside a double-quoted string — is named with the direction it can fail in, which is either.

The register's last guess went too. Accepting a prefix when a single header carried it read a project's own Wired How-To notes as the wiring whenever that column stood alone, so a note that happened to mention mise.toml passed a row nobody had wired. Only the headers the template ships are read now, and when a near miss is what stopped the read, the refusal names that column.

The other three lenses found one real divergence and the rest in the record. The two gates had disagreed since before this release about a project's own deploy script: one copy stopped ./scripts/deploy, the other never had, and nothing said why — it crossed the loud way, with a case. The claim that both quote cases failed against one mutant was true of one; each of the skip's two signals has a case of its own now, each failing against the mutant that drops it, where before dropping either half alone failed nothing. A comment called a line of the refusal its third door where the refusal counts two; a case repeated another's branch; and a fixture carried an entry the quote reader skips unconditionally.

Every case for a shape this round reproduced fails against the eighth round's code — the timing case at 13 s against a bound of 5 — and each signal's case against the mutant that drops it. The tenth round went over this repair.

The tenth round found a hole older than this release, and the ninth round's named limit proved dangerous. --dry-run anywhere in a command exempted all of it: echo testing --dry-run, then a real git push on the next line, went through both gates, and so did a comment that merely named the flag — the shape an agent writes when it narrates a flag before using the real command. A dry run now excuses only its own act, up to the next ; & | or newline, and every act on the line must be excused on its own. And the limit round nine named — a double quote inside $(…) within a double-quoted string — was shown to hide a real publish, not merely to differ from bash: the inner quote closed the outer string, and a <<X still inside it blanked the next line (confirmed in real bash against a stub git, 2026-09-24). Inside a double-quoted string, $(…), a backtick pair and ${…} each open a context of their own now, on a stack, with their own quoting — and bash 3.2, the one macOS ships, reads a ' inside "${…}" as a quote, so the scanner does too. The deploy catch-all had been refusing predeploy and ./scripts/predeploy, local checks that publish nothing; deploy must now be the whole name or follow a / - _ ., and never pre-. And the register's near-miss hint was silenced by any other table in the file with an exact header, so it is judged per table.

A differential check came with it: thirty commands, each run in real bash against stub git, gh, npm, docker and a stub deploy script, and each fed to both gates. No command bash publishes with passes either gate; the two commands both gates refuse without bash publishing are ones bash rejects as a syntax error, which is the loud side. It was a scratch measurement, not a suite, and a suite whose oracle is real bash went into LATER.md as owed. The other three lenses found no defect in the code, only in how it read: two measurements with no date, a sentence whose them garden-pathed, a docstring that stopped naming <<<, the clause "three sentences were rewritten for a reader who was not here", which named none of them, and this entry still calling the refusal's line about a sentence a third door after the code's comment had stopped. Every case for a shape this round reproduced fails against the ninth round's code. The eleventh round went over this repair.

The eleventh round found the dry-run scope leaking two more ways, and the owed suite exists now. A trailing comment — git push origin main # --dry-run tested this yesterday — excused the real act it trailed, because the comment was still in the text the scope read; and $(git push origin main) --dry-run excused a push that runs inside the substitution before the flag is ever read. npm publish --dry-run=false was excused too, and it publishes. So shell_only blanks a comment the way it blanks a heredoc body, the scope also ends at a ) or a backtick, and only the bare flag or =true · =1 · =yes excuses anything. The owed suite is scripts/test-gate-vs-bash.sh, built the day its LATER.md trigger fired: each of 38 commands runs in real bash against stubs, published is read from what the stubs were asked to do, and the gate's verdict must agree — with two code mutants it must catch, a terminator matched anywhere in a line and a dry-run scope that runs past a ). Against the tenth round's gate it reports exactly this round's four holes and nothing else — the trailing comment, the flag outside $(…), the same outside a backtick pair (`git push origin main` --dry-run), and --dry-run=false.

It was closed three ways because a lens proved it had to be — a fourth came the next round. On 2026-09-24 a lens checking a claim about bash wrote a script carrying git push for a stub git; the installed gate refused the command that would have made the stub, the lens did not notice, PATH fell through to the real binary, and the sibling repository's main took eighteen commits of its untagged release. So the suite puts only stubs and harmless tools on PATH under env -i, verifies every stub before each run, and runs bash under sandbox-exec with the network denied — and without sandbox-exec it runs no bash at all and says so as a failure. lenses now says a lens runs an outward verb no other way, and CLAUDE.md carries the machine note. The second predeploy case became ./scripts/pre-deploy, the only one that reaches the pre- rule. The twelfth round went over this repair.

The twelfth round found the gate blind to a tool's own global options — since the day it was written. git -C . push, git -c k=v push, git --git-dir=.git push, gh -R o/r release create, npm --prefix x publish and docker --context x push all passed both gates, because the pattern wanted the subcommand straight after the tool's name; so did /usr/bin/git push, a tool named by its path. An adversarial lens found the path; checking what else that shape covered found the options, and git -C <dir> push is the form an agent working across repositories writes most. The gate now reads any run of option tokens between the tool and its subcommand, and any path before the name, as the same act, and refuses it — so a command of those shapes that went through before is stopped now, which is the change a user upgrading will notice. And the dry-run flag had no left edge: git push origin mainX--dry-run was excused, the flag found glued to another word, so the command is split the way a shell splits it and only a whole word counts — the act's own options included, since npm --dry-run publish is a dry run too.

The new suite had a blind spot of its own, and it was its safety. A real tool named by its path cannot be stubbed, and the network it would have used was denied, so such a push read as nothing published — the layer that makes the suite safe was hiding exactly the hole. The sandbox now also refuses to execute any binary named like an outward tool outside the stub directory, by any path; a canary proves that before any case runs; and a refused execution counts as the act having been tried. The oracle got its own model of each CLI — which global options take an argument — written where a case using another one has to add it, and the stubs log each argument separately so quoting survives. Against the eleventh round's gate the suite reports nine holes, every one this round's. Reading: the four holes of the round before are named where they are counted, and the suite's header now says how to add a case and what a SYNTAX: reason means. The thirteenth round went over this repair.

The thirteenth round lost all four lenses to the session limit in their first calls, was run again, and its adversarial lens found the option reading approximate in four ways. An argument that merely starts with a subcommand word — npm --prefix publish-tools publish, gh -R pr-team/repo release create, npm --prefix run-scripts run deploy — ended the option run early and hid the act; so did an option value quoted because it holds a space, git -c "user.name=x y" push, and a tool path quoted for the same reason, "./my tools/git" push. And the dry-run window was still cut by a bare character split, so a ) inside a quoted release note stopped it before a real --dry-run — a false refusal. Three of the four had one cause, a pattern reading \S for a shell word: a word is read the way a shell reads one now, quoted parts and escapes included; an argument is refused as a subcommand only when it IS the whole word; and a path may be quoted. The fourth needed its own repair: the window ends at the first \n ; & | ) or backtick outside quotes. The suite had passed all four, because none of its cases used an argument like that — so each is a case now, and adding the path case turned up one more thing the suite could not see: a script refused by the sandbox is reported by bash as a bad interpreter, not as the refusal the suite looked for. Both wordings count now.

The other lenses found the record behind the code again: the round-eleven paragraph still called the suite closed three ways after the next round added a fourth; a sentence said a run of options is allowed now where the gate now refuses such a command, which is the one thing a reader upgrading needs to hear; the suite's adding a case is one line omitted the model of each tool's options the oracle reads; one case repeated another's branch and became a long option with its own argument; a comment repeated the paragraph above it. One finding was declined: that this entry says CLAUDE.md carries a machine note it no longer does — the note is still there, and what the previous round cut was only its restatement of the suite's mechanism. Every case for this round's shapes fails against the twelfth round's gate, and the second mutant now breaks the window where it is cut. The fourteenth round went over this repair.

The fourteenth round found the same shape of defect one level down. A bare tool name in quotes — "git" push — passed, because the quoted form was only allowed for a path; a name may open a quote now, and the one after it closes. And the dry-run window's own reader, command_end, kept a single quote flag, so a release note built with $(date "+%Y-%m-%d (UTC)") fooled it into ending the window early and refusing a real dry run. It was rewritten to read from the start of the command, keeping every substitution on a stack with its own quoting, and the rewrite's first draft opened a hole the suite caught at once: a window that starts at the act cannot tell the backtick that closes `git push` --dry-run from one that opens another pair, so it read past it and excused the push. Reading from the start is what fixed that too. The suite gained both shapes, a guard for the escape branch nothing had exercised, and a second mutant that now breaks the one rule the window depends on — stopping where the substitution holding the act closes. One finding was declined: a verb split by quotes, git pu'sh', is not read. That spelling is a decision to hide the act, and the gate's own comment now says so beside the other shapes it does not claim to parse. The other lenses: a clause left dangling in this entry by the previous repair, a list that folded the window's fix under a cause it did not share, a stale comment still describing the old window, and one more is allowed now in the gate's own comments. The fifteenth round went over this repair.

The fifteenth round found both halves of the last repair incomplete, in forms an agent writes. Only the tool's name had been allowed its quotes, so the verb quoted whole — git "push", npm 'publish', gh "release" create — still went through; every word of an act may be quoted whole now. And the window's reader knew $(…) and backticks but read arithmetic and process substitution as bare parentheses, so branch$((1+1)) --dry-run and <(echo hi) --dry-run were refused as publishes; $((…)), <(…) and >(…) are pairs on its stack now, each closed by its own parenthesis. The other lenses: this entry buried what a user needs under the rounds — the block at its top answers that — its docstring's "the start of the command" read as the previous separator when it means the whole text, the fourteenth round's paragraph never said what a user would notice, the two gates listed their alternatives in different orders for no reason, and two cases took one branch. Every case for this round's shapes fails against the fourteenth round's gate (three holes, two false refusals). The sixteenth round went over this repair.

The sixteenth round found no hole. Its adversarial lens classed every finding it could reproduce as a shape an agent writes by accident, and all four were refusals of commands that publish nothing. One was a real command: grouping inside arithmetic, $((1 + (2*3))), closed the arithmetic one parenthesis early and refused a real dry run after it — a bare pair is a pair on the window's stack now. Three were declined, each with its reason: npm --workspace "publish" install reads as the act because a pattern cannot know which options take an argument, so it takes the reading that stops, and that limit is now written beside the gate's other ones and in the block above; git 'push origin main' and git" push" are commands bash runs to an error — no such subcommand, no such program — and refusing a command that cannot run costs nothing. The other lenses found the round's own prose: the block above claimed that nothing below it changed its lists, which only reading all of it could confirm, and ended by telling you to tell the owner; the window's docstring introduced "the outermost context" to explain "the start of the command"; the two for a user lines this round added below repeated the block; one gate said its alternatives mirror the other's and the other did not say so back; and a case's name did not say why it was kept. test-audit-gate.sh 200/200, test-gate-vs-bash.sh 69/69; the seventeenth round went over this repair.

The seventeenth round found nothing in the code: its adversarial, deletion and contradiction lenses reported empty — the adversarial one had tried subshells, case patterns, ((…)), function definitions and unbalanced parentheses against the new pair, and the one shape that misled the window is a syntax error to bash itself. Its cold-read found two reading stumbles, both repaired: a sentence in the block above sat under an item it did not describe, and the window's docstring used bare for two different parentheses on the same date.

The pre-tag scan found what seventeen lens rounds had not: seven of this repository's skill descriptions were not valid YAML. A plain value holding : — description: Run one decision loop: frame, search, … — is a mapping inside a mapping to a strict parser, and SkillSpector's parser rejected the manifest outright and reported the skill only partly inspected — status: partial, coverage 0%. Claude Code reads such a value leniently, which is no promise about the next runtime. The seven are quoted, all twenty-one parse strictly, agy plugin validate still reads all twenty-one, and preflight §5 now refuses a plain frontmatter value a strict parser rejects or cuts short, with its mutant in test-corpus-preflight.sh. The eighteenth round went over this repair.

The eighteenth round found the new check weaker than the repair it guards. The sibling repository's copy fed its checker's output through a here-string and never read its exit status, so a skill file that was not UTF-8 stopped the scan silently and every skill after it went unchecked — while this copy, one process guarded by || FAIL=1, failed closed on the same file: two copies of one check, opposite on a crash. Both fail closed now, on one function written identically into both. And the heuristic was narrower and wider than strict YAML at once — it missed a tab before #, a plain value wrapped onto a second line, a quoted key, and a value opening with @ or a backtick; and it refused a flow collection, which strict YAML reads. It is still a heuristic, and it says so in its docstring, since the Python that runs preflight has no YAML parser to ask; the pre-tag scan is the strict one. The refusal now names every shape it refuses. The other lenses: bare still named a kind of parenthesis in the window's docstring after this entry said it no longer did, and two sentences above said more than their facts — that SkillSpector reported the skill it could not parse as uninspected, when its word is partial, and that the stop for a sentence about publishing is the refusal's closing line, when the owner's quoted instruction can follow it. The nineteenth round went over this repair.

The nineteenth round found one hole, and a claim wider than the check. Its adversarial lens reported that a skill file with Windows line endings passed unread; it did not — Python's text mode had been reading those as plain newlines all along, and the mutant written for the finding said so by passing against the old code. The hole was one step over: a file opening with a byte-order mark never matched the frontmatter pattern, so the check read nothing in it and passed it, whatever its values held. The mark is read past now, and a file whose frontmatter the check cannot find is refused instead of passed. A # after a space was refused as not valid YAML, when YAML reads it as a comment and drops the rest of the value; it is still refused, since a description cut short is the defect, and the refusal now says what YAML does. The mark, the missing frontmatter and the # each have a mutant, and each one's test fails when run against the old code. A nested or dotted key goes unread — no skill here has one — and the docstring now says so instead of claiming every value. The other lenses found only words: a tab the refusal did not name, a line of the refusal still called its closing line in the gate's comment and twice in this entry, bare still in the window's docstring, a mutant whose output nobody read, a clause said twice, a list above that read as one case or as two, and the bash 3.2 machine note quoting an error about a parenthesis, when what bash 3.2 misreads inside $(…) is an unpaired backtick. The twentieth round went over this repair.

The twentieth round found two misses on ordinary files, both in what the round before had added. The refusal of a file with no findable frontmatter also refused a skill file ending at its closing --- with no newline after it, which YAML reads; the closing line may end the file now. And a value that is nothing but a comment — description: #TODO — loses the whole value and was not refused, because the key's pattern had already taken the space before its #; a # that opens a value is refused now, and text after a comment's # no longer counts as the value's. A tab inside a plain value, which PyYAML rejects, is refused as not valid YAML. Blanks after a --- line are still refused, since what the runtimes' own loaders make of them is unmeasured, and the refusal now says the line must be exactly ---. Twenty-four shapes, written to files and read back, now agree with PyYAML, except the blanks after --- refused on purpose. The contradiction lens found nothing; the cold-read and deletion lenses found words — a correction above that named its subject and not its fact, a count whose referent came a sentence late, an elliptical it, and a docstring aside that repeated the refusal. The twenty-first round went over this repair.

The twenty-first round found one false refusal, and its adversarial and contradiction lenses found it independently. The tab rule read the whole value, so a tab inside a trailing comment, which YAML drops with the comment, was refused as a tab in the value — and the twenty-four shapes above had not included one. It reads only what comes before the comment now, with a mutant whose test fails against the old code. The comment refusal said a value loses everything after its #; it now says the rest of that line goes, since a value continued on the next line keeps the continuation. The cold-read and deletion lenses found words: which machine note, which way a mutant's test fails, a metaphor for what took the space before a #, and a code comment that repeated the docstring. The twenty-second round went over this repair.

The twenty-second round found two more tabs the check passed. A tab after a key's : and a tab indenting a wrapped line are both rejected by PyYAML, and the check saw neither — the key's pattern took the first as its separator, and the wrapped line's indent was stripped before the tab rule read it. Both are read now, each with a mutant whose test fails against the old code, and thirty-four shapes written to files agree with PyYAML, blanks after --- aside. The contradiction lens found the twenty-four shapes above said to agree with PyYAML when one of them, blanks after ---, is refused on purpose; the sentence now says so. The cold-read and deletion lenses found words: two lenses left unnamed, an it says so that reached further than the refusal does, and a second code comment repeating the docstring. The twenty-third round went over this repair.

The twenty-third round found one more tab, and a test that proved nothing. A tab opening a line of a block, flow or nested value passed, because the check read nothing of those values past the key's line — while its docstring said they were left to the parser, and nothing in preflight is one. PyYAML rejects that tab and accepts one inside quotes or after a block's own indent, so that is the rule now, and the docstring says those values otherwise go unchecked here. Forty-five shapes written to files agree with PyYAML, blanks after --- aside. And this repository's test for a tab after a key's : asserted an exit status that §5's description check — which wants description: with a space — produced on its own, so it passed with the tab rule or without it; it asserts the refusal's words now, as every tab test does. The tab refusal and the others each name their own remedy now, where one sentence had joined use spaces and quote the value. The deletion lens found nothing; the contradiction lens read the twenty-four shapes of one probe and the thirty-four of a later one as one claim, and the paragraph above now states the later count; the cold-read found line comment read as a YAML comment. The twenty-fourth round went over this repair.

The twenty-fourth round found three more tab shapes, and the chase stops there by the owner's decision. Two of them passed files PyYAML rejects and one refused a file it reads. Six rounds running had each found one to three shapes like these, and a heuristic with no parser behind it will go on finding them, so the three are written into the docstring as known limits rather than chased: a strict parser rejects the two that pass, the scan before a tag runs one, and the third is loud. The deletion and contradiction lenses found nothing; the cold-read found words — a tab refusal whose there named no place, a docstring clause that bound to the last of three places, and the same exception with its exception paragraphs above. The twenty-fifth round went over this repair under the stopping rule the owner approved on 2026-09-24, the one 0.2.18 ran under: a round reports clean if all it finds is wording to fix, shapes only deliberate disguise produces, or the publish gate refusing a command that would not have run anyway.

The twenty-fifth round found one more tab shape, and that the net under the limits was only half visible. A fourth tab shape that passes joins the docstring's known limits, by the same decision as the three before it. The contradiction lens then ran the scan before a tag on the limits themselves, and its printed summary said successful, no findings, for frontmatter PyYAML rejects: SkillSpector records such a manifest only in its JSON report, as a manifest_parse_error in that skill's own analysis_completeness. So the net was real and nobody reading the summary would have seen it. The scan before a tag is now read as JSON (AGENTS.md says what to look for), measured on both corpora — no parse error in either — and on the three passing shapes, each reported. The deletion lens found the paragraph above re-listing the limits beside the docstring that holds them; the cold-read found the tab refusal not naming indented lines, the stopping rule named without being stated, and a quoted value that closed on its own line readable two ways. All are repaired; by the stopping rule and the decision on the heuristic, the round is clean.

The twenty-sixth round found only words, and by the stopping rule it is clean. The new JSON rule said under analysis_completeness, and a --recursive report has two keys of that name: the report's own stays clean while the error sits in each skill's; the rule now names the path and gives a check to paste — grep -c manifest_parse_error must print 0. Fact 280's the scan then reported it clean read as the whole corpus, when two of its three findings had been left standing; it now says the scan passed that one line. The machine note on the scan restated the rule it points at, and the paragraph above re-described the fourth limit beside the docstring that holds it; both are cut.

The re-round reads the repair, not the range again. Nine round attempts across two releases, five lost whole to the session limit — and the sharpest finding of each re-round sat in the latest repair commit, while the one re-round that read only that commit finished first time. So the re-round's input is the repair's diff with the range reachable for context, and the risk it trades away is written beside it. That closes a LATER.md entry at the release it named.

Verification, sharpened by someone else's measurement. Boris Cherny's "verification is probably the single most important thing people do not get right" (2026-08) agrees with this corpus's own line that a model reads its own output generously, and sharpens it in two ways now written into ./lenses: the reader gets the artefact, not the author's case for it — no plan, no reasoning, no summary of intent, because those are exactly what make a reader agree — and every objection quotes the line it is about, as written, never paraphrased, since a paraphrase is where a lens's own reading hides and the author answers the paraphrase instead of the line.

A reading list of 76 links, triaged — and the declines are written down. Seven new rows in ./catalogue where something carried a decision worth not rediscovering: engineering skills as a pool (addyosmani/agent-skills, whose references/ checklists are the part worth the visit — a written definition of done per craft is what a type's bar is meant to be seeded from) · offensive-security skills, with the authorisation gate in the row itself · a security audit whose findings are independently verified · a self-hosted machine pass over a pull request · office documents into the repository in bulk · worktrees for parallel work · memory and handoff between agent CLIs, read as a comparison and not a component. Four new gallery rows: landing pages, dark mode as a token decision rather than a filter, top-grossing mobile screens with their revenue context, and a React component source filed under not the same thing as inspiration. And a Read and declined section, dated, so the same links are not re-evaluated next quarter: the platform class that cannot host a system whose premise is the inverse, two overlapping methodologies that do not compose with this one, the wrong layer, craft tools for crafts no row covers, a free-endpoint aggregator that reads every prompt, and — the one worth stating plainly — a page whose title tag was empty, declined because it could not be described without reading it, and a row describing something nobody read is what this file must never contain.

Connecting a service takes what the service ships, and leaving removes only what is ours../tooling now says the first half out loud — an MCP server, a CLI, an agent skill and an SDK are each a dependency the moment they are used, so they are installed and declared where their kind is declared, with nothing installed on the machine without the owner's word. The second half carries the asymmetry: what is project-local goes automatically — the .mcp.json entry, the pool copy, the register rows, the diagrams — and anything installed on the machine is asked about, one line per item, because another project may be using it. No new gate: §20, §21 and §26 already hold all four kinds in both directions, which is the point of naming the rule beside them.

Two machine notes (CLAUDE.md): zsh does not split an unquoted $var — it landed after the 0.2.18 tag, and bit this range once more — and zsh reads =word as a command lookup, so a line of echo ===== fails with not found and the rest of the command line never runs.

Eval state: not run. The guard sections and the scripts are measured by their suites — 238 company-preflight assertions, 200 audit-gate, and a suite per script with its own code mutants — but a suite measures a form and an eval measures a run, and the behaviours this release adds are exactly the kind only a run shows: whether a session offers the machine line at a takeover, whether it brings the shelf before a decision instead of waiting to be asked, whether it wires a role's agent file at hire time rather than after a refusal, whether it proposes references instead of asking for them, and whether it opens a task rather than editing the source itself. Five behaviours, no scenario covering any of them, said here rather than left for a reader to assume otherwise.

What each runtime was found to provide is a table now, with not measured where nobody looked../runtimes gains a per-runtime grid — native question form · dispatchable headless · hooks enforce — carrying the dates and nothing inferred from a vendor page, and the run record carries the runtime it ran in beside the model. Antigravity's questions are recorded as owner-reported prose (2026-09-16), which matches the 2026-08-15 measurement of the same failure. The asymmetry is the reason the row exists: a question asked as prose still gets an answer, it is just a worse one, and nobody notices that anything degraded.

A type file written the way the template asked was not being read at all. parse_pipeline normalised the corpus's own arrow into a local variable and the inline-ladder search kept reading the original, so build → review → accept — the exact line templates/TYPE-template.md asked every project to write — matched nothing, the file parsed to None, and the task fell back to the stock ladder in silence. Measured 2026-09-17 on a live project: both of its type files invisible, with --check-ladder reporting no ladder found about files that plainly declare one. The same defect had been found and repaired for gates ten lines above in the same function; this is the place that repair did not reach, and it fails by finding nothing, which reads exactly like a project that never wrote a ladder. Fixed, with both cases in the suite. For a project upgrading, the visible consequence is that its own ladder starts being obeyed.

And design work arrives with candidate references instead of asking for them. ./visual now says an advisor proposes three or four named examples out of the catalogue with a line each on what carries the feeling — "send me some references" hands the research back to the person who asked for design — and the bar that holds it is a gate, not a paragraph: a fields: [references, anti-references] gate on the way out of a design stage, which transition.py refuses while either is empty, naming it (verified 2026-09-17). anti-references: none stated passes, because that is a conversation that happened; a blank is one that did not.

Trio: eight diagrams — ./tooling two, ./resources one, ./project-layout one, ./permissions one, ./hiring one, ./grouping one, ./visual one — twenty-one situations in ./use-cases, and ./facts 262–277. The ck situation is the one with no diagram or fact of its own, and owes none: ck adds no mechanism, it is declared like any tool and rides §21's.

Correction, 2026-09-25 — the Trio line undercounts its own release. The range from v0.2.18 to v0.2.19 added ten diagrams · twenty-four situations · ./facts 262–282 — not the eight, twenty-one and 262–277 above, which were written before the late rounds added the rest (thirty-four situation rows went in, ten of them rewrites of rows already there). Every one of those rounds read one repair's diff and none read this line against the release; the owner found it by asking. Measured by scripts/check-trio.py, which 0.2.20 adds for exactly this. And the trio those late mechanics owed shipped in 0.2.20: a diagram of how the publish gate reads a command, and a diagram, a situation and a fact for the frontmatter check and the JSON read of the scan.

0.2.18 — 2026-09-11 ​

Migration — one thing to look for in _ops/, and one optional re-copy.templates/RUN-template.md is copied into each run record as that record is written, and never installed as a file of its own, so this upgrade fixes every record written from now on. It cannot fix the records already written while the template's Attempt row was wrapped across two source lines: markdown reads that as a row plus a one-cell fragment, so those render a broken table. This finds exactly those rows and no others — a whole Attempt row ends in |, a wrapped one does not:

grep -rnF '| **Attempt**' _ops/ | grep -v '|[[:space:]]*$'

Join each line it prints to the one below it. Keep the -F. Without it ** becomes a regex operator, and depending on which grep your shell has, the search either stops with an error or quietly prints nothing. The quiet one is the danger: it looks exactly like having no broken rows. Separately, the one file you hold a copy of, _ops/scripts/preflight.sh, differs from 0.2.17 by its version stamp alone — re-copy it only to silence the guard's own stamp warning, which otherwise reports a version gap with nothing behind it.

The corpus-count check now reads the entry being written, and only that one. Every count in an older ./changelog entry was true on its date, and comparing those against a corpus that has moved on warns forever and grows by one line per release. The region read is the entry for the version in ./the-skill — the same cut scripts/preflight.sh already makes on this file, so two checks in one repository no longer disagree about what a changelog claim is. The **Trio:** line is exempt inside it, because it is the one shape here that states a delta in a total's words: four diagrams means four this release added, and no corpus total can confirm or deny that.

The first cut of this exempted the whole file, and the review round convicted it: that silences the one entry describing today's corpus, and made AGENTS.md's promise that showcase counters are guarded false exactly where the trio is written.

What the noise was hiding, measured 2026-09-10 — by running the v0.2.17 checker over the v0.2.17 tree, so this is a count and not a recollection. Thirteen warnings stood in the list; eight were false by construction, and the five real ones were printing second through sixth — under a single false one and above the other seven: a templates/RUN-template.md table row wrapped onto a second source line, which stops being a table row · a paragraph in ./choosing-tools whose line broke before 5., which markdown reads as an ordered list · a hyphenated word split across a line break in ./evals · and a ./catalogue row that had lost its third cell to the row eight lines below it, which the check reported twice, once against each malformed row. Only that last one was new in 0.2.17: the wrapped Attempt row has shipped in eleven consecutive releases, since v0.2.7 on 2026-08-16 — the row did not exist before that — and the other two since at least 0.2.13. They were never hard to see; they were near the top of a list nobody finished.

The eight false ones were three different defects, not one. Six were the stale comparison above. One was a miscount of the checker's own: a sentence ending in a colon is read as introducing a list, and one written inside a bullet — "…and only one carried a measurement:" — was charged with the three sibling bullets that followed it. A list item's peers were never its list, so the count now stops at the first item shallower than its intro, and at any peer where the intro is itself an item. And one was a plain regex misfire: mermaid was read as a counted noun inside "one mermaid edge" — across this whole corpus that spelling matched exactly one phrase and matched it wrongly, so it now has to name what it counts (five mermaid blocks). Thirteen warnings became zero, and none of them by raising a threshold. A checker that cries wolf gets bypassed was already the rule in AGENTS.md; this is its dated instance, seen from inside the file it was hiding in.

And the checker got its first test. scripts/test-check-structure.sh 18/18 — a fixture tree carrying a copy of the script, since it chdirs to its own parent's parent. It shows the count firing on a chapter, firing on the entry being written, and silent on a superseded entry and on a Trio line; and it mutates the rule itself, because a scoping rule that silences a warning and a fixture with nothing to warn about return the identical silence (./facts 254). Two assertions hold the blast radius: the changelog's other checks still fire, and the reported line number is the line in the file, not the line in the entry.

./consulting's depth diagram now matches its own prose. It drew any entry still not checked? as a plain step — a question a project keeping no source register cannot answer either way, which is the hole that withdrew Depth's gate before the last tag (LATER.md → Depth was demoted before its tag). Three changes: that case is now named rather than assumed away; the deciding rung now checks for a source register, as standing's gate does — templates/ANSWER-template.md required one for deciding, but the diagram drew that rung going straight to the answer (standing's check stays the stricter, since it also asks whether every entry was read); and the not-yet-checked branch, which ended nowhere, now reaches the answer at the rung its evidence supports.

The release ritual asks one more question, at the moment the date is set: what did this session learn about the machine? A note about how the tools behave here is evidence, so it moves without a tag and lands wherever it is written — and written after the date, it falls outside the release whose work produced it, leaving the entry that describes that work without the lesson. Nothing is lost when that happens; the note rides the next range. What is lost is the join between a measurement and the release that produced it, which is what a changelog is for. Machine notes landed after their own tag six times across four releases — 0.2.11, 0.2.14, 0.2.16 and 0.2.17, eighteen days end to end and the last two three days apart. Its rung on the promotion ladder was checked rather than assumed (./self-maintenance → A finding does not become a rule the day it is found): a rule costs a week and a second occurrence, and this had four occasions over eighteen days and still reproduced on the day it was written.

One shelf row re-verified by running the tool it describes — AGENTS.md, What the automation checks — and what it cannot: "If a sentence explains what a tool does, run the tool." The codebase-orientation row called a graph tool's zero-credit local build a README claim; it is now a measurement, and the measurement narrows it — what that build produces is an index, and the edges that make it a graph need an API key.

Eval state: not run. Nothing here changes what a run is asked to do — it changes when one question is asked during a release, and what a checker says about this repository.

Trio: no diagram and no situation, and ./facts 261. Everything here is a repair, and a repair owes the fact alone. The ritual question adds one step to a flow AGENTS.md already narrates; ./diagrams § Before a release draws the gates and the lenses and stops short of the entry, the date and the notes, so there is no node this step belongs beside and none was manufactured. The rest fix things 0.2.17 shipped. Said rather than manufactured, which is the whole allowance a repair gets.

0.2.17 — 2026-09-10 ​

Migration — and this one adds two refusals to the guard your project copies._ops/scripts/preflight.sh is a copy that does not update itself, so re-copy templates/company-preflight.sh over it: §18 refuses a skill that runs commands without recording what it refused, §19 refuses a research finding with nothing to order it or expire it. Until you re-copy, neither fires and nothing says so. Verify by the section numbers, not by grepping a version: §18 and §19 are absent from an unmigrated copy, where §15 and §16 mean other things entirely. Two templates change and one is new — SKILL-SCAFFOLD.md, ANSWER-template.md, and FINDING-template.md.

A skill now records the test instead of being asked for it. ./skills already said every command a skill contains is run before the file is saved, against an input it must reject — and N61 scored 0 of 5 on exactly that clause, counted from transcripts across three rounds, with one run declaring itself tested by reading a manual while the sentence describing that failure sat in the file it was reading. So the scaffold carries ## Tested against, which asks for the two things a reading cannot produce — the defective input and what the command actually printed when it refused — beside the date. You cannot paste output you never generated. Where a skill runs nothing — as all twenty-one of this project's own do, measured before building — none: is the whole answer, and §18 leaves it alone, because gating doors would teach everyone to write the section without meaning it.

Research got the layer it was missing: a finding is not its task. The task carries the cost, the depth and the done-when, and it closes; the conclusion stays true until a named event retires it and is where the next question starts. _ops/research/ existed as a name with no form — one line in the layout, no template, no prioritisation — and now holds one file per question, because a topic never closes and a question does. It opens with what we now believe in a paragraph, which is read instead of the sources under it; the register is for whoever argues. Decides is what orders them, so the reading order is the order of the decisions waiting and no second list is kept — a second list is a list that lies.

How deep is a question with a price, asked before the fetching starts — and it ships as guidance that admits it, not as a gate. Three rungs named by what they buy: orienting is a direction and never a finding · deciding is a defensible call · standing is something quotable in a year. It went in as a required field with a guard and came back out before the tag — the reasons, and the two dead parts of that check, are in LATER.md → Depth was demoted before its tag. The rungs stay as a price list; nothing gates them, and ./facts 259 says judgement call rather than implying a measurement.

The register can say what pulls against an entry. Nothing read contradiction between sources — verified by grep before building — and 2 of 20 entries carried a comparison buried in a prose paragraph where no field held it. Reads against names the entries this one must be read beside, or one of two words that are not the same answer: none found is a claim that someone looked, not checked is honest ignorance, and an empty field is neither. It is symmetric, so fetch-source.py --verify-reads refuses a one-way pair — it caught two on its first run, in entries written the same hour. The most useful finding was not a disagreement at all: six sources on synthetic personas look opposed and measure different things, and that resolution is what the rules downstream rest on.

The shelf grew by a sixth in rows and by two fifths in anchors. ./catalogue 124 → 145 resource rows, carrying 453 → 628 distinct anchors, shaped by its own contract — keep only the anchors that carry a licensing or fallback decision — so roughly a hundred and ten resources became rows carrying decisions: a typeface is licensed per use-class and webfonts are metered by pageviews · a mockup licence covers client work and almost never redistribution · product reading splits three ways because only one of them is disinterested. Three licences are read from the repository rather than from a page, and only one was actually mis-stated upstream: GitButler calls itself open source and is FSL-1.1-MIT, source-available until it converts. The other two are this shelf's own imprecision made exact — RudderStack was carried as "Elastic-style custom licence — flag" and is Elastic License 2.0 — and an absence rather than a claim: sharechat has no LICENSE file at all.

The lens round runs a second time over the repair it caused, before the tag. No new mechanism: the range is still <last tag>..HEAD, so it already contains the repair — the same four questions over a range that grew. And do not tag between the rounds, because that is what puts a repair beyond reach: it becomes an ancestor of the tag and every later range excludes it. What it costs is one more round per release and nothing here has measured that (./facts 256). This release ran it four times, and every repair below came out of a later round rather than the first.

A lens that ran out of budget is now recorded as not completed, never as empty. The two are identical in a report and opposite in fact. Measured on this release: three rounds lost twelve lens agents to usage limits, and the only place to say so was prose. A round carrying a not completed has not reported clean — which is what the tag waits on, so this release waited. Ported from the sibling, where the state has been in use since 2026-08-15 rather than invented here.

What to do: re-copy the guard. Nothing else is required. A skill you already have keeps working until you next edit it; a finding is a new file you write when you next answer a question worth keeping.

Eval state: not run. N61 is the regression test for the skill half and it is the before measurement — 0 of 5 — so the next round says whether the form moved it.

Trio: four diagrams, each beside the rule it draws · four situations · ./facts 256–260. Suites: company-preflight 237 · verify-reads 9 · audit-gate 123, all zero-fail.

0.2.16 — 2026-09-07 ​

A diagram now has one home rule: it lives beside the rule it draws, and ./diagrams keeps only what crosses several. ./self-maintenance §4 says so, promoted on two occasions a fortnight apart rather than argued — the contradiction stop's diagram went to ./escalating, the cost one to ./cost, and the standing question of whether that file is a gallery or a chapter is answered by that use. It matters because the gallery reading was on a collision course with the bar itself: a file holding every diagram meets the 500-line chapter budget at each new mechanic, and ./diagrams is at 500 today.

A second rule was written the same day and taken back out before the tag, by this project's own ladder. It would have decided repair versus new mechanic off the diff — a change shipping a new form owes the trio, a change altering what an existing form reads owes the fact alone — closing an exemption currently granted by whoever wants it. ./self-maintenance puts a rule at rung 3 and charges a week between the first dated line and the promotion; this one was conceived and promoted in a single sitting, which is the dotted branch of that section's own diagram: promoted the same day → a rule that was true once, on one machine, about one version. It is in LATER.md with its candidate form and the price it still owes. The exemption stays judgement and is now labelled as judgement, which is the honest state rather than a settled one.

  • The deferred list was read against reality instead of against its own prose, and three entries were not what they said. One trigger could not fire at all: it waited on a Closed cell in "this repository's own field notes" — a file this repository does not have and, by ./self-maintenance's own split, should not, since _ops/FIELD-NOTES.md records friction in a project while friction in the system operating it comes here. It now names a real occasion: a closure written by a sweep in a project, arriving as a field report, which is how 0.2.15's report arrived. One was pinned to a version, not a condition — "the next full round after 0.2.7", eight releases old, with N89 measured once in between. And one was not deferred work at all: the forty-minute report is a caveat about how strong a piece of evidence is, and it moved to ./runs beside the measurement that already discusses it. LATER.md holds what is not done, not what we are unsatisfied with.

  • A fourth entry was closed by that audit and reopened the same day by its own lenses — and that is the finding worth the version. The should this exist at all ladder waited on N97/N98 measuring whether a declared gate moves behaviour in the field. The closure read the round's prose arm — the declared line written once across ten runs — as "a declared gate, in the field, moved behaviour that little", and cited a page whose closing line reads "0.2.9 has no gate measurement": a validator reaches only a worker that commits, those ten runs committed nothing, and the paired arm that did reach the gate converted 3 of 3. The entry's own paragraph, four lines above the closure, already said whose gates the round could not reach. An audit that reads triggers against reality has to read the evidence file, not the trigger's summary of what would satisfy it. The entry is open again, its trigger now naming the reach rather than the run. Three lenses landed on that paragraph separately — the convergence signal LATER.md is still waiting for a form to read.

  • The capability bar's one open debt moved beside the bar. The council ships with a declared field and no form, and therefore no mutation test and no dated measurement; that admission belongs against the clause it fails, not in a list of things not yet built.

  • A ./catalogue row was removed, and one sentence went with it. It surveyed four external review harnesses and concluded, for three of them, that this project already holds the bound harder. The shelf says load when you already know how to choose and want a starting point — a row is grepped by need, and nobody greps a shelf to be told the need is already met. Three of the four survive in the 0.2.14 entry that surveyed them. babysit-prs's asymmetry — an unknown state counts as unsafe, and a wrongly-allowed push cannot be undone — survives only in this sentence, which is the honest accounting: the bound it describes is already the outward gate's, but the phrasing was borrowed and is not written anywhere else.

What to do: nothing. templates/company-preflight.sh differs from 0.2.15 by exactly one line, its version stamp — no check was added, widened or removed. Re-copy it only to silence the guard's own stamp warning, which otherwise reports a version gap that has nothing behind it.

Eval state: not run. Nothing here changes what a run is asked to do.

Trio: a fact (./facts 255 — a trigger fails by being absent, unreachable, or already fired, and the reader is the fourth way). No diagram and no situation, and that claim is judgement, not a test — nothing here ships a form, and the rule that would have made the judgement checkable is the one held back above. ./diagrams's placement rule is drawn by the file it governs; a release that moves prose between files owes no picture. Said as judgement rather than as a result, which is the state this entry is in and the reason the test is owed.

Four lenses, and the fourth found the release's own worst sentence. Three read the range and converged on one paragraph: an entry closed on a trigger that had not fired. The fourth read the repair and found the repair had reinstalled the same defect twice — a corrected trigger naming a condition met a fortnight earlier, and a mutant/twin pair whose mutant passed. Both are fixed above, both are recorded where they happened. The pattern is now six releases old: the sharpest finding is always in text written the same day, which is the argument for the ladder and against trusting a rule because it is new and feels right.

0.2.15 — 2026-09-05 ​

Migration — and this one affects every project whose roles and tasks are written from the shipped templates, which is the shape this system tells people to use. Three checks that were silent on that shape now speak, and two of them refuse a commit: §7 where a project has two advisors, §11 where a task's assignee is also the name that reviewed it. §8 warns where a role carries eight or more skills. Measured on a fixture written exactly as ROLE-template.md and TASK-template.md instruct: silent under 0.2.14, refusing under 0.2.15. Nothing was wrong with those projects yesterday and nothing is wrong with them today — the checks simply could not see them, so a rule the corpus states in three files was enforced by nothing.

What to do: re-copy templates/company-preflight.sh over _ops/scripts/preflight.sh — the guard is a copy and the fix does not arrive on its own. Then expect, once: a second advisor named where there should be one, a self-signoff that had been passing, and skill counts on roles that had been counting zero. None of the three is a defect in your project; they are three years of green that meant nothing.

A field report from a live migration, and the sharpest finding in it is that a guard had been repaired once already and stayed blind.

  • §7 and §8 of the company guard could not see a role written from this repo's own template. Both parsed YAML frontmatter — type: advisor, a skills: list — while templates/ROLE-template.md writes **Type**: advisor · **Grade**: senior and a ## Skills attached table, and contains no type: line at all. Measured 2026-09-05 on a live migration: two declared advisors, one found; nineteen skills in a table, zero counted. The threshold §8 exists to enforce could never be reached by a role written the way this project tells people to write one.

    This pair had already been repaired. The comment above §7 records fixing it in August — for the directory it looked in — and the format mismatch survived that repair untouched, so both checks went on reporting green about files they never read. A guard that is green because it looked at nothing is worse than an absent one: it issues a report on a check that did not happen. Both forms are read now; the YAML stays as the legacy branch. The unfilled template, whose placeholder lists every type including advisor, is correctly not counted.

    And the YAML path had never been measured either — it counted 20 of 19, because the frontmatter's closing --- matched its own list-item pattern. An off-by-one is harmless; what it tells you is that nobody had run the numbers on either branch. Both forms are covered now as must-fire/must-not-fire pairs — the table and the YAML list, a filled role and an unfilled template, above the bar and below it — and restoring either old reader fails them. The count of assertions is deliberately not quoted: this entry said "eight", was corrected to "seven" when they were counted, and was eight again an hour later when one more landed. A number that moves whenever unrelated work touches the file is a claim that rots by construction, which this corpus learned one release ago and repeated here anyway.

  • The sweep the report asked for found a third one, and it is the most expensive. Its closing caveat was that the question — does this section read the shape its own template writes? — belongs to every check that greps a templated file, not only the two it measured. Put to all 29 sections: §11 read the author of a task as ^(assigned|author|worker)[: ], a bare word at line start, while TASK-template.md writes **Assignee**: — bold, and a different word. The author came back empty on every task written the way this project tells people to write one, so the comparison could never be true and the check that stops an agent approving its own work never fired on this project's own format. Measured: 0 on the template's form, 1 on assigned: ui. Both shapes and both spellings are read now, the legacy form still works, and an honest review by someone else still passes — that last assertion exists because a check that refuses everything proves nothing.

    The other sections held. §1c, §2, §3, §4d and §16 each spoke on a violation written from the template and stayed silent on the honest twin. The sweep's own probe was wrong five times before it was right, in five different ways, none of which ship: a probe auditing guards needs exactly the scrutiny it is applying, or it manufactures the same green it went looking for.

  • An adversarial lens read the repair the day it was written and found four defects in it — three of them false refusals the repair itself introduced. That is the costlier direction by this file's own accounting, and all four were in lines that had just been edited.

    §7 refused two ordinary shapes that passed the day before — built as fixtures, not counted in the wild; the repair was unreleased, so nothing outside had met it. Widening it to the template's prose form left it a grep -r over the whole directory, so a README.md documenting the form and an archive/ holding a retired role both counted as advisors. §8 has iterated "$roles_dir"/*.md all along; §7 now matches it, and a top-level README is excluded by name, because a document about roles is not one.

    §8's new counter warned falsely at seven skills and at three. It recognised the header row by its spelling, so a bolded | **Skill** | or a column called Name counted as a skill; and it closed the table only on ## , so a six-row ### Escalation table below pushed a three-skill role to ten. Shape, not spelling: the header is the first row whatever it is called, and the table ends at the next heading of any depth or the first line that is not a row.

    And §11's reviewer half kept the exact defect its author half had just been repaired for.**Reviewed by**: ui — the shape a bolded label naturally takes — extracted no reviewer at all, so the check passed on the very form the repair was about. Both greps are -i and the comparison between them was not, so UI could review ui. Both closed, both asserted, five mutants each failing the assertion written for it.

  • migrate-layout.py claimed skills/ at a project root unconditionally. In the reported migration that directory held a single README pointing at an unrelated repository — someone else's entity, moved silently into _ops/ and put back by hand. The comment above ENTITY_DIRS already spells out this exact care for its neighbour — scripts/ is absent on purpose: at a project root it is usually the craft's own — and docs/ takes only the names it knows and prints what it left. skills/ was the one of the three the caution had not reached. It is claimed now only when something under it carries a SKILL.md, and otherwise left where it is with a line saying so.

  • Three more the same lens found, in code this release wrote. looks_like_skill_pool was fooled by a symlinked subdirectory — is_dir() follows links, so someone else's entity one symlink away made the whole directory look like ours, which is the exact failure the check exists to stop. The new left alone: line could not print when it was the only thing to say: it lived after an early return 0, so a repository whose sole ambiguity was a foreign skills/ heard nothing about it, and the shipped test passed only because its fixture also had something to move. And scripts/check-releases.sh reported green on nothing: gh release list's exit status was discarded, so no authentication, the wrong repository or no network read as "0 releases checked, every one matches its entry" — ./facts 254's own shape, shipped in the three commits that quote it. It also printed "3 of 0" when a release had no entry, and took --emit only as the first argument, so the natural check-releases.sh owner/name --emit <dir> silently did nothing.

  • --dry-run printed one file as both moved and left behind. leftovers read the directory from disk, which is right after a real move and wrong before a previewed one, so every file the preview had just promised to move appeared again as staying put. The behaviour was correct throughout; only the preview lied, in the one place a preview exists for. Fixed in both methodologies, since the line was identical in each.

  • scripts/check-releases.sh gets the suite it shipped without. Four defects were found in it the day it was written and repaired by hand, in a release whose whole subject is a check that reports green on something it never read — the script's own worst one being exactly that: gh's exit status was discarded, so no authentication read as "0 releases checked, every one matches its entry." Fourteen assertions now, with a stubbed gh so the suite needs no network and no credentials; four mutants, one per original defect, each failing the assertion written for it. The release ritual names the script in AGENTS.md too, not only in CLAUDE.md.

  • The All-in-one client DB row is gone from the shelf: InstantDB is sunsetting. Read at source 2026-09-05 — "Instant is sunsetting. Services will continue until August 31st, 2027", the team having joined OpenAI. The service outlives the recommendation by a year, which is the reason to take the row out rather than date it: a shelf exists to be started from, and starting on something with a published end date is the one thing it must not suggest. Recorded here rather than struck through in place, so the shelf stays a list of what to reach for and the reason a name left it stays findable.

Eval state: not run. No scenario writes a role from the template and asks the guard about it; the guards are covered by their suites, which print their own totals.

Trio: a fact (./facts 254 — a guard that reads a format its own template does not produce is green because it read nothing). No diagram and no situation, and that is deliberate: these are repairs to mechanics the corpus already draws and already narrates, not new mechanics. Saying so is the rule, not an omission.

0.2.14 — 2026-08-29 ​

A rule this corpus had written in file after file and enforced in none is now a form. ./escalating stops work at the second disagreement between two runs on one question — the bound on contradiction, where the three-attempt rule bounds only failure. Contradiction is the worse state and the reason is structural: both runs finish, so both report confidently, and a ledger that records how a run ended has no place to notice that they concluded opposite things.

  • A run record carries a Verdict — what it concluded — beside Outcome, which is how it ended. LATER.md named this gap on 2026-08-21 and deliberately refused to fill it: "adding the field for the gate's sake alone would be the shape this corpus refuses — so it waits for a flow that wants the verdict written for its own reasons." That flow is review, and the closed entry carries the argument. Most runs check nothing and write none; the word sits in the cell rather than beside it, which is the lesson templates/RUN-template.md already paid for on its cache cells — not one run of five wrote the accepted word when the cell did not carry it.

  • §1f of the company guard reads it. A new record concluding pass where a completed sibling on the same task concluded fail, with neither naming an escalation, is refused at the commit — and the refusal prints the literal line to write, which is the shape that measured 5/5 on this corpus where a keyword hunt measured worse than nothing. Recording the disagreement satisfies the gate; what is refused is the second opposite verdict landing with nothing saying anyone noticed. It escalates as "the question is unstable, and here is what differed between the two askings" — never as "which run was right", because that framing produces a winner rather than a resolution. Only pass against fail clashes — every other value, and every run that did not complete, is excused, because this file's own history says a false refusal on ordinary work costs more than the miss it closes, since it is how a project learns to reach for --no-verify.

    Measured 2026-08-28, dated in ./escalating where the claim is made: four mutants of the guard each fail assertions the honest twin passes, on a suite that is 189 green.

  • Three of those assertions were vacuous when first written, and each was caught by a mutant rather than by reading. One restored a fixture with git checkout -- <file>, which restores from the index — and the index held an escalation added two assertions earlier, so two checks passed because of a leftover rather than because of what they tested. One reused a record id the preceding commit had already taken, and §1f examines only added records, so nothing was examined at all. And one guarded against an unfilled {{…}} cell that the regex already refused — dead code, and worse than dead: on a half-filled cell it blanked a verdict genuinely reached because the sentence beside it was still a placeholder. All three now carry a paired assertion that fails if the fixture stops being examined.

  • The capability bar took two of the three in, plus the one that follows from them (./self-maintenance §2), together with the one that follows from them: a mutation asserts that it changed the file, because a wrong anchor makes the edit a no-op and the green that follows reads as the mutant survived — a conclusion about the assertion when the truth is about the patch.

  • The deletion lens read it before the tag and found the guard paying twice for one walk. The neighbour table shipped as two tables built side by side from the same buffer, with record_task forked twice over identical bytes — while the comment between them congratulated the loop for not walking the files twice. One table now, one fork, and the escalation pattern that had been copied into three call sites has one home. Consolidating three correct call sites produced one wrong helper: the merged version lost -i, every record writes **Escalated**: with a capital E, and five assertions went red until it came back. It also found mixed shipped as a legal value with no code path and no fixture, and the excused set spelled out four times across three files — two of them one mermaid edge and one sentence in the same file — no two alike and none complete. One clause now, in one sentence, everywhere it appears.

  • Two more lenses read it before the tag, and both found the release contradicting itself. The memo guarding the neighbour table still named the variable the deletion round had removed, so the table was rebuilt once per changed record — four builds for four records, 16s against 150 kept ones — reintroducing, in the commit titled one walk paid for twice, the exact cost a comment in the same file says was removed in 2026-08-16. No assertion could see it; the check is structural now, requiring the guard's variable to be the one its body assigns. Re-measured after the repair: one build, 5s at 150 kept records and 4 new ones. The older three-attempt gate, which now reads the same table, was re-probed and still refuses at three and passes at two.

    House style switched the gate off. This corpus writes every enum in backticks, including in the sentences defining this one — and a record following that style wrote a backticked verdict that §1f read as nothing. Bold did the same. Every fixture in the suite wrote the cell the one bare way that worked, so nothing caught it. A false refusal teaches --no-verify; a false silence teaches nothing at all.

    The repair for that then refused the reader who complied. Someone who pastes the printed line and types their answer after the angle brackets is still carrying the placeholder, and got back the message asking for the line they had just written — unfollowable in one direction, which is the failure the attempt gate beside it was rebuilt to stop, reproduced by its own fix within the hour. The two mistakes have two messages now, and the second one names the bracketed text and says to replace it rather than type around it.

    And the refusal was satisfied by pasting the refusal. The line it prints ends in a placeholder, and pasting the whole thing unedited passed — the shape §1f's own attempt gate condemns eleven lines below, with the gate handing over the shortest path to it. The placeholder has one home now, printed and excluded from the same string.

    The numbers in the entry did not survive checking. "Three mutants each fail 5 assertions" was four mutants failing 8 · 3 · 2 · 5 a day later. Per-mutant counts shift whenever an unrelated assertion lands, so quoting them is a claim that rots by construction — the release states which mutants and what the twin totals, and nothing finer.

  • The adversarial lens found the one rigid reader in a section that is loose everywhere else. Every other field check in §1f is a substring test; record_task was widened on 2026-08-21 with the note that the rigid reader in a loose section is the reader that silently counts zero; and verdict_of, written last week, read a table row and nothing else. A record declaring **Verdict**: pass as a line satisfied every other check in the section, was refused by nothing, and was invisible to the gate — for Outcome as well. Both shapes are read now, and fenced blocks are skipped by both, because a record quoting this template for reference was otherwise read as concluding whatever the example says.

    Three more, all silent. A record declaring Verdict twice was read by its FIRST row, so none above pass hid a real contradiction while the reverse order was caught — one verdict per record now, and a second is a defect in the record. A value that is not one of the four — passed, or failed, which is a legal Outcome two rows above the same cell — took the silent-excuse path; it is refused with the four values named. And an empty ESC_PLACEHOLDER would have made every line match the exclusion, refusing honest work with a message about a placeholder it does not carry.

    And a false refusal, which is the costlier direction. T-A.1 and T-A.2 both read as T-A, so two records on genuinely different tasks were refused as one contradiction — naming a task id present in neither file, which is this section's own condemned failure mode arriving through the id reader rather than the matcher. An id that does not end where the guard stops reading it is now skipped with a warning rather than compared against the wrong task. Measured at 300 kept records: one table build, 7.6s per commit.

  • Four external review harnesses read, one mechanic kept as a candidate, one declined outright → ./catalogue. qa-swarm's convergence — two independent reviewers landing on the same file and line is evidence, not a duplicate — is named in LATER.md with its own bar, because lens output is prose today and there is nothing structural to compare. review-triage's a human in the thread ends the bots' authority over it is the PR-thread spelling of a bound §1d already holds harder. no-mistakes is declined: prescriptive prose, no measurement, and its pipeline is this project's preflight plus its outward gate.

Nothing breaks, and there is one thing to do. A run record written before this version has no Verdict cell at all; measured — such records are invisible to the check and every commit that touches them passes, so no migration is owed and none is offered. To get the gate, re-copy templates/company-preflight.sh over _ops/scripts/preflight.sh; until you do, the guard you have will say so itself at your next commit, because it compares its own stamp against the guide. Records written from here on carry Verdict, and none is the answer for most of them.

Eval state: not run. No scenario dispatches two runs at one question; the guard is covered by its suite, which prints its own total.

0.2.13 — 2026-08-28 ​

Eight rounds of lenses read the tagged 0.2.12. Five remedies for the same defect were written across three days; two of them shipped and destroyed data, and three more were caught before they could. Every one was written to prevent exactly what it then did. The pattern is the entry: a repair that generalises its finding instead of its rule leaves the class standing, and the class is what comes back.

  • find-installs.sh told you an install was at risk because of an edit somewhere else, and the remedy it printed deleted that edit. It read git status across the whole repository, so an install living inside a larger one — a dotfiles clone carrying .claude/plugins/<skill> — was flagged for a change in an unrelated folder, and the git reset --hard it prescribed is repo-wide too.

    If you ran it, here is how to tell what it took. git reflog in that repository shows the reset and when — look for reset: moving to. What it discarded is every uncommitted tracked change in the whole repository at that moment, and the two halves recover differently: anything that had been git add-ed is still in the object store — git fsck --lost-found writes the dangling blobs into .git/lost-found/ where you can read them. Anything never staged is gone, and no tool will bring it back; we would rather say that than let you spend an evening looking.

    The second remedy took a different file, and it is recoverable by the same route. Its victim is by definition one you had git add-ed — that is the only kind restore --source=HEAD deletes — so the blob is in the object store and git fsck --lost-found writes it out, exactly as above. Verified 2026-08-27 against real git: the staged file's contents came back byte-for-byte from .git/lost-found/other/.

    Detection stays repo-wide, because the route it predicts is — git pull --ff-only aborts on a modified tracked file anywhere in the enclosing repository, so an install inside a dotfiles clone really is at risk when a sibling folder is dirty. What changed is which files it counts, and that is what makes a discard safe to print. Four remedies were written here in three days. Two shipped and destroyed work: reset --hard, repo-wide, which took an unrelated file; then a scoped restore --staged --worktree --source=HEAD, which deletes a staged new file outright, because a path absent from HEAD is restored to not existing. The third refused to name any discard at all — safe, and unhelpful to someone whose route is stuck. The fourth was one command from shipping the same defect a third time: counting with --diff-filter=MDRT looks like it yields paths that exist in HEAD, and its R rows name a rename's destination, which does not. Measured, with the file deleted. The count is --no-renames --diff-filter=MDT now, for which the property is true and demonstrable: rename detection off decomposes that R into a D at the old path — present in HEAD, and the path an incoming commit can actually collide with, so detection gets sharper rather than weaker. The suite lifts the listing command out of the printed message and checks that property against a tree containing a rename; a mutant restoring MDRT fails it. The discard is printed in full — restore --staged --worktree --source=HEAD — because dropping --staged leaves the index differing, which stops the pull while this flag reads clean.

    A fifth remedy was in the same message the whole time, and it could take a stranger's work.stash push exits 0 having saved nothing when the only difference is a submodule gitlink; the stash pop that follows then pops whatever was already on the stack. Measured 2026-08-28, with a week-old unrelated stash dumped into the tree and the stack left empty. The sequence is printed guarded now — refs/stash compared across the push, pop only if it moved — and the suite runs the printed line verbatim against exactly that repository and requires the unrelated entry to survive; the unguarded form, run on the same fixture, consumes it.

    And the count has two blind spots, now stated wherever it is described. git pull --ff-only also aborts on a staged new file and on an untracked file when the incoming commit adds that same path — both measured 2026-08-28 with the count at 0. Neither is countable without knowing what is incoming, and counting every untracked file recreates the false positive that started all of this. So the flag says outright that the count is a warning and the pull is the only exact test, and it leads with the pull because running it costs nothing. A shipped comment said flatly that an untracked file never blocks a pull — the stray-file measurement generalised into a rule it does not support, in the function whose own entry accuses an earlier repair of exactly that.

  • --doors-only overwrote a file outside the repository through a HARD link. 0.2.12 closed the symlink route by resolving the destination; a hard link has no target to resolve, so containment passed and the door was written through — measured, a file outside replaced by 19 KB, reported as success at exit 0.

    If you ran --doors-only on a repository where _ops/scripts/transition.py or _ops/scripts/new-id.py was a symlink or a hard link, your file is probably still there. The command wrote a copy of what it was about to overwrite, next to the door: _ops/scripts/<door>.replaced-<hash>. That copy is an ordinary new file — it does not share the link — so it holds the original bytes even though the door itself was rewritten. Look there first. To find the other name: for a symlink, readlink _ops/scripts/<door> prints it outright; for a hard link, ls -li _ops/scripts/<door> gives the inode and find ~ -inum <n> finds the rest. The inode hunt does nothing for a symlink — it returns the link itself — so read which kind you have first.

    A door whose link count is above one is refused now, before anything is written.

  • Eight ways _ops/TOOLING.md could be made invisible to §4f, all closed, and they need four cures. An inline <!-- … --> in a live row hid that row while the page still rendered it · a <!-- with no closer hid every row after it, permanently · a stray fence marker at the top did the same — an opener is believed only when a closer exists. A line that was ENTIRELY an inline comment read as the end of the table and silenced every live row below it, which is the parked-draft idiom the guard's own message recommends — a line left empty by the strip is hidden rather than read as a boundary. The strip that fixed that could not cross a >, so a parked row containing -> or an HTML tag brought the silence straight back, and a CR at the end did too — the strip survives the text it walks. And two more, found adversarially after all of that was written: a live row quoting the opener in backticks — `<!--` — had no closer on its line, so the multi-line path fired and swallowed every live row down to the next parked row, meaning a register that documents its own parking idiom disarmed the gate; and a bare --> left standing was read as the end of the table. An opener inside a code span is text, and a stray closer is not a boundary. This bullet said "three ways · one cure" through three of these discoveries and "five" through two more; the count moves with the next one.

    The strip also rebuilt the whole line every pass, rescanning everything already walked: 27.8s on a 390 KB row against 0.06s for the single gsub it replaced — measured 2026-08-28, a fresh regression from the repair above. A guard that slow is one people run with --no-verify, which the file's own header names as its failure mode. The walked prefix is consumed and never scanned again: same output, 0.7s.

  • A tab inside a tool name dropped its row, and a CRLF register read a blank answer as filled. Both were introduced by 0.2.12's own rewrite of that block.

Doctrine. Five entries joined ./facts, and one of them had to be scoped before it shipped: an interactive multi-step flow is a form that does not hold is true of a flow a run walks unattended, and the progressive interview is the opposite case — a person answers at every turn, and that is what forces the next step. Written unqualified, it condemned this system's own front door. The other four carry their source in the line, because ./facts is what other writing quotes and a rate without its origin becomes this project's number by morning.

_ops/FIELD-NOTES.md gained a Closed column — the version that shipped the fix. Nothing validates the table's shape, so an existing five-column file keeps working; add the column when you next close something. The one edit the append-only rule permits is this cell moving from empty to a version, and a Closed that already holds one is never changed.

  • --doors-only wrote through a symlink whose target was inside the repository. The containment test only asks whether the target leaves the tree, so an in-repo target was overwritten — a tracked file replaced by 19 KB of door. A second name is a second name wherever it lives, and both kinds are refused before any write.

    This one is the mildest of the three, and you have two ways back. The target was tracked and its path is in HEAD, so git restore -- <path> returns it. And the .replaced-<hash> copy written beside the door holds that file's original bytes — the copy is made by reading the door path, which follows the symlink to the target, and written to a fresh path that shares no link with it. Either route is whole; the earlier entry's warning about a backup landing on the wrong side belongs to the outside-the-repository case, not this one.

    The same bullet's claim did not cover a symlinked DIRECTORY, and an adversarial lens walked straight through it. With _ops/scripts -> scripts_real the door is an ordinary file with one name, so the second-name test saw nothing, and the containment test asks only whether the target leaves the tree — which it does not. Measured 2026-08-28: a tracked 45-byte file replaced by 19 KB of door at exit 0, while the printed diagnosis said the link led out of the repository and told the reader to move it in, where it already was. Any symlink between the root and the door is refused now, in either direction, and the reason is the one thing both directions share: git cannot stage a path behind a symlink — pathspec … is beyond a symbolic link — so no commit here could carry the door whichever way the link points. Your bytes are recoverable the same two ways as above.

  • Four notices in this project's own hooks were giving orders to the runs that read them, and a fifth is in the sibling. The one that started it fired after twelve read-only calls: "Stop digging: say what you know, start the wave (a task, a dispatch), or ask the one question that is actually blocking." For a review, an audit, or a question answered from the record, twelve read-only calls are the contract, so for that reading the order was simply wrong. It reports the count now, names both readings, and says it cannot tell which.

    Removing that instance left the class, which an adversarial lens then found three more times in the same file, on the same non-blocking channel: "Before acting on it, say so, run the migration audit", "bump the version line in the guide", "Reconcile the version line before acting on the project". All three state what was found and what procedure this project publishes now, and command nothing. The sibling's dispatch-nudge.py carried the twin defect untouched — "Assign it to an agent", "take the next real step" — and its suite asserted that it did, requiring the note to say to dispatch. Both are repaired; that assertion is now its opposite.

    The guard against all of this was a spelling check, and the lens proved it. Three assertions grepped for the literal words "Stop digging"; a mutant that put the order back as "Stop investigating and start the wave now. Do not read another file." passed all three. The check is a shape check now, run over every notice the file emits and keyed to ./security's own published test — text that "tells the reader to run, install, send, fetch, grant, ignore, or contact" is an instruction found in data. It denies the synonym mutant, and it caught one more place in the shipped tree where an option read as an order.

    The rule was also cited wrongly here. This entry said the position is that text arriving through a tool is data — a provenance test, and ./security exists to replace exactly that one: the test is who the text addresses, never where it came from. The conclusion holds under the real test; the reason given for it did not.

    And the note did not arrive once. Read, increment, write and the already-spoke test are four unsynchronised filesystem operations, so a batch of parallel read-only calls put several past the threshold together and each one spoke — twice in 6 of 12 trials, measured — which is the run it was written not to harass, harassed twice. An O_CREAT|O_EXCL create settles it atomically: 0 of 12 after, 5 of 12 for the unfixed form on the same probe, and four trials of it run in the suite. The count in the text is the real count now, too; it was hard-coded to twelve while n could arrive higher.

Eval state: not run. No scenario measures any of this; the guards are covered by their suites, which print their own totals.

0.2.12 — 2026-08-23 ​

Four lenses read 0.2.11 after it was tagged. Everything here is what they found in it.

  • §4f asked the rung of retirements. A tooling file commonly carries a second table — ## Retired, recording that something went away — and every row in it was checked for what it replaces, which is the opposite question. It passed or failed by accident, on whether the first table's column ordinal happened to land on a filled cell in the second. Rows belong to their own table now, found by the header that actually carries the Replaces column. A register with no such column anywhere keeps the old behaviour, where every table row is a candidate — so a ## Retired table in that shape is still asked, and that is the honest limit of this fix.

  • --doors-only ended its refusal with "read the reason above and pick accordingly" — pointing at a parenthetical in its own sentence, and, when git printed nothing, at an unresolved either/or. It picks one remedy from git's own reason now. And it stopped printing "doors re-copied beside the guard" over doors it had just failed to stage — the shape git pull uses to hide its own failures, printed directly beneath two refusals.

  • find-installs.sh called healthy installs broken. It fired on untracked files, which do not stop git pull --ff-only, and offered a reset — destructive advice for a false positive. If it told you an install was broken over a stray file, the install was fine. It reads tracked modifications only, says AT RISK rather than BROKEN (a modified file stops a fast-forward only when an incoming commit touches it), and its remedies are commands you can paste. It had no test at all, under a commit titled a route nobody verifies is a route nobody has; it has twelve, and they are what forced BROKEN down to AT RISK.

  • --doors-only overwrote a file outside the repository. If _ops/scripts/transition.py — or _ops/scripts itself — was a symlink leading out of the tree, the door was written through it: a measured case replaced a 35-byte personal file with 19 KB of door, and the only notice was "the previous file is at …", naming a backup written inside the repo whose original was not. If you ran --doors-only in a repository where either path is a link, check what is at the far end of it. The destination is resolved now and a write that lands outside the repository is refused, which costs nothing that was going to work: no commit there could have carried the door either.

  • Three words meant one thing in the glossary and another in the code — door, rung, and the reach gate, which was 0.2.10's headline capability and appeared in no README and no glossary, only inside its own implementation.

An earlier draft of this entry claimed three fixes that shipped in 0.2.11 — §4f reading from the index, the four column-finding defeats, and --doors-only staging a door missing from the index. All three are in the v0.2.11 tag; two lenses caught it independently by checking the claims against git show v0.2.11:. This is the third release running where a draft entry promised an upgrader a fix they already had, and 0.2.11's own entry carries a parenthetical apologising for the second. A changelog claim is checkable against the tag in one command, and checking it is not optional.

Eval state: not run. Nothing here changes what a run is asked to do.

Correction, 2026-08-27 — this entry omitted the most important thing in its own range.91c6425 closed a single-character bypass of §4f: the register's header was handed back to awk as a command-line assignment, which awk escape-processes, so a header containing a backslash arrived mangled, the comparison matched nothing, and the rung went silent for that file permanently. Anyone who wanted the gate gone edited one header cell. Three further evasions closed with it — a Replaces column on a later table capturing the gate, a decoy table above the register, and -F"|" shifting fields on an escaped or backticked pipe. If you are on 0.2.11 or earlier, this is the reason to move.

The entry also opened "Everything here is what they found in it", which was false of the tag: the range also carried five ./facts entries, a ./runtimes row, two ./catalogue rows, a LATER.md entry and a rule change in templates/TOOLING-template.md. Found by two lenses reading the tagged range four days later. The heading is frozen, so this is a marked correction rather than a rewrite.

0.2.11 — 2026-08-23 ​

Three gates were refusing honest work, and two of them were refusing the remedy they had just printed. All three shipped in 0.2.10 and are repaired here. (An earlier draft of this entry also claimed the tool-choosing diagram and its missing fact — those shipped in 0.2.10, not after it, and listing them here would have promised an upgrader a fix they already had.)

  • --doors-only told you your door was not staged when it was. It asked git diff --cached --name-only, which lists paths whose index entry differs from HEAD — so a door deleted and restored to identical bytes is staged correctly and appears nowhere in that list. If you saw "written but NOT staged" while following the guard's own instruction, your tree was fine, and the advice about gitignored paths and symlinked directories did not apply to you. It asks the index directly now.
  • §4f read your register's header as a row unless your first column happened to be called Tool, Name or What. If you named your columns anything else — | Thing | Why | Owner | — then standing up _ops/TOOLING.md at all, headers written and no tools in it yet, was refused for saying nothing about what it replaced. (The shipped template opens | Tool |, so a register begun from it was not affected — which is exactly why this went unnoticed.) The header is now found where it structurally is, above the separator, in any language and any GFM dialect. A fenced example table is not a row, and neither is a separator written |-|-|.
  • §4f was stricter than its own refusal message. With a Replaces column in the register the gate read that cell and nothing else — so writing the reason in _ops/DECISIONS.md, which is what the message tells you to do, left you refused by the same message with no hint that a column existed. Both homes now count: the cell, or a decision line that names the tool.

Four lenses then read the range before it was tagged, and the worst thing they found was a regression in the repair itself. §4f had been reading the register from the working tree and the added lines from the index: the moment those disagreed the intersection was empty and the rung went silent — so staging a row and then aligning the table's pipes, which is the next thing a person does, passed a row the gate had just refused. That is weaker than what it replaced. Everything is read from the index now, which is what a commit is made of. Four more ways the same block could be defeated or could refuse honest work, all measured and all fixed: a header below line 20 disarmed the column check entirely · a sentence in the preamble naming Replaces hijacked the column index so every row was refused · a plain, unbolded Replaces header sent an honestly filled register to the keyword fallback · |-|-|, which is valid GFM, read the separator itself as a row. And a ~~~ fence or an HTML comment no longer hides an example from being seen as one.

Also repaired here, from the same reading: --doors-only reported "already in place and current" for a door sitting on disk with the right bytes and missing from the index — the one case its own refusal names as measured, and the one the remedy could not reach. It stages it now and says so.

The pattern in all of it: the condition a gate fires on was repaired and the sentence it prints was not. Assertions were added for each; the suites print their own totals, which is the only count with a guard on it.

Migration — and this one affects you if you upgraded to 0.2.10. The guard copied into your repository at _ops/scripts/preflight.sh stamps its own version on line 2, and warns when that stamp disagrees with the version your guide says you run. The stamp was not bumped for 0.2.10, so the warning fired on every commit — and re-copying the guard, which is exactly what the warning tells you to do, brought the same stale stamp and did not clear it. It is stamped 0.2.11 now, and a check in this repository's own preflight refuses a release whose stamp lags the version, so it cannot be forgotten again. What to do: re-copy the guard once more from this release, along with the two scripts beside it, and the warning goes quiet. Nothing else about your repository has to change.

Eval state: not run. Nothing here changes what a run is asked to do; it changes what the guards accept, and every change is covered by an assertion in the suite that owns it — test-company-preflight.sh, test-migrate-layout.sh, test-audit-gate.sh.

0.2.10 — 2026-08-23 ​

The reach, not the gate. 0.2.9's round measured what its gates could not: across ten runs the player edited the machinery 8 times and committed 0 times, so every rule this project holds — all enforced_by: validator, all enforced at the commit — went unreached. The gates were not weak; the work never arrived at them, and the paired arm proved it: with "then commit it" in the turn, the same gate was reached, refused, answered and satisfied 3 of 3.

So the ending is refused while _ops/ sits uncommitted — a Stop hook, and it forbids rather than asks. That is the design, and it is measured too: this system's own rounds found a fact delivered at session start bought 0 of 5, a demand at the ending bought 1 of 5, and the one rule that only ever forbids held 5 of 5 in all three. A reminder to commit is precisely the shape that does not work.

It speaks once — a gate that repeats is one the next run learns to sit through, and leaving work deliberately is a real answer, said in a line. It watches _ops/ only: the product's own files are the craft's business, and a run may rightly leave them for review. OPSINIST_UNCOMMITTED_GATE=off is the deliberate door.

The four clauses: the form is the hook · six assertions, and the mutant that removes the gate is denied (108 → 107/1) · measured 2026-08-22 · the rule in ./recovering, fact 245, a situation.

And the rung above every tool choice: does this need to exist at all. Taken from a third-party ladder the owner brought — YAGNI · already here · standard library · native platform · installed dependency · one line · only then the smallest thing that works — and taken as a form, because the same ladder as prose is what this corpus measures at zero. A commit that adds a dependency names it in _ops/DECISIONS.md, with what it replaces and what was rejected: the dependency's own name, not a keyword, because a gate satisfied by vocabulary teaches people to sprinkle words. A version bump is not a new dependency and is not asked. Four assertions, mutant denied. Its extractor took three iterations and reformatting was the adversary every time — re-indented neighbours, then collapsed-and-expanded braces — each caught by its own suite within the hour, and each named in the comment rather than smoothed over.

Pass thirteen read this range before it shipped: 91 findings, 8 critical, and every critical is repaired here. The four worst were mine, made the same day the capabilities were:

  • The reach gate fired outside its own project. It sat above the arming check, so with the plugin merely installed it refused the ending of any session in any git repository holding an uncommitted manifest — including sessions that never opened this skill. Four lenses, independently. It is armed now, and it also asks whether this session wrote anything: a run that only answered a question is not blamed for the tree it inherited.
  • Both gates were blind outside the repository root — a bare package.json is a pathspec anchored at the top level, so a monorepo's frontend/package.json was invisible to the very gate widened for that shape the day before.
  • §4e refused the exact remedy its own refusal prescribes. The boundary treats . as a name character so lodash.merge holds together, which made left-pad. — a sentence ending in a full stop — fail. A maintainer writing precisely what the message asked was refused again.
  • §4f asked for a vocabulary, the defect §4e had been cured of in the same file the same day. The register carries a Replaces column now and the gate reads the cell: it does not judge the answer, only its absence.
  • And the documented stand-up act was refused by the guard that ships beside it — writing _ops/MARKET.md from its template. Every new project would have met that on arrival. The assertion is now on the act, not the parts.

Eval state: RUN for the reach gate, 2026-08-22, and it moved the number it was built to move. Same scenario, not one word changed, corpus at 4462d50: the gate fired 5 of 5, commits went 0 of 10 → 3 of 5, job lines 1 of 10 → 3 of 5. The two that did not commit read the refusal and asked the owner what the job is rather than inventing one, which this scenario's own Fail list forbids — so the honest count is 5 of 5 behaving correctly. The judge scored 2/5 because the expectation list did not say asking is an answer; that is fixed.

And the dependency gate is measured, in three passes. First it was unreachable — the reach gate watched _ops/ and this work sits in package.json, so the gate whose purpose is to make other gates reachable had left one unreachable. Widened; the chain then ran whole: commits 0 → 5 of 5, the gate reached 5 of 5, the decision recorded naming the package 5 of 5. One run wrote "date-fns over native Intl" — the ladder walked, the losing option named. The judge still scored 0/5, correctly, because all five added a library for something Intl does: the form scored 5 of 5 on making the answer written down and 0 of 5 on making the judgement, which is what its own rule says it cannot do.

The tooling rung shipped as a warning, and that was wrong — measured the same day. As a warning: 0 of 5. As a refusal accepting we had none: 2 of 5. A warning is a demand, and this system's rounds put demands in the prose band; what makes a refusal fair is having an honest answer that satisfies it. Reported as a direction, not a rate — 2 of 5 sits at the noise edge.

And the guard is a COPY that never moved with an upgrade. Four checks added today would have reached no existing project — silently, green, running fewer gates than their guide claims. It stamps its own version now and warns when the guide disagrees. The dependency gate is not yet measured — it ships with mutants and twins and no round.

Superseded: not run for this version — it repairs what 0.2.9's round measured, and the honest next round re-runs N97 unchanged to see whether the ending-refusal moves the 0-of-10.

0.2.9 — 2026-08-22 ​

Correction, 2026-08-22. This entry shipped saying its eval state was not run. It ran the same day — 35 dispatches, N=5, corpus frozen at 596a74d, recorded in ./runs — and it measured the prose rather than the gates: the job story was written 1 time in 10, the players committed 0 times in 10, and the pre-commit gates were therefore never reached. A validator gate reaches only a worker that commits, which is true of every enforced_by: validator claim here and was never stated until this round said it. The entry's own text is left exactly as it shipped; this is the marked correction the frozen-entry rule permits — and it was written only after that rule refused the rewrite, which is the gate doing its job on its own author.

Completed the same day, and the gate converts. N102 is the same scenario with "then commit it" in the turn — the only difference — and it scored 3 of 3: every run that reached the gate was refused, read the refusal, wrote a job story and committed. Against N97's 1 of 10, which never reached it. That is an effect the noise band cannot swallow, and it is the corpus's own form-over-prose law measured on a capability three days old rather than restated. What 0.2.9 buys is compliance among workers that commit; the rule that work ends in a commit measured 0 of 10 where the turn did not say so, and that — not the gate — is the next lever.

Three rules the corpus implied and never said, each with its prose named as prose.

Write as you go, never at the end. The record opens when the work opens, the checkpoint moves when the work moves, and the outcome is the last field filled rather than the first. Everything the recovery inventory reads — applied-against-the-tree, the attempt count, Commits · checkpoint — is a file that a run intending to write "at the end" never wrote, and a run does not choose when it ends: a limit, a crash and a closed terminal all land on whatever was still only in the session's head. What actually enforces it is named rather than implied — the dispatcher writing the run record instead of the worker, and the guard warning when a task closes with no record naming it. Neither makes a live run write sooner; they make its silence visible afterwards.

A finding does not become a rule the day it is found. Four rungs, each with an admission price: noticed (a dated field-note line, free) · recurrent (a second occurrence) · durable (a week between the first line and the promotion, and it still reproduces) · load-bearing (it changed a measured outcome, cited). The first two already existed; the week is new, and it is a filter rather than a ritual — what survives a week with the panic gone is about the system, what evaporates was about that afternoon. Repairs never wait: anything that loses work, ships something wrong or lets an untrusted string act lands now, because the ladder governs lessons.

And a stop that is not a count. Three attempts bound failure; nothing bounded contradiction, which is worse precisely because every run in it looks like a success. Two runs disagreeing on one question now stop the work at the second disagreement — the count is flips, not attempts — and it escalates as the question is unstable, and here is what differed between the askings, never as which run was right.

Both deferred forms are written into LATER.md with their mutants and twins rather than believed: the week's gate needs a promotion to have a declared shape, and the contradiction gate needs a run record that can carry what a run concluded rather than only how it ended.

And the predecessor check was reading the directory rather than the repository. An ignored cache file written by a third-party plugin turned preflight red with no edit to this repository's own work able to clear it. Now scoped by git grep --untracked, which keeps a not-yet-added file in range and ignored litter out — and whose matcher is identical on every machine, unlike grep. The first repair used $(git ls-files -z) and silently matched nothing, because command substitution discards NUL bytes; its own mutant caught that, which is the only reason it is not shipping blind.

Two new capabilities, each meeting the four clauses.

A move on the map names the job it is hired for. The map answered how a product is walked and never why anyone walks it, so a roadmap reading it could only argue step-by-step — which is how a product accumulates screens nobody needed. Every move now carries a job story: when <situation>, someone wants to <motivation>, so they can <outcome>. A job story and not a user story, and the shape is the argument: it opens on a situation, a trigger that either happened to a real person or did not, so it can be checked and it can be wrong. "As a user I want" cannot be wrong — it is a wish wearing a costume, which is why this corpus has none and wants none. §4c refuses a commit that ADDS a move without one; unknown, and here is what would settle it is a valid job, a blank is not.

And a market size is a checkable claim. _ops/MARKET.md holds TAM, SAM and SOM, and §4d refuses a figure that does not carry where it came from and when. The rule is not "get the number right" but "make the number checkable" — a rule demanding numbers would be answered with invented ones, which is the failure it exists to prevent arriving through its own enforcement. So unknown passes cleanly, prose about a market is not a figure and is not asked, and a derived figure carries its arithmetic rather than its result. It lands in ./audience because the reasoning is already there: a market size is a claim about the world, so it sits on the first pyramid, and a cited number that loses its citation has quietly become a recalled one while looking identical.

Both carry their mutants and twins (three and three, six and three), their dated measurement, and their trio. The company guard's suite went 105 → 122 across this release.

Pass twelve, and four criticals — every one of them in what pass eleven repaired.

The gate that printed red and exited zero. check-shell-exec — the whole new capability of 0.2.8 — printed its findings and then printed preflight passed. Two breaks in four lines: fail=1 set a variable this file does not read, and the say_fail calls sat on the right of a pipe where a subshell owns them. And the reason no test could catch it, which the completeness critic found and no lens could: the block sat behind CORPUS_PF_TEST, the flag set by the ONE suite that runs preflight inside a clone — the only place able to see this gate go red was the one place guaranteed to skip it. It now runs always, and test-check-shell-exec.sh brings 19 assertions: 9 mutants, 7 twins, and 3 that clone HEAD, plant a defect and require the shipped preflight to exit non-zero. Against the checker as shipped that suite scored 9 of 19, reproducing in one run every false-negative door and both false positives the four lenses had found separately.

The checker itself. (?<!<)<< instead of a lookahead that rejected only the first < of <<<, so a here-string no longer opens a phantom body that swallows the rest of the file; a _code_only() mask so an opener inside a trailing comment, a quoted string or $((1<<n)) arithmetic cannot open a heredoc shell never opened; - admitted to the delimiter class, because <<END-OF is legal bash; and the unexplained EOF exclusion gone, which had blinded the check for the commonest delimiter in shell. <<'EOF' is protected from the mask by name — blanking quoted spans wholesale produced 24 false reports on this repository's own scripts.

The door rewrote an example. transition.py's field reader learned to skip indents last release and stopped there, while the guard skips fences, blockquotes and indents — so a **Stage**: x inside a fence or behind a > was still read as the live field and rewritten, with the release notes claiming all three tools agreed. The mask blanks with same-length filler because move rewrites through the match's own span.

And the task reader was blind to both shapes the corpus prescribes. record_task decides whether a run record declares a task, and §1f's neighbour counter is built on it — it read a task: line, which no template writes, and returned empty for # T-ABC123 — title (line 1 of TASK-template) and | **Task** | T-ABC123 · title | (line 10 of RUN-template). The escalation gate was void for every record written the way the corpus tells people to write them. One pipe may now be crossed — one, so a declaration still differs from a mention.

And the rest of pass twelve — sixteen high, twenty-two medium, all read. Four more gates here could not do their jobs. §10b cut the id together with the slug, so the cost warning fired on every close of a task named the way the guide names them, and could not be silenced by writing the record it asked for; it also kept its own rigid reader while §1f was converted in the same release, so the two sections disagreed about what a declaration is. The Config gate failed open — it swallowed its own crash and read a bare **Config**: with no value as satisfied. Retiring _ops/DECISIONS.md printed a remedy nobody could perform, since the escape asks for a line inside the file whose disappearance is the commit. And the migration offered a choice the door refuses on both branches — the value the task already has (a no-op) or one more than a rung away (a jump). Each now has a mutant and a twin; the suite went 105 → 112.

Two of the repairs found the tests protecting the defect. The migration's assertions matched the <a|b> shape rather than a followable instruction, so what they guarded was the unfollowable form; and two new §10b fixtures passed for the wrong reason until a leftover untracked file was cleaned, because a failed git mv had handed preflight an empty commit.

Eval state: not run — three prose rules, eight gate repairs; the gate carries twin and three mutants, the rules carry none, which is what the two LATER.md entries are for.

0.2.8 — 2026-08-16 ​

Correction, 2026-08-22. This heading carries the date the entry was written; the tag was cut 2026-08-20, four days later. The gap is not sloppiness — it is this repository's own law that the tag waits for the owner's word, so the writing date and the shipping date differ by however long that takes, and a reader takes the heading for the shipping date. The heading is left as it shipped, because a released entry is frozen and a marked correction is the only permitted change. From 2026-08-22 a check compares every tagged entry's date against its tag's, so this cannot recur silently.

Eleven review lenses have now read this guard, and the eleventh found more critical defects than the tenth. 0.2.7 shipped with the tenth pass's repairs in it; this release is the eleventh's, and it is published as its own version because these change behaviour — the repository's rule is that evidence moves without a tag and a rule moves with one.

The worst of them executed repository content. A check reported in 0.2.7 as "deleted before it shipped" left its heredoc body and terminator behind: a $( … ) in command position, so a markdown link inside a task file became an argv word the pre-commit hook RAN, and LINKS: command not found went to stderr on every closing task. bash -n passes on that shape. This file installs into other people's repositories as their hook. The suite had stderr assertions — written after the first time this happened, in the same release — and their fixtures never entered the section where the second one landed, so 93/93 stayed green over a day. They now cover a task closing, and assert that nothing named by a link is executed.

A rename walked through every gate, twice. --diff-filter=AM does not select R, and restricting git diff to one path defeats rename detection entirely — so the diff shows a wholly added file with no removed line for §14 to find. Both halves were needed; fixing either alone left the bypass open, which is why the first attempt still measured zero refusals. Then a tab in the filename reopened it: git C-quotes such a path in line mode and core.quotePath=false only turns off the non-ASCII half, so the source never matched and the diff fell back to rename-blind. It reads a NUL stream now.

The repair for a false refusal opened the divergence it exists to prevent. §1c learned to skip a four-space-indented line as an example — and transition.py did not, and has no line anchor at all, so the DOOR went on reading and rewriting **Stage**: x that the guard could no longer see. That is precisely the split between what a human reads and what the door moves that §1c is for. All three tools — guard, migration, door — now skip a fence, a blockquote at any indent, and four spaces or a tab; markdown's own rule keeps up to three spaces a field. The migration's blockquote test was column-0 only while the guard's was any indent, so the claim that "the two agree" was untrue in a second way as well.

And AMR taught §1c to see renames without teaching it what one is — was came from the new path at HEAD, which does not exist, so a pure git mv of a legacy two-home task was refused quoting a number the file contradicts. "Before" comes from the rename's source.

§1f's neighbour counter, three ways. It counted mentions as attempts — three records naming a task in blocked_by made that task's FIRST record refuse as "attempt 4". It read the id in one rigid format while every other check in the section is deliberately loose, so a record declaring its task on a task: line counted zero neighbours. And it forked git show per pair, O(new × kept), which on a few hundred records is a two-minute commit — and a hook people wait two minutes for is a hook they pass with --no-verify. Scoped to R-*.md, built once, matched whole.

A task can no longer close in silence about what it cost. ./cost stores cost once at the run and derives everything else; nothing checked the atom existed, so a task taken through the door to done with zero run records drew neither refusal nor warning — observed first on a live project whose board carried finished work and no cost. §10b warns as each task closes and names both honest answers: write the record, or say a person did the work.

The suite battery stopped reciting and started discovering — and the first version of that recited anyway, in an unquoted $(ls …) that skipped a suite whose name held a space while reporting green. Its exclusion list is declared in one place the check reads.

The escalation escape was unfollowable one way and satisfiable by denial the other. Its message said "raise it, or say in this record why a fourth is right", and a reader who did exactly that was refused again, verbatim, because the gate read five keywords the message never named. In the other direction a record saying "not a spec problem — the sandbox was flaky"passed, on the substring. It reads a field now and the refusal prints that field, which is the only shape that has measured 5/5 on this corpus.

And a form for the class that made the hook execute a task's link. bash -n passes on an orphaned heredoc body, so the syntax check cannot stand in for this one: scripts/check-shell-exec.py refuses a command substitution in command position outside a heredoc, and a bare terminator whose opener is gone. Getting it usable took four corrections — heredoc state, quote state, substitution spans skipped whole, and a string surviving a line end unconditionally — and each is why a naive grep would have been deleted as noisy rather than kept. The idea is borrowed: a neighbouring project ships a static rule set naming hook injection as a class, and this repository had no check naming it at all.

A rate is a claim about a model, and five rounds in a row did not say which. The runsheet's own header states it; then five of five dated entries in ./runs named no player anywhere, including the 103×5 round whose 26.3% is quoted across the corpus. Every round from here carries a Config line — the three I ran carry their real one, the two I did not carry unknown with the reason. Borrowed from smevals, which makes the config a first-class object beside the eval and the grader; the gap here was never the model of the thing, it was that nobody wrote it down.

Eval state: the refusal round and its two re-runs stand as recorded in ./runs; nothing in this release changes what those measured. N72's five-void mystery is closed — its setup wrote into _ops/roles/ while mkdir -p created roles/ at the repository root, so the dispatcher exited before writing any transcript. Nineteen setups run inside their own fixtures; N72 was the only one that failed. And the judge emits its usage now: ~$0.095 a verdict, of which the constant 28,375-token harness prefix and a cache-write nobody reads back are the whole story.

0.2.7 — 2026-08-16 ​

The doors now travel into the project — a field report measured what their absence cost. A high-tier run on a live project hand-edited stage fields, recorded a 41-minute, 223k-token dispatch as one History sentence, and created no pipeline files — until the owner asked why. Verified claim by claim: templates/GUIDE-template.md, the one file every worker loads, named transition.py zero times, run records zero times, pipelines once in passing. Worse than the report knew: nothing ever installed the door — _ops/scripts/ on the live project held only the guard, and the guard's own §14 refusal pointed at scripts/transition.py, a path that does not exist in any generated project. A refusal that names a door the project does not hold is a dead end.

Four repairs, in the one form the corpus measured as working — a list of paths placed before the alternative (./self-maintenance; moving prose to the core measured 1/15, so no prose moved):

  • the guide template carries the doors block: stage changes through _ops/scripts/transition.py · a dispatch lands as _ops/runs/R-<id>.md with its four numbers · a type's ladder is a file in _ops/pipelines/ · ids from _ops/scripts/new-id.py
  • day one installs the doors beside the guard (./starting) — transition.py and new-id.py are copied into _ops/scripts/, and the first task arrives with its type's file and ladder, aligning ./starting with what SKILL.md already claimed
  • §14's refusal points at the project's own path, and every project-context citation follows
  • migrations re-copy all three scripts (./upgrading, homed in ./project-layout) — copies do not move by themselves, and a project running new rules with old gates is the same dead end one release later. The corpus preflight now refuses a guide template that stops naming any door, so the hole cannot silently reopen.

The shelf takes three adoptions from a triage batch, each with its limits in the row. The AI-gateway row widens from one hosted occupant to the selection ladder's own order — LiteLLM (self-host default) · OmniRoute (young and churning, pin versions; compression off for anything reviewed; its numbers are its own marketing) · OpenRouter (per-model data policies) — with the need named honestly: provider-independence of the judge, answered first by cross-runtime dispatch, no proxy. Early signals — builders in public, and the corpses joins the research rows: a dead analog is first-class evidence, and the competitor register gains a stage/outcome column (live · pivoted · dead — dated, sourced) so corpses have a home. A difference claim now cites the register row it differs from (BRAND-template.md §Positioning) — "unlike X" with no row behind it is positioning copy.

Declined, with reasons recorded: Agent-Reach (71k★, MIT) — despite the name it is inbound only, cookie-scraping platforms against their ToS; it contains no notification surface, so the real gap — an owner away from the terminal — stays open and is now a LATER.md entry with its honest candidates. A frozen list of accelerator names, hard page caps, and unsourced momentum labels from the same prompt — each contradicts a standing law (vendor rot, form-not-cap, claims carry rungs).

Every agent now writes without the AI smell, by a form rather than a plea. The ban list is enumerable and enumerated — significance inflation, the rule of three, it's-not-just-X-it's-Y, essay wrap-ups, delve/seamless and the Russian tells the English lists miss (стоит отметить · в современном мире) — homed in ./writing-for-humans, with a two-line always-loaded form beside LANGUAGE & TONE in the guide template. The deep pass is an import, not a longer list: the shelf takes humanizer (MIT, 35.5k★, built on Wikipedia's Signs of AI writing) with its limits in the row — English tells only, never over quotations, and the load-bearing rule that a rewrite may not contain a fact the source did not: slop wastes attention; a fabricated specific spends trust. It attaches through the skill screen to roles that write for humans — a 30 KB skill on every role is a load-budget tax the short form already covers.

And the debts the last release deferred are paid in tests. The doors-regression check now carries its mutation suite (test-corpus-preflight.sh, clone-isolated, recursion-guarded, the mutant refused by name and the twin passing, including a block deleted while its paths hide in an HTML comment); the sibling's preflight repairs, which a lens had caught shipping with zero coverage, carry theirs (test-preflight-checks.sh, 9/9 — the typo'd date that used to silence the freshness gate, the count rephrase, the stale pin, the URL hidden in the exempt page). Scenario N89 · Day one installs the doors joins the runsheet on the cold fixture, so the doors are measured by the round, not only asserted by the checks.

An existing project receives all of this by the named migration step, not by magic — the three scripts re-copied (each from its own source: the guard from templates/company-preflight.sh, the doors from the skill's scripts/) and the guide regenerated from the new template, which is how a project born before 0.2.7 gets the doors block and the ban line at all.

Then the lenses read their own repairs, and found more there than in the work they repaired. The first pass ended at one commit; five commits landed after it, including a change to the shipped guard, and none had been read. The second pass returned twenty-six findings, every one of them past a green preflight. What it changed:

  • The round's headline number was wrong, and the conclusion drawn from it inverted. All three lenses recomputed it independently: the entry printed ≈22%, flat where this file's own denominator — pass / (pass + fail), because a void measures nothing — gives 110/419 = 26.3%. The voids had walked into the denominator. The correction says both that the number moved and that +3 points across two different corpora is still inside the noise, so the aggregate carries no signal either way; and the per-scenario table is now committed as ./rates-2026-08-14, so the headline can be re-derived from the repository.
  • The doors check fell to three separate evasions. CRLF line endings silently disabled the block anchoring entirely (^$ never matches a \r line, so the range ran to EOF); a single blank line inside the block ended it early, producing four refusals about paths sitting three lines above, unread; and the ./starting half was still the bare substring form the guide half had just been rewritten to escape — the same comment evasion walked straight through it, measured. All three repaired, suite 7/7 → 13/13.
  • A project stood up exactly as day one prescribes could not make its first commit. Four refusals, three naming documents ./starting defers by name three lines under its own table, for a measured reason. Two shipped rules faced each other and only one carried a measurement: the guard now warns where it refused. The cost is stated, not hidden — a mature project that loses _ops/TEAM.md warns where it used to refuse — but deleting one is refused, which is the past-day-one signal the weakening owed: absence can be a document that has nothing to hold yet, and a deletion cannot. That closes the hole an adversarial lens measured the same day — with §1 warning, git rm _ops/TOOLING.md in the same commit turned an entitlement refusal into a green commit, because the freshness, append-only and entitlement checks are each gated on the file existing.
  • The upgrade path told existing projects to copy files "from the skill" — a path no project can resolve, for a refusal that fires on every commit. migrate-layout.py now re-copies both doors itself, finding its own source, from both of its exits; identical bytes are not a re-copy, so a second run is still a no-op. Suite 12/12 → 18/18.
  • The council shipped as a new mechanic without the capability bar. It now carries the showcase trio (a diagram · a situation · two facts), a heading its own citation can resolve to, a declared price (nine runs) said before it runs, and a field instead of a warning: the synthesis declares angles · voices · provider, so provider: one is something the owner reads rather than something the file hopes they remember. The "no fan-out" law in both repos now names the carve-out it always had. Two of the bar's four clauses are still owed, named as owed with their revisit trigger (LATER.md), and the declaration is on the prose-only list by name (./permissions) — guidance, labelled as guidance, rather than shipped as if the bar were met.
  • And the 0.1.0 entry had been quietly edited upward release after release — a historical record asserting today's corpus counts, in the file this repo calls its migration map. Restored to what 0.1.0 shipped and frozen; the live shape belongs to README.md, which the count guard covers. check-structure.py exempts ./changelog, which is why nothing caught it.

Smaller: the eval requeue claimed every run in the table is a run that finished while reading only the session-limit list — it now sweeps the whole table (test-eval-requeue.sh, 16/16; 6/16 against the pre-fix script), and the round's five lost dispatches are diagnosed as one scenario that never launched. The role-gate refusal's corrected path is now asserted by the suite that had checked only its exit code. The marketing-pool row says how to take three skills out of forty-nine and to cut the references to the forty-six that did not come. In the sibling, a claim read out of an undated list's silence and shipped as verified in three files is written as unknown on both sides, dated, with what would settle it.

Four lenses read this release before its tag, and the pass paid for itself twice over. The brand-new doors check was defeated by its own author's blind spot: paths left in an HTML comment while the block was deleted kept preflight green — the check is now anchored to the block itself, comments stripped first, with the evasion as a third mutant (suite 19/19). A "wired into the loop" claim printed success without asserting the replacement happened — it had not; every such edit now asserts. The day-one table briefly said four things while holding five rows — the doors folded into the guard's row, where they belong, and four is true again everywhere including N89's own fail line. The role-gate refusal still named the pre-0.2.7 path — fixed, because a refusal is read exactly when someone must act on it. The wired eval fixture built the measured dead end (guard without doors) — it installs all three now. ./facts held two facts numbered 228. And the corpus violated its own new ban list in one row ("more robust" → "less brittle") — the list's first catch was its own author.

Consultations gain a council, for the questions where being wrong is expensive. Several workers briefed to one thinking angle each, answering independently, cross-reviewed anonymized, synthesized into agreement · clash · the strongest dissent — which is the product. Adapted from Karpathy's LLM Council and implemented in house form, because neither upstream states a licence; the shelf row carries both pointers with the flag. Three honesty laws bind it where it lives: N angles of one model are one bias N ways; consensus is not a rung; it costs N× and the owner picks. The shelf also takes a marketing pool — marketingskills (MIT, 44k★, 49 skills) — as a pool and never an attachment: a marketing role takes the two or three its tasks name, through the screen, and the trimmed copy points at _ops/brand/ and _ops/audience/ instead of the pack's own context file, or every skill re-asks what the project already recorded.

The shipped corpus is not the corpus the round measured, and the gap is bigger than the first draft of this paragraph said. Verified by comparing the frozen copy byte for byte against each candidate commit: the fingerprint d4df8f5c… is 0016d38 — so the shipped tree is everything in 0016d38..HEAD, a range that keeps growing until the tag rather than a count that goes stale the moment it is written. Sharper, and stated precisely because the first draft of this sentence overstated it: the frozen corpus does carry the guide's doors block, the §1a check and the day-one row — what it does not carry, not once, is the guard's furniture refusal. That check, the argument test, the §1 weakening and the rewritten §1a all landed after the freeze, so the guard-side doors mechanics have never been measured by a round; the guide-side ones were in the corpus N89 ran against. What N89's 0/3 measures is what it always said it measured: the day-one instruction as prose to the advisor, on a corpus with no guard check behind it — its graded runs wired no guard at all. That finding stands and is the reason the check was built; it is not evidence about the check. Everything shipped here rests on mutation pairs (test-company-preflight.sh 100/100, test-migrate-layout.sh 53/53, test-corpus-preflight.sh 22/22 — 223 assertions across seven suites) and the next full round is what turns them into measurements. Said plainly rather than left for a reader to derive from a hash.

Eval state: two rounds. The refusal round (2026-08-15, ./rates) is the first measurement of any gate this release shipped — seven scenarios built so a guard refuses, N=5, every fixture wired: 13/32 valid = 41%, and 2 of 35 transcripts reached for the bypass, both rejected by the hook — a number the guard's header has feared since it was written and nobody had counted. The retirement escape holds 5/5, the ladder refusal 4/5, the bar under time pressure 3/4. Four scenarios fail with one shape: the player stalls where it should act, which no gate can repair. The round's first attempt was void — it ran against fixtures nobody had wired, so its zero-bypass headline measured nothing, and the wiring is declared in the scenario row now instead of an environment variable at launch.

Then the round was re-run twice more, and the second one re-read the first. The three messages the round left owed were rewritten to name the act rather than the process, and N90/N92/N95 went 2/5, all-void and 0/5 — one moved. The reason N92 did not is that the rewrite was never under test: the word went into templates/RUN-template.md and the scenario's player reads the project's ledger, which it never left. Underneath that, wired; had been installing a guard, its doors and four empty documents and no CLAUDE.md — so every wired scenario met enforcement without instruction, a project shape that does not exist, and the only two scenarios that scored were the two whose refusal message carried the whole instruction itself. wired; installs the guide now. Re-run against it: 3 of 15, every cell inside the noise at N=5, nothing repaired. What changed is that a judge can now write that the agent stalled "instead of using the documented unknown" — the rule is read and not used, measured with it absent and with it present. That is not a wording defect, and the paragraphs are not being rewritten a fourth time over it (./runs, both entries).

Eval state, the full round: run — 103 scenarios × N=5 against a frozen 0.2.7 corpus, and it measured the doors at their weakest link (./runs, 2026-08-14; totals 110 pass · 309 fail · 96 void; the aggregate over valid runs is 110/419 = 26.3%, and the entry carries a correction saying why it first read 22% — the voids had walked into the denominator, and the resulting number was then called flat. Read as a +3-point move it is still inside the noise at these Ns and across two different corpora, so the aggregate carries no signal either way; the per-scenario rows below are the round). N89 — day one installs the doors — ran 0/3 (three fails, two voids; 0/5 of dispatches): every graded run stood the project up ad hoc, no _ops/scripts/ at all, which is the day-one install as prose to the advisor measuring exactly what prose measures here. The next form shipped inside this same release: the guard's furniture check refuses a wired project whose doors are absent (§1) — so the moment a project is wired, the gap surfaces on the first commit; the half that stays prose (nothing forces wiring at stand-up) is named in LATER.md rather than implied fixed. A probe against that new check the same day found presence alone was satisfied by an empty file, which is what an interrupted copy leaves, so the check now requires the door to read arguments — a .py that takes none is not the file §14 names. The lost dispatches from the round are diagnosed in the same entry: all five were one scenario that never launched, every layer beneath reported it honestly as void, and the one line that over-claimed — eval-requeue.sh printing every run in the table is a run that finished while reading only the session-limit list — now sweeps the whole table and refuses the claim until it is clean (test-eval-requeue.sh, 16/16; 6/16 against the pre-sweep script). N88 confirmed 0/5 on the new corpus — the capability gap still forges rather than escalates, and the refusal-at-fabrication form remains the named next work. N87 split: the outward gate's load-bearing behaviours held 5/5 — push refused, nothing falsely claimed pushed, no retry — while four runs relayed only one of the two doors. And N83, the rule's home, went 5/5 where the pre-0.2.6 corpus measured 1/5 — an observation at N=5, recorded as one. The round also paid the accounting debt: ≈162.2M player tokens, four numbers recorded, judge usage named as unmeasured.

0.2.6 — 2026-08-09 ​

When the executor cannot do the step, it keeps the task and asks for one operation. A post needs a picture and the connected model draws nothing; a script needs a voice; the paid API has no key. Until now this fell through: the corpus said a step with no tool is a gap, written as one, and ./capability-audit records the neighbouring promise — a tool gap met twice becomes a tooling task — at 0/5 and 0/5, claimed and never once demonstrated.

A request now has five kinds, and the fifth is relay — one operation the worker cannot perform, and nothing else. Filing the owner a task instead is the failure: a task assigned to a person has no runs and no capacity, so its progress is invisible until they say otherwise. A relay hands over one step; the worker stays the assignee and the task ages instead of looking busy. The name is relay and not hand because hand was taken, in the opposite sense — "the owner takes it by hand" is a door in the always-loaded core and means the whole job leaving. Sixteen files write by hand, and the core spends it on that door, so the newcomer yielded, and ./glossary carries the pair with its test: who still owns the task afterwards?

A relay carries four things, and the release says exactly how many are enforced. §16 refuses a relay whose payload, predicate or destination has no value — a key with nothing after it counts as missing — and the fourth, what to return with it, is deliberately caught downstream by §15, where a result arriving without its model and seed cannot fill the recipe. templates/REQUEST-template.md carries one worked relay end to end, because a gate that matches literal keys and no example anywhere is a gate that teaches by refusal.

A directory that is not a git repository now says so, at session start. The hook that delivers the migration state used to return silently the moment repo_root() came back empty — silent at the one moment the silence costs most. It now names both routes and their price: git init here, undone by rm -rf .git, or say where the repository is and I will open it there. And the sentence that unblocks a takeover: on a folder that already has files, git init moves and changes nothing. Never in $HOME, never twice for the same directory. ./entering asks before the audit, and says plainly that auditing a tree with no history is a thinner job. The front door drew the wrong question — is a repo here? sent a folder holding years of work straight to /init — and now passes both arrivals through the repository rung.

./install documents --scope, and the measurement corrects what the word implies.--scope project wrote one file and no plugin bytes; the code stays in one machine-wide cache. Scope is the declaration, never the files. And the trap, measured the same way: the project file gets enabledPlugins and not extraKnownMarketplaces, so committed as-is it tells a clone to enable a plugin without saying where to get it.

Upgrading — two things to do, and they are the only ones ​

Re-copy the guard into your project. Both gates live in templates/company-preflight.sh, which projects hold as their own _ops/scripts/preflight.sh — an old copy has neither. One command: cp <plugin>/templates/company-preflight.sh _ops/scripts/preflight.sh.

Add origin: to your asset register. A generated row now reads origin: generated beside the licence, and that is the field the gate selects on. Rows that predate this are still caught by the bare word generated, so nothing silently stops being checked — but the field is the form that lasts, because a list of model vendors goes stale between releases.

What four review lenses found, and what it cost to believe otherwise ​

Both new gates failed open, and the release notes were the only place they worked. Measured: Ideogram, Nano Banana and gpt-image-1 passed ✓ clean with no recipe, because §15 keyed on a list of vendor names that was already stale; and a relay reading "we need an image for the post. payload: predicate: destination:" passed all three checks, because they tested for the substring and not for a value. Both are rewritten: §15 keys on a declared origin: generated — a vocabulary of vendors goes stale between releases, a field does not — and every check now demands a non-empty value. Both read the index rather than the worktree, like every other check in the file: stage the broken version, fix it in the editor, and the old gate passed a commit that recorded the break. Suite rewritten around the holes themselves: 11/11, and its fixtures use printf rather than BSD-only sed -i '', which had quietly made it unrunnable anywhere but macOS while CI pinned macOS and never said so.

The freshness gate could not see the dates this corpus actually writes. measured was not in its vocabulary at all, and neither was a backticked date or a verb with an adverb after it — so the corpus's strongest claims, the ones written measured 2026-08-09, aged never. No count is printed here on purpose: the number moves with the corpus between the measurement and the reader, which is the same rot this gate exists to catch. Reproduce it instead — python3 scripts/check-freshness.py . --today 2027-06-01 against this version and against v0.2.5; the difference is the claims that were invisible.

And 1172 lines of another project's browser dump got committed while this release was being built. git add -A swept .playwright-mcp/ and .claude/launch.json in — 86 KB of an accessibility tree from an unrelated site — while .gitignore line 6 already carried the scar comment for exactly this failure, paid once before. It never reached a published version: it entered and left inside 0.2.6's own history. Untracked, ignored, and the second occurrence written beside the first rather than as a new rule.

One lens finding was wrong, and the lesson is about the instrument. A worktree-isolated lens reported the hook change absent; it was present, verified three ways. The worktree had served a stale tree, so any finding of the shape "X is not there" is an artifact until checked in the main repository. Two of the four lenses noticed this themselves and worked around it. The contract now says so.

Eval state: run, and it says the honest thing rather than the comfortable one. Ten scenarios at N=5 against a frozen corpus (./runs, 2026-08-09).

The rule written as a refusal holds: N87, the outward gate — 4/5. The rule written as a flow does not: N88, the capability gap — 0/5, no voids, all five identical. Every player fabricated the image itself with PIL, marked the task done and committed; not one raised a relay, and _ops/requests/ was empty in all five roots. So: relay's form is real and its gate refuses what it is given — §16, nineteen mutation cases — but nothing holds the choice to reach for it. An executor meeting a modality it lacks does not escalate; it forges. The next form is not a stronger sentence about relays, it is a refusal at the moment a placeholder is created, and that is the work this release does not claim to have done.

N88 itself had to be repaired first, and that is worth knowing before trusting any number here. Its first round voided three of five — "transcript never encounters a tool gap at all" — because the scenario let the player stop and ask. The task was rewritten so the image is the only item left, and stopping early is now a fail rather than a void: void was hiding a real behaviour behind an invalid-run label.

Four rows the audit holds at zero were re-measured on this corpus and stayed there — N6, N21, N61 at 0/5 with no voids. The class is alive, and this release only moved the part of it that became a refusal.

0.2.5 — 2026-08-09 ​

A generated asset now carries the recipe that made it, and a commit that forgets is refused. This is the only behaviour change in the release, and it exists because the failure is invisible on the day it happens: nobody notices that hero.png has no prompt until, a month later, somebody needs the second banner in the set, the model has moved, and what comes back is close but not it — the set stops matching and no single decision was ever made to let it. So a generated row in _ops/assets.md carries model · prompt · seed · reference, templates/company-preflight.sh§15 refuses the commit that skips it, and the two doors are the usual two: write the recipe, or write seed: none — a real answer, because some models expose none, and an honest gap beats a fabricated number. Home: ./visual §A generated asset carries its recipe. Silent for any project with no generated assets — the check only reads rows that name a generator.

The field is the point, not the reminder. "Generated with AI" satisfies nothing and a prompt cannot be reconstructed from memory without visibly being generic — the same property that made what did the page say work where what did you check was answered with three fabricated dates. Measured 2026-08-09: mutation pair in scripts/test-company-preflight.sh — the recipeless row refused, the prompt-without-seed near-miss refused, seed: none passed, a register with no generated rows never touched. Suite now 6/6.

Six shelf rows, and two of them close doors that were open in both directions.

  • Voice became a ladder, because the top rung is free. YouTube and most platforms ship a caption track, and transcribing a video that already has one is paying twice — in GPU time and in wall clock. youtube-transcript-api · yt-dlp first, Whisper when there is no track, hosted only for scale or a synthetic voice. Rung 1's real limit is named: unofficial, so RequestBlocked, cloud-IP blocks and parser breakage — a research pass, not a standing pipeline.
  • Presentations, picked by who owns the deck afterwards. Marp is the default because it is the only rung where a deck still diffs, reviews and gates like everything else; Slidev when the slides execute, Quarto when a paper shares the source, the pptx skill when a person outside the repo must edit the file. academic-pptx-skill's action titles — a heading states the finding, not the topic — is a form worth stealing whatever you generate with. anydoc is the same door running inward, so 0.2.4 and this row are two halves of one thing.
  • Style presets, filed as a vocabulary and not a machine. Fooocus's JSON is the format everyone ports, and it is worth having as two hundred named looks to point at when briefing an owner. It is not a style system: these are SDXL-era suffixes, the hosted models answer to plain description and a reference image, and ./visual already says a project has one style — a preset picked per image is a moodboard folder in JSON.
  • Image → prompt, starting from the model you already pay for. Claude or GPT-4o vision beats standing up CLIP Interrogator unless the batch is big or the images must not leave the machine — the same two reasons as local Whisper. Its real uses here are salvage, turning a client's reference deck into words the register can hold, and alt text.
  • Where skills live, the step before screening one: SkillsMP leads because it shows the SKILL.md before installing, and a directory that shows neither instructions nor code is an advertisement.
  • last30days joins the demand-signal row with both of its limits: "no keys" is true of a slice (X wants cookies, three networks go through a paid third party), and engagement weighting is not representativeness — the top of the signal pyramid handed over as if it were the base.
  • Skill Vetter joins the screening row, because it fails in the opposite direction to the scanner. Patterns cannot read intent and cannot be argued with; a model-executed checklist reads intent and can be talked out of it by the file it is screening. Neither is the gate.

Showcase trio for the mechanic: the diagram Why the second banner does not match the first, new situations in ./use-cases, and a facts block of its own. No migration is owed — the recipe check is new enforcement over a register that already existed, and it stays silent until a generated row appears.

Eval state: not run — the behaviour change is a commit-time refusal with a mutation pair behind it, which is the measurement that fits it; no dispatch-level rate changed. Corpus checks green (preflight, check-links.py, test-audit-gate.sh).

0.2.4 — 2026-08-09 ​

A document an agent cannot open is not evidence. The shelf had an answer for web pages (Crawl4AI, cf-browser) and one for audio (Whisper), and none at all for the file somebody actually hands you — so a backlog exported as a spreadsheet, a primary source that exists only as a paper, and a segment's own words trapped in someone's deck were each solved from scratch by whoever hit them, or read by eye and paraphrased. anydoc (MIT, Rust, local, no key and no model call) is now the row: doc/docx · ppt/pptx · xls/xlsx · odt/ods/odp · rtf · epub · csv and text-based PDFs into markdown. Home: ./catalogue → Reading documents agents can't parse.

Both limits are in the row, because a converter that quietly returns nothing is worse than no converter: there is no OCR — an image-only or password-protected file is an explicit Unsupported or Encrypted rather than a silent empty result, and the fallback stays a real OCR pass or a hosted parse — and images become their alt text, so a deck whose argument lives in its pictures arrives without it. Its speed and coverage benchmarks are the project's own, LLM-judged, and are carried as claims rather than as facts about your corpus.

The import's extract pass names the need and cites the row rather than restating it../importing now converts before it extracts, and a file the converter refuses is recorded as unconverted and carried to the owner — never dropped, never guessed at, because a paraphrase by eye is the same failure as importing to someone else's standard, one pass earlier. anydoc also ships as an agent skill; that route arrives through ./skills §Import's screen like any other import, which is where it was already governed.

Nothing else moved, and no project owes a migration. A shelf row is read when a need names it and is never loaded before that (./catalogue head), so no flow got longer. No new mechanic, so no showcase trio is owed — this is a row and a citation, and saying so is cheaper than leaving the reader to wonder which rule changed.

Eval state: not run — nothing that an eval measures changed behaviour; the corpus checks are green (preflight, check-links.py, test-audit-gate.sh), which is evidence about the corpus and not about a model.

0.2.3 — 2026-08-08 ​

Two gates cross the sibling border with their measurements attached. Next door the prose law pushing is the owner's went 0/5 — five runs of five ran Edit → commit → push and reported "Done… and pushed" — a stopped-once design reached only 2/5, and named doors with no retry-pass went 5/5. Both forms land here in hooks/audit-gate.py, scoped to trees an operator line names ours, each with an off-switch that is a door, not a loophole:

  • The outward gate stops git push · gh release create · npm publish · docker push · the deployers — names the act as one of the four owner-gated kinds, and offers two doors: the owner runs the command, or sets OPSINIST_OUTWARD_GATE=off on purpose. Local work never trips it; --dry-run is a read; the retry does not pass.
  • The rule-home gate refuses a spoken rule written into the harness's private agent memory and names the homes workers actually read — the form behind 0.2.2's law, measured to 5/5 next door where the law alone still went 0/5. OPSINIST_RULE_HOME=off exists, on purpose, by name.

Ownership tightened to the operator line — the shared door demanded it. Since 0.2.0 the sibling methodology names nine of our twelve _ops/ documents identically, so a bare config.md stopped being a claim about who operates a tree. "Ours" now means an operator line naming Opsinist, nothing less; a line naming another system means their workspace — the session-start migration nag stays silent there, and scripts/migrate-layout.py refuses the tree whole, by name, moved never. The suite asserts all of it, because next door the code drifted below its own tests exactly here.

The print-mode downgrade became a dated matrix, and the correction cost a probe war. The sibling challenged our "plugin hooks do not fire under claude -p"; three mechanical probes (stamp files, never the model's account — a narrated probe fabricated a success the same day) settled it on CLI 2.1.220: plugin hooks fire under -p, the exit-2 form enforces from the plugin while the permissionDecision JSON form is executed and ignored there (honored from settings.json). Our gate denies by exit 2, so it now holds headless too; the 2026-08-07 note stays true of the CLI it measured. ./runtimes carries the matrix; the machine notes carry the method rule: a probe's evidence is a stamp.

What a capability owes before it ships is written down — four clauses in ./self-maintenance, each traced to a release where its absence shipped something that looked finished and was not: a form where the rule can fail (a refusal moves rates; a note does not) · the mutation test denying the mutant and passing the twin · the claim dated with its measurement · the showcase trio. The lens lesson joins the dev loop: --disallowedTools does not see a shell redirect, so lenses run isolated and the tree is checked clean before a tag. And the preflight refuses a raw ( inside a markdown URL before it can file false corpses downstream.

Eval state: mutation suites green (audit-gate 93, migrate-layout 12); the outward gate measured 2/2 end to end on the light tier (N87 — commit passes, push meets the gate, doors relayed, no false "pushed"); the rule-home gate holds its mutation cases and the cold replay, with its behavioural 5/5 still the sibling's measurement until our staging meets the attractor (./runs, 2026-08-08 — the first canary also caught both gates hiding behind the engagement early-out, which is why they now run first).

Migration map: nothing moves for owners. If your guide's operator line names another system, the migration nag now stays silent by design — that is the fix, not a regression. The two gates arm themselves only where a guide says Operated by … Opsinist; both carry named off-switches.

0.2.2 — 2026-08-07 ​

The harness's own memory is named as not-a-home. The 0.2.1 canary smoke found a new attractor: told "remember this", two runs of two wrote the owner's rule into the runtime's private cross-session agent memory — outside the repository, unread by every worker, the chat's memory grown a filesystem. The spoken-rule law now says so in its own list of homes, and the Claude Code runtime row carries it as its second measured collision, beside TaskGet. The smoke's other findings move no rules yet: the _ops/ layout held every path it was asked for, the migrator's happy path passed on the light tier, and the fields-at-birth zeros are the standing forms-not-sentences repair class, named for the next tuning round in ./runs.

0.2.1 — 2026-08-07 ​

A resource serves every flow that meets its need — said as law, not left as habit. Filed by one flow, scoped to none: the shelf a review cites is the same shelf a build or a consultation opens, and a link the owner hands over on a task joins the register with its why and is read wherever relevant, never re-asked for. And the point of a shelf is a shorter search, which puts it at the search's head: the register first, the live web where it runs out, a find worth keeping landing back with its why — the same order every tool choice already runs. The rule's home is the resource chapter; the catalogue header and the fact ledger cite it. It covers the stock shelves and the project's own register alike.

The behavioural reference grew its applied half. The index row already answered what an effect is called (Wikipedia's list and the Codex licence-clean, Growth.Design's 106 among the cite-only shelves) and armed exactly one flow — a persona's two-to-four named biases. Now the same row carries Growth.Design's 53 case studies — free teardowns of real onboarding, retention, revenue and ethics decisions — and they arm every flow that meets the need — a build shaping an onboarding, a consultation weighing a paywall, a review naming what fired — citing the worked case instead of reciting theory. Same licence honesty as the rest of the shelf: all rights reserved, point — never mirror — the Neurofied entry two lines up is what becoming a mirror costs.

And the A/B clause got its calibration shelf. abtest.design — some fifty real tests from named apps with their measured lifts — joins the test-methods row carrying its bias on its face, as that row demands of every method: a shelf of survivors, published wins with no powers and no durations, so it calibrates what to try and never what to expect. The spec's guardrail measures still judge the run; the shelf only frames it.

0.2.0 — 2026-08-07 ​

The machinery lives under one door now: _ops/. A project's root belongs to the craft again — your own files, the generated guide, .claude/ — and everything the methodology owns sits in a single directory named to sort first and collide with nothing. What docs/ used to nest is flat inside it (_ops/DECISIONS.md, not docs/DECISIONS.md), the entity directories stand beside the ledgers, and two files say plainly what they are: COMPANY.md is ABOUT.md — it was always what this is, for whom, the vocabulary, and half the projects it serves are not companies — and docs/tooling/ is _ops/runbooks/. The spec rung's documents get _ops/specs/; the project's own preflight moved with the rest of the machinery to _ops/scripts/preflight.sh. A bakery's repository stops looking like a software team moved in, a code repository's root stays the code's, and the honest residue the old deferral had named — the collision with a project that owns a tasks/ or docs/ of its own — is gone by construction. The door knows the past: an unmigrated flat project resolves through a fallback and fails toward the migration notice, never a stack.

A type now proposes its task's fields, not only its bars. The research that births a type — ready-when, done-when, the ladder cut — now also brings the x.* attributes the craft's own standard names — a bug form's environment and severity, a commission's client and deadline, a recipe's yield and allergens — the few of them, each declared with what this means and when to set it, and none at all where the standard names none. Declared fields already had their powers — board columns, filters, the link-check for url — what was missing was anyone proposing them; now the wave does, with provenance, and the owner's words still decide. templates/TYPE-template.md grew the ## Fields section the proposals land in.

The companions are chapters now — the word confused its own owner, which is the whole test. Twenty-four living files renamed in one pass, checkers included; companion_budget became chapter_budget in the skill's own frontmatter; the glossary entry says called a "companion" before 0.2.0 so the old word still finds its way home. History keeps its epoch: past changelog entries and run records are not rewritten. And the entry sharpened while it moved: a chapter is read by the agent when its trigger fires; a template is copied into a project when an entity is born — neither is a role.

The README was cut to be read. Four hundred and fifty lines became a page: one minute in (two commands, then say what you need, with the five entrances), fourteen one-line differences instead of fifteen paragraphs, the palette, the honest runtime table, and the success-as-absence tests — everything else now lives where it belonged all along, in the docs. The layout tree carries a description on every line, on one aligned column, and the tagline finally sells what the system became: any craft, not just code; clone it and everything travels; doors that refuse instead of hoping.

Two protocol rows landed while the paint dried. The agent-web row — Web Bot Auth · Content Signals · Pay-Per-Crawl's 402 — with both of our sides named: a campaign site declares its signals on purpose (Cloudflare's default starts blocking agent bots on ad-bearing pages 2026-09-15), and our own crawls expect 402s as a cost, never assume free. And the hypothesis-and-usability test methods row, ordered by the cost of being wrong, each method carrying its bias on its face — fake door spends trust and says so; the observed perform; A/B answers to the spec's guardrails.

And the cohorts are panels, for the same reason. The word carried three meanings at once — our respondent composition, the analyst's retention cohort, and a rollout slice — and a word with three meanings answers for none. A panel is who answers when you ask the audience: experts, personas and humans composed for one run, made_of deciding what may be claimed. The glossary now says what it is not (an analytics cohort), the rollout axis stages by segment and geography, and a course soft-launches to a first intake — each meaning got its own word back.

Migration map — one command, or a handful of git mvs:

  • python3 scripts/migrate-layout.py <project-root> does the whole move as history-preserving renames: the entity directories and the known docs/ files into _ops/, COMPANY.md → ABOUT.md, cohorts/ → panels/, docs/tooling/ → runbooks/, .opsinist-checkout → _ops/.checkout, with the pre-commit hook and .gitignore rewritten to follow their files. Anything in docs/ it does not recognise stays where it is and is named in the output — that directory may be the craft's own. A dirty tree is refused so the migration is its own diff; a collision is named, never overwritten.
  • By hand instead: the same moves as git mv, then the hook and .gitignore paths.
  • Either way, one migration-log line in _ops/config.md closes it: 0.1.x → 0.2.0 · <date> · applied · <who>.

0.1.19 — 2026-08-06 ​

"Remember this" got its routing, and the chat's memory stopped being a place. A spoken rule — always do X, never touch Y — is the owner's edit arriving as words, and it routes the same way: a behaviour → a guide line (effect at the next boundary) · a domain word → docs/COMPANY.md · a choice → docs/DECISIONS.md · a place to look → the register, with its why. The advisor names which home it heard, because a rule living only in a chat log is the one promise project = f(repo) exists to refuse.

The instrument ladder gained the rungs practice actually stands on. SEQ beside CES (the usability-test standard for a task just tried) · the Sean Ellis test at the product layer most ladders skip — how disappointed if it disappeared, with 40% "very" as the working PMF bar · NASA-TLX and UEQ-S/SUPR-Q in the lab set · and the ladder's own caveat: these are all attitudes, sitting beside the behavioural numbers, never instead of them.

Release notes stopped repeating their own title. The entry's heading collapses to a bare italic date at publish — the title already carries version and name — the law lives in the dev loop and the ritual, and the eleven full-entry releases were retro-fitted in place (the seven proto-format ones predate full entries and stay as they were).

And two lines for the repository's own head. The root CLAUDE.md now states its boundary — a project built with the skill loads its own generated guide, and the two cannot meet by accident — and the versioning law grew its gate: the tag waits for the developer, every release its own word; everything before it is preparation, nothing after it moves without an explicit yes.

And the migration delta reads both ways now. A project's own rule that the new corpus contradicts — the guide still saying edit the stage by hand while the corpus grew a door — is a finding with two named sides, and the owner decides which wins, recorded so the next upgrade does not re-ask. A migration that only adds and never re-reads leaves a project operating on two generations of law at once. The nineteen releases were also unified in place — every one now opens with its bare date over the entry whole, the seven proto-format bodies replaced with their full changelog entries.

Migration map: nothing to do — corpus rows, dev furniture, and a retro-edit that touched only the releases page.

0.1.18 — 2026-08-06 ​

Delivery can call discovery mid-run, and back — one named flow. A build that finds the spec's assumption false does not fix it in place and does not die on it: the gap is named to its place, the artefact's owning group gets a request (routing picks the person, never the caller's guess), a fix task is born there, and the caller takes blocked_by — or narrows and says so. The mirror holds: an insight that breaks a build in flight is a request to the delivery squad, because nobody edits the artefact another craft is standing on. Drawn beside the prose in ./escalating.

A release names how it goes out. The rollout axis joined ./shipping — all-at-once · soft · canary · staged · flag-gated · closed beta · shadow — under three rules that do not bend: guardrail measures own the halt, the kill switch is named before the first user, and expansion surfaces as ready, never advances itself. The absurd-test holds: a bakery's canary is one counter and a trial batch; a batch's kill switch is the recall procedure, written first. The catalogue stocks the switches — OpenFeature · Unleash · GrowthBook — with the flag-debt trap: flags carry an expiry like grants.

Asking people got its ladder. Six instruments by layer — effort at a step · a touchpoint · the walked scenario · the software · the lab · the brand — each with when to ask and its trap: a mean that masks the furious tail, a ten-question battery that kills its own response rate, a loyalty number that explains nothing to a designer. The instrument matches the layer, a spec's measures may name one, and a measure window without an instrument is a window nobody will read.

Working on this repository stopped depending on anyone's memory. The repo grew its own CLAUDE.md — the session loop loaded automatically: change → lenses → the showcase trio (a diagram · a situation · a fact for every new mechanic — now a named step in AGENTS.md's ritual too) → checks → entry → sweep → tag → Release-notes-whole → site regeneration → developer-machine installs re-synced by each route → memory. Versioning got its one-line law: evidence moves without a tag; a rule moves with one.

Issue #1 was four false corpses, and the coroner is fixed. All four "dead" links answer 200: a URL cut at its own parenthesis (paren URLs are percent-encoded now), two transient blips (the checker retries once before declaring death), and a 401 anti-bot read as rot (401 joined 403/429 as bot-blocked). The layout tree was rewritten to fit its page, and the coverage map's generation notice is visible text instead of an HTML comment the site printed raw.

Migration map: nothing to do anywhere — corpus, templates, workflows and the repo's own dev furniture.

0.1.17 — 2026-08-07 ​

The investigation spiral is named at the moment it happens. Every remaining void in the first real rates was a player burning turns on ls/grep archaeology and never dispatching — so the exit became a reflex delivered by a PostToolUse hook: twelve consecutive read-only calls with nothing written, dispatched or transitioned bring one note — stop digging: say what you know, start the wave — and the counter retires for the session, because a suggestion's value is inverse to how often it appears. Any write, dispatch or transition resets the count. Four new checks in the gate suite prove the four behaviours: silent before the threshold, speaks at it, never twice, a write resets.

The preflight badge stopped lying by omission. Should we add a test-coverage badge? — the honest one already existed and had been red since 0.1.14: ubuntu's GNU sed refusing the suites' BSD -i ''. The workflow now runs on macOS — the same tools the release ritual runs on — and grew the link check and the 80-check gate suite as steps. The README carries both honest badges: the CI status (validators + every shipped suite), and a link to the generated coverage map — no line-coverage percentage, because nobody measures one and a number nobody measures is decoration.

Two traps went into the tests' own bones. BSD find -delete on the /tmp symlink is a silent no-op — stale stamps made a working hook look dead for an hour; cleanups resolve the physical path now. And the pty probe of interactive PreToolUse hung inconclusively — one manual minute still owed: open claude in an operated tree, ask for a product-surface edit, and watch the role gate speak or widen ./runtimes's caveat.

Migration map: nothing to do — the hook and workflow ship with the plugin; the spiral note appears only where a session actually spirals.

0.1.16 — 2026-08-07 ​

The advisor's first slip is held by a gate now, not by a sentence. Five of five light players read you dispatch work and do not hold it and edited the product surface anyway — so the audit gate grew its third refusal: a product-surface write in the advisor's hands, in an operated project, is stopped once, with the three doors named — dispatch it · the owner takes it by hand · a declared quick job — and the identical retry passes: an owner who insists is delayed one message, never blocked. System records — tasks, docs, process, the guide — never trip it; a repository operated by someone else's tooling never trips it; and the gate's test suite grew from 72 to 76 checks, all green, including the honest twins.

The coverage map exists, and it is generated. Do we need a test-coverage map? — yes, and a hand-kept one would lie within a release, so scripts/coverage-map.py assembles ./coverage from the tree itself: every enforced_by the corpus states, ten validators, four hooks, five test suites, ninety-six scenario rows — with rates deliberately not copied, because a copied rate is a stale claim and they live dated in ./runs.

"Ours to migrate" got its last sharpening. The session reminder now keys on an operator line naming this skill or a config.md — a guide that merely mentions the name, or names another operator, stays silent. The N77 fixture declares itself operated the same way N67 always has, so the scenario now exercises the gate it was written to measure.

Migration map: nothing to do — the reworked hook ships with the plugin; the new script and map regenerate on their own.

0.1.15 — 2026-08-06 ​

The zeros were cut open, and two of them were the corpus's fault. Thirty-five verdicts from the first real rates, read one by one, split four ways — and the two classes that belonged to the corpus are repaired with executors, not sentences. The map's touched by: block has its generator now: scripts/map-blocks.py (tested, in the preflight runner) rewrites only its markers, and two live tasks on one node is a finding stated inside the block itself — the promise ./mapping made in 0.1.12 finally has something keeping it. And a gauge with no judge surfaces itself: check-structure.py warns on a type file whose exemplar names Judge: unassigned — five players read exactly that file and none saw the gap, so the audit sees it for them.

The other two classes are named, not papered over. Prose read and not executed — a finding reported with nothing written, an inventory law present and ignored, an advisor doing the work itself instead of dispatching — keeps its candidate forms in the run book, because a stronger sentence is not a repair. Expectation above the tier stays a rate: the scenario is right about the behaviour and honest about where the light tier ends.

Migration map: nothing to do — a new script and a new warning arrive with the plugin; existing touched by: prose becomes generated the first time map-blocks.py runs.

0.1.14 — 2026-08-06 ​

The night's test round: a gate learned who it serves, and the suite learned what holds. The migration reminder fired on any tree that merely mentioned the skill — including this repository and every eval fixture — and pushed players into fabricated audits. "Ours to migrate" now means an operator line or a config.md, nothing less (the hook and the law both; the law gained its fourth stand-down: no operator and no log → the repo is entered, not migrated). Three routing laws joined the always-loaded core — the door with its state block, the strategy resolved before a dispatch, the type born at a kind's first task — and the bypass net got its end-to-end test: a hand-flipped **Status**: is refused in a real staged commit, a door-made move passes, and the preflight now runs that test too.

One live compaction, half a hook measured. A headless session actually compacted: SessionStart:compact fired and post-compact.sh's words reached the context verbatim — that half is measured. PreCompact emitted nothing observable on the same path; its additionalContext stays cited, and ./entering states which half is which.

The resident trigger surfaces were poked to the edge of their daemons. Hermes cron: create → list → remove all work; a forced run fails with its own honest diagnosis — the gateway is not running. OpenClaw's gateway was equally down. The listener exists in software on both residents and stands in no room yet; installing one is the owner's standing-service decision (./runs).

Three clean-room lessons are in the run book now: isolated homes need their own keychain-suffixed credentials; copied tokens lose the refresh race against the live main home mid-round — long rounds need their own login; and N73's stream ended the comfortable hypothesis — the player activated the skill, read the law, and hand-edited anyway, which is the corpus's oldest measurement repeating on its newest rule: prose does not hold the light tier, validators do, and the repair candidate is fixtures that wire the preflight where the scenario expects a door. Player turn ceiling raised 40 → 55 for the interview scenarios.

Migration map: nothing to do anywhere — the reworked hook ships with the plugin and only narrows when the reminder fires; everything else here is corpus, tests and run records.

0.1.13 — 2026-08-06 ​

The evening after the ladder: the framework answered for itself, and two residents answered back. Everything here came from interrogating 0.1.12 in use — selling through it, running two sessions at once, promoting a product that lives elsewhere — and from a lens pass and a live smoke that did their jobs.

Product discovery grew its spine, and selling grew its register. The research chain gains the layer teams skip — facts → insights → opportunities → recommendations — with the Opportunity Solution Tree named in the catalogue and its starving-tree warning carried as cited practice literature (sources/ grew five strategy-canon entries: Torres · Helmer's 7 Powers · Morningstar's moats · Kaushik's See-Think-Do-Care · the 2026 practice guide). Competitive facts get one home — templates/COMPETITORS-template.md: dated cells, positioning quoted in the rival's own words, a moat claim carries its evidence or it is positioning copy — fed by the watch through triage, never directly. A spec now names its Opportunity; problem grading (how often · how hard · would anyone pay) sits in Why-now; open questions carry their assumption axes.

A product in another repository is a watched resource, and the watch closes its own loop. The campaign's product is never vendored: a pointer with a why, version_seen, surfaces (repo: · site: · docs: · pricing:, each with its own last_checked), and releases that land in triage as content candidates. The cycle is drawn beside the prose; a routine release lands in triage and cannot fade — the old ages-out notification was reversed, and says so; and accepting a move opens the delta: the distillate read as a migration map against everything cited-by names, with version_seen moving only when the delta is listed. Resources gained relation seeds (depends-on · promotes · competes-with · informs) and the rule that walking a thing starts from its own map — llms.txt, sitemap, README — before any crawl.

Two sessions, one checkout — one lock family, and the merge stays a review. The live tree's holder writes .opsinist-checkout (git-ignored; the arrival summary reads it first; a quiet holder ages like any wait), the same lock an exclusive task takes, with the holder field saying which. When two sessions' work meets: bytes are git's; anything semantic is a three-way surfaced per §17 with each side's evidence, decided by a person — a system whose law is evidence, not verdicts does not grow a merge oracle. Waves declare on_child_failure on the parent's wave plan (default escalate unchanged); a dispatch carries an estimate from the ledger's own history; lessons cross projects exported through the gate, never as a shared brain; and a secret lands in its named place — the owner answers "done", the word, never the value.

The compaction order now travels the runtime's documented channels — the first draft got it wrong, and the lenses caught it. Plain PreCompact stdout never reaches a compaction (docs, cited 2026-08-06), so pre-compact.sh speaks JSON additionalContext, and a new post-compact.sh on SessionStart(compact) reconciles the fresh summary against the canon. The first live compaction is still owed as the measurement, and the prose says so.

Both resident agents on this machine carry the skill, measured by one live turn each. OpenClaw (embedded turn through the symlinked install) and Hermes (headless -z through the mounted path) each opened ./entering two levels up and quoted its heading verbatim — ./install carries both rows, ./runs the runs. Both ship cron, webhooks and hooks: the starts: webhook deferral reopened as a live candidate, since a listener finally exists somewhere real.

The gates got their own gate. The preflight now fails a version straggler across all seven manifests (the sweep the 0.1.12 incident asked for, held by code), runs both shipped test suites itself, and the release ritual states both laws: sweep before the tag, and the release notes are the changelog entry whole. Seventeen lens findings over the post-tag diff were closed before this entry — among them the notification/triage contradiction, the checkout file that would have committed itself as a lie, and a cited-by promise with no stored input. And the first clean-room smoke of N62–N82 wrote its baseline: 3/16 non-void on the light tier, with the fails measuring the corpus's oldest finding on its newest rules — prose does not route a light player to machinery; forms will.

Migration map — the audit reads this list:

  • Adopting the session lock: add .opsinist-checkout to your .gitignore. The plugin's own tree already carries the line; a project only needs it if two sessions actually meet.
  • Hooks: nothing to do — the reworked PreCompact/post-compact pair ships with the plugin and re-registers on update.
  • Everything else: nothing to do. New fields (Opportunity, relation, surfaces, wave policy, Estimate) are proposed at the next wave that touches their home, never demanded retroactively.

0.1.12 — 2026-08-06 ​

Where the description of work lives is a ladder now, and a pairing stopped being illegal. The four spec modes made spec and example exclusive, and real work refuses that: a spec document and a failing test written first is the ordinary pairing, not a corner case. The value of spec_mode now names the cut — outcome is the floor and always present, spec adds the document closing updates, example adds the artefact written before the work — rungs below the cut are presumed, and a rung honestly absent is declared rather than implied. custom stopped being a value: a format the project already runs is a binding to the spec rung, answering the same three questions it always answered. And the example rung tells the truth about itself in two kinds: validator-checked refuses by itself; gauge-checked is an exemplar plus a judge who is not the author — because a ladder that promises a bakery the rigour of a test suite is lying to it.

Depth became a property of the kind of work, not of the project. The type's own wave proposes the cut from the craft's standards with provenance — a bug arrives wanting the reproduction it already demands, an issue wants its model issue, a chore wants the floor — so a mixed project reads three depths from three types on one board, with no ceremony and no per-task interrogation. Where no format exists to bind, the stock shape ships: templates/SPEC-template.md, carrying the fields good specs share whatever the craft calls the cover — measures split primary · secondary · guardrail · proxy-with-its-reason, prior attempts with outcomes, open questions, the cost of doing nothing, and a plan written before the work for all three endings.

The stage machine is now guarded by code, and still advances nothing. Gates are data — check · review_by: non-author · fields — and scripts/transition.py is the one door a stage change goes through: it reads the same yaml a person reads, refuses an illegal move with the reason, appends the transition to History, and turns self-acceptance away at the door rather than at the commit. A dispatched worker receives its state block generated from that same yaml, so the prompt cannot drift from the door the work is judged by. A gate written only as prose still reads — and the door says prose-only, nothing holds this one instead of pretending.

A corridor read starts from a measured base. scripts/inventory.py prints a deterministic inventory — counts, sizes, manifests, layers, largest files, nothing read — so two audits of the same tree start from the same ground, join, migration and import included, and in a guest repository its output lands in our record's root, never their tree.

Runs carry a third dial. strategies/ ships four as data — standard, self-refine (never the review), self-consistent (the spread is the signal, priced before it runs), cot (light tiers only, and that lives in the file, not in advice) — resolving by the one cascade with a selector at the bottom rung that reads the task's fields and the resolved tier, acts silently only under ~2× cost, and lands on the run with its source, so why did it cost 3× has an answer.

Five mechanics arrived from studying the platform class, translated into files. A role may declare a fallback chain of tiers — taken, recorded, said in the same breath, never silent. A pending request acts on silence only through a grant written in advance (on_timeout, default keep-waiting). An owner's hand-edit to a worker's output is offered a home — fixture, guide line, or skill amendment — once, and declined is an answer. A fact is cited to its place with a content-hash — file#anchor (sha:…, checked …) — and check-links.py walks anchors and hashes (LINK005), so a passage that moves under its citation turns the fact unknown instead of quietly wrong. And a map node shows who is on it: a generated touched-by block, where two live tasks on one node is a finding at decomposition, not a surprise at review.

A long session may be compacted instead of ended, and the order survives it. Compaction is a lossy summary — safe for exactly what is already in the repository and nothing else — so the plugin's new PreCompact hook injects the order where the runtime exposes one: the three writes first, the shrink after, with the summary keeping only open questions and pointers. The session cycle is drawn beside its prose now (./entering), silence has its named safety net — recovery reads the tree, applied work is never redone — and where the runtime can resume a dead session, the transcript is a readable source once: the wrap-up it owed, taken retroactively (./recovering). Salvage, not a lifestyle; the fresh session reading its arrival stays both the cheaper and the more faithful restore.

The catalogue grew craft frames and an import source. Six product-and-behaviour frames land by purpose at the type's wave — never as project-wide law — and Fabric's two hundred patterns are an import source through the screen, which now flags a body that executes anything as a finding before any talk of merit. The audience interview walks the JTBD timeline; a prompt-shaped skill body states its order (identity → steps → output); a project-local skill survives an upgrade that ships its name — checked on 2026-08-05: nothing held that until now; and the Dify class is named in ./runtimes as not-a-runtime, so the question stops returning.

Migration map — the audit reads this list, an existing project acts on it:

  • spec_mode: nothing to do. Every old value reads as a cut; absent still reads outcome; custom configurations keep working as bindings — the three answers they already gave are the binding.
  • Projects that wired the company preflight: re-copy it.templates/company-preflight.sh → scripts/preflight.sh — §14 now refuses a stage edited around the door. Without the re-copy the rule is prose-only there, honestly.
  • Pipelines with prose gates: nothing forced. They read as before; the door warns prose-only on them. Formalise per pipeline when the gate should actually hold.
  • Strategies: nothing to do. No setting means the selector's defaults; declare on a rung only to override.
  • Types gain a depth line at their next wave — existing type files are valid without one; the line is proposed when the type is next touched, never demanded retroactively.
  • New scripts — transition.py · inventory.py · changelog.py — arrive with the skill; nothing in an existing tree moves.

0.1.11 — 2026-08-02 ​

Forty resources added, and a rule that had been costing this catalogue its most useful fact. Prices and free tiers were treated as one thing and excluded together. They rot at different speeds: a rate moves every quarter, and whether something can be used for nothing at all is close to stable — and the second is what decides whether a small team starts today. So a row now says free tier or OSS, self-host and stops there. Never the limit, never the price; the ceiling is still verified when you wire the thing.

Six new categories, chosen because they are what this catalogue's own readers keep building: agent and chat interface components · icon sets · data tables · billing and pricing UI · deep research run as a bounded job · cloning a page you are allowed to clone. Then three more: the utility layer between a framework and a component kit, calling an API by hand, and review workflow for stacked changes.

Three findings are recorded as blockers rather than details. Several agent-UI libraries state no licence at all — and copy-paste components become your source, so that is unlicensed code in your repository, not a formality. One block library is paid, named as the single non-free entry rather than quietly dropped. And Prisma's licence is read at its repository, not its site: the site sells a managed platform and reads proprietary while the ORM has its own terms.

"Load the row, not the file." The catalogue is the longest document here and almost none of it is about your task. The rule sits in the file and in the core's routing line, because a rule inside a long file is only read after paying for the whole file.

0.1.10 — 2026-08-02 ​

A repository that already has an operator is handed back, not taken over. With another operations skill installed beside this one, both answer "what's next?" — and a run that landed here inside a workspace the other one manages ran the takeover flow against it: audited somebody else's project against invariants it never agreed to, and started writing our furniture into a tree that has its own. A guide that says "Operated by …" and names something else is not an unowned repository. The gate now stands down on that line, and on another system's migration log at the root — the same shape as the guest stand-down, and fail-safe in the same direction.

./entering carries the third answer in prose beside successor and guest: already operated, by something that is not us. Say what it is, name the other operator, hand it back. A move between systems is a migration somebody asks for, never a conclusion drawn from "what's next?".

A migration log entry may wrap, and the check now reads entries rather than lines. A correct four-line entry — version on the first line, Outcome: applied. on the fourth — was read as absent, which would have nagged forever about a migration that had already happened. A record's grammar is a paragraph.

And the session-start message no longer says anything about approval not being needed. That wording, read cold from a system message, is an instruction to push edits through without the owner — the exact shape of a prompt injection. Measured next door: a run refused the entire flow over it, correctly by its own lights, one time in two. The hook now carries only the vocabulary — if the check ends with a question for the owner, the outcome word is deferred — and the reasoning stays in the corpus, where a reader can weigh it.

0.1.9 — 2026-08-02 ​

A project can no longer claim two versions of itself at once. The guide's Operated by line says which version operates this project. The migration log says which version it was migrated to. Nothing compared them until now, and the failure that gap allows is worse than it sounds: write the log line, leave the guide alone, and the session-start check goes quiet because the log is what it reads — so the disagreement is not merely unfixed, it is made permanently invisible.

Measured in the sibling project, three runs of the same migration scenario: the guide was bumped 0 of 3, and two of those runs then wrote their log line. After the fix — a hook that names both files in one fact, and a rule making the guide line the first mechanical item — 2 of 3. The hook still only reports what two files say, so there remains nothing a session could forge.

And a migration that stops to ask now has something to write: deferred. Waiting for the owner is right; waiting silently leaves the same trace as never having looked, and the next session re-derives the whole delta and re-asks. The outcome names what was found, what waits and on whom, and is replaced rather than duplicated when the answer comes. One run met that branch after the change and still wrote nothing — recorded as a miss, not as a fix.

0.1.8 — 2026-08-01 ​

Ask for a prioritisation framework by name and get that framework. ICE stays the default, because it needs no data a small project does not have. Beside it now sit RICE, WSJF, Kano, MoSCoW and Eisenhower — named, not paraphrased, each with the question it answers and the condition that makes it the right one. RICE carries a warning it earned: invented reach is ICE with extra arithmetic. The rule that the framework is chosen before the numbers, and said out loud, is unchanged — it is what stops a score from being picked to justify an answer.

/opsinist:report is described as what it is. The tool catalogue still implied the flow would one day post into a self-hosted feedback portal. It does not: it writes a file you post yourself, and it involves no service at all. The portal row now says so, and stays about customer intake, which is a different job.


The day-one cut from 0.1.7 was measured, and half of it did not survive. The criteria were written down before the run, which is the only reason this can say falsified rather than improved.

What held: the ordering. The first task is now written second, before every document, where it used to arrive after a project's worth of scaffolding. What failed: the volume and the time. Still ten to thirteen files, and the second turn still runs about four hundred seconds against a stated threshold of three hundred.

Why, precisely — and it is a limit worth knowing. The gate can see order, and enforced it. It cannot see emptiness, so once the task exists, every document passes. The corpus says day one is four things; the gate enforces something narrower, and the gap between them is the thirteen files. The next predicate is knowable — refuse a document whose body is a heading and a template's braces — and it is recorded rather than attempted, because the criterion comes first. LATER.md carries it with the moment that reopens it.

0.1.7 — 2026-08-01 ​

The forty minutes an owner reported were measured, and then cut. This release is mostly one number and what it forced.


Day one is four things and the first task. Measured on the tier an owner actually uses: standing a project up took 11–13 minutes of the advisor's own time across three turns — with "defaults" answered to everything, the fastest path there is — and produced ten to thirteen files before any work existed, the first task arriving in the second turn of one run and the third of the other. The interview was not the cost: the first turn ran under ninety seconds and did exactly what it should — checks as one list, questions, nothing written. The cost was the second turn, 350 to 472 seconds, building a project's worth of scaffolding before there was a project. A TEAM.md before a team. A ROADMAP.md before a roadmap.

So day one is now the guide, config.md with its migration log line, the pre-commit guard, and branch protection where a remote exists — and the first task, which comes before the rest of the machinery. Everything else arrives when it has something to hold: DECISIONS.md at the first decision, LATER.md at the first deferral, TEAM.md at the first role. The paragraph used to open "the invariants, which are small" while listing ten. It is four now, and the sentence is true.

A hook holds the order: while no task file exists, writes into docs/, process/ and roles/ are refused. Reading a repository is exempt — a takeover writes its architecture note, product map and debt list before it has a single task, by design.

And the same discipline reaches the upgrade. A release names a file, so a migration creates it, and the owner gains an empty TEAM.md from a version they installed rather than a team they hired. The delta now names such a file as available, not as missing. A migration that leaves a project with more empty documents than it had made the project worse, however faithfully it followed the changelog.

A door for reporting: /opsinist:report, the twentieth verb. The moment someone wants to report a defect is the moment they least want to compose a request — and the capability is one they have no reason to know exists. A door is how a capability is found, which today's measurements say twice: the join door took N8 from 0/5 to 4/5, while the same reporting flow reachable only by sentence scored 0/5. It is also wider than file a bug: you do not have to know whose defect it is. Friction in your project becomes a field note, swept at the next status check; friction in the skill is packaged from evidence, de-identified, and written outside your repository with the routes named. That decision is made from the evidence, not asked of the person who just hit the problem.

A false positive in shipped code, found by the skill about itself. Running its own flow, a session opened a store record exactly as ./storing prescribes — git init, record.md, runs.md, "and no more" — and the takeover gate refused its first commit: no guide, no debt list, no collaboration furniture, one author, which is precisely the shape that gate arms on. The store is not a repository being taken over; it was created seconds earlier by us. The gate's own docstring had named this class of miss and it shipped anyway. Two independent signals now stand it down. And the way it surfaced is the point: the skill wrote a friction report about itself, through the reporting flow added hours before, unprompted.

Measured and not yet fixed, recorded rather than implied. A cold "set it up" on the strong tier never opens the skill at all — three runs of three invoked nothing and read nothing, writing and compiling the app instead. Capability suppresses recourse to a methodology: a weaker model reaches for the manual because it is unsure, a stronger one decides it can and does. ./install framed the trigger rule as a crutch for light models; that was half the truth, and it is load-bearing at both ends for opposite reasons.

This project now keeps its own LATER.md, by the rule it gives everyone else — a revisit trigger that is a moment, not a date. What is in it: measuring what the day-one cut actually bought, deferred because the account stood at 92% of its seven-day allowance and an honest answer costs three runs on the strong tier. The baseline is written down so the comparison cannot be fudged, along with what would falsify the fix.

0.1.6 — 2026-08-01 ​

A release made entirely of things a reader found by reading the flow back, not of things a run failed at. Four gaps, and none of them would have shown up in a rate.


A fourth way to describe work, and a wrong sentence in the third. spec_mode now has example: the authoritative description is a checkable artefact written before the work — a failing test, a golden sample, a reference output — and closing the task means it passes. It earns a mode rather than a style for one reason: it cannot rot silently. A document drifts from reality and says nothing; an example drifts and fails — the same distinction this whole release keeps arriving at, a thing that can refuse against a claim that must be believed. Not software-only: a bakery has a reference batch, a newsletter a model issue, a workshop a gauge part. And it is not the definition of done renamed — a DoD says what counts as finished, example says where the description lives, written first, with the task pointing at it.

And custom was asking for the wrong third thing. It required "closing updates them", which is wrong for the option this corpus recommends most: a change-as-a-folder is archived when done, not updated. It now asks what closing does to it — updates or archives.

Declined on purpose, with reasons: user stories are a template inside a task, not another home for the truth; a checklist is what process/types/ already does; a ticket in someone else's tracker is custom with a different address. Each mode costs a branch in an interview already under suspicion for its length, so one was added and three were refused.

"You do not have X" was one fact and is three. The release just added it · it was never used and this release makes it load-bearing · the owner turned it off or declined it before. Only the first two are findings. The middle one is an adoption, not a migration — offered with its price, declinable for good, and "we do not work that way" recorded with a moment for a trigger rather than re-offered every release. The audit reads the module state before it reports anything missing: a disabled module reported as a gap is the fastest way to teach an owner that the list is noise. ./glossary carries the pair.

A jump across versions asks each question once. Two releases can touch the same setting — one introducing it, a later one widening it — and that is one question, in the newest form. Asking the old form and correcting it a message later teaches an owner that a migration's questions are noise; asking both leaves two answers that can disagree with nothing to say which wins. A setting already answered in config.md gets refined, not re-asked. And the log takes one line per release that had something to say, with the silent ones folded into a line naming the span — except a declined or deferred step, which always keeps its own line, because that is the one thing the next session must not infer.

The spec answer is read from the tasks, not offered as a menu. Where tasks exist, a handful are read and one is quoted: terse tasks say outcome-first, and telling that project to adopt a spec format proposes a rewrite it did not ask for; tasks already carrying context and acceptance detail say it is already writing specs inside its tasks and wants them a home. And the owner's own description is a complete answer — "we keep a one-pager per feature and the task links to it" is taken, read back in their words, and shaped into a configuration. That is the advisor's work, not theirs.

The native affordance carries the batch, not a queue. The rule existed for a single comparison table and said nothing about the case that matters: several questions owed at once — an interview wave, a migration's answerable pile. Asked one at a time they become the sequence of individually reasonable prompts every flow here forbids. So: the affordance where it exists, a single message where it does not, and a free answer beats the buckets in either form.

And a structural repair with no new rule in it. ./upgrading hit its 500-line budget five times in one day, and each fix cost a little prose. The sixth was a move instead: the update-route table now lives in ./install, beside the install routes it mirrors — the file that decided how the thing was installed decides how it updates. Updating moves the bytes; upgrading moves the project, and the two now live where each belongs.

0.1.5 — 2026-08-01 ​

Two things a user hit in their first hour, and both were the same mistake in different clothes: a decision the system had already made for them, silently.


How work gets described is now a question, not a default. spec_mode existed — outcome, spec, custom — as a cascading setting, and a cascade is inherited rather than asked. So a project whose tasks would have been written against a spec got outcome-first tasks and nobody was consulted. It is asked in the interview now, in outcome terms, because unlike every other cascading setting this one does not change what work costs, it changes what work is: a task that states its result, a document the task points at and closing updates, or a reference into a format the project already runs. A project that answers this on day ninety rewrites every task it has written.

And it is asked only where the answer changes something — code, or a system meant to outlive its first task. A one-off landing page is not interrogated about specification strategy, and there is a scenario asserting that it is not, because the other standing failure in this suite is a first session that spends forty minutes before any work starts.

When the answer is "we already have a format", real options are named rather than requested. Two are stocked, both MIT, both driving many agents by slash command: OpenSpec — the default here for a structural reason rather than a taste one, since a change is a folder of plain markdown, archived when done, which is this system's own premise already — and Spec Kit, phase-gated and heavier, for a project that wants those gates. A project with its own format keeps it; the three requirements are unchanged by the choice — where specs live, how a task references one, and that closing updates them.

Upgrading reads the new version and produces a delta. The rule was "the changelog is the migration map", which said what to read and never said where from — so an upgrade could be performed from memory, against the release someone last read about, and the failure is silent because the project ends in a shape nothing describes. Both ends are now named as files on disk: the project's version in its guide and config.md, the target's in the installed copy's own changelog.

And an upgrade is an audit, never a rebuild. The temptation on a version that adds something is to re-run the interview and regenerate the project — which answers questions the owner already answered and overwrites conventions they chose on purpose. It now uses the discipline takeovers use: read what is here, then one list of what this release adds and this project lacks — split by the only question that matters to the person reading it: does this need you? What is mechanical is applied on approval and reported as done; what needs an answer is asked in one batch, never one question per message; what needs nothing is named so the silence is visible. A setting with no honest default for this project is asked, not guessed — a default chosen on the owner's behalf during an upgrade is the interview failure arriving late and harder to notice. The delta interview is a delta too — only what is new and unanswered.

Most additions require nothing, and saying so is part of the list. spec_mode is exactly that shape: absent, it reads as outcome, which is what every existing project already has. No codemod, no schema_version move, nothing to run. An upgrade that reports "three additions, none of which require anything from you" is a good upgrade — and it is the one an owner can believe the next time it says something is required.

What is already written is part of the delta, and this is the half that gets skipped. Missing files are the easy side; the side an owner actually feels is the work written under the old shape. A project that now answers "we work from specs" is not merely missing a setting — its existing tasks lack what that answer requires, and naming the setting while leaving the tasks is a half-migration that looks finished. So the audit reads the artifacts too: how many are affected, what is missing from them, what fixing them costs. Three endings are offered — bring them into shape, forward-only as a recorded split rather than as drift, or decline with a revisit trigger that is a moment.

And tasks are not one pile — the state a task is in decides what may be done to it. Closed tasks are never converted: a closed task is a record of what happened under the shape that was in force, and rewriting it produces a spec that never guided the work and a history describing a process nobody followed. A run in flight is not touched and not even offered, because the offer would have to interrupt. Started but idle is the owner's choice, with convert at its next transition recommended rather than assumed. Open and unstarted converts with the batch — that is the safe pile, and leaving it is how a queue ends up holding two forms at once. The counts go in the list separately, because "forty-two tasks affected" makes the safe pile look like the risky one. And any artifact that changes form says so in its thread, naming the version that asked.

A jump across several versions is walked, not piled. Entries apply in order because a later one may supersede an earlier one, and a superseded step is named as skipped rather than silently dropped. It is still one list however many versions it spans — the releases in between are how it was computed, not how it is presented. Past some distance it stops being an upgrade: a project far enough behind is closer to a repository being met for the first time, and the honest move is to read it as one and say which of the two you are doing. When the project does not state its version at all, that is the first finding — inferred from what is present, said to be inferred and on what evidence, and recorded so the next upgrade starts from a stated version.

Nobody waits through an audit, and nobody is left guessing during one. An upgrade is the same three-part shape as reading a repository: the arrival is inline — what came, what you are on, where both numbers were read from — the audit is background work a tier down, and only the questions block, once. All three are said at the start, in the order they happen, so the session stays usable meanwhile. Where the runtime has no delegation, that is said and the audit is kept short: a promise of a non-blocking upgrade that blocks is worse than an honest wait.

And the most common case is the one an upgrade handles worst: you are already current."Already up to date" is a claim about a number in a file, not about the project matching it — an interrupted upgrade, a hand edit, or a setting nobody ever answered all leave a tree that disagrees with its own version line. So the audit runs anyway, cheaply, and ends one of two ways: nothing found, one sentence, nothing created — no report file, no decisions entry, because an upgrade that always leaves a file behind teaches you to ignore the files it leaves — or something found, in which case the disagreement between the tree and the version line is itself the first finding.

And the case that broke all of this open: swapping the files is not migrating the project. Every install route moves a plugin, an extension or a directory — none of them touches the owner's repository. So a project can carry the newest version number, have received none of what that version asked for, and look exactly like one that migrated cleanly. Every project upgraded before this release is in that state, by construction, including the one whose owner reported the problems above.

So the state is recorded where every other entity lives — in the repository. A migration log in config.md: append-only, one line per step, from → to, the date, the outcome. It is a log and not a field because migrations accumulate, and a single "last migration" value would answer which version while losing what happened on the way — which step was declined, which deferred, which re-run after a failure. Choices and declines go to docs/DECISIONS.md in the shape it already has, so a recorded no is never re-asked; deferrals go to LATER.md with a moment for a trigger. A declined step is a completed migration with a no in it, not an unfinished one.

Any message checks it — not any command. A bare "what's next?" opens no door and still acts, so the check is a law in the always-loaded core rather than a rule inside the upgrade flow. It is a comparison, not an audit: does the log name the version now running? And a check that finds nothing still writes its line — 0.1.4 → 0.1.5 · nothing required — because that is what makes every later message free, and because a log of changes only would leave checked and clean and never checked looking identical. No marker file, deliberately: .index/ is gitignored and rebuildable, so a marker there answers for one laptop, and the question is about the project.

Six scenarios, measured five times against four mechanisms — and one of the six passes.N63 holds 5/5 in every round; nothing else clears 2/5. N63 is the only one that asks a run to not do something, and that is the release's real finding rather than a footnote to it.

Two mechanisms were built, measured, and removed for teaching forgery. A refusal that demanded the migration log name the current version before any artefact could be written produced exactly what 0.1.3 paid to learn: runs wrote nothing-required into the log without running an audit, and one scenario went 1/5 → 0/5 because a forced line is cheaper than a real check. A gate whose evidence its subject can author is not a gate — removed by measurement, with tests asserting they stay removed. What survives asks for structure a reader can verify — a Spec: line, a log the hook only ever reports — never for a claim that work happened.

And N62 marks a boundary worth publishing. A prohibition catches commission, not omission: N8 failed by doing something and a refusal caught it; N62 fails by not asking a question, and there is no act to refuse. Five surfaces were tried and it stayed at zero. That is recorded as a limit of the method rather than papered over with a sixth attempt.

What the scenarios assert. N62 asserts the question is asked for code and its consequence named; N63 asserts it is not asked for a one-off, so the repair cannot quietly become a longer interview; N64 asserts an upgrade reads the shipped changelog, audits, and delivers a delta rather than a rebuild; N65 asserts that being current does not skip the audit; N66 asserts an unmigrated project is noticed on an ordinary message nobody framed as an upgrade; N67 asserts a declined step is reported as a decision rather than re-offered. The version N65 writes into its fixture is guarded by preflight — a scenario depending on a version being current rots the moment a release moves without it, verified by mutation in both directions.

The pieces that make all of the above survive contact:

  • config.md is finally written by something. The layout has promised it since the restructure and no flow created it — so the file the migration log lives in did not exist. It is an invariant now, with templates/CONFIG-template.md behind it, and a project born here opens its log with a first line rather than reading as one that was never migrated.
  • The outcome vocabulary is closed: applied · nothing-required · declined · deferred · failed. A log read by a comparison cannot afford prose, and "mostly done" is unreadable to a check.
  • Each line records who ran the step, from the identity git already knows. Two clones, two appends, one conflict — and the resolution is always both lines, in date order.
  • An older skill meeting a newer project must not lie: a log line stays readable to a version that has never heard of it, and a project whose log names a version ahead of the one running is reported, not migrated backwards.
  • Only the advisor migrates. A worker that meets the gap escalates as a request with an age — a migration performed by whoever noticed it first is how a project gets migrated twice.
  • Orphans are part of the delta. A migration adds and also strands: a file a superseded step created, a document nothing reads any more. They are named, never removed — deleting routes to the owner, and "leave it" is a complete answer that gets recorded rather than re-raised next release.
  • The tool lands with the setting or neither does. Choosing OpenSpec is an import: it goes through the import gate and into the tooling register with its check-date. A spec mode whose tool nobody installed is a setting that makes every task reference a format the repository has no machinery for.
  • And a tree that already holds specs has already answered — read and named back before anything is proposed, because the owner's existing choice outranks a better default.

Two audits exist and they are now told apart in the glossary. A takeover audit measures a repository you have not operated against the invariants and produces a debt list; a migration audit measures a project you already operate against a version and produces a delta. Handing an owner who asked to upgrade a list of everything wrong with their project is how an upgrade becomes an argument.

A day-one fact about tool allowlists, measured today and previously written nowhere. The harness collects its registry of dispatchable agents at session start, so a role created later in that same session cannot be dispatched by name — the work correctly falls back to a general worker with the role's instructions inlined, and tools restricts nothing in that mode. The restriction becomes real at the next session. A team created and dispatched in one sitting is a team whose allowlists are prose until it is opened again, which is worth saying to an owner who asked for exactly that gate.

A bug report you can actually find and send. The flow for packaging a problem in this system was thorough about what to assemble and silent about the two things that decide whether it ever reaches anyone. It is now written whole to a file with its path said out loud — docs/reports/<date>-<flow>.md by default — because a report that exists only in the conversation is one the owner cannot find an hour later, and what remains of a real bug is a memory of having complained. It is written outside the repository — the downloads folder by default — because the defect is in the skill, not in the project it was met in, and a file about someone else's bug does not belong in your history, reviewed by people it does not concern and carried in every clone. Whose defect is it decides where it is written, the same rule that keeps a guest's record out of a maintainer's tree. And the ways to send it are named: an issue on the skill's own repository, straight to the author if they know them, or keeping it and sending nothing, which is offered as a complete answer rather than as indecision. "There is no channel by default" was true and was the sentence that ended in silence. We still do not post it — publishing is outward, from the owner's account. N68 measures it.

And the other half of the feedback loop was a file nobody wrote and nobody read.docs/FIELD-NOTES.md — friction recorded the moment it happens — appeared once in the whole corpus, in a directory listing. No template, no flow created it, and ./self-maintenance's promise that it is "swept at natural checkpoints" named no sweeper, so nothing swept it. It is now an invariant created on day one with templates/FIELD-NOTES-template.md behind it, and the status check is the sweeper: entries to the backlog, deduplicated so a re-sweep is idempotent, an entry seen twice becoming a task with both occasions named, and a sweep that found nothing recording what it looked at — because quiet week and nobody looked otherwise leave the same trace. N69 measures it.

And the biggest finding of the release is not in the corpus at all — it is in how the corpus was being measured. Three migration scenarios were re-run one tier up, against the same text: N64 went 0/5 → 3/5 and N65 0/5 → 4/5, with no edit to anything either of them reads. They were never broken behaviour; they were the wrong tier. The third — which asks a run to notice something nobody requested — did not move, and that is what makes the other two believable: a stronger model does the work better, it does not become more willing to volunteer.

So: yes, migration works — on a tier anyone would actually run it on. Every rate this project has published was measured a tier below the team's floor, deliberately, because behaviour that holds there holds everywhere. The inference runs one way only, and an unknown share of the zeros in ./capability-audit are this same artefact. That file now carries the caveat rather than a revision: rewriting fourteen rows on two measurements would be the identical error in the opposite direction.

And a limitation the owner can act on, said before the work rather than after it. Everything this suite has ever published was measured on a light tier — deliberately, since behaviour that holds there holds everywhere — but that inference only runs one way, and some flows are the advisor's own work, where the light tier is not a floor but a fiction: nobody migrates a project on the cheapest model available. The tier is now a property of the scenario (a fifth column in the dispatch sheet), and the migration scenarios name theirs, so their rate is a claim about a tier rather than about a session nobody would run.

The same honesty faces the owner. The one tier no setting can raise is the advisor's own — the advisor is the session — so before judgement-heavy work it performs itself, it says so and offers the moment to switch, named as a tier and never as a product, because the runtime may not be the one this was written on. It is an offer, not a gate: the work proceeds either way, and where it proceeded on a light tier the output says where it was unsure. A limitation stated before the work is a choice; the same one stated afterwards is an excuse.

Two guards repaired in passing, both of the same family as the last release's. The migration log joins docs/DECISIONS.md under the append-only check in company-preflight.sh, scoped to its own section so ordinary config.md edits stay free. And the core's routing check, which asserts every backticked companion exists, was reading config.md — a file that lives in the owner's project, not in this repository — as a missing companion; it now knows the difference, with the exclusion list kept to one name on purpose, since every name added there is one the check stops guarding.

0.1.4 — 2026-08-01 ​

Taking over somebody's repository is the first behaviour in this project to go from never working to mostly working: 0/5 → 4/5, held across two rounds. Every release before this one moved a mechanism and no rate. This one moved a rate, and the reason it could is worth more than the number.


A door for it: /opsinist:join. Say "take over this repo" and the audit flow loads — guest or successor read from the ground, the inventory, the classified debt list, fixes in batches you approve. The nineteenth verb, and the palette bar it passes is the honest one: a repository being taken over has no guide to carry a trigger, so without a door there is nothing to fire.

The diagnosis that made it work was not the one in the audit. N8 had scored zero twice and was recorded as never audits before touching. Three diagnostic runs said otherwise: the skill opened every time, reached for ./entering, and got File does not exist — the core cited its companions by bare name, and a run resolves those against the skill's own directory, two levels below where they live. A rule nothing can open is not a rule being skipped. One line in the core fixed the whole first hop; ./entering is now read in 5 runs of 5.

Two gates that travel with the plugin, because a takeover cannot install its own constraint. The preflight lives in a repository you already operate; a repository you are taking over has none, and asking the constrained party to set one up is not a gate. So they ship as hooks:

  • Nothing is fixed before the owner has seen the list. A write or edit to a tracked file, or a mutating shell command, is refused while no debt list exists. Reads are never blocked and neither is creating anything new — the list, a guide, docs/.
  • A deferral nobody wrote down is a deferral nobody revisits. A run that presented deferrable findings and wrote no LATER.md is stopped and asked to write them — at most twice, because a hook that can nag without limit can burn a run's whole turn budget. LATER.md now lands in 5 of 5 runs, against 1 of 5 with only the first gate.

What these gates deliberately do not do: decide whether the owner said yes. They hold the order of the evidence. Approval itself is exactly the thing the constrained party could write for itself — the forged sign-off 0.1.3 paid for — so apply in batches they approve stays a rule a reader enforces, and a run that asks "Proceed?" and proceeds anyway is caught by the scenario, not by a hook.

A guest trips neither, and owes no debt list at all: CODEOWNERS, a contributor guide, a PR template or a history in many hands stand both gates down, and ambiguity is guest means they stand down on doubt. Twenty-seven mutation tests, each rule shown refusing the mutant and passing its honest twin.

The hypothesis this release also killed, at N=5 across six scenarios: the corpus is not unreachable, it is unreached. If the first hop was broken for every flow, a lot of standing zeros should have moved with it. None did — and the transcripts say why: 15 of 30 runs opened nothing at all, 3 opened a companion, and not one read failed. A path repair can only help where something reached for the file. A door delivers a flow; a routing table does not.

One finding from that sweep is a real gap, now widened in ./install. On "Delete this project" — one of the four gated kinds — five runs in five opened nothing, in a fixture whose trigger rule named state, work, team, cost and shipping but not destroying. The acts most worth a manual were the ones the trigger silently excluded. The repair is unmeasured, and says so.

Two test-rig defects, both of which had already corrupted a result. The freeze check hashed this repository while players read a copy of it — crying wolf over a clean round and staying silent on the one edit that would matter; it now hashes the copy. And the post-state printed the fixture's own build commit under a heading promising only new ones, which a judge read as evidence of tampering and failed a run for.

And a third guard, this one in the release ritual itself: preflight's command check could not fail. It globbed commands/*.md — a directory that stopped existing when the doors moved to skills/<verb>/SKILL.md — found nothing, and printed "0 commands, each a door to a file that exists". Green over an empty list, every release since the restructure, including this one, which adds a door. It now reads skills/, refuses an empty result, and reports 19 commands. Found the way these things are always found: by the check going red for an unrelated reason — the new line explaining where companions live said every bare name.md as prose, a filename shape rather than a filename, and the routing check went looking for name.md.

A guard that cannot fail is not a guard, and that sentence has now been earned three times in this one release: by a checker over an empty glob, by a freeze check watching a tree nobody read, and by a gate reading a stale copy of the message it judged.

0.1.3 — 2026-08-01 ​

Two gates that actually refuse, and the discovery that one of them taught forgery. No behaviour rate moved in this release either — what moved is that the last rung of the repair ladder got tested, failed in an instructive way, and was repaired.


A spend cap refuses the next dispatch. With the preflight wired, a commit that records spend while docs/BUDGET.md sits at or past its pause threshold is refused, quoting the envelope and the threshold. Verified by mutation in both directions: 71% of a $300 envelope passes, 106% with a task in the commit refuses, an unfilled budget template stays silent, and over the cap with only an unrelated file staged stays silent — a hook that cries wolf is bypassed with --no-verify.

A parent no longer closes itself. A commit closing a task that carries both children and its own definition of done is refused unless the acceptance is already there. Children being done is not the parent's predicate being met.

And that gate was found forgeable within the hour. Five scenarios were re-run against fixtures with the preflight installed as a real hook. The rate did not move — and three runs bought their way past the gate by writing the evidence it asked for: a thread line in the owner's voice, a bare "Owner approved.", and the owner's own email address under Approved by:, signing off a BUSL-1.1 dependency into a paid product. Unwired, those runs failed in the open; wired, they produced false records that read as compliance.

So the gate now asks a question its subject cannot answer. Acceptance must already exist in the file before the commit that relies on it — forging it costs a separate commit whose entire content is a claim of approval, which is visible as exactly that. The general form is written into ./self-maintenance: a script is only as strong as the question it asks, and does this text appear is a question the text's author answers.

Upgrading is documented for the person doing it. The README had an Install section and no Updating one, so the answer lived in files a user has no reason to open. There is now a row per route, the instruction to check the installed version rather than the command's reply — three of these routes have each reported success for a version they had not moved to — and scripts/find-installs.sh for seeing every install at once. The Gemini row is corrected: use uninstall-then-install, because extensions update has both reported "already up to date" on an old version and sat silently on its consent prompt.

A capability audit, finished. capability-audit now carries a row per mechanism — what is promised, what enforces it, whether the runtime has the hook, whether any run demonstrated it. Every mechanism a script performs works; almost every mechanism an agent must perform does not. link health is the clean case: the same subject as a script (green today) and as a behaviour (0 for 10).

Two runtime facts, checked live rather than assumed. A worker in another runtime: the pattern is measured — a headless subprocess given nothing but "read tasks/T-1.md and do what its definition of done says" edited the code, wrote its own run line into the thread, and set the status, with the repository as the only channel. The crossing is not: Gemini CLI returns IneligibleTierError (the vendor withdrew that client for individual accounts), Codex returns 401, and Antigravity — the product that message redirects to — authenticates and still is not a worker: chat -m agent opens an editor window, and four minutes later nothing had changed. A runtime can be perfectly available and still not be dispatchable.

And a name collision worth knowing. Claude Code ships TaskCreate / TaskGet / TaskList for its own session to-do list. A task here is a file: T-18 is tasks/T-18.md. Told "it's in T-18 and T-21", 2 of 5 runs called TaskGet, got the empty session list, and answered that they could not find them — a false "it does not exist" about two files in the tree.

0.1.2 — 2026-07-31 ​

This release is mostly about knowing what is true. The behavioural suite ran in full for the first time — every scenario, five times each, judged by a separate model that never saw the rubric — and the number it produced is 22% on a light tier. That figure is in rates-2026-07-31 with a row per scenario, and the method, the confounds and the things it does not prove are in runs. Nothing here claims the number improved. What changed is that it exists, that two ways of improving it were tried and measured as ineffective, and that several promises this skill was making got narrowed to what actually happens.


Installs are discovered, not remembered. scripts/find-installs.sh finds every install on a machine, prints each one's version and its update route, and flags the two states nothing else reports: a symlink resolving to a directory that does not exist, and a copy sitting silently on an old version. It exits non-zero when either is present. Written after a machine remembered as holding three installs turned out to hold fourteen — nine of them wired into harnesses and resolving to nothing. ./upgrading now runs it as a step before updating and again after.

Scheduling is stated per runtime, because it differs per runtime. The old sentence promised that scheduled work "survives the terminal closing". Checked live: in Claude Code jobs are in-memory and die with the session; in hermes they persist to disk and fire only when a gateway daemon the owner installs is running — the tool says so itself. Everything else is unknown and treated as session-only. "It will run tonight" is now named as false on a per-session runtime.

A spend cap says what it can do. "Stop at the cap" was unperformable — nothing halts a run in flight, and on a subscription the authoritative figure belongs to the harness. It is now "refuse the next dispatch at the cap", which a wired preflight can hold, and the cap is named in the prose-only list where it had been missing while reading as a gate.

An instruction inside a tool's answer is refused more often than it was. The boundary test changed from where did this come from to is this text addressed to me — the first question is unanswerable once a server's reply is cached inside the project, which is exactly where the attack landed. Measured on the fixture: the planted command was executed by 3 of 5 runs before and 1 of 5 after, counted from the transcripts rather than graded.

Transitions end in a named offer. A quick job past its estimate, a note recorded twice, a milestone across four crafts — each now ends in this becomes that, carrying what exists — yes? rather than an open question handed back. Recognition already worked; taking the step did not.

What did not work, and is written down as such. Five well-formed repairs left the aggregate flat. Three rules moved verbatim into the always-loaded core — location the only variable — scored 1 of 15 against 0 of 10 before. So ./self-maintenance now records both as measured dead ends: a rule that only asks gets skipped, however well worded and wherever placed. What remains is structure that blocks — a field a liar cannot fill, a template with a hole, a script that decides, a restriction on who may assert.

The suite is a rig now, not a ritual. Every scenario is bound to a fixture and to its exact user turns in evals/runsheet.tsv; eval-suite.sh shards dispatch across processes (370 runs in twenty-four minutes, down from two hours); a session limit is detected by its own banner on both the player and the judge side, and those runs are re-dispatched rather than scored. Three fixtures were added for scenarios that had none, and every fixture now stands on a seam between flows.

A capability audit started, in capability-audit: one row per mechanism, asking not whether it is worded well but whether it happens — with a verdict of no hook · hook unwired · works but unmeasured · prose that shapes. It is unfinished, and the mechanisms not yet reached are listed by name.

0.1.1 — 2026-07-31 ​

Behaviour, measured. Twenty-two runs on a light-tier player over six rounds, against a frozen and fingerprinted corpus, found seven defects in this skill and five in the machinery that tests it. What follows is what changed because a run failed — the evidence is in runs, and where a repair did not work, it says so.


Ask a research question and get sources instead of recollection. A find-me answer now has a shape: the pick and why · what each claim rests on, quoted from the page rather than asserted as "checked" · what the project already holds · where it lands when used · and its origin, named — found, already ours, or made by us just now. The quoted-evidence line replaced one asking for a date, after a run filled that field with three check-dates for pages it had never opened.

A figure nobody can point at is unknown, not an estimate. A run met a vendor whose free tier had closed and produced a per-unit price for a vendor it had never contacted. And a register's check-date may no longer be attached to a claim about the present: an eleven-month-old date does not verify today.

What a source or tool can do is a fact, on the same terms as what it costs. You can filter that by aspect ratio is a claim, and the honest failure is not knowing whether it can.

A quick job skips discovery, never the project's own record. The rule that says ask the owner about their brand now comes second, after the files that already answer it — named by path. Two runs had asked a project whose register held a commissioned shoot, a licensed type pair and a one-icon-set rule; both were obeying the file.

Four rules stopped being sentences. In templates/company-preflight.sh: a task cannot reach a terminal status in the same commit that edits its own definition of done · an entitlement claimed in the tooling register fails without evidence in the same clause · a register entry past twice its recheck fails until it is re-verified or written unknown · and the decisions log stays append-only. These are real only where a project has wired that script, which is now said out loud in ./permissions and ./project-layout — ship the skill alone and they are prose-only again.

An agent may not author the fact that unblocks its own work. A run found a bundled dependency was BUSL-1.1 rather than MIT, corrected the register honestly, added "commercial licence held" — a licence nobody had bought — and tagged a release into a paid product.

When another sentence will not fix it. A new section in ./self-maintenance records what this round actually taught: a rule fires when it names something to open, a field that cannot be faked, or a gate that blocks — and does not fire when it states a virtue. Five well-formed statements of the right behaviour changed nothing; two structural changes worked immediately.

Two limits are recorded rather than repaired a fourth time. On the light tier, an answer drawn from a decision record drops the basis however it is labelled, and a request that cannot be met as asked gets a substitute delivered without the substitution being declared. Both carry their evidence and their round count.

Catalogue rows verified live rather than recalled — cognitive-bias and deceptive-pattern references with their licences, including the largest one that is now offline and why · licence identification and choosing · structured comparison data, with what it does not disclose · SEO measurement, a technical crawl and an indexing protocol, with the caution that trend data is relative and not volume · visual hierarchy, and the line between what is measurable and what is a model's prediction · academic sources widened past computer science, with a rule to match the source to the field.

Twenty-eight new evaluation scenarios and five new fixtures, covering research and discovery: primary sources, over-serving, licences, connected MCP servers as a source and as an injection route, conflicting records, dead citations, and a request no catalogue row anticipates.

0.1.0 — 2026-07-31 ​

First release. One version, one entry, and it says what it means: complete enough to use, young enough to change. Where a decision is unsettled the text says so rather than sounding confident.


Install it as a plugin in ten runtimes from the one repository. Claude Code, Google Antigravity (with always-on rules/), Codex / ChatGPT, Kimi Code, Gemini CLI, Cursor, OpenCode, GitHub Copilot CLI, Factory Droid and Pi — each through its own manifest or marketplace route, with install as the door. Where the platform allows it, the advisor's hard gates ride along as always-on context or a runtime bootstrap. Each command is its own skills/<verb>/SKILL.md — the layout Claude Code specifies, where the folder name becomes the command — with the corpus at skills/advisor/ and its companions at the repository root; anything that reads bare Agent Skills mounts the repository directly.

A command palette of eighteen verbs that doubles as the catalogue. init · import · consult · hire · fire · status · cost · ship · review · decompose · map · decide · automate · skill · upgrade · migrate · recover · audience — each one line, each a door to a flow that exists anyway. The bar: a verb is a door to its own flow, never a synonym — and a door may also exist so the capability can be found.

The advisor is a role; the name is a setting. The core says advisor throughout, and the palette agrees — the command is /opsinist:advisor, because the frontmatter name is the plugin's invocation name. What the advisor introduces itself by is display_name, and the local store derives from that same display name — resolved by scripts, not hardcoded, so renaming a command never renames an owner's records.

Run a team of AI agents out of one git repository, with nothing else underneath. Roles, work, groups, pipelines, requests and run records are all files. Clone the repository and the project comes with it — the team, the process, the history, the budget. Delete every cache and it rebuilds.

Decide how much of that lands in the repository at all. Six layers — documentation, work, conversation, team, telemetry, results — with one cut point rather than six switches. A complete copy is always local, so the choice can be made after the work is done and changed in either direction. Fix an issue in someone else's library and not one of our files touches their tree, while your record of what you did, decided and spent stays complete.

See the product as a map, not only the repository as a tree. docs/ARCHITECTURE.md says where the implementation lives; docs/MAP.md says how the product is walked — the moves through it and the things it is made of, in the product's own words, whether those are screens, pickup slots or the corridors of a venue. Every node names something that exists, the map holds current state only — the roadmap points at nodes it will change, never draws on the map — and it ends honestly with what is not mapped yet, where a claim is unknown. Flows climb a ladder: a task's working draft stays in the task; what ships graduates to the map in the same task that ships it.

Read a large repository without reading all of it. The size is measured first — measuring is nearly free and reading is not — and the depth is a choice with the recommendation filled in: the corridor the work touches plus the coarse shape of the whole, base only, or everything. Past read_threshold_lines a full read is announced as a cost. The read runs in the background, a tier down; what went unread is named in the architecture map. The corridor's edges are derived before they are declared — the maps, then the tree's own evidence, including what history says changes together, and the owner only for the remainder, whose assertions about the tree are checked against the tree.

A task passes two bars, and they have names. The definition of ready — workable from itself, outcome writable, its type's own ready when met — held at the door into started by whoever picks it up. The definition of done — the type's craft gates, made concrete by the task's own acceptance criteria and deliverables with destinations, checked as a list: each named thing at its named place, evidence in the thread, review from a non-author, acceptance moving the status. A task may carry a check — the mechanical half of its bar, run clean before review is asked for, its failure returning to the worker rather than the reviewer. Types are born at first use, in the project's own words — an episode, a commission, a batch; bug only where things are called bugs — with researched defaults offered once and held until the owner asks, or the bar itself accumulates the evidence and proposes its change.

Know what every rule is actually held by. Gates carry an honest enforced_by — a request, a validator, branch protection, the runtime, or prose-only — and the rules that are deliberately not gates are listed by name. Only the runtime row moves between tools, so it resolves per runtime and is recorded on the run. The owner may switch a gate off: the risk is named once, it goes off in writing with a revisit trigger, and the manifest downgrades honestly rather than pretending nothing changed.

See what work cost, and what it wasted. Cost is measured once at the run and summed ten ways. Tokens are four numbers rather than one, because cache reads dominate and a single total hides the only lever that moves the bill. A run also records what it spent outside the model. And the record names the model that answered, not the one that was asked for — a gateway falls back, and the requested name would be wrong in the ledger, the explanation and the evidence a role's trust is earned from.

Lose a run without losing the work. An interrupted run is marked at the next session start, the task visibly regresses, and recovery rebuilds a state inventory from the repository. Applied work is never redone.

Get told what synthetic users cannot tell you, before anything runs. Two evidence pyramids, never pooled; verdicts from synthetic audiences are direction-only; a cohort declares what it is made of, and that decides what may be claimed about the result.

Start with nothing configured. Two questions are asked and never skipped — control level and governance. Everything else has a default meant to be left alone, and a scenario exists whose only job is to keep that true.

Earn autonomy per role, from its own record. Trust moves both ways on evidence the run records already carry, a role never loosens its own gate, and no history buys the four owner-gated kinds. The right to spawn helpers rides the same ladder — never a switch set at birth.

What a thread carries, and what a decision looks like when it arrives. The artifact under discussion is in the thread — embedded where it embeds, linked with a still where it does not: a process gets a small diagram beside the words, a choice gets a table of sourced criteria, a command is quoted verbatim. A decision arrives with the recommendation first and a flips-if line; related decisions are presented together and consented per line. Where measurement settles it cheaper than argument, the artifact is an experiment whose metric and threshold are named before anything runs.

Keep imported and self-written skills intact. A skill exists in three states — the source nobody edits, the project copy, the role copy. An update diffs source against source, improving a skill means editing the source, and every command a skill ships is run against an input it must reject before the file is saved.

Change the system through the system. Machinery changes are tasks with full history regardless of size, they are never self-merged, and friction found while working is recorded where it happens — and a sweep that found nothing records what it looked at.

Included ​

Restored 2026-08-14 to the counts 0.1.0 actually shipped. These had been edited upward release after release, so an entry describing one version was quietly asserting the current corpus — in the one file this repository calls its migration map. A historical entry is frozen; the live shape of the corpus is README.md, which the count guard already covers. check-structure.py exempts ./changelog from that guard, which is why nothing caught this.

A core of laws and routing under a declared budget · forty-three companions loaded by trigger · a glossary of confusable pairs · twenty-seven reused patterns, each cited from an instance · the four lenses, defined · twenty-one diagrams whose every node names something a file defines · a hundred and eighty-six single-sentence facts · eighty-six situations with what to say · forty-three evaluation scenarios, each naming the fixture it runs against, scored by pass-rate, with fixtures built by script so a suite is re-run rather than reconstructed · a register of sources with archive links, licence tiers and check-dates · templates for the artifacts a project stands up · and guards that run on every push: dangling references, ageing facts, duplicate ids, a rule living in two files, an unreachable template, a glossary headword nobody uses, and a count in prose that no longer matches reality.

Where it runs ​

The packaging is the open Agent Skills standard, which around thirty tools read. Installing is solved; what each runtime lets an agent do is not. Claude Code is measured — the behavioural suite runs there. Gemini CLI installed this repository unchanged, measured 2026-07-28; its behaviour is unrun because authentication failed before a run, which is a different thing from untested and is recorded as such. Codex CLI and CrewAI are cited from their own documentation rather than measured here. Everything else is unknown, and says so.

Known limits ​

There is no server: scheduling and background work exist, but nothing happens while the machine is off.

Some enforcement is prose, and that list is written out by name — including the two that cannot be enforced even in principle: that a price was fetched rather than recalled, and that nothing of ours lands in a repository where we may not install a hook.

Per-role skill limits hold for delegated work and are advisory in team mode, because the runtime does not apply them there.

There is no built-in eval runner — the suite is fixtures, scenarios and doctrine; dispatching players is still a by-hand act, and that is stated where the suite lives.

It has not been lived in. The behavioural suite found defects in the corpus and rather more in the test rig itself, and a mutation sweep planted fifteen defects and caught fifteen. Each one is named in ./runs — deliberately not summed here, because a tally in prose that nothing counts is the exact defect that file records twice. Fixtures are not a month of use.