@spardutti/claude-skills 2.60.2 → 2.60.4

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.
@@ -57,7 +57,8 @@ export function loadRegistry(root, files) {
57
57
  // Code-shaped words (AimStrategy, pick_target, aimRegistry) or `backticked` — never "implement".
58
58
  export function symbolsOf(extendBy = "") {
59
59
  const ticked = [...extendBy.matchAll(/`([\w.]+)`/g)].map((m) => m[1]);
60
- const shaped = extendBy.match(/\b(?:[A-Z][a-z0-9]+[A-Z]\w*|[a-z]\w*[A-Z]\w*|\w+_\w+)\b/g) ?? [];
60
+ const bare = extendBy.replace(/`[^`]*`/g, " ");
61
+ const shaped = bare.match(/\b(?:[A-Z][a-z0-9]+[A-Z]\w*|[a-z]\w*[A-Z]\w*|\w+_\w+)\b/g) ?? [];
61
62
  return [...new Set([...ticked, ...shaped])];
62
63
  }
63
64
 
@@ -9,6 +9,12 @@ const root = projectRoot();
9
9
  const files = projectFiles(root);
10
10
  const entries = loadRegistry(root, files);
11
11
  const uses = (text, word) => new RegExp(`\\b${escape(word)}\\b`).test(text);
12
+ // Code calls a method on a value (`e.chill(2)`), so `EnemyState.chill` is the type named plus `.chill` used.
13
+ const usesIn = (text, name) => {
14
+ const parts = name.split(".");
15
+ if (parts.length === 1 || uses(text, name)) return uses(text, name);
16
+ return uses(text, parts[0]) && new RegExp(`\\.${escape(parts.at(-1))}\\b`).test(text);
17
+ };
12
18
  const fromCwd = (f) => relative(root, resolve(f));
13
19
 
14
20
  function list() {
@@ -38,7 +44,7 @@ function checkPlan(planPath, stubs) {
38
44
  for (const e of entries) {
39
45
  const mark = marks.get(e.name);
40
46
  if (!mark) fails.push(`registry FAIL ${e.name}: missing from ## Registry — mark it reuse or skip`);
41
- else if (mark === "reuse" && !symbolsOf(e.extend_by).some((s) => uses(added, s))) {
47
+ else if (mark === "reuse" && !symbolsOf(e.extend_by).some((s) => usesIn(added, s))) {
42
48
  fails.push(`registry FAIL ${e.name}: marked reuse, but no stub uses ${symbolsOf(e.extend_by).join(", ") || "a name from extend_by"}`);
43
49
  } else if (mark === "skip") {
44
50
  const hits = triggerHits(e, added);
@@ -80,11 +80,18 @@ git rev-parse --git-dir >/dev/null 2>&1 || exit 0
80
80
  pr_branch() {
81
81
  case "$CMD" in
82
82
  *"gh pr create"*) printf '%s\n' "$CMD" | sed -nE 's/.*(--head[ =]|-H )"?([^ ;&|"]+).*/\2/p' ;;
83
- *"gh pr merge"*) merge_branch ;;
83
+ *"gh pr merge"*) merge_field headRefName ;;
84
84
  esac
85
85
  }
86
86
 
87
- merge_branch() {
87
+ pr_base() {
88
+ case "$CMD" in
89
+ *"gh pr create"*) printf '%s\n' "$CMD" | sed -nE 's/.*(--base[ =]|-B )"?([^ ;&|"]+).*/\2/p' ;;
90
+ *"gh pr merge"*) merge_field baseRefName ;;
91
+ esac
92
+ }
93
+
94
+ merge_field() { # merge_field <gh pr field>
88
95
  local pr="" repo="" next=""
89
96
  for t in $(printf '%s\n' "$CMD" | sed 's/.*gh pr merge//; s/[;&|].*//'); do
90
97
  if [ "$next" = repo ]; then repo=$t; next=""; continue; fi
@@ -96,7 +103,7 @@ merge_branch() {
96
103
  esac
97
104
  done
98
105
  [ -n "$pr" ] || return 0
99
- gh pr view "$pr" ${repo:+-R "$repo"} --json headRefName -q .headRefName 2>/dev/null
106
+ gh pr view "$pr" ${repo:+-R "$repo"} --json "$1" -q ".$1" 2>/dev/null
100
107
  }
101
108
 
102
109
  worktree_of() {
@@ -111,6 +118,11 @@ case "$CMD" in *"gh pr create"*)
111
118
  CWD_TOP=$(git -C "${CWD:-.}" rev-parse --show-toplevel 2>/dev/null) && [ -n "$CWD_TOP" ] && TARGET="$CWD_TOP" ;;
112
119
  esac
113
120
  BRANCH=$(pr_branch)
121
+ # A release PR carries only work each feature PR already gated, and the gate on the
122
+ # trunk measures it against itself, so a receipt there proves nothing.
123
+ case "$BRANCH" in dev|develop|development)
124
+ case "$(pr_base)" in main|master) exit 0 ;; esac ;;
125
+ esac
114
126
  [ -n "$BRANCH" ] && WT=$(worktree_of "$BRANCH") && [ -n "$WT" ] && TARGET="$WT"
