@jaggerxtrm/specialists 2.1.14 → 2.1.15

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/bin/install.js CHANGED
@@ -18,6 +18,7 @@ const GITHUB_PKG = '@jaggerxtrm/specialists';
18
18
 
19
19
  // Bundled specialists dir — resolved relative to this file (bin/../specialists/)
20
20
  const BUNDLED_SPECIALISTS_DIR = new URL('../specialists', import.meta.url).pathname;
21
+ const BUNDLED_HOOKS_DIR = new URL('../hooks', import.meta.url).pathname;
21
22
 
22
23
  // ── ANSI helpers ──────────────────────────────────────────────────────────────
23
24
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
@@ -81,40 +82,6 @@ function registerMCP() {
81
82
 
82
83
  // ── Hook installation ─────────────────────────────────────────────────────────
83
84
 
84
- const HOOK_SCRIPT = `#!/usr/bin/env node
85
- // specialists — Claude Code PreToolUse hook
86
- // Blocks writes and git commit/push on main/master branch.
87
- // Exit 0: allow | Exit 2: block (message shown to user)
88
- //
89
- // Installed by: npx --package=@jaggerxtrm/specialists install
90
-
91
- import { execSync } from 'node:child_process';
92
- import { readFileSync } from 'node:fs';
93
-
94
- let branch = '';
95
- try {
96
- branch = execSync('git branch --show-current', {
97
- encoding: 'utf8',
98
- stdio: ['pipe', 'pipe', 'pipe'],
99
- }).trim();
100
- } catch {}
101
-
102
- if (!branch || (branch !== 'main' && branch !== 'master')) {
103
- process.exit(0);
104
- }
105
-
106
- let input;
107
- try {
108
- input = JSON.parse(readFileSync(0, 'utf8'));
109
- } catch {
110
- process.exit(0);
111
- }
112
-
113
- const tool = input.tool_name ?? '';
114
- const blockMsg =
115
- \`⛔ Direct edits on '\${branch}' are not allowed.\\n\` +
116
- \`Create a feature branch first: git checkout -b feature/<name>\`;
117
-
118
85
  const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
119
86
 
120
87
  if (WRITE_TOOLS.has(tool)) {
@@ -143,164 +110,6 @@ const BEADS_EDIT_GATE_FILE = join(HOOKS_DIR, 'beads-edit-gate.mjs');
143
110
  const BEADS_COMMIT_GATE_FILE = join(HOOKS_DIR, 'beads-commit-gate.mjs');
144
111
  const BEADS_STOP_GATE_FILE = join(HOOKS_DIR, 'beads-stop-gate.mjs');
145
112
 
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
113
  const BEADS_EDIT_GATE_ENTRY = {
305
114
  matcher: 'Edit|Write|MultiEdit|NotebookEdit|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol',
306
115
  hooks: [{ type: 'command', command: BEADS_EDIT_GATE_FILE, timeout: 10000 }],
@@ -316,14 +125,14 @@ const BEADS_STOP_GATE_ENTRY = {
316
125
  function installHook() {
317
126
  mkdirSync(HOOKS_DIR, { recursive: true });
318
127
 
319
- // Write all hook files
320
- writeFileSync(HOOK_FILE, HOOK_SCRIPT, 'utf8');
128
+ // Copy hook files from bundled hooks/ directory
129
+ copyFileSync(join(BUNDLED_HOOKS_DIR, 'specialists-main-guard.mjs'), HOOK_FILE);
321
130
  chmodSync(HOOK_FILE, 0o755);
322
- writeFileSync(BEADS_EDIT_GATE_FILE, BEADS_EDIT_GATE_SCRIPT, 'utf8');
131
+ copyFileSync(join(BUNDLED_HOOKS_DIR, 'beads-edit-gate.mjs'), BEADS_EDIT_GATE_FILE);
323
132
  chmodSync(BEADS_EDIT_GATE_FILE, 0o755);
324
- writeFileSync(BEADS_COMMIT_GATE_FILE, BEADS_COMMIT_GATE_SCRIPT, 'utf8');
133
+ copyFileSync(join(BUNDLED_HOOKS_DIR, 'beads-commit-gate.mjs'), BEADS_COMMIT_GATE_FILE);
325
134
  chmodSync(BEADS_COMMIT_GATE_FILE, 0o755);
326
- writeFileSync(BEADS_STOP_GATE_FILE, BEADS_STOP_GATE_SCRIPT, 'utf8');
135
+ copyFileSync(join(BUNDLED_HOOKS_DIR, 'beads-stop-gate.mjs'), BEADS_STOP_GATE_FILE);
327
136
  chmodSync(BEADS_STOP_GATE_FILE, 0o755);
328
137
 
329
138
  let settings = {};
@@ -427,7 +236,7 @@ installHook();
427
236
  hookExisted
428
237
  ? ok('hooks updated (main-guard + beads gates)')
429
238
  : ok('hooks installed → ~/.claude/hooks/');
430
- info('main-guard: blocks Edit/Write/git commit/push on main or master branch');
239
+ info('main-guard: blocks file edits and direct master pushes (enforces PR workflow)');
431
240
  info('beads-edit-gate: requires in_progress bead before editing files');
432
241
  info('beads-commit-gate: requires issues closed before git commit');
433
242
  info('beads-stop-gate: requires issues closed before session end');
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // beads-commit-gate — Claude Code PreToolUse hook
3
+ // Blocks `git commit` when in_progress beads issues still exist.
4
+ // Forces: close issues first, THEN commit.
5
+ // Exit 0: allow | Exit 2: block (stderr shown to Claude)
6
+ //
7
+ // Installed by: npx --package=@jaggerxtrm/specialists install
8
+
9
+ import { execSync } from 'node:child_process';
10
+ import { readFileSync, existsSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+
13
+ let input;
14
+ try {
15
+ input = JSON.parse(readFileSync(0, 'utf8'));
16
+ } catch {
17
+ process.exit(0);
18
+ }
19
+
20
+ const tool = input.tool_name ?? '';
21
+ if (tool !== 'Bash') process.exit(0);
22
+
23
+ const cmd = input.tool_input?.command ?? '';
24
+ if (!/\bgit\s+commit\b/.test(cmd)) process.exit(0);
25
+
26
+ const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
27
+ if (!existsSync(join(cwd, '.beads'))) process.exit(0);
28
+
29
+ let inProgress = 0;
30
+ let summary = '';
31
+ try {
32
+ const output = execSync('bd list --status=in_progress', {
33
+ encoding: 'utf8',
34
+ cwd,
35
+ stdio: ['pipe', 'pipe', 'pipe'],
36
+ timeout: 8000,
37
+ });
38
+ inProgress = (output.match(/in_progress/g) ?? []).length;
39
+ summary = output.trim();
40
+ } catch {
41
+ process.exit(0);
42
+ }
43
+
44
+ if (inProgress > 0) {
45
+ process.stderr.write(
46
+ '\u{1F6AB} BEADS GATE: Cannot commit with open in_progress issues.\n' +
47
+ 'Close them first, THEN commit:\n\n' +
48
+ ' bd close <id1> <id2> ...\n' +
49
+ ' git add <files> && git commit -m "..."\n\n' +
50
+ `Open issues:\n${summary}\n`
51
+ );
52
+ process.exit(2);
53
+ }
54
+
55
+ process.exit(0);
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // beads-edit-gate — Claude Code PreToolUse hook
3
+ // Blocks file edits when no beads issue is in_progress.
4
+ // Only active in projects with a .beads/ directory.
5
+ // Exit 0: allow | Exit 2: block (stderr shown to Claude)
6
+ //
7
+ // Installed by: npx --package=@jaggerxtrm/specialists install
8
+
9
+ import { execSync } from 'node:child_process';
10
+ import { readFileSync, existsSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+
13
+ let input;
14
+ try {
15
+ input = JSON.parse(readFileSync(0, 'utf8'));
16
+ } catch {
17
+ process.exit(0);
18
+ }
19
+
20
+ const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
21
+ if (!existsSync(join(cwd, '.beads'))) process.exit(0);
22
+
23
+ let inProgress = 0;
24
+ try {
25
+ const output = execSync('bd list --status=in_progress', {
26
+ encoding: 'utf8',
27
+ cwd,
28
+ stdio: ['pipe', 'pipe', 'pipe'],
29
+ timeout: 8000,
30
+ });
31
+ inProgress = (output.match(/in_progress/g) ?? []).length;
32
+ } catch {
33
+ process.exit(0);
34
+ }
35
+
36
+ if (inProgress === 0) {
37
+ process.stderr.write(
38
+ '\u{1F6AB} BEADS GATE: No in_progress issue tracked.\n' +
39
+ 'You MUST create and claim a beads issue BEFORE editing any file:\n\n' +
40
+ ' bd create --title="<task summary>" --type=task --priority=2\n' +
41
+ ' bd update <id> --status=in_progress\n\n' +
42
+ 'No exceptions. Momentum is not an excuse.\n'
43
+ );
44
+ process.exit(2);
45
+ }
46
+
47
+ process.exit(0);
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ // beads-stop-gate — Claude Code Stop hook
3
+ // Blocks the agent from stopping when in_progress beads issues remain.
4
+ // Forces the session close protocol before declaring done.
5
+ // Exit 0: allow stop | Exit 2: block stop (stderr shown to Claude)
6
+ //
7
+ // Installed by: npx --package=@jaggerxtrm/specialists install
8
+
9
+ import { execSync } from 'node:child_process';
10
+ import { readFileSync, existsSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+
13
+ let input;
14
+ try {
15
+ input = JSON.parse(readFileSync(0, 'utf8'));
16
+ } catch {
17
+ process.exit(0);
18
+ }
19
+
20
+ const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
21
+ if (!existsSync(join(cwd, '.beads'))) process.exit(0);
22
+
23
+ let inProgress = 0;
24
+ let summary = '';
25
+ try {
26
+ const output = execSync('bd list --status=in_progress', {
27
+ encoding: 'utf8',
28
+ cwd,
29
+ stdio: ['pipe', 'pipe', 'pipe'],
30
+ timeout: 8000,
31
+ });
32
+ inProgress = (output.match(/in_progress/g) ?? []).length;
33
+ summary = output.trim();
34
+ } catch {
35
+ process.exit(0);
36
+ }
37
+
38
+ if (inProgress > 0) {
39
+ process.stderr.write(
40
+ '\u{1F6AB} BEADS STOP GATE: Cannot stop with unresolved in_progress issues.\n' +
41
+ 'Complete the session close protocol:\n\n' +
42
+ ' bd close <id1> <id2> ...\n' +
43
+ ' git add <files> && git commit -m "..."\n' +
44
+ ' git push\n\n' +
45
+ `Open issues:\n${summary}\n`
46
+ );
47
+ process.exit(2);
48
+ }
49
+
50
+ process.exit(0);
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ // Claude Code PreToolUse hook — block writes and direct master pushes
3
+ // Exit 0: allow | Exit 2: block (message shown to user)
4
+ //
5
+ // Installed by: specialists install
6
+
7
+ import { execSync } from 'node:child_process';
8
+ import { readFileSync } from 'node:fs';
9
+
10
+ let branch = '';
11
+ try {
12
+ branch = execSync('git branch --show-current', {
13
+ encoding: 'utf8',
14
+ stdio: ['pipe', 'pipe', 'pipe'],
15
+ }).trim();
16
+ } catch {}
17
+
18
+ // Not in a git repo or not on a protected branch — allow
19
+ if (!branch || (branch !== 'main' && branch !== 'master')) {
20
+ process.exit(0);
21
+ }
22
+
23
+ let input;
24
+ try {
25
+ input = JSON.parse(readFileSync(0, 'utf8'));
26
+ } catch {
27
+ process.exit(0);
28
+ }
29
+
30
+ const tool = input.tool_name ?? '';
31
+ const blockMsg =
32
+ `⛔ Direct edits on '${branch}' are not allowed.\n` +
33
+ `Create a feature branch first: git checkout -b feature/<name>`;
34
+
35
+ const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
36
+
37
+ if (WRITE_TOOLS.has(tool)) {
38
+ process.stderr.write(blockMsg + '\n');
39
+ process.exit(2);
40
+ }
41
+
42
+ // Block direct pushes to master — agents must use feature branches + gh pr create/merge
43
+ if (tool === 'Bash') {
44
+ const cmd = (input.tool_input?.command ?? '').trim().replace(/\s+/g, ' ');
45
+ if (/^git push/.test(cmd)) {
46
+ const tokens = cmd.split(' ');
47
+ const lastToken = tokens[tokens.length - 1];
48
+ const explicitMaster = /^(master|main)$/.test(lastToken) || /:(master|main)$/.test(lastToken);
49
+ const impliedMaster = tokens.length <= 3 && (branch === 'main' || branch === 'master');
50
+ if (explicitMaster || impliedMaster) {
51
+ process.stderr.write(
52
+ `⛔ Direct push to '${branch}' is not allowed.\n` +
53
+ `Use the PR workflow instead:\n` +
54
+ ` git push -u origin <feature-branch>\n` +
55
+ ` gh pr create --fill\n` +
56
+ ` gh pr merge --squash\n`
57
+ );
58
+ process.exit(2);
59
+ }
60
+ }
61
+ }
62
+
63
+ process.exit(0);
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@jaggerxtrm/specialists",
3
- "version": "2.1.14",
3
+ "version": "2.1.15",
4
4
  "description": "OmniSpecialist — 7-tool MCP orchestration layer powered by the Specialist System. Discover and execute .specialist.yaml files across project/user/system scopes via pi.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "files": [
8
8
  "dist/index.js",
9
9
  "bin/install.js",
10
- "specialists/"
10
+ "specialists/",
11
+ "hooks/"
11
12
  ],
12
13
  "bin": {
13
14
  "specialists": "dist/index.js",