@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.
- package/README.md +8 -1
- package/bin/install.js +209 -10
- package/dist/index.js +16480 -16401
- 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
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
233
|
-
: ok('
|
|
234
|
-
info('
|
|
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');
|