feat(fx-dev): add a materiality bar so reviews converge on what matters - #24
Conversation
Review loops did not terminate. Every observation was treated as a finding, so each pass fixed what the last one raised and surfaced a fresh crop of smaller ones: the findings got less important, the passes kept costing the same, and the loop ended only when someone gave up. A recent docs-only PR ran twelve Codex passes with findings per pass of 12, 13, 10, 5, 5, 5, 3, 4, 3, 3, 4, 6 -- it plateaued rather than converged, and the tail was list-completeness nitpicks plus one already-settled decision being re-argued in new words each round. Scope answers what to review. Nothing answered what is worth reporting or when to stop, so this adds a materiality bar to the shared scope contract that fourteen skills already import, and wires the five review skills to it. A finding is worth reporting when acting on it would change what the artifact does or how a competent reader acts on it. Material and substantive findings are reported individually and block convergence; immaterial ones go in a single non-blocking closing note and never trigger another pass. The test is "if this shipped uncorrected, what breaks" -- if the answer is "nothing, it is just not as good", it is immaterial. Three shapes are explicitly not findings, generalised from the observed failure rather than baked in as examples: an entry missing from a list the artifact itself declares non-exhaustive (assess the rule, since the supply of such entries never runs out), a decision the artifact records with its rationale including "unknown, gated on X" (escalate once, do not re-argue), and a limit the artifact admits and gates. Convergence is redefined as "a pass with no material or substantive findings" rather than zero output, with the shape of the trend as the signal: falling with new categories means keep going, flat or oscillating and immaterial means stop, the same disagreement twice means escalate to a human. Reviewers report the per-pass trend when they stop so it cannot be taken on trust. Per skill: codex-review gains a required convergence-pass prompt block that tells Codex the bar and the settled ground up front rather than filtering its output afterwards; copilot-review and resolve-pr-feedback carry the loop-specific trap that every push re-opens the gate, so editing for an immaterial finding costs a wait cycle and manufactures the next round's input; coderabbit-review notes its own severity labels are an input to the judgment, not a substitute; pr-reviewer must state counts per tier; and dev requires a finding to clear the bar before it can be classified required-by-contract at all. Also adds "verify before fixing" to the reviewer skills. A finding's premise can be wrong, and changing working code to satisfy a misreading is worse than leaving the finding unaddressed.
Codex found four, all genuine contradictions between the new rules and instructions already in the same files. No wording nitpicks — the new prompt structure worked. The codex-review rerun prompt listed the reportable tiers as an exhaustive "only": behaviour, build/test, implementability, truth. A credential, internal URL or private identifier leaked into documentation matches none of those — removing it changes content, not behaviour — so the prompt could have suppressed exactly the class REVIEW.md requires be flagged. Security, privacy and data-loss are now named in the prompt. "Falling with new categories means keep going" contradicted "a pass with no material findings means stop": five immaterial observations becoming three different immaterial observations is a fall, and the rule as written bought another pass for nitpicks. All three copies now count only the tiers that block. resolve-pr-feedback gained materiality triage at the top while its loop and success criterion still demanded zero new feedback, so an agent that correctly resolved immaterial threads by reply would loop or escalate anyway. Convergence there is now no new material or substantive findings plus every thread resolved on the reviewed head. pr-reviewer told reviewers to demote immaterial findings to a non-blocking note while also requiring every project-rule violation to be BLOCKING, permitting opposite verdicts on the same finding. Precedence is now explicit: the bar governs findings a reviewer originates from its own judgment, never rules the project wrote down. The project already decided those matter and that decision is not the reviewer's to re-make.
Codex pass 2 found three, down from four, all still substantive and all one family: precedence between scope, project rules, and materiality. The order is now defined once in the shared contract and referenced by the rest. Scope first — an out-of-scope finding is deferred however material it looks in isolation, because a valid improvement to work the change deliberately did not do is still not this change's problem, and materiality must never promote something back into scope. Then contract: project rules and security or privacy invariants block by virtue of being rules the project wrote down. Materiality ranks only what survives both. Saying "triage by materiality first" had inverted this in codex-review and coderabbit-review, and the dev prefilter could demote a mandatory violation the reviewer had correctly marked blocking. The larger hole was the recorded-decision rule. As written it made any decision with a rationale a non-finding unconditionally, so an artifact that documented why it leaked a credential or lost data would have been escalated once and then allowed to converge. That rule now covers disagreements of preference only: if the decision itself is the defect — security, privacy, data loss, or a mandated contract — it is a Material finding and stays blocking until resolved. Writing down why you did an unsafe thing does not make it safe.
Codex pass 3 found three, all the same shape: the new convergence rules landed in the middle of each skill while the top-level summary and the final gate still said "fix everything it flags" and "re-run until clean". An agent reading either could keep editing and rerunning nitpicks, which is the exact churn this change exists to stop. coderabbit-review's local-first summary and codex-review's introduction now scope fixes and reruns to material or substantive findings, and both PR gates read "converged" rather than "clean", with immaterial observations explicitly not holding the PR. copilot-review's Step 5 said repeat until zero new threads. Since resolving an immaterial thread by reply creates no commit, the head does not move and no further review is owed — chasing a zero-thread pass would spend a wait cycle to change nothing. Convergence there is now no new material or substantive findings, every thread resolved, and the suppressed block triaged, all on a reviewed head, with the note that only a push restarts the loop.
There was a problem hiding this comment.
Pull request overview
Adds a “materiality bar” to the shared scope contract so automated review loops converge on substantive issues (behavioral/correctness/security/contract violations) while batching immaterial observations into a single non-blocking closing note. This updates the core scope contract reference and wires the new triage/stop rules into the main review/resolution skills, along with a “verify before fixing” check to avoid churn from incorrect premises.
Changes:
- Extend
scope-contract.mdto define the materiality bar, filter precedence (scope → contract → materiality), and a convergence definition based on absence of material/substantive findings. - Update reviewer/resolver skills (
codex-review,copilot-review,coderabbit-review,pr-reviewer,resolve-pr-feedback,dev) to apply materiality in triage and redefine stopping conditions accordingly. - Bump versions for
fx-devand the marketplace metadata to reflect the new mandatory review rule.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| plugins/fx-dev/skills/resolve-pr-feedback/SKILL.md | Adds materiality triage and updates convergence criteria for the resolve loop. |
| plugins/fx-dev/skills/pr-reviewer/SKILL.md | Instructs reviewers to rank findings by materiality and report tier counts. |
| plugins/fx-dev/skills/dev/SKILL.md | Applies materiality bar to reviewer-originated findings while preserving contract-required blocking rules. |
| plugins/fx-dev/skills/dev/references/scope-contract.md | Introduces the materiality bar, precedence rules, and convergence definition into the canonical contract. |
| plugins/fx-dev/skills/copilot-review/SKILL.md | Adds materiality-aware triage and convergence definition for Copilot-driven loops. |
| plugins/fx-dev/skills/codex-review/SKILL.md | Shifts from “until clean” to “until converged” and adds a convergence-pass prompt block. |
| plugins/fx-dev/skills/coderabbit-review/SKILL.md | Updates local/PR-level CodeRabbit guidance to fix only material/substantive findings and converge. |
| plugins/fx-dev/.claude-plugin/plugin.json | Bumps fx-dev version to 3.9.0. |
| .claude-plugin/marketplace.json | Bumps marketplace metadata version to 0.22.0. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Copilot found a leftover Codex's fourth pass had declared consistent: coderabbit-review's Mode 1 success criteria still required `cr review --agent` to report no ACTIONABLE findings, contradicting the convergence definition added a few sections above it and reintroducing the rerun-until-silent loop by the back door. Mode 1 now converges on no material or substantive findings, with remaining immaterial observations carried as a closing note.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (1)
plugins/fx-dev/skills/dev/SKILL.md:321
- The new materiality paragraph says reviewer-originated findings must clear the bar “before they can be classified”, but the ledger’s classes are defined earlier as
required-by-contract/regression-caused-by-change/follow-up/out-of-scope. As written, it’s unclear whether “classified” means any ledger classification or specifically the two blocking classes, and it also reads ungrammatically (“findings a reviewer originates”). Clarifying that the bar gates only the two blocking classes will prevent mis-triage (e.g., treating immaterial observations as needing a blocking-class classification).
**Findings a reviewer originates from its own judgment must clear the materiality bar before they can be classified** (`references/scope-contract.md`). An observation that would change nothing if it shipped uncorrected is neither `required-by-contract` nor `regression-caused-by-change` — it is recorded in the ledger as immaterial and never triggers a rerun.
**The bar does NOT apply to violations of a mandatory rule.** Anything Step 2.5 defines as `required-by-contract` — project, security, privacy, test and merge rules the project wrote down — is blocking by virtue of being a rule, whatever its direct behavioural impact. The contract filter runs before the bar, exactly as `references/scope-contract.md` § Three filters specifies, so the coordinator can never demote a violation the reviewer correctly marked blocking. Two shapes in particular are non-findings and MUST NOT enter the ledger as blocking: a missing entry in a list the artifact declares non-exhaustive, and a decision the artifact records with its rationale. The second, raised twice, is an escalation to the user rather than a third cycle.
Copilot's suppressed-comments block flagged a real ambiguity: "must clear the bar before they can be classified" reads as though immaterial findings are kept out of the ledger entirely, which the very next sentence contradicts by recording them there. The bar gates the two BLOCKING classes -- required-by-contract and regression-caused-by-change -- and nothing else. Everything is still recorded; immaterial findings are classified immaterial rather than excluded. Reworded to say so, which also fixes the awkward phrasing.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
plugins/fx-dev/skills/coderabbit-review/SKILL.md:17
- This still says to “Resolve all actionable findings” before opening the PR, but the skill now defines convergence as “no material or substantive findings” and allows remaining immaterial observations to be carried as a closing note. Keeping the older “actionable” wording can reintroduce a fix-everything interpretation and churn.
- Open the PR once the local review has **converged** — no material or substantive findings — **or is correctly degraded as `skipped (rate-limited)`**. Resolve all actionable findings already received before proceeding.
plugins/fx-dev/skills/resolve-pr-feedback/SKILL.md:206
- The convergence condition here can be satisfied with zero unresolved threads even if Copilot’s latest review body contains new findings in the “Suppressed comments” block (which produces no threads). That contradicts this skill’s success criteria (#1) requiring suppressed comments for the current head to be included/triaged, and could cause premature convergence.
6. Stop when a pass produces **no new material or substantive findings** on a head SHA that was actually reviewed (verify: the newest Copilot review's `commit_id` equals `headRefOid`) **and every automated thread on that head is resolved**. Immaterial threads resolved by reply satisfy this — they are not "new feedback" for the purpose of another cycle, because actioning them would produce a push and therefore manufacture the next round's input. Cap at 4 outer iterations and escalate to the user if not converged.
…ctionable" Two suppressed Copilot findings, both real. Rewriting resolve-pr-feedback's step 6 around threads dropped the suppressed-comments condition that the previous "zero new feedback" wording implicitly carried. Since those findings create no thread, the rewritten rule could converge with real findings outstanding -- contradicting the skill's own success criterion 1 and the D4 warning it is built around. Step 6 now requires the suppressed block for the reviewed head to be empty or fully triaged, alongside no new material/substantive findings and all threads resolved. coderabbit-review's local-first bullet still said "resolve all actionable findings" in the same sentence that defined convergence as no material or substantive findings, which reintroduces the fix-everything reading it was meant to remove.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (4)
plugins/fx-dev/skills/dev/SKILL.md:321
- These two paragraphs read as contradictory: the first says the materiality bar gates entry into
required-by-contract, but the next says the bar does not apply to mandatory-rule violations that are also classifiedrequired-by-contract. Clarify that the bar only gates judgment-originated findings before they can be treated as blocking; rule violations remain blocking regardless of materiality.
**A finding that a reviewer raised on its own judgment must clear the materiality bar before it can enter either BLOCKING class** — `required-by-contract` or `regression-caused-by-change` (`references/scope-contract.md`). The bar gates those two classes only; it does not keep anything out of the ledger. An observation that would change nothing if it shipped uncorrected is still recorded, classified immaterial, and never triggers a rerun.
**The bar does NOT apply to violations of a mandatory rule.** Anything Step 2.5 defines as `required-by-contract` — project, security, privacy, test and merge rules the project wrote down — is blocking by virtue of being a rule, whatever its direct behavioural impact. The contract filter runs before the bar, exactly as `references/scope-contract.md` § Three filters specifies, so the coordinator can never demote a violation the reviewer correctly marked blocking. Two shapes in particular are non-findings and MUST NOT enter the ledger as blocking: a missing entry in a list the artifact declares non-exhaustive, and a decision the artifact records with its rationale. The second, raised twice, is an escalation to the user rather than a third cycle.
plugins/fx-dev/skills/dev/references/scope-contract.md:75
- The materiality section defines a reportable finding as one where acting on it changes behavior/reader action, but the very next section establishes a contract filter where mandatory-rule violations are blocking regardless of behavioral impact. To avoid readers applying the first sentence too broadly, scope the definition to the post-scope/post-contract set (judgment-originated findings) so it matches the three-filter precedence described below.
**A finding is worth reporting when acting on it would change what the artifact does, or change how a competent reader acts on it. Everything else is an observation, not a finding.**
plugins/fx-dev/skills/resolve-pr-feedback/SKILL.md:26
- This line calls below-the-bar items “findings”, but the materiality bar definition in scope-contract.md explicitly defines those as “observations, not findings”. Using consistent terminology here matters because this skill’s loop logic hinges on whether something is a new finding that should drive another cycle.
An in-scope finding that would change nothing if it shipped uncorrected — wording, formatting, a count nothing keys on, an entry missing from a list the artifact itself declares non-exhaustive — is **resolved by replying with that reasoning**, not by editing. Every thread still ends resolved; the gate is zero *unresolved* threads, not zero observations acted on.
plugins/fx-dev/skills/copilot-review/SKILL.md:105
- This paragraph refers to below-the-bar items as “findings”, but the scope contract now defines those as “observations, not findings”. Aligning the terminology here helps prevent reviewers from treating immaterial items as “new findings” that require a push/re-review cycle.
Triage by **materiality** as well as scope — see the materiality bar in the same
reference. An in-scope finding that would change nothing if it shipped
uncorrected is **resolved by replying with that reasoning**, not by editing. The
gate is zero *unresolved* Copilot threads, not zero observations acted on.
…minology Four suppressed Copilot findings. Two substantive, two terminology; all four ride in one push because the bar governs whether an observation buys another review CYCLE, not whether you fix it while already editing the file. The dev paragraphs read as a contradiction: the first gated entry into required-by-contract on the bar, the second said the bar does not apply to mandatory-rule violations, which are classified required-by-contract. Both are now framed by origin rather than by class -- the bar applies to what a reviewer raised on its own reading; violations of a written rule are blocking regardless. The materiality headline in the shared contract stated the test universally, then the very next section carved out mandatory rules, inviting a reader to apply the first sentence too broadly. It is now scoped to what survives the scope and contract filters. resolve-pr-feedback and copilot-review called below-the-bar items "findings" where the contract defines them as observations. The loop logic keys on whether something is a new finding, so the two words should not disagree.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
plugins/fx-dev/skills/coderabbit-review/SKILL.md:122
- This step says "Do not open the PR with known unresolved actionable findings", which is ambiguous/inconsistent with the new definition of convergence (material/substantive findings block; immaterial observations travel as a closing note). Tightening this wording helps prevent reintroducing the "fix until silent" loop.
- When you stop, report the per-pass trend and whether the last round's fixes were themselves reviewed.
### Step 4: Open the PR when clean or correctly degraded
A clean local CodeRabbit review is preferred before PR creation. A rate-limited review is correctly degraded and does not block PR creation once all findings already received are addressed. Do not open the PR with known unresolved actionable findings.
plugins/fx-dev/skills/resolve-pr-feedback/SKILL.md:24
- This paragraph says “a project-rule or security violation blocks regardless of the bar”, but omits privacy. Since the contract filter explicitly treats privacy invariants as blocking, this wording could cause a resolver to incorrectly triage privacy issues as non-blocking/immaterial.
Scope decides whether a finding is *ours*. Materiality decides whether it is *worth a fix* — see the materiality bar in `fx-dev/skills/dev/references/scope-contract.md`, which also fixes the order: **scope, then contract, then materiality**. An out-of-scope finding is deferred however material it looks, and a project-rule or security violation blocks regardless of the bar. Materiality ranks only what survives both.
Two suppressed Copilot findings, plus a proactive sweep for the same two classes across every file this change touches, so they stop arriving one per review cycle. coderabbit-review's Mode 2 gate still said "do not open the PR with known unresolved actionable findings", the last of the fix-until-silent wordings. A grep for "unresolved actionable", "until clean" and "reports no actionable" across all five review skills now returns nothing. resolve-pr-feedback's triage-order paragraph listed "project rule or security violation" and omitted privacy, which the contract filter treats as blocking, so a resolver could have triaged a privacy issue as immaterial. The other two paragraphs of the same shape already said security/privacy; this one now matches.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (1)
plugins/fx-dev/skills/dev/references/scope-contract.md:75
- This sentence is grammatically ambiguous: “...one is worth reporting...” can read as “only one finding is worth reporting,” which conflicts with the intended rule and could mislead reviewers applying the contract. Rephrase to clearly state that a reviewer-originated finding is worth reporting when it meets the bar.
**Of the findings a reviewer originates from its own judgment — that is, what survives the scope and contract filters below — one is worth reporting when acting on it would change what the artifact does, or change how a competent reader acts on it. Everything else is an observation, not a finding.**
The previous round's rewrite scoped the bar correctly but introduced a grammatical ambiguity in the process: "...one is worth reporting..." reads as "only one finding is worth reporting". That is the headline sentence of the whole rule, so a misreading there propagates. Split into two sentences: what the bar applies to, then when such a finding clears it.
Codex pass 2 found five, down from nine, and all five were real. The largest was one class across three skills: a passage that decides "immaterial" purely on "would change nothing if it shipped", with no contract filter in front of it. A rule violation with no behavioural impact passes that test, and since a coordinator's disposition is authoritative, an immaterial label on one suppresses a required fix. copilot-review's triage paragraph, and the stopping test in both codex-review and coderabbit-review, now run the contract filter first. The nitpick escape hatch in both said "if it turns out to clear the bar" -- which routes a contract blocker through a bar it is defined never to reach -- and now says "turns out to be blocking". The PR feedback loop could not supply its own newly mandatory dispositions after iteration 1: loop step 3 re-queried with the breakdown query, which returns reviewer names and counts, while step 4 refuses a resolver invocation without a disposition per thread. Every iteration now repeats the full fetch and triage. Dispositions were also being demanded for every thread, and a thread is not automatically a finding. An outdated or convention-misreading thread is a false positive, none of the three dispositions is true of it, and labelling it immaterial to fill the field overrides the resolver's own incorrect/outdated handler -- losing the REVIEW.md entry that is the only thing stopping the finding coming back. The contract now says to verify the premise and leave a false positive undisposed; the three coordinators and dev say the same. CodeRabbit's rate-limit degradation resolved blocking findings and then marked the pass skipped, leaving delivered immaterial and deferred threads open behind a Mode 2 gate that requires zero unresolved threads. Throttling waives the unrun remainder, never a thread already posted -- said now at the top of the skill and in the Mode 2 success criteria, where Mode 1's criteria already said it. Finally, "clean" survived as the stopping word in the two local reviewers, in pr-preparer's PR gate and in team's reviewer gate, next to a contract that now defines convergence as no unresolved blocking finding. A reader reconciling the two resumes the silence chase. Swept to "converged"; left alone where "clean" means evidence rather than a stop -- copilot-review's "absence of a review is not a clean review" and dev's description of the generic instruction it overrides. pr-preparer and team are not otherwise part of this branch. Their gate sentences went stale because this change redefined convergence, so they are in scope as regressions it caused; the brief's blanket "files not modified on this branch" exclusion was too broad for them.
Codex pass 3 found six: 9, 5, 6 across the three passes. Two of the six were siblings of pass 2's own fixes -- a rule tightened at the site that defines it and left unpropagated to the sites that consume it -- and four were ground the earlier passes had not reached. Propagation, first. Pass 2 made CodeRabbit's rate-limit degradation waive only the passes that never ran, so anything already delivered still has to be resolved. That landed in coderabbit-review alone, while dev Step 6.3, team's reviewer gate, dev's and team's gate-table rows and github's merge-gate note all still said record the skip and proceed -- a caller following the outer workflow could merge past an open thread. All six now say the same thing. Conversely pr-preparer demanded convergence with no degradation clause at all, so a legitimately rate-limited CodeRabbit would have held the PR; it now accepts the recorded skip. Second, pass 2 told the coordinators to leave a false-premise thread undisposed so the resolver's own handler takes it -- but the rabbit resolver has no such handler. Its table had no Outdated or Incorrect row, so a CodeRabbit comment about code that no longer exists had no prescribed action and would sit open against a gate that requires zero unresolved threads. The copilot twin has had both rows all along; rabbit now matches, and the table says explicitly that an undisposed thread is a verified rejection rather than an oversight. The four on new ground: The Codex prompt said never to re-report a rejected finding. A rejection is against a state of the tree, and later fixes change that tree -- a finding rejected because some validation made it moot becomes valid the moment a fix removes the validation. The instruction is now conditional on the reason still holding. Both resolvers, invoked standalone, were told to run the scope filter with no Scope Brief to run it against. The contract already requires a reviewer without one to reconstruct it and say so; the two resolvers were the only skills of the seven that carry that rule which did not. dev recorded an in-scope immaterial observation as follow-up/out-of-scope while the rule two sections down said entries in that class exited at filter 1 and are tier n/a. Both origins are legal for the class -- the tier is what differs, and the tier is what picks the resolver disposition, which is the same thing the bounded-review section says. resolve-pr-feedback's loop nudged Copilot and waited before checking whether anything had been pushed. A cycle that only replies and resolves leaves the head unmoved and already reviewed, so the nudge bought a full wait to re-read code nobody changed -- the same churn as editing for an immaterial finding, in the skill that warns against it.
Codex pass 4 found seven: 9, 5, 6, 7 across four passes. Six are fixed; one is deferred as predating the branch. The worst was mine. Pass 2 widened the Codex prompt's open-list rule to match the canonical "for example" OR "declared illustrative", and stopped there -- leaving six other skills restating the narrow form. That is precisely the half-closed class the contract warns about, and the surviving copies would have kept the list-completeness churn alive in every reviewer but Codex. All seven sites now read "does not present as exhaustive", which covers both forms in fewer words, and the canonical heading matches. The ledger could not represent one of its own blocking findings. A reviewer-originated Substantive finding -- a two-way ambiguity in something this change wrote -- is neither required-by-contract nor a regression, and Step 4.5 asserted that anything Material or Substantive belongs to one of those two classes. So it had no legal classification, while the stopping condition three lines down already assumed it did and blocked on it. The classification answers what obliges the fix; the tier answers whether it blocks. Both are now said that way, and "fix only the first two classes" now also covers anything blocking by tier. github's merge-gate table still marked "CodeRabbit comments resolved" optional when rate-limited, one row above the paragraph this branch added saying delivered threads must be settled. The table was the part an agent reads at merge time. CodeRabbit's Mode 2 loop passed its gate on the latest check plus a zero thread count, with no ledger test -- so a blocker raised two passes ago and never fixed, whose thread was resolved by a reply, would pass. Mode 1 has had the ledger test since this branch started; Mode 2 now does too. Two smaller ones: dev listed a rising same-class blocking count as an exit that ends the loop, where the contract makes it a cause to fix and continue, escalating only if the cause is a design choice. And pass 3's own new text declared every coordinator-undisposed thread a verified false positive, when the paragraph above it says the coordinator may simply not have covered a thread -- so an untriaged blocker could be resolved with a reply and no fix. Only a thread undisposed *with a stated reason* takes the outdated/incorrect path. Deferred, out of scope: team and pr-preparer both describe pre-PR Codex as `codex review --base main`, which is the promptless form -- the Scope Brief cannot ride on it. The mutual exclusion of `--base` and a prompt is documented on main and predates this branch, so the staleness is not a defect in work this change did. Worth a follow-up with the bare `args="<PR_NUMBER>"` reviewer calls in team, which have the same cause.
…d not Codex pass 5 found five: 9, 5, 6, 7, 5 across five passes. All five valid, all five fixed. First, a correction. Pass 4's commit message claimed the rabbit resolver's undisposed-thread rule was fixed. It was not -- the batch that applied those edits aborted on an earlier assertion and never reached that one, so the blanket "an undisposed thread is a verified false positive" survived while the message said otherwise. Codex found it again, correctly. It is fixed now: only a thread left undisposed *with a stated reason* takes the outdated/incorrect path, and a bare omission is untriaged and gets the filters, because reading one as a rejection resolves a real blocker with a reply and no fix. Second, pass 4's own ledger fix was half-propagated -- the same failure it was fixing. Step 4.5's remediation rule, Step 6.2's coder prompt, Step 6's dispatch rule and the merge checklist all still said fix the two named classes, so the reviewer-originated blocker that pass 4 made representable still had no path to being fixed. Every remediation and gate site now keys on blocking, not on the class name, and the legality rule is restated as being about the filter rather than the class: an entry excluded at filter 1 cannot carry a Material or Substantive tier, and that is the only illegal pair. Three smaller ones. Step 6.3 described a "15-round remediation loop", which buys a sixteenth pass on top of the initial review -- the bound counts the initial pass as iteration 1, and the local restatement is gone rather than corrected. The contract said blocking and immaterial were "the whole vocabulary", which reads a deferred finding out of existence and invites folding one into immaterial, losing the exclusion; that claim is now scoped to what has passed the scope filter. And the recorded-decision carve-out in pr-reviewer and dev listed credentials, data loss and security/privacy but dropped canonical's fourth limb, so a reasoned violation of any other mandated contract could be waved through as preference.
Codex pass 6 found five: 9, 5, 6, 7, 5, 5. All five valid, all fixed. The ledger-class routing surfaced for the third pass running, so this round fixed the cause rather than the reported sites. The cause was the phrase "blocking-class findings", which reads either as "findings that block" or as "findings in the blocking classes", and the second reading is what kept leaking back: dev's Step 6.3, its three summary rows and team's reviewer policy all used it, so a reviewer-originated Material finding sitting legally in follow-up/out-of-scope was excluded from remediation by the very sentence meant to include it. The term is gone from both skills, every remediation and gate site now names the blocking definition, and the planner and coder prompts say non-blocking rather than naming the class. Proved by a normalised sweep for the routing verbs near a class name: the only remaining hit is project-management's change-document rule, which is about scope expansion rather than review remediation. An out-of-scope comment carrying a nitpick label was being read as a nitpick. Both resolver tables and both nitpick handlers said "clears none of the filters", and scope is the first filter — so an item that exits at filter 1 satisfied the test and lost the exclusion and the follow-up record that deferral keeps. All four now require the item to reach filter 3 and fail it, and the two local reviewers say the same for the closing note. The nitpick handlers also let a thread be closed with an optional acknowledgment while the dispositions require a reply carrying the reasoning, and both summary tables still showed "Auto-resolved" as the action. A silently closed thread leaves no record of why no edit was made. Two smaller ones: the Codex mirror's material limb said "break a build or test" where canonical says build, CI or test, so a lint-only CI failure could be demoted by a reviewer that cannot follow the link. And the iteration bound restated the repeated-disagreement trigger as "twice" where Convergence defines it as successive passes — a disagreement raised, absent, then resurfacing resolved differently depending on which sentence you read.
Codex pass 7 found two: 9, 5, 6, 7, 5, 5, 2. Both valid, both fixed, and both were siblings of my own previous rounds' fixes. Tier `n/a` is carried by two unrelated filter outcomes. A contract blocker is `n/a` because filter 2 stops before the bar; a finding excluded at filter 1 is `n/a` because it never reaches the bar. Pass 3 mapped `n/a` straight to the `deferred` disposition, so a coordinator following it would reply-and-resolve a contract blocker instead of fixing and pushing it -- the single worst outcome the disposition vocabulary can produce. The mapping now reads the filter outcome, and says explicitly that the tier alone cannot pick it. Pass 6 replaced "the same disagreement twice" with the canonical successive-passes trigger in the iteration bound and nowhere else, so five downstream restatements still carried the looser form -- which would stop a loop over a concern raised in pass 1, absent in pass 2 and validly resurfacing in pass 3. All five now reference § Convergence instead of restating it. Proved by a whitespace-normalised search for "twice" near a disagreement or escalation across every SKILL.md and the reference: zero hits.
Codex pass 8 found four: 9, 5, 6, 7, 5, 5, 2, 4. All four valid, all fixed. Two were sites my pass-7 sweeps were too narrow to catch. Pass 7 fixed the `n/a`-to-`deferred` mapping and verified it by searching for that mapping's wording. The same conflation lived in two other forms -- Step 6's dispatch rule and Step 6.2's coder prompt both excluded "tier n/a or immaterial" from implementation, and a required-by-contract entry carries tier `n/a` precisely because filter 2 stops before the bar. So the two sentences that select work for the coder dropped every mandatory rule violation. Both now select on blocking and say why the tier cannot stand in for it, and the PR gate that legitimately pairs the tier with the class now says that pairing is the point. Pass 7 also swept the escalation trigger by searching for "twice", which missed the synonyms: copilot-review said "as soon as the same disagreement repeats" and resolve-pr-feedback said "a repeated disagreement". Both would stop a loop over a concern raised in pass 1, absent in pass 2, and validly resurfacing in pass 3. Verified this time by searching for the concept rather than the word. Two in the review skills. The Codex mirror asserted flatly that wording and formatting are not blocking, three lines below its own item 1 making a violation of a written project rule blocking -- so a project with a mandated wording rule got two contradictory instructions and no link to resolve them. And copilot-review said every non-blocking thread is settled without a commit, which silently forbade the `REVIEW.md` entry that a misread-convention thread requires: that entry is the only thing that stops the false positive recurring, and it is required work rather than an edit made for an immaterial finding.
Codex pass 9 found two, both P2: 9, 5, 6, 7, 5, 5, 2, 4, 2. Both valid, both fixed, and both were the remaining site of a class an earlier pass closed only partway. Pass 5 restored canonical's fourth limb -- a reasoned decision that contradicts a mandated contract stays blocking -- to pr-reviewer and dev, and missed the Codex prompt, which is the one reader that cannot follow the link to find out. A reasoned decision contradicting a spec or change document with no security symptom could be waved through there as settled preference. All four sites now carry all four limbs. Pass 2 gave resolve-pr-feedback a suffix for handing a verified false positive to the resolver, since such a thread deliberately carries no disposition and the reason is the only thing that routes it to the outdated/incorrect path. The other invocation templates never got it, so a coordinator following coderabbit-review or copilot-review dropped the evidence and the resolver re-triaged the omission -- losing the REVIEW.md entry that is the entire point of the incorrect path. Every template that carries dispositions now carries the suffix too, including the two prose restatements; verified by checking every `args="..."` line that mentions a disposition.
…g the bound Codex pass 10 found six: 9, 5, 6, 7, 5, 5, 2, 4, 2, 6. All six valid and fixed. They are six different classes rather than one recurring one, so the rise is the review still finding new ground. The worst let a resolver close a blocker on its own reading. Both rabbit handlers said that when a premise does not hold, reply with the evidence and resolve -- even for a thread the coordinator marked `blocking`, whose disposition is authoritative precisely because the coordinator holds the brief and the ledger and the resolver does not. A correctness or security blocker could therefore be closed with a reply because the resolver read the tree differently. Now the thread goes back for reclassification and stays open; only a disposition the resolver assigned itself may it reverse locally. The contract gave two incompatible treatments for an entry missing from a declared-open list: the Immaterial tier row sent it to the closing note, and the non-findings section said not to report it at all. The tier row no longer lists it, and the exclusion is stated with its reason -- a note listing such entries grows without bound and invites the next pass to extend it. The bound's number was restated in five skills without the clause that makes it right. "Cap at 15 iterations", read after the initial review, permits fifteen reruns and so sixteen passes. Every site now names the bound and says it counts the initial pass as iteration 1; the number appears once, canonically. dev's copy was correct but is now a reference too, since one surviving number is how the drift restarts. Two in this skill's own prompts. The mandatory first-pass template stopped before the BLOCKING block, contradicting the rule three sections down that every prompt carries it -- so the pass that most needs the bar was the one built without it. And the rerun preamble said prior issues were "all fixed except the rejected ones", which describes neither an immaterial item nor a deferred one, so Codex was told they had disappeared and could raise them again stripped of their reasoning. It now carries four lists. Last, dev claimed a Material or Substantive finding could be `required-by-contract`. A written-rule violation stops at filter 2 unranked, so it never reaches the bar at all: anything the bar ranked is by construction something no written rule covers.
…invocations Codex pass 11 found four: 9, 5, 6, 7, 5, 5, 2, 4, 2, 6, 4. All four valid and fixed. The deferral is reversed. Bare reviewer invocations and the promptless `codex review --base main` had been deferred twice as predating the branch, and the item came back a third time -- this pass naming dev/SKILL.md:742, a file this change edits heavily, which the "not modified on this branch" exclusion never covered. Three passes spent re-litigating a three-line fix is worse than the scope purity it was protecting, so all four sites now carry the brief, and team and pr-preparer say why the promptless form cannot: the CLI rejects `--base` together with a prompt. Convergence was being re-derived from materiality in both local reviewers: "stop when what remains would change nothing and none of it is a contract blocker". That is the ledger test rebuilt out of its two edge cases, and rebuilt wrong -- a deferred finding that looks material in isolation, like missing implementation on a docs-only change, reads as something that would change something, so the loop keeps going after it has already converged. Both now name the one test and say it already settles both cases. Yesterday's fix to the Immaterial tier row -- an omission from a declared-open list is excluded, not noted -- reached the contract and not the two reviewers that restate it, so they still routed such entries into the closing note the contract had just stopped listing them in. Both fixed. Last, the rabbit resolver only allowed its outdated/incorrect handlers when a coordinator supplied the rejection reason, and this skill also runs standalone. The three filters rank a real finding; they do not detect a false one. So a resolver that found the misread convention itself had nowhere to put it and lost the REVIEW.md entry.
Seven skills each carried their own copy of the same review rules, and the last eleven Codex passes on this branch were substantially spent on the consequences: a rule tightened in one skill and left narrower in five others, "clean" surviving as a stopping word in four, the open-list rule widened in the Codex mirror and nowhere else, the escalation trigger restated four different ways. Every one of those was the same defect -- a rule with no single home -- and fixing them one sweep at a time was never going to end, because the copies were the cause. So the copies are gone. `fx-dev:review` now holds the whole procedure: carrying the Scope Brief, triaging scope then contract then materiality, the three things that are not findings, verifying a premise before acting on it, sweeping a defect class and proving the sweep, what each disposition means, the REVIEW.md entry for a false positive, the convergence test and its three exits, and what to report. Every reviewer and resolver loads it first and says so, and states that it wins on any disagreement. The division of labour is now: scope-contract.md holds the definitions, this skill holds the procedure, and an adapter holds only what is true of one tool. That leaves the adapters carrying what they should have carried all along -- codex-review the MCP-stall workaround and the --base/prompt exclusivity, coderabbit-review the cr CLI and the App's check and the rate-limit degradation, copilot-review D1-D5 and the waiter and suppressed comments, the resolvers their GraphQL mutations and the table mapping each platform's comment shape onto a disposition. pr-reviewer keeps the project-rule pass and its output format, which is all that was ever specific to it: 169 lines to 113. One exception survives, marked as one: the block passed verbatim to `codex review`. That string crosses a process boundary and cannot follow a link, so it stays a mirror -- the single case the define-once rule exempts. Also folds the same treatment into dev Step 4.5, which had its own copy of the brief-injection rule, and registers the skill in the README along with codex-review, coderabbit-review and copilot-review, which were missing from the list entirely. Net: 2,639 lines across the eight files becomes 2,235, and the review rules have exactly one home each.
Codex reviewed the restructured branch as a fresh artifact, looking specifically for a rule lost in the extraction. It found none — the main risk of the change did not materialise. It found five other things, all valid, all fixed. The canonical skill's own disposition table listed "false premise" as a fourth row, which reads as a fourth disposition a coordinator could assign authoritatively — closing off the very outdated/incorrect route that a false premise exists to reach, and losing the REVIEW.md entry with it. There are three dispositions; a false premise is the absence of one, and the table now says so outside itself. It also said a "correctness defect" carrying a reviewer's label is still blocking, which reads as correctness bypassing the bar. A label demotes nothing, but calling something a correctness defect does not promote it either: a false count nobody acts on reaches filter 3 and ranks immaterial, exactly as the bar says. CodeRabbit's Mode 2 summary and its concurrency note both reduced settlement to a passing check plus zero threads, dropping the ledger condition that Step 3 states three lines up. A thread is closed by a reply and a blocker only by a fix, so those two are not the same test and the summary was the weaker one an agent would read at merge time. copilot-review's Arguments section and copilot-feedback-resolver's invocation line still showed bare calls, after the rest of the branch had been swept for exactly that. And the README and pr-reviewer's frontmatter still advertised "actionable feedback" — the pre-bar model this whole change replaces, sitting in the two places most likely to be read first.
Codex pass 13 found four, all P2, all valid. Second pass running with no rule reported lost in the extraction. The most useful is a capability the skill denied. `cr review` takes `-c, --config <files...>` — "Additional instructions for CodeRabbit AI" — so Mode 1 is a prompt-capable reviewer after all, and the brief can reach it before the review rather than only at triage. The canonical contract already named `cr` as an external tool owed a faithful inline mirror; the adapter said the brief could not reach it, and the two could not both be right. Verified against `cr review --help` on the installed CLI, not assumed. Checking that surfaced two flags this skill has been documenting incorrectly: `--type committed|uncommitted|all` does not exist — the real flags are `--committed`, `--uncommitted` and `--include-untracked` — and `--config`, `--dir`, `--light` and `--base-commit` were missing entirely. Anyone who ran the documented command got an error. The canonical filter-2 summary dropped its third arm. Canonical covers a project rule, a security or privacy invariant, **and any other mandatory requirement the project wrote down, including a change document or a spec it links**; the summary named only the first two, so a requirement recorded in a change document could fall through to materiality and be demoted. "Zero out-of-scope findings means the brief was well built" is only true of a reviewer that received one. For Copilot and the CodeRabbit App the output is independent of the brief's quality, so tightening it in response is a loop that cannot converge. Those runs are judged on triage coverage instead. And the rabbit resolver, on rejecting its own blocking suggestion for a false premise, replied and resolved directly — bypassing the Incorrect row and the REVIEW.md entry that is the only thing stopping the finding recurring. Both handlers now route through it.
Codex pass 14 found four, all P2, all valid. Third pass running with no rule reported lost in the extraction. Three were consequences of pass 13's own fix. Teaching coderabbit-review to pass the brief through `cr -c` told it to include "the BLOCKING block" — which lived in codex-review, a skill it never loads. An agent following it would have omitted the materiality rules or written its own copy, which is the duplication this whole change removes. Since two external tools now need the same string, the block moves to `fx-dev:review` § The external-reviewer block: one mirror, both adapters sending it, neither holding a copy. The rerun command also dropped `-c`, so convergence passes went unscoped after the first. The fourth is a contradiction I introduced across three files. A rising same-class blocking count was described as a loop *exit* in the contract and in `fx-dev:review`, while dev said to fix the cause and continue. Stopping there ends the loop on a root fix nothing has reviewed, which is not convergence. It is a divergence signal, not an exit: fix the cause, keep looping, and end only if the cause is a design choice with two defensible answers. There are two exits, not three, and the heading now says so. Also: an omission from a declared-open list was excluded from the findings list but not from the closing note, in the external mirror and in dev's ledger rule — so the churn survived under a different name. Neither reports it at all now.
…onds Every Codex pass on this branch took 10-15 minutes, because `codex review` inherits `model` and `model_reasoning_effort` from the user's config and that default is `gpt-5.6-sol` at `xhigh`. At that cost the convergence loop is unaffordable long before its iteration bound, which is a real obstacle to the loop this whole branch is about. Benchmarked both alternatives at three efforts, one real ~9 KB review prompt against this 13-file branch, each run in an isolated CODEX_HOME so its rollout and timing were unambiguous, then re-ran the two finalists sequentially to confirm the concurrent numbers were not distorted by contention -- they were not, 85 s and 88 s against 85 s and 103 s. terra/medium at 88 s is the pick: about ten times faster than the sol/xhigh default and still substantive. Speed alone would have chosen luna/low at 45 s, but luna is the wrong trade here -- it reported "no blocking defect found" at both medium and high, while terra at medium found a genuine P1 and terra at high enumerated every site of the class. A reviewer that finishes fast by finding nothing is not faster. Recorded as a per-invocation `-c` override with the measurements behind it, not as an edit to the user's `~/.codex/config.toml` -- that file is theirs and machine-wide, exactly as the MCP section already argues.
A skill is instruction an agent acts on, not a lab notebook. Six rows of one-off timings from one branch on one machine at one point in time are provenance: they tell an agent nothing it needs in order to run a review, they invite it to re-derive a choice that is already made, and they date the moment the models change. They had already drifted -- terra/medium measured 88 s when the table was written and 132 s on the very next pass, against a longer prompt on a branch that had grown. What survives is what an agent has to know: use terra/medium, do not trade down to luna for the extra speed because it reports clean where terra finds a real P1, reach for terra/high when you want a whole class enumerated, and do not go above high. The numbers behind that live in git history, which is where provenance belongs, and the section says how to find them. The copilot-review timeout table was checked and deliberately kept: those are values the agent must actually set, not a record of a past experiment.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
plugins/fx-dev/skills/review/SKILL.md:29
- The reference
dev/references/scope-contract.mddoesn’t match an actual path in this repo (the canonical file is underfx-dev/skills/dev/references/…). This makes the “define-once” pointer hard to follow for readers and risks link-rot in future edits.
This skill is the application of `dev/references/scope-contract.md`
§ Blocking's define-once rule to the review skills themselves.
… siblings Copilot's suppressed-comments block, on the head that pass 15 had just called clean. The premise checks out: `dev/references/scope-contract.md` resolves from nowhere, and it is the odd one out of 31 citations repo-wide -- 30 use `fx-dev/skills/dev/...`, and so do the other six in this very file. It is a stated fact that is false and that a reader acts on: an agent following the define-once pointer tries to open that path and fails. In the file this change adds as the canonical review procedure, which is the worst place to leave a dangling reference. Swept as a class rather than at the reported site. The search is the evidence: one occurrence of the bare form before, zero after.
The branch had walked fx-dev 3.8.2 -> 3.14.1 and the marketplace 0.21.2 -> 0.27.1, bumping on nearly every commit. That reads as six minor releases for what is one change, and the intermediate numbers signal nothing to Claude Code's plugin cache -- only the value that lands on main does. The hook never asked for per-commit bumps. It compares against the merge base with main and blocks only when the version equals main's, which its own header says in as many words: "multiple commits on a feature branch only require ONE version bump, not one per commit." So one step, sized to the change: minor. It adds a skill (fx-dev:review) and new mandatory rules across the review skills, and removes none.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (4)
plugins/fx-dev/skills/coderabbit-review/SKILL.md:165
- This section says not to use
run_in_backgroundbecause it loses the waiter output, butfx-dev:devMode B explicitly backgrounds the CodeRabbit waiter with output redirected to a log file and later read. Either the prohibition here needs to be qualified (background is OK with redirection), or the Mode B recipe becomes invalid.
**⚠️ Do NOT use `run_in_background`** — that loses the script output and breaks
the cycle.
plugins/fx-dev/skills/review/SKILL.md:315
- The external-reviewer block includes the “CONVERGENCE PASS” preamble, but the surrounding text implies it’s only for re-runs. Since the block is meant to be sent verbatim, clarify how pass 1 should populate (or zero) that preamble to avoid contradictory guidance (also referenced by
fx-dev:codex-review).
Send it on **every** pass, the first as well as every re-run. Pass 1 is where a
reviewer with no bar produces the largest crop of low-value findings, so omitting
it there costs the most. On a re-run the CONVERGENCE PASS preamble goes on top.
plugins/fx-dev/skills/codex-review/SKILL.md:233
- This says the CONVERGENCE PASS preamble is added “from pass 2”, but
fx-dev:review’s external-reviewer block currently includes that preamble as part of the verbatim text. Align this guidance so the prompt assembly is unambiguous for pass 1.
Send **`fx-dev:review` § The external-reviewer block, verbatim**, as the last part
of every prompt — the first pass as well as every re-run. That block is the single
mirror both external reviewers use; this skill does not keep its own copy, and
must not paraphrase it. Its CONVERGENCE PASS preamble goes on top from pass 2.
plugins/fx-dev/skills/resolve-pr-feedback/SKILL.md:103
- This skill claims to find every unresolved automated thread, but the GraphQL query only fetches the first 100
reviewThreadsand provides no way to detect pagination. On large PRs (or when multiple bots comment heavily), this can miss unresolved threads and falsely report convergence. Consider addingpageInfo(and looping withafter: endCursorwhenhasNextPageis true).
reviewThreads(first: 100) {
nodes {
id
isResolved
path
Copilot's suppressed block on d9db80d, three of four valid. No threads, so none of this would have surfaced from a thread count. The mirror's fenced text opens with "CONVERGENCE PASS <N>. Prior passes found <count> issues", while the prose above it says to send the block verbatim on every pass including the first, and then that the preamble "goes on top" on a re-run -- which implies it is not part of the block after all. codex-review said "from pass 2". So pass 1 had two readings: send a prefix asking the reviewer to honour a list of prior findings that does not exist, or strip it and hope that was intended. This is not theoretical. Assembling this branch's own pass-15 prompt, I had to pick one, and picked stripping it -- an instruction that needs its reader to guess right is the ambiguity the bar calls blocking. Now there are two labelled parts on two schedules: Part 2, the bar, on every pass including the first; Part 1, the convergence prefix, on top of it from pass 2 only. Fixed as a class across all four sites that describe sending it -- review's own section, codex-review's brief list, its prompt template and its re-run rule, and coderabbit-review's instructions file. Also: coderabbit-review banned `run_in_background` outright because it "loses the script output", while dev Step 6.3 mode B backgrounds that exact waiter with stdout redirected to a log it reads afterwards. Both in this change, opposite instructions to the same reader. The rule was always about losing the output, not about the process, and now says so -- foreground by default, backgrounding permitted when redirected. Swept the class: no other run_in_background prohibition names a script that anything else backgrounds. The Copilot waiter and the CI script are foreground everywhere they appear.
Summary
Review loops did not terminate. Every observation was treated as a finding, so each pass fixed what the last one raised and surfaced a fresh crop of smaller ones — the findings got less important, the passes kept costing the same, and the loop ended only when someone gave up.
Scope already answered what to review. Nothing answered what is worth reporting or when to stop. This adds a materiality bar to the shared scope contract that 17 skills import, and — after review of the change surfaced how much the rules had been copied around — restructures the review skills into one canonical procedure with seven adapters.
The rule
A finding is worth reporting when acting on it would change what the artifact does, or change how a competent reader acts on it. Blocking findings are reported individually; immaterial ones go in a single non-blocking closing note and never buy another pass. The test: if this shipped uncorrected, what breaks? If the answer is "nothing, it's just not as good", it's immaterial.
Three filters, in order — scope, then contract, then materiality. An out-of-scope finding is deferred however material it looks in isolation. Project rules, security and privacy invariants, and any other mandatory requirement the project wrote down — including a change document or a spec it links — block by virtue of being rules the project already decided mattered, so they never reach the bar at all. Materiality ranks only what survives both. Getting this order wrong was the single biggest source of contradictions found while reviewing this change.
Three shapes are not findings — generalized from the observed failure rather than baked in as examples: an entry missing from a list the artifact itself declares non-exhaustive (assess the rule; the supply of such entries never runs out, so these are not reported at any tier, not even the closing note), a preference disagreement with a decision the artifact records with its rationale (escalate once, don't re-argue), and a limit the artifact admits and gates. With an explicit carve-out: a recorded rationale does not make a decision safe — if the decision is the defect, it stays blocking however well reasoned.
Convergence is a ledger test, not a property of the latest pass: no blocking finding left unresolved. Not "zero output", which spends full cycles on wording, and not "a quiet pass" — a blocker carried from an earlier pass still blocks even if this pass didn't repeat it, and a finding that never produced a thread is never discharged by a thread count. There are exactly two exits: converged, or the same disagreement in successive passes (a human decision — escalate by name). A rising blocking count in one class is explicitly not an exit: the last fix is generating them, so fix the cause and keep looping, or the loop ends on a root fix nothing has reviewed.
Fix the class, not the instance. The single biggest cause of a loop that will not end: a reviewer reports the instance it happened to read, you fix exactly that, and the next pass finds a sibling and reports it as new. Every pass is productive and the loop still never terminates. Sweeps must be proven with a search whose empty result is the evidence.
One canonical procedure, seven adapters
Seven skills each carried their own copy of these rules. A copy drifts the moment the original changes, and a stale copy is reliably the narrower one — so it under-blocks, under-converges, or re-opens scope it should not. That was not hypothetical: the divergence was found and fixed repeatedly during this change, which is what motivated the restructure.
dev/references/scope-contract.mdfx-dev:review(new skill)Every normative rule is defined once and referenced. One exception, and only one: text sent verbatim to an external tool. A prompt handed to
codex reviewor an instructions file handed tocrcannot follow a link, so it must inline the rules — there is now exactly one such block, infx-dev:review, marked as a mirror, and both adapters send it rather than keeping copies.Per adapter
cr reviewtakes-c, --config <files...>, so it is prompt-capable and the brief reaches it before the review rather than only at triage. Fixed three flags this skill had been documenting incorrectly; anyone who ran the documented command got an error.[nitpick]prefix is an input to triage, never a verdict.REVIEW.mdentry, which is the only thing that stops the finding recurring.Also adds "verify the premise before acting". A finding's premise can be wrong, and changing working artifacts to satisfy a misreading is worse than leaving the finding unaddressed — a suggestion applied on sight has been observed to introduce the very bug it claimed to report. A false premise is not a fourth disposition: there are three, and a thread whose premise fails carries none of them, which is what routes it to the outdated/incorrect handler.
Validation
The rules were tested on themselves. Fifteen local
codex reviewpasses, each carrying the Scope Brief and the prior passes' dispositions:All 69 findings were genuine; every one was fixed. None was a wording nitpick, and none was rejected as immaterial, deferred, or a false premise. For comparison, the change that motivated this ran twelve passes at
12, 13, 10, 5, 5, 5, 3, 4, 3, 3, 4, 6and never converged — it plateaued, and the tail was list-completeness nitpicks plus one settled decision re-argued in new words each round.Pass 15 covered every commit through the model pinning. Three commits postdate it: removing the benchmark table from
codex-review(measurements are provenance and belong in git history, not in an instruction file an agent acts on), the path fix below, and the version collapse.Copilot reviewed two heads and reported "no new comments" both times — while carrying five findings in its suppressed-comments blocks, which create no review threads and which no thread count would ever have surfaced. Four were valid and are fixed:
dev/references/scope-contract.mdresolved from nowhere — the odd one out of 31 citations repo-wide, and of the seven in that same file.CONVERGENCE PASS <N>while the prose said to send it verbatim on pass 1, so a first pass could either ask the reviewer to honour a list of prior findings that does not exist, or strip the prefix and hope. Now two labelled parts on two schedules, fixed across all four sites that describe sending it. Assembling this branch's own pass 15 prompt, I had to guess — which is what makes it blocking rather than cosmetic.coderabbit-reviewbannedrun_in_backgroundoutright because it "loses the script output", whiledevStep 6.3 mode B backgrounds that exact waiter with stdout redirected to a log it reads afterwards. The rule was always about losing the output, not the process.One is deferred, not fixed:
resolve-pr-feedback's thread query pages atfirst: 100with nopageInfo, so a PR with more than 100 threads could silently under-report. The finding is fair, but the cap is pre-existing onmainand untouched by this change — adding pagination is a follow-up, not a widening of this one. Worth doing, since silent false convergence is exactly what this PR is about.Version bump:
fx-dev3.8.2 → 3.9.0, marketplace 0.21.2 → 0.22.0 — minor, for a new skill and new mandatory rules with nothing removed. One step, not the per-commit walk to 3.14.1 the branch had accumulated: the pre-commit hook compares against the merge base with main, so a feature branch needs exactly one bump, and only the value landing on main signals anything to the plugin cache.