Round two of the same independent review. Three findings, all real, and the
first two share a root cause: the guard was reading command TEXT as though it
were a command.
1. Splitting the endpoint token itself defeats fragment matching outright —
`a=/api/v1/repos/o/r/iss; b=ues/1/comments` leaves no fragment contiguous.
Round one fixed one spelling of this and the reviewer produced the general
form immediately. It is not winnable by more fragments: the endpoint does
not exist until the shell expands it, and this hook runs first. So the guard
stops pretending to read it. A write whose URL contains an expansion, on a
visibly forge-shaped command, is now BLOCKED as unreadable — because "I
could not find an endpoint" must not mean "there is no endpoint". Opaque
URLs that are not forge-shaped (webhooks, artifact stores) still pass.
2. The broadened body detection false-blocked ordinary work: `grep -R "curl -d
https://.../issues" docs/`, `echo "curl -d ..." > note.txt`, printing an
example from python. Talking about a call is not making one, and this is the
direction that actually kills a control — an over-blocking hook gets turned
off, and an off hook permits everything. The client must now appear at
COMMAND POSITION: line start or after a shell operator, optionally behind
VAR=value. In every false positive it sat behind a quote instead. Quotes are
deliberately NOT stripped before matching; real calls quote their URLs.
3. `wt_precious()` aborted `cmd_rm` under `set -euo pipefail`: `grep -v` exits 1
when it filters everything out, which is exactly the disposable-only case,
so a SAFE worktree failed to remove with no message. Fixed, and the same
defect was latent one step upstream in `wt_dirty()`, where `head -200`
SIGPIPEs git on any worktree with 201 changed files. The cap is gone —
counting is cheap and the cap only ever truncated output that is no longer
printed.
Seven new fixtures pin all of it, in both directions. 27/27.
An undocumented tool is, from inside an agent session, indistinguishable from a
tool that was never written. The framework shipped 26 git wrappers and named 6 of
them in its resident index docs — 23% discoverability, with pr-review.sh among the
missing. The observable consequence was an agent obeying Constitution gate 7 as
best it could see it, reaching for raw curl, sending GitHub's APPROVE to a Gitea
host, and getting HTTP 200 with the review silently filed PENDING. Three times.
That is not a discipline failure and no amount of prose fixes it.
Four changes, each converting a rule that decayed into a mechanism that cannot:
- check-tools-index.sh (new, CI-blocking): every tool in an enforced suite must be
named in a resident index doc, and every tool an index names must exist. The git
suite is enforced now; other suites report coverage without failing, so the
ratchet tightens one reviewed PR at a time instead of landing as one sweep. The
enforced list is framework-owned rather than a marker inside operator-owned
TOOLS.md — a doc marker would let an operator silence the gate on exactly the
host where it matters most. Carries --self-test, because a checker that only
ever passes is indistinguishable from one that is not running.
- TOOLS-REFERENCE.md: complete 28-entry git index, plus the APPROVED/APPROVE
dialect note that explains why pr-review.sh is not a formality.
- mosaic-worktree.sh + wrapper-guard.sh (upstreamed): the rule "big work goes on a
work filesystem" already existed in prose, and 255 GB accumulated in $HOME across
842 directories anyway, under five simultaneous placement conventions on one
host. The helper therefore exposes no placement decision — given a branch name,
every path is derived from `git worktree list --porcelain`. Worktrees rather than
clones because enumerability is the only thing that makes reclaim safe, and
reclaim is by evidence (clean tree + no unpushed commits), never by size or age.
The guard blocks three mechanically-detectable mistakes and nothing else:
a checkout into $HOME, a raw provider-API write to an endpoint that has a
wrapper, and the literal APPROVE event. Reads pass untouched.
- STANDARDS.md: model tiering as a standard, named by capability class so it
survives a model generation. Start cheapest, escalate on evidence, benchmark
before demoting a task class, and keep the class->model binding in operator
config with the DB-backed config service as the end state.
Registering the guard in runtime/claude/settings.json is the point of upstreaming
it: ~/.claude/settings.json is a framework-managed copy, so a hand-added hook there
is destroyed by the next upgrade. In the template it survives, and it reaches every
host instead of one.
Completes the bootstrap repo migration with remaining files:
- PowerShell scripts (.ps1) for Windows support (bin/ + tools/)
- Runtime adapters (claude, codex, generic, pi)
- Guides (17 .md files) and profiles (domains, tech-stacks, workflows)
- Wizard test suite (6 test files from bootstrap tests/)
- Memory placeholder, audit history
Bootstrap repo (mosaic/bootstrap) is now fully superseded:
- All 335 files accounted for
- 5 build config files (package.json, tsconfig, etc.) not needed —
monorepo has its own at packages/mosaic/
- skills-local/ superseded by monorepo skills/ with mosaic-* naming
- src/ already lives at packages/mosaic/src/