@clipboard-health/ai-rules 2.15.3 → 2.15.5
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/scripts/toErrorMessage.js +1 -0
- package/skills/flaky-test-debugger/SKILL.md +6 -6
- package/skills/flaky-test-debugger/references/datadog-apm-traces.md +5 -4
- 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,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toErrorMessage = toErrorMessage;
|
|
4
|
+
// Prefer stack over message for Error instances to preserve debugging context.
|
|
4
5
|
function toErrorMessage(error) {
|
|
5
6
|
return error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
6
7
|
}
|
|
@@ -178,7 +178,7 @@ step(click "Submit") → network(POST /api/orders, 201) → step(waitForURL /con
|
|
|
178
178
|
For each timeline entry:
|
|
179
179
|
|
|
180
180
|
- **`kind: "step"`** — test action with `title`, `category`, `durationMs`, `depth`, optional `error`
|
|
181
|
-
- **`kind: "network"`** — HTTP
|
|
181
|
+
- **`kind: "network"`** — slimmed HTTP entry with `method`, `url`, `status`, and `networkId`. Resolve `networkId` against `attempts[].network.instances[]` for per-request detail (`durationMs`, `timings`, `traceId`/`spanId`/`requestId`/`correlationId`, `requestBodyRef`/`responseBodyRef`, redirect links), and against `attempts[].network.groups[instance.groupId]` for shared shape (`resourceType`, `failureText`, `wasAborted`, occurrence counts)
|
|
182
182
|
- **`kind: "console"`** — browser message with `type` (warning/error/pageerror/page-closed/page-crashed) and `text`
|
|
183
183
|
|
|
184
184
|
All entries share `offsetMs` (milliseconds since attempt start), giving a single temporal view.
|
|
@@ -216,11 +216,11 @@ For each attempt, compare `status`, `durationMs`, and `error` across retries —
|
|
|
216
216
|
|
|
217
217
|
### 4Ee: Inspect network activity and extract trace IDs
|
|
218
218
|
|
|
219
|
-
Scan `network[]` for 4xx/5xx responses
|
|
219
|
+
Scan `attempts[].network.instances[]` for 4xx/5xx responses near the failure's `offsetMs` and use per-instance `timings` to isolate slow phases (DNS, connect, wait, receive). Join each instance to its group via `attempts[].network.groups[instance.groupId]` for shape-level signal (`failureText`, `wasAborted`, `resourceType`, `occurrenceCount`). Resolve payloads via `attempts[].network.bodies[instance.requestBodyRef | instance.responseBodyRef]` when the body matters.
|
|
220
220
|
|
|
221
|
-
**`traceId`** — when present on a failing
|
|
221
|
+
**`traceId`** — when present on a failing instance (`attempts[].network.instances[].traceId`), you must follow [`references/datadog-apm-traces.md`](./references/datadog-apm-traces.md) to correlate with backend behavior. This is the bridge between frontend test failure and potential backend root cause.
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
Check `attempts[].network.summary` for saturation. Non-zero `instancesDroppedByGroupCap`, `instancesDroppedByInstanceCap`, or `instancesEvictedAfterAdmission` means retained content is a sample and the request you care about may have been dropped — note this as a confidence-reducing factor. `instancesDroppedByFilter` alone is expected (static assets are filtered by design). v3 caps: instances 500, groups 200, bodies 100 per attempt.
|
|
224
224
|
|
|
225
225
|
### 4Ef: Review test steps
|
|
226
226
|
|
|
@@ -231,10 +231,10 @@ Prefer the timeline view (4Ea) which interleaves steps with network and console.
|
|
|
231
231
|
Do not propose a fix without concrete artifacts. At minimum, include:
|
|
232
232
|
|
|
233
233
|
- One **error artifact** — from `tests[].errors[]` (assertion diff, timeout message) or a trace/log entry
|
|
234
|
-
- One **network artifact** — from `
|
|
234
|
+
- One **network artifact** — an instance from `attempts[].network.instances[]` (status, timing, trace ids) joined to its group via `attempts[].network.groups[instance.groupId]` (shape, `failureText`/`wasAborted`, occurrence counts), plus the body via `attempts[].network.bodies[instance.requestBodyRef | instance.responseBodyRef]` when relevant
|
|
235
235
|
- A **specific code path** that consumed that state — use `tests[].location` to jump to the source
|
|
236
236
|
- When available: **screenshot** from `failureArtifacts.screenshotBase64` showing page state at failure
|
|
237
|
-
- When available: **Datadog trace** via `network[].traceId` showing backend behavior for the failing request
|
|
237
|
+
- When available: **Datadog trace** via `attempts[].network.instances[].traceId` showing backend behavior for the failing request
|
|
238
238
|
- A **confidence score** from 1 to 5 rating how certain you are in the root cause diagnosis
|
|
239
239
|
|
|
240
240
|
### Confidence Score
|
|
@@ -4,11 +4,12 @@ Fetch and display the full APM trace for a given trace ID, or look up a specific
|
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
|
-
The `pup` CLI must be installed and authenticated.
|
|
7
|
+
The `pup` CLI must be installed and authenticated. Two auth paths are supported:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
- **macOS Keychain** (via `pup auth login`) — the default on developer machines.
|
|
10
|
+
- **Environment variables** (`DD_API_KEY` + `DD_APP_KEY`) — the path used in sandboxes and CI.
|
|
11
|
+
|
|
12
|
+
**Do not run `pup auth status` to verify auth.** It only reads the Keychain, so it fails in sandboxes even when env-var auth is working. Call `pup traces search …` directly — env-var auth takes effect there. If the query fails with an auth error, surface it then (check `DD_API_KEY` / `DD_APP_KEY` or run `pup auth login`).
|
|
12
13
|
|
|
13
14
|
## Key pup conventions
|
|
14
15
|
|
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 "$@"
|