@slamb2k/mad-skills 2.0.18 → 2.0.19
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/.claude-plugin/plugin.json +1 -1
- package/hooks/hooks.json +2 -2
- package/hooks/lib/state.cjs +22 -1
- package/hooks/session-guard.cjs +124 -17
- package/package.json +1 -1
- package/skills/manifest.json +1 -1
package/hooks/hooks.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"hooks": {
|
|
3
3
|
"SessionStart": [{
|
|
4
4
|
"matcher": "startup|clear|compact",
|
|
5
|
-
"hooks": [{ "type": "command", "command": "node
|
|
5
|
+
"hooks": [{ "type": "command", "command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/slamb2k\"; node \"$_R/hooks/session-guard.cjs\" check", "timeout": 30 }]
|
|
6
6
|
}],
|
|
7
7
|
"UserPromptSubmit": [{
|
|
8
|
-
"hooks": [{ "type": "command", "command": "node
|
|
8
|
+
"hooks": [{ "type": "command", "command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/slamb2k\"; node \"$_R/hooks/session-guard.cjs\" remind", "timeout": 10 }]
|
|
9
9
|
}]
|
|
10
10
|
}
|
|
11
11
|
}
|
package/hooks/lib/state.cjs
CHANGED
|
@@ -49,4 +49,25 @@ function isRecentlyChecked(projectDir, seconds = 5) {
|
|
|
49
49
|
return (Date.now() - data.timestamp) < seconds * 1000;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
// ─── persistent per-project preferences ───────────────────────────────
|
|
53
|
+
|
|
54
|
+
function prefsPath(projectDir) {
|
|
55
|
+
return join(STATE_DIR, `${projectKey(projectDir)}-prefs.json`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function loadPrefs(projectDir) {
|
|
59
|
+
const path = prefsPath(projectDir);
|
|
60
|
+
if (!existsSync(path)) return {};
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
63
|
+
} catch {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function savePrefs(projectDir, prefs) {
|
|
69
|
+
ensureDir();
|
|
70
|
+
writeFileSync(prefsPath(projectDir), JSON.stringify(prefs, null, 2));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { save, load, clear, isRecentlyChecked, loadPrefs, savePrefs };
|
package/hooks/session-guard.cjs
CHANGED
|
@@ -57,7 +57,8 @@ function check() {
|
|
|
57
57
|
'No CLAUDE.md found. Want me to set up this project for Claude Code?',
|
|
58
58
|
'single_select',
|
|
59
59
|
[
|
|
60
|
-
'"
|
|
60
|
+
'"Set up with BRACE" \u2014 run `/brace` to scaffold CLAUDE.md + GOTCHA framework (goals, tools, context)',
|
|
61
|
+
'"Basic init" \u2014 run `/init` to scaffold CLAUDE.md only',
|
|
61
62
|
'"Skip" \u2014 continue without one',
|
|
62
63
|
],
|
|
63
64
|
);
|
|
@@ -67,6 +68,12 @@ function check() {
|
|
|
67
68
|
|
|
68
69
|
output.add(`[SESSION GUARD] \u2705 CLAUDE.md found in: ${PROJECT_DIR}`);
|
|
69
70
|
|
|
71
|
+
// 1b) BRACE framework check
|
|
72
|
+
checkBrace(PROJECT_DIR, output);
|
|
73
|
+
|
|
74
|
+
// 1c) Rig (dev tooling) check
|
|
75
|
+
checkRig(PROJECT_DIR, output);
|
|
76
|
+
|
|
70
77
|
// 2) Task List ID
|
|
71
78
|
checkTaskList(PROJECT_DIR, gitRoot, output);
|
|
72
79
|
|
|
@@ -111,33 +118,119 @@ function remind() {
|
|
|
111
118
|
|
|
112
119
|
state.clear(PROJECT_DIR);
|
|
113
120
|
|
|
114
|
-
//
|
|
115
|
-
if (!pending.context.includes('\u26A0\uFE0F') && !pending.context.includes('\u2139\uFE0F')) {
|
|
116
|
-
console.log(JSON.stringify({}));
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Strip banner from re-emission (already shown at SessionStart)
|
|
121
|
+
// Split into banner and SESSION GUARD body
|
|
121
122
|
const lines = pending.context.split('\n');
|
|
122
123
|
const guardIdx = lines.findIndex(l => l.startsWith('[SESSION GUARD]'));
|
|
124
|
+
const banner = guardIdx >= 0 ? lines.slice(0, guardIdx).join('\n').trim() : '';
|
|
123
125
|
const body = guardIdx >= 0 ? lines.slice(guardIdx).join('\n') : pending.context;
|
|
124
126
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
const hasWarnings = pending.context.includes('\u26A0\uFE0F') || pending.context.includes('\u2139\uFE0F');
|
|
128
|
+
const parts = [];
|
|
129
|
+
|
|
130
|
+
// Always include banner with display directive
|
|
131
|
+
if (banner) {
|
|
132
|
+
parts.push(
|
|
133
|
+
'[SESSION GUARD] DISPLAY: Render the banner below in a fenced code block before any other response.',
|
|
134
|
+
'',
|
|
135
|
+
banner,
|
|
136
|
+
'',
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Include warnings/signals if present
|
|
141
|
+
if (hasWarnings) {
|
|
142
|
+
parts.push(
|
|
143
|
+
'[SESSION GUARD \u2014 FIRST PROMPT REMINDER]',
|
|
144
|
+
'The following was detected at session start. Act on these items NOW using',
|
|
145
|
+
'AskUserQuestion BEFORE proceeding with the user\'s request.',
|
|
146
|
+
'',
|
|
147
|
+
body,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Nothing to emit (no banner, no warnings)
|
|
152
|
+
if (parts.length === 0) {
|
|
153
|
+
console.log(JSON.stringify({}));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
132
156
|
|
|
133
157
|
console.log(JSON.stringify({
|
|
134
158
|
hookSpecificOutput: {
|
|
135
159
|
hookEventName: 'UserPromptSubmit',
|
|
136
|
-
additionalContext:
|
|
160
|
+
additionalContext: parts.join('\n'),
|
|
137
161
|
},
|
|
138
162
|
}));
|
|
139
163
|
}
|
|
140
164
|
|
|
165
|
+
// ─── brace check ──────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
function checkBrace(projectDir, output) {
|
|
168
|
+
const manifest = join(projectDir, 'goals', 'manifest.md');
|
|
169
|
+
if (existsSync(manifest)) return; // BRACE already set up
|
|
170
|
+
|
|
171
|
+
const prefs = state.loadPrefs(projectDir);
|
|
172
|
+
if (prefs.braceDismissed) return; // User said don't ask again
|
|
173
|
+
|
|
174
|
+
output.add('[SESSION GUARD] \u2139\uFE0F CLAUDE.md exists but no GOTCHA/BRACE framework detected.');
|
|
175
|
+
output.add('[SESSION GUARD] BRACE_DISMISS: If the user selects "Don\'t ask again", run: node <path-to-session-guard.cjs> dismiss-brace');
|
|
176
|
+
output.addQuestion(
|
|
177
|
+
'This project has a CLAUDE.md but no GOTCHA/BRACE framework (goals/, tools/, context/). Want to set it up?',
|
|
178
|
+
'single_select',
|
|
179
|
+
[
|
|
180
|
+
'"Set up BRACE" \u2014 run `/brace` to add the GOTCHA framework structure',
|
|
181
|
+
'"Not now" \u2014 skip for this session',
|
|
182
|
+
'"Don\'t ask again" \u2014 dismiss permanently for this project',
|
|
183
|
+
],
|
|
184
|
+
'low',
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── rig check ────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
const PLATFORM_MARKERS = [
|
|
191
|
+
'package.json', 'pyproject.toml', 'requirements.txt', 'setup.py',
|
|
192
|
+
'go.mod', 'Cargo.toml', 'Gemfile', 'pom.xml', 'build.gradle',
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
const INFRA_MARKERS = [
|
|
196
|
+
'lefthook.yml', '.lefthook.yml', // git hooks
|
|
197
|
+
'.husky', // git hooks (alt)
|
|
198
|
+
'.gitmessage', // commit template
|
|
199
|
+
'.github/pull_request_template.md', // PR template (GitHub)
|
|
200
|
+
'.azuredevops/pull_request_template.md', // PR template (Azure)
|
|
201
|
+
'.github/workflows', // CI (GitHub Actions)
|
|
202
|
+
'azure-pipelines.yml', // CI (Azure DevOps)
|
|
203
|
+
'.gitlab-ci.yml', // CI (GitLab)
|
|
204
|
+
'Jenkinsfile', // CI (Jenkins)
|
|
205
|
+
'.circleci', // CI (CircleCI)
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
function checkRig(projectDir, output) {
|
|
209
|
+
const prefs = state.loadPrefs(projectDir);
|
|
210
|
+
if (prefs.rigDismissed) return;
|
|
211
|
+
|
|
212
|
+
// Need at least one platform
|
|
213
|
+
const hasPlatform = PLATFORM_MARKERS.some(f => existsSync(join(projectDir, f)));
|
|
214
|
+
if (!hasPlatform) return;
|
|
215
|
+
|
|
216
|
+
// If any infra marker exists, rig is (at least partially) set up
|
|
217
|
+
const hasInfra = INFRA_MARKERS.some(f => existsSync(join(projectDir, f)));
|
|
218
|
+
if (hasInfra) return;
|
|
219
|
+
|
|
220
|
+
output.add('[SESSION GUARD] \u2139\uFE0F Project has code but no dev tooling (hooks, CI, PR templates) detected.');
|
|
221
|
+
output.add('[SESSION GUARD] RIG_DISMISS: If the user selects "Don\'t ask again", run: node <path-to-session-guard.cjs> dismiss-rig');
|
|
222
|
+
output.addQuestion(
|
|
223
|
+
'No dev tooling detected (git hooks, CI, PR templates, commit templates). Want to set it up?',
|
|
224
|
+
'single_select',
|
|
225
|
+
[
|
|
226
|
+
'"Set up with /rig" \u2014 configure lefthook, CI workflow, PR template, commit template',
|
|
227
|
+
'"Not now" \u2014 skip for this session',
|
|
228
|
+
'"Don\'t ask again" \u2014 dismiss permanently for this project',
|
|
229
|
+
],
|
|
230
|
+
'low',
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
141
234
|
// ─── helpers ───────────────────────────────────────────────────────────
|
|
142
235
|
|
|
143
236
|
function emit(output) {
|
|
@@ -160,8 +253,22 @@ switch (command) {
|
|
|
160
253
|
case 'remind':
|
|
161
254
|
remind();
|
|
162
255
|
break;
|
|
256
|
+
case 'dismiss-brace': {
|
|
257
|
+
const prefs = state.loadPrefs(PROJECT_DIR);
|
|
258
|
+
prefs.braceDismissed = true;
|
|
259
|
+
state.savePrefs(PROJECT_DIR, prefs);
|
|
260
|
+
console.log(`BRACE prompt dismissed for ${PROJECT_DIR}`);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case 'dismiss-rig': {
|
|
264
|
+
const prefs = state.loadPrefs(PROJECT_DIR);
|
|
265
|
+
prefs.rigDismissed = true;
|
|
266
|
+
state.savePrefs(PROJECT_DIR, prefs);
|
|
267
|
+
console.log(`Rig prompt dismissed for ${PROJECT_DIR}`);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
163
270
|
default:
|
|
164
271
|
console.error(`Session Guard v${config.version}`);
|
|
165
|
-
console.error('Usage: node session-guard.js <check|remind>');
|
|
272
|
+
console.error('Usage: node session-guard.js <check|remind|dismiss-brace|dismiss-rig>');
|
|
166
273
|
process.exit(1);
|
|
167
274
|
}
|
package/package.json
CHANGED
package/skills/manifest.json
CHANGED