Skip to content

fix: verify no longer fails on added files; gate docs regeneration in CI - #13

Open
jordonpeterson wants to merge 5 commits into
mainfrom
fix/verify-tree-deltas-and-docs-gate
Open

fix: verify no longer fails on added files; gate docs regeneration in CI#13
jordonpeterson wants to merge 5 commits into
mainfrom
fix/verify-tree-deltas-and-docs-gate

Conversation

@jordonpeterson

@jordonpeterson jordonpeterson commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Audited the README against the implementation and fixed what didn't hold up. Two of the findings were code bugs, not doc bugs.

verify failed on any PR that added a file

The README's CI recipe — snapshot two branches, verify --scope /services/api/ — failed on any PR that added a file outside the declared scope, with CODEOWNERS byte-identical:

changed: web/newfile.js  (unowned) → {@org/frontend}
INVARIANT VIOLATED: 1 path(s) changed outside the declared scope

Compare treated a path present in only one snapshot as an ownership change. The snapshots come from different refs, so their trees differ on every real PR.

INV-2 preserves what a path resolved to before. An added path has no before and a deleted one has no after, so neither can violate it. They now surface as added:/removed: lines without failing the check.

This does not weaken the gate. A CODEOWNERS edit that reassigns a subtree still shows up on that subtree's pre-existing files — pinned by the new TestR18_AddedFileDoesNotMaskReassignment. Only a scope whose every file is new to the branch goes unchecked, and there the invariant has nothing to say.

⚠️ Behavior change: TestR18_TreeChangesSurface deliberately pinned the old semantics. It's rewritten as TestR18_TreeChangesSurfaceButDoNotViolate — deltas are still surfaced, they just no longer fail verification. If the strict tree-identity reading was intentional, this should become a flag instead.

make docs was not reproducible

docs/BEHAVIOR.md is generated from test doc comments, but parser.ParseDir returns unordered maps and internal/plan holds both the plan and plan_test packages. "First doc comment wins" picked a different header per run — 12 consecutive runs produced three distinct files. Now walks packages and files in a fixed order, preferring the external _test package. Byte-identical across 15 runs.

New CI gate

Nothing enforced that make docs was ever run, and the file had already drifted. Adds make docs-check (regenerate, fail on diff) to the CI test job. Verified it fails on a stale doc and passes on a clean tree — only meaningful now that generation is deterministic.

README

  • "Adding an owner: co-owner, or sole owner?" — the most common task, and the one where hand-editing silently does the wrong thing. Worked add_owner vs set_owners examples with their real diffs and ownership rows, why appending a rule by hand replaces owners, and what a repo-wide * op actually has to touch. Owner token syntax (the @ is required) was undocumented too.
  • CLI reference for all five commands. --repo, --file, --max-size, --warn-size and --checks were reachable only via --help.
  • "How the planner edits lines" — narrowing-rule insertion was undocumented, though the getting-started example itself synthesizes a line. Also covers the refusal when no narrowing is expressible and the inexact-narrowing warning.
  • verify semantics: omitting --scope asserts nothing changed; how tree deltas are handled.
  • Go 1.24+1.24.7, matching go.mod.
  • Noted that a repeated plan exits 1, which breaks a CI step running it unconditionally.

Verification

make vet, go test -race ./..., make docs-check, make build all pass. Every command in the README re-run end-to-end against throwaway repos, including the CI recipe in both the benign and the genuinely-violating case.

🤖 Generated with Claude Code

jordonpeterson and others added 4 commits August 3, 2026 15:39
parser.ParseDir returns unordered maps, and a directory can hold both the
internal and external test packages (plan and plan_test). "First file with a
doc comment wins" therefore picked a different package header on different
runs: 12 consecutive `make docs` runs produced three distinct files.

Walk packages and files in a fixed order, preferring the external _test
package — its doc comment describes the package's behavior, where the
internal one documents a white-box helper.

Output is now byte-identical across runs, which is what makes the README's
claim that the docs cannot drift from the tests actually true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`verify` counted any path present in only one snapshot as an ownership
change, and therefore as an INV-2 violation. The two snapshots normally come
from different refs, so this failed the CI recipe documented in the README on
any PR that added a file outside the declared scope — with CODEOWNERS
byte-identical:

    changed: web/newfile.js  (unowned) -> {@org/frontend}
    INVARIANT VIOLATED: 1 path(s) changed outside the declared scope

INV-2 preserves what a path resolved to BEFORE. A path the branch added has
no before, and one it deleted has no after, so neither can violate it. Such
paths now land in Result.Added/Removed and print as added:/removed: lines
without failing the check.

