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,257 @@
1
+ /**
2
+ * Export Tools - Convert Cell Events to Various Formats
3
+ *
4
+ * GREEN PHASE: Minimal implementation to pass tests
5
+ *
6
+ * Supports:
7
+ * - OTLP (OpenTelemetry Protocol) - for distributed tracing
8
+ * - CSV - for spreadsheet analysis
9
+ * - JSON - for generic data interchange
10
+ */
11
+
12
+ import type { CellEvent } from "./schemas/cell-events.js";
13
+ import { createHash } from "node:crypto";
14
+
15
+ // ============================================================================
16
+ // OTLP Export
17
+ // ============================================================================
18
+
19
+ /**
20
+ * OpenTelemetry OTLP span structure
21
+ */
22
+ interface OTLPSpan {
23
+ traceId: string; // 32 hex chars (16 bytes)
24
+ spanId: string; // 16 hex chars (8 bytes)
25
+ name: string; // event.type
26
+ startTimeUnixNano: string; // timestamp in nanoseconds
27
+ attributes: Array<{
28
+ key: string;
29
+ value: {
30
+ stringValue?: string;
31
+ intValue?: number;
32
+ boolValue?: boolean;
33
+ };
34
+ }>;
35
+ }
36
+
37
+ interface OTLPOutput {
38
+ resourceSpans: Array<{
39
+ resource: {
40
+ attributes: Array<{
41
+ key: string;
42
+ value: { stringValue: string };
43
+ }>;
44
+ };
45
+ scopeSpans: Array<{
46
+ scope: {
47
+ name: string;
48
+ };
49
+ spans: OTLPSpan[];
50
+ }>;
51
+ }>;
52
+ }
53
+
54
+ /**
55
+ * Convert string to hex hash of specified length
56
+ */
57
+ function toHex(input: string, bytes: number): string {
58
+ const hash = createHash("sha256").update(input).digest("hex");
59
+ return hash.slice(0, bytes * 2); // 2 hex chars per byte
60
+ }
61
+
62
+ /**
63
+ * Convert event payload to OTLP attributes
64
+ */
65
+ function eventToAttributes(event: CellEvent): OTLPSpan["attributes"] {
66
+ const attrs: OTLPSpan["attributes"] = [];
67
+
68
+ // Map common fields with "cell." prefix
69
+ if ("title" in event && event.title) {
70
+ attrs.push({
71
+ key: "cell.title",
72
+ value: { stringValue: event.title },
73
+ });
74
+ }
75
+
76
+ if ("issue_type" in event && event.issue_type) {
77
+ attrs.push({
78
+ key: "cell.type",
79
+ value: { stringValue: event.issue_type },
80
+ });
81
+ }
82
+
83
+ if ("priority" in event && typeof event.priority === "number") {
84
+ attrs.push({
85
+ key: "cell.priority",
86
+ value: { intValue: event.priority },
87
+ });
88
+ }
89
+
90
+ if ("description" in event && event.description) {
91
+ attrs.push({
92
+ key: "cell.description",
93
+ value: { stringValue: event.description },
94
+ });
95
+ }
96
+
97
+ // Add type-specific fields
98
+ if ("from_status" in event && event.from_status) {
99
+ attrs.push({
100
+ key: "cell.from_status",
101
+ value: { stringValue: event.from_status },
102
+ });
103
+ }
104
+
105
+ if ("to_status" in event && event.to_status) {
106
+ attrs.push({
107
+ key: "cell.to_status",
108
+ value: { stringValue: event.to_status },
109
+ });
110
+ }
111
+
112
+ if ("reason" in event && event.reason) {
113
+ attrs.push({
114
+ key: "cell.reason",
115
+ value: { stringValue: event.reason },
116
+ });
117
+ }
118
+
119
+ if ("duration_ms" in event && typeof event.duration_ms === "number") {
120
+ attrs.push({
121
+ key: "cell.duration_ms",
122
+ value: { intValue: event.duration_ms },
123
+ });
124
+ }
125
+
126
+ return attrs;
127
+ }
128
+
129
+ /**
130
+ * Export cell events to OpenTelemetry OTLP format
131
+ *
132
+ * Mapping:
133
+ * - epic_id (from metadata) → trace_id (32 hex chars)
134
+ * - cell_id → span_id (16 hex chars)
135
+ * - timestamp → startTimeUnixNano (nanoseconds as string)
136
+ * - event.type → span.name
137
+ * - event payload → span.attributes
138
+ */
139
+ export function exportToOTLP(events: CellEvent[]): OTLPOutput {
140
+ const spans: OTLPSpan[] = events.map((event) => {
141
+ // Determine trace_id: epic_id from metadata, fallback to project_key
142
+ const epicId =
143
+ ("metadata" in event &&
144
+ event.metadata &&
145
+ typeof event.metadata === "object" &&
146
+ "epic_id" in event.metadata &&
147
+ typeof event.metadata.epic_id === "string"
148
+ ? event.metadata.epic_id
149
+ : event.project_key);
150
+
151
+ const traceId = toHex(epicId, 16); // 16 bytes = 32 hex chars
152
+ const spanId = toHex(event.cell_id, 8); // 8 bytes = 16 hex chars
153
+
154
+ // Convert timestamp (ms) to nanoseconds
155
+ const startTimeUnixNano = String(event.timestamp * 1_000_000);
156
+
157
+ return {
158
+ traceId,
159
+ spanId,
160
+ name: event.type,
161
+ startTimeUnixNano,
162
+ attributes: eventToAttributes(event),
163
+ };
164
+ });
165
+
166
+ return {
167
+ resourceSpans: [
168
+ {
169
+ resource: {
170
+ attributes: [
171
+ {
172
+ key: "service.name",
173
+ value: { stringValue: "swarm" },
174
+ },
175
+ ],
176
+ },
177
+ scopeSpans: [
178
+ {
179
+ scope: {
180
+ name: "swarm",
181
+ },
182
+ spans,
183
+ },
184
+ ],
185
+ },
186
+ ],
187
+ };
188
+ }
189
+
190
+ // ============================================================================
191
+ // CSV Export
192
+ // ============================================================================
193
+
194
+ /**
195
+ * Escape CSV field value
196
+ * - Wrap in quotes if contains comma, quote, or newline
197
+ * - Convert JSON escaped quotes (\") to CSV doubled quotes ("")
198
+ * - Double any other interior quotes
199
+ */
200
+ function escapeCsvField(value: string): string {
201
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
202
+ // First convert JSON-escaped quotes \" to just "
203
+ // Then double all quotes for CSV format
204
+ const unescaped = value.replace(/\\"/g, '"');
205
+ return `"${unescaped.replace(/"/g, '""')}"`;
206
+ }
207
+ return value;
208
+ }
209
+
210
+ /**
211
+ * Export cell events to CSV format
212
+ *
213
+ * Format:
214
+ * - Headers: id,type,timestamp,project_key,cell_id,payload
215
+ * - Payload: JSON serialization of entire event (minus headers)
216
+ */
217
+ export function exportToCSV(events: CellEvent[]): string {
218
+ const headers = "id,type,timestamp,project_key,cell_id,payload";
219
+ const rows = [headers];
220
+
221
+ for (const event of events) {
222
+ // Don't escape simple fields that don't need it
223
+ const id = "id" in event && event.id ? String(event.id) : "";
224
+ const type = event.type;
225
+ const timestamp = String(event.timestamp);
226
+ const projectKey = event.project_key;
227
+ const cellId = event.cell_id;
228
+
229
+ // Serialize entire event as JSON for payload column
230
+ const payloadJson = JSON.stringify(event);
231
+ const payload = escapeCsvField(payloadJson);
232
+
233
+ rows.push([id, type, timestamp, projectKey, cellId, payload].join(","));
234
+ }
235
+
236
+ return `${rows.join("\n")}\n`;
237
+ }
238
+
239
+ // ============================================================================
240
+ // JSON Export
241
+ // ============================================================================
242
+
243
+ /**
244
+ * Export cell events to JSON format
245
+ *
246
+ * Format:
247
+ * - Array of event objects
248
+ * - Pretty-printed with 2-space indentation
249
+ * - Preserves all fields and discriminated union types
250
+ */
251
+ export function exportToJSON(events: CellEvent[]): string {
252
+ if (events.length === 0) {
253
+ return "[]";
254
+ }
255
+
256
+ return JSON.stringify(events, null, 2);
257
+ }
package/src/index.ts CHANGED
@@ -50,6 +50,7 @@ import { memoryTools } from "./memory-tools";
50
50
  import { observabilityTools } from "./observability-tools";
