opencode-swarm-plugin 0.43.0 → 0.44.1

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.
Files changed (208) hide show
  1. package/bin/cass.characterization.test.ts +422 -0
  2. package/bin/swarm.serve.test.ts +6 -4
  3. package/bin/swarm.test.ts +68 -0
  4. package/bin/swarm.ts +81 -8
  5. package/dist/compaction-prompt-scoring.js +139 -0
  6. package/dist/contributor-tools.d.ts +42 -0
  7. package/dist/contributor-tools.d.ts.map +1 -0
  8. package/dist/eval-capture.js +12811 -0
  9. package/dist/hive.d.ts.map +1 -1
  10. package/dist/index.d.ts +12 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +7728 -62590
  13. package/dist/plugin.js +23833 -78695
  14. package/dist/sessions/agent-discovery.d.ts +59 -0
  15. package/dist/sessions/agent-discovery.d.ts.map +1 -0
  16. package/dist/sessions/index.d.ts +10 -0
  17. package/dist/sessions/index.d.ts.map +1 -0
  18. package/dist/swarm-orchestrate.d.ts.map +1 -1
  19. package/dist/swarm-prompts.d.ts.map +1 -1
  20. package/dist/swarm-review.d.ts.map +1 -1
  21. package/package.json +17 -5
  22. package/.changeset/swarm-insights-data-layer.md +0 -63
  23. package/.hive/analysis/eval-failure-analysis-2025-12-25.md +0 -331
  24. package/.hive/analysis/session-data-quality-audit.md +0 -320
  25. package/.hive/eval-results.json +0 -483
  26. package/.hive/issues.jsonl +0 -138
  27. package/.hive/memories.jsonl +0 -729
  28. package/.opencode/eval-history.jsonl +0 -327
  29. package/.turbo/turbo-build.log +0 -9
  30. package/CHANGELOG.md +0 -2255
  31. package/SCORER-ANALYSIS.md +0 -598
  32. package/docs/analysis/subagent-coordination-patterns.md +0 -902
  33. package/docs/analysis-socratic-planner-pattern.md +0 -504
  34. package/docs/planning/ADR-001-monorepo-structure.md +0 -171
  35. package/docs/planning/ADR-002-package-extraction.md +0 -393
  36. package/docs/planning/ADR-003-performance-improvements.md +0 -451
  37. package/docs/planning/ADR-004-message-queue-features.md +0 -187
  38. package/docs/planning/ADR-005-devtools-observability.md +0 -202
  39. package/docs/planning/ADR-007-swarm-enhancements-worktree-review.md +0 -168
  40. package/docs/planning/ADR-008-worker-handoff-protocol.md +0 -293
  41. package/docs/planning/ADR-009-oh-my-opencode-patterns.md +0 -353
  42. package/docs/planning/ROADMAP.md +0 -368
  43. package/docs/semantic-memory-cli-syntax.md +0 -123
  44. package/docs/swarm-mail-architecture.md +0 -1147
  45. package/docs/testing/context-recovery-test.md +0 -470
  46. package/evals/ARCHITECTURE.md +0 -1189
  47. package/evals/README.md +0 -768
  48. package/evals/compaction-prompt.eval.ts +0 -149
  49. package/evals/compaction-resumption.eval.ts +0 -289
  50. package/evals/coordinator-behavior.eval.ts +0 -307
  51. package/evals/coordinator-session.eval.ts +0 -154
  52. package/evals/evalite.config.ts.bak +0 -15
  53. package/evals/example.eval.ts +0 -31
  54. package/evals/fixtures/compaction-cases.ts +0 -350
  55. package/evals/fixtures/compaction-prompt-cases.ts +0 -311
  56. package/evals/fixtures/coordinator-sessions.ts +0 -328
  57. package/evals/fixtures/decomposition-cases.ts +0 -105
  58. package/evals/lib/compaction-loader.test.ts +0 -248
  59. package/evals/lib/compaction-loader.ts +0 -320
  60. package/evals/lib/data-loader.evalite-test.ts +0 -289
  61. package/evals/lib/data-loader.test.ts +0 -345
  62. package/evals/lib/data-loader.ts +0 -281
  63. package/evals/lib/llm.ts +0 -115
  64. package/evals/scorers/compaction-prompt-scorers.ts +0 -145
  65. package/evals/scorers/compaction-scorers.ts +0 -305
  66. package/evals/scorers/coordinator-discipline.evalite-test.ts +0 -539
  67. package/evals/scorers/coordinator-discipline.ts +0 -325
  68. package/evals/scorers/index.test.ts +0 -146
  69. package/evals/scorers/index.ts +0 -328
  70. package/evals/scorers/outcome-scorers.evalite-test.ts +0 -27
  71. package/evals/scorers/outcome-scorers.ts +0 -349
  72. package/evals/swarm-decomposition.eval.ts +0 -121
  73. package/examples/commands/swarm.md +0 -745
  74. package/examples/plugin-wrapper-template.ts +0 -2426
  75. package/examples/skills/hive-workflow/SKILL.md +0 -212
  76. package/examples/skills/skill-creator/SKILL.md +0 -223
  77. package/examples/skills/swarm-coordination/SKILL.md +0 -292
  78. package/global-skills/cli-builder/SKILL.md +0 -344
  79. package/global-skills/cli-builder/references/advanced-patterns.md +0 -244
  80. package/global-skills/learning-systems/SKILL.md +0 -644
  81. package/global-skills/skill-creator/LICENSE.txt +0 -202
  82. package/global-skills/skill-creator/SKILL.md +0 -352
  83. package/global-skills/skill-creator/references/output-patterns.md +0 -82
  84. package/global-skills/skill-creator/references/workflows.md +0 -28
  85. package/global-skills/swarm-coordination/SKILL.md +0 -995
  86. package/global-skills/swarm-coordination/references/coordinator-patterns.md +0 -235
  87. package/global-skills/swarm-coordination/references/strategies.md +0 -138
  88. package/global-skills/system-design/SKILL.md +0 -213
  89. package/global-skills/testing-patterns/SKILL.md +0 -430
  90. package/global-skills/testing-patterns/references/dependency-breaking-catalog.md +0 -586
  91. package/opencode-swarm-plugin-0.30.7.tgz +0 -0
  92. package/opencode-swarm-plugin-0.31.0.tgz +0 -0
  93. package/scripts/cleanup-test-memories.ts +0 -346
  94. package/scripts/init-skill.ts +0 -222
  95. package/scripts/migrate-unknown-sessions.ts +0 -349
  96. package/scripts/validate-skill.ts +0 -204
  97. package/src/agent-mail.ts +0 -1724
  98. package/src/anti-patterns.test.ts +0 -1167
  99. package/src/anti-patterns.ts +0 -448
  100. package/src/compaction-capture.integration.test.ts +0 -257
  101. package/src/compaction-hook.test.ts +0 -838
  102. package/src/compaction-hook.ts +0 -1204
  103. package/src/compaction-observability.integration.test.ts +0 -139
  104. package/src/compaction-observability.test.ts +0 -187
  105. package/src/compaction-observability.ts +0 -324
  106. package/src/compaction-prompt-scorers.test.ts +0 -475
  107. package/src/compaction-prompt-scoring.ts +0 -300
  108. package/src/dashboard.test.ts +0 -611
  109. package/src/dashboard.ts +0 -462
  110. package/src/error-enrichment.test.ts +0 -403
  111. package/src/error-enrichment.ts +0 -219
  112. package/src/eval-capture.test.ts +0 -1015
  113. package/src/eval-capture.ts +0 -929
  114. package/src/eval-gates.test.ts +0 -306
  115. package/src/eval-gates.ts +0 -218
  116. package/src/eval-history.test.ts +0 -508
  117. package/src/eval-history.ts +0 -214
  118. package/src/eval-learning.test.ts +0 -378
  119. package/src/eval-learning.ts +0 -360
  120. package/src/eval-runner.test.ts +0 -223
  121. package/src/eval-runner.ts +0 -402
  122. package/src/export-tools.test.ts +0 -476
  123. package/src/export-tools.ts +0 -257
  124. package/src/hive.integration.test.ts +0 -2241
  125. package/src/hive.ts +0 -1628
  126. package/src/index.ts +0 -935
  127. package/src/learning.integration.test.ts +0 -1815
  128. package/src/learning.ts +0 -1079
  129. package/src/logger.test.ts +0 -189
  130. package/src/logger.ts +0 -135
  131. package/src/mandate-promotion.test.ts +0 -473
  132. package/src/mandate-promotion.ts +0 -239
  133. package/src/mandate-storage.integration.test.ts +0 -601
  134. package/src/mandate-storage.test.ts +0 -578
  135. package/src/mandate-storage.ts +0 -794
  136. package/src/mandates.ts +0 -540
  137. package/src/memory-tools.test.ts +0 -195
  138. package/src/memory-tools.ts +0 -344
  139. package/src/memory.integration.test.ts +0 -334
  140. package/src/memory.test.ts +0 -158
  141. package/src/memory.ts +0 -527
  142. package/src/model-selection.test.ts +0 -188
  143. package/src/model-selection.ts +0 -68
  144. package/src/observability-tools.test.ts +0 -359
  145. package/src/observability-tools.ts +0 -871
  146. package/src/output-guardrails.test.ts +0 -438
  147. package/src/output-guardrails.ts +0 -381
  148. package/src/pattern-maturity.test.ts +0 -1160
  149. package/src/pattern-maturity.ts +0 -525
  150. package/src/planning-guardrails.test.ts +0 -491
  151. package/src/planning-guardrails.ts +0 -438
  152. package/src/plugin.ts +0 -23
  153. package/src/post-compaction-tracker.test.ts +0 -251
  154. package/src/post-compaction-tracker.ts +0 -237
  155. package/src/query-tools.test.ts +0 -636
  156. package/src/query-tools.ts +0 -324
  157. package/src/rate-limiter.integration.test.ts +0 -466
  158. package/src/rate-limiter.ts +0 -774
  159. package/src/replay-tools.test.ts +0 -496
  160. package/src/replay-tools.ts +0 -240
  161. package/src/repo-crawl.integration.test.ts +0 -441
  162. package/src/repo-crawl.ts +0 -610
  163. package/src/schemas/cell-events.test.ts +0 -347
  164. package/src/schemas/cell-events.ts +0 -807
  165. package/src/schemas/cell.ts +0 -257
  166. package/src/schemas/evaluation.ts +0 -166
  167. package/src/schemas/index.test.ts +0 -199
  168. package/src/schemas/index.ts +0 -286
  169. package/src/schemas/mandate.ts +0 -232
  170. package/src/schemas/swarm-context.ts +0 -115
  171. package/src/schemas/task.ts +0 -161
  172. package/src/schemas/worker-handoff.test.ts +0 -302
  173. package/src/schemas/worker-handoff.ts +0 -131
  174. package/src/skills.integration.test.ts +0 -1192
  175. package/src/skills.test.ts +0 -643
  176. package/src/skills.ts +0 -1549
  177. package/src/storage.integration.test.ts +0 -341
  178. package/src/storage.ts +0 -884
  179. package/src/structured.integration.test.ts +0 -817
  180. package/src/structured.test.ts +0 -1046
  181. package/src/structured.ts +0 -762
  182. package/src/swarm-decompose.test.ts +0 -188
  183. package/src/swarm-decompose.ts +0 -1302
  184. package/src/swarm-deferred.integration.test.ts +0 -157
  185. package/src/swarm-deferred.test.ts +0 -38
  186. package/src/swarm-insights.test.ts +0 -214
  187. package/src/swarm-insights.ts +0 -459
  188. package/src/swarm-mail.integration.test.ts +0 -970
  189. package/src/swarm-mail.ts +0 -739
  190. package/src/swarm-orchestrate.integration.test.ts +0 -282
  191. package/src/swarm-orchestrate.test.ts +0 -548
  192. package/src/swarm-orchestrate.ts +0 -3084
  193. package/src/swarm-prompts.test.ts +0 -1270
  194. package/src/swarm-prompts.ts +0 -2077
  195. package/src/swarm-research.integration.test.ts +0 -701
  196. package/src/swarm-research.test.ts +0 -698
  197. package/src/swarm-research.ts +0 -472
  198. package/src/swarm-review.integration.test.ts +0 -285
  199. package/src/swarm-review.test.ts +0 -879
  200. package/src/swarm-review.ts +0 -709
  201. package/src/swarm-strategies.ts +0 -407
  202. package/src/swarm-worktree.test.ts +0 -501
  203. package/src/swarm-worktree.ts +0 -575
  204. package/src/swarm.integration.test.ts +0 -2377
  205. package/src/swarm.ts +0 -38
  206. package/src/tool-adapter.integration.test.ts +0 -1221
  207. package/src/tool-availability.ts +0 -461
  208. package/tsconfig.json +0 -28
