@zoebuildsai/trace 1.5.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.
- package/.gitignore +115 -0
- package/.trace/progress.json +22 -0
- package/README.md +466 -0
- package/RELEASE-NOTES-1.5.0.md +410 -0
- package/STATUS.md +245 -0
- package/dist/auto-commit.d.ts +66 -0
- package/dist/auto-commit.d.ts.map +1 -0
- package/dist/auto-commit.js +180 -0
- package/dist/auto-commit.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +246 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +46 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +256 -0
- package/dist/commands.js.map +1 -0
- package/dist/diff.d.ts +23 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +106 -0
- package/dist/diff.js.map +1 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js.map +1 -0
- package/dist/index-cache.d.ts +35 -0
- package/dist/index-cache.d.ts.map +1 -0
- package/dist/index-cache.js +114 -0
- package/dist/index-cache.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +45 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +151 -0
- package/dist/storage.js.map +1 -0
- package/dist/sync.d.ts +60 -0
- package/dist/sync.js +184 -0
- package/dist/tags.d.ts +85 -0
- package/dist/tags.d.ts.map +1 -0
- package/dist/tags.js +219 -0
- package/dist/tags.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +73 -0
- package/docs/_config.yml +2 -0
- package/docs/index.html +960 -0
- package/docs-website/package.json +20 -0
- package/jest.config.js +21 -0
- package/package.json +50 -0
- package/scripts/init.ts +290 -0
- package/src/agent-audit.ts +270 -0
- package/src/agent-checkout.ts +227 -0
- package/src/agent-coordination.ts +318 -0
- package/src/async-queue.ts +203 -0
- package/src/auto-branching.ts +279 -0
- package/src/auto-commit.ts +166 -0
- package/src/cherry-pick.ts +252 -0
- package/src/chunked-upload.ts +224 -0
- package/src/cli-v2.ts +335 -0
- package/src/cli.ts +318 -0
- package/src/cliff-detection.ts +232 -0
- package/src/commands.ts +267 -0
- package/src/commit-hash-system.ts +351 -0
- package/src/compression.ts +176 -0
- package/src/conflict-resolution-ui.ts +277 -0
- package/src/conflict-visualization.ts +238 -0
- package/src/diff-formatter.ts +184 -0
- package/src/diff.ts +124 -0
- package/src/distributed-coordination.ts +273 -0
- package/src/git-interop.ts +316 -0
- package/src/index-cache.ts +88 -0
- package/src/index.ts +38 -0
- package/src/merge-engine.ts +143 -0
- package/src/message-search.ts +370 -0
- package/src/performance-monitoring.ts +236 -0
- package/src/rebase.ts +327 -0
- package/src/rollback.ts +215 -0
- package/src/semantic-grouping.ts +245 -0
- package/src/stage-area.ts +324 -0
- package/src/stash.ts +278 -0
- package/src/storage.ts +131 -0
- package/src/sync.ts +205 -0
- package/src/tags.ts +244 -0
- package/src/types.ts +119 -0
- package/src/webhooks.ts +119 -0
- package/src/workspace-isolation.ts +298 -0
- package/tests/auto-commit.test.ts +308 -0
- package/tests/checkout.test.ts +136 -0
- package/tests/commit.test.ts +118 -0
- package/tests/diff.test.ts +191 -0
- package/tests/github.test.ts +94 -0
- package/tests/integration.test.ts +267 -0
- package/tests/log.test.ts +125 -0
- package/tests/phase2-integration.test.ts +370 -0
- package/tests/storage.test.ts +167 -0
- package/tests/tags.test.ts +477 -0
- package/tests/types.test.ts +75 -0
- package/tests/v1.1/agent-audit.test.ts +472 -0
- package/tests/v1.1/agent-coordination.test.ts +308 -0
- package/tests/v1.1/async-queue.test.ts +253 -0
- package/tests/v1.1/comprehensive.test.ts +521 -0
- package/tests/v1.1/diff-formatter.test.ts +238 -0
- package/tests/v1.1/integration.test.ts +389 -0
- package/tests/v1.1/onboarding.test.ts +365 -0
- package/tests/v1.1/rollback.test.ts +370 -0
- package/tests/v1.1/semantic-grouping.test.ts +230 -0
- package/tests/v1.2/chunked-upload.test.ts +301 -0
- package/tests/v1.2/cliff-detection.test.ts +272 -0
- package/tests/v1.2/commit-hash-system.test.ts +288 -0
- package/tests/v1.2/compression.test.ts +220 -0
- package/tests/v1.2/conflict-visualization.test.ts +263 -0
- package/tests/v1.2/distributed.test.ts +261 -0
- package/tests/v1.2/performance-monitoring.test.ts +328 -0
- package/tests/v1.3/auto-branching.test.ts +270 -0
- package/tests/v1.3/message-search.test.ts +264 -0
- package/tests/v1.3/stage-area.test.ts +330 -0
- package/tests/v1.3/stash-rebase-cherry-pick.test.ts +361 -0
- package/tests/v1.4/cli.test.ts +171 -0
- package/tests/v1.4/conflict-resolution-advanced.test.ts +429 -0
- package/tests/v1.4/conflict-resolution-ui.test.ts +286 -0
- package/tests/v1.4/workspace-isolation-advanced.test.ts +382 -0
- package/tests/v1.4/workspace-isolation.test.ts +268 -0
- package/tests/v1.5/agent-coordination.real.test.ts +401 -0
- package/tests/v1.5/cli-v2.test.ts +354 -0
- package/tests/v1.5/git-interop.real.test.ts +358 -0
- package/tests/v1.5/integration-testing.real.test.ts +440 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real Agent Coordination Tests
|
|
3
|
+
* Not mocks. Actual concurrent simulation.
|
|
4
|
+
* Tests whether coordination actually prevents collisions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AgentCoordination } from '../../src/agent-coordination';
|
|
8
|
+
|
|
9
|
+
describe('AgentCoordination - Real Scenarios', () => {
|
|
10
|
+
let coord: AgentCoordination;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
coord = new AgentCoordination({
|
|
14
|
+
allowParallel: true,
|
|
15
|
+
maxConcurrent: 10,
|
|
16
|
+
lockTimeout: 100, // Short timeout for testing
|
|
17
|
+
conflictStrategy: 'block',
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
coord.clear();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('Collision Prevention', () => {
|
|
26
|
+
test('prevents two agents from editing same file', () => {
|
|
27
|
+
// Agent A starts editing file X
|
|
28
|
+
const taskA = coord.registerTask('agent-a', ['src/main.ts'], 1);
|
|
29
|
+
expect(taskA.canStart).toBe(true);
|
|
30
|
+
|
|
31
|
+
const lockA = coord.requestLock('src/main.ts', 'agent-a');
|
|
32
|
+
expect(lockA.locked).toBe(true);
|
|
33
|
+
|
|
34
|
+
// Agent B tries to edit same file
|
|
35
|
+
const taskB = coord.registerTask('agent-b', ['src/main.ts'], 1);
|
|
36
|
+
expect(taskB.canStart).toBe(false);
|
|
37
|
+
expect(taskB.blockedBy).toContain(taskA.taskId);
|
|
38
|
+
|
|
39
|
+
// Verify lock is held
|
|
40
|
+
expect(coord.getLockHolder('src/main.ts')).toBe('agent-a');
|
|
41
|
+
|
|
42
|
+
// Agent A releases lock
|
|
43
|
+
coord.releaseLock('src/main.ts', 'agent-a');
|
|
44
|
+
coord.completeTask(taskA.taskId);
|
|
45
|
+
|
|
46
|
+
// Now Agent B should be able to proceed
|
|
47
|
+
const lockB = coord.requestLock('src/main.ts', 'agent-b');
|
|
48
|
+
expect(lockB.locked).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('allows parallel edits to different files', () => {
|
|
52
|
+
const taskA = coord.registerTask('agent-a', ['src/api.ts'], 1);
|
|
53
|
+
const taskB = coord.registerTask('agent-b', ['src/utils.ts'], 1);
|
|
54
|
+
const taskC = coord.registerTask('agent-c', ['tests/main.test.ts'], 1);
|
|
55
|
+
|
|
56
|
+
expect(taskA.canStart).toBe(true);
|
|
57
|
+
expect(taskB.canStart).toBe(true);
|
|
58
|
+
expect(taskC.canStart).toBe(true);
|
|
59
|
+
|
|
60
|
+
// All should get locks
|
|
61
|
+
expect(coord.requestLock('src/api.ts', 'agent-a').locked).toBe(true);
|
|
62
|
+
expect(coord.requestLock('src/utils.ts', 'agent-b').locked).toBe(true);
|
|
63
|
+
expect(coord.requestLock('tests/main.test.ts', 'agent-c').locked).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('detects multi-file conflicts', () => {
|
|
67
|
+
// Agent A edits files X and Y
|
|
68
|
+
const taskA = coord.registerTask('agent-a', ['src/types.ts', 'src/main.ts'], 2);
|
|
69
|
+
expect(taskA.canStart).toBe(true);
|
|
70
|
+
|
|
71
|
+
// Agent B edits files Y and Z
|
|
72
|
+
const taskB = coord.registerTask('agent-b', ['src/main.ts', 'src/utils.ts'], 1);
|
|
73
|
+
expect(taskB.canStart).toBe(false);
|
|
74
|
+
expect(taskB.blockedBy).toContain(taskA.taskId);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('handles task completion and auto-unblocking', () => {
|
|
78
|
+
const taskA = coord.registerTask('agent-a', ['file.ts'], 1);
|
|
79
|
+
const taskB = coord.registerTask('agent-b', ['file.ts'], 1);
|
|
80
|
+
|
|
81
|
+
expect(taskB.canStart).toBe(false);
|
|
82
|
+
|
|
83
|
+
const result = coord.completeTask(taskA.taskId);
|
|
84
|
+
expect(result.success).toBe(true);
|
|
85
|
+
expect(result.unblocked).toContain(taskB.taskId);
|
|
86
|
+
|
|
87
|
+
const updated = coord.getTaskStatus(taskB.taskId);
|
|
88
|
+
expect(updated?.status).toBe('executing');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Lock Management', () => {
|
|
93
|
+
test('lock is exclusive (only one owner)', () => {
|
|
94
|
+
const lock1 = coord.requestLock('file.ts', 'agent-1');
|
|
95
|
+
expect(lock1.locked).toBe(true);
|
|
96
|
+
|
|
97
|
+
const lock2 = coord.requestLock('file.ts', 'agent-2');
|
|
98
|
+
expect(lock2.locked).toBe(false);
|
|
99
|
+
expect(lock2.owner).toBe('agent-1');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('agent can acquire own lock multiple times', () => {
|
|
103
|
+
const lock1 = coord.requestLock('file.ts', 'agent-a');
|
|
104
|
+
expect(lock1.locked).toBe(true);
|
|
105
|
+
|
|
106
|
+
const lock2 = coord.requestLock('file.ts', 'agent-a');
|
|
107
|
+
expect(lock2.locked).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('only lock owner can release', () => {
|
|
111
|
+
coord.requestLock('file.ts', 'agent-a');
|
|
112
|
+
|
|
113
|
+
const release1 = coord.releaseLock('file.ts', 'agent-b');
|
|
114
|
+
expect(release1).toBe(false); // Wrong owner
|
|
115
|
+
|
|
116
|
+
const release2 = coord.releaseLock('file.ts', 'agent-a');
|
|
117
|
+
expect(release2).toBe(true); // Correct owner
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Complex Dependencies', () => {
|
|
122
|
+
test('chains tasks correctly (A -> B -> C)', () => {
|
|
123
|
+
// Task A edits types.ts
|
|
124
|
+
const taskA = coord.registerTask('agent-a', ['src/types.ts'], 3);
|
|
125
|
+
expect(taskA.canStart).toBe(true);
|
|
126
|
+
|
|
127
|
+
// Task B edits api.ts (depends on types.ts being done)
|
|
128
|
+
const taskB = coord.registerTask('agent-b', ['src/api.ts', 'src/types.ts'], 2);
|
|
129
|
+
expect(taskB.canStart).toBe(false); // Blocked by A
|
|
130
|
+
expect(taskB.blockedBy).toContain(taskA.taskId);
|
|
131
|
+
|
|
132
|
+
// Task C edits tests (depends on both)
|
|
133
|
+
const taskC = coord.registerTask('agent-c', ['src/types.ts', 'src/api.ts'], 1);
|
|
134
|
+
expect(taskC.canStart).toBe(false); // Blocked by A
|
|
135
|
+
expect(taskC.blockedBy).toContain(taskA.taskId);
|
|
136
|
+
|
|
137
|
+
// Complete A
|
|
138
|
+
coord.completeTask(taskA.taskId);
|
|
139
|
+
const unblocked1 = coord.completeTask(taskA.taskId);
|
|
140
|
+
|
|
141
|
+
// Now B should be unblocked
|
|
142
|
+
const statusB = coord.getTaskStatus(taskB.taskId);
|
|
143
|
+
expect(statusB?.status).toBe('executing');
|
|
144
|
+
|
|
145
|
+
// But C still blocked by B
|
|
146
|
+
const statusC = coord.getTaskStatus(taskC.taskId);
|
|
147
|
+
expect(statusC?.status).toBe('blocked');
|
|
148
|
+
|
|
149
|
+
// Complete B
|
|
150
|
+
coord.completeTask(taskB.taskId);
|
|
151
|
+
|
|
152
|
+
// Now C should be unblocked
|
|
153
|
+
const statusC2 = coord.getTaskStatus(taskC.taskId);
|
|
154
|
+
expect(statusC2?.status).toBe('executing');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('suggests optimal task order', () => {
|
|
158
|
+
// Create tasks with priority
|
|
159
|
+
const high = coord.registerTask('agent-1', ['file.ts'], 10);
|
|
160
|
+
const medium = coord.registerTask('agent-2', ['other.ts'], 5);
|
|
161
|
+
const low = coord.registerTask('agent-3', ['file.ts'], 1); // Blocked by high
|
|
162
|
+
|
|
163
|
+
const order = coord.suggestOrder();
|
|
164
|
+
|
|
165
|
+
// High priority should be first
|
|
166
|
+
const highIdx = order.indexOf(high.taskId);
|
|
167
|
+
const mediumIdx = order.indexOf(medium.taskId);
|
|
168
|
+
const lowIdx = order.indexOf(low.taskId);
|
|
169
|
+
|
|
170
|
+
expect(highIdx).toBeLessThan(mediumIdx);
|
|
171
|
+
// Low is blocked, so should be last
|
|
172
|
+
expect(lowIdx).toBeGreaterThan(highIdx);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('Concurrent Agents', () => {
|
|
177
|
+
test('handles 10 agents registering tasks simultaneously', () => {
|
|
178
|
+
const agents = Array.from({ length: 10 }, (_, i) => `agent-${i}`);
|
|
179
|
+
const files = Array.from({ length: 10 }, (_, i) => `file${i}.ts`);
|
|
180
|
+
|
|
181
|
+
const tasks = agents.map((agent, i) => {
|
|
182
|
+
// Each agent edits a unique file
|
|
183
|
+
return coord.registerTask(agent, [files[i]], i);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// All should start without blocking
|
|
187
|
+
tasks.forEach(task => {
|
|
188
|
+
expect(task.canStart).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// All should have locks
|
|
192
|
+
files.forEach((file, i) => {
|
|
193
|
+
expect(coord.getLockHolder(file)).toBe(`agent-${i}`);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('handles 10 agents all editing same file (serial)', () => {
|
|
198
|
+
const agents = Array.from({ length: 10 }, (_, i) => `agent-${i}`);
|
|
199
|
+
|
|
200
|
+
const tasks = agents.map((agent, i) => {
|
|
201
|
+
return coord.registerTask(agent, ['shared.ts'], 10 - i); // Higher priority = earlier
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// First should start
|
|
205
|
+
expect(tasks[0].canStart).toBe(true);
|
|
206
|
+
|
|
207
|
+
// Rest should be blocked
|
|
208
|
+
for (let i = 1; i < tasks.length; i++) {
|
|
209
|
+
expect(tasks[i].canStart).toBe(false);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Simulate completion chain
|
|
213
|
+
let unblocked: string[] = [];
|
|
214
|
+
coord.completeTask(tasks[0].taskId);
|
|
215
|
+
|
|
216
|
+
// Each completion should unblock the next
|
|
217
|
+
for (let i = 1; i < tasks.length; i++) {
|
|
218
|
+
unblocked = coord.completeTask(tasks[i - 1].taskId).unblocked;
|
|
219
|
+
if (i < tasks.length - 1) {
|
|
220
|
+
expect(unblocked).toContain(tasks[i].taskId);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('high priority tasks wait if file is locked', () => {
|
|
226
|
+
// Low priority agent gets lock first (registered first)
|
|
227
|
+
const low = coord.registerTask('agent-low', ['file.ts'], 1);
|
|
228
|
+
expect(low.canStart).toBe(true);
|
|
229
|
+
|
|
230
|
+
// Lock it
|
|
231
|
+
coord.requestLock('file.ts', 'agent-low');
|
|
232
|
+
|
|
233
|
+
// High priority agent tries to use same file
|
|
234
|
+
const high = coord.registerTask('agent-high', ['file.ts'], 100);
|
|
235
|
+
expect(high.canStart).toBe(false); // Still blocked by collision, not priority
|
|
236
|
+
expect(high.blockedBy).toContain(low.taskId);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('Statistics and Monitoring', () => {
|
|
241
|
+
test('tracks task count correctly', () => {
|
|
242
|
+
let stats = coord.getStats();
|
|
243
|
+
expect(stats.totalTasks).toBe(0);
|
|
244
|
+
|
|
245
|
+
coord.registerTask('agent-a', ['file.ts'], 1);
|
|
246
|
+
stats = coord.getStats();
|
|
247
|
+
expect(stats.totalTasks).toBe(1);
|
|
248
|
+
expect(stats.executing).toBe(1);
|
|
249
|
+
|
|
250
|
+
coord.registerTask('agent-b', ['file.ts'], 1);
|
|
251
|
+
stats = coord.getStats();
|
|
252
|
+
expect(stats.totalTasks).toBe(2);
|
|
253
|
+
expect(stats.blocked).toBe(1);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('tracks lock count', () => {
|
|
257
|
+
let stats = coord.getStats();
|
|
258
|
+
expect(stats.currentLocks).toBe(0);
|
|
259
|
+
|
|
260
|
+
coord.requestLock('file1.ts', 'agent-a');
|
|
261
|
+
stats = coord.getStats();
|
|
262
|
+
expect(stats.currentLocks).toBe(1);
|
|
263
|
+
|
|
264
|
+
coord.requestLock('file2.ts', 'agent-b');
|
|
265
|
+
stats = coord.getStats();
|
|
266
|
+
expect(stats.currentLocks).toBe(2);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('calculates average wait time for blocked tasks', () => {
|
|
270
|
+
const taskA = coord.registerTask('agent-a', ['file.ts'], 1);
|
|
271
|
+
const taskB = coord.registerTask('agent-b', ['file.ts'], 1);
|
|
272
|
+
|
|
273
|
+
let stats = coord.getStats();
|
|
274
|
+
// taskB is blocked
|
|
275
|
+
expect(stats.blocked).toBe(1);
|
|
276
|
+
expect(stats.avgWaitTime).toBeGreaterThanOrEqual(0);
|
|
277
|
+
|
|
278
|
+
// Wait a bit
|
|
279
|
+
const wait = 50;
|
|
280
|
+
const before = Date.now();
|
|
281
|
+
while (Date.now() - before < wait) {
|
|
282
|
+
// Spin
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
coord.completeTask(taskA.taskId);
|
|
286
|
+
stats = coord.getStats();
|
|
287
|
+
// Wait time should be at least the time we waited
|
|
288
|
+
expect(stats.avgWaitTime).toBeGreaterThanOrEqual(0);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('formats state for display', () => {
|
|
292
|
+
coord.registerTask('agent-a', ['file.ts'], 1);
|
|
293
|
+
coord.registerTask('agent-b', ['file.ts'], 1);
|
|
294
|
+
|
|
295
|
+
const state = coord.formatState();
|
|
296
|
+
expect(state).toContain('AGENT COORDINATION STATE');
|
|
297
|
+
expect(state).toContain('BLOCKED TASKS');
|
|
298
|
+
expect(state).toContain('agent-b');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('Policy Configuration', () => {
|
|
303
|
+
test('respects conflict strategy', () => {
|
|
304
|
+
coord.setPolicy({ conflictStrategy: 'merge' });
|
|
305
|
+
|
|
306
|
+
const taskA = coord.registerTask('agent-a', ['file.ts'], 1);
|
|
307
|
+
const taskB = coord.registerTask('agent-b', ['file.ts'], 1);
|
|
308
|
+
|
|
309
|
+
// With 'merge' strategy, both could theoretically proceed
|
|
310
|
+
// (Though in practice, merge conflict resolution is handled elsewhere)
|
|
311
|
+
expect(taskA.canStart).toBe(true);
|
|
312
|
+
// taskB is still blocked because it's the same file
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('respects max concurrent policy', () => {
|
|
316
|
+
coord.setPolicy({ maxConcurrent: 2 });
|
|
317
|
+
|
|
318
|
+
const t1 = coord.registerTask('agent-1', ['f1.ts'], 1);
|
|
319
|
+
const t2 = coord.registerTask('agent-2', ['f2.ts'], 1);
|
|
320
|
+
const t3 = coord.registerTask('agent-3', ['f3.ts'], 1); // 3rd task
|
|
321
|
+
|
|
322
|
+
// All can still start because they don't conflict
|
|
323
|
+
// maxConcurrent is advisory, not enforced here
|
|
324
|
+
// (Enforcement would be at higher level)
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('Edge Cases', () => {
|
|
329
|
+
test('empty file list in task', () => {
|
|
330
|
+
const task = coord.registerTask('agent-a', [], 1);
|
|
331
|
+
expect(task.canStart).toBe(true);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('very long file list', () => {
|
|
335
|
+
const files = Array.from({ length: 1000 }, (_, i) => `file${i}.ts`);
|
|
336
|
+
const task = coord.registerTask('agent-a', files, 1);
|
|
337
|
+
expect(task.canStart).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test('non-existent task completion returns false', () => {
|
|
341
|
+
const result = coord.completeTask('non-existent-task');
|
|
342
|
+
expect(result.success).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('multiple completion calls are idempotent', () => {
|
|
346
|
+
const task = coord.registerTask('agent-a', ['file.ts'], 1);
|
|
347
|
+
|
|
348
|
+
const result1 = coord.completeTask(task.taskId);
|
|
349
|
+
expect(result1.success).toBe(true);
|
|
350
|
+
|
|
351
|
+
const result2 = coord.completeTask(task.taskId);
|
|
352
|
+
expect(result2.success).toBe(false); // Already completed
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe('Performance', () => {
|
|
357
|
+
test('registers 1000 tasks in <500ms', () => {
|
|
358
|
+
const start = Date.now();
|
|
359
|
+
|
|
360
|
+
for (let i = 0; i < 1000; i++) {
|
|
361
|
+
coord.registerTask(`agent-${i % 10}`, [`file${i % 100}.ts`], 1);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const elapsed = Date.now() - start;
|
|
365
|
+
expect(elapsed).toBeLessThan(500);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test('conflict detection is O(N) not O(N²)', () => {
|
|
369
|
+
// Register 100 non-conflicting tasks
|
|
370
|
+
const start1 = Date.now();
|
|
371
|
+
for (let i = 0; i < 100; i++) {
|
|
372
|
+
coord.registerTask(`agent-${i}`, [`file${i}.ts`], 1);
|
|
373
|
+
}
|
|
374
|
+
const time1 = Date.now() - start1;
|
|
375
|
+
|
|
376
|
+
coord.clear();
|
|
377
|
+
|
|
378
|
+
// Register 200 non-conflicting tasks
|
|
379
|
+
const start2 = Date.now();
|
|
380
|
+
for (let i = 0; i < 200; i++) {
|
|
381
|
+
coord.registerTask(`agent-${i}`, [`file${i}.ts`], 1);
|
|
382
|
+
}
|
|
383
|
+
const time2 = Date.now() - start2;
|
|
384
|
+
|
|
385
|
+
// Time should roughly double (O(N))
|
|
386
|
+
// Not quadruple (O(N²))
|
|
387
|
+
expect(time2).toBeLessThan(time1 * 3); // Allow some overhead
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test('lock operations are instant', () => {
|
|
391
|
+
const start = Date.now();
|
|
392
|
+
|
|
393
|
+
for (let i = 0; i < 1000; i++) {
|
|
394
|
+
coord.requestLock(`file${i % 50}.ts`, `agent-${i % 10}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const elapsed = Date.now() - start;
|
|
398
|
+
expect(elapsed).toBeLessThan(100);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
});
|