@clipboard-health/ai-rules 2.15.4 → 2.15.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/babysit-pr/SKILL.md +39 -35
- package/skills/fix-ci/SKILL.md +0 -85
- package/skills/iterate-pr/SKILL.md +0 -135
- package/skills/learn-from-session/SKILL.md +0 -123
- package/skills/unresolved-pr-comments/SKILL.md +0 -47
- package/skills/unresolved-pr-comments/scripts/parseNitpicks.sh +0 -165
- package/skills/unresolved-pr-comments/scripts/unresolvedPrComments.sh +0 -246
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: babysit-pr
|
|
3
|
-
description: "Watch a PR through CI and review feedback: commit/push, wait for CI, auto-fix high-confidence failures, reply to active review threads, and summarize parsed CodeRabbit review-body comments with sentinel-tagged comments. Runs
|
|
4
|
-
argument-hint: "[
|
|
3
|
+
description: "Watch a PR through CI and review feedback: commit/push, wait for CI, auto-fix high-confidence failures, reply to active review threads, and summarize parsed CodeRabbit review-body comments with sentinel-tagged comments. Runs one pass against the current branch's PR; pass a PR number or URL to `gh pr checkout` that PR first. Use when the user says 'babysit my PR', 'babysit PR 482', 'watch my PR', 'keep my PR moving', or 'respond to comments'."
|
|
4
|
+
argument-hint: "[pr-number-or-url]"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Babysit PR
|
|
@@ -12,22 +12,16 @@ This skill is self-contained: it does not invoke other skills. It works in Claud
|
|
|
12
12
|
|
|
13
13
|
## Inputs
|
|
14
14
|
|
|
15
|
-
Parse an optional
|
|
15
|
+
Parse an optional PR number or URL from the invocation arguments if the host exposes them; otherwise read the user's request text. Parse in **this priority order** and stop at the first match — do not fall back to a generic "first integer in prose" regex, which would grab unrelated numbers (issue refs, quoted counts, etc.):
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
1. **Full PR URL** — if `$ARGUMENTS` or the user's text contains `https?://github\.com/[^/\s]+/[^/\s]+/pull/\d+`, capture the URL and pass it to `gh pr checkout` as-is.
|
|
18
|
+
2. **Explicit PR token** — match `(?:PR|pr|pull request)\s*#?(\d+)` or a bare `#(\d+)` in the user's text. Capture the numeric group.
|
|
19
|
+
3. **Bare numeric argument** — only when the entire `$ARGUMENTS` string is a positive integer (no surrounding prose).
|
|
20
|
+
4. **None of the above** — operate on the PR for the current branch (existing Step 2 behavior).
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
\b(\d+)\s*(s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours)\b
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Rules:
|
|
22
|
+
When a match is found, the checkout happens in Preflight before Step 2.
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
- Accept a bare number as seconds **only** when the bare number is the entire argument (not embedded in prose).
|
|
27
|
-
- Normalize to seconds: `s*=1`, `m*=60`, `h*=3600`.
|
|
28
|
-
- Empty → one iteration, then exit with a summary.
|
|
29
|
-
- Normalized `<= 240` → best-effort same-turn loop: `sleep <seconds>` between iterations.
|
|
30
|
-
- Normalized `> 240` → run one pass, then report that longer cadences need an external loop wrapper (the Claude Code `/loop` skill or a shell `while` loop outside the agent). Do not sleep inside the agent turn — blocking `sleep` past ~5 minutes will exceed prompt-cache TTLs and may hit tool-call timeouts.
|
|
24
|
+
This skill always runs exactly one pass. It never waits or repeats internally. For recurring execution, wrap the call with `/loop <cadence> /babysit-pr` or an external shell `while` loop.
|
|
31
25
|
|
|
32
26
|
## Sentinels
|
|
33
27
|
|
|
@@ -72,12 +66,22 @@ gh auth status
|
|
|
72
66
|
|
|
73
67
|
If this fails, stop and tell the user to run `gh auth login`.
|
|
74
68
|
|
|
69
|
+
If a PR number or URL was parsed from Inputs, check out that PR now:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
gh pr checkout <pr-number-or-url>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This switches the local worktree to the PR's head branch (and handles forks automatically). If the command fails (PR not found, conflicting local branch, etc.), stop and report. The `git status --short` check above runs first so we never check out over dirty work.
|
|
76
|
+
|
|
75
77
|
### 2. Locate the PR
|
|
76
78
|
|
|
77
79
|
```bash
|
|
78
80
|
gh pr view --json number,url,headRefName,statusCheckRollup 2>/dev/null
|
|
79
81
|
```
|
|
80
82
|
|
|
83
|
+
If Preflight checked out a PR explicitly, `gh pr view` will find it by construction and the fallback below does not apply. The fallback only fires when no PR number was supplied and the current branch has no open PR.
|
|
84
|
+
|
|
81
85
|
If no PR exists for the current branch:
|
|
82
86
|
|
|
83
87
|
- Verify there are commits ahead of the base branch: `git log --oneline @{u}..HEAD 2>/dev/null || git log --oneline origin/HEAD..HEAD`. If nothing is ahead, stop and report "no commits to push".
|
|
@@ -278,7 +282,7 @@ Report:
|
|
|
278
282
|
- Review threads replied to, grouped by verdict (including any Defer count: "X threads deferred as follow-ups").
|
|
279
283
|
- Nitpicks summarized (or skipped because already covered), including the Deferred count: "Y nitpicks deferred as follow-ups".
|
|
280
284
|
- Threads left active because of bot-acknowledgement uncertainty (flag by thread URL).
|
|
281
|
-
- The stop condition triggered for this
|
|
285
|
+
- The stop condition triggered for this pass (clean / progressing / stuck).
|
|
282
286
|
|
|
283
287
|
When the report mentions any deferrals, include a one-liner the user can run later to enumerate them, e.g.:
|
|
284
288
|
|
|
@@ -290,20 +294,18 @@ Do not rely only on `gh pr view --json comments,reviews` — that view can miss
|
|
|
290
294
|
|
|
291
295
|
## Loop control
|
|
292
296
|
|
|
293
|
-
After
|
|
294
|
-
|
|
295
|
-
- **Exit clean** — all CI checks passed AND every thread in `activeThreads` was either marked Skip-reply during step 6's inspection or has already received a fresh sentinel reply in this iteration (Agree / Disagree / Already-fixed / **Defer** all count — a Defer reply is a sentinel reply), AND every current nitpick fingerprint is covered by an existing sentinel comment (deferred nitpicks count; they're in the summary's fingerprint block). Do not use raw `totalActiveThreads` from the script output — it is pre-inspection and will stay non-zero for Skip-reply cases. A PR with Deferred threads is still clean from babysit's perspective: the skill has done what it can without widening scope. Report success and stop.
|
|
296
|
-
- **Exit stuck** — iteration made no commits, posted no new replies, and no CI check changed state from the previous iteration. Report state and stop; tell the user to investigate.
|
|
297
|
-
- **Continue** — interval set, normalized `<= 240`, not clean, not stuck:
|
|
297
|
+
After the single pass completes, pick exactly one outcome:
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
299
|
+
- **Exit clean** — all CI checks passed AND every thread in `activeThreads` was either marked Skip-reply during step 6's inspection or has already received a fresh sentinel reply in this pass (Agree / Disagree / Already-fixed / **Defer** all count — a Defer reply is a sentinel reply), AND every current nitpick fingerprint is covered by an existing sentinel comment (deferred nitpicks count; they're in the summary's fingerprint block). Do not use raw `totalActiveThreads` from the script output — it is pre-inspection and will stay non-zero for Skip-reply cases. A PR with Deferred threads is still clean from babysit's perspective: the skill has done what it can without widening scope. Report success and stop.
|
|
300
|
+
- **Exit progressing** — pass made commits, posted new replies, or both, and the PR is not yet clean (CI is still pending, a new CI run was triggered by this pass's commits, or more work remains). There is real work still in flight that another run would pick up. Report what was done and what is pending, and tell the user to re-run `/babysit-pr` once CI settles, or to wrap the call with `/loop <cadence> /babysit-pr` (or a shell `while true; do ...; done`) for automatic re-runs.
|
|
301
|
+
- **Exit stuck** — pass made no commits and posted no new replies, and the PR is still not clean. Nothing actionable happened this pass. Use this whenever progress is blocked on something outside the skill's scope, including:
|
|
302
|
+
- CI still running (`gh pr checks --watch` timed out with pending checks).
|
|
303
|
+
- CI failing with a diagnosis-only verdict from Step 5 (flaky / infra / auth / external check / ambiguous / out-of-scope failure).
|
|
304
|
+
- Only Skip-reply threads remained AND CI was already red or pending.
|
|
302
305
|
|
|
303
|
-
|
|
306
|
+
Report the specific blocker (pending vs. diagnosed-failure, with the diagnosis text) and tell the user to investigate or re-run once state may have changed.
|
|
304
307
|
|
|
305
|
-
|
|
306
|
-
- **Sanity cap** — hard-stop at 20 iterations regardless, with a clear "sanity cap hit" message. The cap only applies to internal looping.
|
|
308
|
+
This skill never waits or repeats internally.
|
|
307
309
|
|
|
308
310
|
## Portability notes
|
|
309
311
|
|
|
@@ -319,20 +321,22 @@ After an iteration, pick exactly one outcome:
|
|
|
319
321
|
|
|
320
322
|
User: `babysit my PR`
|
|
321
323
|
|
|
322
|
-
- No
|
|
324
|
+
- No PR arg → operate on the current branch.
|
|
323
325
|
- Preflight OK, PR #482 found.
|
|
324
326
|
- `gh pr checks --watch` times out at 600s — two checks still pending.
|
|
325
327
|
- `unresolvedPrComments.sh` returns 0 active threads, 0 nitpicks.
|
|
326
328
|
- No commits, no replies posted, CI state unchanged vs. start.
|
|
327
|
-
- Outcome: **stuck**. Report: "CI still running after 10 min; no comments to address. Re-run `/babysit-pr
|
|
329
|
+
- Outcome: **stuck**. Report: "CI still running after 10 min; no comments to address. Re-run `/babysit-pr` once CI settles, or wrap with `/loop 2m /babysit-pr`."
|
|
328
330
|
|
|
329
|
-
### Example 2:
|
|
331
|
+
### Example 2: explicit PR number checks out and babysits that PR
|
|
330
332
|
|
|
331
|
-
User: `babysit
|
|
333
|
+
User: `babysit PR 482`
|
|
332
334
|
|
|
333
|
-
-
|
|
334
|
-
-
|
|
335
|
-
-
|
|
335
|
+
- Preflight OK. Input parser matches the explicit-token rule and captures `482`.
|
|
336
|
+
- `gh pr checkout 482` switches the worktree to PR #482's head branch (say, `feat/xyz`).
|
|
337
|
+
- Step 2's `gh pr view` confirms PR #482 on the now-current branch; the new-PR fallback does not fire.
|
|
338
|
+
- Remainder proceeds as a normal single pass (CI watch, thread / nitpick assessment, replies).
|
|
339
|
+
- Report final state on exit.
|
|
336
340
|
|
|
337
341
|
### Example 3: out-of-scope nitpick gets deferred
|
|
338
342
|
|
|
@@ -353,4 +357,4 @@ User: `babysit my PR`
|
|
|
353
357
|
|
|
354
358
|
## Input
|
|
355
359
|
|
|
356
|
-
|
|
360
|
+
PR number or URL: $ARGUMENTS
|
package/skills/fix-ci/SKILL.md
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: fix-ci
|
|
3
|
-
description: "Analyze and fix CI failures for a GitHub pull request. Use this when CI checks are failing, the build is red, or you need to diagnose GitHub Actions failures. Triggers on: 'fix CI', 'CI is failing', 'checks failed', 'build is broken', 'tests failing in CI', 'why is CI red', or any request to investigate and resolve PR check failures."
|
|
4
|
-
argument-hint: "[pr-url]"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Fix CI Failures
|
|
8
|
-
|
|
9
|
-
Diagnose and fix CI failures for a GitHub pull request by retrieving logs, identifying root causes, and applying targeted fixes.
|
|
10
|
-
|
|
11
|
-
## Arguments
|
|
12
|
-
|
|
13
|
-
- `$ARGUMENTS` - GitHub PR URL (e.g., `https://github.com/owner/repo/pull/123`). If omitted, uses the PR for the current branch.
|
|
14
|
-
|
|
15
|
-
## Instructions
|
|
16
|
-
|
|
17
|
-
### Step 1: Get PR and Identify Failed Checks
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
# If $ARGUMENTS contains a PR URL, extract the PR number from the path.
|
|
21
|
-
# Otherwise, get the PR for the current branch:
|
|
22
|
-
gh pr view --json number,url,headRefName,statusCheckRollup
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
From `statusCheckRollup`, find checks where `conclusion` is `"failure"` or `state` is `"FAILURE"`. If no checks have failed, report that CI is green and stop.
|
|
26
|
-
|
|
27
|
-
When multiple checks failed, prioritize **build/compile checks over test checks** — build errors are often the root cause of downstream test failures, so fixing them first may resolve multiple failures at once.
|
|
28
|
-
|
|
29
|
-
### Step 2: Retrieve Failed Job Logs
|
|
30
|
-
|
|
31
|
-
Extract the run ID from the failed check's `detailsUrl` (the numeric suffix of the GitHub Actions URL, e.g., `.../actions/runs/123456789`).
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
# List failed job IDs for the run
|
|
35
|
-
gh run view $RUN_ID --json jobs --jq '.jobs[] | select(.conclusion == "failure") | .databaseId'
|
|
36
|
-
|
|
37
|
-
# Get only failed step output (concise)
|
|
38
|
-
gh run view --job $JOB_ID --log-failed
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Start with `--log-failed` because it shows only the output from failed steps, cutting through potentially thousands of lines of passing output. Only fall back to `--log` with targeted grep if `--log-failed` doesn't provide enough context.
|
|
42
|
-
|
|
43
|
-
When parsing large log output, focus on:
|
|
44
|
-
|
|
45
|
-
- **The first error message** — later failures often cascade from this
|
|
46
|
-
- **Summary lines** like "Test Suites:", "FAILED", build error counts
|
|
47
|
-
- **Stack traces** immediately after assertion or compilation errors
|
|
48
|
-
|
|
49
|
-
### Step 3: Diagnose Root Cause
|
|
50
|
-
|
|
51
|
-
Read the relevant source files and tests to understand the failure in context.
|
|
52
|
-
|
|
53
|
-
**Build/compilation errors** — type errors, missing imports, syntax issues. These block everything else.
|
|
54
|
-
**Test failures** — read both the failing test and the code it exercises. The fix could be in either place; don't assume the test is wrong just because it's failing.
|
|
55
|
-
**Lint/format errors** — usually auto-fixable with project formatting tools.
|
|
56
|
-
**Infrastructure issues** — missing env vars, CI config problems, dependency resolution failures.
|
|
57
|
-
|
|
58
|
-
### Step 4: Present Findings and Proposed Fix
|
|
59
|
-
|
|
60
|
-
Before changing code, present:
|
|
61
|
-
|
|
62
|
-
1. **Root cause** — what's failing and why
|
|
63
|
-
2. **Proposed fix** — which files to change and what the changes are
|
|
64
|
-
|
|
65
|
-
Then ask the user whether to proceed. This confirmation step matters because CI failures can be ambiguous — what looks like a test bug might actually be the test correctly catching a real regression.
|
|
66
|
-
|
|
67
|
-
### Step 5: Apply Fix and Verify
|
|
68
|
-
|
|
69
|
-
After approval, implement the changes. Then run the relevant checks locally (build, test, lint for the affected project/files) to verify the fix before pushing. Report the local results.
|
|
70
|
-
|
|
71
|
-
### Failure Patterns
|
|
72
|
-
|
|
73
|
-
**Removed code but tests still reference it:** Delete or update the tests. Check for leftover references in shared test fixtures or configurations.
|
|
74
|
-
|
|
75
|
-
**Missing mocks/providers after adding a dependency:** Add the mock to test module setup. Update existing mocks when signatures change.
|
|
76
|
-
|
|
77
|
-
**Type errors from interface changes:** Fix in the changed files. If the type change is intentional, update downstream consumers too.
|
|
78
|
-
|
|
79
|
-
**Snapshot mismatches:** Check if the change is expected (you modified rendering/output). If so, update snapshots. If not, investigate the regression.
|
|
80
|
-
|
|
81
|
-
**Flaky/intermittent failures:** If the test passes locally, the failure looks timing-dependent, or it's unrelated to the PR's changes, flag this to the user rather than making speculative changes.
|
|
82
|
-
|
|
83
|
-
## Input
|
|
84
|
-
|
|
85
|
-
Pull Request URL: $ARGUMENTS
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: iterate-pr
|
|
3
|
-
description: "Iteratively commit, push, and address PR feedback until CI passes and comments are resolved. Use this skill when the user wants to get a PR to a mergeable state, fix CI and address review comments in a loop, or says things like 'iterate on my PR', 'make this PR pass', 'fix everything on the PR', 'get this PR ready to merge', or 'keep going until CI is green'."
|
|
4
|
-
argument-hint: "[max-iterations]"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Iterate PR
|
|
8
|
-
|
|
9
|
-
Autonomously iterate on a pull request until CI checks pass and all comments are resolved. Each iteration runs in a fresh subagent.
|
|
10
|
-
|
|
11
|
-
## Arguments
|
|
12
|
-
|
|
13
|
-
- `$ARGUMENTS` - Maximum iterations (default: 3)
|
|
14
|
-
|
|
15
|
-
## Instructions
|
|
16
|
-
|
|
17
|
-
### Step 1: Initialize
|
|
18
|
-
|
|
19
|
-
Parse max iterations from `$ARGUMENTS` (default: 3). Set iteration counter to 0.
|
|
20
|
-
|
|
21
|
-
### Step 2: Check State
|
|
22
|
-
|
|
23
|
-
Get the PR for the current branch:
|
|
24
|
-
|
|
25
|
-
!`gh pr view --json number,url,headRefName,statusCheckRollup 2>/dev/null || echo "NO_PR"`
|
|
26
|
-
|
|
27
|
-
**If no PR exists:** Proceed to Step 3 (first iteration will create one).
|
|
28
|
-
|
|
29
|
-
**If PR exists:** Get unresolved comments data:
|
|
30
|
-
|
|
31
|
-
!`bash "${CLAUDE_PLUGIN_ROOT:-.agents}/skills/unresolved-pr-comments/scripts/unresolvedPrComments.sh" 2>/dev/null`
|
|
32
|
-
|
|
33
|
-
Parse the JSON output and evaluate exit conditions.
|
|
34
|
-
|
|
35
|
-
**If any CI checks are still PENDING, QUEUED, or IN_PROGRESS:** Proceed to Step 3 regardless of comment count. Automated reviewers (e.g. CodeRabbit) may not have posted comments yet, so comment data is unreliable until all checks complete.
|
|
36
|
-
|
|
37
|
-
**If statusCheckRollup is empty or null:** Treat CI as passing (some PRs have no required checks configured).
|
|
38
|
-
|
|
39
|
-
**Exit with success** when ALL conditions are met:
|
|
40
|
-
|
|
41
|
-
- All CI checks have completed (no PENDING, QUEUED, or IN_PROGRESS statuses in statusCheckRollup) and none have failed (or no checks exist)
|
|
42
|
-
- No unresolved comments (totalUnresolvedComments == 0)
|
|
43
|
-
- No nitpicks (totalNitpicks == 0)
|
|
44
|
-
|
|
45
|
-
**JSON Structure for Exit Evaluation:**
|
|
46
|
-
|
|
47
|
-
```json
|
|
48
|
-
{
|
|
49
|
-
"totalUnresolvedComments": number,
|
|
50
|
-
"totalNitpicks": number,
|
|
51
|
-
"unresolvedComments": [...],
|
|
52
|
-
"nitpickComments": [...]
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Check `totalUnresolvedComments == 0` and `totalNitpicks == 0` for exit condition.
|
|
57
|
-
|
|
58
|
-
Report: "PR is clean! All CI checks pass and no unresolved comments."
|
|
59
|
-
|
|
60
|
-
**Exit with status report** when iteration counter >= max iterations:
|
|
61
|
-
|
|
62
|
-
- List failing CI checks
|
|
63
|
-
- List unresolved comment/nitpick counts
|
|
64
|
-
- Suggest running `/iterate-pr` again to continue
|
|
65
|
-
|
|
66
|
-
### Step 3: Spawn Iteration Subagent
|
|
67
|
-
|
|
68
|
-
Spawn a Task subagent with `subagent_type: "general-purpose"` using this prompt:
|
|
69
|
-
|
|
70
|
-
> Handle one iteration of the PR feedback loop:
|
|
71
|
-
>
|
|
72
|
-
> 1. **Record Starting Commit**: !`git rev-parse HEAD` (save as `startCommit`)
|
|
73
|
-
> 2. **Commit and Push**: Invoke `core:commit-push-pr` via the Skill tool
|
|
74
|
-
> 3. **Get PR Number**: !`gh pr view --json number --jq '.number'`
|
|
75
|
-
> 4. **Wait for CI**: !`rc=0; if command -v gtimeout >/dev/null 2>&1; then gtimeout 600 gh pr checks --watch || rc=$?; elif command -v timeout >/dev/null 2>&1; then timeout 600 gh pr checks --watch || rc=$?; else gh pr checks --watch || rc=$?; fi; case $rc in 0|1|8|124) ;; *) exit $rc;; esac` (10 minute timeout; exit codes: 0=pass, 1=fail, 8=pending, 124=timeout are expected and handled in next step; other codes like 4=auth error are re-raised; uses gtimeout on macOS, timeout on Linux, no timeout as fallback)
|
|
76
|
-
> 5. **Check CI Status**: Run `gh pr checks --json name,state,bucket` and parse the output
|
|
77
|
-
> - If any check has `bucket: "fail"`, invoke `core:fix-ci` via the Skill tool. Since you are running autonomously, do NOT wait for user approval — apply the fixes directly. Report what was fixed and exit.
|
|
78
|
-
> 6. **Check Comments**: Run `bash "${CLAUDE_PLUGIN_ROOT:-.agents}/skills/unresolved-pr-comments/scripts/unresolvedPrComments.sh"` and parse the JSON output
|
|
79
|
-
> - If unresolved comments or nitpicks exist:
|
|
80
|
-
> 1. Group comments by file path and read each file once (not per-comment)
|
|
81
|
-
> 2. If a file no longer exists, note the comment may be outdated and skip it
|
|
82
|
-
> 3. Assess each comment with an explicit verdict:
|
|
83
|
-
> - **Agree**: Explain why, then fix it
|
|
84
|
-
> - **Disagree**: Explain why the current code is acceptable; do NOT change the code
|
|
85
|
-
> - **Already fixed**: Note that the code already addresses this concern
|
|
86
|
-
> 4. After assessing all comments, fix only those you agreed with and exit (next iteration will commit fixes)
|
|
87
|
-
> 7. **Report**: Run `git rev-parse HEAD` and compare to `startCommit` to determine if commits were made. Include PR status, CI status, and comments addressed
|
|
88
|
-
|
|
89
|
-
### Step 4: Loop
|
|
90
|
-
|
|
91
|
-
After the subagent completes:
|
|
92
|
-
|
|
93
|
-
1. Increment iteration counter
|
|
94
|
-
2. If no commits were made this iteration:
|
|
95
|
-
- Get unresolved comments by running: `bash "${CLAUDE_PLUGIN_ROOT:-.agents}/skills/unresolved-pr-comments/scripts/unresolvedPrComments.sh"`
|
|
96
|
-
- If unresolved comments remain, exit with: "Comments addressed, awaiting reviewer resolution. Run `/iterate-pr` after reviewer responds."
|
|
97
|
-
3. Report: "Iteration [N]/[max] complete. Checking state..."
|
|
98
|
-
4. Return to Step 2
|
|
99
|
-
|
|
100
|
-
## Examples
|
|
101
|
-
|
|
102
|
-
**Successful completion:**
|
|
103
|
-
|
|
104
|
-
```text
|
|
105
|
-
Iteration 1/3: No PR exists
|
|
106
|
-
> commit-push-pr -> PR #347 created
|
|
107
|
-
> CI failed (lint errors) -> fix-ci -> fixed missing semicolons
|
|
108
|
-
|
|
109
|
-
Iteration 2/3: CI failures remain
|
|
110
|
-
> commit-push-pr -> pushed fixes
|
|
111
|
-
> CI passed, 2 review comments
|
|
112
|
-
> unresolved-pr-comments -> addressed null check, const usage
|
|
113
|
-
|
|
114
|
-
Iteration 3/3: Comments pending
|
|
115
|
-
> commit-push-pr -> pushed fixes
|
|
116
|
-
> CI passed, no unresolved comments
|
|
117
|
-
|
|
118
|
-
PR is clean! All CI checks pass and no unresolved comments.
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
**Awaiting reviewer resolution:**
|
|
122
|
-
|
|
123
|
-
```text
|
|
124
|
-
Iteration 1/3: PR exists with comments
|
|
125
|
-
> commit-push-pr -> pushed comment fixes
|
|
126
|
-
> CI passed, 1 comment remains (disagreed with suggestion)
|
|
127
|
-
|
|
128
|
-
Iteration 2/3: No commits made, comments remain
|
|
129
|
-
|
|
130
|
-
Comments addressed, awaiting reviewer resolution. Run `/iterate-pr` after reviewer responds.
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## Input
|
|
134
|
-
|
|
135
|
-
Maximum iterations: $ARGUMENTS
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: learn-from-session
|
|
3
|
-
description: |
|
|
4
|
-
Analyze the current session for agent efficiency, quality, and actionable improvements. Use this skill when the user says things like "learn from this session", "session retro", "what went well", "how did the agent do", "what should I improve", "review this session", "session review", or any request to reflect on how the current session went. Also use when the user wants to extract learnings, identify friction, or improve their workflow based on what just happened.
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
You are reviewing the current Claude Code session. Your job is to surface the 2-3 most impactful findings — things that would actually change how the next session goes — not to produce an exhaustive report card.
|
|
8
|
-
|
|
9
|
-
Be candid. A session where everything scores 4/5 but you have nothing concrete to suggest is a wasted review. Prioritize specificity over coverage: one sharp observation beats five generic ones.
|
|
10
|
-
|
|
11
|
-
Analyze the session transcript and produce the following:
|
|
12
|
-
|
|
13
|
-
## Agent efficiency analysis
|
|
14
|
-
|
|
15
|
-
Evaluate how well the agent used tools and how much human course-correction was needed.
|
|
16
|
-
|
|
17
|
-
Score each dimension 1-5. Calibration: 1=actively harmful or completely wrong approach, 2=significant waste or frequent missteps, 3=adequate but with clear room for improvement, 4=good with minor issues, 5=genuinely impressive and hard to improve on. Reserve 5 for sessions that would make you say "I wish the agent always worked like this."
|
|
18
|
-
|
|
19
|
-
- **Tool precision**: Did the agent use the right tools for each task, or did it flail between tools, run unnecessary reads, or use grep when it should have used targeted file reads?
|
|
20
|
-
- **Iteration efficiency**: How many attempts did it take to get things right? Count tool retries, failed bash commands, and edit-then-re-edit cycles.
|
|
21
|
-
- **Context utilization**: Did the agent leverage CLAUDE.md, AGENTS.md, and project conventions, or did it ignore available context and make assumptions?
|
|
22
|
-
- **Autonomy level**: How often did the agent work without human intervention? Each rejection/abort/course-correction is a friction event.
|
|
23
|
-
- **Autonomy span**: What was the longest streak of productive tool calls without human intervention?
|
|
24
|
-
|
|
25
|
-
Provide a brief narrative (2-3 sentences) explaining the scores with specific examples from the session.
|
|
26
|
-
|
|
27
|
-
## Agent quality analysis
|
|
28
|
-
|
|
29
|
-
Evaluate how well the agent followed project coding standards and CLAUDE.md rules.
|
|
30
|
-
|
|
31
|
-
Use the same 1-5 calibration as above.
|
|
32
|
-
|
|
33
|
-
- **CLAUDE.md compliance**: Did the agent follow the rules and conventions defined in CLAUDE.md and AGENTS.md? Flag specific violations.
|
|
34
|
-
- **Code pattern adherence**: Did the generated code follow established project patterns (naming conventions, error handling, file organization, test structure)?
|
|
35
|
-
- **Test coverage intent**: Did the agent write or update tests when modifying behavior? Did it run tests before declaring done?
|
|
36
|
-
- **PR hygiene**: Is the commit history clean? Are changes scoped appropriately? Did the agent avoid touching unrelated files?
|
|
37
|
-
- **Documentation awareness**: Did the agent update relevant docs, comments, or type definitions when changing interfaces?
|
|
38
|
-
|
|
39
|
-
Provide a brief narrative (2-3 sentences) explaining the scores with specific examples.
|
|
40
|
-
|
|
41
|
-
## Session reflection
|
|
42
|
-
|
|
43
|
-
Surface things that would save time if known at the start of the next session. The bar for inclusion: "would I tell a teammate about this before they start working on this codebase?" Skip categories with nothing worth noting.
|
|
44
|
-
|
|
45
|
-
- **Bash commands**: Commands that were used, discovered, or would have been useful to know upfront.
|
|
46
|
-
- **Code style patterns**: Patterns followed or discovered that aren't yet documented.
|
|
47
|
-
- **Testing approaches**: Testing strategies that worked or should have been used.
|
|
48
|
-
- **Environment/configuration quirks**: Gotchas, workarounds, or setup details encountered.
|
|
49
|
-
- **Warnings**: Errors, deprecations, or edge cases that tripped up the agent.
|
|
50
|
-
|
|
51
|
-
## Actionable improvements
|
|
52
|
-
|
|
53
|
-
This is the most important section. Produce recommendations in these categories, but only if genuinely actionable — a suggestion the user can apply in under 5 minutes. Skip any category with nothing worth doing.
|
|
54
|
-
|
|
55
|
-
### CLAUDE.md updates
|
|
56
|
-
|
|
57
|
-
**Do not suggest rules that duplicate existing automated enforcement.** If a lint rule, pre-commit hook, CI check, or other tooling already catches an issue, documenting it in CLAUDE.md/AGENTS.md is redundant. Before suggesting a rule, check whether the session transcript shows the error was already caught and blocked by automated tooling (e.g. a pre-commit hook rejected the commit, a linter flagged the issue). If so, skip it — the tooling is already doing its job.
|
|
58
|
-
|
|
59
|
-
For each suggestion, specify whether it belongs in:
|
|
60
|
-
|
|
61
|
-
- **Team-shared** (checked into git, e.g. `./CLAUDE.md` or `./AGENTS.md`)
|
|
62
|
-
- **Personal/local** (git-ignored, e.g. `~/.claude/CLAUDE.md`)
|
|
63
|
-
|
|
64
|
-
Format each as a ready-to-paste diff:
|
|
65
|
-
|
|
66
|
-
```diff
|
|
67
|
-
+ [the addition - keep it brief]
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Hooks, skills, or agents
|
|
71
|
-
|
|
72
|
-
Suggest new or revised hooks, skills, or agents that would have prevented issues or improved the workflow.
|
|
73
|
-
|
|
74
|
-
### Prompt technique
|
|
75
|
-
|
|
76
|
-
One concrete thing the engineer could do differently in their prompts to get better results (be specific, not generic).
|
|
77
|
-
|
|
78
|
-
### Agent pattern note
|
|
79
|
-
|
|
80
|
-
One observation about how the agent behaved that the team should know about — a strength to replicate or a weakness to work around.
|
|
81
|
-
|
|
82
|
-
## Structured data block
|
|
83
|
-
|
|
84
|
-
After the human-readable review, emit a fenced JSON block that a scraper can parse:
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"session_review": {
|
|
89
|
-
"version": "1.1",
|
|
90
|
-
"timestamp": "<ISO 8601>",
|
|
91
|
-
"efficiency": {
|
|
92
|
-
"tool_precision": <1-5>,
|
|
93
|
-
"iteration_efficiency": <1-5>,
|
|
94
|
-
"context_utilization": <1-5>,
|
|
95
|
-
"autonomy_level": <1-5>,
|
|
96
|
-
"autonomy_span": <1-5>,
|
|
97
|
-
"friction_events": <count of rejections/aborts/course-corrections>,
|
|
98
|
-
"total_tool_calls": <count>,
|
|
99
|
-
"failed_tool_calls": <count>
|
|
100
|
-
},
|
|
101
|
-
"quality": {
|
|
102
|
-
"claude_md_compliance": <1-5>,
|
|
103
|
-
"code_pattern_adherence": <1-5>,
|
|
104
|
-
"test_coverage_intent": <1-5>,
|
|
105
|
-
"pr_hygiene": <1-5>,
|
|
106
|
-
"documentation_awareness": <1-5>,
|
|
107
|
-
"violations": ["<brief description of each CLAUDE.md violation>"]
|
|
108
|
-
},
|
|
109
|
-
"improvements": {
|
|
110
|
-
"claude_md_rules": ["<each suggested rule, ready to paste>"],
|
|
111
|
-
"hooks_skills_agents": ["<each suggestion>"],
|
|
112
|
-
"prompt_technique": "<the suggestion>",
|
|
113
|
-
"agent_pattern_note": "<the observation>"
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
## Presenting the review
|
|
120
|
-
|
|
121
|
-
1. Display the human-readable sections.
|
|
122
|
-
2. If there is an open PR for the current branch, offer to post the review as a PR comment (using `gh pr comment` with `--body-file`).
|
|
123
|
-
3. If the user has CLAUDE.md updates to apply, offer to apply them directly.
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: unresolved-pr-comments
|
|
3
|
-
description: "Get unresolved review comments from a GitHub pull request. Use this skill when the user asks about PR feedback, review comments, unresolved threads, what reviewers said, CodeRabbit review-body comments, or wants to address PR review feedback. Also use when the user says 'check my PR', 'what's left on my PR', or 'resolve comments'."
|
|
4
|
-
argument-hint: "[pr-number]"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Unresolved PR Comments
|
|
8
|
-
|
|
9
|
-
Fetch and analyze unresolved review comments from a GitHub pull request.
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
Run the script to fetch PR comment data:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
bash scripts/unresolvedPrComments.sh [pr-number]
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
If no PR number is provided, it uses the PR associated with the current branch.
|
|
20
|
-
|
|
21
|
-
Note: Limited to 100 review threads, 10 comments per thread, and 100 reviews.
|
|
22
|
-
|
|
23
|
-
## Processing Instructions
|
|
24
|
-
|
|
25
|
-
Using the JSON output from the script:
|
|
26
|
-
|
|
27
|
-
1. **If error**: Display the error message and suggest the fix
|
|
28
|
-
2. **If no comments**: Report the PR has no pending feedback
|
|
29
|
-
3. **If comments exist**: Present a brief summary (e.g., "Found 3 unresolved comments and 5 CodeRabbit review-body comments")
|
|
30
|
-
|
|
31
|
-
Then, for EVERY comment (both `unresolvedComments` AND `nitpickComments`):
|
|
32
|
-
|
|
33
|
-
1. Group comments by file path and read each file once (not per-comment)
|
|
34
|
-
2. If a file no longer exists, note that the comment may be outdated
|
|
35
|
-
3. Assess each comment against the current code. When multiple comments appear at the same file and line, they are part of the same review thread — read them together as a conversation
|
|
36
|
-
4. Group your assessment as follows:
|
|
37
|
-
|
|
38
|
-
**Should address**
|
|
39
|
-
- Description of the comment with file path(s)
|
|
40
|
-
- Why it's a real issue (bug, a11y, UX, etc.)
|
|
41
|
-
|
|
42
|
-
**Can ignore**
|
|
43
|
-
- Description of the comment with file path(s)
|
|
44
|
-
- Why: already fixed, not actionable, not worth addressing in this PR, etc.
|
|
45
|
-
|
|
46
|
-
**Net**
|
|
47
|
-
Summary of how many are worth fixing and what kind of issues they are. Offer to fix, but **do NOT start until the user confirms**.
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# parseNitpicks.sh — Parse CodeRabbit review-body comments from PR review bodies.
|
|
3
|
-
# Sourced by unresolvedPrComments.sh. Requires: jq, perl.
|
|
4
|
-
|
|
5
|
-
# Extract CodeRabbit review-body comments from reviews JSON (passed via stdin).
|
|
6
|
-
# Outputs a JSON array of comment objects. The function name is retained for
|
|
7
|
-
# backward compatibility with existing skill scripts.
|
|
8
|
-
extract_nitpick_comments() {
|
|
9
|
-
local reviews_json="$1"
|
|
10
|
-
|
|
11
|
-
printf '%s' "$reviews_json" | perl -e '
|
|
12
|
-
use strict;
|
|
13
|
-
use warnings;
|
|
14
|
-
use JSON::PP;
|
|
15
|
-
|
|
16
|
-
local $/;
|
|
17
|
-
my $reviews_json = <STDIN>;
|
|
18
|
-
my $reviews = decode_json($reviews_json);
|
|
19
|
-
|
|
20
|
-
# Find latest coderabbitai review with supported review-body comment sections.
|
|
21
|
-
my $latest_review;
|
|
22
|
-
my $latest_time = "";
|
|
23
|
-
for my $review (@$reviews) {
|
|
24
|
-
my $author = $review->{author}{login} // "";
|
|
25
|
-
my $body = $review->{body} // "";
|
|
26
|
-
next unless $author eq "coderabbitai" && has_supported_sections($body);
|
|
27
|
-
my $created = $review->{createdAt} // "";
|
|
28
|
-
if ($created gt $latest_time) {
|
|
29
|
-
$latest_time = $created;
|
|
30
|
-
$latest_review = $review;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
unless ($latest_review) {
|
|
35
|
-
print "[]";
|
|
36
|
-
exit 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
my $body = $latest_review->{body};
|
|
40
|
-
my $author = $latest_review->{author}{login} // "deleted-user";
|
|
41
|
-
my $created_at = $latest_review->{createdAt} // "";
|
|
42
|
-
|
|
43
|
-
my @sections = extract_review_body_comment_sections($body);
|
|
44
|
-
unless (@sections) {
|
|
45
|
-
print "[]";
|
|
46
|
-
exit 0;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
my @comments;
|
|
50
|
-
for my $section (@sections) {
|
|
51
|
-
my $section_content = $section->{content};
|
|
52
|
-
my $category = $section->{category};
|
|
53
|
-
|
|
54
|
-
# Extract file sections: <details><summary>filename (count)</summary><blockquote>...</blockquote></details>
|
|
55
|
-
while ($section_content =~ /<details>\s*<summary>([^<]+?)\s+\(\d+\)<\/summary>\s*<blockquote>([\s\S]*?)<\/blockquote>\s*<\/details>/g) {
|
|
56
|
-
my $raw_file_name = trim($1);
|
|
57
|
-
my $file_content = $2;
|
|
58
|
-
|
|
59
|
-
# Extract individual comments: `line-range`: severity metadata, **title**, body
|
|
60
|
-
while ($file_content =~ /`(\d+(?:-\d+)?)`:\s*(?:_[^_]+_\s*\|\s*_[^_]+_\s*)?\*\*([^*]+)\*\*\s*([\s\S]*?)(?=---|\n`\d|<\/blockquote>|$)/g) {
|
|
61
|
-
my $line_range = $1;
|
|
62
|
-
my $title = trim($2);
|
|
63
|
-
my $clean_body = clean_comment_body(trim($3));
|
|
64
|
-
my $file_name = normalize_file_name($raw_file_name, $line_range);
|
|
65
|
-
push @comments, {
|
|
66
|
-
author => $author,
|
|
67
|
-
body => "$title\n\n$clean_body",
|
|
68
|
-
category => $category,
|
|
69
|
-
createdAt => $created_at,
|
|
70
|
-
file => $file_name,
|
|
71
|
-
line => $line_range,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
print encode_json(\@comments);
|
|
78
|
-
|
|
79
|
-
sub has_supported_sections {
|
|
80
|
-
my ($text) = @_;
|
|
81
|
-
$text = strip_markdown_blockquote_prefixes($text);
|
|
82
|
-
return $text =~ /<summary>\s*[^<]*(?:Nitpick comments|Minor comments|Outside diff range comments)\s*\(\d+\)<\/summary>\s*<blockquote>/i;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
sub extract_review_body_comment_sections {
|
|
86
|
-
my ($text) = @_;
|
|
87
|
-
$text = strip_markdown_blockquote_prefixes($text);
|
|
88
|
-
|
|
89
|
-
my @sections;
|
|
90
|
-
while ($text =~ /<summary>\s*[^<]*(Nitpick comments|Minor comments|Outside diff range comments)\s*\(\d+\)<\/summary>\s*<blockquote>/ig) {
|
|
91
|
-
my $category = section_category($1);
|
|
92
|
-
my $content_start = $+[0];
|
|
93
|
-
my $after = substr($text, $content_start);
|
|
94
|
-
|
|
95
|
-
my $depth = 1;
|
|
96
|
-
my @tags;
|
|
97
|
-
while ($after =~ /(<blockquote>|<\/blockquote>)/gi) {
|
|
98
|
-
my $tag = $1;
|
|
99
|
-
my $pos = $-[0];
|
|
100
|
-
my $is_open = ($tag =~ /^<blockquote>/i) ? 1 : 0;
|
|
101
|
-
push @tags, [$pos, $is_open];
|
|
102
|
-
}
|
|
103
|
-
for my $tag (@tags) {
|
|
104
|
-
$depth += $tag->[1] ? 1 : -1;
|
|
105
|
-
if ($depth == 0) {
|
|
106
|
-
push @sections, {
|
|
107
|
-
category => $category,
|
|
108
|
-
content => substr($after, 0, $tag->[0]),
|
|
109
|
-
};
|
|
110
|
-
last;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return @sections;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
sub section_category {
|
|
118
|
-
my ($label) = @_;
|
|
119
|
-
return "nitpick" if $label =~ /Nitpick comments/i;
|
|
120
|
-
return "minor" if $label =~ /Minor comments/i;
|
|
121
|
-
return "outside-diff" if $label =~ /Outside diff range comments/i;
|
|
122
|
-
return "unknown";
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
sub normalize_file_name {
|
|
126
|
-
my ($file_name, $line_range) = @_;
|
|
127
|
-
my $suffix = "-" . $line_range;
|
|
128
|
-
$file_name =~ s/\Q$suffix\E$//;
|
|
129
|
-
return $file_name;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
sub strip_markdown_blockquote_prefixes {
|
|
133
|
-
my ($text) = @_;
|
|
134
|
-
$text =~ s/^[ \t]*>[ \t]?//mg;
|
|
135
|
-
return $text;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
sub clean_comment_body {
|
|
139
|
-
my ($text) = @_;
|
|
140
|
-
# Iteratively remove innermost <details> elements
|
|
141
|
-
my $prev = "";
|
|
142
|
-
while ($text ne $prev) {
|
|
143
|
-
$prev = $text;
|
|
144
|
-
$text =~ s/<details>(?:(?!<details>)[\s\S])*?<\/details>//g;
|
|
145
|
-
}
|
|
146
|
-
$text =~ s/</</g;
|
|
147
|
-
$text =~ s/>/>/g;
|
|
148
|
-
return trim($text);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
sub trim {
|
|
152
|
-
my ($s) = @_;
|
|
153
|
-
$s =~ s/^\s+//;
|
|
154
|
-
$s =~ s/\s+$//;
|
|
155
|
-
return $s;
|
|
156
|
-
}
|
|
157
|
-
'
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
# Extract code scanning alert number from comment body.
|
|
161
|
-
# Outputs the alert number or empty string.
|
|
162
|
-
extract_code_scanning_alert_number() {
|
|
163
|
-
local body="$1"
|
|
164
|
-
printf '%s' "$body" | perl -ne 'print $1 if m{/code-scanning/(\d+)}'
|
|
165
|
-
}
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# unresolvedPrComments.sh — Fetch unresolved review comments from a GitHub PR.
|
|
3
|
-
# Usage: bash unresolvedPrComments.sh [pr-number]
|
|
4
|
-
# Outputs JSON with unresolved comments and CodeRabbit nitpicks.
|
|
5
|
-
# Compatible with macOS bash 3.2. Requires: gh, jq, perl.
|
|
6
|
-
|
|
7
|
-
set -euo pipefail
|
|
8
|
-
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
-
# shellcheck source=parseNitpicks.sh
|
|
11
|
-
source "${SCRIPT_DIR}/parseNitpicks.sh"
|
|
12
|
-
|
|
13
|
-
# Save original stdout so output_error works inside $() command substitutions
|
|
14
|
-
exec 3>&1
|
|
15
|
-
|
|
16
|
-
# --- Helpers (inlined from ghClient.ts / prClient.ts) ---
|
|
17
|
-
|
|
18
|
-
output_error() {
|
|
19
|
-
printf '%s' "$1" | jq -Rsc '{ error: . }' >&3
|
|
20
|
-
exit 1
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
validate_prerequisites() {
|
|
24
|
-
if ! command -v jq >/dev/null 2>&1; then
|
|
25
|
-
printf '{"error":"jq not found. Install from https://stedolan.github.io/jq"}\n' >&3
|
|
26
|
-
exit 1
|
|
27
|
-
fi
|
|
28
|
-
if ! command -v gh >/dev/null 2>&1; then
|
|
29
|
-
output_error "gh CLI not found. Install from https://cli.github.com"
|
|
30
|
-
fi
|
|
31
|
-
if ! command -v perl >/dev/null 2>&1; then
|
|
32
|
-
output_error "perl not found."
|
|
33
|
-
fi
|
|
34
|
-
if ! gh api user --jq '.login' >/dev/null 2>&1; then
|
|
35
|
-
output_error "Not authenticated with GitHub. Run: gh auth login"
|
|
36
|
-
fi
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get_pr_number() {
|
|
40
|
-
local arg="${1:-}"
|
|
41
|
-
if [ -n "$arg" ]; then
|
|
42
|
-
if ! printf '%s' "$arg" | grep -qE '^[0-9]+$'; then
|
|
43
|
-
output_error "Invalid PR number: ${arg}"
|
|
44
|
-
fi
|
|
45
|
-
printf '%s' "$arg"
|
|
46
|
-
return
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
local pr_json
|
|
50
|
-
if ! pr_json="$(gh pr view --json number 2>/dev/null)"; then
|
|
51
|
-
output_error "No PR found for current branch. Provide PR number as argument."
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
|
-
local pr_num
|
|
55
|
-
pr_num="$(printf '%s' "$pr_json" | jq -r '.number // empty')"
|
|
56
|
-
if [ -z "$pr_num" ]; then
|
|
57
|
-
output_error "No PR found for current branch. Provide PR number as argument."
|
|
58
|
-
fi
|
|
59
|
-
printf '%s' "$pr_num"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get_repo_info() {
|
|
63
|
-
local repo_json
|
|
64
|
-
if ! repo_json="$(gh repo view --json owner,name 2>/dev/null)"; then
|
|
65
|
-
output_error "Could not determine repository. Are you in a git repo with a GitHub remote?"
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
REPO_OWNER="$(printf '%s' "$repo_json" | jq -r '.owner.login // empty')"
|
|
69
|
-
REPO_NAME="$(printf '%s' "$repo_json" | jq -r '.name // empty')"
|
|
70
|
-
|
|
71
|
-
if [ -z "$REPO_OWNER" ] || [ -z "$REPO_NAME" ]; then
|
|
72
|
-
output_error "Failed to parse repository info from gh CLI output."
|
|
73
|
-
fi
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
# --- GraphQL ---
|
|
77
|
-
|
|
78
|
-
# Pagination limits: 100 review threads, 10 comments per thread, 100 reviews.
|
|
79
|
-
# Sufficient for typical PRs; data may be truncated on exceptionally active PRs.
|
|
80
|
-
GRAPHQL_QUERY='
|
|
81
|
-
query($owner: String!, $repo: String!, $pr: Int!) {
|
|
82
|
-
repository(owner: $owner, name: $repo) {
|
|
83
|
-
pullRequest(number: $pr) {
|
|
84
|
-
title
|
|
85
|
-
url
|
|
86
|
-
reviewThreads(first: 100) {
|
|
87
|
-
nodes {
|
|
88
|
-
isResolved
|
|
89
|
-
comments(first: 10) {
|
|
90
|
-
nodes {
|
|
91
|
-
body
|
|
92
|
-
path
|
|
93
|
-
line
|
|
94
|
-
originalLine
|
|
95
|
-
author { login }
|
|
96
|
-
createdAt
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
reviews(first: 100) {
|
|
102
|
-
nodes {
|
|
103
|
-
body
|
|
104
|
-
author { login }
|
|
105
|
-
createdAt
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}'
|
|
111
|
-
|
|
112
|
-
execute_graphql_query() {
|
|
113
|
-
local owner="$1" repo="$2" pr_number="$3"
|
|
114
|
-
local result
|
|
115
|
-
if ! result="$(gh api graphql \
|
|
116
|
-
-f "query=${GRAPHQL_QUERY}" \
|
|
117
|
-
-f "owner=${owner}" \
|
|
118
|
-
-f "repo=${repo}" \
|
|
119
|
-
-F "pr=${pr_number}" 2>&1)"; then
|
|
120
|
-
output_error "GraphQL query failed: ${result}"
|
|
121
|
-
fi
|
|
122
|
-
printf '%s' "$result"
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
# --- Code scanning filter ---
|
|
126
|
-
|
|
127
|
-
is_code_scanning_alert_fixed() {
|
|
128
|
-
local owner="$1" repo="$2" alert_number="$3"
|
|
129
|
-
local result
|
|
130
|
-
if ! result="$(gh api "repos/${owner}/${repo}/code-scanning/alerts/${alert_number}" 2>/dev/null)"; then
|
|
131
|
-
return 1
|
|
132
|
-
fi
|
|
133
|
-
|
|
134
|
-
local state
|
|
135
|
-
state="$(printf '%s' "$result" | jq -r '.most_recent_instance.state // empty')"
|
|
136
|
-
[ "$state" = "fixed" ]
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
# --- Main ---
|
|
140
|
-
|
|
141
|
-
main() {
|
|
142
|
-
validate_prerequisites
|
|
143
|
-
|
|
144
|
-
local pr_number
|
|
145
|
-
pr_number="$(get_pr_number "${1:-}")"
|
|
146
|
-
|
|
147
|
-
get_repo_info
|
|
148
|
-
local owner="$REPO_OWNER"
|
|
149
|
-
local repo="$REPO_NAME"
|
|
150
|
-
|
|
151
|
-
local response
|
|
152
|
-
response="$(execute_graphql_query "$owner" "$repo" "$pr_number")"
|
|
153
|
-
|
|
154
|
-
# Validate response
|
|
155
|
-
if [ "$(printf '%s' "$response" | jq -r '.data.repository // empty')" = "" ]; then
|
|
156
|
-
output_error "Repository ${owner}/${repo} not found or not accessible."
|
|
157
|
-
fi
|
|
158
|
-
if [ "$(printf '%s' "$response" | jq -r '.data.repository.pullRequest // empty')" = "" ]; then
|
|
159
|
-
output_error "PR #${pr_number} not found or not accessible."
|
|
160
|
-
fi
|
|
161
|
-
|
|
162
|
-
local title url
|
|
163
|
-
title="$(printf '%s' "$response" | jq -r '.data.repository.pullRequest.title')"
|
|
164
|
-
url="$(printf '%s' "$response" | jq -r '.data.repository.pullRequest.url')"
|
|
165
|
-
|
|
166
|
-
# Extract unresolved comments: filter unresolved threads, flatten comments
|
|
167
|
-
local all_unresolved
|
|
168
|
-
all_unresolved="$(printf '%s' "$response" | jq '[
|
|
169
|
-
.data.repository.pullRequest.reviewThreads.nodes[]
|
|
170
|
-
| select(.isResolved == false)
|
|
171
|
-
| .comments.nodes[]
|
|
172
|
-
| {
|
|
173
|
-
author: (.author.login // "deleted-user"),
|
|
174
|
-
body: .body,
|
|
175
|
-
createdAt: .createdAt,
|
|
176
|
-
file: .path,
|
|
177
|
-
line: (.line // .originalLine)
|
|
178
|
-
}
|
|
179
|
-
]')"
|
|
180
|
-
|
|
181
|
-
# Filter out fixed code-scanning alerts from github-advanced-security
|
|
182
|
-
local unresolved_comments="[]"
|
|
183
|
-
local count
|
|
184
|
-
count="$(printf '%s' "$all_unresolved" | jq 'length')"
|
|
185
|
-
|
|
186
|
-
local i=0
|
|
187
|
-
while [ "$i" -lt "$count" ]; do
|
|
188
|
-
local comment
|
|
189
|
-
comment="$(printf '%s' "$all_unresolved" | jq ".[$i]")"
|
|
190
|
-
local comment_author
|
|
191
|
-
comment_author="$(printf '%s' "$comment" | jq -r '.author')"
|
|
192
|
-
|
|
193
|
-
local keep=true
|
|
194
|
-
if [ "$comment_author" = "github-advanced-security" ]; then
|
|
195
|
-
local comment_body alert_number
|
|
196
|
-
comment_body="$(printf '%s' "$comment" | jq -r '.body')"
|
|
197
|
-
alert_number="$(extract_code_scanning_alert_number "$comment_body")"
|
|
198
|
-
if [ -n "$alert_number" ]; then
|
|
199
|
-
if is_code_scanning_alert_fixed "$owner" "$repo" "$alert_number"; then
|
|
200
|
-
keep=false
|
|
201
|
-
fi
|
|
202
|
-
fi
|
|
203
|
-
fi
|
|
204
|
-
|
|
205
|
-
if [ "$keep" = true ]; then
|
|
206
|
-
unresolved_comments="$(printf '%s' "$unresolved_comments" | jq --argjson c "$comment" '. + [$c]')"
|
|
207
|
-
fi
|
|
208
|
-
|
|
209
|
-
i=$((i + 1))
|
|
210
|
-
done
|
|
211
|
-
|
|
212
|
-
# Extract nitpick comments from reviews
|
|
213
|
-
local reviews_json
|
|
214
|
-
reviews_json="$(printf '%s' "$response" | jq '[.data.repository.pullRequest.reviews.nodes[]]')"
|
|
215
|
-
local nitpick_comments
|
|
216
|
-
nitpick_comments="$(extract_nitpick_comments "$reviews_json")"
|
|
217
|
-
|
|
218
|
-
# Build final output
|
|
219
|
-
local total_unresolved total_nitpicks
|
|
220
|
-
total_unresolved="$(printf '%s' "$unresolved_comments" | jq 'length')"
|
|
221
|
-
total_nitpicks="$(printf '%s' "$nitpick_comments" | jq 'length')"
|
|
222
|
-
|
|
223
|
-
jq -n \
|
|
224
|
-
--argjson nitpickComments "$nitpick_comments" \
|
|
225
|
-
--arg owner "$owner" \
|
|
226
|
-
--argjson prNumber "$pr_number" \
|
|
227
|
-
--arg repo "$repo" \
|
|
228
|
-
--arg title "$title" \
|
|
229
|
-
--argjson totalNitpicks "$total_nitpicks" \
|
|
230
|
-
--argjson totalUnresolvedComments "$total_unresolved" \
|
|
231
|
-
--argjson unresolvedComments "$unresolved_comments" \
|
|
232
|
-
--arg url "$url" \
|
|
233
|
-
'{
|
|
234
|
-
nitpickComments: $nitpickComments,
|
|
235
|
-
owner: $owner,
|
|
236
|
-
prNumber: $prNumber,
|
|
237
|
-
repo: $repo,
|
|
238
|
-
title: $title,
|
|
239
|
-
totalNitpicks: $totalNitpicks,
|
|
240
|
-
totalUnresolvedComments: $totalUnresolvedComments,
|
|
241
|
-
unresolvedComments: $unresolvedComments,
|
|
242
|
-
url: $url
|
|
243
|
-
}'
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
main "$@"
|