opencode-swarm-plugin 0.22.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 (128) hide show
  1. package/.turbo/turbo-build.log +9 -0
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +109 -429
  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 +498 -4246
  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 +493 -4241
  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/package.json +13 -24
  73. package/src/agent-mail.ts +1 -1
  74. package/src/beads.ts +1 -2
  75. package/src/index.ts +2 -2
  76. package/src/learning.integration.test.ts +66 -11
  77. package/src/mandate-storage.test.ts +3 -3
  78. package/src/storage.ts +78 -10
  79. package/src/swarm-mail.ts +3 -3
  80. package/src/swarm-orchestrate.ts +7 -7
  81. package/src/tool-availability.ts +1 -1
  82. package/tsconfig.json +1 -1
  83. package/.beads/.local_version +0 -1
  84. package/.beads/README.md +0 -81
  85. package/.beads/analysis/skill-architecture-meta-skills.md +0 -1562
  86. package/.beads/config.yaml +0 -62
  87. package/.beads/issues.jsonl +0 -2197
  88. package/.beads/metadata.json +0 -4
  89. package/.gitattributes +0 -3
  90. package/.github/workflows/ci.yml +0 -30
  91. package/.github/workflows/opencode.yml +0 -31
  92. package/.opencode/skills/tdd/SKILL.md +0 -182
  93. package/INTEGRATION_EXAMPLE.md +0 -66
  94. package/VERIFICATION_QUALITY_PATTERNS.md +0 -565
  95. package/bun.lock +0 -286
  96. package/dist/pglite.data +0 -0
  97. package/dist/pglite.wasm +0 -0
  98. package/src/streams/agent-mail.test.ts +0 -777
  99. package/src/streams/agent-mail.ts +0 -535
  100. package/src/streams/debug.test.ts +0 -500
  101. package/src/streams/debug.ts +0 -727
  102. package/src/streams/effect/ask.integration.test.ts +0 -314
  103. package/src/streams/effect/ask.ts +0 -202
  104. package/src/streams/effect/cursor.integration.test.ts +0 -418
  105. package/src/streams/effect/cursor.ts +0 -288
  106. package/src/streams/effect/deferred.test.ts +0 -357
  107. package/src/streams/effect/deferred.ts +0 -445
  108. package/src/streams/effect/index.ts +0 -17
  109. package/src/streams/effect/layers.ts +0 -73
  110. package/src/streams/effect/lock.test.ts +0 -385
  111. package/src/streams/effect/lock.ts +0 -399
  112. package/src/streams/effect/mailbox.test.ts +0 -260
  113. package/src/streams/effect/mailbox.ts +0 -318
  114. package/src/streams/events.test.ts +0 -924
  115. package/src/streams/events.ts +0 -329
  116. package/src/streams/index.test.ts +0 -229
  117. package/src/streams/index.ts +0 -578
  118. package/src/streams/migrations.test.ts +0 -359
  119. package/src/streams/migrations.ts +0 -362
  120. package/src/streams/projections.test.ts +0 -611
  121. package/src/streams/projections.ts +0 -504
  122. package/src/streams/store.integration.test.ts +0 -658
  123. package/src/streams/store.ts +0 -1075
  124. package/src/streams/swarm-mail.ts +0 -552
  125. package/test-bug-fixes.ts +0 -86
  126. package/vitest.integration.config.ts +0 -19
  127. package/vitest.integration.setup.ts +0 -48
  128. 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
- }