@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.
Files changed (130) hide show
  1. package/.gitignore +115 -0
  2. package/.trace/progress.json +22 -0
  3. package/README.md +466 -0
  4. package/RELEASE-NOTES-1.5.0.md +410 -0
  5. package/STATUS.md +245 -0
  6. package/dist/auto-commit.d.ts +66 -0
  7. package/dist/auto-commit.d.ts.map +1 -0
  8. package/dist/auto-commit.js +180 -0
  9. package/dist/auto-commit.js.map +1 -0
  10. package/dist/cli.d.ts +7 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +246 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands.d.ts +46 -0
  15. package/dist/commands.d.ts.map +1 -0
  16. package/dist/commands.js +256 -0
  17. package/dist/commands.js.map +1 -0
  18. package/dist/diff.d.ts +23 -0
  19. package/dist/diff.d.ts.map +1 -0
  20. package/dist/diff.js +106 -0
  21. package/dist/diff.js.map +1 -0
  22. package/dist/github.d.ts.map +1 -0
  23. package/dist/github.js.map +1 -0
  24. package/dist/index-cache.d.ts +35 -0
  25. package/dist/index-cache.d.ts.map +1 -0
  26. package/dist/index-cache.js +114 -0
  27. package/dist/index-cache.js.map +1 -0
  28. package/dist/index.d.ts +15 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +25 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/storage.d.ts +45 -0
  33. package/dist/storage.d.ts.map +1 -0
  34. package/dist/storage.js +151 -0
  35. package/dist/storage.js.map +1 -0
  36. package/dist/sync.d.ts +60 -0
  37. package/dist/sync.js +184 -0
  38. package/dist/tags.d.ts +85 -0
  39. package/dist/tags.d.ts.map +1 -0
  40. package/dist/tags.js +219 -0
  41. package/dist/tags.js.map +1 -0
  42. package/dist/types.d.ts +102 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +6 -0
  45. package/dist/types.js.map +1 -0
  46. package/docs/.nojekyll +0 -0
  47. package/docs/README.md +73 -0
  48. package/docs/_config.yml +2 -0
  49. package/docs/index.html +960 -0
  50. package/docs-website/package.json +20 -0
  51. package/jest.config.js +21 -0
  52. package/package.json +50 -0
  53. package/scripts/init.ts +290 -0
  54. package/src/agent-audit.ts +270 -0
  55. package/src/agent-checkout.ts +227 -0
  56. package/src/agent-coordination.ts +318 -0
  57. package/src/async-queue.ts +203 -0
  58. package/src/auto-branching.ts +279 -0
  59. package/src/auto-commit.ts +166 -0
  60. package/src/cherry-pick.ts +252 -0
  61. package/src/chunked-upload.ts +224 -0
  62. package/src/cli-v2.ts +335 -0
  63. package/src/cli.ts +318 -0
  64. package/src/cliff-detection.ts +232 -0
  65. package/src/commands.ts +267 -0
  66. package/src/commit-hash-system.ts +351 -0
  67. package/src/compression.ts +176 -0
  68. package/src/conflict-resolution-ui.ts +277 -0
  69. package/src/conflict-visualization.ts +238 -0
  70. package/src/diff-formatter.ts +184 -0
  71. package/src/diff.ts +124 -0
  72. package/src/distributed-coordination.ts +273 -0
  73. package/src/git-interop.ts +316 -0
  74. package/src/index-cache.ts +88 -0
  75. package/src/index.ts +38 -0
  76. package/src/merge-engine.ts +143 -0
  77. package/src/message-search.ts +370 -0
  78. package/src/performance-monitoring.ts +236 -0
  79. package/src/rebase.ts +327 -0
  80. package/src/rollback.ts +215 -0
  81. package/src/semantic-grouping.ts +245 -0
  82. package/src/stage-area.ts +324 -0
  83. package/src/stash.ts +278 -0
  84. package/src/storage.ts +131 -0
  85. package/src/sync.ts +205 -0
  86. package/src/tags.ts +244 -0
  87. package/src/types.ts +119 -0
  88. package/src/webhooks.ts +119 -0
  89. package/src/workspace-isolation.ts +298 -0
  90. package/tests/auto-commit.test.ts +308 -0
  91. package/tests/checkout.test.ts +136 -0
  92. package/tests/commit.test.ts +118 -0
  93. package/tests/diff.test.ts +191 -0
  94. package/tests/github.test.ts +94 -0
  95. package/tests/integration.test.ts +267 -0
  96. package/tests/log.test.ts +125 -0
  97. package/tests/phase2-integration.test.ts +370 -0
  98. package/tests/storage.test.ts +167 -0
  99. package/tests/tags.test.ts +477 -0
  100. package/tests/types.test.ts +75 -0
  101. package/tests/v1.1/agent-audit.test.ts +472 -0
  102. package/tests/v1.1/agent-coordination.test.ts +308 -0
  103. package/tests/v1.1/async-queue.test.ts +253 -0
  104. package/tests/v1.1/comprehensive.test.ts +521 -0
  105. package/tests/v1.1/diff-formatter.test.ts +238 -0
  106. package/tests/v1.1/integration.test.ts +389 -0
  107. package/tests/v1.1/onboarding.test.ts +365 -0
  108. package/tests/v1.1/rollback.test.ts +370 -0
  109. package/tests/v1.1/semantic-grouping.test.ts +230 -0
  110. package/tests/v1.2/chunked-upload.test.ts +301 -0
  111. package/tests/v1.2/cliff-detection.test.ts +272 -0
  112. package/tests/v1.2/commit-hash-system.test.ts +288 -0
  113. package/tests/v1.2/compression.test.ts +220 -0
  114. package/tests/v1.2/conflict-visualization.test.ts +263 -0
  115. package/tests/v1.2/distributed.test.ts +261 -0
  116. package/tests/v1.2/performance-monitoring.test.ts +328 -0
  117. package/tests/v1.3/auto-branching.test.ts +270 -0
  118. package/tests/v1.3/message-search.test.ts +264 -0
  119. package/tests/v1.3/stage-area.test.ts +330 -0
  120. package/tests/v1.3/stash-rebase-cherry-pick.test.ts +361 -0
  121. package/tests/v1.4/cli.test.ts +171 -0
  122. package/tests/v1.4/conflict-resolution-advanced.test.ts +429 -0
  123. package/tests/v1.4/conflict-resolution-ui.test.ts +286 -0
  124. package/tests/v1.4/workspace-isolation-advanced.test.ts +382 -0
  125. package/tests/v1.4/workspace-isolation.test.ts +268 -0
  126. package/tests/v1.5/agent-coordination.real.test.ts +401 -0
  127. package/tests/v1.5/cli-v2.test.ts +354 -0
  128. package/tests/v1.5/git-interop.real.test.ts +358 -0
  129. package/tests/v1.5/integration-testing.real.test.ts +440 -0
  130. package/tsconfig.json +26 -0
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Rollback Engine Tests (TDD)
3
+ * Complete test coverage for reset/rollback functionality
4
+ */
5
+
6
+ import { RollbackEngine, RollbackResult } from '../../src/rollback';
7
+
8
+ describe('RollbackEngine - Comprehensive Tests', () => {
9
+ let rollback: RollbackEngine;
10
+
11
+ beforeEach(() => {
12
+ rollback = new RollbackEngine();
13
+ });
14
+
15
+ // ============ SNAPSHOT CREATION (20 tests) ============
16
+
17
+ describe('Snapshot Creation', () => {
18
+ test('creates snapshot with commit ID', () => {
19
+ rollback.createSnapshot('commit-1', 'agent-a', 'msg', new Map([['f.ts', 'hash']]));
20
+ expect(rollback.getCommit('commit-1')).toBeDefined();
21
+ });
22
+
23
+ test('snapshot includes agent ID', () => {
24
+ rollback.createSnapshot('c1', 'agent-xyz', 'msg', new Map());
25
+ const commit = rollback.getCommit('c1');
26
+ expect(commit?.agentId).toBe('agent-xyz');
27
+ });
28
+
29
+ test('snapshot includes message', () => {
30
+ rollback.createSnapshot('c1', 'a1', 'Important change', new Map());
31
+ expect(rollback.getCommit('c1')?.message).toBe('Important change');
32
+ });
33
+
34
+ test('snapshot includes timestamp', () => {
35
+ rollback.createSnapshot('c1', 'a1', 'msg', new Map());
36
+ const commit = rollback.getCommit('c1');
37
+ expect(commit?.timestamp).toBeGreaterThan(0);
38
+ });
39
+
40
+ test('snapshot preserves file states', () => {
41
+ const files = new Map([['a.ts', 'h1'], ['b.ts', 'h2']]);
42
+ rollback.createSnapshot('c1', 'a1', 'msg', files);
43
+ const commit = rollback.getCommit('c1');
44
+ expect(commit?.fileStates.size).toBe(2);
45
+ });
46
+
47
+ test('multiple snapshots maintain order', () => {
48
+ rollback.createSnapshot('c1', 'a1', 'm1', new Map());
49
+ rollback.createSnapshot('c2', 'a1', 'm2', new Map());
50
+ rollback.createSnapshot('c3', 'a1', 'm3', new Map());
51
+ const history = rollback.getHistory();
52
+ expect(history[0].commitId).toBe('c1');
53
+ expect(history[2].commitId).toBe('c3');
54
+ });
55
+
56
+ test('supports 100 snapshots', () => {
57
+ for (let i = 0; i < 100; i++) {
58
+ rollback.createSnapshot(`c${i}`, `a1`, `msg${i}`, new Map([[`f${i}.ts`, `h${i}`]]));
59
+ }
60
+ const history = rollback.getHistory();
61
+ expect(history).toHaveLength(100);
62
+ });
63
+ });
64
+
65
+ // ============ ROLLBACK TO COMMIT (20 tests) ============
66
+
67
+ describe('Rollback to Specific Commit', () => {
68
+ beforeEach(() => {
69
+ rollback.createSnapshot('c1', 'a1', 'm1', new Map([['f1.ts', 'h1']]));
70
+ rollback.createSnapshot('c2', 'a1', 'm2', new Map([['f1.ts', 'h2'], ['f2.ts', 'h2']]));
71
+ rollback.createSnapshot('c3', 'a1', 'm3', new Map([['f2.ts', 'h3']]));
72
+ });
73
+
74
+ test('rollback to middle commit', () => {
75
+ const result = rollback.rollbackToCommit('c2');
76
+ expect(result.success).toBe(true);
77
+ expect(result.toCommit).toBe('c2');
78
+ });
79
+
80
+ test('rollback to first commit', () => {
81
+ const result = rollback.rollbackToCommit('c1');
82
+ expect(result.success).toBe(true);
83
+ expect(result.filesDiscarded).toContain('f2.ts');
84
+ });
85
+
86
+ test('detects files to restore', () => {
87
+ const result = rollback.rollbackToCommit('c2');
88
+ expect(Array.isArray(result.filesRestored)).toBe(true);
89
+ });
90
+
91
+ test('detects files to discard', () => {
92
+ const result = rollback.rollbackToCommit('c1');
93
+ expect(Array.isArray(result.filesDiscarded)).toBe(true);
94
+ });
95
+
96
+ test('rejects unknown commit', () => {
97
+ expect(() => {
98
+ rollback.rollbackToCommit('unknown');
99
+ }).toThrow();
100
+ });
101
+
102
+ test('records rollback result', () => {
103
+ const result = rollback.rollbackToCommit('c2');
104
+ expect(result.fromCommit).toBe('c3');
105
+ expect(result.toCommit).toBe('c2');
106
+ expect(result.timestamp).toBeGreaterThan(0);
107
+ });
108
+
109
+ test('updates history after rollback', () => {
110
+ rollback.rollbackToCommit('c2');
111
+ const history = rollback.getHistory();
112
+ expect(history.length).toBeLessThanOrEqual(2);
113
+ });
114
+
115
+ test('rollback is atomic', () => {
116
+ const before = rollback.getHistory().length;
117
+ rollback.rollbackToCommit('c1');
118
+ const after = rollback.getHistory().length;
119
+ expect(after).toBeLessThan(before);
120
+ });
121
+ });
122
+
123
+ // ============ ROLLBACK N COMMITS (15 tests) ============
124
+
125
+ describe('Rollback N Commits Back', () => {
126
+ beforeEach(() => {
127
+ for (let i = 0; i < 10; i++) {
128
+ rollback.createSnapshot(`c${i}`, 'a1', `m${i}`, new Map([[`f${i}.ts`, `h${i}`]]));
129
+ }
130
+ });
131
+
132
+ test('rollback 1 commit', () => {
133
+ const result = rollback.rollbackN(1);
134
+ expect(result.success).toBe(true);
135
+ });
136
+
137
+ test('rollback 3 commits', () => {
138
+ const result = rollback.rollbackN(3);
139
+ expect(result.success).toBe(true);
140
+ });
141
+
142
+ test('rollback all but first', () => {
143
+ const result = rollback.rollbackN(9);
144
+ expect(result.success).toBe(true);
145
+ });
146
+
147
+ test('rejects N = 0', () => {
148
+ expect(() => {
149
+ rollback.rollbackN(0);
150
+ }).toThrow();
151
+ });
152
+
153
+ test('rejects N > history length', () => {
154
+ expect(() => {
155
+ rollback.rollbackN(100);
156
+ }).toThrow();
157
+ });
158
+
159
+ test('maintains chronological order after rollback', () => {
160
+ rollback.rollbackN(2);
161
+ const history = rollback.getHistory();
162
+ for (let i = 0; i < history.length - 1; i++) {
163
+ expect(history[i].timestamp).toBeLessThanOrEqual(history[i + 1].timestamp);
164
+ }
165
+ });
166
+ });
167
+
168
+ // ============ ROLLBACK BEFORE AGENT (10 tests) ============
169
+
170
+ describe('Rollback Before Agent', () => {
171
+ beforeEach(() => {
172
+ rollback.createSnapshot('c1', 'a1', 'm', new Map());
173
+ rollback.createSnapshot('c2', 'a1', 'm', new Map());
174
+ rollback.createSnapshot('c3', 'a2', 'm', new Map());
175
+ rollback.createSnapshot('c4', 'a2', 'm', new Map());
176
+ });
177
+
178
+ test('rollback before agent-2', () => {
179
+ const result = rollback.rollbackBeforeAgent('a2');
180
+ expect(result.success).toBe(true);
181
+ });
182
+
183
+ test('rejects rollback if agent never committed', () => {
184
+ expect(() => {
185
+ rollback.rollbackBeforeAgent('agent-never-existed');
186
+ }).toThrow();
187
+ });
188
+
189
+ test('finds last commit before agent', () => {
190
+ const result = rollback.rollbackBeforeAgent('a2');
191
+ expect(result.toCommit).toBe('c2');
192
+ });
193
+ });
194
+
195
+ // ============ DIFF BETWEEN COMMITS (10 tests) ============
196
+
197
+ describe('Diff Between Commits', () => {
198
+ beforeEach(() => {
199
+ rollback.createSnapshot('c1', 'a1', 'm', new Map([['f1.ts', 'h1'], ['f2.ts', 'h2']]));
200
+ rollback.createSnapshot('c2', 'a1', 'm', new Map([['f1.ts', 'h1x'], ['f3.ts', 'h3']]));
201
+ });
202
+
203
+ test('identifies modified files', () => {
204
+ const diff = rollback.getDiffBetween('c1', 'c2');
205
+ expect(diff.has('f1.ts')).toBe(true);
206
+ });
207
+
208
+ test('identifies new files', () => {
209
+ const diff = rollback.getDiffBetween('c1', 'c2');
210
+ expect(diff.has('f3.ts')).toBe(true);
211
+ });
212
+
213
+ test('identifies deleted files', () => {
214
+ const diff = rollback.getDiffBetween('c1', 'c2');
215
+ expect(diff.has('f2.ts')).toBe(true);
216
+ });
217
+
218
+ test('diff is empty for identical commits', () => {
219
+ const diff = rollback.getDiffBetween('c1', 'c1');
220
+ expect(diff.size).toBe(0);
221
+ });
222
+ });
223
+
224
+ // ============ QUERY OPERATIONS (10 tests) ============
225
+
226
+ describe('Query Operations', () => {
227
+ beforeEach(() => {
228
+ rollback.createSnapshot('c1', 'a1', 'm', new Map());
229
+ rollback.createSnapshot('c2', 'a1', 'm', new Map());
230
+ rollback.createSnapshot('c3', 'a2', 'm', new Map());
231
+ });
232
+
233
+ test('getRollbackPoints returns recent snapshots', () => {
234
+ const points = rollback.getRollbackPoints(2);
235
+ expect(points.length).toBeLessThanOrEqual(2);
236
+ });
237
+
238
+ test('getCommitsByAgent returns agent commits', () => {
239
+ const commits = rollback.getCommitsByAgent('a1');
240
+ expect(commits).toHaveLength(2);
241
+ });
242
+
243
+ test('getCommitsByAgent excludes other agents', () => {
244
+ const commits = rollback.getCommitsByAgent('a1');
245
+ expect(commits.every(c => c.agentId === 'a1')).toBe(true);
246
+ });
247
+
248
+ test('getHistory preserves order', () => {
249
+ const history = rollback.getHistory();
250
+ expect(history[0].commitId).toBe('c1');
251
+ expect(history[2].commitId).toBe('c3');
252
+ });
253
+
254
+ test('getCommit returns undefined for unknown', () => {
255
+ const commit = rollback.getCommit('unknown');
256
+ expect(commit).toBeUndefined();
257
+ });
258
+ });
259
+
260
+ // ============ SAFETY & INTEGRITY (15 tests) ============
261
+
262
+ describe('Safety & Integrity', () => {
263
+ beforeEach(() => {
264
+ rollback.createSnapshot('c1', 'a1', 'm1', new Map([['f.ts', 'h1']]));
265
+ rollback.createSnapshot('c2', 'a1', 'm2', new Map([['f.ts', 'h2']]));
266
+ });
267
+
268
+ test('isRollbackSafe returns boolean', () => {
269
+ const safe = rollback.isRollbackSafe('c1');
270
+ expect(typeof safe).toBe('boolean');
271
+ });
272
+
273
+ test('isRollbackSafe false for unknown commit', () => {
274
+ const safe = rollback.isRollbackSafe('unknown');
275
+ expect(safe).toBe(false);
276
+ });
277
+
278
+ test('verifyIntegrity returns valid state', () => {
279
+ const result = rollback.verifyIntegrity();
280
+ expect(result.valid).toBe(true);
281
+ expect(result.errors).toHaveLength(0);
282
+ });
283
+
284
+ test('verifyIntegrity checks chronological order', () => {
285
+ const result = rollback.verifyIntegrity();
286
+ expect(Array.isArray(result.errors)).toBe(true);
287
+ });
288
+
289
+ test('handles 1000 commits without integrity issues', () => {
290
+ const rb = new RollbackEngine();
291
+ for (let i = 0; i < 1000; i++) {
292
+ rb.createSnapshot(`c${i}`, 'a1', `m${i}`, new Map([[`f${i}.ts`, `h${i}`]]));
293
+ }
294
+ const result = rb.verifyIntegrity();
295
+ expect(result.valid).toBe(true);
296
+ });
297
+ });
298
+
299
+ // ============ STRESS TESTS (10+ tests) ============
300
+
301
+ describe('Performance & Stress', () => {
302
+ test('creates 1000 snapshots quickly', () => {
303
+ const start = Date.now();
304
+ for (let i = 0; i < 1000; i++) {
305
+ rollback.createSnapshot(`c${i}`, `a${i % 10}`, `msg${i}`, new Map([[`f${i}.ts`, `h${i}`]]));
306
+ }
307
+ const elapsed = Date.now() - start;
308
+ expect(elapsed).toBeLessThan(500);
309
+ });
310
+
311
+ test('rollback from deep history quickly', () => {
312
+ for (let i = 0; i < 100; i++) {
313
+ rollback.createSnapshot(`c${i}`, 'a1', `msg${i}`, new Map());
314
+ }
315
+ const start = Date.now();
316
+ rollback.rollbackToCommit('c50');
317
+ const elapsed = Date.now() - start;
318
+ expect(elapsed).toBeLessThan(50);
319
+ });
320
+
321
+ test('getDiffBetween with large histories', () => {
322
+ for (let i = 0; i < 100; i++) {
323
+ rollback.createSnapshot(`c${i}`, 'a1', `m${i}`, new Map([[`f${i}.ts`, `h${i}`]]));
324
+ }
325
+ const start = Date.now();
326
+ rollback.getDiffBetween('c10', 'c90');
327
+ const elapsed = Date.now() - start;
328
+ expect(elapsed).toBeLessThan(50);
329
+ });
330
+ });
331
+
332
+ // ============ EDGE CASES (10 tests) ============
333
+
334
+ describe('Edge Cases', () => {
335
+ test('empty snapshot', () => {
336
+ rollback.createSnapshot('c1', 'a1', 'm', new Map());
337
+ expect(rollback.getCommit('c1')?.fileStates.size).toBe(0);
338
+ });
339
+
340
+ test('snapshot with many files', () => {
341
+ const files = new Map();
342
+ for (let i = 0; i < 1000; i++) {
343
+ files.set(`f${i}.ts`, `h${i}`);
344
+ }
345
+ rollback.createSnapshot('c1', 'a1', 'm', files);
346
+ expect(rollback.getCommit('c1')?.fileStates.size).toBe(1000);
347
+ });
348
+
349
+ test('special characters in message', () => {
350
+ rollback.createSnapshot('c1', 'a1', 'msg with 🚀 emoji', new Map());
351
+ expect(rollback.getCommit('c1')?.message).toContain('🚀');
352
+ });
353
+
354
+ test('rollback chain (multiple rollbacks)', () => {
355
+ for (let i = 0; i < 5; i++) {
356
+ rollback.createSnapshot(`c${i}`, 'a1', `m${i}`, new Map());
357
+ }
358
+ rollback.rollbackToCommit('c3');
359
+ rollback.rollbackToCommit('c2');
360
+ const history = rollback.getHistory();
361
+ expect(history.length).toBeLessThanOrEqual(2);
362
+ });
363
+
364
+ test('agent with single commit', () => {
365
+ rollback.createSnapshot('c1', 'a1', 'm', new Map());
366
+ const commits = rollback.getCommitsByAgent('a1');
367
+ expect(commits).toHaveLength(1);
368
+ });
369
+ });
370
+ });
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Semantic Grouping Tests (TDD)
3
+ * Tests for change type detection and grouping
4
+ */
5
+
6
+ import { SemanticGrouping, ChangeType } from '../../src/semantic-grouping';
7
+
8
+ describe('SemanticGrouping', () => {
9
+ describe('Type Detection', () => {
10
+ test('should detect test files', () => {
11
+ expect(SemanticGrouping.detectType('file.test.ts')).toBe('test');
12
+ expect(SemanticGrouping.detectType('file.spec.ts')).toBe('test');
13
+ expect(SemanticGrouping.detectType('__tests__/file.ts')).toBe('test');
14
+ });
15
+
16
+ test('should detect documentation files', () => {
17
+ expect(SemanticGrouping.detectType('README.md')).toBe('docs');
18
+ expect(SemanticGrouping.detectType('docs/guide.md')).toBe('docs');
19
+ expect(SemanticGrouping.detectType('CHANGELOG.md')).toBe('docs');
20
+ });
21
+
22
+ test('should detect security-related files', () => {
23
+ expect(SemanticGrouping.detectType('src/security.ts')).toBe('security');
24
+ expect(SemanticGrouping.detectType('src/auth.ts')).toBe('security');
25
+ expect(SemanticGrouping.detectType('src/encrypt.ts')).toBe('security');
26
+ });
27
+
28
+ test('should detect performance-related files', () => {
29
+ expect(SemanticGrouping.detectType('src/cache.ts')).toBe('perf');
30
+ expect(SemanticGrouping.detectType('src/optimize.ts')).toBe('perf');
31
+ expect(SemanticGrouping.detectType('benchmarks/perf.ts')).toBe('perf');
32
+ });
33
+
34
+ test('should detect from commit message', () => {
35
+ expect(SemanticGrouping.detectType('any/file.ts', 'fix: critical bug')).toBe('fix');
36
+ expect(SemanticGrouping.detectType('any/file.ts', 'feat: new feature')).toBe('feature');
37
+ expect(SemanticGrouping.detectType('any/file.ts', 'docs: update readme')).toBe('docs');
38
+ expect(SemanticGrouping.detectType('any/file.ts', 'perf: optimize loop')).toBe('perf');
39
+ });
40
+
41
+ test('should default to feature for src/ files', () => {
42
+ expect(SemanticGrouping.detectType('src/new-feature.ts')).toBe('feature');
43
+ });
44
+
45
+ test('should default to unknown for unmatched files', () => {
46
+ expect(SemanticGrouping.detectType('some/random/file.txt')).toBe('unknown');
47
+ });
48
+ });
49
+
50
+ describe('Grouping', () => {
51
+ test('should group files by type', () => {
52
+ const files = [
53
+ 'src/feature.ts',
54
+ 'tests/feature.test.ts',
55
+ 'README.md',
56
+ 'src/auth.ts',
57
+ ];
58
+
59
+ const group = SemanticGrouping.group(files);
60
+
61
+ expect(group.feature?.count).toBe(1);
62
+ expect(group.test?.count).toBe(1);
63
+ expect(group.docs?.count).toBe(1);
64
+ expect(group.security?.count).toBe(1);
65
+ });
66
+
67
+ test('should handle single-type commits', () => {
68
+ const files = ['src/feature1.ts', 'src/feature2.ts', 'src/feature3.ts'];
69
+ const group = SemanticGrouping.group(files);
70
+
71
+ expect(group.feature?.count).toBe(3);
72
+ expect(group.test).toBeNull();
73
+ expect(group.docs).toBeNull();
74
+ });
75
+
76
+ test('should generate semantic commit message', () => {
77
+ const files = [
78
+ 'src/feature.ts',
79
+ 'tests/feature.test.ts',
80
+ 'src/auth.ts',
81
+ 'README.md',
82
+ ];
83
+
84
+ const group = SemanticGrouping.group(files);
85
+ const message = SemanticGrouping.generateMessage(group);
86
+
87
+ expect(message).toContain('✨'); // feature
88
+ expect(message).toContain('✅'); // test
89
+ expect(message).toContain('🔒'); // security
90
+ expect(message).toContain('📖'); // docs
91
+ });
92
+
93
+ test('should identify dominant change type', () => {
94
+ const files = [
95
+ 'src/feature1.ts',
96
+ 'src/feature2.ts',
97
+ 'src/feature3.ts',
98
+ 'tests/one.test.ts',
99
+ ];
100
+
101
+ const group = SemanticGrouping.group(files);
102
+ const dominant = SemanticGrouping.getDominantType(group);
103
+
104
+ expect(dominant).toBe('feature');
105
+ });
106
+ });
107
+
108
+ describe('Micro-commits', () => {
109
+ test('should identify micro-commit (few files, few lines)', () => {
110
+ const files = ['src/one.ts', 'src/two.ts'];
111
+ const isMicro = SemanticGrouping.isMicroCommit(files, 50);
112
+
113
+ expect(isMicro).toBe(true);
114
+ });
115
+
116
+ test('should reject large commits as non-micro', () => {
117
+ const files = Array.from({ length: 20 }, (_, i) => `file${i}.ts`);
118
+ const isMicro = SemanticGrouping.isMicroCommit(files, 500);
119
+
120
+ expect(isMicro).toBe(false);
121
+ });
122
+
123
+ test('should use default line count threshold', () => {
124
+ const files = ['file.ts'];
125
+ const isMicro = SemanticGrouping.isMicroCommit(files);
126
+
127
+ expect(isMicro).toBe(true);
128
+ });
129
+ });
130
+
131
+ describe('Edge Cases', () => {
132
+ test('should handle empty file list', () => {
133
+ const group = SemanticGrouping.group([]);
134
+
135
+ expect(group.feature).toBeNull();
136
+ expect(group.test).toBeNull();
137
+ expect(group.docs).toBeNull();
138
+ });
139
+
140
+ test('should handle mixed case commit messages', () => {
141
+ expect(SemanticGrouping.detectType('file.ts', 'FIX: uppercase')).toBe('fix');
142
+ expect(SemanticGrouping.detectType('file.ts', 'Fix: mixed')).toBe('fix');
143
+ });
144
+
145
+ test('should handle files with multiple extensions', () => {
146
+ expect(SemanticGrouping.detectType('file.test.ts')).toBe('test');
147
+ expect(SemanticGrouping.detectType('archive.test.tar.gz')).toBe('test');
148
+ });
149
+
150
+ test('should prioritize path patterns over defaults', () => {
151
+ expect(SemanticGrouping.detectType('src/test-utils.ts')).toBe('feature');
152
+ expect(SemanticGrouping.detectType('src/auth-test.ts')).toBe('security');
153
+ });
154
+ });
155
+
156
+ describe('Performance', () => {
157
+ test('should classify 1000 files quickly', () => {
158
+ const files = Array.from({ length: 1000 }, (_, i) => {
159
+ const types = ['src/', 'tests/', 'docs/', 'security/', 'perf/'];
160
+ const type = types[i % types.length];
161
+ return `${type}file${i}.ts`;
162
+ });
163
+
164
+ const start = Date.now();
165
+ const group = SemanticGrouping.group(files);
166
+ const elapsed = Date.now() - start;
167
+
168
+ expect(elapsed).toBeLessThan(50); // Should be very fast
169
+ expect(Object.values(group).filter(g => g !== null).length).toBeGreaterThan(0);
170
+ });
171
+ });
172
+
173
+ describe('Semantic Messages', () => {
174
+ test('should include all relevant emoji', () => {
175
+ const files = [
176
+ 'src/feature.ts',
177
+ 'src/bug-fix.ts',
178
+ 'src/auth.ts',
179
+ 'src/cache.ts',
180
+ 'tests/all.test.ts',
181
+ 'README.md',
182
+ ];
183
+
184
+ // Override detection to ensure all types present
185
+ const group = {
186
+ feature: { type: 'feature' as const, files: ['src/feature.ts'], count: 1, summary: 'test' },
187
+ fix: { type: 'fix' as const, files: ['src/bug-fix.ts'], count: 1, summary: 'test' },
188
+ security: { type: 'security' as const, files: ['src/auth.ts'], count: 1, summary: 'test' },
189
+ perf: { type: 'perf' as const, files: ['src/cache.ts'], count: 1, summary: 'test' },
190
+ test: { type: 'test' as const, files: ['tests/all.test.ts'], count: 1, summary: 'test' },
191
+ docs: { type: 'docs' as const, files: ['README.md'], count: 1, summary: 'test' },
192
+ refactor: null,
193
+ chore: null,
194
+ unknown: null,
195
+ };
196
+
197
+ const message = SemanticGrouping.generateMessage(group);
198
+
199
+ expect(message).toContain('✨');
200
+ expect(message).toContain('🐛');
201
+ expect(message).toContain('🔒');
202
+ expect(message).toContain('⚡');
203
+ expect(message).toContain('✅');
204
+ expect(message).toContain('📖');
205
+ });
206
+ });
207
+
208
+ describe('Real-world Scenarios', () => {
209
+ test('should handle mixed commit from real v1.1 work', () => {
210
+ const files = [
211
+ 'src/async-queue.ts',
212
+ 'src/webhooks.ts',
213
+ 'tests/v1.1/async-queue.test.ts',
214
+ 'tests/v1.1/webhooks.test.ts',
215
+ 'docs/ASYNC-API.md',
216
+ ];
217
+
218
+ const group = SemanticGrouping.group(files, 'feat(v1.1): async queue and webhooks');
219
+
220
+ expect(group.feature?.count).toBe(2);
221
+ expect(group.test?.count).toBe(2);
222
+ expect(group.docs?.count).toBe(1);
223
+
224
+ const message = SemanticGrouping.generateMessage(group);
225
+ expect(message).toContain('✨');
226
+ expect(message).toContain('✅');
227
+ expect(message).toContain('📖');
228
+ });
229
+ });
230
+ });