clud-bug 0.5.9 → 0.5.11
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/bin/clud-bug.js +6 -2
- package/lib/render.js +16 -2
- package/lib/skills.js +68 -0
- package/lib/update.js +6 -2
- package/package.json +1 -1
- package/templates/audit.yml.tmpl +2 -2
- package/templates/workflow-py.yml.tmpl +5 -3
- package/templates/workflow-ts.yml.tmpl +5 -3
- package/templates/workflow.yml.tmpl +7 -3
package/bin/clud-bug.js
CHANGED
|
@@ -183,7 +183,9 @@ async function runInit(args) {
|
|
|
183
183
|
|
|
184
184
|
// Install the audit workflow alongside the per-PR review one.
|
|
185
185
|
// Manual-trigger by default; users opt into the cron by uncommenting.
|
|
186
|
-
|
|
186
|
+
// Routed through renderFile so {{CCA_VERSION}} substitution pins
|
|
187
|
+
// claude-code-action consistently with the review workflow.
|
|
188
|
+
const auditTmpl = await renderFile(join(TEMPLATES, 'audit.yml.tmpl'), {});
|
|
187
189
|
const auditPath = join(cwd, '.github', 'workflows', 'clud-bug-audit.yml');
|
|
188
190
|
await writeFile(auditPath, auditTmpl);
|
|
189
191
|
log(` wrote ${rel(cwd, auditPath)}`);
|
|
@@ -191,7 +193,9 @@ async function runInit(args) {
|
|
|
191
193
|
// Install the self-update workflow. Cron weekly Mondays 12:00 UTC; opens
|
|
192
194
|
// a PR if a newer clud-bug version is published. Disable by deleting the
|
|
193
195
|
// file or pinning via .claude/skills/.clud-bug.json.
|
|
194
|
-
|
|
196
|
+
// Routed through renderFile for parity (no CCA ref today but future
|
|
197
|
+
// tokens should propagate uniformly).
|
|
198
|
+
const selfUpdateTmpl = await renderFile(join(TEMPLATES, 'self-update.yml.tmpl'), {});
|
|
195
199
|
const selfUpdatePath = join(cwd, '.github', 'workflows', 'clud-bug-self-update.yml');
|
|
196
200
|
await writeFile(selfUpdatePath, selfUpdateTmpl);
|
|
197
201
|
log(` wrote ${rel(cwd, selfUpdatePath)}`);
|
package/lib/render.js
CHANGED
|
@@ -2,12 +2,26 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
|
|
3
3
|
const PLACEHOLDER_RE = /\{\{([A-Z_]+)\}\}/g;
|
|
4
4
|
|
|
5
|
+
// Default values for substitution tokens that every template uses.
|
|
6
|
+
// Callers can override per-render by passing the same key in `vars`.
|
|
7
|
+
//
|
|
8
|
+
// CCA_VERSION pins `anthropics/claude-code-action` to a specific tag in
|
|
9
|
+
// every shipped workflow. Without this, templates resolved `@v1` (the
|
|
10
|
+
// floating major), so upstream changes could silently land in installed
|
|
11
|
+
// workflows mid-cycle. Bumping the pin requires a clud-bug release, which
|
|
12
|
+
// makes the upgrade visible + lets users opt out by pinning a different
|
|
13
|
+
// version in their own forked workflow.
|
|
14
|
+
export const DEFAULTS = {
|
|
15
|
+
CCA_VERSION: 'v1.0.133',
|
|
16
|
+
};
|
|
17
|
+
|
|
5
18
|
export function render(template, vars) {
|
|
19
|
+
const merged = { ...DEFAULTS, ...vars };
|
|
6
20
|
return template.replace(PLACEHOLDER_RE, (match, key) => {
|
|
7
|
-
if (!(key in
|
|
21
|
+
if (!(key in merged)) {
|
|
8
22
|
throw new Error(`Missing template variable: ${key}`);
|
|
9
23
|
}
|
|
10
|
-
return
|
|
24
|
+
return merged[key];
|
|
11
25
|
});
|
|
12
26
|
}
|
|
13
27
|
|
package/lib/skills.js
CHANGED
|
@@ -400,4 +400,72 @@ export function partitionByReviewMode(skills) {
|
|
|
400
400
|
return { shared, dedicated };
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
// Pull the line for `skillName` from a clud-bug review's `### Per-skill scan`
|
|
404
|
+
// block. The block format (set by the v3+ prompt) is one line per loaded skill:
|
|
405
|
+
//
|
|
406
|
+
// ### Per-skill scan
|
|
407
|
+
// - [critical-issues-only]: scanned all paths. 2 critical findings below.
|
|
408
|
+
// - [brand-voice-review]: scanned 3 microcopy changes. 1 finding (below).
|
|
409
|
+
// - [pii-and-compliance]: scanned analytics + logging. 0 findings.
|
|
410
|
+
//
|
|
411
|
+
// Returns the OUTCOME portion (everything after the `- [name]: ` prefix), with
|
|
412
|
+
// trailing whitespace stripped. Returns null if the skill isn't mentioned, the
|
|
413
|
+
// comment has no Per-skill scan block, or `comment` is empty.
|
|
414
|
+
//
|
|
415
|
+
// The brackets in the line prefix anchor the match so a partial-name collision
|
|
416
|
+
// (e.g. `brand-voice` finding `brand-voice-review`) is impossible.
|
|
417
|
+
export function extractPerSkillLine(comment, skillName) {
|
|
418
|
+
if (typeof comment !== 'string' || !comment) return null;
|
|
419
|
+
if (typeof skillName !== 'string' || !skillName) return null;
|
|
420
|
+
// Escape regex metacharacters in the skill name. A skill name with a `.` or
|
|
421
|
+
// `+` would otherwise alter the match. Skills are conventionally kebab-case,
|
|
422
|
+
// but defense in depth is cheap.
|
|
423
|
+
const escaped = skillName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
424
|
+
// Anchor on the bracket-prefix; tolerate optional leading whitespace and
|
|
425
|
+
// dash. The OUTCOME is everything from after `]:` to end-of-line.
|
|
426
|
+
const re = new RegExp(`^\\s*-\\s*\\[${escaped}\\]:\\s*(.+?)\\s*$`, 'm');
|
|
427
|
+
const m = comment.match(re);
|
|
428
|
+
return m ? m[1] : null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Classify a Per-skill scan outcome line into the check-run conclusion the
|
|
432
|
+
// composite action will emit for that skill. Source of truth for the BB.3
|
|
433
|
+
// gate decision — the v0.5.10 composite shells out to node + this helper
|
|
434
|
+
// rather than parsing in bash, so the gate has unit-test coverage and the
|
|
435
|
+
// v0.6 App can reuse the same classification when it routes its own
|
|
436
|
+
// parallel calls.
|
|
437
|
+
//
|
|
438
|
+
// Contract:
|
|
439
|
+
// - `null` (skill not mentioned in the review) → 'failure'
|
|
440
|
+
// - line contains "0 findings" / "0 finding" as a STANDALONE TOKEN → 'success'
|
|
441
|
+
// - line contains "n/a" as a standalone token → 'success'
|
|
442
|
+
// - empty line (bot emitted "- [name]:" with no outcome) → 'failure'
|
|
443
|
+
// - otherwise (typically "N finding" / "N findings" with N>0) → 'failure'
|
|
444
|
+
//
|
|
445
|
+
// Why null → failure (not neutral): GitHub's branch-protection contract
|
|
446
|
+
// treats `conclusion: neutral` as PASSING for required status checks —
|
|
447
|
+
// only `failure`, `cancelled`, `timed_out`, `action_required` block merge.
|
|
448
|
+
// A strictSkills entry that doesn't appear in the per-skill scan block
|
|
449
|
+
// (typo, prompt regression, mid-review race) emitting `neutral` would
|
|
450
|
+
// silently pass branch protection, defeating the gate the user opted into.
|
|
451
|
+
// Failing loud is the right posture for a gate that ships with "strict" in
|
|
452
|
+
// its name; the cost is a re-run if a bot mid-review somehow drops a skill.
|
|
453
|
+
//
|
|
454
|
+
// The "0 findings" match is anchored on a leading word boundary so "10
|
|
455
|
+
// findings" / "100 findings" don't substring-match to success — the exact
|
|
456
|
+
// bug that v0.5.10's first revision had, caught by clud-bug-review + claude-
|
|
457
|
+
// review on PR #57.
|
|
458
|
+
export function classifyPerSkillOutcome(outcomeLine) {
|
|
459
|
+
if (outcomeLine == null) return 'failure';
|
|
460
|
+
const text = String(outcomeLine);
|
|
461
|
+
// 0 findings / 0 finding — anchored: NOT preceded by a digit. So "10 findings"
|
|
462
|
+
// doesn't match (the `0 findings` substring has `1` before it), and "0
|
|
463
|
+
// findings" / " 0 findings." both match.
|
|
464
|
+
if (/(^|[^0-9])0\s+findings?\b/i.test(text)) return 'success';
|
|
465
|
+
// n/a — anchored on word boundaries either side (so "n/a." at sentence end
|
|
466
|
+
// matches but "diagnostics" would not contain a matching n/a).
|
|
467
|
+
if (/\bn\/a\b/i.test(text)) return 'success';
|
|
468
|
+
return 'failure';
|
|
469
|
+
}
|
|
470
|
+
|
|
403
471
|
export const _internal = { normalizeList, sanitizeSlug, entryKey, MAX_SKILLS, API_BASE, MANIFEST_FILE };
|
package/lib/update.js
CHANGED
|
@@ -51,16 +51,20 @@ export async function runUpdate({
|
|
|
51
51
|
await maybeRefreshVersioned(join(cwd, '.github/workflows/clud-bug-review.yml'), newReview, changed, unchanged, skipped, 'review workflow');
|
|
52
52
|
|
|
53
53
|
// 2. Re-render audit workflow if it's installed (init from v0.3+ ships it).
|
|
54
|
+
// Routed through renderFile (was raw readFile pre-v0.5.11) so
|
|
55
|
+
// {{CCA_VERSION}} substitution lands in audit alongside review.
|
|
54
56
|
const auditPath = join(cwd, '.github/workflows/clud-bug-audit.yml');
|
|
55
57
|
if (await pathExists(auditPath)) {
|
|
56
|
-
const newAudit = await
|
|
58
|
+
const newAudit = await renderFile(join(templatesDir, 'audit.yml.tmpl'), {});
|
|
57
59
|
await maybeRefreshVersioned(auditPath, newAudit, changed, unchanged, skipped, 'audit workflow');
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// 2b. Re-render self-update workflow if installed (init from v0.4+ ships it).
|
|
63
|
+
// Routed through renderFile for parity — no CCA ref in self-update today
|
|
64
|
+
// but future tokens should propagate uniformly without another refactor.
|
|
61
65
|
const selfUpdatePath = join(cwd, '.github/workflows/clud-bug-self-update.yml');
|
|
62
66
|
if (await pathExists(selfUpdatePath)) {
|
|
63
|
-
const newSelfUpdate = await
|
|
67
|
+
const newSelfUpdate = await renderFile(join(templatesDir, 'self-update.yml.tmpl'), {});
|
|
64
68
|
await maybeRefreshVersioned(selfUpdatePath, newSelfUpdate, changed, unchanged, skipped, 'self-update workflow');
|
|
65
69
|
}
|
|
66
70
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clud-bug",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.11",
|
|
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",
|
package/templates/audit.yml.tmpl
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# clud-bug-template-version:
|
|
1
|
+
# clud-bug-template-version: v2
|
|
2
2
|
name: Clud Bug 🐛 Audit
|
|
3
3
|
|
|
4
4
|
# A scheduled / on-demand walk through the whole habitat (or a recent slice).
|
|
@@ -89,7 +89,7 @@ jobs:
|
|
|
89
89
|
git push -u origin "$BRANCH"
|
|
90
90
|
echo "stub_written=true" >> "$GITHUB_OUTPUT"
|
|
91
91
|
|
|
92
|
-
- uses: anthropics/claude-code-action@
|
|
92
|
+
- uses: anthropics/claude-code-action@{{CCA_VERSION}}
|
|
93
93
|
if: steps.prep.outputs.stub_written == 'true'
|
|
94
94
|
env:
|
|
95
95
|
AUDIT_DATE: ${{ steps.prep.outputs.date }}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# clud-bug-template-version:
|
|
1
|
+
# clud-bug-template-version: v5
|
|
2
2
|
name: Clud Bug 🐛 Crawls Your Code
|
|
3
3
|
|
|
4
4
|
on:
|
|
@@ -12,6 +12,8 @@ jobs:
|
|
|
12
12
|
contents: read
|
|
13
13
|
pull-requests: write
|
|
14
14
|
id-token: write
|
|
15
|
+
# checks: write — composite emits per-skill check-runs (BB.3).
|
|
16
|
+
checks: write
|
|
15
17
|
|
|
16
18
|
steps:
|
|
17
19
|
- uses: actions/checkout@v6
|
|
@@ -48,7 +50,7 @@ jobs:
|
|
|
48
50
|
echo "::error::Set it: Settings → Secrets and variables → Actions → New repository secret."
|
|
49
51
|
exit 1
|
|
50
52
|
|
|
51
|
-
- uses: anthropics/claude-code-action@
|
|
53
|
+
- uses: anthropics/claude-code-action@{{CCA_VERSION}}
|
|
52
54
|
if: steps.guard.outputs.skip != 'true'
|
|
53
55
|
env:
|
|
54
56
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
@@ -220,6 +222,6 @@ jobs:
|
|
|
220
222
|
# Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
|
|
221
223
|
- name: Strict mode — fail check on critical findings
|
|
222
224
|
if: success()
|
|
223
|
-
uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.
|
|
225
|
+
uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.10
|
|
224
226
|
with:
|
|
225
227
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# clud-bug-template-version:
|
|
1
|
+
# clud-bug-template-version: v5
|
|
2
2
|
name: Clud Bug 🐛 Crawls Your Code
|
|
3
3
|
|
|
4
4
|
on:
|
|
@@ -12,6 +12,8 @@ jobs:
|
|
|
12
12
|
contents: read
|
|
13
13
|
pull-requests: write
|
|
14
14
|
id-token: write
|
|
15
|
+
# checks: write — composite emits per-skill check-runs (BB.3).
|
|
16
|
+
checks: write
|
|
15
17
|
|
|
16
18
|
steps:
|
|
17
19
|
- uses: actions/checkout@v6
|
|
@@ -48,7 +50,7 @@ jobs:
|
|
|
48
50
|
echo "::error::Set it: Settings → Secrets and variables → Actions → New repository secret."
|
|
49
51
|
exit 1
|
|
50
52
|
|
|
51
|
-
- uses: anthropics/claude-code-action@
|
|
53
|
+
- uses: anthropics/claude-code-action@{{CCA_VERSION}}
|
|
52
54
|
if: steps.guard.outputs.skip != 'true'
|
|
53
55
|
env:
|
|
54
56
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
@@ -221,6 +223,6 @@ jobs:
|
|
|
221
223
|
# Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
|
|
222
224
|
- name: Strict mode — fail check on critical findings
|
|
223
225
|
if: success()
|
|
224
|
-
uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.
|
|
226
|
+
uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.10
|
|
225
227
|
with:
|
|
226
228
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# clud-bug-template-version:
|
|
1
|
+
# clud-bug-template-version: v5
|
|
2
2
|
name: Clud Bug 🐛 Crawls Your Code
|
|
3
3
|
|
|
4
4
|
on:
|
|
@@ -12,6 +12,10 @@ jobs:
|
|
|
12
12
|
contents: read
|
|
13
13
|
pull-requests: write
|
|
14
14
|
id-token: write
|
|
15
|
+
# checks: write — composite action emits per-skill check-runs via
|
|
16
|
+
# the GitHub Checks API for any skill in .clud-bug.json's strictSkills
|
|
17
|
+
# list (BB.3, v0.5.10+). No-op when strictSkills is unset.
|
|
18
|
+
checks: write
|
|
15
19
|
|
|
16
20
|
steps:
|
|
17
21
|
- uses: actions/checkout@v6
|
|
@@ -71,7 +75,7 @@ jobs:
|
|
|
71
75
|
echo "::error::Without it, Clud Bug review is a silent no-op."
|
|
72
76
|
exit 1
|
|
73
77
|
|
|
74
|
-
- uses: anthropics/claude-code-action@
|
|
78
|
+
- uses: anthropics/claude-code-action@{{CCA_VERSION}}
|
|
75
79
|
# Skip the action when guard already posted the bot/fork advisory.
|
|
76
80
|
if: steps.guard.outputs.skip != 'true'
|
|
77
81
|
env:
|
|
@@ -253,6 +257,6 @@ jobs:
|
|
|
253
257
|
# Letting the action's own failure fail the check is louder and right.
|
|
254
258
|
- name: Strict mode — fail check on critical findings
|
|
255
259
|
if: success()
|
|
256
|
-
uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.
|
|
260
|
+
uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.10
|
|
257
261
|
with:
|
|
258
262
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|