opencode-swarm-plugin 0.46.0 → 0.48.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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  /**
3
3
  * OpenCode Swarm Plugin CLI
4
4
  *
@@ -51,7 +51,7 @@ import {
51
51
  createDurableStreamAdapter,
52
52
  createDurableStreamServer,
53
53
  } from "swarm-mail";
54
- import { execSync } from "child_process";
54
+ import { execSync, spawn } from "child_process";
55
55
  import { tmpdir } from "os";
56
56
 
57
57
  // Query & observability tools
@@ -464,6 +464,15 @@ interface CheckResult {
464
464
  // ============================================================================
465
465
 
466
466
  const DEPENDENCIES: Dependency[] = [
467
+ {
468
+ name: "Bun",
469
+ command: "bun",
470
+ checkArgs: ["--version"],
471
+ required: true,
472
+ install: "curl -fsSL https://bun.sh/install | bash",
473
+ installType: "manual",
474
+ description: "JavaScript runtime (required for CLI)",
475
+ },
467
476
  {
468
477
  name: "OpenCode",
469
478
  command: "opencode",
@@ -512,34 +521,53 @@ async function checkCommand(
512
521
  cmd: string,
513
522
  args: string[],
514
523
  ): Promise<{ available: boolean; version?: string }> {
515
- try {
516
- const proc = Bun.spawn([cmd, ...args], {
517
- stdout: "pipe",
518
- stderr: "pipe",
519
- });
520
- const exitCode = await proc.exited;
521
- if (exitCode === 0) {
522
- const output = await new Response(proc.stdout).text();
523
- const versionMatch = output.match(/v?(\d+\.\d+\.\d+)/);
524
- return { available: true, version: versionMatch?.[1] };
524
+ return new Promise((resolve) => {
525
+ try {
526
+ const proc = spawn(cmd, args, {
527
+ stdio: ["ignore", "pipe", "pipe"],
528
+ });
529
+
530
+ let stdout = "";
531
+ proc.stdout?.on("data", (data) => {
532
+ stdout += data.toString();
533
+ });
534
+
535
+ proc.on("error", () => {
536
+ resolve({ available: false });
537
+ });
538
+
539
+ proc.on("close", (exitCode) => {
540
+ if (exitCode === 0) {
541
+ const versionMatch = stdout.match(/v?(\d+\.\d+\.\d+)/);
542
+ resolve({ available: true, version: versionMatch?.[1] });
543
+ } else {
544
+ resolve({ available: false });
545
+ }
546
+ });
547
+ } catch {
548
+ resolve({ available: false });
525
549
  }
526
- return { available: false };
527
- } catch {
528
- return { available: false };
529
- }
550
+ });
530
551
  }
531
552
 
532
553
  async function runInstall(command: string): Promise<boolean> {
533
- try {
534
- const proc = Bun.spawn(["bash", "-c", command], {
535
- stdout: "inherit",
536
- stderr: "inherit",
537
- });
538
- const exitCode = await proc.exited;
539
- return exitCode === 0;
540
- } catch {
541
- return false;
542
- }
554
+ return new Promise((resolve) => {
555
+ try {
556
+ const proc = spawn("bash", ["-c", command], {
557
+ stdio: "inherit",
558
+ });
559
+
560
+ proc.on("error", () => {
561
+ resolve(false);
562
+ });
563
+
564
+ proc.on("close", (exitCode) => {
565
+ resolve(exitCode === 0);
566
+ });
567
+ } catch {
568
+ resolve(false);
569
+ }
570
+ });
543
571
  }
544
572
 
545
573
  async function checkAllDependencies(): Promise<CheckResult[]> {
@@ -707,68 +735,173 @@ function buildAgentsSkillsSection(
707
735
  ].join(newline);
708
736
  }
709
737
 
710
- function buildAgentsCassSection(newline: string): string {
738
+ /**
739
+ * Build the unified Hivemind section (ADR-011).
740
+ * Replaces separate CASS and Semantic Memory sections.
741
+ */
742
+ function buildAgentsHivemindSection(newline: string): string {
711
743
  return [
712
- "## CASS - Cross-Agent Session Search",
744
+ "## Hivemind - Unified Memory System",
745
+ "",
746
+ "The hive remembers everything. Learnings, sessions, patterns—all searchable.",
747
+ "",
748
+ "**Unified storage:** Manual learnings and AI agent session histories stored in the same database, searchable together. Powered by libSQL vectors + Ollama embeddings.",
713
749
  "",
714
- "Search across ALL your AI coding agent histories before solving problems from scratch.",
750
+ "**Indexed agents:** Claude Code, Codex, Cursor, Gemini, Aider, ChatGPT, Cline, OpenCode, Amp, Pi-Agent",
715
751
  "",
716
752
  "### When to Use",
717
753
  "",
718
- '- **BEFORE implementing anything**: check if any agent solved it before',
719
- '- **Debugging**: "what did I try last time this error happened?"',
720
- '- **Learning patterns**: "how did Cursor handle this API?"',
754
+ "- **BEFORE implementing** - check if you or any agent solved it before",
755
+ "- **After solving hard problems** - store learnings for future sessions",
756
+ "- **Debugging** - search past sessions for similar errors",
757
+ "- **Architecture decisions** - record reasoning, alternatives, tradeoffs",
758
+ "- **Project-specific patterns** - capture domain rules and gotchas",
759
+ "",
760
+ "### Tools",
761
+ "",
762
+ "| Tool | Purpose |",
763
+ "|------|---------|",
764
+ "| `hivemind_store` | Store a memory (learnings, decisions, patterns) |",
765
+ "| `hivemind_find` | Search all memories (learnings + sessions, semantic + FTS fallback) |",
766
+ "| `hivemind_get` | Get specific memory by ID |",
767
+ "| `hivemind_remove` | Delete outdated/incorrect memory |",
768
+ "| `hivemind_validate` | Confirm memory still accurate (resets 90-day decay timer) |",
769
+ "| `hivemind_stats` | Memory statistics and health check |",
770
+ "| `hivemind_index` | Index AI session directories |",
771
+ "| `hivemind_sync` | Sync to .hive/memories.jsonl (git-backed, team-shared) |",
721
772
  "",
722
773
  "### Usage",
723
774
  "",
775
+ "**Store a learning** (include WHY, not just WHAT):",
776
+ "",
777
+ "```typescript",
778
+ "hivemind_store({",
779
+ ' information: "OAuth refresh tokens need 5min buffer before expiry to avoid race conditions. Without buffer, token refresh can fail mid-request if expiry happens between check and use.",',
780
+ ' tags: "auth,oauth,tokens,race-conditions"',
781
+ "})",
782
+ "```",
783
+ "",
784
+ "**Search all memories** (learnings + sessions):",
785
+ "",
786
+ "```typescript",
787
+ "// Search everything",
788
+ 'hivemind_find({ query: "token refresh", limit: 5 })',
789
+ "",
790
+ "// Search only learnings (manual entries)",
791
+ 'hivemind_find({ query: "authentication", collection: "default" })',
792
+ "",
793
+ "// Search only Claude sessions",
794
+ 'hivemind_find({ query: "Next.js caching", collection: "claude" })',
795
+ "",
796
+ "// Search only Cursor sessions",
797
+ 'hivemind_find({ query: "API design", collection: "cursor" })',
798
+ "```",
799
+ "",
800
+ "**Get specific memory**:",
801
+ "",
802
+ "```typescript",
803
+ 'hivemind_get({ id: "mem_xyz123" })',
804
+ "```",
805
+ "",
806
+ "**Delete outdated memory**:",
807
+ "",
808
+ "```typescript",
809
+ 'hivemind_remove({ id: "mem_old456" })',
810
+ "```",
811
+ "",
812
+ "**Validate memory is still accurate** (resets decay):",
813
+ "",
814
+ "```typescript",
815
+ "// Confirmed this memory is still relevant",
816
+ 'hivemind_validate({ id: "mem_xyz123" })',
817
+ "```",
818
+ "",
819
+ "**Index new sessions**:",
820
+ "",
821
+ "```typescript",
822
+ "// Automatically indexes ~/.config/opencode/sessions, ~/.cursor-tutor, etc.",
823
+ "hivemind_index()",
824
+ "```",
825
+ "",
826
+ "**Sync to git**:",
827
+ "",
828
+ "```typescript",
829
+ "// Writes learnings to .hive/memories.jsonl for git sync",
830
+ "hivemind_sync()",
831
+ "```",
832
+ "",
833
+ "**Check stats**:",
834
+ "",
835
+ "```typescript",
836
+ "hivemind_stats()",
837
+ "```",
838
+ "",
839
+ "### Usage Pattern",
840
+ "",
724
841
  "```bash",
725
- "# Search all agents",
726
- 'cass_search(query="authentication token refresh", limit=5)',
842
+ "# 1. Before starting work - query for relevant learnings",
843
+ 'hivemind_find({ query: "<task keywords>", limit: 5 })',
727
844
  "",
728
- "# Filter by agent/time",
729
- 'cass_search(query="useEffect cleanup", agent="claude", days=7)',
845
+ "# 2. Do the work...",
730
846
  "",
731
- "# View specific result",
732
- 'cass_view(path="/path/from/search", line=42)',
847
+ "# 3. After solving hard problem - store learning",
848
+ "hivemind_store({",
849
+ ' information: "<what you learned, WHY it matters>",',
850
+ ' tags: "<relevant,tags>"',
851
+ "})",
733
852
  "",
734
- "# Expand context around match",
735
- 'cass_expand(path="/path", line=42, context=10)',
853
+ "# 4. Validate memories when you confirm they're still accurate",
854
+ 'hivemind_validate({ id: "<memory-id>" })',
736
855
  "```",
737
856
  "",
738
- "**Pro tip:** Query CASS at the START of complex tasks. Past solutions save time.",
739
- ].join(newline);
740
- }
741
-
742
- function buildAgentsSemanticMemorySection(newline: string): string {
743
- return [
744
- "## Semantic Memory - Persistent Learning",
857
+ "### Integration with Workflow",
745
858
  "",
746
- "Store and retrieve learnings across sessions. Memories persist and are searchable.",
859
+ "**At task start** (query BEFORE implementing):",
747
860
  "",
748
- "### When to Use",
861
+ "```bash",
862
+ "# Check if you or any agent solved similar problems",
863
+ 'hivemind_find({ query: "OAuth token refresh buffer", limit: 5 })',
864
+ "```",
749
865
  "",
750
- "- After solving a tricky problem - store the solution",
751
- "- After making architectural decisions - store the reasoning",
752
- "- Before starting work - search for relevant past learnings",
753
- "- When you discover project-specific patterns",
866
+ "**During debugging** (search past sessions):",
754
867
  "",
755
- "### Usage",
868
+ "```bash",
869
+ "# Find similar errors from past sessions",
870
+ 'hivemind_find({ query: "cannot read property of undefined", collection: "claude" })',
871
+ "```",
872
+ "",
873
+ "**After solving problems** (store learnings):",
756
874
  "",
757
875
  "```bash",
758
- "# Store a learning",
759
- 'semantic-memory_store(information="OAuth refresh tokens need 5min buffer before expiry", metadata="auth, tokens")',
876
+ '# Store root cause + solution, not just "fixed it"',
877
+ "hivemind_store({",
878
+ ' information: "Next.js searchParams causes dynamic rendering. Workaround: destructure in parent, pass as props to cached child.",',
879
+ ' tags: "nextjs,cache-components,dynamic-rendering,searchparams"',
880
+ "})",
881
+ "```",
760
882
  "",
761
- "# Search for relevant memories",
762
- 'semantic-memory_find(query="token refresh", limit=5)',
883
+ "**Learning from other agents**:",
763
884
  "",
764
- "# Validate a memory is still accurate (resets decay timer)",
765
- 'semantic-memory_validate(id="mem_123")',
885
+ "```bash",
886
+ "# See how Cursor handled similar feature",
887
+ 'hivemind_find({ query: "implement authentication", collection: "cursor" })',
766
888
  "```",
767
889
  "",
768
- "**Pro tip:** Store the WHY, not just the WHAT. Future you needs context.",
890
+ "**Pro tip:** Query Hivemind at the START of complex tasks. Past solutions (yours or other agents') save time and prevent reinventing wheels.",
769
891
  ].join(newline);
770
892
  }
