Skip to content

feat(fx-dev): add a materiality bar so reviews converge on what matters - #24

Merged
fx merged 48 commits into
mainfrom
feat/review-materiality-bar
Aug 16, 2026
Merged

feat(fx-dev): add a materiality bar so reviews converge on what matters#24
fx merged 48 commits into
mainfrom
feat/review-materiality-bar

Conversation

@fx

@fx fx commented Aug 14, 2026

Copy link
Copy Markdown
Owner

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.

Layer Holds
dev/references/scope-contract.md Definitions — the Scope Brief, the bar, what blocking means, the three filters, resolver dispositions, convergence, the iteration bound, fix-the-class
fx-dev:review (new skill) Procedure — Steps 1–8, how a reviewer applies those definitions from first pass to final report
The seven adapters Only what is true of one tool: its command, flags, quirks, output shape, and the threads or gates it exposes

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 review or an instructions file handed to cr cannot follow a link, so it must inline the rules — there is now exactly one such block, in fx-dev:review, marked as a mirror, and both adapters send it rather than keeping copies.

Per adapter

  • codex-review — the scope prompt is mandatory (a run without it is an incomplete pass); MCP servers must be disabled per server by name or the run can hang forever with zero output; scope flags and a custom prompt are mutually exclusive; and the model and effort are pinned per invocation, because the inherited default made every pass a 10–15 minute wait and the convergence loop unaffordable long before its bound.
  • coderabbit-reviewcr review takes -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.
  • copilot-review / copilot-feedback-resolver — the loop-specific trap: every push re-opens the gate, so editing for an immaterial finding costs a full wait cycle and manufactures the next round's input. Reply-and-resolve without a commit instead. Every thread still ends resolved — the gate is zero unresolved threads, not zero observations acted on. A [nitpick] prefix is an input to triage, never a verdict.
  • rabbit-feedback-resolver / resolve-pr-feedback — a rejected suggestion routes through the incorrect handler and the REVIEW.md entry, which is the only thing that stops the finding recurring.
  • pr-reviewer — state counts per tier; "a review that reports twenty things equally has reported nothing".
  • dev — the bar applies to findings a reviewer originates from its own judgment, never to contract violations, so the coordinator can't demote something the reviewer correctly marked blocking.

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 review passes, each carrying the Scope Brief and the prior passes' dispositions:

Passes Findings Character
1–11 9, 5, 6, 7, 5, 5, 2, 4, 2, 7, 4 Building the bar. Substantive contradictions throughout — filter precedence, leftover "fix everything / until clean" directives, half-swept classes
12–14 5, 4, 4 The restructure reviewed as a fresh artifact, looking specifically for a rule lost in the extraction. None was lost in any of the three
15 0 "The reviewed instruction artifacts are internally consistent, their canonical references resolve, and the new Codex model/effort guidance matches the documented CLI configuration mechanism"

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, 6 and 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.md resolved from nowhere — the odd one out of 31 citations repo-wide, and of the seven in that same file.
  • The external mirror opened with 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-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. The rule was always about losing the output, not the process.

One is deferred, not fixed: resolve-pr-feedback's thread query pages at first: 100 with no pageInfo, so a PR with more than 100 threads could silently under-report. The finding is fair, but the cap is pre-existing on main and 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-dev 3.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.

fx added 4 commits August 14, 2026 22:29
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.
Copilot AI lite review requested due to automatic review settings August 14, 2026 23:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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.md to 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-dev and 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.

Comment thread plugins/fx-dev/skills/coderabbit-review/SKILL.md Outdated
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.
@fx
fx requested a lite review from Copilot August 14, 2026 23:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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.
@fx
fx requested a lite review from Copilot August 14, 2026 23:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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.
@fx
fx requested a lite review from Copilot August 14, 2026 23:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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 classified required-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.
@fx
fx requested a lite review from Copilot August 14, 2026 23:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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.
@fx
fx requested a lite review from Copilot August 14, 2026 23:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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.
@fx
fx requested a lite review from Copilot August 14, 2026 23:31
fx added 16 commits August 15, 2026 22:46
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.
@fx
fx requested a lite review from Copilot August 16, 2026 03:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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.md doesn’t match an actual path in this repo (the canonical file is under fx-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.

fx added 2 commits August 16, 2026 03:50
… 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.
@fx
fx requested a lite review from Copilot August 16, 2026 03:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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_background because it loses the waiter output, but fx-dev:dev Mode 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 reviewThreads and 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 adding pageInfo (and looping with after: endCursor when hasNextPage is 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.
@fx
fx requested a lite review from Copilot August 16, 2026 03:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

@fx
fx merged commit f664500 into main Aug 16, 2026
3 checks passed
@fx
fx deleted the feat/review-materiality-bar branch August 16, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants