@spardutti/claude-skills 1.28.0 → 1.29.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.
- package/README.md +20 -5
- package/lib/setup-hook.mjs +15 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,18 +101,22 @@ The CLI will:
|
|
|
101
101
|
|
|
102
102
|
After installing skills, the CLI asks if you want to set up automatic skill evaluation. If you say yes, it will:
|
|
103
103
|
|
|
104
|
-
- **
|
|
104
|
+
- **Install two hooks** in `.claude/hooks/`:
|
|
105
|
+
- `skill-gate.sh` — PreToolUse gate on `Write|Edit|MultiEdit`
|
|
106
|
+
- `skill-gate-automark.sh` — PostToolUse on `Skill` that auto-clears the gate
|
|
105
107
|
- **Update your `CLAUDE.md`** with the skill-evaluation rule
|
|
106
108
|
|
|
107
|
-
The gate hard-blocks `Write`, `Edit`, and `MultiEdit`
|
|
109
|
+
The gate hard-blocks `Write`, `Edit`, and `MultiEdit` until a per-session marker file 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 by running `touch /tmp/claude-skill-gate-<SESSION_ID>`.
|
|
110
|
+
|
|
111
|
+
The marker is **per-session, not per-turn** — short follow-ups like "yes" don't re-lock the gate after evaluation has already happened.
|
|
108
112
|
|
|
109
113
|
Unlike a soft reminder injected into context (which Claude can ignore), the gate denies the tool call outright — so the only path forward is to actually evaluate skills.
|
|
110
114
|
|
|
111
|
-
The gate auto-passes when the project has no skills
|
|
115
|
+
The gate auto-passes when the project has no `.claude/skills/*/SKILL.md` files, so it's safe to leave on globally.
|
|
112
116
|
|
|
113
117
|
### What gets created
|
|
114
118
|
|
|
115
|
-
**`.claude/settings.json`** — Registers
|
|
119
|
+
**`.claude/settings.json`** — Registers both hooks:
|
|
116
120
|
|
|
117
121
|
```json
|
|
118
122
|
{
|
|
@@ -127,12 +131,23 @@ The gate auto-passes when the project has no skills installed, so it's safe to l
|
|
|
127
131
|
}
|
|
128
132
|
]
|
|
129
133
|
}
|
|
134
|
+
],
|
|
135
|
+
"PostToolUse": [
|
|
136
|
+
{
|
|
137
|
+
"matcher": "Skill",
|
|
138
|
+
"hooks": [
|
|
139
|
+
{
|
|
140
|
+
"type": "command",
|
|
141
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-gate-automark.sh"
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
130
145
|
]
|
|
131
146
|
}
|
|
132
147
|
}
|
|
133
148
|
```
|
|
134
149
|
|
|
135
|
-
**`CLAUDE.md`** — Appends the evaluation rule
|
|
150
|
+
**`CLAUDE.md`** — Appends the skill-evaluation rule that tells Claude to enumerate skills as ACTIVATE/SKIP and call `Skill()` for ACTIVATE entries before writing code.
|
|
136
151
|
|
|
137
152
|
## Manual Install
|
|
138
153
|
|
package/lib/setup-hook.mjs
CHANGED
|
@@ -2,25 +2,15 @@ import { mkdir, writeFile, readFile, chmod } from "node:fs/promises";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
// PreToolUse gate on Write|Edit|MultiEdit. Blocks the tool call unless
|
|
5
|
-
// a per-
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// writes assistant content blocks to JSONL only after the turn completes,
|
|
11
|
-
// so [skills-checked] text emitted in the same message as a tool_use is
|
|
12
|
-
// invisible to PreToolUse). /tmp keeps the marker out of the project
|
|
13
|
-
// directory so it can't leak into commits.
|
|
14
|
-
//
|
|
15
|
-
// LAST_PROMPT_UUID is detected by matching user-role lines whose content
|
|
16
|
-
// is a JSON string ("role":"user","content":"..."), which excludes
|
|
17
|
-
// tool_results, skill loads, task notifications, and slash-command
|
|
18
|
-
// payloads (all of which use array content).
|
|
5
|
+
// a per-session marker file exists at /tmp/claude-skill-gate-<SESSION_ID>.
|
|
6
|
+
// Per-session (not per-prompt) so simple confirmations like "yes" don't
|
|
7
|
+
// re-lock the gate after evaluation has already happened in the session.
|
|
8
|
+
// The PostToolUse hook on Skill creates the marker automatically; for
|
|
9
|
+
// all-SKIP cases the model can `touch` the path manually.
|
|
19
10
|
//
|
|
20
11
|
// Pass-through cases:
|
|
21
12
|
// - project has no .claude/skills/*/SKILL.md files
|
|
22
|
-
// -
|
|
23
|
-
// - no typed user prompt found in transcript
|
|
13
|
+
// - session_id missing from hook input
|
|
24
14
|
const GATE_SCRIPT = `#!/bin/bash
|
|
25
15
|
# PreToolUse gate: forces skill evaluation before file-writing tools run.
|
|
26
16
|
|
|
@@ -33,48 +23,34 @@ if ! find "$PROJECT_DIR" -path '*/.claude/skills/*/SKILL.md' 2>/dev/null | grep
|
|
|
33
23
|
exit 0
|
|
34
24
|
fi
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
if [ -z "$
|
|
38
|
-
exit 0
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
LAST_LINE=$(grep -E '"role":"user","content":"' "$TRANSCRIPT" 2>/dev/null | tail -1)
|
|
42
|
-
LAST_PROMPT_UUID=$(printf '%s' "$LAST_LINE" | grep -o '"uuid":"[^"]*"' | head -1 | sed 's/"uuid":"//;s/"$//')
|
|
43
|
-
if [ -z "$LAST_PROMPT_UUID" ]; then
|
|
26
|
+
SESSION_ID=$(printf '%s' "$INPUT" | grep -o '"session_id":"[^"]*"' | head -1 | sed 's/"session_id":"//; s/"$//')
|
|
27
|
+
if [ -z "$SESSION_ID" ]; then
|
|
44
28
|
exit 0
|
|
45
29
|
fi
|
|
46
30
|
|
|
47
|
-
MARKER="/tmp/claude-skill-gate-$
|
|
31
|
+
MARKER="/tmp/claude-skill-gate-$SESSION_ID"
|
|
48
32
|
if [ -f "$MARKER" ]; then
|
|
49
|
-
find /tmp -maxdepth 1 -name 'claude-skill-gate-*' ! -name "claude-skill-gate-$LAST_PROMPT_UUID" -delete 2>/dev/null
|
|
50
33
|
exit 0
|
|
51
34
|
fi
|
|
52
35
|
|
|
53
36
|
cat <<EOF
|
|
54
|
-
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"
|
|
37
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"BLOCKED: skill evaluation required before file edits in this session.\\n\\nStep 1 — evaluate every available skill as ACTIVATE or SKIP with a one-line reason.\\n\\nStep 2 — you MUST take EXACTLY ONE of these tool actions to clear the gate. Listing skills in text is NOT enough; retrying the edit without doing one of these will be denied again:\\n (a) If any skill is ACTIVATE → call Skill(name) for it. This auto-clears the gate.\\n (b) If ALL skills are SKIP → run this Bash tool call: touch /tmp/claude-skill-gate-$SESSION_ID\\n\\nStep 3 — only after Step 2 completes, retry the file edit."}}
|
|
55
38
|
EOF
|
|
56
39
|
exit 0
|
|
57
40
|
`;
|
|
58
41
|
|
|
59
|
-
// PostToolUse on Skill: auto-creates the gate marker
|
|
60
|
-
// need an explicit touch. Uses the same UUID-detection logic as the gate.
|
|
42
|
+
// PostToolUse on Skill: auto-creates the per-session gate marker.
|
|
61
43
|
const AUTO_MARK_SCRIPT = `#!/bin/bash
|
|
62
|
-
# PostToolUse on Skill: auto-marks the skill-gate as satisfied.
|
|
44
|
+
# PostToolUse on Skill: auto-marks the skill-gate as satisfied for the session.
|
|
63
45
|
|
|
64
46
|
INPUT=$(cat)
|
|
65
47
|
|
|
66
|
-
|
|
67
|
-
if [ -z "$
|
|
68
|
-
exit 0
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
LAST_LINE=$(grep -E '"role":"user","content":"' "$TRANSCRIPT" 2>/dev/null | tail -1)
|
|
72
|
-
LAST_PROMPT_UUID=$(printf '%s' "$LAST_LINE" | grep -o '"uuid":"[^"]*"' | head -1 | sed 's/"uuid":"//;s/"$//')
|
|
73
|
-
if [ -z "$LAST_PROMPT_UUID" ]; then
|
|
48
|
+
SESSION_ID=$(printf '%s' "$INPUT" | grep -o '"session_id":"[^"]*"' | head -1 | sed 's/"session_id":"//; s/"$//')
|
|
49
|
+
if [ -z "$SESSION_ID" ]; then
|
|
74
50
|
exit 0
|
|
75
51
|
fi
|
|
76
52
|
|
|
77
|
-
touch "/tmp/claude-skill-gate-$
|
|
53
|
+
touch "/tmp/claude-skill-gate-$SESSION_ID"
|
|
78
54
|
exit 0
|
|
79
55
|
`;
|
|
80
56
|
|