@spardutti/claude-skills 2.13.0 → 2.14.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.
- package/README.md +6 -1
- package/lib/setup-hook.mjs +17 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ The CLI will:
|
|
|
51
51
|
|
|
52
52
|
## Skill Catalog
|
|
53
53
|
|
|
54
|
-
**
|
|
54
|
+
**14 skills**, grouped the same way the installer presents them.
|
|
55
55
|
|
|
56
56
|
> [!NOTE]
|
|
57
57
|
> 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.
|
|
@@ -69,6 +69,7 @@ The CLI will:
|
|
|
69
69
|
| Skill | What it covers |
|
|
70
70
|
|-------|----------------|
|
|
71
71
|
| `fastapi` 📦 | FastAPI — async correctness, `Annotated` dependency injection, `lifespan`, response models, testing with dependency overrides; bundle covers Pydantic, Alembic, Celery, and list endpoints (pagination/filtering/search/sorting) |
|
|
72
|
+
| `express` 📦 | Express 5 — automatic async error forwarding, the four-argument error handler, named wildcards, `req.query` as a getter, validation at the boundary, thin routes, the helmet/CORS/rate-limit baseline; bundle covers tRPC v11 on Express |
|
|
72
73
|
| `drf-best-practices` | Django REST Framework — thin serializers, service layer, queryset optimization, object-level permissions |
|
|
73
74
|
| `drizzle-orm` | Drizzle ORM — schema design, identity columns, relations, migration safety, type inference |
|
|
74
75
|
| `docker-best-practices` | Multi-stage builds, layer caching, security hardening, Compose Watch, health checks |
|
|
@@ -346,6 +347,7 @@ scripts/ validate-skills.mjs — checks skill length caps and reference in
|
|
|
346
347
|
ship-gate-hook.sh — refuses git commit/push without a ship-gate receipt
|
|
347
348
|
gauntlet-selftest.sh — 15 behavioural tests for the hook (runs on pre-push)
|
|
348
349
|
gauntlet-survey.sh — what the hook would do in every repo under a directory
|
|
350
|
+
preflight.sh — everything that must pass before a release
|
|
349
351
|
cli/ The npm installer (npx @spardutti/claude-skills); version in cli/package.json
|
|
350
352
|
.husky/ pre-push hook running the skill validator
|
|
351
353
|
package.json Private dev-tooling package (claude-skills-dev) — not the published one
|
|
@@ -359,6 +361,9 @@ Skills live in `skills/<name>/SKILL.md`. Authoring conventions are in [CLAUDE.md
|
|
|
359
361
|
- `SKILL.md` ≤ 350 lines; reference files ≤ 500 and need a `## Contents` TOC past 100 lines.
|
|
360
362
|
- References are one level deep — `SKILL.md` links them, they don't link each other.
|
|
361
363
|
- `npm run validate-skills` enforces this; it also runs on `pre-push`.
|
|
364
|
+
- Before any release, run `bash scripts/preflight.sh` — validator, self-tests, a check that the
|
|
365
|
+
hook scripts embedded in the CLI still match `scripts/`, detection against every repo under
|
|
366
|
+
`~/projects`, and that the version is ahead of the registry. Do not publish while it is red.
|
|
362
367
|
- Changing `scripts/gauntlet.sh`? Run `bash scripts/gauntlet-selftest.sh` (also on `pre-push`),
|
|
363
368
|
and run `bash scripts/gauntlet-survey.sh ~/projects` before releasing. Detection is where
|
|
364
369
|
this hook keeps breaking, because real repos are shaped in ways invented test repos aren't —
|
package/lib/setup-hook.mjs
CHANGED
|
@@ -941,6 +941,18 @@ export async function writeGauntletConf(targetDir, testCommand) {
|
|
|
941
941
|
return confPath;
|
|
942
942
|
}
|
|
943
943
|
|
|
944
|
+
// Register the entry, or CORRECT the one already there. Skipping when a hook of
|
|
945
|
+
// this name exists leaves an existing install pinned to whatever matcher it was
|
|
946
|
+
// first written with — which is how the skill gates kept watching only
|
|
947
|
+
// Write|Edit|MultiEdit after they learned to cover Bash.
|
|
948
|
+
function upsert(list, filename, entry) {
|
|
949
|
+
const existing = list.find((e) => e.hooks?.some((h) => h.command?.endsWith(filename)));
|
|
950
|
+
if (!existing) return list.push(entry);
|
|
951
|
+
if (entry.matcher === undefined) delete existing.matcher;
|
|
952
|
+
else existing.matcher = entry.matcher;
|
|
953
|
+
existing.hooks = entry.hooks;
|
|
954
|
+
}
|
|
955
|
+
|
|
944
956
|
export async function setupHook(targetDir = process.cwd()) {
|
|
945
957
|
const resolved = resolve(targetDir);
|
|
946
958
|
const hooksDir = join(resolved, ".claude", "hooks");
|
|
@@ -1006,10 +1018,7 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
1006
1018
|
};
|
|
1007
1019
|
|
|
1008
1020
|
if (Array.isArray(settings.hooks.PreToolUse)) {
|
|
1009
|
-
|
|
1010
|
-
entry.hooks?.some((h) => h.command?.endsWith(GATE_FILENAME))
|
|
1011
|
-
);
|
|
1012
|
-
if (!exists) settings.hooks.PreToolUse.push(gateEntry);
|
|
1021
|
+
upsert(settings.hooks.PreToolUse, GATE_FILENAME, gateEntry);
|
|
1013
1022
|
} else {
|
|
1014
1023
|
settings.hooks.PreToolUse = [gateEntry];
|
|
1015
1024
|
}
|
|
@@ -1020,10 +1029,7 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
1020
1029
|
matcher: "Write|Edit|MultiEdit|Bash",
|
|
1021
1030
|
hooks: [{ type: "command", command: applicationCommand }],
|
|
1022
1031
|
};
|
|
1023
|
-
|
|
1024
|
-
entry.hooks?.some((h) => h.command?.endsWith(APPLICATION_GATE_FILENAME))
|
|
1025
|
-
);
|
|
1026
|
-
if (!applicationAlreadyRegistered) settings.hooks.PreToolUse.push(applicationEntry);
|
|
1032
|
+
upsert(settings.hooks.PreToolUse, APPLICATION_GATE_FILENAME, applicationEntry);
|
|
1027
1033
|
|
|
1028
1034
|
// Register PreToolUse ship-gate receipt check on Bash.
|
|
1029
1035
|
const shipGateCommand = `$CLAUDE_PROJECT_DIR/.claude/hooks/${SHIP_GATE_HOOK_FILENAME}`;
|
|
@@ -1031,10 +1037,7 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
1031
1037
|
matcher: "Bash",
|
|
1032
1038
|
hooks: [{ type: "command", command: shipGateCommand }],
|
|
1033
1039
|
};
|
|
1034
|
-
|
|
1035
|
-
entry.hooks?.some((h) => h.command?.endsWith(SHIP_GATE_HOOK_FILENAME))
|
|
1036
|
-
);
|
|
1037
|
-
if (!shipGateRegistered) settings.hooks.PreToolUse.push(shipGateEntry);
|
|
1040
|
+
upsert(settings.hooks.PreToolUse, SHIP_GATE_HOOK_FILENAME, shipGateEntry);
|
|
1038
1041
|
|
|
1039
1042
|
// Register PostToolUse auto-mark on Skill.
|
|
1040
1043
|
const autoMarkCommand = `$CLAUDE_PROJECT_DIR/.claude/hooks/${AUTO_MARK_FILENAME}`;
|
|
@@ -1044,10 +1047,7 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
1044
1047
|
};
|
|
1045
1048
|
|
|
1046
1049
|
if (Array.isArray(settings.hooks.PostToolUse)) {
|
|
1047
|
-
|
|
1048
|
-
entry.hooks?.some((h) => h.command?.endsWith(AUTO_MARK_FILENAME))
|
|
1049
|
-
);
|
|
1050
|
-
if (!exists) settings.hooks.PostToolUse.push(autoMarkEntry);
|
|
1050
|
+
upsert(settings.hooks.PostToolUse, AUTO_MARK_FILENAME, autoMarkEntry);
|
|
1051
1051
|
} else {
|
|
1052
1052
|
settings.hooks.PostToolUse = [autoMarkEntry];
|
|
1053
1053
|
}
|
|
@@ -1057,10 +1057,7 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
1057
1057
|
const gauntletEntry = { hooks: [{ type: "command", command: gauntletCommand }] };
|
|
1058
1058
|
|
|
1059
1059
|
if (Array.isArray(settings.hooks.Stop)) {
|
|
1060
|
-
|
|
1061
|
-
entry.hooks?.some((h) => h.command?.endsWith(GAUNTLET_FILENAME))
|
|
1062
|
-
);
|
|
1063
|
-
if (!exists) settings.hooks.Stop.push(gauntletEntry);
|
|
1060
|
+
upsert(settings.hooks.Stop, GAUNTLET_FILENAME, gauntletEntry);
|
|
1064
1061
|
} else {
|
|
1065
1062
|
settings.hooks.Stop = [gauntletEntry];
|
|
1066
1063
|
}
|