clud-bug 0.5.10 → 0.5.12

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 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
- const auditTmpl = await readFile(join(TEMPLATES, 'audit.yml.tmpl'), 'utf8');
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
- const selfUpdateTmpl = await readFile(join(TEMPLATES, 'self-update.yml.tmpl'), 'utf8');
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 vars)) {
21
+ if (!(key in merged)) {
8
22
  throw new Error(`Missing template variable: ${key}`);
9
23
  }
10
- return vars[key];
24
+ return merged[key];
11
25
  });
12
26
  }
13
27
 
package/lib/skills.js CHANGED
@@ -428,6 +428,98 @@ export function extractPerSkillLine(comment, skillName) {
428
428
  return m ? m[1] : null;
429
429
  }
430
430
 
431
+ // Find the latest clud-bug review header line from a list of PR comments.
432
+ // Source of truth for the v0.5.x strict-mode-gate header selection — the
433
+ // composite action shells out to node + this helper rather than parsing
434
+ // in bash, so the gate has unit-test coverage and the v0.6 App can reuse
435
+ // the same logic.
436
+ //
437
+ // Contract (called by .github/actions/strict-mode-gate/action.yml):
438
+ // - Walk `comments` (newest-first per gh api ?sort=created&direction=desc).
439
+ // - Skip comments not authored by `botLogin`.
440
+ // - For each remaining comment, find the FIRST line starting with the
441
+ // H2 sentinel `## 🐛 Clud Bug review`. If present, return that line.
442
+ // - Return null if no matching comment exists.
443
+ //
444
+ // Why this isn't `comments.find(c => c.body.startsWith("## 🐛 Clud Bug review"))`:
445
+ // claude-code-action prepends a `**Claude finished @user's task in Nm Ns**`
446
+ // preamble to every bot comment, so the H2 review header never appears at
447
+ // body position 0. The pre-v0.5.12 composite used `.body | startswith(...)`
448
+ // in jq and matched ZERO comments in practice — silently disabling strict
449
+ // mode on every install with strictMode: true. Caught when this repo
450
+ // dogfooded BB.3 on PR #60: bot wrote "— critical findings" header, gate
451
+ // passed anyway.
452
+ //
453
+ // The line-anchored extraction preserves the original "don't trip on
454
+ // quoted sentinels in body text" property: a comment that mentions the
455
+ // strict-mode header in prose (inline-code, blockquote) won't match
456
+ // because the quoted version isn't at start-of-line.
457
+ export function selectReviewHeader(comments, botLogin) {
458
+ if (!Array.isArray(comments)) return null;
459
+ if (typeof botLogin !== 'string' || !botLogin) return null;
460
+ for (const c of comments) {
461
+ if (!c || typeof c !== 'object') continue;
462
+ const author = c.user?.login;
463
+ const body = c.body;
464
+ if (author !== botLogin || typeof body !== 'string') continue;
465
+ const headerLine = extractFirstReviewHeaderLine(body);
466
+ if (headerLine) return headerLine;
467
+ }
468
+ return null;
469
+ }
470
+
471
+ // Pull the FIRST line of `body` that starts with the H2 sentinel.
472
+ // Exported separately so callers can extract a header from a known body
473
+ // without re-running the comment filter (useful in tests + the v0.6 App).
474
+ export function extractFirstReviewHeaderLine(body) {
475
+ if (typeof body !== 'string') return null;
476
+ const m = body.match(/^## 🐛 Clud Bug review[^\n]*/m);
477
+ return m ? m[0] : null;
478
+ }
479
+
480
+ // Companion to selectReviewHeader: returns the FULL BODY of the latest
481
+ // clud-bug review comment from `botLogin`, not just its header line.
482
+ // Same filter contract (line-anchored H2 sentinel, claude-code-action
483
+ // preamble tolerated). Used by the BB.3 per-skill check-runs step,
484
+ // which needs the body to extract per-skill outcome lines from the
485
+ // "### Per-skill scan" block — the header alone isn't enough.
486
+ //
487
+ // Returns null if no matching comment exists. The composite action
488
+ // treats null as "no review yet; emit no check-runs" (the same posture
489
+ // that pre-v0.5.12 bash code intended via the `[ -z "$LATEST" ]` branch,
490
+ // only now actually reachable instead of always-fires-due-to-bug).
491
+ //
492
+ // Same-bug fix as selectReviewHeader: PR #61 caught that BB.3 step 2
493
+ // of the composite still used the broken `.body | startswith(...)` jq
494
+ // filter even after step 1 was refactored, leaving per-skill check-runs
495
+ // silently disabled on every install with strictSkills since v0.5.10.
496
+ export function selectReviewBody(comments, botLogin) {
497
+ if (!Array.isArray(comments)) return null;
498
+ if (typeof botLogin !== 'string' || !botLogin) return null;
499
+ for (const c of comments) {
500
+ if (!c || typeof c !== 'object') continue;
501
+ const author = c.user?.login;
502
+ const body = c.body;
503
+ if (author !== botLogin || typeof body !== 'string') continue;
504
+ if (extractFirstReviewHeaderLine(body)) return body;
505
+ }
506
+ return null;
507
+ }
508
+
509
+ // Decide whether a review-header line is the strict-mode "critical findings"
510
+ // verdict that should fail the gate. Mirrors the v0.5.x bash predicate
511
+ // `grep -q "Clud Bug review — critical findings"`.
512
+ //
513
+ // Returns false for null/non-string input so a "no header found" path
514
+ // (selectReviewHeader returning null) safely falls through to the gate
515
+ // passing — which is the right posture: if the bot didn't post a review
516
+ // with the strict-mode header, there's nothing for the gate to fail on.
517
+ // "Loud failure for missing manifest" is handled upstream in the composite.
518
+ export function isCriticalReviewHeader(headerLine) {
519
+ if (typeof headerLine !== 'string') return false;
520
+ return /Clud Bug review — critical findings/.test(headerLine);
521
+ }
522
+
431
523
  // Classify a Per-skill scan outcome line into the check-run conclusion the
432
524
  // composite action will emit for that skill. Source of truth for the BB.3
433
525
  // gate decision — the v0.5.10 composite shells out to node + this helper
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 readFile(join(templatesDir, 'audit.yml.tmpl'), 'utf8');
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 readFile(join(templatesDir, 'self-update.yml.tmpl'), 'utf8');
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.10",
3
+ "version": "0.5.12",
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",
@@ -1,4 +1,4 @@
1
- # clud-bug-template-version: v1
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@v1
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: v4
1
+ # clud-bug-template-version: v6
2
2
  name: Clud Bug 🐛 Crawls Your Code
3
3
 
4
4
  on:
@@ -50,7 +50,7 @@ jobs:
50
50
  echo "::error::Set it: Settings → Secrets and variables → Actions → New repository secret."
51
51
  exit 1
52
52
 
53
- - uses: anthropics/claude-code-action@v1
53
+ - uses: anthropics/claude-code-action@{{CCA_VERSION}}
54
54
  if: steps.guard.outputs.skip != 'true'
55
55
  env:
56
56
  PR_NUMBER: ${{ github.event.pull_request.number }}
@@ -222,6 +222,6 @@ jobs:
222
222
  # Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
223
223
  - name: Strict mode — fail check on critical findings
224
224
  if: success()
225
- uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.10
225
+ uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.12
226
226
  with:
227
227
  github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -1,4 +1,4 @@
1
- # clud-bug-template-version: v4
1
+ # clud-bug-template-version: v6
2
2
  name: Clud Bug 🐛 Crawls Your Code
3
3
 
4
4
  on:
@@ -50,7 +50,7 @@ jobs:
50
50
  echo "::error::Set it: Settings → Secrets and variables → Actions → New repository secret."
51
51
  exit 1
52
52
 
53
- - uses: anthropics/claude-code-action@v1
53
+ - uses: anthropics/claude-code-action@{{CCA_VERSION}}
54
54
  if: steps.guard.outputs.skip != 'true'
55
55
  env:
56
56
  PR_NUMBER: ${{ github.event.pull_request.number }}
@@ -223,6 +223,6 @@ jobs:
223
223
  # Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
224
224
  - name: Strict mode — fail check on critical findings
225
225
  if: success()
226
- uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.10
226
+ uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.12
227
227
  with:
228
228
  github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -1,4 +1,4 @@
1
- # clud-bug-template-version: v4
1
+ # clud-bug-template-version: v6
2
2
  name: Clud Bug 🐛 Crawls Your Code
3
3
 
4
4
  on:
@@ -75,7 +75,7 @@ jobs:
75
75
  echo "::error::Without it, Clud Bug review is a silent no-op."
76
76
  exit 1
77
77
 
78
- - uses: anthropics/claude-code-action@v1
78
+ - uses: anthropics/claude-code-action@{{CCA_VERSION}}
79
79
  # Skip the action when guard already posted the bot/fork advisory.
80
80
  if: steps.guard.outputs.skip != 'true'
81
81
  env:
@@ -257,6 +257,6 @@ jobs:
257
257
  # Letting the action's own failure fail the check is louder and right.
258
258
  - name: Strict mode — fail check on critical findings
259
259
  if: success()
260
- uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.10
260
+ uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.12
261
261
  with:
262
262
  github-token: ${{ secrets.GITHUB_TOKEN }}