@jaggerxtrm/specialists 2.1.7 → 2.1.9

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.
Files changed (4) hide show
  1. package/README.md +8 -1
  2. package/bin/install.js +209 -10
  3. package/dist/index.js +16480 -16401
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -92,7 +92,14 @@ npm install -g @jaggerxtrm/specialists
92
92
  specialists install
93
93
  ```
94
94
 
95
- Installs: **pi** (`@mariozechner/pi-coding-agent`), **beads** (`@beads/bd`), **dolt** (interactive sudo on Linux / brew on macOS), registers the `specialists` MCP at user scope, scaffolds `~/.agents/specialists/`, and installs the `main-guard` PreToolUse hook (`~/.claude/hooks/main-guard.mjs`) to protect `main`/`master` branches from direct edits.
95
+ Installs: **pi** (`@mariozechner/pi-coding-agent`), **beads** (`@beads/bd`), **dolt** (interactive sudo on Linux / brew on macOS), registers the `specialists` MCP at user scope, scaffolds `~/.agents/specialists/`, copies built-in specialists, and installs four Claude Code hooks into `~/.claude/hooks/`:
96
+
97
+ | Hook | Event | Enforces |
98
+ |------|-------|---------|
99
+ | `specialists-main-guard.mjs` | `PreToolUse` | No direct edits/commits on `main`/`master` — use a feature branch |
100
+ | `beads-edit-gate.mjs` | `PreToolUse` | No file edits without an `in_progress` beads issue (beads projects only) |
101
+ | `beads-commit-gate.mjs` | `PreToolUse` | No `git commit` while issues are still `in_progress` — close them first |
102
+ | `beads-stop-gate.mjs` | `Stop` | Agent cannot declare done while `in_progress` issues remain |
96
103
 
97
104
  After running, **restart Claude Code** to load the MCP. Re-run `specialists install` at any time to update or repair the installation.
98
105
 
package/bin/install.js CHANGED
@@ -138,24 +138,220 @@ const HOOK_ENTRY = {
138
138
  hooks: [{ type: 'command', command: HOOK_FILE }],
139
139
  };
140
140
 
141
+
142
+ const BEADS_EDIT_GATE_FILE = join(HOOKS_DIR, 'beads-edit-gate.mjs');
143
+ const BEADS_COMMIT_GATE_FILE = join(HOOKS_DIR, 'beads-commit-gate.mjs');
144
+ const BEADS_STOP_GATE_FILE = join(HOOKS_DIR, 'beads-stop-gate.mjs');
145
+
146
+ const BEADS_EDIT_GATE_SCRIPT = `#!/usr/bin/env node
147
+ // beads-edit-gate — Claude Code PreToolUse hook
148
+ // Blocks file edits when no beads issue is in_progress.
149
+ // Only active in projects with a .beads/ directory.
150
+ // Exit 0: allow | Exit 2: block (stderr shown to Claude)
151
+ //
152
+ // Installed by: npx --package=@jaggerxtrm/specialists install
153
+
154
+ import { execSync } from 'node:child_process';
155
+ import { readFileSync, existsSync } from 'node:fs';
156
+ import { join } from 'node:path';
157
+
158
+ let input;
159
+ try {
160
+ input = JSON.parse(readFileSync(0, 'utf8'));
161
+ } catch {
162
+ process.exit(0);
163
+ }
164
+
165
+ const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
166
+ if (!existsSync(join(cwd, '.beads'))) process.exit(0);
167
+
168
+ let inProgress = 0;
169
+ try {
170
+ const output = execSync('bd list --status=in_progress', {
171
+ encoding: 'utf8',
172
+ cwd,
173
+ stdio: ['pipe', 'pipe', 'pipe'],
174
+ timeout: 8000,
175
+ });
176
+ inProgress = (output.match(/in_progress/g) ?? []).length;
177
+ } catch {
178
+ process.exit(0);
179
+ }
180
+
181
+ if (inProgress === 0) {
182
+ process.stderr.write(
183
+ '\\u{1F6AB} BEADS GATE: No in_progress issue tracked.\\n' +
184
+ 'You MUST create and claim a beads issue BEFORE editing any file:\\n\\n' +
185
+ ' bd create --title="<task summary>" --type=task --priority=2\\n' +
186
+ ' bd update <id> --status=in_progress\\n\\n' +
187
+ 'No exceptions. Momentum is not an excuse.\\n'
188
+ );
189
+ process.exit(2);
190
+ }
191
+
192
+ process.exit(0);
193
+ `;
194
+
195
+ const BEADS_COMMIT_GATE_SCRIPT = `#!/usr/bin/env node
196
+ // beads-commit-gate — Claude Code PreToolUse hook
197
+ // Blocks \`git commit\` when in_progress beads issues still exist.
198
+ // Forces: close issues first, THEN commit.
199
+ // Exit 0: allow | Exit 2: block (stderr shown to Claude)
200
+ //
201
+ // Installed by: npx --package=@jaggerxtrm/specialists install
202
+
203
+ import { execSync } from 'node:child_process';
204
+ import { readFileSync, existsSync } from 'node:fs';
205
+ import { join } from 'node:path';
206
+
207
+ let input;
208
+ try {
209
+ input = JSON.parse(readFileSync(0, 'utf8'));
210
+ } catch {
211
+ process.exit(0);
212
+ }
213
+
214
+ const tool = input.tool_name ?? '';
215
+ if (tool !== 'Bash') process.exit(0);
216
+
217
+ const cmd = input.tool_input?.command ?? '';
218
+ if (!/\\bgit\\s+commit\\b/.test(cmd)) process.exit(0);
219
+
220
+ const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
221
+ if (!existsSync(join(cwd, '.beads'))) process.exit(0);
222
+
223
+ let inProgress = 0;
224
+ let summary = '';
225
+ try {
226
+ const output = execSync('bd list --status=in_progress', {
227
+ encoding: 'utf8',
228
+ cwd,
229
+ stdio: ['pipe', 'pipe', 'pipe'],
230
+ timeout: 8000,
231
+ });
232
+ inProgress = (output.match(/in_progress/g) ?? []).length;
233
+ summary = output.trim();
234
+ } catch {
235
+ process.exit(0);
236
+ }
237
+
238
+ if (inProgress > 0) {
239
+ process.stderr.write(
240
+ '\\u{1F6AB} BEADS GATE: Cannot commit with open in_progress issues.\\n' +
241
+ 'Close them first, THEN commit:\\n\\n' +
242
+ ' bd close <id1> <id2> ...\\n' +
243
+ ' git add <files> && git commit -m "..."\\n\\n' +
244
+ \`Open issues:\\n\${summary}\\n\`
245
+ );
246
+ process.exit(2);
247
+ }
248
+
249
+ process.exit(0);
250
+ `;
251
+
252
+ const BEADS_STOP_GATE_SCRIPT = `#!/usr/bin/env node
253
+ // beads-stop-gate — Claude Code Stop hook
254
+ // Blocks the agent from stopping when in_progress beads issues remain.
255
+ // Forces the session close protocol before declaring done.
256
+ // Exit 0: allow stop | Exit 2: block stop (stderr shown to Claude)
257
+ //
258
+ // Installed by: npx --package=@jaggerxtrm/specialists install
259
+
260
+ import { execSync } from 'node:child_process';
261
+ import { readFileSync, existsSync } from 'node:fs';
262
+ import { join } from 'node:path';
263
+
264
+ let input;
265
+ try {
266
+ input = JSON.parse(readFileSync(0, 'utf8'));
267
+ } catch {
268
+ process.exit(0);
269
+ }
270
+
271
+ const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
272
+ if (!existsSync(join(cwd, '.beads'))) process.exit(0);
273
+
274
+ let inProgress = 0;
275
+ let summary = '';
276
+ try {
277
+ const output = execSync('bd list --status=in_progress', {
278
+ encoding: 'utf8',
279
+ cwd,
280
+ stdio: ['pipe', 'pipe', 'pipe'],
281
+ timeout: 8000,
282
+ });
283
+ inProgress = (output.match(/in_progress/g) ?? []).length;
284
+ summary = output.trim();
285
+ } catch {
286
+ process.exit(0);
287
+ }
288
+
289
+ if (inProgress > 0) {
290
+ process.stderr.write(
291
+ '\\u{1F6AB} BEADS STOP GATE: Cannot stop with unresolved in_progress issues.\\n' +
292
+ 'Complete the session close protocol:\\n\\n' +
293
+ ' bd close <id1> <id2> ...\\n' +
294
+ ' git add <files> && git commit -m "..."\\n' +
295
+ ' git push\\n\\n' +
296
+ \`Open issues:\\n\${summary}\\n\`
297
+ );
298
+ process.exit(2);
299
+ }
300
+
301
+ process.exit(0);
302
+ `;
303
+
304
+ const BEADS_EDIT_GATE_ENTRY = {
305
+ matcher: 'Edit|Write|MultiEdit|NotebookEdit|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol',
306
+ hooks: [{ type: 'command', command: BEADS_EDIT_GATE_FILE, timeout: 10 }],
307
+ };
308
+ const BEADS_COMMIT_GATE_ENTRY = {
309
+ matcher: 'Bash',
310
+ hooks: [{ type: 'command', command: BEADS_COMMIT_GATE_FILE, timeout: 10 }],
311
+ };
312
+ const BEADS_STOP_GATE_ENTRY = {
313
+ hooks: [{ type: 'command', command: BEADS_STOP_GATE_FILE, timeout: 10 }],
314
+ };
315
+
141
316
  function installHook() {
142
317
  mkdirSync(HOOKS_DIR, { recursive: true });
318
+
319
+ // Write all hook files
143
320
  writeFileSync(HOOK_FILE, HOOK_SCRIPT, 'utf8');
144
321
  chmodSync(HOOK_FILE, 0o755);
322
+ writeFileSync(BEADS_EDIT_GATE_FILE, BEADS_EDIT_GATE_SCRIPT, 'utf8');
323
+ chmodSync(BEADS_EDIT_GATE_FILE, 0o755);
324
+ writeFileSync(BEADS_COMMIT_GATE_FILE, BEADS_COMMIT_GATE_SCRIPT, 'utf8');
325
+ chmodSync(BEADS_COMMIT_GATE_FILE, 0o755);
326
+ writeFileSync(BEADS_STOP_GATE_FILE, BEADS_STOP_GATE_SCRIPT, 'utf8');
327
+ chmodSync(BEADS_STOP_GATE_FILE, 0o755);
145
328
 
146
329
  let settings = {};
147
330
  if (existsSync(SETTINGS_FILE)) {
148
331
  try { settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf8')); } catch {}
149
332
  }
150
333
 
151
- if (!Array.isArray(settings.hooks?.PreToolUse)) {
152
- settings.hooks = settings.hooks ?? {};
153
- settings.hooks.PreToolUse = [];
154
- }
155
-
156
- settings.hooks.PreToolUse = settings.hooks.PreToolUse
157
- .filter(e => !e.hooks?.some(h => h.command?.includes('specialists-main-guard')));
334
+ settings.hooks = settings.hooks ?? {};
335
+
336
+ // PreToolUse replace any existing specialists-managed entries
337
+ if (!Array.isArray(settings.hooks.PreToolUse)) settings.hooks.PreToolUse = [];
338
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(e =>
339
+ !e.hooks?.some(h =>
340
+ h.command?.includes('specialists-main-guard') ||
341
+ h.command?.includes('beads-edit-gate') ||
342
+ h.command?.includes('beads-commit-gate')
343
+ )
344
+ );
158
345
  settings.hooks.PreToolUse.push(HOOK_ENTRY);
346
+ settings.hooks.PreToolUse.push(BEADS_EDIT_GATE_ENTRY);
347
+ settings.hooks.PreToolUse.push(BEADS_COMMIT_GATE_ENTRY);
348
+
349
+ // Stop — replace any existing beads-stop-gate entry
350
+ if (!Array.isArray(settings.hooks.Stop)) settings.hooks.Stop = [];
351
+ settings.hooks.Stop = settings.hooks.Stop.filter(e =>
352
+ !e.hooks?.some(h => h.command?.includes('beads-stop-gate'))
353
+ );
354
+ settings.hooks.Stop.push(BEADS_STOP_GATE_ENTRY);
159
355
 
160
356
  mkdirSync(CLAUDE_DIR, { recursive: true });
161
357
  writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n', 'utf8');
@@ -229,9 +425,12 @@ section('Claude Code hooks');
229
425
  const hookExisted = existsSync(HOOK_FILE);
230
426
  installHook();
231
427
  hookExisted
232
- ? ok('main-guard hook updated')
233
- : ok('main-guard hook installed → ~/.claude/hooks/specialists-main-guard.mjs');
234
- info('Blocks Edit/Write/git commit/push on main or master branch');
428
+ ? ok('hooks updated (main-guard + beads gates)')
429
+ : ok('hooks installed → ~/.claude/hooks/');
430
+ info('main-guard: blocks Edit/Write/git commit/push on main or master branch');
431
+ info('beads-edit-gate: requires in_progress bead before editing files');
432
+ info('beads-commit-gate: requires issues closed before git commit');
433
+ info('beads-stop-gate: requires issues closed before session end');
235
434
 
236
435
  // 7. Health check
237
436
  section('Health check');