@jaggerxtrm/specialists 2.1.14 → 2.1.16

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,58 @@
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: 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
+ '🚫 BEADS GATE: Close open issues before committing.\n\n' +
47
+ `Open issues:\n${summary}\n\n` +
48
+ 'Next steps:\n' +
49
+ ' 3. bd close <id1> <id2> ... ← you are here\n' +
50
+ ' 4. git add <files> && git commit -m "..."\n' +
51
+ ' 5. git push -u origin <feature-branch>\n' +
52
+ ' 6. gh pr create --fill && gh pr merge --squash\n' +
53
+ ' 7. git checkout master && git reset --hard origin/master\n'
54
+ );
55
+ process.exit(2);
56
+ }
57
+
58
+ process.exit(0);
@@ -0,0 +1,53 @@
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: 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
+ '🚫 BEADS GATE: No active issue — create one before editing files.\n\n' +
39
+ ' bd create --title="<what you\'re doing>" --type=task --priority=2\n' +
40
+ ' bd update <id> --status=in_progress\n\n' +
41
+ 'Full workflow (do this every session):\n' +
42
+ ' 1. bd create + bd update in_progress ← you are here\n' +
43
+ ' 2. Edit files / write code\n' +
44
+ ' 3. bd close <id> close when done\n' +
45
+ ' 4. git add <files> && git commit\n' +
46
+ ' 5. git push -u origin <feature-branch>\n' +
47
+ ' 6. gh pr create --fill && gh pr merge --squash\n' +
48
+ ' 7. git checkout master && git reset --hard origin/master\n'
49
+ );
50
+ process.exit(2);
51
+ }
52
+
53
+ process.exit(0);
@@ -0,0 +1,52 @@
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
+ // Exit 0: allow stop | Exit 2: block stop (stderr shown to Claude)
5
+ //
6
+ // Installed by: specialists install
7
+
8
+ import { execSync } from 'node:child_process';
9
+ import { readFileSync, existsSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+
12
+ let input;
13
+ try {
14
+ input = JSON.parse(readFileSync(0, 'utf8'));
15
+ } catch {
16
+ process.exit(0);
17
+ }
18
+
19
+ const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
20
+ if (!existsSync(join(cwd, '.beads'))) process.exit(0);
21
+
22
+ let inProgress = 0;
23
+ let summary = '';
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
+ summary = output.trim();
33
+ } catch {
34
+ process.exit(0);
35
+ }
36
+
37
+ if (inProgress > 0) {
38
+ process.stderr.write(
39
+ '🚫 BEADS STOP GATE: Unresolved issues — complete the session close protocol.\n\n' +
40
+ `Open issues:\n${summary}\n\n` +
41
+ 'Session close protocol:\n' +
42
+ ' 3. bd close <id1> <id2> ... close all in_progress issues\n' +
43
+ ' 4. git add <files> && git commit -m "..." commit your changes\n' +
44
+ ' 5. git push -u origin <feature-branch> push feature branch\n' +
45
+ ' 6. gh pr create --fill create PR\n' +
46
+ ' 7. gh pr merge --squash merge PR\n' +
47
+ ' 8. git checkout master && git reset --hard origin/master\n'
48
+ );
49
+ process.exit(2);
50
+ }
51
+
52
+ process.exit(0);
@@ -0,0 +1,74 @@
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
+
32
+ const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
33
+
34
+ if (WRITE_TOOLS.has(tool)) {
35
+ process.stderr.write(
36
+ `⛔ You are on '${branch}' — never edit files directly on master.\n\n` +
37
+ 'Full workflow:\n' +
38
+ ' 1. git checkout -b feature/<name> ← start here\n' +
39
+ ' 2. bd create + bd update in_progress track your work\n' +
40
+ ' 3. Edit files / write code\n' +
41
+ ' 4. bd close <id> && git add && git commit\n' +
42
+ ' 5. git push -u origin feature/<name>\n' +
43
+ ' 6. gh pr create --fill && gh pr merge --squash\n' +
44
+ ' 7. git checkout master && git reset --hard origin/master\n'
45
+ );
46
+ process.exit(2);
47
+ }
48
+
49
+ // Block direct pushes to master — agents must use feature branches + gh pr create/merge
50
+ if (tool === 'Bash') {
51
+ const cmd = (input.tool_input?.command ?? '').trim().replace(/\s+/g, ' ');
52
+ if (/^git push/.test(cmd)) {
53
+ const tokens = cmd.split(' ');
54
+ const lastToken = tokens[tokens.length - 1];
55
+ const explicitMaster = /^(master|main)$/.test(lastToken) || /:(master|main)$/.test(lastToken);
56
+ const impliedMaster = tokens.length <= 3 && (branch === 'main' || branch === 'master');
57
+ if (explicitMaster || impliedMaster) {
58
+ process.stderr.write(
59
+ `⛔ Don't push directly to '${branch}' — use the PR workflow.\n\n` +
60
+ 'Next steps:\n' +
61
+ ' 5. git push -u origin <feature-branch> ← push your branch\n' +
62
+ ' 6. gh pr create --fill create PR\n' +
63
+ ' gh pr merge --squash merge it\n' +
64
+ ' 7. git checkout master sync master\n' +
65
+ ' git reset --hard origin/master\n\n' +
66
+ 'If you\'re not on a feature branch yet:\n' +
67
+ ' git checkout -b feature/<name> (then re-commit and push)\n'
68
+ );
69
+ process.exit(2);
70
+ }
71
+ }
72
+ }
73
+
74
+ 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.16",
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",