@spardutti/claude-skills 2.54.2 → 2.55.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 +3 -3
- package/hooks/ship-gate-files.sh +19 -11
- package/lib/setup-claude-md.mjs +1 -0
- package/lib/sync.mjs +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ npx @spardutti/claude-skills
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```text
|
|
39
|
-
Claude Skills Installer v2.
|
|
39
|
+
Claude Skills Installer v2.55.1
|
|
40
40
|
|
|
41
41
|
── Frontend ──────────────────────────────
|
|
42
42
|
◉ react ◯ tanstack-query
|
|
@@ -200,7 +200,7 @@ scripts/ validate-skills.mjs — checks skill length caps and reference in
|
|
|
200
200
|
edit-check.sh — PostToolUse check on each edited file: line-limit.sh, lint-ratchet.mjs, .edit-rules.json
|
|
201
201
|
diary-check.mjs — run at the end of /ship; says when the diaries hold enough for /add-rule
|
|
202
202
|
line-limit.sh — the 200-line ratchet, shared by the edit check, the ship gate and preflight
|
|
203
|
-
gauntlet-selftest.sh — behavioural tests for the hooks (runs
|
|
203
|
+
gauntlet-selftest.sh — behavioural tests for the hooks (runs in preflight)
|
|
204
204
|
gauntlet-survey.sh — what the hook would do in every repo under a directory
|
|
205
205
|
preflight.sh — everything that must pass before a release
|
|
206
206
|
check-freshness.mjs — compares each skill's `tracks:` versions to the registries
|
|
@@ -226,7 +226,7 @@ Skills live in `skills/<name>/SKILL.md`. Authoring conventions are in [CLAUDE.md
|
|
|
226
226
|
- Before any release, run `bash scripts/preflight.sh` — validator, self-tests, a check that the
|
|
227
227
|
hook scripts embedded in the CLI still match `scripts/`, detection against every repo under
|
|
228
228
|
`~/projects`, and that the version is ahead of the registry. Do not publish while it is red.
|
|
229
|
-
- Changing `scripts/gauntlet.sh`? Run `bash scripts/gauntlet-selftest.sh` (also
|
|
229
|
+
- Changing `scripts/gauntlet.sh`? Run `bash scripts/gauntlet-selftest.sh` (also in preflight),
|
|
230
230
|
and run `bash scripts/gauntlet-survey.sh ~/projects` before releasing. Detection is where
|
|
231
231
|
this hook keeps breaking, because real repos are shaped in ways invented test repos aren't —
|
|
232
232
|
the survey is a dry run that reports what each of your repos would get without executing
|
package/hooks/ship-gate-files.sh
CHANGED
|
@@ -14,14 +14,13 @@ changed_tests() { # changed_tests <family>
|
|
|
14
14
|
done
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
# Everything a file's verdict rests on besides the file itself and the tests
|
|
18
|
-
#
|
|
17
|
+
# Everything a file's verdict rests on besides the file itself and the tests: the base, the gate,
|
|
18
|
+
# and the test setup. Other code is left out, so one fix no longer re-mutates every clean file.
|
|
19
19
|
files_context() { # files_context <owner> <owned files>
|
|
20
20
|
fam=$(family_of "$1/.")
|
|
21
21
|
{ printf '%s\n' "$PROJECT_DIR" "$1" "$(git rev-parse "$BASE" 2>/dev/null)"
|
|
22
22
|
cat "$HERE/ship-gate.sh" "$HERE/ship-gate-projects.sh" "$HERE/ship-gate-files.sh" "$HERE/gate-config.sh" "$HERE/line-limit.sh"
|
|
23
|
-
printf '%s\n' "$CHANGED" | grep -
|
|
24
|
-
| grep -vxF -f <(printf '%s\n' "$2") | while IFS= read -r f; do
|
|
23
|
+
printf '%s\n' "$CHANGED" | grep -E "$SETUP_RE" | while IFS= read -r f; do
|
|
25
24
|
[ -n "$f" ] || continue
|
|
26
25
|
case "$(family_of "$f")" in "$fam"|any) printf '%s\n' "$f"; [ -f "$f" ] && cat "$f" ;; esac
|
|
27
26
|
done; } | git hash-object --stdin
|
|
@@ -36,31 +35,40 @@ test_then() { # test_then <snapshot> <test>: its content when the snapshot was
|
|
|
36
35
|
else git show "$BASE:$2" 2>/dev/null; fi
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
|
|
38
|
+
# The repo-relative paths a test names in quotes (./, ../, @/ or ~/), extension dropped.
|
|
39
|
+
test_names() { # test_names <test path> <owner prefix> < test text
|
|
40
|
+
grep -oE "['\"](\.{1,2}/|@/|~/)[^'\"]+['\"]" | tr -d "'\"" | while IFS= read -r s; do
|
|
41
|
+
case "$s" in @/*|~/*) printf '%s%s\n' "$2" "${s#?/}" ;; *) printf '%s/%s\n' "$(dirname "$1")" "$s" ;; esac
|
|
42
|
+
done | awk -F/ '{ n = 0; for (i = 1; i <= NF; i++) { if ($i == "..") n--; else if ($i != "." && $i != "") p[++n] = $i }
|
|
43
|
+
o = p[1]; for (i = 2; i <= n; i++) o = o "/" p[i]; print o }' | sed -E 's/\.(m|c)?[jt]sx?$//; s#/index$##'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# A test that lost a line can revive mutants only in the files it names, so only those lose their pass.
|
|
47
|
+
# Never `| grep -q` under pipefail: diff exits 1 on any difference, and an early grep exit fails the writer.
|
|
48
|
+
tests_lost_lines() { # tests_lost_lines <snapshot> <family> <file> <owner prefix>
|
|
42
49
|
[ -f "$1/manifest" ] || return 0
|
|
50
|
+
local f="${3%.*}"; f=${f%/index}
|
|
43
51
|
while IFS= read -r t; do
|
|
44
52
|
[ -n "$t" ] || continue
|
|
45
|
-
case "$(diff <(test_then "$1" "$t") <(cat "$t" 2>/dev/null))" in *$'\n<'*)
|
|
53
|
+
case "$(diff <(test_then "$1" "$t") <(cat "$t" 2>/dev/null))" in *$'\n<'*) ;; *) continue ;; esac
|
|
54
|
+
[ -n "$({ test_then "$1" "$t"; cat "$t" 2>/dev/null || true; } | test_names "$t" "$4" | grep -xF "$f")" ] && return 0
|
|
46
55
|
done <<< "$({ cat "$1/manifest"; changed_tests "$2"; } | sort -u)"
|
|
47
56
|
return 1
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
# Sets OWNED to the files that still need mutating.
|
|
51
60
|
files_split() { # files_split <context> <owner> <label>
|
|
52
|
-
fam=$(family_of "$2/."); kept=""; rest=""
|
|
61
|
+
fam=$(family_of "$2/."); kept=""; rest=""; b=""; [ "$2" != "." ] && b="$2/"
|
|
53
62
|
while IFS= read -r f; do
|
|
54
63
|
[ -n "$f" ] || continue
|
|
55
64
|
r=$(file_receipt "$1" "$f")
|
|
56
|
-
if [ -f "$r" ] && ! tests_lost_lines "$(cat "$r")" "$fam"; then kept="$kept ${f#"$2"/}"
|
|
65
|
+
if [ -f "$r" ] && ! tests_lost_lines "$(cat "$r")" "$fam" "$f" "${b}"; then kept="$kept ${f#"$2"/}"
|
|
57
66
|
else rest="$rest$f"$'\n'; fi
|
|
58
67
|
done <<< "$OWNED"
|
|
59
68
|
OWNED=${rest%$'\n'}
|
|
60
69
|
[ -n "$kept" ] || return 0
|
|
61
70
|
echo " $3 stryker — ok, not re-run: passed and unchanged since:$kept"
|
|
62
71
|
echo " A change in a file they call is not re-checked."
|
|
63
|
-
b=""; [ "$2" != "." ] && b="$2/"
|
|
64
72
|
[ -z "$OWNED" ] && stryker_page_tests "$b" "$3"
|
|
65
73
|
return 0
|
|
66
74
|
}
|
package/lib/setup-claude-md.mjs
CHANGED
|
@@ -27,6 +27,7 @@ The hooks and skills in \`.claude/\` come from \`@spardutti/claude-skills\`. Fac
|
|
|
27
27
|
- **Make it mandatory on some files:** put \`gate-paths: "src/**/*.tsx"\` under \`metadata:\`, quoted. A bare top-level \`paths:\` makes Claude Code drop the skill. Add \`tracks: react@19\` to fire only when the project depends on that package.
|
|
28
28
|
- **Hook settings** live in \`.claude/gauntlet.conf\` (shell syntax): \`GAUNTLET_TEST\`, \`GAUNTLET_TYPECHECK\`, \`GAUNTLET_BASE\` (the trunk branch, if not develop/dev/main), \`GAUNTLET_MAX_LINES\`, \`GAUNTLET_IGNORE_FILES\`, \`GAUNTLET_OFF=1\`.
|
|
29
29
|
- **Shipping:** open, merge or release a PR only through the \`/ship\` command; load it every time, even when you remember the steps. Never run \`ship-gate.sh\` with \`--force\` or \`--baseline\` unless the user asks.
|
|
30
|
+
- **Tests:** The Stop hook runs the changed files' tests when you end your turn. Don't run them yourself to check; run one test file only to see a new test fail first.
|
|
30
31
|
- **Never edit \`.claude/hooks/\`** — a sync overwrites it. Update with \`npx @spardutti/claude-skills --sync\`.`;
|
|
31
32
|
|
|
32
33
|
const BLOCKS = [
|
package/lib/sync.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { readdir } from "node:fs/promises";
|
|
|
4
4
|
import { join, basename } from "node:path";
|
|
5
5
|
import { installSkills, installCommands, installRequiredAgents } from "./install.mjs";
|
|
6
6
|
import { setupHook } from "./setup-hook.mjs";
|
|
7
|
+
import { setupClaudeMd } from "./setup-claude-md.mjs";
|
|
7
8
|
import { readManifest, writeManifest, computeOrphans, removeArtifacts, MANIFEST_FILE } from "./manifest.mjs";
|
|
8
9
|
|
|
9
10
|
const SEARCH_DEPTH = 5;
|
|
@@ -28,7 +29,7 @@ export async function runSync(dir, catalog, version) {
|
|
|
28
29
|
if (skills.length > 0) { console.log(); await installSkills(skills, dir); }
|
|
29
30
|
if (commands.length > 0) { console.log(); await installCommands(commands, dir); }
|
|
30
31
|
const { installed } = await installRequiredAgents(commands, catalog.agents, dir);
|
|
31
|
-
if (existsSync(join(dir, ".claude", "hooks", "skill-gate.sh"))) { console.log(); await setupHook(dir); }
|
|
32
|
+
if (existsSync(join(dir, ".claude", "hooks", "skill-gate.sh"))) { console.log(); await setupHook(dir); await setupClaudeMd(dir); }
|
|
32
33
|
|
|
33
34
|
await writeManifest(dir, {
|
|
34
35
|
catalogVersion: version,
|