| name | code-review |
| description | Systematically retrieve, categorize, and address PR code review comments with an auditable evidence ledger — AI review loop, per-comment commits, suppression-free fixes, and pushed-head verification of required checks and approval. Use when handling code review feedback, addressing PR comments, or driving a reviewed PR to merge-ready state. |
Code Review Workflow Skill
Profile keys consumed
project.repo
make.ci, make.ai_review_loop, make.pr_comments, make.psalm, make.deptrac, make.tests, make.phpinsights, make.infection, make.post_review_findings
quality.phpinsights.quality, quality.phpinsights.architecture, quality.phpinsights.style, quality.phpinsights.complexity
quality.deptrac_violations, quality.psalm_errors, quality.infection_msi
capabilities.publish_pr_comments
ci.provider, ci.required_checks
review.ai_review_agents, review.coderabbit, review.request_changes_blocking
Context (Input)
- PR has unresolved code review comments
- Need systematic approach to address feedback
- Ready to implement reviewer suggestions
- Need to maintain quality standards during review implementation
- Profile loaded from
.claude/php-sdlc.yml (run /sdlc-setup if missing).
When review.coderabbit is false, the AI review loop is the primary
comment source; review threads still go through the same evidence protocol.
Task (Function)
Systematically retrieve, categorize, and address all PR code review comments
while maintaining quality standards and PR readiness.
Success Criteria:
- Direct GitHub GraphQL review-thread query shows 0 unresolved review comments,
and the
PR_COMMENT_EVIDENCE ledger records
SNAPSHOT_STARTED_AT=<auto-captured ISO time>,
SNAPSHOT_CAPTURED_BY=code-review-skill, PR_HEAD=<sha>, plus every
review-thread, top-level PR issue, and review body comment from that
snapshot as COMMENT_META|url|updatedAt|body_sha256 and
COMMENT|url|commit|sha, COMMENT|url|reply|url, or
COMMENT|url|decline|url; reply/decline evidence comments must be posted by
the PR author or a login in PR_COMMENT_TRUSTED_EVIDENCE_ACTORS and include
structured EVIDENCE_SOURCE, EVIDENCE_ACTION, and decline
EVIDENCE_REASON; non-evidence comments created or edited after the
snapshot block completion until the snapshot and evidence are restarted,
except an otherwise qualifying approval review on pushed HEAD whose body
is empty or exactly FINAL_APPROVAL_NO_ACTION: true
- The target mapped by
make.ci exits 0
- A final AI review loop run (the target mapped by
make.ai_review_loop, or
"${CLAUDE_PLUGIN_ROOT}/scripts/ai-review-loop.sh" when that key is null)
reports AI_REVIEW_VERDICT: PASS after the make.ci target, on the same
commit; if it applies fixes, repeat CI and the loop until both pass without
new changes
- Local
HEAD is pushed and matches
gh pr view <number> --repo "$PR_REPO" --json headRefOid
- Commit-scoped status/check rollup for local
HEAD is queried from the base
PR repository (project.repo), is non-empty, non-required contexts have
only allowed terminal states (SUCCESS, SKIPPED, or NEUTRAL), and every
check named in ci.required_checks plus every live base-branch protection
required status/check is present with state SUCCESS on that pushed head;
GitHub Actions check URLs must point at the base PR repository
- Final direct GitHub GraphQL review-thread query after the pushed-head
verification still shows 0 unresolved review comments
gh pr view <number> --repo "$PR_REPO" --json state,mergeStateStatus,mergeable,reviewDecision,isDraft,reviewRequests
shows the PR is open, not draft, not conflicting, is
, is empty, and a direct review query shows an
review on the pushed submitted after the latest addressed
comment evidence by a non-author reviewer with , , or
association. When is
(default), any review state blocks completion
until re-reviewed.
Degrade rule (NFR-4): when ci.provider is null, skip the check-waiting
and check-rollup steps with an explicit capability-absent note; all local
gates (CI target, AI review loop, evidence ledger, unresolved-thread queries)
still apply.
Workflow Overview
AI Review Loop → PR Comments (snapshot) → Categorize → Apply by Priority →
Verify → Run CI → Final AI Review Loop → Push → GitHub Readiness → Done
Evidence Ledger Protocol
Every addressed comment leaves an auditable trace in a ledger file pointed to
by PR_COMMENT_EVIDENCE. The protocol prevents silently dropped feedback and
backdated "done" claims.
Snapshot capture — auto-capture the timestamp; never supply or backdate it
manually:
capture_review_comment_snapshot() {
REVIEW_COMMENT_SNAPSHOT_STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
export REVIEW_COMMENT_SNAPSHOT_STARTED_AT
}
capture_review_comment_snapshot
printf 'SNAPSHOT_STARTED_AT=%s\nSNAPSHOT_CAPTURED_BY=code-review-skill\n' \
"$REVIEW_COMMENT_SNAPSHOT_STARTED_AT" > "$PR_COMMENT_EVIDENCE"
Ledger format (one line each):
SNAPSHOT_STARTED_AT=<ISO-8601 UTC>
SNAPSHOT_CAPTURED_BY=code-review-skill
PR_HEAD=<final local head sha>
COMMENT_META|<comment url>|<updatedAt>|<sha256 of bodyText>
COMMENT|<comment url>|commit|<sha> # fixed by a commit
COMMENT|<comment url>|reply|<url> # answered by a reply comment
COMMENT|<comment url>|decline|<url> # declined with reasoned comment
The body hash comes from the comment's bodyText:
body_hash_from_b64() {
printf '%s' "$1" | base64 --decode | sha256sum | awk '{ print $1 }'
}
iso_time_epoch() { date -u -d "$1" +%s; }
Validation rules — each COMMENT line must satisfy, against the comment's
effective time (the later of createdAt and updatedAt):
commit evidence: the sha resolves (git rev-parse --verify <sha>^{commit}),
is an ancestor of local HEAD, is NOT an ancestor of the trusted base ref,
its committer time is later than the comment's effective time, and its
commit message references the source comment URL.
reply evidence: a same-PR comment URL different from the source, with a
later effective time, posted by the PR author or a login listed in the
comma-separated PR_COMMENT_TRUSTED_EVIDENCE_ACTORS, whose body contains
EVIDENCE_SOURCE: <source URL> and a positive
EVIDENCE_ACTION: (addressed|resolved|fixed|implemented|updated|changed|applied).
decline evidence: same actor and URL rules, body contains
EVIDENCE_SOURCE: <source URL>, a no-change
EVIDENCE_ACTION: (declined|stale|duplicate|not applicable|not needed|won't fix|will not fix),
and a non-empty EVIDENCE_REASON: <reason>.
- Every
COMMENT_META line must have a matching, valid COMMENT line, and
the recorded updatedAt/hash must still match the live comment (an edited
comment invalidates its evidence).
Post-snapshot rule: any non-evidence PR/review comment created or edited
after SNAPSHOT_STARTED_AT forces a restart of snapshot capture and evidence
collection. Allowed exceptions: validated reply/decline evidence URLs, and an
otherwise qualifying approval review on pushed HEAD whose body is empty or
exactly FINAL_APPROVAL_NO_ACTION: true.
Comment enumeration — three sources, each paginated with 100-node cursor
pages (pageInfo{hasNextPage endCursor}); never trust a single unpaginated
page:
- Review-thread comments —
pullRequest.reviewThreads(first:100) with nested
comments(first:100); threads whose inner comments.pageInfo.hasNextPage
is true must be drained via
node(id:$threadId){... on PullRequestReviewThread{comments(first:100,after:$cursor)...}}.
- Top-level PR issue comments —
pullRequest.comments(first:100).
- Review bodies —
pullRequest.reviews(first:100).
Each node yields url, createdAt, updatedAt, bodyText; skip empty
bodies; emit url|createdAt|updatedAt|base64(bodyText) items, deduplicate
with sort -u, and filter before/after the snapshot by effective time.
Execution Steps
Step 0: Run Autonomous AI Review Loop
Before addressing PR comments manually, fetch the PR base into a trusted ref
and run the autonomous review loop against that base:
set -euo pipefail
command -v jq >/dev/null || { echo "jq is required" >&2; exit 1; }
: "${PR:?Set PR to the pull request number}"
case "$PR" in
''|*[!0-9]*) echo "PR must be numeric" >&2; exit 1 ;;
esac
: "${PR_REPO:?Set PR_REPO=owner/repo for the base PR repository (profile project.repo)}"
PR_META="$(gh pr view "$PR" --repo "$PR_REPO" --json baseRefName,baseRefOid)"
BASE_REF="$(printf '%s\n' "$PR_META" | jq -r .baseRefName)"
BASE_OID="$(printf '%s\n' "$PR_META" | jq -r .baseRefOid)"
TRUSTED_BASE_REF="refs/remotes/pr-base/$PR"
git fetch "git@github.com:${PR_REPO}.git" "$BASE_REF:$TRUSTED_BASE_REF"
test "$(git rev-parse "$TRUSTED_BASE_REF")" = "$BASE_OID"
Then run the loop. If make.ai_review_loop is non-null, invoke that make
target with the trusted base as the diff base; when it is null, the plugin
substitutes its script:
"${CLAUDE_PLUGIN_ROOT}/scripts/ai-review-loop.sh" --diff-base "$TRUSTED_BASE_REF"
The loop:
- Runs the review agent(s) from
review.ai_review_agents against the diff
from the given base (v1 supports claude only; other entries warn+skip)
- On a
FAIL verdict, lets the reviewer apply safe fixes in the same
iteration (the plugin script runs claude with
--permission-mode acceptEdits)
- Re-reviews until a
PASS verdict or the iteration cap (plugin script:
--max-iterations, default 5)
The plugin script runs no CI between iterations — verify the fixed tree with
the target mapped by make.ci afterwards (Step 6); a repo-mapped
make.ai_review_loop may run its own verification command per iteration
(commonly the CI target). The plugin script's
default prompt covers correctness, security, FR/NFR coverage, and code
health: system design tradeoffs, appropriate design pattern use, code
smells, SOLID/DRY/KISS, DDD/CQRS, Hexagonal Architecture, and repository
rules; set the REVIEW_PROMPT environment variable to override the scope.
Review failures must stay concrete and scoped to changed code or directly
affected behavior.
Success contract: the loop exits 0 with a final
AI_REVIEW_VERDICT: PASS line (plugin script contract). Repo-provided loops
may print their own banner — treat exit status as the contract:
AI_REVIEW_OUTPUT="$(AI_REVIEW_BASE="$TRUSTED_BASE_REF" make ai-review-loop 2>&1)"
printf '%s\n' "$AI_REVIEW_OUTPUT"
AI_REVIEW_LAST_LINE="$(printf '%s\n' "$AI_REVIEW_OUTPUT" | sed '/^[[:space:]]*$/d' | tail -n 1)"
test "$AI_REVIEW_LAST_LINE" = "AI review PASS."
Configuration — plugin script flags (canonical): --agents LIST
(overrides review.ai_review_agents), --max-iterations N (default 5),
--diff-base REF (default main). Repo-provided loops commonly accept
environment overrides instead:
AI_REVIEW_BASE="$TRUSTED_BASE_REF" AI_REVIEW_AGENTS=claude make ai-review-loop
AI_REVIEW_BASE=develop AI_REVIEW_MAX_ITER=1 make ai-review-loop
Step 1: Get PR Comments
Capture the snapshot and initialize the ledger (Evidence Ledger Protocol
above), then list comments through the target mapped by make.pr_comments;
when it is null, the plugin substitutes:
"${CLAUDE_PLUGIN_ROOT}/scripts/get-pr-comments.sh" --pr "$PR"
"${CLAUDE_PLUGIN_ROOT}/scripts/get-pr-comments.sh" --pr "$PR" --unresolved-only
"${CLAUDE_PLUGIN_ROOT}/scripts/get-pr-comments.sh" --pr "$PR" --json
Output: all unresolved comments with file/line, author, timestamp, URL.
Step 2: Categorize Comments
| Type | Identifier | Priority | Action |
|---|
| Committable Suggestion | Code block, "```suggestion" | Highest | Apply immediately, commit separately |
| LLM Prompt | "Prompt for AI Agents" | High | Execute prompt, implement changes |
| Architecture Concern | Class naming, file location | High | Invoke appropriate skill |
| Question | Ends with "?" | Medium | Answer inline or via code change |
| General Feedback | Discussion, recommendation | Low | Consider and improve |
| Resolved/Stale | Outdated or already fixed | None | Do not change code; record reason |
Step 3: Verify Architecture & Organization
For code changes (suggestions, prompts, new files), invoke verification
skills:
Quick verification: run the repo's style auto-fixer, then the targets
mapped by make.psalm, make.deptrac, and make.tests.
Step 4: Apply Changes Systematically
For Committable Suggestions
-
Verify the suggestion still applies to current code
-
Apply the suggestion exactly when it is still valid and compatible with
repository rules
-
If the suggestion is stale, implement the current equivalent fix or record
why no change is needed (decline evidence)
-
Commit with reference:
git commit -m "Apply review suggestion: [brief description]
Ref: [comment URL]"
For LLM Prompts
- Copy prompt from comment
- Verify every finding against current code before changing files
- Execute still-valid instructions
- Skip stale, duplicate, or contradicted findings with a brief reason
- Verify output meets requirements
- Commit with reference
For Architecture/Organization Concerns
- Invoke the appropriate skill (code-organization
or implementing-ddd-architecture)
- Implement recommended changes
- Verify: style auto-fixer plus the
make.psalm, make.deptrac, and
make.tests targets
- Commit with reference
For Questions
- Determine if code change or reply needed
- If code: implement + commit
- If reply: respond on GitHub with
EVIDENCE_SOURCE/EVIDENCE_ACTION fields
For General Feedback
- Evaluate suggestion merit
- Implement if beneficial
- Document reasoning if declined (decline evidence with
EVIDENCE_REASON)
One commit per comment — never batch unrelated review fixes.
Step 5: Verify All Addressed
Query unresolved review threads directly (paginated; generic GraphQL):
set -euo pipefail
: "${PR:?Set PR to the pull request number}"
: "${PR_REPO:?Set PR_REPO=owner/repo for the base PR repository}"
owner="${PR_REPO%%/*}"
repo="${PR_REPO#*/}"
query='query($owner:String!,$repo:String!,$pr:Int!,$cursor:String){repository(owner:$owner,name:$repo){pullRequest(number:$pr){reviewThreads(first:100,after:$cursor){pageInfo{hasNextPage endCursor} nodes{isResolved comments(first:1){nodes{id}}}}}}}'
total_count=0
cursor=''
while :; do
if [ -n "$cursor" ]; then
page_json="$(gh api graphql -f owner="$owner" -f repo="$repo" -F pr="$PR" -f cursor="$cursor" -f query="$query")"
else
page_json="$(gh api graphql -f owner="$owner" -f repo="$repo" -F pr="$PR" -f query="$query")"
fi
page_count="$(printf '%s\n' "$page_json" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and (.comments.nodes | length > 0))] | length')"
total_count=$((total_count + page_count))
has_next="$(printf '%s\n' "" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')"
[ = ] ||
cursor=
-n && !=
-eq 0
If unresolved comments remain, repeat categorization and implementation. If a
remaining thread is stale, duplicate, or answer-only, respond or resolve it
with reply/decline evidence before continuing. Then complete the ledger:
write PR_HEAD=<sha> plus a COMMENT_META line for every snapshotted
comment (all three sources), and one validated COMMENT|url|commit|sha,
COMMENT|url|reply|url, or COMMENT|url|decline|url line per COMMENT_META
line, following the Evidence Ledger Protocol exactly — including the
post-snapshot restart rule.
Step 5b: Publish findings (gated)
When capabilities.publish_pr_comments is true, project the Step 2
priority/disposition categorization and the evidence-ledger dispositions
(fixed = commit, dropped = decline/stale) to the canonical ledger JSON
(schema in the poster header) at
${SDLC_LEDGER_DIR:-.sdlc/review-ledgers}/code-review.json, then publish ONE
consolidated, idempotent PR comment via the target mapped by
make.post_review_findings; when that key is null, the plugin substitutes
its script (the same null-substitution pattern as make.pr_comments →
get-pr-comments.sh in Step 1):
"${CLAUDE_PLUGIN_ROOT}/scripts/post-review-findings.sh" code-review \
--file "${SDLC_LEDGER_DIR:-.sdlc/review-ledgers}/code-review.json" --pr "$PR"
The poster is idempotent (hidden <!-- sdlc-review:code-review --> marker — it
updates its prior comment, never spams), authorized (writes only to the resolved
repo's own PR), and DEGRADES (NFR-3): capabilities.publish_pr_comments
false/absent, gh absent, no PR, an empty ledger, a mismatched base repo, or a
gh write failure all skip-with-note and exit 0 — publishing NEVER fails this
workflow. When the flag is false/absent, skip this step with a note.
Step 6: Run Quality Checks
MANDATORY: run the target mapped by make.ci after implementing all
changes; it must exit 0. Many repositories print a success banner — treat
exit status as the contract, the banner as confirmation:
CI_OUTPUT="$(make ci 2>&1)"
printf '%s\n' "$CI_OUTPUT"
CI_LAST_LINE="$(printf '%s\n' "$CI_OUTPUT" | sed '/^[[:space:]]*$/d' | tail -n 1)"
test "$CI_LAST_LINE" = "✅ CI checks successfully passed!"
Suppression scan — after CI, scan the PR diff for forbidden suppression
or ignore directives. Fix root causes; never silence tools:
command -v rg >/dev/null || { echo "rg is required for suppression scan" >&2; exit 1; }
set -o pipefail
diff_output="$(git diff --unified=0 "$TRUSTED_BASE_REF"...HEAD)" || {
echo "Unable to compute PR diff for suppression scan" >&2
exit 1
}
forbidden_suppression_pattern() {
printf '%s\n' '@Suppress''Warnings|@psalm-''suppress|@phpstan-''ignore|phpstan-''ignore|phpcs:(''ignore|disable)|@infection-''ignore|@codeCoverage''Ignore|@phpinsights-''ignore|@codingStandards''Ignore|codingStandards''Ignore'
}
if printf '%s\n' "$diff_output" | rg '^\+[^+]' | rg -n "$(forbidden_suppression_pattern)"; then
echo "Forbidden suppression/ignore directive found in PR diff" >&2
exit 1
fi
GATE_DEFINITION_FILE_PATTERN=
all_changes=
gate_definition_changes=
non_gate_definition_changes=
GATE_DEFINITION_CHANGES_PRESENT=
[ -n ] && [ -z ];
GATE_DEFINITION_CHANGES_PRESENT=
quality_config_changes=
[ -n ] && [ != ];
>&2
1
On a dedicated gate-definition PR (GATE_DEFINITION_CHANGES_PRESENT=true,
i.e. only gate-definition files changed vs $TRUSTED_BASE_REF) the
quality-config hard block is skipped so the PR can legitimately tighten
quality-tool configs, while the forbidden-suppression line scan still runs
and any threshold change must be raise-only (see Thresholds below). A PR that
mixes gate-definition and product changes does not qualify and is blocked.
Gate-definition isolation rule: changes to CI/review gate definitions
(Makefile, CI workflow files, quality-tool configs, lint/formatter configs,
review scripts, agent skill files, required-check declarations) must never
piggyback on a product PR. They belong in a dedicated gate-definition PR with
no product, runtime, or unrelated test-code changes, reviewed on its own.
A product PR must not validate itself against gate definitions it supplies.
Thresholds come from quality.* in the profile and are raise-only
(canonical floors: quality.phpinsights.quality 100,
quality.phpinsights.architecture 100, quality.phpinsights.style 100,
quality.phpinsights.complexity 94, quality.infection_msi 100; fixed
ceilings: quality.deptrac_violations 0, quality.psalm_errors 0). A
profile may tighten these floors, never relax them. Fix the code, never the
threshold.
If CI fails, invoke the appropriate skill:
DO NOT finish the task until the make.ci target exits 0.
Step 7: Run Final AI Review Loop
After the final successful CI run, capture CI_HEAD="$(git rev-parse HEAD)"
and run the AI review loop again (same invocation as Step 0, diff base
$TRUSTED_BASE_REF) before any push or ready-for-review action. Then assert:
test "$CI_HEAD" = "$(git rev-parse HEAD)"
test -z "$(git status --short)"
VERIFIED_HEAD="$CI_HEAD"
CURRENT_BASE_JSON="$(gh pr view "$PR" --repo "$PR_REPO" --json baseRefName,baseRefOid)"
test "$BASE_OID" = "$(printf '%s\n' "$CURRENT_BASE_JSON" | jq -r .baseRefOid)"
test "$BASE_REF" = "$(printf '%s\n' "$CURRENT_BASE_JSON" | jq -r .baseRefName)"
If the loop applies fixes or changes any tracked file, repeat:
- Review
git status --short
- Commit intentional tracked changes with the relevant review or AI-loop
reference
- Re-run the
make.ci target
- Re-run the forbidden-suppression and quality-config scans
- Re-capture
CI_HEAD="$(git rev-parse HEAD)"
- Re-run the loop and require
AI_REVIEW_VERDICT: PASS
Before the final loop, write PR_HEAD=<CI_HEAD> plus generated
COMMENT_META lines into PR_COMMENT_EVIDENCE, then add one validated
COMMENT|... evidence line per COMMENT_META line. After the final loop,
git rev-parse HEAD must still equal the captured CI_HEAD,
git status --short must be empty, and the current PR baseRefName and
baseRefOid must still equal the BASE_REF and BASE_OID captured before
CI. Set VERIFIED_HEAD="$CI_HEAD" only after those checks pass. Step 8
reruns this local gate and recomputes VERIFIED_HEAD before pushing —
caller-supplied VERIFIED_HEAD/BASE_OID values are never trusted as proof.
Do not push, mark ready, or declare completion until the loop reports PASS on
the same commit and base that passed CI without leaving new uncommitted
changes. If the loop cannot be run at all, completion is blocked.
Step 8: Push And Verify GitHub PR Readiness
8a. Rerun the local readiness gate on a clean worktree (recompute, don't
trust earlier variables): assert git status --short is empty → run the
make.ci target (exit 0) → capture LOCAL_HEAD → rewrite ledger metadata
(PR_HEAD, fresh COMMENT_META lines from a fresh enumeration of all three
comment sources, preserving existing COMMENT lines) → validate every
evidence line → run the AI review loop (PASS) → assert HEAD unchanged,
worktree clean, and PR base (baseRefName/baseRefOid) unchanged. Set
VERIFIED_HEAD="$LOCAL_HEAD".
8b. Push to the actual PR head repository (fork-aware):
test "$(git rev-parse HEAD)" = "$VERIFIED_HEAD"
LOCAL_HEAD="$VERIFIED_HEAD"
HEAD_REF="$(gh pr view "$PR" --repo "$PR_REPO" --json headRefName --jq .headRefName)"
PR_HEAD_REPO="$(gh pr view "$PR" --repo "$PR_REPO" --json headRepository --jq .headRepository.nameWithOwner)"
CURRENT_REPO="$(gh repo view --json nameWithOwner --jq .nameWithOwner)"
if [ "$PR_HEAD_REPO" = "$CURRENT_REPO" ]; then
git push origin HEAD:"$HEAD_REF"
else
if git remote get-url pr-head >/dev/null 2>&1; then
git remote set-url pr-head "git@github.com:${PR_HEAD_REPO}.git"
else
git remote add pr-head "git@github.com:${PR_HEAD_REPO}.git"
fi
git push pr-head HEAD:"$HEAD_REF"
fi
PR_HEAD="$(gh pr view "$PR" --repo "$PR_REPO" --json headRefOid --jq .headRefOid)"
test "$LOCAL_HEAD" = ""
8c. Wait for checks (skip with a degrade note when ci.provider is
null):
gh pr checks "$PR" --repo "$PR_REPO" --watch --interval 30
8d. Verify the commit-scoped check rollup on the pushed head, queried
from the base PR repository (never the fork). Paginate
statusCheckRollup.contexts(first:100) on the commit object:
query='query($owner:String!,$repo:String!,$oid:GitObjectID!,$cursor:String){repository(owner:$owner,name:$repo){object(oid:$oid){... on Commit{statusCheckRollup{contexts(first:100,after:$cursor){pageInfo{hasNextPage endCursor} nodes{__typename ... on CheckRun{name conclusion status detailsUrl checkSuite{app{slug}}} ... on StatusContext{context state targetUrl creator{login}}}}}}}}}'
Normalize each node to {type, name, state, source, url} (CheckRun →
check_run with checkSuite.app.slug as source; StatusContext →
status_context with creator.login as source), then assert:
-
The rollup is non-empty; an empty or incomplete check list is a blocker
-
Every context has an allowed terminal state:
jq -e 'length > 0 and all(.[]; .state | IN("SUCCESS", "SKIPPED", "NEUTRAL"))'
-
Every check named in the profile ci.required_checks list is present with
state SUCCESS on this exact commit
-
Every live base-branch protection required status/check is present with
state SUCCESS — query the protection rule and compare:
branch_query='query($owner:String!,$repo:String!,$baseRefName:String!){repository(owner:$owner,name:$repo){ref(qualifiedName:$baseRefName){branchProtectionRule{requiredStatusCheckContexts requiredStatusChecks{context app{slug}}}}}}'
-
GitHub Actions check detailsUrl values start with
https://github.com/<PR_REPO>/actions/runs/ — checks pointing at another
repository are not trusted
8e. Verify a qualifying approval on the pushed head. Paginate
pullRequest.reviews(first:100) selecting
{url state submittedAt author{login} authorAssociation commit{oid}} and
require at least one review where:
state == "APPROVED"
commit.oid == LOCAL_HEAD
author.login != PR author
authorAssociation in (OWNER, MEMBER, COLLABORATOR)
submittedAt > latest addressed-comment/evidence event time
The "latest evidence event" is the maximum effective time across all ledger
source comments and their commit/reply/decline evidence.
8f. Poll the final readiness predicate (retry up to 30 times, 10s apart):
gh pr view "$PR" --repo "$PR_REPO" --json state,mergeStateStatus,mergeable,reviewDecision,isDraft,reviewRequests,headRefOid,baseRefOid,baseRefName |
jq -e --arg head "$LOCAL_HEAD" --arg base "$BASE_OID" --arg base_ref "$BASE_REF" '
.headRefOid == $head and
.baseRefOid == $base and
.baseRefName == $base_ref and
.state == "OPEN" and
.isDraft == false and
.mergeable == "MERGEABLE" and
(.mergeStateStatus | IN("CLEAN", "HAS_HOOKS")) and
.reviewDecision == "APPROVED" and
([.reviewRequests[]?] | length) == 0
'
8g. Re-verify after the wait — checks and reviews can change while
polling, so run the full set again on the same pushed head: unresolved-thread
query shows 0, ledger evidence still validates (review-thread and non-thread
sources), no unaccounted post-snapshot comments (only validated evidence URLs
and a qualifying empty/FINAL_APPROVAL_NO_ACTION: true approval are
allowed), required checks still SUCCESS, approval still present, readiness
predicate still true. If anything regressed, return to the failing step.
Required state:
- PR
headRefOid equals local HEAD before and after waiting for checks
- PR
baseRefName and baseRefOid still equal the BASE_REF/BASE_OID
used for CI, the suppression scan, the final AI review loop, and the
branch-protection query
- Commit-scoped status/check rollup queried from the base PR repository is
non-empty; non-required contexts only
SUCCESS, SKIPPED, or NEUTRAL
- Every
ci.required_checks entry and every live base-branch protection
required status/check is SUCCESS on the pushed head; GitHub Actions check
URLs point at the base PR repository
- Final direct review-thread query after pushed-head verification shows 0
unresolved review comments
PR_COMMENT_EVIDENCE records the auto-captured snapshot, PR_HEAD,
validated COMMENT_META lines, and validated COMMENT|url|action|evidence
lines for every snapshotted comment from all three sources
- No non-evidence comments created/edited after the snapshot (or the
snapshot/evidence loop was restarted), modulo the qualifying-approval
exception
state OPEN, isDraft false, mergeable MERGEABLE,
mergeStateStatus CLEAN/HAS_HOOKS, reviewDecision APPROVED,
reviewRequests empty
- Direct review query shows a qualifying
APPROVED review per 8e
Constraints (Parameters)
NEVER:
- Skip the autonomous AI review loop (
make.ai_review_loop target or plugin
script)
- Skip committable suggestions
- Batch unrelated changes in one commit
- Ignore LLM prompts from reviewers
- Apply stale or invalid review suggestions blindly
- Commit without running verification
- Leave questions unanswered
- Accept organizational violations (invoke
code-organization)
- Accept architecture violations (invoke
implementing-ddd-architecture)
- Add suppression/ignore annotations to "fix" review comments or CI failures
- Lower any
quality.* threshold or edit deptrac.yaml to make findings
disappear — thresholds are raise-only, fix the code instead
- Finish before the
make.ci target exits 0
- Finish when the AI review loop was not run after the final CI run
- Finish while
git status --short reports uncommitted changes after the
final AI review loop
- Finish while local
HEAD differs from the PR headRefOid
- Finish while GitHub reports failing checks, conflicts, draft status,
requested changes (when
review.request_changes_blocking is true),
required review, or a blocking merge state
ALWAYS:
- Run the AI review loop before manually addressing PR comments and again
after the final CI run, before push/ready
- Verify review findings against current code before applying them
- Commit each suggestion separately with URL reference
- Invoke code-organization for structural
issues and implementing-ddd-architecture
for DDD violations
- Run the
make.ci target after implementing all changes
- Scan the PR diff for forbidden suppression/ignore directives and blocked
quality-config changes
- Push and verify PR
headRefOid equals local HEAD
- Check
gh pr checks and gh pr view after pushing
- Address ALL CI failures before finishing
- Mark conversations resolved after addressing
Format (Output)
Commit Message Template:
Apply review suggestion: [concise description]
[Optional: explanation if non-obvious]
Ref: https://github.com/<owner>/<repo>/pull/XX#discussion_rYYYYYYY
Final Verification:
✅ direct GitHub GraphQL review-thread query shows 0 unresolved
✅ make.ci target exits 0
✅ final AI review loop reports AI_REVIEW_VERDICT: PASS after CI
✅ git status --short is empty after the final loop
✅ PR baseRefName/baseRefOid still match the base used for CI, the final loop, and the branch-protection query
✅ local HEAD matches the PR headRefOid
✅ base-repository check rollup: non-empty, no disallowed non-required states, every ci.required_checks entry and live branch-protection check SUCCESS, Actions URLs in the base repo
✅ final review-thread query after pushed-head verification shows 0 unresolved
✅ PR_COMMENT_EVIDENCE has auto-captured snapshot metadata, pushed HEAD, comment updatedAt/body hashes, and validated trusted-author action/evidence for every snapshotted comment URL
✅ no non-evidence comments created/edited after the snapshot (qualifying approval exception only)
✅ gh pr view shows OPEN, no conflicts, not draft, mergeStateStatus CLEAN/HAS_HOOKS, reviewDecision APPROVED, no reviewRequests
✅ direct review query shows an APPROVED review on the pushed HEAD, submitted after the latest evidence event, by a non-author OWNER/MEMBER/COLLABORATOR reviewer
Verification Checklist
Quick Reference: When to Use Related Skills
Related Skills