@@ -0,0 +1,422 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CASS Binary Characterization Tests
4
+ *
5
+ * These tests capture the CURRENT behavior of the CASS binary tools.
6
+ * They document WHAT the binary DOES, not what it SHOULD do.
7
+ *
8
+ * Purpose: Enable safe refactoring during ADR-010 (CASS inhousing).
9
+ * Our inhouse implementation must match these behaviors exactly.
10
+ *
11
+ * Pattern: Feathers Characterization Testing
12
+ * 1. Write a test you KNOW will fail
13
+ * 2. Run it - let the failure tell you actual behavior
14
+ * 3. Change the test to expect actual behavior
15
+ * 4. Repeat until you've characterized the code
16
+ *
17
+ * DO NOT modify these tests to match desired behavior.
18
+ * These are BASELINE tests - they verify behaviors ARE present.
19
+ */
20
+ import { describe, test, expect } from "bun:test";
21
+ import { $ } from "bun";
22
+ import {
23
+ cassStatsBaseline,
24
+ cassSearchBaseline,
25
+ cassHealthHumanBaseline,
26
+ cassStatsHumanBaseline,
27
+ cassViewBaseline,
28
+ cassErrorBaseline,
29
+ type CassStatsResponse,
30
+ type CassSearchResponse,
31
+ } from "../evals/fixtures/cass-baseline.ts";
32
+
33
+ describe("CASS Binary - cass stats", () => {
34
+ test("JSON output structure matches baseline", async () => {
35
+ // CHARACTERIZATION: This documents the actual JSON structure
36
+ const result = await $`cass stats --json`.quiet().json();
37
+
38
+ // Verify top-level structure
39
+ expect(result).toHaveProperty("by_agent");
40
+ expect(result).toHaveProperty("conversations");
41
+ expect(result).toHaveProperty("date_range");
42
+ expect(result).toHaveProperty("db_path");
43
+ expect(result).toHaveProperty("messages");
44
+ expect(result).toHaveProperty("top_workspaces");
45
+
46
+ // Verify by_agent structure
47
+ expect(Array.isArray(result.by_agent)).toBe(true);
48
+ if (result.by_agent.length > 0) {
49
+ const firstAgent = result.by_agent[0];
50
+ expect(firstAgent).toHaveProperty("agent");
51
+ expect(firstAgent).toHaveProperty("count");
52
+ expect(typeof firstAgent.agent).toBe("string");
53
+ expect(typeof firstAgent.count).toBe("number");
54
+ }
55
+
56
+ // Verify date_range structure
57
+ expect(result.date_range).toHaveProperty("newest");
58
+ expect(result.date_range).toHaveProperty("oldest");
59
+ expect(typeof result.date_range.newest).toBe("string");
60
+ expect(typeof result.date_range.oldest).toBe("string");
61
+
62
+ // Verify top_workspaces structure
63
+ expect(Array.isArray(result.top_workspaces)).toBe(true);
64
+ if (result.top_workspaces.length > 0) {
65
+ const firstWorkspace = result.top_workspaces[0];
66
+ expect(firstWorkspace).toHaveProperty("count");
67
+ expect(firstWorkspace).toHaveProperty("workspace");
68
+ expect(typeof firstWorkspace.count).toBe("number");
69
+ expect(typeof firstWorkspace.workspace).toBe("string");
70
+ }
71
+
72
+ // Verify numeric fields are numbers
73
+ expect(typeof result.conversations).toBe("number");
74
+ expect(typeof result.messages).toBe("number");
75
+ });
76
+
77
+ test("human-readable output format matches baseline", async () => {
78
+ // CHARACTERIZATION: This documents the actual human-readable format
79
+ const result = await $`cass stats`.quiet().text();
80
+
81
+ // Verify presence of key sections (order matters)
82
+ expect(result).toContain("CASS Index Statistics");
83
+ expect(result).toContain("Database:");
84
+ expect(result).toContain("Totals:");
85
+ expect(result).toContain("Conversations:");
86
+ expect(result).toContain("Messages:");
87
+ expect(result).toContain("By Agent:");
88
+ expect(result).toContain("Top Workspaces:");
89
+ expect(result).toContain("Date Range:");
90
+
91
+ // Verify format patterns
92
+ expect(result).toMatch(/Conversations: \d+/);
93
+ expect(result).toMatch(/Messages: \d+/);
94
+ expect(result).toMatch(/\w+: \d+/); // Agent counts
95
+ });
96
+ });
97
+
98
+ describe("CASS Binary - cass search", () => {
99
+ test("JSON output structure matches baseline", async () => {
100
+ // CHARACTERIZATION: This documents the actual search response structure
101
+ const result = await $`cass search "test" --limit 2 --json`.quiet().json();
102
+
103
+ // Verify top-level structure
104
+ expect(result).toHaveProperty("count");
105
+ expect(result).toHaveProperty("cursor");
106
+ expect(result).toHaveProperty("hits");
107
+ expect(result).toHaveProperty("hits_clamped");
108
+ expect(result).toHaveProperty("limit");
109
+ expect(result).toHaveProperty("max_tokens");
110
+ expect(result).toHaveProperty("offset");
111
+ expect(result).toHaveProperty("query");
112
+ expect(result).toHaveProperty("request_id");
113
+ expect(result).toHaveProperty("total_matches");
114
+
115
+ // Verify types
116
+ expect(typeof result.count).toBe("number");
117
+ expect(typeof result.hits_clamped).toBe("boolean");
118
+ expect(typeof result.limit).toBe("number");
119
+ expect(typeof result.offset).toBe("number");
120
+ expect(typeof result.query).toBe("string");
121
+ expect(typeof result.total_matches).toBe("number");
122
+ expect(Array.isArray(result.hits)).toBe(true);
123
+
124
+ // Verify hit structure (if any hits returned)
125
+ if (result.hits.length > 0) {
126
+ const firstHit = result.hits[0];
127
+ expect(firstHit).toHaveProperty("agent");
128
+ expect(firstHit).toHaveProperty("content");
129
+ expect(firstHit).toHaveProperty("created_at");
130
+ expect(firstHit).toHaveProperty("line_number");
131
+ expect(firstHit).toHaveProperty("match_type");
132
+ expect(firstHit).toHaveProperty("score");
133
+ expect(firstHit).toHaveProperty("snippet");
134
+ expect(firstHit).toHaveProperty("source_path");
135
+ expect(firstHit).toHaveProperty("title");
136
+ expect(firstHit).toHaveProperty("workspace");
137
+
138
+ expect(typeof firstHit.agent).toBe("string");
139
+ expect(typeof firstHit.content).toBe("string");
140
+ expect(typeof firstHit.created_at).toBe("number");
141
+ expect(typeof firstHit.line_number).toBe("number");
142
+ expect(typeof firstHit.match_type).toBe("string");
143
+ expect(typeof firstHit.score).toBe("number");
144
+ expect(typeof firstHit.snippet).toBe("string");
145
+ expect(typeof firstHit.source_path).toBe("string");
146
+ expect(typeof firstHit.title).toBe("string");
147
+ expect(typeof firstHit.workspace).toBe("string");
148
+ }
149
+ });
150
+
151
+ test("query parameter is preserved in response", async () => {
152
+ // CHARACTERIZATION: Query echoed back in response
153
+ const testQuery = "characterization-test-query-12345";
154
+ const result = await $`cass search "${testQuery}" --json`.quiet().json();
155
+
156
+ expect(result.query).toBe(testQuery);
157
+ });
158
+
159
+ test("limit parameter is respected", async () => {
160
+ // CHARACTERIZATION: Limit controls max hits returned
161
+ const result = await $`cass search "test" --limit 3 --json`.quiet().json();
162
+
163
+ expect(result.limit).toBe(3);
164
+ if (result.hits.length > 0) {
165
+ expect(result.hits.length).toBeLessThanOrEqual(3);
166
+ }
167
+ });
168
+
169
+ test("empty results include suggestions field", async () => {
170
+ // CHARACTERIZATION: Empty results return suggestions
171
+ const result =
172
+ await $`cass search "xyzzy-nonexistent-term-99999" --json`.quiet().json();
173
+
174
+ if (result.total_matches === 0) {
175
+ // Empty results may include suggestions (optional feature)
176
+ // Just verify structure if present
177
+ if (result.suggestions) {
178
+ expect(Array.isArray(result.suggestions)).toBe(true);
179
+ }
180
+ }
181
+ });
182
+ });
183
+
184
+ describe("CASS Binary - cass health", () => {
185
+ test("health check returns status indicator", async () => {
186
+ // CHARACTERIZATION: Health check outputs status with timing
187
+ const result = await $`cass health`.quiet().text();
188
+
189
+ // Should contain status indicator (✓ or ✗)
190
+ const hasHealthyIndicator = result.includes("✓ Healthy");
191
+ const hasUnhealthyIndicator = result.includes("✗");
192
+
193
+ expect(hasHealthyIndicator || hasUnhealthyIndicator).toBe(true);
194
+
195
+ // Should include timing information
196
+ if (hasHealthyIndicator) {
197
+ expect(result).toMatch(/\(\d+ms\)/);
198
+ }
199
+ });
200
+
201
+ test("health check may include staleness note", async () => {
202
+ // CHARACTERIZATION: May warn about stale index
203
+ const result = await $`cass health`.quiet().text();
204
+
205
+ // If index is stale, should mention it
206
+ // This is conditional - test just verifies format if present
207
+ if (result.includes("stale")) {
208
+ expect(result).toContain("Note:");
209
+ expect(result).toMatch(/older than \d+s/);
210
+ }
211
+ });
212
+
213
+ test("health check exits with code 0 when healthy", async () => {
214
+ // CHARACTERIZATION: Exit code 0 = healthy
215
+ const proc = Bun.spawn(["cass", "health"], {
216
+ stdout: "pipe",
217
+ stderr: "pipe",
218
+ });
219
+
220
+ const exitCode = await proc.exited;
221
+ // Exit code 0 means healthy (even if stale)
222
+ // Exit code 3 means missing index
223
+ expect([0, 3]).toContain(exitCode);
224
+ });
225
+ });
226
+
227
+ describe("CASS Binary - cass view", () => {
228
+ test("view output includes file path header", async () => {
229
+ // CHARACTERIZATION: View starts with file path
230
+ // We can only test this if session files exist
231
+ const sessionFiles = await $`ls ~/.config/swarm-tools/sessions/*.jsonl`
232
+ .quiet()
233
+ .text()
234
+ .catch(() => "");
235
+
236
+ if (sessionFiles.trim()) {
237
+ const firstFile = sessionFiles.split("\n")[0].trim();
238
+ const result = await $`cass view ${firstFile} -n 1`.quiet().text();
239
+
240
+ expect(result).toContain(`File: ${firstFile}`);
241
+ expect(result).toContain("Line: 1");
242
+ expect(result).toContain("context:");
243
+ expect(result).toContain("----------------------------------------");
244
+ }
245
+ });
246
+
247
+ test("view output shows line numbers with content", async () => {
248
+ // CHARACTERIZATION: Lines prefixed with numbers
249
+ const sessionFiles = await $`ls ~/.config/swarm-tools/sessions/*.jsonl`
250
+ .quiet()
251
+ .text()
252
+ .catch(() => "");
253
+
254
+ if (sessionFiles.trim()) {
255
+ const firstFile = sessionFiles.split("\n")[0].trim();
256
+ const result = await $`cass view ${firstFile} -n 1`.quiet().text();
257
+
258
+ // Target line marked with >
259
+ expect(result).toMatch(/>\s+\d+\s+\|/);
260
+ }
261
+ });
262
+
263
+ test("view with non-existent file returns error", async () => {
264
+ // CHARACTERIZATION: File not found error structure
265
+ const result = await $`cass view /nonexistent/path.jsonl -n 1 --json`
266
+ .quiet()
267
+ .json()
268
+ .catch((e) => JSON.parse(e.stderr.toString()));
269
+
270
+ expect(result).toHaveProperty("error");
271
+ expect(result.error).toHaveProperty("code");
272
+ expect(result.error).toHaveProperty("kind");
273
+ expect(result.error).toHaveProperty("message");
274
+ expect(result.error).toHaveProperty("retryable");
275
+
276
+ expect(result.error.code).toBe(3);
277
+ expect(result.error.kind).toBe("file-not-found");
278
+ expect(result.error.retryable).toBe(false);
279
+ });
280
+ });
281
+
282
+ describe("CASS Binary - Error handling", () => {
283
+ test("invalid arguments return usage error with hints", async () => {
284
+ // CHARACTERIZATION: Helpful error messages with examples
285
+ const proc = Bun.spawn(["cass", "stats", "--invalid-flag"], {
286
+ stdout: "pipe",
287
+ stderr: "pipe",
288
+ });
289
+
290
+ await proc.exited;
291
+ const stderr = await new Response(proc.stderr).text();
292
+
293
+ // Should contain helpful error information
294
+ expect(stderr).toBeTruthy();
295
+ // Typically shows usage or suggests --help
296
+ expect(stderr.toLowerCase()).toMatch(/usage|help|invalid|unexpected/);
297
+ });
298
+
299
+ test("error responses include exit codes", async () => {
300
+ // CHARACTERIZATION: Exit codes documented in baseline
301
+ // Code 2 = usage error
302
+ // Code 3 = missing file/db
303
+ // Code 0 = success
304
+
305
+ const procInvalidArg = Bun.spawn(["cass", "stats", "--invalid-flag"], {
306
+ stdout: "pipe",
307
+ stderr: "pipe",
308
+ });
309
+ const exitCodeInvalid = await procInvalidArg.exited;
310
+ expect(exitCodeInvalid).toBe(2); // Usage error
311
+
312
+ const procSuccess = Bun.spawn(["cass", "stats", "--json"], {
313
+ stdout: "pipe",
314
+ stderr: "pipe",
315
+ });
316
+ const exitCodeSuccess = await procSuccess.exited;
317
+ expect([0, 3]).toContain(exitCodeSuccess); // Success or missing index
318
+ });
319
+ });
320
+
321
+ describe("CASS Binary - Robot mode documentation", () => {
322
+ test("--robot-help provides machine-readable documentation", async () => {
323
+ // CHARACTERIZATION: Robot help is designed for AI agents
324
+ const result = await $`cass --robot-help`.quiet().text();
325
+
326
+ expect(result).toContain("cass --robot-help (contract v1)");
327
+ expect(result).toContain("QUICKSTART (for AI agents):");
328
+ expect(result).toContain("TIME FILTERS:");
329
+ expect(result).toContain("WORKFLOW:");
330
+ expect(result).toContain("OUTPUT:");
331
+ expect(result).toContain("Exit codes:");
332
+ });
333
+
334
+ test("robot-docs subcommand exists", async () => {
335
+ // CHARACTERIZATION: robot-docs provides detailed docs for AI
336
+ const result = await $`cass robot-docs commands`.quiet().text();
337
+
338
+ // Should return command documentation (exact format may vary)
339
+ expect(result.length).toBeGreaterThan(0);
340
+ });
341
+ });
342
+
343
+ describe("CASS Binary - Flag behavior", () => {
344
+ test("--json flag produces machine-readable output", async () => {
345
+ // CHARACTERIZATION: --json enables JSON mode
346
+ const result = await $`cass stats --json`.quiet().text();
347
+
348
+ // Should be valid JSON
349
+ expect(() => JSON.parse(result)).not.toThrow();
350
+ });
351
+
352
+ test("--json and --robot are equivalent", async () => {
353
+ // CHARACTERIZATION: Both flags enable robot mode
354
+ const jsonResult = await $`cass search "test" --limit 1 --json`
355
+ .quiet()
356
+ .json();
357
+ const robotResult = await $`cass search "test" --limit 1 --robot`
358
+ .quiet()
359
+ .json();
360
+
361
+ // Both should return same structure
362
+ expect(jsonResult).toHaveProperty("hits");
363
+ expect(robotResult).toHaveProperty("hits");
364
+ });
365
+
366
+ test("limit flag controls result count", async () => {
367
+ // CHARACTERIZATION: --limit parameter
368
+ const result = await $`cass search "test" --limit 1 --json`.quiet().json();
369
+
370
+ expect(result.limit).toBe(1);
371
+ expect(result.hits.length).toBeLessThanOrEqual(1);
372
+ });
373
+ });
374
+
375
+ /**
376
+ * CHARACTERIZATION NOTES:
377
+ *
378
+ * These tests document the following CASS binary behaviors:
379
+ *
380
+ * 1. JSON Output Structure:
381
+ * - cass stats: by_agent[], conversations, date_range, messages, top_workspaces[]
382
+ * - cass search: count, cursor, hits[], limit, offset, query, total_matches
383
+ * - Hit objects: agent, content, created_at, line_number, score, source_path, etc.
384
+ *
385
+ * 2. Human-Readable Output:
386
+ * - Formatted tables with headers
387
+ * - Numeric statistics
388
+ * - Date ranges
389
+ *
390
+ * 3. Error Handling:
391
+ * - Exit code 0 = success
392
+ * - Exit code 2 = usage error
393
+ * - Exit code 3 = missing file/db
394
+ * - Error objects with code, kind, message, retryable fields
395
+ *
396
+ * 4. Robot Mode:
397
+ * - --json and --robot flags are equivalent
398
+ * - --robot-help provides AI-friendly documentation
399
+ * - robot-docs subcommand for detailed docs
400
+ *
401
+ * 5. Search Behavior:
402
+ * - Query parameter echoed in response
403
+ * - Limit parameter controls max hits
404
+ * - Empty results may include suggestions
405
+ *
406
+ * 6. View Behavior:
407
+ * - File path header
408
+ * - Line numbers with > indicator for target
409
+ * - Context window (default 5 lines)
410
+ * - Horizontal separators
411
+ *
412
+ * 7. Health Check:
413
+ * - Status indicator (✓ or ✗)
414
+ * - Timing in milliseconds
415
+ * - Optional staleness warning
416
+ *
417
+ * When implementing the inhouse version:
418
+ * - Match these structures exactly
419
+ * - Preserve field names and types
420
+ * - Maintain error response format
421
+ * - Keep exit codes consistent
422
+ */
@@ -11,18 +11,18 @@ describe("swarm serve command", () => {
11
11
  const args = ["serve", "--port", "8080"];
12
12
  const port = args.includes("--port")
13
13
  ? Number.parseInt(args[args.indexOf("--port") + 1])
14
- : 3001;
14
+ : 4483;
15
15
 
16
16
  expect(port).toBe(8080);
17
17
  });
