opencode-swarm-plugin 0.22.0 → 0.23.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 (128) hide show
  1. package/.turbo/turbo-build.log +9 -0
  2. package/CHANGELOG.md +20 -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,611 +0,0 @@
1
- /**
2
- * Unit tests for Projections Layer (TDD - RED phase)
3
- *
4
- * Projections query materialized views to compute current state.
5
- * These are the read-side of CQRS - fast queries over denormalized data.
6
- */
7
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
8
- import { mkdir, rm } from "node:fs/promises";
9
- import { join } from "node:path";
10
- import { tmpdir } from "node:os";
11
- import { closeDatabase } from "./index";
12
- import { registerAgent, sendMessage, reserveFiles, appendEvent } from "./store";
13
- import { createEvent } from "./events";
14
- import {
15
- getAgents,
16
- getAgent,
17
- getInbox,
18
- getMessage,
19
- getActiveReservations,
20
- checkConflicts,
21
- getThreadMessages,
22
- } from "./projections";
23
-
24
- let TEST_PROJECT_PATH: string;
25
- const PROJECT_KEY = "test-project";
26
-
27
- describe("Projections", () => {
28
- beforeEach(async () => {
29
- TEST_PROJECT_PATH = join(
30
- tmpdir(),
31
- `projections-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
32
- );
33
- await mkdir(TEST_PROJECT_PATH, { recursive: true });
34
- });
35
-
36
- afterEach(async () => {
37
- await closeDatabase(TEST_PROJECT_PATH);
38
- try {
39
- await rm(join(TEST_PROJECT_PATH, ".opencode"), { recursive: true });
40
- } catch {
41
- // Ignore
42
- }
43
- });
44
-
45
- // ==========================================================================
46
- // Agent Projections
47
- // ==========================================================================
48
-
49
- describe("getAgents", () => {
50
- it("returns empty array when no agents registered", async () => {
51
- const agents = await getAgents(PROJECT_KEY, TEST_PROJECT_PATH);
52
- expect(agents).toEqual([]);
53
- });
54
-
55
- it("returns all agents for a project", async () => {
56
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
57
- await registerAgent(PROJECT_KEY, "RedStone", {}, TEST_PROJECT_PATH);
58
-
59
- const agents = await getAgents(PROJECT_KEY, TEST_PROJECT_PATH);
60
-
61
- expect(agents.length).toBe(2);
62
- expect(agents.map((a) => a.name).sort()).toEqual([
63
- "BlueLake",
64
- "RedStone",
65
- ]);
66
- });
67
-
68
- it("only returns agents for specified project", async () => {
69
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
70
- await registerAgent("other-project", "RedStone", {}, TEST_PROJECT_PATH);
71
-
72
- const agents = await getAgents(PROJECT_KEY, TEST_PROJECT_PATH);
73
-
74
- expect(agents.length).toBe(1);
75
- expect(agents[0]?.name).toBe("BlueLake");
76
- });
77
- });
78
-
79
- describe("getAgent", () => {
80
- it("returns null for non-existent agent", async () => {
81
- const agent = await getAgent(
82
- PROJECT_KEY,
83
- "NonExistent",
84
- TEST_PROJECT_PATH,
85
- );
86
- expect(agent).toBeNull();
87
- });
88
-
89
- it("returns agent details", async () => {
90
- await registerAgent(
91
- PROJECT_KEY,
92
- "BlueLake",
93
- {
94
- program: "opencode",
95
- model: "claude-sonnet-4",
96
- taskDescription: "Testing",
97
- },
98
- TEST_PROJECT_PATH,
99
- );
100
-
101
- const agent = await getAgent(PROJECT_KEY, "BlueLake", TEST_PROJECT_PATH);
102
-
103
- expect(agent).not.toBeNull();
104
- expect(agent?.name).toBe("BlueLake");
105
- expect(agent?.program).toBe("opencode");
106
- expect(agent?.model).toBe("claude-sonnet-4");
107
- expect(agent?.task_description).toBe("Testing");
108
- });
109
-
110
- it("returns updated last_active_at after activity", async () => {
111
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
112
-
113
- const before = await getAgent(PROJECT_KEY, "BlueLake", TEST_PROJECT_PATH);
114
- const beforeActive = before?.last_active_at;
115
-
116
- // Wait a bit and send activity
117
- await new Promise((r) => setTimeout(r, 10));
118
- await appendEvent(
119
- createEvent("agent_active", {
120
- project_key: PROJECT_KEY,
121
- agent_name: "BlueLake",
122
- }),
123
- TEST_PROJECT_PATH,
124
- );
125
-
126
- const after = await getAgent(PROJECT_KEY, "BlueLake", TEST_PROJECT_PATH);
127
-
128
- expect(after?.last_active_at).toBeGreaterThan(beforeActive ?? 0);
129
- });
130
- });
131
-
132
- // ==========================================================================
133
- // Message Projections
134
- // ==========================================================================
135
-
136
- describe("getInbox", () => {
137
- it("returns empty array when no messages", async () => {
138
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
139
-
140
- const inbox = await getInbox(
141
- PROJECT_KEY,
142
- "BlueLake",
143
- {},
144
- TEST_PROJECT_PATH,
145
- );
146
-
147
- expect(inbox).toEqual([]);
148
- });
149
-
150
- it("returns messages sent to agent", async () => {
151
- await registerAgent(PROJECT_KEY, "Sender", {}, TEST_PROJECT_PATH);
152
- await registerAgent(PROJECT_KEY, "Receiver", {}, TEST_PROJECT_PATH);
153
-
154
- await sendMessage(
155
- PROJECT_KEY,
156
- "Sender",
157
- ["Receiver"],
158
- "Hello",
159
- "World",
160
- {},
161
- TEST_PROJECT_PATH,
162
- );
163
-
164
- const inbox = await getInbox(
165
- PROJECT_KEY,
166
- "Receiver",
167
- {},
168
- TEST_PROJECT_PATH,
169
- );
170
-
171
- expect(inbox.length).toBe(1);
172
- expect(inbox[0]?.subject).toBe("Hello");
173
- expect(inbox[0]?.from_agent).toBe("Sender");
174
- });
175
-
176
- it("respects limit parameter", async () => {
177
- await registerAgent(PROJECT_KEY, "Sender", {}, TEST_PROJECT_PATH);
178
- await registerAgent(PROJECT_KEY, "Receiver", {}, TEST_PROJECT_PATH);
179
-
180
- for (let i = 0; i < 5; i++) {
181
- await sendMessage(
182
- PROJECT_KEY,
183
- "Sender",
184
- ["Receiver"],
185
- `Message ${i}`,
186
- "Body",
187
- {},
188
- TEST_PROJECT_PATH,
189
- );
190
- }
191
-
192
- const inbox = await getInbox(
193
- PROJECT_KEY,
194
- "Receiver",
195
- { limit: 2 },
196
- TEST_PROJECT_PATH,
197
- );
198
-
199
- expect(inbox.length).toBe(2);
200
- });
201
-
202
- it("filters by urgentOnly", async () => {
203
- await registerAgent(PROJECT_KEY, "Sender", {}, TEST_PROJECT_PATH);
204
- await registerAgent(PROJECT_KEY, "Receiver", {}, TEST_PROJECT_PATH);
205
-
206
- await sendMessage(
207
- PROJECT_KEY,
208
- "Sender",
209
- ["Receiver"],
210
- "Normal",
211
- "Body",
212
- { importance: "normal" },
213
- TEST_PROJECT_PATH,
214
- );
215
- await sendMessage(
216
- PROJECT_KEY,
217
- "Sender",
218
- ["Receiver"],
219
- "Urgent",
220
- "Body",
221
- { importance: "urgent" },
222
- TEST_PROJECT_PATH,
223
- );
224
-
225
- const inbox = await getInbox(
226
- PROJECT_KEY,
227
- "Receiver",
228
- { urgentOnly: true },
229
- TEST_PROJECT_PATH,
230
- );
231
-
232
- expect(inbox.length).toBe(1);
233
- expect(inbox[0]?.subject).toBe("Urgent");
234
- });
235
-
236
- it("filters by unreadOnly", async () => {
237
- await registerAgent(PROJECT_KEY, "Sender", {}, TEST_PROJECT_PATH);
238
- await registerAgent(PROJECT_KEY, "Receiver", {}, TEST_PROJECT_PATH);
239
-
240
- await sendMessage(
241
- PROJECT_KEY,
242
- "Sender",
243
- ["Receiver"],
244
- "Message 1",
245
- "Body",
246
- {},
247
- TEST_PROJECT_PATH,
248
- );
249
- await sendMessage(
250
- PROJECT_KEY,
251
- "Sender",
252
- ["Receiver"],
253
- "Message 2",
254
- "Body",
255
- {},
256
- TEST_PROJECT_PATH,
257
- );
258
-
259
- // Mark second message as read
260
- await appendEvent(
261
- createEvent("message_read", {
262
- project_key: PROJECT_KEY,
263
- message_id: 2, // Second message
264
- agent_name: "Receiver",
265
- }),
266
- TEST_PROJECT_PATH,
267
- );
268
-
269
- const inbox = await getInbox(
270
- PROJECT_KEY,
271
- "Receiver",
272
- { unreadOnly: true },
273
- TEST_PROJECT_PATH,
274
- );
275
-
276
- expect(inbox.length).toBe(1);
277
- expect(inbox[0]?.subject).toBe("Message 1");
278
- });
279
-
280
- it("excludes body when includeBodies is false", async () => {
281
- await registerAgent(PROJECT_KEY, "Sender", {}, TEST_PROJECT_PATH);
282
- await registerAgent(PROJECT_KEY, "Receiver", {}, TEST_PROJECT_PATH);
283
-
284
- await sendMessage(
285
- PROJECT_KEY,
286
- "Sender",
287
- ["Receiver"],
288
- "Hello",
289
- "This is the body",
290
- {},
291
- TEST_PROJECT_PATH,
292
- );
293
-
294
- const inbox = await getInbox(
295
- PROJECT_KEY,
296
- "Receiver",
297
- { includeBodies: false },
298
- TEST_PROJECT_PATH,
299
- );
300
-
301
- expect(inbox[0]?.body).toBeUndefined();
302
- });
303
- });
304
-
305
- describe("getMessage", () => {
306
- it("returns null for non-existent message", async () => {
307
- const msg = await getMessage(PROJECT_KEY, 999, TEST_PROJECT_PATH);
308
- expect(msg).toBeNull();
309
- });
310
-
311
- it("returns full message with body", async () => {
312
- await registerAgent(PROJECT_KEY, "Sender", {}, TEST_PROJECT_PATH);
313
- await registerAgent(PROJECT_KEY, "Receiver", {}, TEST_PROJECT_PATH);
314
-
315
- await sendMessage(
316
- PROJECT_KEY,
317
- "Sender",
318
- ["Receiver"],
319
- "Hello",
320
- "Full body content",
321
- { threadId: "bd-123" },
322
- TEST_PROJECT_PATH,
323
- );
324
-
325
- const msg = await getMessage(PROJECT_KEY, 1, TEST_PROJECT_PATH);
326
-
327
- expect(msg).not.toBeNull();
328
- expect(msg?.subject).toBe("Hello");
329
- expect(msg?.body).toBe("Full body content");
330
- expect(msg?.thread_id).toBe("bd-123");
331
- });
332
- });
333
-
334
- describe("getThreadMessages", () => {
335
- it("returns empty array for non-existent thread", async () => {
336
- const messages = await getThreadMessages(
337
- PROJECT_KEY,
338
- "non-existent",
339
- TEST_PROJECT_PATH,
340
- );
341
- expect(messages).toEqual([]);
342
- });
343
-
344
- it("returns all messages in a thread", async () => {
345
- await registerAgent(PROJECT_KEY, "Agent1", {}, TEST_PROJECT_PATH);
346
- await registerAgent(PROJECT_KEY, "Agent2", {}, TEST_PROJECT_PATH);
347
-
348
- const threadId = "bd-epic-123";
349
-
350
- await sendMessage(
351
- PROJECT_KEY,
352
- "Agent1",
353
- ["Agent2"],
354
- "First",
355
- "Body 1",
356
- { threadId },
357
- TEST_PROJECT_PATH,
358
- );
359
- await sendMessage(
360
- PROJECT_KEY,
361
- "Agent2",
362
- ["Agent1"],
363
- "Reply",
364
- "Body 2",
365
- { threadId },
366
- TEST_PROJECT_PATH,
367
- );
368
- await sendMessage(
369
- PROJECT_KEY,
370
- "Agent1",
371
- ["Agent2"],
372
- "Unrelated",
373
- "Body 3",
374
- {}, // No thread
375
- TEST_PROJECT_PATH,
376
- );
377
-
378
- const messages = await getThreadMessages(
379
- PROJECT_KEY,
380
- threadId,
381
- TEST_PROJECT_PATH,
382
- );
383
-
384
- expect(messages.length).toBe(2);
385
- expect(messages[0]?.subject).toBe("First");
386
- expect(messages[1]?.subject).toBe("Reply");
387
- });
388
- });
389
-
390
- // ==========================================================================
391
- // Reservation Projections
392
- // ==========================================================================
393
-
394
- describe("getActiveReservations", () => {
395
- it("returns empty array when no reservations", async () => {
396
- const reservations = await getActiveReservations(
397
- PROJECT_KEY,
398
- TEST_PROJECT_PATH,
399
- );
400
- expect(reservations).toEqual([]);
401
- });
402
-
403
- it("returns active reservations", async () => {
404
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
405
-
406
- await reserveFiles(
407
- PROJECT_KEY,
408
- "BlueLake",
409
- ["src/auth/**"],
410
- { reason: "Working on auth", ttlSeconds: 3600 },
411
- TEST_PROJECT_PATH,
412
- );
413
-
414
- const reservations = await getActiveReservations(
415
- PROJECT_KEY,
416
- TEST_PROJECT_PATH,
417
- );
418
-
419
- expect(reservations.length).toBe(1);
420
- expect(reservations[0]?.agent_name).toBe("BlueLake");
421
- expect(reservations[0]?.path_pattern).toBe("src/auth/**");
422
- });
423
-
424
- it("excludes released reservations", async () => {
425
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
426
-
427
- await reserveFiles(
428
- PROJECT_KEY,
429
- "BlueLake",
430
- ["src/auth/**"],
431
- { ttlSeconds: 3600 },
432
- TEST_PROJECT_PATH,
433
- );
434
-
435
- // Release the reservation
436
- await appendEvent(
437
- createEvent("file_released", {
438
- project_key: PROJECT_KEY,
439
- agent_name: "BlueLake",
440
- paths: ["src/auth/**"],
441
- }),
442
- TEST_PROJECT_PATH,
443
- );
444
-
445
- const reservations = await getActiveReservations(
446
- PROJECT_KEY,
447
- TEST_PROJECT_PATH,
448
- );
449
-
450
- expect(reservations.length).toBe(0);
451
- });
452
-
453
- it("excludes expired reservations", async () => {
454
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
455
-
456
- // Create reservation that expires immediately
457
- await appendEvent(
458
- createEvent("file_reserved", {
459
- project_key: PROJECT_KEY,
460
- agent_name: "BlueLake",
461
- paths: ["src/expired/**"],
462
- exclusive: true,
463
- ttl_seconds: 0,
464
- expires_at: Date.now() - 1000, // Already expired
465
- }),
466
- TEST_PROJECT_PATH,
467
- );
468
-
469
- const reservations = await getActiveReservations(
470
- PROJECT_KEY,
471
- TEST_PROJECT_PATH,
472
- );
473
-
474
- expect(reservations.length).toBe(0);
475
- });
476
-
477
- it("filters by agent when specified", async () => {
478
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
479
- await registerAgent(PROJECT_KEY, "RedStone", {}, TEST_PROJECT_PATH);
480
-
481
- await reserveFiles(
482
- PROJECT_KEY,
483
- "BlueLake",
484
- ["src/a/**"],
485
- {},
486
- TEST_PROJECT_PATH,
487
- );
488
- await reserveFiles(
489
- PROJECT_KEY,
490
- "RedStone",
491
- ["src/b/**"],
492
- {},
493
- TEST_PROJECT_PATH,
494
- );
495
-
496
- const reservations = await getActiveReservations(
497
- PROJECT_KEY,
498
- TEST_PROJECT_PATH,
499
- "BlueLake",
500
- );
501
-
502
- expect(reservations.length).toBe(1);
503
- expect(reservations[0]?.agent_name).toBe("BlueLake");
504
- });
505
- });
506
-
507
- describe("checkConflicts", () => {
508
- it("returns empty array when no conflicts", async () => {
509
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
510
- await reserveFiles(
511
- PROJECT_KEY,
512
- "BlueLake",
513
- ["src/a/**"],
514
- {},
515
- TEST_PROJECT_PATH,
516
- );
517
-
518
- const conflicts = await checkConflicts(
519
- PROJECT_KEY,
520
- "RedStone",
521
- ["src/b/**"], // Different path
522
- TEST_PROJECT_PATH,
523
- );
524
-
525
- expect(conflicts).toEqual([]);
526
- });
527
-
528
- it("detects exact path conflicts", async () => {
529
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
530
- await reserveFiles(
531
- PROJECT_KEY,
532
- "BlueLake",
533
- ["src/auth.ts"],
534
- { exclusive: true },
535
- TEST_PROJECT_PATH,
536
- );
537
-
538
- const conflicts = await checkConflicts(
539
- PROJECT_KEY,
540
- "RedStone",
541
- ["src/auth.ts"],
542
- TEST_PROJECT_PATH,
543
- );
544
-
545
- expect(conflicts.length).toBe(1);
546
- expect(conflicts[0]?.path).toBe("src/auth.ts");
547
- expect(conflicts[0]?.holder).toBe("BlueLake");
548
- });
549
-
550
- it("detects glob pattern conflicts", async () => {
551
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
552
- await reserveFiles(
553
- PROJECT_KEY,
554
- "BlueLake",
555
- ["src/auth/**"],
556
- { exclusive: true },
557
- TEST_PROJECT_PATH,
558
- );
559
-
560
- const conflicts = await checkConflicts(
561
- PROJECT_KEY,
562
- "RedStone",
563
- ["src/auth/oauth.ts"], // Matches glob
564
- TEST_PROJECT_PATH,
565
- );
566
-
567
- expect(conflicts.length).toBe(1);
568
- expect(conflicts[0]?.holder).toBe("BlueLake");
569
- });
570
-
571
- it("ignores own reservations", async () => {
572
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
573
- await reserveFiles(
574
- PROJECT_KEY,
575
- "BlueLake",
576
- ["src/auth/**"],
577
- { exclusive: true },
578
- TEST_PROJECT_PATH,
579
- );
580
-
581
- const conflicts = await checkConflicts(
582
- PROJECT_KEY,
583
- "BlueLake", // Same agent
584
- ["src/auth/oauth.ts"],
585
- TEST_PROJECT_PATH,
586
- );
587
-
588
- expect(conflicts).toEqual([]);
589
- });
590
-
591
- it("ignores non-exclusive reservations", async () => {
592
- await registerAgent(PROJECT_KEY, "BlueLake", {}, TEST_PROJECT_PATH);
593
- await reserveFiles(
594
- PROJECT_KEY,
595
- "BlueLake",
596
- ["src/shared/**"],
597
- { exclusive: false }, // Non-exclusive
598
- TEST_PROJECT_PATH,
599
- );
600
-
601
- const conflicts = await checkConflicts(
602
- PROJECT_KEY,
603
- "RedStone",
604
- ["src/shared/utils.ts"],
605
- TEST_PROJECT_PATH,
606
- );
607
-
608
- expect(conflicts).toEqual([]);
609
- });
610
- });
611
- });