clud-bug 0.5.9 → 0.5.10

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/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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clud-bug",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
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: v3
1
+ # clud-bug-template-version: v4
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
@@ -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.8
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: v3
1
+ # clud-bug-template-version: v4
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
@@ -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.8
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: v3
1
+ # clud-bug-template-version: v4
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
@@ -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.8
260
+ uses: thrillmot/clud-bug/.github/actions/strict-mode-gate@v0.5.10
257
261
  with:
258
262
  github-token: ${{ secrets.GITHUB_TOKEN }}