@spardutti/claude-skills 2.32.4 → 2.33.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 +2 -2
- package/hooks/ship-gate-projects.sh +74 -0
- package/hooks/ship-gate.sh +30 -56
- package/lib/setup-hook.mjs +4 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ Cross-cutting craft — applies to any stack, any language.
|
|
|
122
122
|
| `git-merge-conflicts` | Resolve a merge/rebase conflict by intent, not by side — `zdiff3` to see the merge base, the inverted meaning of `ours`/`theirs` during a rebase, the semantic and tree conflicts git never marks, lockfiles and migrations you regenerate instead of merging |
|
|
123
123
|
| `research` | Open the source, don't recall it — route to the tool that owns the answer, primary sources only, pin the version you're actually on, run the validator instead of guessing, and mark every claim VERIFIED / INFERRED / UNVERIFIED |
|
|
124
124
|
| `typescript-best-practices` | TypeScript 7.x — type design, generics, type guards, `satisfies`, `using`, error handling, `tsconfig` |
|
|
125
|
-
| `testing-best-practices` 📦 | Arrange-Act-Assert, factory-based test data, isolation, mocking boundaries, a pyramid-balanced suite; bundle covers mutation testing — Stryker and mutmut setup, `ignoreStatic`, diff-scoped runs, the traps, and working score thresholds |
|
|
125
|
+
| `testing-best-practices` 📦 | Arrange-Act-Assert, assertions that fail when the code breaks — exact values, list ties and short pages, side effects, both sides of a limit — factory-based test data, isolation, mocking boundaries, a pyramid-balanced suite; bundle covers mutation testing — Stryker and mutmut setup, `ignoreStatic`, diff-scoped runs, the traps, and working score thresholds |
|
|
126
126
|
| `security-practices` | OWASP Top 10 prevention, input validation, auth, SQL injection, XSS, CSRF, secure defaults |
|
|
127
127
|
|
|
128
128
|
## Commands
|
|
@@ -133,7 +133,7 @@ Portable slash commands installed to `.claude/commands/`. Some orchestrate paral
|
|
|
133
133
|
|---------|--------------|
|
|
134
134
|
| `/ship` | Unified delivery pipeline — commit → gate → PR → merge → release. The gate is the one enforcement moment: it checks the diff's file lengths, audits it against the skills installed in the project, and mutation-tests the changed lines to prove the tests would catch a break — **fixing what it finds** rather than handing you a list. It runs before the **PR**, not before every commit: its scope is the whole branch, so gating each commit re-mutated every file the branch had ever touched. `--force` skips it. No argument steps through interactively; `/ship pr` runs through PR creation; `/ship release` runs the full pipeline |
|
|
135
135
|
| `/discover` | Find the right problem before deciding what to build — diverges first: generates competing framings through blind subagents under forced constraints, stress-tests the winner with a blind critic, and refuses to converge until every open question is answered or deferred, then drafts scope, non-goals, edge cases and success criteria for you to correct. Run before `/plan-feature` |
|
|
136
|
-
| `/plan-feature` | Integration-first feature planning, then building — 3 parallel subagents scan for reusable code, patterns and touch points, and the plan lands with a contract and a per-worker file split; on your go it dispatches parallel implementation workers that each load their own skills and
|
|
136
|
+
| `/plan-feature` | Integration-first feature planning, then building — 3 parallel subagents scan for reusable code, patterns and touch points, and the plan lands with a contract and a per-worker file split; on your go it dispatches parallel implementation workers that each load their own skills, then mutation-tests their work once and sends each test gap back to the worker that wrote it, and reports one diff |
|
|
137
137
|
| `/refactor` | Detect size / complexity / duplication / coupling issues via 4 parallel subagents, then refactor |
|
|
138
138
|
| `/deep-review` | Multi-agent deep code review — 5 parallel subagents catch guard bypasses, lost async state, wrong-table queries, dead references, protocol violations |
|
|
139
139
|
| `/test-review` | Write-then-verify test review — scopes to the diff, runs red-green + mutation gates, then an isolated read-only subagent proves each test would catch a regression instead of rubber-stamping it |
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Sourced by ship-gate.sh: which project owns a file, how it runs mutmut, and
|
|
3
|
+
# whether a project's last clean verdict still stands.
|
|
4
|
+
|
|
5
|
+
# mutmut is installed into the project's environment, not onto PATH. Ask the
|
|
6
|
+
# project how to run its own tools before falling back to a bare binary.
|
|
7
|
+
py_mutmut() {
|
|
8
|
+
d="$1"
|
|
9
|
+
# Returned relative to the PROJECT, because the command runs from inside it.
|
|
10
|
+
# Tests that need a database, a queue, or any other service cannot run on the
|
|
11
|
+
# host: mutmut still generates every mutant and reports them all "not
|
|
12
|
+
# checked", which prints exactly like a clean run. An executable .mutmut-run
|
|
13
|
+
# in the project runs mutmut wherever those services are — typically
|
|
14
|
+
# `docker compose run` against the app container — and takes precedence over
|
|
15
|
+
# every host-local option below.
|
|
16
|
+
if [ -x "$d.mutmut-run" ]; then printf './.mutmut-run\n'; return; fi
|
|
17
|
+
if [ -x "$d.venv/bin/mutmut" ]; then printf './.venv/bin/mutmut\n'; return; fi
|
|
18
|
+
if [ -f "$d""uv.lock" ] && command -v uv >/dev/null 2>&1; then printf 'uv run mutmut\n'; return; fi
|
|
19
|
+
if [ -f "$d""poetry.lock" ] && command -v poetry >/dev/null 2>&1; then printf 'poetry run mutmut\n'; return; fi
|
|
20
|
+
command -v mutmut >/dev/null 2>&1 && printf 'mutmut\n'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
owner_of() {
|
|
24
|
+
d=$(dirname "$1")
|
|
25
|
+
while :; do
|
|
26
|
+
if [ -f "$d/package.json" ] || [ -f "$d/pyproject.toml" ] \
|
|
27
|
+
|| [ -f "$d/pytest.ini" ] || [ -f "$d/setup.cfg" ]; then
|
|
28
|
+
printf '%s\n' "${d#./}"; return
|
|
29
|
+
fi
|
|
30
|
+
[ "$d" = "." ] || [ "$d" = "/" ] && { printf '.\n'; return; }
|
|
31
|
+
d=$(dirname "$d")
|
|
32
|
+
done
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# mutmut records every mutant "not checked" when it stops before testing one.
|
|
36
|
+
# Blaming an unreachable database for that sent one agent chasing Docker for 20 minutes.
|
|
37
|
+
mutmut_unchecked() { # mutmut_unchecked <label> <base> <count> <run log>
|
|
38
|
+
echo " $1 mutmut — UNPROVEN: $3 mutant(s) were never run."
|
|
39
|
+
stop='Failed to run clean test|failed to collect stats|Failed to collect list of tests|Stopping early'
|
|
40
|
+
if grep -aqsE "$stop" "$4"; then
|
|
41
|
+
echo " mutmut stopped before testing a single mutant:"
|
|
42
|
+
grep -aE "^(FAILED|ERROR) |^[A-Za-z]*Error: |$stop" "$4" | tail -8 | sed 's/^/ /'
|
|
43
|
+
echo " It runs the whole suite, then these tests again in one process, so a"
|
|
44
|
+
echo " test that passes alone can fail here on state an earlier test left."
|
|
45
|
+
return
|
|
46
|
+
fi
|
|
47
|
+
echo " They are recorded \"not checked\", so the suite proved nothing"
|
|
48
|
+
echo " about them. Usually the tests need a service this run cannot"
|
|
49
|
+
echo " reach — a database, a queue. Put an executable .mutmut-run in"
|
|
50
|
+
echo " ${2}that runs mutmut where those services are, typically"
|
|
51
|
+
echo " docker compose run against the app container; the gate uses"
|
|
52
|
+
echo " it ahead of every host-local option."
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
family_of() { # js, py, or any when no manifest says
|
|
56
|
+
o=$(owner_of "$1")
|
|
57
|
+
if [ -f "$o/package.json" ]; then echo js
|
|
58
|
+
elif [ -f "$o/pyproject.toml" ] || [ -f "$o/pytest.ini" ] || [ -f "$o/setup.cfg" ]; then echo py
|
|
59
|
+
else echo any
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# A project's verdict can only move when the gate or a changed file of its own
|
|
64
|
+
# kind moves, so fixing the API's tests no longer re-runs Stryker on two JS apps.
|
|
65
|
+
project_key() { # project_key <owner> <tool>
|
|
66
|
+
fam=$(family_of "$1/.")
|
|
67
|
+
{ printf '%s\n' "$PROJECT_DIR" "$1" "$2" "$(git rev-parse "$BASE" 2>/dev/null)" \
|
|
68
|
+
"$GAUNTLET_NO_MUTATE" "$GAUNTLET_IGNORE_FILES"
|
|
69
|
+
cat "$HERE/ship-gate.sh" "$HERE/ship-gate-projects.sh"
|
|
70
|
+
printf '%s\n' "$CHANGED" | grep -vE '\.(md|mdx|txt|rst|adoc)$' | while IFS= read -r f; do
|
|
71
|
+
[ -n "$f" ] || continue
|
|
72
|
+
case "$(family_of "$f")" in "$fam"|any) printf '%s\n' "$f"; [ -f "$f" ] && cat "$f" ;; esac
|
|
73
|
+
done; } | git hash-object --stdin
|
|
74
|
+
}
|
package/hooks/ship-gate.sh
CHANGED
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
|
|
46
46
|
set -uo pipefail
|
|
47
47
|
|
|
48
|
+
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
49
|
+
. "$HERE/ship-gate-projects.sh" || { echo "ship-gate: cannot read $HERE/ship-gate-projects.sh"; exit 1; }
|
|
50
|
+
|
|
48
51
|
MODE=""
|
|
49
52
|
case "${1:-}" in
|
|
50
53
|
--key) MODE=key; shift ;;
|
|
@@ -218,36 +221,6 @@ fi
|
|
|
218
221
|
# ---------------------------------------------------------- which project owns
|
|
219
222
|
# A monorepo holds several projects, each with its own runner and its own
|
|
220
223
|
# mutation tool. Walk up from each changed file to the nearest manifest.
|
|
221
|
-
# mutmut is installed into the project's environment, not onto PATH. Ask the
|
|
222
|
-
# project how to run its own tools before falling back to a bare binary.
|
|
223
|
-
py_mutmut() {
|
|
224
|
-
d="$1"
|
|
225
|
-
# Returned relative to the PROJECT, because the command runs from inside it.
|
|
226
|
-
# Tests that need a database, a queue, or any other service cannot run on the
|
|
227
|
-
# host: mutmut still generates every mutant and reports them all "not
|
|
228
|
-
# checked", which prints exactly like a clean run. An executable .mutmut-run
|
|
229
|
-
# in the project runs mutmut wherever those services are — typically
|
|
230
|
-
# `docker compose run` against the app container — and takes precedence over
|
|
231
|
-
# every host-local option below.
|
|
232
|
-
if [ -x "$d.mutmut-run" ]; then printf './.mutmut-run\n'; return; fi
|
|
233
|
-
if [ -x "$d.venv/bin/mutmut" ]; then printf './.venv/bin/mutmut\n'; return; fi
|
|
234
|
-
if [ -f "$d""uv.lock" ] && command -v uv >/dev/null 2>&1; then printf 'uv run mutmut\n'; return; fi
|
|
235
|
-
if [ -f "$d""poetry.lock" ] && command -v poetry >/dev/null 2>&1; then printf 'poetry run mutmut\n'; return; fi
|
|
236
|
-
command -v mutmut >/dev/null 2>&1 && printf 'mutmut\n'
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
owner_of() {
|
|
240
|
-
d=$(dirname "$1")
|
|
241
|
-
while :; do
|
|
242
|
-
if [ -f "$d/package.json" ] || [ -f "$d/pyproject.toml" ] \
|
|
243
|
-
|| [ -f "$d/pytest.ini" ] || [ -f "$d/setup.cfg" ]; then
|
|
244
|
-
printf '%s\n' "${d#./}"; return
|
|
245
|
-
fi
|
|
246
|
-
[ "$d" = "." ] || [ "$d" = "/" ] && { printf '.\n'; return; }
|
|
247
|
-
d=$(dirname "$d")
|
|
248
|
-
done
|
|
249
|
-
}
|
|
250
|
-
|
|
251
224
|
OWNERS=$(while IFS= read -r f; do owner_of "$f"; done <<< "$FILES" | sort -u)
|
|
252
225
|
|
|
253
226
|
# --------------------------------------------- check 2: mutation, per project
|
|
@@ -291,7 +264,7 @@ for owner in $OWNERS; do
|
|
|
291
264
|
esac
|
|
292
265
|
done <<< "$OWNED"
|
|
293
266
|
|
|
294
|
-
base=""; RUN=""
|
|
267
|
+
base=""; RUN=""; SCOPE_RE=""; GLOBS=""; MODS=""; MLOG="${TMPDIR:-/tmp}/ship-gate-mutmut.$$"
|
|
295
268
|
if [ "$owner" != "." ]; then base="$owner/"; RUN="cd '$owner' && "; fi
|
|
296
269
|
if [ -n "$GAUNTLET_MUTATE" ]; then
|
|
297
270
|
CMD="$GAUNTLET_MUTATE"; TOOL="config"
|
|
@@ -318,15 +291,18 @@ for owner in $OWNERS; do
|
|
|
318
291
|
continue
|
|
319
292
|
elif [ -n "$(py_mutmut "$base")" ]; then
|
|
320
293
|
TOOL="mutmut"
|
|
321
|
-
#
|
|
322
|
-
#
|
|
323
|
-
# NAMES (app.balance.reserve.*). There is no per-line scoping at all.
|
|
294
|
+
# 3.x has no per-line scoping, only fnmatch globs over mutant NAMES, so a
|
|
295
|
+
# compare run is scoped to the changed modules. A baseline is recorded whole.
|
|
324
296
|
#
|
|
325
297
|
# `mutmut run` prints 🙁 for a survivor and exits 0 either way — parsing it
|
|
326
298
|
# reports clean with survivors sitting there, which is a false green and
|
|
327
299
|
# worse than reporting nothing. `mutmut results` is the readable source:
|
|
328
300
|
# it prints "<mutant name>: survived" per survivor.
|
|
329
301
|
M="$(py_mutmut "$base")"
|
|
302
|
+
[ "$MODE" != baseline ] && [ -f "$base.mutmut-baseline" ] && MODS=$(printf '%s\n' "$OWNED" \
|
|
303
|
+
| sed -n "s#^$base\(.*\)\.py\$#\1#p" | tr / . | sed 's#^src\.##' | sort -u)
|
|
304
|
+
[ -n "$MODS" ] && GLOBS=$(printf " '%s.x*'" $MODS) \
|
|
305
|
+
&& SCOPE_RE=$(printf '%s\n' "$MODS" | sed 's/\./\\./g' | paste -sd'|' -)
|
|
330
306
|
# mutmut caches verdicts in ./mutants and only invalidates one when the
|
|
331
307
|
# SOURCE of that function changes. Adding a test does not, so it replays the
|
|
332
308
|
# old "survived" and calls mutants the new tests kill still alive — measured
|
|
@@ -335,7 +311,7 @@ for owner in $OWNERS; do
|
|
|
335
311
|
# makes both describe the tree being shipped; `mutmut run` rewrites it
|
|
336
312
|
# anyway, and a cold run is ~35s for ~1000 mutants.
|
|
337
313
|
# One cd for all three: they run in the same shell, already there.
|
|
338
|
-
CMD="${RUN}rm -rf mutants; $M run
|
|
314
|
+
CMD="${RUN}rm -rf mutants; $M run$GLOBS >'$MLOG' 2>&1; $M results"
|
|
339
315
|
else
|
|
340
316
|
MISSING="$MISSING $label needs mutmut:
|
|
341
317
|
uv add --dev mutmut # or: poetry add --group dev mutmut, pip install mutmut
|
|
@@ -348,7 +324,14 @@ for owner in $OWNERS; do
|
|
|
348
324
|
continue
|
|
349
325
|
fi
|
|
350
326
|
|
|
327
|
+
# Only a clean run is recorded, and a baseline run always asks mutmut afresh.
|
|
328
|
+
PKEY=""; [ "$TOOL" != config ] && PKEY="/tmp/claude-shipgate-project-$(project_key "$owner" "$TOOL")"
|
|
329
|
+
if [ -n "$PKEY" ] && [ -f "$PKEY" ] && { [ "$TOOL" != mutmut ] || [ "$MODE" != baseline ]; }; then
|
|
330
|
+
echo " $label $TOOL — ok, not re-run: nothing of its kind changed since it passed"
|
|
331
|
+
continue
|
|
332
|
+
fi
|
|
351
333
|
OUT=$(eval "$CMD" 2>&1); RC=$?
|
|
334
|
+
[ -n "$SCOPE_RE" ] && [ $RC -eq 0 ] && OUT=$(printf '%s\n' "$OUT" | grep -E "^[[:space:]]*($SCOPE_RE)\.x")
|
|
352
335
|
# Match a finding, never a summary row. Stryker prints a `# survived` COLUMN
|
|
353
336
|
# HEADER every run and repeats the word in its table, so a bare grep reports
|
|
354
337
|
# survivors on a clean run — worse than not running at all. Stryker marks each
|
|
@@ -389,19 +372,15 @@ for owner in $OWNERS; do
|
|
|
389
372
|
FIXED=0
|
|
390
373
|
NOTE=""
|
|
391
374
|
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
# --use-patch-file and never replaced it — so it mutates all of source_paths
|
|
395
|
-
# and reports the repo's entire backlog. Held to the same bar, the Python half
|
|
396
|
-
# answers "is this repo perfect" instead, which it never is, so it could never
|
|
397
|
-
# go green. The baseline supplies the missing half: survivors recorded once
|
|
398
|
-
# are accepted debt, and only a name that is not in it fails the gate.
|
|
399
|
-
#
|
|
400
|
-
# A mutant name carries an index within its own function, not a line number,
|
|
401
|
-
# so editing elsewhere in the file does not rename it — and editing the
|
|
402
|
-
# function itself DOES, which correctly re-charges its survivors to that edit.
|
|
375
|
+
# Module scope still returns a changed file's old survivors; the baseline accepts
|
|
376
|
+
# them. Names index within a function, so only editing that function re-charges them.
|
|
403
377
|
if [ "$TOOL" = mutmut ]; then
|
|
404
378
|
BL="$base.mutmut-baseline"
|
|
379
|
+
# A glob that matches no mutant aborts the run, so nothing in scope was tested.
|
|
380
|
+
if [ -n "$SCOPE_RE" ] && grep -qs "nothing matches" "$MLOG"; then
|
|
381
|
+
echo " $label mutmut — UNPROVEN: no mutant in the changed module(s): $(printf '%s ' $MODS)"
|
|
382
|
+
[ "$STATUS" = 0 ] && STATUS=2; rm -f "$MLOG"; continue
|
|
383
|
+
fi
|
|
405
384
|
NOWF=$(mktemp)
|
|
406
385
|
printf '%s\n' "$OUT" \
|
|
407
386
|
| sed -n 's/^[[:space:]]*\([^[:space:]]*\): survived$/\1/p' | sort -u > "$NOWF"
|
|
@@ -419,15 +398,9 @@ for owner in $OWNERS; do
|
|
|
419
398
|
# meaning the mutant was never executed at all.
|
|
420
399
|
NOTCHECKED=$(printf '%s\n' "$OUT" | grep -c ': not checked$')
|
|
421
400
|
if [ "$NOTCHECKED" -gt 0 ]; then
|
|
422
|
-
|
|
423
|
-
echo " They are recorded \"not checked\", so the suite proved nothing"
|
|
424
|
-
echo " about them. Usually the tests need a service this run cannot"
|
|
425
|
-
echo " reach — a database, a queue. Put an executable .mutmut-run in"
|
|
426
|
-
echo " ${base}that runs mutmut where those services are, typically"
|
|
427
|
-
echo " docker compose run against the app container; the gate uses"
|
|
428
|
-
echo " it ahead of every host-local option."
|
|
401
|
+
mutmut_unchecked "$label" "$base" "$NOTCHECKED" "$MLOG"
|
|
429
402
|
[ "$STATUS" = 0 ] && STATUS=2
|
|
430
|
-
rm -f "$NOWF"
|
|
403
|
+
rm -f "$NOWF" "$MLOG"
|
|
431
404
|
continue
|
|
432
405
|
fi
|
|
433
406
|
|
|
@@ -439,7 +412,7 @@ for owner in $OWNERS; do
|
|
|
439
412
|
# -Fxv against an empty baseline reports every survivor, which is right:
|
|
440
413
|
# an empty baseline means nothing has been accepted.
|
|
441
414
|
SURVIVED=$(grep -Fxv -f "$BL" "$NOWF" | head -20)
|
|
442
|
-
FIXED=$(grep -Fxvc -f "$NOWF"
|
|
415
|
+
FIXED=$(grep -E "^(${SCOPE_RE:-.*})\.x" "$BL" | grep -Fxvc -f "$NOWF")
|
|
443
416
|
else
|
|
444
417
|
cp "$NOWF" "$BL"
|
|
445
418
|
NOTE=" $label mutmut — no baseline, so nothing could be compared. Recorded
|
|
@@ -448,7 +421,7 @@ for owner in $OWNERS; do
|
|
|
448
421
|
SURVIVED=""
|
|
449
422
|
[ "$STATUS" = 0 ] && STATUS=2
|
|
450
423
|
fi
|
|
451
|
-
rm -f "$NOWF"
|
|
424
|
+
rm -f "$NOWF" "$MLOG"
|
|
452
425
|
fi
|
|
453
426
|
|
|
454
427
|
if [ -n "$SURVIVED" ]; then
|
|
@@ -473,6 +446,7 @@ for owner in $OWNERS; do
|
|
|
473
446
|
printf '%s\n' "$NOTE"
|
|
474
447
|
else
|
|
475
448
|
echo " $label $TOOL — ok, nothing survived and nothing was uncovered"
|
|
449
|
+
[ -n "$PKEY" ] && touch "$PKEY"
|
|
476
450
|
[ "$FIXED" -gt 0 ] && echo " ($FIXED baselined survivor(s) now killed — --baseline banks them)"
|
|
477
451
|
fi
|
|
478
452
|
done
|
package/lib/setup-hook.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { homedir } from "node:os";
|
|
|
17
17
|
// explicitly applied before the first edit
|
|
18
18
|
// gauntlet.sh Stop — fast typecheck and tests on the diff
|
|
19
19
|
// ship-gate.sh file length and mutation, behind an exit code
|
|
20
|
+
// ship-gate-projects.sh sourced by ship-gate.sh — project detection, reuse
|
|
20
21
|
// ship-gate-hook.sh PreToolUse on Bash — no commit without a receipt
|
|
21
22
|
// version-check.sh SessionStart — a line when a newer catalog exists
|
|
22
23
|
|
|
@@ -25,6 +26,7 @@ const AUTO_MARK_FILENAME = "skill-gate-automark.sh";
|
|
|
25
26
|
const APPLICATION_GATE_FILENAME = "skill-application-gate.sh";
|
|
26
27
|
const GAUNTLET_FILENAME = "gauntlet.sh";
|
|
27
28
|
const SHIP_GATE_FILENAME = "ship-gate.sh";
|
|
29
|
+
const SHIP_GATE_PROJECTS_FILENAME = "ship-gate-projects.sh";
|
|
28
30
|
const SHIP_GATE_HOOK_FILENAME = "ship-gate-hook.sh";
|
|
29
31
|
const VERSION_CHECK_FILENAME = "version-check.sh";
|
|
30
32
|
const LEGACY_EVAL_FILENAME = "skill-forced-eval-hook.sh";
|
|
@@ -103,6 +105,7 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
103
105
|
APPLICATION_GATE_FILENAME,
|
|
104
106
|
GAUNTLET_FILENAME,
|
|
105
107
|
SHIP_GATE_FILENAME,
|
|
108
|
+
SHIP_GATE_PROJECTS_FILENAME,
|
|
106
109
|
SHIP_GATE_HOOK_FILENAME,
|
|
107
110
|
VERSION_CHECK_FILENAME,
|
|
108
111
|
]) {
|
|
@@ -124,7 +127,7 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
124
127
|
console.log(` Hook installed: .claude/hooks/${AUTO_MARK_FILENAME}`);
|
|
125
128
|
console.log(` Hook installed: .claude/hooks/${APPLICATION_GATE_FILENAME}`);
|
|
126
129
|
console.log(` Hook installed: .claude/hooks/${GAUNTLET_FILENAME}`);
|
|
127
|
-
console.log(` Installed: .claude/hooks/${SHIP_GATE_FILENAME} (used by /ship)`);
|
|
130
|
+
console.log(` Installed: .claude/hooks/${SHIP_GATE_FILENAME} (used by /ship and /plan-feature)`);
|
|
128
131
|
console.log(` Hook installed: .claude/hooks/${SHIP_GATE_HOOK_FILENAME}`);
|
|
129
132
|
console.log(` Settings updated: .claude/settings.json`);
|
|
130
133
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spardutti/claude-skills",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.33.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-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-hook.sh ../scripts/version-check.sh hooks/",
|
|
15
15
|
"postpack": "git checkout -- README.md && rm -rf hooks"
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|