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,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
- });