Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The fleet's checks and review tooling, run by hand, with the deterministic ones

Python only, and standard library only, with markdown-it-py the one exception. `build_dist.py`, `canonical_review.py`, `carry.py`, `pr_review.py`, `pr_review_copilot.py`, and `pr_cleanup.py` declare it in their inline script metadata. `build_dist.py` and `carry.py` import it only on the path that reads a region, since `skills_install.py` and `handoff.py` import them under a plain `python3`. Every check script is read-only and exits non-zero on a finding. `build_dist.py`, `local_review.py`, `canonical_review.py`, `handoff.py`, and `skills_install.py` below are the exceptions, since a generator, two recorders, an installer, and the handoff chain all exist to write. `build_dist.py --check` and `skills_install.py --report` are read-only modes for CI and for asking without changing anything. `local_review.py status` and `check` answer without recording anything, but they are not read-only in that same sense: reading the working tree stages it, which writes inside `.git` and runs any `filter.*.clean` the repository configures, and only the receipt is left untouched. `canonical_review.py` reads files rather than staging them, so its `list`, `status`, `sweep`, and `report` are read-only outright, and `record` alone writes.

The directory separates its kinds by name and by tree. A gate checks and exits non-zero on a finding, and its name carries a `_lint` or `_gate` suffix saying what it gates. The `prose_lint.py` and `repo_gate.py` entry points delegate to the action-owned implementations that gate this tree in CI, and `host_gate.py` gates the machine it runs on. A utility does work rather than gating and carries no suffix: `backlog_metrics.py`, `build_dist.py`, `canonical_review.py`, `handoff.py`, `local_review.py`, `pr_cleanup.py`, `pr_review.py`, `pr_review_copilot.py`, `skills_install.py`. `gh_graphql.py` is a library with no command line, the GraphQL transport and cursor walker that `backlog_metrics.py`, `handoff.py`, `pr_cleanup.py`, and `pr_review.py` import from beside them. `pr_review_checks.py` and `pr_review_rounds.py` are libraries with no command line as well. They are the pure readers `pr_review.py` imports for the head's checks and its review rounds. The unit tests live apart at the repository root under [`tests/`][tests], one `test_<script>.py` per script, driving the canonical implementations against input they must reject. The gate's two reader modules are tested through `test_pr_review.py`. A gate nobody has watched fail is a gate nobody knows works. Where a case covers a table it reads the live table rather than restating it, and each one asserts a floor on what a healthy run reaches, since a check whose scan matches nothing reports zero findings and reads exactly like a pass.
The directory separates its kinds by name and by tree. A gate checks and exits non-zero on a finding, and its name carries a `_lint` or `_gate` suffix saying what it gates. The `prose_lint.py` and `repo_gate.py` entry points delegate to the action-owned implementations that gate this tree in CI, and `host_gate.py` gates the machine it runs on. A utility does work rather than gating and carries no suffix: `backlog_metrics.py`, `build_dist.py`, `canonical_review.py`, `handoff.py`, `local_review.py`, `pr_cleanup.py`, `pr_review.py`, `pr_review_copilot.py`, `skills_install.py`. `gh_graphql.py` is a library with no command line, the GraphQL transport and cursor walker that `backlog_metrics.py`, `handoff.py`, `pr_cleanup.py`, and `pr_review.py` import from beside them. `pr_review_checks.py`, `pr_review_rounds.py`, and `pr_review_digest.py` are libraries with no command line as well. They are the pure readers `pr_review.py` imports for the head's checks, its review rounds, and the digest rendered from both. The unit tests live apart at the repository root under [`tests/`][tests], one `test_<script>.py` per script, driving the canonical implementations against input they must reject. The reader modules `pr_review.py` imports are tested through `test_pr_review.py`. A gate nobody has watched fail is a gate nobody knows works. Where a case covers a table it reads the live table rather than restating it, and each one asserts a floor on what a healthy run reaches, since a check whose scan matches nothing reports zero findings and reads exactly like a pass.

```sh
python3 tests/test_prose_lint.py
Expand Down Expand Up @@ -187,7 +187,7 @@ uv run --no-project --script scripts/pr_review.py attest 452 --repo ptr727/Proje
uv run --no-project --script scripts/pr_review.py cleanup 452 --repo ptr727/ProjectTemplate
```

