@jaggerxtrm/specialists 2.1.7 → 2.1.8
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 +209 -10
- package/dist/index.js +35 -16
- package/package.json +1 -1
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');
|
package/dist/index.js
CHANGED
|
@@ -24894,6 +24894,12 @@ function getProviderArgs(model) {
|
|
|
24894
24894
|
}
|
|
24895
24895
|
|
|
24896
24896
|
// src/pi/session.ts
|
|
24897
|
+
class SessionKilledError extends Error {
|
|
24898
|
+
constructor() {
|
|
24899
|
+
super("Session was killed");
|
|
24900
|
+
this.name = "SessionKilledError";
|
|
24901
|
+
}
|
|
24902
|
+
}
|
|
24897
24903
|
function mapPermissionToTools(level) {
|
|
24898
24904
|
switch (level?.toUpperCase()) {
|
|
24899
24905
|
case "READ_ONLY":
|
|
@@ -24952,6 +24958,7 @@ class PiAgentSession {
|
|
|
24952
24958
|
this._doneResolve = resolve;
|
|
24953
24959
|
this._doneReject = reject;
|
|
24954
24960
|
});
|
|
24961
|
+
donePromise.catch(() => {});
|
|
24955
24962
|
this._donePromise = donePromise;
|
|
24956
24963
|
this.proc.stdout?.on("data", (chunk) => {
|
|
24957
24964
|
this._lineBuffer += chunk.toString();
|
|
@@ -25064,10 +25071,12 @@ class PiAgentSession {
|
|
|
25064
25071
|
return this._lastOutput;
|
|
25065
25072
|
}
|
|
25066
25073
|
kill() {
|
|
25074
|
+
if (this._killed)
|
|
25075
|
+
return;
|
|
25067
25076
|
this._killed = true;
|
|
25068
25077
|
this.proc?.kill();
|
|
25069
25078
|
this.proc = undefined;
|
|
25070
|
-
this.
|
|
25079
|
+
this._doneReject?.(new SessionKilledError);
|
|
25071
25080
|
}
|
|
25072
25081
|
}
|
|
25073
25082
|
|
|
@@ -25232,6 +25241,7 @@ You have access via Bash:
|
|
|
25232
25241
|
onBeadCreated?.(beadId);
|
|
25233
25242
|
}
|
|
25234
25243
|
let output;
|
|
25244
|
+
let sessionBackend = model;
|
|
25235
25245
|
let session;
|
|
25236
25246
|
try {
|
|
25237
25247
|
session = await this.sessionFactory({
|
|
@@ -25251,20 +25261,28 @@ You have access via Bash:
|
|
|
25251
25261
|
onKillRegistered?.(session.kill.bind(session));
|
|
25252
25262
|
await session.prompt(renderedTask);
|
|
25253
25263
|
await session.waitForDone();
|
|
25264
|
+
sessionBackend = session.meta.backend;
|
|
25254
25265
|
output = await session.getLastOutput();
|
|
25266
|
+
sessionBackend = session.meta.backend;
|
|
25255
25267
|
const postScripts = spec.specialist.skills?.scripts?.filter((s) => s.phase === "post") ?? [];
|
|
25256
25268
|
for (const script of postScripts)
|
|
25257
25269
|
runScript(script.path);
|
|
25258
25270
|
circuitBreaker.recordSuccess(model);
|
|
25259
25271
|
} catch (err) {
|
|
25260
|
-
|
|
25261
|
-
if (
|
|
25262
|
-
|
|
25272
|
+
const isCancelled = err instanceof SessionKilledError;
|
|
25273
|
+
if (!isCancelled) {
|
|
25274
|
+
circuitBreaker.recordFailure(model);
|
|
25275
|
+
}
|
|
25276
|
+
const beadStatus = isCancelled ? "CANCELLED" : "ERROR";
|
|
25277
|
+
if (beadId) {
|
|
25278
|
+
beadsClient?.closeBead(beadId, beadStatus, Date.now() - start, model);
|
|
25279
|
+
beadsClient?.auditBead(beadId, metadata.name, model, 1);
|
|
25280
|
+
}
|
|
25263
25281
|
await hooks.emit("post_execute", invocationId, metadata.name, metadata.version, {
|
|
25264
|
-
status: "ERROR",
|
|
25282
|
+
status: isCancelled ? "CANCELLED" : "ERROR",
|
|
25265
25283
|
duration_ms: Date.now() - start,
|
|
25266
25284
|
output_valid: false,
|
|
25267
|
-
error: { type: "backend_error", message: err.message }
|
|
25285
|
+
error: { type: isCancelled ? "cancelled" : "backend_error", message: err.message }
|
|
25268
25286
|
});
|
|
25269
25287
|
throw err;
|
|
25270
25288
|
} finally {
|
|
@@ -25285,7 +25303,7 @@ You have access via Bash:
|
|
|
25285
25303
|
}
|
|
25286
25304
|
return {
|
|
25287
25305
|
output,
|
|
25288
|
-
backend:
|
|
25306
|
+
backend: sessionBackend,
|
|
25289
25307
|
model,
|
|
25290
25308
|
durationMs,
|
|
25291
25309
|
specialistVersion: metadata.version,
|
|
@@ -25460,16 +25478,16 @@ async function runPipeline(steps, runner, onProgress) {
|
|
|
25460
25478
|
}
|
|
25461
25479
|
|
|
25462
25480
|
// src/tools/specialist/run_parallel.tool.ts
|
|
25463
|
-
var InvocationSchema =
|
|
25464
|
-
name:
|
|
25465
|
-
prompt:
|
|
25466
|
-
variables:
|
|
25467
|
-
backend_override:
|
|
25481
|
+
var InvocationSchema = objectType({
|
|
25482
|
+
name: stringType(),
|
|
25483
|
+
prompt: stringType(),
|
|
25484
|
+
variables: recordType(stringType()).optional(),
|
|
25485
|
+
backend_override: stringType().optional()
|
|
25468
25486
|
});
|
|
25469
|
-
var runParallelSchema =
|
|
25470
|
-
specialists:
|
|
25471
|
-
merge_strategy:
|
|
25472
|
-
timeout_ms:
|
|
25487
|
+
var runParallelSchema = objectType({
|
|
25488
|
+
specialists: arrayType(InvocationSchema).min(1),
|
|
25489
|
+
merge_strategy: enumType(["collect", "synthesize", "vote", "pipeline"]).default("collect"),
|
|
25490
|
+
timeout_ms: numberType().default(120000)
|
|
25473
25491
|
});
|
|
25474
25492
|
function createRunParallelTool(runner) {
|
|
25475
25493
|
return {
|
|
@@ -25499,6 +25517,7 @@ function createRunParallelTool(runner) {
|
|
|
25499
25517
|
status: r.status,
|
|
25500
25518
|
output: r.status === "fulfilled" ? r.value.output : null,
|
|
25501
25519
|
durationMs: r.status === "fulfilled" ? r.value.durationMs : null,
|
|
25520
|
+
beadId: r.status === "fulfilled" ? r.value.beadId : undefined,
|
|
25502
25521
|
error: r.status === "rejected" ? String(r.reason?.message) : null
|
|
25503
25522
|
}));
|
|
25504
25523
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaggerxtrm/specialists",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.8",
|
|
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",
|