clud-bug 0.5.3 → 0.5.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/README.md +30 -4
- package/bin/clud-bug.js +122 -0
- package/lib/branch-protection.js +113 -0
- package/package.json +1 -1
- package/templates/workflow-py.yml.tmpl +34 -0
- package/templates/workflow-ts.yml.tmpl +34 -0
- package/templates/workflow.yml.tmpl +34 -0
package/README.md
CHANGED
|
@@ -36,16 +36,22 @@ The naturalist arrives at your repo, surveys the habitat, and assembles a field
|
|
|
36
36
|
4. **Writes** the chosen specimens to `.claude/skills/<name>/SKILL.md` (Claude Code auto-loads them in the GitHub Action).
|
|
37
37
|
5. **Drafts the field kit** at `.github/workflows/clud-bug-review.yml` with your project description filled in and the right permissions/tool allowlist for `gh pr comment` to actually post.
|
|
38
38
|
6. **Briefs other agents** by adding a `<!-- clud-bug-start -->` block to `AGENTS.md` (creating it if missing — it's the cross-tool canonical), and idempotently to `CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`, `.cursorrules`, `.windsurfrules`, `.clinerules`, `.continuerules`, and `.cursor/rules/*.md` where they already exist. Re-runs replace the prior block in place. Files you didn't already have are left uncreated — no proliferating stubs.
|
|
39
|
+
7. **Offers to enable `required_conversation_resolution`** on your default branch. Clud Bug auto-resolves its own review threads when fixes land — but that only gates merges when conversation-resolution is required. Init detects the current state via `gh`, prompts to enable (auto-yes with `--accept-all`), and degrades to an advisory message if you lack admin perms / `gh` isn't installed / the branch has no base protection rule. Pass `--no-set-protection` to skip the prompt entirely — for repos that manage branch protection via ruleset or org policy.
|
|
39
40
|
|
|
40
41
|
## CLI options
|
|
41
42
|
|
|
42
43
|
```
|
|
43
44
|
npx clud-bug init [options]
|
|
44
45
|
|
|
45
|
-
--offline
|
|
46
|
-
--accept-all,-y
|
|
47
|
-
|
|
48
|
-
--
|
|
46
|
+
--offline Skip skills.sh; install only the bundled baseline skills.
|
|
47
|
+
--accept-all,-y Accept the recommended skill set (and the
|
|
48
|
+
branch-protection prompt) without prompting.
|
|
49
|
+
--no-set-protection Skip the prompt that offers to enable
|
|
50
|
+
required_conversation_resolution on the default
|
|
51
|
+
branch. For repos that manage branch protection
|
|
52
|
+
via ruleset or org policy.
|
|
53
|
+
--commit git add + commit the generated files when done.
|
|
54
|
+
--help,-h Show help.
|
|
49
55
|
```
|
|
50
56
|
|
|
51
57
|
## Staying up to date
|
|
@@ -182,6 +188,26 @@ After install:
|
|
|
182
188
|
3. Within ~2 min, Clud Bug should post a comment flagging it.
|
|
183
189
|
4. If no comment: check the **Actions** tab logs. Look for `gh pr comment` invocations and any "Resource not accessible by integration" errors (usually a permissions issue or a fork PR).
|
|
184
190
|
|
|
191
|
+
### Reading a review
|
|
192
|
+
|
|
193
|
+
Every Clud Bug review opens with a status line that tells you exactly what changed since the previous pass — particularly useful on re-review after you push a fix:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
## 🐛 Clud Bug review
|
|
197
|
+
|
|
198
|
+
**This round:** 0 critical · 1 minor · 3 resolved from prior · 0 still open
|
|
199
|
+
|
|
200
|
+
### Findings
|
|
201
|
+
…
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- **critical** — new critical findings in this review (these are what strict mode gates on)
|
|
205
|
+
- **minor** — non-critical findings (suggestions / nits)
|
|
206
|
+
- **resolved from prior** — prior unresolved threads the bot just cleared because it verified your fix in the diff
|
|
207
|
+
- **still open** — prior threads whose issue is still standing
|
|
208
|
+
|
|
209
|
+
Same format every time; zero values are always present so the line is easy to scan and parse.
|
|
210
|
+
|
|
185
211
|
## Manual install (advanced)
|
|
186
212
|
|
|
187
213
|
If you don't want to use the CLI, you can install a generic workflow by hand:
|
package/bin/clud-bug.js
CHANGED
|
@@ -16,6 +16,7 @@ import { computeAuditFileSet, renderAuditHeader } from '../lib/audit.js';
|
|
|
16
16
|
import { runUpdate } from '../lib/update.js';
|
|
17
17
|
import { getPendingWorkflowEdits, makeBranchName, git as gitCmd } from '../lib/edit-workflow.js';
|
|
18
18
|
import { applyToRepo as applyAgentDocs } from '../lib/agents-md.js';
|
|
19
|
+
import { detectRepo, detectDefaultBranch, getProtectionState, enableConversationResolution } from '../lib/branch-protection.js';
|
|
19
20
|
|
|
20
21
|
const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
21
22
|
const TEMPLATES = join(PKG_ROOT, 'templates');
|
|
@@ -25,6 +26,7 @@ function parseArgs(argv) {
|
|
|
25
26
|
const args = {
|
|
26
27
|
_: [], offline: false, acceptAll: false, commit: false, help: false, version: false,
|
|
27
28
|
since: null, changedIn: null, scopes: [], out: null,
|
|
29
|
+
setProtection: true,
|
|
28
30
|
};
|
|
29
31
|
for (let i = 0; i < argv.length; i++) {
|
|
30
32
|
const a = argv[i];
|
|
@@ -37,6 +39,7 @@ function parseArgs(argv) {
|
|
|
37
39
|
else if (a === '--changed-in') args.changedIn = argv[++i];
|
|
38
40
|
else if (a === '--scope') args.scopes.push(argv[++i]);
|
|
39
41
|
else if (a === '--out') args.out = argv[++i];
|
|
42
|
+
else if (a === '--no-set-protection') args.setProtection = false;
|
|
40
43
|
else args._.push(a);
|
|
41
44
|
}
|
|
42
45
|
return args;
|
|
@@ -64,6 +67,10 @@ Options:
|
|
|
64
67
|
--offline Skip skills.sh; pin only the bundled baseline specimens.
|
|
65
68
|
--accept-all,-y Accept the recommended specimens without prompting.
|
|
66
69
|
--commit git add + commit the generated kit when done (init only).
|
|
70
|
+
--no-set-protection Skip the prompt that offers to enable
|
|
71
|
+
required_conversation_resolution on the default
|
|
72
|
+
branch (init only). Use for repos that manage
|
|
73
|
+
branch protection via ruleset or org policy.
|
|
67
74
|
--since <date> Audit only files changed in commits after <date> (git date string).
|
|
68
75
|
--changed-in <dur> Audit only files changed in the past <dur>: 7d, 2w, 1mo, 1y. (audit only)
|
|
69
76
|
--scope <glob> Limit audit to files matching <glob>; repeatable. (audit only)
|
|
@@ -236,6 +243,13 @@ async function runInit(args) {
|
|
|
236
243
|
spawnSync('git', ['commit', '-m', 'Add clud-bug 🐛 — a field guide to specimens crawling your code'], { cwd, stdio: 'inherit' });
|
|
237
244
|
}
|
|
238
245
|
|
|
246
|
+
// Offer to enable required_conversation_resolution on the default
|
|
247
|
+
// branch. clud-bug auto-resolves its own review threads when fixes
|
|
248
|
+
// land — without this setting, that doesn't gate merges. Skipped on
|
|
249
|
+
// --no-set-protection for repos that manage protection via ruleset
|
|
250
|
+
// or org policy.
|
|
251
|
+
await runInitBranchProtection(args);
|
|
252
|
+
|
|
239
253
|
log('');
|
|
240
254
|
log('Field kit assembled. Next:');
|
|
241
255
|
log(' 1. Set ANTHROPIC_API_KEY in your repo secrets:');
|
|
@@ -278,6 +292,114 @@ async function promptForSkills(recommended) {
|
|
|
278
292
|
}
|
|
279
293
|
}
|
|
280
294
|
|
|
295
|
+
// Branch-protection setup step at the end of `clud-bug init`.
|
|
296
|
+
// Offers to enable required_conversation_resolution on the default
|
|
297
|
+
// branch via gh API. Skipped cleanly when --no-set-protection is
|
|
298
|
+
// passed. Failure modes (no admin perms, no base protection rule,
|
|
299
|
+
// network error) all degrade to advisory log messages — they never
|
|
300
|
+
// fail the init run.
|
|
301
|
+
//
|
|
302
|
+
// gh and prompt are injectable for tests (defaults to spawning real
|
|
303
|
+
// gh + reading from real stdin).
|
|
304
|
+
async function runInitBranchProtection(args, { gh, prompt } = {}) {
|
|
305
|
+
if (!args.setProtection) {
|
|
306
|
+
log('');
|
|
307
|
+
log('🐛 Branch protection: skipped (--no-set-protection).');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
log('');
|
|
311
|
+
log('🐛 Branch protection');
|
|
312
|
+
|
|
313
|
+
// Detect repo + default branch. If gh isn't installed or the local
|
|
314
|
+
// dir isn't a github repo, treat as advisory and move on.
|
|
315
|
+
let owner, repo, branch;
|
|
316
|
+
try {
|
|
317
|
+
({ owner, repo } = await detectRepo({ gh }));
|
|
318
|
+
branch = await detectDefaultBranch({ owner, repo, gh });
|
|
319
|
+
} catch (err) {
|
|
320
|
+
log(` Could not detect repo/branch (${err.message.split('\n')[0]}). Skipping.`);
|
|
321
|
+
log(' You can enable it manually: gh api -X POST repos/<owner>/<repo>/branches/<default>/protection/required_conversation_resolution');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
log(` Default branch: ${branch}`);
|
|
326
|
+
|
|
327
|
+
// Inspect current state.
|
|
328
|
+
const current = await getProtectionState({ owner, repo, branch, gh });
|
|
329
|
+
if (current.state === 'enabled') {
|
|
330
|
+
log(' required_conversation_resolution: already on — your repo is all set.');
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (current.state === 'forbidden') {
|
|
334
|
+
log(' Could not read branch protection (no admin perms). Ask the repo owner to enable required_conversation_resolution, or re-run with --no-set-protection to silence this prompt.');
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (current.state === 'unknown') {
|
|
338
|
+
log(` Could not read branch protection (${current.reason}). Skipping.`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Short-circuit on no-protection BEFORE prompting. The single-flag
|
|
343
|
+
// POST endpoint requires a base protection rule on the branch — if
|
|
344
|
+
// there's none, enableConversationResolution would just 404. Skip
|
|
345
|
+
// the prompt and go straight to the actionable guidance (set up
|
|
346
|
+
// basic protection first, then re-run).
|
|
347
|
+
if (current.state === 'no-protection') {
|
|
348
|
+
log(' required_conversation_resolution: not set (no base protection rule on this branch)');
|
|
349
|
+
log(' Cannot enable yet: this branch has no base protection rule.');
|
|
350
|
+
log(` Set one up first: Settings → Branches → Add rule for ${branch}`);
|
|
351
|
+
log(' Then re-run clud-bug init (or toggle the setting in the GUI).');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// current.state is 'disabled'.
|
|
356
|
+
log(' required_conversation_resolution: not set');
|
|
357
|
+
|
|
358
|
+
// Decide whether to prompt.
|
|
359
|
+
let shouldEnable;
|
|
360
|
+
if (args.acceptAll) {
|
|
361
|
+
// --accept-all is a real side-effect flag here: it flips a
|
|
362
|
+
// merge-gating repo setting. Make that explicit in the log so
|
|
363
|
+
// CI users running `clud-bug init --accept-all` see exactly
|
|
364
|
+
// what's happening instead of silently noticing later.
|
|
365
|
+
log(' --accept-all: will enable required_conversation_resolution. Pass --no-set-protection to skip.');
|
|
366
|
+
shouldEnable = true;
|
|
367
|
+
} else {
|
|
368
|
+
const ask = prompt ?? (async (q) => {
|
|
369
|
+
const rl = createInterface({ input, output });
|
|
370
|
+
try { return await rl.question(q); } finally { rl.close(); }
|
|
371
|
+
});
|
|
372
|
+
log('');
|
|
373
|
+
log(' Clud Bug auto-resolves its own review threads when fixes land.');
|
|
374
|
+
log(' Without required_conversation_resolution, that doesn\'t actually gate merges.');
|
|
375
|
+
const answer = await ask(` Enable required_conversation_resolution on ${branch}? [Y/n] `);
|
|
376
|
+
shouldEnable = !['n', 'no'].includes(answer.trim().toLowerCase());
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!shouldEnable) {
|
|
380
|
+
log(' Skipped. Re-run with --accept-all or set it manually anytime.');
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const result = await enableConversationResolution({ owner, repo, branch, gh });
|
|
385
|
+
if (result.ok) {
|
|
386
|
+
log(' ✓ Enabled required_conversation_resolution.');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (result.state === 'no-protection') {
|
|
390
|
+
log(' Cannot enable: this branch has no base protection rule. Set up basic branch protection first:');
|
|
391
|
+
log(` Settings → Branches → Add rule for ${branch}`);
|
|
392
|
+
log(' Then re-run clud-bug init (or just toggle the setting in the GUI).');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (result.state === 'forbidden') {
|
|
396
|
+
log(' Cannot enable: you do not have admin permissions on this repository.');
|
|
397
|
+
log(' Ask the repo owner to enable it, or re-run with --no-set-protection to silence this prompt.');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
log(` Cannot enable (${result.reason}). You can enable it manually anytime.`);
|
|
401
|
+
}
|
|
402
|
+
|
|
281
403
|
async function runList(_args) {
|
|
282
404
|
const skillsDir = join(process.cwd(), '.claude', 'skills');
|
|
283
405
|
const groups = await listInstalled(skillsDir);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Helpers for managing `required_conversation_resolution` on the default
|
|
2
|
+
// branch via the `gh` CLI. Factored out so `runInit` can call this without
|
|
3
|
+
// embedding `spawnSync` boilerplate, and so tests can swap a mock for the
|
|
4
|
+
// real `gh` invocation.
|
|
5
|
+
//
|
|
6
|
+
// Why `gh` rather than direct fetch(): clud-bug already depends on `gh`
|
|
7
|
+
// being installed and authenticated (workflows use `gh pr comment`, edit
|
|
8
|
+
// workflows use `gh pr create`). Reusing it inherits the user's auth
|
|
9
|
+
// instead of asking them to set up another token.
|
|
10
|
+
//
|
|
11
|
+
// API endpoints used:
|
|
12
|
+
// GET /repos/{owner}/{repo}
|
|
13
|
+
// → .default_branch
|
|
14
|
+
// GET /repos/{owner}/{repo}/branches/{branch}/protection
|
|
15
|
+
// → .required_conversation_resolution.enabled (true/false), OR 404 if
|
|
16
|
+
// the branch has no protection rule at all.
|
|
17
|
+
// POST /repos/{owner}/{repo}/branches/{branch}/protection/required_conversation_resolution
|
|
18
|
+
// → enables the single flag without touching other settings. This is
|
|
19
|
+
// a real single-flag endpoint; we don't have to GET-merge-PUT the
|
|
20
|
+
// full protection JSON.
|
|
21
|
+
|
|
22
|
+
import { spawn } from 'node:child_process';
|
|
23
|
+
|
|
24
|
+
// Default `gh` invoker: spawns `gh <args>` and resolves with
|
|
25
|
+
// { code, stdout, stderr }. Tests pass a function with the same shape.
|
|
26
|
+
function defaultGh(args, { stdin } = {}) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const child = spawn('gh', args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
29
|
+
let stdout = '';
|
|
30
|
+
let stderr = '';
|
|
31
|
+
child.stdout.on('data', (d) => { stdout += d; });
|
|
32
|
+
child.stderr.on('data', (d) => { stderr += d; });
|
|
33
|
+
child.on('error', reject);
|
|
34
|
+
child.on('close', (code) => resolve({ code, stdout, stderr }));
|
|
35
|
+
if (stdin) child.stdin.end(stdin);
|
|
36
|
+
else child.stdin.end();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Returns { owner, repo } from the local git remote. Uses
|
|
41
|
+
// `gh repo view --json owner,name` so it doesn't depend on parsing URLs.
|
|
42
|
+
export async function detectRepo({ gh = defaultGh } = {}) {
|
|
43
|
+
const { code, stdout, stderr } = await gh(['repo', 'view', '--json', 'owner,name']);
|
|
44
|
+
if (code !== 0) {
|
|
45
|
+
throw new Error(`gh repo view failed (${code}): ${stderr.trim() || '(no stderr)'}`);
|
|
46
|
+
}
|
|
47
|
+
const parsed = JSON.parse(stdout);
|
|
48
|
+
return { owner: parsed.owner.login, repo: parsed.name };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Returns the default branch name (e.g. "main", "master", "trunk").
|
|
52
|
+
export async function detectDefaultBranch({ owner, repo, gh = defaultGh } = {}) {
|
|
53
|
+
const { code, stdout, stderr } = await gh(['api', `repos/${owner}/${repo}`, '--jq', '.default_branch']);
|
|
54
|
+
if (code !== 0) {
|
|
55
|
+
throw new Error(`Could not read default_branch for ${owner}/${repo}: ${stderr.trim() || stdout.trim()}`);
|
|
56
|
+
}
|
|
57
|
+
return stdout.trim();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Inspects the current required_conversation_resolution state. Returns
|
|
61
|
+
// one of:
|
|
62
|
+
// { state: 'enabled' }
|
|
63
|
+
// { state: 'disabled' }
|
|
64
|
+
// { state: 'no-protection' } // branch has no protection rule at all
|
|
65
|
+
// { state: 'forbidden' } // user lacks admin perms
|
|
66
|
+
// { state: 'unknown', reason } // any other failure mode
|
|
67
|
+
//
|
|
68
|
+
// The reason this returns a discriminated union rather than throwing is
|
|
69
|
+
// that runInit decides what to do based on the state: each value above
|
|
70
|
+
// has a different user-facing message and follow-up action.
|
|
71
|
+
export async function getProtectionState({ owner, repo, branch, gh = defaultGh } = {}) {
|
|
72
|
+
const { code, stdout, stderr } = await gh([
|
|
73
|
+
'api',
|
|
74
|
+
`repos/${owner}/${repo}/branches/${branch}/protection`,
|
|
75
|
+
'--jq', '.required_conversation_resolution.enabled // false',
|
|
76
|
+
]);
|
|
77
|
+
if (code === 0) {
|
|
78
|
+
return { state: stdout.trim() === 'true' ? 'enabled' : 'disabled' };
|
|
79
|
+
}
|
|
80
|
+
// gh prints HTTP details to stderr. Look for the markers we recognize.
|
|
81
|
+
// We deliberately key on 403 / 'Forbidden' / 'Resource not accessible'
|
|
82
|
+
// rather than the bare word 'admin' — gh's error vocabulary can mention
|
|
83
|
+
// 'admin' in unrelated contexts (administrator@…, admin api endpoint,
|
|
84
|
+
// future error copy) and we don't want to misclassify those as
|
|
85
|
+
// permission failures.
|
|
86
|
+
const blob = `${stdout}\n${stderr}`;
|
|
87
|
+
if (/404|Branch not protected|Not Found/i.test(blob)) return { state: 'no-protection' };
|
|
88
|
+
if (/403|Forbidden|Resource not accessible/i.test(blob)) return { state: 'forbidden' };
|
|
89
|
+
return { state: 'unknown', reason: stderr.trim() || stdout.trim() || `gh exited ${code}` };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Enables the single flag via the dedicated endpoint. Doesn't touch any
|
|
93
|
+
// other protection settings. Returns { ok: true } on success or
|
|
94
|
+
// { ok: false, state, reason } using the same state taxonomy as
|
|
95
|
+
// getProtectionState() so callers can produce a consistent message.
|
|
96
|
+
export async function enableConversationResolution({ owner, repo, branch, gh = defaultGh } = {}) {
|
|
97
|
+
const { code, stdout, stderr } = await gh([
|
|
98
|
+
'api', '-X', 'POST',
|
|
99
|
+
`repos/${owner}/${repo}/branches/${branch}/protection/required_conversation_resolution`,
|
|
100
|
+
]);
|
|
101
|
+
if (code === 0) return { ok: true };
|
|
102
|
+
// Match the same precise alternatives as getProtectionState — no bare
|
|
103
|
+
// 'admin' fallback to avoid misclassifying unrelated error messages
|
|
104
|
+
// that happen to contain the word.
|
|
105
|
+
const blob = `${stdout}\n${stderr}`;
|
|
106
|
+
if (/404|Branch not protected|Not Found/i.test(blob)) {
|
|
107
|
+
return { ok: false, state: 'no-protection', reason: 'Branch has no base protection rule; enable basic branch protection first.' };
|
|
108
|
+
}
|
|
109
|
+
if (/403|Forbidden|Resource not accessible/i.test(blob)) {
|
|
110
|
+
return { ok: false, state: 'forbidden', reason: 'You do not have admin permissions on this repository.' };
|
|
111
|
+
}
|
|
112
|
+
return { ok: false, state: 'unknown', reason: stderr.trim() || stdout.trim() || `gh exited ${code}` };
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clud-bug",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Claude PR review with project-aware skills. CLI installs a working GitHub Actions workflow and curates skills from skills.sh.",
|
|
5
5
|
"homepage": "https://cludbug.dev",
|
|
6
6
|
"bugs": "https://github.com/thrillmot/clud-bug/issues",
|
|
@@ -113,6 +113,40 @@ jobs:
|
|
|
113
113
|
|
|
114
114
|
## 🐛 Clud Bug review
|
|
115
115
|
|
|
116
|
+
Immediately after the H2 header — on the next non-empty
|
|
117
|
+
line — emit a status block in this exact format:
|
|
118
|
+
|
|
119
|
+
**This round:** N critical · N minor · N resolved from prior · N still open
|
|
120
|
+
|
|
121
|
+
This applies to BOTH the bare "## 🐛 Clud Bug review" header
|
|
122
|
+
and the strict-mode variants ("— critical findings" /
|
|
123
|
+
"— clean"). The status line goes on the next non-empty line
|
|
124
|
+
regardless of which header you used. Do not omit the H2
|
|
125
|
+
header variant in strict mode just to fit the status line —
|
|
126
|
+
the strict-mode gate reads the H2 line and would break.
|
|
127
|
+
|
|
128
|
+
The four counters (always include all four, even when 0 —
|
|
129
|
+
fixed format is grep-able and lets agents reading the
|
|
130
|
+
comment parse it deterministically):
|
|
131
|
+
• critical — count of NEW critical findings
|
|
132
|
+
in this review (the ones strict
|
|
133
|
+
mode gates on)
|
|
134
|
+
• minor — count of non-critical findings
|
|
135
|
+
(suggestions / nits / observations)
|
|
136
|
+
• resolved from prior — count of prior unresolved threads
|
|
137
|
+
YOU (claude[bot]) just resolved on
|
|
138
|
+
this pass via resolveReviewThread
|
|
139
|
+
(the loop-closing signal — this
|
|
140
|
+
tells the author the bot read
|
|
141
|
+
their fixes)
|
|
142
|
+
• still open — count of prior unresolved threads
|
|
143
|
+
whose issue still stands AFTER
|
|
144
|
+
this pass
|
|
145
|
+
|
|
146
|
+
On a first-time review, "resolved from prior" and "still
|
|
147
|
+
open" are both 0. On follow-up reviews after a fix-push,
|
|
148
|
+
"resolved from prior" should typically be positive.
|
|
149
|
+
|
|
116
150
|
Post it via:
|
|
117
151
|
gh pr comment "$PR_NUMBER" --body "<your review>"
|
|
118
152
|
|
|
@@ -114,6 +114,40 @@ jobs:
|
|
|
114
114
|
|
|
115
115
|
## 🐛 Clud Bug review
|
|
116
116
|
|
|
117
|
+
Immediately after the H2 header — on the next non-empty
|
|
118
|
+
line — emit a status block in this exact format:
|
|
119
|
+
|
|
120
|
+
**This round:** N critical · N minor · N resolved from prior · N still open
|
|
121
|
+
|
|
122
|
+
This applies to BOTH the bare "## 🐛 Clud Bug review" header
|
|
123
|
+
and the strict-mode variants ("— critical findings" /
|
|
124
|
+
"— clean"). The status line goes on the next non-empty line
|
|
125
|
+
regardless of which header you used. Do not omit the H2
|
|
126
|
+
header variant in strict mode just to fit the status line —
|
|
127
|
+
the strict-mode gate reads the H2 line and would break.
|
|
128
|
+
|
|
129
|
+
The four counters (always include all four, even when 0 —
|
|
130
|
+
fixed format is grep-able and lets agents reading the
|
|
131
|
+
comment parse it deterministically):
|
|
132
|
+
• critical — count of NEW critical findings
|
|
133
|
+
in this review (the ones strict
|
|
134
|
+
mode gates on)
|
|
135
|
+
• minor — count of non-critical findings
|
|
136
|
+
(suggestions / nits / observations)
|
|
137
|
+
• resolved from prior — count of prior unresolved threads
|
|
138
|
+
YOU (claude[bot]) just resolved on
|
|
139
|
+
this pass via resolveReviewThread
|
|
140
|
+
(the loop-closing signal — this
|
|
141
|
+
tells the author the bot read
|
|
142
|
+
their fixes)
|
|
143
|
+
• still open — count of prior unresolved threads
|
|
144
|
+
whose issue still stands AFTER
|
|
145
|
+
this pass
|
|
146
|
+
|
|
147
|
+
On a first-time review, "resolved from prior" and "still
|
|
148
|
+
open" are both 0. On follow-up reviews after a fix-push,
|
|
149
|
+
"resolved from prior" should typically be positive.
|
|
150
|
+
|
|
117
151
|
Post it via:
|
|
118
152
|
gh pr comment "$PR_NUMBER" --body "<your review>"
|
|
119
153
|
|
|
@@ -134,6 +134,40 @@ jobs:
|
|
|
134
134
|
|
|
135
135
|
## 🐛 Clud Bug review
|
|
136
136
|
|
|
137
|
+
Immediately after the H2 header — on the next non-empty
|
|
138
|
+
line — emit a status block in this exact format:
|
|
139
|
+
|
|
140
|
+
**This round:** N critical · N minor · N resolved from prior · N still open
|
|
141
|
+
|
|
142
|
+
This applies to BOTH the bare "## 🐛 Clud Bug review" header
|
|
143
|
+
and the strict-mode variants ("— critical findings" /
|
|
144
|
+
"— clean"). The status line goes on the next non-empty line
|
|
145
|
+
regardless of which header you used. Do not omit the H2
|
|
146
|
+
header variant in strict mode just to fit the status line —
|
|
147
|
+
the strict-mode gate reads the H2 line and would break.
|
|
148
|
+
|
|
149
|
+
The four counters (always include all four, even when 0 —
|
|
150
|
+
fixed format is grep-able and lets agents reading the
|
|
151
|
+
comment parse it deterministically):
|
|
152
|
+
• critical — count of NEW critical findings
|
|
153
|
+
in this review (the ones strict
|
|
154
|
+
mode gates on)
|
|
155
|
+
• minor — count of non-critical findings
|
|
156
|
+
(suggestions / nits / observations)
|
|
157
|
+
• resolved from prior — count of prior unresolved threads
|
|
158
|
+
YOU (claude[bot]) just resolved on
|
|
159
|
+
this pass via resolveReviewThread
|
|
160
|
+
(the loop-closing signal — this
|
|
161
|
+
tells the author the bot read
|
|
162
|
+
their fixes)
|
|
163
|
+
• still open — count of prior unresolved threads
|
|
164
|
+
whose issue still stands AFTER
|
|
165
|
+
this pass
|
|
166
|
+
|
|
167
|
+
On a first-time review, "resolved from prior" and "still
|
|
168
|
+
open" are both 0. On follow-up reviews after a fix-push,
|
|
169
|
+
"resolved from prior" should typically be positive.
|
|
170
|
+
|
|
137
171
|
Post it via:
|
|
138
172
|
gh pr comment "$PR_NUMBER" --body "<your review>"
|
|
139
173
|
|