[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M734YFZ9YPZ847550TAT57G6":3,"skill-same:01M734YFZ9YPZ847550TAT57G6":52},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":31,"commitSha":32,"skillDirectory":33,"forksCount":34,"repositoryHtmlUrl":35,"repositoryHomepageUrl":36,"ownerHtmlUrl":37,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":38,"install":39,"resources":51},"01M734YFZ9YPZ847550TAT57G6","ard",null,"Address, rebut, or defer review items.","Morrison-Lab\u002Fai-config","Morrison-Lab","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F104235520?v=4","01M734G0451S2G35MN4HFQK7XP","Morrison Lab","01M734G059BYFDFQA8YTPDGPMH",4,"MIT","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fard\u002FSKILL.md",[],[],"2026-11-11 15:11:14",false,"---\nname: ard\ndescription: \"Address, rebut, or defer review items.\"\nuser-invocable: true\nallowed-tools:\n  - Bash\n  - Agent\n  - Read\n  - Edit\n  - Write\n---\n\n# ARD — Address, Rebut, Defer, or Acknowledge\n\nEvery review comment gets exactly one disposition: **A**, **R**, **D**, or **K**. Ignoring is not an option.\n\n## What counts as a finding\n\nA *finding* is anything the reviewer requests or implies a change to — including items tagged \"nit\", \"minor\", \"non-blocker\", \"optional\", or \"consider\". All of these require **Address**, **Rebut**, or **Defer**.\n\nPure praise or neutral observations with **no** requested change (\"nice refactor\", \"TIL\") still get a row — disposition **Acknowledge** — so the summary accounts for *every* comment the reviewer made and nothing reads as silently dropped.\n\n## The four dispositions\n\n| Code | Meaning | Action required |\n|------|---------|-----------------|\n| **A** --- Address | Valid and in-scope. | Fix it in this PR\u002FMR and commit. |\n| **R** --- Rebut | Incorrect, already handled, or a misunderstanding. | Explain *why*, citing concrete evidence (line, test, doc, spec). Specific enough that the reviewer can verify it without re-reading the whole PR. |\n| **D** --- Defer | Valid but out of scope (new feature, broad refactor, needs design discussion). | File a follow-up issue (`CREATE_ISSUE` on GitHub --- `gh issue create`; `glab issue create` on GitLab), labelled `ai-authored`, `model:\u003Cmodel-id>` and one of `P1`, `P2` or `P3` per [`issue-first`](..\u002F..\u002Fshared\u002Fworkflow\u002Fissue-first.md), link it, and add it to the PR\u002FMR's **Deferred \u002F Out-of-Scope** section. |\n| **K** --- Acknowledge | Praise or a neutral observation with no change requested. | Give it a row so it's accounted for; no code change, no rebuttal needed. Don't stretch this to dodge a real finding. |\n\n### Decision order\n\nFor anything that requests a change, choose among the first three (Acknowledge is only for no-ask comments):\n\n1. **Address** — the default. Most findings are 1–5 line fixes; if it takes under ~2 min, just fix it.\n2. **Rebut** — only when you're confident the reviewer is mistaken. A rebuttal that isn't falsifiable (\"I think it's fine\") is not a rebuttal. **Before applying *or* rebutting any factual claim about an API, deprecation, signature, or behavior, TEST it** — run the code both ways. Reviewers (including the `@claude` bot) state such claims confidently but are sometimes exactly backwards, and blindly \"Addressing\" a wrong one breaks the build. (Seen on rme #873 with igraph ≥ 2.x: the bot insisted `bfs(mode=)` was deprecated in favor of `neimode=`; the reverse is true — `neimode` is a hard `deprecate_stop` error, `mode` is current. Re-running the script revealed it instantly. The deprecation *direction* can differ across igraph major versions, so test on the locked version.)\n   The same testing discipline applies when the claim is about the **PR's own state or metadata** rather than code behavior — which footer\u002FURL convention a given post uses, whether a file matches `main`, whether a check still exists — not just runnable code. Fetch the actual PR data (the API call, not memory) before Addressing or Rebutting. (Seen on ai-config#688: a review claimed a memory bullet's cited attribution-footer text was wrong, reasoning from the PR's own body — but the PR *body* and a *reply comment* get their footer through two different code paths that don't match. Fetching both directly via the API before responding showed the bullet was right and the reviewer had compared the wrong artifact; rebutted with that evidence instead of either implementing the suggested fix or dismissing the finding unchecked.)\n3. **Defer** — only when the fix genuinely expands scope. Never defer just because a fix is \"minor\" — minor fixes get Addressed.\n\n**Recording a finding as a known limitation, to close it without changing anything, is a Rebut --- and it owes a Rebut's evidence.**\nWriting the finding into a code comment closes it without fixing it, reads as rigour, and leaves nothing red.\nThe claim it rests on --- *no rule could decide this* --- is one nobody asks you to support, unlike the claims in a posted rebuttal.\nBefore recording a limitation, name the mechanism that makes the input undecidable, and check it against the format's own generator rather than against how the input looks.\nIf the generator cannot emit the shape you are excusing, it is decidable and the disposition is Address.\n\n- **Do:** cite the generator's own behaviour when you claim an input is ambiguous, the way any other Rebut cites a line or a spec.\n- **Don't:** let a comment describing a limitation stand in for a disposition --- it is a Rebut written where the reviewer cannot see it.\n\n(Measured on d-morrison\u002Faltdoc#125, 2026-08-28.\nA parser input was documented as one of several inherent ambiguities.\nroxygen2 joins its file list with `\", \"` and wraps at spaces, so the shape being excused --- a continuation line beginning with a comma --- is one that format cannot produce.\nOne added predicate rejected it.\nThe claim had survived several review rounds inside a comment that read as careful.)\n\n> **\"You deleted\u002Fremoved X\" findings — check for branch-behind-main first.** When\n> a reviewer says the PR removed something you don't recall touching, run\n> `git log HEAD..origin\u002Fmain` and `git show HEAD:\u003Cfile>` before Addressing. Main\n> may have *added* X after you branched, so X is absent only because the branch is\n> stale — not because your diff deleted it. The fix is **merge `origin\u002Fmain` in**\n> (the standing keep-synced rule), not \"restore\" a line you never removed. Verify\n> the actual committed diff (`git show \u003Ccommit> -- \u003Cfile>`) rather than trusting\n> the finding's framing. (Seen on ai-config PR #52.)\n\n> **\"Blocking: missing X\" findings that cite a CI job or policy — check the job\n> still exists on `main` first.** A review can flag a CI failure (e.g. \"the\n> Require Changelog Entry job is failing, add a CHANGELOG.md entry\") as\n> blocking, but the requirement itself can be removed from `main` in the time\n> between when the review ran and when you address it — a different PR deletes\n> the CI job and\u002For the file it checks. This isn't the branch-behind-main case\n> above (nothing was deleted from *this* PR); the requirement stopped existing\n> project-wide. Before Addressing, check whether the cited job\u002Ffile is still on\n> `main` (`git log --oneline origin\u002Fmain -- \u003Cpath>`, or `git show origin\u002Fmain:\u003Cpath>`).\n> If it's gone, Rebut with the removal commit(s) as evidence rather than\n> resurrecting a dead requirement. (Seen on ai-config PR #376: a \"blocking\"\n> CHANGELOG.md finding referenced `require-changelog.yml`, which had been\n> deleted from `main` in #385\u002F#387 by the time the fix round ran.)\n\n## Procedure\n\n### 1. Gather every finding\n\nCollect the full set *before* dispositioning, so none slips through. Pull both the summary comment and the inline review threads — `READ_PR_COMMENTS` and `READ_PR_REVIEW_COMMENTS` (abstract operation tokens; resolve to your model's tool via [`tool-mappings.md`](..\u002F..\u002Ftool-mappings.md)).\n\n**GitHub**\n\n```bash\ngh pr view \u003CN> --comments                            # READ_PR_COMMENTS — top-level + summary comments\ngh api repos\u002F{owner}\u002F{repo}\u002Fpulls\u002F\u003CN>\u002Fcomments       # READ_PR_REVIEW_COMMENTS — inline review-thread comments\n```\n\n**GitLab**\n\n```bash\nglab mr view \u003CN> --comments                          # discussion notes\nglab api --paginate \"projects\u002F:id\u002Fmerge_requests\u002F\u003CN>\u002Fnotes?per_page=100\"  # authoritative unresolved DiffNote sweep\nglab api \"projects\u002F:id\u002Fmerge_requests\u002F\u003CN>\u002Fdiscussions\"                    # thread identifiers for replies\u002Fresolution\n```\n\nBots often post the same finding twice — once inline and once in the summary comment. Collect the **union and dedupe** before numbering, so one issue doesn't get two rows (or two conflicting dispositions). Then number 1..n; every number must end up with a row in the summary.\n\nFilter the paginated notes response for `.resolvable == true and .resolved == false`;\nGitLab can return unresolved `DiffNote`s there even when a discussion-level timestamp filter finds no recent unresolved thread.\nThe Discussions endpoint still owns the reply and resolution actions once the note has been identified.\n\nA PR can also carry **more than one review surface from different reviewers**: an inline code-review bot, a separate *agent* post-step that posts its own top-level summary, and Copilot are each distinct. `gh pr view \u003CN> --comments` returns every top-level comment, so disposition each reviewer's findings — not just the inline review or the most recent comment. A whole review summary left un-dispositioned reads as ignored even when every inline thread was handled.\n\n### 2. Disposition each one\n\nApply the decision order above. For Address items, make the edits now.\n\n### 3. Commit Addressed fixes (one commit per review round)\n\n```bash\ngit add -p                                           # stage deliberately\ngit commit -m \"fix: address round \u003Ck> review findings\"   # COMMIT\n```\n\nPush as a separate Bash call, per [`check-before-pushing`](..\u002F..\u002Fshared\u002Fworkflow\u002Fcheck-before-pushing.md)'s \"Keep the commit in its own Bash call\":\n\n```bash\ngit push   # PUSH\n```\n\n- **One commit per round**, not one per finding — reference its SHA in every Address row.\n- **Never `--amend`** the already-reviewed commits: the reviewer (and CI) ran against them and others may have pulled. A fresh commit keeps the audit trail.\n- Don't push generated cruft (`.Rout`, build artifacts); respect the repo's `.gitignore`.\n- If every finding is Rebut\u002FDefer, there's nothing to commit — skip to step 4 and still post the summary.\n\n### 4. Post one summary comment\n\nWrite the summary to a file and post it *from* the file — **never inline on GitLab**, because `glab` mis-parses backticks (e.g. commit SHAs in code spans) as shell subcommands:\n\n```bash\n# write the summary to ard-summary.md, then:\ngh pr comment \u003CN> --body-file ard-summary.md         # COMMENT_PR — GitHub\nglab mr note \u003CN> -F ard-summary.md                   # GitLab\n```\n\n**End `ard-summary.md` with the agent-disclosure marker**, on its own line after a blank line:\n\n```\n_Posted by Claude Code (AI agent) --- not written by a human._\n```\n\nThis comment is posted under the account holder's own login on every round of every PR, so it is among the likeliest in the corpus to be mistaken for their own writing --- see [`disclose-agent-authorship`](..\u002F..\u002Fshared\u002Fworkflow\u002Fdisclose-agent-authorship.md).\nThe body arrives via `--body-file`, which the disclosure guard cannot read.\nIt still warns --- but only that the body is unreadable, never that the marker is absent, and that weaker note reads as a formality rather than a finding.\nSo this step states the requirement itself.\n\n**Keep the bot's trigger phrase out of the summary body.** The `issue_comment`\ntrigger fires on the bare bot `@`-mention **anywhere** in a comment — even in a\nsentence saying you're *not* re-requesting a review. Refer to it obliquely\n(\"re-request review\", \"the review-trigger mention\") or split the tokens (e.g.\n`@ claude`, with a space, so the raw body never contains the contiguous handle);\npaste the literal `@`-mention only when you actually intend to dispatch a review. A\nstray mention spawns a run that cancels the push-triggered review on\n`cancel-in-progress` setups. On some mention-bot setups it also starts a\nsession whose residual-commit sweep can churn the branch.\n\nSummary format:\n\n```\nAddressed findings from review of \u003Ccommit-or-range>:\n\n| # | Finding | Disposition | Detail |\n|---|---------|-------------|--------|\n| 1 | \u003Csummary> | ✅ Address | Fixed in \u003Ccommit-sha> |\n| 2 | \u003Csummary> | 🔄 Rebut | \u003Cone-line reason> |\n| 3 | \u003Csummary> | 📌 Defer | \u003Cissue-link> |\n| 4 | \u003Csummary> | 👍 Acknowledge | \u003Cone-line thanks \u002F note> |\n\n### Rebuttal: Finding 2\n\u003Cfull explanation with evidence>\n```\n\nExpand each Rebut below the table. Deferred rows must carry a real issue link.\n\n**Backtick any verdict phrase you quote, including the one in the opening line.**\nA disposition summary names the round it is disposing of, and the natural way to write that names the round's verdict too:\n\n```\nAddressed GitHub Claude of `9508454e` (Needs more work). Pushed `8af4edc9`.\n```\n\n`check-pr-fully-clean.py` reads that parenthetical as this comment's OWN verdict.\nBecause a driving session never posts a superseding clean one, the per-reviewer rule from #2274 then keeps the PR not-clean permanently, on a citation of a round that was already addressed.\nMeasured on ai-config#2341: that line alone froze the PR, while the disposition table and a self-imposed hold in the same comment produced no verdict at all.\n\nThe fix is one pair of backticks, because `strip_cited_finding_vocab` already blanks inline code spans as citation (#1202):\n\n```\nAddressed GitHub Claude of `9508454e` (`Needs more work`). Pushed `8af4edc9`.\n```\n\nDo not expect the checker to infer it from the sentence instead.\nThree attempts were built and deleted on ai-config#2409 -- a driver-ledger classifier keyed on claim wording, the same classifier gated on the agent-disclosure marker, and a citation rule keyed on the disposition verb.\nEach dropped or blanked a genuine reviewer's not-clean, because a reviewer rejecting a claimed fix writes the same sentence a driver does (\"Addressed in `abc1234` (still Needs more work)\").\nThe disambiguation has to come from the author, and a code span is how this corpus already spells it.\n\n**The opening line is the commonest instance, not the only one.**\nThe same wire is tripped by a verdict phrase left bare anywhere in the comment --- a table cell summarizing what the reviewer said, or a paraphrase inside a Rebuttal expansion:\n\n```\n| 1 | Reviewer said `Needs more work` on the null check | Address | Fixed in `8af4edc9` |\n```\n\nVerified by execution: without those backticks, that row alone classifies the whole comment not-clean, exactly as the header line does.\nSo sweep the finished comment for the phrase rather than backticking only the line the example shows.\n\n- **Do:** wrap a quoted verdict phrase in backticks wherever it appears in a disposition comment --- the opening line, a table cell, and a Rebuttal paraphrase alike.\n- **Do:** re-read the finished comment for any remaining bare verdict phrase before posting it.\n- **Don't:** leave a bare `(Needs more work)` next to a cited SHA -- that is the exact shape that freezes the PR.\n- **Don't:** backtick the opening line and stop, on the strength of the worked example above.\n\n### 4b. Reply to every inline review thread — and resolve where appropriate\n\n(The standalone [`resolve-pr-threads`](..\u002Fresolve-pr-threads\u002FSKILL.md) skill\nruns just the resolve half of this step on its own — useful for sweeping\nalready-settled threads without a full ARD pass.)\n\nThe one summary comment is **not** enough on its own. A reviewer who left\ninline comments wants a response **on each thread**, not just a table posted\nelsewhere. For every inline comment, post a short reply on its own thread with\nthe disposition (and the commit SHA for an Address), then **resolve** the thread\nonce the item is genuinely settled.\n\n**GitHub** — reply on the comment's thread (`REPLY_REVIEW_COMMENT`), then\nresolve via GraphQL (`RESOLVE_REVIEW_THREAD`):\n\n```bash\n# Reply on the same thread as inline comment \u003Ccomment_id>:\n# (quote every argument containing a \u003Cplaceholder> -- bash treats a bare `\u003C`\n# as a redirection operator even mid-word, so an unquoted path like\n# \u002Ftmp\u002Freply-\u003Ccomment_id>.md silently writes to the wrong file)\ncat > \"\u002Ftmp\u002Freply-\u003Ccomment_id>.md\" \u003C\u003C'EOF'\n✅ Addressed in `\u003Csha>`.\n\n_Posted by Claude Code (AI agent) --- not written by a human._\nEOF\ngh api \"repos\u002F{owner}\u002F{repo}\u002Fpulls\u002F\u003CN>\u002Fcomments\" \\\n  -F in_reply_to=\"\u003Ccomment_id>\" -F body=\"@\u002Ftmp\u002Freply-\u003Ccomment_id>.md\"   # REPLY_REVIEW_COMMENT\n\n# List threads to get the node id, then resolve the settled one:\ngh api graphql -f query='query { repository(owner:\"\u003Cowner>\",name:\"\u003Crepo>\") {\n  pullRequest(number:\u003CN>) { reviewThreads(first:100) { nodes {\n    id isResolved comments(first:1){ nodes { databaseId body } } } } } } }'\ngh api graphql -f query='mutation {\n  resolveReviewThread(input:{threadId:\"\u003Cthread_node_id>\"}) { thread { isResolved } } }'   # RESOLVE_REVIEW_THREAD\n```\n\nIn a remote\u002Fweb session without `gh`, resolve `RESOLVE_REVIEW_THREAD` via\n`mcp__github__resolve_review_thread` instead (see `tool-mappings.md`).\n\n**GitLab** — reply to the discussion, then resolve it:\n\n```bash\ncat > \"\u002Ftmp\u002Freply-\u003Cdiscussion_id>.md\" \u003C\u003C'EOF'\nAddressed in `\u003Csha>`.\n\n_Posted by Claude Code (AI agent) --- not written by a human._\nEOF\nglab api -X POST \"projects\u002F:id\u002Fmerge_requests\u002F\u003CN>\u002Fdiscussions\u002F\u003Cdiscussion_id>\u002Fnotes\" \\\n  -F body=\"@\u002Ftmp\u002Freply-\u003Cdiscussion_id>.md\"\nglab api -X PUT \"projects\u002F:id\u002Fmerge_requests\u002F\u003CN>\u002Fdiscussions\u002F\u003Cdiscussion_id>?resolved=true\"\n```\n\n**Resolve only when the item is actually settled:**\n\n- **Address** — resolve after the fix is **pushed** (reply names the SHA). Never\n  resolve an Address whose fix isn't on the branch yet. Not confident the fix\n  fully settles the concern? When the finding is ambiguous, turns on a design\n  choice, or admits only a partial fix, reply explaining what you did and\n  **ask for confirmation** instead of resolving. Leave the thread open for the\n  reviewer to verify.\n- **Defer** — reply with the tracked issue link, then resolve (work lives\n  elsewhere now).\n- **Acknowledge** — reply briefly, then resolve.\n- **Rebut** — reply with the falsifiable evidence. A rebuttal only counts as\n  settled once it **convinces the reviewer** — i.e. they don't re-raise it on\n  the next round. While that's still in question, **leave the thread open**:\n  resolve a bot's thread once it drops the item, and leave a human's thread\n  open if they may want to respond. Resolving on acceptance happens a round\n  *later* than the rebuttal, so it's easy to forget — **at the start of each\n  new round, first sweep your still-open rebuttal threads and resolve any the\n  latest review didn't re-raise**, before dispositioning the new findings. If\n  you and the reviewer reach an impasse (your rebuttal didn't convince them,\n  their re-raise didn't convince you), **escalate to a human reviewer** —\n  request `the repository owner` via the `request-pr-review` skill (or\n  `gh pr edit \u003CN> --add-reviewer \u003Creviewer>`, which bypasses that skill and so\n  does **not** inherit its `Lacaedemon\u002Fsparta` exception --- in sparta, escalate\n  to the user in chat instead of requesting a reviewer) and `@`-mention them\n  with the impasse --- rather than resolving unilaterally or looping forever.\n  Not every re-raise is an impasse, though: if the reviewer **accepts your fact\n  but objects that the claim is over-generalized** (you verified one case and\n  stated a sweeping rule), **narrow the claim to what you actually observed** —\n  that's an Address, not a continued Rebut. Reserve escalation for genuine\n  factual disputes. (Seen on ai-config PR #183: a tested\n  project-root `{{\u003C include >}}` behavior was correct but asserted as a\n  universal Quarto-projects rule; the reviewer granted the test and asked only\n  that it be scoped to the observed case, so the fix was to soften the wording,\n  not re-argue.)\n\nDon't resolve a thread you haven't replied to. Every inline comment ends with\nboth a reply and (where appropriate) a resolution — silence on a thread reads as\nignored, exactly the failure ARD exists to prevent.\n\n**End-state (fully clean):** when the PR\u002FMR is fully clean, **every inline\nreview thread is resolved**, and the only conversation left open is the final\nall-clear exchange — the reviewer's all-clear comment (usually a top-level PR\ncomment, not an inline thread) and your reply to it. A leftover open inline\nthread (an unaccepted rebuttal, an un-resolved Address) means you're not clean\nyet.\n\n### ARD round-close checklist\n\n**Pause point: before reporting the round done.**\nDo-Confirm; per\n[`shared\u002Fworkflow\u002Fskill-checklists.md`](..\u002F..\u002Fshared\u002Fworkflow\u002Fskill-checklists.md).\n\n- [ ] Every finding is captured exactly once in the ARD table (after\n      deduping summary + inline duplicates).\n- [ ] Every table row has exactly one disposition (A\u002FR\u002FD\u002FK), and no change\n      request is labeled K.\n- [ ] Every Address row points to a pushed commit SHA.\n- [ ] Every Address row whose finding quoted a concrete input re-ran **that**\n      input against the fixed code, with the result in the reply --- see\n      [`address-every-comment`](..\u002F..\u002Fshared\u002Fworkflow\u002Faddress-every-comment.md)'s\n      \"The same reviewer's case, never run at all\".\n- [ ] Every Defer row links a filed tracking issue.\n- [ ] **Killer item:** every inline thread has a reply; settled\n      Address\u002FDefer\u002FAcknowledge threads are **resolved**; unresolved rebuttals\n      stay open.\n      Marked because addressing and resolving are separate actions and only\n      the second clears\n      [`fully-clean`](..\u002F..\u002Fshared\u002Fworkflow\u002Ffully-clean.md)'s criterion --- an\n      addressed-but-unresolved thread reads as outstanding work to every later\n      reviewer.\n      Sweep by thread id, not by finding: a re-raise usually opens a *second*\n      thread on the same line, often already marked outdated.\n- [ ] The summary comment is posted from a body file (not inline), and no\n      accidental review-trigger mention is present unless this round must\n      explicitly re-request review.\n\n### 5. Report back with a link\n\nTell the user what you did and give a **clickable URL** to the PR\u002FMR (and to the posted summary comment if available), so they can review in one click.\n\n## Rules\n\n- **Every reviewer comment appears in the table AND gets a reply on its own inline thread** (step 4b). The summary table is the overview; the per-thread reply is what the reviewer sees in context. A thread with no reply reads as ignored.\n- **Resolve threads once settled** (Addressed-and-pushed \u002F Deferred-with-issue \u002F Acknowledged), but never resolve a thread you haven't replied to or whose fix isn't pushed — and leave a human reviewer's thread open if they may want to respond.\n- **Severity never exempts.**\n  \"Informational\" \u002F \"Not a blocker\" \u002F \"Nit\" \u002F \"optional\" \u002F \"consider\" still require A, R, or D — never K.\n- **Rebuttals must be falsifiable** — point to specific code, behavior, or documentation.\n- **Deferrals must be tracked.** A defer without a filed issue is just ignoring with extra words.\n- **Push before you post.** The reviewer should be able to verify Addressed fixes are on the branch.\n- **A `suggestion` block is a hint, not gospel.** Before committing a reviewer's suggested fix, verify it actually works and handles the *general* case — not just the flagged spot. A confident suggestion can overcorrect (e.g. a regex tweak that fixes one form but breaks all the others). If the correct fix differs, apply that and note the divergence in your reply, so the reviewer sees why.\n\n## Delegating sidecar work\n\nWhen dispositioning a finding (step 2) turns up something that needs\nindependent digging --- testing a reviewer's factual claim before Addressing\nor Rebutting it (see the testing rule above), researching how a prior PR\nhandled the same pattern, or checking whether a cited CI job still exists on\n`main` --- hand it to a subagent via the `Agent` tool rather than blocking the\nrest of the round on it. Keep dispositioning the remaining findings, posting\nthe summary, and pushing fixes on the main thread.\n\nGive the subagent a stronger model (e.g. `model: 'opus'` on the `Agent` tool\ncall) when the check is judgment-heavy --- a subtle behavioral claim, a\ndisputed rebuttal --- rather than leaving it at the session default.\nSymmetrically, drop to a cheaper\u002Ffaster tier (`model: 'fable'` or `'haiku'`) for a mechanical check (a lookup, confirming a file\u002Fsymbol still exists);\nsee [`select-model`](..\u002F..\u002Fskills\u002Fselect-model\u002FSKILL.md)'s decision tree for both directions.\nFor a heavy fan-out verification pass, prefer a separately-billed provider (e.g. the `codex` CLI, in UCDH projects only) first when available --- see [`delegate-to-codex`](..\u002Fdelegate-to-codex\u002FSKILL.md).\n\n## Proactive hook compliance\n\nActive hooks guard review interactions and round responses (see [`memories\u002Fhooks.md`](..\u002F..\u002Fmemories\u002Fhooks.md)):\n- **`require-agent-disclosure.py`**: All posted round summaries and per-thread replies\n  must include `_Posted by \u003CAgent Name> (AI agent) --- not written by a human._`\n  on its own line after a blank line.\n  Never use the robot emoji.\n- **`flag-uncited-rebuttal.py`**: If a reviewer finding cites an external documentation URL,\n  always fetch that URL via `WebFetch` or `WebSearch` before posting a rebuttal.\n- **`flag-uncounted-comment-claims.py`**: When stating file counts or listing identifiers in forge comments,\n  run and cite deriving commands (`grep -c`, `wc -l`) in the session.\n- **`flag-unmeasured-timestamp.py`**: A round summary or reply that carries a clock time\n  needs a fresh `TZ=America\u002FLos_Angeles date \"+%Y-%m-%d %H:%M %Z\"` in the same turn.\n  Never infer the time from how many tool calls have run since the last reading.\n- **`no-handrolled-verdict-parse.py`**: Always run `python3 scripts\u002Fcheck-pr-fully-clean.py \u003Cpr>`\n  to verify review and CI cleanliness.\n  Do not grep comments for ad-hoc verdict keywords.\n- **`remind-learn-from-review.py` & `remind-ums-on-scrutiny.py`**: Whenever accepting valid reviewer concerns or scrutinizing feedback,\n  run an explicit UMS pass (`skills\u002Fums`) to update memories or skills before ending the loop.\n\n## Integration with ardi\n\nInside the `ardi` loop (also reachable as `iterate`):\n\n1. Read the latest review (`ardi` step 2)\n2. Apply ARD to each finding (this skill: steps 1–2)\n3. Commit + push fixes (this skill: step 3)\n4. Post the ARD summary and per-thread replies (this skill: steps 4–4b)\n5. Re-request review (`ardi` step 6) — **even if this round was Rebut\u002FDefer only**, so the reviewer re-evaluates.\n\nThe loop continues until the PR\u002FMR is **fully clean** — zero findings, all CI\nworkflows green, and every inline review thread resolved (the only open\nconversation being the final all-clear exchange — the reviewer's all-clear and\nyour reply to it). A rebuttal counts only once it convinces the reviewer; on an\nimpasse, escalate to a human reviewer (`the repository owner`; see step 4b for how).\n\n## Edge cases\n\n- **Ambiguous finding** — if you can't tell whether it's a change request or an observation, treat it as a finding and Address or Rebut. Don't assume it's informational.\n- **Reviewers contradict each other** — Address the most recent reviewer; note the contradiction in your response so the user can arbitrate.\n- **Finding duplicates a deferred item** — Rebut by pointing to the existing issue. Keeping the **Deferred \u002F Out-of-Scope** section current in the PR\u002FMR description should prevent re-flagging; if it recurs, update that section.\n- **Findings sourced from a different PR\u002FMR than the target branch** — treat the cited review as the finding source, but apply dispositions on the target PR\u002FMR branch the user names. Post the ARD summary on the target PR\u002FMR, then reply on each original inline thread with the target PR\u002FMR link plus disposition-specific evidence: Address -> target fix commit SHA; Rebut -> target PR\u002FMR comment link with the falsifiable evidence; Defer -> follow-up issue link; Acknowledge -> brief acknowledgment. This keeps provenance auditable across both threads without requiring a commit in non-Address rounds.\n","# ARD — Address, Rebut, Defer, or Acknowledge\n\nEvery review comment gets exactly one disposition: **A**, **R**, **D**, or **K**. Ignoring is not an option.\n\n## What counts as a finding\n\nA *finding* is anything the reviewer requests or implies a change to — including items tagged \"nit\", \"minor\", \"non-blocker\", \"optional\", or \"consider\". All of these require **Address**, **Rebut**, or **Defer**.\n\nPure praise or neutral observations with **no** requested change (\"nice refactor\", \"TIL\") still get a row — disposition **Acknowledge** — so the summary accounts for *every* comment the reviewer made and nothing reads as silently dropped.\n\n## The four dispositions\n\n| Code | Meaning | Action required |\n|------|---------|-----------------|\n| **A** --- Address | Valid and in-scope. | Fix it in this PR\u002FMR and commit. |\n| **R** --- Rebut | Incorrect, already handled, or a misunderstanding. | Explain *why*, citing concrete evidence (line, test, doc, spec). Specific enough that the reviewer can verify it without re-reading the whole PR. |\n| **D** --- Defer | Valid but out of scope (new feature, broad refactor, needs design discussion). | File a follow-up issue (`CREATE_ISSUE` on GitHub --- `gh issue create`; `glab issue create` on GitLab), labelled `ai-authored`, `model:\u003Cmodel-id>` and one of `P1`, `P2` or `P3` per [`issue-first`](..\u002F..\u002Fshared\u002Fworkflow\u002Fissue-first.md), link it, and add it to the PR\u002FMR's **Deferred \u002F Out-of-Scope** section. |\n| **K** --- Acknowledge | Praise or a neutral observation with no change requested. | Give it a row so it's accounted for; no code change, no rebuttal needed. Don't stretch this to dodge a real finding. |\n\n### Decision order\n\nFor anything that requests a change, choose among the first three (Acknowledge is only for no-ask comments):\n\n1. **Address** — the default. Most findings are 1–5 line fixes; if it takes under ~2 min, just fix it.\n2. **Rebut** — only when you're confident the reviewer is mistaken. A rebuttal that isn't falsifiable (\"I think it's fine\") is not a rebuttal. **Before applying *or* rebutting any factual claim about an API, deprecation, signature, or behavior, TEST it** — run the code both ways. Reviewers (including the `@claude` bot) state such claims confidently but are sometimes exactly backwards, and blindly \"Addressing\" a wrong one breaks the build. (Seen on rme #873 with igraph ≥ 2.x: the bot insisted `bfs(mode=)` was deprecated in favor of `neimode=`; the reverse is true — `neimode` is a hard `deprecate_stop` error, `mode` is current. Re-running the script revealed it instantly. The deprecation *direction* can differ across igraph major versions, so test on the locked version.)\n   The same testing discipline applies when the claim is about the **PR's own state or metadata** rather than code behavior — which footer\u002FURL convention a given post uses, whether a file matches `main`, whether a check still exists — not just runnable code. Fetch the actual PR data (the API call, not memory) before Addressing or Rebutting. (Seen on ai-config#688: a review claimed a memory bullet's cited attribution-footer text was wrong, reasoning from the PR's own body — but the PR *body* and a *reply comment* get their footer through two different code paths that don't match. Fetching both directly via the API before responding showed the bullet was right and the reviewer had compared the wrong artifact; rebutted with that evidence instead of either implementing the suggested fix or dismissing the finding unchecked.)\n3. **Defer** — only when the fix genuinely expands scope. Never defer just because a fix is \"minor\" — minor fixes get Addressed.\n\n**Recording a finding as a known limitation, to close it without changing anything, is a Rebut --- and it owes a Rebut's evidence.**\nWriting the finding into a code comment closes it without fixing it, reads as rigour, and leaves nothing red.\nThe claim it rests on --- *no rule could decide this* --- is one nobody asks you to support, unlike the claims in a posted rebuttal.\nBefore recording a limitation, name the mechanism that makes the input undecidable, and check it against the format's own generator rather than against how the input looks.\nIf the generator cannot emit the shape you are excusing, it is decidable and the disposition is Address.\n\n- **Do:** cite the generator's own behaviour when you claim an input is ambiguous, the way any other Rebut cites a line or a spec.\n- **Don't:** let a comment describing a limitation stand in for a disposition --- it is a Rebut written where the reviewer cannot see it.\n\n(Measured on d-morrison\u002Faltdoc#125, 2026-08-28.\nA parser input was documented as one of several inherent ambiguities.\nroxygen2 joins its file list with `\", \"` and wraps at spaces, so the shape being excused --- a continuation line beginning with a comma --- is one that format cannot produce.\nOne added predicate rejected it.\nThe claim had survived several review rounds inside a comment that read as careful.)\n\n> **\"You deleted\u002Fremoved X\" findings — check for branch-behind-main first.** When\n> a reviewer says the PR removed something you don't recall touching, run\n> `git log HEAD..origin\u002Fmain` and `git show HEAD:\u003Cfile>` before Addressing. Main\n> may have *added* X after you branched, so X is absent only because the branch is\n> stale — not because your diff deleted it. The fix is **merge `origin\u002Fmain` in**\n> (the standing keep-synced rule), not \"restore\" a line you never removed. Verify\n> the actual committed diff (`git show \u003Ccommit> -- \u003Cfile>`) rather than trusting\n> the finding's framing. (Seen on ai-config PR #52.)\n\n> **\"Blocking: missing X\" findings that cite a CI job or policy — check the job\n> still exists on `main` first.** A review can flag a CI failure (e.g. \"the\n> Require Changelog Entry job is failing, add a CHANGELOG.md entry\") as\n> blocking, but the requirement itself can be removed from `main` in the time\n> between when the review ran and when you address it — a different PR deletes\n> the CI job and\u002For the file it checks. This isn't the branch-behind-main case\n> above (nothing was deleted from *this* PR); the requirement stopped existing\n> project-wide. Before Addressing, check whether the cited job\u002Ffile is still on\n> `main` (`git log --oneline origin\u002Fmain -- \u003Cpath>`, or `git show origin\u002Fmain:\u003Cpath>`).\n> If it's gone, Rebut with the removal commit(s) as evidence rather than\n> resurrecting a dead requirement. (Seen on ai-config PR #376: a \"blocking\"\n> CHANGELOG.md finding referenced `require-changelog.yml`, which had been\n> deleted from `main` in #385\u002F#387 by the time the fix round ran.)\n\n## Procedure\n\n### 1. Gather every finding\n\nCollect the full set *before* dispositioning, so none slips through. Pull both the summary comment and the inline review threads — `READ_PR_COMMENTS` and `READ_PR_REVIEW_COMMENTS` (abstract operation tokens; resolve to your model's tool via [`tool-mappings.md`](..\u002F..\u002Ftool-mappings.md)).\n\n**GitHub**\n\n```bash\ngh pr view \u003CN> --comments                            # READ_PR_COMMENTS — top-level + summary comments\ngh api repos\u002F{owner}\u002F{repo}\u002Fpulls\u002F\u003CN>\u002Fcomments       # READ_PR_REVIEW_COMMENTS — inline review-thread comments\n```\n\n**GitLab**\n\n```bash\nglab mr view \u003CN> --comments                          # discussion notes\nglab api --paginate \"projects\u002F:id\u002Fmerge_requests\u002F\u003CN>\u002Fnotes?per_page=100\"  # authoritative unresolved DiffNote sweep\nglab api \"projects\u002F:id\u002Fmerge_requests\u002F\u003CN>\u002Fdiscussions\"                    # thread identifiers for replies\u002Fresolution\n```\n\nBots often post the same finding twice — once inline and once in the summary comment. Collect the **union and dedupe** before numbering, so one issue doesn't get two rows (or two conflicting dispositions). Then number 1..n; every number must end up with a row in the summary.\n\nFilter the paginated notes response for `.resolvable == true and .resolved == false`;\nGitLab can return unresolved `DiffNote`s there even when a discussion-level timestamp filter finds no recent unresolved thread.\nThe Discussions endpoint still owns the reply and resolution actions once the note has been identified.\n\nA PR can also carry **more than one review surface from different reviewers**: an inline code-review bot, a separate *agent* post-step that posts its own top-level summary, and Copilot are each distinct. `gh pr view \u003CN> --comments` returns every top-level comment, so disposition each reviewer's findings — not just the inline review or the most recent comment. A whole review summary left un-dispositioned reads as ignored even when every inline thread was handled.\n\n### 2. Disposition each one\n\nApply the decision order above. For Address items, make the edits now.\n\n### 3. Commit Addressed fixes (one commit per review round)\n\n```bash\ngit add -p                                           # stage deliberately\ngit commit -m \"fix: address round \u003Ck> review findings\"   # COMMIT\n```\n\nPush as a separate Bash call, per [`check-before-pushing`](..\u002F..\u002Fshared\u002Fworkflow\u002Fcheck-before-pushing.md)'s \"Keep the commit in its own Bash call\":\n\n```bash\ngit push   # PUSH\n```\n\n- **One commit per round**, not one per finding — reference its SHA in every Address row.\n- **Never `--amend`** the already-reviewed commits: the reviewer (and CI) ran against them and others may have pulled. A fresh commit keeps the audit trail.\n- Don't push generated cruft (`.Rout`, build artifacts); respect the repo's `.gitignore`.\n- If every finding is Rebut\u002FDefer, there's nothing to commit — skip to step 4 and still post the summary.\n\n### 4. Post one summary comment\n\nWrite the summary to a file and post it *from* the file — **never inline on GitLab**, because `glab` mis-parses backticks (e.g. commit SHAs in code spans) as shell subcommands:\n\n```bash\n# write the summary to ard-summary.md, then:\ngh pr comment \u003CN> --body-file ard-summary.md         # COMMENT_PR — GitHub\nglab mr note \u003CN> -F ard-summary.md                   # GitLab\n```\n\n**End `ard-summary.md` with the agent-disclosure marker**, on its own line after a blank line:\n\n```\n_Posted by Claude Code (AI agent) --- not written by a human._\n```\n\nThis comment is posted under the account holder's own login on every round of every PR, so it is among the likeliest in the corpus to be mistaken for their own writing --- see [`disclose-agent-authorship`](..\u002F..\u002Fshared\u002Fworkflow\u002Fdisclose-agent-authorship.md).\nThe body arrives via `--body-file`, which the disclosure guard cannot read.\nIt still warns --- but only that the body is unreadable, never that the marker is absent, and that weaker note reads as a formality rather than a finding.\nSo this step states the requirement itself.\n\n**Keep the bot's trigger phrase out of the summary body.** The `issue_comment`\ntrigger fires on the bare bot `@`-mention **anywhere** in a comment — even in a\nsentence saying you're *not* re-requesting a review. Refer to it obliquely\n(\"re-request review\", \"the review-trigger mention\") or split the tokens (e.g.\n`@ claude`, with a space, so the raw body never contains the contiguous handle);\npaste the literal `@`-mention only when you actually intend to dispatch a review. A\nstray mention spawns a run that cancels the push-triggered review on\n`cancel-in-progress` setups. On some mention-bot setups it also starts a\nsession whose residual-commit sweep can churn the branch.\n\nSummary format:\n\n```\nAddressed findings from review of \u003Ccommit-or-range>:\n\n| # | Finding | Disposition | Detail |\n|---|---------|-------------|--------|\n| 1 | \u003Csummary> | ✅ Address | Fixed in \u003Ccommit-sha> |\n| 2 | \u003Csummary> | 🔄 Rebut | \u003Cone-line reason> |\n| 3 | \u003Csummary> | 📌 Defer | \u003Cissue-link> |\n| 4 | \u003Csummary> | 👍 Acknowledge | \u003Cone-line thanks \u002F note> |\n\n### Rebuttal: Finding 2\n\u003Cfull explanation with evidence>\n```\n\nExpand each Rebut below the table. Deferred rows must carry a real issue link.\n\n**Backtick any verdict phrase you quote, including the one in the opening line.**\nA disposition summary names the round it is disposing of, and the natural way to write that names the round's verdict too:\n\n```\nAddressed GitHub Claude of `9508454e` (Needs more work). Pushed `8af4edc9`.\n```\n\n`check-pr-fully-clean.py` reads that parenthetical as this comment's OWN verdict.\nBecause a driving session never posts a superseding clean one, the per-reviewer rule from #2274 then keeps the PR not-clean permanently, on a citation of a round that was already addressed.\nMeasured on ai-config#2341: that line alone froze the PR, while the disposition table and a self-imposed hold in the same comment produced no verdict at all.\n\nThe fix is one pair of backticks, because `strip_cited_finding_vocab` already blanks inline code spans as citation (#1202):\n\n```\nAddressed GitHub Claude of `9508454e` (`Needs more work`). Pushed `8af4edc9`.\n```\n\nDo not expect the checker to infer it from the sentence instead.\nThree attempts were built and deleted on ai-config#2409 -- a driver-ledger classifier keyed on claim wording, the same classifier gated on the agent-disclosure marker, and a citation rule keyed on the disposition verb.\nEach dropped or blanked a genuine reviewer's not-clean, because a reviewer rejecting a claimed fix writes the same sentence a driver does (\"Addressed in `abc1234` (still Needs more work)\").\nThe disambiguation has to come from the author, and a code span is how this corpus already spells it.\n\n**The opening line is the commonest instance, not the only one.**\nThe same wire is tripped by a verdict phrase left bare anywhere in the comment --- a table cell summarizing what the reviewer said, or a paraphrase inside a Rebuttal expansion:\n\n```\n| 1 | Reviewer said `Needs more work` on the null check | Address | Fixed in `8af4edc9` |\n```\n\nVerified by execution: without those backticks, that row alone classifies the whole comment not-clean, exactly as the header line does.\nSo sweep the finished comment for the phrase rather than backticking only the line the example shows.\n\n- **Do:** wrap a quoted verdict phrase in backticks wherever it appears in a disposition comment --- the opening line, a table cell, and a Rebuttal paraphrase alike.\n- **Do:** re-read the finished comment for any remaining bare verdict phrase before posting it.\n- **Don't:** leave a bare `(Needs more work)` next to a cited SHA -- that is the exact shape that freezes the PR.\n- **Don't:** backtick the opening line and stop, on the strength of the worked example above.\n\n### 4b. Reply to every inline review thread — and resolve where appropriate\n\n(The standalone [`resolve-pr-threads`](..\u002Fresolve-pr-threads\u002FSKILL.md) skill\nruns just the resolve half of this step on its own — useful for sweeping\nalready-settled threads without a full ARD pass.)\n\nThe one summary comment is **not** enough on its own. A reviewer who left\ninline comments wants a response **on each thread**, not just a table posted\nelsewhere. For every inline comment, post a short reply on its own thread with\nthe disposition (and the commit SHA for an Address), then **resolve** the thread\nonce the item is genuinely settled.\n\n**GitHub** — reply on the comment's thread (`REPLY_REVIEW_COMMENT`), then\nresolve via GraphQL (`RESOLVE_REVIEW_THREAD`):\n\n```bash\n# Reply on the same thread as inline comment \u003Ccomment_id>:\n# (quote every argument containing a \u003Cplaceholder> -- bash treats a bare `\u003C`\n# as a redirection operator even mid-word, so an unquoted path like\n# \u002Ftmp\u002Freply-\u003Ccomment_id>.md silently writes to the wrong file)\ncat > \"\u002Ftmp\u002Freply-\u003Ccomment_id>.md\" \u003C\u003C'EOF'\n✅ Addressed in `\u003Csha>`.\n\n_Posted by Claude Code (AI agent) --- not written by a human._\nEOF\ngh api \"repos\u002F{owner}\u002F{repo}\u002Fpulls\u002F\u003CN>\u002Fcomments\" \\\n  -F in_reply_to=\"\u003Ccomment_id>\" -F body=\"@\u002Ftmp\u002Freply-\u003Ccomment_id>.md\"   # REPLY_REVIEW_COMMENT\n\n# List threads to get the node id, then resolve the settled one:\ngh api graphql -f query='query { repository(owner:\"\u003Cowner>\",name:\"\u003Crepo>\") {\n  pullRequest(number:\u003CN>) { reviewThreads(first:100) { nodes {\n    id isResolved comments(first:1){ nodes { databaseId body } } } } } } }'\ngh api graphql -f query='mutation {\n  resolveReviewThread(input:{threadId:\"\u003Cthread_node_id>\"}) { thread { isResolved } } }'   # RESOLVE_REVIEW_THREAD\n```\n\nIn a remote\u002Fweb session without `gh`, resolve `RESOLVE_REVIEW_THREAD` via\n`mcp__github__resolve_review_thread` instead (see `tool-mappings.md`).\n\n**GitLab** — reply to the discussion, then resolve it:\n\n```bash\ncat > \"\u002Ftmp\u002Freply-\u003Cdiscussion_id>.md\" \u003C\u003C'EOF'\nAddressed in `\u003Csha>`.\n\n_Posted by Claude Code (AI agent) --- not written by a human._\nEOF\nglab api -X POST \"projects\u002F:id\u002Fmerge_requests\u002F\u003CN>\u002Fdiscussions\u002F\u003Cdiscussion_id>\u002Fnotes\" \\\n  -F body=\"@\u002Ftmp\u002Freply-\u003Cdiscussion_id>.md\"\nglab api -X PUT \"projects\u002F:id\u002Fmerge_requests\u002F\u003CN>\u002Fdiscussions\u002F\u003Cdiscussion_id>?resolved=true\"\n```\n\n**Resolve only when the item is actually settled:**\n\n- **Address** — resolve after the fix is **pushed** (reply names the SHA). Never\n  resolve an Address whose fix isn't on the branch yet. Not confident the fix\n  fully settles the concern? When the finding is ambiguous, turns on a design\n  choice, or admits only a partial fix, reply explaining what you did and\n  **ask for confirmation** instead of resolving. Leave the thread open for the\n  reviewer to verify.\n- **Defer** — reply with the tracked issue link, then resolve (work lives\n  elsewhere now).\n- **Acknowledge** — reply briefly, then resolve.\n- **Rebut** — reply with the falsifiable evidence. A rebuttal only counts as\n  settled once it **convinces the reviewer** — i.e. they don't re-raise it on\n  the next round. While that's still in question, **leave the thread open**:\n  resolve a bot's thread once it drops the item, and leave a human's thread\n  open if they may want to respond. Resolving on acceptance happens a round\n  *later* than the rebuttal, so it's easy to forget — **at the start of each\n  new round, first sweep your still-open rebuttal threads and resolve any the\n  latest review didn't re-raise**, before dispositioning the new findings. If\n  you and the reviewer reach an impasse (your rebuttal didn't convince them,\n  their re-raise didn't convince you), **escalate to a human reviewer** —\n  request `the repository owner` via the `request-pr-review` skill (or\n  `gh pr edit \u003CN> --add-reviewer \u003Creviewer>`, which bypasses that skill and so\n  does **not** inherit its `Lacaedemon\u002Fsparta` exception --- in sparta, escalate\n  to the user in chat instead of requesting a reviewer) and `@`-mention them\n  with the impasse --- rather than resolving unilaterally or looping forever.\n  Not every re-raise is an impasse, though: if the reviewer **accepts your fact\n  but objects that the claim is over-generalized** (you verified one case and\n  stated a sweeping rule), **narrow the claim to what you actually observed** —\n  that's an Address, not a continued Rebut. Reserve escalation for genuine\n  factual disputes. (Seen on ai-config PR #183: a tested\n  project-root `{{\u003C include >}}` behavior was correct but asserted as a\n  universal Quarto-projects rule; the reviewer granted the test and asked only\n  that it be scoped to the observed case, so the fix was to soften the wording,\n  not re-argue.)\n\nDon't resolve a thread you haven't replied to. Every inline comment ends with\nboth a reply and (where appropriate) a resolution — silence on a thread reads as\nignored, exactly the failure ARD exists to prevent.\n\n**End-state (fully clean):** when the PR\u002FMR is fully clean, **every inline\nreview thread is resolved**, and the only conversation left open is the final\nall-clear exchange — the reviewer's all-clear comment (usually a top-level PR\ncomment, not an inline thread) and your reply to it. A leftover open inline\nthread (an unaccepted rebuttal, an un-resolved Address) means you're not clean\nyet.\n\n### ARD round-close checklist\n\n**Pause point: before reporting the round done.**\nDo-Confirm; per\n[`shared\u002Fworkflow\u002Fskill-checklists.md`](..\u002F..\u002Fshared\u002Fworkflow\u002Fskill-checklists.md).\n\n- [ ] Every finding is captured exactly once in the ARD table (after\n      deduping summary + inline duplicates).\n- [ ] Every table row has exactly one disposition (A\u002FR\u002FD\u002FK), and no change\n      request is labeled K.\n- [ ] Every Address row points to a pushed commit SHA.\n- [ ] Every Address row whose finding quoted a concrete input re-ran **that**\n      input against the fixed code, with the result in the reply --- see\n      [`address-every-comment`](..\u002F..\u002Fshared\u002Fworkflow\u002Faddress-every-comment.md)'s\n      \"The same reviewer's case, never run at all\".\n- [ ] Every Defer row links a filed tracking issue.\n- [ ] **Killer item:** every inline thread has a reply; settled\n      Address\u002FDefer\u002FAcknowledge threads are **resolved**; unresolved rebuttals\n      stay open.\n      Marked because addressing and resolving are separate actions and only\n      the second clears\n      [`fully-clean`](..\u002F..\u002Fshared\u002Fworkflow\u002Ffully-clean.md)'s criterion --- an\n      addressed-but-unresolved thread reads as outstanding work to every later\n      reviewer.\n      Sweep by thread id, not by finding: a re-raise usually opens a *second*\n      thread on the same line, often already marked outdated.\n- [ ] The summary comment is posted from a body file (not inline), and no\n      accidental review-trigger mention is present unless this round must\n      explicitly re-request review.\n\n### 5. Report back with a link\n\nTell the user what you did and give a **clickable URL** to the PR\u002FMR (and to the posted summary comment if available), so they can review in one click.\n\n## Rules\n\n- **Every reviewer comment appears in the table AND gets a reply on its own inline thread** (step 4b). The summary table is the overview; the per-thread reply is what the reviewer sees in context. A thread with no reply reads as ignored.\n- **Resolve threads once settled** (Addressed-and-pushed \u002F Deferred-with-issue \u002F Acknowledged), but never resolve a thread you haven't replied to or whose fix isn't pushed — and leave a human reviewer's thread open if they may want to respond.\n- **Severity never exempts.**\n  \"Informational\" \u002F \"Not a blocker\" \u002F \"Nit\" \u002F \"optional\" \u002F \"consider\" still require A, R, or D — never K.\n- **Rebuttals must be falsifiable** — point to specific code, behavior, or documentation.\n- **Deferrals must be tracked.** A defer without a filed issue is just ignoring with extra words.\n- **Push before you post.** The reviewer should be able to verify Addressed fixes are on the branch.\n- **A `suggestion` block is a hint, not gospel.** Before committing a reviewer's suggested fix, verify it actually works and handles the *general* case — not just the flagged spot. A confident suggestion can overcorrect (e.g. a regex tweak that fixes one form but breaks all the others). If the correct fix differs, apply that and note the divergence in your reply, so the reviewer sees why.\n\n## Delegating sidecar work\n\nWhen dispositioning a finding (step 2) turns up something that needs\nindependent digging --- testing a reviewer's factual claim before Addressing\nor Rebutting it (see the testing rule above), researching how a prior PR\nhandled the same pattern, or checking whether a cited CI job still exists on\n`main` --- hand it to a subagent via the `Agent` tool rather than blocking the\nrest of the round on it. Keep dispositioning the remaining findings, posting\nthe summary, and pushing fixes on the main thread.\n\nGive the subagent a stronger model (e.g. `model: 'opus'` on the `Agent` tool\ncall) when the check is judgment-heavy --- a subtle behavioral claim, a\ndisputed rebuttal --- rather than leaving it at the session default.\nSymmetrically, drop to a cheaper\u002Ffaster tier (`model: 'fable'` or `'haiku'`) for a mechanical check (a lookup, confirming a file\u002Fsymbol still exists);\nsee [`select-model`](..\u002F..\u002Fskills\u002Fselect-model\u002FSKILL.md)'s decision tree for both directions.\nFor a heavy fan-out verification pass, prefer a separately-billed provider (e.g. the `codex` CLI, in UCDH projects only) first when available --- see [`delegate-to-codex`](..\u002Fdelegate-to-codex\u002FSKILL.md).\n\n## Proactive hook compliance\n\nActive hooks guard review interactions and round responses (see [`memories\u002Fhooks.md`](..\u002F..\u002Fmemories\u002Fhooks.md)):\n- **`require-agent-disclosure.py`**: All posted round summaries and per-thread replies\n  must include `_Posted by \u003CAgent Name> (AI agent) --- not written by a human._`\n  on its own line after a blank line.\n  Never use the robot emoji.\n- **`flag-uncited-rebuttal.py`**: If a reviewer finding cites an external documentation URL,\n  always fetch that URL via `WebFetch` or `WebSearch` before posting a rebuttal.\n- **`flag-uncounted-comment-claims.py`**: When stating file counts or listing identifiers in forge comments,\n  run and cite deriving commands (`grep -c`, `wc -l`) in the session.\n- **`flag-unmeasured-timestamp.py`**: A round summary or reply that carries a clock time\n  needs a fresh `TZ=America\u002FLos_Angeles date \"+%Y-%m-%d %H:%M %Z\"` in the same turn.\n  Never infer the time from how many tool calls have run since the last reading.\n- **`no-handrolled-verdict-parse.py`**: Always run `python3 scripts\u002Fcheck-pr-fully-clean.py \u003Cpr>`\n  to verify review and CI cleanliness.\n  Do not grep comments for ad-hoc verdict keywords.\n- **`remind-learn-from-review.py` & `remind-ums-on-scrutiny.py`**: Whenever accepting valid reviewer concerns or scrutinizing feedback,\n  run an explicit UMS pass (`skills\u002Fums`) to update memories or skills before ending the loop.\n\n## Integration with ardi\n\nInside the `ardi` loop (also reachable as `iterate`):\n\n1. Read the latest review (`ardi` step 2)\n2. Apply ARD to each finding (this skill: steps 1–2)\n3. Commit + push fixes (this skill: step 3)\n4. Post the ARD summary and per-thread replies (this skill: steps 4–4b)\n5. Re-request review (`ardi` step 6) — **even if this round was Rebut\u002FDefer only**, so the reviewer re-evaluates.\n\nThe loop continues until the PR\u002FMR is **fully clean** — zero findings, all CI\nworkflows green, and every inline review thread resolved (the only open\nconversation being the final all-clear exchange — the reviewer's all-clear and\nyour reply to it). A rebuttal counts only once it convinces the reviewer; on an\nimpasse, escalate to a human reviewer (`the repository owner`; see step 4b for how).\n\n## Edge cases\n\n- **Ambiguous finding** — if you can't tell whether it's a change request or an observation, treat it as a finding and Address or Rebut. Don't assume it's informational.\n- **Reviewers contradict each other** — Address the most recent reviewer; note the contradiction in your response so the user can arbitrate.\n- **Finding duplicates a deferred item** — Rebut by pointing to the existing issue. Keeping the **Deferred \u002F Out-of-Scope** section current in the PR\u002FMR description should prevent re-flagging; if it recurs, update that section.\n- **Findings sourced from a different PR\u002FMR than the target branch** — treat the cited review as the finding source, but apply dispositions on the target PR\u002FMR branch the user names. Post the ARD summary on the target PR\u002FMR, then reply on each original inline thread with the target PR\u002FMR link plus disposition-specific evidence: Address -> target fix commit SHA; Rebut -> target PR\u002FMR comment link with the falsifiable evidence; Defer -> follow-up issue link; Acknowledge -> brief acknowledgment. This keeps provenance auditable across both threads without requiring a commit in non-Address rounds.\n",{"name":5,"description":7,"allowed-tools":24,"user-invocable":30},[25,26,27,28,29],"Bash","Agent","Read","Edit","Write",true,"main","f8f5ea79cc52e903c30773b5e264e384e715c4c7","skills\u002Fard",1,"https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config","https:\u002F\u002Fmorrison-lab.github.io\u002Fai-config\u002F","https:\u002F\u002Fgithub.com\u002FMorrison-Lab","2026-11-12 06:23:19",{"source":8,"skillName":5,"commands":40,"prompt":50,"zipAvailable":30},[41,44,47],{"tool":42,"command":43},"npx","npx skills add Morrison-Lab\u002Fai-config --skill ard",{"tool":45,"command":46},"bunx","bunx skills add Morrison-Lab\u002Fai-config --skill ard",{"tool":48,"command":49},"pnpm","pnpm dlx skills add Morrison-Lab\u002Fai-config --skill ard","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fard\u002FSKILL.md",[],[53,60,67,74,81,88],{"skillNo":54,"name":55,"nameZh":6,"description":56,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":57,"categories":58,"keywords":59,"publishedAt":19},"01M735B6T9KP24YK73HZYS361B","triage","Triage backlog: P1\u002FP2\u002FP3 or close.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Ftriage\u002FSKILL.md",[],[],{"skillNo":61,"name":62,"nameZh":6,"description":63,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":64,"categories":65,"keywords":66,"publishedAt":19},"01M735ABCWNZ3WJKPC1ABJ7W8B","st","Start task issue-first.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fst\u002FSKILL.md",[],[],{"skillNo":68,"name":69,"nameZh":6,"description":70,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":19},"01M7355RRVTEEFJ16QVM86EK7X","mwc","Session merge grant with confidence gate.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fmwc\u002FSKILL.md",[],[],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":19},"01M7355JBSBYP7AQQ2165FJKBW","migrate-discussion","Move items between Issues and Discussions.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fmigrate-discussion\u002FSKILL.md",[],[],{"skillNo":82,"name":83,"nameZh":6,"description":84,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":87,"publishedAt":19},"01M73558FN1TYWMMRZYJQSTAK8","memorize","Persist memory across sessions.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fmemorize\u002FSKILL.md",[],[],{"skillNo":89,"name":90,"nameZh":6,"description":91,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":92,"categories":93,"keywords":94,"publishedAt":19},"01M7351KXGW9QTKQHH1HJFS4YF","defer-issue","File issue for deferred work.","https:\u002F\u002Fgithub.com\u002FMorrison-Lab\u002Fai-config\u002Fblob\u002Fmain\u002Fskills\u002Fdefer-issue\u002FSKILL.md",[],[]]