@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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mad-skills",
3
3
  "description": "AI-assisted planning, development and governance tools",
4
- "version": "2.0.18",
4
+ "version": "2.0.19",
5
5
  "author": {
6
6
  "name": "slamb2k",
7
7
  "url": "https://github.com/slamb2k"
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 ./hooks/session-guard.cjs check", "timeout": 30 }]
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 ./hooks/session-guard.cjs remind", "timeout": 10 }]
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
  }
@@ -49,4 +49,25 @@ function isRecentlyChecked(projectDir, seconds = 5) {
49
49
  return (Date.now() - data.timestamp) < seconds * 1000;
50
50
  }
51
51
 
52
- module.exports = { save, load, clear, isRecentlyChecked };
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 };
@@ -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
- '"Initialise" \u2014 run `/init` to scaffold CLAUDE.md',
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
- // Skip re-emit if no warnings were found
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 wrapped = [
126
- '[SESSION GUARD \u2014 FIRST PROMPT REMINDER]',
127
- 'The following was detected at session start. Act on these items NOW using',
128
- 'AskUserQuestion BEFORE proceeding with the user\'s request.',
129
- '',
130
- body,
131
- ].join('\n');
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: wrapped,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slamb2k/mad-skills",
3
- "version": "2.0.18",
3
+ "version": "2.0.19",
4
4
  "description": "Claude Code skills collection — planning, development and governance tools",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-03-09T17:23:53.026Z",
2
+ "generated": "2026-03-09T17:55:20.156Z",
3
3
  "count": 10,
4
4
  "skills": [
5
5
  {