@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 +4 -0
- package/lib/setup-hook.mjs +17 -20
- package/package.json +1 -1
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 —
|
package/lib/setup-hook.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|