771
893
 
894
+ // Legacy functions kept for backwards compatibility during migration
895
+ function buildAgentsCassSection(newline: string): string {
896
+ // Redirect to Hivemind section
897
+ return buildAgentsHivemindSection(newline);
898
+ }
899
+
900
+ function buildAgentsSemanticMemorySection(newline: string): string {
901
+ // Redirect to Hivemind section
902
+ return buildAgentsHivemindSection(newline);
903
+ }
904
+
772
905
  function buildAgentsSwarmCoordinatorSection(newline: string): string {
773
906
  return [
774
907
  "## Swarm Coordinator Checklist (MANDATORY)",
@@ -935,6 +1068,53 @@ function updateAgentsMdContent({
935
1068
  changes.push("Updated bundled skills list");
936
1069
  }
937
1070
 
1071
+ // ADR-011: Migrate old tool names to hivemind
1072
+ const hasOldCassTools = /cass_search\(|cass_view\(|cass_expand\(/i.test(updated);
1073
+ const hasOldSemanticTools = /semantic-memory_store\(|semantic-memory_find\(/i.test(updated);
1074
+ const hasHivemindTools = /hivemind_store\(|hivemind_find\(/i.test(updated);
1075
+
1076
+ // If has old tools but not new hivemind tools, we need to update
1077
+ if ((hasOldCassTools || hasOldSemanticTools) && !hasHivemindTools) {
1078
+ // Replace old tool references with hivemind equivalents
1079
+ const beforeMigration = updated;
1080
+
1081
+ // Tool name replacements
1082
+ updated = updated.replace(/semantic-memory_store\(/g, "hivemind_store(");
1083
+ updated = updated.replace(/semantic-memory_find\(/g, "hivemind_find(");
1084
+ updated = updated.replace(/semantic-memory_get\(/g, "hivemind_get(");
1085
+ updated = updated.replace(/semantic-memory_remove\(/g, "hivemind_remove(");
1086
+ updated = updated.replace(/semantic-memory_validate\(/g, "hivemind_validate(");
1087
+ updated = updated.replace(/semantic-memory_list\(/g, "hivemind_find(");
1088
+ updated = updated.replace(/semantic-memory_stats\(/g, "hivemind_stats(");
1089
+ updated = updated.replace(/cass_search\(/g, "hivemind_find(");
1090
+ updated = updated.replace(/cass_view\(/g, "hivemind_get(");
1091
+ updated = updated.replace(/cass_expand\(/g, "hivemind_get(");
1092
+ updated = updated.replace(/cass_health\(/g, "hivemind_stats(");
1093
+ updated = updated.replace(/cass_index\(/g, "hivemind_index(");
1094
+ updated = updated.replace(/cass_stats\(/g, "hivemind_stats(");
1095
+
1096
+ // Table references (without parentheses)
1097
+ updated = updated.replace(/\| `semantic-memory_store` \|/g, "| `hivemind_store` |");
1098
+ updated = updated.replace(/\| `semantic-memory_find` \|/g, "| `hivemind_find` |");
1099
+ updated = updated.replace(/\| `semantic-memory_get` \|/g, "| `hivemind_get` |");
1100
+ updated = updated.replace(/\| `semantic-memory_remove` \|/g, "| `hivemind_remove` |");
1101
+ updated = updated.replace(/\| `semantic-memory_validate` \|/g, "| `hivemind_validate` |");
1102
+ updated = updated.replace(/\| `semantic-memory_list` \|/g, "| `hivemind_find` |");
1103
+ updated = updated.replace(/\| `semantic-memory_stats` \|/g, "| `hivemind_stats` |");
1104
+ updated = updated.replace(/\| `semantic-memory_migrate` \|/g, "| `hivemind_stats` |");
1105
+ updated = updated.replace(/\| `semantic-memory_check` \|/g, "| `hivemind_stats` |");
1106
+ updated = updated.replace(/\| `cass_search` \|/g, "| `hivemind_find` |");
1107
+ updated = updated.replace(/\| `cass_view` \|/g, "| `hivemind_get` |");
1108
+ updated = updated.replace(/\| `cass_expand` \|/g, "| `hivemind_get` |");
1109
+ updated = updated.replace(/\| `cass_health` \|/g, "| `hivemind_stats` |");
1110
+ updated = updated.replace(/\| `cass_index` \|/g, "| `hivemind_index` |");
1111
+ updated = updated.replace(/\| `cass_stats` \|/g, "| `hivemind_stats` |");
1112
+
1113
+ if (updated !== beforeMigration) {
1114
+ changes.push("Migrated cass_*/semantic-memory_* to hivemind_* (ADR-011)");
1115
+ }
1116
+ }
1117
+
938
1118
  // Update tool preferences block if present
939
1119
  const toolPrefsResult = updateAgentsToolPreferencesBlock(updated, newline);
940
1120
  if (toolPrefsResult.changed) {
@@ -942,14 +1122,11 @@ function updateAgentsMdContent({
942
1122
  changes.push("Updated tool_preferences tool list");
943
1123
  }
944
1124
 
945
- // Add missing sections (append at end)
1125
+ // Check for sections - now unified under Hivemind
946
1126
  const hasSkillsSection =
947
1127
  /^#{1,6}\s+Skills\b/im.test(updated) || /skills_list\(\)/.test(updated);
948
- const hasCassSection =
949
- /^#{1,6}\s+.*CASS\b/im.test(updated) || /cass_search\(/.test(updated);
950
- const hasSemanticMemorySection =
951
- /^#{1,6}\s+Semantic Memory\b/im.test(updated) ||
952
- /semantic-memory_store\(/.test(updated);
1128
+ const hasHivemindSection =
1129
+ /^#{1,6}\s+Hivemind\b/im.test(updated) || /hivemind_store\(/.test(updated);
953
1130
  const hasSwarmCoordinatorSection =
954
1131
  /^#{1,6}\s+Swarm Coordinator\b/im.test(updated) ||
955
1132
  /swarm_review\(/.test(updated) ||
@@ -962,13 +1139,9 @@ function updateAgentsMdContent({
962
1139
  );
963
1140
  changes.push("Added Skills section");
964
1141
  }
965
- if (!hasCassSection) {
966
- sectionsToAppend.push(buildAgentsCassSection(newline));
967
- changes.push("Added CASS section");
968
- }
969
- if (!hasSemanticMemorySection) {
970
- sectionsToAppend.push(buildAgentsSemanticMemorySection(newline));
971
- changes.push("Added Semantic Memory section");
1142
+ if (!hasHivemindSection) {
1143
+ sectionsToAppend.push(buildAgentsHivemindSection(newline));
1144
+ changes.push("Added Hivemind section (unified memory)");
972
1145
  }
973
1146
  if (!hasSwarmCoordinatorSection) {
974
1147
  sectionsToAppend.push(buildAgentsSwarmCoordinatorSection(newline));
@@ -1434,15 +1607,33 @@ function getFixCommand(dep: Dependency): string | null {
1434
1607
  }
1435
1608
  }
1436
1609
 
1437
- async function doctor() {
1610
+ async function doctor(debug = false) {
1438
1611
  p.intro("swarm doctor v" + VERSION);
1439
1612
 
1613
+ if (debug) {
1614
+ p.log.step("Debug info:");
1615
+ p.log.message(dim(` Runtime: ${typeof Bun !== 'undefined' ? 'Bun' : 'Node.js'}`));
1616
+ p.log.message(dim(` Node version: ${process.version}`));
1617
+ p.log.message(dim(` Platform: ${process.platform}`));
1618
+ p.log.message(dim(` Arch: ${process.arch}`));
1619
+ p.log.message(dim(` CWD: ${process.cwd()}`));
1620
+ p.log.message(dim(` PATH entries: ${(process.env.PATH || '').split(':').length}`));
1621
+ }
1622
+
1440
1623
  const s = p.spinner();
1441
1624
  s.start("Checking dependencies...");
1442
1625
 
1443
1626
  const results = await checkAllDependencies();
1444
1627
 
1445
1628
  s.stop("Dependencies checked");
1629
+
1630
+ if (debug) {
1631
+ p.log.step("Dependency check details:");
1632
+ for (const { dep, available, version } of results) {
1633
+ const status = available ? green("✓") : red("✗");
1634
+ p.log.message(dim(` ${status} ${dep.command} ${dep.checkArgs.join(" ")} → ${available ? `v${version || "unknown"}` : "not found"}`));
1635
+ }
1636
+ }
1446
1637
 
1447
1638
  const required = results.filter((r) => r.dep.required);
1448
1639
  const optional = results.filter((r) => !r.dep.required);
@@ -1571,6 +1762,23 @@ async function setup(forceReinstall = false, nonInteractive = false) {
1571
1762
 
1572
1763
  p.intro("opencode-swarm-plugin v" + VERSION);
1573
1764
 
1765
+ // CRITICAL: Check for Bun first - the CLI requires Bun runtime
1766
+ const bunCheck = await checkCommand("bun", ["--version"]);
1767
+ if (!bunCheck.available) {
1768
+ p.log.error("Bun is required but not installed!");
1769
+ console.log();
1770
+ console.log(dim(" The swarm CLI requires Bun runtime for Bun-specific APIs."));
1771
+ console.log();
1772
+ console.log(" Install Bun:");
1773
+ console.log(cyan(" curl -fsSL https://bun.sh/install | bash"));
1774
+ console.log();
1775
+ console.log(dim(" Or via Homebrew:"));
1776
+ console.log(cyan(" brew install oven-sh/bun/bun"));
1777
+ console.log();
1778
+ process.exit(1);
1779
+ }
1780
+ p.log.success(`Bun v${bunCheck.version} detected`);
1781
+
1574
1782
  // Migrate legacy database if present (do this first, before config check)
1575
1783
  const cwd = process.cwd();
1576
1784
  const tempDirName = getLibSQLProjectTempDirName(cwd);
@@ -3008,6 +3216,7 @@ ${cyan("Commands:")}
3008
3216
  swarm viz Alias for 'swarm serve' (deprecated, use serve)
3009
3217
  --port <n> Port to listen on (default: 4483)
3010
3218
  swarm cells List or get cells from database (replaces 'swarm tool hive_query')
3219
+ swarm memory Manage unified memory system (learnings + sessions)
3011
3220
  swarm log View swarm logs with filtering
3012
3221
  swarm stats Show swarm health metrics powered by swarm-insights (strategy success rates, patterns)
3013
3222
  swarm o11y Show observability health - hook coverage, event capture, session quality
@@ -3035,6 +3244,17 @@ ${cyan("Cell Management:")}
3035
3244
  swarm cells --ready Show next ready (unblocked) cell
3036
3245
  swarm cells --json Raw JSON output (array, no wrapper)
3037
3246
 
3247
+ ${cyan("Memory Management (Hivemind):")}
3248
+ swarm memory store <info> [--tags <tags>] Store a learning/memory
3249
+ swarm memory find <query> [--limit <n>] Search all memories (semantic + FTS)
3250
+ swarm memory get <id> Get specific memory by ID
3251
+ swarm memory remove <id> Delete outdated/incorrect memory
3252
+ swarm memory validate <id> Confirm accuracy (resets 90-day decay)
3253
+ swarm memory stats Show database statistics
3254
+ swarm memory index Index AI sessions (use hivemind_index tool)
3255
+ swarm memory sync Sync to .hive/memories.jsonl (use hivemind_sync tool)
3256
+ swarm memory <command> --json Output JSON for all commands
3257
+
3038
3258
  ${cyan("Log Viewing:")}
3039
3259
  swarm log Tail recent logs (last 50 lines)
3040
3260
  swarm log <module> Filter by module (e.g., compaction)
@@ -3257,7 +3477,7 @@ async function listTools() {
3257
3477
  // Agents Command - Update AGENTS.md with skill awareness
3258
3478
  // ============================================================================
3259
3479
 
3260
- async function agents() {
3480
+ async function agents(nonInteractive = false) {
3261
3481
  const home = process.env.HOME || process.env.USERPROFILE || "~";
3262
3482
  const agentsPath = join(home, ".config", "opencode", "AGENTS.md");
3263
3483
 
@@ -3273,44 +3493,70 @@ async function agents() {
3273
3493
  return;
3274
3494
  }
3275
3495
 
3276
- const confirm = await p.confirm({
3277
- message: "Update AGENTS.md with skill awareness?",
3278
- initialValue: true,
3279
- });
3496
+ if (!nonInteractive) {
3497
+ const result = await p.confirm({
3498
+ message: "Update AGENTS.md with Hivemind unification?",
3499
+ initialValue: true,
3500
+ });
3280
3501
 
3281
- if (p.isCancel(confirm) || !confirm) {
3282
- p.outro("Aborted");
3283
- return;
3502
+ if (p.isCancel(result) || !result) {
3503
+ p.outro("Aborted");
3504
+ return;
3505
+ }
3284
3506
  }
3285
3507
 
3286
3508
  const s = p.spinner();
3287
- s.start("Updating AGENTS.md with skill awareness...");
3509
+ s.start("Updating AGENTS.md via LLM...");
3288
3510
 
3289
- const bundledSkillsPath = join(__dirname, "..", "global-skills");
3290
- const bundledSkills = listDirectoryNames(bundledSkillsPath);
3511
+ const prompt = `You are updating ~/.config/opencode/AGENTS.md to unify memory tools under Hivemind (ADR-011).
3512
+
3513
+ TASK: Update the AGENTS.md file to:
3514
+
3515
+ 1. **Rename tool references** throughout the file:
3516
+ - \`cass_search\` → \`hivemind_find\`
3517
+ - \`cass_view\` → \`hivemind_get\`
3518
+ - \`cass_expand\` → \`hivemind_get\`
3519
+ - \`cass_health\` → \`hivemind_stats\`
3520
+ - \`cass_index\` → \`hivemind_index\`
3521
+ - \`cass_stats\` → \`hivemind_stats\`
3522
+ - \`semantic-memory_store\` → \`hivemind_store\`
3523
+ - \`semantic-memory_find\` → \`hivemind_find\`
3524
+ - \`semantic-memory_get\` → \`hivemind_get\`
3525
+ - \`semantic-memory_remove\` → \`hivemind_remove\`
3526
+ - \`semantic-memory_validate\` → \`hivemind_validate\`
3527
+ - \`semantic-memory_list\` → \`hivemind_find\`
3528
+ - \`semantic-memory_stats\` → \`hivemind_stats\`
3529
+
3530
+ 2. **Consolidate sections**: If there are separate "CASS" and "Semantic Memory" sections, merge them into a single "Hivemind - Unified Memory System" section that covers:
3531
+ - Unified storage (learnings + sessions in same DB)
3532
+ - All hivemind_* tools with descriptions
3533
+ - Usage examples showing both storing learnings and searching sessions
3534
+ - The 90-day decay and validation workflow
3535
+
3536
+ 3. **Update any prose** that mentions "CASS" or "semantic memory" separately to use "Hivemind" terminology.
3537
+
3538
+ 4. **Keep existing structure** - don't reorganize unrelated sections.
3539
+
3540
+ Read the file, make the updates, and save it. Create a backup first.`;
3291
3541
 
3292
3542
  try {
3293
- const bundledSkillsCsv =
3294
- bundledSkills.length > 0
3295
- ? bundledSkills.join(", ")
3296
- : "cli-builder, learning-systems, skill-creator, swarm-coordination, system-design, testing-patterns";
3297
-
3298
- const result = updateAgentsMdFile({ agentsPath, bundledSkillsCsv });
3299
-
3300
- if (result.changed) {
3301
- s.stop("AGENTS.md updated with skill awareness");
3302
- p.log.success("Skills section added to " + agentsPath);
3303
- p.log.message(
3304
- dim("Skills available: skills_list, skills_use, skills_read"),
3305
- );
3306
- if (result.backupPath) {
3307
- p.log.message(dim("Backup: " + result.backupPath));
3308
- }
3543
+ const proc = Bun.spawn(["opencode", "run", prompt], {
3544
+ stdio: ["inherit", "pipe", "pipe"],
3545
+ cwd: home,
3546
+ });
3547
+
3548
+ const exitCode = await proc.exited;
3549
+
3550
+ if (exitCode === 0) {
3551
+ s.stop("AGENTS.md updated via LLM");
3552
+ p.log.success("Hivemind unification complete");
3309
3553
  } else {
3310
- s.stop("AGENTS.md already up to date");
3554
+ const stderr = await new Response(proc.stderr).text();
3555
+ s.stop("LLM update failed");
3556
+ p.log.error(stderr || `Exit code: ${exitCode}`);
3311
3557
  }
3312
3558
  } catch (error) {
3313
- s.stop("Failed to update AGENTS.md");
3559
+ s.stop("Failed to run opencode");
3314
3560
  p.log.error(String(error));
3315
3561
  }
3316
3562
 
@@ -5269,6 +5515,319 @@ async function capture() {
5269
5515
  }
5270
5516
  }
5271
5517
 
5518
+ // ============================================================================
5519
+ // Memory Commands
5520
+ // ============================================================================
5521
+
5522
+ /**
5523
+ * Parse args for memory commands
5524
+ */
5525
+ function parseMemoryArgs(subcommand: string, args: string[]): {
5526
+ json: boolean;
5527
+ info?: string;
5528
+ query?: string;
5529
+ id?: string;
5530
+ tags?: string;
5531
+ limit?: number;
5532
+ collection?: string;
5533
+ } {
5534
+ let json = false;
5535
+ let info: string | undefined;
5536
+ let query: string | undefined;
5537
+ let id: string | undefined;
5538
+ let tags: string | undefined;
5539
+ let limit: number | undefined;
5540
+ let collection: string | undefined;
5541
+
5542
+ // First positional arg for store/find/get/remove/validate
5543
+ if (args.length > 0 && !args[0].startsWith("--")) {
5544
+ if (subcommand === "store") {
5545
+ info = args[0];
5546
+ } else if (subcommand === "find") {
5547
+ query = args[0];
5548
+ } else if (subcommand === "get" || subcommand === "remove" || subcommand === "validate") {
5549
+ id = args[0];
5550
+ }
5551
+ }
5552
+
5553
+ for (let i = 0; i < args.length; i++) {
5554
+ const arg = args[i];
5555
+ if (arg === "--json") {
5556
+ json = true;
5557
+ } else if (arg === "--tags" && i + 1 < args.length) {
5558
+ tags = args[++i];
5559
+ } else if (arg === "--limit" && i + 1 < args.length) {
5560
+ const val = parseInt(args[++i], 10);
5561
+ if (!isNaN(val)) limit = val;
5562
+ } else if (arg === "--collection" && i + 1 < args.length) {
5563
+ collection = args[++i];
5564
+ }
5565
+ }
5566
+
5567
+ return { json, info, query, id, tags, limit, collection };
5568
+ }
5569
+
5570
+ /**
5571
+ * Memory command - unified interface to memory operations
5572
+ *
5573
+ * Commands:
5574
+ * swarm memory store <info> [--tags <tags>]
5575
+ * swarm memory find <query> [--limit <n>] [--collection <name>]
5576
+ * swarm memory get <id>
5577
+ * swarm memory remove <id>
5578
+ * swarm memory validate <id>
5579
+ * swarm memory stats
5580
+ * swarm memory index
5581
+ * swarm memory sync
5582
+ */
5583
+ async function memory() {
5584
+ const subcommand = process.argv[3];
5585
+ const args = process.argv.slice(4);
5586
+ const parsed = parseMemoryArgs(subcommand, args);
5587
+
5588
+ // Get project path for libSQL database
5589
+ const projectPath = process.cwd();
5590
+
5591
+ try {
5592
+ // Get database instance using getDb from swarm-mail
5593
+ // This returns a drizzle instance (SwarmDb) that memory adapter expects
5594
+ const { getDb } = await import("swarm-mail");
5595
+
5596
+ // Calculate DB path (same logic as libsql.convenience.ts)
5597
+ const tempDirName = getLibSQLProjectTempDirName(projectPath);
5598
+ const tempDir = join(tmpdir(), tempDirName);
5599
+
5600
+ // Ensure temp directory exists
5601
+ if (!existsSync(tempDir)) {
5602
+ mkdirSync(tempDir, { recursive: true });
5603
+ }
5604
+
5605
+ const dbPath = join(tempDir, "streams.db");
5606
+
5607
+ // Convert to file:// URL (required by libSQL)
5608
+ const dbUrl = `file://${dbPath}`;
5609
+
5610
+ const db = await getDb(dbUrl);
5611
+
5612
+ // Create memory adapter with default Ollama config
5613
+ const { createMemoryAdapter } = await import("swarm-mail");
5614
+ const adapter = createMemoryAdapter(db, {
5615
+ ollamaHost: process.env.OLLAMA_HOST || "http://localhost:11434",
5616
+ ollamaModel: process.env.OLLAMA_MODEL || "mxbai-embed-large",
5617
+ });
5618
+
5619
+ switch (subcommand) {
5620
+ case "store": {
5621
+ if (!parsed.info) {
5622
+ console.error("Usage: swarm memory store <information> [--tags <tags>]");
5623
+ process.exit(1);
5624
+ }
5625
+
5626
+ const result = await adapter.store(parsed.info, {
5627
+ tags: parsed.tags,
5628
+ collection: parsed.collection || "default",
5629
+ });
5630
+
5631
+ if (parsed.json) {
5632
+ console.log(JSON.stringify({ success: true, id: result.id }));
5633
+ } else {
5634
+ p.intro("swarm memory store");
5635
+ p.log.success(`Stored memory: ${result.id}`);
5636
+ if (result.autoTags) {
5637
+ p.log.message(`Auto-tags: ${result.autoTags.tags.join(", ")}`);
5638
+ }
5639
+ p.outro("Done");
5640
+ }
5641
+ break;
5642
+ }
5643
+
5644
+ case "find": {
5645
+ if (!parsed.query) {
5646
+ console.error("Usage: swarm memory find <query> [--limit <n>] [--collection <name>]");
5647
+ process.exit(1);
5648
+ }
5649
+
5650
+ const results = await adapter.find(parsed.query, {
5651
+ limit: parsed.limit || 10,
5652
+ collection: parsed.collection,
5653
+ });
5654
+
5655
+ if (parsed.json) {
5656
+ console.log(JSON.stringify({ success: true, results }));
5657
+ } else {
5658
+ p.intro(`swarm memory find: "${parsed.query}"`);
5659
+ if (results.length === 0) {
5660
+ p.log.warn("No memories found");
5661
+ } else {
5662
+ for (const result of results) {
5663
+ console.log();
5664
+ console.log(cyan(`[${result.memory.id}] Score: ${result.score.toFixed(3)}`));
5665
+ console.log(dim(` Created: ${new Date(result.memory.createdAt).toLocaleDateString()}`));
5666
+ console.log(` ${result.memory.content.slice(0, 200)}${result.memory.content.length > 200 ? "..." : ""}`);
5667
+ if (result.memory.metadata.tags) {
5668
+ console.log(dim(` Tags: ${(result.memory.metadata.tags as string[]).join(", ")}`));
5669
+ }
5670
+ }
5671
+ }
5672
+ p.outro(`Found ${results.length} result(s)`);
5673
+ }
5674
+ break;
5675
+ }
5676
+
5677
+ case "get": {
5678
+ if (!parsed.id) {
5679
+ console.error("Usage: swarm memory get <id>");
5680
+ process.exit(1);
5681
+ }
5682
+
5683
+ const memory = await adapter.get(parsed.id);
5684
+
5685
+ if (parsed.json) {
5686
+ if (memory) {
5687
+ console.log(JSON.stringify({ success: true, memory }));
5688
+ } else {
5689
+ console.log(JSON.stringify({ success: false, error: "Memory not found" }));
5690
+ process.exit(1);
5691
+ }
5692
+ } else {
5693
+ p.intro(`swarm memory get: ${parsed.id}`);
5694
+ if (!memory) {
5695
+ p.log.error("Memory not found");
5696
+ p.outro("Aborted");
5697
+ process.exit(1);
5698
+ } else {
5699
+ console.log();
5700
+ console.log(cyan("Content:"));
5701
+ console.log(memory.content);
5702
+ console.log();
5703
+ console.log(dim(`Created: ${new Date(memory.createdAt).toLocaleDateString()}`));
5704
+ console.log(dim(`Collection: ${memory.collection}`));
5705
+ console.log(dim(`Confidence: ${memory.confidence ?? 0.7}`));
5706
+ if (memory.metadata.tags) {
5707
+ console.log(dim(`Tags: ${(memory.metadata.tags as string[]).join(", ")}`));
5708
+ }
5709
+ p.outro("Done");
5710
+ }
5711
+ }
5712
+ break;
5713
+ }
5714
+
5715
+ case "remove": {
5716
+ if (!parsed.id) {
5717
+ console.error("Usage: swarm memory remove <id>");
5718
+ process.exit(1);
5719
+ }
5720
+
5721
+ await adapter.remove(parsed.id);
5722
+
5723
+ if (parsed.json) {
5724
+ console.log(JSON.stringify({ success: true }));
5725
+ } else {
5726
+ p.intro("swarm memory remove");
5727
+ p.log.success(`Removed memory: ${parsed.id}`);
5728
+ p.outro("Done");
5729
+ }
5730
+ break;
5731
+ }
5732
+
5733
+ case "validate": {
5734
+ if (!parsed.id) {
5735
+ console.error("Usage: swarm memory validate <id>");
5736
+ process.exit(1);
5737
+ }
5738
+
5739
+ await adapter.validate(parsed.id);
5740
+
5741
+ if (parsed.json) {
5742
+ console.log(JSON.stringify({ success: true }));
5743
+ } else {
5744
+ p.intro("swarm memory validate");
5745
+ p.log.success(`Validated memory: ${parsed.id} (decay timer reset)`);
5746
+ p.outro("Done");
5747
+ }
5748
+ break;
5749
+ }
5750
+
5751
+ case "stats": {
5752
+ const stats = await adapter.stats();
5753
+
5754
+ if (parsed.json) {
5755
+ console.log(JSON.stringify({ success: true, stats }));
5756
+ } else {
5757
+ p.intro("swarm memory stats");
5758
+ console.log();
5759
+ console.log(cyan("Database Statistics:"));
5760
+ console.log(` Memories: ${stats.memories}`);
5761
+ console.log(` Embeddings: ${stats.embeddings}`);
5762
+ p.outro("Done");
5763
+ }
5764
+ break;
5765
+ }
5766
+
5767
+ case "index": {
5768
+ // Index is a stub - actual indexing happens via session indexing
5769
+ // which is handled by hivemind_index tool
5770
+ if (parsed.json) {
5771
+ console.log(JSON.stringify({ success: true, message: "Use hivemind_index tool for session indexing" }));
5772
+ } else {
5773
+ p.intro("swarm memory index");
5774
+ p.log.message("Session indexing is handled by the hivemind_index tool");
5775
+ p.log.message("Use: swarm tool hivemind_index");
5776
+ p.outro("Done");
5777
+ }
5778
+ break;
5779
+ }
5780
+
5781
+ case "sync": {
5782
+ // Sync is a stub - actual sync happens via .hive/memories.jsonl
5783
+ // which is handled by hivemind_sync tool
5784
+ if (parsed.json) {
5785
+ console.log(JSON.stringify({ success: true, message: "Use hivemind_sync tool for git sync" }));
5786
+ } else {
5787
+ p.intro("swarm memory sync");
5788
+ p.log.message("Memory sync to .hive/memories.jsonl is handled by the hivemind_sync tool");
5789
+ p.log.message("Use: swarm tool hivemind_sync");
5790
+ p.outro("Done");
5791
+ }
5792
+ break;
5793
+ }
5794
+
5795
+ default: {
5796
+ console.error(`Unknown subcommand: ${subcommand}`);
5797
+ console.error("");
5798
+ console.error("Usage: swarm memory <subcommand> [options]");
5799
+ console.error("");
5800
+ console.error("Subcommands:");
5801
+ console.error(" store <info> [--tags <tags>] Store a memory");
5802
+ console.error(" find <query> [--limit <n>] Search memories");
5803
+ console.error(" get <id> Get memory by ID");
5804
+ console.error(" remove <id> Delete memory");
5805
+ console.error(" validate <id> Reset decay timer");
5806
+ console.error(" stats Show database stats");
5807
+ console.error(" index Index sessions (use hivemind_index)");
5808
+ console.error(" sync Sync to git (use hivemind_sync)");
5809
+ console.error("");
5810
+ console.error("Global options:");
5811
+ console.error(" --json Output JSON");
5812
+ process.exit(1);
5813
+ }
5814
+ }
5815
+ } catch (error) {
5816
+ if (parsed.json) {
5817
+ console.log(JSON.stringify({
5818
+ success: false,
5819
+ error: error instanceof Error ? error.message : String(error),
5820
+ }));
5821
+ process.exit(1);
5822
+ } else {
5823
+ p.log.error("Memory operation failed");
5824
+ p.log.message(error instanceof Error ? error.message : String(error));
5825
+ p.outro("Aborted");
5826
+ process.exit(1);
5827
+ }
5828
+ }
5829
+ }
5830
+
5272
5831
  // ============================================================================
5273
5832
  // Main
5274
5833
  // ============================================================================
@@ -5282,9 +5841,11 @@ switch (command) {
5282
5841
  await setup(reinstallFlag || yesFlag, yesFlag);
5283
5842
  break;
5284
5843
  }
5285
- case "doctor":
5286
- await doctor();
5844
+ case "doctor": {
5845
+ const debugFlag = process.argv.includes("--debug") || process.argv.includes("-d");
5846
+ await doctor(debugFlag);
5287
5847
  break;
5848
+ }
5288
5849
  case "init":
5289
5850
  await init();
5290
5851
  break;
@@ -5313,9 +5874,11 @@ switch (command) {
5313
5874
  }
5314
5875
  break;
5315
5876
  }
5316
- case "agents":
5317
- await agents();
5877
+ case "agents": {
5878
+ const agentsNonInteractive = process.argv.includes("--yes") || process.argv.includes("-y");
5879
+ await agents(agentsNonInteractive);
5318
5880
  break;
5881
+ }
5319
5882
  case "migrate":
5320
5883
  await migrate();
5321
5884
  break;
@@ -5344,6 +5907,9 @@ switch (command) {
5344
5907
  case "capture":
5345
5908
  await capture();
5346
5909
  break;
5910
+ case "memory":
5911
+ await memory();
5912
+ break;
5347
5913
  case "query":
5348
5914
  await query();
5349
5915
  break;