clud-bug 0.6.28 → 0.6.29
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 +108 -3
- package/lib/skill-usage.js +3 -2
- package/package.json +1 -1
- package/templates/workflow-py.yml.tmpl +25 -1
- package/templates/workflow-ts.yml.tmpl +25 -1
- package/templates/workflow.yml.tmpl +39 -1
package/bin/clud-bug.js
CHANGED
|
@@ -84,12 +84,18 @@ Commands:
|
|
|
84
84
|
\`.claude/skills/.clud-bug.json\` usage block + renders
|
|
85
85
|
archive-candidate / stale / new / healthy status per skill.
|
|
86
86
|
Read-only — no automation acts on the output. Humans
|
|
87
|
-
decide which skills to prune.
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
decide which skills to prune. v0.6.29 wires the workflow
|
|
88
|
+
post-step that auto-writes per-review deltas; v0.6.30
|
|
89
|
+
will aggregate across runs into the dashboard read path.
|
|
90
90
|
eval Run the golden-set regression gate against the rendered review
|
|
91
91
|
prompt (must-contain / must-not-contain / byte-budget). Same as
|
|
92
92
|
\`node --test test/prompts.eval.test.js\` but works from any cwd.
|
|
93
|
+
update-skill-usage Update the .claude/skills/.clud-bug.json usage block from
|
|
94
|
+
a structured-output JSON payload (the action's
|
|
95
|
+
\`outputs.structured_output\`). Called as a workflow
|
|
96
|
+
post-step alongside \`render\` (v0.6.29 / Component 4).
|
|
97
|
+
Pipe the JSON to stdin. Idempotent + atomic write.
|
|
98
|
+
Silent no-op on empty stdin (parity with \`render\`).
|
|
93
99
|
render --stdin Render a structured-output JSON payload (the action's
|
|
94
100
|
\`outputs.structured_output\`, piped via stdin) to the
|
|
95
101
|
GitHub-markdown summary comment shape. Invoked by the
|
|
@@ -147,6 +153,7 @@ async function main() {
|
|
|
147
153
|
case 'usage': return runUsage(args);
|
|
148
154
|
case 'eval': return runEval();
|
|
149
155
|
case 'render': return runRender(args);
|
|
156
|
+
case 'update-skill-usage': return runUpdateSkillUsage(args);
|
|
150
157
|
default:
|
|
151
158
|
process.stderr.write(`Unknown command: ${cmd || '(none)'}\n\n${HELP}`);
|
|
152
159
|
process.exit(2);
|
|
@@ -193,6 +200,104 @@ async function runRender(args) {
|
|
|
193
200
|
}
|
|
194
201
|
}
|
|
195
202
|
|
|
203
|
+
// v0.6.29 — Component 4. Pipe the action's structured_output through
|
|
204
|
+
// the skill-usage data layer (v0.6.28) + write the merged result back
|
|
205
|
+
// to .claude/skills/.clud-bug.json atomically.
|
|
206
|
+
//
|
|
207
|
+
// Workflow integration (post-step in workflow.yml.tmpl):
|
|
208
|
+
//
|
|
209
|
+
// echo "${{ steps.review.outputs.structured_output }}" \
|
|
210
|
+
// | npx clud-bug@latest update-skill-usage --stdin
|
|
211
|
+
//
|
|
212
|
+
// Runs AFTER the render post-step. Silent no-op on empty stdin
|
|
213
|
+
// (same contract as `render` — preserves the workflow's existing
|
|
214
|
+
// "skip both if empty" branch). Idempotent: running on the same JSON
|
|
215
|
+
// twice produces the same result.
|
|
216
|
+
async function runUpdateSkillUsage(args) {
|
|
217
|
+
const fs = await import('node:fs/promises');
|
|
218
|
+
const path = await import('node:path');
|
|
219
|
+
const {
|
|
220
|
+
computeSkillUsageDelta,
|
|
221
|
+
mergeSkillUsage,
|
|
222
|
+
} = await import('../lib/skill-usage.js');
|
|
223
|
+
|
|
224
|
+
if (!args.stdin) {
|
|
225
|
+
process.stderr.write('clud-bug update-skill-usage: --stdin is required.\n');
|
|
226
|
+
process.exit(2);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let raw = '';
|
|
230
|
+
for await (const chunk of process.stdin) raw += chunk;
|
|
231
|
+
raw = raw.trim();
|
|
232
|
+
if (!raw) {
|
|
233
|
+
// Empty structured_output → render is also skipped → nothing to
|
|
234
|
+
// update. Match the render contract: exit 0 with a stderr note.
|
|
235
|
+
process.stderr.write('clud-bug update-skill-usage: stdin empty — no usage update.\n');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let reviewJson;
|
|
240
|
+
try {
|
|
241
|
+
reviewJson = JSON.parse(raw);
|
|
242
|
+
} catch (e) {
|
|
243
|
+
process.stderr.write(`clud-bug update-skill-usage: invalid JSON: ${e.message}\n`);
|
|
244
|
+
process.exit(2);
|
|
245
|
+
}
|
|
246
|
+
if (!reviewJson || typeof reviewJson !== 'object') {
|
|
247
|
+
process.stderr.write('clud-bug update-skill-usage: payload must be a JSON object.\n');
|
|
248
|
+
process.exit(2);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Compute per-review delta. Empty delta is fine — just means no
|
|
252
|
+
// skills loaded or cited (workflow-only PRs, e.g.).
|
|
253
|
+
const delta = computeSkillUsageDelta(reviewJson);
|
|
254
|
+
if (Object.keys(delta).length === 0) {
|
|
255
|
+
process.stderr.write('clud-bug update-skill-usage: no skills in payload — nothing to record.\n');
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Read existing .clud-bug.json. The path is canonical:
|
|
260
|
+
// .claude/skills/.clud-bug.json relative to cwd (the workflow runs
|
|
261
|
+
// from the repo root).
|
|
262
|
+
const jsonPath = path.resolve(process.cwd(), '.claude', 'skills', '.clud-bug.json');
|
|
263
|
+
let parsed;
|
|
264
|
+
try {
|
|
265
|
+
const existingRaw = await fs.readFile(jsonPath, 'utf-8');
|
|
266
|
+
parsed = JSON.parse(existingRaw);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
if (err.code === 'ENOENT') {
|
|
269
|
+
process.stderr.write(
|
|
270
|
+
`clud-bug update-skill-usage: no .clud-bug.json at ${jsonPath} — skipping. ` +
|
|
271
|
+
`Run \`npx clud-bug init\` first.\n`
|
|
272
|
+
);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
process.stderr.write(`clud-bug update-skill-usage: parse failed: ${err.message}\n`);
|
|
276
|
+
process.exit(2);
|
|
277
|
+
}
|
|
278
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
279
|
+
process.stderr.write('clud-bug update-skill-usage: .clud-bug.json malformed.\n');
|
|
280
|
+
process.exit(2);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const existingUsage = parsed.usage || {};
|
|
284
|
+
const timestamp = new Date().toISOString();
|
|
285
|
+
const mergedUsage = mergeSkillUsage(existingUsage, delta, timestamp);
|
|
286
|
+
parsed.usage = mergedUsage;
|
|
287
|
+
|
|
288
|
+
// Write back ATOMICALLY: temp file + rename. Guards against a
|
|
289
|
+
// crashed write leaving the JSON half-written + unparseable on next
|
|
290
|
+
// read (which would brick the entire skill catalog).
|
|
291
|
+
const tmpPath = jsonPath + '.tmp';
|
|
292
|
+
const serialized = JSON.stringify(parsed, null, 2) + '\n';
|
|
293
|
+
await fs.writeFile(tmpPath, serialized, 'utf-8');
|
|
294
|
+
await fs.rename(tmpPath, jsonPath);
|
|
295
|
+
|
|
296
|
+
const skillCount = Object.keys(delta).length;
|
|
297
|
+
ok(`update-skill-usage: merged ${skillCount} skill${skillCount === 1 ? '' : 's'} from review`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
196
301
|
// 0.0.E (v0.6.17): thin wrapper around the golden-set test file. Devs
|
|
197
302
|
// who follow the README invoke `clud-bug eval` — this routes to the
|
|
198
303
|
// same `node --test` runner CI uses, so dev and CI verdicts match.
|
package/lib/skill-usage.js
CHANGED
|
@@ -225,8 +225,9 @@ export function formatHealthDashboard(rows) {
|
|
|
225
225
|
return (
|
|
226
226
|
'Skill health: no usage data yet.\n\n' +
|
|
227
227
|
'Usage data accumulates after clud-bug reviews land in your repo.\n' +
|
|
228
|
-
'
|
|
229
|
-
'
|
|
228
|
+
'v0.6.29 wires up per-review delta writes via the workflow post-step\n' +
|
|
229
|
+
'and uploads them as 90-day artifacts. v0.6.30 will add cross-review\n' +
|
|
230
|
+
'aggregation so this dashboard reads the full artifact stream.'
|
|
230
231
|
);
|
|
231
232
|
}
|
|
232
233
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clud-bug",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.29",
|
|
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",
|
|
@@ -283,6 +283,30 @@ jobs:
|
|
|
283
283
|
|
|
284
284
|
$CALIBRATION"
|
|
285
285
|
|
|
286
|
+
# v0.6.29 / Component 4 — see workflow.yml.tmpl for design notes.
|
|
287
|
+
- name: Update skill-usage tracking
|
|
288
|
+
if: success() && steps.clud-bug-review.outputs.structured_output != ''
|
|
289
|
+
continue-on-error: true
|
|
290
|
+
env:
|
|
291
|
+
STRUCTURED: ${{ steps.clud-bug-review.outputs.structured_output }}
|
|
292
|
+
run: |
|
|
293
|
+
set -euo pipefail
|
|
294
|
+
mkdir -p .claude/skills
|
|
295
|
+
if [ ! -f .claude/skills/.clud-bug.json ]; then
|
|
296
|
+
echo '{"version": 1}' > .claude/skills/.clud-bug.json
|
|
297
|
+
fi
|
|
298
|
+
printf '%s\n' "$STRUCTURED" | npx --yes clud-bug@{{CLUD_BUG_VERSION}} update-skill-usage --stdin
|
|
299
|
+
echo "::notice title=skill-usage::recorded review delta to ephemeral .clud-bug.json (v0.6.30 will add cross-review aggregation)"
|
|
300
|
+
|
|
301
|
+
- name: Upload skill-usage artifact
|
|
302
|
+
if: success() && steps.clud-bug-review.outputs.structured_output != ''
|
|
303
|
+
continue-on-error: true
|
|
304
|
+
uses: actions/upload-artifact@v4
|
|
305
|
+
with:
|
|
306
|
+
name: clud-bug-skill-usage-pr-${{ github.event.pull_request.number }}
|
|
307
|
+
path: .claude/skills/.clud-bug.json
|
|
308
|
+
retention-days: 90
|
|
309
|
+
|
|
286
310
|
# v0.6.26 / §5.5 Layer 6 fallback render-from-inlines — see workflow.yml.tmpl for design notes.
|
|
287
311
|
- name: Fallback summary (structured_output empty)
|
|
288
312
|
if: success() && steps.clud-bug-review.outputs.structured_output == ''
|
|
@@ -339,7 +363,7 @@ jobs:
|
|
|
339
363
|
# Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
|
|
340
364
|
- name: Strict mode — fail check on critical findings
|
|
341
365
|
if: success()
|
|
342
|
-
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.
|
|
366
|
+
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.29
|
|
343
367
|
with:
|
|
344
368
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
345
369
|
# v0.6.22 / 0.0.O: summary now posted by github-actions[bot].
|
|
@@ -283,6 +283,30 @@ jobs:
|
|
|
283
283
|
|
|
284
284
|
$CALIBRATION"
|
|
285
285
|
|
|
286
|
+
# v0.6.29 / Component 4 — see workflow.yml.tmpl for design notes.
|
|
287
|
+
- name: Update skill-usage tracking
|
|
288
|
+
if: success() && steps.clud-bug-review.outputs.structured_output != ''
|
|
289
|
+
continue-on-error: true
|
|
290
|
+
env:
|
|
291
|
+
STRUCTURED: ${{ steps.clud-bug-review.outputs.structured_output }}
|
|
292
|
+
run: |
|
|
293
|
+
set -euo pipefail
|
|
294
|
+
mkdir -p .claude/skills
|
|
295
|
+
if [ ! -f .claude/skills/.clud-bug.json ]; then
|
|
296
|
+
echo '{"version": 1}' > .claude/skills/.clud-bug.json
|
|
297
|
+
fi
|
|
298
|
+
printf '%s\n' "$STRUCTURED" | npx --yes clud-bug@{{CLUD_BUG_VERSION}} update-skill-usage --stdin
|
|
299
|
+
echo "::notice title=skill-usage::recorded review delta to ephemeral .clud-bug.json (v0.6.30 will add cross-review aggregation)"
|
|
300
|
+
|
|
301
|
+
- name: Upload skill-usage artifact
|
|
302
|
+
if: success() && steps.clud-bug-review.outputs.structured_output != ''
|
|
303
|
+
continue-on-error: true
|
|
304
|
+
uses: actions/upload-artifact@v4
|
|
305
|
+
with:
|
|
306
|
+
name: clud-bug-skill-usage-pr-${{ github.event.pull_request.number }}
|
|
307
|
+
path: .claude/skills/.clud-bug.json
|
|
308
|
+
retention-days: 90
|
|
309
|
+
|
|
286
310
|
# v0.6.26 / §5.5 Layer 6 fallback render-from-inlines — see workflow.yml.tmpl for design notes.
|
|
287
311
|
- name: Fallback summary (structured_output empty)
|
|
288
312
|
if: success() && steps.clud-bug-review.outputs.structured_output == ''
|
|
@@ -339,7 +363,7 @@ jobs:
|
|
|
339
363
|
# Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
|
|
340
364
|
- name: Strict mode — fail check on critical findings
|
|
341
365
|
if: success()
|
|
342
|
-
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.
|
|
366
|
+
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.29
|
|
343
367
|
with:
|
|
344
368
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
345
369
|
# v0.6.22 / 0.0.O: summary now posted by github-actions[bot].
|
|
@@ -494,6 +494,44 @@ jobs:
|
|
|
494
494
|
|
|
495
495
|
$CALIBRATION"
|
|
496
496
|
|
|
497
|
+
# v0.6.29 / Component 4: pipe structured_output through
|
|
498
|
+
# `clud-bug update-skill-usage` to compute the per-skill loads +
|
|
499
|
+
# citations delta from this one review. Writes to the ephemeral
|
|
500
|
+
# workspace .clud-bug.json AND uploads as a workflow artifact
|
|
501
|
+
# (90-day retention). v0.6.30 will add aggregation logic so
|
|
502
|
+
# `clud-bug usage --health` reads + merges artifacts across runs.
|
|
503
|
+
#
|
|
504
|
+
# Why artifact instead of committing back to main: committing
|
|
505
|
+
# would require `contents: write` permission (private-repo
|
|
506
|
+
# trigger-firing regression risk per v0.6.23 hotfix) + race
|
|
507
|
+
# handling. Artifacts are GitHub-native persistence with zero
|
|
508
|
+
# permission expansion + zero commit noise.
|
|
509
|
+
- name: Update skill-usage tracking
|
|
510
|
+
if: success() && steps.clud-bug-review.outputs.structured_output != ''
|
|
511
|
+
continue-on-error: true # don't fail the whole review if usage update flakes
|
|
512
|
+
env:
|
|
513
|
+
STRUCTURED: ${{ steps.clud-bug-review.outputs.structured_output }}
|
|
514
|
+
run: |
|
|
515
|
+
set -euo pipefail
|
|
516
|
+
mkdir -p .claude/skills
|
|
517
|
+
# If a .clud-bug.json already exists in the workspace (PR's
|
|
518
|
+
# snapshot), keep it. Otherwise initialize an empty shell so
|
|
519
|
+
# update-skill-usage has something to merge into.
|
|
520
|
+
if [ ! -f .claude/skills/.clud-bug.json ]; then
|
|
521
|
+
echo '{"version": 1}' > .claude/skills/.clud-bug.json
|
|
522
|
+
fi
|
|
523
|
+
printf '%s\n' "$STRUCTURED" | npx --yes clud-bug@{{CLUD_BUG_VERSION}} update-skill-usage --stdin
|
|
524
|
+
echo "::notice title=skill-usage::recorded review delta to ephemeral .clud-bug.json (v0.6.30 will add cross-review aggregation)"
|
|
525
|
+
|
|
526
|
+
- name: Upload skill-usage artifact
|
|
527
|
+
if: success() && steps.clud-bug-review.outputs.structured_output != ''
|
|
528
|
+
continue-on-error: true
|
|
529
|
+
uses: actions/upload-artifact@v4
|
|
530
|
+
with:
|
|
531
|
+
name: clud-bug-skill-usage-pr-${{ github.event.pull_request.number }}
|
|
532
|
+
path: .claude/skills/.clud-bug.json
|
|
533
|
+
retention-days: 90
|
|
534
|
+
|
|
497
535
|
# Fallback comment when the model couldn't produce schema-valid
|
|
498
536
|
# output after max retries (structured_output is empty). Keeps a
|
|
499
537
|
# bare H2 header so the strict-mode gate sees a comment and falls
|
|
@@ -589,7 +627,7 @@ jobs:
|
|
|
589
627
|
# Letting the action's own failure fail the check is louder and right.
|
|
590
628
|
- name: Strict mode — fail check on critical findings
|
|
591
629
|
if: success()
|
|
592
|
-
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.
|
|
630
|
+
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.29
|
|
593
631
|
with:
|
|
594
632
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
595
633
|
# v0.6.22 / 0.0.O: the summary is now posted by the workflow
|