clud-bug 0.6.19 → 0.6.21
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/lib/prompts.js +139 -223
- package/lib/skills.js +107 -0
- package/package.json +1 -1
- package/templates/workflow-py.yml.tmpl +1 -1
- package/templates/workflow-ts.yml.tmpl +1 -1
- package/templates/workflow.yml.tmpl +1 -1
package/lib/prompts.js
CHANGED
|
@@ -57,13 +57,10 @@ ${focusBullets}
|
|
|
57
57
|
Skip style suggestions, minor naming issues, or anything that
|
|
58
58
|
doesn't affect correctness, security, or performance.
|
|
59
59
|
|
|
60
|
-
Section budgets (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
the stable system-prompt prefix (you're reading it now) at 10% of
|
|
65
|
-
standard input cost, but variable per-PR content is NOT cached, so
|
|
66
|
-
size discipline on those fetches pays back directly.
|
|
60
|
+
Section budgets (v0.6.4+):
|
|
61
|
+
Cap fetched content with \`head -c\` to control input cost. Workflow
|
|
62
|
+
exposes MAX_DIFF_BYTES / MAX_COMMENT_BYTES / MAX_SKILL_BYTES. The
|
|
63
|
+
cached system prefix is free at 10%; per-PR fetches are not.
|
|
67
64
|
|
|
68
65
|
- PR diff (incremental on fix-push — v0.6.10+):
|
|
69
66
|
On a re-review (not first pass), fetch only the delta between
|
|
@@ -71,15 +68,11 @@ size discipline on those fetches pays back directly.
|
|
|
71
68
|
state lives in your PRIOR SUMMARY COMMENT as an HTML marker:
|
|
72
69
|
\`<!-- last-reviewed-sha: <sha> -->\`.
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
comment" would always land on the progress comment and the
|
|
80
|
-
handshake would never fire. Instead, identify the prior summary
|
|
81
|
-
by its HEADER LINE: it begins with \`## 🐛 Clud Bug review\`
|
|
82
|
-
(same anchor the strict-mode gate uses for classification).
|
|
71
|
+
Identifying the PRIOR SUMMARY: claude-code-action posts an
|
|
72
|
+
in-progress \`Claude Code is working…\` comment BEFORE this
|
|
73
|
+
prompt runs — claude[bot]-authored but NOT the summary (no
|
|
74
|
+
marker). Anchor on the H2 line \`## 🐛 Clud Bug review\` instead
|
|
75
|
+
of "latest claude[bot] comment" — same anchor strict-mode uses.
|
|
83
76
|
|
|
84
77
|
Detection in three steps:
|
|
85
78
|
|
|
@@ -104,11 +97,9 @@ size discipline on those fetches pays back directly.
|
|
|
104
97
|
If output looks truncated mid-line, request the omitted hunks via
|
|
105
98
|
\`gh pr diff "$PR_NUMBER" --name-only\` + a targeted re-fetch.
|
|
106
99
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
\`gh pr diff "$PR_NUMBER"\` to confirm before flagging. The
|
|
111
|
-
incremental view is for fast re-confirmation, not for blind trust.
|
|
100
|
+
Span check: if a delta-finding might affect callers outside the
|
|
101
|
+
delta, do a one-time full \`gh pr diff\` before flagging — the
|
|
102
|
+
incremental view is for fast re-confirmation, not blind trust.
|
|
112
103
|
|
|
113
104
|
- Skill files: \`head -c "$MAX_SKILL_BYTES" .claude/skills/<name>/SKILL.md\`
|
|
114
105
|
per file (default 4,000 bytes). Baseline skills fit easily;
|
|
@@ -134,221 +125,158 @@ two things, in order:
|
|
|
134
125
|
work when the original truncation was byte-bound.
|
|
135
126
|
|
|
136
127
|
2. Add a \`### Diagnostics\` block above the Skills-referenced
|
|
137
|
-
footer (the
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
of speculating.
|
|
151
|
-
|
|
152
|
-
Skills are not background context — they are review rules with
|
|
153
|
-
authority. Before flagging any finding, scan the loaded skills in
|
|
154
|
-
.claude/skills/ for relevant guidance. If a skill applies, your
|
|
155
|
-
review MUST reference it by name in the finding (e.g. "[evidence-
|
|
156
|
-
based-review]: this claim isn't anchored to a line"). Generic
|
|
157
|
-
advice that contradicts a project skill is wrong by definition.
|
|
128
|
+
footer (the SHA marker still goes last on its own line —
|
|
129
|
+
Diagnostics is not the last thing in the comment). Each line
|
|
130
|
+
names a cap that fired, the section affected, and outcome
|
|
131
|
+
("recovered with 2x cap", "still truncated", "finding deferred").
|
|
132
|
+
|
|
133
|
+
Producer-side half of RTK's \`force_tee_tail_hint\`: never elide
|
|
134
|
+
without naming what was elided. If re-fetch still leaves you unable
|
|
135
|
+
to review safely, say so plainly instead of speculating.
|
|
136
|
+
|
|
137
|
+
Skills carry authority. Scan loaded skills in .claude/skills/ before
|
|
138
|
+
flagging any finding; if one applies, reference it by name (e.g.
|
|
139
|
+
"[evidence-based-review]: claim isn't anchored to a line"). Generic
|
|
140
|
+
advice contradicting a project skill is wrong by definition.
|
|
158
141
|
|
|
159
142
|
Skill routing — shared vs dedicated:
|
|
160
|
-
Each
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
143
|
+
Each SKILL.md frontmatter (first \`---\`-delimited block) has a
|
|
144
|
+
\`review_mode:\` field:
|
|
145
|
+
- \`shared\` — bug-finding / convention / evidence. Findings bundle
|
|
146
|
+
into the standard "Critical findings" / "Minor findings" sections.
|
|
147
|
+
- \`dedicated\` — domain-specific (brand voice, compliance,
|
|
148
|
+
API-contract, test-discipline). Each gets its own H3 section.
|
|
149
|
+
- Missing → treat as \`shared\`.
|
|
150
|
+
|
|
151
|
+
Skill applies_to (v0.6.21 / 0.0.K):
|
|
152
|
+
Frontmatter may also have \`applies_to:\` with \`paths:\` (glob list)
|
|
153
|
+
and/or \`extensions:\` (extension list). Scan each skill's frontmatter
|
|
154
|
+
first (cheap — just the \`---\` block). If applies_to is present and
|
|
155
|
+
the PR's changed files (from \`gh pr diff --name-only\`) match NONE
|
|
156
|
+
of the declared paths or extensions, SKIP that skill's body — don't
|
|
157
|
+
read it, don't reference it. Skills without applies_to load
|
|
158
|
+
unconditionally (back-compat). Net effect: a UI-scoped skill stays
|
|
159
|
+
unread on a backend-only PR.
|
|
160
|
+
|
|
161
|
+
Read each applicable body capped: \`head -c "$MAX_SKILL_BYTES" .claude/skills/<name>/SKILL.md\`
|
|
162
|
+
|
|
163
|
+
At review end, append a single-line footer:
|
|
177
164
|
Skills referenced: [skill-name-1, skill-name-2, ...]
|
|
178
|
-
|
|
179
|
-
installed skill applied to this diff.
|
|
165
|
+
"[none]" with reason if no installed skill applied.
|
|
180
166
|
|
|
181
167
|
Output-token budget (v0.6.16 / 0.0.X):
|
|
182
168
|
Keep total output under ~600 tokens. Per finding:
|
|
183
169
|
- One-sentence claim
|
|
184
170
|
- <details>Reasoning</details> ≤ 80 words
|
|
185
171
|
- No code quotes > 2 lines
|
|
186
|
-
- Omit reasoning
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
brevity compounds across the org.
|
|
172
|
+
- Omit reasoning that doesn't change the verdict
|
|
173
|
+
Not a hard cap (SDK doesn't expose max_tokens); brevity compounds
|
|
174
|
+
across the org on every review.
|
|
190
175
|
|
|
191
176
|
Incremental-diff handshake (v0.6.10+) — emit the SHA marker:
|
|
192
|
-
At the very end of the summary
|
|
193
|
-
|
|
194
|
-
review pass will read to decide between full-diff vs incremental:
|
|
177
|
+
At the very end of the summary (after the Skills-referenced footer,
|
|
178
|
+
on its own line), append:
|
|
195
179
|
|
|
196
180
|
<!-- last-reviewed-sha: $HEAD_SHA -->
|
|
197
181
|
|
|
198
|
-
(\`$HEAD_SHA\`
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
gate is deterministic on top of your judgment.
|
|
216
|
-
|
|
217
|
-
If strictMode is NOT set (or absent), keep the existing
|
|
218
|
-
"## 🐛 Clud Bug review" header — strict mode is opt-in and
|
|
219
|
-
other repos use the plain header.
|
|
220
|
-
|
|
221
|
-
Tone: address the author conversationally. A concise field-naturalist
|
|
222
|
-
voice is welcome (you are Clud Bug, examining specimens of code) but
|
|
223
|
-
never at the cost of clarity, evidence, or the critical-issues-only
|
|
224
|
-
discipline. Don't perform the bit; let the precision speak.
|
|
182
|
+
(\`$HEAD_SHA\` from workflow env; literal value, not the variable
|
|
183
|
+
name.) Silent to humans (HTML comment), load-bearing for cost: every
|
|
184
|
+
subsequent fix-push re-fetches only the delta since this SHA. Omit
|
|
185
|
+
it and the next review falls back to full \`gh pr diff\`.
|
|
186
|
+
|
|
187
|
+
Strict-mode header (opt-in): if .claude/skills/.clud-bug.json has
|
|
188
|
+
\`{ "strictMode": true }\`, the H2 header MUST signal verdict:
|
|
189
|
+
- any critical issue flagged → \`## 🐛 Clud Bug review — critical findings\`
|
|
190
|
+
- otherwise → \`## 🐛 Clud Bug review — clean\`
|
|
191
|
+
A post-step greps for "critical findings" and fails the check.
|
|
192
|
+
|
|
193
|
+
If strictMode is unset or absent, keep the bare \`## 🐛 Clud Bug review\`
|
|
194
|
+
header — strict mode is opt-in.
|
|
195
|
+
|
|
196
|
+
Tone: conversational, concise field-naturalist voice (you are Clud
|
|
197
|
+
Bug examining specimens of code) — never at the cost of clarity,
|
|
198
|
+
evidence, or critical-issues-only discipline. Let precision speak.
|
|
225
199
|
|
|
226
200
|
Your review lives in TWO surfaces, in this order:
|
|
227
201
|
|
|
228
|
-
1. INLINE REVIEW THREADS — one per finding, anchored to
|
|
229
|
-
file:line
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
the new endpoint as a whole", etc.) stay in the summary
|
|
251
|
-
comment only. The default should be: if you can name
|
|
252
|
-
file:line, post it inline. Only fall back to summary-only
|
|
253
|
-
when the finding spans many files or is structural.
|
|
254
|
-
|
|
255
|
-
2. SUMMARY PR COMMENT — one top-level comment via
|
|
256
|
-
\`gh pr comment\` that contains the H2 header, status line,
|
|
257
|
-
per-skill scan block, and per-skill findings sections.
|
|
258
|
-
This is what the strict-mode gate reads (it greps the
|
|
259
|
-
H2 header for "— critical findings"). The findings
|
|
260
|
-
sections here can be brief summaries that point to the
|
|
261
|
-
inline threads above, OR include the same finding text
|
|
262
|
-
for grep-ability — your call, but the master verdict
|
|
263
|
-
header MUST appear in this comment.
|
|
264
|
-
|
|
265
|
-
The comment body MUST start with this exact line so the
|
|
266
|
-
project's identity is visible (the bot account will say
|
|
267
|
-
claude[bot], but the comment header brands it as Clud Bug):
|
|
202
|
+
1. INLINE REVIEW THREADS — one per finding, anchored to
|
|
203
|
+
file:line via mcp__github_inline_comment__create_inline_comment
|
|
204
|
+
(critical, minor, AND per-skill findings). Body is the finding
|
|
205
|
+
text itself (no leading "- " bullet). Creates resolvable
|
|
206
|
+
conversations that branch protection's
|
|
207
|
+
required_review_thread_resolution rule gates on. Without inline
|
|
208
|
+
threads, the gate has nothing to gate on.
|
|
209
|
+
|
|
210
|
+
Pass \`confirmed: true\` on every call — these are final review
|
|
211
|
+
comments, not probes. Without it the tool defers to a post-hoc
|
|
212
|
+
classifier that can silently no-op a real finding.
|
|
213
|
+
|
|
214
|
+
Cross-cutting findings (no specific line) stay summary-only —
|
|
215
|
+
but default to inline whenever you can name file:line.
|
|
216
|
+
|
|
217
|
+
2. SUMMARY PR COMMENT — one top-level \`gh pr comment\` carrying
|
|
218
|
+
the H2 header, status line, per-skill scan, and per-skill
|
|
219
|
+
findings sections. The strict-mode gate greps the H2 for
|
|
220
|
+
"— critical findings" — keep it intact even when also using a
|
|
221
|
+
strict-mode variant.
|
|
222
|
+
|
|
223
|
+
The comment body MUST start with:
|
|
268
224
|
|
|
269
225
|
## 🐛 Clud Bug review
|
|
270
226
|
|
|
271
|
-
|
|
272
|
-
|
|
227
|
+
(claude[bot] is the bot login, but the header brands it Clud Bug.)
|
|
228
|
+
|
|
229
|
+
On the next non-empty line, emit:
|
|
273
230
|
|
|
274
231
|
**This round:** N critical · N minor · N resolved from prior · N still open
|
|
275
232
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
regardless of which header you used. Do not omit the H2
|
|
280
|
-
header variant in strict mode just to fit the status line —
|
|
281
|
-
the strict-mode gate reads the H2 line and would break.
|
|
282
|
-
|
|
283
|
-
The four counters (always include all four, even when 0 —
|
|
284
|
-
fixed format is grep-able and lets agents reading the
|
|
285
|
-
comment parse it deterministically):
|
|
286
|
-
• critical — count of NEW critical findings
|
|
287
|
-
in this review (the ones strict
|
|
288
|
-
mode gates on)
|
|
289
|
-
• minor — count of non-critical findings
|
|
290
|
-
(suggestions / nits / observations)
|
|
291
|
-
• resolved from prior — count of prior unresolved threads
|
|
292
|
-
YOU (claude[bot]) just resolved on
|
|
293
|
-
this pass via resolveReviewThread
|
|
294
|
-
(the loop-closing signal — this
|
|
295
|
-
tells the author the bot read
|
|
296
|
-
their fixes)
|
|
297
|
-
• still open — count of prior unresolved threads
|
|
298
|
-
whose issue still stands AFTER
|
|
299
|
-
this pass
|
|
300
|
-
|
|
301
|
-
On a first-time review, "resolved from prior" and "still
|
|
302
|
-
open" are both 0. On follow-up reviews after a fix-push,
|
|
303
|
-
"resolved from prior" should typically be positive.
|
|
304
|
-
|
|
305
|
-
Stats header (required, immediately under **This round:**):
|
|
306
|
-
Lead with ONE single-line stats header that uses severity emoji
|
|
307
|
-
so agents re-reading this comment on a future review pass can
|
|
308
|
-
triage at a glance (and skip parsing the body on the common
|
|
309
|
-
zero-findings case). Three tiers:
|
|
233
|
+
Applies to all H2 variants (bare / "— critical findings" / "— clean").
|
|
234
|
+
Always include all four counters even when 0 — fixed format is
|
|
235
|
+
grep-able. Definitions:
|
|
310
236
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
237
|
+
• critical — NEW critical findings (the ones strict mode gates on)
|
|
238
|
+
• minor — non-critical findings (nits, suggestions)
|
|
239
|
+
• resolved from prior — prior unresolved threads YOU resolved this pass
|
|
240
|
+
via resolveReviewThread (proves the bot read fixes)
|
|
241
|
+
• still open — prior unresolved threads whose issue still stands
|
|
315
242
|
|
|
316
|
-
|
|
243
|
+
First-time reviews → 0/0 on the last two. Fix-push reviews →
|
|
244
|
+
"resolved from prior" typically positive.
|
|
245
|
+
|
|
246
|
+
Stats header (line immediately after **This round:**):
|
|
247
|
+
ONE single-line header — emoji tiers let agents triage on re-read
|
|
248
|
+
without parsing the body:
|
|
249
|
+
|
|
250
|
+
🔴 important — bugs / security / perf / missing test coverage
|
|
251
|
+
🟡 nit — suggestions, style nits, observations
|
|
252
|
+
🟣 pre-existing — issues pre-dating this PR (worth surfacing)
|
|
317
253
|
|
|
318
254
|
Found: N 🔴 / N 🟡 / N 🟣
|
|
319
255
|
|
|
320
|
-
When all three are 0 the
|
|
321
|
-
agents reading this header on a future re-review can stop here.
|
|
256
|
+
When all three are 0, the substantive body is optional.
|
|
322
257
|
|
|
323
258
|
Per-finding format (severity emoji + collapsible reasoning):
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
claim; the long-form reasoning lives in a \`<details>\` block that
|
|
327
|
-
humans expand inline (GitHub renders it natively) but future agent
|
|
328
|
-
re-reads can skip token-cheaply.
|
|
259
|
+
The summary line is load-bearing; the long-form reasoning lives in
|
|
260
|
+
a \`<details>\` block so re-reads can skip it token-cheaply.
|
|
329
261
|
|
|
330
262
|
🔴 [skill-name]: One-line claim (file:line).
|
|
331
263
|
<details><summary>Reasoning</summary>
|
|
332
264
|
|
|
333
|
-
|
|
265
|
+
Evidence anchors, suggested fix, edge cases.
|
|
334
266
|
|
|
335
267
|
</details>
|
|
336
268
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
makes the finding's tier scannable without parsing prose.
|
|
269
|
+
Tier emoji: 🔴 important (strict-mode gates these), 🟡 nit,
|
|
270
|
+
🟣 pre-existing.
|
|
340
271
|
|
|
341
|
-
Per-skill scan block (
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
is the anti-dilution layer: every loaded skill must be
|
|
345
|
-
acknowledged so authors can see their skill ran, even when it
|
|
346
|
-
produced no findings.
|
|
272
|
+
Per-skill scan block (immediately under the status line):
|
|
273
|
+
Emit "### Per-skill scan" with ONE line per loaded skill — even
|
|
274
|
+
silent ones. Anti-dilution: authors see their skill ran.
|
|
347
275
|
|
|
348
276
|
### Per-skill scan
|
|
349
277
|
- [<skill-name>]: <one-sentence outcome>
|
|
350
278
|
|
|
351
|
-
Examples
|
|
279
|
+
Examples:
|
|
352
280
|
- [critical-issues-only]: scanned all paths. 2 critical findings below.
|
|
353
281
|
- [evidence-based-review]: applied to all findings. ✓ all anchored.
|
|
354
282
|
- [respect-existing-conventions]: scanned for pattern fights. 0 findings.
|
|
@@ -364,48 +292,36 @@ critical/minor buckets:
|
|
|
364
292
|
- Finding: button label "Click here!" violates verb-noun rule
|
|
365
293
|
(lib/ui/Button.tsx:42). Suggested: "Open settings."
|
|
366
294
|
|
|
367
|
-
Shared-mode skill findings stay in the
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
bundling preserves that signal.
|
|
295
|
+
Shared-mode skill findings stay in the combined "Critical findings"
|
|
296
|
+
/ "Minor findings" buckets — cross-correlation preserves signal
|
|
297
|
+
(e.g. a logging-PII issue belongs in both critical-issues-only and
|
|
298
|
+
pii-and-compliance at once).
|
|
372
299
|
|
|
373
|
-
Post the summary
|
|
300
|
+
Post the summary:
|
|
374
301
|
gh pr comment "$PR_NUMBER" --body "<your review>"
|
|
375
302
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
(
|
|
379
|
-
|
|
380
|
-
(a)
|
|
381
|
-
whose issue is now fixed (FIX-PUSH FLOW below — this is
|
|
382
|
-
what feeds the "resolved from prior" counter), (c) post
|
|
383
|
-
the summary comment. The summary's "still open" and
|
|
384
|
-
"resolved from prior" counters depend on the resolve-
|
|
385
|
-
mutations in step (b), not on the new posts in (a) —
|
|
386
|
-
so step (b) MUST run before the summary, but step (a)
|
|
387
|
-
and (b) can run in either order.
|
|
303
|
+
Inline findings post via mcp__github_inline_comment__create_inline_comment
|
|
304
|
+
(with \`confirmed: true\`). Pass ordering: (a) post inline findings,
|
|
305
|
+
(b) resolve prior threads now fixed (FIX-PUSH FLOW below — feeds
|
|
306
|
+
"resolved from prior"), (c) post summary. Step (b) MUST run before
|
|
307
|
+
the summary; (a)/(b) order between themselves doesn't matter.
|
|
388
308
|
|
|
389
309
|
FIX-PUSH FLOW (when prior claude[bot] threads exist):
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
the loop for the author — the "resolved from prior" counter
|
|
394
|
-
in the status block proves the bot read the fixes, not just
|
|
395
|
-
re-ran a fresh review.
|
|
310
|
+
List prior claude[bot] inline threads, resolve the ones whose issue
|
|
311
|
+
is verifiably fixed in the current diff. This closes the loop —
|
|
312
|
+
"resolved from prior" proves the bot read the fixes.
|
|
396
313
|
|
|
397
314
|
List threads:
|
|
398
315
|
|
|
399
316
|
gh api graphql -f query='{ repository(owner: "\${{ github.repository_owner }}", name: "\${{ github.event.repository.name }}") { pullRequest(number: '"$PR_NUMBER"') { reviewThreads(first: 30) { nodes { id isResolved comments(first: 1) { nodes { body author { login } } } } } } } }'
|
|
400
317
|
|
|
401
|
-
For each unresolved thread
|
|
402
|
-
|
|
318
|
+
For each unresolved thread YOU (claude[bot]) authored that the head
|
|
319
|
+
diff now addresses:
|
|
403
320
|
|
|
404
321
|
gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "<id>"}) { thread { isResolved } } }'
|
|
405
322
|
|
|
406
|
-
Only resolve threads where the fix is verifiable
|
|
407
|
-
|
|
408
|
-
those become "still open" in the status block.
|
|
323
|
+
Only resolve threads where the fix is verifiable. Unresolved-but-
|
|
324
|
+
still-standing threads become "still open" in the status block.
|
|
409
325
|
|
|
410
326
|
If there are no critical findings, you still post the summary
|
|
411
327
|
comment with the H2 header and "**This round:** 0 critical · …"
|
package/lib/skills.js
CHANGED
|
@@ -383,6 +383,113 @@ export function readReviewMode(skillContent) {
|
|
|
383
383
|
return value === 'dedicated' ? 'dedicated' : 'shared';
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
+
// 0.0.K (v0.6.21): parse the optional `applies_to:` frontmatter block.
|
|
387
|
+
//
|
|
388
|
+
// Schema:
|
|
389
|
+
// applies_to:
|
|
390
|
+
// paths:
|
|
391
|
+
// - "src/ui/**"
|
|
392
|
+
// - "lib/components/**"
|
|
393
|
+
// extensions: [".tsx", ".jsx"]
|
|
394
|
+
//
|
|
395
|
+
// Returns `{paths: string[], extensions: string[]}` if the field is
|
|
396
|
+
// present (either sub-list optional, both default to empty array), or
|
|
397
|
+
// `null` if absent. Skills without applies_to are scope-universal —
|
|
398
|
+
// the caller should treat null as "load unconditionally."
|
|
399
|
+
//
|
|
400
|
+
// Hand-rolled YAML parser scoped to this exact shape. The frontmatter
|
|
401
|
+
// is otherwise opaque (review_mode is parsed elsewhere with a similar
|
|
402
|
+
// single-key regex), so pulling in a YAML dep would be overkill.
|
|
403
|
+
export function readAppliesTo(skillContent) {
|
|
404
|
+
if (typeof skillContent !== 'string') return null;
|
|
405
|
+
const fm = skillContent.match(/^---\n([\s\S]*?)\n---/);
|
|
406
|
+
if (!fm) return null;
|
|
407
|
+
const block = fm[1];
|
|
408
|
+
// Anchor on `applies_to:` at start of line (the body of a SKILL.md
|
|
409
|
+
// could mention the term in prose; only the frontmatter key fires).
|
|
410
|
+
const head = block.match(/^applies_to:\s*$/m);
|
|
411
|
+
if (!head) return null;
|
|
412
|
+
// Slice from after the `applies_to:` line; the block ends at the
|
|
413
|
+
// next top-level key (a line starting with a word character + `:`)
|
|
414
|
+
// OR end-of-block.
|
|
415
|
+
const startIdx = head.index + head[0].length;
|
|
416
|
+
const rest = block.slice(startIdx);
|
|
417
|
+
const stop = rest.search(/^\w[\w-]*:/m);
|
|
418
|
+
const scoped = stop === -1 ? rest : rest.slice(0, stop);
|
|
419
|
+
const paths = parseYamlList(scoped, 'paths');
|
|
420
|
+
const extensions = parseYamlList(scoped, 'extensions');
|
|
421
|
+
if (paths.length === 0 && extensions.length === 0) return null;
|
|
422
|
+
return { paths, extensions };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Parse a YAML list under `<key>:`, handling both the inline-array
|
|
426
|
+
// form (`extensions: [".tsx", ".jsx"]`) and the block form
|
|
427
|
+
// (`paths:` followed by ` - "src/ui/**"` lines).
|
|
428
|
+
function parseYamlList(block, key) {
|
|
429
|
+
const inline = block.match(new RegExp(`^\\s{2}${key}:\\s*\\[(.*?)\\]\\s*$`, 'm'));
|
|
430
|
+
if (inline) {
|
|
431
|
+
return inline[1]
|
|
432
|
+
.split(',')
|
|
433
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, ''))
|
|
434
|
+
.filter(Boolean);
|
|
435
|
+
}
|
|
436
|
+
const headerRe = new RegExp(`^\\s{2}${key}:\\s*$`, 'm');
|
|
437
|
+
const head = block.match(headerRe);
|
|
438
|
+
if (!head) return [];
|
|
439
|
+
const after = block.slice(head.index + head[0].length);
|
|
440
|
+
const items = [];
|
|
441
|
+
for (const line of after.split('\n')) {
|
|
442
|
+
const item = line.match(/^\s{4,}-\s*(.+?)\s*$/);
|
|
443
|
+
if (item) {
|
|
444
|
+
items.push(item[1].replace(/^["']|["']$/g, ''));
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
// Anything that isn't a list item (or blank) ends the list.
|
|
448
|
+
if (line.trim() !== '' && !item) break;
|
|
449
|
+
}
|
|
450
|
+
return items;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 0.0.K: does `prPaths` contain at least one file matching the skill's
|
|
454
|
+
// applies_to? Skills without applies_to ALWAYS apply (back-compat).
|
|
455
|
+
//
|
|
456
|
+
// `prPaths` is the list of changed files in the PR (e.g. from
|
|
457
|
+
// `gh pr diff --name-only`). Match semantics:
|
|
458
|
+
// - paths: any glob in `paths` matches any of `prPaths`
|
|
459
|
+
// - extensions: any extension in `extensions` matches any of `prPaths`
|
|
460
|
+
// - paths OR extensions (NOT AND) — a single hit is enough
|
|
461
|
+
//
|
|
462
|
+
// Skill `paths` use the minimal glob set logmind already uses
|
|
463
|
+
// (`*` matches non-slash, `**` matches across slashes, `?` single
|
|
464
|
+
// char). Anything fancier would need a real glob lib.
|
|
465
|
+
export function appliesToPr(skillContent, prPaths) {
|
|
466
|
+
const rule = readAppliesTo(skillContent);
|
|
467
|
+
if (rule === null) return true; // back-compat: no rule → applies
|
|
468
|
+
if (!Array.isArray(prPaths)) return true; // be permissive on bad input
|
|
469
|
+
for (const path of prPaths) {
|
|
470
|
+
if (typeof path !== 'string') continue;
|
|
471
|
+
for (const ext of rule.extensions) {
|
|
472
|
+
if (path.endsWith(ext)) return true;
|
|
473
|
+
}
|
|
474
|
+
for (const glob of rule.paths) {
|
|
475
|
+
if (globMatch(glob, path)) return true;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Minimal glob → regex: `**` → `.*`, `*` → `[^/]*`, `?` → `.`,
|
|
482
|
+
// everything else escaped. Anchored full-string match.
|
|
483
|
+
function globMatch(glob, path) {
|
|
484
|
+
const escaped = glob
|
|
485
|
+
.replace(/([.+^${}()|[\]\\])/g, '\\$1')
|
|
486
|
+
.replace(/\*\*/g, '__DOUBLESTAR__')
|
|
487
|
+
.replace(/\*/g, '[^/]*')
|
|
488
|
+
.replace(/__DOUBLESTAR__/g, '.*')
|
|
489
|
+
.replace(/\?/g, '.');
|
|
490
|
+
return new RegExp(`^${escaped}$`).test(path);
|
|
491
|
+
}
|
|
492
|
+
|
|
386
493
|
// Partition a set of loaded skills into {shared, dedicated} buckets per
|
|
387
494
|
// each skill's review_mode frontmatter. Expects skills with a `content`
|
|
388
495
|
// field (SKILL.md text). Skills without content default to `shared`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clud-bug",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.21",
|
|
4
4
|
"description": "Skill-driven Claude PR review. Ship a brand-voice skill, get brand reviews. Each finding cites the skill that motivated it. CLI installs the workflow + a baseline kit; add more from skills.sh.",
|
|
5
5
|
"homepage": "https://cludbug.dev",
|
|
6
6
|
"bugs": "https://github.com/thrillmade/clud-bug/issues",
|
|
@@ -156,6 +156,6 @@ jobs:
|
|
|
156
156
|
# Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
|
|
157
157
|
- name: Strict mode — fail check on critical findings
|
|
158
158
|
if: success()
|
|
159
|
-
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.
|
|
159
|
+
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.21
|
|
160
160
|
with:
|
|
161
161
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -156,6 +156,6 @@ jobs:
|
|
|
156
156
|
# Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
|
|
157
157
|
- name: Strict mode — fail check on critical findings
|
|
158
158
|
if: success()
|
|
159
|
-
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.
|
|
159
|
+
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.21
|
|
160
160
|
with:
|
|
161
161
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -247,6 +247,6 @@ jobs:
|
|
|
247
247
|
# Letting the action's own failure fail the check is louder and right.
|
|
248
248
|
- name: Strict mode — fail check on critical findings
|
|
249
249
|
if: success()
|
|
250
|
-
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.
|
|
250
|
+
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.21
|
|
251
251
|
with:
|
|
252
252
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|