18
18
 
19
- test("serve command defaults to port 3001", () => {
19
+ test("serve command defaults to port 4483 (HIVE on phone keypad)", () => {
20
20
  const args = ["serve"];
21
21
  const port = args.includes("--port")
22
22
  ? Number.parseInt(args[args.indexOf("--port") + 1])
23
- : 3001;
23
+ : 4483;
24
24
 
25
- expect(port).toBe(3001);
25
+ expect(port).toBe(4483);
26
26
  });
27
27
 
28
28
  test("serve command uses project path from CWD", () => {
@@ -32,7 +32,9 @@ describe("swarm serve command", () => {
32
32
  });
33
33
 
34
34
  test("serve command appears in help text", async () => {
35
+ const packageDir = import.meta.dir.replace("/bin", "");
35
36
  const proc = spawn(["bun", "run", "bin/swarm.ts", "help"], {
37
+ cwd: packageDir,
36
38
  stdout: "pipe",
37
39
  stderr: "pipe",
38
40
  });
package/bin/swarm.test.ts CHANGED
@@ -1938,6 +1938,74 @@ describe("swarm replay", () => {
1938
1938
  });
1939
1939
  });
1940
1940
 
1941
+ describe("swarm viz", () => {
1942
+ test("parses port flag", () => {
1943
+ function parseVizArgs(args: string[]): { port: number } {
1944
+ let port = 4483; // HIVE on phone keypad
1945
+
1946
+ for (let i = 0; i < args.length; i++) {
1947
+ if (args[i] === "--port") {
1948
+ const portNum = Number.parseInt(args[i + 1]);
1949
+ if (!isNaN(portNum) && portNum > 0) {
1950
+ port = portNum;
1951
+ }
1952
+ i++;
1953
+ }
1954
+ }
1955
+
1956
+ return { port };
1957
+ }
1958
+
1959
+ const result = parseVizArgs(["--port", "8080"]);
1960
+
1961
+ expect(result.port).toBe(8080);
1962
+ });
1963
+
1964
+ test("defaults to port 4483 (HIVE)", () => {
1965
+ function parseVizArgs(args: string[]): { port: number } {
1966
+ let port = 4483;
1967
+
1968
+ for (let i = 0; i < args.length; i++) {
1969
+ if (args[i] === "--port") {
1970
+ const portNum = Number.parseInt(args[i + 1]);
1971
+ if (!isNaN(portNum) && portNum > 0) {
1972
+ port = portNum;
1973
+ }
1974
+ i++;
1975
+ }
1976
+ }
1977
+
1978
+ return { port };
1979
+ }
1980
+
1981
+ const result = parseVizArgs([]);
1982
+
1983
+ expect(result.port).toBe(4483);
1984
+ });
1985
+
1986
+ test("ignores invalid port values", () => {
1987
+ function parseVizArgs(args: string[]): { port: number } {
1988
+ let port = 4483;
1989
+
1990
+ for (let i = 0; i < args.length; i++) {
1991
+ if (args[i] === "--port") {
1992
+ const portNum = Number.parseInt(args[i + 1]);
1993
+ if (!isNaN(portNum) && portNum > 0) {
1994
+ port = portNum;
1995
+ }
1996
+ i++;
1997
+ }
1998
+ }
1999
+
2000
+ return { port };
2001
+ }
2002
+
2003
+ const result = parseVizArgs(["--port", "invalid"]);
2004
+
2005
+ expect(result.port).toBe(4483); // Falls back to default
2006
+ });
2007
+ });
2008
+
1941
2009
  describe("swarm export", () => {
1942
2010
  test("parses format flag", () => {
1943
2011
  function parseExportArgs(args: string[]): {
package/bin/swarm.ts CHANGED
@@ -2917,8 +2917,10 @@ ${cyan("Commands:")}
2917
2917
  swarm config Show paths to generated config files
2918
2918
  swarm agents Update AGENTS.md with skill awareness
2919
2919
  swarm migrate Migrate PGlite database to libSQL
2920
- swarm serve Start SSE server for real-time event streaming
2921
- --port <n> Port to listen on (default: 3001)
2920
+ swarm serve Start SSE server for real-time event streaming (port 4483 - HIVE)
2921
+ --port <n> Port to listen on (default: 4483)
2922
+ swarm viz Alias for 'swarm serve' (deprecated, use serve)
2923
+ --port <n> Port to listen on (default: 4483)
2922
2924
  swarm cells List or get cells from database (replaces 'swarm tool hive_query')
2923
2925
  swarm log View swarm logs with filtering
2924
2926
  swarm stats Show swarm health metrics powered by swarm-insights (strategy success rates, patterns)
@@ -4919,21 +4921,21 @@ async function evalRun() {
4919
4921
  async function serve() {
4920
4922
  p.intro("swarm serve v" + VERSION);
4921
4923
 
4922
- // Parse --port flag (default 3001)
4924
+ // Parse --port flag (default 4483 - HIVE on phone keypad)
4923
4925
  const portFlagIndex = process.argv.indexOf("--port");
4924
4926
  const port = portFlagIndex !== -1
4925
- ? Number.parseInt(process.argv[portFlagIndex + 1]) || 3001
4926
- : 3001;
4927
+ ? Number.parseInt(process.argv[portFlagIndex + 1]) || 4483
4928
+ : 4483;
4927
4929
 
4928
4930
  const projectPath = process.cwd();
4929
4931
 
4930
4932
  p.log.step("Starting DurableStreamServer...");
4931
4933
  p.log.message(dim(` Project: ${projectPath}`));
4932
- p.log.message(dim(` Port: ${port}`));
4934
+ p.log.message(dim(` Port: ${port} (HIVE on phone keypad)`));
4933
4935
 
4934
4936
  try {
4935
4937
  // Import dependencies
4936
- const { getSwarmMailLibSQL } = await import("swarm-mail");
4938
+ const { getSwarmMailLibSQL, createHiveAdapter } = await import("swarm-mail");
4937
4939
  const { createDurableStreamAdapter, createDurableStreamServer } = await import("swarm-mail");
4938
4940
 
4939
4941
  // Get swarm-mail adapter
@@ -4942,9 +4944,14 @@ async function serve() {
4942
4944
  // Create stream adapter
4943
4945
  const streamAdapter = createDurableStreamAdapter(swarmMail, projectPath);
4944
4946
 
4947
+ // Create hive adapter for cells endpoint
4948
+ const db = await swarmMail.getDatabase(projectPath);
4949
+ const hiveAdapter = createHiveAdapter(db, projectPath);
4950
+
4945
4951
  // Create and start server
4946
4952
  const server = createDurableStreamServer({
4947
4953
  adapter: streamAdapter,
4954
+ hiveAdapter,
4948
4955
  port,
4949
4956
  projectKey: projectPath,
4950
4957
  });
@@ -4953,8 +4960,9 @@ async function serve() {
4953
4960
 
4954
4961
  p.log.success("Server started!");
4955
4962
  p.log.message("");
4956
- p.log.message(cyan(" Dashboard: http://localhost:5173"));
4963
+ p.log.message(cyan(` Dashboard: http://localhost:5173`));
4957
4964
  p.log.message(cyan(` SSE Endpoint: ${server.url}/streams/${encodeURIComponent(projectPath)}`));
4965
+ p.log.message(cyan(` Cells API: ${server.url}/cells`));
4958
4966
  p.log.message("");
4959
4967
  p.log.message(dim(" Press Ctrl+C to stop"));
4960
4968
 
@@ -4968,6 +4976,68 @@ async function serve() {
4968
4976
  }
4969
4977
  }
4970
4978
 
4979
+ // ============================================================================
4980
+ // Viz Command - Start Dashboard Server
4981
+ // ============================================================================
4982
+
4983
+ async function viz() {
4984
+ p.intro("swarm viz v" + VERSION);
4985
+
4986
+ // Parse --port flag (default 4483 - HIVE on phone keypad)
4987
+ const portFlagIndex = process.argv.indexOf("--port");
4988
+ const port = portFlagIndex !== -1
4989
+ ? Number.parseInt(process.argv[portFlagIndex + 1]) || 4483
4990
+ : 4483;
4991
+
4992
+ const projectPath = process.cwd();
4993
+
4994
+ p.log.step("Starting dashboard server...");
4995
+ p.log.message(dim(` Project: ${projectPath}`));
4996
+ p.log.message(dim(` Port: ${port}`));
4997
+
4998
+ try {
4999
+ // Import dependencies
5000
+ const { getSwarmMailLibSQL, createHiveAdapter } = await import("swarm-mail");
5001
+ const { createDurableStreamAdapter, createDurableStreamServer } = await import("swarm-mail");
5002
+
5003
+ // Get swarm-mail adapter
5004
+ const swarmMail = await getSwarmMailLibSQL(projectPath);
5005
+
5006
+ // Create stream adapter
5007
+ const streamAdapter = createDurableStreamAdapter(swarmMail, projectPath);
5008
+
5009
+ // Create hive adapter for cells endpoint
5010
+ const db = await swarmMail.getDatabase(projectPath);
5011
+ const hiveAdapter = createHiveAdapter(db, projectPath);
5012
+
5013
+ // Create and start server
5014
+ const server = createDurableStreamServer({
5015
+ adapter: streamAdapter,
5016
+ hiveAdapter,
5017
+ port,
5018
+ projectKey: projectPath,
5019
+ });
5020
+
5021
+ await server.start();
5022
+
5023
+ p.log.success("Dashboard server running!");
5024
+ p.log.message("");
5025
+ p.log.message(cyan(` Dashboard: http://localhost:${port}`));
5026
+ p.log.message(cyan(` SSE endpoint: http://localhost:${port}/streams/${encodeURIComponent(projectPath)}`));
5027
+ p.log.message(cyan(` Cells API: http://localhost:${port}/cells`));
5028
+ p.log.message("");
5029
+ p.log.message(dim(" Press Ctrl+C to stop"));
5030
+
5031
+ // Keep process alive
5032
+ await new Promise(() => {});
5033
+ } catch (error) {
5034
+ p.log.error("Failed to start dashboard server");
5035
+ p.log.message(error instanceof Error ? error.message : String(error));
5036
+ p.outro("Aborted");
5037
+ process.exit(1);
5038
+ }
5039
+ }
5040
+
4971
5041
  // ============================================================================
4972
5042
  // Main
4973
5043
  // ============================================================================
@@ -4993,6 +5063,9 @@ switch (command) {
4993
5063
  case "serve":
4994
5064
  await serve();
4995
5065
  break;
5066
+ case "viz":
5067
+ await viz();
5068
+ break;
4996
5069
  case "update":
4997
5070
  await update();
4998
5071
  break;