@spardutti/claude-skills 2.16.0 → 2.17.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 -3
  2. package/package.json +1 -1
@@ -565,6 +565,8 @@ const SHIP_GATE_SCRIPT = `#!/usr/bin/env bash
565
565
  # bash .claude/hooks/ship-gate.sh [base-ref]
566
566
  # bash .claude/hooks/ship-gate.sh --key print the receipt key and stop
567
567
  # bash .claude/hooks/ship-gate.sh --force write a FORCED receipt, run nothing
568
+ # bash .claude/hooks/ship-gate.sh --baseline record today's mutmut survivors as
569
+ # accepted debt, write no receipt
568
570
  #
569
571
  # On completion it writes a RECEIPT at /tmp/claude-shipgate-<key>, and the
570
572
  # PreToolUse hook refuses \`git commit\` / \`git push\` without a matching one. The
@@ -594,7 +596,11 @@ const SHIP_GATE_SCRIPT = `#!/usr/bin/env bash
594
596
  set -uo pipefail
595
597
 
596
598
  MODE=""
597
- case "\${1:-}" in --key) MODE=key; shift ;; --force) MODE=force; shift ;; esac
599
+ case "\${1:-}" in
600
+ --key) MODE=key; shift ;;
601
+ --force) MODE=force; shift ;;
602
+ --baseline) MODE=baseline; shift ;;
603
+ esac
598
604
 
599
605
  PROJECT_DIR="\${CLAUDE_PROJECT_DIR:-$PWD}"
600
606
  cd "$PROJECT_DIR" 2>/dev/null || { echo "ship-gate: cannot read $PROJECT_DIR"; exit 1; }
@@ -756,8 +762,15 @@ for owner in $OWNERS; do
756
762
  # worse than reporting nothing. \`mutmut results\` is the readable source:
757
763
  # it prints "<mutant name>: survived" per survivor.
758
764
  M="$(py_mutmut "$base")"
759
- # One cd for both: the second command runs in the same shell, already there.
760
- CMD="\${RUN}$M run >/dev/null 2>&1; $M results"
765
+ # mutmut caches verdicts in ./mutants and only invalidates one when the
766
+ # SOURCE of that function changes. Adding a test does not, so it replays the
767
+ # old "survived" and calls mutants the new tests kill still alive — measured
768
+ # at 20 of them on one commit. \`results\` then prints that whole cache, so
769
+ # survivors turn up for files nowhere near the diff. Deleting the dir first
770
+ # makes both describe the tree being shipped; \`mutmut run\` rewrites it
771
+ # anyway, and a cold run is ~35s for ~1000 mutants.
772
+ # One cd for all three: they run in the same shell, already there.
773
+ CMD="\${RUN}rm -rf mutants; $M run >/dev/null 2>&1; $M results"
761
774
  else
762
775
  MISSING="$MISSING $label needs mutmut:
763
776
  uv add --dev mutmut # or: poetry add --group dev mutmut, pip install mutmut
@@ -781,16 +794,59 @@ for owner in $OWNERS; do
781
794
  esac
782
795
  SURVIVED=$(printf '%s\\n' "$OUT" | grep -E "$PATTERN" \\
783
796
  | grep -vE '^[[:space:]]*[#│|+-]|# *surviv' | head -20)
797
+ FIXED=0
798
+ NOTE=""
799
+
800
+ # Stryker takes --mutate per changed hunk, so it already answers "did YOUR
801
+ # change get tested". mutmut cannot be asked that — 3.x dropped 1.x's
802
+ # --use-patch-file and never replaced it — so it mutates all of source_paths
803
+ # and reports the repo's entire backlog. Held to the same bar, the Python half
804
+ # answers "is this repo perfect" instead, which it never is, so it could never
805
+ # go green. The baseline supplies the missing half: survivors recorded once
806
+ # are accepted debt, and only a name that is not in it fails the gate.
807
+ #
808
+ # A mutant name carries an index within its own function, not a line number,
809
+ # so editing elsewhere in the file does not rename it — and editing the
810
+ # function itself DOES, which correctly re-charges its survivors to that edit.
811
+ if [ "$TOOL" = mutmut ]; then
812
+ BL="$base.mutmut-baseline"
813
+ NOWF=$(mktemp)
814
+ printf '%s\\n' "$OUT" \\
815
+ | sed -n 's/^[[:space:]]*\\([^[:space:]]*\\): survived$/\\1/p' | sort -u > "$NOWF"
816
+ if [ "$MODE" = baseline ]; then
817
+ cp "$NOWF" "$BL"
818
+ NOTE=" $label mutmut — baseline set: $(wc -l < "$BL") survivor(s) accepted. Commit $BL."
819
+ SURVIVED=""
820
+ elif [ -f "$BL" ]; then
821
+ # -Fxv against an empty baseline reports every survivor, which is right:
822
+ # an empty baseline means nothing has been accepted.
823
+ SURVIVED=$(grep -Fxv -f "$BL" "$NOWF" | head -20)
824
+ FIXED=$(grep -Fxvc -f "$NOWF" "$BL")
825
+ else
826
+ cp "$NOWF" "$BL"
827
+ NOTE=" $label mutmut — no baseline, so nothing could be compared. Recorded
828
+ $(wc -l < "$BL") existing survivor(s) in $BL — commit it, then run the
829
+ gate again. From here only NEW survivors fail; --baseline accepts more."
830
+ SURVIVED=""
831
+ [ "$STATUS" = 0 ] && STATUS=2
832
+ fi
833
+ rm -f "$NOWF"
834
+ fi
835
+
784
836
  if [ -n "$SURVIVED" ]; then
785
837
  echo " $label $TOOL — surviving mutants; these lines can break and no test notices:"
786
838
  printf '%s\\n' "$SURVIVED" | sed 's/^/ /'
839
+ [ "$FIXED" -gt 0 ] && echo " ($FIXED baselined survivor(s) now killed — --baseline banks them)"
787
840
  STATUS=1
788
841
  elif [ $RC -ne 0 ]; then
789
842
  echo " $label $TOOL — the run failed:"
790
843
  printf '%s\\n' "$OUT" | tail -12 | sed 's/^/ /'
791
844
  [ "$STATUS" = 0 ] && STATUS=2
845
+ elif [ -n "$NOTE" ]; then
846
+ printf '%s\\n' "$NOTE"
792
847
  else
793
848
  echo " $label $TOOL — ok, no surviving mutants in the changed lines"
849
+ [ "$FIXED" -gt 0 ] && echo " ($FIXED baselined survivor(s) now killed — --baseline banks them)"
794
850
  fi
795
851
  done
796
852
 
@@ -801,6 +857,12 @@ if [ -n "$MISSING" ]; then
801
857
  [ "$STATUS" = 0 ] && STATUS=2
802
858
  fi
803
859
 
860
+ if [ "$MODE" = baseline ]; then
861
+ echo
862
+ echo "ship-gate: baselines updated. No receipt written — run the gate for real."
863
+ exit 0
864
+ fi
865
+
804
866
  # The receipt is written by this script and nothing else. A hand-rolled check
805
867
  # produces no receipt, which is the whole point: substituting a weaker check is
806
868
  # a choice that can be argued for, but it cannot be passed off as this one.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spardutti/claude-skills",
3
- "version": "2.16.0",
3
+ "version": "2.17.0",
4
4
  "description": "CLI to install Claude Code skills from the claude-skills collection",
5
5
  "type": "module",
6
6
  "bin": {