opencode-swarm-plugin 0.42.9 → 0.44.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.
Files changed (48) hide show
  1. package/.hive/issues.jsonl +14 -0
  2. package/.turbo/turbo-build.log +2 -2
  3. package/CHANGELOG.md +110 -0
  4. package/README.md +296 -6
  5. package/bin/cass.characterization.test.ts +422 -0
  6. package/bin/swarm.test.ts +683 -0
  7. package/bin/swarm.ts +501 -0
  8. package/dist/contributor-tools.d.ts +42 -0
  9. package/dist/contributor-tools.d.ts.map +1 -0
  10. package/dist/dashboard.d.ts +83 -0
  11. package/dist/dashboard.d.ts.map +1 -0
  12. package/dist/error-enrichment.d.ts +49 -0
  13. package/dist/error-enrichment.d.ts.map +1 -0
  14. package/dist/export-tools.d.ts +76 -0
  15. package/dist/export-tools.d.ts.map +1 -0
  16. package/dist/index.d.ts +14 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +95 -2
  19. package/dist/observability-tools.d.ts +2 -2
  20. package/dist/plugin.js +95 -2
  21. package/dist/query-tools.d.ts +59 -0
  22. package/dist/query-tools.d.ts.map +1 -0
  23. package/dist/replay-tools.d.ts +28 -0
  24. package/dist/replay-tools.d.ts.map +1 -0
  25. package/dist/sessions/agent-discovery.d.ts +59 -0
  26. package/dist/sessions/agent-discovery.d.ts.map +1 -0
  27. package/dist/sessions/index.d.ts +10 -0
  28. package/dist/sessions/index.d.ts.map +1 -0
  29. package/docs/planning/ADR-010-cass-inhousing.md +1215 -0
  30. package/evals/fixtures/cass-baseline.ts +217 -0
  31. package/examples/plugin-wrapper-template.ts +89 -0
  32. package/package.json +1 -1
  33. package/src/contributor-tools.test.ts +133 -0
  34. package/src/contributor-tools.ts +201 -0
  35. package/src/dashboard.test.ts +611 -0
  36. package/src/dashboard.ts +462 -0
  37. package/src/error-enrichment.test.ts +403 -0
  38. package/src/error-enrichment.ts +219 -0
  39. package/src/export-tools.test.ts +476 -0
  40. package/src/export-tools.ts +257 -0
  41. package/src/index.ts +8 -3
  42. package/src/query-tools.test.ts +636 -0
  43. package/src/query-tools.ts +324 -0
  44. package/src/replay-tools.test.ts +496 -0
  45. package/src/replay-tools.ts +240 -0
  46. package/src/sessions/agent-discovery.test.ts +137 -0
  47. package/src/sessions/agent-discovery.ts +112 -0
  48. package/src/sessions/index.ts +15 -0
@@ -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
+ */