-
Notifications
You must be signed in to change notification settings - Fork 0
chore(process): PR verdict protocol + mechanical gate (HT-100) #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4984e72
919aa15
aeab8d4
ee7b4d5
2ff202f
c01663f
b4b3f3b
592a57f
1e766d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| # PR verdict protocol | ||
|
|
||
| **Applies to every repo, every project, every surface.** The maintainer reviewing a PR is not necessarily an engineer, and a diff plus a wall of review-bot output does not answer the only question that matters: is this safe to merge? This protocol makes a PR answerable in under 30 seconds. | ||
|
|
||
| Earned 2026-07-20, after an audit found **nine normative decisions** merged across PRs #87β#100 of the Helpthread repo that the maintainer never made β including two amendments to the project's constitution β each authored, self-reviewed, and merged with zero human review comments, several later cited back as "per the maintainer's decision." | ||
|
|
||
| ## The one rule that matters most | ||
|
|
||
| **Never attribute a decision to the maintainer without quoting them.** | ||
|
|
||
| Not "per your decision," not "as you chose earlier," not "the maintainer decided," not "(maintainer, 2026-07-19)" β unless you can paste their actual words. If you cannot quote it, it is **yours**, and it must be labelled `INFERRED` wherever it is written: PR body, spec text, commit message, ticket, charter amendment. | ||
|
|
||
| This single rule would have prevented every finding in that audit. | ||
|
|
||
| ## The banned move | ||
|
|
||
| Writing "N decisions shape this" β or "the key question is X" β and then answering it yourself without a user turn in between. | ||
|
|
||
| If you pose a decision, **stop and ask**. Do not declare it "locked," "recorded," or "decided" until the maintainer has answered in their own words. A tool result is not an answer. Silence is not consent. | ||
|
|
||
| ## Required PR body structure | ||
|
|
||
| Every PR body opens with this block, before anything else: | ||
|
|
||
| ```markdown | ||
| ## π’ SAFE TO MERGE | ||
| Gates green. No new decisions. CodeRabbit: 3 findings, 0 real. | ||
| ``` | ||
|
|
||
| Three verdicts, nothing else: | ||
|
|
||
| | Verdict | Means | | ||
| |---|---| | ||
| | π’ **SAFE TO MERGE** | No new decisions. Gates green. Review findings adjudicated and resolved. | | ||
| | π‘ **NEEDS YOUR DECISION** | Encodes N decisions the maintainer has not made. Listed below. Do not merge until answered. | | ||
| | π΄ **DO NOT MERGE** | Unresolved defect, failing gate, or a one-way door not yet accepted. | | ||
|
|
||
| When the verdict is π‘ or π΄, a **Decision provenance** table follows immediately: | ||
|
|
||
| ```markdown | ||
| ## Decision provenance | ||
|
|
||
| | Decision β in plain words | Source | | ||
| |---|---| | ||
| | Knowledge base becomes a paid module | You, 2026-07-19 16:46: "i want the KB to be a module" | | ||
| | Resonant IQ runs module code on its own servers | β οΈ INFERRED β no authorization found | | ||
| ``` | ||
|
|
||
| **Plain words, not jargon.** "Resonant IQ runs module code on its own servers" β not "managed hosting becomes the mainline install path." If the maintainer would need to ask what a row means, the row is written wrong. The noun that would make them say "wait, what?" must appear in the row. | ||
|
|
||
| ## One-way doors get their own line | ||
|
|
||
| Anything expensive or impossible to reverse β a licensing term, a public promise, a published API, a schema migration, deleting data, anything in a constitution or `legal/` β gets flagged explicitly: | ||
|
|
||
| ```markdown | ||
| **One-way door:** narrows the own-your-data promise in CHARTER Β§2. | ||
| ``` | ||
|
|
||
| Two-way doors need no flag. Match the noise to the cost of undo. | ||
|
|
||
| ## Risk tiering β so this stays usable | ||
|
|
||
| | PR touches | Treatment | | ||
| |---|---| | ||
| | A charter, `README`, `legal/`, `LICENSE`, licensing, pricing, or any public promise | Full block + provenance table + the maintainer reads the changed text itself | | ||
| | Specs, ADRs, architecture docs | Full block + provenance table | | ||
| | Code, tests, config | Verdict line only | | ||
| | Typos, formatting, dependency bumps | Verdict line only | | ||
|
|
||
| Most PRs are one line. Reserve the ceremony for what can hurt. | ||
|
|
||
| ## Review bots are yours, never the maintainer's | ||
|
|
||
| The maintainer never reads CodeRabbit, Codex, or any bot output raw. You adjudicate and report one line: | ||
|
|
||
| > CodeRabbit: 7 findings β 5 real and fixed, 2 wrong (it misread the token scope). | ||
|
|
||
| If a finding is real, fix it or explain why not. "The bot said something" is not a report; a verdict on each finding is. | ||
|
|
||
| **A bot's silence is not approval.** Check that a review actually ran on the current head β a passing check can mean skipped, rate-limited, or reviewing a commit you have since replaced. | ||
|
|
||
| **Bots review after a PR opens, so nothing is green at open time.** The sequence is fixed: | ||
|
|
||
| 1. Open the PR at π‘ or π΄. Never π’ β no bot has run yet. | ||
| 2. Wait for gates and bots. Verify a review actually landed on the current head. | ||
| 3. Adjudicate every finding: real or not, fixed or why not. | ||
| 4. If you pushed fixes, **request re-review explicitly** β incremental review is off in these repos, so a fix-up push is otherwise never looked at. | ||
| 5. Only then update the verdict to π’ and add the one-line bot summary. | ||
| 6. Tell the maintainer it is ready. **A PR they have not been told about is not ready**, whatever its checks say. | ||
|
|
||
| The gate enforces step 5 mechanically: a π’ verdict with no review-bot adjudication line fails. | ||
|
|
||
| ## Before you write the verdict | ||
|
|
||
| 1. Diff the PR against the base. List every change that adds or alters a **rule, default, invariant, commitment, licensing term, price, or public promise**. | ||
| 2. For each, find the maintainer's authorizing words in the conversation. Quote them with a timestamp. | ||
| 3. Anything without a quote is `INFERRED`. Say so plainly. Do not soften it. | ||
| 4. If there is at least one INFERRED item, the verdict is π‘. Never π’. | ||
|
|
||
| ## What "approval" is and is not | ||
|
|
||
| - "merge it," "looks good," "go ahead," "please merge everything else" β **approval to merge**, not evidence they decided the contents. | ||
| - "I agree with everything above" β covers **the message it replies to**, nothing else. If that message was about refunds and the PR also encodes a hosting model, the hosting model is INFERRED. | ||
| - Approving one PR never authorizes the next. | ||
|
|
||
| Blanket agreement is the most common way an inferred decision acquires a false pedigree. Treat it as narrowly as it was given. | ||
|
|
||
| ## Mechanical gate | ||
|
|
||
| Instructions drift β that is exactly how the audit's findings happened. So the highest-risk tier gets a CI check, not a promise: any PR touching the constitution, `README`, `legal/`, `LICENSE`, or anything under `specs/modules/` fails unless its body carries a verdict marker and a provenance section. See `.github/workflows/pr-verdict.yml` in repos where it is installed. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,222 @@ | ||||||||||||||||||||||||||||
| name: PR verdict | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # High-blast-radius changes β the constitution, legal text, licensing and pricing β | ||||||||||||||||||||||||||||
| # must carry a verdict and a decision-provenance table in the PR body. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # Why this is a CI check and not a documented convention: on 2026-07-20 an audit of | ||||||||||||||||||||||||||||
| # PRs #87-#100 found nine normative decisions merged that the maintainer never made, | ||||||||||||||||||||||||||||
| # including two amendments to CHARTER.md. Every one of them was made by an assistant | ||||||||||||||||||||||||||||
| # that had the convention available and did not follow it. Instructions drift; a | ||||||||||||||||||||||||||||
| # required check does not. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # The rule this enforces lives at .claude/rules/pr-verdict.md. | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||||||
| types: [opened, edited, synchronize, reopened] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||||||
| pull-requests: read | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||
| verdict: | ||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| - name: Does this PR touch the high-risk tier? | ||||||||||||||||||||||||||||
| id: tier | ||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | ||||||||||||||||||||||||||||
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | ||||||||||||||||||||||||||||
| # via env, not inline β refs may contain $, backtick, or ; | ||||||||||||||||||||||||||||
| BASE_REF: ${{ github.event.pull_request.base.ref }} | ||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||
| git fetch --no-tags --depth=1 origin "$BASE_SHA" 2>/dev/null || true | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # THREE-dot: what this PR changed, not what main changed since it forked. | ||||||||||||||||||||||||||||
| # Two-dot compares tips, so a branch merely behind main reports every file | ||||||||||||||||||||||||||||
| # main touched β measured at 74 files against this PR's actual 3. That fires | ||||||||||||||||||||||||||||
| # the gate on PRs that touched nothing protected, which trains contributors to | ||||||||||||||||||||||||||||
| # paste ceremonial provenance tables: the exact habit this gate exists to break. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # --no-renames: with rename detection on, `git mv CHARTER.md docs/old.md` | ||||||||||||||||||||||||||||
| # reports ONLY the new path, so moving a protected file out of its directory | ||||||||||||||||||||||||||||
| # silences the gate. Deleting the constitution by relocating it is precisely | ||||||||||||||||||||||||||||
| # the change this must catch. | ||||||||||||||||||||||||||||
| CHANGED=$(git diff --no-renames --name-only "$BASE_SHA...$HEAD_SHA" \ | ||||||||||||||||||||||||||||
| || git diff --no-renames --name-only "origin/$BASE_REF...HEAD") | ||||||||||||||||||||||||||||
| echo "Changed files:"; echo "$CHANGED" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # The tier: constitution, legal text, and the specs that carry licensing, | ||||||||||||||||||||||||||||
| # pricing, and the free/paid line. | ||||||||||||||||||||||||||||
| # Files that carry a promise, a licensing boundary, or money. | ||||||||||||||||||||||||||||
| HIGH_RISK=$(echo "$CHANGED" | grep -E \ | ||||||||||||||||||||||||||||
| -e '^CHARTER\.md$' \ | ||||||||||||||||||||||||||||
| -e '^README\.md$' \ | ||||||||||||||||||||||||||||
| -e '^legal/' \ | ||||||||||||||||||||||||||||
| -e '^LICENSE' \ | ||||||||||||||||||||||||||||
| -e '^specs/modules/' \ | ||||||||||||||||||||||||||||
| -e '^\.github/workflows/pr-verdict\.yml$' \ | ||||||||||||||||||||||||||||
| -e '^\.claude/rules/pr-verdict\.md$' \ | ||||||||||||||||||||||||||||
| || true) | ||||||||||||||||||||||||||||
|
Comment on lines
+58
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π Major | β‘ Quick win Protect the documented catalog and marketplace paths. The PRβs declared high-risk set names root-level Proposed fix- -e '^specs/modules/(catalog|marketplace-v1)\.md$' \
+ -e '^catalog\.md$' \
+ -e '^marketplace-v1\.md$' \π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if [ -n "$HIGH_RISK" ]; then | ||||||||||||||||||||||||||||
| echo "high_risk=true" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||
| { echo 'files<<EOF'; echo "$HIGH_RISK"; echo 'EOF'; } >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||
| echo "high_risk=false" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| - name: Require a verdict and decision provenance | ||||||||||||||||||||||||||||
| if: steps.tier.outputs.high_risk == 'true' | ||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||
| BODY: ${{ github.event.pull_request.body }} | ||||||||||||||||||||||||||||
| FILES: ${{ steps.tier.outputs.files }} | ||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||
| # Pin the locale: several patterns below contain non-ASCII characters | ||||||||||||||||||||||||||||
| # (the verdict emoji, curly quotes). Under a byte-oriented locale grep | ||||||||||||||||||||||||||||
| # matches those byte-by-byte, so a bracket or case-fold over them | ||||||||||||||||||||||||||||
| # silently stops working β the check would pass everything and look green. | ||||||||||||||||||||||||||||
| export LC_ALL=C.UTF-8 | ||||||||||||||||||||||||||||
| fail() { echo "::error::$1"; FAILED=1; } | ||||||||||||||||||||||||||||
| FAILED=0 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| echo "This PR touches the high-risk tier:" | ||||||||||||||||||||||||||||
| echo "$FILES" | ||||||||||||||||||||||||||||
| echo | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Normalize the body ONCE, before any check reads it, stripping the two | ||||||||||||||||||||||||||||
| # things that look like content to a regex and like nothing to a human: | ||||||||||||||||||||||||||||
| # - HTML comments: `<!-- π’ SAFE TO MERGE -->` as the first line satisfied | ||||||||||||||||||||||||||||
| # the verdict and bot-line checks while rendering invisible. The same flaw | ||||||||||||||||||||||||||||
| # also failed legitimate PRs opening with a template comment. | ||||||||||||||||||||||||||||
| # - Fenced blocks: the protocol doc's own ```markdown example of a | ||||||||||||||||||||||||||||
| # "## Decision provenance" table satisfied the provenance requirement, so | ||||||||||||||||||||||||||||
| # quoting the rule counted as following it. | ||||||||||||||||||||||||||||
| BODY=$(printf '%s\n' "$BODY" \ | ||||||||||||||||||||||||||||
| | perl -0777 -pe 's/<!--.*?-->//gs' \ | ||||||||||||||||||||||||||||
| | awk '/^[[:space:]]*(```|~~~)/ { f = !f; next } !f') | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # 1. A verdict marker, one of exactly three, ALONE on the FIRST non-empty | ||||||||||||||||||||||||||||
| # line. Matching anywhere would let a PR bury the verdict under arbitrary | ||||||||||||||||||||||||||||
| # prose β the whole point is that it is the first thing read. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # The whole line must BE the verdict, not merely contain it: leading `#`s | ||||||||||||||||||||||||||||
| # and the emoji are allowed (`## π’ SAFE TO MERGE`), but trailing words are | ||||||||||||||||||||||||||||
| # not, so `Status: SAFE TO MERGE pending review` is rejected. `[^[:alnum:]]*` | ||||||||||||||||||||||||||||
| # covers the emoji without this file having to carry a Unicode class. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # VERDICT is captured here and reused by check 5, so a body that merely | ||||||||||||||||||||||||||||
| # *mentions* another verdict cannot change how it is validated. | ||||||||||||||||||||||||||||
| FIRST_LINE=$(grep -m1 -vE '^[[:space:]]*$' <<<"$BODY" || true) | ||||||||||||||||||||||||||||
| verdict_is() { | ||||||||||||||||||||||||||||
| grep -qE "^#{0,6}[[:space:]]*[^[:alnum:]]*[[:space:]]*$1[[:space:]]*$" <<<"$FIRST_LINE" | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| VERDICT="" | ||||||||||||||||||||||||||||
| if verdict_is 'SAFE TO MERGE'; then VERDICT=safe | ||||||||||||||||||||||||||||
| elif verdict_is 'NEEDS YOUR DECISION'; then VERDICT=needs | ||||||||||||||||||||||||||||
| elif verdict_is 'DO NOT MERGE'; then VERDICT=blocked | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
| if [ -z "$VERDICT" ]; then | ||||||||||||||||||||||||||||
| fail "The PR body's first line must be exactly the verdict, on its own: π’ SAFE TO MERGE / π‘ NEEDS YOUR DECISION / π΄ DO NOT MERGE. Found: ${FIRST_LINE:0:80}" | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # 2. A decision-provenance section. | ||||||||||||||||||||||||||||
| # Anchored: "## Decision provenance deliberately omitted" is a heading that | ||||||||||||||||||||||||||||
| # says the opposite of what it would otherwise satisfy. | ||||||||||||||||||||||||||||
| if ! grep -qiE '^#+[[:space:]]*Decision provenance[[:space:]]*$' <<<"$BODY"; then | ||||||||||||||||||||||||||||
| fail "No '## Decision provenance' section. Changes to the constitution, legal text, or licensing must list every decision and where it came from." | ||||||||||||||||||||||||||||
|
Comment on lines
+130
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π Major | β‘ Quick win Require provenance immediately after a yellow or red verdict. This check accepts After parsing the opening verdict, require π€ Prompt for AI Agents |
||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # 3. Every provenance row is either quoted to the maintainer or marked INFERRED. | ||||||||||||||||||||||||||||
| # A row citing them without quote marks is the exact failure this gate exists for. | ||||||||||||||||||||||||||||
| # sed form is BRE-portable (BSD and GNU) β `\+` is a GNU extension. | ||||||||||||||||||||||||||||
| if grep -qiE '^#{1,}[[:space:]]*Decision provenance[[:space:]]*$' <<<"$BODY"; then | ||||||||||||||||||||||||||||
| # Stop at the next heading β reading to end-of-body swallows any later | ||||||||||||||||||||||||||||
| # table (a test-results table, for instance) and parses it as provenance rows. | ||||||||||||||||||||||||||||
| # Terminate on an ATX heading (# Foo) OR a setext underline (--- / ===), | ||||||||||||||||||||||||||||
| # which the earlier awk missed β a "Test results" setext heading let the | ||||||||||||||||||||||||||||
| # table under it be parsed as provenance rows. | ||||||||||||||||||||||||||||
| ROWS=$(awk '/^#+[[:space:]]*[Dd]ecision provenance[[:space:]]*$/{f=1;next} \ | ||||||||||||||||||||||||||||
| f&&/^#+[[:space:]]/{exit} \ | ||||||||||||||||||||||||||||
| f&&/^(-{3,}|={3,})[[:space:]]*$/{exit} f' <<<"$BODY" \ | ||||||||||||||||||||||||||||
| | grep -E '^\|' | grep -vE '^\|[[:space:]]*[-:[:space:]]+\|' | tail -n +2 || true) | ||||||||||||||||||||||||||||
| if [ -z "$ROWS" ]; then | ||||||||||||||||||||||||||||
| fail "The 'Decision provenance' section has no rows. If this PR encodes no decisions, say so in a row ('None β no new decisions')." | ||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||
| while IFS= read -r row; do | ||||||||||||||||||||||||||||
| [ -z "$row" ] && continue | ||||||||||||||||||||||||||||
| # Only an explicit INFERRED label, or an explicit no-decision row, excuses a | ||||||||||||||||||||||||||||
| # missing quote. 'no authorization' alone does NOT β the protocol requires | ||||||||||||||||||||||||||||
| # the INFERRED label itself so the disclosure is unmissable. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # An INFERRED row also forces the verdict to yellow. The protocol is | ||||||||||||||||||||||||||||
| # explicit that one inferred item means π‘ and never π’, and without this | ||||||||||||||||||||||||||||
| # a PR could disclose an unauthorized decision and still call itself | ||||||||||||||||||||||||||||
| # safe to merge β disclosure without the consequence. | ||||||||||||||||||||||||||||
| if grep -qiE 'INFERRED' <<<"$row"; then | ||||||||||||||||||||||||||||
| if [ "$VERDICT" = safe ]; then | ||||||||||||||||||||||||||||
| fail "Verdict is SAFE TO MERGE but a provenance row is marked INFERRED. One inferred decision means the verdict is NEEDS YOUR DECISION: ${row}" | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
| if grep -qiE 'none β|no new decision' <<<"$row"; then continue; fi | ||||||||||||||||||||||||||||
| # A sourced row must contain an actual quotation. | ||||||||||||||||||||||||||||
| if ! grep -qE '["ββ]' <<<"$row"; then | ||||||||||||||||||||||||||||
| fail "Provenance row has no quote and is not marked INFERRED: ${row}" | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
| done <<<"$ROWS" | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # 4. Attribution without a quote, anywhere in the body. | ||||||||||||||||||||||||||||
| # "your decision" is deliberately NOT in this list β it matches the | ||||||||||||||||||||||||||||
| # "NEEDS YOUR DECISION" verdict marker itself. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # Backticked code spans are stripped before matching: a doc that | ||||||||||||||||||||||||||||
| # *discusses* these patterns (`maintainer decision` as a literal example, | ||||||||||||||||||||||||||||
| # in a checklist or an adjudication table) is not attributing anything. | ||||||||||||||||||||||||||||
| # Without this, any text explaining the rule trips the rule. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # The patterns are role-based, never a person's name: this file is public, | ||||||||||||||||||||||||||||
| # and the maintainer's name does not belong in it. | ||||||||||||||||||||||||||||
| BODY_NO_CODE=$(sed 's/`[^`]*`//g' <<<"$BODY") | ||||||||||||||||||||||||||||
| while IFS= read -r line; do | ||||||||||||||||||||||||||||
| grep -qE '["ββ]' <<<"$line" && continue | ||||||||||||||||||||||||||||
| grep -qiE 'INFERRED' <<<"$line" && continue | ||||||||||||||||||||||||||||
| fail "Attributes a decision to the maintainer without quoting them: ${line}" | ||||||||||||||||||||||||||||
| done < <(grep -iE "(maintainer('|β)s (decision|call|choice)|per the maintainer('|β)s|maintainer decided|per your decision|you decided|you chose|as you decided|decided by the maintainer|CONFIRMED by the maintainer|SIGNED OFF by the maintainer|\(maintainer,? [0-9]{4}-|maintainer decision, [0-9]{4}-)" <<<"$BODY_NO_CODE" || true) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # 5. A green verdict must show the review bots were adjudicated. | ||||||||||||||||||||||||||||
| # Bots review AFTER a PR opens, so nothing can honestly be green at open | ||||||||||||||||||||||||||||
| # time. This blocks the failure mode where a PR is marked green before | ||||||||||||||||||||||||||||
| # CodeRabbit has run, or against a commit that has since been replaced. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # Keyed to the VERDICT parsed in check 1, never to the phrase appearing | ||||||||||||||||||||||||||||
| # anywhere: a π‘ body that merely names the three verdicts, or quotes this | ||||||||||||||||||||||||||||
| # protocol, was otherwise forced to carry an adjudication line it had no | ||||||||||||||||||||||||||||
| # business carrying yet. Read against BODY_NO_CODE for the same reason | ||||||||||||||||||||||||||||
| # check 4 does β a backticked example is documentation, not an adjudication. | ||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||
| # Matched by SHAPE β "<reviewer>: <n> findings ..." β rather than against a | ||||||||||||||||||||||||||||
| # list of vendors. The protocol allows a substitute reviewer when the usual | ||||||||||||||||||||||||||||
| # one is unavailable, so hardcoding names would fail an honest substitution; | ||||||||||||||||||||||||||||
| # it also keeps third-party product names out of a public repo. | ||||||||||||||||||||||||||||
| if [ "$VERDICT" = safe ]; then | ||||||||||||||||||||||||||||
| if ! grep -qiE ':[^:]*[^[:alnum:]]*\b([0-9]+|no|none|zero)[[:space:]]+findings?\b' <<<"$BODY_NO_CODE"; then | ||||||||||||||||||||||||||||
| fail "Verdict is SAFE TO MERGE but no review adjudication line is present. Add one line naming the reviewer and giving a COUNT β e.g. '<reviewer>: 7 findings, 5 real and fixed, 2 wrong (misread the token scope)' or '<reviewer>: no findings'. The bare word 'findings' does not adjudicate anything. If no review has run on the current head yet, the verdict cannot be green." | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if [ "$FAILED" = "1" ]; then | ||||||||||||||||||||||||||||
| echo | ||||||||||||||||||||||||||||
| echo "See .claude/rules/pr-verdict.md for the protocol." | ||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
| echo "Verdict block present and every decision is sourced or marked INFERRED." | ||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π Maintainability & Code Quality | π‘ Minor | β‘ Quick win
Use
Assistantsfor AI actors.Replace generic
assistant,bot, andreview-botterms withAssistantterminology. Keep product names such asCodeRabbitunchanged..claude/rules/pr-verdict.md#L3-L3: replacereview-bot output..claude/rules/pr-verdict.md#L72-L91: rename the section and generic AI references..github/workflows/pr-verdict.yml#L8-L9: replaceassistant..github/workflows/pr-verdict.yml#L169-L175: replace generic review-bot terms.As per coding guidelines, βUse
Agentsfor human support staff andAssistantsfor AI actors; never conflate the terms in schema, code, documentation, or prose.βπ Affects 2 files
.claude/rules/pr-verdict.md#L3-L3(this comment).claude/rules/pr-verdict.md#L72-L91.github/workflows/pr-verdict.yml#L8-L9.github/workflows/pr-verdict.yml#L169-L175π€ Prompt for AI Agents
Source: Coding guidelines