@spardutti/claude-skills 2.13.0 → 2.13.1

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
@@ -346,6 +346,7 @@ scripts/ validate-skills.mjs — checks skill length caps and reference in
346
346
  ship-gate-hook.sh — refuses git commit/push without a ship-gate receipt
347
347
  gauntlet-selftest.sh — 15 behavioural tests for the hook (runs on pre-push)
348
348
  gauntlet-survey.sh — what the hook would do in every repo under a directory
349
+ preflight.sh — everything that must pass before a release
349
350
  cli/ The npm installer (npx @spardutti/claude-skills); version in cli/package.json
350
351
  .husky/ pre-push hook running the skill validator
351
352
  package.json Private dev-tooling package (claude-skills-dev) — not the published one
@@ -359,6 +360,9 @@ Skills live in `skills/<name>/SKILL.md`. Authoring conventions are in [CLAUDE.md
359
360
  - `SKILL.md` ≤ 350 lines; reference files ≤ 500 and need a `## Contents` TOC past 100 lines.
360
361
  - References are one level deep — `SKILL.md` links them, they don't link each other.
361
362
  - `npm run validate-skills` enforces this; it also runs on `pre-push`.
363
+ - Before any release, run `bash scripts/preflight.sh` — validator, self-tests, a check that the
364
+ hook scripts embedded in the CLI still match `scripts/`, detection against every repo under
365
+ `~/projects`, and that the version is ahead of the registry. Do not publish while it is red.
362
366
  - Changing `scripts/gauntlet.sh`? Run `bash scripts/gauntlet-selftest.sh` (also on `pre-push`),
363
367
  and run `bash scripts/gauntlet-survey.sh ~/projects` before releasing. Detection is where
364
368
  this hook keeps breaking, because real repos are shaped in ways invented test repos aren't —
@@ -941,6 +941,18 @@ export async function writeGauntletConf(targetDir, testCommand) {
941
941
  return confPath;
942
942
  }
943
943
 
944
+ // Register the entry, or CORRECT the one already there. Skipping when a hook of
945
+ // this name exists leaves an existing install pinned to whatever matcher it was
946
+ // first written with — which is how the skill gates kept watching only
947
+ // Write|Edit|MultiEdit after they learned to cover Bash.
948
+ function upsert(list, filename, entry) {
949
+ const existing = list.find((e) => e.hooks?.some((h) => h.command?.endsWith(filename)));
950
+ if (!existing) return list.push(entry);
951
+ if (entry.matcher === undefined) delete existing.matcher;
952
+ else existing.matcher = entry.matcher;
953
+ existing.hooks = entry.hooks;
954
+ }
955
+
944
956
  export async function setupHook(targetDir = process.cwd()) {
945
957
  const resolved = resolve(targetDir);
946
958
  const hooksDir = join(resolved, ".claude", "hooks");
@@ -1006,10 +1018,7 @@ export async function setupHook(targetDir = process.cwd()) {
1006
1018
  };
1007
1019
 
1008
1020
  if (Array.isArray(settings.hooks.PreToolUse)) {
1009
- const exists = settings.hooks.PreToolUse.some((entry) =>
1010
- entry.hooks?.some((h) => h.command?.endsWith(GATE_FILENAME))
1011
- );
1012
- if (!exists) settings.hooks.PreToolUse.push(gateEntry);
1021
+ upsert(settings.hooks.PreToolUse, GATE_FILENAME, gateEntry);
1013
1022
  } else {
1014
1023
  settings.hooks.PreToolUse = [gateEntry];
1015
1024
  }
@@ -1020,10 +1029,7 @@ export async function setupHook(targetDir = process.cwd()) {
1020
1029
  matcher: "Write|Edit|MultiEdit|Bash",
1021
1030
  hooks: [{ type: "command", command: applicationCommand }],
1022
1031
  };
1023
- const applicationAlreadyRegistered = settings.hooks.PreToolUse.some((entry) =>
1024
- entry.hooks?.some((h) => h.command?.endsWith(APPLICATION_GATE_FILENAME))
1025
- );
1026
- if (!applicationAlreadyRegistered) settings.hooks.PreToolUse.push(applicationEntry);
1032
+ upsert(settings.hooks.PreToolUse, APPLICATION_GATE_FILENAME, applicationEntry);
1027
1033
 
1028
1034
  // Register PreToolUse ship-gate receipt check on Bash.
1029
1035
  const shipGateCommand = `$CLAUDE_PROJECT_DIR/.claude/hooks/${SHIP_GATE_HOOK_FILENAME}`;
@@ -1031,10 +1037,7 @@ export async function setupHook(targetDir = process.cwd()) {
1031
1037
  matcher: "Bash",
1032
1038
  hooks: [{ type: "command", command: shipGateCommand }],
1033
1039
  };
1034
- const shipGateRegistered = settings.hooks.PreToolUse.some((entry) =>
1035
- entry.hooks?.some((h) => h.command?.endsWith(SHIP_GATE_HOOK_FILENAME))
1036
- );
1037
- if (!shipGateRegistered) settings.hooks.PreToolUse.push(shipGateEntry);
1040
+ upsert(settings.hooks.PreToolUse, SHIP_GATE_HOOK_FILENAME, shipGateEntry);
1038
1041
 
1039
1042
  // Register PostToolUse auto-mark on Skill.
1040
1043
  const autoMarkCommand = `$CLAUDE_PROJECT_DIR/.claude/hooks/${AUTO_MARK_FILENAME}`;
@@ -1044,10 +1047,7 @@ export async function setupHook(targetDir = process.cwd()) {
1044
1047
  };
1045
1048
 
1046
1049
  if (Array.isArray(settings.hooks.PostToolUse)) {
1047
- const exists = settings.hooks.PostToolUse.some((entry) =>
1048
- entry.hooks?.some((h) => h.command?.endsWith(AUTO_MARK_FILENAME))
1049
- );
1050
- if (!exists) settings.hooks.PostToolUse.push(autoMarkEntry);
1050
+ upsert(settings.hooks.PostToolUse, AUTO_MARK_FILENAME, autoMarkEntry);
1051
1051
  } else {
1052
1052
  settings.hooks.PostToolUse = [autoMarkEntry];
1053
1053
  }
@@ -1057,10 +1057,7 @@ export async function setupHook(targetDir = process.cwd()) {
1057
1057
  const gauntletEntry = { hooks: [{ type: "command", command: gauntletCommand }] };
1058
1058
 
1059
1059
  if (Array.isArray(settings.hooks.Stop)) {
1060
- const exists = settings.hooks.Stop.some((entry) =>
1061
- entry.hooks?.some((h) => h.command?.endsWith(GAUNTLET_FILENAME))
1062
- );
1063
- if (!exists) settings.hooks.Stop.push(gauntletEntry);
1060
+ upsert(settings.hooks.Stop, GAUNTLET_FILENAME, gauntletEntry);
1064
1061
  } else {
1065
1062
  settings.hooks.Stop = [gauntletEntry];
1066
1063
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spardutti/claude-skills",
3
- "version": "2.13.0",
3
+ "version": "2.13.1",
4
4
  "description": "CLI to install Claude Code skills from the claude-skills collection",
5
5
  "type": "module",
6
6
  "bin": {