Skip to content

Promote Develop: Bind Local Reviews to the Reviewer's Report, Hold Merge on a Running Hosted Review, and Gate Skill Description Length - #3843

Merged
ptr727 merged 18 commits into
mainfrom
develop
Nov 3, 2026
Merged

ptr727 merged 18 commits into
mainfrom
develop

Conversation

@ptr727

@ptr727 ptr727 commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Promotes the changes merged into develop since promotion #3797 to main, the end of an away-mode backlog burndown.

  • #3803 exempts the Roslyn embedded-language marker (// lang=json, language=) from the prose gate's comment rules as a tool directive.
  • #3807 has local_review.py record take --report, the file the reviewer wrote, bind the pass to its sha256, and derive the finding count from it. status gains reportPath.
  • #3809 holds pr_review.py merge while a hosted review is still running on the head, and routes a declined Qodo finding to a dismiss request.
  • #3811 dates an anchored intentRef by the section its anchor names, and lets a driftNote acknowledge a verified intent advisory.
  • #3816 moves the null GraphQL node check into the shared gh_graphql transport as NullNodeError.
  • #3818 explains the workspace-edit default in GOVERNANCE.md and names the aggregator's expression form.
  • #3822 moves the abandon discard to the backlog-burndown orchestrator, aligns the abandoned-branch delete, and holds a grown agent-safety set.
  • #3823 scopes the Husky prose gate to staged paths and adds --skip-generated so a named file inside a generated tree is skipped.
  • #3828 reads Copilot's Open findings: label, drops the duplicate null_node, and matches scripts/README.md to the Merge Gate.
  • #3829 bumps the two stale hub caller snippet pins and documents the fallback's mixed-pair refusal.
  • #3831 keeps an inherited GIT_DIR from rerooting the prose gate's subdirectory git reads.
  • #3832 gives backlog-burndown a disposition for a worker's standalone clone and keeps the agent-safety hold past a park.
  • #3835 makes carry.py check-sections refuse what apply-sections would, and name an open fence.
  • #3836 pins the reusable-workflow stubs by placeholder and ticks the PlexCleaner gates item.
  • #3838 has the runtime-secrets audit dimension detect a bare secrets/ directory from the tracked files that name it.
  • #3839 gates skill descriptions at the 1024-character limit and trims every one over it.
  • #3846 fixes an adjacent doc, an in-file pre-existing defect, and an edge case on new code in the open pull request instead of filing an issue for each, and keeps filing for code that runs elsewhere.
  • #3847 refuses skill frontmatter that parses to a list or a scalar, so the description length gate cannot be bypassed.

Fixes #3770
Fixes #3699
Fixes #3705
Fixes #3789
Fixes #1819
Fixes #727
Fixes #3710
Fixes #3706
Fixes #3623
Fixes #3687
Fixes #3617
Fixes #3757
Fixes #3790
Fixes #3786
Fixes #3785
Fixes #3772
Fixes #1657
Fixes #3443
Fixes #3824
Fixes #3817
Fixes #3805
Fixes #1666
Fixes #3700
Fixes #3821
Fixes #3771
Fixes #3794
Fixes #3825
Fixes #3690
Fixes #1472
Fixes #3813
Fixes #3719
Fixes #1592
Fixes #3845

🤖 Generated with Claude Code

