@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,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Monitoring Tests
|
|
3
|
+
* Track operation times and identify bottlenecks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { PerformanceMonitoring } from '../../src/performance-monitoring';
|
|
7
|
+
|
|
8
|
+
describe('PerformanceMonitoring', () => {
|
|
9
|
+
let monitor: PerformanceMonitoring;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
monitor = new PerformanceMonitoring();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('Operation Tracking', () => {
|
|
16
|
+
test('tracks operation time', () => {
|
|
17
|
+
monitor.startOperation('op1');
|
|
18
|
+
// Simulate some work
|
|
19
|
+
const start = Date.now();
|
|
20
|
+
while (Date.now() - start < 10) {} // Sleep 10ms
|
|
21
|
+
|
|
22
|
+
const metric = monitor.endOperation('op1', 'test-operation', 'success');
|
|
23
|
+
expect(metric.duration).toBeGreaterThanOrEqual(10);
|
|
24
|
+
expect(metric.operation).toBe('test-operation');
|
|
25
|
+
expect(metric.status).toBe('success');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('tracks multiple operations', () => {
|
|
29
|
+
monitor.startOperation('op1');
|
|
30
|
+
monitor.endOperation('op1', 'operation1', 'success');
|
|
31
|
+
|
|
32
|
+
monitor.startOperation('op2');
|
|
33
|
+
monitor.endOperation('op2', 'operation2', 'success');
|
|
34
|
+
|
|
35
|
+
monitor.startOperation('op3');
|
|
36
|
+
monitor.endOperation('op3', 'operation3', 'success');
|
|
37
|
+
|
|
38
|
+
const summary = monitor.getSummary();
|
|
39
|
+
expect(summary.totalMetrics).toBe(3);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('tracks operation failure', () => {
|
|
43
|
+
monitor.startOperation('op1');
|
|
44
|
+
const metric = monitor.endOperation('op1', 'test-operation', 'failure');
|
|
45
|
+
expect(metric.status).toBe('failure');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('includes metadata', () => {
|
|
49
|
+
monitor.startOperation('op1');
|
|
50
|
+
const metric = monitor.endOperation('op1', 'commit', 'success', {
|
|
51
|
+
files: 5,
|
|
52
|
+
size: 1024,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(metric.metadata?.files).toBe(5);
|
|
56
|
+
expect(metric.metadata?.size).toBe(1024);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('throws on missing timer', () => {
|
|
60
|
+
expect(() => {
|
|
61
|
+
monitor.endOperation('nonexistent', 'test', 'success');
|
|
62
|
+
}).toThrow('No timer found');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('Statistics Calculation', () => {
|
|
67
|
+
test('calculates basic stats', () => {
|
|
68
|
+
for (let i = 0; i < 10; i++) {
|
|
69
|
+
monitor.startOperation(`op${i}`);
|
|
70
|
+
const start = Date.now();
|
|
71
|
+
while (Date.now() - start < 5) {}
|
|
72
|
+
monitor.endOperation(`op${i}`, 'test-op', 'success');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const stats = monitor.getStats('test-op');
|
|
76
|
+
expect(stats.count).toBe(10);
|
|
77
|
+
expect(stats.avgDuration).toBeGreaterThan(0);
|
|
78
|
+
expect(stats.minDuration).toBeGreaterThan(0);
|
|
79
|
+
expect(stats.maxDuration).toBeGreaterThanOrEqual(stats.minDuration);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('calculates percentiles', () => {
|
|
83
|
+
for (let i = 0; i < 100; i++) {
|
|
84
|
+
monitor.startOperation(`op${i}`);
|
|
85
|
+
const duration = i % 2 === 0 ? 5 : 50;
|
|
86
|
+
const start = Date.now();
|
|
87
|
+
while (Date.now() - start < duration) {}
|
|
88
|
+
monitor.endOperation(`op${i}`, 'test-op', 'success');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const stats = monitor.getStats('test-op');
|
|
92
|
+
expect(stats.p95Duration).toBeGreaterThanOrEqual(stats.avgDuration);
|
|
93
|
+
expect(stats.p99Duration).toBeGreaterThanOrEqual(stats.p95Duration);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('calculates failure rate', () => {
|
|
97
|
+
for (let i = 0; i < 10; i++) {
|
|
98
|
+
monitor.startOperation(`op${i}`);
|
|
99
|
+
monitor.endOperation(`op${i}`, 'test-op', i % 2 === 0 ? 'success' : 'failure');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const stats = monitor.getStats('test-op');
|
|
103
|
+
expect(stats.failureRate).toBe(0.5);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('returns zero stats for unknown operation', () => {
|
|
107
|
+
const stats = monitor.getStats('unknown');
|
|
108
|
+
expect(stats.count).toBe(0);
|
|
109
|
+
expect(stats.avgDuration).toBe(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('Bottleneck Detection', () => {
|
|
114
|
+
test('identifies slow operations', () => {
|
|
115
|
+
// Fast operation
|
|
116
|
+
for (let i = 0; i < 10; i++) {
|
|
117
|
+
monitor.startOperation(`fast${i}`);
|
|
118
|
+
const start = Date.now();
|
|
119
|
+
while (Date.now() - start < 5) {}
|
|
120
|
+
monitor.endOperation(`fast${i}`, 'fast-op', 'success');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Slow operation
|
|
124
|
+
for (let i = 0; i < 10; i++) {
|
|
125
|
+
monitor.startOperation(`slow${i}`);
|
|
126
|
+
const start = Date.now();
|
|
127
|
+
while (Date.now() - start < 150) {}
|
|
128
|
+
monitor.endOperation(`slow${i}`, 'slow-op', 'success');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const bottlenecks = monitor.findBottlenecks(100);
|
|
132
|
+
expect(bottlenecks.some(b => b.operation === 'slow-op')).toBe(true);
|
|
133
|
+
expect(bottlenecks.some(b => b.operation === 'fast-op')).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('returns empty for no bottlenecks', () => {
|
|
137
|
+
for (let i = 0; i < 10; i++) {
|
|
138
|
+
monitor.startOperation(`op${i}`);
|
|
139
|
+
const start = Date.now();
|
|
140
|
+
while (Date.now() - start < 5) {}
|
|
141
|
+
monitor.endOperation(`op${i}`, 'fast-op', 'success');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const bottlenecks = monitor.findBottlenecks(100);
|
|
145
|
+
expect(bottlenecks).toHaveLength(0);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Report Generation', () => {
|
|
150
|
+
test('generates performance report', () => {
|
|
151
|
+
for (let i = 0; i < 20; i++) {
|
|
152
|
+
monitor.startOperation(`op${i}`);
|
|
153
|
+
const start = Date.now();
|
|
154
|
+
while (Date.now() - start < 10) {}
|
|
155
|
+
monitor.endOperation(`op${i}`, 'test-op', 'success');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const report = monitor.generateReport();
|
|
159
|
+
expect(report.totalOperations).toBe(20);
|
|
160
|
+
expect(report.averageLatency).toBeGreaterThan(0);
|
|
161
|
+
expect(Array.isArray(report.bottlenecks)).toBe(true);
|
|
162
|
+
expect(Array.isArray(report.recommendations)).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('generates recommendations for slow operations', () => {
|
|
166
|
+
for (let i = 0; i < 100; i++) {
|
|
167
|
+
monitor.startOperation(`op${i}`);
|
|
168
|
+
const start = Date.now();
|
|
169
|
+
while (Date.now() - start < 200) {} // Very slow
|
|
170
|
+
monitor.endOperation(`op${i}`, 'slow-op', 'success');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const report = monitor.generateReport();
|
|
174
|
+
expect(report.recommendations.length).toBeGreaterThan(0);
|
|
175
|
+
expect(report.recommendations[0]).toContain('slow-op');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('generates recommendations for failures', () => {
|
|
179
|
+
for (let i = 0; i < 20; i++) {
|
|
180
|
+
monitor.startOperation(`op${i}`);
|
|
181
|
+
monitor.endOperation(`op${i}`, 'flaky-op', i % 2 === 0 ? 'success' : 'failure');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const report = monitor.generateReport();
|
|
185
|
+
expect(report.recommendations.some(r => r.includes('failure rate'))).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('generates recommendations for high variance', () => {
|
|
189
|
+
for (let i = 0; i < 50; i++) {
|
|
190
|
+
monitor.startOperation(`op${i}`);
|
|
191
|
+
const start = Date.now();
|
|
192
|
+
// Alternating between very fast and very slow
|
|
193
|
+
const duration = i % 2 === 0 ? 5 : 300;
|
|
194
|
+
while (Date.now() - start < duration) {}
|
|
195
|
+
monitor.endOperation(`op${i}`, 'variable-op', 'success');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const report = monitor.generateReport();
|
|
199
|
+
expect(report.recommendations.some(r => r.includes('High variance'))).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('Report Formatting', () => {
|
|
204
|
+
test('formats clean report', () => {
|
|
205
|
+
const report = {
|
|
206
|
+
timestamp: Date.now(),
|
|
207
|
+
totalOperations: 100,
|
|
208
|
+
averageLatency: 45,
|
|
209
|
+
bottlenecks: [],
|
|
210
|
+
recommendations: [],
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const formatted = PerformanceMonitoring.formatReport(report);
|
|
214
|
+
expect(formatted).toContain('PERFORMANCE REPORT');
|
|
215
|
+
expect(formatted).toContain('100');
|
|
216
|
+
expect(formatted).toContain('All operations performing');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('formats report with bottlenecks', () => {
|
|
220
|
+
const report = {
|
|
221
|
+
timestamp: Date.now(),
|
|
222
|
+
totalOperations: 100,
|
|
223
|
+
averageLatency: 45,
|
|
224
|
+
bottlenecks: [
|
|
225
|
+
{
|
|
226
|
+
operation: 'slow-op',
|
|
227
|
+
count: 10,
|
|
228
|
+
avgDuration: 150,
|
|
229
|
+
minDuration: 140,
|
|
230
|
+
maxDuration: 160,
|
|
231
|
+
p95Duration: 158,
|
|
232
|
+
p99Duration: 159,
|
|
233
|
+
failureRate: 0,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
recommendations: ['Consider optimizing slow-op'],
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const formatted = PerformanceMonitoring.formatReport(report);
|
|
240
|
+
expect(formatted).toContain('BOTTLENECK(S)');
|
|
241
|
+
expect(formatted).toContain('slow-op');
|
|
242
|
+
expect(formatted).toContain('150.00ms');
|
|
243
|
+
expect(formatted).toContain('RECOMMENDATIONS');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('Metrics Management', () => {
|
|
248
|
+
test('clears metrics', () => {
|
|
249
|
+
monitor.startOperation('op1');
|
|
250
|
+
monitor.endOperation('op1', 'test-op', 'success');
|
|
251
|
+
|
|
252
|
+
expect(monitor.getSummary().totalMetrics).toBe(1);
|
|
253
|
+
monitor.clearMetrics();
|
|
254
|
+
expect(monitor.getSummary().totalMetrics).toBe(0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('gets summary', () => {
|
|
258
|
+
for (let i = 0; i < 30; i++) {
|
|
259
|
+
monitor.startOperation(`op${i}`);
|
|
260
|
+
monitor.endOperation(`op${i}`, `op-${i % 3}`, 'success');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const summary = monitor.getSummary();
|
|
264
|
+
expect(summary.totalMetrics).toBe(30);
|
|
265
|
+
expect(summary.uniqueOperations).toBe(3);
|
|
266
|
+
expect(summary.timeRange.start).toBeGreaterThan(0);
|
|
267
|
+
expect(summary.timeRange.end).toBeGreaterThanOrEqual(summary.timeRange.start);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('gets all stats', () => {
|
|
271
|
+
monitor.startOperation('op1');
|
|
272
|
+
monitor.endOperation('op1', 'read', 'success');
|
|
273
|
+
|
|
274
|
+
monitor.startOperation('op2');
|
|
275
|
+
monitor.endOperation('op2', 'write', 'success');
|
|
276
|
+
|
|
277
|
+
monitor.startOperation('op3');
|
|
278
|
+
monitor.endOperation('op3', 'delete', 'success');
|
|
279
|
+
|
|
280
|
+
const allStats = monitor.getAllStats();
|
|
281
|
+
expect(allStats).toHaveLength(3);
|
|
282
|
+
expect(allStats.map(s => s.operation).sort()).toEqual(['delete', 'read', 'write']);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('Real-world Scenarios', () => {
|
|
287
|
+
test('monitors agent workflow', () => {
|
|
288
|
+
// Simulate agent workflow
|
|
289
|
+
const operations = ['commit', 'push', 'pull', 'merge'];
|
|
290
|
+
|
|
291
|
+
for (let cycle = 0; cycle < 10; cycle++) {
|
|
292
|
+
for (const op of operations) {
|
|
293
|
+
monitor.startOperation(`${op}-${cycle}`);
|
|
294
|
+
const start = Date.now();
|
|
295
|
+
while (Date.now() - start < 20 + Math.random() * 30) {}
|
|
296
|
+
monitor.endOperation(`${op}-${cycle}`, op, 'success');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const report = monitor.generateReport();
|
|
301
|
+
expect(report.totalOperations).toBe(40);
|
|
302
|
+
const stats = monitor.getStats('push');
|
|
303
|
+
expect(stats.count).toBe(10);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('detects performance regression', () => {
|
|
307
|
+
// Phase 1: Fast operations
|
|
308
|
+
for (let i = 0; i < 20; i++) {
|
|
309
|
+
monitor.startOperation(`old${i}`);
|
|
310
|
+
const start = Date.now();
|
|
311
|
+
while (Date.now() - start < 10) {}
|
|
312
|
+
monitor.endOperation(`old${i}`, 'commit', 'success');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Phase 2: Slow operations (regression)
|
|
316
|
+
for (let i = 0; i < 20; i++) {
|
|
317
|
+
monitor.startOperation(`new${i}`);
|
|
318
|
+
const start = Date.now();
|
|
319
|
+
while (Date.now() - start < 200) {} // 20x slower
|
|
320
|
+
monitor.endOperation(`new${i}`, 'commit', 'success');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const report = monitor.generateReport();
|
|
324
|
+
expect(report.bottlenecks.length).toBeGreaterThan(0);
|
|
325
|
+
expect(report.recommendations.length).toBeGreaterThan(0);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
});
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Branching Tests
|
|
3
|
+
* Agent-per-task branch management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AutoBranching } from '../../src/auto-branching';
|
|
7
|
+
|
|
8
|
+
describe('AutoBranching', () => {
|
|
9
|
+
let branching: AutoBranching;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
branching = new AutoBranching();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('Branch Creation', () => {
|
|
16
|
+
test('creates branch for agent task', () => {
|
|
17
|
+
const branch = branching.createBranchForTask('agent-a', 'implement-login');
|
|
18
|
+
expect(branch.name).toBe('agent-a/implement-login');
|
|
19
|
+
expect(branch.agent).toBe('agent-a');
|
|
20
|
+
expect(branch.task).toBe('implement-login');
|
|
21
|
+
expect(branch.isActive).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('normalizes branch names', () => {
|
|
25
|
+
const branch = branching.createBranchForTask('Agent A', 'Implement Login');
|
|
26
|
+
expect(branch.name).toBe('agent a/implement login');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('prevents duplicate branches', () => {
|
|
30
|
+
branching.createBranchForTask('agent-a', 'task1');
|
|
31
|
+
expect(() => {
|
|
32
|
+
branching.createBranchForTask('agent-a', 'task1');
|
|
33
|
+
}).toThrow('already exists');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('tracks parent branch', () => {
|
|
37
|
+
const branch = branching.createBranchForTask('agent-a', 'feature', 'main', 'abc123');
|
|
38
|
+
expect(branch.parentBranch).toBe('main');
|
|
39
|
+
expect(branch.headCommit).toBe('abc123');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('Branch Retrieval', () => {
|
|
44
|
+
test('gets branch by name', () => {
|
|
45
|
+
const created = branching.createBranchForTask('agent-a', 'task1');
|
|
46
|
+
const retrieved = branching.getBranch('agent-a/task1');
|
|
47
|
+
expect(retrieved).toEqual(created);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('returns undefined for unknown branch', () => {
|
|
51
|
+
const branch = branching.getBranch('unknown');
|
|
52
|
+
expect(branch).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('gets all branches for agent', () => {
|
|
56
|
+
branching.createBranchForTask('agent-a', 'task1');
|
|
57
|
+
branching.createBranchForTask('agent-a', 'task2');
|
|
58
|
+
branching.createBranchForTask('agent-b', 'task1');
|
|
59
|
+
|
|
60
|
+
const agentABranches = branching.getAgentBranches('agent-a');
|
|
61
|
+
expect(agentABranches).toHaveLength(2);
|
|
62
|
+
expect(agentABranches.every(b => b.agent === 'agent-a')).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('lists all branches', () => {
|
|
66
|
+
branching.createBranchForTask('agent-a', 'task1');
|
|
67
|
+
branching.createBranchForTask('agent-b', 'task1');
|
|
68
|
+
branching.createBranchForTask('agent-c', 'task1');
|
|
69
|
+
|
|
70
|
+
const all = branching.listBranches();
|
|
71
|
+
expect(all).toHaveLength(3);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('filters active branches', () => {
|
|
75
|
+
const b1 = branching.createBranchForTask('agent-a', 'task1');
|
|
76
|
+
const b2 = branching.createBranchForTask('agent-b', 'task1');
|
|
77
|
+
|
|
78
|
+
branching.mergeBranch('agent-a/task1');
|
|
79
|
+
|
|
80
|
+
const active = branching.listBranches(true);
|
|
81
|
+
expect(active).toHaveLength(1);
|
|
82
|
+
expect(active[0].name).toBe('agent-b/task1');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('Get or Create', () => {
|
|
87
|
+
test('returns existing branch', () => {
|
|
88
|
+
const created = branching.createBranchForTask('agent-a', 'task1');
|
|
89
|
+
const retrieved = branching.getOrCreateBranch('agent-a', 'task1');
|
|
90
|
+
expect(retrieved).toEqual(created);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('creates if not exists', () => {
|
|
94
|
+
const branch = branching.getOrCreateBranch('agent-a', 'newtask');
|
|
95
|
+
expect(branch.name).toBe('agent-a/newtask');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('Commit Recording', () => {
|
|
100
|
+
test('records commit on branch', () => {
|
|
101
|
+
const branch = branching.createBranchForTask('agent-a', 'task1');
|
|
102
|
+
branching.recordCommit('agent-a/task1', 'abc123def456');
|
|
103
|
+
|
|
104
|
+
const updated = branching.getBranch('agent-a/task1')!;
|
|
105
|
+
expect(updated.headCommit).toBe('abc123def456');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('throws on unknown branch', () => {
|
|
109
|
+
expect(() => {
|
|
110
|
+
branching.recordCommit('unknown', 'abc123');
|
|
111
|
+
}).toThrow('not found');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('tracks commit count', () => {
|
|
115
|
+
const branch = branching.createBranchForTask('agent-a', 'task1');
|
|
116
|
+
branching.recordCommit('agent-a/task1', 'commit1');
|
|
117
|
+
branching.recordCommit('agent-a/task1', 'commit2');
|
|
118
|
+
branching.recordCommit('agent-a/task1', 'commit3');
|
|
119
|
+
|
|
120
|
+
const stats = branching.getStats();
|
|
121
|
+
expect(stats.commitsByBranch.get('agent-a/task1')).toBe(3);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('Branch Merging', () => {
|
|
126
|
+
test('marks branch inactive on merge', () => {
|
|
127
|
+
branching.createBranchForTask('agent-a', 'task1');
|
|
128
|
+
branching.mergeBranch('agent-a/task1');
|
|
129
|
+
|
|
130
|
+
const branch = branching.getBranch('agent-a/task1')!;
|
|
131
|
+
expect(branch.isActive).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('merge and delete removes from active list', () => {
|
|
135
|
+
branching.createBranchForTask('agent-a', 'task1');
|
|
136
|
+
branching.mergeBranchAndDelete('agent-a/task1');
|
|
137
|
+
|
|
138
|
+
const active = branching.listBranches(true);
|
|
139
|
+
expect(active.some(b => b.name === 'agent-a/task1')).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('Branch Statistics', () => {
|
|
144
|
+
test('calculates branch stats', () => {
|
|
145
|
+
branching.createBranchForTask('agent-a', 'task1');
|
|
146
|
+
branching.createBranchForTask('agent-a', 'task2');
|
|
147
|
+
branching.createBranchForTask('agent-b', 'task1');
|
|
148
|
+
|
|
149
|
+
const stats = branching.getStats();
|
|
150
|
+
expect(stats.totalBranches).toBe(3);
|
|
151
|
+
expect(stats.activeBranches).toBe(3);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('tracks oldest and newest branches', () => {
|
|
155
|
+
const b1 = branching.createBranchForTask('agent-a', 'task1');
|
|
156
|
+
// Small delay
|
|
157
|
+
const b2 = branching.createBranchForTask('agent-b', 'task1');
|
|
158
|
+
|
|
159
|
+
const stats = branching.getStats();
|
|
160
|
+
expect(stats.oldestBranch?.name).toBe('agent-a/task1');
|
|
161
|
+
expect(stats.newestBranch?.name).toBe('agent-b/task1');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('Stale Branch Detection', () => {
|
|
166
|
+
test('detects stale branches', () => {
|
|
167
|
+
const b1 = branching.createBranchForTask('agent-a', 'old-task');
|
|
168
|
+
|
|
169
|
+
// Manually set last modified to 8 days ago
|
|
170
|
+
b1.lastModified = Date.now() - 8 * 24 * 60 * 60 * 1000;
|
|
171
|
+
|
|
172
|
+
branching.createBranchForTask('agent-b', 'new-task');
|
|
173
|
+
|
|
174
|
+
const stale = branching.detectStaleBranches(7);
|
|
175
|
+
expect(stale).toHaveLength(1);
|
|
176
|
+
expect(stale[0].name).toBe('agent-a/old-task');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('ignores merged branches', () => {
|
|
180
|
+
const branch = branching.createBranchForTask('agent-a', 'task1');
|
|
181
|
+
branch.lastModified = Date.now() - 8 * 24 * 60 * 60 * 1000;
|
|
182
|
+
branching.mergeBranch('agent-a/task1');
|
|
183
|
+
|
|
184
|
+
const stale = branching.detectStaleBranches(7);
|
|
185
|
+
expect(stale).toHaveLength(0);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('Branch Tree', () => {
|
|
190
|
+
test('builds branch hierarchy', () => {
|
|
191
|
+
branching.createBranchForTask('agent-a', 'feature', 'main');
|
|
192
|
+
branching.createBranchForTask('agent-b', 'fix', 'main');
|
|
193
|
+
branching.createBranchForTask('agent-c', 'nested', 'agent-a/feature');
|
|
194
|
+
|
|
195
|
+
const tree = branching.getBranchTree();
|
|
196
|
+
expect(tree.get('main')).toHaveLength(2);
|
|
197
|
+
expect(tree.get('agent-a/feature')).toHaveLength(1);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('Branch Rebase', () => {
|
|
202
|
+
test('rebases branch to new parent', () => {
|
|
203
|
+
const child = branching.createBranchForTask('agent-a', 'feature', 'main');
|
|
204
|
+
const newParent = branching.createBranchForTask('agent-b', 'develop', 'main');
|
|
205
|
+
|
|
206
|
+
branching.rebaseBranch('agent-a/feature', 'agent-b/develop');
|
|
207
|
+
|
|
208
|
+
const updated = branching.getBranch('agent-a/feature')!;
|
|
209
|
+
expect(updated.parentBranch).toBe('agent-b/develop');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('throws on unknown target parent', () => {
|
|
213
|
+
branching.createBranchForTask('agent-a', 'feature', 'main');
|
|
214
|
+
|
|
215
|
+
expect(() => {
|
|
216
|
+
branching.rebaseBranch('agent-a/feature', 'unknown');
|
|
217
|
+
}).toThrow('Parent branch');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('Formatting', () => {
|
|
222
|
+
test('formats branch list', () => {
|
|
223
|
+
const b1 = branching.createBranchForTask('agent-a', 'task1');
|
|
224
|
+
branching.recordCommit('agent-a/task1', 'abc123');
|
|
225
|
+
|
|
226
|
+
const formatted = AutoBranching.formatBranchList([b1]);
|
|
227
|
+
expect(formatted).toContain('agent-a/task1');
|
|
228
|
+
expect(formatted).toContain('abc123');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('handles empty list', () => {
|
|
232
|
+
const formatted = AutoBranching.formatBranchList([]);
|
|
233
|
+
expect(formatted).toContain('No branches');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('Real-world Scenarios', () => {
|
|
238
|
+
test('manages parallel agent tasks', () => {
|
|
239
|
+
// 5 agents, 3 tasks each
|
|
240
|
+
for (let a = 1; a <= 5; a++) {
|
|
241
|
+
for (let t = 1; t <= 3; t++) {
|
|
242
|
+
branching.createBranchForTask(`agent-${a}`, `task-${t}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const stats = branching.getStats();
|
|
247
|
+
expect(stats.totalBranches).toBe(15);
|
|
248
|
+
|
|
249
|
+
const agent1Tasks = branching.getAgentBranches('agent-1');
|
|
250
|
+
expect(agent1Tasks).toHaveLength(3);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('handles task completion flow', () => {
|
|
254
|
+
// Agent creates and works on branch
|
|
255
|
+
const branch = branching.createBranchForTask('agent-a', 'implement-auth');
|
|
256
|
+
branching.recordCommit('agent-a/implement-auth', 'commit1');
|
|
257
|
+
branching.recordCommit('agent-a/implement-auth', 'commit2');
|
|
258
|
+
|
|
259
|
+
// Task complete
|
|
260
|
+
branching.mergeBranch('agent-a/implement-auth');
|
|
261
|
+
|
|
262
|
+
// Agent moves to new task
|
|
263
|
+
const newBranch = branching.createBranchForTask('agent-a', 'fix-bug-123');
|
|
264
|
+
|
|
265
|
+
const stats = branching.getStats();
|
|
266
|
+
expect(stats.activeBranches).toBe(1);
|
|
267
|
+
expect(branching.getAgentBranches('agent-a')).toHaveLength(2);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|