agent-reviews 0.1.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/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +13 -0
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/bin/agent-reviews.js +506 -0
- package/lib/comments.js +334 -0
- package/lib/format.js +166 -0
- package/lib/github.js +128 -0
- package/package.json +33 -0
- package/skills/agent-reviews/SKILL.md +189 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-reviews",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Paul Bakaus"
|
|
5
|
+
},
|
|
6
|
+
"metadata": {
|
|
7
|
+
"description": "CLI and Claude Code skill for managing GitHub PR review comments"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "agent-reviews",
|
|
12
|
+
"source": "./",
|
|
13
|
+
"description": "Manage GitHub PR review comments — list, filter, reply, and watch for bot findings"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-reviews",
|
|
3
|
+
"description": "Manage GitHub PR review comments from Claude Code. Automatically triage, fix, and respond to bot findings.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Paul Bakaus",
|
|
7
|
+
"url": "https://github.com/pbakaus"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/pbakaus/agent-reviews",
|
|
10
|
+
"repository": "https://github.com/pbakaus/agent-reviews",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["github", "pull-request", "code-review", "claude-code", "ai-agent", "review-bot"]
|
|
13
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Paul Bakaus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# agent-reviews
|
|
2
|
+
|
|
3
|
+
Manage GitHub PR review comments from the terminal and from AI coding agents.
|
|
4
|
+
|
|
5
|
+
PR review bots (Copilot, Cursor Bugbot, CodeRabbit, etc.) leave inline comments on your pull requests. agent-reviews gives you a CLI to list, filter, reply to, and watch those comments — plus a Claude Code skill that automates the entire triage-fix-reply loop.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
### CLI (npm)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g agent-reviews
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### CLI (Homebrew)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
brew install agent-reviews
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Claude Code skill
|
|
22
|
+
|
|
23
|
+
Install as a skill for the full automated workflow — no npm install required:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx skills add pbakaus/agent-reviews@agent-reviews
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This registers the `/agent-reviews` slash command. When invoked, it uses `npx` to auto-download the CLI on first run.
|
|
30
|
+
|
|
31
|
+
> You can also use both: install the CLI globally for direct terminal use, and the skill for the Claude Code workflow.
|
|
32
|
+
|
|
33
|
+
## Authentication
|
|
34
|
+
|
|
35
|
+
The primary authentication method is the **GitHub CLI** — if you're logged in with `gh auth login`, agent-reviews picks up the token automatically. No configuration needed.
|
|
36
|
+
|
|
37
|
+
For environments where `gh` isn't available (such as Claude Code Cloud, which routes git through an HTTPS proxy), agent-reviews falls back to:
|
|
38
|
+
|
|
39
|
+
1. `GITHUB_TOKEN` environment variable
|
|
40
|
+
2. `.env.local` in the repo root
|
|
41
|
+
|
|
42
|
+
The proxy environment is also why agent-reviews includes [undici](https://github.com/nodejs/undici) `ProxyAgent` support — when `HTTPS_PROXY` is set, GitHub API requests are routed through it automatically.
|
|
43
|
+
|
|
44
|
+
**Resolution order** (first match wins):
|
|
45
|
+
|
|
46
|
+
1. `GITHUB_TOKEN` environment variable
|
|
47
|
+
2. `.env.local` in the repo root
|
|
48
|
+
3. `gh auth token` (GitHub CLI)
|
|
49
|
+
|
|
50
|
+
## CLI Usage
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# List all review comments on the current branch's PR
|
|
54
|
+
agent-reviews
|
|
55
|
+
|
|
56
|
+
# Only unresolved comments
|
|
57
|
+
agent-reviews --unresolved
|
|
58
|
+
|
|
59
|
+
# Only unanswered bot comments
|
|
60
|
+
agent-reviews --unanswered --bots-only
|
|
61
|
+
|
|
62
|
+
# Full detail for a specific comment (diff hunk + replies)
|
|
63
|
+
agent-reviews --detail 12345678
|
|
64
|
+
|
|
65
|
+
# Reply to a comment
|
|
66
|
+
agent-reviews --reply 12345678 "Fixed in abc1234"
|
|
67
|
+
|
|
68
|
+
# JSON output for scripting / AI agents
|
|
69
|
+
agent-reviews --json
|
|
70
|
+
|
|
71
|
+
# Watch for new comments (polls every 30s, exits after 10 min idle)
|
|
72
|
+
agent-reviews --watch --bots-only
|
|
73
|
+
|
|
74
|
+
# Target a specific PR (otherwise auto-detects from branch)
|
|
75
|
+
agent-reviews --pr 42
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Options
|
|
79
|
+
|
|
80
|
+
| Flag | Short | Description |
|
|
81
|
+
|------|-------|-------------|
|
|
82
|
+
| `--unresolved` | `-u` | Only unresolved/pending comments |
|
|
83
|
+
| `--unanswered` | `-a` | Only comments without any replies |
|
|
84
|
+
| `--reply <id> "msg"` | `-r` | Reply to a comment |
|
|
85
|
+
| `--detail <id>` | `-d` | Full detail for a comment |
|
|
86
|
+
| `--pr <number>` | `-p` | Target a specific PR |
|
|
87
|
+
| `--json` | `-j` | JSON output |
|
|
88
|
+
| `--bots-only` | `-b` | Only bot comments |
|
|
89
|
+
| `--humans-only` | `-H` | Only human comments |
|
|
90
|
+
| `--watch` | `-w` | Poll for new comments |
|
|
91
|
+
| `--interval <sec>` | `-i` | Poll interval in seconds (default: 30) |
|
|
92
|
+
| `--timeout <sec>` | | Inactivity timeout in seconds (default: 600) |
|
|
93
|
+
|
|
94
|
+
## Claude Code Skill
|
|
95
|
+
|
|
96
|
+
The `/agent-reviews` skill automates the full PR review bot workflow:
|
|
97
|
+
|
|
98
|
+
1. Fetch all unanswered bot comments via `npx agent-reviews --bots-only --json`
|
|
99
|
+
2. Evaluate each finding — true positive, false positive, or uncertain
|
|
100
|
+
3. Fix real bugs and run lint/type-check
|
|
101
|
+
4. Dismiss false positives with an explanation
|
|
102
|
+
5. Reply to every comment with the outcome
|
|
103
|
+
6. Watch for new comments as bots re-analyze the fixes
|
|
104
|
+
7. Report a summary of all actions taken
|
|
105
|
+
|
|
106
|
+
### Skill behavior
|
|
107
|
+
|
|
108
|
+
- **True positives** get fixed, verified, and replied with `✅ Fixed in {commit}`
|
|
109
|
+
- **False positives** get replied with `⚠️ Won't fix — {reason}`
|
|
110
|
+
- **Uncertain findings** prompt the user via `AskUserQuestion`
|
|
111
|
+
- All fixes are batched into a single commit before polling begins
|
|
112
|
+
- Watch mode runs for up to 10 minutes, processing any new findings
|
|
113
|
+
|
|
114
|
+
## How It Works
|
|
115
|
+
|
|
116
|
+
### Comment types
|
|
117
|
+
|
|
118
|
+
agent-reviews fetches three types of GitHub PR comments:
|
|
119
|
+
|
|
120
|
+
| Type | Label | Description |
|
|
121
|
+
|------|-------|-------------|
|
|
122
|
+
| Review comment | `CODE` | Inline comment attached to a specific line |
|
|
123
|
+
| Issue comment | `COMMENT` | General PR-level comment |
|
|
124
|
+
| Review | `REVIEW` | Review summary (approved, changes requested) |
|
|
125
|
+
|
|
126
|
+
### Meta-comment filtering
|
|
127
|
+
|
|
128
|
+
Status updates from bots are automatically filtered out:
|
|
129
|
+
|
|
130
|
+
- Vercel deployment status (`[vc]:...`)
|
|
131
|
+
- Supabase branch status (`[supa]:...`)
|
|
132
|
+
- Cursor Bugbot summary ("Cursor Bugbot has reviewed your changes...")
|
|
133
|
+
|
|
134
|
+
Only actionable findings are shown.
|
|
135
|
+
|
|
136
|
+
### Reply status
|
|
137
|
+
|
|
138
|
+
Each comment displays its reply status:
|
|
139
|
+
|
|
140
|
+
| Status | Meaning |
|
|
141
|
+
|--------|---------|
|
|
142
|
+
| `○ no reply` | No one has replied |
|
|
143
|
+
| `✓ replied` | A human has replied |
|
|
144
|
+
| `⚡ bot replied` | Only bots have replied |
|
|
145
|
+
|
|
146
|
+
### Watch mode
|
|
147
|
+
|
|
148
|
+
Polls the GitHub API at a configurable interval and reports new comments as they appear. Outputs both formatted text and JSON for AI agent consumption. Exits automatically after a configurable inactivity timeout (default: 10 minutes).
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* agent-reviews — CLI for managing GitHub PR review comments
|
|
5
|
+
*
|
|
6
|
+
* List, filter, reply to, and watch PR review comments from the terminal.
|
|
7
|
+
* Designed for both human use and as a tool for AI coding agents.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* agent-reviews # List all review comments
|
|
11
|
+
* agent-reviews --unresolved # List unresolved comments only
|
|
12
|
+
* agent-reviews --unanswered # List comments without replies
|
|
13
|
+
* agent-reviews --reply <id> "msg" # Reply to a specific comment
|
|
14
|
+
* agent-reviews --detail <id> # Show full detail (no truncation)
|
|
15
|
+
* agent-reviews --json # Output as JSON for scripting
|
|
16
|
+
* agent-reviews --watch # Watch for new comments (poll mode)
|
|
17
|
+
*
|
|
18
|
+
* Options:
|
|
19
|
+
* --pr <number> Target specific PR (auto-detects from branch)
|
|
20
|
+
* --bots-only Only show bot comments
|
|
21
|
+
* --humans-only Only show human comments
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
getProxyFetch,
|
|
26
|
+
getGitHubToken,
|
|
27
|
+
getRepoInfo,
|
|
28
|
+
getCurrentBranch,
|
|
29
|
+
} = require("../lib/github");
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
findPRForBranch,
|
|
33
|
+
fetchPRComments,
|
|
34
|
+
processComments,
|
|
35
|
+
filterComments,
|
|
36
|
+
replyToComment,
|
|
37
|
+
} = require("../lib/comments");
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
colors,
|
|
41
|
+
formatComment,
|
|
42
|
+
formatDetailedComment,
|
|
43
|
+
formatOutput,
|
|
44
|
+
} = require("../lib/format");
|
|
45
|
+
|
|
46
|
+
const proxyFetch = getProxyFetch();
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Argument parsing
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
function parseArgs() {
|
|
53
|
+
const args = process.argv.slice(2);
|
|
54
|
+
const result = {
|
|
55
|
+
command: "list",
|
|
56
|
+
prNumber: null,
|
|
57
|
+
filter: null,
|
|
58
|
+
replyTo: null,
|
|
59
|
+
replyMessage: null,
|
|
60
|
+
json: false,
|
|
61
|
+
botsOnly: false,
|
|
62
|
+
humansOnly: false,
|
|
63
|
+
detail: null,
|
|
64
|
+
help: false,
|
|
65
|
+
version: false,
|
|
66
|
+
watch: false,
|
|
67
|
+
watchInterval: 30,
|
|
68
|
+
watchTimeout: 600,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < args.length; i++) {
|
|
72
|
+
switch (args[i]) {
|
|
73
|
+
case "--unresolved":
|
|
74
|
+
case "-u":
|
|
75
|
+
result.filter = "unresolved";
|
|
76
|
+
break;
|
|
77
|
+
case "--unanswered":
|
|
78
|
+
case "-a":
|
|
79
|
+
result.filter = "unanswered";
|
|
80
|
+
break;
|
|
81
|
+
case "--reply":
|
|
82
|
+
case "-r":
|
|
83
|
+
result.command = "reply";
|
|
84
|
+
result.replyTo = args[++i];
|
|
85
|
+
result.replyMessage = args[++i];
|
|
86
|
+
break;
|
|
87
|
+
case "--pr":
|
|
88
|
+
case "-p":
|
|
89
|
+
result.prNumber = Number.parseInt(args[++i], 10);
|
|
90
|
+
break;
|
|
91
|
+
case "--json":
|
|
92
|
+
case "-j":
|
|
93
|
+
result.json = true;
|
|
94
|
+
break;
|
|
95
|
+
case "--bots-only":
|
|
96
|
+
case "-b":
|
|
97
|
+
result.botsOnly = true;
|
|
98
|
+
break;
|
|
99
|
+
case "--humans-only":
|
|
100
|
+
case "-H":
|
|
101
|
+
result.humansOnly = true;
|
|
102
|
+
break;
|
|
103
|
+
case "--detail":
|
|
104
|
+
case "-d":
|
|
105
|
+
result.command = "detail";
|
|
106
|
+
result.detail = args[++i];
|
|
107
|
+
break;
|
|
108
|
+
case "--watch":
|
|
109
|
+
case "-w":
|
|
110
|
+
result.watch = true;
|
|
111
|
+
result.command = "watch";
|
|
112
|
+
break;
|
|
113
|
+
case "--interval":
|
|
114
|
+
case "-i":
|
|
115
|
+
result.watchInterval = Number.parseInt(args[++i], 10);
|
|
116
|
+
break;
|
|
117
|
+
case "--exit-after":
|
|
118
|
+
case "--timeout":
|
|
119
|
+
result.watchTimeout = Number.parseInt(args[++i], 10);
|
|
120
|
+
break;
|
|
121
|
+
case "--help":
|
|
122
|
+
case "-h":
|
|
123
|
+
result.help = true;
|
|
124
|
+
break;
|
|
125
|
+
case "--version":
|
|
126
|
+
case "-v":
|
|
127
|
+
result.version = true;
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function showHelp() {
|
|
138
|
+
console.log(`
|
|
139
|
+
${colors.bright}agent-reviews${colors.reset} — Manage PR review comments from the CLI
|
|
140
|
+
|
|
141
|
+
Designed for both human use and as a tool for AI coding agents (Claude Code, etc.).
|
|
142
|
+
|
|
143
|
+
${colors.bright}Usage:${colors.reset}
|
|
144
|
+
agent-reviews List all review comments
|
|
145
|
+
agent-reviews --unresolved List unresolved comments only
|
|
146
|
+
agent-reviews --unanswered List comments without replies
|
|
147
|
+
agent-reviews --reply <id> "msg" Reply to a specific comment
|
|
148
|
+
agent-reviews --detail <id> Show full detail for a comment
|
|
149
|
+
agent-reviews --watch Watch for new comments (poll mode)
|
|
150
|
+
agent-reviews --json Output as JSON for scripting
|
|
151
|
+
|
|
152
|
+
${colors.bright}Options:${colors.reset}
|
|
153
|
+
-u, --unresolved Show only unresolved/pending comments
|
|
154
|
+
-a, --unanswered Show only comments without any replies
|
|
155
|
+
-r, --reply Reply to a comment (requires ID and message)
|
|
156
|
+
-d, --detail Show full detail for a specific comment
|
|
157
|
+
-p, --pr Target specific PR number (auto-detects from branch)
|
|
158
|
+
-j, --json Output as JSON instead of formatted text
|
|
159
|
+
-b, --bots-only Only show comments from bots
|
|
160
|
+
-H, --humans-only Only show comments from humans
|
|
161
|
+
-h, --help Show this help
|
|
162
|
+
-v, --version Show version
|
|
163
|
+
|
|
164
|
+
${colors.bright}Watch Mode:${colors.reset}
|
|
165
|
+
-w, --watch Poll for new comments periodically
|
|
166
|
+
-i, --interval Poll interval in seconds (default: 30)
|
|
167
|
+
--timeout Exit after N seconds of inactivity (default: 600)
|
|
168
|
+
|
|
169
|
+
${colors.bright}Examples:${colors.reset}
|
|
170
|
+
agent-reviews # Show all comments
|
|
171
|
+
agent-reviews -u # Show unresolved only
|
|
172
|
+
agent-reviews -a --bots-only # Unanswered bot comments
|
|
173
|
+
agent-reviews --reply 12345 "Fixed!" # Reply to comment #12345
|
|
174
|
+
agent-reviews --detail 12345 # Full detail for a comment
|
|
175
|
+
agent-reviews --detail 12345 --json # Detail as JSON
|
|
176
|
+
agent-reviews --json | jq '.[]' # Pipe to jq
|
|
177
|
+
agent-reviews --watch --bots-only # Watch for new bot comments
|
|
178
|
+
agent-reviews -w -i 15 --timeout 300 # Poll every 15s, exit after 5 min
|
|
179
|
+
|
|
180
|
+
${colors.bright}Authentication:${colors.reset}
|
|
181
|
+
Set GITHUB_TOKEN env var, or use 'gh auth login' (gh CLI).
|
|
182
|
+
|
|
183
|
+
${colors.dim}Comment IDs are shown in brackets, e.g., [12345678]${colors.reset}
|
|
184
|
+
`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Watch mode
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
function formatTimestamp() {
|
|
192
|
+
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function sleep(seconds) {
|
|
196
|
+
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function watchForComments(context, options) {
|
|
200
|
+
const { owner, repo, prNumber, prUrl, token } = context;
|
|
201
|
+
const seenIds = new Set();
|
|
202
|
+
let lastActivityTime = Date.now();
|
|
203
|
+
let pollCount = 0;
|
|
204
|
+
|
|
205
|
+
function getWatchFilterDesc() {
|
|
206
|
+
if (options.botsOnly) return "bots-only";
|
|
207
|
+
if (options.humansOnly) return "humans-only";
|
|
208
|
+
return "all";
|
|
209
|
+
}
|
|
210
|
+
const filterDesc = getWatchFilterDesc();
|
|
211
|
+
|
|
212
|
+
console.log(
|
|
213
|
+
`\n${colors.bright}=== PR Comments Watch Mode ===${colors.reset}`
|
|
214
|
+
);
|
|
215
|
+
console.log(`${colors.dim}PR #${prNumber}: ${prUrl}${colors.reset}`);
|
|
216
|
+
console.log(
|
|
217
|
+
`${colors.dim}Polling every ${options.watchInterval}s, exit after ${options.watchTimeout}s of inactivity${colors.reset}`
|
|
218
|
+
);
|
|
219
|
+
console.log(
|
|
220
|
+
`${colors.dim}Filters: ${filterDesc}, ${options.filter || "all comments"}${colors.reset}`
|
|
221
|
+
);
|
|
222
|
+
console.log(
|
|
223
|
+
`${colors.dim}Started at ${formatTimestamp()}${colors.reset}\n`
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Initial fetch to populate seen IDs
|
|
227
|
+
const initialData = await fetchPRComments(
|
|
228
|
+
owner,
|
|
229
|
+
repo,
|
|
230
|
+
prNumber,
|
|
231
|
+
token,
|
|
232
|
+
proxyFetch
|
|
233
|
+
);
|
|
234
|
+
const initialProcessed = processComments(initialData);
|
|
235
|
+
const initialFiltered = filterComments(initialProcessed, options);
|
|
236
|
+
|
|
237
|
+
for (const comment of initialFiltered) {
|
|
238
|
+
seenIds.add(comment.id);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log(
|
|
242
|
+
`${colors.dim}[${formatTimestamp()}] Initial state: ${initialFiltered.length} existing comments tracked${colors.reset}`
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (initialFiltered.length > 0) {
|
|
246
|
+
console.log(`\n${colors.yellow}=== EXISTING COMMENTS ===${colors.reset}`);
|
|
247
|
+
for (const comment of initialFiltered) {
|
|
248
|
+
console.log(formatComment(comment));
|
|
249
|
+
console.log("");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Watch loop
|
|
254
|
+
while (true) {
|
|
255
|
+
await sleep(options.watchInterval);
|
|
256
|
+
pollCount++;
|
|
257
|
+
|
|
258
|
+
const rawData = await fetchPRComments(
|
|
259
|
+
owner,
|
|
260
|
+
repo,
|
|
261
|
+
prNumber,
|
|
262
|
+
token,
|
|
263
|
+
proxyFetch
|
|
264
|
+
);
|
|
265
|
+
const processed = processComments(rawData);
|
|
266
|
+
const filtered = filterComments(processed, options);
|
|
267
|
+
|
|
268
|
+
const newComments = filtered.filter((c) => !seenIds.has(c.id));
|
|
269
|
+
|
|
270
|
+
if (newComments.length > 0) {
|
|
271
|
+
lastActivityTime = Date.now();
|
|
272
|
+
|
|
273
|
+
console.log(
|
|
274
|
+
`\n${colors.green}=== NEW COMMENTS DETECTED [${formatTimestamp()}] ===${colors.reset}`
|
|
275
|
+
);
|
|
276
|
+
console.log(
|
|
277
|
+
`${colors.bright}Found ${newComments.length} new comment${newComments.length === 1 ? "" : "s"}${colors.reset}\n`
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
for (const comment of newComments) {
|
|
281
|
+
seenIds.add(comment.id);
|
|
282
|
+
console.log(formatComment(comment));
|
|
283
|
+
console.log("");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// JSON output for AI agent parsing
|
|
287
|
+
console.log(`${colors.dim}--- JSON for processing ---${colors.reset}`);
|
|
288
|
+
console.log(JSON.stringify(newComments, null, 2));
|
|
289
|
+
console.log(`${colors.dim}--- end JSON ---${colors.reset}\n`);
|
|
290
|
+
} else {
|
|
291
|
+
const inactiveSeconds = Math.round(
|
|
292
|
+
(Date.now() - lastActivityTime) / 1000
|
|
293
|
+
);
|
|
294
|
+
console.log(
|
|
295
|
+
`${colors.dim}[${formatTimestamp()}] Poll #${pollCount}: No new comments (${inactiveSeconds}s/${options.watchTimeout}s idle)${colors.reset}`
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (inactiveSeconds >= options.watchTimeout) {
|
|
299
|
+
console.log(`\n${colors.green}=== WATCH COMPLETE ===${colors.reset}`);
|
|
300
|
+
console.log(
|
|
301
|
+
`${colors.dim}No new comments after ${options.watchTimeout}s of inactivity.${colors.reset}`
|
|
302
|
+
);
|
|
303
|
+
console.log(
|
|
304
|
+
`${colors.dim}Total comments tracked: ${seenIds.size}${colors.reset}`
|
|
305
|
+
);
|
|
306
|
+
console.log(
|
|
307
|
+
`${colors.dim}Exiting at ${formatTimestamp()}${colors.reset}`
|
|
308
|
+
);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
// Main
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
async function main() {
|
|
320
|
+
const options = parseArgs();
|
|
321
|
+
|
|
322
|
+
if (options.version) {
|
|
323
|
+
const pkg = require("../package.json");
|
|
324
|
+
console.log(pkg.version);
|
|
325
|
+
process.exit(0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (options.help) {
|
|
329
|
+
showHelp();
|
|
330
|
+
process.exit(0);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Get GitHub token
|
|
334
|
+
const token = getGitHubToken();
|
|
335
|
+
if (!token) {
|
|
336
|
+
console.error(`${colors.red}Error: GitHub token not found${colors.reset}`);
|
|
337
|
+
console.error(
|
|
338
|
+
"Set GITHUB_TOKEN env var, or authenticate with: gh auth login"
|
|
339
|
+
);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Get repo info
|
|
344
|
+
const repoInfo = getRepoInfo();
|
|
345
|
+
if (!repoInfo) {
|
|
346
|
+
console.error(
|
|
347
|
+
`${colors.red}Error: Could not determine repository from git remote${colors.reset}`
|
|
348
|
+
);
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Find PR
|
|
353
|
+
let prNumber = options.prNumber;
|
|
354
|
+
let prUrl = null;
|
|
355
|
+
|
|
356
|
+
if (!prNumber) {
|
|
357
|
+
const branch = getCurrentBranch();
|
|
358
|
+
if (!branch) {
|
|
359
|
+
console.error(
|
|
360
|
+
`${colors.red}Error: Could not determine current branch${colors.reset}`
|
|
361
|
+
);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!options.json) {
|
|
366
|
+
console.log(
|
|
367
|
+
`${colors.dim}Finding PR for branch: ${branch}...${colors.reset}`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const pr = await findPRForBranch(
|
|
372
|
+
repoInfo.owner,
|
|
373
|
+
repoInfo.repo,
|
|
374
|
+
branch,
|
|
375
|
+
token,
|
|
376
|
+
proxyFetch
|
|
377
|
+
);
|
|
378
|
+
if (!pr) {
|
|
379
|
+
console.error(
|
|
380
|
+
`${colors.red}Error: No open PR found for branch '${branch}'${colors.reset}`
|
|
381
|
+
);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
prNumber = pr.number;
|
|
386
|
+
prUrl = pr.html_url;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Handle reply command
|
|
390
|
+
if (options.command === "reply") {
|
|
391
|
+
if (!(options.replyTo && options.replyMessage)) {
|
|
392
|
+
console.error(
|
|
393
|
+
`${colors.red}Error: --reply requires comment ID and message${colors.reset}`
|
|
394
|
+
);
|
|
395
|
+
console.error('Usage: agent-reviews --reply <id> "message"');
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!options.json) {
|
|
400
|
+
console.log(
|
|
401
|
+
`${colors.dim}Replying to comment ${options.replyTo}...${colors.reset}`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const result = await replyToComment(
|
|
406
|
+
repoInfo.owner,
|
|
407
|
+
repoInfo.repo,
|
|
408
|
+
prNumber,
|
|
409
|
+
options.replyTo,
|
|
410
|
+
options.replyMessage,
|
|
411
|
+
token,
|
|
412
|
+
proxyFetch
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
if (options.json) {
|
|
416
|
+
console.log(JSON.stringify(result, null, 2));
|
|
417
|
+
} else {
|
|
418
|
+
console.log(
|
|
419
|
+
`${colors.green}✓ Reply posted successfully${colors.reset}`
|
|
420
|
+
);
|
|
421
|
+
console.log(` ${colors.dim}${result.html_url}${colors.reset}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Handle detail command
|
|
428
|
+
if (options.command === "detail") {
|
|
429
|
+
if (!options.detail) {
|
|
430
|
+
console.error(
|
|
431
|
+
`${colors.red}Error: --detail requires a comment ID${colors.reset}`
|
|
432
|
+
);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!options.json) {
|
|
437
|
+
console.log(
|
|
438
|
+
`${colors.dim}Fetching comments for PR #${prNumber}...${colors.reset}`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const rawData = await fetchPRComments(
|
|
443
|
+
repoInfo.owner,
|
|
444
|
+
repoInfo.repo,
|
|
445
|
+
prNumber,
|
|
446
|
+
token,
|
|
447
|
+
proxyFetch
|
|
448
|
+
);
|
|
449
|
+
const processed = processComments(rawData);
|
|
450
|
+
const targetId = Number(options.detail);
|
|
451
|
+
const comment = processed.find((c) => c.id === targetId);
|
|
452
|
+
|
|
453
|
+
if (!comment) {
|
|
454
|
+
console.error(
|
|
455
|
+
`${colors.red}Error: Comment ${options.detail} not found in PR #${prNumber}${colors.reset}`
|
|
456
|
+
);
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (options.json) {
|
|
461
|
+
console.log(JSON.stringify(comment, null, 2));
|
|
462
|
+
} else {
|
|
463
|
+
console.log(formatDetailedComment(comment));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Handle watch command
|
|
470
|
+
if (options.command === "watch") {
|
|
471
|
+
await watchForComments(
|
|
472
|
+
{ owner: repoInfo.owner, repo: repoInfo.repo, prNumber, prUrl, token },
|
|
473
|
+
options
|
|
474
|
+
);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Default: fetch and display comments
|
|
479
|
+
if (!options.json) {
|
|
480
|
+
console.log(
|
|
481
|
+
`${colors.dim}Fetching comments for PR #${prNumber}...${colors.reset}`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const rawData = await fetchPRComments(
|
|
486
|
+
repoInfo.owner,
|
|
487
|
+
repoInfo.repo,
|
|
488
|
+
prNumber,
|
|
489
|
+
token,
|
|
490
|
+
proxyFetch
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
const processed = processComments(rawData);
|
|
494
|
+
const filtered = filterComments(processed, options);
|
|
495
|
+
|
|
496
|
+
console.log(formatOutput(filtered, options));
|
|
497
|
+
|
|
498
|
+
if (!options.json && prUrl) {
|
|
499
|
+
console.log(`\n${colors.dim}PR: ${prUrl}${colors.reset}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
main().catch((error) => {
|
|
504
|
+
console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
});
|