ptr727 and others added 16 commits November 2, 2026 08:39
…ent Rules (#3803)

## Summary

The .NET analyzers read the Roslyn marker `// lang=json` to classify a
raw string literal. Without it, `dotnet format style --verify-no-changes
--severity=info` fails with JSON002. The marker opens in lowercase, so
the prose gate's `comment-case` rule reported it, and `comment-added`
read it as prose.

This adds `lang(?:uage)?\s*=` to the carried
`exemptions.tool_directives` in
`.github/actions/prose-gate/prose-gate.json`. It covers `lang=`, the
`language=` spelling Rider also reads, and spaces around the `=`. A
comment opening on a tool directive is not prose to either rule, so the
lowercase marker passes both.

`tests/test_prose_lint.py` adds three rows to the existing
`test_a_directive_opening_the_body_is_still_exempt` table, one per
spelling. Each fails without the JSON entry.

## Not in this pull request

Issue #1657 was grouped with this one. Its accepted fix passes the
staged path list to the prose gate from the Husky pre-commit hooks
instead of `.`. A named file is judged even inside a `generated_trees`
directory (#3443), so a staged file under `reports/` would then fail the
hook. That change waits on #3443, and this pull request does not carry
it.

Closes on promotion: #3770

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary

- `scripts/local_review.py record` takes `--report`, the file the
agent-skill reviewer wrote, and no longer accepts a typed `--findings`
count. The report's first line is `contentDigest: <digest>`, and each
later non-blank line is one finding. `record` refuses a report that is
absent or names another digest. Otherwise it stores the report's sha256
on the pass as `reportSha256` and derives the count from the report's
lines. There is no dispatch-time stamp, per the recorded decision on
#3699.
- `status` gains a `reportPath` field, the digest-keyed path in the
worktree's git directory where the reviewer writes. A record removes
reports written for any other digest. The fields `pr_review.py` reads
(`covered`, `receiptProblems`, `findings`) are unchanged.
- The receipt format moves to version 2, so a receipt an earlier engine
wrote, its pass carrying a typed count, no longer covers.
- `check`'s printed remedy carries `--report` with the real path, so it
still runs as printed once the reviewer has written its report.
- The `local-strict-review` skill's brief has the reviewer write that
report. "Running It" says to record only once the harness delivers the
reviewer's completion notification, and that text in the driver's own
turn is never that notification. The handback route and "Recording the
Pass" carry `--report`, and on the handback route the dispatcher reads
`reportPath` from `status` itself.
- `scripts/README.md` describes the new `record` contract and names the
uv no-cache exception at both places it promises `uvx` alone can read
the action files (#3705).

Closes on promotion: #3699, #3705

## Local Review Findings

The first push's local-strict-review edit budget ran out with two
`introduced` findings open. Commit d2512ee fixes both, and the pass
over that commit found nothing:

- `introduced`, fixed: "What It Does" said the pass dispatches a
read-only subagent, while the brief now has the reviewer write its
report. It now says the reviewer needs an agent type that can write a
file.
- `introduced`, fixed: `read_report`'s `OSError` and
`UnicodeDecodeError` branches only reworded a failure that `main()`
already reports with exit 2, so they are gone.

Filed rather than fixed here: #3804, the typed `--findings` count
`canonical_review.py record` still takes.

## Verification

- `python3 tests/test_local_review.py`: new cases cover an absent
report, a report for another digest, a report with no digest line, the
derived count and stored sha256, a header-only report, `reportPath`
sitting in the git directory without moving the digest, pruning, and the
removed `--findings` flag.
- `tests/test_pr_review.py`, `tests/test_canonical_review.py`, and
`tests/test_cli_usage.py` pass against the changed engine.
- `prose_lint.py --diff origin/develop`, `ruff format --check`, `ruff
check`, `repo_gate.py`, `spec/validate.py`, and `build_dist.py --check`
are clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… Qodo Findings to a Dismiss Request (#3809)

## Summary

Two Merge Gate gaps `pr_review.py merge` hit on PlexCleaner pull
requests.

**A hosted review still running holds the merge (#3789).** `merge`
squash-merged a feature pull request while CodeRabbit was still
reviewing its head. That happened because an `UNSTABLE` merge state let
an unrequired check stop holding the wait.

- `check_nodes` now records who posted each check. That is a
StatusContext's `creator` login, newly read by the query, or a
CheckRun's app slug.
- `hosted_reviews_running` names each CodeRabbit or Qodo check on the
head still settling. It matches by the poster or by a context named for
the reviewer. The reviewers come from `OTHER_REVIEWERS`. It skips a
reviewer whose newest output is a rate limit posted or edited since the
check began, so that state still clears. An older limit the reviewer has
moved past does not. The held read now carries each comment's
`updatedAt` for that.
- `wait` holds an attested head on such a check whatever the merge state
reads. Its `CHECKS_PENDING` line then adds `(reviewing: <name>)`.
- `merge`'s fresh read refuses it as `CHECKS_PENDING`, which routes
`redrive`. That covers a head `wait` does not poll and a review that
starts after the gate read.
- A `merge` run inside the write scope that merged nothing and routes to
`redrive` or the maintainer now prints `pending=` before `next=`. It
names each check on the head that has neither passed nor failed, from a
fresh read. So a pull request handed to the maintainer names what still
runs.
- A hosted status that has not registered on the head yet is not held.
That second factor from #3789 needs a design choice, filed as #3806.

**Qodo findings are answered with a dismiss request (#2252).** Per the
maintainer's answer on #2252, `qodo_open` keeps reading Qodo's own
badge.

- The digest's Qodo remedy text no longer says to answer in the
conversation, which never adds the badge. It says Qodo marks a fixed
finding Resolved itself. For a disproved one, the `--ack` comment's body
mentions `@<Qodo's login>` and asks it to dismiss that finding by its
number, with the reason. The number comes from the findings comment.
- `pr-review-conduct` Merge Gate item 3 and `drive-pr` step 4 say the
same thing. `drive-pr` routes an `UNTHREADED_FINDINGS` line naming
`qodo_open` to that answer rather than to a blocked merge.
- `pr-review-conduct` item 2 states the hosted-review hold.

The live check #2252 asks for is not part of this change. It confirms on
a PlexCleaner pull request that a dismiss request adds the badge, which
wording Qodo accepts, and which number it wants. So #2252 stays open.

`scripts/README.md` is held by another group in this round, so its
`pr_merge.py`, `wait`, and findings paragraphs are not updated here.
#3805 tracks that.

Line counts stay under every ceiling: `pr_review.py` 2171 of 2200,
`pr_review_answers.py` 402 of 450, `pr_review_checks.py` 435 of 450,
`pr_merge.py` 246 of 250.

## Local-review finding outcomes

- `introduced`, listed on the opening push: `limited_since` dated a rate
limit by the newest edit across every reviewer comment, so a summary
comment edited as a review starts could clear the hold. Fixed in
f38355e, which compares the edit time of the node `rate_limited_by`
reads, with a test for that case.
- Copilot, `[P1] Hold every unfinished hosted check`: fixed in 7eb8ee6
and 75b4dd1. The hold now reads every shape short of a pass or a
failure, so a hosted check queued past the pickup grace, waiting, or a
stale record still holds. Its thread was already resolved when the fix
landed.

## Verification

- `tests/test_pr_review.py` (550), `tests/test_pr_merge.py` (70),
`tests/test_pr_review_copilot.py`, `tests/test_pr_review_size.py`,
`tests/test_pr_cleanup.py`, and `tests/test_build_dist.py` pass.
- New cases cover the poster each check carries and each way a hosted
check matches. They also cover the rate-limit clear and an older limit
that does not clear. They cover the hold on `CLEAN`, `UNSTABLE`, and
`HAS_HOOKS`, and `merge`'s `CHECKS_PENDING` refusal. The `pending=`
line, its unread form, and its absence on an out-of-scope refusal are
covered, and so is the Qodo dismiss remedy.
- ruff format and check, mypy, the prose gate, `repo_gate.py --check
eol`, and `build_dist.py --check` are clean.

Closes on promotion: #3789

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…edge a Verified Intent Advisory (#3811)

## Summary

Each of the five issues is implemented the way the maintainer answered
it on 2026-11-01.

- **#1819, anchored intentRef dating.** `spec/audit.py`
`hub_last_change` now takes the anchor and dates an anchored `intentRef`
by the section its anchor names. It narrows each revision to that region
and then runs `_last_effective_change`, the way `check_verbatim` narrows
a section's history. When the anchor names no heading in the current
canonical, it dates the whole file. `spec/fidelity_text.py` gains
`heading_slug` and `extract_anchored`, a slug-keyed region extract that
works at any heading level (both live anchors are H3). A new test checks
that every anchored `intentRef` in `spec/files.json` resolves in the
hub. That way a typo cannot fall back to whole-file dating without
anyone noticing.
- **#727, clearing a verified intent finding.** The advisory now has its
own check id, `files.intent`. `intent_ref` parses an intent unit's
canonical and anchor in one place. It is a repo-scope check that runs
after the file stage, with `hub: false` and `suppression: {
endsWithType: null }` in `spec/checks.json`. Before this, the advisory
sat inside `files.presence`. A registry `driftNote` naming
`(files.intent)` acknowledges a copy with a `` `<path>@<sha>` `` code
span. That clears the copy until its intent canonical changes again. The
note reader, `spanned_notes`, is shared with the `filenaming.scripts`
exemption, so the file-naming check now goes through it too. The check
reports a stale acknowledgement, meaning one the advisory no longer
raises at that sha, and it also reports a note that names no such span.
`AUDIT.md` section 7 describes the acknowledgement, and section 8's list
of suppression notes now names it.
- **#3710.** `section_key` keeps `strip().lower()`. Its docstring now
says `lower()` rather than `casefold()` is intended, since fleet
headings are ASCII and the two agree there.
- **#3706.** `spec/validate.py` keeps the exact section-name match. A
comment at the comparison says why: the declared name is the verbatim
disposition label.
- **#3623.** The stale `RegressionTests` clause is dropped from
PlexCleaner's driftNote. PlexCleaner#1064's merge commit is on
PlexCleaner `main`, which carries `RegressionTests/tests/` and passes
`python-directories: RegressionTests` from both validator callers.

## Verification

- The owned test files pass on the head. The full `tests/` suite passed
on the first commit.
- `spec/validate.py` passes.
- The pre-commit gate passes: ruff format, ruff check, mypy, and the
prose gate. The prose gate needs the `comments` label for two comment
lines in `spec/validate.py`: the one #3706 asks for, and the existing
comment that now names `intent_ref()`.
- A live audit of one fleet repo on hub `23c7d784` no longer flags
`.editorconfig`, `.editorconfig-checker.json`, or `.gitattributes`
against the latest whole-file `GOVERNANCE.md` change. `version.json` is
still flagged, but against the change that actually edited `WORKFLOW.md`
"D3 - Versioning and Classification", not the whole file's latest edit.

## Local findings fixed after opening

The third local pass raised three `introduced` findings once the first
push's edit budget was spent. Commit 0ac4676 fixes all three under a
fresh pass:

- `tests/test_audit_helpers.py` now pins that `files.intent` runs after
`files.presence`, the read it depends on, since `spec/validate.py`'s
ordering guard covers only file checks. Commit 7bff4e5 widens that test
to the whole class. Every repo check whose function reads `run.content`,
`run.found`, `run.docs`, or the README through `_readme` must sit after
the first baseline check.
- A test now pins that `intent_trail` passes the intentRef's anchor to
`hub_last_change`.
- The `heading_slug` docstring now says `_` is kept as well as `-`.

The fourth local pass found one more `introduced` gap: no test drove
`check_files_intent` with a copy read at a declared alternative. Commit
d014dcd adds that test. The fifth pass found that no test pinned
`files.intent`'s suppression role, and commit b3a8d04 adds one.

## Declined local findings

- `spec/audit.py` `hub_last_change` anchored narrowing no longer catches
an edit to the hub's own `.gitattributes`, `.editorconfig`,
`.editorconfig-checker.json` or `version.json` unless the anchored
section also moves (local pass, `introduced`): declined. The
maintainer's 2026-11-01 answer on #1819 chose option 1, dating the
anchored section only. The decision record says option 2, adding the
file's own hub canonical, "still takes the noisy doc date unless
combined with option 1", and option 2 was not chosen. #727's 2026-08-15
comment shows hub-only edits to those files, such as a CRLF default or a
hub-tree pin, are exactly the noise a downstream copy must not be dated
by. The content-identical shortcut against the hub file at `path` stays,
and it only ever clears.

- `spec/fidelity_text.py` `extract_anchored`'s extent against
`sections()` (local pass, `introduced`, design): declined. `sections()`
is the manifest's level-two unit, which ends only at the next H2, and
every verbatim section and the carry depend on that extent. An anchor
names a GitHub section, which ends at the next heading of its own level
or above, and that is what bounds an H3 such as "Line Endings" at all.
If one primitive served both, it would either move the H2 unit's extent
or lose the H3 case. So the `sections()` docstring now claims only the
level-two extent. The any-level duplicate inside `scripts/build_dist.py`
is #3810.
- `spec/audit.py` `spanned_notes` shares only the no-span half of a
code-span driftNote (local pass, `introduced`, consolidation): declined.
The stale half matches differently at each site. A file-naming path is
checked against what the check reports. An intent `path@sha` is checked
by sha prefix against the change the advisory cites. Folding the two
would mean passing the matcher in, which costs more than the few lines
it saves.
- `spec/validate.py` #3706 comment gives the verbatim disposition label
as the reason for the exact match, but the match also covers
intent-fidelity and string-form sections (local pass, `introduced`):
declined. The comment states the reason the maintainer's 2026-11-01
answer on #3706 recorded, "the files.json name is the verbatim
disposition label". A section's fidelity is a one-field manifest edit,
and an exact rule that holds for every declared section means flipping
one to verbatim never needs a spelling fix first.

## Pre-existing findings, filed

- `docs/reusable-workflows.md` tracker: the PlexCleaner item is a
sibling of #3623's stale claim (local pass, `pre-existing`), filed as
#3813.

## Pre-existing findings, not filed

- `registry/repos.json` PlexCleaner driftNote: the clause this change
keeps still says "uvx scripts profile", a term the python-codestyle
build-versus-lint-only split no longer uses (local pass,
`pre-existing`).

## Dropped findings

- `spec/fidelity_text.py` `section_span` and `extract_section`
docstrings, and `scripts/build_dist.py` `heading_body`, say
"case-folded" or "folded" (local pass, `pre-existing`, wording on text
this change did not touch).

## Deferred

- `RESYNC.md` section 5, "What a Resync Cannot Detect", still summarizes
the advisory without the anchor and acknowledgement cases. `RESYNC.md`
is outside this change's file claim, so it is filed as #3808 (local
pass, `introduced`, deferred).
- The three line-ending units name `GOVERNANCE.md#line-endings`, which
is a one-paragraph pointer to the `comment-and-doc-style` Skill. Dated
by that section alone, their advisory fires only when the pointer itself
is reworded. Re-pointing them is a `spec/files.json` decision outside
this change's claim, filed as #3812 (local pass, `introduced`,
deferred).
- `spec/fidelity_text.py` `extract_anchored` and `scripts/build_dist.py`
`heading_body` compute the same any-level region.
`scripts/build_dist.py` is outside this change's file claim, so it is
filed as #3810 (local pass, `introduced`, consolidation, deferred).

Closes on promotion: #1819, #727, #3710, #3706, #3623

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* Added a repository check that flags outdated intent copies and
supports acknowledgements tied to a cited change.
* Intent checks can now focus on a named section and ignore changes
limited to formatting or workflow normalization.
* **Documentation**
* Clarified how to record verified intent advisories and how
acknowledged copies are checked.
* Updated the PlexCleaner advisory to reflect that Python lockfile
pinning does not apply.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…on Form (#3818)

## Summary

Three small `GOVERNANCE.md` prose changes, each made the way the
maintainer answered it, plus the `.gitignore` entry one of them calls
for.

- **#3617, the workspace-edit default.** The maintainer kept the default
that treats another VS Code window as hosting no session. Both sentences
stating it, in "Verification Discipline" and "Editor and Tasks", now
give the reason. Another window's session state is normally
unobservable, so assuming a live one would hand every such edit back to
the maintainer. That reason is there so a carrier's reviewer does not
raise the same finding again. `agent-conduct` includes "Verification
Discipline", so `scripts/build_dist.py` regenerated it.
- **#3757, the Python Environments extension.** "Editor and Tasks" now
says a repository shipping Python lists `.vscode/settings.json` in
`.gitignore`. The reason given is that the Python extension installs the
Python Environments extension, which writes settings there (it is in
`ms-python.python`'s `extensionPack`). The rule that settings live only
in the `.code-workspace` file stays. Its `.vscode/` clause now speaks of
tracked files, so the generated file does not contradict it. The hub
ships Python, so its own `.gitignore` gains the entry.
- **#3790, the aggregator's expression name.** The "Job and step `name:`
suffixes" bullet now says the ruleset-bound name is the one a
`pull_request` run posts. It points to `WORKFLOW.md` D1.5 for the
expression form and says each string that expression yields ends in
"job". The same-string sentence is scoped to that `pull_request` name
too, since an expression `name:` is not literally the context string.

## Not in this change

#3768 (Repository Layout) was assigned to this group, but `develop`
already has every item its decision lists. #1685 added them on
2026-09-18, before #1155 was split. Its `.github/workflows/` row already
scopes the reusable task workflows to the hub, and the
`STANDUP.md`/`RESYNC.md`, `.agents/`, and `host-setup/` rows are all
there. That issue is left open with a comment giving the evidence, so
this pull request does not close it.

## Verification

- `scripts/build_dist.py --check`: generated distributions are current.
Only `agent-conduct` changed.
- `prose_lint.py . --diff` against the merge base: clean.
- `spec/validate.py`: OK.
- The `tests/` unittest suite: 3332 tests, OK (7 skipped).
- `local-strict-review`: three recorded passes at the `frontier` tier.
The first two raised findings and each was fixed or classed. The third
ran over the final content.

## Pre-existing findings, not filed

- `GOVERNANCE.md` "Verification Discipline", the workspace-edit bullet,
`pre-existing`: "Where it cannot tell, it asks the maintainer" can be
read as covering the unobservable other-window state. The default
sentence just before it already settles that case, and the "Editor and
Tasks" copy scopes its question to "whether the rule binds it". The
sentence predates this change, which adds only the reason clause.

A resync is owed to carriers, since the sections this changes are
carried.

Closes on promotion: #3617, #3757, #3790

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary

- `gh_graphql.request()` now raises `NullNodeError` (a `GraphQLError`)
when any `nodes` list in the response holds a null. The check and
`null_node()` live in the shared transport.
- `pr_review.gh_graphql()` keeps its query-only re-read around the new
error and ends the run naming where the null sat. A mutation is never
sent twice.
- Removed the per-site `isinstance(n, dict)` guards in `pr_cleanup.py`
that the transport makes redundant. `handoff.py`'s label-node read,
which raised `TypeError` on a null node, now reports exit 2 through its
existing `GraphQLError` handling.
- Tests: null node through the shared transport, the moved `null_node`
helper, and `handoff.labeled` with a null label node.

`pr_review.py` shrinks by one line. `pr_review_rounds.null_node` is now
unused by scripts and is left for a follow-up since that file is outside
this change.

Closes on promotion: #3687

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…nch Delete, and Hold a Grown Agent-Safety Set (#3822)

## Summary

This changes `backlog-burndown`'s "Cleanup Is the Orchestrator's"
section, the re-verify bullet in its "Grouping and File Claims" section,
and the abandon-discard exception that `git-commit-conventions` and
`GOVERNANCE.md` "Git and Commit Rules" both state.

- **#3786: the orchestrator does the abandon discard, per the
maintainer's answer on that issue.**
- A worker told to abandon its branch reports its worktree path and
ends. It discards nothing.
- The orchestrator waits until the worker has ended, per "Bounding the
Wait on a Worker".
- It then reads a live `git worktree list --porcelain` for a linked
worktree whose `branch` line names the claimed branch, and runs `git -C
<path> reset --hard` and `git -C <path> clean -fd` in that entry's own
`worktree` path. Where no such entry exists, the group stops for the
maintainer.
- The matching exception in `git-commit-conventions` and the one
sentence in `GOVERNANCE.md` now name the orchestrator instead of the
worker. Nothing in agent-safety is edited.
- **#3785: the local branch delete now matches the is-ancestor check.**
- The orchestrator records the branch tip and runs the is-ancestor check
against `refs/remotes/origin/develop`. Both happen before anything is
removed.
- The local branch is then deleted with `git update-ref -d
refs/heads/<branch> <tip>`. That command refuses only if the branch has
moved off the tip the check judged.
- A `git config --remove-section` step drops the branch's tracking
section, which `git branch -d` would have removed.
- `git branch -d` judged a branch with no upstream against the base
clone's `HEAD`. The fetch does not advance that `HEAD`, so a branch with
no commits of its own could be refused even though deleting it discarded
nothing.
- **#3772: a file set that grows into `host-setup/agent-safety/` gets
the agent-safety hold.**
- When a worker reports that its files now reach into
`host-setup/agent-safety/`, the orchestrator applies the "Ranking" hold
as though it were ranking the issue now.
- In an attended run, a group whose issue needing that edit has an
approval recorded continues on a brief carrying the no-merge line.
- Every other such group, and each one in an away-mode run, stops per
"Raising a Blocked Question". An away run turns that stop into an
ordinary park through the existing "Parking Instead of Asking" rule, so
this adds no new parking path.

Closes on promotion: #3786, #3785, #3772

## Verification

- `uv run --script scripts/build_dist.py --check`: generated skill
distributions are current.
- The prose gate on the diff against `origin/develop`: no violations.
- `scripts/repo_gate.py --check eol`: 0 issues.
- `spec/validate.py`: OK.
- Local strict review: a recorded pass covers this head's content, with
no findings open against this change. The pre-existing findings it
raised are filed below.

## Pre-existing findings, filed

- `.agents/skills/backlog-burndown/SKILL.md` "Cleanup Is the
Orchestrator's", the discard path for a standalone clone
(`pre-existing`). The base text already limited the discard to a linked
worktree. Tracked in #3771.
- `.agents/skills/backlog-burndown/SKILL.md` "Ranking" (the
resuming-group sentence) and the resume paragraph in "Parking Instead of
Asking" state no precedence against "Ranking"'s away-mode agent-safety
hold (`pre-existing`). A worker could already park on an agent-safety
edit through "Needs the Maintainer" before this change. Filed as #3825.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…y Flag (#3823)

## Summary

The Husky pre-commit hooks now hand the prose gate the paths the commit
stages, rather than `.`. An unrelated untracked scratch file no longer
refuses every commit (#1657, option 1 of the maintainer's answer).

Naming paths would have made the gate read a staged file inside a
generated tree, which a `.` scan skips. So the gate gains
`--skip-generated`. It drops a named file inside a `generated_trees`
directory the same way a directory scan does. It reads the carried list
and the repository's own `.prose-gate.json` additions. The `pre-commit`
snippet passes the flag and drops its `exclude: (^|/)reports/` copy of
that list, and its README drops the sentence about mirroring it (#3443).

- `.github/actions/prose-gate/prose_lint.py`, `prose-gate.json`:
`--skip-generated`, applied in `discover()` to a named file. Without the
flag a named file is still read wherever it sits, so `prose_lint.py
reports/x.md` by hand is unchanged.
- `.husky/pre-commit`, `catalog/snippets/husky/pre-commit`: `git diff
--cached --name-only --diff-filter=ACMRT -z`, kept only where `[ -f ]`
holds, so a submodule, a dangling symlink, or a staged-then-deleted path
does not reach the gate's `error-not-a-path` refusal. The inner `sh`
skips the gate on an empty list, since a gate given no path scans `.`
with untracked files. It checks itself rather than relying on `xargs
-r`, because GNU xargs runs once on empty input and BSD xargs does not.
The list is read once on its own before the pipeline, so a failing `git
diff` stops the hook rather than reading as an empty list. The catalog
snippet calls `uv run --no-project python "$hub_fetch_run"` for the
gate, the form the `pre-commit` snippet already uses, since xargs cannot
call the `run_py` shell function. The working-tree content is still what
is read, so a partially staged file is still judged on all of its edits.
- `prose_lint.py` `repo_root()`: git hands a hook in a linked worktree
`GIT_DIR`, and under it `git -C <dir> rev-parse --show-toplevel` reports
`<dir>` itself. So the first commit through the new hook, from a
worktree, was refused as spanning several repositories. The lookup now
drops the location variables `checkout_provenance` already drops
(`GIT_LOCATION`). A `.` scan never met this, because the hook stands at
the top level.
- `scripts/README.md`, `catalog/snippets/pre-commit/README.md`: the
hook's scope and the generated-tree sentence now describe this.

## Verification

- `tests/test_prose_lint.py`: three new cases in
`TestTheScanScopeIsTheScopeReported`, on constructed data. A staged file
at `reports/run/audit.md` reports its bait when named and passes under
`--skip-generated`. A repository-declared `generated_trees` entry is
honored while an authored file in the same run is still read. An
untracked file is not read when the run names only the staged path. A
test also pins one repository lookup per directory. Full suite under
`tests/requirements.txt`: 3336 tests OK.
- `tests/test_pre_commit_hook.py`: the fixture moves to a shared
`HookCase`. Five new cases drive the hub hook through real commits
beside an untracked file holding a finding. A deletion-only commit skips
the gate. An edit plus a staged report under `reports/` passes, and a
staged finding is refused without naming the untracked file. Those three
fail against the hook that scanned `.`. A staged-then-removed path is
dropped and a typechange is read, each failing once its filter is
removed. The merge-commit cases still pass.
- Hub hook driven through constructed commits in a throwaway repository.
An untracked scratch comment, a copy of `reports/_template.md` staged at
`reports/run/audit.md`, a deletion-only commit, a staged-then-deleted
path, and a dangling symlink each exit 0. An authored file with a
duplicated word, its name holding a space, exits 1. So do a new comment
line and a symlink replaced by a regular file holding a finding.
- The catalog Husky snippet driven the same way, with a local shim
standing in for `hub_fetch_run.py`. An untracked note and a staged
report pass, and an added comment is refused.
- shellcheck and shfmt clean on both hooks. ruff format, ruff check,
mypy clean. `repo_gate.py` clean.

The hook comments are wanted, so this PR carries the `comments` label.

## Local review

- The typechange gap is fixed: `ACMR` dropped a symlink replaced by a
regular file, so `T` is kept. So is the per-path repository lookup,
which now runs once per directory. The duplicated git-environment filter
now shares one `git_env()` helper. And the Husky README's count of
`hub_fetch_run.py` calls is dropped.
- Second pass: a staged-then-removed path and a typechange each now have
a hook case. Each fails when the `[ -f ]` filter or the `T` filter is
dropped. `lookup_start()` now holds where a git lookup starts, for
`repo_root()` and `main()`.
- Declined: moving the non-file filter and the empty-list skip into the
gate. The gate's `error-not-a-path` refusal deliberately refuses rather
than falling back to another scan, for every caller. The `pre-commit`
framework already drops a non-file through its default `types: [file]`.
- Declined: xargs may split a very large commit into several gate runs.
The hook still fails when any batch fails, since xargs exits 123, and
each batch's scope line names its gate commit. So a split never turns
into a false pass.
- Third pass, edit budget spent. Declined:
`catalog/snippets/hub_fetch_run.py` line 9's example `prose_lint.py .
--diff HEAD` is a valid by-hand run of the fetcher, the form
`scripts/README.md` documents. It makes no claim about what a hook
passes, so it is left as written.
- Fix pass, after the hosted review: the inner `sh` takes a literal `sh`
as `$0`. So an error such as a missing uv names the shell, not
`hub_fetch_run.py` or the base. `main()`'s `scan_root` fallback now
calls `lookup_start()` too.
- Last pass, edit budget spent. Declined: the standalone read of the
staged list before the pipeline was called over-hardening, since git has
loaded the index before it starts the hook. It stays. Without it a
failed read silently skips the gate, and the hook is also run by hand,
which its own comment allows for. The cost is one `git diff
--name-only`.

## Pre-existing findings, not filed

- `catalog/snippets/husky/pre-commit`: no test drives the catalog Husky
snippet, only the hub's own hook (coverage, pre-existing). This change
ran it by hand against constructed commits, as listed under
Verification. Stand-ins replaced `hub_fetch_run.py`, `dotnet` and the
formatters.
- `catalog/snippets/husky/pre-commit`,
`catalog/snippets/pre-commit/.pre-commit-config.yaml`: a merge commit
downstream still diffs against `HEAD` rather than `MERGE_HEAD`
(correctness, pre-existing). It was closed unfixed under the
backlog-scope decision, so it is not refiled here.

## Dropped findings

- `prose_lint.py` `discover()`: its start computation, with a `.`
fallback for a path that is neither file nor directory, could call
`lookup_start` (consolidation, untouched text).

- `prose_lint.py` `GIT_LOCATION`: its list differs from
`spec/git_env.py`'s, which the fetched gate cannot import. The list
predates this change, which only moves its comprehension into
`git_env()` (consolidation, untouched text).

## Deferred

- `GOVERNANCE.md` line 299 still implies a plain git hook scans the
tree. That file is held by another lane, so the one-clause fix is #3820.

## Pre-existing findings, filed

- `prose_lint.py` `tracked_paths`, `untracked_paths`,
`shallow_checkout`, `git_bytes`: an inherited `GIT_DIR` reroots a
subdirectory run inside a linked-worktree hook (#3821).

Closes on promotion: #1657, #3443

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… Match the Scripts README to the Merge Gate (#3828)

Read Copilot's `Open findings:` label, drop the duplicate
`pr_review_rounds.null_node` (callers use `gh_graphql.null_node`), and
bring three `scripts/README.md` passages in line with the code on
develop (hosted-reviewer hold in `wait` exit 30, `merge`'s
`CHECKS_PENDING` refusal with `pending=`, the Qodo dismiss remedy).

Closes on promotion: #3824, #3817, #3805

Pre-existing findings, not filed:
- scripts/pr_review_checks.py `limited_since`: a queued check with no
start time reads any past rate limit as newer (pre-existing, code
outside this diff).
- scripts/README.md: "neither passed nor failed" mirrors the code
docstrings, which also count a failed check whose newer run is
unfinished (pre-existing wording, low severity).

Local strict review: attested below after the push.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…y Git Reads (#3831)

## Summary

A hook in a linked worktree inherits `GIT_DIR`. Under `GIT_DIR` with no
`GIT_WORK_TREE`, git takes the directory a command starts in as the work
tree's top level. So a run naming a subdirectory, such as `prose_lint.py
sub`, read `git -C sub ls-files` as though `sub/` were the root, and the
top level's ignores lapsed.

- The reads of the scanned tree now run under a new `tree_env()`. These
are `tracked_paths`, `untracked_paths`, `shallow_checkout`, `git_bytes`,
and the dead-path rule's `once_tracked`. `tree_env()` drops the location
variables as `git_env()` does for `repo_root` and provenance.
`once_tracked` reads the same tree through the same `git -C`, so it is
the same class and moves with the rest.
- `tree_env()` keeps `GIT_INDEX_FILE`. Measured on git 2.47, `git commit
-a` hands the hook `index.lock`, and `git commit <paths>` hands it
`next-index-*.lock`. That index is what the commit records, and the
checkout's own index is not, so dropping it would make the gate read the
wrong one. A plain commit in the main worktree hands the hook a relative
`.git/index` with `GIT_DIR` unset, which was already inherited and is
unchanged.

## Tests

The tests are built from the issue's constructed repro, with temporary
repositories and synthetic content only.

- `test_a_hook_in_a_linked_worktree_names_a_subdirectory`: under
`GIT_DIR`, an ignored file in the named subdirectory stays unread.
- `test_each_scanned_tree_read_answers_for_the_directory_it_names`: with
`GIT_DIR` naming a different, shallow repository, each of the five reads
answers for the directory it names. Reverting any one call site alone
fails this case, checked site by site.
- `test_a_commit_naming_paths_keeps_the_index_it_hands_its_hook`: a real
`git commit <paths>` in a linked worktree runs a pre-commit hook that
lists a subdirectory. The listing holds the file the commit's index
still tracks and the checkout's index dropped. It fails both when the
whole environment is inherited and when `GIT_INDEX_FILE` is dropped with
`GIT_DIR`. It is skipped off POSIX, as the textconv-driver case is,
since the hook is written for a POSIX shell.

`prose_lint.py` grows by 14 lines and no ceiling moves.

## Local review

Two recorded `local-strict-review` passes. The first raised one
`introduced` finding, the real-hook case running unguarded on the
Windows leg, which was fixed in f47ab60. The second raised none.

## Pre-existing findings, not filed

- `.github/actions/prose-gate/prose_lint.py` `GIT_LOCATION`,
consolidation: its list differs from `spec/git_env.py`'s
`GIT_LOCATION_VARS`, for example in `GIT_SHALLOW_FILE` and
`GIT_GRAFT_FILE`. No hook sets the missing ones, and the gate cannot
import `spec/`.

Closes on promotion: #3821

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…r Refusal (#3829)

## Summary

- Bump the two stale hub caller snippet pins,
`merge-bot-pull-request.yml` (was 2.0.338) and
`run-periodic-codegen-pull-request.yml` (was 2.0.352), to the 2.0.1319
release commit `320713d9`. Each snippet's `secrets:` matches the
callee's `workflow_call` at that commit. The merge-bot's optional
`rules`, `delete-branch` and `app-login` inputs, which the snippet's
comments name, all exist there. The codegen callee dropped its optional
`NINJA_API_KEY` secret, which the snippet never passed.
`test-pull-request.yml`, `publish-release.yml` and
`test-pull-request-smoke.yml` were already past the prose gate's
non-UTF-8 fix and are unchanged.
- GOVERNANCE.md "Hub-Hosted Tooling" gains the one sentence the
maintainer chose on #3745: Dependabot keeps the `uses:` pin current, so
the never-pinned rule for a doc gate fetched from `main` does not reach
it.
- State `hub_fetch_run.py`'s mixed-pair refusal where its
unresolved-commit fallback is documented: GOVERNANCE.md "Running the
Linters Locally" and the husky and pre-commit snippet READMEs. The
fallback reads the prose gate and its data from `main` twice, refuses
when the reads differ, and a re-run is the remedy.

The residual per-URL CDN caching risk from #3696's review needs an
immutable fetch, a design change filed as #3827 (`deferred`).

Not changed: `docs/reusable-workflows.md`'s adopt-stub copies of these
two pins (the "Adopting the Merge-Bot" and codegen stubs) and
`catalog/snippets/workflows/README.md`'s history line still name 2.0.338
and 2.0.352. A Markdown-quoted pin is never bumped by Dependabot, which
is exactly #1472's open question, so they are left for that issue rather
than hand-bumped here.

Closes on promotion: #1666, #3700

## Pre-existing findings, not filed

- `catalog/snippets/workflows/publish-release.yml`,
`test-pull-request-smoke.yml`, `test-pull-request.yml`: these still pin
2.0.1122 and 2.0.1166, which are past the fix #1666 needed but older
than 2.0.1319, and the hub's Dependabot config does not scan
`catalog/snippets/workflows`. That is `pre-existing`, outside this
change's file claim. The class, a snippet pin nothing keeps current, is
#1472's.

## Verification

- `git rev-parse '2.0.1319^{commit}'` resolves to
`320713d9d491f578b5604a8d67b82478717d7b05`, the latest release per `gh
release list`.
- Callee `on.workflow_call` read at that commit for both snippets.
- Prose gate against `origin/develop`: clean. Repo gate: every check ok,
`sha-pin` resolving both new pins against GitHub.
`tests/test_release_guards.py`: 33 passed.
- `local-strict-review`, two recorded passes: the first raised two
`introduced` wording findings in GOVERNANCE.md (a carried sentence
leaning on a reason stated only in a hub-only section, and an ambiguous
"That fallback"), both fixed in the second commit. The second pass
raised nothing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…he Agent-Safety Hold Past a Park (#3832)

## Summary

Gives `backlog-burndown` a disposition for a worker's standalone clone
on every path that retires or removes one, and states that releasing a
parked group never lifts the away-mode agent-safety hold.

- **Finding the clone (#3771).** A worker choosing the `repo-worktree`
standalone-clone fallback records the clone's directory name on its
group's claim comments before cloning, keeping the claim opening. The
brief names the temporary root, which is never posted. This is what lets
the orchestrator find a dead worker's clone.
- **Disposing of it (#3771, #3794).** "Cleanup Is the Orchestrator's"
gains one clone paragraph. A clone holds its branch alone, so the
base-clone commands do not reach it. It must be clean. Retired alone,
its tip must equal the live remote tip. Removed with its branch, its tip
must be on `origin/develop`. The directory is deleted per
`repo-worktree`, and its recorded name is removed from the claim
comments. Any failed check leaves the clone standing and stops the
group. The abandoned-worker discard does not run in a clone.
- **Paths that cite it.** The dead-worker path ("Bounding the Wait on a
Worker"), the abandoned path, the attended resume ("Raising a Blocked
Question"), and the away-mode retire step ("The Lean Orchestrator" step
2, since `git worktree remove` errors on a clone path). The stop prompt
says whether a clone holds commits the remote lacks, and pushing or
removing them is the maintainer's.
- **Agent-safety hold (#3825).** "Ranking" now says an answered decision
never lifts the away-mode hold on an edit under
`host-setup/agent-safety/`, so such a group resumes only in an attended
run and the report names it as held. "Parking Instead of Asking" defers
to "Ranking" there.

Generated copies regenerated with `scripts/build_dist.py`.

Closes on promotion: #3771, #3794, #3825

## Findings from the local passes

Each finding the pass on the first push left open has an outcome. Each
one the hosted round raised is answered in its thread.

- Resume bullet, `introduced`: a removed or never-pushed clone could
never pass the retire-alone check. Fixed in 1573113 and cb8addf: the
resume waits until the remote holds the clone's branch.
- Dispatch bullet, `introduced`: "That root is never posted" was false.
Fixed in 1573113 by deleting the claim.
- Clone paragraph, `introduced`: "strikes its recorded name". Fixed in
1573113 ("removes").
- Clone paragraph, `introduced`: the release also reached a clone
retired alone, and a later run could not find a clone under a root it
never named. Fixed in 873a9b7: a clone retired alone keeps its claim,
and the paragraph says a later run that named another root sends the
group to the maintainer.
- Clone paragraph, `introduced`: the retire-alone `ls-remote` read the
caller's `origin`. Fixed in dee2602 (`git -C <clone> ls-remote`).

- Clone paragraph retire-alone check, stop bullet, and park step 4,
`introduced`: the check needs tip equality while the prompt and park
comment asked whether the remote lacked any of the branch. Fixed in
1cafaa2: all three now say whether the clone's tip equals the remote
tip.

## Pre-existing findings, filed

- "Cleanup Is the Orchestrator's" merged-group clone check,
`pre-existing`: it requires tip equality with `headRefOid` where
`repo-worktree` accepts a tip at or behind it. Filed on the unit tracker
#3833.

## Deferred, filed

- #3830: the "Invoking It" bullet still calls every answered parked
group free to resume. It sits outside the units these issues name.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ame an Open Fence (#3835)

## Summary

Fixes the three follow-ups #3690 records against `scripts/carry.py`'s
section modes.

1. `plan_unit` now renders the insertion and the splice in memory, so
`check-sections` meets every refusal `apply-sections` raises.
`SectionPlan` carries the rendered `inserted` and `rewritten` lines, and
`apply_plans` only writes and asserts them. The `scripts/README.md`
sentence on refusals now says so.
2. An inserted section whose heading lands inside a code fence the
target leaves open is refused naming that fence and its line, rather
than as an absent section. Any other block that swallows the heading
gets a refusal naming the insertion line. Tests assert both messages.
3. `RESYNC.md` step 1 and `scripts/README.md` now say the rename check
covers text in the same file, since the scan is per file.

## Verification

- `python -m unittest test_carry` from `tests/`, with
`tests/requirements.txt`: 78 tests pass.
- Each new test fails against the code before its fix.
- `ruff check`, `ruff format --check`, and `prose_lint.py --diff
origin/develop` over the changed files: clean. The pre-commit hooks
passed.

## Local strict review

Two rounds. Round 1 raised two findings, and round 2 raised none.

- Fixed in 62e5d18: when the inserted hub region held a fence of its
own, that fence closed the target's open one in the spliced text. So the
refusal fell back to the generic message. `unparsed_insertion` now reads
the open fence from the text ahead of the insertion, and a test covers
it.
- Declined: consolidate the open-fence filter with
`scripts/build_dist.py:364`. Both sites already call the shared
primitive, `spec/fidelity_text.py`'s `code_blocks` and its `closed`
field. What is left at each site is a one-line filter with a different
range meaning. There it keeps blocks that start strictly inside a
heading's body, and here it keeps a block reaching the end of a prefix.

Closes on promotion: #3690

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ates Item (#3836)

## Summary

- **Stub pins become placeholders.** Every hub `uses:` pin in
`docs/reusable-workflows.md`'s stubs now reads `@<hub-main-commit-sha> #
<release-tag>`, the shape the maintainer chose on #1472. No Dependabot
scan reads a pin quoted in a document, so a literal there only went
stale.
- **Snippets keep a literal floor pin.** Each
`catalog/snippets/workflows/` snippet is unchanged and keeps its literal
minimum known-good pin, which moves when a task's interface changes.
`spec/workflow_reuse.py` and the pin gate read them as canonical text.
- **Adoption resolves the pin.** "Pinning" now states the rule and the
two read-only `gh` commands that name the latest release and its `main`
commit. The snippets README and `STANDUP.md` section 3 point there.
"Byte for byte" no longer covers the pin lines.
- **Tests.** The tests comparing a doc stub to its snippet substitute
the placeholder through one `documented()` helper, and the merge-bot
stub is now compared too. A new test holds both pin shapes, placeholders
in the doc and literals in the snippets, judging every hub reference
including one with no tag comment.
- **PlexCleaner Stage 2 item ticked (#3813).** Checked live against
PlexCleaner `main`. ptr727/PlexCleaner#1064 merged into `develop` at
`84fd00e` and was promoted in ptr727/PlexCleaner#1098 at `1876563`. Both
validator callers pass `python-directories: RegressionTests`. The Stage
3, 4, and 5 PlexCleaner items it also bears on are filed as #3834 rather
than widened into this change.

## Verification

- Full unittest suite, `spec/validate.py`, `test_audit_helpers.py`, and
`spec/workflow_reuse.py --selftest` all exit 0.
- The prose gate, ruff, mypy, and the eol check pass in the pre-commit
hook.

Closes on promotion: #1472, #3813

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…e Over It (#3839)

Closes on promotion: #1592

## Summary

The `description:` frontmatter field is what makes a skill fire. Most
fleet skill descriptions ran past the 1024-character Agent Skills limit,
and nothing bounded them. A loader can truncate a description past that
point. The part it drops is the tail, which is where these descriptions
say which sibling skill wins a shared trigger.

- **Gate.** `scripts/build_dist.py` parses each
`.agents/skills/<name>/SKILL.md` frontmatter with PyYAML. It refuses a
`description` longer than `DESCRIPTION_LIMIT` (1024):
  - exit 2 under `--check`
  - exit 1 on a regenerate, before anything is written
- also refused: frontmatter that never closes, invalid YAML, and a
description that is not a string
  - a skill with no frontmatter or no description is not measured
- PyYAML is imported lazily, so `skills_install.py` still imports
`build_dist` under a plain `python3`
- **Trims.** Every over-limit description is trimmed to the limit or
under. The longest is now `git-commit-conventions` at 1014. Each trim
keeps what the skill governs, its "use this whenever" triggers, and its
sibling-precedence and co-fire clauses.
- Narrative the body did not already hold is moved into it. That is one
new paragraph in `session-handoff` "Why This Exists".
  - Narrative the body already covers is deleted.
- **skill-lifecycle.** Step 2 states the cap and its source constant. It
also says incident and "Triggers even when" reasoning goes in the body.
The Pipeline section's exit-2 list gets the new cause.
- **scripts/README.md.** It names PyYAML as the second dependency
exception, and adds the gate to the `build_dist.py` `--check` exit-2
list.

## Verification

- `uv run --no-project --script scripts/build_dist.py --check` exits 0.
Before the trims it exited 2 and listed every over-limit skill.
- New `DescriptionCase` tests in `tests/test_build_dist.py`:
  - a description at the limit passes and one past it is refused
  - the limit is measured on the parsed string, not the raw lines
  - the exit codes, with nothing written
  - a skill with no description is not measured
  - the malformed-frontmatter refusals
  - importing `skills_install` does not import `yaml`
- Full suite, `python -m unittest discover -s tests`: 3355 tests OK, 7
skipped.
- The pre-commit hook passed: ruff format and check, mypy, the prose
gate, and eol.
- `local-strict-review`: three recorded passes.
- The first raised 5 findings, all fixed: two dropped trigger words, one
trigger widened from "a composite action under .github/actions/" to the
whole tree, and two descriptions whose sentences had gone over 25 words.
- The second raised 1, fixed: an over-long sentence in
`backlog-burndown`.
  - The third was clean.
- A Copilot finding on the opening head (an ungrammatical "Triggers
whatever" in `upstream-contribution-workflow`) is fixed in a3b6163.
Running the fix through its own `local-strict-review` swept every
trimmed description for the same kind of slip. It found four more and
then one: a pronoun with no clear target, a possessive, a dropped
"explicitly", and two narrowings of the history-rewrite clause. The
final pass at 2c9d839 is clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…3838)

## Summary

The `runtime-secrets` audit dimension never reported a bare `secrets/`
directory. A runtime-secrets directory is ignored by design, so it holds
no tracked path, and `secret_stem_findings` judged tracked paths only.
The check now reads the tracked files that name such a directory
instead.

- **The bare directory.** `secrets.stems` reads each tracked
`.gitignore` and each compose or editor config file (`compose*.yml`,
`docker-compose*.yml`, `.vscode/*.json`, `.devcontainer` configs). A
compose file is read by its YAML keys, for the paths compose resolves on
the host. A `.gitignore` line ignoring a directory named `secrets`
(`secrets/`, `/secrets`, `**/secrets/`, `secrets/*`), or a repo-relative
`secrets/<name>` path, reports the bare directory with each file naming
it. `.secrets` is never matched. A container mount point such as
`/run/secrets/<name>` is out of scope, and so is a path that climbs out
of the repository.
- **A file the repository names but does not track.** Each secret file
such a path names, in `secrets/` or `.secrets/`, is judged by the env
and secret file rule even though the tree lacks it. A compose path
resolves against its own directory, and an editor path against the
workspace root. A file the tree tracks, or whose `.example` it tracks,
is judged once, from the tree.
- **The `.txt` rule.** A `.txt` extension on a file in a secrets
directory now breaks the rule, per GOVERNANCE.md "Repo-Scoped Secrets"
(a single opaque credential carries no extension). It applies to tracked
and named files alike.
- **A directory that tracks code is not one.** A reference into a
`secrets` directory that tracks more than its own `.gitignore`,
`.gitkeep`, `README.md`, templates, and files beside their templates is
no signal, since that directory is not ignored. A bare directory's own
tracked files report it.
- **Only a marked path counts.** A path counts where a `./`, `../`, or
workspace variable marks it, or where it is a compose `file:` value. A
URL path, a cloud resource name, a Vault path, or a shell `$PWD` is not
one. A whole-directory mount counts. A relative editor path reports the
directory but judges no file name, since it depends on a task's working
directory.
- **A `.gitignore` line counts where it ignores the folder or its files
by glob**, and an anchored one inside a code directory does not.
- A named file is counted once however many files name it, and is listed
with each of them.
- A signal file whose read returns no text is reported as undecided
rather than read as clean.

The issue's constructed case is the end-to-end test in
`tests/test_audit.py`. The parser and regex edges are in
`tests/test_audit_helpers.py`. `spec/project-types.json` already states
both the bare directory and the `.txt` file as drift, so its assert text
needs no change.

## Local review findings

Every `introduced` finding the local passes raised is fixed or filed,
through `266f99f6`. The last pass, at `266f99f6`, raised one finding,
deferred:

- `spec/audit.py:543`, `introduced`: a `.gitignore` line with a
character class in the folder segment, such as `[Ss]ecrets/`, is not
matched. Deferred to #3842, which covers the same case-insensitive
match.

## Declined findings

- `spec/project-types.json:175`, `introduced`: a host-side home path
such as `~/.secrets/Api_Token.txt` is not judged. Declined. `AUDIT.md`
section 7's runtime-secrets bullet leaves host-side names to hand
judgment: "The rest is judged by hand. ... A host-side name is judged
where the repo's docs and scripts spell it out, never by reading a
host."

## Pre-existing findings, filed

- #3841: `Run.read_text` reads by branch name while the tree is listed
at the head commit, so a push between the two turns a repository's
result into one ERROR. This change adds reads through it.

## Follow-ups filed

- #3842: a capitalized `Secrets/` or `.Secrets/` directory, or a
`[Ss]ecrets/` ignore line, is matched by no part of the check.
- #3840: a multi-file compose set, such as a devcontainer's
`dockerComposeFile` list, resolves against its first file. This check
resolves each file against its own directory.
- #3837: `AUDIT.md` section 7's runtime-secrets bullet and the
`spec/checks.json` `secrets.stems` description still describe the
narrower check. Both are outside this change's files.

Closes on promotion: #3719

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings November 3, 2026 01:22
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@codecov

codecov Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.22481% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.30%. Comparing base (23c7d78) to head (536bddb).
⚠️ Report is 461 commits behind head on main.

Files with missing lines Patch % Lines
spec/audit.py 98.70% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3843      +/-   ##
==========================================
+ Coverage   82.72%   83.30%   +0.57%     
==========================================
  Files          29       29              
  Lines        9629     9937     +308     
==========================================
+ Hits         7966     8278     +312     
+ Misses       1663     1659       -4     
Flag Coverage Δ
python-3.13 83.30% <99.22%> (+0.57%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Critical pre-commit loop bugs can hang processing and prevent the prose gate from running, and the fidelity model still contains a repository-specific reference that must be removed.

3 open issues
What changed in this PR

This promotion consolidates the develop changes into main, improving review gating, local hook scoping, fidelity auditing, and generated skill validation.

Changes:

  • Holds merges while hosted review checks remain pending and improves Qodo and Copilot finding handling.
  • Adds staged-path and generated-tree handling to prose hooks, plus related documentation.
  • Adds intent-fidelity, secret-directory, GraphQL null-node, and skill-description validation.

Review findings:

  • Remove the repository issue reference from spec/fidelity-model.md.
  • Make the new staged-path pipelines failure-safe in both Husky hooks.
File Description
tests/​test_release_guards.py Updated as part of this pull request.
tests/​test_pr_review_copilot.py Updated as part of this pull request.
tests/​test_pr_merge.py Updated as part of this pull request.
tests/​test_pr_cleanup.py Updated as part of this pull request.
tests/​test_handoff.py Updated as part of this pull request.
tests/​test_gh_graphql.py Updated as part of this pull request.
tests/​test_fidelity_text.py Updated as part of this pull request.
tests/​test_carry.py Updated as part of this pull request.
tests/​test_build_dist.py Updated as part of this pull request.
tests/​test_audit.py Updated as part of this pull request.
STANDUP.md Updated as part of this pull request.
spec/​validate.py Updated as part of this pull request.
spec/​fidelity-model.md Updated as part of this pull request.
spec/​fidelity_text.py Updated as part of this pull request.
spec/​checks.json Updated as part of this pull request.
scripts/​reviewers/​copilot.json Updated as part of this pull request.
scripts/​pr_review_rounds.py Updated as part of this pull request.
scripts/​pr_review_answers.py Updated as part of this pull request.
scripts/​pr_merge.py Updated as part of this pull request.
scripts/​pr_cleanup.py Updated as part of this pull request.
scripts/​gh_graphql.py Updated as part of this pull request.
scripts/​build_dist.py Updated as part of this pull request.
registry/​repos.json Updated as part of this pull request.
catalog/​snippets/​workflows/​run-periodic-codegen-pull-request.yml Updated as part of this pull request.
catalog/​snippets/​workflows/​README.md Updated as part of this pull request.
catalog/​snippets/​workflows/​merge-bot-pull-request.yml Updated as part of this pull request.
catalog/​snippets/​pre-commit/​README.md Updated as part of this pull request.
catalog/​snippets/​pre-commit/​.pre-commit-config.yaml Updated as part of this pull request.
catalog/​snippets/​husky/​README.md Updated as part of this pull request.
catalog/​snippets/​husky/​pre-commit Updated as part of this pull request.
.husky/​pre-commit Updated as part of this pull request.
.gitignore Updated as part of this pull request.
.github/​skills/​workflow-ci-contract/​SKILL.md Updated as part of this pull request.
.github/​skills/​upstream-contribution-workflow/​SKILL.md Updated as part of this pull request.
.github/​skills/​skill-lifecycle/​SKILL.md Updated as part of this pull request.
.github/​skills/​shell-codestyle/​SKILL.md Updated as part of this pull request.
.github/​skills/​session-handoff/​SKILL.md Updated as part of this pull request.
.github/​skills/​repo-worktree/​SKILL.md Updated as part of this pull request.
.github/​skills/​python-codestyle/​SKILL.md Updated as part of this pull request.
.github/​skills/​pr-review-conduct/​SKILL.md Updated as part of this pull request.
.github/​skills/​merge-and-release/​SKILL.md Updated as part of this pull request.
.github/​skills/​git-commit-conventions/​SKILL.md Updated as part of this pull request.
.github/​skills/​drive-pr/​SKILL.md Updated as part of this pull request.
.github/​skills/​dotnet-codestyle/​SKILL.md Updated as part of this pull request.
.github/​skills/​decision-triage/​SKILL.md Updated as part of this pull request.
.github/​skills/​copilot-instructions-keeper/​SKILL.md Updated as part of this pull request.
.github/​skills/​comment-and-doc-style/​SKILL.md Updated as part of this pull request.
.github/​skills/​check-this-repo/​SKILL.md Updated as part of this pull request.
.github/​skills/​carried-instruction-file-guard/​SKILL.md Updated as part of this pull request.
.github/​skills/​branching-and-release-model/​SKILL.md Updated as part of this pull request.
.github/​skills/​audit-a-repo/​SKILL.md Updated as part of this pull request.
.github/​actions/​prose-gate/​prose-gate.json Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​workflow-ci-contract/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​upstream-contribution-workflow/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​skill-lifecycle/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​shell-codestyle/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​session-handoff/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​repo-worktree/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​python-codestyle/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​pr-review-conduct/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​merge-and-release/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​git-commit-conventions/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​drive-pr/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​dotnet-codestyle/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​decision-triage/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​copilot-instructions-keeper/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​comment-and-doc-style/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​check-this-repo/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​carried-instruction-file-guard/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​branching-and-release-model/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​skills/​audit-a-repo/​SKILL.md Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​workflow-ci-contract Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​upstream-contribution-workflow Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​skill-lifecycle Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​shell-codestyle Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​session-handoff Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​repo-worktree Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​python-codestyle Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​pr-review-conduct Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​merge-and-release Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​local-strict-review Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​git-commit-conventions Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​drive-pr Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​dotnet-codestyle Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​decision-triage Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​copilot-instructions-keeper Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​comment-and-doc-style Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​check-this-repo Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​carried-instruction-file-guard Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​branching-and-release-model Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​backlog-burndown Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​audit-a-repo Updated as part of this pull request.
.claude-plugin/​fleet-skills/​.source-digests/​agent-conduct Updated as part of this pull request.
.agents/​skills/​workflow-ci-contract/​SKILL.md Updated as part of this pull request.
.agents/​skills/​upstream-contribution-workflow/​SKILL.md Updated as part of this pull request.
.agents/​skills/​skill-lifecycle/​SKILL.md Updated as part of this pull request.
.agents/​skills/​shell-codestyle/​SKILL.md Updated as part of this pull request.
.agents/​skills/​session-handoff/​SKILL.md Updated as part of this pull request.
.agents/​skills/​repo-worktree/​SKILL.md Updated as part of this pull request.
.agents/​skills/​python-codestyle/​SKILL.md Updated as part of this pull request.
.agents/​skills/​pr-review-conduct/​SKILL.md Updated as part of this pull request.
.agents/​skills/​merge-and-release/​SKILL.md Updated as part of this pull request.
.agents/​skills/​git-commit-conventions/​SKILL.md Updated as part of this pull request.
.agents/​skills/​drive-pr/​SKILL.md Updated as part of this pull request.
.agents/​skills/​dotnet-codestyle/​SKILL.md Updated as part of this pull request.
.agents/​skills/​decision-triage/​SKILL.md Updated as part of this pull request.
.agents/​skills/​copilot-instructions-keeper/​SKILL.md Updated as part of this pull request.
.agents/​skills/​comment-and-doc-style/​SKILL.md Updated as part of this pull request.
.agents/​skills/​check-this-repo/​SKILL.md Updated as part of this pull request.
.agents/​skills/​carried-instruction-file-guard/​SKILL.md Updated as part of this pull request.
.agents/​skills/​branching-and-release-model/​SKILL.md Updated as part of this pull request.
.agents/​skills/​audit-a-repo/​SKILL.md Updated as part of this pull request.

Review effort: Lite

Comment thread .husky/pre-commit Resolved
Comment thread catalog/snippets/husky/pre-commit Resolved
Comment thread spec/fidelity-model.md Resolved
…f Filing Issues (#3846)

## Summary

Until now, filing an issue was always an acceptable way to dispose of a
finding. backlog-burndown's file claim and its prose bound ("the next
issue, filed") made filing the required route for a doc describing a
group's own change. This PR closes those routes. A fix that can land in
the pull request already open now lands there. Filing is kept for work
that really needs its own lane.

- **pr-review-conduct, outcome 4** (drive-pr carries it as an include):
  - An `introduced` finding is never filed.
- An edge case on new code is fixed where its deployment can produce the
input. Otherwise it is declined under Trust Boundaries.
- A `pre-existing` correctness defect in a file the diff touches is
fixed in this PR, within the same two-round budget. This uses the same
file grain as the class sweep, and Merge Gate item 3 excepts these
defects from the canonical tracker.
- Only a `pre-existing` correctness defect **in code that runs**,
elsewhere, is filed.
- An edge case on new code follows Trust Boundaries' whole
fix-or-decline test.
- The open-ended "Any other deferral is filed" catch-all is gone. The
only filings left are a design finding's untouched-file sites (one
issue, per "Triaging findings") and a fix belonging to another
repository.
- **GOVERNANCE.md, Verification Discipline** (agent-conduct carries it
as an include): the doc, manifest entry, or rule sentence describing
changed behavior is a sibling the change put in disagreement. It is
fixed in the change, not filed.
- **backlog-burndown**:
  - Claims predict the docs that describe each fix.
- A grown claim into an unclaimed file is granted at once. A grown claim
into a held file is sequenced: the requester's PR waits for the holder's
merge, and parks behind a holder that is stopped. A mutual wait is
adjudicated as a conflict. A file on an unclaimed branch stops for the
maintainer.
  - The prose bound admits units the change put in disagreement.
- Growth into carried canonical content or a design change under a
worker below `frontier` re-tiers the group, so that work never falls to
a lower tier.
- Hub workers read develop's copy of any skill develop has changed. This
closes the stale-installed-skill gap behind the self-written reports on
#3835, #3836, and #3838.
- **local-strict-review**: the reviewer brief stops asking for wording,
style, or consolidation remarks on text the diff did not write. Those
were dropped anyway, so this only cuts output and disposal tokens.

Maintainer decisions, 2026-11-02:
- Lever 1: agreed.
- Lever 2: file only code that runs.
- In-file pre-existing defects: fix them in the PR.
- Lever 3: rejected.

## Open `introduced` findings (loop stopped by the maintainer)

There were twelve recorded local passes at the `frontier` tier, raising
10, 9, 8, 6, 3, 3, 5, 4, 4, 2, 6, and 3 findings. Nearly every finding
was against text an earlier round had written. Of pass 11's six
findings, 1 to 5 were fixed in d70a96e. Finding 6, which asked to
revive the automatic re-tier, is declined: that hand-off drew new
findings in every round, so it was replaced on purpose with the existing
stop for the maintainer. By the maintainer's call, the last pass's
findings get no further edit round. On 2026-11-02 the maintainer
accepted both as known gaps (outcome 3):

1. `introduced`, backlog-burndown "Choosing the Worker's Model Tier":
the tier-stop paragraph's "claim comment is extended first" does not
repeat re-verify's limit to a file no branch holds. Read alone, it could
extend a claim over a file another group holds.
2. `introduced`, pr-review-conduct outcome 4, last bullet: where the
other repository's fix goes to the maintainer, no upstream issue exists
for the local blocker record to name.

## Pre-existing findings, not filed

- `pre-existing`, local-strict-review "The Carried-Content Sweep" with
pr-review-conduct outcome 4: a sweep pass's findings are on units the
sweep branch has not edited yet, so they route to the per-unit tracker
rather than to a fix.
- `pre-existing`, pr-review-conduct outcome 1 and "Triaging findings":
the untouched-file sites of a design finding are routed two ways. One
route files them as one issue, and the other drops them as a
consolidation on untouched text.

## Replay of the 25 issues the run filed

| Category | Issues | Under these rules |
| --- | --- | --- |
| Adjacent doc outside the claim | #3804, #3805, #3808, #3810, #3817,
#3820, #3837 | Fixed in the originating PR through the predicted or
granted claim. #3810 is a consolidation and #3817 is dead code, so each
is fixed if in a touched file and otherwise dropped or listed |
| Old defect next to the diff | #3813, #3821, #3824, #3825, #3833,
#3834, #3841, #3844 | Fixed where it sits in a touched file or unit.
Still filed only if it is code that runs elsewhere, which is about
#3821, #3824, and #3841. #3833 keeps its canonical tracker |
| Edge case past the done-definition | #3806, #3827, #3830, #3840, #3842
| `introduced`, so fixed in the PR or declined. #3830 is a contradiction
the fix wrote and is never filed |
| Decision | #3812, #3814, #3815, #3826 | Unchanged, still `decision`
issues |

Expected result for the same run: about 7 issues filed instead of 25.

Closes on promotion: #3845

## Verification

- `scripts/build_dist.py --check`: generated distributions are current.
- `prose_lint.py --diff origin/develop`: clean.
- `spec/validate.py`: OK.
- `repo_gate.py --check eol`: 0 issues.
- Docker markdownlint and cspell: 0 issues.
- local-strict-review: twelve passes recorded at the `frontier` tier.
The receipt covers the pushed head.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

A moderate active finding remains because non-mapping frontmatter can bypass the required description validation, so the change still requires correction.

1 open issue
3 resolved since last review

Review effort: Lite

Comment thread scripts/build_dist.py Resolved
## Summary

Fixes a finding Copilot raised on promotion PR #3843, against the
description length gate #3839 added.

`skill_description` in `scripts/build_dist.py` returned None ("states no
description") for any frontmatter that was not a mapping. A `SKILL.md`
whose frontmatter parsed to a list or a scalar was therefore never
measured, and it got past the 1024-character gate. `skill-lifecycle` and
`scripts/README.md` both say the gate refuses "frontmatter too malformed
to measure one". The code now does the same: frontmatter that is not a
mapping raises `frontmatter is not a mapping`, as unclosed and invalid
frontmatter already did. Empty frontmatter still reads as stating no
description.

- `scripts/build_dist.py`: refuse non-mapping frontmatter, and keep
returning None for empty frontmatter.
- `tests/test_build_dist.py`: a refusal case for list frontmatter, and a
not-measured case for empty frontmatter.

## Verification

- `python -m unittest discover -s tests -p test_build_dist.py`: 99 tests
OK.
- `ruff check` and `ruff format --check` on both files: clean.
- `scripts/build_dist.py --check`: generated distributions are current.
- Every `SKILL.md` under `.agents/skills/` opens with mapping
frontmatter, so no existing skill newly fails the build.
- local-strict-review: two passes recorded at the `frontier` tier. The
first raised one finding (no test covered the empty-frontmatter branch),
fixed in ce00bf3. The second raised none. The receipt covers the pushed
head.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@ptr727
ptr727 requested a lite review from Copilot November 3, 2026 05:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The change affects GitHub review gating, GraphQL transport, and commit hooks, while live check/reviewer behavior and hook execution remain unvalidated beyond modeled unit tests.

No open issues 🎉

1 resolved since last review

Review effort: Lite

@ptr727

ptr727 commented Nov 3, 2026

Copy link
Copy Markdown
Owner Author

Declining the needs a closer look headline on 536bddb, with evidence. It names no defect. It notes that live check, reviewer, GraphQL, and hook behavior are validated only by modeled unit tests. Each of those paths ran live on this develop code during this drive:

Every constituent pull request also merged with full coverage: an attested local pass, or a Copilot file table naming exactly its changed files.

@ptr727
ptr727 merged commit 3a83cd0 into main Nov 3, 2026
14 checks passed
ptr727 added a commit that referenced this pull request Nov 3, 2026
…er Tracker Items (#3849)

Promotes the changes merged into `develop` since promotion #3843 to
`main`.

- [#3848](#3848) uploads
the .NET unit-test coverage in `validate-task.yml` under a fixed
`dotnet` Codecov flag, beside the Python legs' own flags, with a
release-guard test pinning it. It also ticks the PlexCleaner Stage 3 and
Stage 4 items in `docs/reusable-workflows.md` and adds a ticked Stage 5
item, after re-checking PlexCleaner `main`.
- [#3850](#3850) answers
this promotion's Copilot finding, naming the upload on both sides of
that guard test's docstring.

Fixes #3834
Fixes #3819

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants