opencode-swarm-plugin 0.61.0 → 0.62.0

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,8 +8,6 @@
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)
13
11
  * swarm version - Show version info
14
12
  * swarm - Interactive mode (same as setup)
15
13
  */
@@ -19,20 +17,17 @@ import {
19
17
  chmodSync,
20
18
  copyFileSync,
21
19
  existsSync,
22
- lstatSync,
23
20
  mkdirSync,
24
21
  readFileSync,
25
- readlinkSync,
26
22
  readdirSync,
27
23
  renameSync,
28
24
  rmdirSync,
29
25
  rmSync,
30
26
  statSync,
31
- symlinkSync,
32
27
  writeFileSync,
33
28
  } from "fs";
34
29
  import { homedir } from "os";
35
- import { basename, dirname, join, resolve } from "path";
30
+ import { basename, dirname, join } from "path";
36
31
  import { fileURLToPath } from "url";
37
32
  import {
38
33
  checkBeadsMigrationNeeded,
@@ -55,21 +50,17 @@ import {
55
50
  resolvePartialId,
56
51
  createDurableStreamAdapter,
57
52
  createDurableStreamServer,
58
- consolidateDatabases,
59
- getGlobalDbPath,
60
53
  } from "swarm-mail";
61
- import { createMemoryAdapter } from "../src/memory";
62
54
  import { execSync, spawn } from "child_process";
63
55
  import { tmpdir } from "os";
64
56
 
65
57
  // Query & observability tools
66
58
  import {
67
- executeQueryCLI,
59
+ executeQuery,
68
60
  executePreset,
69
61
  formatAsTable,
70
62
  formatAsCSV,
71
63
  formatAsJSON,
72
- type QueryResult,
73
64
  } from "../src/query-tools.js";
74
65
  import {
75
66
  getWorkerStatus,
@@ -89,11 +80,6 @@ import {
89
80
  exportToCSV,
90
81
  exportToJSON,
91
82
  } from "../src/export-tools.js";
92
- import { tree } from "./commands/tree.js";
93
- import { session } from "./commands/session.js";
94
- import { log } from "./commands/log.js";
95
- import { status } from "./commands/status.js";
96
- import { queue } from "./commands/queue.js";
97
83
  import {
98
84
  querySwarmHistory,
99
85
  formatSwarmHistory,
@@ -106,9 +92,6 @@ import {
106
92
  formatHealthDashboard,
107
93
  } from "../src/observability-health.js";
108
94
 
109
- // Swarm insights
110
- import { getRejectionAnalytics, getCompactionAnalytics } from "../src/swarm-insights.js";
111
-
112
95
  // Eval tools
113
96
  import { getPhase, getScoreHistory, recordEvalRun, getEvalHistoryPath } from "../src/eval-history.js";
114
97
  import { DEFAULT_THRESHOLDS, checkGate } from "../src/eval-gates.js";
@@ -119,15 +102,10 @@ import { detectRegressions } from "../src/regression-detection.js";
119
102
  import { allTools } from "../src/index.js";
120
103
 
121
104
  const __dirname = dirname(fileURLToPath(import.meta.url));
122
- // When running from bin/swarm.ts, go up one level to find package.json
123
- // When bundled to dist/bin/swarm.js, go up two levels
124
- const pkgPath = existsSync(join(__dirname, "..", "package.json"))
125
- ? join(__dirname, "..", "package.json")
126
- : join(__dirname, "..", "..", "package.json");
105
+ // When bundled to dist/bin/swarm.js, need to go up two levels to find package.json
106
+ const pkgPath = join(__dirname, "..", "..", "package.json");
127
107
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
128
108
  const VERSION: string = pkg.version;
129
- const PACKAGE_ROOT = dirname(pkgPath);
130
- const CLAUDE_PLUGIN_NAME = "swarm";
131
109
 
132
110
  // ============================================================================
133
111
  // ASCII Art & Branding
@@ -465,13 +443,24 @@ function showUpdateNotification(info: UpdateInfo) {
465
443
  // Types
466
444
  // ============================================================================
467
445
 
446
+ type InstallType = "brew" | "npm" | "bun" | "pnpm" | "yarn" | "paru" | "choco" | "scoop" | "manual";
447
+
448
+ interface InstallMethod {
449
+ name: string;
450
+ command: string;
451
+ type: InstallType;
452
+ platforms?: ("darwin" | "linux" | "win32")[];
453
+ requires?: string; // Command that must exist for this method to work
454
+ }
455
+
468
456
  interface Dependency {
469
457
  name: string;
470
458
  command: string;
471
459
  checkArgs: string[];
472
460
  required: boolean;
473
461
  install: string;
474
- installType: "brew" | "curl" | "go" | "npm" | "manual";
462
+ installType: InstallType;
463
+ installMethods?: InstallMethod[]; // Multiple installation options
475
464
  description: string;
476
465
  }
477
466
 
@@ -485,6 +474,77 @@ interface CheckResult {
485
474
  // Dependencies
486
475
  // ============================================================================
487
476
 
477
+ /**
478
+ * All available installation methods for OpenCode.
479
+ * Ordered by recommendation (curl first as it's most universal).
480
+ */
481
+ const OPENCODE_INSTALL_METHODS: InstallMethod[] = [
482
+ // Universal (works on macOS and Linux)
483
+ {
484
+ name: "curl (recommended)",
485
+ command: "curl -fsSL https://opencode.ai/install | bash",
486
+ type: "manual",
487
+ platforms: ["darwin", "linux"],
488
+ requires: "curl",
489
+ },
490
+ // macOS and Linux package managers
491
+ {
492
+ name: "Homebrew",
493
+ command: "brew install opencode",
494
+ type: "brew",
495
+ platforms: ["darwin", "linux"],
496
+ requires: "brew",
497
+ },
498
+ // Node.js package managers (cross-platform)
499
+ {
500
+ name: "npm",
501
+ command: "npm install -g opencode-ai",
502
+ type: "npm",
503
+ requires: "npm",
504
+ },
505
+ {
506
+ name: "Bun",
507
+ command: "bun install -g opencode-ai",
508
+ type: "bun",
509
+ requires: "bun",
510
+ },
511
+ {
512
+ name: "pnpm",
513
+ command: "pnpm install -g opencode-ai",
514
+ type: "pnpm",
515
+ requires: "pnpm",
516
+ },
517
+ {
518
+ name: "Yarn",
519
+ command: "yarn global add opencode-ai",
520
+ type: "yarn",
521
+ requires: "yarn",
522
+ },
523
+ // Linux-specific
524
+ {
525
+ name: "paru (Arch Linux)",
526
+ command: "paru -S opencode-bin",
527
+ type: "paru",
528
+ platforms: ["linux"],
529
+ requires: "paru",
530
+ },
531
+ // Windows-specific
532
+ {
533
+ name: "Chocolatey",
534
+ command: "choco install opencode",
535
+ type: "choco",
536
+ platforms: ["win32"],
537
+ requires: "choco",
538
+ },
539
+ {
540
+ name: "Scoop",
541
+ command: "scoop bucket add extras && scoop install extras/opencode",
542
+ type: "scoop",
543
+ platforms: ["win32"],
544
+ requires: "scoop",
545
+ },
546
+ ];
547
+
488
548
  const DEPENDENCIES: Dependency[] = [
489
549
  {
490
550
  name: "Bun",
@@ -500,18 +560,10 @@ const DEPENDENCIES: Dependency[] = [
500
560
  command: "opencode",
501
561
  checkArgs: ["--version"],
502
562
  required: true,
503
- install: "brew install sst/tap/opencode",
504
- installType: "brew",
505
- description: "AI coding assistant (plugin host)",
506
- },
507
- {
508
- name: "Claude Code",
509
- command: "claude",
510
- checkArgs: ["--version"],
511
- required: false,
512
- install: "https://docs.anthropic.com/claude-code",
563
+ install: "curl -fsSL https://opencode.ai/install | bash", // Default to curl
513
564
  installType: "manual",
514
- description: "Claude Code CLI (optional host)",
565
+ installMethods: OPENCODE_INSTALL_METHODS,
566
+ description: "AI coding assistant (plugin host)",
515
567
  },
516
568
  // Note: Beads CLI (bd) is NO LONGER required - we use HiveAdapter from swarm-mail
517
569
  // which provides the same functionality programmatically without external dependencies
@@ -524,6 +576,15 @@ const DEPENDENCIES: Dependency[] = [
524
576
  installType: "manual",
525
577
  description: "Indexes and searches AI coding agent history for context",
526
578
  },
579
+ {
580
+ name: "UBS (Ultimate Bug Scanner)",
581
+ command: "ubs",
582
+ checkArgs: ["--help"],
583
+ required: false,
584
+ install: "https://github.com/Dicklesworthstone/ultimate_bug_scanner",
585
+ installType: "manual",
586
+ description: "AI-powered static analysis for pre-completion bug scanning",
587
+ },
527
588
  {
528
589
  name: "Ollama",
529
590
  command: "ollama",
@@ -604,154 +665,79 @@ async function checkAllDependencies(): Promise<CheckResult[]> {
604
665
  return results;
605
666
  }
606
667
 
607
- // ============================================================================
608
- // Claude Code Helpers
609
- // ============================================================================
610
-
611
- interface ClaudeHookInput {
612
- project?: { path?: string };
613
- cwd?: string;
614
- session?: { id?: string };
615
- metadata?: { cwd?: string };
616
- prompt?: string; // UserPromptSubmit includes the user's prompt text
617
- }
618
-
619
668
  /**
620
- * Resolve the Claude Code plugin root bundled with the package.
669
+ * Get available installation methods for a dependency based on:
670
+ * - Current platform (darwin, linux, win32)
671
+ * - Available package managers on the system
621
672
  */
622
- function getClaudePluginRoot(): string {
623
- return join(PACKAGE_ROOT, "claude-plugin");
624
- }
673
+ async function getAvailableInstallMethods(
674
+ methods: InstallMethod[],
675
+ ): Promise<InstallMethod[]> {
676
+ const platform = process.platform as "darwin" | "linux" | "win32";
677
+ const available: InstallMethod[] = [];
678
+
679
+ for (const method of methods) {
680
+ // Check platform compatibility
681
+ if (method.platforms && !method.platforms.includes(platform)) {
682
+ continue;
683
+ }
625
684
 
626
- /**
627
- * Resolve the Claude Code config directory.
628
- */
629
- function getClaudeConfigDir(): string {
630
- return join(homedir(), ".claude");
631
- }
685
+ // Check if required command is available
686
+ if (method.requires) {
687
+ const { available: cmdAvailable } = await checkCommand(method.requires, ["--version"]);
688
+ // Some commands don't support --version, try --help as fallback
689
+ if (!cmdAvailable) {
690
+ const { available: helpAvailable } = await checkCommand(method.requires, ["--help"]);
691
+ if (!helpAvailable) {
692
+ // Special case: curl doesn't have --version on some systems, check with -V
693
+ if (method.requires === "curl") {
694
+ const { available: curlAvailable } = await checkCommand("curl", ["-V"]);
695
+ if (!curlAvailable) continue;
696
+ } else {
697
+ continue;
698
+ }
699
+ }
700
+ }
701
+ }
632
702
 
633
- /**
634
- * Read JSON input from stdin for Claude Code hooks.
635
- */
636
- async function readHookInput<T>(): Promise<T | null> {
637
- if (process.stdin.isTTY) return null;
638
- const chunks: string[] = [];
639
- for await (const chunk of process.stdin) {
640
- chunks.push(chunk.toString());
641
- }
642
- const raw = chunks.join("").trim();
643
- if (!raw) return null;
644
- try {
645
- return JSON.parse(raw) as T;
646
- } catch {
647
- return null;
703
+ available.push(method);
648
704
  }
649
- }
650
-
651
- /**
652
- * Resolve the project path for Claude Code hook executions.
653
- */
654
- function resolveClaudeProjectPath(input: ClaudeHookInput | null): string {
655
- return (
656
- input?.project?.path ||
657
- input?.cwd ||
658
- input?.metadata?.cwd ||
659
- process.env.CLAUDE_PROJECT_DIR ||
660
- process.env.PWD ||
661
- process.cwd()
662
- );
663
- }
664
705
 
665
- /**
666
- * Format hook output for Claude Code context injection.
667
- * Uses the proper JSON format with hookSpecificOutput for structured feedback.
668
- */
669
- function writeClaudeHookOutput(
670
- hookEventName: string,
671
- additionalContext: string,
672
- options?: { suppressOutput?: boolean }
673
- ): void {
674
- if (!additionalContext.trim()) return;
675
- process.stdout.write(
676
- `${JSON.stringify({
677
- suppressOutput: options?.suppressOutput,
678
- hookSpecificOutput: {
679
- hookEventName,
680
- additionalContext,
681
- },
682
- })}\n`,
683
- );
706
+ return available;
684
707
  }
685
708
 
686
709
  /**
687
- * @deprecated Use writeClaudeHookOutput for proper hook-specific JSON format
710
+ * Prompt user to select an installation method for a dependency
688
711
  */
689
- function writeClaudeHookContext(additionalContext: string): void {
690
- if (!additionalContext.trim()) return;
691
- process.stdout.write(
692
- `${JSON.stringify({
693
- additionalContext,
694
- })}\n`,
695
- );
696
- }
697
-
698
- interface ClaudeInstallStatus {
699
- pluginRoot: string;
700
- globalPluginPath: string;
701
- globalPluginTarget?: string;
702
- globalPluginExists: boolean;
703
- globalPluginLinked: boolean;
704
- projectClaudeDir: string;
705
- projectConfigExists: boolean;
706
- projectConfigPaths: string[];
707
- }
712
+ async function promptInstallMethod(
713
+ dep: Dependency,
714
+ availableMethods: InstallMethod[],
715
+ ): Promise<InstallMethod | null> {
716
+ if (availableMethods.length === 0) {
717
+ p.log.error(`No installation methods available for ${dep.name} on this system`);
718
+ p.log.message(dim(" You may need to install it manually"));
719
+ return null;
720
+ }
708
721
 
709
- /**
710
- * Inspect Claude Code install state for global and project scopes.
711
- */
712
- function getClaudeInstallStatus(projectPath: string): ClaudeInstallStatus {
713
- const pluginRoot = getClaudePluginRoot();
714
- const claudeConfigDir = getClaudeConfigDir();
715
- const globalPluginPath = join(claudeConfigDir, "plugins", CLAUDE_PLUGIN_NAME);
722
+ if (availableMethods.length === 1) {
723
+ // Only one option, use it directly
724
+ return availableMethods[0];
725
+ }
716
726
 
717
- let globalPluginExists = false;
718
- let globalPluginLinked = false;
719
- let globalPluginTarget: string | undefined;
727
+ const selected = await p.select({
728
+ message: `How would you like to install ${dep.name}?`,
729
+ options: availableMethods.map((method) => ({
730
+ value: method,
731
+ label: method.name,
732
+ hint: method.command,
733
+ })),
734
+ });
720
735
 
721
- if (existsSync(globalPluginPath)) {
722
- globalPluginExists = true;
723
- try {
724
- const stat = lstatSync(globalPluginPath);
725
- if (stat.isSymbolicLink()) {
726
- globalPluginLinked = true;
727
- const target = readlinkSync(globalPluginPath);
728
- globalPluginTarget = resolve(dirname(globalPluginPath), target);
729
- }
730
- } catch {
731
- // Ignore errors
732
- }
736
+ if (p.isCancel(selected)) {
737
+ return null;
733
738
  }
734
739
 
735
- const projectClaudeDir = join(projectPath, ".claude");
736
- const projectConfigPaths = [
737
- join(projectClaudeDir, "commands"),
738
- join(projectClaudeDir, "agents"),
739
- join(projectClaudeDir, "skills"),
740
- join(projectClaudeDir, "hooks"),
741
- join(projectClaudeDir, ".mcp.json"),
742
- ];
743
- const projectConfigExists = projectConfigPaths.some((path) => existsSync(path));
744
-
745
- return {
746
- pluginRoot,
747
- globalPluginPath,
748
- globalPluginTarget,
749
- globalPluginExists,
750
- globalPluginLinked,
751
- projectClaudeDir,
752
- projectConfigExists,
753
- projectConfigPaths,
754
- };
740
+ return selected;
755
741
  }
756
742
 
757
743
  // ============================================================================
@@ -1570,7 +1556,7 @@ skills_list()
1570
1556
  # Check what MCP servers are available (look for context7, pdf-brain, fetch, etc.)
1571
1557
  # Note: No direct MCP listing tool - infer from task context or ask coordinator
1572
1558
 
1573
- # Check for CLI tools if relevant (bd, cass, ollama)
1559
+ # Check for CLI tools if relevant (bd, cass, ubs, ollama)
1574
1560
  # Use Bash tool to check: which <tool-name>
1575
1561
  \`\`\`
1576
1562
 
@@ -1699,6 +1685,7 @@ bash("which <tool>", description="Check if <tool> is available")
1699
1685
 
1700
1686
  # Examples:
1701
1687
  bash("which cass", description="Check CASS availability")
1688
+ bash("which ubs", description="Check UBS availability")
1702
1689
  bash("ollama --version", description="Check Ollama availability")
1703
1690
  \`\`\`
1704
1691
 
@@ -1758,20 +1745,28 @@ Begin by executing Step 1 (swarmmail_init).
1758
1745
 
1759
1746
  /**
1760
1747
  * Get the fix command for a dependency
1761
- * Returns null for manual installs (those show a link instead)
1748
+ * Returns platform-appropriate install suggestions
1762
1749
  */
1763
1750
  function getFixCommand(dep: Dependency): string | null {
1751
+ const platform = process.platform;
1752
+
1764
1753
  switch (dep.name) {
1765
1754
  case "OpenCode":
1766
- return "brew install sst/tap/opencode";
1767
- case "Claude Code":
1768
- return "See: https://docs.anthropic.com/claude-code";
1755
+ // Show platform-appropriate suggestions
1756
+ if (platform === "win32") {
1757
+ return "choco install opencode OR scoop bucket add extras && scoop install extras/opencode OR npm install -g opencode-ai";
1758
+ } else {
1759
+ return "curl -fsSL https://opencode.ai/install | bash OR brew install opencode OR npm install -g opencode-ai";
1760
+ }
1769
1761
  case "Ollama":
1762
+ if (platform === "win32") {
1763
+ return "See: https://ollama.ai/download (then: ollama pull mxbai-embed-large)";
1764
+ }
1770
1765
  return "brew install ollama && ollama pull mxbai-embed-large";
1771
- case "Redis":
1772
- return "brew install redis && brew services start redis";
1773
1766
  case "CASS (Coding Agent Session Search)":
1774
1767
  return "See: https://github.com/Dicklesworthstone/coding_agent_session_search";
1768
+ case "UBS (Ultimate Bug Scanner)":
1769
+ return "See: https://github.com/Dicklesworthstone/ultimate_bug_scanner";
1775
1770
  default:
1776
1771
  // Fallback to generic install command if available
1777
1772
  return dep.installType !== "manual" ? dep.install : null;
@@ -1843,7 +1838,7 @@ async function doctor(debug = false) {
1843
1838
  // Check skills
1844
1839
  p.log.step("Skills:");
1845
1840
  const configDir = join(homedir(), ".config", "opencode");
1846
- const globalSkillsPath = join(configDir, "skill");
1841
+ const globalSkillsPath = join(configDir, "skills");
1847
1842
  const bundledSkillsPath = join(__dirname, "..", "global-skills");
1848
1843
 
1849
1844
  // Global skills directory
@@ -1901,42 +1896,6 @@ async function doctor(debug = false) {
1901
1896
  }
1902
1897
  }
1903
1898
 
1904
- // Claude Code checks
1905
- p.log.step("Claude Code:");
1906
- const claudeResult = results.find((result) => result.dep.name === "Claude Code");
1907
- const claudeStatus = getClaudeInstallStatus(process.cwd());
1908
-
1909
- if (!claudeResult?.available) {
1910
- p.log.warn("Claude Code CLI not detected (optional)");
1911
- p.log.message(dim(" Install: https://docs.anthropic.com/claude-code"));
1912
- } else {
1913
- p.log.success(`Claude Code CLI detected${claudeResult.version ? ` v${claudeResult.version}` : ""}`);
1914
-
1915
- if (existsSync(claudeStatus.pluginRoot)) {
1916
- p.log.message(dim(` Plugin bundle: ${claudeStatus.pluginRoot}`));
1917
- } else {
1918
- p.log.warn(`Claude plugin bundle missing: ${claudeStatus.pluginRoot}`);
1919
- }
1920
-
1921
- if (claudeStatus.globalPluginExists) {
1922
- if (claudeStatus.globalPluginLinked) {
1923
- p.log.success(`Global plugin symlink: ${claudeStatus.globalPluginPath}`);
1924
- } else {
1925
- p.log.warn(`Global plugin exists but is not a symlink: ${claudeStatus.globalPluginPath}`);
1926
- }
1927
- } else {
1928
- p.log.warn("Global Claude plugin not installed");
1929
- p.log.message(dim(" Run: swarm claude install"));
1930
- }
1931
-
1932
- if (claudeStatus.projectConfigExists) {
1933
- p.log.success(`Project Claude config: ${claudeStatus.projectClaudeDir}`);
1934
- } else {
1935
- p.log.warn("Project Claude config not found");
1936
- p.log.message(dim(" Run: swarm claude init"));
1937
- }
1938
- }
1939
-
1940
1899
  if (requiredMissing.length > 0) {
1941
1900
  p.outro(
1942
1901
  "Missing " +
@@ -2173,8 +2132,7 @@ async function setup(forceReinstall = false, nonInteractive = false) {
2173
2132
  p.log.step("Missing " + requiredMissing.length + " required dependencies");
2174
2133
 
2175
2134
  for (const { dep } of requiredMissing) {
2176
- // In non-interactive mode, auto-install required deps
2177
- const shouldInstall = nonInteractive ? true : await p.confirm({
2135
+ const shouldInstall = await p.confirm({
2178
2136
  message: "Install " + dep.name + "? (" + dep.description + ")",
2179
2137
  initialValue: true,
2180
2138
  });
@@ -2185,16 +2143,47 @@ async function setup(forceReinstall = false, nonInteractive = false) {
2185
2143
  }
2186
2144
 
2187
2145
  if (shouldInstall) {
2188
- const installSpinner = p.spinner();
2189
- installSpinner.start("Installing " + dep.name + "...");
2146
+ // Check if this dependency has multiple installation methods
2147
+ if (dep.installMethods && dep.installMethods.length > 0) {
2148
+ // Detect available methods on this system
2149
+ const detectSpinner = p.spinner();
2150
+ detectSpinner.start("Detecting available installation methods...");
2151
+ const availableMethods = await getAvailableInstallMethods(dep.installMethods);
2152
+ detectSpinner.stop(`Found ${availableMethods.length} installation method(s)`);
2153
+
2154
+ // Prompt user to select method
2155
+ const selectedMethod = await promptInstallMethod(dep, availableMethods);
2156
+
2157
+ if (!selectedMethod) {
2158
+ p.log.warn("Skipping " + dep.name + " - no installation method selected");
2159
+ continue;
2160
+ }
2190
2161
 
2191
- const success = await runInstall(dep.install);
2162
+ const installSpinner = p.spinner();
2163
+ installSpinner.start(`Installing ${dep.name} via ${selectedMethod.name}...`);
2164
+
2165
+ const success = await runInstall(selectedMethod.command);
2192
2166
 
2193
- if (success) {
2194
- installSpinner.stop(dep.name + " installed");
2167
+ if (success) {
2168
+ installSpinner.stop(dep.name + " installed");
2169
+ } else {
2170
+ installSpinner.stop("Failed to install " + dep.name);
2171
+ p.log.error("Command failed: " + selectedMethod.command);
2172
+ p.log.message(dim(" Try running the command manually in your terminal"));
2173
+ }
2195
2174
  } else {
2196
- installSpinner.stop("Failed to install " + dep.name);
2197
- p.log.error("Manual install: " + dep.install);
2175
+ // Fallback to single install command
2176
+ const installSpinner = p.spinner();
2177
+ installSpinner.start("Installing " + dep.name + "...");
2178
+
2179
+ const success = await runInstall(dep.install);
2180
+
2181
+ if (success) {
2182
+ installSpinner.stop(dep.name + " installed");
2183
+ } else {
2184
+ installSpinner.stop("Failed to install " + dep.name);
2185
+ p.log.error("Manual install: " + dep.install);
2186
+ }
2198
2187
  }
2199
2188
  } else {
2200
2189
  p.log.warn("Skipping " + dep.name + " - swarm may not work correctly");
@@ -2370,85 +2359,6 @@ async function setup(forceReinstall = false, nonInteractive = false) {
2370
2359
  p.log.message(dim(' No OpenCode config found (skipping MCP check)'));
2371
2360
  }
2372
2361
 
2373
- // Check for stray databases and consolidate to global database
2374
- p.log.step("Checking for stray databases...");
2375
- const globalDbPath = getGlobalDbPath();
2376
-
2377
- try {
2378
- const report = await consolidateDatabases(cwd, globalDbPath, {
2379
- yes: nonInteractive,
2380
- interactive: !nonInteractive,
2381
- });
2382
-
2383
- if (report.straysFound > 0) {
2384
- if (report.totalRowsMigrated > 0) {
2385
- p.log.success(
2386
- `Migrated ${report.totalRowsMigrated} records from ${report.straysMigrated} stray database(s)`
2387
- );
2388
- for (const migration of report.migrations) {
2389
- const { migrated, skipped } = migration.result;
2390
- if (migrated > 0 || skipped > 0) {
2391
- p.log.message(
2392
- dim(
2393
- ` ${migration.path}: ${migrated} migrated, ${skipped} skipped`
2394
- )
2395
- );
2396
- }
2397
- }
2398
- } else {
2399
- p.log.message(
2400
- dim(" All data already in global database (no migration needed)")
2401
- );
2402
- }
2403
- } else {
2404
- p.log.message(dim(" No stray databases found"));
2405
- }
2406
-
2407
- if (report.errors.length > 0) {
2408
- p.log.warn(`${report.errors.length} error(s) during consolidation`);
2409
- for (const error of report.errors) {
2410
- p.log.message(dim(` ${error}`));
2411
- }
2412
- }
2413
- } catch (error) {
2414
- p.log.warn("Database consolidation check failed");
2415
- if (error instanceof Error) {
2416
- p.log.message(dim(` ${error.message}`));
2417
- }
2418
- // Don't fail setup - this is non-critical
2419
- }
2420
-
2421
- // Run database repair after consolidation
2422
- p.log.step("Running database integrity check...");
2423
- try {
2424
- const repairResult = await runDbRepair({ dryRun: false });
2425
-
2426
- if (repairResult.totalCleaned === 0) {
2427
- p.log.success("Database integrity verified - no issues found");
2428
- } else {
2429
- p.log.success(`Cleaned ${repairResult.totalCleaned} orphaned/invalid records`);
2430
-
2431
- if (repairResult.nullBeads > 0) {
2432
- p.log.message(dim(` - ${repairResult.nullBeads} beads with NULL IDs`));
2433
- }
2434
- if (repairResult.orphanedRecipients > 0) {
2435
- p.log.message(dim(` - ${repairResult.orphanedRecipients} orphaned message recipients`));
2436
- }
2437
- if (repairResult.messagesWithoutRecipients > 0) {
2438
- p.log.message(dim(` - ${repairResult.messagesWithoutRecipients} messages without recipients`));
2439
- }
2440
- if (repairResult.expiredReservations > 0) {
2441
- p.log.message(dim(` - ${repairResult.expiredReservations} expired reservations`));
2442
- }
2443
- }
2444
- } catch (error) {
2445
- p.log.warn("Database repair check failed (non-critical)");
2446
- if (error instanceof Error) {
2447
- p.log.message(dim(` ${error.message}`));
2448
- }
2449
- // Don't fail setup - this is non-critical
2450
- }
2451
-
2452
2362
  // Model defaults: opus for coordinator, sonnet for worker, haiku for lite
2453
2363
  const DEFAULT_COORDINATOR = "anthropic/claude-opus-4-5";
2454
2364
  const DEFAULT_WORKER = "anthropic/claude-sonnet-4-5";
@@ -2753,36 +2663,6 @@ async function setup(forceReinstall = false, nonInteractive = false) {
2753
2663
  }
2754
2664
  }
2755
2665
 
2756
- // Claude Code checks
2757
- p.log.step("Claude Code integration (optional)...");
2758
- const claudeResult = results.find((result) => result.dep.name === "Claude Code");
2759
- const claudeStatus = getClaudeInstallStatus(cwd);
2760
-
2761
- if (!claudeResult?.available) {
2762
- p.log.warn("Claude Code not detected (optional)");
2763
- p.log.message(dim(" Install: https://docs.anthropic.com/claude-code"));
2764
- } else {
2765
- const versionInfo = claudeResult.version ? ` v${claudeResult.version}` : "";
2766
- p.log.success(`Claude Code detected${versionInfo}`);
2767
- p.log.message(dim(` Plugin bundle: ${claudeStatus.pluginRoot}`));
2768
-
2769
- if (claudeStatus.globalPluginExists) {
2770
- if (claudeStatus.globalPluginLinked) {
2771
- p.log.success(`Claude plugin linked: ${claudeStatus.globalPluginPath}`);
2772
- } else {
2773
- p.log.warn(`Claude plugin exists but is not a symlink: ${claudeStatus.globalPluginPath}`);
2774
- }
2775
- } else {
2776
- p.log.message(dim(" Run 'swarm claude install' for a dev symlink"));
2777
- }
2778
-
2779
- if (claudeStatus.projectConfigExists) {
2780
- p.log.success(`Project Claude config: ${claudeStatus.projectClaudeDir}`);
2781
- } else {
2782
- p.log.message(dim(" Run 'swarm claude init' to create .claude/ config"));
2783
- }
2784
- }
2785
-
2786
2666
  // Show setup summary
2787
2667
  const totalFiles = stats.created + stats.updated + stats.unchanged;
2788
2668
  const summaryParts: string[] = [];
@@ -2801,903 +2681,43 @@ async function setup(forceReinstall = false, nonInteractive = false) {
2801
2681
  p.outro("Run 'swarm doctor' to verify installation.");
2802
2682
  }
2803
2683
 
2804
- // ============================================================================
2805
- // Claude Code Commands
2806
- // ============================================================================
2807
-
2808
- /**
2809
- * Handle Claude Code subcommands.
2810
- */
2811
- async function claudeCommand() {
2812
- const args = process.argv.slice(3);
2813
- const subcommand = args[0];
2814
-
2815
- if (!subcommand || ["help", "--help", "-h"].includes(subcommand)) {
2816
- showClaudeHelp();
2817
- return;
2818
- }
2819
-
2820
- switch (subcommand) {
2821
- case "path":
2822
- claudePath();
2823
- break;
2824
- case "install":
2825
- await claudeInstall();
2826
- break;
2827
- case "uninstall":
2828
- await claudeUninstall();
2829
- break;
2830
- case "init":
2831
- await claudeInit();
2832
- break;
2833
- case "session-start":
2834
- await claudeSessionStart();
2835
- break;
2836
- case "user-prompt":
2837
- await claudeUserPrompt();
2838
- break;
2839
- case "pre-edit":
2840
- await claudePreEdit();
2841
- break;
2842
- case "pre-complete":
2843
- await claudePreComplete();
2844
- break;
2845
- case "post-complete":
2846
- await claudePostComplete();
2847
- break;
2848
- case "pre-compact":
2849
- await claudePreCompact();
2850
- break;
2851
- case "session-end":
2852
- await claudeSessionEnd();
2853
- break;
2854
- case "track-tool":
2855
- await claudeTrackTool(Bun.argv[4]); // tool name is 4th arg
2856
- break;
2857
- case "compliance":
2858
- await claudeCompliance();
2859
- break;
2860
- default:
2861
- console.error(`Unknown subcommand: ${subcommand}`);
2862
- showClaudeHelp();
2863
- process.exit(1);
2864
- }
2865
- }
2866
-
2867
- function showClaudeHelp() {
2868
- console.log(`
2869
- Usage: swarm claude <command>
2870
-
2871
- Commands:
2872
- path Print Claude plugin path (for --plugin-dir)
2873
- install Symlink plugin into ~/.claude/plugins/${CLAUDE_PLUGIN_NAME}
2874
- uninstall Remove Claude plugin symlink
2875
- init Create project-local .claude/ config
2876
- session-start Hook: session start context (JSON output)
2877
- user-prompt Hook: prompt submit context (JSON output)
2878
- pre-edit Hook: pre-Edit/Write reminder (hivemind check)
2879
- pre-complete Hook: pre-swarm_complete checklist
2880
- post-complete Hook: post-swarm_complete learnings reminder
2881
- pre-compact Hook: pre-compaction handler
2882
- session-end Hook: session cleanup
2883
- `);
2884
- }
2684
+ async function init() {
2685
+ p.intro("swarm init v" + VERSION);
2885
2686
 
2886
- /**
2887
- * Print the bundled Claude plugin path.
2888
- */
2889
- function claudePath() {
2890
- const pluginRoot = getClaudePluginRoot();
2891
- if (!existsSync(pluginRoot)) {
2892
- console.error(`Claude plugin not found at ${pluginRoot}`);
2893
- process.exit(1);
2894
- }
2895
- console.log(pluginRoot);
2896
- }
2687
+ const projectPath = process.cwd();
2897
2688
 
2898
- /**
2899
- * Install the Claude plugin symlink for local development.
2900
- */
2901
- async function claudeInstall() {
2902
- p.intro("swarm claude install");
2903
- const pluginRoot = getClaudePluginRoot();
2904
- if (!existsSync(pluginRoot)) {
2905
- p.log.error(`Claude plugin not found at ${pluginRoot}`);
2689
+ const gitDir = existsSync(".git");
2690
+ if (!gitDir) {
2691
+ p.log.error("Not in a git repository");
2692
+ p.log.message("Run 'git init' first, or cd to a git repo");
2906
2693
  p.outro("Aborted");
2907
2694
  process.exit(1);
2908
2695
  }
2909
2696
 
2910
- const claudeConfigDir = getClaudeConfigDir();
2911
- const pluginsDir = join(claudeConfigDir, "plugins");
2912
- const pluginPath = join(pluginsDir, CLAUDE_PLUGIN_NAME);
2697
+ // Check for existing .hive or .beads directories
2698
+ const hiveDir = existsSync(".hive");
2699
+ const beadsDir = existsSync(".beads");
2700
+
2701
+ if (hiveDir) {
2702
+ p.log.warn("Hive already initialized in this project (.hive/ exists)");
2913
2703
 
2914
- mkdirWithStatus(pluginsDir);
2704
+ const reinit = await p.confirm({
2705
+ message: "Continue anyway?",
2706
+ initialValue: false,
2707
+ });
2915
2708
 
2916
- if (existsSync(pluginPath)) {
2917
- const stat = lstatSync(pluginPath);
2918
- if (!stat.isSymbolicLink()) {
2919
- p.log.error(`Existing path is not a symlink: ${pluginPath}`);
2709
+ if (p.isCancel(reinit) || !reinit) {
2920
2710
  p.outro("Aborted");
2921
- process.exit(1);
2922
- }
2923
-
2924
- const target = readlinkSync(pluginPath);
2925
- const resolved = resolve(dirname(pluginPath), target);
2926
- if (resolved === pluginRoot) {
2927
- p.log.success("Claude plugin already linked");
2928
- p.outro("Done");
2929
- return;
2711
+ process.exit(0);
2930
2712
  }
2931
-
2932
- rmSync(pluginPath, { force: true });
2933
- }
2934
-
2935
- symlinkSync(pluginRoot, pluginPath);
2936
- p.log.success(`Linked ${CLAUDE_PLUGIN_NAME} → ${pluginRoot}`);
2937
- p.log.message(dim(" Claude Code will auto-launch MCP from .mcp.json"));
2938
- p.outro("Claude plugin installed");
2939
- }
2940
-
2941
- /**
2942
- * Remove the Claude plugin symlink.
2943
- */
2944
- async function claudeUninstall() {
2945
- p.intro("swarm claude uninstall");
2946
- const pluginPath = join(getClaudeConfigDir(), "plugins", CLAUDE_PLUGIN_NAME);
2947
-
2948
- if (!existsSync(pluginPath)) {
2949
- p.log.warn("Claude plugin symlink not found");
2950
- p.outro("Nothing to remove");
2951
- return;
2952
- }
2953
-
2954
- rmSync(pluginPath, { recursive: true, force: true });
2955
- p.log.success(`Removed ${pluginPath}`);
2956
- p.outro("Claude plugin uninstalled");
2957
- }
2958
-
2959
- /**
2960
- * Create project-local Claude Code config from bundled plugin assets.
2961
- */
2962
- async function claudeInit() {
2963
- p.intro("swarm claude init");
2964
- const projectPath = process.cwd();
2965
- const pluginRoot = getClaudePluginRoot();
2966
-
2967
- if (!existsSync(pluginRoot)) {
2968
- p.log.error(`Claude plugin not found at ${pluginRoot}`);
2969
- p.outro("Aborted");
2970
- process.exit(1);
2971
- }
2972
-
2973
- const projectClaudeDir = join(projectPath, ".claude");
2974
- const commandDir = join(projectClaudeDir, "commands");
2975
- const agentDir = join(projectClaudeDir, "agents");
2976
- const skillsDir = join(projectClaudeDir, "skills");
2977
- const hooksDir = join(projectClaudeDir, "hooks");
2978
-
2979
- for (const dir of [projectClaudeDir, commandDir, agentDir, skillsDir, hooksDir]) {
2980
- mkdirWithStatus(dir);
2981
- }
2982
-
2983
- const copyMap = [
2984
- { src: join(pluginRoot, "commands"), dest: commandDir, label: "Commands" },
2985
- { src: join(pluginRoot, "agents"), dest: agentDir, label: "Agents" },
2986
- { src: join(pluginRoot, "skills"), dest: skillsDir, label: "Skills" },
2987
- { src: join(pluginRoot, "hooks"), dest: hooksDir, label: "Hooks" },
2988
- ];
2989
-
2990
- for (const { src, dest, label } of copyMap) {
2991
- if (existsSync(src)) {
2992
- copyDirRecursiveSync(src, dest);
2993
- p.log.success(`${label}: ${dest}`);
2994
- }
2995
- }
2996
-
2997
- const mcpSourcePath = join(pluginRoot, ".mcp.json");
2998
- if (existsSync(mcpSourcePath)) {
2999
- const mcpDestPath = join(projectClaudeDir, ".mcp.json");
3000
- const content = readFileSync(mcpSourcePath, "utf-8");
3001
- writeFileWithStatus(mcpDestPath, content, "MCP config");
3002
- }
3003
-
3004
- const lspSourcePath = join(pluginRoot, ".lsp.json");
3005
- if (existsSync(lspSourcePath)) {
3006
- const lspDestPath = join(projectClaudeDir, ".lsp.json");
3007
- const content = readFileSync(lspSourcePath, "utf-8");
3008
- writeFileWithStatus(lspDestPath, content, "LSP config");
3009
- }
3010
-
3011
- p.log.message(dim(" Uses ${CLAUDE_PLUGIN_ROOT} in MCP config"));
3012
- p.outro("Claude project config ready");
3013
- }
3014
-
3015
- /**
3016
- * Claude hook: start a session and emit comprehensive context for Claude Code.
3017
- *
3018
- * Gathers:
3019
- * - Session info + previous handoff notes
3020
- * - In-progress cells (work that was mid-flight)
3021
- * - Open epics with their children
3022
- * - Recent activity summary
3023
- */
3024
- async function claudeSessionStart() {
3025
- try {
3026
- const input = await readHookInput<ClaudeHookInput>();
3027
- const projectPath = resolveClaudeProjectPath(input);
3028
- const swarmMail = await getSwarmMailLibSQL(projectPath);
3029
- const db = await swarmMail.getDatabase(projectPath);
3030
- const adapter = createHiveAdapter(db, projectPath);
3031
-
3032
- await adapter.runMigrations();
3033
-
3034
- const session = await adapter.startSession(projectPath, {});
3035
- const contextLines: string[] = [];
3036
-
3037
- // Session basics
3038
- contextLines.push(`## Swarm Session: ${session.id}`);
3039
- contextLines.push(`Source: ${(input as { source?: string }).source || "startup"}`);
3040
- contextLines.push("");
3041
-
3042
- // Previous handoff notes (critical for continuation)
3043
- if (session.previous_handoff_notes) {
3044
- contextLines.push("## Previous Handoff Notes");
3045
- contextLines.push(session.previous_handoff_notes);
3046
- contextLines.push("");
3047
- }
3048
-
3049
- // Active cell from previous session
3050
- if (session.active_cell_id) {
3051
- const activeCell = await adapter.getCell(projectPath, session.active_cell_id);
3052
- if (activeCell) {
3053
- contextLines.push("## Active Cell (from previous session)");
3054
- contextLines.push(`- **${activeCell.id}**: ${activeCell.title}`);
3055
- contextLines.push(` Status: ${activeCell.status}, Priority: ${activeCell.priority}`);
3056
- if (activeCell.description) {
3057
- contextLines.push(` ${activeCell.description.slice(0, 200)}...`);
3058
- }
3059
- contextLines.push("");
3060
- }
3061
- }
3062
-
3063
- // In-progress cells (work that was mid-flight)
3064
- const inProgressCells = await adapter.getInProgressCells(projectPath);
3065
- if (inProgressCells.length > 0) {
3066
- contextLines.push("## In-Progress Work");
3067
- for (const cell of inProgressCells.slice(0, 5)) {
3068
- contextLines.push(`- **${cell.id}**: ${cell.title} (${cell.type}, P${cell.priority})`);
3069
- if (cell.description) {
3070
- contextLines.push(` ${cell.description.slice(0, 150)}`);
3071
- }
3072
- }
3073
- if (inProgressCells.length > 5) {
3074
- contextLines.push(` ... and ${inProgressCells.length - 5} more`);
3075
- }
3076
- contextLines.push("");
3077
- }
3078
-
3079
- // Open epics (high-level context)
3080
- const openEpics = await adapter.queryCells(projectPath, {
3081
- status: "open",
3082
- type: "epic",
3083
- limit: 3
3084
- });
3085
- if (openEpics.length > 0) {
3086
- contextLines.push("## Open Epics");
3087
- for (const epic of openEpics) {
3088
- const children = await adapter.getEpicChildren(projectPath, epic.id);
3089
- const openChildren = children.filter(c => c.status !== "closed");
3090
- contextLines.push(`- **${epic.id}**: ${epic.title}`);
3091
- contextLines.push(` ${openChildren.length}/${children.length} subtasks remaining`);
3092
- }
3093
- contextLines.push("");
3094
- }
3095
-
3096
- // Stats summary
3097
- const stats = await adapter.getCellsStats(projectPath);
3098
- contextLines.push("## Hive Stats");
3099
- contextLines.push(`Open: ${stats.open} | In Progress: ${stats.in_progress} | Blocked: ${stats.blocked} | Closed: ${stats.closed}`);
3100
-
3101
- writeClaudeHookOutput("SessionStart", contextLines.join("\n"));
3102
- } catch (error) {
3103
- console.error(error instanceof Error ? error.message : String(error));
3104
- }
3105
- }
3106
-
3107
- /**
3108
- * Claude hook: provide active context on prompt submission.
3109
- *
3110
- * Lightweight hook that runs on every prompt. Provides:
3111
- * - Active session info
3112
- * - Current in-progress cell (if any)
3113
- * - Quick stats for awareness
3114
- *
3115
- * Output is suppressed from verbose mode to avoid noise.
3116
- */
3117
- async function claudeUserPrompt() {
3118
- try {
3119
- const input = await readHookInput<ClaudeHookInput>();
3120
- const projectPath = resolveClaudeProjectPath(input);
3121
- const swarmMail = await getSwarmMailLibSQL(projectPath);
3122
- const db = await swarmMail.getDatabase(projectPath);
3123
- const adapter = createHiveAdapter(db, projectPath);
3124
-
3125
- await adapter.runMigrations();
3126
-
3127
- const session = await adapter.getCurrentSession(projectPath);
3128
- if (!session) return;
3129
-
3130
- const contextLines: string[] = [];
3131
-
3132
- // Always inject current timestamp for temporal awareness
3133
- const now = new Date();
3134
- const timestamp = now.toLocaleString("en-US", {
3135
- weekday: "short",
3136
- year: "numeric",
3137
- month: "short",
3138
- day: "numeric",
3139
- hour: "2-digit",
3140
- minute: "2-digit",
3141
- timeZoneName: "short",
3142
- });
3143
- contextLines.push(`**Now**: ${timestamp}`);
3144
-
3145
- // Semantic memory recall - search hivemind for relevant context
3146
- if (input.prompt && input.prompt.trim().length > 10) {
3147
- try {
3148
- const memoryAdapter = await createMemoryAdapter(db);
3149
- const findResult = await memoryAdapter.find({ query: input.prompt, limit: 3 });
3150
- const memories = findResult.results;
3151
-
3152
- if (memories && memories.length > 0) {
3153
- // Only include high-confidence matches (score > 0.5)
3154
- const relevant = memories.filter((m: { score?: number }) => (m.score ?? 0) > 0.5);
3155
- if (relevant.length > 0) {
3156
- const memorySnippets = relevant
3157
- .slice(0, 2) // Max 2 memories to keep context light
3158
- .map((m: { content?: string; information?: string }) => {
3159
- const content = m.content || m.information || "";
3160
- return content.length > 200 ? content.slice(0, 200) + "..." : content;
3161
- })
3162
- .join(" | ");
3163
- contextLines.push(`**Recall**: ${memorySnippets}`);
3164
- }
3165
- }
3166
- } catch {
3167
- // Memory recall is optional - don't fail the hook
3168
- }
3169
- }
3170
-
3171
- // Current active cell (most relevant context)
3172
- if (session.active_cell_id) {
3173
- const activeCell = await adapter.getCell(projectPath, session.active_cell_id);
3174
- if (activeCell && activeCell.status !== "closed") {
3175
- contextLines.push(`**Active**: ${activeCell.id} - ${activeCell.title}`);
3176
- }
3177
- }
3178
-
3179
- // Quick in-progress count for awareness
3180
- const inProgress = await adapter.getInProgressCells(projectPath);
3181
- if (inProgress.length > 0) {
3182
- const titles = inProgress.slice(0, 3).map(c => c.title.slice(0, 40)).join(", ");
3183
- contextLines.push(`**WIP (${inProgress.length})**: ${titles}${inProgress.length > 3 ? "..." : ""}`);
3184
- }
3185
-
3186
- // Only output if there's meaningful context
3187
- if (contextLines.length > 0) {
3188
- writeClaudeHookOutput("UserPromptSubmit", contextLines.join(" | "), { suppressOutput: true });
3189
- }
3190
- } catch (error) {
3191
- console.error(error instanceof Error ? error.message : String(error));
3192
- }
3193
- }
3194
-
3195
- /**
3196
- * Claude hook: capture comprehensive state before compaction.
3197
- *
3198
- * This is the CRITICAL hook for continuation. It captures:
3199
- * - All in-progress work with details
3200
- * - Active epic state and progress
3201
- * - Any pending/blocked cells
3202
- * - Reserved files
3203
- * - Session context
3204
- *
3205
- * The output becomes part of the compacted summary, enabling
3206
- * Claude to continue work seamlessly after context window fills.
3207
- */
3208
- async function claudePreCompact() {
3209
- try {
3210
- const input = await readHookInput<ClaudeHookInput & { trigger?: string; custom_instructions?: string }>();
3211
- const projectPath = resolveClaudeProjectPath(input);
3212
- const swarmMail = await getSwarmMailLibSQL(projectPath);
3213
- const db = await swarmMail.getDatabase(projectPath);
3214
- const adapter = createHiveAdapter(db, projectPath);
3215
-
3216
- await adapter.runMigrations();
3217
-
3218
- const session = await adapter.getCurrentSession(projectPath);
3219
- const contextLines: string[] = [];
3220
-
3221
- contextLines.push("# Swarm State Snapshot (Pre-Compaction)");
3222
- contextLines.push(`Trigger: ${input.trigger || "auto"}`);
3223
- if (input.custom_instructions) {
3224
- contextLines.push(`Instructions: ${input.custom_instructions}`);
3225
- }
3226
- contextLines.push("");
3227
-
3228
- // Session info
3229
- if (session) {
3230
- contextLines.push("## Session");
3231
- contextLines.push(`ID: ${session.id}`);
3232
- if (session.active_cell_id) {
3233
- contextLines.push(`Active cell: ${session.active_cell_id}`);
3234
- }
3235
- }
3236
- contextLines.push("");
3237
-
3238
- // In-progress work (CRITICAL for continuation)
3239
- const inProgressCells = await adapter.getInProgressCells(projectPath);
3240
- if (inProgressCells.length > 0) {
3241
- contextLines.push("## In-Progress Work (CONTINUE THESE)");
3242
- for (const cell of inProgressCells) {
3243
- contextLines.push(`### ${cell.id}: ${cell.title}`);
3244
- contextLines.push(`Type: ${cell.type} | Priority: ${cell.priority} | Status: ${cell.status}`);
3245
- if (cell.parent_id) {
3246
- contextLines.push(`Parent: ${cell.parent_id}`);
3247
- }
3248
- if (cell.description) {
3249
- contextLines.push(`Description: ${cell.description}`);
3250
- }
3251
- // Get comments for context on progress
3252
- const comments = await adapter.getComments(projectPath, cell.id);
3253
- if (comments.length > 0) {
3254
- const recent = comments.slice(-3);
3255
- contextLines.push("Recent notes:");
3256
- for (const comment of recent) {
3257
- contextLines.push(`- ${comment.body.slice(0, 200)}`);
3258
- }
3259
- }
3260
- contextLines.push("");
3261
- }
3262
- }
3263
-
3264
- // Open epics with children status
3265
- const openEpics = await adapter.queryCells(projectPath, {
3266
- status: ["open", "in_progress"],
3267
- type: "epic",
3268
- limit: 5
3269
- });
3270
- if (openEpics.length > 0) {
3271
- contextLines.push("## Active Epics");
3272
- for (const epic of openEpics) {
3273
- const children = await adapter.getEpicChildren(projectPath, epic.id);
3274
- const completed = children.filter(c => c.status === "closed").length;
3275
- const inProgress = children.filter(c => c.status === "in_progress");
3276
- const open = children.filter(c => c.status === "open");
3277
-
3278
- contextLines.push(`### ${epic.id}: ${epic.title}`);
3279
- contextLines.push(`Progress: ${completed}/${children.length} completed`);
3280
-
3281
- if (inProgress.length > 0) {
3282
- contextLines.push("In progress:");
3283
- for (const c of inProgress) {
3284
- contextLines.push(`- ${c.id}: ${c.title}`);
3285
- }
3286
- }
3287
- if (open.length > 0 && open.length <= 5) {
3288
- contextLines.push("Remaining:");
3289
- for (const c of open) {
3290
- contextLines.push(`- ${c.id}: ${c.title}`);
3291
- }
3292
- } else if (open.length > 5) {
3293
- contextLines.push(`Remaining: ${open.length} tasks`);
3294
- }
3295
- contextLines.push("");
3296
- }
3297
- }
3298
-
3299
- // Blocked cells (so Claude knows what's waiting)
3300
- const blockedCells = await adapter.getBlockedCells(projectPath);
3301
- if (blockedCells.length > 0) {
3302
- contextLines.push("## Blocked Work");
3303
- for (const { cell, blockers } of blockedCells.slice(0, 5)) {
3304
- contextLines.push(`- ${cell.id}: ${cell.title}`);
3305
- contextLines.push(` Blocked by: ${blockers.join(", ")}`);
3306
- }
3307
- contextLines.push("");
3308
- }
3309
-
3310
- // Ready cells (next work available)
3311
- const readyCell = await adapter.getNextReadyCell(projectPath);
3312
- if (readyCell) {
3313
- contextLines.push("## Next Ready Task");
3314
- contextLines.push(`${readyCell.id}: ${readyCell.title} (P${readyCell.priority})`);
3315
- contextLines.push("");
3316
- }
3317
-
3318
- // Stats
3319
- const stats = await adapter.getCellsStats(projectPath);
3320
- contextLines.push("## Hive Stats");
3321
- contextLines.push(`Open: ${stats.open} | In Progress: ${stats.in_progress} | Blocked: ${stats.blocked} | Closed: ${stats.closed}`);
3322
-
3323
- writeClaudeHookOutput("PreCompact", contextLines.join("\n"));
3324
- } catch (error) {
3325
- console.error(error instanceof Error ? error.message : String(error));
3326
- }
3327
- }
3328
-
3329
- /**
3330
- * Claude hook: end the active session.
3331
- */
3332
- async function claudeSessionEnd() {
3333
- try {
3334
- const input = await readHookInput<ClaudeHookInput>();
3335
- const projectPath = resolveClaudeProjectPath(input);
3336
- const swarmMail = await getSwarmMailLibSQL(projectPath);
3337
- const db = await swarmMail.getDatabase(projectPath);
3338
- const adapter = createHiveAdapter(db, projectPath);
3339
-
3340
- await adapter.runMigrations();
3341
-
3342
- const currentSession = await adapter.getCurrentSession(projectPath);
3343
- if (!currentSession) return;
3344
-
3345
- await adapter.endSession(projectPath, currentSession.id, {});
3346
- } catch (error) {
3347
- console.error(error instanceof Error ? error.message : String(error));
3348
- }
3349
- }
3350
-
3351
- /**
3352
- * Claude hook: pre-edit reminder for workers
3353
- *
3354
- * Runs BEFORE Edit/Write tool calls. Reminds worker to query hivemind first.
3355
- * This is a gentle nudge, not a blocker.
3356
- */
3357
- async function claudePreEdit() {
3358
- try {
3359
- const input = await readHookInput<ClaudeHookInput & { tool_name?: string; tool_input?: Record<string, unknown> }>();
3360
-
3361
- // Only inject reminder if this looks like a worker context
3362
- // (workers have initialized via swarmmail_init)
3363
- const projectPath = resolveClaudeProjectPath(input);
3364
-
3365
- // Check if we've seen hivemind_find in this session
3366
- // For now, we always remind - tracking will come later
3367
- const contextLines: string[] = [];
3368
-
3369
- contextLines.push("⚠️ **Before this edit**: Did you run `hivemind_find` to check for existing solutions?");
3370
- contextLines.push("If you haven't queried hivemind yet, consider doing so to avoid re-solving problems.");
3371
-
3372
- writeClaudeHookOutput("PreToolUse:Edit", contextLines.join("\n"), { suppressOutput: true });
3373
- } catch (error) {
3374
- // Non-fatal - don't block the edit
3375
- console.error(error instanceof Error ? error.message : String(error));
3376
- }
3377
- }
3378
-
3379
- /**
3380
- * Claude hook: pre-complete check for workers
3381
- *
3382
- * Runs BEFORE swarm_complete. Checks compliance with mandatory steps
3383
- * and warns if any were skipped.
3384
- */
3385
- async function claudePreComplete() {
3386
- try {
3387
- const input = await readHookInput<ClaudeHookInput & { tool_input?: Record<string, unknown> }>();
3388
- const projectPath = resolveClaudeProjectPath(input);
3389
- const contextLines: string[] = [];
3390
-
3391
- // Check session tracking markers
3392
- const trackingDir = join(projectPath, ".claude", ".worker-tracking");
3393
- const sessionId = (input as { session_id?: string }).session_id || "";
3394
- const sessionDir = join(trackingDir, sessionId.slice(0, 8));
3395
-
3396
- const mandatoryTools = [
3397
- { name: "swarmmail_init", label: "Initialize coordination" },
3398
- { name: "hivemind_find", label: "Query past learnings" },
3399
- ];
3400
- const recommendedTools = [
3401
- { name: "skills_use", label: "Load relevant skills" },
3402
- { name: "hivemind_store", label: "Store new learnings" },
3403
- ];
3404
-
3405
- const missing: string[] = [];
3406
- const skippedRecommended: string[] = [];
3407
-
3408
- for (const tool of mandatoryTools) {
3409
- const markerPath = join(sessionDir, `${tool.name}.marker`);
3410
- if (!existsSync(markerPath)) {
3411
- missing.push(tool.label);
3412
- }
3413
- }
3414
-
3415
- for (const tool of recommendedTools) {
3416
- const markerPath = join(sessionDir, `${tool.name}.marker`);
3417
- if (!existsSync(markerPath)) {
3418
- skippedRecommended.push(tool.label);
3419
- }
3420
- }
3421
-
3422
- if (missing.length > 0) {
3423
- contextLines.push("⚠️ **MANDATORY STEPS SKIPPED:**");
3424
- for (const step of missing) {
3425
- contextLines.push(` ❌ ${step}`);
3426
- }
3427
- contextLines.push("");
3428
- contextLines.push("These steps are critical for swarm coordination.");
3429
- contextLines.push("Consider running `hivemind_find` before future completions.");
3430
- }
3431
-
3432
- if (skippedRecommended.length > 0 && missing.length === 0) {
3433
- contextLines.push("📝 **Recommended steps not observed:**");
3434
- for (const step of skippedRecommended) {
3435
- contextLines.push(` - ${step}`);
3436
- }
3437
- }
3438
-
3439
- if (missing.length === 0 && skippedRecommended.length === 0) {
3440
- contextLines.push("✅ **All mandatory and recommended steps completed!**");
3441
- }
3442
-
3443
- // Emit compliance event for analytics
3444
- try {
3445
- const swarmMail = await getSwarmMailLibSQL(projectPath);
3446
- const db = await swarmMail.getDatabase(projectPath);
3447
-
3448
- await db.execute({
3449
- sql: `INSERT INTO swarm_events (event_type, project_path, agent_name, epic_id, bead_id, payload, created_at)
3450
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
3451
- args: [
3452
- "worker_compliance",
3453
- projectPath,
3454
- "worker",
3455
- "",
3456
- "",
3457
- JSON.stringify({
3458
- session_id: sessionId,
3459
- mandatory_skipped: missing.length,
3460
- recommended_skipped: skippedRecommended.length,
3461
- score: Math.round(((mandatoryTools.length - missing.length) / mandatoryTools.length) * 100),
3462
- }),
3463
- ],
3464
- });
3465
- } catch {
3466
- // Non-fatal
3467
- }
3468
-
3469
- if (contextLines.length > 0) {
3470
- writeClaudeHookOutput("PreToolUse:swarm_complete", contextLines.join("\n"), { suppressOutput: missing.length === 0 });
3471
- }
3472
- } catch (error) {
3473
- console.error(error instanceof Error ? error.message : String(error));
3474
- }
3475
- }
3476
-
3477
- /**
3478
- * Claude hook: post-complete reminder for workers
3479
- *
3480
- * Runs AFTER swarm_complete. Reminds to store learnings if any were discovered.
3481
- */
3482
- async function claudePostComplete() {
3483
- try {
3484
- const input = await readHookInput<ClaudeHookInput & { tool_output?: string }>();
3485
- const contextLines: string[] = [];
3486
-
3487
- contextLines.push("✅ Task completed. If you discovered anything valuable during this work:");
3488
- contextLines.push("```");
3489
- contextLines.push("hivemind_store(");
3490
- contextLines.push(' information="<what you learned, WHY it matters>",');
3491
- contextLines.push(' tags="<domain, pattern-type>"');
3492
- contextLines.push(")");
3493
- contextLines.push("```");
3494
- contextLines.push("**The swarm's collective intelligence grows when agents share learnings.**");
3495
-
3496
- writeClaudeHookOutput("PostToolUse:swarm_complete", contextLines.join("\n"), { suppressOutput: true });
3497
- } catch (error) {
3498
- console.error(error instanceof Error ? error.message : String(error));
3499
- }
3500
- }
3501
-
3502
- /**
3503
- * Track when a mandatory tool is called.
3504
- *
3505
- * Creates a session-specific marker file that records tool usage.
3506
- * These markers are checked at swarm_complete to calculate compliance.
3507
- */
3508
- async function claudeTrackTool(toolName: string) {
3509
- if (!toolName) return;
3510
-
3511
- try {
3512
- const input = await readHookInput<ClaudeHookInput>();
3513
- const projectPath = resolveClaudeProjectPath(input);
3514
-
3515
- // Get or create session tracking directory
3516
- const trackingDir = join(projectPath, ".claude", ".worker-tracking");
3517
- const sessionId = (input as { session_id?: string }).session_id || `unknown-${Date.now()}`;
3518
- const sessionDir = join(trackingDir, sessionId.slice(0, 8)); // Use first 8 chars of session ID
3519
-
3520
- if (!existsSync(sessionDir)) {
3521
- mkdirSync(sessionDir, { recursive: true });
3522
- }
3523
-
3524
- // Write marker file for this tool
3525
- const markerPath = join(sessionDir, `${toolName}.marker`);
3526
- writeFileSync(markerPath, new Date().toISOString());
3527
-
3528
- // Also emit an event for long-term analytics (non-blocking)
3529
- try {
3530
- const swarmMail = await getSwarmMailLibSQL(projectPath);
3531
- const db = await swarmMail.getDatabase(projectPath);
3532
-
3533
- await db.execute({
3534
- sql: `INSERT INTO swarm_events (event_type, project_path, agent_name, epic_id, bead_id, payload, created_at)
3535
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
3536
- args: [
3537
- "worker_tool_call",
3538
- projectPath,
3539
- "worker", // We don't have agent name in hook context
3540
- "",
3541
- "",
3542
- JSON.stringify({ tool: toolName, session_id: sessionId }),
3543
- ],
3544
- });
3545
- } catch {
3546
- // Non-fatal - tracking is best-effort
3547
- }
3548
- } catch (error) {
3549
- // Silent failure - don't interrupt the tool call
3550
- console.error(`[track-tool] ${error instanceof Error ? error.message : String(error)}`);
3551
- }
3552
- }
3553
-
3554
- /**
3555
- * Check worker compliance - which mandatory tools were called.
3556
- *
3557
- * Returns compliance data based on session tracking markers.
3558
- */
3559
- async function claudeCompliance() {
3560
- try {
3561
- const input = await readHookInput<ClaudeHookInput>();
3562
- const projectPath = resolveClaudeProjectPath(input);
3563
-
3564
- const trackingDir = join(projectPath, ".claude", ".worker-tracking");
3565
- const sessionId = (input as { session_id?: string }).session_id || "";
3566
- const sessionDir = join(trackingDir, sessionId.slice(0, 8));
3567
-
3568
- const mandatoryTools = ["swarmmail_init", "hivemind_find", "skills_use"];
3569
- const recommendedTools = ["hivemind_store"];
3570
-
3571
- const compliance: Record<string, boolean> = {};
3572
-
3573
- for (const tool of [...mandatoryTools, ...recommendedTools]) {
3574
- const markerPath = join(sessionDir, `${tool}.marker`);
3575
- compliance[tool] = existsSync(markerPath);
3576
- }
3577
-
3578
- const mandatoryCount = mandatoryTools.filter(t => compliance[t]).length;
3579
- const score = Math.round((mandatoryCount / mandatoryTools.length) * 100);
3580
-
3581
- console.log(JSON.stringify({
3582
- session_id: sessionId,
3583
- compliance,
3584
- mandatory_score: score,
3585
- mandatory_met: mandatoryCount,
3586
- mandatory_total: mandatoryTools.length,
3587
- stored_learnings: compliance.hivemind_store,
3588
- }));
3589
- } catch (error) {
3590
- console.error(error instanceof Error ? error.message : String(error));
3591
- }
3592
- }
3593
-
3594
- /**
3595
- * Query worker compliance stats across all sessions.
3596
- */
3597
- async function showWorkerCompliance() {
3598
- const projectPath = process.cwd();
3599
-
3600
- try {
3601
- // Use shared executeQuery (handles DB connection internally)
3602
- const toolUsageSql = `SELECT
3603
- json_extract(payload, '$.tool') as tool,
3604
- COUNT(*) as count,
3605
- COUNT(DISTINCT json_extract(payload, '$.session_id')) as sessions
3606
- FROM swarm_events
3607
- WHERE event_type = 'worker_tool_call'
3608
- AND created_at > datetime('now', '-7 days')
3609
- GROUP BY tool
3610
- ORDER BY count DESC`;
3611
-
3612
- const toolResult = await executeQueryCLI(projectPath, toolUsageSql);
3613
- const rows = toolResult.rows;
3614
-
3615
- console.log(yellow(BANNER));
3616
- console.log(cyan("\n📊 Worker Tool Usage (Last 7 Days)\n"));
3617
-
3618
- if (rows.length === 0) {
3619
- console.log(dim("No worker tool usage data found."));
3620
- console.log(dim("Data is collected when workers run with the Claude Code plugin."));
3621
- return;
3622
- }
3623
-
3624
- console.log(dim("Tool Calls Sessions"));
3625
- console.log(dim("─".repeat(45)));
3626
-
3627
- for (const row of rows) {
3628
- const tool = String(row.tool).padEnd(22);
3629
- const count = String(row.count).padStart(6);
3630
- const sessions = String(row.sessions).padStart(8);
3631
- console.log(`${tool} ${count} ${sessions}`);
3632
- }
3633
-
3634
- // Calculate compliance rate
3635
- const hivemindFinds = rows.find(r => r.tool === "hivemind_find");
3636
- const completesSql = `SELECT COUNT(*) as count FROM swarm_events
3637
- WHERE event_type = 'worker_completed'
3638
- AND created_at > datetime('now', '-7 days')`;
3639
- const completesResult = await executeQueryCLI(projectPath, completesSql);
3640
-
3641
- const completes = Number(completesResult.rows[0]?.count || 0);
3642
- const finds = Number(hivemindFinds?.count || 0);
3643
-
3644
- if (completes > 0) {
3645
- const rate = Math.round((Math.min(finds, completes) / completes) * 100);
3646
- console.log(dim("\n─".repeat(45)));
3647
- console.log(`\n${green("Hivemind compliance rate:")} ${rate}% (${finds} queries / ${completes} completions)`);
3648
- }
3649
-
3650
- } catch (error) {
3651
- const msg = error instanceof Error ? error.message : String(error);
3652
- if (msg.includes("no such table")) {
3653
- console.log(yellow(BANNER));
3654
- console.log(cyan("\n📊 Worker Tool Usage\n"));
3655
- console.log(dim("No tracking data yet."));
3656
- console.log(dim("Compliance tracking starts when workers run with the Claude Code plugin hooks."));
3657
- console.log(dim("\nThe swarm_events table will be created on first tracked tool call."));
3658
- } else {
3659
- console.error("Error fetching compliance data:", msg);
3660
- }
3661
- }
3662
- }
3663
-
3664
- async function init() {
3665
- p.intro("swarm init v" + VERSION);
3666
-
3667
- const projectPath = process.cwd();
3668
-
3669
- const gitDir = existsSync(".git");
3670
- if (!gitDir) {
3671
- p.log.error("Not in a git repository");
3672
- p.log.message("Run 'git init' first, or cd to a git repo");
3673
- p.outro("Aborted");
3674
- process.exit(1);
3675
- }
3676
-
3677
- // Check for existing .hive or .beads directories
3678
- const hiveDir = existsSync(".hive");
3679
- const beadsDir = existsSync(".beads");
3680
-
3681
- if (hiveDir) {
3682
- p.log.warn("Hive already initialized in this project (.hive/ exists)");
3683
-
3684
- const reinit = await p.confirm({
3685
- message: "Continue anyway?",
3686
- initialValue: false,
3687
- });
3688
-
3689
- if (p.isCancel(reinit) || !reinit) {
3690
- p.outro("Aborted");
3691
- process.exit(0);
3692
- }
3693
- } else if (beadsDir) {
3694
- // Offer migration from .beads to .hive
3695
- p.log.warn("Found legacy .beads/ directory");
3696
-
3697
- const migrate = await p.confirm({
3698
- message: "Migrate .beads/ to .hive/?",
3699
- initialValue: true,
3700
- });
2713
+ } else if (beadsDir) {
2714
+ // Offer migration from .beads to .hive
2715
+ p.log.warn("Found legacy .beads/ directory");
2716
+
2717
+ const migrate = await p.confirm({
2718
+ message: "Migrate .beads/ to .hive/?",
2719
+ initialValue: true,
2720
+ });
3701
2721
 
3702
2722
  if (!p.isCancel(migrate) && migrate) {
3703
2723
  const s = p.spinner();
@@ -3829,7 +2849,7 @@ function config() {
3829
2849
  const plannerAgentPath = join(agentDir, "swarm-planner.md");
3830
2850
  const workerAgentPath = join(agentDir, "swarm-worker.md");
3831
2851
  const researcherAgentPath = join(agentDir, "swarm-researcher.md");
3832
- const globalSkillsPath = join(configDir, "skill");
2852
+ const globalSkillsPath = join(configDir, "skills");
3833
2853
 
3834
2854
  console.log(yellow(BANNER));
3835
2855
  console.log(dim(" " + TAGLINE + " v" + VERSION));
@@ -4008,16 +3028,16 @@ async function query() {
4008
3028
  const projectPath = process.cwd();
4009
3029
 
4010
3030
  try {
4011
- let result: QueryResult;
3031
+ let rows: any[];
4012
3032
 
4013
3033
  if (parsed.preset) {
4014
3034
  // Execute preset query
4015
3035
  p.log.step(`Executing preset: ${parsed.preset}`);
4016
- result = await executePreset(projectPath, parsed.preset);
3036
+ rows = await executePreset(projectPath, parsed.preset);
4017
3037
  } else if (parsed.query) {
4018
3038
  // Execute custom SQL
4019
3039
  p.log.step("Executing custom SQL");
4020
- result = await executeQueryCLI(projectPath, parsed.query);
3040
+ rows = await executeQuery(projectPath, parsed.query);
4021
3041
  } else {
4022
3042
  p.log.error("No query specified. Use --sql or --preset");
4023
3043
  p.outro("Aborted");
@@ -4028,14 +3048,14 @@ async function query() {
4028
3048
  let output: string;
4029
3049
  switch (parsed.format) {
4030
3050
  case "csv":
4031
- output = formatAsCSV(result);
3051
+ output = formatAsCSV(rows);
4032
3052
  break;
4033
3053
  case "json":
4034
- output = formatAsJSON(result);
3054
+ output = formatAsJSON(rows);
4035
3055
  break;
4036
3056
  case "table":
4037
3057
  default:
4038
- output = formatAsTable(result);
3058
+ output = formatAsTable(rows);
4039
3059
  break;
4040
3060
  }
4041
3061
 
@@ -4043,7 +3063,7 @@ async function query() {
4043
3063
  console.log(output);
4044
3064
  console.log();
4045
3065
 
4046
- p.outro(`Found ${result.rowCount} result(s)`);
3066
+ p.outro(`Found ${rows.length} result(s)`);
4047
3067
  } catch (error) {
4048
3068
  p.log.error("Query failed");
4049
3069
  p.log.message(error instanceof Error ? error.message : String(error));
@@ -4372,11 +3392,6 @@ async function exportEvents() {
4372
3392
  }
4373
3393
  }
4374
3394
 
4375
- async function treeCommand() {
4376
- const args = process.argv.slice(3);
4377
- await tree(args);
4378
- }
4379
-
4380
3395
  async function help() {
4381
3396
  console.log(yellow(BANNER));
4382
3397
  console.log(dim(" " + TAGLINE + " v" + VERSION));
@@ -4384,24 +3399,16 @@ async function help() {
4384
3399
  console.log(magenta(" " + getRandomMessage()));
4385
3400
  console.log(`
4386
3401
  ${cyan("Commands:")}
4387
- swarm Status dashboard (default when no subcommand)
4388
- swarm status Status dashboard (same as running swarm with no args)
4389
- --json Machine-readable JSON output
4390
3402
  swarm setup Interactive installer - checks and installs dependencies
4391
3403
  --reinstall, -r Skip prompt, go straight to reinstall
4392
3404
  --yes, -y Non-interactive with defaults (opus/sonnet/haiku)
4393
3405
  swarm doctor Health check - shows status of all dependencies
4394
- --deep Deep DB health checks (integrity, orphans, cycles, zombies)
4395
- --deep --fix Auto-repair fixable issues
4396
- --deep --json Machine-readable JSON output
4397
3406
  swarm init Initialize beads in current project
4398
3407
  swarm config Show paths to generated config files
4399
- swarm claude Claude Code integration (path/install/uninstall/init/hooks)
4400
3408
  swarm agents Update AGENTS.md with skill awareness
4401
3409
  swarm migrate Migrate PGlite database to libSQL
4402
3410
  swarm serve Start SSE server for real-time event streaming (port 4483 - HIVE)
4403
3411
  --port <n> Port to listen on (default: 4483)
4404
- swarm mcp-serve Debug-only MCP server (Claude auto-launches via .mcp.json)
4405
3412
  swarm viz Alias for 'swarm serve' (deprecated, use serve)
4406
3413
  --port <n> Port to listen on (default: 4483)
4407
3414
  swarm cells List or get cells from database (replaces 'swarm tool hive_query')
@@ -4413,12 +3420,8 @@ ${cyan("Commands:")}
4413
3420
  swarm eval Eval-driven development commands
4414
3421
  swarm query SQL analytics with presets (--sql, --preset, --format)
4415
3422
  swarm dashboard Live terminal UI with worker status (--epic, --refresh)
4416
- swarm compliance Worker tool usage compliance stats (hivemind, skills, etc)
4417
3423
  swarm replay Event replay with timing (--speed, --type, --agent, --since, --until)
4418
3424
  swarm export Export events (--format otlp/csv/json, --epic, --output)
4419
- swarm tree Visualize cell hierarchy as ASCII tree (--status, --epic, --json)
4420
- swarm session Manage work sessions with handoff notes (start, end, status, history)
4421
- swarm queue Manage distributed job queue (submit, status, list, worker)
4422
3425
  swarm update Update to latest version
4423
3426
  swarm version Show version and banner
4424
3427
  swarm tool Execute a tool (for plugin wrapper)
@@ -4438,21 +3441,15 @@ ${cyan("Cell Management:")}
4438
3441
  swarm cells --json Raw JSON output (array, no wrapper)
4439
3442
 
4440
3443
  ${cyan("Memory Management (Hivemind):")}
4441
- swarm memory store <info> [options] Store a learning/memory
4442
- --tags <tags> Comma-separated tags
4443
- --extract-entities Extract and link entities (uses local LLM)
4444
- --debug Show debug logging
4445
- swarm memory find <query> [--limit <n>] Search all memories (semantic + FTS)
4446
- swarm memory get <id> Get specific memory by ID
4447
- swarm memory remove <id> Delete outdated/incorrect memory
4448
- swarm memory validate <id> Confirm accuracy (resets 90-day decay)
4449
- swarm memory stats Show database statistics
4450
- swarm memory index Index AI sessions (use hivemind_index tool)
4451
- swarm memory sync Sync to .hive/memories.jsonl (use hivemind_sync tool)
4452
- swarm memory entities [--type <type>] List all entities (optionally filtered by type)
4453
- swarm memory entity <name> Show entity details with taxonomy relationships
4454
- swarm memory taxonomy <entity> [--direction <dir>] Show taxonomy tree (broader|narrower|related)
4455
- swarm memory <command> --json Output JSON for all commands
3444
+ swarm memory store <info> [--tags <tags>] Store a learning/memory
3445
+ swarm memory find <query> [--limit <n>] Search all memories (semantic + FTS)
3446
+ swarm memory get <id> Get specific memory by ID
3447
+ swarm memory remove <id> Delete outdated/incorrect memory
3448
+ swarm memory validate <id> Confirm accuracy (resets 90-day decay)
3449
+ swarm memory stats Show database statistics
3450
+ swarm memory index Index AI sessions (use hivemind_index tool)
3451
+ swarm memory sync Sync to .hive/memories.jsonl (use hivemind_sync tool)
3452
+ swarm memory <command> --json Output JSON for all commands
4456
3453
 
4457
3454
  ${cyan("Log Viewing:")}
4458
3455
  swarm log Tail recent logs (last 50 lines)
@@ -4473,8 +3470,6 @@ ${cyan("Stats & History:")}
4473
3470
  swarm stats Show swarm health metrics powered by swarm-insights (last 7 days)
4474
3471
  swarm stats --since 24h Show stats for custom time period
4475
3472
  swarm stats --regressions Show eval regressions (>10% score drops)
4476
- swarm stats --rejections Show rejection reason analytics
4477
- swarm stats --compaction-prompts Show compaction prompt analytics (visibility into generated prompts)
4478
3473
  swarm stats --json Output as JSON for scripting
4479
3474
  swarm o11y Show observability health dashboard (hook coverage, events, sessions)
4480
3475
  swarm o11y --since 7d Custom time period for event stats (default: 7 days)
@@ -4507,28 +3502,6 @@ ${cyan("Observability Commands:")}
4507
3502
  swarm export --format csv Export as CSV
4508
3503
  swarm export --epic <id> Export specific epic only
4509
3504
  swarm export --output <file> Write to file instead of stdout
4510
- swarm compliance Show worker tool usage compliance stats
4511
- swarm tree Show all cells as tree
4512
- swarm tree --status open Show only open cells
4513
- swarm tree --epic <id> Show specific epic subtree
4514
- swarm tree --json Output as JSON
4515
-
4516
- ${cyan("Session Management (Chainlink-inspired):")}
4517
- swarm session start [--cell <id>] Start new session (shows previous handoff notes)
4518
- swarm session end [--notes "..."] End session with handoff notes for next session
4519
- swarm session status Show current session info
4520
- swarm session history [--limit n] Show session history (default: 10)
4521
-
4522
- ${cyan("Queue Management (BullMQ + Redis):")}
4523
- swarm queue submit <type> [options] Submit job to distributed queue
4524
- --payload '{...}' Job payload (JSON)
4525
- --priority <n> Priority (lower = higher, default: 5)
4526
- --delay <ms> Delay before processing (default: 0)
4527
- swarm queue status <jobId> Show job status
4528
- swarm queue list [--state <state>] List jobs and metrics (waiting|active|completed|failed|delayed)
4529
- swarm queue worker [options] Start worker to process jobs
4530
- --concurrency <n> Concurrent jobs (default: 5)
4531
- --sandbox Enable resource limits via systemd
4532
3505
 
4533
3506
  ${cyan("Usage in OpenCode:")}
4534
3507
  /swarm "Add user authentication with OAuth"
@@ -4536,21 +3509,6 @@ ${cyan("Usage in OpenCode:")}
4536
3509
  @swarm-worker "Execute this specific subtask"
4537
3510
  @swarm-researcher "Research Next.js caching APIs"
4538
3511
 
4539
- ${cyan("Claude Code:")}
4540
- swarm claude path Show bundled Claude plugin path
4541
- swarm claude install Symlink plugin into ~/.claude/plugins
4542
- swarm claude uninstall Remove Claude plugin symlink
4543
- swarm claude init Create project-local .claude config
4544
- swarm claude session-start Hook: session start context
4545
- swarm claude user-prompt Hook: prompt submit context
4546
- swarm claude pre-edit Hook: pre-Edit/Write (hivemind reminder)
4547
- swarm claude pre-complete Hook: pre-swarm_complete (compliance check)
4548
- swarm claude post-complete Hook: post-swarm_complete (store learnings)
4549
- swarm claude track-tool <name> Hook: track mandatory tool usage
4550
- swarm claude compliance Hook: show session compliance data
4551
- swarm claude pre-compact Hook: pre-compaction handler
4552
- swarm claude session-end Hook: session cleanup
4553
-
4554
3512
  ${cyan("Customization:")}
4555
3513
  Edit the generated files to customize behavior:
4556
3514
  ${dim("~/.config/opencode/command/swarm.md")} - /swarm command prompt
@@ -4675,102 +3633,13 @@ async function executeTool(toolName: string, argsJson?: string) {
4675
3633
  }
4676
3634
  }
4677
3635
 
4678
- /**
4679
- * Convert OpenCode plugin schema (Zod standard schema format) to JSON Schema
4680
- */
4681
- function zodToJsonSchema(args: Record<string, unknown> | undefined): {
4682
- type: "object";
4683
- properties: Record<string, unknown>;
4684
- required?: string[];
4685
- } {
4686
- if (!args) return { type: "object", properties: {} };
4687
-
4688
- const properties: Record<string, unknown> = {};
4689
- const required: string[] = [];
4690
-
4691
- for (const [key, schema] of Object.entries(args)) {
4692
- const s = schema as { type?: string; def?: { type?: string; innerType?: unknown; entries?: Record<string, string>; element?: unknown; shape?: Record<string, unknown> } };
4693
- const def = s.def;
4694
-
4695
- // Handle optional wrapper
4696
- const isOptional = def?.type === "optional";
4697
- const innerSchema = isOptional ? (def?.innerType as typeof s) : s;
4698
- const innerDef = (innerSchema as typeof s).def;
4699
-
4700
- if (!isOptional) {
4701
- required.push(key);
4702
- }
4703
-
4704
- // Convert type
4705
- const schemaType = innerDef?.type || (innerSchema as typeof s).type;
4706
- switch (schemaType) {
4707
- case "string":
4708
- properties[key] = { type: "string" };
4709
- break;
4710
- case "number":
4711
- properties[key] = { type: "number" };
4712
- break;
4713
- case "boolean":
4714
- properties[key] = { type: "boolean" };
4715
- break;
4716
- case "enum":
4717
- properties[key] = {
4718
- type: "string",
4719
- enum: Object.keys(innerDef?.entries || {}),
4720
- };
4721
- break;
4722
- case "array": {
4723
- const element = innerDef?.element as typeof s | undefined;
4724
- const elementType = element?.def?.type || element?.type;
4725
- if (elementType === "object") {
4726
- properties[key] = {
4727
- type: "array",
4728
- items: zodToJsonSchema(element?.def?.shape as Record<string, unknown>),
4729
- };
4730
- } else {
4731
- properties[key] = {
4732
- type: "array",
4733
- items: { type: elementType || "string" },
4734
- };
4735
- }
4736
- break;
4737
- }
4738
- case "object":
4739
- properties[key] = zodToJsonSchema(innerDef?.shape as Record<string, unknown>);
4740
- break;
4741
- default:
4742
- properties[key] = { type: "string" }; // fallback
4743
- }
4744
- }
4745
-
4746
- return {
4747
- type: "object",
4748
- properties,
4749
- ...(required.length > 0 ? { required } : {}),
4750
- };
4751
- }
4752
-
4753
3636
  /**
4754
3637
  * List all available tools
4755
3638
  */
4756
- async function listTools(jsonOutput = false) {
3639
+ async function listTools() {
4757
3640
  // Static import at top of file
4758
3641
  const tools = Object.keys(allTools).sort();
4759
3642
 
4760
- // JSON output for MCP server discovery
4761
- if (jsonOutput) {
4762
- const toolList = tools.map((name) => {
4763
- const tool = allTools[name as keyof typeof allTools];
4764
- return {
4765
- name,
4766
- description: tool?.description || `Swarm tool: ${name}`,
4767
- inputSchema: zodToJsonSchema(tool?.args as Record<string, unknown> | undefined),
4768
- };
4769
- });
4770
- console.log(JSON.stringify(toolList));
4771
- return;
4772
- }
4773
-
4774
3643
  console.log(yellow(BANNER));
4775
3644
  console.log(dim(" " + TAGLINE + " v" + VERSION));
4776
3645
  console.log();
@@ -4872,320 +3741,22 @@ Read the file, make the updates, and save it. Create a backup first.`;
4872
3741
  cwd: home,
4873
3742
  });
4874
3743
 
4875
- const exitCode = await proc.exited;
4876
-
4877
- if (exitCode === 0) {
4878
- s.stop("AGENTS.md updated via LLM");
4879
- p.log.success("Hivemind unification complete");
4880
- } else {
4881
- const stderr = await new Response(proc.stderr).text();
4882
- s.stop("LLM update failed");
4883
- p.log.error(stderr || `Exit code: ${exitCode}`);
4884
- }
4885
- } catch (error) {
4886
- s.stop("Failed to run opencode");
4887
- p.log.error(String(error));
4888
- }
4889
-
4890
- p.outro("Done");
4891
- }
4892
-
4893
- // ============================================================================
4894
- // Backup Command - Rolling database backups
4895
- // ============================================================================
4896
-
4897
- const BACKUP_DIR = join(homedir(), ".config", "swarm-tools", "backups");
4898
- const SWARM_DB_PATH = join(homedir(), ".config", "swarm-tools", "swarm.db");
4899
-
4900
- interface BackupInfo {
4901
- path: string;
4902
- timestamp: Date;
4903
- size: number;
4904
- type: "hourly" | "daily" | "weekly" | "manual";
4905
- }
4906
-
4907
- /**
4908
- * Get backup file path for a given type and timestamp
4909
- */
4910
- function getBackupPath(type: "hourly" | "daily" | "weekly" | "manual", date: Date): string {
4911
- const dateStr = date.toISOString().slice(0, 10); // YYYY-MM-DD
4912
- const timeStr = date.toISOString().slice(11, 16).replace(":", ""); // HHMM
4913
- return join(BACKUP_DIR, `swarm-${type}-${dateStr}-${timeStr}.db`);
4914
- }
4915
-
4916
- /**
4917
- * List all existing backups
4918
- */
4919
- function listBackups(): BackupInfo[] {
4920
- if (!existsSync(BACKUP_DIR)) return [];
4921
-
4922
- const files = readdirSync(BACKUP_DIR).filter(f => f.endsWith(".db"));
4923
- return files.map(f => {
4924
- const path = join(BACKUP_DIR, f);
4925
- const stat = statSync(path);
4926
- const type = f.includes("-hourly-") ? "hourly" :
4927
- f.includes("-daily-") ? "daily" :
4928
- f.includes("-weekly-") ? "weekly" : "manual";
4929
- return {
4930
- path,
4931
- timestamp: stat.mtime,
4932
- size: stat.size,
4933
- type: type as BackupInfo["type"],
4934
- };
4935
- }).sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
4936
- }
4937
-
4938
- /**
4939
- * Create a backup of the swarm database
4940
- */
4941
- async function createBackup(type: "hourly" | "daily" | "weekly" | "manual" = "manual"): Promise<string | null> {
4942
- if (!existsSync(SWARM_DB_PATH)) {
4943
- console.error("No swarm.db found at", SWARM_DB_PATH);
4944
- return null;
4945
- }
4946
-
4947
- // Ensure backup directory exists
4948
- if (!existsSync(BACKUP_DIR)) {
4949
- mkdirSync(BACKUP_DIR, { recursive: true });
4950
- }
4951
-
4952
- const now = new Date();
4953
- const backupPath = getBackupPath(type, now);
4954
-
4955
- // Copy the database (use sqlite3 backup command for consistency)
4956
- try {
4957
- execSync(`sqlite3 "${SWARM_DB_PATH}" ".backup '${backupPath}'"`, {
4958
- encoding: "utf-8",
4959
- timeout: 60000,
4960
- });
4961
-
4962
- // Verify backup
4963
- const srcSize = statSync(SWARM_DB_PATH).size;
4964
- const dstSize = statSync(backupPath).size;
4965
-
4966
- if (Math.abs(srcSize - dstSize) > 1024) { // Allow 1KB difference for WAL
4967
- console.error(`Backup size mismatch: source ${srcSize}, backup ${dstSize}`);
4968
- unlinkSync(backupPath);
4969
- return null;
4970
- }
4971
-
4972
- return backupPath;
4973
- } catch (error) {
4974
- console.error("Backup failed:", error);
4975
- return null;
4976
- }
4977
- }
4978
-
4979
- /**
4980
- * Rotate old backups based on retention policy
4981
- * - Hourly: keep last 24
4982
- * - Daily: keep last 7
4983
- * - Weekly: keep last 4
4984
- * - Manual: keep last 10
4985
- */
4986
- function rotateBackups(): { deleted: number; kept: number } {
4987
- const limits: Record<string, number> = {
4988
- hourly: 24,
4989
- daily: 7,
4990
- weekly: 4,
4991
- manual: 10,
4992
- };
4993
-
4994
- const backups = listBackups();
4995
- const byType: Record<string, BackupInfo[]> = { hourly: [], daily: [], weekly: [], manual: [] };
4996
-
4997
- for (const b of backups) {
4998
- byType[b.type].push(b);
4999
- }
5000
-
5001
- let deleted = 0;
5002
- let kept = 0;
5003
-
5004
- for (const [type, list] of Object.entries(byType)) {
5005
- const limit = limits[type] || 10;
5006
- const toKeep = list.slice(0, limit);
5007
- const toDelete = list.slice(limit);
5008
-
5009
- kept += toKeep.length;
5010
-
5011
- for (const b of toDelete) {
5012
- try {
5013
- unlinkSync(b.path);
5014
- deleted++;
5015
- } catch (error) {
5016
- console.error(`Failed to delete ${b.path}:`, error);
5017
- }
5018
- }
5019
- }
5020
-
5021
- return { deleted, kept };
5022
- }
5023
-
5024
- /**
5025
- * Verify backup integrity by opening and running a simple query
5026
- */
5027
- async function verifyBackup(backupPath: string): Promise<boolean> {
5028
- try {
5029
- const result = execSync(`sqlite3 "${backupPath}" "SELECT COUNT(*) FROM events;"`, {
5030
- encoding: "utf-8",
5031
- timeout: 10000,
5032
- });
5033
- return result.trim().length > 0;
5034
- } catch {
5035
- return false;
5036
- }
5037
- }
5038
-
5039
- /**
5040
- * Main backup command handler
5041
- */
5042
- async function backup(action: string) {
5043
- switch (action) {
5044
- case "create": {
5045
- p.intro("swarm backup v" + VERSION);
5046
-
5047
- const s = p.spinner();
5048
- s.start("Creating backup...");
5049
-
5050
- const backupPath = await createBackup("manual");
5051
- if (backupPath) {
5052
- const size = statSync(backupPath).size;
5053
- s.stop(`Backup created: ${backupPath} (${(size / 1024).toFixed(1)} KB)`);
5054
-
5055
- // Verify
5056
- s.start("Verifying backup...");
5057
- const valid = await verifyBackup(backupPath);
5058
- s.stop(valid ? "Backup verified ✓" : "Backup verification FAILED");
5059
-
5060
- // Rotate
5061
- const { deleted, kept } = rotateBackups();
5062
- if (deleted > 0) {
5063
- p.log.info(`Rotated: kept ${kept} backups, deleted ${deleted} old backups`);
5064
- }
5065
- } else {
5066
- s.stop("Backup failed");
5067
- }
5068
-
5069
- p.outro("Done");
5070
- break;
5071
- }
5072
-
5073
- case "list": {
5074
- const backups = listBackups();
5075
- if (backups.length === 0) {
5076
- console.log("No backups found");
5077
- return;
5078
- }
5079
-
5080
- console.log(`\n${cyan("Backups")} (${BACKUP_DIR}):\n`);
5081
-
5082
- for (const b of backups) {
5083
- const age = Math.round((Date.now() - b.timestamp.getTime()) / 1000 / 60);
5084
- const ageStr = age < 60 ? `${age}m ago` : age < 1440 ? `${Math.round(age/60)}h ago` : `${Math.round(age/1440)}d ago`;
5085
- const sizeStr = `${(b.size / 1024).toFixed(1)} KB`;
5086
- console.log(` ${dim(b.type.padEnd(8))} ${basename(b.path)} ${dim(`(${sizeStr}, ${ageStr})`)}`);
5087
- }
5088
- console.log("");
5089
- break;
5090
- }
5091
-
5092
- case "rotate": {
5093
- const { deleted, kept } = rotateBackups();
5094
- console.log(`Rotated: kept ${kept} backups, deleted ${deleted} old backups`);
5095
- break;
5096
- }
5097
-
5098
- case "verify": {
5099
- const backups = listBackups();
5100
- if (backups.length === 0) {
5101
- console.log("No backups to verify");
5102
- return;
5103
- }
5104
-
5105
- console.log(`\nVerifying ${backups.length} backups...\n`);
5106
-
5107
- let passed = 0;
5108
- let failed = 0;
5109
-
5110
- for (const b of backups) {
5111
- const valid = await verifyBackup(b.path);
5112
- if (valid) {
5113
- console.log(` ${green("✓")} ${basename(b.path)}`);
5114
- passed++;
5115
- } else {
5116
- console.log(` ${red("✗")} ${basename(b.path)}`);
5117
- failed++;
5118
- }
5119
- }
5120
-
5121
- console.log(`\n${passed} passed, ${failed} failed\n`);
5122
- break;
5123
- }
5124
-
5125
- case "restore": {
5126
- const backupFile = process.argv[4];
5127
- if (!backupFile) {
5128
- console.error("Usage: swarm backup restore <backup-file>");
5129
- console.error("\nAvailable backups:");
5130
- const backups = listBackups();
5131
- for (const b of backups.slice(0, 5)) {
5132
- console.error(` ${basename(b.path)}`);
5133
- }
5134
- return;
5135
- }
5136
-
5137
- const backupPath = backupFile.startsWith("/") ? backupFile : join(BACKUP_DIR, backupFile);
5138
- if (!existsSync(backupPath)) {
5139
- console.error(`Backup not found: ${backupPath}`);
5140
- return;
5141
- }
5142
-
5143
- // Verify before restore
5144
- const valid = await verifyBackup(backupPath);
5145
- if (!valid) {
5146
- console.error("Backup verification failed - aborting restore");
5147
- return;
5148
- }
5149
-
5150
- // Create a backup of current db first
5151
- const preRestoreBackup = await createBackup("manual");
5152
- console.log(`Created pre-restore backup: ${preRestoreBackup}`);
5153
-
5154
- // Restore
5155
- try {
5156
- copyFileSync(backupPath, SWARM_DB_PATH);
5157
- console.log(`Restored from: ${backupPath}`);
5158
- } catch (error) {
5159
- console.error("Restore failed:", error);
5160
- }
5161
- break;
3744
+ const exitCode = await proc.exited;
3745
+
3746
+ if (exitCode === 0) {
3747
+ s.stop("AGENTS.md updated via LLM");
3748
+ p.log.success("Hivemind unification complete");
3749
+ } else {
3750
+ const stderr = await new Response(proc.stderr).text();
3751
+ s.stop("LLM update failed");
3752
+ p.log.error(stderr || `Exit code: ${exitCode}`);
5162
3753
  }
5163
-
5164
- case "help":
5165
- default:
5166
- console.log(`
5167
- ${cyan("swarm backup")} - Database backup management
5168
-
5169
- ${bold("Commands:")}
5170
- create Create a new backup (default)
5171
- list List all backups
5172
- rotate Rotate old backups based on retention policy
5173
- verify Verify all backups are valid
5174
- restore Restore from a backup file
5175
-
5176
- ${bold("Retention Policy:")}
5177
- Hourly: 24 backups
5178
- Daily: 7 backups
5179
- Weekly: 4 backups
5180
- Manual: 10 backups
5181
-
5182
- ${bold("Examples:")}
5183
- swarm backup # Create a manual backup
5184
- swarm backup list # List all backups
5185
- swarm backup restore latest.db # Restore from a backup
5186
- `);
5187
- break;
3754
+ } catch (error) {
3755
+ s.stop("Failed to run opencode");
3756
+ p.log.error(String(error));
5188
3757
  }
3758
+
3759
+ p.outro("Done");
5189
3760
  }
5190
3761
 
5191
3762
  // ============================================================================
@@ -6157,178 +4728,7 @@ async function logs() {
6157
4728
  *
6158
4729
  * Helps debug which database is being used and its schema state.
6159
4730
  */
6160
- /**
6161
- * Run database repair programmatically
6162
- * Returns counts of cleaned records
6163
- */
6164
- async function runDbRepair(options: { dryRun: boolean }): Promise<{
6165
- nullBeads: number;
6166
- orphanedRecipients: number;
6167
- messagesWithoutRecipients: number;
6168
- expiredReservations: number;
6169
- totalCleaned: number;
6170
- }> {
6171
- const { dryRun } = options;
6172
- const globalDbPath = getGlobalDbPath();
6173
- const swarmMail = await getSwarmMailLibSQL(globalDbPath);
6174
- const db = await swarmMail.getDatabase();
6175
-
6176
- // Count records before cleanup
6177
- const nullBeadsResult = await db.query<{ count: number }>("SELECT COUNT(*) as count FROM beads WHERE id IS NULL");
6178
- const nullBeads = Number(nullBeadsResult[0]?.count ?? 0);
6179
-
6180
- const orphanedRecipientsResult = await db.query<{ count: number }>(
6181
- "SELECT COUNT(*) as count FROM message_recipients WHERE NOT EXISTS (SELECT 1 FROM agents WHERE agents.name = message_recipients.agent_name)"
6182
- );
6183
- const orphanedRecipients = Number(orphanedRecipientsResult[0]?.count ?? 0);
6184
-
6185
- const messagesWithoutRecipientsResult = await db.query<{ count: number }>(
6186
- "SELECT COUNT(*) as count FROM messages WHERE NOT EXISTS (SELECT 1 FROM message_recipients WHERE message_recipients.message_id = messages.id)"
6187
- );
6188
- const messagesWithoutRecipients = Number(messagesWithoutRecipientsResult[0]?.count ?? 0);
6189
-
6190
- const expiredReservationsResult = await db.query<{ count: number }>(
6191
- "SELECT COUNT(*) as count FROM reservations WHERE released_at IS NULL AND expires_at < strftime('%s', 'now') * 1000"
6192
- );
6193
- const expiredReservations = Number(expiredReservationsResult[0]?.count ?? 0);
6194
-
6195
- const totalCleaned = nullBeads + orphanedRecipients + messagesWithoutRecipients + expiredReservations;
6196
-
6197
- // If dry run or nothing to clean, return early
6198
- if (dryRun || totalCleaned === 0) {
6199
- return {
6200
- nullBeads,
6201
- orphanedRecipients,
6202
- messagesWithoutRecipients,
6203
- expiredReservations,
6204
- totalCleaned,
6205
- };
6206
- }
6207
-
6208
- // Execute cleanup queries
6209
- if (nullBeads > 0) {
6210
- await db.query("DELETE FROM beads WHERE id IS NULL");
6211
- }
6212
-
6213
- if (orphanedRecipients > 0) {
6214
- await db.query(
6215
- "DELETE FROM message_recipients WHERE NOT EXISTS (SELECT 1 FROM agents WHERE agents.name = message_recipients.agent_name)"
6216
- );
6217
- }
6218
-
6219
- if (messagesWithoutRecipients > 0) {
6220
- await db.query(
6221
- "DELETE FROM messages WHERE NOT EXISTS (SELECT 1 FROM message_recipients WHERE message_recipients.message_id = messages.id)"
6222
- );
6223
- }
6224
-
6225
- if (expiredReservations > 0) {
6226
- await db.query(
6227
- "UPDATE reservations SET released_at = strftime('%s', 'now') * 1000 WHERE released_at IS NULL AND expires_at < strftime('%s', 'now') * 1000"
6228
- );
6229
- }
6230
-
6231
- return {
6232
- nullBeads,
6233
- orphanedRecipients,
6234
- messagesWithoutRecipients,
6235
- expiredReservations,
6236
- totalCleaned,
6237
- };
6238
- }
6239
-
6240
- /**
6241
- * Database repair command (CLI interface)
6242
- * Executes cleanup SQL to remove orphaned/invalid data
6243
- */
6244
- async function dbRepair() {
6245
- const args = process.argv.slice(4); // Skip 'swarm', 'db', 'repair'
6246
- let dryRun = false;
6247
-
6248
- // Parse --dry-run flag
6249
- for (const arg of args) {
6250
- if (arg === "--dry-run") {
6251
- dryRun = true;
6252
- }
6253
- }
6254
-
6255
- p.intro(dryRun ? "swarm db repair (DRY RUN)" : "swarm db repair");
6256
-
6257
- const s = p.spinner();
6258
- s.start("Analyzing database...");
6259
-
6260
- try {
6261
- // Use shared helper for analysis
6262
- const result = await runDbRepair({ dryRun: true });
6263
-
6264
- s.stop("Analysis complete");
6265
-
6266
- // Show counts
6267
- p.log.step(dryRun ? "Would delete:" : "Deleting:");
6268
- if (result.nullBeads > 0) {
6269
- p.log.message(` - ${result.nullBeads} beads with NULL IDs`);
6270
- }
6271
- if (result.orphanedRecipients > 0) {
6272
- p.log.message(` - ${result.orphanedRecipients} orphaned message_recipients`);
6273
- }
6274
- if (result.messagesWithoutRecipients > 0) {
6275
- p.log.message(` - ${result.messagesWithoutRecipients} messages without recipients`);
6276
- }
6277
- if (result.expiredReservations > 0) {
6278
- p.log.message(` - ${result.expiredReservations} expired unreleased reservations`);
6279
- }
6280
-
6281
- if (result.totalCleaned === 0) {
6282
- p.outro(green("✓ Database is clean! No records to delete."));
6283
- return;
6284
- }
6285
-
6286
- console.log();
6287
- p.log.message(dim(`Total: ${result.totalCleaned} records ${dryRun ? "would be" : "will be"} cleaned`));
6288
- console.log();
6289
-
6290
- // If dry run, stop here
6291
- if (dryRun) {
6292
- p.outro(dim("Run without --dry-run to execute cleanup"));
6293
- return;
6294
- }
6295
-
6296
- // Confirm before actual deletion
6297
- const confirmed = await p.confirm({
6298
- message: `Delete ${result.totalCleaned} records?`,
6299
- initialValue: false,
6300
- });
6301
-
6302
- if (p.isCancel(confirmed) || !confirmed) {
6303
- p.cancel("Cleanup cancelled");
6304
- return;
6305
- }
6306
-
6307
- // Execute cleanup
6308
- const cleanupSpinner = p.spinner();
6309
- cleanupSpinner.start("Cleaning database...");
6310
-
6311
- await runDbRepair({ dryRun: false });
6312
-
6313
- cleanupSpinner.stop("Cleanup complete");
6314
-
6315
- p.outro(green(`✓ Successfully cleaned ${result.totalCleaned} records`));
6316
- } catch (error) {
6317
- s.stop("Error");
6318
- p.log.error(error instanceof Error ? error.message : String(error));
6319
- process.exit(1);
6320
- }
6321
- }
6322
-
6323
4731
  async function db() {
6324
- const args = process.argv.slice(3);
6325
-
6326
- // Check for 'repair' subcommand
6327
- if (args[0] === "repair") {
6328
- await dbRepair();
6329
- return;
6330
- }
6331
-
6332
4732
  const projectPath = process.cwd();
6333
4733
  const projectName = basename(projectPath);
6334
4734
  const hash = hashLibSQLProjectPath(projectPath);
@@ -6597,8 +4997,6 @@ async function stats() {
6597
4997
  let period = "7d"; // default to 7 days
6598
4998
  let format: "text" | "json" = "text";
6599
4999
  let showRegressions = false;
6600
- let showRejections = false;
6601
- let showCompactionPrompts = false;
6602
5000
 
6603
5001
  for (let i = 0; i < args.length; i++) {
6604
5002
  if (args[i] === "--since" || args[i] === "-s") {
@@ -6608,10 +5006,6 @@ async function stats() {
6608
5006
  format = "json";
6609
5007
  } else if (args[i] === "--regressions") {
6610
5008
  showRegressions = true;
6611
- } else if (args[i] === "--rejections") {
6612
- showRejections = true;
6613
- } else if (args[i] === "--compaction-prompts") {
6614
- showCompactionPrompts = true;
6615
5009
  }
6616
5010
  }
6617
5011
 
@@ -6768,88 +5162,6 @@ async function stats() {
6768
5162
  console.log("✅ No eval regressions detected (>10% threshold)\n");
6769
5163
  }
6770
5164
  }
6771
- } else if (showRejections) {
6772
- // If --rejections flag, show rejection analytics
6773
- const rejectionAnalytics = await getRejectionAnalytics(swarmMail);
6774
-
6775
- if (format === "json") {
6776
- console.log(JSON.stringify(rejectionAnalytics, null, 2));
6777
- } else {
6778
- console.log();
6779
- const boxWidth = 61;
6780
- const pad = (text: string) => text + " ".repeat(Math.max(0, boxWidth - text.length));
6781
-
6782
- console.log("┌─────────────────────────────────────────────────────────────┐");
6783
- console.log("│" + pad(" REJECTION ANALYSIS (last " + period + ")") + "│");
6784
- console.log("├─────────────────────────────────────────────────────────────┤");
6785
- console.log("│" + pad(" Total Reviews: " + rejectionAnalytics.totalReviews) + "│");
6786
-
6787
- const rejectionRate = rejectionAnalytics.totalReviews > 0
6788
- ? (100 - rejectionAnalytics.approvalRate).toFixed(0)
6789
- : "0";
6790
- console.log("│" + pad(" Approved: " + rejectionAnalytics.approved + " (" + rejectionAnalytics.approvalRate.toFixed(0) + "%)") + "│");
6791
- console.log("│" + pad(" Rejected: " + rejectionAnalytics.rejected + " (" + rejectionRate + "%)") + "│");
6792
- console.log("│" + pad("") + "│");
6793
-
6794
- if (rejectionAnalytics.topReasons.length > 0) {
6795
- console.log("│" + pad(" Top Rejection Reasons:") + "│");
6796
- for (const reason of rejectionAnalytics.topReasons) {
6797
- const line = " ├── " + reason.category + ": " + reason.count + " (" + reason.percentage.toFixed(0) + "%)";
6798
- console.log("│" + pad(line) + "│");
6799
- }
6800
- } else {
6801
- console.log("│" + pad(" No rejections in this period") + "│");
6802
- }
6803
-
6804
- console.log("└─────────────────────────────────────────────────────────────┘");
6805
- console.log();
6806
- }
6807
- } else if (showCompactionPrompts) {
6808
- // If --compaction-prompts flag, show compaction analytics
6809
- const compactionAnalytics = await getCompactionAnalytics(swarmMail);
6810
-
6811
- if (format === "json") {
6812
- console.log(JSON.stringify(compactionAnalytics, null, 2));
6813
- } else {
6814
- console.log();
6815
- const boxWidth = 61;
6816
- const pad = (text: string) => text + " ".repeat(Math.max(0, boxWidth - text.length));
6817
-
6818
- console.log("┌─────────────────────────────────────────────────────────────┐");
6819
- console.log("│" + pad(" COMPACTION PROMPT ANALYTICS (all time)") + "│");
6820
- console.log("├─────────────────────────────────────────────────────────────┤");
6821
- console.log("│" + pad(" Total Events: " + compactionAnalytics.totalEvents) + "│");
6822
- console.log("│" + pad(" Success Rate: " + compactionAnalytics.successRate.toFixed(1) + "%") + "│");
6823
- console.log("│" + pad(" Avg Prompt Size: " + compactionAnalytics.avgPromptSize + " chars") + "│");
6824
- console.log("│" + pad("") + "│");
6825
- console.log("│" + pad(" By Type:") + "│");
6826
- console.log("│" + pad(" ├── Prompts Generated: " + compactionAnalytics.byType.prompt_generated) + "│");
6827
- console.log("│" + pad(" └── Detections Failed: " + compactionAnalytics.byType.detection_failed) + "│");
6828
- console.log("│" + pad("") + "│");
6829
- console.log("│" + pad(" Confidence Distribution:") + "│");
6830
- console.log("│" + pad(" ├── High: " + compactionAnalytics.byConfidence.high) + "│");
6831
- console.log("│" + pad(" ├── Medium: " + compactionAnalytics.byConfidence.medium) + "│");
6832
- console.log("│" + pad(" └── Low: " + compactionAnalytics.byConfidence.low) + "│");
6833
-
6834
- if (compactionAnalytics.recentPrompts.length > 0) {
6835
- console.log("│" + pad("") + "│");
6836
- console.log("│" + pad(" Recent Prompts:") + "│");
6837
- for (const prompt of compactionAnalytics.recentPrompts.slice(0, 5)) {
6838
- const timestamp = new Date(prompt.timestamp).toLocaleDateString();
6839
- const conf = prompt.confidence ? ` (${prompt.confidence})` : "";
6840
- const line = ` ├── ${timestamp}: ${prompt.length} chars${conf}`;
6841
- console.log("│" + pad(line) + "│");
6842
-
6843
- if (prompt.preview) {
6844
- const previewLine = ` ${prompt.preview.substring(0, 50)}...`;
6845
- console.log("│" + pad(previewLine) + "│");
6846
- }
6847
- }
6848
- }
6849
-
6850
- console.log("└─────────────────────────────────────────────────────────────┘");
6851
- console.log();
6852
- }
6853
5165
  } else {
6854
5166
  // Normal stats output
6855
5167
  if (format === "json") {
@@ -7191,49 +5503,6 @@ async function evalRun() {
7191
5503
  }
7192
5504
  }
7193
5505
 
7194
- // ============================================================================
7195
- // MCP Server (Debug Only)
7196
- // ============================================================================
7197
-
7198
- /**
7199
- * Start the MCP server over stdio.
7200
- * When run non-interactively (piped stdin), runs silently for MCP protocol.
7201
- * When run interactively (TTY), shows debug output.
7202
- */
7203
- async function mcpServe() {
7204
- const isInteractive = process.stdin.isTTY;
7205
-
7206
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || getClaudePluginRoot();
7207
- const candidates = [
7208
- join(pluginRoot, "bin", "swarm-mcp-server.ts"),
7209
- join(PACKAGE_ROOT, "bin", "swarm-mcp-server.ts"),
7210
- ];
7211
- const serverPath = candidates.find((path) => existsSync(path));
7212
-
7213
- if (!serverPath) {
7214
- if (isInteractive) {
7215
- p.intro("swarm mcp-serve");
7216
- p.log.error("MCP server entrypoint not found");
7217
- p.log.message(dim(` Looked for: ${candidates.join(", ")}`));
7218
- p.outro("Aborted");
7219
- } else {
7220
- console.error("[swarm-mcp] Server entrypoint not found");
7221
- }
7222
- process.exit(1);
7223
- }
7224
-
7225
- if (isInteractive) {
7226
- p.intro("swarm mcp-serve");
7227
- p.log.step("Starting MCP server...");
7228
- p.log.message(dim(` Using: ${serverPath}`));
7229
- }
7230
-
7231
- const proc = spawn("bun", ["run", serverPath], { stdio: "inherit" });
7232
- proc.on("close", (exitCode) => {
7233
- process.exit(exitCode ?? 0);
7234
- });
7235
- }
7236
-
7237
5506
  // ============================================================================
7238
5507
  // Serve Command - Start SSE Server
7239
5508
  // ============================================================================
@@ -7451,32 +5720,22 @@ async function capture() {
7451
5720
  */
7452
5721
  function parseMemoryArgs(subcommand: string, args: string[]): {
7453
5722
  json: boolean;
7454
- debug: boolean;
7455
- extractEntities: boolean;
7456
5723
  info?: string;
7457
5724
  query?: string;
7458
5725
  id?: string;
7459
- name?: string;
7460
5726
  tags?: string;
7461
5727
  limit?: number;
7462
5728
  collection?: string;
7463
- type?: string;
7464
- direction?: string;
7465
5729
  } {
7466
5730
  let json = false;
7467
- let debug = false;
7468
- let extractEntities = false;
7469
5731
  let info: string | undefined;
7470
5732
  let query: string | undefined;
7471
5733
  let id: string | undefined;
7472
- let name: string | undefined;
7473
5734
  let tags: string | undefined;
7474
5735
  let limit: number | undefined;
7475
5736
  let collection: string | undefined;
7476
- let type: string | undefined;
7477
- let direction: string | undefined;
7478
5737
 
7479
- // First positional arg for store/find/get/remove/validate/entity/taxonomy
5738
+ // First positional arg for store/find/get/remove/validate
7480
5739
  if (args.length > 0 && !args[0].startsWith("--")) {
7481
5740
  if (subcommand === "store") {
7482
5741
  info = args[0];
@@ -7484,8 +5743,6 @@ function parseMemoryArgs(subcommand: string, args: string[]): {
7484
5743
  query = args[0];
7485
5744
  } else if (subcommand === "get" || subcommand === "remove" || subcommand === "validate") {
7486
5745
  id = args[0];
7487
- } else if (subcommand === "entity" || subcommand === "taxonomy") {
7488
- name = args[0];
7489
5746
  }
7490
5747
  }
7491
5748
 
@@ -7493,10 +5750,6 @@ function parseMemoryArgs(subcommand: string, args: string[]): {
7493
5750
  const arg = args[i];
7494
5751
  if (arg === "--json") {
7495
5752
  json = true;
7496
- } else if (arg === "--debug" || arg === "-v" || arg === "--verbose") {
7497
- debug = true;
7498
- } else if (arg === "--extract-entities" || arg === "-e") {
7499
- extractEntities = true;
7500
5753
  } else if (arg === "--tags" && i + 1 < args.length) {
7501
5754
  tags = args[++i];
7502
5755
  } else if (arg === "--limit" && i + 1 < args.length) {
@@ -7504,19 +5757,15 @@ function parseMemoryArgs(subcommand: string, args: string[]): {
7504
5757
  if (!isNaN(val)) limit = val;
7505
5758
  } else if (arg === "--collection" && i + 1 < args.length) {
7506
5759
  collection = args[++i];
7507
- } else if (arg === "--type" && i + 1 < args.length) {
7508
- type = args[++i];
7509
- } else if (arg === "--direction" && i + 1 < args.length) {
7510
- direction = args[++i];
7511
5760
  }
7512
5761
  }
7513
5762
 
7514
- return { json, debug, extractEntities, info, query, id, name, tags, limit, collection, type, direction };
5763
+ return { json, info, query, id, tags, limit, collection };
7515
5764
  }
7516
5765
 
7517
5766
  /**
7518
5767
  * Memory command - unified interface to memory operations
7519
- *
5768
+ *
7520
5769
  * Commands:
7521
5770
  * swarm memory store <info> [--tags <tags>]
7522
5771
  * swarm memory find <query> [--limit <n>] [--collection <name>]
@@ -7526,9 +5775,6 @@ function parseMemoryArgs(subcommand: string, args: string[]): {
7526
5775
  * swarm memory stats
7527
5776
  * swarm memory index
7528
5777
  * swarm memory sync
7529
- * swarm memory entities [--type <type>]
7530
- * swarm memory entity <name>
7531
- * swarm memory taxonomy <entity> [--direction broader|narrower|related]
7532
5778
  */
7533
5779
  async function memory() {
7534
5780
  const subcommand = process.argv[3];
@@ -7542,20 +5788,21 @@ async function memory() {
7542
5788
  // Get database instance using getDb from swarm-mail
7543
5789
  // This returns a drizzle instance (SwarmDb) that memory adapter expects
7544
5790
  const { getDb } = await import("swarm-mail");
7545
-
7546
- // Use single global DB: ~/.config/swarm-tools/swarm.db
7547
- const globalDbDir = join(homedir(), ".config", "swarm-tools");
7548
-
7549
- // Ensure global DB directory exists
7550
- if (!existsSync(globalDbDir)) {
7551
- mkdirSync(globalDbDir, { recursive: true });
5791
+
5792
+ // Calculate DB path (same logic as libsql.convenience.ts)
5793
+ const tempDirName = getLibSQLProjectTempDirName(projectPath);
5794
+ const tempDir = join(tmpdir(), tempDirName);
5795
+
5796
+ // Ensure temp directory exists
5797
+ if (!existsSync(tempDir)) {
5798
+ mkdirSync(tempDir, { recursive: true });
7552
5799
  }
7553
-
7554
- const dbPath = join(globalDbDir, "swarm.db");
7555
-
5800
+
5801
+ const dbPath = join(tempDir, "streams.db");
5802
+
7556
5803
  // Convert to file:// URL (required by libSQL)
7557
5804
  const dbUrl = `file://${dbPath}`;
7558
-
5805
+
7559
5806
  const db = await getDb(dbUrl);
7560
5807
 
7561
5808
  // Create memory adapter with default Ollama config
@@ -7568,39 +5815,23 @@ async function memory() {
7568
5815
  switch (subcommand) {
7569
5816
  case "store": {
7570
5817
  if (!parsed.info) {
7571
- console.error("Usage: swarm memory store <information> [--tags <tags>] [--extract-entities] [--debug]");
5818
+ console.error("Usage: swarm memory store <information> [--tags <tags>]");
7572
5819
  process.exit(1);
7573
5820
  }
7574
5821
 
7575
- if (parsed.debug) {
7576
- console.log("[DEBUG] Store options:", {
7577
- content: parsed.info.substring(0, 50) + "...",
7578
- tags: parsed.tags,
7579
- collection: parsed.collection || "default",
7580
- extractEntities: parsed.extractEntities,
7581
- });
7582
- }
7583
-
7584
5822
  const result = await adapter.store(parsed.info, {
7585
5823
  tags: parsed.tags,
7586
5824
  collection: parsed.collection || "default",
7587
- extractEntities: parsed.extractEntities,
7588
5825
  });
7589
5826
 
7590
5827
  if (parsed.json) {
7591
- console.log(JSON.stringify({ success: true, id: result.id, autoTags: result.autoTags, links: result.links }));
5828
+ console.log(JSON.stringify({ success: true, id: result.id }));
7592
5829
  } else {
7593
5830
  p.intro("swarm memory store");
7594
5831
  p.log.success(`Stored memory: ${result.id}`);
7595
5832
  if (result.autoTags) {
7596
5833
  p.log.message(`Auto-tags: ${result.autoTags.tags.join(", ")}`);
7597
5834
  }
7598
- if (result.links && result.links.length > 0) {
7599
- p.log.message(`Linked to ${result.links.length} related memories`);
7600
- }
7601
- if (parsed.extractEntities) {
7602
- p.log.message(`Entity extraction: enabled`);
7603
- }
7604
5835
  p.outro("Done");
7605
5836
  }
7606
5837
  break;
@@ -7743,237 +5974,6 @@ async function memory() {
7743
5974
  break;
7744
5975
  }
7745
5976
 
7746
- case "entities": {
7747
- const { createClient } = await import("@libsql/client");
7748
- const client = createClient({ url: dbUrl });
7749
-
7750
- const { getEntitiesByType } = await import("swarm-mail");
7751
-
7752
- let query = "SELECT id, name, entity_type, created_at FROM entities";
7753
- const params: string[] = [];
7754
-
7755
- if (parsed.type) {
7756
- query += " WHERE entity_type = ?";
7757
- params.push(parsed.type);
7758
- }
7759
-
7760
- query += " ORDER BY created_at DESC";
7761
-
7762
- const result = await client.execute(query, params);
7763
-
7764
- if (parsed.json) {
7765
- console.log(JSON.stringify({
7766
- success: true,
7767
- entities: result.rows.map((row) => ({
7768
- id: row.id,
7769
- name: row.name,
7770
- entityType: row.entity_type,
7771
- createdAt: row.created_at,
7772
- })),
7773
- }));
7774
- } else {
7775
- p.intro("swarm memory entities");
7776
- if (result.rows.length === 0) {
7777
- p.log.warn("No entities found");
7778
- } else {
7779
- console.log();
7780
- for (const row of result.rows) {
7781
- console.log(cyan(`[${row.id}] ${row.name}`));
7782
- console.log(dim(` Type: ${row.entity_type}`));
7783
- console.log(dim(` Created: ${new Date(row.created_at as string).toLocaleDateString()}`));
7784
- }
7785
- }
7786
- p.outro(`Found ${result.rows.length} entit${result.rows.length === 1 ? "y" : "ies"}`);
7787
- }
7788
-
7789
- client.close();
7790
- break;
7791
- }
7792
-
7793
- case "entity": {
7794
- if (!parsed.name) {
7795
- console.error("Usage: swarm memory entity <name>");
7796
- process.exit(1);
7797
- }
7798
-
7799
- const { createClient } = await import("@libsql/client");
7800
- const client = createClient({ url: dbUrl });
7801
-
7802
- const { getTaxonomyForEntity } = await import("swarm-mail");
7803
-
7804
- // Lookup entity by name (case-insensitive)
7805
- const entityResult = await client.execute(
7806
- "SELECT id, name, entity_type, created_at FROM entities WHERE LOWER(name) = LOWER(?) LIMIT 1",
7807
- [parsed.name]
7808
- );
7809
-
7810
- if (entityResult.rows.length === 0) {
7811
- if (parsed.json) {
7812
- console.log(JSON.stringify({ success: false, error: "Entity not found" }));
7813
- } else {
7814
- p.intro("swarm memory entity");
7815
- p.log.error(`Entity "${parsed.name}" not found`);
7816
- p.outro("Aborted");
7817
- }
7818
- client.close();
7819
- process.exit(1);
7820
- }
7821
-
7822
- const entity = entityResult.rows[0];
7823
- const entityId = entity.id as string;
7824
-
7825
- // Get taxonomy relationships
7826
- const taxonomy = await getTaxonomyForEntity(entityId, client);
7827
-
7828
- // Fetch related entity names
7829
- const relatedIds = new Set<string>();
7830
- for (const rel of taxonomy) {
7831
- relatedIds.add(rel.entityId);
7832
- relatedIds.add(rel.relatedEntityId);
7833
- }
7834
-
7835
- const relatedEntities = new Map<string, string>();
7836
- for (const id of relatedIds) {
7837
- const res = await client.execute("SELECT name FROM entities WHERE id = ? LIMIT 1", [id]);
7838
- if (res.rows.length > 0) {
7839
- relatedEntities.set(id, res.rows[0].name as string);
7840
- }
7841
- }
7842
-
7843
- if (parsed.json) {
7844
- console.log(JSON.stringify({
7845
- success: true,
7846
- entity: {
7847
- id: entity.id,
7848
- name: entity.name,
7849
- entityType: entity.entity_type,
7850
- createdAt: entity.created_at,
7851
- },
7852
- taxonomy: taxonomy.map((rel) => ({
7853
- id: rel.id,
7854
- subjectEntity: relatedEntities.get(rel.entityId),
7855
- relationshipType: rel.relationshipType,
7856
- relatedEntity: relatedEntities.get(rel.relatedEntityId),
7857
- })),
7858
- }));
7859
- } else {
7860
- p.intro(`swarm memory entity: ${entity.name}`);
7861
- console.log();
7862
- console.log(cyan("Entity Details:"));
7863
- console.log(` ID: ${entity.id}`);
7864
- console.log(` Name: ${entity.name}`);
7865
- console.log(` Type: ${entity.entity_type}`);
7866
- console.log(` Created: ${new Date(entity.created_at as string).toLocaleDateString()}`);
7867
-
7868
- if (taxonomy.length > 0) {
7869
- console.log();
7870
- console.log(cyan("Taxonomy Relationships:"));
7871
- for (const rel of taxonomy) {
7872
- const subject = relatedEntities.get(rel.entityId);
7873
- const related = relatedEntities.get(rel.relatedEntityId);
7874
- console.log(` ${subject} ${dim(`--[${rel.relationshipType}]-->`)} ${related}`);
7875
- }
7876
- } else {
7877
- console.log();
7878
- console.log(dim(" No taxonomy relationships"));
7879
- }
7880
-
7881
- p.outro("Done");
7882
- }
7883
-
7884
- client.close();
7885
- break;
7886
- }
7887
-
7888
- case "taxonomy": {
7889
- if (!parsed.name) {
7890
- console.error("Usage: swarm memory taxonomy <entity> [--direction broader|narrower|related]");
7891
- process.exit(1);
7892
- }
7893
-
7894
- const { createClient } = await import("@libsql/client");
7895
- const client = createClient({ url: dbUrl });
7896
-
7897
- const { getTaxonomyForEntity, findByTaxonomy } = await import("swarm-mail");
7898
-
7899
- // Lookup entity by name (case-insensitive)
7900
- const entityResult = await client.execute(
7901
- "SELECT id, name FROM entities WHERE LOWER(name) = LOWER(?) LIMIT 1",
7902
- [parsed.name]
7903
- );
7904
-
7905
- if (entityResult.rows.length === 0) {
7906
- if (parsed.json) {
7907
- console.log(JSON.stringify({ success: false, error: "Entity not found" }));
7908
- } else {
7909
- p.intro("swarm memory taxonomy");
7910
- p.log.error(`Entity "${parsed.name}" not found`);
7911
- p.outro("Aborted");
7912
- }
7913
- client.close();
7914
- process.exit(1);
7915
- }
7916
-
7917
- const entity = entityResult.rows[0];
7918
- const entityId = entity.id as string;
7919
-
7920
- let relatedIds: string[];
7921
-
7922
- if (parsed.direction === "broader" || parsed.direction === "narrower" || parsed.direction === "related") {
7923
- // Filter by relationship type
7924
- relatedIds = await findByTaxonomy(entityId, parsed.direction, client);
7925
- } else {
7926
- // Get all relationships
7927
- const taxonomy = await getTaxonomyForEntity(entityId, client);
7928
- relatedIds = taxonomy
7929
- .filter((rel) => rel.entityId === entityId)
7930
- .map((rel) => rel.relatedEntityId);
7931
- }
7932
-
7933
- // Fetch related entity details
7934
- const relatedEntities: Array<{ id: string; name: string; relationshipType?: string }> = [];
7935
- for (const id of relatedIds) {
7936
- const res = await client.execute("SELECT name FROM entities WHERE id = ? LIMIT 1", [id]);
7937
- if (res.rows.length > 0) {
7938
- relatedEntities.push({
7939
- id,
7940
- name: res.rows[0].name as string,
7941
- });
7942
- }
7943
- }
7944
-
7945
- if (parsed.json) {
7946
- console.log(JSON.stringify({
7947
- success: true,
7948
- entity: {
7949
- id: entity.id,
7950
- name: entity.name,
7951
- },
7952
- direction: parsed.direction || "all",
7953
- related: relatedEntities,
7954
- }));
7955
- } else {
7956
- const directionLabel = parsed.direction
7957
- ? `${parsed.direction} relationships`
7958
- : "all relationships";
7959
- p.intro(`swarm memory taxonomy: ${entity.name} (${directionLabel})`);
7960
-
7961
- if (relatedEntities.length === 0) {
7962
- p.log.warn("No related entities found");
7963
- } else {
7964
- console.log();
7965
- for (const rel of relatedEntities) {
7966
- console.log(` ${cyan(rel.name)} ${dim(`[${rel.id}]`)}`);
7967
- }
7968
- }
7969
-
7970
- p.outro(`Found ${relatedEntities.length} related entit${relatedEntities.length === 1 ? "y" : "ies"}`);
7971
- }
7972
-
7973
- client.close();
7974
- break;
7975
- }
7976
-
7977
5977
  case "sync": {
7978
5978
  // Sync is a stub - actual sync happens via .hive/memories.jsonl
7979
5979
  // which is handled by hivemind_sync tool
@@ -7994,20 +5994,17 @@ async function memory() {
7994
5994
  console.error("Usage: swarm memory <subcommand> [options]");
7995
5995
  console.error("");
7996
5996
  console.error("Subcommands:");
7997
- console.error(" store <info> [--tags <tags>] Store a memory");
7998
- console.error(" find <query> [--limit <n>] Search memories");
7999
- console.error(" get <id> Get memory by ID");
8000
- console.error(" remove <id> Delete memory");
8001
- console.error(" validate <id> Reset decay timer");
8002
- console.error(" stats Show database stats");
8003
- console.error(" index Index sessions (use hivemind_index)");
8004
- console.error(" sync Sync to git (use hivemind_sync)");
8005
- console.error(" entities [--type <type>] List all entities (optionally filtered by type)");
8006
- console.error(" entity <name> Show entity details with taxonomy relationships");
8007
- console.error(" taxonomy <entity> [--direction <dir>] Show taxonomy tree (direction: broader|narrower|related)");
5997
+ console.error(" store <info> [--tags <tags>] Store a memory");
5998
+ console.error(" find <query> [--limit <n>] Search memories");
5999
+ console.error(" get <id> Get memory by ID");
6000
+ console.error(" remove <id> Delete memory");
6001
+ console.error(" validate <id> Reset decay timer");
6002
+ console.error(" stats Show database stats");
6003
+ console.error(" index Index sessions (use hivemind_index)");
6004
+ console.error(" sync Sync to git (use hivemind_sync)");
8008
6005
  console.error("");
8009
6006
  console.error("Global options:");
8010
- console.error(" --json Output JSON");
6007
+ console.error(" --json Output JSON");
8011
6008
  process.exit(1);
8012
6009
  }
8013
6010
  }
@@ -8041,14 +6038,8 @@ switch (command) {
8041
6038
  break;
8042
6039
  }
8043
6040
  case "doctor": {
8044
- const deepFlag = process.argv.includes("--deep");
8045
- if (deepFlag) {
8046
- const { doctorDeep } = await import("./commands/doctor.js");
8047
- await doctorDeep(process.argv.slice(3));
8048
- } else {
8049
- const debugFlag = process.argv.includes("--debug") || process.argv.includes("-d");
8050
- await doctor(debugFlag);
8051
- }
6041
+ const debugFlag = process.argv.includes("--debug") || process.argv.includes("-d");
6042
+ await doctor(debugFlag);
8052
6043
  break;
8053
6044
  }
8054
6045
  case "init":
@@ -8057,12 +6048,6 @@ switch (command) {
8057
6048
  case "config":
8058
6049
  config();
8059
6050
  break;
8060
- case "claude":
8061
- await claudeCommand();
8062
- break;
8063
- case "mcp-serve":
8064
- await mcpServe();
8065
- break;
8066
6051
  case "serve":
8067
6052
  await serve();
8068
6053
  break;
@@ -8075,8 +6060,7 @@ switch (command) {
8075
6060
  case "tool": {
8076
6061
  const toolName = process.argv[3];
8077
6062
  if (!toolName || toolName === "--list" || toolName === "-l") {
8078
- const jsonOutput = process.argv.includes("--json");
8079
- await listTools(jsonOutput);
6063
+ await listTools();
8080
6064
  } else {
8081
6065
  // Look for --json flag
8082
6066
  const jsonFlagIndex = process.argv.indexOf("--json");
@@ -8094,11 +6078,6 @@ switch (command) {
8094
6078
  case "migrate":
8095
6079
  await migrate();
8096
6080
  break;
8097
- case "backup": {
8098
- const backupAction = process.argv[3] || "create";
8099
- await backup(backupAction);
8100
- break;
8101
- }
8102
6081
  case "db":
8103
6082
  await db();
8104
6083
  break;
@@ -8107,7 +6086,7 @@ switch (command) {
8107
6086
  break;
8108
6087
  case "log":
8109
6088
  case "logs":
8110
- await log();
6089
+ await logs();
8111
6090
  break;
8112
6091
  case "stats":
8113
6092
  await stats();
@@ -8133,27 +6112,12 @@ switch (command) {
8133
6112
  case "dashboard":
8134
6113
  await dashboard();
8135
6114
  break;
8136
- case "compliance":
8137
- await showWorkerCompliance();
8138
- break;
8139
6115
  case "replay":
8140
6116
  await replay();
8141
6117
  break;
8142
6118
  case "export":
8143
6119
  await exportEvents();
8144
6120
  break;
8145
- case "tree":
8146
- await treeCommand();
8147
- break;
8148
- case "session":
8149
- await session();
8150
- break;
8151
- case "queue":
8152
- await queue();
8153
- break;
8154
- case "status":
8155
- await status(process.argv.slice(3));
8156
- break;
8157
6121
  case "version":
8158
6122
  case "--version":
8159
6123
  case "-v":
@@ -8165,7 +6129,7 @@ switch (command) {
8165
6129
  await help();
8166
6130
  break;
8167
6131
  case undefined:
8168
- await status(process.argv.slice(3));
6132
+ await setup();
8169
6133
  break;
8170
6134
  default:
8171
6135
  console.error("Unknown command: " + command);