This does not weaken the gate: a CODEOWNERS edit that reassigns a subtree
still shows up on that subtree's pre-existing files, which the new
TestR18_AddedFileDoesNotMaskReassignment pins. Only a scope whose every file
is new to the branch goes unchecked, and there the invariant has nothing to
say.

TestR18_TreeChangesSurface pinned the old behavior and is rewritten as
TestR18_TreeChangesSurfaceButDoNotViolate: tree deltas are still surfaced,
they just no longer fail verification.

The usage banner also now states that omitting --scope asserts nothing
changed, and lists the flags it was missing: --file for audit/snapshot,
--max-size/--warn-size for plan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/BEHAVIOR.md is generated from the test suite's doc comments, so it is
only true as long as every change regenerates it. Nothing enforced that, and
the file had already drifted.

Adds a `make docs-check` target that regenerates and fails on any diff, wired
into the CI test job. This is only meaningful now that gendocs is
deterministic — before, the check would have flagged innocent changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gaps found auditing the README against the implementation:

- No CLI reference. --repo, --file, --max-size, --warn-size and --checks were
  reachable only through --help; --checks is a notable omission next to the
  prominent A-1..A-12 table, and --file is the only escape hatch when A-10
  reports more than one CODEOWNERS file.
- Narrowing rules were unexplained. The planner inserts lines the user did
  not write — the getting-started example itself produces one — and can
  refuse when no narrowing is expressible, or warn when a synthesized glob is
  exact today but not provably confined for files added later. New "How the
  planner edits lines" section covers all three.
- The snapshot/verify CI recipe did not say that omitting --scope asserts
  nothing changed at all, nor how added and deleted files are treated.
- "Go 1.24+" understated go.mod, which pins 1.24.7.
- Getting started called a repeated plan a no-op without noting it exits 1,
  which breaks a CI step that runs it unconditionally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 21:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes verify so it no longer fails solely due to tree deltas (files added/removed between refs), makes docs/BEHAVIOR.md generation deterministic, and adds a CI gate to ensure generated docs stay in sync with test doc comments. It also updates the README and CLI help text to reflect the audited/updated behavior.

Changes:

  • Adjust verify.Compare to treat paths present in only one snapshot as informational Added/Removed, not invariant violations; update tests accordingly.
  • Make tools/gendocs walk packages/files deterministically to produce stable docs/BEHAVIOR.md.
  • Add make docs-check and run it in CI; expand README with CLI reference and clarified semantics.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tools/gendocs/main.go Deterministic ordering of parsed packages/files to make doc generation reproducible.
internal/verify/verify.go New Added/Removed reporting for tree-only deltas; invariant violations only for ownership changes on shared paths.
internal/verify/verify_test.go Updates and adds tests to pin the new verify semantics and prevent masking real reassignments.
internal/cli/cli.go Updates usage output and verify printing to surface added/removed paths.
Makefile Adds docs-check target to fail if generated docs differ from committed output.
.github/workflows/ci.yml Runs make docs-check in CI to enforce doc regeneration.
docs/BEHAVIOR.md Regenerated content reflecting updated/added tests and new counts.
README.md Expanded CLI reference and updated documentation for plan/verify semantics and planner behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/verify/verify.go
Comment on lines 73 to 77
// Compare diffs two snapshots. scopes, when non-empty, are CODEOWNERS
// patterns declaring where change is allowed; every change outside them is a
// violation (INV-2 from raw data). With no scopes, ANY change is a violation.
// A scope that fails to compile is a hard error — silently dropping it would
// misreport which changes are in scope (found in review).
Comment thread internal/cli/cli.go
[--repo DIR] [--branch REF] [--file PATH]
snapshot [--repo DIR] [--branch REF] [--file PATH] [--out snap.json]
verify --before before.json --after after.json [--scope PATTERN ...]
(no --scope means: assert NOTHING changed)
Comment thread README.md
Comment on lines +104 to +105
- **Omitting `--scope` asserts that nothing changed at all.** Scopes are the
allowlist; with none, every difference is a violation.
"Add team X to CODEOWNERS" is the most common task and the one where
hand-editing silently does the wrong thing, but the README only stated the
add_owner/set_owners distinction in a table cell. Adds a worked section with
both commands, their real line diffs and resolved-ownership rows, and the
reason appending a rule by hand replaces owners rather than adding to them.

Also documents owner token syntax, which was absent: the @ is required, and
@org/team_a and @team_a both parse but mean different things to GitHub.

The repo-wide `*` example shows the whole plan — the catch-all plus the
amendment of every rule that would otherwise shadow it, and the previously
unowned paths it pulls into ownership.

All examples are verbatim output from the current build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants