# @geonosis/verify

## 3.7.0

### Minor Changes

- 7a5ac6a: `--affected` fails closed on a path no workspace owns, and narrows a test batch by Vitest's own import graph (#189 follow-up, plan 047 row "Void", ruled 2026-10-16). A changed path in no workspace runs every workspace and every test, and the run names the path; a change of documents only, named by the top-level `"documents"` globs of `geonosis.json` (default `["**/*.md", ".changeset/**"]`), skips the runtime tests and says so. A batch step that declares `"graph": "vitest"` is handed `--changed --experimental.vcsProvider` with `@geonosis/verify`'s provider, which answers Vitest with the changed sources, the git-ignored build files of every affected workspace and the test files under the step's `also` paths, so Vitest runs only the tests whose imports reach one of them. A changed file that is not a module the graph follows runs every test of the affected workspaces, names them, and says why, and so does a changed module that is gone from the tree, deleted or renamed away: Vitest 5's graph holds only files that exist, and a test whose import no longer resolves is not reached from the path it imported. A rename is a change at both of its paths. vitest's "No affected test files found" and "No changed files found" are read as a step that ran nothing.
  
  The kit's own `.geonosis/` directory is never read as a change, as the tree hash never reads it, so a run's own report does not select every workspace on the next one.
- 3d313a8: `geonosis-verify green record <name> [--asked <text>]` and `green check <name> [--asked <text>]` give a tool that keeps a green of its own a record shaped as a tier's: `.geonosis/tool-green-<name>.json`, the tree hash, and a question that must match. `check` prints the record and exits 0 while the tree hashes as it did, and exits 1 otherwise. No tier, Stop hook or release check reads it, so `green record fast` cannot turn a red tier green: a tier is green only by a run of its own. A name that is not one path segment is refused. The plugin's PreToolUse Bash hook asks the person before any `geonosis-verify green record …` or `geonosis verify green record …` an agent runs, naming the record, the tree it is written over and what it lets a tool skip; `geonosis update` writes its own records through the binary and is not asked. `geonosis update` keeps its resume records with it (#186).
- f878b27: A red step's whole output is kept (#159). `geonosis-verify` writes it to `.geonosis/logs/<tier>/<id>.log`, the step id spelled with `-` for its `/` (`fast-1.log`) and, after its `@`, `%2F` for a `/` and `%25` for a `%` (`fast-1@packages%2Fa-b.log`), so two steps never share a log, names it in the gate report as the step's `log` and prints `full output: <path>` under the FAIL; an output past 4 MB keeps its head and its tail and says how many bytes it dropped between them. `geonosis-ledger land` copies the log of a refused step to `.geonosis/land/<tree>/` before the merge-result worktree is removed, ends the refusal's tail with `full output: <path>`, and keeps the logs of the newest 20 lands.
  
  A step may declare `failureMark`, a pattern (a regex source) its own failure lines carry, such as vitest's `" FAIL "` or a mutation lane's `"^(?:survived|unjudged)\\s"`. A red step's evidence then opens on the first line the mark matches, not on the first line that looks like an error, which a passing line quoting an `Error:` can own.
- 7b1e29c: `keyFiles` in `geonosis.json`, beside `keyEnv`, names git-ignored files a tier reads, such as a Worker's `.dev.vars` (#209). Their content is hashed into the key the gate daemon and verify's own green record are kept under, so a changed file runs the tier again and an unchanged one is answered from the green. A path outside the repo is refused. The gate report records that key as `inputs`, and the plugin's Stop hook computes it too: it reads a green report, a judged tree or a daemon's green only while the `keyEnv` values and `keyFiles` contents are the ones it was run under, and hands the daemon the key verify asks under, so a changed `.dev.vars` runs the fast tier again at the next turn end.
- e0d7589: A per-workspace step may name a turbo task (`"turbo": "typecheck"`), and in a repo with a `turbo.json` and turbo installed it runs as one `turbo run <task> --continue --concurrency=<jobs> --filter=./<workspace> …` over the affected workspaces, in place of one command per workspace (#215, plan 049 T1). Local verify and a CI job running `turbo run` then read one definition of green, and turbo's cache answers the workspaces whose inputs did not change. `"force": true` adds `--force` for a task whose green may never be replayed from the cache. Without turbo, the step's own command runs per workspace as before, so every tier a repo declared keeps its shape, and a workspace whose `package.json` has no script for the task runs the step's own command too, since turbo would run nothing there: a tier reads the same verdict with turbo and without it. `prove` runs the task through turbo, forced and with `--env-mode=loose`, so the `PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false` it sets reaches pnpm in the copy; turbo 2.11.8's default strict env mode dropped it, and pnpm then refused to run over the copy's linked install.

### Patch Changes

- e0f0882: New `@geonosis/gate`: the heavy slots, the machine-wide heavy lock and the `geonosis-gate` daemon, moved out of `@geonosis/ratchet` with their behaviour and tests unchanged (#215, plan 049 T5). It joins the fixed group; `geonosis-gate serve`, `status` and `stop` are its bin now, and a repo that starts the daemon adds `@geonosis/gate` by name.
  
  `@geonosis/ratchet` no longer ships the `geonosis-gate` bin and re-exports none of what moved. Import these from `@geonosis/gate` instead: `@geonosis/ratchet: acquireExclusive`, `@geonosis/ratchet: availableMemoryMb`, `@geonosis/ratchet: buildLockPath`, `@geonosis/ratchet: DEFAULT_MEMORY_MB_PER_SLOT`, `@geonosis/ratchet: describeBudget`, `@geonosis/ratchet: describeHolders`, `@geonosis/ratchet: GATE_PARENT`, `@geonosis/ratchet: GATE_RUN`, `@geonosis/ratchet: GateAccepted`, `@geonosis/ratchet: GateAnswer`, `@geonosis/ratchet: gateCacheDir`, `@geonosis/ratchet: gateDaemonPid`, `@geonosis/ratchet: gateKey`, `@geonosis/ratchet: gateLastLine`, `@geonosis/ratchet: gateLogPath`, `@geonosis/ratchet: GateLost`, `@geonosis/ratchet: GateRefused`, `@geonosis/ratchet: GateRun`, `@geonosis/ratchet: gateRunsDir`, `@geonosis/ratchet: gateSocketPath`, `@geonosis/ratchet: gateStatus`, `@geonosis/ratchet: HEAVY_SLOT_HELD`, `@geonosis/ratchet: HeavyBudget`, `@geonosis/ratchet: heavyBudget`, `@geonosis/ratchet: heavyLockPath`, `@geonosis/ratchet: heavySlotDir`, `@geonosis/ratchet: HeavySlotHolder`, `@geonosis/ratchet: heavySlots`, `@geonosis/ratchet: HeavySlotTimeout`, `@geonosis/ratchet: Holder`, `@geonosis/ratchet: readCacheEntry`, `@geonosis/ratchet: runViaGate`, `@geonosis/ratchet: serveGate`, `@geonosis/ratchet: slotHolders`, `@geonosis/ratchet: slotInherited`, `@geonosis/ratchet: stopGate`, `@geonosis/ratchet: submitToGate`, `@geonosis/ratchet: takeSlot`, `@geonosis/ratchet: waitForBuild`, `@geonosis/ratchet: watchGateParent`.
  
  `@geonosis/verify`, `@geonosis/evals`, `@geonosis/rails` and `@geonosis/release` import them from `@geonosis/gate` and depend on it. The doctor's fixed group and the metapackage's by-name list name `@geonosis/gate`.
- 1e5cd61: The `lint-config` FAIL names the approval command a repo can run: the repo's own `node_modules/.bin/geonosis-verify` where it links one, else the `geonosis` door, `<runner> geonosis verify lint-config --accept-config-change` (or the linked `node_modules/.bin/geonosis`). It named `<runner> geonosis-verify`, which under pnpm is a command only where `@geonosis/verify` is a direct dependency (#68).
- c19e51a: A message that tells a person what to run names it through the `geonosis` door: the baseline write refusal and the ratchet's FAIL name `geonosis ratchet --raise …`, an unproven fall `geonosis ratchet --bank …`, a missing or stale recording `geonosis ratchet --record …`, the session doctor `geonosis doctor`, the Stop hook, `geonosis verify` and the doctor's `runner` check `geonosis verify prove`, the ledger's handoff check `geonosis ledger handoff`, its push guard `geonosis ledger land --attest-only`, a plan envelope's next step `geonosis ledger plan check`, the release's smoke and migrations messages `geonosis release …`, each tool's `--help` and `--print-config-shape` pointers `geonosis <tool> …`, the write guard on `.geonosis/` `geonosis verify fast`, and init and post-update's census line name `geonosis ratchet` and `geonosis verify` for the gates init writes. Init no longer asks the root to declare `@geonosis/ratchet` and `@geonosis/verify` for their bins: its gates call the door. Under pnpm a tool's own bin is a command only where its package is a direct dependency (#68).
- b505542: A tool run through the door prints its usage the way it was run: `geonosis ratchet --help` says `geonosis ratchet [--cwd <dir>] …`, not `geonosis-ratchet …`, and the same for every tool the door fronts. The door tells the tool in `GEONOSIS_DOOR`, naming the bin it started, so a tool that tool starts in turn keeps its own name. `@geonosis/base/node` exports `doorSpelled`, `doorEnvFor` and `DOOR_ENV`. A tool run by its own bin prints its own name, as before (#68).
- 20a815f: When `--affected` cannot narrow a batch by Vitest's import graph, it says it runs every test of the affected workspaces and names them, as `1 of 2 workspace(s), the affected ones (packages/a)`. It said "runs the tests whole" while a workspace that imports the deleted module without declaring a dependency on its workspace was not run.
- 87c07b1: A `keyFiles` path that is a directory, `.` among them, is refused with exit 2 and a message naming it. It crashed the tier with an uncaught `EISDIR`. A path that is absent is still read as absent.
- 838fa52: A red step whose log file name would pass 200 bytes, as for a workspace path of about 250 characters, keeps its log under the name cut short with a hash of the whole step id, and the run prints `full output:` for it. The write failed on the name's length, and the run kept no log and said nothing.
- Updated dependencies [1830b16]
- Updated dependencies [cd20fc3]
- Updated dependencies [415b7f5]
- Updated dependencies [6552c12]
- Updated dependencies [91e682b]
- Updated dependencies [fcaa6ab]
- Updated dependencies [5ecf548]
- Updated dependencies [e0f0882]
- Updated dependencies [1dabee8]
- Updated dependencies [c19e51a]
- Updated dependencies [5f03fe8]
- Updated dependencies [1974fe6]
- Updated dependencies [957ee47]
- Updated dependencies [74147f7]
- Updated dependencies [8eda0d0]
- Updated dependencies [b505542]
- Updated dependencies [281381c]
- Updated dependencies [cc48e0e]
- Updated dependencies [7492312]
  - @geonosis/base@3.7.0
  - @geonosis/ratchet@3.7.0
  - @geonosis/gate@3.7.0

## 3.6.1

### Patch Changes

- ecf5c52: The budget line says what the tier could run at once. A tier of plain steps prints `3 steps ran one after another; steps in a { "parallel": [...] } group, or one step per workspace, run up to 7 at a time`. A tier with a group prints `up to 2 steps at once, of 7 slots`. Before, every tier printed `7 heavy slot(s) … --jobs 7`, which read as seven at a time on a tier that ran serially. One group took a 13-step fast tier from 30.3 s to 14.6 s (#204).
- 71be23b: The `geonosis-gate` daemon keeps only green reports. A red can come of the machine's load, an ignored file or the environment, so once the run that made it is over, the next verify runs the tier again. Before, the red was answered from the cache until `--fresh`. A cached green is answered with when and at what load it was made: `answered from the green kept at … (3 min ago, load 4.1)`. The tree a run is keyed by now includes the install record npm, pnpm or yarn writes under `node_modules`. A repo names the environment variables a tier's verdict depends on under `keyEnv` in `geonosis.json`, and a change in their values runs the tier again, through the daemon and through verify's own green record (#209).
- a852cb7: A batch step's known-green files are all run again when any non-test file in no workspace changed: a lockfile, a runner config, a patch, a path the step names in `also`, a document a test reads. Before, only a fixed list of root files did this, so a change under `also` or to `package-lock.json`, `yarn.lock` or `vitest.config.mts` left the tests that read it passed as `--exclude`, and the step went green over a failing test. The verdict line now counts the files it did not run, `(2 test files known green, not run)` (#189).
  
  `--affected` runs every workspace when `package-lock.json`, `yarn.lock`, `bunfig.toml`, `.npmrc`, a `vitest.config` or `vitest.workspace` of any extension, any root `tsconfig.*.json` or a file under `patches/` changed. Before, only pnpm's and bun's lockfiles and `vitest.config.ts` counted, so an npm or yarn repo's lockfile change ran no workspace.
- Updated dependencies [71be23b]
- Updated dependencies [ac7f890]
  - @geonosis/ratchet@3.6.1
  - @geonosis/base@3.6.1

## 3.6.0

### Minor Changes

- 7e26fd0: A `batch` step with `counts: "vitest-json"` keeps the test files it knows green, from green runs and red runs alike, and passes them to the runner as `--exclude` on the next run. A red run followed by a fix runs the files that failed and what the fix reached, instead of the whole step. A file stops being known green when it changed, when a non-test file in its workspace or in a workspace it depends on changed, or when a root input changed. `--fresh` runs every file.

### Patch Changes

- 630d44c: `geonosis-evals run` takes `--jobs <n>`: the sessions of one round run that many at once, default the machine's heavy budget, and are scored in the order the set names them. Rounds still run one after another, so the cost ceiling is checked between them. A 3.5.0 armed run took 55 minutes one session at a time. `@geonosis/base` exports `pooled`, the bounded pool `geonosis-verify` used for its parallel groups, now shared by both.
- e65a3b0: Every temp directory a kit package makes is held by the process that made it (`<dir>.holder`, the process id), through `heldTempDir` from `@geonosis/base/node`. The holder goes when that process exits. Before, a sweep of unheld `geonosis-*` directories, run by any test run on the machine, could delete a live one. An evals session waiting on its model holds no file open, and lost its workdir that way 11 minutes into a scored run.
- Updated dependencies [630d44c]
- Updated dependencies [e65a3b0]
- Updated dependencies [adb7c4d]
- Updated dependencies [f275d25]
  - @geonosis/base@3.6.0
  - @geonosis/ratchet@3.6.0

## 3.5.0

### Minor Changes

- 2cc5f78: A red step that failed on the network is marked as an environment failure (#121). DNS (`getaddrinfo ENOTFOUND`, `EAI_AGAIN`), `ECONNRESET`, `ETIMEDOUT`, an unreachable network, and wrangler's "Unable to resolve Cloudflare's API hostname" all count. The step stays FAIL, the line under it says "an environment failure, not a code fault — re-run it", and the gate report carries the line that named it as `environment`. A local server that never started (`ECONNREFUSED`) is not one.
  
  The Stop hook does not block on a red that is only the network: it runs the tier again. A red it does block on names the run it came from: when it started, how long ago, and over which commit. While a newer heavy gate is running in the same repository, an older red report is not replayed as current.
- 2cc5f78: A tier runs once per tree. A tier already green on this tree with the same flags (`--affected`, `--require-work`) is answered from its green record in milliseconds, naming the tree and when it went green, and runs nothing. `--fresh` runs it again. The green record now stores the flags it ran with, and `greenOnThisTree(cwd, tier, asked?)` can match them. Before, the record existed but only a gate daemon consulted one, so a Stop hook or an agent re-proved an unchanged tree at full cost every time. Through a gate daemon, `--fresh` reaches the run the daemon starts, so it runs the tier instead of being answered from the record.
- 2cc5f78: `geonosis-verify <tier> --retry-environment-failures <n>` re-runs a step up to `n` times when its failure is only the network: DNS, a reset or a timeout, as #121 classifies it (#125). A red that is the code is never re-run. Each re-run is named in the run's output and recorded in the report as `retried: { after, times }`. A release script whose verify calls a third-party API can take it, so a DNS blip does not abort a release before anything deploys. It defaults to 0 and passes through the gate daemon.

### Patch Changes

- 2cc5f78: `oxc-parser` is accepted at `^0.154.0` beside `^0.152.0 || ^0.153.0`.
- 2cc5f78: When a run changes the tree it ran on, verify names the step and the files: `green not recorded: fast/10 changed scripts/gate-baseline.identity.json while it ran` (#130). Before, it said only "a step changed the tree". Verify watches the tree after each group of steps, and `@geonosis/ratchet` exports `treeFilesOf` and `filesThatMoved` beside `treeHashOf` to name the files.
- 2cc5f78: `geonosis-verify prove` takes `--jobs` as a tier does (#129); it refused the flag before. `--help` and the README name the whole order behind the default: `GEONOSIS_JOBS`, `GEONOSIS_HEAVY_SLOTS`, then `geonosis.json → caps.heavy`, then cores and memory. `caps.heavy` is the one key that caps every entry point: a tier, `prove`, the Stop hook and the `GEONOSIS_SLOTS` a step is handed. It went unfound because the help left it out.
- 2cc5f78: `--require-work` counts a source file a test reaches by naming the file it is built to (`resolve(ROOT, 'dist/stub.js')`), as it already did for an import of a `dist/` path. A stub a bin harness names by its built path was reported as reached by no test.
- Updated dependencies [2cc5f78]
- Updated dependencies [b01150f]
- Updated dependencies [2cc5f78]
- Updated dependencies [2cc5f78]
- Updated dependencies [2cc5f78]
- Updated dependencies [2cc5f78]
  - @geonosis/base@3.5.0
  - @geonosis/ratchet@3.5.0

## 3.4.1

### Patch Changes

- @geonosis/base@3.4.1
  - @geonosis/ratchet@3.4.1

## 3.4.0

### Minor Changes

- 584cee6: `geonosis-verify <tier> --affected` diffs against a mark of that tier's own. The `fast` tier keeps
  `refs/geonosis/verified`; every other tier keeps `refs/geonosis/verified-<tier>`, moved only by a
  green run of that tier and only when no lint input differs from HEAD. With no mark a tier other than
  `fast` runs every workspace, and its first green run sets the mark. `GEONOSIS_BASE` is the base of
  the tier being run.
- 8581bd8: A tier that goes green records the hash of the tree it ran on in `.geonosis/green-<tier>.json`, and
  `greenOnThisTree(root, tier)` answers that record only while the tree still hashes the same. A red
  run, a run limited by `--since`, and a run whose steps changed the tree write no record.
- ab2f233: `HEAVY_SLOT_HELD` (`@geonosis/ratchet`) and `BASE_ENV` and `SLOTS_ENV` (`@geonosis/verify`) name the
  variables a verify tier hands its steps (`GEONOSIS_HEAVY_SLOT_HELD`, `GEONOSIS_BASE`,
  `GEONOSIS_SLOTS`), so a repo whose test run must not inherit them can list them from one place.
- 344816b: `--affected` no longer reads a version cut as a change to every workspace. A `package.json` whose
  only difference from the base is `version`, any `CHANGELOG.md` or `GROUP-CHANGELOG.md`, and each file listed under
  `versionFiles` in `geonosis.json` whose only difference is a version number select nothing. A
  dependency range or script that moved beside the version still selects its workspace.

### Patch Changes

- 22981ae: A kit bin starts on bun when bun is on PATH and falls back to Node when it is not (#52). An npm
  repo with no bun used to get `env: bun: No such file or directory` and exit 127 from a bin that
  needs only the Node its `engines` declares. The shim is now a POSIX shell line ahead of the same
  JavaScript: it `exec`s bun when it finds one, otherwise it says once on stderr that it runs on Node
  and how to get bun, and `exec`s Node. Under `npx`, `npm exec`, `pnpm exec`, `node_modules/.bin` and a
  direct path it adds about 3 ms to a start on bun. Windows stays a non-goal. The doctor's `tool runtime`
  warning no longer says a machine without bun cannot start the bins.
- Updated dependencies [ebb216e]
- Updated dependencies [22981ae]
- Updated dependencies [053eb94]
- Updated dependencies [7492bfa]
- Updated dependencies [13e1386]
- Updated dependencies [77f9a0a]
- Updated dependencies [f328b39]
- Updated dependencies [ab2f233]
- Updated dependencies [4086919]
  - @geonosis/base@3.4.0
  - @geonosis/ratchet@3.4.0

## 3.3.0

### Patch Changes

- fb08b28: Every git path the kit reads is now relative to the root, and limited to it, when `geonosis.json` sits in a subdirectory of the repository (#69). `git diff` and `diff-tree` print paths from the repository's top level and `git status --porcelain` always does, while `ls-files` and every configured path are relative to the root, so in a monorepo app: `release migrations` read no committed or staged migration and passed (`NONE`, exit 0) over SQL a release then applied; `verify --affected` mapped no change to a workspace; and the ledger's tick, shape, land, drift and handoff compared paths that could never match. Diffs now pass `--relative`, porcelain paths are mapped through `rev-parse --show-prefix`, and `git show` reads `rev:./path`.
- fb08b28: The published manifests no longer declare `engines.bun`, which yarn 1 warned about once per installed package (`The engine "bun" appears to be invalid`). A package that starts its bins on bun now names it in `devEngines.runtime`, the same list the kit's root declares; measured on yarn 1.22.22, npm 11.13.0, pnpm 11.8.0 and 12.10.1 and bun 1.4.2, none of them warns on that field in a dependency. A test over every published manifest holds that `engines` has only keys yarn 1 knows and that the list equals the root's, and a yarn 1 install of those manifests prints no engine line (#46).
- fb08b28: `geonosis update` under a release-age cooldown (pnpm `minimumReleaseAge`, bun `minimumReleaseAge`) checks the third-party packages the kit's packages declare, once the kit's own packages are exempt or the manager's override is in `--install-args`. It stops with exit 2, before writing, and lists each package whose newest release satisfying the declared range is younger than the age: under an exemption that means no old enough release satisfies the range, under an override it means the newest one is young, because the override lifts the age for every package. The README's "Under a release-age cooldown" holds the exemption each manager has, measured: pnpm `minimumReleaseAgeExclude` takes `'geonosis'` and `'@geonosis/*'`, bun `minimumReleaseAgeExcludes` takes exact names and ignores a glob, npm has no per-package exemption. The kit's cut holds what it publishes to the same age: `release:check` refuses a range in a publishable manifest (`dependencies`, `optionalDependencies`, required `peerDependencies`) that no release at least `release.thirdPartyAge` days old (default 7) satisfies, and names the range that would. The published floors of `oxc-parser`, `pixelmatch`, `@modelcontextprotocol/sdk` and six `@radix-ui` packages are lowered to a release that old, with the lockfile still resolving the latest. The doctor's version reader now reads a partial version (`>=19`, `^1`) and an `||` list of such ranges (#67).
- fb08b28: A step whose command starts with `pnpm exec <bin>`, `npx <bin>` or `bunx <bin>` runs `node_modules/.bin/<bin>` when it exists, and every step runs with `pnpm_config_verify_deps_before_run=error` unless the environment or `pnpm-workspace.yaml` already sets it, so a gate step does not install into `node_modules` while it measures it (#57). The command `lint-config` prints for approval names the local `geonosis-verify` when there is one.
- fb08b28: `geonosis-verify` says what `(unproven)` means: a run that prints it adds one legend line under the step list naming the `geonosis.json` path of each such step (`verify.fast[4].proves`) to declare `proves: { plant, expect }` at. A red step whose output carries a runner timeout (vitest, jest or bun message) records the load average and core count in the report and prints `timed out at load average X on N cores` under the FAIL; the verdict stays FAIL. A command naming `{report}` outside a counting batch is refused at load instead of writing a file called `{report}`.
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
- Updated dependencies [fb08b28]
  - @geonosis/base@3.3.0
  - @geonosis/ratchet@3.3.0

## 3.2.0

### Minor Changes

- dd5f57a: REFUSAL-IN-MINOR: `geonosis-verify` refuses a step, a `proves` or a parallel group in `geonosis.json` that carries a key nothing reads, naming the key and the keys a step takes. It dropped the key and loaded the step without it, so a step declared `provse` ran with no proof and said nothing. Rename or delete the key. `unknownKeyOf(block, known)` is exported from `@geonosis/ratchet`, the one test of "a key this reader does not know" that `geonosis-release` now uses too (#728).
- deef511: `geonosis-gate serve | status | stop` (`@geonosis/ratchet`): one daemon per machine that runs a tier once for every `geonosis-verify` over the same tree and flags, answers the report it kept to a verify over a tree it already read, and queues the heavy slots first-come within the budget. The budget reads `GEONOSIS_HEAVY_SLOTS`, then the new `geonosis.json → caps.heavy`, then the machine. `geonosis-verify --fresh` runs again whatever the daemon holds; `--exclusive` never asks it. `takeSlot` leases its slot through the daemon and uses the slot files when none runs, and the Stop hook reads a kept green for the tree and hands `fast` to the daemon. REFUSAL-IN-MINOR: `geonosis-verify --prove` now also plants a failure inside a parallel group and fails a runner that loses it or runs the steps after it, and `loadCaps` refuses a `caps.heavy` that is not a whole number of at least 1.
- deef511: A batch can report a count per workspace, and a prove says how large its copy was.
  A per-workspace step with a `batch` takes `counts: "vitest-json"`: its command names `{report}`, the gate runs it with a file there, and the step in the report carries `workspaces`, one `{ workspace, files, passed, failed, skipped }` for every workspace of the batch (a workspace that ran no test is named with zeros, and a file no workspace holds is kept apart as `(outside every workspace)`). A run that left no report it could read fails the step and says so. `also` names paths that always ride with the batch, so a tier runs the whole unit class in one runner process, `plugin/` and `tooling/` included, and the batch is no longer skipped when no workspace is affected. `geonosis-verify` prints the totals under the step and each workspace that failed.
  `geonosis-verify prove` runs each path a plant reached alone, and calls the step proven only if every one of them goes red with the evidence the plant promised; it replaces `{report}` with a path inside the copy, and each result carries `copyBytes`, printed as `(copy 12.3 MB)`. New exports: `vitestCounts`, `WorkspaceCount`, `bytesIn`. Every step now sees `GEONOSIS_SLOTS`, the heavy slots the run was budgeted, so a command can pass the machine's number to the tool it runs (`--concurrency=${GEONOSIS_SLOTS:-2}`).
- dd5f57a: A per-workspace step can leave workspaces out (`exclude`: paths or globs, refused when one matches no workspace), a step can run `per: "shard"` with `shards` pieces (`{shard}` and `{shards}` in the command), and `prove:steps` looks twice at a plant that went red without its evidence: on a fresh copy with the plant put back, proven when the second red carries it, NOT PROVEN with both tails otherwise.
  
  A tier now waits while a live build holds the tree (`.geonosis/build.lock`) instead of reading `dist/` while it is rewritten, and gives up with the holder named after `GEONOSIS_HEAVY_WAIT_MS`. `@geonosis/ratchet` exports `buildLockPath` and `waitForBuild` for it.
- 67aefe3: `geonosis-verify lint-config` holds the lint a tier runs against the same command at `refs/geonosis/verified`: a rule whose effective level drops or that is gone, a plugin gone, a changed option, a target no longer linted, anything wrapped around `oxlint`, and a diagnostic the mark raised that the tree no longer raises each FAIL, naming what changed. A new rule at error, a raised level and a removed ignore pass. `--accept-config-change` records the person's approval in `.geonosis/lint-config-approval.json`; the geonosis plugin asks for it at Claude Code's permission prompt, listing the changes in plain words.
- 67aefe3: A verify step may declare `"history": true`: its `prove` copy is made a repository whose one commit is the copy, with `refs/geonosis/verified` at it, and runs with the host's `GIT_` redirects and git config out of the way, so a step that compares against the mark can be watched failing. `@geonosis/ratchet` exports the isolated git runner (`isolatedGit`, `withoutGitRedirects`, `GIT_IDENTITY`) that `@geonosis/release`'s smoke already used and now imports from there.

### Patch Changes

- dd5f57a: A smoke phase whose command the shell did not run (exit 127, not found, or 126, not executable) reads `NOT MEASURED` instead of "already failing at their versions"; exit 2 stays a result, because `tsc` exits 2 on a type error. `geonosis-verify` counts such a step in the envelope's `refused`, with the exit and the command, instead of reading it as a step it ran (#308).
- f5632b7: New `@geonosis/base`: the zero-dependency bottom of the method, holding what packages used to copy. `@geonosis/base` is `isRecord`, `isStringList`, `stringsOf`, the error text helpers and the markdown readers (`headingsOf`, `sectionUnder`, `fencedLines`, `titleOf`), safe in a browser or a Worker; `@geonosis/base/node` is `packageRootFrom`, `versionOf` and `isDirectory`; `@geonosis/base/bin-harness` is `binRunner(runtime)` for running a package's bin in a test, with `GIT_ISOLATED` for a bin that shells to git.
  `cli`, `create-geonosis`, `ledger`, `mcp`, `observability`, `rails`, `release`, `review`, `themekit`, `verify`, `verify-arch` and `walk` import the guards from it instead of carrying their own; their behaviour is unchanged and each now installs `@geonosis/base`, which a consumer never declares.
  The ratchet keeps its own copies: the build runs on its source before any package exists, so nothing it imports may be a package the build produces.
- 5404b2a: Dependencies move to their newest release the install policy accepts: `@modelcontextprotocol/sdk` 1.32.1 (`@geonosis/mcp`), `oxc-parser` 0.153.0 (`@geonosis/ratchet`, `@geonosis/verify`), and the `squawk-cli` 2.68.0 that `@geonosis/release` and `geonosis` ask for as an exact optional peer, so the install line a refusal prints is `squawk-cli@2.68.0`. A scaffold written by `create-geonosis` pins `oxlint` ^1.87.0, `oxfmt` ^0.72.0, `vitest` ^5.0.3 and `@types/node` ^26.6.4.
- 58eaf3a: `geonosis-ratchet`, `geonosis-verify` and `geonosis-lint-parity` end with `process.exitCode` instead of `process.exit()`, so a report longer than a pipe holds (a `--json` run over a few thousand findings or counters) reaches the reader whole. `process.exit()` after a write past 64 KiB cut the report at the pipe's size, which for `--json` is a document that no longer parses.
- 58eaf3a: A `geonosis.json` that does not parse is refused with the file named, where `geonosis-verify` printed the bare JSON syntax error.
- 58eaf3a: `--affected` and `--require-work` now name a changed file whose path git quotes (a space, an accent) as the file it is, so a change to `my file.ts` no longer reads as a change outside every workspace.
- 58eaf3a: `--affected` and per-workspace steps name the workspace manifest that does not parse, where the run refused with a bare JSON syntax error.
- 58eaf3a: `--require-work` no longer asks a test of a file that only moved: a changed file whose bytes equal a file the base held at a path that is gone now is not judged. A moved file with any byte changed still is.
- Updated dependencies [deef511]
- Updated dependencies [dd5f57a]
- Updated dependencies [f5632b7]
- Updated dependencies [a4a7bba]
- Updated dependencies [5404b2a]
- Updated dependencies [58eaf3a]
- Updated dependencies [58eaf3a]
- Updated dependencies [a4a7bba]
- Updated dependencies [58eaf3a]
- Updated dependencies [58eaf3a]
- Updated dependencies [58eaf3a]
- Updated dependencies [58eaf3a]
- Updated dependencies [58eaf3a]
- Updated dependencies [58eaf3a]
- Updated dependencies [58eaf3a]
- Updated dependencies [dd5f57a]
- Updated dependencies [deef511]
- Updated dependencies [58eaf3a]
- Updated dependencies [deef511]
- Updated dependencies [deef511]
- Updated dependencies [dd5f57a]
- Updated dependencies [67aefe3]
  - @geonosis/ratchet@3.2.0
  - @geonosis/base@3.2.0

## 3.1.0

### Patch Changes

- Updated dependencies [bd82a2e]
  - @geonosis/ratchet@3.1.0

## 3.0.0

### Major Changes

- 59c575f: `geonosis-verify prove` proves a tier's steps in parallel, each in its own copy, up to the heavy budget (`--jobs` and `GEONOSIS_JOBS` as for a tier run), and reports them in order. A step run in the copy never installs: pnpm 12 checks dependencies before `run` and `exec` and, over a copy whose timestamps match the original, went to install into the copy's linked `node_modules` and failed, so the proof of a pnpm-run step depended on a cache's timing. A batched step's plant is proved by the batch over the workspaces it landed in. `proveTier` is async and takes `jobs`; `proveRunner` is the runner a copy's steps use.

### Patch Changes

- 1e38e4e: `geonosis-themekit`, `geonosis-rails` and `geonosis-evals` read their arguments with `node:util`'s `parseArgs`. A flag one does not know is refused by name, exit 2, where the hand-written readers ignored it and ran without it; a flag given twice keeps its last value in all three (evals kept the first). `geonosis-evals compare --tolerance x=0.1 a.json b.json` reads `a.json` and `b.json` as the scores: the old reader took the tolerance's value for the first score file. `geonosis-ratchet` and `geonosis-verify`, which already refused what they did not know, read their flags the same way; verify still refuses a flag that belongs to another of its commands. `geonosis-doctor` reads its flags with `parseArgs` too, its optional `--baseline-against` ref through the parser's tokens. `@geonosis/ledger`'s exported `parseArgs(argv, valued, switches)` is `node:util`'s underneath and takes the switches it accepts, refusing an undeclared one and a valued flag followed by another flag (`--note --plan 3` used to store `--plan` as the note); `rejectUnknownSwitches` is gone. `geonosis-walk`, `geonosis-review` and `geonosis-lint-parity` parse the same way; parity keeps its own words for a bare `--corpus` or `--expect-changed`. `geonosis-observability`, `geonosis-verify-arch`, `geonosis-testbed` and `geonosis-visual-diff` follow; the last two used to accept a flag they did not know (testbed ignored it, visual-diff read it as a switch) and now refuse it. `@geonosis/release`'s exported `parseArgs` keeps its `Parsed` shape over `node:util`'s. `@geonosis/cli`'s `parseArgv` and `create-geonosis`'s `parseArgs` are built on it too, keeping their own refusals; `parseArgv` now refuses a valued flag followed by another flag instead of taking that flag as its value.
- 30ae83d: `geonosis-verify prove` gives its copy of the tree a real `node_modules` whose entries link to the installed ones. pnpm 12 refuses to run a task in a tree whose `node_modules` is itself a link, so every `pnpm --filter` step went red without its evidence.
- 7fda2db: `--require-work` reads which tests reach a changed file with `oxc-parser`, a dependency of its own, instead of asking the repo for `typescript`'s compiler API, which TypeScript 7 no longer ships. The optional `typescript` peer is gone.
- Updated dependencies [0f272e2]
- Updated dependencies [1e38e4e]
- Updated dependencies [5560282]
- Updated dependencies [feb9983]
- Updated dependencies [559c117]
- Updated dependencies [7d381dd]
- Updated dependencies [9b91c6a]
- Updated dependencies [a6006d7]
- Updated dependencies [53c995b]
- Updated dependencies [670f7c1]
  - @geonosis/ratchet@3.0.0

## 2.13.0

### Minor Changes

- f422aea: A `per: "workspace"` step may declare `batch`, the command that runs once with every affected workspace in `{workspaces}`. A tree whose diff touches many workspaces pays one process instead of one per workspace, and `prove` exercises the batch. In this repo a gate across every workspace went from 386 s of step time to 130 s: unit tests take 17 s in one vitest instead of 209 s in 39.

### Patch Changes

- f422aea: Both now ship their LICENSE in the tarball, as every other package already did.
- f422aea: `geonosis-verify prove` takes seconds where it took minutes.
  - Each planted copy of the tree links the installed root `node_modules` instead of copying it, and leaves turbo's `.turbo` cache behind; those two were 25 of every copy's 30 seconds.
  - A step that runs once per workspace runs the workspace the plant landed in first. It used to reach it only after typechecking, linting or testing every workspace ahead of it.
  
  On this tree, `prove fast` went from 408 s to 38 s, still proving the same six steps.
- Updated dependencies [f422aea]
- Updated dependencies [f422aea]
- Updated dependencies [f422aea]
- Updated dependencies [ae995df]
  - @geonosis/ratchet@2.13.0

## 2.12.0

### Patch Changes

- Updated dependencies [7c3879a]
  - @geonosis/ratchet@2.12.0

## 2.11.1

### Patch Changes

- 08611c4: Every child whose output a tool captures is read whole, where Node's default silently cut it at 1 MiB and killed the child.
  
  - **Real bug:** a gate step printing more than 1 MiB read as failed to `geonosis-verify`.
  - **At risk:** the smoke's `pnpm pack --json`, `discover`'s oxlint run, and the git, squawk and oxfmt reads could hit the same cap.
  
  `--require-work` also counts a file that a test runs through a helper naming its bin, not only through the test itself.
- Updated dependencies [e67fc1b]
  - @geonosis/ratchet@2.11.1

## 2.11.0

### Minor Changes

- 0f99e26: `--affected` diffs against the mark this repo advances, not against `main`.
  
  `--since` defaulted to `main`, so a run ON main took a merge-base of HEAD and read every committed
  change as already in the base: the affected set came back empty and every per-workspace step was
  recorded skipped. The default is now the commit `refs/geonosis/verified` marks — the same base the
  steps already receive as `GEONOSIS_BASE` — falling back to `main` when nothing has been marked yet.
  One base for the tier: the workspaces it expands over and the tests a runner selects answer to the
  same commit. `--since <ref>` still wins when it is given, and a tree where no base can be read still
  runs every workspace.
- 0f99e26: `--require-work` asks whether code went unchecked, not whether the runner idled.
  
  Every runner step exiting 0 having run nothing failed the tier on its own, which made a docs-only
  commit red and a re-run over an unchanged tree red — neither had skipped anything. The flag now
  reads the diff since the baseline the steps were given: a code file (`.ts`, `.tsx`, `.js` and their
  siblings; a `.d.ts` belongs to the typecheck step) that came with no test file of its own fails the
  tier, and the line names every such file. A diff of docs, changesets or data passes, a tree already
  at the mark says `nothing to do`, and a tree no diff can be read from still fails, since nothing
  there can say what was skipped.
- a9eb439: `--require-work` judges each changed code file by whether a test reaches it — by import, through
  the files that import reaches, or by naming a script it runs (a bin reaches its `src/` through
  `dist/`). A test of one file no longer excuses another, and a runner that worked no longer hides a
  file nothing tests; every test in the tree counts, touched or not. A file with no statement that
  runs, tool configuration, fixtures and declarations ask for none. This refuses diffs it passed
  before: expect a red naming each untested file you touch. Reads the repo's `typescript`, now a peer.
- 0f99e26: A green run marks the commit it verified, and every step is told which one.
  
  `refs/geonosis/verified` moves to HEAD after a tier goes green, and each step now runs with
  `GEONOSIS_BASE` set to the merge-base of HEAD and that mark — unset until a run has been verified
  here. LAW 7 forbids a push, so `origin/main` never advances and a selection made against it grows
  with every commit until it is the whole suite; a mark this repo advances is the only baseline that
  tracks "the tree was last known good here".
  
  Whether an empty selection is a red is not the runner's question to answer. `--require-work`
  decides it from the diff that selection was made against — see the changeset beside this one.

### Patch Changes

- 0f99e26: Every bin's stale-build guard now sees the workspace dependencies its build read. A `dist`
  current against its own `src` could still have been built against an older dependency's `dist` —
  literally, where tsup inlined a workspace devDependency into it — and a dependency whose own `src`
  had moved past its `dist` was read stale by every bin above it; the guard answered "current" for
  both. Now each shim holds every `workspace:` dependency, transitively, to the same reading as its
  own package (never built, stale against its src, current), and refuses when this `dist` is older
  than a dependency's — by content, so a dependency rebuilt to the same bytes with
  `--force --filter` is the same build and does not poison the tree. Unknown is still stale: the
  first run over a tree where a dependency was rebuilt alone, before any record, refuses once and
  names `--force --filter=<this package>`. Only a checkout with `src/` beside `dist/` sees any of
  this; the published package ships no `src/` and the guard is skipped. Measured: within noise for a
  bin with one dependency, +8 ms for `geonosis`, which walks fourteen.
- 0f99e26: Every bin's stale-build guard decides, and never throws. The digest guard's refusal read
  `package.json` to name `pnpm build --force --filter=<name>`; over a checkout with no manifest
  beside the bin it ended with exit 1 and a `node:fs` frame instead of exit 2 and a sentence. The
  name is now a courtesy the refusal does without. The contract, stated: a source newer by mtime whose
  content the bin saw beside a current dist is not stale; one whose content differs is, and the
  refusal names the fix; one the bin has no record for is stale — unknown is stale; a missing dist is
  "never built". Each is exit 2 with a sentence. Only a checkout with `src/` beside `dist/` sees any
  of this; the published package ships no `src/` and the guard is skipped.
- 0f99e26: `geonosis-verify`: a red build step whose output says a workspace sibling has no declaration file
  (`TS7016 … '@scope/dep'. '…/dep/dist/index.js'`), or that a file under its own `dist` vanished
  while it was writing declarations (`ENOENT … unlink '…/dist/x.d.cts'`), now opens its digest with
  a line naming the cause: another build was running in this tree, nothing in the code is wrong, run
  `pnpm build` again alone. Measured: one turbo at a time, 25 of 25 green; two in the same tree, 3
  pairs of 5 red with exactly those lines. Before, the red named a type in a file nobody touched, and
  the stale `dist` it left made the next typecheck report an export that was never missing. A
  dependency under `node_modules` that ships no types is still a plain type error.
- 0f99e26: The kit names no consumer. Docs, doc comments and messages describe the repos a pattern was measured in by their stack (a Medusa storefront, a Workers + D1 app, a Mercur v1 marketplace). Example configs are named the same way: `examples/medusa-storefront.*.json`, `examples/workers-app.*.json`. No behaviour changes.
- 0f99e26: Every order a tool writes, compares or applies — generated files, migration order, listings, cursors — sorts with the `en-US` collation, named rather than inherited from the machine. On an `en-US` machine nothing moves; on one set to another language (Estonian sorts `z` before `t`) the output is now the same one.
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [c84e4a0]
- Updated dependencies [7db8a78]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
- Updated dependencies [ad81ece]
- Updated dependencies [0f99e26]
- Updated dependencies [0f99e26]
  - @geonosis/ratchet@2.11.0

## 2.10.0

### Patch Changes

- @geonosis/ratchet@2.10.0

## 2.9.0

### Patch Changes

- @geonosis/ratchet@2.9.0

## 2.8.0

### Patch Changes

- @geonosis/ratchet@2.8.0

## 2.7.0

### Minor Changes

- d90986b: `geonosis-verify` tells a step that RAN NOTHING from one that passed. A step that exits 0 saying so
  — turbo's "No tasks were executed as part of this run" / "Running x in 0 packages", vitest's "No
  test files found", bun's "Ran 0 tests", or any command that prints `SKIP …` on a line of its own —
  is printed `idle` instead of `ok`, carries `idle: true` in the gate report, and the tier's verdict
  ends `(N step(s) ran nothing: …)`. A step may declare its own `idle` pattern (a regex source).
  `--require-work` fails the tier when every step marked `runner` idled — the honest answer for a CI
  job or a push gate. Measured cause: during.day's fast tier runs turbo `--affected` inside its
  commands, so on a clean tree every suite step exited 0 having run nothing, the runner said PASS,
  the Stop hook was satisfied, and `main` stayed red in two workerd suites for days.
  
  `geonosis sync --check` prints `SKIP — sync has nothing to generate here: …` where it used to say
  "nothing for this command to generate here", so the verify runner reads it as idle.

### Patch Changes

- @geonosis/ratchet@2.7.0

## 2.6.1

### Patch Changes

- Updated dependencies [e1db50f]
- Updated dependencies [db89b0b]
  - @geonosis/ratchet@2.6.1

## 2.6.0

### Minor Changes

- 58565d7: Plan 029, the factory floor: the gate learns to run things at once, and the heavy budget stops
  being one laptop's number.
  
  `@geonosis/verify` — `TierStep` carries `group`, the number that says which steps run at once, so a
  tier can declare `{ "parallel": [ … ] }` groups and `{ "command": "… {workspace} …", "per":
  "workspace" }` steps. New: `affected.ts` (`affected`, `affectedOf`, `workspacesIn`, `changedSince`,
  `mergeBase`, `ROOT_SHAPED`, types `Affected` and `Workspace`), `PerWorkspace`, `ParallelStep`,
  `CommandStep` on `config`, and `planSteps`, `runGroups`, `shellRunAsync`, `PlannedStep`,
  `AsyncStepRun` on `run`. `GateReport` gains an optional `affected` block. `runSteps`, `shellRun` and
  `stepIds` are unchanged. The bin gains `--affected`, `--since <ref>` and `--jobs <n>`.
  
  `@geonosis/ratchet` and `@geonosis/release` — `heavySlots()` and `DEFAULT_HEAVY_SLOTS` beside
  `HEAVY_SLOTS`, which is no longer the constant 2: the budget is `GEONOSIS_HEAVY_SLOTS` or
  `availableParallelism() - 1`, read on every claim rather than at module load (D-073).
  
  `@geonosis/ledger` — `shape.ts`: `shapeCheck`, `readManifest`, `exportsOf`, `formatShape` and the
  types `BriefManifest`, `ShapeRefusal`, `ShapeOutcome` — the lock that refuses an engineer diff
  touching a locked test, renaming an export or adding a file the brief did not name (D-072). Not yet
  on the package's public entry: the CLI door lands with W115's dispatch unit.
  
  `geonosis` — the plugin's post-edit hook takes its formatter from `geonosis.json → stack.format`
  (D-070) and the stop hook reads the machine's heavy budget.
- 58565d7: The heavy budget is memory-aware (D-081, amending D-073). `heavyBudget()` — on `@geonosis/ratchet`'s door, copied into `@geonosis/release` and the plugin's Stop hook and held to one answer by a test — is the smaller of one less than the cores and the memory the machine can spend divided by what one run holds: `geonosis.json → stack.memoryMbPerSlot`, 512 MB when nothing is declared. `GEONOSIS_HEAVY_SLOTS` still wins outright; `GEONOSIS_HEAVY_MEMORY_MB` is the memory to spend instead of the machine's. `heavySlots()` is the budget's number read fresh, and `describeBudget()` names the number and both inputs. `geonosis-verify --jobs` defaults to it, its report carries it as `heavy`, and the run prints it; the kit's own vitest workers read it too. Measured on a 4-core, 3.4 GB box: three slots over 1.4 GB swapped; two do not.

### Patch Changes

- b7d8aae: `geonosis-verify prove` expands a `per: "workspace"` step before it plants. The command a
  per-workspace step declares is a TEMPLATE, and the prove ran it verbatim: `pnpm --filter
  ./{workspace} typecheck` matches no package and exits 0 — measured 2026-09-07 on this tree — so both
  steps the light gate is made of reported "this gate has never been watched failing" while nothing had
  been typechecked or linted at all. The plant lands in ONE workspace, so the expansions are run until
  one is watched going red carrying the evidence it promised. `pnpm prove:steps` on the kit went from
  1 proven / 2 NOT PROVEN to 3 proven, the typecheck plant exiting 2 with `wall-probe.ts` and the lint
  plant exiting 1 with `no-debugger`. A gate that cannot be watched failing has not passed, and these
  two are the gate 029's factory floor introduced.
- Updated dependencies [58565d7]
- Updated dependencies [58565d7]
- Updated dependencies [58565d7]
  - @geonosis/ratchet@2.6.0

## 2.5.3

### Patch Changes

- @geonosis/ratchet@2.5.3

## 2.5.2

### Patch Changes

- @geonosis/ratchet@2.5.2

## 2.5.1

### Patch Changes

- 7f24da6: `prove:steps`' NOT PROVEN for a step that went red without the promised evidence now carries the
  step's own last lines (#394) — the only thing that names why the plant went red, which the
  report used to throw away.
- 7f24da6: A machine-wide heavy budget, two slots, held by every tool that spends one (#385): measured load 48
  with during.day's Stop-hook tier, this kit's Stop-hook tier, its ratchet and an engineer's suite all
  running at once — nothing held the two-slot budget across the two repos, and three kit tests read
  red under it that were green alone. `@geonosis/ratchet` ships `takeSlot`/`heavySlotDir` (a lock
  directory under `os.tmpdir()/geonosis-heavy`, two slot files each holding `{ pid, command, cwd,
  startedAt }`, a dead holder reaped on the next take, a taker that finds both held waiting once
  before refusing naming them); `geonosis-verify` (each tier run) and `geonosis-ratchet` (the run, not
  `--prove`'s inner unit) take a slot for the run's duration. `@geonosis/release`'s `smoke
  baseline`/`smoke run` take one too, from their own copy (this layer imports nothing, same posture as
  `envelope.ts`). The plugin's Stop hook reads a fresh green `gate-report.<tier>.json` — its own
  recorded commit equal to the current `HEAD` with a clean tree — and reports PASS without re-running;
  otherwise it peeks the same two slots (never claims one itself, or the verify child it is about to
  spawn would be racing against its own reservation) and blocks naming both holders rather than
  running a third tier.
- 7f24da6: The prove copy carries the tree, never the worktrees beside it (#384): `copyTree` left behind only
  `.geonosis` and `.git`, so the agent worktrees under `.claude/worktrees/*` — each with its own
  `node_modules` — were copied whole, and a worktree changing under the copy killed the run with
  `ENOENT: chmod` from `copyDir`. `.claude` now joins the never-carried set; a directory that owns a
  nested `.git` FILE (a git worktree's marker) is left behind wherever it sits; and the copy runs one
  `cpSync` per top-level entry, so an entry gone by its own turn is skipped rather than fatal.
- 7f24da6: The prove copy preserves every file's mtime (#395). Without it, src and dist inside the copy were
  stamped by copy order, every bin shim there read "dist is older than src", and the `pnpm test`
  and `pnpm typecheck` plants went red without the evidence they promised — a proof that failed for
  a reason the report could not name.
- Updated dependencies [7f24da6]
  - @geonosis/ratchet@2.5.1

## 2.5.0

### Patch Changes

- @geonosis/ratchet@2.5.0

## 2.4.3

### Patch Changes

- @geonosis/ratchet@2.4.3

## 2.4.2

### Patch Changes

- @geonosis/ratchet@2.4.2

## 2.4.1

### Patch Changes

- @geonosis/ratchet@2.4.1

## 2.4.0

### Patch Changes

- bef73fb: Every shim tells a tree that was never built from a build that fell behind. `newest(dist)` answers 0 for a directory that is not there and for one that is merely old, so both conditions were one comparison and one sentence — and the staleness sentence was printed over trees that had never been built at all. It blocked a session's turn in a fresh git worktree, which has no `dist` because `dist` is not committed, with a message about a build going stale. Only one of the two is evidence of a mistake, and the two send a reader to different places.
  
  The test that holds it plants BOTH cases and requires different sentences, which the previous one never did: it planted only a stale `dist`, so the check had never been shown a case whose answer should differ. Named for what it is, since the same gap produced two wrong published numbers the same day — a probe never shown a differing case has not been shown to discriminate, which is this repo's own "a gate never seen red has not been shown to measure", one layer down, applied to the thing doing the observing.
  
  (This changeset names four packages of the fixed group deliberately: the group versions as one number and a changeset's description lands only in the packages it names, so naming several puts it in more than one place a reader might look. See #356.)
- Updated dependencies [bef73fb]
- Updated dependencies [3b7b2ae]
- Updated dependencies [56e0f33]
  - @geonosis/ratchet@2.4.0

## 2.3.1

### Patch Changes

- 88d2e4a: The Stop hook's block names the checkout its tier ran in, and says when that is a linked worktree — an engineer's subagent runs its gate in its own worktree and the block is reported into its parent's transcript, where it is indistinguishable from the parent's own. A signal that crosses a session boundary carries its provenance, or the receiver cannot tell a neighbour's failure from their own.
- 0f6f7ef: The scratch sweep asks the kernel, not a pid: where a root's creator is dead it checks whether anything still holds a file beneath it before removing it, and counts a missing `lsof` or a timeout as in use. A creator's pid is a proxy for "somebody is using this", and an orphaned process reparented to init is exactly where the proxy and the fact disagree.
- @geonosis/ratchet@2.3.1

## 2.3.0

### Minor Changes

- 3dac9a7: **One `--since`, and a report a machine can read (#97).** `--since` names a plan number, a day and
  a git ref in three commands, and all three took any string: `plan check --since garbage` exempted
  nothing and printed the verdict it would have printed with no flag, `stats --since notadate` put
  every recorded day outside the window and printed `0 turns` as a pass rate. One reader now declares
  which kind each means and refuses an unreadable value by name. `geonosis-ratchet`,
  `geonosis-verify-arch` and `geonosis-lint-parity` take `--json`: the same report, on stdout,
  whatever the verdict — `--json` with an explicit `--format` is refused rather than resolved.

### Patch Changes

- 3dac9a7: **One documented spelling (#90).** Every README of a package the metapackage fronts now names
  `geonosis <tool>` — the door that pins the whole toolchain at one version — beside the bare bin, and
  a `tooling/` test holds it. `#230`'s stamp test lands with it: every flag `docs/orchestration.md`'s
  adoption loop names has to carry the version it first ships in, because the loop once told a
  consumer to run a flag the published version did not have.
- Updated dependencies [3dac9a7]
- Updated dependencies [3dac9a7]
- Updated dependencies [3dac9a7]
- Updated dependencies [3dac9a7]
- Updated dependencies [3dac9a7]
  - @geonosis/ratchet@2.3.0

## 2.2.0

### Minor Changes

- d15a654: **A test runner's failure evidence opens at the first failing TEST (#229).** A red step's `tail` is
  `failureEvidence`, which opened at the first error-bearing line — so a suite that logs an `Error:`
  on purpose owned the window and the forty lines closed above the eight `×` lines of the run's real
  failures. A step may now say `"runner": true` in `geonosis.json`, and a red runner's window opens at
  its first failing-test line, falling back to the error-bearing one when the run died before naming
  a test. The "this line names a failing test" reading lives in one place and the digest reads it too,
  so the two cannot disagree; the window also strips colour, as the green tail already did.
  `failureEvidence`'s second parameter is now `{ limit, runner }`.

### Patch Changes

- Updated dependencies [d15a654]
- Updated dependencies [d15a654]
- Updated dependencies [d15a654]
- Updated dependencies [d15a654]
  - @geonosis/ratchet@2.2.0

## 2.1.0

### Patch Changes

- @geonosis/ratchet@2.1.0

## 2.0.0

### Minor Changes

- 64e4e41: **A red step's report carries a FAILURE DIGEST beside its tail, and `land`'s refusal prints it first (#200).** The tail begins at the first error-bearing line, so a suite that prints an expected stack owns the evidence window long before the failing test says its name — twice on 2026-09-02 a land refusal quoted the observability suite's PostHog stub-500 dump and named no test, and the environment had to be reproduced by hand both times. `GateStep.digest` now holds the `×`/`FAIL` lines and the `Test Files`/`Tests` counts of the step's own output, and a refusal reads digest, then tail. Parsed from the output rather than read from a JSON reporter, because MEASURED (vitest 3.2.7, node 24.16.0): a run SIGKILLed mid-flight writes no `--outputFile` at all, while the `×` line of a test that already failed is in the captured stdout.
- a0f456c: **A verify step may declare how it is falsified (#65).** `{ "command": …, "proves": { "plant": …, "expect": … } }`: `geonosis-verify prove <tier>` runs each plant in a COPY of the tree and requires the step to go red carrying that evidence. A step with no `proves` is `PASS (unproven)` — in `prove`, on the line an ordinary run prints, as `"unproven": true` in the report, and in the Stop hook's green message. Unproven is not failure; it is the absence of evidence, said out loud.

### Patch Changes

- Updated dependencies [74ae3ec]
- Updated dependencies [97bc8e9]
  - @geonosis/ratchet@2.0.0

## 1.4.0

### Minor Changes

- d23aef3: **Each tier records its own gate report (#121).** `geonosis-verify <tier>` writes `.geonosis/gate-report.<tier>.json` beside the shared latest-run report (which stays, for every reader that wants "what ran last"). `fastTierMs` reads its own tier's file first — `fast` straight after `full` refused deterministically while every step printed ok (dielime, reproduced on purpose). The shared file remains a fallback for trees written by an older verify, but a wrong-tier fallback is a refusal naming the file the right tier writes.
- ce2167f: **A failed step's tail is evidence that can diagnose itself (#127).** `failureEvidence` cites the FIRST error-bearing lines of the FAILING task (runner prefixes filtered, warnings dropped, never starting inside a truncated line) instead of the last N lines of the merged stream — which carried turbo's cache warnings while the failing task's output was cut, and once stopped one line short of the only line naming the cause (two verbatim dielime captures). Green steps keep the plain tail.

### Patch Changes

- a7fe848: README: a verify step a build runner satisfies from cache is green by REPLAY — a tier that exists to catch non-determinism must bust the runner's cache (#128, measured in dielime: 50 cached tasks over a live race).
- bc2926f: **`geonosis-verify` writes its envelope beside the gate report.** `.geonosis/envelopes/verify.json` counts every step the tier named against the steps that actually ran. A step the first red stopped is `excused`, with the id of the step that stopped it in the reason; a step the shell could not START is `refused`, because it has no exit status of its own and counting it as run is how a tier reports green over a command that was never on the machine. A tier whose steps do not add up renders no verdict at all.
- ef29fc0: READMEs: a tool catalog publishes value vocabularies, not just field names — a zero from a wrong filter reads as good news (#135); and the pairing rule — a scoped fast tier is safe exactly when the full tier is total, every hard cap needs a full-tier counter (#136). Both measured in dielime.
- Updated dependencies [ce2167f]
- Updated dependencies [0d99139]
- Updated dependencies [8f5dc0c]
- Updated dependencies [5df33fd]
- Updated dependencies [d23aef3]
- Updated dependencies [a430c81]
- Updated dependencies [565db59]
- Updated dependencies [3de7f28]
- Updated dependencies [ef29fc0]
  - @geonosis/ratchet@1.4.0

## 1.3.0

### Minor Changes

- 2d1d841: **`geonosis-verify --help` was an unknown option, exit 2 (#66).** `--help`/`-h` now print the full
  usage — the tier form, `--prove`, `stats`, and the three exit codes — at exit 0, instead of a
  one-line usage attached to a refusal.
  
  **`--print-config-shape` (#75), and it reads the repo to print the tiers FOUND.** Every key under `verify` becomes a tier, so a typo'd `"fastt"` was silently a tier nobody runs; it is now visible in the answer. A tier holding an empty command is refused — a step that runs no process is a gate that passes having done nothing.

### Patch Changes

- Updated dependencies [db1cebd]
- Updated dependencies [2d1d841]
- Updated dependencies [f37950f]
  - @geonosis/ratchet@1.3.0

## 1.2.0

### Patch Changes

- @geonosis/ratchet@1.2.0

## 1.1.0

### Patch Changes

- @geonosis/ratchet@1.1.0

## 1.0.1

### Patch Changes

- 1c2ee0b: **Every package ships its declarations and says where they are.** `@geonosis/visual-diff` and
  `@geonosis/verify` emitted no `.d.ts` at all (TS7016 in a consumer: the comparator every visual test
  calls became `any` at the one seam that decides whether a screenshot regressed), and no package
  declared `types` — the nineteen that worked did so through the sibling-file fallback, which ends the
  day `exports` gains a condition. Every export now carries a `types` condition beside `default`, every
  manifest a `types` field, and `tooling/types.test.ts` holds all twenty-one to it after the build.
  
  `geonosis-verify-arch` prints a check once when its label is its id ("direction, orphan", not
  "direction direction").
- Updated dependencies [1c2ee0b]
  - @geonosis/ratchet@1.0.1

## 1.0.0

### Major Changes

- c575f44: Five counters, `tolerance`, and a baseline rewrite that only ever goes down.
  
  - `disabledCiJobs` — lines of `if: false` across the workflow files (`dir`). A job switched off to
    get a release through is debt exactly like a lint error: invisible, because the pipeline goes
    green. A condition that merely mentions `false` is not one, and a repo with no workflows
    directory reads 0 rather than refusing; a workflow that is there and cannot be read refuses.
  - `bundleBytes` — one integer out of whatever the repo's sizing command printed (`command`,
    `match`), separators and all. With `match` it takes the group that pattern names and refuses when
    it matches nothing, rather than falling back to any integer and reporting the wrong number as
    confidently as the right one.
  - `fastTierMs` — `finishedAt − startedAt` from the gate report `geonosis-verify` wrote (`report`,
    `tier`). It refuses a report of another tier: the full tier takes minutes, and timing it against
    the fast tier's baseline would report a regression of the wrong thing.
  - `testsWithoutRunner` — workspaces holding tests with no `test` script. Unmeasured and passing are
    indistinguishable from outside, so the count of unmeasured things has to be a number of its own.
  - `packagesWithoutTypecheck` — workspaces with no `typecheck` script (`script`).
  
  `tolerance` is new on the verdict: the fraction of the baseline a number may drift UP before the
  ratchet calls it growth. Bytes and milliseconds move on a dependency patch nobody chose, and a gate
  that fails on +40 bytes is a gate that gets switched off. It forgives noise upward only — a shrink
  of any size still lowers the baseline — and a `tolerance` that is not a non-negative number stops
  the run naming the entry, because one that silently became `NaN` reads exactly like a counter that
  can never grow.
  
  **Fix:** a baseline rewrite now lowers only the numbers that SHRANK. Writing every measured key back
  was a harmless no-op while `held` implied `now === baseline`. Tolerance breaks that implication, and
  the blanket rewrite would have laundered growth-within-tolerance into the new floor on the first
  unrelated win — a baseline going up on a pass.
  
  `examples/dielime.ratchet.json` is dielime's Phase 3 config: 36 keys, one `testFailures` entry per
  workspace. It keeps `--format=unix` and `expectFormat` on the two oxlint counters, which dielime's
  own config drops — the summary cross-check can tell a misparse from a clean run only when the run
  printed a summary, and a warnings-only run in an unreadable shape prints one that says nothing
  about errors. Pinning the format is what closes that window.
- c418f91: The `geonosis` Claude Code plugin — the method as enforcement rather than advice.
  
  `plugin/` ships five hooks, Node ≥ 22, zero dependencies, every tool resolved from the CONSUMER's
  `node_modules/.bin` so a repo that has not adopted a tool is never blocked on a tool it lacks:
  
  - **SessionStart** runs `geonosis-doctor` if it is installed and prints a one-line digest of
    `.oxlintrc.json` — which plugins are loaded, how many architecture rules are at `error`. It never
    fails a session.
  - **PreToolUse / Bash** refuses `prettier` (law 4) and `--no-verify` (law 7), and hands a
    `git commit` to `geonosis-ledger` — the message its `-m` flags compose, and whether the staged
    diff delivers anything but comments. The detectors are the ledger's, never reimplemented here; a
    repo without it gets a note and no opinion.
  - **PreToolUse / Edit|Write|MultiEdit** refuses a write that ADDS `eslint-disable`,
    `oxlint-disable`, `@ts-ignore` or `@ts-expect-error` (law 3), any write to `gate-baseline.json` or
    under `.geonosis/` (D-007 — the scored agent never writes the scoreboard), and a lint config that
    sets a rule to `"warn"`, `"off"`, `0` or `1` (law 2). It reads what the call would ADD, never what
    it removes, so the edit that DELETES a suppression is not refused by the guard against them.
  - **PostToolUse / Edit|Write** runs oxfmt on the file just written. It never blocks.
  - **Stop** runs `geonosis-verify fast` and will not let the turn end on a red one, capped at five
    blocks per session — an agent retrying the same broken thing five times has a problem it will not
    solve on the sixth, so the sixth prints the report and lets go.
  
  `.claude-plugin/marketplace.json` offers it from `./plugin`. `pnpm generate:skill` now writes the
  plugin's copy of the mechanical skill, the engineer agent with the machine-specific path taken out,
  and `plugin.json` carrying the kit's own version; staleness tests fail when any of the four drift.
- 5f09ddc: Every hook is driven end to end, as Claude Code drives it.
  
  Each script is SPAWNED with real stdin JSON over a throwaway repo carrying a `geonosis.json`, a
  `gate-baseline.json`, an `.oxlintrc.json` and a `node_modules/.bin` linked to the BUILT
  `geonosis-verify` and `geonosis-ratchet`. A hook tested by calling its policy function proves the
  policy; only a spawn proves the exit code, the JSON on stdout and the tool resolution — which is
  the whole of what Claude Code actually reads.
  
  The Stop hook is exercised against a real fast tier that really goes red: a law file one line over
  its baseline, so the ratchet grows and the hook blocks with exit 2, the failing step and its tail on
  stderr. Then the sixth block prints the report and exits 0; a green run clears the session's count;
  `stop_hook_active` short-circuits; and a repo with a BACKLOG that did not regress is not blocked at
  all — which is the whole adoption argument, tested rather than asserted.
  
  The suite was falsified before it was trusted: making Stop exit 0 instead of 2, dropping the
  delivery check, and raising the cap to 50 each turned exactly the tests that should have gone red,
  red.
- 60f4d8f: **The law ceiling in both consumers, and an instrument for finding out whether cutting the law
  helped.** `lawLineCount` is now in `examples/dielime.ratchet.json` and
  `examples/during-day.ratchet.json`, in the **fast** tier — it reads one file, and a law that grew is
  exactly the regression a turn should not be allowed to end on. Measured read-only 2026-08-30: those
  two laws are **382** and **1,104** lines against guidance of 200, and those are the baselines each
  repo starts from.
  
  `geonosis-verify stats [--since <YYYY-MM-DD>] [--json]` reports the **first-try pass rate**: the
  share of turn-ends whose fast tier was green on the first run. `--since` prints that window AND
  everything before it, so a before/after comes from one command and one instrument.
  
  The Stop hook now records what it gated. It had to: `stop-blocks.json` is the block cap's working
  memory and a **green turn deletes its own row from it**, so a session that never blocked and one
  that blocked and recovered are both absent — there was no history to read. The new
  `.geonosis/stop-turns.json` is two integers a day, and the hook writes it itself because a hook is
  fetched as a git tree with no install step and may import nothing; a test reads the hook's own output
  back through `readStats` so the two spellings of the format cannot drift apart.
  
  Three refusals, all the same refusal — **an empty record is not a perfect score.** No record says so
  and gives no rate rather than 100 %; a record that will not parse is an error rather than zero turns;
  and a turn the hook never gated (`stop_hook_active`, where the gate does not run) is not counted,
  because there was no first try to have.
  
  `docs/law-diet.md` is the M6 procedure end to end: arm the ceiling, record a before, cut in one
  commit on a day you write down, read the after against the same instrument — and write down what
  happened even when nothing moved, because a cut that changed nothing says the law was not the
  binding constraint.
- 89c6769: The ratchet's own layers are walled, and the walls have a door that is proved open.
  
  The three ratchet layers named FILE paths only, so `packages/ratchet/src/index.ts` — the published
  entry — sat in no layer and could import anything, the CLI included, with lint green. It is
  `ratchet-api` now, and the formatter it shares with the CLI has moved into a `ratchet-report` layer
  of its own rather than being counted as part of the CLI it is not.
  
  The dogfood suite gains a second kind of plant. A wall that refused everything would be as useless
  as one that refused nothing, so each layer's permitted import — `verify` reaching the lock through
  `@geonosis/ratchet` — is planted and asserted to stay CLEAN. Without that, the next engineer widens
  `mayImport` to get their work through and the wall goes with it.
- b2d915f: **Fix:** a counter's command no longer loses what it wrote to stderr.
  
  `<command> 2>&1` binds the redirect to the LAST command of the string. `echo boom >&2; echo fine`
  therefore merged nothing, and on a ZERO exit `execSync` hands back stdout alone — so a tool that
  prints its findings to stderr and exits 0 read as a tool that found nothing. That is the exact shape
  of a gate that has quietly stopped gating, and it is the one direction in which this kind of bug is
  dangerous: the number comes back lower than the truth and the baseline banks it.
  
  On a NON-ZERO exit the line survived, because the failure path concatenates both captured buffers —
  but concatenation put every stderr line after every stdout line, moving each diagnostic away from
  the line it was about, which is what the evidence tail on a regression is for.
  
  The command now runs in a subshell, so the shell itself merges the streams in the order they were
  written. Measured before and after: `echo boom >&2` on a zero exit read `""` and now reads `boom`;
  `echo one; echo two >&2; echo three; exit 4` read `one three two` and now reads `one two three`.
- 48bce0c: **Fix:** only a counter that measures a quantity accepts a `tolerance`.
  
  Any counter took one. `{"counter":"lawLineCount","tolerance":0.5}` over a two-line law with a
  baseline of 2 printed `ratchet PASS` at three lines — a 50 % growth, waved through. The same edit is
  `"tolerance": 100` on `oxlintErrors`: the gate switched off from the config file, with the run still
  reporting green. That is law 2 — never downgrade a rule — wearing a friendlier name, and it was the
  easiest edit in the repo to get past a reviewer.
  
  `Counter` gains `tolerates?: true`, declared only by `bundleBytes` and `fastTierMs`. Whether a
  number is a measured quantity or a count of findings is the counter's to know, not the config's.
  Anything else now stops the run, naming the entry:
  
  ```
  geonosis-ratchet: "oxlintErrors" does not accept a tolerance — only a counter that measures a
  quantity declares one
  ```
  
  The plugin's write guard gains its first **ask**. A write to `geonosis.ratchet.json` that introduces
  or raises a `"tolerance"` is neither refused nor allowed: it goes in front of the human as
  `permissionDecision: "ask"`. Widening a band is legitimate and is not the agent's call — a band
  somebody chose and a band somebody's agent chose are not the same thing. A tolerance carried through
  an edit unchanged is not a change and says nothing.
- 461233c: Docs for the two new things: `docs/plugin.md` and `packages/verify/README.md`.
  
  `docs/plugin.md` covers installing the plugin from a local path and from the remote once it is
  pushed, what each of the five hooks does and the reason it does it that way, the five-block cap,
  which files the runner owns and why the agent may not write them, what to put in `geonosis.json`,
  and — the section every README skips — what the plugin CANNOT do. A `fast` tier of `["true"]` passes
  for ever; only `geonosis-ratchet --prove` shows a counter can go red.
  
  `packages/verify/README.md` is the runner's own: `--prove` first, the config with no default tier,
  the three exit codes and why a report that could not be written is exit 2, the report shape with the
  `<tier>/<n>` id rule, and `--exclusive`.
  
  `docs/README.md` indexes both. The ratchet README gains rows for the five new counters, a section on
  `tolerance` and the shrank-only rewrite it forces, and the landmine that cost an afternoon: a
  loader or shim named RELATIVELY in a counter's command resolves to nothing under `--prove`, whose
  probes run in a scratch directory. Write it `$PWD/…` and it works in both.
- e48501b: `@geonosis/verify` — one gate runner, with a report a hook can read.
  
  `geonosis-verify <tier>` runs the shell steps a repo declared under `verify` in `geonosis.json`,
  serially, stopping at the first red, and writes `.geonosis/gate-report.json`:
  `{ tier, startedAt, finishedAt, ok, steps: [{ id, command, ok, exitCode, ms, tail, skipped? }] }`.
  A step whose text is another tier's name is that tier, inlined in place; the inlined steps keep the
  tier that declared them, so `fast/2` is the same command whether the operator ran `fast` or ran the
  `full` tier that inlines it.
  
  There is no default tier and there never will be one: no config, no `verify` object, or an unknown
  tier is refused by name with exit 2, and so is a green run whose report could not be written — a
  gate that cannot record has not passed. A failed step is exit 1.
  
  `--exclusive` takes the ratchet's machine-wide lock through `@geonosis/ratchet`'s published entry
  (`--exclusive-timeout <seconds>` bounds the wait), so a verify and a ratchet on one laptop serialise
  against each other rather than each against itself. `--prove` plants a tier with one passing step
  and one that exits 3, runs the real binary over it in a temp directory, and checks the failure
  reached the report — a runner that reports green is worth nothing until it has been watched going
  red on purpose.
  
  The kit walls the new package with its own `layer-walls` rule: `verify` may import `ratchet-api`
  and nothing behind it, planted against in the dogfood suite like the other walls.

### Patch Changes

- Updated dependencies [e6a54d5]
- Updated dependencies [9858ee5]
- Updated dependencies [c575f44]
- Updated dependencies [90d38ec]
- Updated dependencies [c418f91]
- Updated dependencies [5f09ddc]
- Updated dependencies [60f4d8f]
- Updated dependencies [89c6769]
- Updated dependencies [b2d915f]
- Updated dependencies [7762a01]
- Updated dependencies [48bce0c]
- Updated dependencies [461233c]
- Updated dependencies [e48501b]
- Updated dependencies [7e8d2c1]
  - @geonosis/ratchet@1.0.0
