@jaggerxtrm/specialists 2.1.13 → 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/README.md +62 -4
- package/bin/install.js +10 -201
- package/dist/index.js +1 -1
- package/hooks/beads-commit-gate.mjs +55 -0
- package/hooks/beads-edit-gate.mjs +47 -0
- package/hooks/beads-stop-gate.mjs +50 -0
- package/hooks/specialists-main-guard.mjs +63 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -195,13 +195,18 @@ Pre-script output is formatted as `<pre_flight_context>` XML and available in `t
|
|
|
195
195
|
|
|
196
196
|
## CLI
|
|
197
197
|
|
|
198
|
-
Once installed globally,
|
|
198
|
+
Once installed globally, `specialists <command>` provides:
|
|
199
199
|
|
|
200
200
|
| Command | Description |
|
|
201
201
|
|---------|-------------|
|
|
202
|
-
| `specialists install` | Full-stack installer: pi, beads, dolt, MCP registration, hooks
|
|
203
|
-
| `specialists
|
|
204
|
-
| `specialists
|
|
202
|
+
| `specialists install` | Full-stack installer: pi, beads, dolt, MCP registration, hooks |
|
|
203
|
+
| `specialists init` | Scaffold `./specialists/` and inject usage block into `AGENTS.md` |
|
|
204
|
+
| `specialists list` | List discovered specialists with model, description, and scope |
|
|
205
|
+
| `specialists edit <name> --<field> <value>` | Edit a specialist field in-place |
|
|
206
|
+
| `specialists run <name>` | Run a specialist and stream output to stdout |
|
|
207
|
+
| `specialists status` | Show system health: specialists, pi, beads, MCP |
|
|
208
|
+
| `specialists version` | Print installed package version |
|
|
209
|
+
| `specialists help` | Show command reference |
|
|
205
210
|
| `specialists` | Start the MCP server (called by Claude Code — not for direct use) |
|
|
206
211
|
|
|
207
212
|
### specialists list
|
|
@@ -218,6 +223,59 @@ Specialists (9)
|
|
|
218
223
|
|
|
219
224
|
Scopes: `[project]` = `./specialists/` (or `.claude/specialists/`), `[user]` = `~/.agents/specialists/`
|
|
220
225
|
|
|
226
|
+
Filter by scope or category: `specialists list --scope user --category analysis`
|
|
227
|
+
|
|
228
|
+
### specialists edit
|
|
229
|
+
|
|
230
|
+
Edit individual fields without opening the file:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
specialists edit init-session --model anthropic/claude-sonnet-4-6
|
|
234
|
+
specialists edit bug-hunt --permission MEDIUM
|
|
235
|
+
specialists edit overthinker --timeout 300000
|
|
236
|
+
specialists edit codebase-explorer --tags "analysis,architecture"
|
|
237
|
+
specialists edit my-spec --description "New description" --dry-run
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Editable fields: `model`, `fallback-model`, `description`, `permission`, `timeout`, `tags`
|
|
241
|
+
|
|
242
|
+
### specialists run
|
|
243
|
+
|
|
244
|
+
Run a specialist directly from the terminal — no MCP required:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Inline prompt
|
|
248
|
+
specialists run init-session --prompt "What changed recently?"
|
|
249
|
+
|
|
250
|
+
# Pipe from stdin
|
|
251
|
+
echo "Analyse the architecture" | specialists run codebase-explorer
|
|
252
|
+
|
|
253
|
+
# Override model, skip beads
|
|
254
|
+
specialists run overthinker --prompt "Refactor strategy?" --model anthropic/claude-sonnet-4-6 --no-beads
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Output streams to stdout in real time. Model, duration, and bead ID appear on stderr.
|
|
258
|
+
|
|
259
|
+
### specialists status
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
specialists status
|
|
263
|
+
|
|
264
|
+
── Specialists ───────────────────────────
|
|
265
|
+
✓ 9 found (9 project)
|
|
266
|
+
|
|
267
|
+
── pi (coding agent runtime) ────────────
|
|
268
|
+
✓ v0.57.1 — 4 providers active (anthropic, google-gemini-cli, qwen, zai)
|
|
269
|
+
|
|
270
|
+
── beads (issue tracker) ────────────────
|
|
271
|
+
✓ bd installed v0.59.0
|
|
272
|
+
✓ .beads/ present in project
|
|
273
|
+
|
|
274
|
+
── MCP ───────────────────────────────────
|
|
275
|
+
✓ specialists binary installed /usr/local/bin/specialists
|
|
276
|
+
verify registration: claude mcp get specialists
|
|
277
|
+
```
|
|
278
|
+
|
|
221
279
|
---
|
|
222
280
|
|
|
223
281
|
## Development
|
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,187 +110,29 @@ 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
|
-
hooks: [{ type: 'command', command: BEADS_EDIT_GATE_FILE, timeout:
|
|
115
|
+
hooks: [{ type: 'command', command: BEADS_EDIT_GATE_FILE, timeout: 10000 }],
|
|
307
116
|
};
|
|
308
117
|
const BEADS_COMMIT_GATE_ENTRY = {
|
|
309
118
|
matcher: 'Bash',
|
|
310
|
-
hooks: [{ type: 'command', command: BEADS_COMMIT_GATE_FILE, timeout:
|
|
119
|
+
hooks: [{ type: 'command', command: BEADS_COMMIT_GATE_FILE, timeout: 10000 }],
|
|
311
120
|
};
|
|
312
121
|
const BEADS_STOP_GATE_ENTRY = {
|
|
313
|
-
hooks: [{ type: 'command', command: BEADS_STOP_GATE_FILE, timeout:
|
|
122
|
+
hooks: [{ type: 'command', command: BEADS_STOP_GATE_FILE, timeout: 10000 }],
|
|
314
123
|
};
|
|
315
124
|
|
|
316
125
|
function installHook() {
|
|
317
126
|
mkdirSync(HOOKS_DIR, { recursive: true });
|
|
318
127
|
|
|
319
|
-
//
|
|
320
|
-
|
|
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
|
-
|
|
131
|
+
copyFileSync(join(BUNDLED_HOOKS_DIR, 'beads-edit-gate.mjs'), BEADS_EDIT_GATE_FILE);
|
|
323
132
|
chmodSync(BEADS_EDIT_GATE_FILE, 0o755);
|
|
324
|
-
|
|
133
|
+
copyFileSync(join(BUNDLED_HOOKS_DIR, 'beads-commit-gate.mjs'), BEADS_COMMIT_GATE_FILE);
|
|
325
134
|
chmodSync(BEADS_COMMIT_GATE_FILE, 0o755);
|
|
326
|
-
|
|
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
|
|
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');
|
package/dist/index.js
CHANGED
|
@@ -18149,7 +18149,7 @@ import { execFileSync } from "node:child_process";
|
|
|
18149
18149
|
import { fileURLToPath } from "node:url";
|
|
18150
18150
|
import { dirname as dirname2, join as join4 } from "node:path";
|
|
18151
18151
|
async function run() {
|
|
18152
|
-
const installerPath = join4(dirname2(fileURLToPath(import.meta.url)), "..", "
|
|
18152
|
+
const installerPath = join4(dirname2(fileURLToPath(import.meta.url)), "..", "bin", "install.js");
|
|
18153
18153
|
execFileSync(process.execPath, [installerPath], { stdio: "inherit" });
|
|
18154
18154
|
}
|
|
18155
18155
|
var init_install = () => {};
|
|
@@ -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.
|
|
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",
|