51
51
  import { researchTools } from "./swarm-research";
52
52
  import { evalTools } from "./eval-runner";
53
+ import { contributorTools } from "./contributor-tools";
53
54
  import {
54
55
  guardrailOutput,
55
56
  DEFAULT_GUARDRAIL_CONFIG,
@@ -80,6 +81,7 @@ import { createCompactionHook } from "./compaction-hook";
80
81
  * - skills:* - Agent skills discovery, activation, and execution
81
82
  * - mandate:* - Agent voting system for collaborative knowledge curation
82
83
  * - semantic-memory:* - Semantic memory with vector embeddings (Ollama + PGLite)
84
+ * - contributor_lookup - GitHub contributor profile lookup with changeset credit generation
83
85
  *
84
86
  * @param input - Plugin context from OpenCode
85
87
  * @returns Plugin hooks including tools, events, and tool execution hooks
@@ -159,9 +161,10 @@ const SwarmPlugin: Plugin = async (
159
161
  * - beads:* - Legacy aliases (deprecated, use hive:* instead)
160
162
  * - agent-mail:init, agent-mail:send, agent-mail:reserve, etc. (legacy MCP)
161
163
  * - swarm-mail:init, swarm-mail:send, swarm-mail:reserve, etc. (embedded)
162
- * - repo-crawl:readme, repo-crawl:structure, etc.
163
- * - mandate:file, mandate:vote, mandate:query, etc.
164
- * - semantic-memory:store, semantic-memory:find, semantic-memory:get, etc.
164
+ * - repo-crawl:readme, repo-crawl:structure, etc.
165
+ * - mandate:file, mandate:vote, mandate:query, etc.
166
+ * - semantic-memory:store, semantic-memory:find, semantic-memory:get, etc.
167
+ * - contributor_lookup - GitHub contributor profile lookup with changeset credits
165
168
  */
166
169
  tool: {
167
170
  ...hiveTools,
@@ -177,6 +180,7 @@ const SwarmPlugin: Plugin = async (
177
180
  ...observabilityTools,
178
181
  ...researchTools,
179
182
  ...evalTools,
183
+ ...contributorTools,
180
184
  },
181
185
 
182
186
  /**
@@ -516,6 +520,7 @@ export const allTools = {
516
520
  ...mandateTools,
517
521
  ...memoryTools,
518
522
  ...observabilityTools,
523
+ ...contributorTools,
519
524
  } as const;
520
525
 
521
526
  /**