**The gate and its readers are separate files.** This script holds the GraphQL and REST plumbing, the verdict, the wait loop, and the writes. The head's checks are read by `pr_review_checks.py` and its review rounds by `pr_review_rounds.py`. Both read the payload the gate already fetched, and neither makes a call or imports the gate. What a reviewer's body says is read by `pr_review_copilot.py`, whose result is a small typed reading rather than a body. The reviewer's wording has changed many times and has no programmatic API, so a change in it is a data edit in `reviewers/` plus one fixture, and never reaches the gate. A part of a head round that the reader cannot place holds the gate, `shapes=HELD` and exit `50`. A finding can sit in it where no count reaches. Its text prints under `UNPLACED ON THE HEAD`, each part cut to `EXCERPT_LINES` lines of `EXCERPT_WIDTH` characters. Beside it prints a fingerprint, the first `FINGERPRINT_HEX` hex characters of a SHA-256 over the set's text. An owner, member, or collaborator clears the hold, `shapes=ACKNOWLEDGED`, with written outcomes posted by `comment --fingerprint`. That comment carries `<!-- fleet-shape-ack: fingerprint=<fp> -->` as an HTML line of its own. A new head carrying new unplaced text has a new fingerprint and owes a new acknowledgment. A part on an earlier round prints under `REVIEWER OUTPUT NOTED` with a one-line note. So does a label nested in a list, a quote, or a placed part. It decides no exit code. `shapes=NOTED` shows only where no head hold takes the field.
**The gate and its readers are separate files.** This script holds the GraphQL and REST plumbing, the verdict, the wait loop, and the writes. The head's checks are read by `pr_review_checks.py` and its review rounds by `pr_review_rounds.py`, and `pr_review_digest.py` renders the `status` and `wait` digest from both. Each reads the payload the gate already fetched, and none makes a call or imports the gate. The digest's header is an ordered list of fields, and its fixed advisory lines are an ordered table. What a reviewer's body says is read by `pr_review_copilot.py`, whose result is a small typed reading rather than a body. The reviewer's wording has changed many times and has no programmatic API, so a change in it is a data edit in `reviewers/` plus one fixture, and never reaches the gate. A part of a head round that the reader cannot place holds the gate, `shapes=HELD` and exit `50`. A finding can sit in it where no count reaches. Its text prints under `UNPLACED ON THE HEAD`, each part cut to `EXCERPT_LINES` lines of `EXCERPT_WIDTH` characters. Beside it prints a fingerprint, the first `FINGERPRINT_HEX` hex characters of a SHA-256 over the set's text. An owner, member, or collaborator clears the hold, `shapes=ACKNOWLEDGED`, with written outcomes posted by `comment --fingerprint`. That comment carries `<!-- fleet-shape-ack: fingerprint=<fp> -->` as an HTML line of its own. A new head carrying new unplaced text has a new fingerprint and owes a new acknowledgment. A part on an earlier round prints under `REVIEWER OUTPUT NOTED` with a one-line note. So does a label nested in a list, a quote, or a placed part. It decides no exit code. `shapes=NOTED` shows only where no head hold takes the field.

**A Copilot round is requested at defined moments rather than on every push.** The one moment is the head of a pull request into the default branch, a promotion among them. A promotion re-reviews content its feature pull requests already carried, so the hosted review runs once per change, at the promotion. On a pull request into any other branch, every head is covered by the recorded local pass the push already owes. That pass lives in the checkout's git directory, where nothing on GitHub can read it, so `attest` publishes it as a comment carrying `<!-- fleet-local-review: head=<sha> base=<branch> findings=<n> -->`. It does so only after confirming the checkout is the pushed head, holds no other change, measures the pull request's own merge base, and has a covering pass in `local_review.py status`. `status` then reads that head as `review_on_head=local` with `coverage=local`, where the attestation comes from an owner, member, or collaborator, stands as an HTML line of its own outside any code, and no round on record states partial coverage with the whole history in view. A hidden attestation comment does not count. `wait` requests nothing on such a head and polls its checks instead, while they can still move the merge. Where it is not attested, `wait` exits `49`, and `--request` asks for a round anyway. The comment's findings count prints beside the reading and gates nothing, since a local pass's findings are advisory. A default branch the payload does not name reads as a promotion, so an unreadable field costs a Copilot request rather than passing a head on a local pass alone.

Expand Down
Loading
Loading