@olegkoval/agent-skills 1.18.0 → 1.19.0
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/.claude-plugin/plugin.json +3 -2
- package/.cursor-plugin/index.json +5 -0
- package/.grok-plugin/index.json +5 -0
- package/.kiro/steering/qodoloop.md +127 -0
- package/.windsurf/rules/qodoloop.md +126 -0
- package/README.md +4 -3
- package/catalog/skills.json +24 -0
- package/package.json +1 -1
- package/packages/software-development/qodoloop/SKILL.md +134 -0
- package/packages/software-development/qodoloop/adapters/claude/plugin.json +5 -0
- package/packages/software-development/qodoloop/adapters/claude/skills/qodoloop/SKILL.md +135 -0
- package/packages/software-development/qodoloop/adapters/codex/README.md +20 -0
- package/packages/software-development/qodoloop/adapters/cursor/plugin.json +6 -0
- package/packages/software-development/qodoloop/adapters/cursor/skills/qodoloop/SKILL.md +136 -0
- package/packages/software-development/qodoloop/adapters/grok/plugin.json +6 -0
- package/packages/software-development/qodoloop/adapters/grok/skills/qodoloop/SKILL.md +135 -0
- package/packages/software-development/qodoloop/adapters/kiro/steering/qodoloop.md +127 -0
- package/packages/software-development/qodoloop/adapters/windsurf/rules/qodoloop.md +126 -0
- package/packages/software-development/qodoloop/references/graphql-queries.md +151 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Codex Adapter for qodoloop
|
|
2
|
+
|
|
3
|
+
This is a Codex-specific adapter for the `olko:qodoloop` skill.
|
|
4
|
+
The canonical skill definition is in `../../SKILL.md`.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
Invoke in a Codex session:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
Use the olko:qodoloop skill to drive this PR to zero unresolved Qodo findings.
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Workflow
|
|
15
|
+
|
|
16
|
+
See `../../SKILL.md` for the full workflow: wait for Qodo's review of the current head, fetch
|
|
17
|
+
unresolved inline review threads via paginated GraphQL, read each finding's own Agent Prompt,
|
|
18
|
+
apply the fix, commit and push once the fix is durable, then reply to each thread (resolving fixed
|
|
19
|
+
ones, leaving blocked ones open) and repeat until clean, a rate limit is hit, or the iteration cap
|
|
20
|
+
is reached. The procedure is plain `git`/`gh` and is agent-agnostic.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "olko:qodoloop",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Iteratively drives a GitHub PR to zero unresolved Qodo findings — reads each finding's own Agent Prompt, applies the fix, replies to the thread, resolves it, pushes, and waits for Qodo's next pass.",
|
|
5
|
+
"skills": "skills/"
|
|
6
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qodoloop
|
|
3
|
+
description: >
|
|
4
|
+
Iteratively drives a GitHub PR to zero unresolved Qodo (qodo-code-review[bot]) findings. Reads
|
|
5
|
+
each finding's own "Agent Prompt" (Qodo hands you a ready-made fix spec per issue), applies the
|
|
6
|
+
fix, replies to the thread explaining what was done, resolves it, pushes, and waits for Qodo's
|
|
7
|
+
next pass. Repeats until clean or a real blocker needs a human. Use when the user wants a PR fully
|
|
8
|
+
addressed against Qodo's review, or says "run qodoloop" / "satisfy Qodo" / "resolve Qodo comments".
|
|
9
|
+
compatibility: Requires git and gh (GitHub CLI) authenticated, with Qodo Merge installed on the repo.
|
|
10
|
+
metadata:
|
|
11
|
+
targets: ["cursor"]
|
|
12
|
+
version: "1.0"
|
|
13
|
+
allowed-tools: Bash(gh:*) Bash(git:*)
|
|
14
|
+
---
|
|
15
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
16
|
+
|
|
17
|
+
# Qodoloop
|
|
18
|
+
|
|
19
|
+
Fix every actionable Qodo finding on a PR, answer each thread, resolve it, and keep going until Qodo has nothing left to say (or a finding needs a human call).
|
|
20
|
+
|
|
21
|
+
## Inputs
|
|
22
|
+
|
|
23
|
+
- **PR number** (optional): detect from the current branch if not given.
|
|
24
|
+
- **Include optional findings** (optional, default off): Qodo tags each finding "Review recommended" (actionable) or "Optional" (informational/lower-confidence). By default only "Review recommended" findings count toward done; pass `--include-optional` to also fix and resolve the optional ones.
|
|
25
|
+
|
|
26
|
+
## How Qodo actually posts (verified against a live install — don't assume otherwise)
|
|
27
|
+
|
|
28
|
+
Qodo runs as `qodo-code-review[bot]` and posts **two independent things** per review pass, not one:
|
|
29
|
+
|
|
30
|
+
1. A rollup **issue comment** titled `<h3>Code Review by Qodo</h3>` — human-readable, all findings in one place. A separate `<h3>PR Summary by Qodo</h3>` comment is a plain description, not a review — ignore it. While running, Qodo posts `<h3>Qodo is busy working</h3>` as its own comment (not an edit of a prior one).
|
|
31
|
+
2. A **formal PR review** (`state: COMMENTED`) carrying one **inline review comment per finding**, each a real, resolvable GitHub review thread at the finding's file/line — same machinery as any human inline comment.
|
|
32
|
+
|
|
33
|
+
**Use the inline threads as the source of truth.** They carry `isResolved` state natively (fetch via GraphQL, see `references/graphql-queries.md`), so "done" is answerable directly instead of re-parsing the rollup's HTML on every iteration. Each thread's body contains a `<details><summary>Agent Prompt</summary>` (or `**Agent Prompt**`) section with a ready-made fix spec, and a severity badge image whose `alt` text is `Remediation recommended` or `Informational` (recommended ↔ "Review recommended", informational ↔ "Optional").
|
|
34
|
+
|
|
35
|
+
Qodo has **no check run to poll** — there is nothing in `gh pr checks` to wait on. Completion means a new `qodo-code-review[bot]` comment/review appears after your push; poll comments, not checks.
|
|
36
|
+
|
|
37
|
+
**Rate limit is a real terminal state, not a bug.** A completed-looking comment reading "Qodo reviews are paused" / "you've reached your PR review limit" means Qodo will not review again this cycle — stop and report it, don't loop waiting for something that isn't coming.
|
|
38
|
+
|
|
39
|
+
## Instructions
|
|
40
|
+
|
|
41
|
+
### 1. Identify the PR
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
gh pr view --json number,headRefName,headRefOid -q '{number, branch: .headRefName, sha: .headRefOid}'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Check out the branch if not already on it.
|
|
48
|
+
|
|
49
|
+
### 2. Loop (max 5 iterations)
|
|
50
|
+
|
|
51
|
+
#### A. Wait for a review of the current head
|
|
52
|
+
|
|
53
|
+
On **iteration 1**, if there is nothing to push yet (you haven't fixed anything in this loop), don't wait for a brand-new review — use whatever Qodo has already posted as your starting point and go straight to step B. Otherwise:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
57
|
+
git push
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Poll (every ~10s, timeout ~5min), fetching **every page** of comments, not just the first:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
gh api --paginate "repos/{owner}/{repo}/issues/<PR_NUMBER>/comments?per_page=100" | jq -s '
|
|
64
|
+
add | map(select(.user.login == "qodo-code-review[bot]")) | sort_by(.created_at)'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
From the comments created **after `$SINCE`** (ignore anything older — that's leftover from a prior push, not this one):
|
|
68
|
+
- Any of them contains "review limit" or "paused" → **stop the loop now**, go to Report with `blocked: rate-limited`.
|
|
69
|
+
- The latest one starts with `<h3>Code Review by Qodo</h3>` → proceed to step B.
|
|
70
|
+
- Otherwise (nothing yet, or only "Qodo is busy working") → sleep 10s and poll again, up to the 5-minute timeout.
|
|
71
|
+
|
|
72
|
+
#### B. Fetch unresolved findings
|
|
73
|
+
|
|
74
|
+
Run the paginated GraphQL query in `references/graphql-queries.md` (`unresolvedQodoThreads`) — **follow `pageInfo.hasNextPage`/`endCursor` until it's exhausted**; a PR with more than 100 Qodo threads silently loses every finding past the first page otherwise. For each unresolved thread from `qodo-code-review[bot]`: parse the finding title, severity (recommended/optional), file/line, and the **Agent Prompt** block.
|
|
75
|
+
|
|
76
|
+
Build the working set: all `recommended` threads, plus `optional` ones too if `--include-optional`.
|
|
77
|
+
|
|
78
|
+
#### C. Check exit conditions
|
|
79
|
+
|
|
80
|
+
Stop if the working set is empty, or max iterations reached.
|
|
81
|
+
|
|
82
|
+
#### D. Fix each finding
|
|
83
|
+
|
|
84
|
+
For each thread in the working set, in order: read the Agent Prompt in full — it already names the issue, the context, the fix focus files/lines, and often the suggested fix. Apply it. Do not go beyond its stated scope. Track two lists as you go: **fixed** (thread id + the exact files you touched for it) and **blocked** (thread id + why — the prompt's suggestion was wrong, unsafe, or needs a product decision). Do not force a fix into the blocked list; do not resolve anything yet.
|
|
85
|
+
|
|
86
|
+
#### E. Commit and push
|
|
87
|
+
|
|
88
|
+
If **fixed** is empty, skip straight to F (nothing to commit). Otherwise, stage **only the files you touched in D** — never `git add -A`, which can sweep in unrelated local changes or secrets that have nothing to do with this loop:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git add <files touched by this iteration's fixes>
|
|
92
|
+
git commit -m "address Qodo review feedback (qodoloop iteration N)"
|
|
93
|
+
git push
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Confirm the push actually succeeded before moving on — a resolved thread whose fix never made it to the branch is worse than an unresolved one.
|
|
97
|
+
|
|
98
|
+
#### F. Answer each finding
|
|
99
|
+
|
|
100
|
+
This is the "answer to the comments" half of the job, not optional cleanup — and it only runs **after** E's push is confirmed durable, so a thread is never marked resolved while its fix still only exists locally:
|
|
101
|
+
|
|
102
|
+
- **Fixed** findings: reply with one or two sentences (what you changed and why), **then** resolve:
|
|
103
|
+
```bash
|
|
104
|
+
gh api graphql -f query='
|
|
105
|
+
mutation($threadId: ID!, $body: String!) {
|
|
106
|
+
addPullRequestReviewThreadReply(input: {pullRequestReviewThreadId: $threadId, body: $body}) {
|
|
107
|
+
comment { id }
|
|
108
|
+
}
|
|
109
|
+
}' -f threadId="$THREAD_ID" -f body="$REPLY_TEXT"
|
|
110
|
+
```
|
|
111
|
+
Pass the thread id and reply text as GraphQL **variables** (`-f threadId=... -f body=...`), never interpolated into the query string itself — a reply containing a quote, backtick, or newline breaks (or worse, injects into) a hand-built query. Then resolve with `resolveReviewThread` (see references file).
|
|
112
|
+
- **Blocked** findings: reply explaining exactly why (false positive / needs a human call), but **do not resolve** — leave the thread open. A blocked finding that gets silently resolved is a false "done".
|
|
113
|
+
|
|
114
|
+
Go back to step A.
|
|
115
|
+
|
|
116
|
+
### 3. Report
|
|
117
|
+
|
|
118
|
+
| Field | Value |
|
|
119
|
+
|--------------------|-------|
|
|
120
|
+
| Iterations | N |
|
|
121
|
+
| Findings resolved | N |
|
|
122
|
+
| Findings blocked | N (with reasons, left unresolved) |
|
|
123
|
+
| Remaining | N (if any) |
|
|
124
|
+
| Blocked | rate-limited / human-intervention / max-iterations / none |
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
Qodoloop complete.
|
|
128
|
+
Iterations: 2
|
|
129
|
+
Resolved: 4 (fixed, replied + resolved)
|
|
130
|
+
Blocked: 1 (needs a human call, replied, left unresolved)
|
|
131
|
+
Remaining: 0
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## References
|
|
135
|
+
|
|
136
|
+
- `references/graphql-queries.md` — the exact `unresolvedQodoThreads` query and the `resolveReviewThread` / `addPullRequestReviewThreadReply` mutations, with field notes.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "olko:qodoloop",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Iteratively drives a GitHub PR to zero unresolved Qodo findings — reads each finding's own Agent Prompt, applies the fix, replies to the thread, resolves it, pushes, and waits for Qodo's next pass.",
|
|
5
|
+
"skills": "skills/"
|
|
6
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qodoloop
|
|
3
|
+
description: >
|
|
4
|
+
Iteratively drives a GitHub PR to zero unresolved Qodo (qodo-code-review[bot]) findings. Reads
|
|
5
|
+
each finding's own "Agent Prompt" (Qodo hands you a ready-made fix spec per issue), applies the
|
|
6
|
+
fix, replies to the thread explaining what was done, resolves it, pushes, and waits for Qodo's
|
|
7
|
+
next pass. Repeats until clean or a real blocker needs a human. Use when the user wants a PR fully
|
|
8
|
+
addressed against Qodo's review, or says "run qodoloop" / "satisfy Qodo" / "resolve Qodo comments".
|
|
9
|
+
compatibility: Requires git and gh (GitHub CLI) authenticated, with Qodo Merge installed on the repo.
|
|
10
|
+
metadata:
|
|
11
|
+
version: "1.0"
|
|
12
|
+
allowed-tools: Bash(gh:*) Bash(git:*)
|
|
13
|
+
---
|
|
14
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
15
|
+
|
|
16
|
+
# Qodoloop
|
|
17
|
+
|
|
18
|
+
Fix every actionable Qodo finding on a PR, answer each thread, resolve it, and keep going until Qodo has nothing left to say (or a finding needs a human call).
|
|
19
|
+
|
|
20
|
+
## Inputs
|
|
21
|
+
|
|
22
|
+
- **PR number** (optional): detect from the current branch if not given.
|
|
23
|
+
- **Include optional findings** (optional, default off): Qodo tags each finding "Review recommended" (actionable) or "Optional" (informational/lower-confidence). By default only "Review recommended" findings count toward done; pass `--include-optional` to also fix and resolve the optional ones.
|
|
24
|
+
|
|
25
|
+
## How Qodo actually posts (verified against a live install — don't assume otherwise)
|
|
26
|
+
|
|
27
|
+
Qodo runs as `qodo-code-review[bot]` and posts **two independent things** per review pass, not one:
|
|
28
|
+
|
|
29
|
+
1. A rollup **issue comment** titled `<h3>Code Review by Qodo</h3>` — human-readable, all findings in one place. A separate `<h3>PR Summary by Qodo</h3>` comment is a plain description, not a review — ignore it. While running, Qodo posts `<h3>Qodo is busy working</h3>` as its own comment (not an edit of a prior one).
|
|
30
|
+
2. A **formal PR review** (`state: COMMENTED`) carrying one **inline review comment per finding**, each a real, resolvable GitHub review thread at the finding's file/line — same machinery as any human inline comment.
|
|
31
|
+
|
|
32
|
+
**Use the inline threads as the source of truth.** They carry `isResolved` state natively (fetch via GraphQL, see `references/graphql-queries.md`), so "done" is answerable directly instead of re-parsing the rollup's HTML on every iteration. Each thread's body contains a `<details><summary>Agent Prompt</summary>` (or `**Agent Prompt**`) section with a ready-made fix spec, and a severity badge image whose `alt` text is `Remediation recommended` or `Informational` (recommended ↔ "Review recommended", informational ↔ "Optional").
|
|
33
|
+
|
|
34
|
+
Qodo has **no check run to poll** — there is nothing in `gh pr checks` to wait on. Completion means a new `qodo-code-review[bot]` comment/review appears after your push; poll comments, not checks.
|
|
35
|
+
|
|
36
|
+
**Rate limit is a real terminal state, not a bug.** A completed-looking comment reading "Qodo reviews are paused" / "you've reached your PR review limit" means Qodo will not review again this cycle — stop and report it, don't loop waiting for something that isn't coming.
|
|
37
|
+
|
|
38
|
+
## Instructions
|
|
39
|
+
|
|
40
|
+
### 1. Identify the PR
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
gh pr view --json number,headRefName,headRefOid -q '{number, branch: .headRefName, sha: .headRefOid}'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Check out the branch if not already on it.
|
|
47
|
+
|
|
48
|
+
### 2. Loop (max 5 iterations)
|
|
49
|
+
|
|
50
|
+
#### A. Wait for a review of the current head
|
|
51
|
+
|
|
52
|
+
On **iteration 1**, if there is nothing to push yet (you haven't fixed anything in this loop), don't wait for a brand-new review — use whatever Qodo has already posted as your starting point and go straight to step B. Otherwise:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
56
|
+
git push
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Poll (every ~10s, timeout ~5min), fetching **every page** of comments, not just the first:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
gh api --paginate "repos/{owner}/{repo}/issues/<PR_NUMBER>/comments?per_page=100" | jq -s '
|
|
63
|
+
add | map(select(.user.login == "qodo-code-review[bot]")) | sort_by(.created_at)'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
From the comments created **after `$SINCE`** (ignore anything older — that's leftover from a prior push, not this one):
|
|
67
|
+
- Any of them contains "review limit" or "paused" → **stop the loop now**, go to Report with `blocked: rate-limited`.
|
|
68
|
+
- The latest one starts with `<h3>Code Review by Qodo</h3>` → proceed to step B.
|
|
69
|
+
- Otherwise (nothing yet, or only "Qodo is busy working") → sleep 10s and poll again, up to the 5-minute timeout.
|
|
70
|
+
|
|
71
|
+
#### B. Fetch unresolved findings
|
|
72
|
+
|
|
73
|
+
Run the paginated GraphQL query in `references/graphql-queries.md` (`unresolvedQodoThreads`) — **follow `pageInfo.hasNextPage`/`endCursor` until it's exhausted**; a PR with more than 100 Qodo threads silently loses every finding past the first page otherwise. For each unresolved thread from `qodo-code-review[bot]`: parse the finding title, severity (recommended/optional), file/line, and the **Agent Prompt** block.
|
|
74
|
+
|
|
75
|
+
Build the working set: all `recommended` threads, plus `optional` ones too if `--include-optional`.
|
|
76
|
+
|
|
77
|
+
#### C. Check exit conditions
|
|
78
|
+
|
|
79
|
+
Stop if the working set is empty, or max iterations reached.
|
|
80
|
+
|
|
81
|
+
#### D. Fix each finding
|
|
82
|
+
|
|
83
|
+
For each thread in the working set, in order: read the Agent Prompt in full — it already names the issue, the context, the fix focus files/lines, and often the suggested fix. Apply it. Do not go beyond its stated scope. Track two lists as you go: **fixed** (thread id + the exact files you touched for it) and **blocked** (thread id + why — the prompt's suggestion was wrong, unsafe, or needs a product decision). Do not force a fix into the blocked list; do not resolve anything yet.
|
|
84
|
+
|
|
85
|
+
#### E. Commit and push
|
|
86
|
+
|
|
87
|
+
If **fixed** is empty, skip straight to F (nothing to commit). Otherwise, stage **only the files you touched in D** — never `git add -A`, which can sweep in unrelated local changes or secrets that have nothing to do with this loop:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
git add <files touched by this iteration's fixes>
|
|
91
|
+
git commit -m "address Qodo review feedback (qodoloop iteration N)"
|
|
92
|
+
git push
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Confirm the push actually succeeded before moving on — a resolved thread whose fix never made it to the branch is worse than an unresolved one.
|
|
96
|
+
|
|
97
|
+
#### F. Answer each finding
|
|
98
|
+
|
|
99
|
+
This is the "answer to the comments" half of the job, not optional cleanup — and it only runs **after** E's push is confirmed durable, so a thread is never marked resolved while its fix still only exists locally:
|
|
100
|
+
|
|
101
|
+
- **Fixed** findings: reply with one or two sentences (what you changed and why), **then** resolve:
|
|
102
|
+
```bash
|
|
103
|
+
gh api graphql -f query='
|
|
104
|
+
mutation($threadId: ID!, $body: String!) {
|
|
105
|
+
addPullRequestReviewThreadReply(input: {pullRequestReviewThreadId: $threadId, body: $body}) {
|
|
106
|
+
comment { id }
|
|
107
|
+
}
|
|
108
|
+
}' -f threadId="$THREAD_ID" -f body="$REPLY_TEXT"
|
|
109
|
+
```
|
|
110
|
+
Pass the thread id and reply text as GraphQL **variables** (`-f threadId=... -f body=...`), never interpolated into the query string itself — a reply containing a quote, backtick, or newline breaks (or worse, injects into) a hand-built query. Then resolve with `resolveReviewThread` (see references file).
|
|
111
|
+
- **Blocked** findings: reply explaining exactly why (false positive / needs a human call), but **do not resolve** — leave the thread open. A blocked finding that gets silently resolved is a false "done".
|
|
112
|
+
|
|
113
|
+
Go back to step A.
|
|
114
|
+
|
|
115
|
+
### 3. Report
|
|
116
|
+
|
|
117
|
+
| Field | Value |
|
|
118
|
+
|--------------------|-------|
|
|
119
|
+
| Iterations | N |
|
|
120
|
+
| Findings resolved | N |
|
|
121
|
+
| Findings blocked | N (with reasons, left unresolved) |
|
|
122
|
+
| Remaining | N (if any) |
|
|
123
|
+
| Blocked | rate-limited / human-intervention / max-iterations / none |
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
Qodoloop complete.
|
|
127
|
+
Iterations: 2
|
|
128
|
+
Resolved: 4 (fixed, replied + resolved)
|
|
129
|
+
Blocked: 1 (needs a human call, replied, left unresolved)
|
|
130
|
+
Remaining: 0
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## References
|
|
134
|
+
|
|
135
|
+
- `references/graphql-queries.md` — the exact `unresolvedQodoThreads` query and the `resolveReviewThread` / `addPullRequestReviewThreadReply` mutations, with field notes.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
inclusion: manual
|
|
5
|
+
description: "Iteratively drives a GitHub PR to zero unresolved Qodo findings — reads each finding's own Agent Prompt, applies the fix, replies to the thread, resolves it, pushes, and waits for Qodo's next pass."
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Qodoloop
|
|
9
|
+
|
|
10
|
+
Fix every actionable Qodo finding on a PR, answer each thread, resolve it, and keep going until Qodo has nothing left to say (or a finding needs a human call).
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- **PR number** (optional): detect from the current branch if not given.
|
|
15
|
+
- **Include optional findings** (optional, default off): Qodo tags each finding "Review recommended" (actionable) or "Optional" (informational/lower-confidence). By default only "Review recommended" findings count toward done; pass `--include-optional` to also fix and resolve the optional ones.
|
|
16
|
+
|
|
17
|
+
## How Qodo actually posts (verified against a live install — don't assume otherwise)
|
|
18
|
+
|
|
19
|
+
Qodo runs as `qodo-code-review[bot]` and posts **two independent things** per review pass, not one:
|
|
20
|
+
|
|
21
|
+
1. A rollup **issue comment** titled `<h3>Code Review by Qodo</h3>` — human-readable, all findings in one place. A separate `<h3>PR Summary by Qodo</h3>` comment is a plain description, not a review — ignore it. While running, Qodo posts `<h3>Qodo is busy working</h3>` as its own comment (not an edit of a prior one).
|
|
22
|
+
2. A **formal PR review** (`state: COMMENTED`) carrying one **inline review comment per finding**, each a real, resolvable GitHub review thread at the finding's file/line — same machinery as any human inline comment.
|
|
23
|
+
|
|
24
|
+
**Use the inline threads as the source of truth.** They carry `isResolved` state natively (fetch via GraphQL, see `references/graphql-queries.md`), so "done" is answerable directly instead of re-parsing the rollup's HTML on every iteration. Each thread's body contains a `<details><summary>Agent Prompt</summary>` (or `**Agent Prompt**`) section with a ready-made fix spec, and a severity badge image whose `alt` text is `Remediation recommended` or `Informational` (recommended ↔ "Review recommended", informational ↔ "Optional").
|
|
25
|
+
|
|
26
|
+
Qodo has **no check run to poll** — there is nothing in `gh pr checks` to wait on. Completion means a new `qodo-code-review[bot]` comment/review appears after your push; poll comments, not checks.
|
|
27
|
+
|
|
28
|
+
**Rate limit is a real terminal state, not a bug.** A completed-looking comment reading "Qodo reviews are paused" / "you've reached your PR review limit" means Qodo will not review again this cycle — stop and report it, don't loop waiting for something that isn't coming.
|
|
29
|
+
|
|
30
|
+
## Instructions
|
|
31
|
+
|
|
32
|
+
### 1. Identify the PR
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
gh pr view --json number,headRefName,headRefOid -q '{number, branch: .headRefName, sha: .headRefOid}'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Check out the branch if not already on it.
|
|
39
|
+
|
|
40
|
+
### 2. Loop (max 5 iterations)
|
|
41
|
+
|
|
42
|
+
#### A. Wait for a review of the current head
|
|
43
|
+
|
|
44
|
+
On **iteration 1**, if there is nothing to push yet (you haven't fixed anything in this loop), don't wait for a brand-new review — use whatever Qodo has already posted as your starting point and go straight to step B. Otherwise:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
48
|
+
git push
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Poll (every ~10s, timeout ~5min), fetching **every page** of comments, not just the first:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
gh api --paginate "repos/{owner}/{repo}/issues/<PR_NUMBER>/comments?per_page=100" | jq -s '
|
|
55
|
+
add | map(select(.user.login == "qodo-code-review[bot]")) | sort_by(.created_at)'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
From the comments created **after `$SINCE`** (ignore anything older — that's leftover from a prior push, not this one):
|
|
59
|
+
- Any of them contains "review limit" or "paused" → **stop the loop now**, go to Report with `blocked: rate-limited`.
|
|
60
|
+
- The latest one starts with `<h3>Code Review by Qodo</h3>` → proceed to step B.
|
|
61
|
+
- Otherwise (nothing yet, or only "Qodo is busy working") → sleep 10s and poll again, up to the 5-minute timeout.
|
|
62
|
+
|
|
63
|
+
#### B. Fetch unresolved findings
|
|
64
|
+
|
|
65
|
+
Run the paginated GraphQL query in `references/graphql-queries.md` (`unresolvedQodoThreads`) — **follow `pageInfo.hasNextPage`/`endCursor` until it's exhausted**; a PR with more than 100 Qodo threads silently loses every finding past the first page otherwise. For each unresolved thread from `qodo-code-review[bot]`: parse the finding title, severity (recommended/optional), file/line, and the **Agent Prompt** block.
|
|
66
|
+
|
|
67
|
+
Build the working set: all `recommended` threads, plus `optional` ones too if `--include-optional`.
|
|
68
|
+
|
|
69
|
+
#### C. Check exit conditions
|
|
70
|
+
|
|
71
|
+
Stop if the working set is empty, or max iterations reached.
|
|
72
|
+
|
|
73
|
+
#### D. Fix each finding
|
|
74
|
+
|
|
75
|
+
For each thread in the working set, in order: read the Agent Prompt in full — it already names the issue, the context, the fix focus files/lines, and often the suggested fix. Apply it. Do not go beyond its stated scope. Track two lists as you go: **fixed** (thread id + the exact files you touched for it) and **blocked** (thread id + why — the prompt's suggestion was wrong, unsafe, or needs a product decision). Do not force a fix into the blocked list; do not resolve anything yet.
|
|
76
|
+
|
|
77
|
+
#### E. Commit and push
|
|
78
|
+
|
|
79
|
+
If **fixed** is empty, skip straight to F (nothing to commit). Otherwise, stage **only the files you touched in D** — never `git add -A`, which can sweep in unrelated local changes or secrets that have nothing to do with this loop:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git add <files touched by this iteration's fixes>
|
|
83
|
+
git commit -m "address Qodo review feedback (qodoloop iteration N)"
|
|
84
|
+
git push
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Confirm the push actually succeeded before moving on — a resolved thread whose fix never made it to the branch is worse than an unresolved one.
|
|
88
|
+
|
|
89
|
+
#### F. Answer each finding
|
|
90
|
+
|
|
91
|
+
This is the "answer to the comments" half of the job, not optional cleanup — and it only runs **after** E's push is confirmed durable, so a thread is never marked resolved while its fix still only exists locally:
|
|
92
|
+
|
|
93
|
+
- **Fixed** findings: reply with one or two sentences (what you changed and why), **then** resolve:
|
|
94
|
+
```bash
|
|
95
|
+
gh api graphql -f query='
|
|
96
|
+
mutation($threadId: ID!, $body: String!) {
|
|
97
|
+
addPullRequestReviewThreadReply(input: {pullRequestReviewThreadId: $threadId, body: $body}) {
|
|
98
|
+
comment { id }
|
|
99
|
+
}
|
|
100
|
+
}' -f threadId="$THREAD_ID" -f body="$REPLY_TEXT"
|
|
101
|
+
```
|
|
102
|
+
Pass the thread id and reply text as GraphQL **variables** (`-f threadId=... -f body=...`), never interpolated into the query string itself — a reply containing a quote, backtick, or newline breaks (or worse, injects into) a hand-built query. Then resolve with `resolveReviewThread` (see references file).
|
|
103
|
+
- **Blocked** findings: reply explaining exactly why (false positive / needs a human call), but **do not resolve** — leave the thread open. A blocked finding that gets silently resolved is a false "done".
|
|
104
|
+
|
|
105
|
+
Go back to step A.
|
|
106
|
+
|
|
107
|
+
### 3. Report
|
|
108
|
+
|
|
109
|
+
| Field | Value |
|
|
110
|
+
|--------------------|-------|
|
|
111
|
+
| Iterations | N |
|
|
112
|
+
| Findings resolved | N |
|
|
113
|
+
| Findings blocked | N (with reasons, left unresolved) |
|
|
114
|
+
| Remaining | N (if any) |
|
|
115
|
+
| Blocked | rate-limited / human-intervention / max-iterations / none |
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
Qodoloop complete.
|
|
119
|
+
Iterations: 2
|
|
120
|
+
Resolved: 4 (fixed, replied + resolved)
|
|
121
|
+
Blocked: 1 (needs a human call, replied, left unresolved)
|
|
122
|
+
Remaining: 0
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## References
|
|
126
|
+
|
|
127
|
+
- `references/graphql-queries.md` — the exact `unresolvedQodoThreads` query and the `resolveReviewThread` / `addPullRequestReviewThreadReply` mutations, with field notes.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
description: "Iteratively drives a GitHub PR to zero unresolved Qodo findings — reads each finding's own Agent Prompt, applies the fix, replies to the thread, resolves it, pushes, and waits for Qodo's next pass."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Qodoloop
|
|
8
|
+
|
|
9
|
+
Fix every actionable Qodo finding on a PR, answer each thread, resolve it, and keep going until Qodo has nothing left to say (or a finding needs a human call).
|
|
10
|
+
|
|
11
|
+
## Inputs
|
|
12
|
+
|
|
13
|
+
- **PR number** (optional): detect from the current branch if not given.
|
|
14
|
+
- **Include optional findings** (optional, default off): Qodo tags each finding "Review recommended" (actionable) or "Optional" (informational/lower-confidence). By default only "Review recommended" findings count toward done; pass `--include-optional` to also fix and resolve the optional ones.
|
|
15
|
+
|
|
16
|
+
## How Qodo actually posts (verified against a live install — don't assume otherwise)
|
|
17
|
+
|
|
18
|
+
Qodo runs as `qodo-code-review[bot]` and posts **two independent things** per review pass, not one:
|
|
19
|
+
|
|
20
|
+
1. A rollup **issue comment** titled `<h3>Code Review by Qodo</h3>` — human-readable, all findings in one place. A separate `<h3>PR Summary by Qodo</h3>` comment is a plain description, not a review — ignore it. While running, Qodo posts `<h3>Qodo is busy working</h3>` as its own comment (not an edit of a prior one).
|
|
21
|
+
2. A **formal PR review** (`state: COMMENTED`) carrying one **inline review comment per finding**, each a real, resolvable GitHub review thread at the finding's file/line — same machinery as any human inline comment.
|
|
22
|
+
|
|
23
|
+
**Use the inline threads as the source of truth.** They carry `isResolved` state natively (fetch via GraphQL, see `references/graphql-queries.md`), so "done" is answerable directly instead of re-parsing the rollup's HTML on every iteration. Each thread's body contains a `<details><summary>Agent Prompt</summary>` (or `**Agent Prompt**`) section with a ready-made fix spec, and a severity badge image whose `alt` text is `Remediation recommended` or `Informational` (recommended ↔ "Review recommended", informational ↔ "Optional").
|
|
24
|
+
|
|
25
|
+
Qodo has **no check run to poll** — there is nothing in `gh pr checks` to wait on. Completion means a new `qodo-code-review[bot]` comment/review appears after your push; poll comments, not checks.
|
|
26
|
+
|
|
27
|
+
**Rate limit is a real terminal state, not a bug.** A completed-looking comment reading "Qodo reviews are paused" / "you've reached your PR review limit" means Qodo will not review again this cycle — stop and report it, don't loop waiting for something that isn't coming.
|
|
28
|
+
|
|
29
|
+
## Instructions
|
|
30
|
+
|
|
31
|
+
### 1. Identify the PR
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gh pr view --json number,headRefName,headRefOid -q '{number, branch: .headRefName, sha: .headRefOid}'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Check out the branch if not already on it.
|
|
38
|
+
|
|
39
|
+
### 2. Loop (max 5 iterations)
|
|
40
|
+
|
|
41
|
+
#### A. Wait for a review of the current head
|
|
42
|
+
|
|
43
|
+
On **iteration 1**, if there is nothing to push yet (you haven't fixed anything in this loop), don't wait for a brand-new review — use whatever Qodo has already posted as your starting point and go straight to step B. Otherwise:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
47
|
+
git push
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Poll (every ~10s, timeout ~5min), fetching **every page** of comments, not just the first:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
gh api --paginate "repos/{owner}/{repo}/issues/<PR_NUMBER>/comments?per_page=100" | jq -s '
|
|
54
|
+
add | map(select(.user.login == "qodo-code-review[bot]")) | sort_by(.created_at)'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
From the comments created **after `$SINCE`** (ignore anything older — that's leftover from a prior push, not this one):
|
|
58
|
+
- Any of them contains "review limit" or "paused" → **stop the loop now**, go to Report with `blocked: rate-limited`.
|
|
59
|
+
- The latest one starts with `<h3>Code Review by Qodo</h3>` → proceed to step B.
|
|
60
|
+
- Otherwise (nothing yet, or only "Qodo is busy working") → sleep 10s and poll again, up to the 5-minute timeout.
|
|
61
|
+
|
|
62
|
+
#### B. Fetch unresolved findings
|
|
63
|
+
|
|
64
|
+
Run the paginated GraphQL query in `references/graphql-queries.md` (`unresolvedQodoThreads`) — **follow `pageInfo.hasNextPage`/`endCursor` until it's exhausted**; a PR with more than 100 Qodo threads silently loses every finding past the first page otherwise. For each unresolved thread from `qodo-code-review[bot]`: parse the finding title, severity (recommended/optional), file/line, and the **Agent Prompt** block.
|
|
65
|
+
|
|
66
|
+
Build the working set: all `recommended` threads, plus `optional` ones too if `--include-optional`.
|
|
67
|
+
|
|
68
|
+
#### C. Check exit conditions
|
|
69
|
+
|
|
70
|
+
Stop if the working set is empty, or max iterations reached.
|
|
71
|
+
|
|
72
|
+
#### D. Fix each finding
|
|
73
|
+
|
|
74
|
+
For each thread in the working set, in order: read the Agent Prompt in full — it already names the issue, the context, the fix focus files/lines, and often the suggested fix. Apply it. Do not go beyond its stated scope. Track two lists as you go: **fixed** (thread id + the exact files you touched for it) and **blocked** (thread id + why — the prompt's suggestion was wrong, unsafe, or needs a product decision). Do not force a fix into the blocked list; do not resolve anything yet.
|
|
75
|
+
|
|
76
|
+
#### E. Commit and push
|
|
77
|
+
|
|
78
|
+
If **fixed** is empty, skip straight to F (nothing to commit). Otherwise, stage **only the files you touched in D** — never `git add -A`, which can sweep in unrelated local changes or secrets that have nothing to do with this loop:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git add <files touched by this iteration's fixes>
|
|
82
|
+
git commit -m "address Qodo review feedback (qodoloop iteration N)"
|
|
83
|
+
git push
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Confirm the push actually succeeded before moving on — a resolved thread whose fix never made it to the branch is worse than an unresolved one.
|
|
87
|
+
|
|
88
|
+
#### F. Answer each finding
|
|
89
|
+
|
|
90
|
+
This is the "answer to the comments" half of the job, not optional cleanup — and it only runs **after** E's push is confirmed durable, so a thread is never marked resolved while its fix still only exists locally:
|
|
91
|
+
|
|
92
|
+
- **Fixed** findings: reply with one or two sentences (what you changed and why), **then** resolve:
|
|
93
|
+
```bash
|
|
94
|
+
gh api graphql -f query='
|
|
95
|
+
mutation($threadId: ID!, $body: String!) {
|
|
96
|
+
addPullRequestReviewThreadReply(input: {pullRequestReviewThreadId: $threadId, body: $body}) {
|
|
97
|
+
comment { id }
|
|
98
|
+
}
|
|
99
|
+
}' -f threadId="$THREAD_ID" -f body="$REPLY_TEXT"
|
|
100
|
+
```
|
|
101
|
+
Pass the thread id and reply text as GraphQL **variables** (`-f threadId=... -f body=...`), never interpolated into the query string itself — a reply containing a quote, backtick, or newline breaks (or worse, injects into) a hand-built query. Then resolve with `resolveReviewThread` (see references file).
|
|
102
|
+
- **Blocked** findings: reply explaining exactly why (false positive / needs a human call), but **do not resolve** — leave the thread open. A blocked finding that gets silently resolved is a false "done".
|
|
103
|
+
|
|
104
|
+
Go back to step A.
|
|
105
|
+
|
|
106
|
+
### 3. Report
|
|
107
|
+
|
|
108
|
+
| Field | Value |
|
|
109
|
+
|--------------------|-------|
|
|
110
|
+
| Iterations | N |
|
|
111
|
+
| Findings resolved | N |
|
|
112
|
+
| Findings blocked | N (with reasons, left unresolved) |
|
|
113
|
+
| Remaining | N (if any) |
|
|
114
|
+
| Blocked | rate-limited / human-intervention / max-iterations / none |
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
Qodoloop complete.
|
|
118
|
+
Iterations: 2
|
|
119
|
+
Resolved: 4 (fixed, replied + resolved)
|
|
120
|
+
Blocked: 1 (needs a human call, replied, left unresolved)
|
|
121
|
+
Remaining: 0
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## References
|
|
125
|
+
|
|
126
|
+
- `references/graphql-queries.md` — the exact `unresolvedQodoThreads` query and the `resolveReviewThread` / `addPullRequestReviewThreadReply` mutations, with field notes.
|