opencode-swarm-plugin 0.21.0 → 0.23.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 (131) hide show
  1. package/.turbo/turbo-build.log +9 -0
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +111 -166
  4. package/dist/agent-mail.d.ts +480 -0
  5. package/dist/agent-mail.d.ts.map +1 -0
  6. package/dist/anti-patterns.d.ts +257 -0
  7. package/dist/anti-patterns.d.ts.map +1 -0
  8. package/dist/beads.d.ts +377 -0
  9. package/dist/beads.d.ts.map +1 -0
  10. package/dist/eval-capture.d.ts +206 -0
  11. package/dist/eval-capture.d.ts.map +1 -0
  12. package/dist/index.d.ts +1299 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +776 -4387
  15. package/dist/learning.d.ts +670 -0
  16. package/dist/learning.d.ts.map +1 -0
  17. package/dist/mandate-promotion.d.ts +93 -0
  18. package/dist/mandate-promotion.d.ts.map +1 -0
  19. package/dist/mandate-storage.d.ts +209 -0
  20. package/dist/mandate-storage.d.ts.map +1 -0
  21. package/dist/mandates.d.ts +230 -0
  22. package/dist/mandates.d.ts.map +1 -0
  23. package/dist/output-guardrails.d.ts +125 -0
  24. package/dist/output-guardrails.d.ts.map +1 -0
  25. package/dist/pattern-maturity.d.ts +246 -0
  26. package/dist/pattern-maturity.d.ts.map +1 -0
  27. package/dist/plugin.d.ts +22 -0
  28. package/dist/plugin.d.ts.map +1 -0
  29. package/dist/plugin.js +755 -4375
  30. package/dist/rate-limiter.d.ts +218 -0
  31. package/dist/rate-limiter.d.ts.map +1 -0
  32. package/dist/repo-crawl.d.ts +146 -0
  33. package/dist/repo-crawl.d.ts.map +1 -0
  34. package/dist/schemas/bead.d.ts +255 -0
  35. package/dist/schemas/bead.d.ts.map +1 -0
  36. package/dist/schemas/evaluation.d.ts +161 -0
  37. package/dist/schemas/evaluation.d.ts.map +1 -0
  38. package/dist/schemas/index.d.ts +34 -0
  39. package/dist/schemas/index.d.ts.map +1 -0
  40. package/dist/schemas/mandate.d.ts +336 -0
  41. package/dist/schemas/mandate.d.ts.map +1 -0
  42. package/dist/schemas/swarm-context.d.ts +131 -0
  43. package/dist/schemas/swarm-context.d.ts.map +1 -0
  44. package/dist/schemas/task.d.ts +188 -0
  45. package/dist/schemas/task.d.ts.map +1 -0
  46. package/dist/skills.d.ts +471 -0
  47. package/dist/skills.d.ts.map +1 -0
  48. package/dist/storage.d.ts +260 -0
  49. package/dist/storage.d.ts.map +1 -0
  50. package/dist/structured.d.ts +196 -0
  51. package/dist/structured.d.ts.map +1 -0
  52. package/dist/swarm-decompose.d.ts +201 -0
  53. package/dist/swarm-decompose.d.ts.map +1 -0
  54. package/dist/swarm-mail.d.ts +240 -0
  55. package/dist/swarm-mail.d.ts.map +1 -0
  56. package/dist/swarm-orchestrate.d.ts +708 -0
  57. package/dist/swarm-orchestrate.d.ts.map +1 -0
  58. package/dist/swarm-prompts.d.ts +292 -0
  59. package/dist/swarm-prompts.d.ts.map +1 -0
  60. package/dist/swarm-strategies.d.ts +100 -0
  61. package/dist/swarm-strategies.d.ts.map +1 -0
  62. package/dist/swarm.d.ts +455 -0
  63. package/dist/swarm.d.ts.map +1 -0
  64. package/dist/tool-availability.d.ts +91 -0
  65. package/dist/tool-availability.d.ts.map +1 -0
  66. package/docs/planning/ADR-001-monorepo-structure.md +171 -0
  67. package/docs/planning/ADR-002-package-extraction.md +393 -0
  68. package/docs/planning/ADR-003-performance-improvements.md +451 -0
  69. package/docs/planning/ADR-004-message-queue-features.md +187 -0
  70. package/docs/planning/ADR-005-devtools-observability.md +202 -0
  71. package/docs/planning/ROADMAP.md +368 -0
  72. package/docs/semantic-memory-cli-syntax.md +123 -0
  73. package/docs/swarm-mail-architecture.md +1147 -0
  74. package/package.json +13 -24
  75. package/scripts/cleanup-test-memories.ts +346 -0
  76. package/src/agent-mail.ts +1 -1
  77. package/src/beads.ts +1 -2
  78. package/src/index.ts +2 -2
  79. package/src/learning.integration.test.ts +80 -10
  80. package/src/mandate-storage.test.ts +3 -3
  81. package/src/storage.ts +189 -9
  82. package/src/swarm-mail.ts +3 -3
  83. package/src/swarm-orchestrate.ts +399 -246
  84. package/src/swarm.integration.test.ts +124 -0
  85. package/src/tool-availability.ts +1 -1
  86. package/tsconfig.json +1 -1
  87. package/.beads/.local_version +0 -1
  88. package/.beads/README.md +0 -81
  89. package/.beads/analysis/skill-architecture-meta-skills.md +0 -1562
  90. package/.beads/config.yaml +0 -62
  91. package/.beads/issues.jsonl +0 -2186
  92. package/.beads/metadata.json +0 -4
  93. package/.gitattributes +0 -3
  94. package/.github/workflows/ci.yml +0 -30
  95. package/.github/workflows/opencode.yml +0 -31
  96. package/.opencode/skills/tdd/SKILL.md +0 -182
  97. package/INTEGRATION_EXAMPLE.md +0 -66
  98. package/VERIFICATION_QUALITY_PATTERNS.md +0 -565
  99. package/bun.lock +0 -286
  100. package/dist/pglite.data +0 -0
  101. package/dist/pglite.wasm +0 -0
  102. package/src/streams/agent-mail.test.ts +0 -777
  103. package/src/streams/agent-mail.ts +0 -535
  104. package/src/streams/debug.test.ts +0 -500
  105. package/src/streams/debug.ts +0 -727
  106. package/src/streams/effect/ask.integration.test.ts +0 -314
  107. package/src/streams/effect/ask.ts +0 -202
  108. package/src/streams/effect/cursor.integration.test.ts +0 -418
  109. package/src/streams/effect/cursor.ts +0 -288
  110. package/src/streams/effect/deferred.test.ts +0 -357
  111. package/src/streams/effect/deferred.ts +0 -445
  112. package/src/streams/effect/index.ts +0 -17
  113. package/src/streams/effect/layers.ts +0 -73
  114. package/src/streams/effect/lock.test.ts +0 -385
  115. package/src/streams/effect/lock.ts +0 -399
  116. package/src/streams/effect/mailbox.test.ts +0 -260
  117. package/src/streams/effect/mailbox.ts +0 -318
  118. package/src/streams/events.test.ts +0 -924
  119. package/src/streams/events.ts +0 -329
  120. package/src/streams/index.test.ts +0 -229
  121. package/src/streams/index.ts +0 -578
  122. package/src/streams/migrations.test.ts +0 -359
  123. package/src/streams/migrations.ts +0 -362
  124. package/src/streams/projections.test.ts +0 -611
  125. package/src/streams/projections.ts +0 -504
  126. package/src/streams/store.integration.test.ts +0 -658
  127. package/src/streams/store.ts +0 -1075
  128. package/src/streams/swarm-mail.ts +0 -552
  129. package/test-bug-fixes.ts +0 -86
  130. package/vitest.integration.config.ts +0 -13
  131. package/workflow-integration-analysis.md +0 -876
