@spardutti/claude-skills 2.28.0 → 2.30.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.
Files changed (2) hide show
  1. package/lib/setup-hook.mjs +65 -0
  2. package/package.json +1 -1
@@ -454,6 +454,18 @@ const GAUNTLET_SCRIPT = `#!/usr/bin/env bash
454
454
  # Setting either command switches the whole repo to explicit mode — auto-detection
455
455
  # is off, and only what you set runs.
456
456
  #
457
+ # Per-project runners, for tests that cannot run on the host at all:
458
+ # <project>/.gauntlet-test executable; run instead of the detected test
459
+ # command, with $FILES as its arguments
460
+ # <project>/.gauntlet-typecheck executable; run instead of the detected
461
+ # typecheck command
462
+ #
463
+ # <project> is any directory holding a package.json or pyproject.toml, so one
464
+ # half of a monorepo can run on the host while the other runs in a container.
465
+ # Unlike GAUNTLET_TEST these do not disable detection anywhere else. Typically a
466
+ # two-line \`docker compose run --rm -T api pytest "$@"\`. Same shape as
467
+ # ship-gate's .mutmut-run.
468
+ #
457
469
  # Every run records why it ended in /tmp/claude-gauntlet-<session>.why, so a green
458
470
  # run and a skipped one are told apart without re-running the hook.
459
471
 
@@ -551,6 +563,26 @@ add_dir_gates() {
551
563
  [ -n "$d" ] && [ -n "\${ROOT_TC:-}" ] && have_tc="skip"
552
564
  [ -n "$d" ] && [ -n "\${ROOT_TEST:-}" ] && have_test="skip"
553
565
 
566
+ # A project whose tests need a database, a queue, or any other service cannot
567
+ # run them on the host, and detection here finds the host runner every time.
568
+ # An executable runner in the project directory says where they DO run —
569
+ # usually \`docker compose run\` against the app container — and wins over
570
+ # everything detected below.
571
+ #
572
+ # Per project, not per repo: GAUNTLET_TEST switches the WHOLE repo to explicit
573
+ # mode, so a monorepo needing a container for one stack had to hand-write
574
+ # commands for all of them. These let one half run on the host and the other
575
+ # in a container. Same shape as ship-gate's .mutmut-run, deliberately.
576
+ RUNNER_EXT="($JS_EXT|py)"
577
+ if [ -z "$have_tc" ] && [ -x "$d\${d:+/}.gauntlet-typecheck" ]; then
578
+ add_gate typecheck "$pre$RUNNER_EXT\\$" "\${run}./.gauntlet-typecheck"
579
+ have_tc="skip"
580
+ fi
581
+ if [ -z "$have_test" ] && [ -x "$d\${d:+/}.gauntlet-test" ]; then
582
+ add_gate tests "$pre$RUNNER_EXT\\$" "\${run}./.gauntlet-test \\$FILES"
583
+ have_test="skip"
584
+ fi
585
+
554
586
  if [ -f "$d\${d:+/}package.json" ]; then
555
587
  pkg="$d\${d:+/}package.json"
556
588
  if [ -z "$have_tc" ]; then
@@ -946,6 +978,13 @@ fi
946
978
  py_mutmut() {
947
979
  d="$1"
948
980
  # Returned relative to the PROJECT, because the command runs from inside it.
981
+ # Tests that need a database, a queue, or any other service cannot run on the
982
+ # host: mutmut still generates every mutant and reports them all "not
983
+ # checked", which prints exactly like a clean run. An executable .mutmut-run
984
+ # in the project runs mutmut wherever those services are — typically
985
+ # \`docker compose run\` against the app container — and takes precedence over
986
+ # every host-local option below.
987
+ if [ -x "$d.mutmut-run" ]; then printf './.mutmut-run\\n'; return; fi
949
988
  if [ -x "$d.venv/bin/mutmut" ]; then printf './.venv/bin/mutmut\\n'; return; fi
950
989
  if [ -f "$d""uv.lock" ] && command -v uv >/dev/null 2>&1; then printf 'uv run mutmut\\n'; return; fi
951
990
  if [ -f "$d""poetry.lock" ] && command -v poetry >/dev/null 2>&1; then printf 'poetry run mutmut\\n'; return; fi
@@ -1121,6 +1160,32 @@ for owner in $OWNERS; do
1121
1160
  NOWF=$(mktemp)
1122
1161
  printf '%s\\n' "$OUT" \\
1123
1162
  | sed -n 's/^[[:space:]]*\\([^[:space:]]*\\): survived$/\\1/p' | sort -u > "$NOWF"
1163
+
1164
+ # When the tests need a service the run cannot reach — a database, a queue
1165
+ # — mutmut still generates every mutant and records it "not checked". No
1166
+ # survivors come out, so the gate printed "nothing survived" and, with no
1167
+ # baseline yet, wrote an EMPTY one and told the user to commit it. That
1168
+ # repo's Python gate could never fail again.
1169
+ #
1170
+ # Counting kills cannot catch it: \`mutmut results\` SKIPS killed mutants
1171
+ # unless --all (mutmut/__main__.py results()), so a perfect suite and a
1172
+ # suite that ran nothing both print nothing. The status string is what
1173
+ # separates them — "not checked" is exit code None in status_by_exit_code,
1174
+ # meaning the mutant was never executed at all.
1175
+ NOTCHECKED=$(printf '%s\\n' "$OUT" | grep -c ': not checked$')
1176
+ if [ "$NOTCHECKED" -gt 0 ]; then
1177
+ echo " $label mutmut — UNPROVEN: $NOTCHECKED mutant(s) were never run."
1178
+ echo " They are recorded \\"not checked\\", so the suite proved nothing"
1179
+ echo " about them. Usually the tests need a service this run cannot"
1180
+ echo " reach — a database, a queue. Put an executable .mutmut-run in"
1181
+ echo " \${base}that runs mutmut where those services are, typically"
1182
+ echo " docker compose run against the app container; the gate uses"
1183
+ echo " it ahead of every host-local option."
1184
+ [ "$STATUS" = 0 ] && STATUS=2
1185
+ rm -f "$NOWF"
1186
+ continue
1187
+ fi
1188
+
1124
1189
  if [ "$MODE" = baseline ]; then
1125
1190
  cp "$NOWF" "$BL"
1126
1191
  NOTE=" $label mutmut — baseline set: $(wc -l < "$BL") survivor(s) accepted. Commit $BL."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spardutti/claude-skills",
3
- "version": "2.28.0",
3
+ "version": "2.30.0",
4
4
  "description": "Guardrails for Claude Code — best-practice skills, planning commands, and delivery gates.",
5
5
  "type": "module",
6
6
  "repository": {