@heretyc/subagent-mcp 2.8.0 → 2.8.2

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/dist/routing.js CHANGED
@@ -199,7 +199,7 @@ function readPairings(table, taskCategory, branch = DEFAULT_BRANCH) {
199
199
  const entries = raw.filter((e) => !!e && typeof e === "object" && typeof e.model === "string");
200
200
  return [...entries].sort((a, b) => (a.rank ?? Number.MAX_SAFE_INTEGER) - (b.rank ?? Number.MAX_SAFE_INTEGER));
201
201
  }
202
- // The fixed 10 task categories + fallback_default. Maps directly to the
202
+ // The fixed 14 task categories + fallback_default. Maps directly to the
203
203
  // routing-table category keys (param-contract.md). Immutable taxonomy.
204
204
  export const TASK_CATEGORIES = [
205
205
  "math_proof",
@@ -212,6 +212,10 @@ export const TASK_CATEGORIES = [
212
212
  "coding",
213
213
  "knowledge_synthesis",
214
214
  "mechanical",
215
+ "prompt_engineering",
216
+ "vulnerability_research",
217
+ "molecular_biology",
218
+ "ml_accelerator_design",
215
219
  "fallback_default",
216
220
  ];
217
221
  // Shared error hint blocks (resolution-matrix.md). Appended verbatim.
@@ -233,11 +237,11 @@ export function validatePresence(p) {
233
237
  // 1. task_category valid?
234
238
  if (!task_category || !TASK_CATEGORIES.includes(task_category)) {
235
239
  const got = task_category ? String(task_category) : "<none>";
236
- return `Error: task_category is required and must be one of: math_proof, security_review, debugging, quality_review, architecture, agentic_execution, data_analysis, coding, knowledge_synthesis, mechanical, fallback_default. Got: ${got}.\n${SPLIT_HINT}\n${AUTO_HINT}`;
240
+ return `Error: task_category is required and must be one of: ${TASK_CATEGORIES.join(", ")}. Got: ${got}.\n${SPLIT_HINT}\n${AUTO_HINT}`;
237
241
  }
238
242
  // 2. deadlock cannot be combined with provider/model/effort.
239
243
  if (deadlock === true && (provider || model || effort)) {
240
- return `Error: deadlock cannot be combined with provider, model, or effort. If repeated attempts at this task have failed, switch to pure auto mode — pass only prompt + task_category and let the server select unless your assignment explicitly demands a specific model. Omit provider/model/effort and retry.\n${AUTO_HINT}`;
244
+ return `Error: deadlock cannot be combined with provider, model, or effort. From the 3rd attempt for the same atomic task, deadlock outranks capability overrides: drop provider/model/effort and retry.\n${AUTO_HINT}`;
241
245
  }
242
246
  // 3. effort present must come with provider AND model (checked before model rule).
243
247
  if (effort && !(provider && model)) {
package/dist/setup.js CHANGED
@@ -34,6 +34,7 @@ export function serverPaths(root = INSTALL_ROOT) {
34
34
  return {
35
35
  server: `${f}/dist/index.js`,
36
36
  claudeHook: `${f}/dist/hooks/orchestration-claude.js`,
37
+ claudePreToolHook: `${f}/dist/hooks/orchestration-claude-pretool.js`,
37
38
  codexHook: `${f}/dist/hooks/orchestration-codex.js`,
38
39
  };
39
40
  }
@@ -61,37 +62,53 @@ export function findOnPath(cmd, env = process.env, platform = process.platform)
61
62
  return null;
62
63
  }
63
64
  /**
64
- * Reconcile the UserPromptSubmit hook in a parsed ~/.claude/settings.json.
65
+ * Reconcile the UserPromptSubmit and PreToolUse hooks in a parsed ~/.claude/settings.json.
65
66
  * Mutates `s` in place. Exact wiring present -> ok. A hook referencing
66
67
  * orchestration-claude.js at any OTHER path/shape -> repaired (rewritten to the
67
68
  * canonical exec form). Absent -> added. Unrelated hooks are never touched.
68
69
  */
69
- export function reconcileClaudeSettings(s, hookPath) {
70
+ export function reconcileClaudeSettings(s, hookPath, preToolHookPath = hookPath.replace(/orchestration-claude\.js$/, "orchestration-claude-pretool.js")) {
70
71
  const hooksBlock = (s.hooks ?? {});
71
72
  s.hooks = hooksBlock;
72
- const upsList = (hooksBlock.UserPromptSubmit ?? []);
73
- hooksBlock.UserPromptSubmit = upsList;
74
- for (const grp of upsList) {
75
- for (const hk of grp.hooks ?? []) {
76
- if (!JSON.stringify(hk).includes("orchestration-claude.js"))
77
- continue;
78
- const args = hk.args;
79
- const exact = hk.command === "node" &&
80
- Array.isArray(args) &&
81
- args.length === 1 &&
82
- args[0] === hookPath;
83
- if (exact)
84
- return { changed: false, status: "ok" };
85
- hk.type = "command";
86
- hk.command = "node";
87
- hk.args = [hookPath];
88
- return { changed: true, status: "repaired" };
73
+ const reconcile = (event, scriptName, desired) => {
74
+ const list = (hooksBlock[event] ?? []);
75
+ hooksBlock[event] = list;
76
+ for (const grp of list) {
77
+ for (const hk of grp.hooks ?? []) {
78
+ if (!JSON.stringify(hk).includes(scriptName))
79
+ continue;
80
+ const args = hk.args;
81
+ const exact = hk.type === desired.type &&
82
+ hk.command === desired.command &&
83
+ Array.isArray(args) &&
84
+ JSON.stringify(args) === JSON.stringify(desired.args) &&
85
+ (desired.timeout === undefined || hk.timeout === desired.timeout);
86
+ if (exact)
87
+ return { changed: false, status: "ok" };
88
+ Object.assign(hk, desired);
89
+ return { changed: true, status: "repaired" };
90
+ }
89
91
  }
90
- }
91
- upsList.push({
92
- hooks: [{ type: "command", command: "node", args: [hookPath] }],
92
+ list.push({ hooks: [{ ...desired }] });
93
+ return { changed: true, status: "added" };
94
+ };
95
+ const prompt = reconcile("UserPromptSubmit", "orchestration-claude.js", {
96
+ type: "command",
97
+ command: "node",
98
+ args: [hookPath],
99
+ });
100
+ const pretool = reconcile("PreToolUse", "orchestration-claude-pretool.js", {
101
+ type: "command",
102
+ command: "node",
103
+ args: [preToolHookPath],
104
+ timeout: 5,
93
105
  });
94
- return { changed: true, status: "added" };
106
+ const status = prompt.status === "repaired" || pretool.status === "repaired"
107
+ ? "repaired"
108
+ : prompt.status === "added" || pretool.status === "added"
109
+ ? "added"
110
+ : "ok";
111
+ return { changed: prompt.changed || pretool.changed, status };
95
112
  }
96
113
  /**
97
114
  * Reconcile the user-scope MCP server entry in a parsed ~/.claude.json.
@@ -211,6 +228,7 @@ export function verifyInstall(root = INSTALL_ROOT) {
211
228
  "dist/index.js",
212
229
  "dist/advanced-ruleset.py",
213
230
  "dist/hooks/orchestration-claude.js",
231
+ "dist/hooks/orchestration-claude-pretool.js",
214
232
  "dist/hooks/orchestration-codex.js",
215
233
  "directives/orchestration-claude.md",
216
234
  "directives/orchestration-codex.md",
@@ -358,8 +376,9 @@ function repairPromptFor(vendor, problem) {
358
376
  `The install root is "${fwd(INSTALL_ROOT)}". Please repair my Claude Code wiring: ` +
359
377
  `(1) register a user-scope MCP server named "subagent-mcp" running ` +
360
378
  `the global bin shim "subagent-mcp" (use 'claude mcp add subagent-mcp subagent-mcp -s user' or edit the mcpServers ` +
361
- `key in ~/.claude.json), and (2) ensure ~/.claude/settings.json has a ` +
362
- `hooks.UserPromptSubmit entry {type:"command", command:"node", args:["${p.claudeHook}"]}. ` +
379
+ `key in ~/.claude.json), and (2) ensure ~/.claude/settings.json has ` +
380
+ `hooks.UserPromptSubmit -> {type:"command", command:"node", args:["${p.claudeHook}"]} and ` +
381
+ `hooks.PreToolUse -> {type:"command", command:"node", args:["${p.claudePreToolHook}"], timeout:5}. ` +
363
382
  `Back up any file before editing it.`);
364
383
  }
365
384
  return (`subagent-mcp setup hit a problem on my machine: ${problem}. ` +
@@ -474,7 +493,7 @@ function wireClaude() {
474
493
  catch (e) {
475
494
  fail("claude", `could not register the MCP server: ${e.message}`);
476
495
  }
477
- // 2) UserPromptSubmit hook in ~/.claude/settings.json.
496
+ // 2) UserPromptSubmit and PreToolUse hooks in ~/.claude/settings.json.
478
497
  try {
479
498
  const sfile = join(homedir(), ".claude", "settings.json");
480
499
  const s = readJson(sfile, {});
@@ -483,7 +502,7 @@ function wireClaude() {
483
502
  backup(sfile);
484
503
  writeFileSync(sfile, JSON.stringify(s, null, 2));
485
504
  }
486
- describe(status, "UserPromptSubmit hook");
505
+ describe(status, "UserPromptSubmit + PreToolUse hooks");
487
506
  if (changed && DRY_RUN)
488
507
  console.log(" (dry-run: not written)");
489
508
  }
@@ -572,7 +591,7 @@ export function verifyWiring(root = INSTALL_ROOT, repair = false) {
572
591
  detail: registrationDetail(registered, attemptedRepair),
573
592
  });
574
593
  results.push({
575
- label: "claude: UserPromptSubmit hook",
594
+ label: "claude: UserPromptSubmit + PreToolUse hooks",
576
595
  ok: hk.status === "ok",
577
596
  detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} - run: subagent-mcp setup`,
578
597
  });
@@ -588,7 +607,7 @@ export function verifyWiring(root = INSTALL_ROOT, repair = false) {
588
607
  detail: srv.status === "ok" ? "registered (file fallback)" : "config stale — run: subagent-mcp setup",
589
608
  });
590
609
  results.push({
591
- label: "claude: UserPromptSubmit hook",
610
+ label: "claude: UserPromptSubmit + PreToolUse hooks",
592
611
  ok: hk.status === "ok",
593
612
  detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} - run: subagent-mcp setup`,
594
613
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heretyc/subagent-mcp",
3
- "version": "2.8.0",
3
+ "version": "2.8.2",
4
4
  "description": "MCP server that launches and manages local Claude Code and Codex CLI sub-agents as child processes (no direct Anthropic/OpenAI API).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "postinstall": "node scripts/postinstall.mjs",
22
22
  "prepare": "npm run build",
23
23
  "prepublishOnly": "npm test",
24
- "test": "node test/effort.test.mjs && node test/platform.test.mjs && node test/wait.test.mjs && node test/status.test.mjs && node test/output.test.mjs && node test/stream.test.mjs && node test/routing.test.mjs && node test/deadlock.test.mjs && node test/handler-validation.test.mjs && node test/index-handler.test.mjs && node test/ruleset.test.mjs && node test/ruleset-exec.test.mjs && node test/ruleset-handler.test.mjs && node test/failover.test.mjs && node test/orchestration-marker.test.mjs && node test/orchestration-hook-core.test.mjs && node test/orchestration-adapters.test.mjs && node test/orchestration-directives.test.mjs && node test/performance-tier-effort.test.mjs && node test/setup-repair.test.mjs && node test/setup-quoting.test.mjs && node test/setup-wire.test.mjs && node test/setup-cli-integration.test.mjs && node test/cli-args.test.mjs && node scripts/validate_provider.mjs && node scripts/validate_seed_sites.mjs && node scripts/validate_routing_audit.mjs && node test/seed-sites.test.mjs && node test/mcp-compliance.test.mjs"
24
+ "test": "node test/effort.test.mjs && node test/platform.test.mjs && node test/wait.test.mjs && node test/status.test.mjs && node test/output.test.mjs && node test/stream.test.mjs && node test/routing.test.mjs && node test/deadlock.test.mjs && node test/handler-validation.test.mjs && node test/index-handler.test.mjs && node test/ruleset.test.mjs && node test/ruleset-exec.test.mjs && node test/ruleset-handler.test.mjs && node test/failover.test.mjs && node test/orchestration-marker.test.mjs && node test/orchestration-hook-core.test.mjs && node test/orchestration-adapters.test.mjs && node test/orchestration-pretool.test.mjs && node test/orchestration-directives.test.mjs && node test/performance-tier-effort.test.mjs && node test/setup-repair.test.mjs && node test/setup-quoting.test.mjs && node test/setup-wire.test.mjs && node test/setup-cli-integration.test.mjs && node test/init.test.mjs && node test/cli-args.test.mjs && node scripts/validate_provider.mjs && node scripts/validate_seed_sites.mjs && node scripts/validate_routing_audit.mjs && node test/seed-sites.test.mjs && node test/mcp-compliance.test.mjs"
25
25
  },
26
26
  "author": "Lexi Blackburn",
27
27
  "license": "Apache-2.0",
@@ -71,7 +71,7 @@ try {
71
71
  // Detected vendors — concrete, so the user knows what setup will touch.
72
72
  if (hasClaude || hasCodex) {
73
73
  line(" Detected on this machine:");
74
- if (hasClaude) line(" - Claude Code CLI (will get MCP server + UserPromptSubmit hook)");
74
+ if (hasClaude) line(" - Claude Code CLI (will get MCP server + UserPromptSubmit/PreToolUse hooks)");
75
75
  if (hasCodex) line(" - Codex CLI (will get MCP server + SessionStart/UserPromptSubmit hooks)");
76
76
  } else {
77
77
  line(" No Claude Code or Codex CLI detected yet. Install one,");