@spardutti/claude-skills 2.19.5 → 2.20.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.
Files changed (2) hide show
  1. package/lib/setup-hook.mjs +63 -11
  2. package/package.json +1 -1
@@ -579,7 +579,7 @@ const SHIP_GATE_SCRIPT = `#!/usr/bin/env bash
579
579
  # what makes a fix re-gate itself instead of riding on the previous verdict.
580
580
  #
581
581
  # Exit codes:
582
- # 0 clean — nothing over the line limit, no surviving mutants
582
+ # 0 clean — nothing over the line limit, nothing survived, nothing uncovered
583
583
  # 1 findings that must be dealt with before shipping
584
584
  # 2 ran, but could not prove the tests (a mutation tool is missing) — report,
585
585
  # don't block; the output names exactly what to install and where
@@ -629,7 +629,7 @@ GAUNTLET_MAX_LINES=200
629
629
  GAUNTLET_MUTATE=""
630
630
  GAUNTLET_SOURCE_EXT="ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|c|h|cpp|hpp|cs|swift|gd"
631
631
  GAUNTLET_IGNORE_EXT="md|mdx|txt|rst|adoc|json|ya?ml|toml|lock|cfg|ini|env|csv|tsv|sql|html|css|scss|svg|png|jpg|jpeg|gif|webp|ico|pdf|woff2?|ttf|otf|mp3|mp4|wav|zip|gz|tres|tscn|import|godot"
632
- GAUNTLET_IGNORE_FILES="*.gen.ts *.gen.tsx *.generated.* */migrations/*.py */alembic/versions/*.py */components/ui/*.tsx"
632
+ GAUNTLET_IGNORE_FILES="*.gen.ts *.gen.tsx *.generated.* */migrations/*.py */alembic/versions/*.py */components/ui/*.tsx */*.config.*"
633
633
  [ -n "\${HOME:-}" ] && [ -f "$HOME/.claude/gauntlet.conf" ] && . "$HOME/.claude/gauntlet.conf"
634
634
  [ -f ".claude/gauntlet.conf" ] && . ".claude/gauntlet.conf"
635
635
 
@@ -677,6 +677,11 @@ SOURCED=$(printf '%s\\n' "$CHANGED" | grep -E "\\.($GAUNTLET_SOURCE_EXT)$" \\
677
677
  # both checks — and printed, because a silent skip is the same lie as a fake
678
678
  # PASS. The leading / is what lets one pattern match components/ui at the repo
679
679
  # root and apps/web/src/components/ui alike.
680
+ #
681
+ # */*.config.* is here for the same reason, and became urgent the moment an
682
+ # uncovered line started failing the gate: vitest.config.ts has no tests, will
683
+ # never have tests, and is not a thing anyone writes a test for. Left in, every
684
+ # diff that touches a config reports findings nobody can close.
680
685
  # set -f matters: an unquoted list is glob-expanded before it is split, so
681
686
  # */components/ui/*.tsx turned itself into the very path it was meant to match
682
687
  # and then matched nothing.
@@ -799,13 +804,24 @@ for owner in $OWNERS; do
799
804
  # when that one file had no tests, Stryker exited on "No tests were executed"
800
805
  # and the gate reported UNPROVEN, which writes a receipt and blocks nothing.
801
806
  FLAGS=""
807
+ GLOBBED=","
802
808
  while IFS= read -r f; do
803
809
  hunks=$(git diff -U0 "$BASE" -- "$f" 2>/dev/null \\
804
810
  | grep -oE '^@@ -[0-9,]+ \\+[0-9]+(,[0-9]+)?' | sed 's/.*+//' \\
805
811
  | while IFS=, read -r s l; do l=\${l:-1}; [ "$l" -gt 0 ] && echo "$s-$((s+l-1))"; done)
806
812
  [ -z "$hunks" ] && hunks="1-$(wc -l < "$f")"
807
813
  rel=\${f#$owner/}
808
- for r in $hunks; do FLAGS="$FLAGS,$rel:$r"; done
814
+ # A Next.js route folder — [id], [[...slug]] — is a glob expression to
815
+ # Stryker, which refuses "Cannot combine a glob expression with a mutation
816
+ # range" and fails the entire run, not just that file. Escaping the brackets
817
+ # stops the path resolving at all. Replacing each with ? keeps it matching
818
+ # that one file and drops the range, so the file is mutated whole: a
819
+ # superset of the diff, slower than a range but never less than what changed.
820
+ case "$rel" in
821
+ *[][*?{}]*) FLAGS="$FLAGS,$(printf '%s' "$rel" | sed 's/[][*?{}]/?/g')"
822
+ GLOBBED="$GLOBBED$rel," ;;
823
+ *) for r in $hunks; do FLAGS="$FLAGS,$rel:$r"; done ;;
824
+ esac
809
825
  done <<< "$OWNED"
810
826
 
811
827
  base=""; RUN=""
@@ -870,12 +886,39 @@ for owner in $OWNERS; do
870
886
  # HEADER every run and repeats the word in its table, so a bare grep reports
871
887
  # survivors on a clean run — worse than not running at all. Stryker marks each
872
888
  # real finding \`[Survived]\`; table and border lines are excluded outright.
873
- case "$TOOL" in
874
- stryker) PATTERN='\\[Survived\\]' ;;
875
- *) PATTERN='[Ss]urvived' ;;
876
- esac
877
- SURVIVED=$(printf '%s\\n' "$OUT" | grep -E "$PATTERN" \\
878
- | grep -vE '^[[:space:]]*[#│|+-]|# *surviv' | head -20)
889
+ # NoCoverage is printed exactly like Survived and means something worse: not
890
+ # "a test missed this mutation" but "no test executes this line at all". The
891
+ # gate matched only [Survived], so a diff whose files have no tests came back
892
+ # PASS — 306 uncovered mutants reported as proven on one commit. It is judged
893
+ # only for files that went in WITH a range: a glob-shaped path is mutated
894
+ # whole, so its untouched lines would report uncovered forever, and a finding
895
+ # nobody can close is what teaches people to --force.
896
+ #
897
+ # The status line carries no file name; the line under it does, as
898
+ # file:line:column. They are paired here rather than grepped separately.
899
+ if [ "$TOOL" = stryker ]; then
900
+ ALL=$(printf '%s\\n' "$OUT" | awk -v globbed="$GLOBBED" '
901
+ /^\\[Survived\\]/ { pend = $0; kind = "s"; next }
902
+ /^\\[NoCoverage\\]/ { pend = $0; kind = "n"; next }
903
+ pend != "" {
904
+ file = $0; sub(/:[0-9]+:[0-9]+$/, "", file)
905
+ if (kind == "s" || index(globbed, "," file ",") == 0) print pend " " $0
906
+ pend = ""
907
+ }
908
+ # A survivor with no location under it is still a survivor, never dropped.
909
+ END { if (pend != "" && kind == "s") print pend }
910
+ ')
911
+ else
912
+ ALL=$(printf '%s\\n' "$OUT" | grep -E '[Ss]urvived' \\
913
+ | grep -vE '^[[:space:]]*[#│|+-]|# *surviv')
914
+ fi
915
+ # The detail list is capped so one bad file cannot bury the output. The cap
916
+ # used to be the whole story, and it hid the WORST file: 20 findings from one
917
+ # file filled it, and a second file with 54 uncovered mutants never appeared
918
+ # at all. A gate whose biggest finding is invisible gets under-fixed. Every
919
+ # file with findings is now named below the cut, worst first.
920
+ FOUND=$(printf '%s\\n' "$ALL" | grep -c .)
921
+ SURVIVED=$(printf '%s\\n' "$ALL" | head -20)
879
922
  FIXED=0
880
923
  NOTE=""
881
924
 
@@ -916,8 +959,17 @@ for owner in $OWNERS; do
916
959
  fi
917
960
 
918
961
  if [ -n "$SURVIVED" ]; then
919
- echo " $label $TOOL — surviving mutants; these lines can break and no test notices:"
962
+ echo " $label $TOOL — these lines can break and no test notices:"
920
963
  printf '%s\\n' "$SURVIVED" | sed 's/^/ /'
964
+ if [ "$FOUND" -gt 20 ]; then
965
+ echo " ... $((FOUND - 20)) more not shown. Every file with findings:"
966
+ if [ "$TOOL" = stryker ]; then
967
+ printf '%s\\n' "$ALL" \\
968
+ | awk '{ f = $NF; sub(/:[0-9]+:[0-9]+$/, "", f); n[f]++ }
969
+ END { for (k in n) printf "%8d %s\\n", n[k], k }' \\
970
+ | sort -rn | sed 's/^/ /'
971
+ fi
972
+ fi
921
973
  [ "$FIXED" -gt 0 ] && echo " ($FIXED baselined survivor(s) now killed — --baseline banks them)"
922
974
  STATUS=1
923
975
  elif [ $RC -ne 0 ]; then
@@ -927,7 +979,7 @@ for owner in $OWNERS; do
927
979
  elif [ -n "$NOTE" ]; then
928
980
  printf '%s\\n' "$NOTE"
929
981
  else
930
- echo " $label $TOOL — ok, no surviving mutants in the changed lines"
982
+ echo " $label $TOOL — ok, nothing survived and nothing was uncovered"
931
983
  [ "$FIXED" -gt 0 ] && echo " ($FIXED baselined survivor(s) now killed — --baseline banks them)"
932
984
  fi
933
985
  done
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spardutti/claude-skills",
3
- "version": "2.19.5",
3
+ "version": "2.20.1",
4
4
  "description": "CLI to install Claude Code skills from the claude-skills collection",
5
5
  "type": "module",
6
6
  "repository": {