@@ -1,504 +0,0 @@
1
- /**
2
- * Swarm Mail Projections Layer - Query materialized views
3
- *
4
- * Projections are the read-side of CQRS. They query denormalized
5
- * materialized views for fast reads. Views are updated by the
6
- * event store when events are appended.
7
- *
8
- * Key projections:
9
- * - getAgents: List registered agents
10
- * - getInbox: Get messages for an agent
11
- * - getActiveReservations: Get current file locks
12
- * - checkConflicts: Detect reservation conflicts
13
- */
14
- import { getDatabase } from "./index";
15
- import { minimatch } from "minimatch";
16
-
17
- // ============================================================================
18
- // Types
19
- // ============================================================================
20
-
21
- export interface Agent {
22
- id: number;
23
- name: string;
24
- program: string;
25
- model: string;
26
- task_description: string | null;
27
- registered_at: number;
28
- last_active_at: number;
29
- }
30
-
31
- export interface Message {
32
- id: number;
33
- from_agent: string;
34
- subject: string;
35
- body?: string;
36
- thread_id: string | null;
37
- importance: string;
38
- ack_required: boolean;
39
- created_at: number;
40
- read_at?: number | null;
41
- acked_at?: number | null;
42
- }
43
-
44
- export interface Reservation {
45
- id: number;
46
- agent_name: string;
47
- path_pattern: string;
48
- exclusive: boolean;
49
- reason: string | null;
50
- created_at: number;
51
- expires_at: number;
52
- }
53
-
54
- export interface Conflict {
55
- path: string;
56
- holder: string;
57
- pattern: string;
58
- exclusive: boolean;
59
- }
60
-
61
- // ============================================================================
62
- // Agent Projections
63
- // ============================================================================
64
-
65
- /**
66
- * Get all agents for a project
67
- */
68
- export async function getAgents(
69
- projectKey: string,
70
- projectPath?: string,
71
- ): Promise<Agent[]> {
72
- const db = await getDatabase(projectPath);
73
-
74
- const result = await db.query<Agent>(
75
- `SELECT id, name, program, model, task_description, registered_at, last_active_at
76
- FROM agents
77
- WHERE project_key = $1
78
- ORDER BY registered_at ASC`,
79
- [projectKey],
80
- );
81
-
82
- return result.rows;
83
- }
84
-
85
- /**
86
- * Get a specific agent by name
87
- */
88
- export async function getAgent(
89
- projectKey: string,
90
- agentName: string,
91
- projectPath?: string,
92
- ): Promise<Agent | null> {
93
- const db = await getDatabase(projectPath);
94
-
95
- const result = await db.query<Agent>(
96
- `SELECT id, name, program, model, task_description, registered_at, last_active_at
97
- FROM agents
98
- WHERE project_key = $1 AND name = $2`,
99
- [projectKey, agentName],
100
- );
101
-
102
- return result.rows[0] ?? null;
103
- }
104
-
105
- // ============================================================================
106
- // Message Projections
107
- // ============================================================================
108
-
109
- export interface InboxOptions {
110
- limit?: number;
111
- urgentOnly?: boolean;
112
- unreadOnly?: boolean;
113
- includeBodies?: boolean;
114
- sinceTs?: string;
115
- }
116
-
117
- /**
118
- * Get inbox messages for an agent
119
- */
120
- export async function getInbox(
121
- projectKey: string,
122
- agentName: string,
123
- options: InboxOptions = {},
124
- projectPath?: string,
125
- ): Promise<Message[]> {
126
- const db = await getDatabase(projectPath);
127
-
128
- const {
129
- limit = 50,
130
- urgentOnly = false,
131
- unreadOnly = false,
132
- includeBodies = true,
133
- } = options;
134
-
135
- // Build query with conditions
136
- const conditions = ["m.project_key = $1", "mr.agent_name = $2"];
137
- const params: (string | number)[] = [projectKey, agentName];
138
- let paramIndex = 3;
139
-
140
- if (urgentOnly) {
141
- conditions.push(`m.importance = 'urgent'`);
142
- }
143
-
144
- if (unreadOnly) {
145
- conditions.push(`mr.read_at IS NULL`);
146
- }
147
-
148
- const bodySelect = includeBodies ? ", m.body" : "";
149
-
150
- const query = `
151
- SELECT m.id, m.from_agent, m.subject${bodySelect}, m.thread_id,
152
- m.importance, m.ack_required, m.created_at,
153
- mr.read_at, mr.acked_at
154
- FROM messages m
155
- JOIN message_recipients mr ON m.id = mr.message_id
156
- WHERE ${conditions.join(" AND ")}
157
- ORDER BY m.created_at DESC
158
- LIMIT $${paramIndex}
159
- `;
160
- params.push(limit);
161
-
162
- const result = await db.query<Message>(query, params);
163
-
164
- return result.rows;
165
- }
166
-
167
- /**
168
- * Get a single message by ID with full body
169
- */
170
- export async function getMessage(
171
- projectKey: string,
172
- messageId: number,
173
- projectPath?: string,
174
- ): Promise<Message | null> {
175
- const db = await getDatabase(projectPath);
176
-
177
- const result = await db.query<Message>(
178
- `SELECT id, from_agent, subject, body, thread_id, importance, ack_required, created_at
179
- FROM messages
180
- WHERE project_key = $1 AND id = $2`,
181
- [projectKey, messageId],
182
- );
183
-
184
- return result.rows[0] ?? null;
185
- }
186
-
187
- /**
188
- * Get all messages in a thread
189
- */
190
- export async function getThreadMessages(
191
- projectKey: string,
192
- threadId: string,
193
- projectPath?: string,
194
- ): Promise<Message[]> {
195
- const db = await getDatabase(projectPath);
196
-
197
- const result = await db.query<Message>(
198
- `SELECT id, from_agent, subject, body, thread_id, importance, ack_required, created_at
199
- FROM messages
200
- WHERE project_key = $1 AND thread_id = $2
201
- ORDER BY created_at ASC`,
202
- [projectKey, threadId],
203
- );
204
-
205
- return result.rows;
206
- }
207
-
208
- // ============================================================================
209
- // Reservation Projections
210
- // ============================================================================
211
-
212
- /**
213
- * Get active (non-expired, non-released) reservations
214
- */
215
- export async function getActiveReservations(
216
- projectKey: string,
217
- projectPath?: string,
218
- agentName?: string,
219
- ): Promise<Reservation[]> {
220
- const db = await getDatabase(projectPath);
221
-
222
- const now = Date.now();
223
- const baseQuery = `
224
- SELECT id, agent_name, path_pattern, exclusive, reason, created_at, expires_at
225
- FROM reservations
226
- WHERE project_key = $1
227
- AND released_at IS NULL
228
- AND expires_at > $2
229
- `;
230
- const params: (string | number)[] = [projectKey, now];
231
- let query = baseQuery;
232
-
233
- if (agentName) {
234
- query += ` AND agent_name = $3`;
235
- params.push(agentName);
236
- }
237
-
238
- query += ` ORDER BY created_at ASC`;
239
-
240
- const result = await db.query<Reservation>(query, params);
241
-
242
- return result.rows;
243
- }
244
-
245
- /**
246
- * Check for conflicts with existing reservations
247
- *
248
- * Returns conflicts where:
249
- * - Another agent holds an exclusive reservation
250
- * - The path matches (exact or glob pattern)
251
- * - The reservation is still active
252
- */
253
- export async function checkConflicts(
254
- projectKey: string,
255
- agentName: string,
256
- paths: string[],
257
- projectPath?: string,
258
- ): Promise<Conflict[]> {
259
- // Get all active exclusive reservations from OTHER agents
260
- const reservations = await getActiveReservations(projectKey, projectPath);
261
-
262
- const conflicts: Conflict[] = [];
263
-
264
- for (const reservation of reservations) {
265
- // Skip own reservations
266
- if (reservation.agent_name === agentName) {
267
- continue;
268
- }
269
-
270
- // Skip non-exclusive reservations
271
- if (!reservation.exclusive) {
272
- continue;
273
- }
274
-
275
- // Check each requested path against the reservation pattern
276
- for (const path of paths) {
277
- if (pathMatches(path, reservation.path_pattern)) {
278
- console.warn("[SwarmMail] Conflict detected", {
279
- path,
280
- holder: reservation.agent_name,
281
- pattern: reservation.path_pattern,
282
- requestedBy: agentName,
283
- });
284
-
285
- conflicts.push({
286
- path,
287
- holder: reservation.agent_name,
288
- pattern: reservation.path_pattern,
289
- exclusive: reservation.exclusive,
290
- });
291
- }
292
- }
293
- }
294
-
295
- if (conflicts.length > 0) {
296
- console.warn("[SwarmMail] Total conflicts detected", {
297
- count: conflicts.length,
298
- requestedBy: agentName,
299
- paths,
300
- });
301
- }
302
-
303
- return conflicts;
304
- }
305
-
306
- /**
307
- * Check if a path matches a pattern (supports glob patterns)
308
- */
309
- function pathMatches(path: string, pattern: string): boolean {
310
- // Exact match
311
- if (path === pattern) {
312
- return true;
313
- }
314
-
315
- // Glob match using minimatch
316
- return minimatch(path, pattern);
317
- }
318
-
319
- // ============================================================================
320
- // Eval Records Projections
321
- // ============================================================================
322
-
323
- export interface EvalRecord {
324
- id: string;
325
- project_key: string;
326
- task: string;
327
- context: string | null;
328
- strategy: string;
329
- epic_title: string;
330
- subtasks: Array<{
331
- title: string;
332
- files: string[];
333
- priority?: number;
334
- }>;
335
- outcomes?: Array<{
336
- bead_id: string;
337
- planned_files: string[];
338
- actual_files: string[];
339
- duration_ms: number;
340
- error_count: number;
341
- retry_count: number;
342
- success: boolean;
343
- }>;
344
- overall_success: boolean | null;
345
- total_duration_ms: number | null;
346
- total_errors: number | null;
347
- human_accepted: boolean | null;
348
- human_modified: boolean | null;
349
- human_notes: string | null;
350
- file_overlap_count: number | null;
351
- scope_accuracy: number | null;
352
- time_balance_ratio: number | null;
353
- created_at: number;
354
- updated_at: number;
355
- }
356
-
357
- export interface EvalStats {
358
- totalRecords: number;
359
- successRate: number;
360
- avgDurationMs: number;
361
- byStrategy: Record<string, number>;
362
- }
363
-
364
- /**
365
- * Get eval records with optional filters
366
- */
367
- export async function getEvalRecords(
368
- projectKey: string,
369
- options?: { limit?: number; strategy?: string },
370
- projectPath?: string,
371
- ): Promise<EvalRecord[]> {
372
- const db = await getDatabase(projectPath);
373
-
374
- const conditions = ["project_key = $1"];
375
- const params: (string | number)[] = [projectKey];
376
- let paramIndex = 2;
377
-
378
- if (options?.strategy) {
379
- conditions.push(`strategy = $${paramIndex++}`);
380
- params.push(options.strategy);
381
- }
382
-
383
- const whereClause = conditions.join(" AND ");
384
- let query = `
385
- SELECT id, project_key, task, context, strategy, epic_title, subtasks,
386
- outcomes, overall_success, total_duration_ms, total_errors,
387
- human_accepted, human_modified, human_notes,
388
- file_overlap_count, scope_accuracy, time_balance_ratio,
389
- created_at, updated_at
390
- FROM eval_records
391
- WHERE ${whereClause}
392
- ORDER BY created_at DESC
393
- `;
394
-
395
- if (options?.limit) {
396
- query += ` LIMIT $${paramIndex}`;
397
- params.push(options.limit);
398
- }
399
-
400
- const result = await db.query<{
401
- id: string;
402
- project_key: string;
403
- task: string;
404
- context: string | null;
405
- strategy: string;
406
- epic_title: string;
407
- subtasks: string;
408
- outcomes: string | null;
409
- overall_success: boolean | null;
410
- total_duration_ms: number | null;
411
- total_errors: number | null;
412
- human_accepted: boolean | null;
413
- human_modified: boolean | null;
414
- human_notes: string | null;
415
- file_overlap_count: number | null;
416
- scope_accuracy: number | null;
417
- time_balance_ratio: number | null;
418
- created_at: string;
419
- updated_at: string;
420
- }>(query, params);
421
-
422
- return result.rows.map((row) => ({
423
- id: row.id,
424
- project_key: row.project_key,
425
- task: row.task,
426
- context: row.context,
427
- strategy: row.strategy,
428
- epic_title: row.epic_title,
429
- // PGlite returns JSONB columns as already-parsed objects
430
- subtasks:
431
- typeof row.subtasks === "string"
432
- ? JSON.parse(row.subtasks)
433
- : row.subtasks,
434
- outcomes: row.outcomes
435
- ? typeof row.outcomes === "string"
436
- ? JSON.parse(row.outcomes)
437
- : row.outcomes
438
- : undefined,
439
- overall_success: row.overall_success,
440
- total_duration_ms: row.total_duration_ms,
441
- total_errors: row.total_errors,
442
- human_accepted: row.human_accepted,
443
- human_modified: row.human_modified,
444
- human_notes: row.human_notes,
445
- file_overlap_count: row.file_overlap_count,
446
- scope_accuracy: row.scope_accuracy,
447
- time_balance_ratio: row.time_balance_ratio,
448
- created_at: parseInt(row.created_at as string),
449
- updated_at: parseInt(row.updated_at as string),
450
- }));
451
- }
452
-
453
- /**
454
- * Get eval statistics for a project
455
- */
456
- export async function getEvalStats(
457
- projectKey: string,
458
- projectPath?: string,
459
- ): Promise<EvalStats> {
460
- const db = await getDatabase(projectPath);
461
-
462
- // Get overall stats
463
- const overallResult = await db.query<{
464
- total_records: string;
465
- success_count: string;
466
- avg_duration: string;
467
- }>(
468
- `SELECT
469
- COUNT(*) as total_records,
470
- COUNT(*) FILTER (WHERE overall_success = true) as success_count,
471
- AVG(total_duration_ms) as avg_duration
472
- FROM eval_records
473
- WHERE project_key = $1`,
474
- [projectKey],
475
- );
476
-
477
- const totalRecords = parseInt(overallResult.rows[0]?.total_records || "0");
478
- const successCount = parseInt(overallResult.rows[0]?.success_count || "0");
479
- const avgDurationMs = parseFloat(overallResult.rows[0]?.avg_duration || "0");
480
-
481
- // Get by-strategy breakdown
482
- const strategyResult = await db.query<{
483
- strategy: string;
484
- count: string;
485
- }>(
486
- `SELECT strategy, COUNT(*) as count
487
- FROM eval_records
488
- WHERE project_key = $1
489
- GROUP BY strategy`,
490
- [projectKey],
491
- );
492
-
493
- const byStrategy: Record<string, number> = {};
494
- for (const row of strategyResult.rows) {
495
- byStrategy[row.strategy] = parseInt(row.count);
496
- }
497
-
498
- return {
499
- totalRecords,
500
- successRate: totalRecords > 0 ? successCount / totalRecords : 0,
501
- avgDurationMs,
502
- byStrategy,
503
- };
504
- }