@spardutti/claude-skills 2.46.0 → 2.47.0

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/README.md CHANGED
@@ -137,6 +137,7 @@ Portable slash commands installed to `.claude/commands/`. Some orchestrate paral
137
137
  | `/optimize` | Make one measured job faster without changing what it proves — fixes a benchmark and a result that must not move, sends 4 read-only subagents to find suspects, then changes one thing per round with a prediction written first, keeping only what beats the noise. Profiles after every wrong prediction, reruns before judging a result that flipped, and ends by writing the cause as a skill rule for you |
138
138
  | `/handoff` | Write a handoff doc so a fresh session picks the work up — captures what auto-compaction loses: decisions made and rejected, dead ends already ruled out, why the tree is dirty, and the one next step |
139
139
  | `/lockdown` | Per-repo supply-chain hardening — detects the package managers, Dockerfiles, and CI in use, then guides you through and applies install-time, deploy, and pipeline hardening for npm/pnpm, pip/uv, Docker, and GitHub Actions |
140
+ | `/skill-report` | Which installed skills earn their place in this repo — every `/ship` skills audit is logged by a hook, and this tallies it: how often each skill applied, how often it caught a violation, and the rule it catches most. Names rules that break often enough to become a script check, and skills that never flag and may be dead weight |
140
141
 
141
142
  ## How It Works
142
143
 
@@ -193,6 +194,7 @@ scripts/ validate-skills.mjs — checks skill length caps and reference in
193
194
  ship-gate.sh — /ship's file-length, folder-structure and mutation checks, behind an exit code
194
195
  ship-gate-hook.sh — refuses gh pr create/merge without a ship-gate receipt
195
196
  version-check.sh — SessionStart nudge when a newer catalog is published
197
+ skill-flags-record.sh — logs each /ship skills-audit verdict; skill-report.mjs tallies it
196
198
  gauntlet-selftest.sh — behavioural tests for the hooks (runs on pre-push)
197
199
  gauntlet-survey.sh — what the hook would do in every repo under a directory
