opencode-swarm-plugin 0.62.0 → 0.62.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/bin/swarm.ts CHANGED
@@ -8,6 +8,8 @@
8
8
  * swarm setup - Interactive installer for all dependencies
9
9
  * swarm doctor - Check dependency health with detailed status
10
10
  * swarm init - Initialize swarm in current project
11
+ * swarm claude - Claude Code integration commands
12
+ * swarm mcp-serve - Debug-only MCP server (Claude auto-launches)
11
13
  * swarm version - Show version info
12
14
  * swarm - Interactive mode (same as setup)
13
15
  */
@@ -17,17 +19,20 @@ import {
17
19
  chmodSync,
18
20
  copyFileSync,
19
21
  existsSync,
22
+ lstatSync,
20
23
  mkdirSync,
21
24
  readFileSync,
25
+ readlinkSync,
22
26
  readdirSync,
23
27
  renameSync,
24
28
  rmdirSync,
25
29
  rmSync,
26
30
  statSync,
31
+ symlinkSync,
27
32
  writeFileSync,
28
33
  } from "fs";
29
34
  import { homedir } from "os";
30
- import { basename, dirname, join } from "path";
35
+ import { basename, dirname, join, resolve } from "path";
31
36
  import { fileURLToPath } from "url";
32
37
  import {
33
38
  checkBeadsMigrationNeeded,
@@ -50,13 +55,17 @@ import {
50
55
  resolvePartialId,
51
56
  createDurableStreamAdapter,
52
57
  createDurableStreamServer,
58
+ consolidateDatabases,
59
+ getGlobalDbPath,
53
60
  } from "swarm-mail";
61
+ import { createMemoryAdapter } from "../src/memory";
54
62
  import { execSync, spawn } from "child_process";
55
63
  import { tmpdir } from "os";
56
64
 
57
65
  // Query & observability tools
58
66
  import {
59
67
  executeQuery,
68
+ executeQueryCLI,
60
69
  executePreset,
61
70
  formatAsTable,
62
71
  formatAsCSV,
@@ -101,11 +110,16 @@ import { detectRegressions } from "../src/regression-detection.js";
101
110
  // All tools (for tool command)
102
111
  import { allTools } from "../src/index.js";
103
112
 
113
+ // Skills (for skill-reload command)
114
+ import { invalidateSkillsCache, discoverSkills } from "../src/skills.js";
115
+
104
116
  const __dirname = dirname(fileURLToPath(import.meta.url));
105
117
  // When bundled to dist/bin/swarm.js, need to go up two levels to find package.json
106
118
  const pkgPath = join(__dirname, "..", "..", "package.json");
107
119
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
108
120
  const VERSION: string = pkg.version;
121
+ const PACKAGE_ROOT = dirname(pkgPath);
122
+ const CLAUDE_PLUGIN_NAME = "swarm";
109
123
 
110
124
  // ============================================================================
111
125
  // ASCII Art & Branding
@@ -202,6 +216,156 @@ function rmWithStatus(path: string, label: string): void {
202
216
  }
203
217
  }
204
218
 
219
+ // ============================================================================
220
+ // Claude Code Integration Types & Helpers
221
+ // ============================================================================
222
+
223
+ interface ClaudeHookInput {
224
+ project?: { path?: string };
225
+ cwd?: string;
226
+ session?: { id?: string };
227
+ metadata?: { cwd?: string };
228
+ prompt?: string; // UserPromptSubmit includes the user's prompt text
229
+ }
230
+
231
+ /**
232
+ * Resolve the Claude Code plugin root bundled with the package.
233
+ */
234
+ function getClaudePluginRoot(): string {
235
+ return join(PACKAGE_ROOT, "claude-plugin");
236
+ }
237
+
238
+ /**
239
+ * Resolve the Claude Code config directory.
240
+ */
241
+ function getClaudeConfigDir(): string {
242
+ return join(homedir(), ".claude");
243
+ }
244
+
245
+ /**
246
+ * Read JSON input from stdin for Claude Code hooks.
247
+ */
248
+ async function readHookInput<T>(): Promise<T | null> {
249
+ if (process.stdin.isTTY) return null;
250
+ const chunks: string[] = [];
251
+ for await (const chunk of process.stdin) {
252
+ chunks.push(chunk.toString());
253
+ }
254
+ const raw = chunks.join("").trim();
255
+ if (!raw) return null;
256
+ try {
257
+ return JSON.parse(raw) as T;
258
+ } catch {
259
+ return null;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Resolve the project path for Claude Code hook executions.
265
+ */
266
+ function resolveClaudeProjectPath(input: ClaudeHookInput | null): string {
267
+ return (
268
+ input?.project?.path ||
269
+ input?.cwd ||
270
+ input?.metadata?.cwd ||
271
+ process.env.CLAUDE_PROJECT_DIR ||
272
+ process.env.PWD ||
273
+ process.cwd()
274
+ );
275
+ }
276
+
277
+ /**
278
+ * Format hook output for Claude Code context injection.
279
+ * Uses the proper JSON format with hookSpecificOutput for structured feedback.
280
+ */
281
+ function writeClaudeHookOutput(
282
+ hookEventName: string,
283
+ additionalContext: string,
284
+ options?: { suppressOutput?: boolean }
285
+ ): void {
286
+ if (!additionalContext.trim()) return;
287
+ process.stdout.write(
288
+ `${JSON.stringify({
289
+ suppressOutput: options?.suppressOutput,
290
+ hookSpecificOutput: {
291
+ hookEventName,
292
+ additionalContext,
293
+ },
294
+ })}\n`,
295
+ );
296
+ }
297
+
298
+ /**
299
+ * @deprecated Use writeClaudeHookOutput for proper hook-specific JSON format
300
+ */
301
+ function writeClaudeHookContext(additionalContext: string): void {
302
+ if (!additionalContext.trim()) return;
303
+ process.stdout.write(
304
+ `${JSON.stringify({
305
+ additionalContext,
306
+ })}\n`,
307
+ );
308
+ }
309
+
310
+ interface ClaudeInstallStatus {
311
+ pluginRoot: string;
312
+ globalPluginPath: string;
313
+ globalPluginTarget?: string;
314
+ globalPluginExists: boolean;
315
+ globalPluginLinked: boolean;
316
+ projectClaudeDir: string;
317
+ projectConfigExists: boolean;
318
+ projectConfigPaths: string[];
319
+ }
320
+
321
+ /**
322
+ * Inspect Claude Code install state for global and project scopes.
323
+ */
324
+ function getClaudeInstallStatus(projectPath: string): ClaudeInstallStatus {
325
+ const pluginRoot = getClaudePluginRoot();
326
+ const claudeConfigDir = getClaudeConfigDir();
327
+ const globalPluginPath = join(claudeConfigDir, "plugins", CLAUDE_PLUGIN_NAME);
328
+
329
+ let globalPluginExists = false;
330
+ let globalPluginLinked = false;
331
+ let globalPluginTarget: string | undefined;
332
+
333
+ if (existsSync(globalPluginPath)) {
334
+ globalPluginExists = true;
335
+ try {
336
+ const stat = lstatSync(globalPluginPath);
337
+ if (stat.isSymbolicLink()) {
338
+ globalPluginLinked = true;
339
+ const target = readlinkSync(globalPluginPath);
340
+ globalPluginTarget = resolve(dirname(globalPluginPath), target);
341
+ }
342
+ } catch {
343
+ // Ignore errors
344
+ }
345
+ }
346
+
347
+ const projectClaudeDir = join(projectPath, ".claude");
348
+ const projectConfigPaths = [
349
+ join(projectClaudeDir, "commands"),
350
+ join(projectClaudeDir, "agents"),
351
+ join(projectClaudeDir, "skills"),
352
+ join(projectClaudeDir, "hooks"),
353
+ join(projectClaudeDir, ".mcp.json"),
354
+ ];
355
+ const projectConfigExists = projectConfigPaths.some((path) => existsSync(path));
356
+
357
+ return {
358
+ pluginRoot,
359
+ globalPluginPath,
360
+ globalPluginTarget,
361
+ globalPluginExists,
362
+ globalPluginLinked,
363
+ projectClaudeDir,
364
+ projectConfigExists,
365
+ projectConfigPaths,
366
+ };
367
+ }
368
+
205
369
  // ============================================================================
206
370
  // Seasonal Messages (inspired by Astro's Houston)
207
371
  // ============================================================================
@@ -6024,6 +6188,1004 @@ async function memory() {
6024
6188
  }
6025
6189
  }
6026
6190
 
6191
+ // ============================================================================
6192
+ // Claude Code Integration Commands
6193
+ // ============================================================================
6194
+
6195
+ /**
6196
+ * Claude subcommand dispatcher.
6197
+ */
6198
+ async function claudeCommand() {
6199
+ const args = process.argv.slice(3);
6200
+ const subcommand = args[0];
6201
+
6202
+ if (!subcommand || ["help", "--help", "-h"].includes(subcommand)) {
6203
+ showClaudeHelp();
6204
+ return;
6205
+ }
6206
+
6207
+ switch (subcommand) {
6208
+ case "path":
6209
+ claudePath();
6210
+ break;
6211
+ case "install":
6212
+ await claudeInstall();
6213
+ break;
6214
+ case "uninstall":
6215
+ await claudeUninstall();
6216
+ break;
6217
+ case "init":
6218
+ await claudeInit();
6219
+ break;
6220
+ case "session-start":
6221
+ await claudeSessionStart();
6222
+ break;
6223
+ case "user-prompt":
6224
+ await claudeUserPrompt();
6225
+ break;
6226
+ case "pre-edit":
6227
+ await claudePreEdit();
6228
+ break;
6229
+ case "pre-complete":
6230
+ await claudePreComplete();
6231
+ break;
6232
+ case "post-complete":
6233
+ await claudePostComplete();
6234
+ break;
6235
+ case "pre-compact":
6236
+ await claudePreCompact();
6237
+ break;
6238
+ case "session-end":
6239
+ await claudeSessionEnd();
6240
+ break;
6241
+ case "track-tool":
6242
+ await claudeTrackTool(Bun.argv[4]); // tool name is 4th arg
6243
+ break;
6244
+ case "compliance":
6245
+ await claudeCompliance();
6246
+ break;
6247
+ case "skill-reload":
6248
+ await claudeSkillReload();
6249
+ break;
6250
+ case "coordinator-start":
6251
+ await claudeCoordinatorStart();
6252
+ break;
6253
+ case "worker-start":
6254
+ await claudeWorkerStart();
6255
+ break;
6256
+ case "subagent-stop":
6257
+ await claudeSubagentStop();
6258
+ break;
6259
+ case "agent-stop":
6260
+ await claudeAgentStop();
6261
+ break;
6262
+ case "track-task":
6263
+ await claudeTrackTask();
6264
+ break;
6265
+ case "post-task-update":
6266
+ await claudePostTaskUpdate();
6267
+ break;
6268
+ default:
6269
+ console.error(`Unknown subcommand: ${subcommand}`);
6270
+ showClaudeHelp();
6271
+ process.exit(1);
6272
+ }
6273
+ }
6274
+
6275
+ function showClaudeHelp() {
6276
+ console.log(`
6277
+ Usage: swarm claude <command>
6278
+
6279
+ Commands:
6280
+ path Print Claude plugin path (for --plugin-dir)
6281
+ install Symlink plugin into ~/.claude/plugins/${CLAUDE_PLUGIN_NAME}
6282
+ uninstall Remove Claude plugin symlink
6283
+ init Create project-local .claude/ config
6284
+ session-start Hook: session start context (JSON output)
6285
+ user-prompt Hook: prompt submit context (JSON output)
6286
+ pre-edit Hook: pre-Edit/Write reminder (hivemind check)
6287
+ pre-complete Hook: pre-swarm_complete checklist
6288
+ post-complete Hook: post-swarm_complete learnings reminder
6289
+ pre-compact Hook: pre-compaction handler
6290
+ session-end Hook: session cleanup
6291
+ track-tool Hook: track mandatory tool usage
6292
+ compliance Hook: check worker compliance
6293
+ skill-reload Hook: hot-reload skills after modification
6294
+ coordinator-start Hook: coordinator subagent start
6295
+ worker-start Hook: worker subagent start
6296
+ subagent-stop Hook: subagent stop cleanup
6297
+ agent-stop Hook: agent stop cleanup
6298
+ track-task Hook: track task events
6299
+ post-task-update Hook: post task update tracking
6300
+ `);
6301
+ }
6302
+
6303
+ /**
6304
+ * Print the bundled Claude plugin path.
6305
+ */
6306
+ function claudePath() {
6307
+ const pluginRoot = getClaudePluginRoot();
6308
+ if (!existsSync(pluginRoot)) {
6309
+ console.error(`Claude plugin not found at ${pluginRoot}`);
6310
+ process.exit(1);
6311
+ }
6312
+ console.log(pluginRoot);
6313
+ }
6314
+
6315
+ /**
6316
+ * Install the Claude plugin symlink for local development.
6317
+ */
6318
+ async function claudeInstall() {
6319
+ p.intro("swarm claude install");
6320
+ const pluginRoot = getClaudePluginRoot();
6321
+ if (!existsSync(pluginRoot)) {
6322
+ p.log.error(`Claude plugin not found at ${pluginRoot}`);
6323
+ p.outro("Aborted");
6324
+ process.exit(1);
6325
+ }
6326
+
6327
+ const claudeConfigDir = getClaudeConfigDir();
6328
+ const pluginsDir = join(claudeConfigDir, "plugins");
6329
+ const pluginPath = join(pluginsDir, CLAUDE_PLUGIN_NAME);
6330
+
6331
+ mkdirWithStatus(pluginsDir);
6332
+
6333
+ if (existsSync(pluginPath)) {
6334
+ const stat = lstatSync(pluginPath);
6335
+ if (!stat.isSymbolicLink()) {
6336
+ p.log.error(`Existing path is not a symlink: ${pluginPath}`);
6337
+ p.outro("Aborted");
6338
+ process.exit(1);
6339
+ }
6340
+
6341
+ const target = readlinkSync(pluginPath);
6342
+ const resolved = resolve(dirname(pluginPath), target);
6343
+ if (resolved === pluginRoot) {
6344
+ p.log.success("Claude plugin already linked");
6345
+ p.outro("Done");
6346
+ return;
6347
+ }
6348
+
6349
+ rmSync(pluginPath, { force: true });
6350
+ }
6351
+
6352
+ symlinkSync(pluginRoot, pluginPath);
6353
+ p.log.success(`Linked ${CLAUDE_PLUGIN_NAME} → ${pluginRoot}`);
6354
+ p.log.message(dim(" Claude Code will auto-launch MCP from .mcp.json"));
6355
+ p.outro("Claude plugin installed");
6356
+ }
6357
+
6358
+ /**
6359
+ * Remove the Claude plugin symlink.
6360
+ */
6361
+ async function claudeUninstall() {
6362
+ p.intro("swarm claude uninstall");
6363
+ const pluginPath = join(getClaudeConfigDir(), "plugins", CLAUDE_PLUGIN_NAME);
6364
+
6365
+ if (!existsSync(pluginPath)) {
6366
+ p.log.warn("Claude plugin symlink not found");
6367
+ p.outro("Nothing to remove");
6368
+ return;
6369
+ }
6370
+
6371
+ rmSync(pluginPath, { recursive: true, force: true });
6372
+ p.log.success(`Removed ${pluginPath}`);
6373
+ p.outro("Claude plugin uninstalled");
6374
+ }
6375
+
6376
+ /**
6377
+ * Create project-local Claude Code config from bundled plugin assets.
6378
+ */
6379
+ async function claudeInit() {
6380
+ p.intro("swarm claude init");
6381
+ const projectPath = process.cwd();
6382
+ const pluginRoot = getClaudePluginRoot();
6383
+
6384
+ if (!existsSync(pluginRoot)) {
6385
+ p.log.error(`Claude plugin not found at ${pluginRoot}`);
6386
+ p.outro("Aborted");
6387
+ process.exit(1);
6388
+ }
6389
+
6390
+ const projectClaudeDir = join(projectPath, ".claude");
6391
+ const commandDir = join(projectClaudeDir, "commands");
6392
+ const agentDir = join(projectClaudeDir, "agents");
6393
+ const skillsDir = join(projectClaudeDir, "skills");
6394
+ const hooksDir = join(projectClaudeDir, "hooks");
6395
+
6396
+ for (const dir of [projectClaudeDir, commandDir, agentDir, skillsDir, hooksDir]) {
6397
+ mkdirWithStatus(dir);
6398
+ }
6399
+
6400
+ const copyMap = [
6401
+ { src: join(pluginRoot, "commands"), dest: commandDir, label: "Commands" },
6402
+ { src: join(pluginRoot, "agents"), dest: agentDir, label: "Agents" },
6403
+ { src: join(pluginRoot, "skills"), dest: skillsDir, label: "Skills" },
6404
+ { src: join(pluginRoot, "hooks"), dest: hooksDir, label: "Hooks" },
6405
+ ];
6406
+
6407
+ for (const { src, dest, label } of copyMap) {
6408
+ if (existsSync(src)) {
6409
+ copyDirRecursiveSync(src, dest);
6410
+ p.log.success(`${label}: ${dest}`);
6411
+ }
6412
+ }
6413
+
6414
+ const mcpSourcePath = join(pluginRoot, ".mcp.json");
6415
+ if (existsSync(mcpSourcePath)) {
6416
+ const mcpDestPath = join(projectClaudeDir, ".mcp.json");
6417
+ const content = readFileSync(mcpSourcePath, "utf-8");
6418
+ writeFileWithStatus(mcpDestPath, content, "MCP config");
6419
+ }
6420
+
6421
+ const lspSourcePath = join(pluginRoot, ".lsp.json");
6422
+ if (existsSync(lspSourcePath)) {
6423
+ const lspDestPath = join(projectClaudeDir, ".lsp.json");
6424
+ const content = readFileSync(lspSourcePath, "utf-8");
6425
+ writeFileWithStatus(lspDestPath, content, "LSP config");
6426
+ }
6427
+
6428
+ p.log.message(dim(" Uses ${CLAUDE_PLUGIN_ROOT} in MCP config"));
6429
+ p.outro("Claude project config ready");
6430
+ }
6431
+
6432
+ /**
6433
+ * Claude hook: start a session and emit comprehensive context for Claude Code.
6434
+ *
6435
+ * Gathers:
6436
+ * - Session info + previous handoff notes
6437
+ * - In-progress cells (work that was mid-flight)
6438
+ * - Open epics with their children
6439
+ * - Recent activity summary
6440
+ */
6441
+ async function claudeSessionStart() {
6442
+ try {
6443
+ const input = await readHookInput<ClaudeHookInput>();
6444
+ const projectPath = resolveClaudeProjectPath(input);
6445
+ const swarmMail = await getSwarmMailLibSQL(projectPath);
6446
+ const db = await swarmMail.getDatabase(projectPath);
6447
+ const adapter = createHiveAdapter(db, projectPath);
6448
+
6449
+ await adapter.runMigrations();
6450
+
6451
+ const session = await adapter.startSession(projectPath, {});
6452
+ const contextLines: string[] = [];
6453
+
6454
+ // Session basics
6455
+ contextLines.push(`## Swarm Session: ${session.id}`);
6456
+ contextLines.push(`Source: ${(input as { source?: string }).source || "startup"}`);
6457
+ contextLines.push("");
6458
+
6459
+ // Previous handoff notes (critical for continuation)
6460
+ if (session.previous_handoff_notes) {
6461
+ contextLines.push("## Previous Handoff Notes");
6462
+ contextLines.push(session.previous_handoff_notes);
6463
+ contextLines.push("");
6464
+ }
6465
+
6466
+ // Active cell from previous session
6467
+ if (session.active_cell_id) {
6468
+ const activeCell = await adapter.getCell(projectPath, session.active_cell_id);
6469
+ if (activeCell) {
6470
+ contextLines.push("## Active Cell (from previous session)");
6471
+ contextLines.push(`- **${activeCell.id}**: ${activeCell.title}`);
6472
+ contextLines.push(` Status: ${activeCell.status}, Priority: ${activeCell.priority}`);
6473
+ if (activeCell.description) {
6474
+ contextLines.push(` ${activeCell.description.slice(0, 200)}...`);
6475
+ }
6476
+ contextLines.push("");
6477
+ }
6478
+ }
6479
+
6480
+ // In-progress cells (work that was mid-flight)
6481
+ const inProgressCells = await adapter.getInProgressCells(projectPath);
6482
+ if (inProgressCells.length > 0) {
6483
+ contextLines.push("## In-Progress Work");
6484
+ for (const cell of inProgressCells.slice(0, 5)) {
6485
+ contextLines.push(`- **${cell.id}**: ${cell.title} (${cell.type}, P${cell.priority})`);
6486
+ if (cell.description) {
6487
+ contextLines.push(` ${cell.description.slice(0, 150)}`);
6488
+ }
6489
+ }
6490
+ if (inProgressCells.length > 5) {
6491
+ contextLines.push(` ... and ${inProgressCells.length - 5} more`);
6492
+ }
6493
+ contextLines.push("");
6494
+ }
6495
+
6496
+ // Open epics (high-level context)
6497
+ const openEpics = await adapter.queryCells(projectPath, {
6498
+ status: "open",
6499
+ type: "epic",
6500
+ limit: 3
6501
+ });
6502
+ if (openEpics.length > 0) {
6503
+ contextLines.push("## Open Epics");
6504
+ for (const epic of openEpics) {
6505
+ const children = await adapter.getEpicChildren(projectPath, epic.id);
6506
+ const openChildren = children.filter(c => c.status !== "closed");
6507
+ contextLines.push(`- **${epic.id}**: ${epic.title}`);
6508
+ contextLines.push(` ${openChildren.length}/${children.length} subtasks remaining`);
6509
+ }
6510
+ contextLines.push("");
6511
+ }
6512
+
6513
+ // Stats summary
6514
+ const stats = await adapter.getCellsStats(projectPath);
6515
+ contextLines.push("## Hive Stats");
6516
+ contextLines.push(`Open: ${stats.open} | In Progress: ${stats.in_progress} | Blocked: ${stats.blocked} | Closed: ${stats.closed}`);
6517
+
6518
+ writeClaudeHookOutput("SessionStart", contextLines.join("\n"));
6519
+ } catch (error) {
6520
+ console.error(error instanceof Error ? error.message : String(error));
6521
+ }
6522
+ }
6523
+
6524
+ /**
6525
+ * Claude hook: provide active context on prompt submission.
6526
+ *
6527
+ * Lightweight hook that runs on every prompt. Provides:
6528
+ * - Active session info
6529
+ * - Current in-progress cell (if any)
6530
+ * - Quick stats for awareness
6531
+ *
6532
+ * Output is suppressed from verbose mode to avoid noise.
6533
+ */
6534
+ async function claudeUserPrompt() {
6535
+ try {
6536
+ const input = await readHookInput<ClaudeHookInput>();
6537
+ const projectPath = resolveClaudeProjectPath(input);
6538
+ const swarmMail = await getSwarmMailLibSQL(projectPath);
6539
+ const db = await swarmMail.getDatabase(projectPath);
6540
+ const adapter = createHiveAdapter(db, projectPath);
6541
+
6542
+ await adapter.runMigrations();
6543
+
6544
+ const session = await adapter.getCurrentSession(projectPath);
6545
+ if (!session) return;
6546
+
6547
+ const contextLines: string[] = [];
6548
+
6549
+ // Always inject current timestamp for temporal awareness
6550
+ const now = new Date();
6551
+ const timestamp = now.toLocaleString("en-US", {
6552
+ weekday: "short",
6553
+ year: "numeric",
6554
+ month: "short",
6555
+ day: "numeric",
6556
+ hour: "2-digit",
6557
+ minute: "2-digit",
6558
+ timeZoneName: "short",
6559
+ });
6560
+ contextLines.push(`**Now**: ${timestamp}`);
6561
+
6562
+ // Semantic memory recall - search hivemind for relevant context
6563
+ if (input.prompt && input.prompt.trim().length > 10) {
6564
+ try {
6565
+ const memoryAdapter = await createMemoryAdapter(db);
6566
+ const findResult = await memoryAdapter.find({ query: input.prompt, limit: 3 });
6567
+ const memories = findResult.results;
6568
+
6569
+ if (memories && memories.length > 0) {
6570
+ // Only include high-confidence matches (score > 0.5)
6571
+ const relevant = memories.filter((m: { score?: number }) => (m.score ?? 0) > 0.5);
6572
+ if (relevant.length > 0) {
6573
+ const memorySnippets = relevant
6574
+ .slice(0, 2) // Max 2 memories to keep context light
6575
+ .map((m: { content?: string; information?: string }) => {
6576
+ const content = m.content || m.information || "";
6577
+ return content.length > 200 ? content.slice(0, 200) + "..." : content;
6578
+ })
6579
+ .join(" | ");
6580
+ contextLines.push(`**Recall**: ${memorySnippets}`);
6581
+ }
6582
+ }
6583
+ } catch {
6584
+ // Memory recall is optional - don't fail the hook
6585
+ }
6586
+ }
6587
+
6588
+ // Current active cell (most relevant context)
6589
+ if (session.active_cell_id) {
6590
+ const activeCell = await adapter.getCell(projectPath, session.active_cell_id);
6591
+ if (activeCell && activeCell.status !== "closed") {
6592
+ contextLines.push(`**Active**: ${activeCell.id} - ${activeCell.title}`);
6593
+ }
6594
+ }
6595
+
6596
+ // Quick in-progress count for awareness
6597
+ const inProgress = await adapter.getInProgressCells(projectPath);
6598
+ if (inProgress.length > 0) {
6599
+ const titles = inProgress.slice(0, 3).map(c => c.title.slice(0, 40)).join(", ");
6600
+ contextLines.push(`**WIP (${inProgress.length})**: ${titles}${inProgress.length > 3 ? "..." : ""}`);
6601
+ }
6602
+
6603
+ // Only output if there's meaningful context
6604
+ if (contextLines.length > 0) {
6605
+ writeClaudeHookOutput("UserPromptSubmit", contextLines.join(" | "), { suppressOutput: true });
6606
+ }
6607
+ } catch (error) {
6608
+ console.error(error instanceof Error ? error.message : String(error));
6609
+ }
6610
+ }
6611
+
6612
+ /**
6613
+ * Claude hook: capture comprehensive state before compaction.
6614
+ *
6615
+ * This is the CRITICAL hook for continuation. It captures:
6616
+ * - All in-progress work with details
6617
+ * - Active epic state and progress
6618
+ * - Any pending/blocked cells
6619
+ * - Reserved files
6620
+ * - Session context
6621
+ *
6622
+ * The output becomes part of the compacted summary, enabling
6623
+ * Claude to continue work seamlessly after context window fills.
6624
+ */
6625
+ async function claudePreCompact() {
6626
+ try {
6627
+ const input = await readHookInput<ClaudeHookInput & { trigger?: string; custom_instructions?: string }>();
6628
+ const projectPath = resolveClaudeProjectPath(input);
6629
+ const swarmMail = await getSwarmMailLibSQL(projectPath);
6630
+ const db = await swarmMail.getDatabase(projectPath);
6631
+ const adapter = createHiveAdapter(db, projectPath);
6632
+
6633
+ await adapter.runMigrations();
6634
+
6635
+ const session = await adapter.getCurrentSession(projectPath);
6636
+ const contextLines: string[] = [];
6637
+
6638
+ contextLines.push("# Swarm State Snapshot (Pre-Compaction)");
6639
+ contextLines.push(`Trigger: ${input.trigger || "auto"}`);
6640
+ if (input.custom_instructions) {
6641
+ contextLines.push(`Instructions: ${input.custom_instructions}`);
6642
+ }
6643
+ contextLines.push("");
6644
+
6645
+ // Session info
6646
+ if (session) {
6647
+ contextLines.push("## Session");
6648
+ contextLines.push(`ID: ${session.id}`);
6649
+ if (session.active_cell_id) {
6650
+ contextLines.push(`Active cell: ${session.active_cell_id}`);
6651
+ }
6652
+ }
6653
+ contextLines.push("");
6654
+
6655
+ // In-progress work (CRITICAL for continuation)
6656
+ const inProgressCells = await adapter.getInProgressCells(projectPath);
6657
+ if (inProgressCells.length > 0) {
6658
+ contextLines.push("## In-Progress Work (CONTINUE THESE)");
6659
+ for (const cell of inProgressCells) {
6660
+ contextLines.push(`### ${cell.id}: ${cell.title}`);
6661
+ contextLines.push(`Type: ${cell.type} | Priority: ${cell.priority} | Status: ${cell.status}`);
6662
+ if (cell.parent_id) {
6663
+ contextLines.push(`Parent: ${cell.parent_id}`);
6664
+ }
6665
+ if (cell.description) {
6666
+ contextLines.push(`Description: ${cell.description}`);
6667
+ }
6668
+ // Get comments for context on progress
6669
+ const comments = await adapter.getComments(projectPath, cell.id);
6670
+ if (comments.length > 0) {
6671
+ const recent = comments.slice(-3);
6672
+ contextLines.push("Recent notes:");
6673
+ for (const comment of recent) {
6674
+ contextLines.push(`- ${comment.body.slice(0, 200)}`);
6675
+ }
6676
+ }
6677
+ contextLines.push("");
6678
+ }
6679
+ }
6680
+
6681
+ // Open epics with children status
6682
+ const openEpics = await adapter.queryCells(projectPath, {
6683
+ status: ["open", "in_progress"],
6684
+ type: "epic",
6685
+ limit: 5
6686
+ });
6687
+ if (openEpics.length > 0) {
6688
+ contextLines.push("## Active Epics");
6689
+ for (const epic of openEpics) {
6690
+ const children = await adapter.getEpicChildren(projectPath, epic.id);
6691
+ const completed = children.filter(c => c.status === "closed").length;
6692
+ const inProgress = children.filter(c => c.status === "in_progress");
6693
+ const open = children.filter(c => c.status === "open");
6694
+
6695
+ contextLines.push(`### ${epic.id}: ${epic.title}`);
6696
+ contextLines.push(`Progress: ${completed}/${children.length} completed`);
6697
+
6698
+ if (inProgress.length > 0) {
6699
+ contextLines.push("In progress:");
6700
+ for (const c of inProgress) {
6701
+ contextLines.push(`- ${c.id}: ${c.title}`);
6702
+ }
6703
+ }
6704
+ if (open.length > 0 && open.length <= 5) {
6705
+ contextLines.push("Remaining:");
6706
+ for (const c of open) {
6707
+ contextLines.push(`- ${c.id}: ${c.title}`);
6708
+ }
6709
+ } else if (open.length > 5) {
6710
+ contextLines.push(`Remaining: ${open.length} tasks`);
6711
+ }
6712
+ contextLines.push("");
6713
+ }
6714
+ }
6715
+
6716
+ // Blocked cells (so Claude knows what's waiting)
6717
+ const blockedCells = await adapter.getBlockedCells(projectPath);
6718
+ if (blockedCells.length > 0) {
6719
+ contextLines.push("## Blocked Work");
6720
+ for (const { cell, blockers } of blockedCells.slice(0, 5)) {
6721
+ contextLines.push(`- ${cell.id}: ${cell.title}`);
6722
+ contextLines.push(` Blocked by: ${blockers.join(", ")}`);
6723
+ }
6724
+ contextLines.push("");
6725
+ }
6726
+
6727
+ // Ready cells (next work available)
6728
+ const readyCell = await adapter.getNextReadyCell(projectPath);
6729
+ if (readyCell) {
6730
+ contextLines.push("## Next Ready Task");
6731
+ contextLines.push(`${readyCell.id}: ${readyCell.title} (P${readyCell.priority})`);
6732
+ contextLines.push("");
6733
+ }
6734
+
6735
+ // Stats
6736
+ const stats = await adapter.getCellsStats(projectPath);
6737
+ contextLines.push("## Hive Stats");
6738
+ contextLines.push(`Open: ${stats.open} | In Progress: ${stats.in_progress} | Blocked: ${stats.blocked} | Closed: ${stats.closed}`);
6739
+
6740
+ writeClaudeHookOutput("PreCompact", contextLines.join("\n"));
6741
+ } catch (error) {
6742
+ console.error(error instanceof Error ? error.message : String(error));
6743
+ }
6744
+ }
6745
+
6746
+ /**
6747
+ * Claude hook: end the active session.
6748
+ */
6749
+ async function claudeSessionEnd() {
6750
+ try {
6751
+ const input = await readHookInput<ClaudeHookInput>();
6752
+ const projectPath = resolveClaudeProjectPath(input);
6753
+ const swarmMail = await getSwarmMailLibSQL(projectPath);
6754
+ const db = await swarmMail.getDatabase(projectPath);
6755
+ const adapter = createHiveAdapter(db, projectPath);
6756
+
6757
+ await adapter.runMigrations();
6758
+
6759
+ const currentSession = await adapter.getCurrentSession(projectPath);
6760
+ if (!currentSession) return;
6761
+
6762
+ await adapter.endSession(projectPath, currentSession.id, {});
6763
+ } catch (error) {
6764
+ console.error(error instanceof Error ? error.message : String(error));
6765
+ }
6766
+ }
6767
+
6768
+ /**
6769
+ * Claude hook: pre-edit reminder for workers
6770
+ *
6771
+ * Runs BEFORE Edit/Write tool calls. Reminds worker to query hivemind first.
6772
+ * This is a gentle nudge, not a blocker.
6773
+ */
6774
+ async function claudePreEdit() {
6775
+ try {
6776
+ const input = await readHookInput<ClaudeHookInput & { tool_name?: string; tool_input?: Record<string, unknown> }>();
6777
+
6778
+ // Only inject reminder if this looks like a worker context
6779
+ // (workers have initialized via swarmmail_init)
6780
+ const projectPath = resolveClaudeProjectPath(input);
6781
+
6782
+ // Check if we've seen hivemind_find in this session
6783
+ // For now, we always remind - tracking will come later
6784
+ const contextLines: string[] = [];
6785
+
6786
+ contextLines.push("**Before this edit**: Did you run `hivemind_find` to check for existing solutions?");
6787
+ contextLines.push("If you haven't queried hivemind yet, consider doing so to avoid re-solving problems.");
6788
+
6789
+ writeClaudeHookOutput("PreToolUse:Edit", contextLines.join("\n"), { suppressOutput: true });
6790
+ } catch (error) {
6791
+ // Non-fatal - don't block the edit
6792
+ console.error(error instanceof Error ? error.message : String(error));
6793
+ }
6794
+ }
6795
+
6796
+ /**
6797
+ * Claude hook: pre-complete check for workers
6798
+ *
6799
+ * Runs BEFORE swarm_complete. Checks compliance with mandatory steps
6800
+ * and warns if any were skipped.
6801
+ */
6802
+ async function claudePreComplete() {
6803
+ try {
6804
+ const input = await readHookInput<ClaudeHookInput & { tool_input?: Record<string, unknown> }>();
6805
+ const projectPath = resolveClaudeProjectPath(input);
6806
+ const contextLines: string[] = [];
6807
+
6808
+ // Check session tracking markers
6809
+ const trackingDir = join(projectPath, ".claude", ".worker-tracking");
6810
+ const sessionId = (input as { session_id?: string }).session_id || "";
6811
+ const sessionDir = join(trackingDir, sessionId.slice(0, 8));
6812
+
6813
+ const mandatoryTools = [
6814
+ { name: "swarmmail_init", label: "Initialize coordination" },
6815
+ { name: "hivemind_find", label: "Query past learnings" },
6816
+ ];
6817
+ const recommendedTools = [
6818
+ { name: "skills_use", label: "Load relevant skills" },
6819
+ { name: "hivemind_store", label: "Store new learnings" },
6820
+ ];
6821
+
6822
+ const missing: string[] = [];
6823
+ const skippedRecommended: string[] = [];
6824
+
6825
+ for (const tool of mandatoryTools) {
6826
+ const markerPath = join(sessionDir, `${tool.name}.marker`);
6827
+ if (!existsSync(markerPath)) {
6828
+ missing.push(tool.label);
6829
+ }
6830
+ }
6831
+
6832
+ for (const tool of recommendedTools) {
6833
+ const markerPath = join(sessionDir, `${tool.name}.marker`);
6834
+ if (!existsSync(markerPath)) {
6835
+ skippedRecommended.push(tool.label);
6836
+ }
6837
+ }
6838
+
6839
+ if (missing.length > 0) {
6840
+ contextLines.push("**MANDATORY STEPS SKIPPED:**");
6841
+ for (const step of missing) {
6842
+ contextLines.push(` - ${step}`);
6843
+ }
6844
+ contextLines.push("");
6845
+ contextLines.push("These steps are critical for swarm coordination.");
6846
+ contextLines.push("Consider running `hivemind_find` before future completions.");
6847
+ }
6848
+
6849
+ if (skippedRecommended.length > 0 && missing.length === 0) {
6850
+ contextLines.push("**Recommended steps not observed:**");
6851
+ for (const step of skippedRecommended) {
6852
+ contextLines.push(` - ${step}`);
6853
+ }
6854
+ }
6855
+
6856
+ if (missing.length === 0 && skippedRecommended.length === 0) {
6857
+ contextLines.push("**All mandatory and recommended steps completed!**");
6858
+ }
6859
+
6860
+ // Emit compliance event for analytics
6861
+ try {
6862
+ const swarmMail = await getSwarmMailLibSQL(projectPath);
6863
+ const db = await swarmMail.getDatabase(projectPath);
6864
+
6865
+ await db.execute({
6866
+ sql: `INSERT INTO swarm_events (event_type, project_path, agent_name, epic_id, bead_id, payload, created_at)
6867
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
6868
+ args: [
6869
+ "worker_compliance",
6870
+ projectPath,
6871
+ "worker",
6872
+ "",
6873
+ "",
6874
+ JSON.stringify({
6875
+ session_id: sessionId,
6876
+ mandatory_skipped: missing.length,
6877
+ recommended_skipped: skippedRecommended.length,
6878
+ score: Math.round(((mandatoryTools.length - missing.length) / mandatoryTools.length) * 100),
6879
+ }),
6880
+ ],
6881
+ });
6882
+ } catch {
6883
+ // Non-fatal
6884
+ }
6885
+
6886
+ if (contextLines.length > 0) {
6887
+ writeClaudeHookOutput("PreToolUse:swarm_complete", contextLines.join("\n"), { suppressOutput: missing.length === 0 });
6888
+ }
6889
+ } catch (error) {
6890
+ console.error(error instanceof Error ? error.message : String(error));
6891
+ }
6892
+ }
6893
+
6894
+ /**
6895
+ * Claude hook: post-complete reminder for workers
6896
+ *
6897
+ * Runs AFTER swarm_complete. Reminds to store learnings if any were discovered.
6898
+ */
6899
+ async function claudePostComplete() {
6900
+ try {
6901
+ const input = await readHookInput<ClaudeHookInput & { tool_output?: string }>();
6902
+ const contextLines: string[] = [];
6903
+
6904
+ contextLines.push("Task completed. If you discovered anything valuable during this work:");
6905
+ contextLines.push("```");
6906
+ contextLines.push("hivemind_store(");
6907
+ contextLines.push(' information="<what you learned, WHY it matters>",');
6908
+ contextLines.push(' tags="<domain, pattern-type>"');
6909
+ contextLines.push(")");
6910
+ contextLines.push("```");
6911
+ contextLines.push("**The swarm's collective intelligence grows when agents share learnings.**");
6912
+
6913
+ writeClaudeHookOutput("PostToolUse:swarm_complete", contextLines.join("\n"), { suppressOutput: true });
6914
+ } catch (error) {
6915
+ console.error(error instanceof Error ? error.message : String(error));
6916
+ }
6917
+ }
6918
+
6919
+ /**
6920
+ * Track when a mandatory tool is called.
6921
+ *
6922
+ * Creates a session-specific marker file that records tool usage.
6923
+ * These markers are checked at swarm_complete to calculate compliance.
6924
+ */
6925
+ async function claudeTrackTool(toolName: string) {
6926
+ if (!toolName) return;
6927
+
6928
+ try {
6929
+ const input = await readHookInput<ClaudeHookInput>();
6930
+ const projectPath = resolveClaudeProjectPath(input);
6931
+
6932
+ // Get or create session tracking directory
6933
+ const trackingDir = join(projectPath, ".claude", ".worker-tracking");
6934
+ const sessionId = (input as { session_id?: string }).session_id || `unknown-${Date.now()}`;
6935
+ const sessionDir = join(trackingDir, sessionId.slice(0, 8)); // Use first 8 chars of session ID
6936
+
6937
+ if (!existsSync(sessionDir)) {
6938
+ mkdirSync(sessionDir, { recursive: true });
6939
+ }
6940
+
6941
+ // Write marker file for this tool
6942
+ const markerPath = join(sessionDir, `${toolName}.marker`);
6943
+ writeFileSync(markerPath, new Date().toISOString());
6944
+
6945
+ // Also emit an event for long-term analytics (non-blocking)
6946
+ try {
6947
+ const swarmMail = await getSwarmMailLibSQL(projectPath);
6948
+ const db = await swarmMail.getDatabase(projectPath);
6949
+
6950
+ await db.execute({
6951
+ sql: `INSERT INTO swarm_events (event_type, project_path, agent_name, epic_id, bead_id, payload, created_at)
6952
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
6953
+ args: [
6954
+ "worker_tool_call",
6955
+ projectPath,
6956
+ "worker", // We don't have agent name in hook context
6957
+ "",
6958
+ "",
6959
+ JSON.stringify({ tool: toolName, session_id: sessionId }),
6960
+ ],
6961
+ });
6962
+ } catch {
6963
+ // Non-fatal - tracking is best-effort
6964
+ }
6965
+ } catch (error) {
6966
+ // Silent failure - don't interrupt the tool call
6967
+ console.error(`[track-tool] ${error instanceof Error ? error.message : String(error)}`);
6968
+ }
6969
+ }
6970
+
6971
+ /**
6972
+ * Check worker compliance - which mandatory tools were called.
6973
+ *
6974
+ * Returns compliance data based on session tracking markers.
6975
+ */
6976
+ async function claudeCompliance() {
6977
+ try {
6978
+ const input = await readHookInput<ClaudeHookInput>();
6979
+ const projectPath = resolveClaudeProjectPath(input);
6980
+
6981
+ const trackingDir = join(projectPath, ".claude", ".worker-tracking");
6982
+ const sessionId = (input as { session_id?: string }).session_id || "";
6983
+ const sessionDir = join(trackingDir, sessionId.slice(0, 8));
6984
+
6985
+ const mandatoryTools = ["swarmmail_init", "hivemind_find", "skills_use"];
6986
+ const recommendedTools = ["hivemind_store"];
6987
+
6988
+ const compliance: Record<string, boolean> = {};
6989
+
6990
+ for (const tool of [...mandatoryTools, ...recommendedTools]) {
6991
+ const markerPath = join(sessionDir, `${tool}.marker`);
6992
+ compliance[tool] = existsSync(markerPath);
6993
+ }
6994
+
6995
+ const mandatoryCount = mandatoryTools.filter(t => compliance[t]).length;
6996
+ const score = Math.round((mandatoryCount / mandatoryTools.length) * 100);
6997
+
6998
+ console.log(JSON.stringify({
6999
+ session_id: sessionId,
7000
+ compliance,
7001
+ mandatory_score: score,
7002
+ mandatory_met: mandatoryCount,
7003
+ mandatory_total: mandatoryTools.length,
7004
+ stored_learnings: compliance.hivemind_store,
7005
+ }));
7006
+ } catch (error) {
7007
+ console.error(error instanceof Error ? error.message : String(error));
7008
+ }
7009
+ }
7010
+
7011
+ /**
7012
+ * Hot-reload skills by clearing cache and re-scanning directories.
7013
+ * Triggered by Claude Code hook when skill files are modified.
7014
+ */
7015
+ async function claudeSkillReload() {
7016
+ try {
7017
+ // Clear the skills cache
7018
+ invalidateSkillsCache();
7019
+
7020
+ // Re-scan skill directories to get updated list
7021
+ const skills = await discoverSkills();
7022
+
7023
+ // Return success with count
7024
+ console.log(JSON.stringify({
7025
+ success: true,
7026
+ reloaded: skills.size,
7027
+ message: `Reloaded ${skills.size} skill(s)`,
7028
+ }));
7029
+ } catch (error) {
7030
+ console.error(JSON.stringify({
7031
+ success: false,
7032
+ error: error instanceof Error ? error.message : String(error),
7033
+ }));
7034
+ }
7035
+ }
7036
+
7037
+ /**
7038
+ * Query worker compliance stats across all sessions.
7039
+ */
7040
+ async function showWorkerCompliance() {
7041
+ const projectPath = process.cwd();
7042
+
7043
+ try {
7044
+ // Use shared executeQuery (handles DB connection internally)
7045
+ const toolUsageSql = `SELECT
7046
+ json_extract(payload, '$.tool') as tool,
7047
+ COUNT(*) as count,
7048
+ COUNT(DISTINCT json_extract(payload, '$.session_id')) as sessions
7049
+ FROM swarm_events
7050
+ WHERE event_type = 'worker_tool_call'
7051
+ AND created_at > datetime('now', '-7 days')
7052
+ GROUP BY tool
7053
+ ORDER BY count DESC`;
7054
+
7055
+ const toolResult = await executeQueryCLI(projectPath, toolUsageSql);
7056
+ const rows = toolResult.rows;
7057
+
7058
+ console.log(yellow(BANNER));
7059
+ console.log(cyan("\n Worker Tool Usage (Last 7 Days)\n"));
7060
+
7061
+ if (rows.length === 0) {
7062
+ console.log(dim("No worker tool usage data found."));
7063
+ console.log(dim("Data is collected when workers run with the Claude Code plugin."));
7064
+ return;
7065
+ }
7066
+
7067
+ console.log(dim("Tool Calls Sessions"));
7068
+ console.log(dim("\u2500".repeat(45)));
7069
+
7070
+ for (const row of rows) {
7071
+ const tool = String(row.tool).padEnd(22);
7072
+ const count = String(row.count).padStart(6);
7073
+ const sessions = String(row.sessions).padStart(8);
7074
+ console.log(`${tool} ${count} ${sessions}`);
7075
+ }
7076
+
7077
+ // Calculate compliance rate
7078
+ const hivemindFinds = rows.find(r => r.tool === "hivemind_find");
7079
+ const completesSql = `SELECT COUNT(*) as count FROM swarm_events
7080
+ WHERE event_type = 'worker_completed'
7081
+ AND created_at > datetime('now', '-7 days')`;
7082
+ const completesResult = await executeQueryCLI(projectPath, completesSql);
7083
+
7084
+ const completes = Number(completesResult.rows[0]?.count || 0);
7085
+ const finds = Number(hivemindFinds?.count || 0);
7086
+
7087
+ if (completes > 0) {
7088
+ const rate = Math.round((Math.min(finds, completes) / completes) * 100);
7089
+ console.log(dim("\n\u2500".repeat(45)));
7090
+ console.log(`\n${green("Hivemind compliance rate:")} ${rate}% (${finds} queries / ${completes} completions)`);
7091
+ }
7092
+
7093
+ } catch (error) {
7094
+ const msg = error instanceof Error ? error.message : String(error);
7095
+ if (msg.includes("no such table")) {
7096
+ console.log(yellow(BANNER));
7097
+ console.log(cyan("\n Worker Tool Usage\n"));
7098
+ console.log(dim("No tracking data yet."));
7099
+ console.log(dim("Compliance tracking starts when workers run with the Claude Code plugin hooks."));
7100
+ console.log(dim("\nThe swarm_events table will be created on first tracked tool call."));
7101
+ } else {
7102
+ console.error("Error fetching compliance data:", msg);
7103
+ }
7104
+ }
7105
+ }
7106
+
7107
+ // ============================================================================
7108
+ // Claude Code Integration - New Stub Handlers
7109
+ // ============================================================================
7110
+
7111
+ /**
7112
+ * Claude hook: coordinator subagent start.
7113
+ * Initializes coordinator context for subagent spawning.
7114
+ */
7115
+ async function claudeCoordinatorStart() {
7116
+ try {
7117
+ const input = await readHookInput<ClaudeHookInput>();
7118
+ const projectPath = resolveClaudeProjectPath(input);
7119
+ writeClaudeHookOutput("SubagentStart:coordinator",
7120
+ `Coordinator initialized for project: ${projectPath}`);
7121
+ } catch (e) {
7122
+ // Non-fatal
7123
+ }
7124
+ }
7125
+
7126
+ /**
7127
+ * Claude hook: worker subagent start.
7128
+ * Initializes worker context for subagent execution.
7129
+ */
7130
+ async function claudeWorkerStart() {
7131
+ try {
7132
+ const input = await readHookInput<ClaudeHookInput>();
7133
+ const projectPath = resolveClaudeProjectPath(input);
7134
+ writeClaudeHookOutput("SubagentStart:worker",
7135
+ `Worker initialized for project: ${projectPath}`);
7136
+ } catch (e) {
7137
+ // Non-fatal
7138
+ }
7139
+ }
7140
+
7141
+ /**
7142
+ * Claude hook: subagent stop cleanup.
7143
+ */
7144
+ async function claudeSubagentStop() {
7145
+ try {
7146
+ await readHookInput<ClaudeHookInput>();
7147
+ // Cleanup - non-fatal
7148
+ } catch (e) {
7149
+ // Silent
7150
+ }
7151
+ }
7152
+
7153
+ /**
7154
+ * Claude hook: agent stop cleanup.
7155
+ */
7156
+ async function claudeAgentStop() {
7157
+ try {
7158
+ await readHookInput<ClaudeHookInput>();
7159
+ // Cleanup - non-fatal
7160
+ } catch (e) {
7161
+ // Silent
7162
+ }
7163
+ }
7164
+
7165
+ /**
7166
+ * Claude hook: track task events.
7167
+ */
7168
+ async function claudeTrackTask() {
7169
+ try {
7170
+ await readHookInput<ClaudeHookInput>();
7171
+ // Track task event - non-fatal
7172
+ } catch (e) {
7173
+ // Silent
7174
+ }
7175
+ }
7176
+
7177
+ /**
7178
+ * Claude hook: post task update tracking.
7179
+ */
7180
+ async function claudePostTaskUpdate() {
7181
+ try {
7182
+ await readHookInput<ClaudeHookInput>();
7183
+ // Post-update tracking - non-fatal
7184
+ } catch (e) {
7185
+ // Silent
7186
+ }
7187
+ }
7188
+
6027
7189
  // ============================================================================
6028
7190
  // Main
6029
7191
  // ============================================================================
@@ -6048,6 +7210,9 @@ switch (command) {
6048
7210
  case "config":
6049
7211
  config();
6050
7212
  break;
7213
+ case "claude":
7214
+ await claudeCommand();
7215
+ break;
6051
7216
  case "serve":
6052
7217
  await serve();
6053
7218
  break;