Systematic whole-codebase review tracking¶
Prompt¶
Before responding to questions or discussion points in this document,
ground yourself in the referenced tooling and repositories. Read the
weAudit source (especially its serialization types) rather than relying
on its README, inspect the actual .weaudit state files produced during
the Phase 0 trial, and look at how divergulent-reviews structures its
signed review ledger before proposing changes to review-state storage.
Where a question touches on external concepts (git signing, crev-style
review proofs), research as needed. Flag uncertainty explicitly rather
than guessing.
Situation¶
The consistency audits (see PLAN-consistency-audits-v2.md and
audits/) catch mechanical drift across Shaken Fist projects --
missing files, wrong configuration, packaging issues. What they do not
catch is drift within the code itself: inconsistencies in style,
architecture, and approach that creep into a codebase over years of
incremental change. Catching those requires a human actually reading
the code, file by file.
The problem is that the codebases are now large enough that a full read-through cannot happen in one sitting. That creates a tracking problem: which files have been reviewed, when, at what version of the code, and with what notes? Without tracking, partial reviews are wasted effort because you cannot resume them, and completed reviews silently go stale as files change.
The desired workflow, as discussed:
- Review one file at a time inside the editor, with normal cross-referencing (go-to-definition etc.) available for context.
- Mark each file as reviewed, with optional notes.
- Have each review "signed" in the divergulent sense -- an attestation of who reviewed what, when, at which content version.
- Have reviews automatically flagged as stale when the underlying file changes, so re-review is a mechanical query rather than a judgement call.
- Be able to ask an LLM questions during review (Claude Code in the integrated terminal covers this with no integration work).
A strong constraint: avoid writing new software where possible. The personal backlog is already long; the design below deliberately composes existing tools (weAudit, git) with at most one small script.
Analysis and recommendations¶
Tool selection: weAudit¶
weAudit is the VSCode extension Trail of Bits uses for security audits, and audit work has exactly this shape (large codebase, many sittings, coverage tracking). It provides:
- "Mark File as Reviewed" / "Mark Region as Reviewed" commands, with a tick in the file explorer and highlighting so unreviewed code is visually obvious while browsing.
- Findings and notes attached to code regions.
- A daily log of files and lines reviewed per day.
- State stored as JSON in
.vscode/<username>.weaudit(per user), which can be committed and shared.
Alternatives considered and rejected for now:
- Writing our own extension -- rejected for backlog reasons; only reconsider if Phase 0 shows weAudit is unsuitable.
- Forking weAudit (Apache-2.0) to add hash-keyed staleness -- fallback option, not the starting point.
- crev / cargo-vet style signed review proofs -- the right data model (review proof = path + content hash + reviewer + date + notes + signature) but heavyweight to adopt directly. We get an equivalent binding from signed git commits (below). Keep as a reference model if the git-based approach proves insufficient.
What weAudit does and does not record¶
Verified against src/types.ts in the weAudit repository (July 2026):
- An audited-file entry is
{path, author}-- no per-file hash, no timestamp. (PartiallyAuditedFileaddsstartLine/endLine.) - The state file has a single workspace-level
gitSha, not a per-mark one. - weAudit has no concept of a stale review: a file keeps its tick after it changes, until the mark is removed from the JSON.
So the JSON carries almost no provenance. That is fine: git carries it instead.
Attestation via signed git commits¶
Commit each update to .vscode/<user>.weaudit as a signed commit.
The signature covers the whole commit tree, which contains both the
review mark and the exact content of the reviewed source file. So
"Michael reviewed foo.py as it existed at blob X, on date D" is
implicit in the signed commit -- author, date, identity, and content
binding all come from git for free, with nothing added to the JSON.
Rules required to keep that binding honest:
- Mark reviews only on a clean working tree. If a file has uncommitted local edits when it is marked reviewed, the committed tree will not match what was actually read.
- Commit review marks at the end of each session (or more often), before pulling or rebasing, so the marking commit's tree reflects the reviewed state.
- Protect the branch carrying review state from force-pushes and rebases; rewriting history rewrites the attestation trail.
- Verification is
git verify-commiton the marking commits.
Important corollary: the state file must live in the same repository
as the code it reviews. A central reviews repository (the
divergulent-reviews pattern) would break the tree binding -- the
signed commit would no longer contain the reviewed content. The
divergulent case is different because its ledger reviews external
artifacts (Debian patches) that cannot live in the same repo.
Per-repo .vscode/<user>.weaudit is also what weAudit supports
natively.
Staleness via stamped blob SHAs and git hooks¶
Corrected on 2026-10-24: the hooks described in this section and in
"New code required" below were removed in cde84bb (#9,
2026-07-18), and .pre-commit-hooks.yaml went with them. Review state
changing in the middle of unrelated git operations confused more than
it helped. stamp and prune are now run by hand through a
per-repository wrapper. Since PLAN-review-coverage.md, prune and
import also run from each adopter's prune-reviews workflow on its
own default branch. The stamped-SHA sidecar, the O(1) staleness
comparison and the reasons for both still stand. This section is
kept as the record of the design, and docs/code-review-tracking.md
is the authority on how the pieces run today.
Phase 0 confirmed the gap: weAudit has no idea when a reviewed file changes. The revised design (July 2026, after trialling weAudit) solves this entirely client-side with two hooks and one small script, replacing the history-walking approach originally planned (preserved below as a fallback).
Stamp at commit time (pre-commit hook). When a commit includes
review marks, record the blob SHA of each reviewed file: for every
path in auditedFiles that does not yet have a stamp, store the
staged blob SHA (git rev-parse :"<path>" -- the index version,
which is what lands in the commit tree). Compare content in git
terms, never timestamps (mtimes do not survive clones, and touch-only
commits would false-positive).
Store stamps in a sidecar file, not inside the weAudit JSON.
Verified against src/codeMarker.ts (July 2026): weAudit
reconstructs its save data from only the fields it knows about
({path, author} for audited files; findings are field-by-field
mapped), so any extra fields we inject are dropped the next time
weAudit saves -- which happens on every mark. Losing a stamp is the
dangerous failure mode: re-stamping an old review at the current
SHA would silently refresh a stale review. A sidecar
(.vscode/<user>.weaudit-shas.json, mapping path to blob SHA and
review date) is never touched by weAudit and cannot be clobbered.
The pre-commit hook only ever stamps entries absent from the
sidecar, and drops
sidecar entries whose paths have left auditedFiles (unmarked
files).
The hook should also warn when a newly-stamped file has staged content changes in the same commit -- that violates the clean-tree rule above (the mark would attest to content the reviewer may not have read).
Prune when history moves (post-merge / post-checkout /
post-rewrite hooks). After a pull, checkout, or rebase, staleness
is an O(1) check per file with no history archaeology: for each
sidecar entry, compare the stored SHA to git rev-parse
HEAD:"<path>". On mismatch (or the path no longer existing --
covers renames, since blob SHAs follow content not paths), remove
the entry from both auditedFiles and the sidecar, and print what
was pruned. Partially-audited (region) marks are pruned wholesale on
the same trigger: line ranges shift as files change, so any change
to a file removes its region marks just like a whole-file mark.
The pre-commit framework supports all three hook types, and
default_install_hook_types in
.pre-commit-config.yaml makes a plain pre-commit install set
them all up.
The pruned state files are left modified in the working tree and get committed (signed) at the end of the next session like any other review-state change -- the prune is authored by the reviewer, so the signed-mutations-only property is preserved.
Two operational notes:
- weAudit does not watch its state file for external changes
(verified against
codeMarker.ts), so a prune while VSCode has the workspace open needs a window reload or weAudit refresh before the ticks update. Pruning fires on pull, which usually precedes opening the editor; the session-start habit covers the rest. - Hooks are per-clone: a fresh clone without
pre-commit installsilently shows stale ticks. Acceptable for a single reviewer; the optional CI backstop below covers it if it ever matters.
Attestation is unchanged, and slightly strengthened. The signed commit tree remains the real proof (it contains both the mark and the content); the sidecar stamp additionally makes the reviewed blob SHA an explicit signed assertion rather than one derived from the tree.
Fallback: deriving staleness from history. If stamps are ever
missing (pre-sidecar marks, forensic verification of old reviews),
staleness can still be derived by walking the state file's history:
find the commit R where a path entered auditedFiles by parsing
git show <rev>:.vscode/<user>.weaudit at each revision (note git
log -S"<path>" is unreliable -- findings in the same JSON also
contain paths), then test git diff --quiet R HEAD -- <path> and
optionally git verify-commit R. This is not planned to be built
unless needed.
Review scope: direct code only¶
Reviews cover direct code. Generated artifacts (protobuf output and
similar), vendored code, and test data are out of scope; whether
unit tests are in scope is a per-repo call (see open questions).
Each adopted repo carries a small scope configuration the hooks
read -- include/exclude globs, e.g. .vscode/review-scope.toml.
The scope config gives coverage reporting a meaningful denominator
("N of M in-scope files reviewed") and lets the stamp hook warn
when an out-of-scope file is marked.
Surfacing reviews: a generated REVIEWS.md¶
Review marks buried in .vscode/*.weaudit* are invisible to anyone
who does not know to look for them. Each adopted repo therefore
carries a REVIEWS.md at its root -- generated, never hand-edited
-- built from the weAudit state plus the sidecar.
The file opens with a header for someone encountering it cold: a short explanation of what the file is (periodic whole-file human review, distinct from PR review), a pointer to this tooling, a note that the file is generated and how, and a coverage summary ("N of M in-scope files reviewed"). Then the table: one row per reviewed file with path, reviewer, review date, and the stamped (short) blob SHA; partial reviews listed with their line ranges. Output is deterministic (sorted by path) so its diffs read as "these reviews were added, these were obsoleted".
Both hooks regenerate it whenever they change the sidecar: the
stamp hook after adding stamps, so REVIEWS.md lands in the same
signed commit as the marks it describes, and the prune hooks after
removing stale marks. A REVIEWS.md was prototyped by hand for
ryll during Phase 0; the generated file replaces it.
Session driver: pick the next file to review¶
With a scope config in place, the helper can also answer "what
should I review now": a next subcommand picks a random in-scope
file with no current review mark (never-reviewed and pruned-stale
files land in the same pool, which is the right behaviour) and
opens it in VSCode (code <path>), printing the path for
non-editor use. Random choice is deliberate -- it avoids both
alphabetical ruts and cherry-picking easy files -- and it removes
the "where do I start today" friction from a session. Combined with
the automatic prune on pull, a session becomes: pull, next,
review, mark, repeat, commit signed.
New code required¶
One script with stamp and prune subcommands (invoked by the
pre-commit and post-merge/checkout/rewrite hooks respectively),
each regenerating REVIEWS.md whenever the sidecar changes; a
standalone regen subcommand for bootstrap and repair; and the
interactive next subcommand above.
Target repos should not carry a copy: the pre-commit framework
supports remote hook repositories, so the script and its
.pre-commit-hooks.yaml live once in this repository
(shakenfist/development, decided -- it is already the home of the
plan, the audits, and the shared review tooling) and each project
adds a few lines to its existing .pre-commit-config.yaml.
CI drift detection (the previously-planned scheduled workflow
following the consistency-audit.yml pattern) is now an optional
backstop rather than the mechanism: with stamps in the sidecar, a
CI check reduces to the same O(1) comparison and needs no history
walking. Defer until there is a demonstrated need (e.g. multiple
clones or reviewers) -- and if built, CI reports only and never
edits the state files, since a bot commit would interleave unsigned
mutations with human attestations.
Open questions¶
Resolved so far (decisions are recorded in the analysis above): the
hook script lives in shakenfist/development; .gitignore gets a
!.vscode/*.weaudit* exception per repo at adoption (already done
for ryll); scope is direct code only, excluding generated and
vendored artifacts; region marks are pruned wholesale like file
marks; reviews are surfaced via a generated REVIEWS.md opening
with an explanatory header and coverage summary; a next
subcommand picks a random unreviewed in-scope file and opens it in
VSCode.
Corrected on 2026-10-24: two of the resolutions above did not
last. The hooks are gone (see the note at the top of "Staleness via
stamped blob SHAs and git hooks"). Scope is no longer direct code
only: ed10382 made code plus the prose that documents it the
recommended default, and 9c4821a added YAML and other declarative
configuration. The
current rule is the scope config entry under "The pieces" in
docs/code-review-tracking.md.
The three questions below were left open in July. Practice has answered each, which is what Phase 4 step 5 asked for:
- Are unit tests in scope? Direct code is in and generated code
is out (decided); unit tests sit in between. Probably a per-repo
call recorded in that repo's scope config. Settled as a per-repo
call, which is how
docs/code-review-tracking.mdstates it, and each adopter's.vscode/review-scope.tomlrecords the choice. - Should REVIEWS.md link to findings? The header and coverage
summary are decided; whether rows should also reference weAudit
findings/notes for the file is not. Settled as no, by default.
The rows carry path, reviewer, date, blob SHA and, since
PLAN-review-import.md, the source of an imported review. No findings link was built and nobody has asked for one. Findings stay in the weAudit state, which is committed besideREVIEWS.md. - Multi-reviewer: weAudit state is per-user
(
<username>.weaudit). For now there is one reviewer; the design extends naturally (one sidecar per state file) but the hook script should not assume a single state file, andREVIEWS.mdmust merge all reviewers' state. Settled as designed.review-tracking.pyreads every.vscode/*.weauditfile.REVIEWER_IDENTITIESmaps each reviewer to the signing identity thatimportverifies. The imports file is a reviewer-shaped sidecar of its own. There is still only one human reviewer, so the multi-reviewer path has had no real use.
Execution¶
| Phase | Status | Merged |
|---|---|---|
| 0. Trial weAudit | Complete | nothing in this repository; the trial ran in ryll |
| 1. Conventions for signed review state | Complete | d180c39 |
| 2. Stamp and prune hooks | Complete | 0957d15, 9f8d47f, 4eae793, ee6d4ea; ryll 7775679e (#144), ef94c321 (#146) |
| 3. Optional CI backstop | Superseded | nothing; replaced by PLAN-review-coverage.md |
| 4. Documentation and rollout as a consistency audit | Complete | accreted over many landings; reconstructed in Phase 5 |
| 5. Push audit | Complete | c39b06e (#314) |
This table was added on 2026-10-24, when Phases 0--4 were closed
out together. Until then the plan carried no status table and every
phase section still read as if work were outstanding. The Merged
cells for Phases 1 and 2 come from the reconstruction that
PLAN-push-audit-phase added to Phase 5. Phase 4's landings were
never recorded, and Phase 5 reconstructs them.
Phase 0: Trial weAudit¶
The point of this phase is to find out whether the core review loop feels good before investing in any automation. Course-correct the rest of this plan based on the outcome.
Outcome so far (July 2026): trialling in shakenfist/ryll, with a
couple of files reviewed with weAudit. The review loop itself works;
the confirmed big gap is that weAudit does not notice when a reviewed
file later changes. That finding produced the stamped-SHA / git-hook
design now in the analysis section, replacing the original
history-walking approach. ryll remains the proving ground through
Phases 1 and 2; the tooling itself lands in development once solid.
- Install the weAudit extension from the VSCode marketplace
(
trailofbits.weaudit). - Pick a trial codebase (see open questions) and do several real review sessions across multiple days.
- Evaluate specifically:
- Does the mark-as-reviewed / explorer-tick workflow suit?
- Are the state-file diffs reviewable and merge-friendly enough to commit per session?
- Is the daily log useful for pacing?
- Do findings/notes cover the "notes with the signature" need, or is something more needed?
- Any friction using Claude Code in the integrated terminal alongside it?
- Decision point: continue with weAudit, fork it, or revisit the build-vs-buy question with what was learned.
Phase 1: Conventions for signed review state¶
- ~~Confirm
.vscode/*.weauditis committable in the trial repo (adjust.gitignoreif needed).~~ Done for ryll; every other repo needs the same.gitignoreexception as part of adoption (captured in the Phase 4 template). - ~~Adopt the session discipline: clean tree before marking, signed commit of the state file per session.~~ Done, and stronger than planned: reviews happen from a dedicated user account on the review machine whose clones never carry development edits, making the clean-tree rule structural. That account signs with gitsign (Sigstore keyless; example: ryll commit 755a3cc "review: glz.rs"), which also records every attestation in the Rekor transparency log. The conventions doc records setup and verification.
- ~~Document the conventions in
docs/code-review-tracking.mdin this repository (workflow, signing, verification commands).~~ Done. - ~~Confirm branch protection on the target repo prevents history
rewrites on the branch carrying review state.~~ Done. It was
initially absent (ryll had only a tag ruleset restricting
releases); Mikal created the "Protect default branch history"
ruleset (
deletion+non_fast_forwardon~DEFAULT_BRANCH) on 2026-07-09 using the command now recorded in the conventions doc. shakenfist/shakenfist's "Develop branch" ruleset is a stricter superset and also satisfies the requirement; the possible audit item for this is listed under future work.
Phase 1 note (2026-07-09): Phase 0 prototyping happens in a
dedicated review account on the same host (invisible from the
development account, and initially mistaken for another machine).
The .gitignore exception and .vscode/mikal.weaudit are already
pushed to ryll's origin/develop; the hand-written draft
REVIEWS.md is local to the review account and will be replaced
by the generated file in Phase 2 (agreed). ryll's pushed exception
covers !.vscode/*.weaudit only -- the sidecar needs an
additional !.vscode/*.weaudit-shas.json line when Phase 2 wires
the hooks up.
Phase 2: Stamp and prune hooks¶
- ~~Write the review script in this repository with
stamp,prune,regen, andnextsubcommands as described in the analysis --stampandpruneregenerateREVIEWS.mdwhenever they change the sidecar -- plus tests against a fixture repo.~~ Done:scripts/review-tracking.py, with ten fixture-repo tests inscripts/test_review_tracking.py. - ~~Define the per-repo scope config and wire it into the script:
out-of-scope warnings at stamp time, the coverage denominator
for the
REVIEWS.mdheader, and the candidate pool fornext.~~ Done:.vscode/review-scope.toml, fnmatch include/exclude lists; the tracking machinery itself (.vscode/*,REVIEWS.md) is always excluded. - ~~Add
.pre-commit-hooks.yamlto this repository exposing the hooks (stamp at thepre-commitstage; prune atpost-merge,post-checkout, andpost-rewrite).~~ Done, and the whole loop validated throughpre-commit try-repoagainst a fixture: stamp (fails asking for a re-stage, then passes), upstream change, prune at post-merge discarding the mark from the weAudit state, the sidecar, andREVIEWS.md. - ~~Wire the trial repo's
.pre-commit-config.yamlto the hooks, includingdefault_install_hook_types, and re-runpre-commit install.~~ Prepared and staged in the primary ryll clone (2026-07-09), pinned to development rev0957d15; awaiting Mikal's review and commit. Each clone (including the review account's) then needspre-commit installre-run. - ~~Bootstrap: stamp the files already marked reviewed during
Phase 0 after eyeballing that they are unchanged since review,
and replace ryll's hand-maintained
REVIEWS.mdwith the generated one.~~ Folded into the same staged ryll change, which is essential: the stamp hook isalways_run, so once the wiring lands, the next commit by anyone would stamp any unstamped mark at the file's current content -- discovered when a scoped pre-commit run during preparation auto-stamped the stale mark. The eyeball check foundglz.rsunchanged since its signed review (stamped at6600fc53, dated 2026-07-05, the true review date) andbyte_bounded_lru.rschanged by two post-review "Lint fix." commits (the review commit itself also modified the file -- the exact clean-tree violation the stamp hook now warns about), so its mark was removed; it returns vianext. The bootstrap-with-wiring requirement is now documented as adoption step 6 in the conventions doc. - Validate the loop end to end in ryll: mark, commit (stamp lands
and
REVIEWS.mdupdates), change the file upstream, pull (prune fires andREVIEWS.mdupdates), confirm the tick disappears after a weAudit refresh, re-review, re-commit. The hook-level loop is already validated against the fixture; this is the live confirmation from the review account. Closed out on 2026-10-24 without being run. The hooks this step would have checked were removed nine days after they landed, incde84bb(#9, 2026-07-18), and the script has been run by hand ever since. The same loop running from CI was confirmed in production byPLAN-review-coverage.mdphase 5.
Phase 3: Optional CI backstop (deferred)¶
Superseded on 2026-08-02 by PLAN-review-coverage.md, which built
both halves of this phase in a different shape. Each adopting
repository prunes its own default branch from CI, rather than one
scheduled workflow comparing stamps across the fleet. Never-reviewed
coverage is reported by the daily audit's review-coverage
criterion.
Only if a demonstrated need appears (multiple clones or reviewers, hooks not installed somewhere):
- Add a scheduled workflow (pattern:
consistency-audit.yml) that fresh-clones each participating repo and compares sidecar stamps to HEAD blob SHAs -- report only, never edit state files. - Consider adding never-reviewed coverage reporting ("N of M files reviewed") here, which may justify the workflow on its own.
Phase 4: Documentation and rollout as a consistency audit¶
Once the ryll prototype is solid, this stops being an experiment and becomes yet another thing the consistency audits check for:
- Update this repository's
README.mdto describe the review tracking system alongside the consistency audits. - Add
audits/code-review-tracking.mdfollowing the modular audit pattern (what we check, per-project status table), and atemplates/code-review-tracking/directory containing the.pre-commit-config.yamlstanza, a starter scope config, and setup instructions (default_install_hook_types, the.gitignoreexception for.vscode/*.weaudit*). - Extend
scripts/audit-check.pywith the mechanical check (hook config present in.pre-commit-config.yaml), so drift is caught by the existing dailyconsistency-audit.ymlrun and issue automation. - Roll out to further codebases as first passes are scheduled.
- Revisit the open questions above with real experience.
What actually landed (survey of 2026-10-24). This phase was never run as a unit. Its work arrived in pieces between July and October, some as unplanned landings and some under later plans, and none of it was recorded here at the time. Phase 5's survey reconstructs those landings. Taking the steps in turn:
- Done.
README.mddescribes review tracking among its documentation links. It first did so in Phase 2, and was reworded by #9 andb51f550. - Done in a different shape. There is no
audits/code-review-tracking.md. The criteria aredocs/audits/review-coverage.md(backlog against HEAD, fromPLAN-review-coverage.md),docs/audits/review-scope-completeness.md(nothing leaves scope silently, #72), and thereview-marks-pre-commitcheck indocs/audits/workflow-standards.md(#14, #15). The template directory istemplates/review-tracking/, nottemplates/code-review-tracking/. It holds the prune workflow, its script and the by-hand wrapper (PLAN-review-import.md), not a.pre-commit-config.yamlstanza: that stanza went when the hooks did. - No longer applicable. There is no hook configuration to check for. The checks that do exist are listed under step 2.
- Done. Besides ryll, the adopters are kerbside (kerbside#214),
actions (actions#24), hunkydory (hunkydory#8), sfui (sfui#39),
kerbside-client (kerbside-client#10) and this repository (#36). The
review-tracking-adoptionskill (b51f550) is the procedure for adopting a repository. - Settled in practice and now recorded under "Open questions" above.
Phase 5: Push audit¶
Run PUSH-AUDIT.md over the accumulated diff of every phase in
this plan against main, not over the last phase's diff alone --
the interactions between phases are most of what a whole-plan
audit is for. Findings land as their own pull request. The plan is
not complete until each finding is resolved or declined in writing,
with the reason recorded here. If the audit finds nothing, say so in
one sentence: that is a real result, and a run of them is the
evidence for making this phase conditional rather than mandatory.
Planning effort: high. Every phase of this plan merged between two and fifteen weeks ago, and none of Phase 4 was recorded as it landed. Nearly all of the judgement here is about which landings belong to this plan and which tree to report against. Getting either wrong produces a confident audit of nothing. Review effort: the runbook's own settings, raised to high for 2a and 2b. Fourteen ranges have to be mapped onto a tree that has since been restructured, and that mapping is where a lighter run would go wrong.
In scope:
- running
PUSH-AUDIT.mdover the fourteen ranges in D5.1; - the consumer check D5.6 adds for the interfaces this plan removed;
- writing the Outcome into this section;
- closing the plan.
Out of scope:
- fixing anything the audit finds, which lands as its own pull request;
- re-auditing work that other plans own and have audited (D5.2);
- re-auditing the adoption pull requests in other repositories (D5.4);
- the template drift in sfui and kerbside-client, which the survey found and which is filed rather than fixed (D5.5);
- development#22 (Dockerfiles in human review scope), a per-repository scope decision that this plan's machinery already supports;
- the
default-branch-protectioncriterion, which stays under Future work.
What the survey found¶
The plan's status record was wrong in every phase. Phase 0
still read "UNDERWAY", Phase 2 step 6 and every Phase 4 step were
still open, and there was no Execution table. 4b15e15 closes out
Phases 0--4. 9acc3bc, the commit that wrote this plan, corrects the
false claims where they are made, not by rewriting the history: a dated
note at the top of "Staleness via stamped blob SHAs and git hooks",
another under "Open questions", the answers to the three open
questions, and a step-by-step "What actually landed" under Phase 4.
cde84bb deliberately left this file as "a historical record" when
it removed the hooks, and PLAN-review-coverage.md phase 5 made the
same call. The notes respect both decisions, so a later step need not
redo them.
Phase 4 landed in fourteen pieces that nobody recorded, besides
the work later plans did under their own names. The ranges below are
every first-parent landing on main that touched the review tracking
paths and is not claimed by another plan's Execution record.
scripts/review-tracking.py, scripts/*test_review_tracking.py,
docs/code-review-tracking.md, templates/review-tracking/,
.claude/skills/review-tracking-adoption/,
scripts/audit/checks/review.py, the two review criterion specs,
tools/*review*.sh and .github/workflows/prune-reviews.yml were
searched with git log --first-parent main. Every landing was then
checked for single or double parents with git log -1 --format=%p.
These are the diffstats the audit must reproduce:
| # | Range | Landed | What | Files | Diffstat |
|---|---|---|---|---|---|
| 1 | d180c39^..d180c39 |
2026-07-09 direct | Phase 1 conventions | 5 | +244 -6 |
| 2 | 0957d15^..ee6d4ea |
2026-07-09 direct | Phase 2 script, hooks, bootstrap rule, next workspace, derived dirs |
8 | +854 -60 |
| 3 | a3b7f50^1..a3b7f50 |
#9, 2026-07-18 | Hooks removed; run by hand | 7 | +93 -130 |
| 4 | ed10382^..ed10382 |
2026-07-24 direct | Code plus docs as the default scope | 1 | +24 -7 |
| 5 | 079e34e^1..079e34e |
2026-08-05 local merge | YAML into review scope | 1 | +25 -12 |
| 6 | ba4a6a7^..ba4a6a7 |
2026-08-07 direct | Scope example: JavaScript and protobuf | 1 | +16 -7 |
| 7 | b51f550^..b51f550 |
2026-08-08 direct | review-tracking-adoption skill |
4 | +110 -0 |
| 8 | ccaac6e^1..ccaac6e |
#14, 2026-08-11 | Review marks exempt from rewriting hooks; the review-marks-pre-commit check |
5 | +209 -0 |
| 9 | 127079e^1..127079e |
#15, 2026-08-11 | Narrow that exclude | 4 | +143 -43 |
| 10 | a33e694^1..a33e694 |
#36, 2026-08-21 | This repository adopted | 7 | +172 -0 |
| 11 | 44748d4^1..44748d4 |
#56, 2026-08-27 | stamp announces out-of-scope reviews |
3 | +171 -10 |
| 12 | 0ba8920^1..0ba8920 |
#62, 2026-08-29 | Stale-stamp CI gate dropped | 3 | +19 -43 |
| 13 | 39f8d5b^1..39f8d5b -- ':!.vscode' ':!REVIEWS.md' |
#72, 2026-08-31 | review-scope-completeness and scope-orphans |
11 | +515 -20 |
| 14 | f624d26^1..f624d26 |
#236, 2026-10-09 | next picks from a subtree, in scope order |
3 | +204 -7 |
The total is 63 file-diffs, +2,799 -345. Ranges 1 and 2 repeat the
reconstruction that PLAN-push-audit-phase wrote here in August,
which this section's earlier text recorded as four direct commits.
They are written as one first^..last range because the four commits
are consecutive on main. Range 5 is a local merge with two parents,
so ^1 reads it whole, and it is the same diff as its only branch
commit 9c4821a. Range 3 is #9's merge commit, and its only branch
commit is cde84bb, the one the correction notes cite. Range 13 is a
review-session pull request that also carried a criterion. The
pathspec removes its marks, which record a review session rather than
the plan's work: 15 files and +771 -42 unscoped.
Landings that touched these paths and are deliberately not in the table, with whose record covers each:
PLAN-review-coverage.md: #11 (b677b61), #139, #146. Its phase 6 audited them.PLAN-review-import.md: #177, #186, #203, #249, #251, #254, and the re-syncs in actions, hunkydory, kerbside and ryll. Its phase 5 audited them.PLAN-audit-scripts-restructure.md: #79, which moved every check out ofaudit-check.py.PLAN-review-unit-size.md: #160.PLAN-consistency-audits-v2.md: #47 (e3d1333) as a whole, includingba3876a. That commit taughtstampto refuse a stale stamp, and it is the one review-tracking change inside a PR that another plan has recorded. The cav2fixPRs (#266 to #285) are also that plan's.- Not review-tracking work: #17's fixture git-environment scrub
(
4697409),805cb21's lint pass and #167's test-suite move (b9e8b6c). Each touched these files only as one of many. - Review sessions (#47, #52, #72, #167, #277, #292, #296, #301, #303) commit marks, and that is attestation, not plan work.
The tree has moved under every range. Map each finding to a current path before reporting it (D5.3):
- The check functions that #14, #15 and #72 added to
scripts/audit-check.pyare now theReviewMarksPreCommit,ReviewCoverageandReviewScopeCompletenessclasses. They are atscripts/audit/checks/review.py:130,:172and:247, and registered atscripts/audit/registry.py:86-88. scripts/test_audit_check.pyis gone. The criteria's tests arescripts/tests/test_review.py.scripts/test_review_tracking.pyis nowscripts/tests/test_review_tracking.py(b9e8b6c). This correctsPLAN-review-coverage.md's phase 6 survey, which was true when written.audits/workflow-standards.mdis nowdocs/audits/workflow-standards.md..pre-commit-hooks.yamlis gone (range 3). Its hook ids werereview-stampandreview-prune.
Every file the audit would report against carries a current human
review mark. For each live file the ranges touched, the REVIEWS.md
stamp equals git rev-parse HEAD:<path> at aa8d259. That holds for
scripts/review-tracking.py, scripts/audit/checks/review.py, both
test modules, docs/code-review-tracking.md, the two criterion specs,
docs/audits/workflow-standards.md, the adoption skill, both tools/
scripts, prune-reviews.yml, ci.yml, .pre-commit-config.yaml,
README.md, AGENTS.md and ARCHITECTURE.md. All were reviewed
between 2026-09-27 and 2026-10-21. .vscode/review-scope.toml is
tracking machinery and never carries a mark. A person has therefore
read every file in its present form. That shapes D5.3 and D5.7: what
whole-file review does not do is read a diff for what it removed, who
consumed it, or how two landings interact.
sfui and kerbside-client carry stale copies of the review tracking
CI. templates/review-tracking/README.md says all three files
"must stay byte-identical to the template in every adopted
repository". Blob hashes fetched on 2026-10-24 with
gh api repos/shakenfist/<repo>/contents/<path> show this:
| Repository | prune-reviews.yml |
ci-prune-reviews.sh |
review-tracking.sh |
|---|---|---|---|
| actions, hunkydory, kerbside, ryll | identical | identical | identical |
| sfui, kerbside-client | differs | differs | identical |
Both adopted on 2026-09-27 (sfui#39, kerbside-client#10). The
template directory then took eleven commits on 2026-10-13 and
2026-10-14, 99a2783 to 467b09a. Several of those isolate the
prune job from shared runner state: 9556eba, 014f82e, ba549e4
and 130d700. The
re-sync wave that PLAN-review-import.md phase 5 audited covered the
four older adopters only. No criterion measures the byte-identity
the README requires, which is why nothing has reported this. D5.5
files it as development#307.
PUSH-AUDIT.md agrees with the tree on every point this phase
leans on: AUDIT_RANGE throughout, the six-file rule pointing at
docs/consistency-audits.md, and the suites 2b names, all of which
now sit under scripts/tests/. Nothing else disagreed.
Review marks this phase can stale: none. The phase edits only
this plan, and .vscode/review-scope.toml excludes docs/plans/*.
Decisions¶
D5.1. The audit reads the fourteen ranges in the survey table,
each separately. They are not joined into one span, because
fifteen weeks of unrelated work sits between range 1 and range 14.
Each agent runs every check once per range. Each reports a totals
table, one row per range, with files, insertions and deletions. A
report whose totals do not match the survey's is re-run, not
triaged, as PUSH-AUDIT.md requires. Each brief below carries the
ranges literally, not as $AUDIT_RANGE.
D5.2. Work another plan owns is cited, not re-audited. This is
the same rule plan-push-audit-phase gives for cross-repository
landings, applied to cross-plan ones. PLAN-review-coverage.md and
PLAN-review-import.md are both Complete, and their audits
covered the steady-state machinery and the template. Reading those
ranges again would double the cost and turn up findings that already
carry dispositions there. The exclusion list in the survey is the
boundary. An agent that finds a defect it can only attribute to an
excluded range reports it as outside this plan, so the management
session can check it against that plan's Outcome before deciding.
D5.3. Findings are reported against today's tree, and the ranges
are read for intent. This is PLAN-review-coverage.md D6.3,
adopted unchanged, and the reasoning is stronger here. A finding is
only actionable where a fix would land, and for most of these ranges
that is a different path. The survey also shows a person has read
every live file in its current form. Each agent therefore states,
for each finding, the current path and line, or that the code no
longer exists. A finding that later work already resolved is
reported as resolved, with the commit, not dropped.
D5.4. The adoption pull requests in other repositories are listed, not re-audited. The block expects each cross-repository landing to carry its own audit and be cited. None of these did, because all of them predate the block or adopted a template whose own audit was cited:
- ryll#144, #146, #170;
- kerbside#214, #219, #254;
- actions#24, #49;
- hunkydory#8;
- sfui#39;
- kerbside-client#10.
What they added falls into three kinds, and none needs a fresh audit:
- Hook wiring. ryll#170 removed it, the hooks are gone, and D5.6 checks that nothing still references them.
- Copies of the CI template. These are byte-identical in four
repositories, and
PLAN-review-import.mdphase 5 audited the copies. The two that are not identical are D5.5's subject. - Per-repository scope configs and
.gitignorelines. The dailyreview-scope-completenessandreview-marks-pre-commitcriteria measure these, and they are policy rather than code.
kerbside#254 (skipping functional tests on review-only pull
requests) is the one piece of real CI logic among them. The
review-tracking-adoption skill re-verifies it. It is named here so
the gap is stated rather than silent.
D5.5. The sfui and kerbside-client drift is filed, not fixed or
audited here. Re-syncing two repositories is a fix with its own
review, and the audit's own findings land as their own pull request.
The missing measurement belongs to a criterion, and a criterion has
a blast radius this phase should not take on. A new
review-tracking-template check would newly fail exactly those two
repositories today, and probably none elsewhere. It belongs in the
issue, with that count. It was filed during planning, as
development#307, so the Outcome can cite it.
D5.6. One extra check: nothing still consumes what the plan
removed. plan-push-audit-phase item 4 is not in this
repository's PUSH-AUDIT.md, which never had to ask it. This plan
removed several things:
- a whole published interface,
.pre-commit-hooks.yamland its hook idsreview-stampandreview-prune; - a test assertion (range 12);
- a set of
audit-check.pyfunctions, which #79 moved.
A repository still pinning the hooks would fail on its next
pre-commit autoupdate, not today. Nothing in the fleet would
report it. The check is a grep across every fleet clone, and the
greps are stated in the report.
D5.7. Wave 2 runs in full, at the runbook's settings, raised to high for 2a and 2b. This is the decision most likely to be argued with. Every live file has had a whole-file human read since these ranges landed. Later plans' push audits read most of the steady state. The case for spending four agents on July's diffs is weak if the question is "is this code good". It is not the question. Each wave 2 brief below is aimed at what neither of those reads covered:
- what a range removed;
- whether a later range silently undid an earlier one, as range 12
undid part of
ba3876a's guard; - whether the documentation still tells one story across fourteen edits;
- the token and runner surface that range 10 introduced to this repository.
Skipping a dimension because the code "has been reviewed" would
also be the first audit to argue its way out of the mandatory phase.
That argument belongs in plan-push-audit-phase, with evidence, not
in one plan's execution. 2a and 2b go to high because they carry the
path mapping. 2c and 2d keep the runbook's settings.
D5.8. The phase closes the plan. Where the audit finds nothing,
step 5h sets Phase 5 and the index row to Complete in this
phase's own pull request, and records no Merged cell. Where it
finds something, the findings pull request is the carrier, as
plan-phase-landing requires.
Step plan¶
Steps 5b to 5f are independent, and run in parallel once 5a's totals
are in hand. Every brief below must carry the fourteen ranges
literally, copied from the survey table with their pathspecs, and
the totals the agent must reproduce. Every agent reports and does not
fix, and none prunes, regenerates or commits REVIEWS.md.
| Step | Effort | Model | Isolation | Brief for sub-agent |
|---|---|---|---|---|
| 5a | medium | sonnet | none | Wave 1 of PUSH-AUDIT.md, in this worktree, which differs from origin/main only in this plan. Run git fetch origin, then pre-commit run --all-files once on a clean tree. Then run every grep in the wave 1 block once per range, for the fourteen ranges in the survey table, by exporting AUDIT_RANGE to each in turn. For range 13, append the pathspec ':!.vscode' ':!REVIEWS.md' to each command's own pathspec. First run git diff --shortstat per range and confirm it matches the table's files and diffstat. Stop and report if any does not, because a mismatch means the range was read wrong. Report every hit with a verdict: accepted or blocking, and why. Expect and explain, rather than ignore: paths under audits/ and scripts/test_audit_check.py in ranges 8, 9 and 13 predate the restructure; REVIEWS.md moves inside range 10, which is adoption working; and the plan-reference grep excludes *.md, so a hit in docs/code-review-tracking.md means its pathspec was dropped. No commit. |
| 5b | high | sonnet | none | Wave 2a, code quality, per PUSH-AUDIT.md, over the fourteen ranges, taking 5a's report as input. Apply D5.3: read each range for intent, then resolve the code to its current home using the survey's path map before writing a finding. Three questions matter most. (1) Did a later range undo an earlier one without saying so? Range 12 (0ba8920) deleted a test that asserted every stamp matches its content. ba3876a, inside #47 and outside this audit, added that test as one of two guards. Confirm that the other guard, stamp refusing a stale stamp, still exists in scripts/review-tracking.py at HEAD, and say where. (2) stamp's out-of-scope announcement (range 11) and scope-orphans (range 13) both decide what is in scope. Do they use one matcher, or two that could disagree? (3) The next subtree and order options (range 14): what happens with a subtree that matches no in-scope file, or a scope file whose include order lists a pattern twice? Apply the comment-proportion and source-file-size blocks to scripts/review-tracking.py at HEAD, and give its line count. Report findings with the current file and line, blocking or advisory, and the range that introduced each. |
| 5c | high | sonnet | none | Wave 2b, test review, per PUSH-AUDIT.md, over the fourteen ranges, applying D5.3. Tests now live in scripts/tests/test_review_tracking.py and scripts/tests/test_review.py. Ranges 8, 9 and 13 wrote theirs into scripts/test_audit_check.py, which is gone, so the first question is whether every assertion those ranges added survived the moves. Name any that did not. For each subcommand and check the ranges introduced or changed, find a test exercising each status it can return: next with a subtree and with ordering; stamp's out-of-scope path; scope-orphans; ReviewMarksPreCommit pass, fail and not_applicable; ReviewScopeCompleteness pass, fail and not_applicable. Name the gaps. Range 12 removed a test. Say whether anything still proves a committed stamp matches the content it names, and if nothing does, whether the review-coverage criterion makes that redundant. Name the test that would fail if prune stopped running, or say there is none. Report grouped by file. |
| 5d | medium | sonnet | none | Wave 2c, documentation review, per PUSH-AUDIT.md, over the fourteen ranges. docs/code-review-tracking.md was edited by every one of them. Check the following: (1) no page outside docs/plans/ describes the removed hooks as live, so grep docs/, README.md, AGENTS.md, ARCHITECTURE.md, templates/ and .claude/skills/ for pre-commit-hooks, review-stamp, review-prune, post-merge, post-checkout and default_install_hook_types, and judge each hit; (2) the default scope (code plus docs plus YAML, from ranges 4 to 6) is stated once, and agrees between docs/code-review-tracking.md, the adoption skill and this repository's own .vscode/review-scope.toml comments; (3) the template README's claim that copies "must stay byte-identical" is not contradicted by any page that tells an adopter to edit a copy; (4) the adoption procedure in the doc and in .claude/skills/review-tracking-adoption/SKILL.md lists the same steps in the same order. The skill was added in range 7 and edited since. Also check that the two *Corrected on 2026-10-24* notes in this plan's analysis are accurate against the doc. Report as a bullet list. |
| 5e | high | opus | none | Wave 2d, security, per PUSH-AUDIT.md, over the fourteen ranges. Read the actual code at HEAD. The surface this plan introduced to this repository is range 10 (a33e694). It added .github/workflows/prune-reviews.yml and tools/ci-prune-reviews.sh, which push to main with a bot token. Both have since been replaced by the template copies, which PLAN-review-import.md phase 5 audited. So the question is narrow: did range 10 add anything to ci.yml (12 lines) or elsewhere that the template replacement left behind and that still runs? Read ci.yml at HEAD for it. Then the path surface: scripts/review-tracking.py builds paths from .vscode/review-scope.toml, from weAudit JSON and from next's subtree argument (range 14). Apply the path-traversal block to each, bearing in mind that the first two need a merged pull request to change and the third is a local argument. Then stamp (ranges 2 and 11), which shells out to git with paths from weAudit JSON: is any of them passed where git would read it as an option? Report severity, current file and line, class, and fix. |
| 5f | medium | sonnet | none | The removed-interface consumer check, D5.6. Fetch every Shaken Fist clone beside this one under src/shakenfist/, skipping -wt- worktrees. Use gh repo list shakenfist --limit 200 to find any repository with no local clone, and use gh search code --owner shakenfist for those. Grep each default branch (git grep <pattern> origin/HEAD) for: review-stamp, review-prune, pre-commit-hooks.yaml together with development, repo: https://github.com/shakenfist/development in any .pre-commit-config.yaml, test_every_stamp_matches, test_audit_check, check_review_coverage and check_review_marks. State every command run and the repositories covered, and count the repositories searched. A hit in a plan file or a changelog is history, not a consumer, so say which each hit is. No commit, no issue, no edits in any repository. |
| 5g | high | opus | none | Management triage, in the management session. Read all five reports and check each one's totals table against the survey's. Work the management checklist at the end of PUSH-AUDIT.md. Decide each finding blocking, advisory or declined. Check every finding attributed to an excluded range against the owning plan's Outcome (D5.2). Write an Outcome subsection here: the ranges and totals seen, what wave 1 found, what each of 5b to 5f found, and for every finding where it will be fixed or why it was declined. Cite development#307, and record D5.4's list as not audited, with the reason. |
| 5h | low | sonnet | none | Close the plan, per D5.8. If 5g recorded no findings to fix, set Phase 5's row and the plan's row in docs/plans/index.md to Complete, leave the Merged cell empty, and update the index Intent to say the push audit ran and found nothing. Otherwise leave both rows In progress and say in the Outcome that the findings pull request carries the close-out: it sets both to Complete and fills the Merged cell with this phase's merge commit. Fill the Bugs fixed during this work section from what the plan's landings fixed, at least the auto-stamp of a stale mark that Phase 2 step 5 records, or say none were recorded. Run pre-commit run --all-files. Commit subject: Record the review tracking push audit. |
Risks and mitigations¶
- An agent reads the wrong range and reports a clean audit. This is the characteristic failure of auditing merged work. Every brief carries the ranges literally, and every report carries a totals table. In 5g, the management session compares each table to the survey's, row by row, before reading any findings.
- Range 13 is read without its pathspec, and the review marks in it get audited as code. The unscoped total is +771 -42 and the scoped total is +515 -20, so the totals check catches it.
- A finding is reported against a path that no longer exists. D5.3 and the survey's path map cover this. 5g rejects any finding without a current path or a "no longer exists" statement.
- Two plans audit the same diff and disagree. D5.2's exclusion list is explicit. 5g checks findings against excluded ranges against the owning plan's Outcome before deciding them.
- The consumer grep misses a repository with no local clone. 5f
reconciles against
gh repo listand states its count. The management session checks that count against the organisation's repository count. - The issue D5.5 files reads as a fix request, and someone re-syncs without the criterion. The drift then recurs at the next template change. The issue asks for both, and says the criterion is what prevents recurrence.
Definition of done¶
- The Outcome's totals table matches the survey's on every row. This prints fourteen labelled lines that must equal the table's Files and Diffstat columns. Range 13 prints last, because it needs its pathspec:
for r in d180c39^..d180c39 0957d15^..ee6d4ea a3b7f50^1..a3b7f50 \
ed10382^..ed10382 079e34e^1..079e34e ba4a6a7^..ba4a6a7 \
b51f550^..b51f550 ccaac6e^1..ccaac6e 127079e^1..127079e \
a33e694^1..a33e694 44748d4^1..44748d4 0ba8920^1..0ba8920 \
f624d26^1..f624d26; do echo "$r $(git diff --shortstat $r)"; done
echo "39f8d5b^1..39f8d5b $(git diff --shortstat 39f8d5b^1..39f8d5b -- ':!.vscode' ':!REVIEWS.md')"
- Every finding in the Outcome has a current path and line or a "no longer exists", and a disposition: fixed in a named pull request, or declined with a reason.
- 5f's report names every repository searched, and their count equals
gh repo list shakenfist --limit 200 --json name --jq length, archived repositories included. Searching those as well costs nothing and leaves no gap to name. - The Outcome links development#307.
- Phase 5's row and the plan's index row agree with the Outcome. If
it records findings to fix, both read
In progress, andgrep -c '^| 5. Push audit | In progress | |' docs/plans/PLAN-code-review-tracking.mdreturns 1. If it records none, both readCompleteand theMergedcell is empty. The anchor matters: without it the grep also matches this bullet, which quotes the row. pre-commit run --all-filespasses, andgit diff origin/main --staton the branch lists only this plan anddocs/plans/index.md.
Back brief¶
Before running 5a, back brief Mikal: the fourteen ranges, the exclusion list, and D5.7. If the attribution of landings in the survey looks wrong to him, that is the cheapest point to correct it. Every brief inherits it.
Outcome¶
The audit ran on 2026-10-24 (AEDT), in the order the step table gives. 5a ran first and alone. 5b to 5f then ran in parallel as read-only reporters, and 5g's triage below is the management session's. The back brief was taken first, and Mikal confirmed the attribution of landings in the survey unchanged.
Ranges. All five reporting agents reproduced the survey table exactly, every one of the fourteen rows, including range 13 with its pathspec. The management session re-ran ranges 2 and 13 by hand. No report was re-run.
Wave 1 (5a): clean. pre-commit run --all-files passed. The only
grep hits were these, all accepted:
- stdlib imports in range 2;
- one plan citation in range 2's
.pre-commit-hooks.yaml, which range 3 deleted with the file; - two files over the size threshold at HEAD,
scripts/review-tracking.py(1,560 lines) andscripts/tests/test_review_tracking.py(2,135); - paths that predate the restructure.
Wave 2.
- 5b (code quality): ten advisory findings, none blocking.
- 5c (tests): no assertion lost in either move, 162 tests passing.
35 mutants ran under
timeout, and four survived. - 5d (documentation): no gaps found. One imprecision, in this plan's
own correction note, is fixed in
8e1b850. - 5e (security): one medium finding, two low and two informational,
with four proofs of concept. There is no option injection: every
path from weAudit or the scope config reaches git prefixed with
:orHEAD:, so none can start with-(PoC 3).shell=Trueappears nowhere inscripts/ortools/. - 5f (consumers of removed interfaces): no live consumer across the
organisation's 40 repositories. 26 were searched by fetched clone
and 14 by
gh; 10 of the 14 are archived.
Every finding below was checked against the tree at aa8d259 by the
management session. F1 and F2 were reproduced by running the code,
and the rest by reading it.
Findings to fix¶
These land as one follow-up pull request against main after this
phase merges. That pull request sets this phase's Status to
Complete and fills its Merged cell, and sets the index row to
Complete, as plan-phase-landing requires. Until then both rows
read In progress. development#315 tracks the findings, so a slip in
the follow-up stays visible outside this plan.
- F1 (medium, security; range 2, reached through #11).
load_json()atscripts/review-tracking.py:283opens with no symlink, containment or size check.statusandscope-orphansrun it inside every audited repository's checkout, fromReviewCoverage(scripts/audit/checks/review.py:200) andReviewScopeCompleteness. A merged.vscode/x.weaudit -> /dev/zeroin any adopter used 1 GiB in under half a second (5e PoC 2, re-run here against a copy of HEAD's script). The runner is the shared[self-hosted, static]pool, and the 60-second timeout does not bound memory. The audit's own guardedRepo.read()(scripts/audit/repo.py,READ_MAX_BYTES) refuses the same link, so this sidesteps a guard the package already has. No content disclosure was achieved.PLAN-review-coverage.md's audit recorded the timeout case only, so this is new. Fix: route every read through one guarded opener: realpath within the toplevel, regular files only,O_NOFOLLOW, and a size cap matchingREAD_MAX_BYTES. - F2 (low, security; range 2).
write_json()at:291and theREVIEWS.mdwrite at:551follow symlinks. A newline in a weAudit path injects a line into the target (5e PoC 4). This needs a merged pull request, by someone who can usually already run code on the runners, so it is low. Fix: the same opener for writes, and reject control characters in marked paths. - F3 (low, integrity; range 2).
stampresolvesblob_sha(':%s' % path)(:633,:645). A weAudit path/xbecomes:/x, git's commit-message search, and stamps a commit SHA. It heals itself at prune. Fix:':0:%s'. - F4 (low, latent false pass; range 8, widened by 9).
pre_commit_excludes_review_marks()atscripts/audit/checks/review.py:64accepts anyexclude:line in the file: - a YAML block scalar (
exclude: |) captures|, which matches every path (5b reproduced it); - an exclude on an unrelated hook counts for the rewriting hook, which it does not protect (5c).
Either way review-marks-pre-commit passes a repository whose
rewriting hook still rewrites the marks. None of the six local
adopter clones trips it today. Fix: reject a block-scalar
indicator or an empty value, and require the matching exclude on
the rewriting hook or at the top level. Add tests for both.
* F5 (advisory, robustness; range 2). cmd_prune indexes
stamps[path]['sha'] (scripts/review-tracking.py:696, :705).
For a sidecar entry with no sha it raises KeyError, right after
stamp has told the user to run prune. Fix: .get('sha').
* F6 (test gaps; ranges 8 and 11). Three mutants survived:
dropping mixed-line-ending, pretty-format-json or
file-contents-sorter from FILE_REWRITING_HOOK_IDS
(review.py:42). So did one ignoring partial marks in
stamp's out-of-scope detection (review-tracking.py:628). Fix:
one test per hook id (or a parameterised one), and a partial-mark
out-of-scope case.
* F7 (advisory, small; ranges 2 and 11). The --help text
reflows the module docstring into one paragraph (:1530): use
RawDescriptionHelpFormatter. The out-of-scope banner says
"excluded by review-scope.toml" (:587) for files that are only
unmatched: say "not in scope".
* F8 (informational; range 13, and #11). Both review checks run
the child without environment_without_credentials()
(review.py:200, :285). SfuiVendor uses it. The child runs no
repository-chosen code today. Fix: pass the scrubbed environment,
in the same PR as F1.
How the findings were resolved¶
The findings pull request fixed every item above, in three commits, each through a sub-agent and reviewed in the management session:
| Finding | Commit | Proof |
|---|---|---|
| F1, F2, F8 | 30bf5aa Guard every review state read and write. |
11 mutants killed; 5e PoC 2 re-run, refused with exit 2 |
| F3, F5, F7, F6 (partial marks) | a914f48 Fix four small review-tracking.py defects. |
5 mutants killed |
| F4, F6 (hook ids) | fc52a33 Judge review-mark excludes per rewriting hook. |
19 mutants killed; every adopter's verdict unchanged |
F1, F2 and F8 share one commit because they share the new I/O helper's hunks and the tests that drive the child.
The pull request's automated review found F2 only partly fixed. The
first commit guarded marked paths, but the reviewer name, stamp dates
and shas, imported fields and line numbers also reach a REVIEWS.md
cell. 00d4425 guards every cell, table-escapes |, refuses before
writing in stamp, prune and import, and refuses a malformed mark
entry without a traceback (21 mutants). aeb765e refuses malformed
JSON and format characters, and stops dropping an import whose date
is null (5 mutants).
Findings declined, with reasons¶
- The
nextwording in both wrapper copies is stale (tools/review-tracking.sh:11and its template): it says "random", but range 14 added subtree andlistedorder. This is not fixed alone, because the template copies must stay byte-identical and an edit here means a re-sync of six repositories. It goes with the re-sync development#307 already asks for, and is noted there. .vscode/review-scope.tomlis inci.yml'spaths-ignore(.github/workflows/ci.yml:39; 5b and 5e F5). A scope-only pull request skips lint, the smoke run and the automated reviewer, although the scope file is policy rather than review state. This is not a defect in this repository's copy. It is the fleet convention in adoption step 8 (docs/code-review-tracking.md:318) and the adoption skill, carried by seven repositories. Changing it is a convention decision for Mikal, and the findings pull request either takes it or records why not. Declined there, on 2026-10-24, and filed as development#316. The change touches step 8, the adoption skill, any criterion asserting the four-path set and seven repositories'ci.yml, so it needs its own change and review rather than being folded into security fixes.- The three review checks repeat the scope-file skip in
run()instead ofapplies()(review.py:154,:189,:280). Verdicts are unaffected. This is a style point outside any range's intent, and it belongs to the next change that touches them. - The 3.11 floor (
tomllib,review-tracking.py:237) is undocumented for by-hand use. The audit runner is guarded (ci.yml:97). The failure is a clearImportError, and no by-hand user is known to be on an older interpreter, so this is declined until one is. - Comment proportion: the
report_out_of_scopeandscope_orphansdocstrings narrate more than they need (:556-573,:1393-1413). Advisory. Both carry their reasons, and a person read them in full on 2026-10-16. - Source file size:
scripts/review-tracking.pyis 1,560 lines, over the 1,500 that wants a stated reason. About 1,000 of them are the import machinery fromPLAN-review-import.md, outside this plan. Its seam is clean (the imports file, the history walk,verify_commit,cmd_import), and splitting it is recorded under Future work rather than done in an audit. - Smaller gaps, declined for their size, as advisories:
nextignores a mistyped path among valid ones;scope-orphanshas no assertion on its zero-orphan message;prune-reviews.yml's triggers are pinned only by byte comparison with the template;- range 13 did not state its blast radius, a historical record that the compliance page now carries.
What the audit did not do¶
- It did not audit the adoption pull requests in other repositories, per D5.4. They are listed there, with kerbside#254 named as the unaudited CI logic.
- It did not read
import(review-tracking.pyaround:781-1300), which belongs toPLAN-review-import.md. - It did not re-review the template copies of
prune-reviews.ymlandci-prune-reviews.sh, which that plan's audit read. - The 14 uncloned repositories in 5f were covered by
gh search code, which only indexes default branches and can lag. 5f gives their result as indicative.
Administration and logistics¶
Success criteria¶
We will know this plan has been successfully implemented when:
- A large codebase can be reviewed across many sittings without losing track of what has been covered.
- Every "reviewed" mark is attributable to a signed commit binding reviewer, date, and exact file content.
- "What needs (re-)review" is answered mechanically -- stale marks are pruned automatically by git hooks when history moves, rather than remembered.
- The in-editor view (weAudit ticks) never shows a stale review for longer than the gap between a pull and the next weAudit refresh.
- Anyone browsing an adopted repo can see what has been reviewed,
by whom, and when, from a generated
REVIEWS.mdthat is never out of step with the review state it is committed alongside. - The amount of newly-written software is one small script plus pre-commit configuration.
Future work¶
- Fork weAudit (or upstream a patch) to record a per-file blob hash at mark time and display staleness natively, removing the need for the prune step.
- Integrate review coverage into a cross-project dashboard alongside the consistency audit status.
-
Consider whether LLM-assisted pre-review (Claude flagging candidate inconsistencies for the human pass) is worth adding once the human workflow is established.
-
A
default-branch-protectionconsistency audit item, surfaced by Phase 1: nothing currently audits branch protection (the github-security audit covers Dependabot, secret scanning, and CodeQL only), and ryll'sdevelopturned out to have no branch ruleset at all. Every project's default branch should carry at least thedeletionandnon_fast_forwardrules, checkable mechanically viagh api repos/<org>/<repo>/rulesetsinaudit-check.py's existing gh-based style. This is general repo hygiene rather than review-specific, so it can proceed independently of this plan. - A criterion holding every adopter's review tracking CI copies
byte-identical to
templates/review-tracking/, as that template's README requires. development#307 records the two repositories that have drifted and asks for both the re-sync and the criterion. - Split the import machinery out of
scripts/review-tracking.py, which is 1,560 lines, about 1,000 of themimport. The push audit found the seam already clean.
Bugs fixed during this work¶
This section should list any bugs we encounter during development that we fixed.
- A scoped pre-commit run while the ryll wiring was being prepared
auto-stamped a stale mark at the file's current content. The
bootstrap rule in
9f8d47fmade bootstrap land with the wiring, and is now adoption step 6 (Phase 2 step 5). - weAudit records derived directory entries in
auditedFiles, and the script treated them as files (ee6d4ea). -
stampskipped an already-stamped file whose content had since changed, silently and with exit zero, so a stale mark could reach the default branch. Fixed inba3876a, which landed under #47 and is recorded inPLAN-consistency-audits-v2.md. -
Review state was read with a bare
open(). The daily audit'sstatusandscope-orphansruns, inside each audited checkout, would follow a committed symlink out of the clone or into/dev/zero. Writes followed symlinks, and a newline in a marked path could inject a line intoREVIEWS.md. Fixed in30bf5aa, which also runs both review criteria without credentials, and completed in00d4425andaeb765e, which guard every otherREVIEWS.mdcell. stampread a marked path beginning with/as git's commit-message search, andpruneraisedKeyErroron a stamp with no sha. Fixed ina914f48, with two smaller wording fixes.review-marks-pre-commitpassed a block-scalarexclude: |, and an exclude on an unrelated hook. Fixed infc52a33.
The push audit found all three (Phase 5 Outcome, F1 to F8), and development#315 tracked them.
Back brief¶
Before executing any step of this plan, please back brief the operator as to your understanding of the plan and how the work you intend to do aligns with that plan.