@spardutti/claude-skills 2.51.2 → 2.51.3
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 +3 -2
- package/hooks/ship-gate-files.sh +19 -0
- package/hooks/ship-gate.sh +1 -2
- package/lib/scaffold-stryker.mjs +7 -2
- package/lib/stryker-ts7.mjs +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ npx @spardutti/claude-skills
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```text
|
|
39
|
-
Claude Skills Installer v2.
|
|
39
|
+
Claude Skills Installer v2.51.2
|
|
40
40
|
|
|
41
41
|
── Frontend ──────────────────────────────
|
|
42
42
|
◉ react ◯ tanstack-query
|
|
@@ -76,7 +76,8 @@ install adds the part that does not negotiate:
|
|
|
76
76
|
- A `Stop` hook that runs your typecheck and tests on the changed files every turn,
|
|
77
77
|
and refuses to let the turn end red.
|
|
78
78
|
- A delivery gate that **changes your code on purpose and checks whether any test
|
|
79
|
-
notices.** When none do, the PR does not open.
|
|
79
|
+
notices.** When none do, the PR does not open. The same gate runs one
|
|
80
|
+
`/code-review` round for logic bugs and fixes what it finds.
|
|
80
81
|
|
|
81
82
|
That last one is the difference. Your agent says the tests pass. This finds out.
|
|
82
83
|
|
package/hooks/ship-gate-files.sh
CHANGED
|
@@ -110,3 +110,22 @@ stryker_findings() { # stryker_findings <output> <globbed paths, comma-wrapped>
|
|
|
110
110
|
END { if (pend != "" && kind == "s") print pend }
|
|
111
111
|
'
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
# A survivor needs a test run against it. Survivors with 0 tests per mutant and
|
|
115
|
+
# nothing killed mean the runner filtered every test out (stryker-js#6210, Vitest 5).
|
|
116
|
+
stryker_zero_tests() { # stryker_zero_tests <output> <label> <seconds>
|
|
117
|
+
grep -q '^\[Survived\]' <<< "$1" && grep -q 'Ran 0\.00 tests per mutant' <<< "$1" || return 1
|
|
118
|
+
[ "$(awk -F'|' '/^ *All files/ { gsub(/ /, "", $4); print $4 }' <<< "$1")" = 0 ] || return 1
|
|
119
|
+
echo " $2 stryker (${3}s) — its survivors ran 0 tests, so they prove nothing and are dropped."
|
|
120
|
+
echo " On Vitest 5 this is stryker-js#6210: pin vitest@4 until a fixed runner ships."
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Sets ALL from a Stryker run. Fails when a broken run leaves nothing provable.
|
|
124
|
+
stryker_judge() { # uses OUT GLOBBED FCTX RC owner label T from ship-gate.sh
|
|
125
|
+
ALL=$(stryker_findings "$OUT" "$GLOBBED")
|
|
126
|
+
if stryker_zero_tests "$OUT" "$label" "$T"; then
|
|
127
|
+
ALL=$(grep -v '^\[Survived\]' <<< "$ALL"); [ -n "$ALL" ]; return
|
|
128
|
+
fi
|
|
129
|
+
[ -n "$FCTX" ] && [ "$RC" -eq 0 ] && files_record "$FCTX" "$owner" "$ALL"
|
|
130
|
+
return 0
|
|
131
|
+
}
|
package/hooks/ship-gate.sh
CHANGED
|
@@ -324,8 +324,7 @@ for owner in $OWNERS; do
|
|
|
324
324
|
[ "$TOOL" = mutmut ] && [ $RC -eq 0 ] && { printf '%s\n' "$OUT" > "$PKEY.out"; cp "$MLOG" "$PKEY.log" 2>/dev/null; }
|
|
325
325
|
[ -n "$SCOPE_RE" ] && [ $RC -eq 0 ] && OUT=$(printf '%s\n' "$OUT" | grep -E "^[[:space:]]*($SCOPE_RE)\.x")
|
|
326
326
|
if [ "$TOOL" = stryker ]; then
|
|
327
|
-
|
|
328
|
-
[ -n "$FCTX" ] && [ $RC -eq 0 ] && files_record "$FCTX" "$owner" "$ALL"
|
|
327
|
+
stryker_judge || { [ "$STATUS" = 0 ] && STATUS=2; continue; }
|
|
329
328
|
else
|
|
330
329
|
ALL=$(printf '%s\n' "$OUT" | grep -E '[Ss]urvived' \
|
|
331
330
|
| grep -vE '^[[:space:]]*[#│|+-]|# *surviv')
|
package/lib/scaffold-stryker.mjs
CHANGED
|
@@ -35,6 +35,7 @@ const CONFIG = {
|
|
|
35
35
|
tempDirName: ".stryker-tmp",
|
|
36
36
|
};
|
|
37
37
|
import { IGNORER, IGNORER_VERSION } from "./stryker-ignorer.mjs";
|
|
38
|
+
import { TS7_TSCONFIG, usesTs7 } from "./stryker-ts7.mjs";
|
|
38
39
|
|
|
39
40
|
async function exists(p) {
|
|
40
41
|
try {
|
|
@@ -69,6 +70,7 @@ export async function scaffoldStryker(projectDir, rel) {
|
|
|
69
70
|
const kept = [];
|
|
70
71
|
const patched = [];
|
|
71
72
|
const unreadable = [];
|
|
73
|
+
const ts7 = await usesTs7(dir, projectDir);
|
|
72
74
|
|
|
73
75
|
// A project that already runs Stryker keeps every setting it chose. The two
|
|
74
76
|
// entries that wire the ignorer in are added, because a config written before
|
|
@@ -95,8 +97,9 @@ export async function scaffoldStryker(projectDir, rel) {
|
|
|
95
97
|
const coverage = cfg.coverageAnalysis;
|
|
96
98
|
const canSetStatic = coverage === undefined || coverage === "perTest";
|
|
97
99
|
const needsStatic = cfg.ignoreStatic !== true && canSetStatic;
|
|
100
|
+
const needsTsconfig = ts7 && cfg.tsconfigFile === undefined;
|
|
98
101
|
|
|
99
|
-
if (!needsIgnorer && !needsStatic) {
|
|
102
|
+
if (!needsIgnorer && !needsStatic && !needsTsconfig) {
|
|
100
103
|
kept.push(`${at}stryker.config.json`);
|
|
101
104
|
} else {
|
|
102
105
|
if (needsIgnorer) {
|
|
@@ -107,12 +110,14 @@ export async function scaffoldStryker(projectDir, rel) {
|
|
|
107
110
|
cfg.coverageAnalysis = "perTest";
|
|
108
111
|
cfg.ignoreStatic = true;
|
|
109
112
|
}
|
|
113
|
+
if (needsTsconfig) cfg.tsconfigFile = TS7_TSCONFIG;
|
|
110
114
|
await writeFile(configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
111
115
|
patched.push(`${at}stryker.config.json`);
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
} else {
|
|
115
|
-
|
|
119
|
+
const config = ts7 ? { ...CONFIG, tsconfigFile: TS7_TSCONFIG } : CONFIG;
|
|
120
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
116
121
|
wrote.push(`${at}stryker.config.json`);
|
|
117
122
|
}
|
|
118
123
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
// Stryker rewrites tsconfig through TypeScript's JS API, which 7 no longer has
|
|
5
|
+
// (stryker-js#6111). Naming a file that does not exist skips the rewrite.
|
|
6
|
+
export const TS7_TSCONFIG = "no-tsconfig-rewrite-typescript-7-has-no-compiler-api.json";
|
|
7
|
+
|
|
8
|
+
async function readJson(p) {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(await readFile(p, "utf-8"));
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// A range like "catalog:" or "workspace:*" carries no number, so the installed copy wins.
|
|
17
|
+
export async function usesTs7(...dirs) {
|
|
18
|
+
for (const d of dirs) {
|
|
19
|
+
const installed = await readJson(join(d, "node_modules", "typescript", "package.json"));
|
|
20
|
+
const pkg = await readJson(join(d, "package.json"));
|
|
21
|
+
const version = installed?.version ?? pkg?.devDependencies?.typescript ?? pkg?.dependencies?.typescript;
|
|
22
|
+
const major = Number(version?.match(/\d+/)?.[0]);
|
|
23
|
+
if (major) return major >= 7;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|