198
200
  preflight.sh — everything that must pass before a release
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+ # SubagentStop: appends each gauntlet-skills verdict to .claude/skill-flags.jsonl.
3
+
4
+ INPUT=$(cat | tr -d '\n')
5
+
6
+ case "$INPUT" in
7
+ *'"agent_type":"gauntlet-skills"'*) ;;
8
+ *) exit 0 ;;
9
+ esac
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ PROJECT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
13
+
14
+ # Kept JSON-escaped as it arrived, so the line stays valid JSON without a parser.
15
+ REPLY=$(printf '%s' "$INPUT" | sed -nE 's/.*"last_assistant_message":"(([^"\\]|\\.)*)".*/\1/p')
16
+ [ -z "$REPLY" ] && exit 0
17
+
18
+ TRANSCRIPT=$(printf '%s' "$INPUT" | grep -o '"agent_transcript_path":"[^"]*"' | head -1 | sed 's/.*:"//; s/"$//')
19
+ MODEL=""
20
+ [ -f "$TRANSCRIPT" ] && MODEL=$(grep -o '"model":"[^"]*"' "$TRANSCRIPT" | tail -1 | sed 's/.*:"//; s/"$//')
21
+
22
+ printf '{"ts":"%s","model":"%s","reply":"%s"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$MODEL" "$REPLY" \
23
+ >> "$PROJECT_DIR/.claude/skill-flags.jsonl"
24
+
25
+ exit 0
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ // Prints, per skill, how often the /ship skills audit applied it and flagged a violation.
3
+ import { readFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const DEFAULT_LOG = fileURLToPath(new URL("../skill-flags.jsonl", import.meta.url));
7
+
8
+ function parseVerdict(line) {
9
+ try {
10
+ const { model, reply } = JSON.parse(line);
11
+ return { model, verdict: JSON.parse(reply.slice(reply.indexOf("{"), reply.lastIndexOf("}") + 1)) };
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+
17
+ function tally(lines) {
18
+ const skills = new Map();
19
+ const models = new Map();
20
+ let unreadable = 0;
21
+ for (const line of lines) {
22
+ const parsed = parseVerdict(line);
23
+ if (!parsed?.verdict?.skill) { unreadable++; continue; }
24
+ const { model, verdict } = parsed;
25
+ if (!(verdict.checked > 0)) continue;
26
+ const s = skills.get(verdict.skill) ?? { audits: 0, flagged: 0, rules: new Map() };
27
+ s.audits++;
28
+ const violations = Array.isArray(verdict.violations) ? verdict.violations : [];
29
+ if (violations.length) s.flagged++;
30
+ for (const v of violations) s.rules.set(v.rule, (s.rules.get(v.rule) ?? 0) + 1);
31
+ skills.set(verdict.skill, s);
32
+ if (model) models.set(model, (models.get(model) ?? 0) + 1);
33
+ }
34
+ return { skills, models, unreadable };
35
+ }
36
+
37
+ function topRule(rules) {
38
+ const [rule] = [...rules].sort((a, b) => b[1] - a[1])[0] ?? ["-"];
39
+ return rule.length > 60 ? rule.slice(0, 57) + "..." : rule;
40
+ }
41
+
42
+ function render({ skills, models, unreadable }) {
43
+ if (!skills.size) return "No skill audits recorded yet. Run /ship to record some.";
44
+ const rows = [...skills].sort((a, b) => b[1].flagged - a[1].flagged || a[0].localeCompare(b[0]));
45
+ const width = Math.max(5, ...rows.map(([name]) => name.length));
46
+ const out = [`${"skill".padEnd(width)} audits flagged top rule broken`];
47
+ for (const [name, s] of rows) {
48
+ out.push(`${name.padEnd(width)} ${String(s.audits).padStart(6)} ${String(s.flagged).padStart(7)} ${topRule(s.rules)}`);
49
+ }
50
+ if (models.size) out.push("", "models: " + [...models].map(([m, n]) => `${m} (${n})`).join(", "));
51
+ if (unreadable) out.push(`unreadable lines: ${unreadable}`);
52
+ return out.join("\n");
53
+ }
54
+
55
+ let text = "";
56
+ try {
57
+ text = readFileSync(process.argv[2] ?? DEFAULT_LOG, "utf-8");
58
+ } catch {
59
+ // no log yet
60
+ }
61
+ console.log(render(tally(text.split("\n").filter(Boolean))));
@@ -11,6 +11,7 @@ const GAUNTLET_FILENAME = "gauntlet.sh";
11
11
  const SHIP_GATE_FILENAME = "ship-gate.sh";
12
12
  const SHIP_GATE_HOOK_FILENAME = "ship-gate-hook.sh";
13
13
  const VERSION_CHECK_FILENAME = "version-check.sh";
14
+ const SKILL_FLAGS_FILENAME = "skill-flags-record.sh";
14
15
  const LEGACY_EVAL_FILENAME = "skill-forced-eval-hook.sh";
15
16
  const LEGACY_AUDIT_RUNNER_FILENAME = "skill-audit-runner.sh";
16
17
 
@@ -116,6 +117,16 @@ export async function registerHooks(settingsPath) {
116
117
  settings.hooks.Stop = [gauntletEntry];
117
118
  }
118
119
 
120
+ // No matcher: the script filters on agent_type itself.
121
+ const skillFlagsCommand = `$CLAUDE_PROJECT_DIR/.claude/hooks/${SKILL_FLAGS_FILENAME}`;
122
+ const skillFlagsEntry = { hooks: [{ type: "command", command: skillFlagsCommand }] };
123
+
124
+ if (Array.isArray(settings.hooks.SubagentStop)) {
125
+ upsert(settings.hooks.SubagentStop, SKILL_FLAGS_FILENAME, skillFlagsEntry);
126
+ } else {
127
+ settings.hooks.SubagentStop = [skillFlagsEntry];
128
+ }
129
+
119
130
  // Both gates clear by touching a marker under /tmp. The hook scripts let those
120
131
  // commands through themselves, but auto mode's permission classifier does not:
121
132
  // a touch whose only purpose is to unlock a gate is exactly what it refuses.
@@ -20,6 +20,8 @@ import { homedir } from "node:os";
20
20
  // ship-gate-projects.sh sourced by ship-gate.sh — project detection, reuse
21
21
  // ship-gate-hook.sh PreToolUse on Bash — no commit without a receipt
22
22
  // version-check.sh SessionStart — a line when a newer catalog exists
23
+ // skill-flags-record.sh SubagentStop — logs each skills-audit verdict
24
+ // skill-report.mjs read by /skill-report — tallies that log
23
25
 
24
26
  const GATE_FILENAME = "skill-gate.sh";
25
27
  const AUTO_MARK_FILENAME = "skill-gate-automark.sh";
@@ -31,6 +33,8 @@ const SHIP_GATE_STRUCTURE_FILENAME = "ship-gate-structure.sh";
31
33
  const SHIP_GATE_FILES_FILENAME = "ship-gate-files.sh";
32
34
  const SHIP_GATE_HOOK_FILENAME = "ship-gate-hook.sh";
33
35
  const VERSION_CHECK_FILENAME = "version-check.sh";
36
+ const SKILL_FLAGS_FILENAME = "skill-flags-record.sh";
37
+ const SKILL_REPORT_FILENAME = "skill-report.mjs";
34
38
  const LEGACY_EVAL_FILENAME = "skill-forced-eval-hook.sh";
35
39
  const LEGACY_AUDIT_RUNNER_FILENAME = "skill-audit-runner.sh";
36
40
 
@@ -112,6 +116,8 @@ export async function setupHook(targetDir = process.cwd()) {
112
116
  SHIP_GATE_FILES_FILENAME,
113
117
  SHIP_GATE_HOOK_FILENAME,
114
118
  VERSION_CHECK_FILENAME,
119
+ SKILL_FLAGS_FILENAME,
120
+ SKILL_REPORT_FILENAME,
115
121
  ]) {
116
122
  const dest = join(hooksDir, name);
117
123
  await writeFile(dest, await readHookSource(name), { mode: 0o755 });
@@ -133,5 +139,6 @@ export async function setupHook(targetDir = process.cwd()) {
133
139
  console.log(` Hook installed: .claude/hooks/${GAUNTLET_FILENAME}`);
134
140
  console.log(` Installed: .claude/hooks/${SHIP_GATE_FILENAME} (used by /ship)`);
135
141
  console.log(` Hook installed: .claude/hooks/${SHIP_GATE_HOOK_FILENAME}`);
142
+ console.log(` Hook installed: .claude/hooks/${SKILL_FLAGS_FILENAME}`);
136
143
  console.log(` Settings updated: .claude/settings.json`);
137
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spardutti/claude-skills",
3
- "version": "2.46.0",
3
+ "version": "2.47.0",
4
4
  "description": "Guardrails for Claude Code — best-practice skills, planning commands, and delivery gates.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -11,7 +11,7 @@
11
11
  "claude-skills": "bin/cli.mjs"
12
12
  },
13
13
  "scripts": {
14
- "prepack": "cp ../README.md README.md && mkdir -p hooks && cp ../scripts/skill-gate.sh ../scripts/skill-gate-automark.sh ../scripts/skill-application-gate.sh ../scripts/gauntlet.sh ../scripts/ship-gate.sh ../scripts/ship-gate-projects.sh ../scripts/ship-gate-structure.sh ../scripts/ship-gate-files.sh ../scripts/ship-gate-hook.sh ../scripts/version-check.sh hooks/",
14
+ "prepack": "cp ../README.md README.md && mkdir -p hooks && cp ../scripts/skill-gate.sh ../scripts/skill-gate-automark.sh ../scripts/skill-application-gate.sh ../scripts/gauntlet.sh ../scripts/ship-gate.sh ../scripts/ship-gate-projects.sh ../scripts/ship-gate-structure.sh ../scripts/ship-gate-files.sh ../scripts/ship-gate-hook.sh ../scripts/version-check.sh ../scripts/skill-flags-record.sh ../scripts/skill-report.mjs hooks/",
15
15
  "postpack": "git checkout -- README.md && rm -rf hooks"
16
16
  },
17
17
  "engines": {