Audit Review Decisions Skill
Mine merged PR review threads for agreed-but-deferred suggestions that were never
implemented. Identify review debt before it compounds.
When to Use
- User says "audit review decisions", "find deferred review items", "surface review
debt", "what did reviewers flag for later"
Arguments
$1 — Time period (e.g. 14d, 30d, 7d). Default: 14d.
$2 — Output path. Default:
${AUTOSKILLIT_TEMP}/audit-review-decisions/review_decisions_audit_$(date +%Y-%m-%d_%H%M%S).md
Critical Constraints
NEVER:
- Create files outside
${AUTOSKILLIT_TEMP}/audit-review-decisions/
- Have triage or validation subagents make GitHub API calls (local data only for Step 2)
- Post duplicate
[AUDIT] markers — check for existing marker before posting
- Run subagents in the background (
run_in_background: true is prohibited)
- Use
gh pr list without --limit to avoid pagination truncation
- Use
\| in Grep patterns — use | for alternation (ERE, not BRE)
ALWAYS:
- Save raw PR JSON to temp before any analysis (Step 1)
- Use GraphQL alias batching (~20 PRs per query) for data collection
- Include
rateLimit { cost remaining resetAt } in every GraphQL query
- Sleep 1s between consecutive mutating GitHub API calls (Step 5 watermark posts)
- Step 2 triage subagents read local JSON files only — zero API calls
- Step 3 validation subagents grep the actual current codebase
- Skip threads that already contain an
[AUDIT] comment
- Resolve owner/repo from
git remote get-url origin — never hardcode
- Use
/autoskillit: prefix when invoking any other skill
Workflow
Step 0: Watermark Resolution
Parse $1 for time period. Default 14d. Compute PERIOD_DAYS.
Resolve OWNER and REPO from git remote get-url origin.
Query the most recent [AUDIT] sentinel comment across recently merged PRs:
gh api graphql -f query='
query($owner:String!, $name:String!) {
rateLimit { cost remaining resetAt }
repository(owner:$owner, name:$name) {
pullRequests(first:500, states:MERGED, orderBy:{field:UPDATED_AT,direction:DESC}) {
nodes { number
reviewThreads(first:50) {
nodes { comments(first:10) { nodes { body createdAt } } }
}
}
}
}
}' -f owner="${OWNER}" -f name="${REPO}"
Extract the most recent createdAt from any comment whose body starts with
[AUDIT]. Store as LAST_AUDIT_TS (empty string if none — first run).
Compute SCAN_SINCE:
- If
LAST_AUDIT_TS is set: max(LAST_AUDIT_TS, date -d "now - PERIOD_DAYS days")
- Else:
date -d "now - PERIOD_DAYS days" --iso-8601=seconds
Log: Scan window: ${SCAN_SINCE} to now (${PERIOD_DAYS}d configured, last audit: ${LAST_AUDIT_TS:-none})
Step 1: Data Collection (GraphQL Batch)
List merged PRs in the scan window:
SCAN_DATE=$(echo "${SCAN_SINCE}" | cut -c1-10)
PR_NUMS=$(gh pr list --state merged \
--search "merged:>=${SCAN_DATE}" \
--json number --limit 500 | jq -r '.[].number')
Create temp directory:
mkdir -p "${AUTOSKILLIT_TEMP}/audit-review-decisions/raw"
Batch fetch in groups of 20 using GraphQL aliases. For each batch, build a query
with aliased pr${i}: pullRequest(number: ${NUM}) nodes. Each node fetches:
number title mergedAt
reviews(first: 100) {
nodes { author { login } body state submittedAt }
}
reviewThreads(first: 100) {
pageInfo { hasNextPage endCursor }
nodes {
isResolved
comments(first: 100) {
nodes { databaseId author { login } body path line createdAt }
}
}
}
Include rateLimit { cost remaining resetAt } at query root.
After the initial fetch, for each PR where reviewThreads.pageInfo.hasNextPage is
true, issue additional aliased queries with reviewThreads(first:100, after:$endCursor)
until hasNextPage is false. Merge the nodes arrays across pages before filtering.
For each PR in the batch response:
- Filter out threads whose
comments list contains any comment with body
starting with [AUDIT] (already watermarked — skip entirely).
- If the PR has zero remaining threads: skip saving.
- Otherwise: save filtered data to
${AUTOSKILLIT_TEMP}/audit-review-decisions/raw/pr_${number}.json
Step 2: Triage (Haiku — Broad Pass)
List all JSON files in raw/. Split into batches of ~5 files per agent.
Launch parallel Haiku subagents (one per batch, model: "haiku"). Each agent:
- Reads its assigned JSON files only (no API calls).
- Flags a thread if it matches any signal:
<!-- REVIEW-FLAG: tag present
- Body contains one of:
"Valid observation — flagged for design decision",
"out of scope for this fix cycle", "requires a dedicated cleanup commit",
"left open for human review", "future improvement", "beyond this PR's scope",
"requires team consensus"
- Thread
isResolved: false AND author acknowledged validity in a reply
- Review body
state: COMMENTED with no corresponding thread (needs_human indicator)
- Returns candidates as response text only — no file writes. Per-candidate format:
PR: {number}
thread_index: {N}
comment_id: {databaseId of first comment in thread}
path: {file path or empty}
line: {line number or empty}
signal: REVIEW-FLAG|KEYWORD|UNRESOLVED|NEEDS_HUMAN
severity: {from REVIEW-FLAG tag, or "unknown"}
dimension: {from REVIEW-FLAG tag, or "unknown"}
quote: {first 200 chars of flagged comment body}
- False positives are acceptable; false negatives are not.
Collect and parse candidate text from all agent responses.
Step 3: Validation (Sonnet — Deep Pass)
Group candidates into batches of ~10. Launch parallel Sonnet subagents
(model: "sonnet") per batch.
Each Sonnet agent receives its candidate batch and, for each candidate:
Collect and parse validated findings from all agent responses.
Step 4: Report Generation
- Collect all validated findings from Step 3 subagent responses.
- Sort findings: VALID first (by priority HIGH→MEDIUM→LOW), then RESOLVED, then STALE.
- Resolve the output path:
- Use
$2 if provided.
- Otherwise:
${AUTOSKILLIT_TEMP}/audit-review-decisions/review_decisions_audit_$(date +%Y-%m-%d_%H%M%S).md
- Create parent directory:
mkdir -p "$(dirname "${OUTPUT_PATH}")"
- Write the markdown report to
${OUTPUT_PATH}. Structure:
PR Review Decisions Audit — {PERIOD_DAYS}d window
Generated: {ISO timestamp}
Scan window: {SCAN_SINCE} to {now}
PRs scanned: {N} | Threads examined: {M} | Threads skipped (already audited): {K}
Summary
| Classification |
Count |
| VALID (ticket-worthy) |
{N} |
| RESOLVED (already fixed) |
{N} |
| STALE (no longer applicable) |
{N} |
Priority Triage
HIGH Priority
For each HIGH VALID finding, write a section:
### {suggested_title}
**PR:** #{number} | **File:** {path}:{line} | **Severity:** {severity} | **Dimension:** {dimension}
> {reviewer_quote}
**Current relevance:** VALID — {impact}
**Suggested issue title:** {suggested_title}
**Affected files:** {path}
MEDIUM Priority
{Same structure}
LOW Priority
{Same structure}
RESOLVED Findings
{List: PR, file, one-line description of what was fixed}
STALE Findings
{List: PR, file, one-line description of why no longer applicable}
Open PR Findings
{Findings from PRs that were open (not merged) at scan time — may still be addressed.
Same per-finding structure but labeled as pending.}
Pattern Analysis
Most common deferral phrases (by frequency):
{Table: phrase | count | % of all candidates}
Dimensions with highest VALID rate:
{Table: dimension | valid | resolved | stale | valid_rate}
Systemic escape hatches detected:
{Narrative: which phrases act as systematic blockers to tracking, with counts}
Recommendations:
{2–4 concrete process recommendations based on the pattern data}
- After writing the file, print a terminal summary:
audit-review-decisions complete
Output: {OUTPUT_PATH}
VALID: {N} | RESOLVED: {N} | STALE: {N}
Top finding: {first HIGH priority suggested_title, or "none"}
Step 5: Watermark (Thread Annotation)
For every finding processed in Steps 2–3 (all classifications — VALID, RESOLVED, STALE):
Re-check for existing audit marker (live): fetch the thread's current comments
directly from the GitHub API — do not use the Step 1 JSON cache, which already
filtered out [AUDIT]-marked threads and cannot detect markers posted after Step 1:
gh api "repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/comments" \
--jq "[.[] | select(.id == ${COMMENT_ID} or .in_reply_to_id == ${COMMENT_ID}) | .body | startswith(\"[AUDIT]\")] | any"
If the result is true: skip this thread (idempotent — no duplicate post).
Determine marker body based on classification and ticket status:
| Classification |
Ticket created? |
Marker body |
| VALID |
Yes |
[AUDIT] — tracked in #{issue_number} |
| VALID |
No |
[AUDIT] — acknowledged, no action taken |
| RESOLVED |
— |
[AUDIT] — verified resolved in current codebase |
| STALE |
— |
[AUDIT] — no longer applicable |
Post reply comment:
gh api "repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/comments/${COMMENT_ID}/replies" \
--method POST \
--field body="${MARKER_BODY}"
sleep 1
COMMENT_ID is the databaseId of the first comment in the thread (from Step 1 JSON).
Thread reply constraint: These calls cannot be batched via the reviews API — each
requires an individual POST. The 1s delay between calls is mandatory per GitHub API
discipline.
Log progress per finding: [AUDIT] Posted marker on PR #{number} thread {comment_id}: {marker_body}
1---2name: audit-review-decisions3description: Audit merged PR review threads for agreed-but-deferred suggestions (design decisions, future work, out-of-scope items) that were never implemented. Mines REVIEW-FLAG markers from resolve-review and legacy keyword signals. Produces a structured markdown report with VALID/RESOLVED/STALE classifications and annotates processed threads with [AUDIT] markers to prevent re-identification on future runs.4---56# Audit Review Decisions Skill78Mine merged PR review threads for agreed-but-deferred suggestions that were never9implemented. Identify review debt before it compounds.1011## When to Use1213- User says "audit review decisions", "find deferred review items", "surface review14 debt", "what did reviewers flag for later"1516## Arguments1718- `$1` — Time period (e.g. `14d`, `30d`, `7d`). Default: `14d`.19- `$2` — Output path. Default:20 `${AUTOSKILLIT_TEMP}/audit-review-decisions/review_decisions_audit_$(date +%Y-%m-%d_%H%M%S).md`2122## Critical Constraints2324**NEVER:**25- Create files outside `${AUTOSKILLIT_TEMP}/audit-review-decisions/`26- Have triage or validation subagents make GitHub API calls (local data only for Step 2)27- Post duplicate `[AUDIT]` markers — check for existing marker before posting28- Run subagents in the background (`run_in_background: true` is prohibited)29- Use `gh pr list` without `--limit` to avoid pagination truncation30- Use `\|` in Grep patterns — use `|` for alternation (ERE, not BRE)3132**ALWAYS:**33- Save raw PR JSON to temp before any analysis (Step 1)34- Use GraphQL alias batching (~20 PRs per query) for data collection35- Include `rateLimit { cost remaining resetAt }` in every GraphQL query36- Sleep 1s between consecutive mutating GitHub API calls (Step 5 watermark posts)37- Step 2 triage subagents read local JSON files only — zero API calls38- Step 3 validation subagents grep the actual current codebase39- Skip threads that already contain an `[AUDIT]` comment40- Resolve owner/repo from `git remote get-url origin` — never hardcode41- Use `/autoskillit:` prefix when invoking any other skill4243---4445## Workflow4647### Step 0: Watermark Resolution48491. Parse `$1` for time period. Default `14d`. Compute `PERIOD_DAYS`.50512. Resolve `OWNER` and `REPO` from `git remote get-url origin`.52533. Query the most recent `[AUDIT]` sentinel comment across recently merged PRs:54 ```bash55 gh api graphql -f query='56 query($owner:String!, $name:String!) {57 rateLimit { cost remaining resetAt }58 repository(owner:$owner, name:$name) {59 pullRequests(first:500, states:MERGED, orderBy:{field:UPDATED_AT,direction:DESC}) {60 nodes { number61 reviewThreads(first:50) {62 nodes { comments(first:10) { nodes { body createdAt } } }63 }64 }65 }66 }67 }' -f owner="${OWNER}" -f name="${REPO}"68 ```69 Extract the most recent `createdAt` from any comment whose `body` starts with70 `[AUDIT]`. Store as `LAST_AUDIT_TS` (empty string if none — first run).71724. Compute `SCAN_SINCE`:73 - If `LAST_AUDIT_TS` is set: `max(LAST_AUDIT_TS, date -d "now - PERIOD_DAYS days")`74 - Else: `date -d "now - PERIOD_DAYS days" --iso-8601=seconds`75765. Log: `Scan window: ${SCAN_SINCE} to now (${PERIOD_DAYS}d configured, last audit: ${LAST_AUDIT_TS:-none})`7778---7980### Step 1: Data Collection (GraphQL Batch)81821. List merged PRs in the scan window:83 ```bash84 SCAN_DATE=$(echo "${SCAN_SINCE}" | cut -c1-10)85 PR_NUMS=$(gh pr list --state merged \86 --search "merged:>=${SCAN_DATE}" \87 --json number --limit 500 | jq -r '.[].number')88 ```89902. Create temp directory:91 ```bash92 mkdir -p "${AUTOSKILLIT_TEMP}/audit-review-decisions/raw"93 ```94953. Batch fetch in groups of 20 using GraphQL aliases. For each batch, build a query96 with aliased `pr${i}: pullRequest(number: ${NUM})` nodes. Each node fetches:97 ```graphql98 number title mergedAt99 reviews(first: 100) {100 nodes { author { login } body state submittedAt }101 }102 reviewThreads(first: 100) {103 pageInfo { hasNextPage endCursor }104 nodes {105 isResolved106 comments(first: 100) {107 nodes { databaseId author { login } body path line createdAt }108 }109 }110 }111 ```112 Include `rateLimit { cost remaining resetAt }` at query root.113 After the initial fetch, for each PR where `reviewThreads.pageInfo.hasNextPage` is114 `true`, issue additional aliased queries with `reviewThreads(first:100, after:$endCursor)`115 until `hasNextPage` is `false`. Merge the `nodes` arrays across pages before filtering.1161174. For each PR in the batch response:118 - Filter out threads whose `comments` list contains any comment with `body`119 starting with `[AUDIT]` (already watermarked — skip entirely).120 - If the PR has zero remaining threads: skip saving.121 - Otherwise: save filtered data to122 `${AUTOSKILLIT_TEMP}/audit-review-decisions/raw/pr_${number}.json`123124---125126### Step 2: Triage (Haiku — Broad Pass)1271281. List all JSON files in `raw/`. Split into batches of ~5 files per agent.1291302. Launch **parallel Haiku subagents** (one per batch, `model: "haiku"`). Each agent:131 - Reads its assigned JSON files only (no API calls).132 - Flags a thread if it matches any signal:133 - `<!-- REVIEW-FLAG:` tag present134 - Body contains one of: `"Valid observation — flagged for design decision"`,135 `"out of scope for this fix cycle"`, `"requires a dedicated cleanup commit"`,136 `"left open for human review"`, `"future improvement"`, `"beyond this PR's scope"`,137 `"requires team consensus"`138 - Thread `isResolved: false` AND author acknowledged validity in a reply139 - Review body `state: COMMENTED` with no corresponding thread (needs_human indicator)140 - Returns candidates as **response text only — no file writes**. Per-candidate format:141 ```142 PR: {number}143 thread_index: {N}144 comment_id: {databaseId of first comment in thread}145 path: {file path or empty}146 line: {line number or empty}147 signal: REVIEW-FLAG|KEYWORD|UNRESOLVED|NEEDS_HUMAN148 severity: {from REVIEW-FLAG tag, or "unknown"}149 dimension: {from REVIEW-FLAG tag, or "unknown"}150 quote: {first 200 chars of flagged comment body}151 ```152 - False positives are acceptable; false negatives are not.1531543. Collect and parse candidate text from all agent responses.155156---157158### Step 3: Validation (Sonnet — Deep Pass)1591601. Group candidates into batches of ~10. Launch **parallel Sonnet subagents**161 (`model: "sonnet"`) per batch.1621632. Each Sonnet agent receives its candidate batch and, for each candidate:164 - If `path` is set: reads the file and surrounding context.165 - Greps the current codebase for the core concern from `quote` (judgment-based166 pattern, not a literal string match).167 - Classifies:168 - `VALID` — issue still present, impactful, ticket-worthy169 - `RESOLVED` — code changed; concern no longer applies170 - `STALE` — code deleted/refactored; finding irrelevant171 - Returns findings as **response text only — no file writes**. Per-finding format:172 ```173 PR: {number}174 comment_id: {databaseId}175 classification: VALID|RESOLVED|STALE176 path: {file:line or empty}177 severity: {critical|warning|info|unknown}178 dimension: {arch|bugs|defense|tests|cohesion|slop|unknown}179 priority: HIGH|MEDIUM|LOW180 impact: {one sentence}181 suggested_title: {short GitHub issue title}182 reviewer_quote: {verbatim first 300 chars}183 ```184 - Priority assignment: `HIGH` = severity=critical OR dimension in (bugs, arch);185 `MEDIUM` = severity=warning; `LOW` = severity=info or unknown.1861873. Collect and parse validated findings from all agent responses.188189---190191### Step 4: Report Generation1921931. Collect all validated findings from Step 3 subagent responses.1942. Sort findings: VALID first (by priority HIGH→MEDIUM→LOW), then RESOLVED, then STALE.1953. Resolve the output path:196 - Use `$2` if provided.197 - Otherwise: `${AUTOSKILLIT_TEMP}/audit-review-decisions/review_decisions_audit_$(date +%Y-%m-%d_%H%M%S).md`1984. Create parent directory: `mkdir -p "$(dirname "${OUTPUT_PATH}")"`1995. Write the markdown report to `${OUTPUT_PATH}`. Structure:200201---202203# PR Review Decisions Audit — {PERIOD_DAYS}d window204205**Generated:** {ISO timestamp}206**Scan window:** {SCAN_SINCE} to {now}207**PRs scanned:** {N} | **Threads examined:** {M} | **Threads skipped (already audited):** {K}208209## Summary210211| Classification | Count |212|---|---|213| VALID (ticket-worthy) | {N} |214| RESOLVED (already fixed) | {N} |215| STALE (no longer applicable) | {N} |216217## Priority Triage218219### HIGH Priority220221For each HIGH VALID finding, write a section:222223```224### {suggested_title}225226**PR:** #{number} | **File:** {path}:{line} | **Severity:** {severity} | **Dimension:** {dimension}227228> {reviewer_quote}229230**Current relevance:** VALID — {impact}231232**Suggested issue title:** {suggested_title}233**Affected files:** {path}234```235236### MEDIUM Priority237{Same structure}238239### LOW Priority240{Same structure}241242## RESOLVED Findings243244{List: PR, file, one-line description of what was fixed}245246## STALE Findings247248{List: PR, file, one-line description of why no longer applicable}249250## Open PR Findings251252{Findings from PRs that were open (not merged) at scan time — may still be addressed.253Same per-finding structure but labeled as pending.}254255## Pattern Analysis256257**Most common deferral phrases (by frequency):**258{Table: phrase | count | % of all candidates}259260**Dimensions with highest VALID rate:**261{Table: dimension | valid | resolved | stale | valid_rate}262263**Systemic escape hatches detected:**264{Narrative: which phrases act as systematic blockers to tracking, with counts}265266**Recommendations:**267{2–4 concrete process recommendations based on the pattern data}268269---2706. After writing the file, print a terminal summary:271 ```272 audit-review-decisions complete273 Output: {OUTPUT_PATH}274 VALID: {N} | RESOLVED: {N} | STALE: {N}275 Top finding: {first HIGH priority suggested_title, or "none"}276 ```277278---279280### Step 5: Watermark (Thread Annotation)281282For every finding processed in Steps 2–3 (all classifications — VALID, RESOLVED, STALE):2832841. **Re-check for existing audit marker (live)**: fetch the thread's current comments285 directly from the GitHub API — do not use the Step 1 JSON cache, which already286 filtered out `[AUDIT]`-marked threads and cannot detect markers posted after Step 1:287 ```bash288 gh api "repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/comments" \289 --jq "[.[] | select(.id == ${COMMENT_ID} or .in_reply_to_id == ${COMMENT_ID}) | .body | startswith(\"[AUDIT]\")] | any"290 ```291 If the result is `true`: skip this thread (idempotent — no duplicate post).2922932. **Determine marker body** based on classification and ticket status:294295 | Classification | Ticket created? | Marker body |296 |---|---|---|297 | VALID | Yes | `[AUDIT] — tracked in #{issue_number}` |298 | VALID | No | `[AUDIT] — acknowledged, no action taken` |299 | RESOLVED | — | `[AUDIT] — verified resolved in current codebase` |300 | STALE | — | `[AUDIT] — no longer applicable` |3013023. **Post reply comment**:303 ```bash304 gh api "repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/comments/${COMMENT_ID}/replies" \305 --method POST \306 --field body="${MARKER_BODY}"307 sleep 1308 ```309 `COMMENT_ID` is the `databaseId` of the first comment in the thread (from Step 1 JSON).3103114. **Thread reply constraint**: These calls cannot be batched via the reviews API — each312 requires an individual POST. The 1s delay between calls is mandatory per GitHub API313 discipline.3143155. Log progress per finding: `[AUDIT] Posted marker on PR #{number} thread {comment_id}: {marker_body}`