115
127
 
116
128
  # Ask the gate for the key rather than recomputing it here. Two implementations
@@ -45,7 +45,19 @@ function summarize(record) {
45
45
  return { minutes: Math.round(minutes * 10) / 10, gateRuns: record.gate.length, gateSeconds: record.gate.reduce((n, r) => n + (r.seconds ?? 0), 0), findings: by };
46
46
  }
47
47
 
48
+ // Survivors fixed but never logged: a FAIL that a later PASS cleared, with no mutation finding since.
49
+ function settleSurvivors(record) {
50
+ const i = record.gate.findLastIndex((g) => g.verdict === "FAIL" && g.projects?.some((p) => p.survivors?.length));
51
+ if (i < 0 || !record.gate.slice(i + 1).some((g) => g.verdict === "PASS")) return;
52
+ if (record.findings.some((f) => f.by === "mutation" && f.ts >= record.gate[i].ts)) return;
53
+ for (const s of record.gate[i].projects.flatMap((p) => p.survivors ?? [])) {
54
+ record.findings.push({ ts: now(), by: "mutation", what: `${s.mutator ?? s.kind} at ${s.at}`, file: s.at.split(":")[0],
55
+ verdict: "confirmed", fixed: true, note: "logged by ship-log: a later gate run passed" });
56
+ }
57
+ }
58
+
48
59
  function finalize(dir, path, record, tag, status, reason, at) {
60
+ settleSurvivors(record);
49
61
  Object.assign(record, { status, reason: reason ?? null, finished: at ?? now() });
50
62
  record.summary = summarize(record);
51
63
  const stem = `${record.app}_${record.finished.slice(0, 10)}_${tag}`;
@@ -69,8 +69,11 @@ scope() {
69
69
  fi
70
70
  echo "diff $diff"
71
71
  echo "review /code-review medium origin/$(base_branch)...$branch"
72
- node "$HERE/ship-log.mjs" step audit start >/dev/null 2>&1
73
- node "$HERE/ship-log.mjs" step review start >/dev/null 2>&1
72
+ # A release branch skips the audit and the review, so starting them left steps that never end.
73
+ case "$branch" in chore/release-*) ;; *)
74
+ node "$HERE/ship-log.mjs" step audit start >/dev/null 2>&1
75
+ node "$HERE/ship-log.mjs" step review start >/dev/null 2>&1 ;;
76
+ esac
74
77
  return 0
75
78
  }
76
79
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spardutti/claude-skills",
3
- "version": "2.60.2",
3
+ "version": "2.60.4",
4
4
  "description": "Guardrails for Claude Code — best-practice skills, planning commands, and delivery gates.",
5
5
  "type": "module",
6
6
  "repository": {