@spardutti/claude-skills 2.25.0 → 2.25.2
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 +66 -234
- package/bin/cli.mjs +7 -3
- package/lib/scaffold-stryker.mjs +16 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
# Claude Skills
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Guardrails for Claude Code — best-practice skills, planning commands, and delivery gates.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@spardutti/claude-skills)
|
|
6
6
|
[](https://www.npmjs.com/package/@spardutti/claude-skills)
|
|
7
7
|
[](./LICENSE)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Coding agents are good at writing code. They are much worse at holding to
|
|
10
|
+
engineering discipline — reading the codebase before changing it, keeping to
|
|
11
|
+
current idioms, proving a test would actually catch a break.
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
The ecosystem's answer has been **process** — planning frameworks, spec-driven workflows, agent orchestration. Those steer *how* the agent works. None of them fix *what the code looks like*.
|
|
13
|
+
This installs both halves into your project: **current technical guidance**, and
|
|
14
|
+
**the enforcement that makes it stick**. Guidance your agent can skip is a
|
|
15
|
+
suggestion. Every skill here loads behind a hook that requires it to be applied,
|
|
16
|
+
and nothing reaches a PR without passing a gate that ran for real.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Contents
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
- [Quick Start](#quick-start)
|
|
21
|
+
- [Why This Exists](#why-this-exists)
|
|
22
|
+
- [Skill Catalog](#skill-catalog)
|
|
23
|
+
- [Commands](#commands)
|
|
24
|
+
- [How It Works](#how-it-works)
|
|
25
|
+
- [Hooks & Internals](./HOOKS.md)
|
|
26
|
+
- [Repository Layout](#repository-layout)
|
|
27
|
+
- [Getting Help](#getting-help)
|
|
28
|
+
- [Contributing](#contributing)
|
|
20
29
|
|
|
21
30
|
## Quick Start
|
|
22
31
|
|
|
@@ -27,7 +36,7 @@ npx @spardutti/claude-skills
|
|
|
27
36
|
```
|
|
28
37
|
|
|
29
38
|
```text
|
|
30
|
-
Claude Skills Installer v2.
|
|
39
|
+
Claude Skills Installer v2.25.0
|
|
31
40
|
|
|
32
41
|
── Frontend ──────────────────────────────
|
|
33
42
|
◉ react ◯ tanstack-query
|
|
@@ -47,11 +56,33 @@ The CLI will:
|
|
|
47
56
|
2. Let you pick skills to install → `.claude/skills/`
|
|
48
57
|
3. Let you pick commands to install → `.claude/commands/`
|
|
49
58
|
4. Auto-install any subagents the selected commands declare → `.claude/agents/`
|
|
50
|
-
5. Optionally set up the **hooks** — skill evaluation before edits, verification after (recommended
|
|
59
|
+
5. Optionally set up the **hooks** — skill evaluation before edits, verification after (recommended)
|
|
60
|
+
|
|
61
|
+
## Why This Exists
|
|
62
|
+
|
|
63
|
+
**A model's knowledge is frozen and averaged.** Ask for React and it reaches for
|
|
64
|
+
`useEffect` and manual `useMemo`. Ask for TypeScript and it assumes 5.x. The code
|
|
65
|
+
compiles, passes review, and is two years out of date. Skills fix that: tight,
|
|
66
|
+
opinionated playbooks pinned to current practice for one tool each — React 19.2 and
|
|
67
|
+
the Compiler, TypeScript 7.0, TanStack Query v5, Drizzle, FastAPI, SQL. They track
|
|
68
|
+
the changelogs so you don't.
|
|
69
|
+
|
|
70
|
+
**But guidance nobody checks is a suggestion.** An agent under deadline pressure
|
|
71
|
+
skips the playbook, writes the test after the code, and reports a pass. So the same
|
|
72
|
+
install adds the part that does not negotiate:
|
|
73
|
+
|
|
74
|
+
- A gate that blocks the first edit until your skills have been applied to the file
|
|
75
|
+
being written — not just read.
|
|
76
|
+
- A `Stop` hook that runs your typecheck and tests on the changed files every turn,
|
|
77
|
+
and refuses to let the turn end red.
|
|
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.
|
|
80
|
+
|
|
81
|
+
That last one is the difference. Your agent says the tests pass. This finds out.
|
|
51
82
|
|
|
52
83
|
## Skill Catalog
|
|
53
84
|
|
|
54
|
-
**
|
|
85
|
+
**16 skills**, grouped the same way the installer presents them.
|
|
55
86
|
|
|
56
87
|
> [!NOTE]
|
|
57
88
|
> Skills marked **📦 Bundle** ship a concise always-loaded entry point plus reference files Claude reads only when a task needs them — comprehensive coverage at a low context cost.
|
|
@@ -91,7 +122,7 @@ Cross-cutting craft — applies to any stack, any language.
|
|
|
91
122
|
| `git-merge-conflicts` | Resolve a merge/rebase conflict by intent, not by side — `zdiff3` to see the merge base, the inverted meaning of `ours`/`theirs` during a rebase, the semantic and tree conflicts git never marks, lockfiles and migrations you regenerate instead of merging |
|
|
92
123
|
| `research` | Open the source, don't recall it — route to the tool that owns the answer, primary sources only, pin the version you're actually on, run the validator instead of guessing, and mark every claim VERIFIED / INFERRED / UNVERIFIED |
|
|
93
124
|
| `typescript-best-practices` | TypeScript 7.x — type design, generics, type guards, `satisfies`, `using`, error handling, `tsconfig` |
|
|
94
|
-
| `testing-best-practices` 📦 | Arrange-Act-Assert, factory-based test data, isolation, mocking boundaries, a pyramid-balanced suite; bundle covers mutation testing — Stryker and mutmut setup, diff-scoped runs, the traps, and working score thresholds |
|
|
125
|
+
| `testing-best-practices` 📦 | Arrange-Act-Assert, factory-based test data, isolation, mocking boundaries, a pyramid-balanced suite; bundle covers mutation testing — Stryker and mutmut setup, `ignoreStatic`, diff-scoped runs, the traps, and working score thresholds |
|
|
95
126
|
| `security-practices` | OWASP Top 10 prevention, input validation, auth, SQL injection, XSS, CSRF, secure defaults |
|
|
96
127
|
|
|
97
128
|
## Commands
|
|
@@ -106,8 +137,8 @@ Portable slash commands installed to `.claude/commands/`. Some orchestrate paral
|
|
|
106
137
|
| `/refactor` | Detect size / complexity / duplication / coupling issues via 4 parallel subagents, then refactor |
|
|
107
138
|
| `/deep-review` | Multi-agent deep code review — 5 parallel subagents catch guard bypasses, lost async state, wrong-table queries, dead references, protocol violations |
|
|
108
139
|
| `/test-review` | Write-then-verify test review — scopes to the diff, runs red-green + mutation gates, then an isolated read-only subagent proves each test would catch a regression instead of rubber-stamping it |
|
|
140
|
+
| `/handoff` | Write a handoff doc so a fresh session picks the work up — captures what auto-compaction loses: decisions made and rejected, dead ends already ruled out, why the tree is dirty, and the one next step |
|
|
109
141
|
| `/lockdown` | Per-repo supply-chain hardening — detects the package managers, Dockerfiles, and CI in use, then guides you through and applies install-time, deploy, and pipeline hardening for npm/pnpm, pip/uv, Docker, and GitHub Actions |
|
|
110
|
-
| `/handoff` | Write a handoff doc so a fresh session picks the work up — captures what auto-compaction loses: decisions made and rejected, dead ends already ruled out, why the tree is dirty, and the one next step. Links to specs, commits, and PRs instead of restating them |
|
|
111
142
|
|
|
112
143
|
## How It Works
|
|
113
144
|
|
|
@@ -117,239 +148,35 @@ The CLI installs three kinds of artifact into your project's `.claude/` director
|
|
|
117
148
|
- **Commands** → `.claude/commands/` — slash commands you invoke directly.
|
|
118
149
|
- **Subagents** → `.claude/agents/` — declared by commands via `requires-agents`, installed for you.
|
|
119
150
|
|
|
151
|
+
It then offers to install the hooks. Two guard the **front**: nothing is written
|
|
152
|
+
until your skills have been evaluated *and* applied to the file being written.
|
|
153
|
+
Two guard the **back**: a `Stop` hook runs your typecheck and tests on the changed
|
|
154
|
+
files every turn, and a `PreToolUse` gate refuses to open or merge a PR without a
|
|
155
|
+
receipt proving the delivery gate passed on exactly that code.
|
|
156
|
+
|
|
157
|
+
Full details — every hook, every marker, every config key, and what to do when one
|
|
158
|
+
blocks something it shouldn't — are in **[HOOKS.md](./HOOKS.md)**.
|
|
159
|
+
|
|
120
160
|
### Tracking & Updates
|
|
121
161
|
|
|
122
162
|
Every install writes a manifest at `.claude/.claude-skills.json` recording what the CLI installed and the catalog version. On the next run it uses the manifest to:
|
|
123
163
|
|
|
124
164
|
- **Pre-check what you already have** in the picker — re-running doubles as an update screen; toggle to add or remove.
|
|
125
|
-
- **Detect stale items** — skills/commands renamed or removed from the catalog upstream
|
|
165
|
+
- **Detect stale items** — skills/commands renamed or removed from the catalog upstream are flagged, and the CLI offers to delete them.
|
|
126
166
|
- **Never touch what it didn't install** — the manifest is the CLI's own record; hand-written skills are invisible to it and always safe.
|
|
127
167
|
|
|
128
168
|
```bash
|
|
129
169
|
npx @spardutti/claude-skills --sync
|
|
130
170
|
```
|
|
131
171
|
|
|
132
|
-
`--local[=path]` reads the catalog from a working copy instead of GitHub, so an unreleased
|
|
133
|
-
change can be installed and tried without publishing it first:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
node ~/projects/claude-skills/cli/bin/cli.mjs --local
|
|
137
|
-
```
|
|
138
|
-
|
|
139
172
|
`--sync` refreshes every tracked item to the latest catalog and prunes stale ones in one shot — no menu. For a project that predates the manifest, the first normal run offers a one-time cleanup of `.claude/` content no longer in the catalog.
|
|
140
173
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
After installing skills, the CLI offers to set up a hook that **guarantees** Claude evaluates your skills before writing code — instead of a soft reminder it can ignore.
|
|
144
|
-
|
|
145
|
-
It installs four hooks and appends a rule to your `CLAUDE.md`. Three guard the
|
|
146
|
-
**front** — nothing is written until your skills are considered:
|
|
147
|
-
|
|
148
|
-
- `skill-gate.sh` — a `PreToolUse` gate on `Write|Edit|MultiEdit|Bash`
|
|
149
|
-
- `skill-application-gate.sh` — a second `PreToolUse` gate requiring each loaded skill to be applied, not just read
|
|
150
|
-
|
|
151
|
-
Both cover **Bash**, not only the file-writing tools. A session that edits through
|
|
152
|
-
`python3 - <<'PY'` in Bash walked past the old matcher entirely — twelve source files, not
|
|
153
|
-
one prompt — and some harnesses actively tell the model to prefer Bash for edits. Only
|
|
154
|
-
commands that can write are gated (a redirect, `tee`, `sed -i`, `cp`/`mv`, or an
|
|
155
|
-
interpreter given inline code or a heredoc); `git status` and a redirect to `/dev/null`
|
|
156
|
-
pass untouched, and the command that clears the gate is never gated against itself.
|
|
157
|
-
|
|
158
|
-
Neither gate fires on **prose** — `.md`, `.mdx`, `.txt`, `.rst`, `.adoc`, images. Skills
|
|
159
|
-
are about code, and a repo full of `PLAN_*.md` otherwise hits the gate on every write.
|
|
160
|
-
Config files (`.json`, `.yml`, `.toml`) stay gated, because skills do have rules about
|
|
161
|
-
`tsconfig`, `package.json`, and Compose.
|
|
162
|
-
- `skill-gate-automark.sh` — a `PostToolUse` hook on `Skill` that clears the gate
|
|
163
|
-
|
|
164
|
-
The last two guard the **back** — nothing finishes or ships until the code passes:
|
|
165
|
-
|
|
166
|
-
- `gauntlet.sh` — a `Stop` hook running the fast gates on your changed files (see [Automatic Verification](#automatic-verification-gauntlet))
|
|
167
|
-
- `ship-gate-hook.sh` — a `PreToolUse` gate on `Bash` that refuses `git commit` and `git push` without a receipt from `ship-gate.sh`
|
|
168
|
-
|
|
169
|
-
One more hook stays out of the way entirely:
|
|
170
|
-
|
|
171
|
-
- `version-check.sh` — a `SessionStart` hook that mentions a newer catalog at most once a day. It never touches the network in the foreground: it reads a cache the previous session refreshed in the background, so it costs a file read. Silent when there is no cache, no network, or nothing new. `CLAUDE_SKILLS_NO_VERSION_CHECK=1` turns it off.
|
|
172
|
-
|
|
173
|
-
<details>
|
|
174
|
-
<summary>How the gate works</summary>
|
|
175
|
-
|
|
176
|
-
The gate hard-blocks `Write`, `Edit`, and `MultiEdit` until a per-session marker exists at `/tmp/claude-skill-gate-<SESSION_ID>`. The marker is created automatically the first time Claude invokes any `Skill()` in the session — so the normal flow is: Claude lists skills as ACTIVATE/SKIP, calls `Skill()` for the ACTIVATE ones, and the gate clears for the rest of the session. If every skill is SKIP, Claude clears the gate with `touch /tmp/claude-skill-gate-<SESSION_ID>`.
|
|
177
|
-
|
|
178
|
-
The marker is **per-session, not per-turn** — short follow-ups like "yes" don't re-lock it. The gate auto-passes when a project has no `.claude/skills/*/SKILL.md`, so it's safe to leave on globally.
|
|
179
|
-
|
|
180
|
-
It registers in `.claude/settings.json`:
|
|
181
|
-
|
|
182
|
-
```json
|
|
183
|
-
{
|
|
184
|
-
"hooks": {
|
|
185
|
-
"PreToolUse": [
|
|
186
|
-
{ "matcher": "Write|Edit|MultiEdit|Bash", "hooks": [
|
|
187
|
-
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-gate.sh" } ] },
|
|
188
|
-
{ "matcher": "Write|Edit|MultiEdit|Bash", "hooks": [
|
|
189
|
-
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-application-gate.sh" } ] }
|
|
190
|
-
],
|
|
191
|
-
"PostToolUse": [
|
|
192
|
-
{ "matcher": "Skill", "hooks": [
|
|
193
|
-
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-gate-automark.sh" } ] }
|
|
194
|
-
]
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
</details>
|
|
200
|
-
|
|
201
|
-
### Automatic Verification (Gauntlet)
|
|
202
|
-
|
|
203
|
-
The same install adds `gauntlet.sh` — a `Stop` hook that runs the cheap, deterministic
|
|
204
|
-
gates on your changed files every time Claude finishes a turn. Green is silent; red exits 2,
|
|
205
|
-
which keeps the turn from ending, shows you the failure, and hands it to Claude to fix.
|
|
206
|
-
|
|
207
|
-
Exit 2 matters: `{"decision":"continue"}` also keeps the turn going, but its reason reaches
|
|
208
|
-
Claude alone. A failing gate was then invisible from the outside — indistinguishable from a
|
|
209
|
-
gauntlet that does nothing.
|
|
210
|
-
|
|
211
|
-
Keep the gates fast for a reason beyond patience: a `command` hook that reaches its timeout
|
|
212
|
-
(600s by default) is cancelled and its **output discarded**, so the turn ends as if the hook
|
|
213
|
-
never ran. A stalled gate is a silent pass, not a block.
|
|
214
|
-
|
|
215
|
-
Only fast gates live here (target: under ~30s). Mutation testing and deep review stay
|
|
216
|
-
manual — they belong to `/test-review` and `/deep-review`, not to every turn.
|
|
217
|
-
|
|
218
|
-
At install time the CLI reports what it found, so a silent no-op is never a surprise:
|
|
219
|
-
|
|
220
|
-
```text
|
|
221
|
-
Gauntlet detected tsc + vitest — it will run those on changed files.
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
If no test runner is detected it says so, and offers to write a `.claude/gauntlet.conf`
|
|
225
|
-
pointing at your test command. Detection covers vitest, jest, pytest, `tsc`, and `mypy`;
|
|
226
|
-
everything else needs that one config line.
|
|
227
|
-
|
|
228
|
-
<details>
|
|
229
|
-
<summary>How the gauntlet hook works</summary>
|
|
230
|
-
|
|
231
|
-
It runs a skip ladder first, cheapest check first, and quits at the first "no":
|
|
232
|
-
|
|
233
|
-
1. no changed files (planning turns, questions)
|
|
234
|
-
2. no changed **code** files (docs/config only)
|
|
235
|
-
3. this exact diff already passed — the hash of a green run is cached per session and repo
|
|
236
|
-
4. no gates detected for this repo
|
|
237
|
-
|
|
238
|
-
Rule 3 is what makes repeated `/ship` runs free: the gates run once per distinct diff.
|
|
239
|
-
|
|
240
|
-
To confirm it is alive when everything is green — green prints nothing, by design:
|
|
174
|
+
`--local[=path]` reads the catalog from a working copy instead of GitHub, so an unreleased change can be installed and tried without publishing it first:
|
|
241
175
|
|
|
242
176
|
```bash
|
|
243
|
-
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
Every run records why it ended in `/tmp/claude-gauntlet-<session>.why`, so a green run
|
|
247
|
-
and a skipped one are told apart without re-running the hook. `GAUNTLET_DEBUG=1` prints
|
|
248
|
-
the same line to stderr:
|
|
249
|
-
|
|
250
|
-
```text
|
|
251
|
-
gauntlet: green: gates passed
|
|
252
|
-
gauntlet: skipped: no changed code files (docs/config only)
|
|
253
|
-
gauntlet: green: gates passed, but the runner matched 0 test files — the changed code was never executed
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
That last one is the false green worth knowing about. A test runner given files it has no
|
|
257
|
-
tests for exits 0 and looks exactly like a pass — in a monorepo whose vitest workspace
|
|
258
|
-
excludes a package, **no** change in that package can ever be covered. The hook names it
|
|
259
|
-
instead of reporting green. Set `GAUNTLET_REQUIRE_TESTS=1` to make it block instead.
|
|
260
|
-
|
|
261
|
-
The stack is auto-detected: vitest / jest / pytest for tests, and for typechecking the
|
|
262
|
-
repo's own `typecheck` script if it has one, else bare `tsc --noEmit` or `mypy`. The script
|
|
263
|
-
is preferred because a workspace root `tsconfig.json` is often solution-style, where
|
|
264
|
-
`tsc --noEmit` does not mean what the repo's `tsc -b` means.
|
|
265
|
-
|
|
266
|
-
**Monorepos work, including the ones with nothing at the root.** Detection collects every
|
|
267
|
-
gate it finds rather than stopping at the first, and each gate carries the file pattern it
|
|
268
|
-
applies to — so a JS + Python repo gets four gates, and a turn that touched only `.py` files
|
|
269
|
-
runs the Python two and skips the JS two entirely. Typechecks always run before tests, and
|
|
270
|
-
the first red stops the rest.
|
|
271
|
-
|
|
272
|
-
When the root has no manifest, detection looks two levels down for `package.json` /
|
|
273
|
-
`pyproject.toml` / `pytest.ini` — the `web/` + `api/`, `apps/*`, `packages/*` layouts — and
|
|
274
|
-
scopes each gate to that folder's files, running the command from inside it. Nested gates
|
|
275
|
-
are only added for the kind the root did not already provide, so a repo that works today
|
|
276
|
-
keeps working.
|
|
277
|
-
|
|
278
|
-
Setting `GAUNTLET_TEST` or `GAUNTLET_TYPECHECK` switches the repo to explicit mode:
|
|
279
|
-
auto-detection is off and only what you set runs.
|
|
280
|
-
|
|
281
|
-
**A missing tool is a skip, not a red.** A fresh clone with no `node_modules`, or a venv
|
|
282
|
-
without pytest, would otherwise fail every gate and block every turn — so the hook reports
|
|
283
|
-
`skipped: node_modules is missing — run your install first` and gets out of the way. Only
|
|
284
|
-
a gate that actually ran and actually failed blocks. Override anything in
|
|
285
|
-
a config file — two are read, in this order, and the project's wins key by key:
|
|
286
|
-
|
|
287
|
-
```text
|
|
288
|
-
~/.claude/gauntlet.conf your defaults for every project
|
|
289
|
-
<project>/.claude/gauntlet.conf overrides for this one
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
```bash
|
|
293
|
-
GAUNTLET_OFF=1 # disable entirely
|
|
294
|
-
GAUNTLET_TYPECHECK='npm run check' # "" to skip the gate
|
|
295
|
-
GAUNTLET_TEST='npm test -- $FILES' # $FILES = changed code files
|
|
296
|
-
GAUNTLET_CODE_EXT='ts|tsx|py' # what counts as a change worth checking
|
|
297
|
-
GAUNTLET_DEBUG=1 # print the outcome to stderr every run
|
|
298
|
-
GAUNTLET_REQUIRE_TESTS=1 # block when the runner matched 0 test files
|
|
299
|
-
|
|
300
|
-
# /ship's gate reads its own keys from the same file
|
|
301
|
-
GAUNTLET_MAX_LINES=200 # per-file limit; 0 turns the check off
|
|
302
|
-
GAUNTLET_SOURCE_EXT='ts|tsx|py' # what counts as SOURCE — not the same question
|
|
303
|
-
GAUNTLET_MUTATE='bash mutate.sh' # replace per-project mutation detection
|
|
304
|
-
GAUNTLET_IGNORE_FILES='*.gen.ts' # globs that are never gated — see below
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
`GAUNTLET_IGNORE_FILES` holds files nobody wrote, so the line limit and mutation both
|
|
308
|
-
skip them. It defaults to `*.gen.ts *.gen.tsx *.generated.* */migrations/*.py
|
|
309
|
-
*/alembic/versions/*.py */components/ui/*.tsx` — a TanStack `routeTree.gen.ts`, a Django
|
|
310
|
-
or Alembic migration, and a vendored shadcn component are all over 200 lines, none of
|
|
311
|
-
them can be split, and gating them only teaches `--force`. Every skipped file is named in
|
|
312
|
-
the output. Patterns match against `/<path>`, so `*/components/ui/*.tsx` covers the repo
|
|
313
|
-
root and `apps/web/src` alike. Set it to `""` to gate everything.
|
|
314
|
-
|
|
315
|
-
`GAUNTLET_SOURCE_EXT` is separate from `GAUNTLET_CODE_EXT` on purpose. The Stop hook asks
|
|
316
|
-
"did anything worth checking change", so a docs repo sets `CODE_EXT` to include `.md`. The
|
|
317
|
-
line limit and mutation ask about source code, where that answer is wrong.
|
|
318
|
-
|
|
319
|
-
Set your house rules once in `~/.claude/gauntlet.conf`; only reach for the project file
|
|
320
|
-
where a repo does something unusual (or set `GAUNTLET_OFF=1` there to opt one out).
|
|
321
|
-
|
|
322
|
-
It registers in `.claude/settings.json`:
|
|
323
|
-
|
|
324
|
-
```json
|
|
325
|
-
{
|
|
326
|
-
"hooks": {
|
|
327
|
-
"Stop": [
|
|
328
|
-
{ "hooks": [
|
|
329
|
-
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/gauntlet.sh" } ] }
|
|
330
|
-
]
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
</details>
|
|
336
|
-
|
|
337
|
-
<details>
|
|
338
|
-
<summary>Manual install (without the CLI)</summary>
|
|
339
|
-
|
|
340
|
-
```bash
|
|
341
|
-
# Skills
|
|
342
|
-
cp -r skills/<skill-name> /path/to/project/.claude/skills/
|
|
343
|
-
|
|
344
|
-
# Commands
|
|
345
|
-
cp commands/<command-name>.md /path/to/project/.claude/commands/
|
|
346
|
-
|
|
347
|
-
# Subagents — see the command's `requires-agents` frontmatter
|
|
348
|
-
cp agents/<agent-name>.md /path/to/project/.claude/agents/
|
|
177
|
+
node ~/projects/claude-skills/cli/bin/cli.mjs --local
|
|
349
178
|
```
|
|
350
179
|
|
|
351
|
-
</details>
|
|
352
|
-
|
|
353
180
|
## Repository Layout
|
|
354
181
|
|
|
355
182
|
```text
|
|
@@ -359,7 +186,7 @@ agents/ Subagent definitions — commands declare which they need via req
|
|
|
359
186
|
scripts/ validate-skills.mjs — checks skill length caps and reference integrity
|
|
360
187
|
gauntlet.sh — the Stop-hook verification gates, embedded by the CLI
|
|
361
188
|
ship-gate.sh — /ship's file-length and mutation checks, behind an exit code
|
|
362
|
-
ship-gate-hook.sh — refuses
|
|
189
|
+
ship-gate-hook.sh — refuses gh pr create/merge without a ship-gate receipt
|
|
363
190
|
version-check.sh — SessionStart nudge when a newer catalog is published
|
|
364
191
|
gauntlet-selftest.sh — behavioural tests for the hooks (runs on pre-push)
|
|
365
192
|
gauntlet-survey.sh — what the hook would do in every repo under a directory
|
|
@@ -370,6 +197,12 @@ cli/ The npm installer (npx @spardutti/claude-skills); version in cli/
|
|
|
370
197
|
package.json Private dev-tooling package (claude-skills-dev) — not the published one
|
|
371
198
|
```
|
|
372
199
|
|
|
200
|
+
## Getting Help
|
|
201
|
+
|
|
202
|
+
- **Something broken, or a skill teaching something stale?** [Open an issue](https://github.com/Spardutti/claude-skills/issues).
|
|
203
|
+
- **A hook not firing, or a gate blocking something it shouldn't?** [HOOKS.md](./HOOKS.md) covers the markers, the matchers, and the escape hatches — including the auto-mode permission rule the gates need.
|
|
204
|
+
- **Want a skill for a tool that isn't here?** Issues are the place; see [Contributing](#contributing) for what a good one looks like.
|
|
205
|
+
|
|
373
206
|
## Contributing
|
|
374
207
|
|
|
375
208
|
Skills live in `skills/<name>/SKILL.md`. Authoring conventions are in [CLAUDE.md](./CLAUDE.md) — the short version:
|
|
@@ -388,9 +221,8 @@ Skills live in `skills/<name>/SKILL.md`. Authoring conventions are in [CLAUDE.md
|
|
|
388
221
|
anything. Read the rows that say `nothing`.
|
|
389
222
|
|
|
390
223
|
To get the validator on **every turn** instead of only on push, drop this into
|
|
391
|
-
this repo's `.claude/gauntlet.conf
|
|
392
|
-
|
|
393
|
-
only right for a skills repo:
|
|
224
|
+
this repo's `.claude/gauntlet.conf`. It has to be the project file, not
|
|
225
|
+
`~/.claude/gauntlet.conf`: these settings are only right for a skills repo:
|
|
394
226
|
|
|
395
227
|
```bash
|
|
396
228
|
GAUNTLET_CODE_EXT='md|mjs|js|json|sh' # markdown IS the product here
|
package/bin/cli.mjs
CHANGED
|
@@ -273,6 +273,7 @@ async function main() {
|
|
|
273
273
|
});
|
|
274
274
|
if (doScaffold) {
|
|
275
275
|
const installs = [];
|
|
276
|
+
const apiInstalls = [];
|
|
276
277
|
const vitest = [];
|
|
277
278
|
for (const n of stryker) {
|
|
278
279
|
const r = await scaffoldStryker(CWD, n.rel);
|
|
@@ -284,7 +285,9 @@ async function main() {
|
|
|
284
285
|
}
|
|
285
286
|
// A project that already runs Stryker needs neither the install nor
|
|
286
287
|
// the vitest exclude repeated at it; it only lacked the ignorer.
|
|
287
|
-
if (
|
|
288
|
+
if (n.ignorerOnly) {
|
|
289
|
+
apiInstalls.push(r.apiInstall);
|
|
290
|
+
} else {
|
|
288
291
|
installs.push(r.install);
|
|
289
292
|
vitest.push(r.vitest);
|
|
290
293
|
}
|
|
@@ -300,9 +303,10 @@ async function main() {
|
|
|
300
303
|
// The ignorer imports @stryker-mutator/api, which npm hoists by
|
|
301
304
|
// accident and pnpm does not. Missing, it prints one WARN PluginLoader
|
|
302
305
|
// line, loads nothing, and every className mutant comes back survived.
|
|
303
|
-
if (
|
|
306
|
+
if (apiInstalls.length > 0) {
|
|
304
307
|
console.log(`\n ${chalk.yellow("!")} ${chalk.bold("The ignorer needs its own dependency:")}`);
|
|
305
|
-
console.log(` ${chalk.cyan(
|
|
308
|
+
for (const c of apiInstalls) console.log(` ${chalk.cyan(c)}`);
|
|
309
|
+
console.log(` ${chalk.dim("without it the ignorer silently does not load")}`);
|
|
306
310
|
}
|
|
307
311
|
}
|
|
308
312
|
}
|
package/lib/scaffold-stryker.mjs
CHANGED
|
@@ -78,9 +78,11 @@ async function exists(p) {
|
|
|
78
78
|
// npm's flat node_modules hoists it so the import resolves by accident, and
|
|
79
79
|
// pnpm's isolated store does not. The failure is one WARN PluginLoader line and
|
|
80
80
|
// an ignorer that never loads, so every class-name mutant comes back survived.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
// A workspace keeps its lockfile at the root, not in apps/web, so asking only
|
|
82
|
+
// the sub-project hands a pnpm monorepo an npm command.
|
|
83
|
+
async function installCommand(projectDir, dir, rel, pkgs) {
|
|
84
|
+
const pnpm =
|
|
85
|
+
(await exists(join(projectDir, "pnpm-lock.yaml"))) || (await exists(join(dir, "pnpm-lock.yaml")));
|
|
84
86
|
return pnpm
|
|
85
87
|
? `pnpm --dir ${rel || "."} add -D ${pkgs}`
|
|
86
88
|
: `npm --prefix ${rel || "."} i -D ${pkgs}`;
|
|
@@ -185,7 +187,17 @@ export async function scaffoldStryker(projectDir, rel) {
|
|
|
185
187
|
kept,
|
|
186
188
|
patched,
|
|
187
189
|
unreadable,
|
|
188
|
-
install: await installCommand(
|
|
190
|
+
install: await installCommand(
|
|
191
|
+
projectDir,
|
|
192
|
+
dir,
|
|
193
|
+
rel,
|
|
194
|
+
"@stryker-mutator/core @stryker-mutator/vitest-runner @stryker-mutator/api",
|
|
195
|
+
),
|
|
196
|
+
// A project that already runs Stryker has the other two packages and needs
|
|
197
|
+
// only this one, which nothing depends on directly. Printed as a command
|
|
198
|
+
// rather than a sentence: the sentence made every reader guess their own
|
|
199
|
+
// package manager and path.
|
|
200
|
+
apiInstall: await installCommand(projectDir, dir, rel, "@stryker-mutator/api"),
|
|
189
201
|
// Stryker copies the whole project into .stryker-tmp/sandbox-*/, so every
|
|
190
202
|
// test file briefly exists twice and the runner collects both. They pass,
|
|
191
203
|
// so a doubled test count is the only symptom. Spread configDefaults.exclude
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spardutti/claude-skills",
|
|
3
|
-
"version": "2.25.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.25.2",
|
|
4
|
+
"description": "Guardrails for Claude Code — best-practice skills, planning commands, and delivery gates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|