@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,288 @@
1
+ /**
2
+ * Commit Hash System Tests
3
+ * Git-like SHA-256 hashing for agent checkout
4
+ */
5
+
6
+ import { CommitHashSystem, CommitHash } from '../../src/commit-hash-system';
7
+
8
+ describe('CommitHashSystem', () => {
9
+ let system: CommitHashSystem;
10
+
11
+ beforeEach(() => {
12
+ system = new CommitHashSystem();
13
+ });
14
+
15
+ describe('Hash Generation', () => {
16
+ test('generates deterministic hash', () => {
17
+ const files = new Map([['file.ts', 'content']]);
18
+ const hash1 = CommitHashSystem.createHash('agent-a', 'msg', files);
19
+ const hash2 = CommitHashSystem.createHash('agent-a', 'msg', files);
20
+ expect(hash1.full).toBe(hash2.full);
21
+ });
22
+
23
+ test('hash changes with different content', () => {
24
+ const files1 = new Map([['file.ts', 'content1']]);
25
+ const files2 = new Map([['file.ts', 'content2']]);
26
+ const hash1 = CommitHashSystem.createHash('agent-a', 'msg', files1);
27
+ const hash2 = CommitHashSystem.createHash('agent-a', 'msg', files2);
28
+ expect(hash1.full).not.toBe(hash2.full);
29
+ });
30
+
31
+ test('creates short hash (7 chars)', () => {
32
+ const files = new Map([['f.ts', 'c']]);
33
+ const hash = CommitHashSystem.createHash('a', 'm', files);
34
+ expect(hash.short).toHaveLength(7);
35
+ expect(hash.short).toBe(hash.full.substring(0, 7));
36
+ });
37
+
38
+ test('includes parent in hash', () => {
39
+ const files = new Map([['f.ts', 'c']]);
40
+ const hash1 = CommitHashSystem.createHash('a', 'm', files);
41
+ const hash2 = CommitHashSystem.createHash('a', 'm', files, hash1.full);
42
+ expect(hash1.full).not.toBe(hash2.full);
43
+ });
44
+
45
+ test('full hash is valid SHA-256', () => {
46
+ const files = new Map([['f.ts', 'c']]);
47
+ const hash = CommitHashSystem.createHash('a', 'm', files);
48
+ expect(hash.full).toMatch(/^[a-f0-9]{64}$/);
49
+ });
50
+ });
51
+
52
+ describe('Commit Recording', () => {
53
+ test('records commit', () => {
54
+ const files = new Map([['f.ts', 'content']]);
55
+ const hash = system.recordCommit('agent-a', 'msg', files, 'sig1');
56
+ expect(hash).toBeDefined();
57
+ expect(hash.full).toBeDefined();
58
+ });
59
+
60
+ test('retrieves commit by full hash', () => {
61
+ const files = new Map([['f.ts', 'content']]);
62
+ const hash = system.recordCommit('agent-a', 'msg', files, 'sig1');
63
+ const commit = system.getCommit(hash.full);
64
+ expect(commit?.author).toBe('agent-a');
65
+ expect(commit?.message).toBe('msg');
66
+ });
67
+
68
+ test('retrieves commit by short hash', () => {
69
+ const files = new Map([['f.ts', 'content']]);
70
+ const hash = system.recordCommit('agent-a', 'msg', files, 'sig1');
71
+ const commit = system.getCommit(hash.short);
72
+ expect(commit?.author).toBe('agent-a');
73
+ });
74
+
75
+ test('tracks parent-child relationships', () => {
76
+ const files = new Map([['f.ts', 'c1']]);
77
+ const h1 = system.recordCommit('a', 'm1', files, 's1');
78
+
79
+ const files2 = new Map([['f.ts', 'c2']]);
80
+ const h2 = system.recordCommit('a', 'm2', files2, 's2', h1.full);
81
+
82
+ const children = system.getChildren(h1.full);
83
+ expect(children).toHaveLength(1);
84
+ expect(children[0].message).toBe('m2');
85
+ });
86
+
87
+ test('tracks author commits', () => {
88
+ system.recordCommit('agent-a', 'm1', new Map([['f.ts', 'c']]), 's1');
89
+ system.recordCommit('agent-a', 'm2', new Map([['g.ts', 'c']]), 's2');
90
+ system.recordCommit('agent-b', 'm3', new Map([['h.ts', 'c']]), 's3');
91
+
92
+ const commits = system.getCommitsByAuthor('agent-a');
93
+ expect(commits).toHaveLength(2);
94
+ expect(commits.every(c => c.author === 'agent-a')).toBe(true);
95
+ });
96
+ });
97
+
98
+ describe('Commit Chain', () => {
99
+ test('gets commit ancestry chain', () => {
100
+ const h1 = system.recordCommit('a', 'm1', new Map([['f', 'c']]), 's1');
101
+ const h2 = system.recordCommit('a', 'm2', new Map([['f', 'c']]), 's2', h1.full);
102
+ const h3 = system.recordCommit('a', 'm3', new Map([['f', 'c']]), 's3', h2.full);
103
+
104
+ const chain = system.getChain(h3.full);
105
+ expect(chain).toHaveLength(3);
106
+ expect(chain[0].message).toBe('m3');
107
+ expect(chain[2].message).toBe('m1');
108
+ });
109
+
110
+ test('handles single commit chain', () => {
111
+ const h = system.recordCommit('a', 'm', new Map([['f', 'c']]), 's');
112
+ const chain = system.getChain(h.full);
113
+ expect(chain).toHaveLength(1);
114
+ });
115
+ });
116
+
117
+ describe('Ancestry Queries', () => {
118
+ test('checks if commit is ancestor', () => {
119
+ const h1 = system.recordCommit('a', 'm1', new Map([['f', 'c']]), 's1');
120
+ const h2 = system.recordCommit('a', 'm2', new Map([['f', 'c']]), 's2', h1.full);
121
+ const h3 = system.recordCommit('a', 'm3', new Map([['f', 'c']]), 's3', h2.full);
122
+
123
+ expect(system.isAncestor(h1.full, h3.full)).toBe(true);
124
+ expect(system.isAncestor(h3.full, h1.full)).toBe(false);
125
+ });
126
+
127
+ test('returns false if not ancestor', () => {
128
+ const h1 = system.recordCommit('a', 'm1', new Map([['f', 'c']]), 's1');
129
+ const h2 = system.recordCommit('a', 'm2', new Map([['f', 'c']]), 's2');
130
+ expect(system.isAncestor(h1.full, h2.full)).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe('Commit Range', () => {
135
+ test('gets range between commits', () => {
136
+ const h1 = system.recordCommit('a', 'm1', new Map([['f', 'c']]), 's1');
137
+ const h2 = system.recordCommit('a', 'm2', new Map([['f', 'c']]), 's2', h1.full);
138
+ const h3 = system.recordCommit('a', 'm3', new Map([['f', 'c']]), 's3', h2.full);
139
+
140
+ const range = system.getRange(h1.full, h3.full);
141
+ expect(range.length).toBeGreaterThan(0);
142
+ });
143
+
144
+ test('handles branching scenarios', () => {
145
+ const h1 = system.recordCommit('a', 'm1', new Map([['f', 'c']]), 's1');
146
+ const h2a = system.recordCommit('a', 'm2a', new Map([['f', 'c']]), 's2a', h1.full);
147
+ const h2b = system.recordCommit('a', 'm2b', new Map([['f', 'c']]), 's2b', h1.full);
148
+
149
+ const range = system.getRange(h2a.full, h2b.full);
150
+ expect(Array.isArray(range)).toBe(true);
151
+ });
152
+ });
153
+
154
+ describe('Hash Resolution', () => {
155
+ test('resolves short hash to full', () => {
156
+ const hash = system.recordCommit('a', 'm', new Map([['f', 'c']]), 's');
157
+ const resolved = system.resolveHash(hash.short);
158
+ expect(resolved).toBe(hash.full);
159
+ });
160
+
161
+ test('returns undefined for unknown short hash', () => {
162
+ const resolved = system.resolveHash('unknown');
163
+ expect(resolved).toBeUndefined();
164
+ });
165
+ });
166
+
167
+ describe('Statistics', () => {
168
+ test('calculates commit statistics', () => {
169
+ system.recordCommit('a', 'm1', new Map([['f', 'c']]), 's1');
170
+ system.recordCommit('a', 'm2', new Map([['g', 'c']]), 's2');
171
+ system.recordCommit('b', 'm3', new Map([['h', 'c']]), 's3');
172
+
173
+ const stats = system.getStats();
174
+ expect(stats.totalCommits).toBe(3);
175
+ expect(stats.uniqueAuthors).toBe(2);
176
+ expect(stats.branchCount).toBe(3); // 3 root commits
177
+ });
178
+
179
+ test('tracks oldest and newest commits', () => {
180
+ const h1 = system.recordCommit('a', 'm1', new Map([['f', 'c']]), 's1');
181
+ const h2 = system.recordCommit('b', 'm2', new Map([['g', 'c']]), 's2', h1.full);
182
+
183
+ const stats = system.getStats();
184
+ expect(stats.oldestCommit?.hash).toBe(h1.full);
185
+ expect(stats.newestCommit?.hash).toBe(h2.full);
186
+ });
187
+ });
188
+
189
+ describe('History Validation', () => {
190
+ test('validates clean history', () => {
191
+ system.recordCommit('a', 'm1', new Map([['f', 'c']]), 's1');
192
+ const result = system.validateHistory();
193
+ expect(result.valid).toBe(true);
194
+ expect(result.errors).toHaveLength(0);
195
+ });
196
+
197
+ test('detects orphaned commits', () => {
198
+ // Manually create invalid state (for testing)
199
+ const result = system.validateHistory();
200
+ expect(Array.isArray(result.errors)).toBe(true);
201
+ });
202
+ });
203
+
204
+ describe('Commit Verification', () => {
205
+ test('verifies valid commit', () => {
206
+ const hash = system.recordCommit('a', 'm', new Map([['f', 'c']]), 'sig1');
207
+ const valid = system.verifyCommit(hash.full, 'sig1');
208
+ expect(valid).toBe(true);
209
+ });
210
+
211
+ test('rejects invalid signature', () => {
212
+ const hash = system.recordCommit('a', 'm', new Map([['f', 'c']]), 'sig1');
213
+ const valid = system.verifyCommit(hash.full, 'wrong-sig');
214
+ expect(valid).toBe(false);
215
+ });
216
+
217
+ test('rejects unknown commit', () => {
218
+ const valid = system.verifyCommit('unknown', 'sig');
219
+ expect(valid).toBe(false);
220
+ });
221
+ });
222
+
223
+ describe('Performance', () => {
224
+ test('records 1000 commits quickly', () => {
225
+ const start = Date.now();
226
+ for (let i = 0; i < 1000; i++) {
227
+ system.recordCommit(`agent-${i % 10}`, `m${i}`, new Map([[`f${i}.ts`, `c${i}`]]), `s${i}`);
228
+ }
229
+ const elapsed = Date.now() - start;
230
+ expect(elapsed).toBeLessThan(500);
231
+ });
232
+
233
+ test('retrieves commits by full hash quickly', () => {
234
+ const hashes = [];
235
+ for (let i = 0; i < 100; i++) {
236
+ const h = system.recordCommit('a', `m${i}`, new Map([[`f${i}.ts`, `c`]]), `s${i}`);
237
+ hashes.push(h.full);
238
+ }
239
+
240
+ const start = Date.now();
241
+ for (const hash of hashes) {
242
+ system.getCommit(hash);
243
+ }
244
+ const elapsed = Date.now() - start;
245
+ expect(elapsed).toBeLessThan(100);
246
+ });
247
+
248
+ test('retrieves commits by short hash quickly', () => {
249
+ const hashes = [];
250
+ for (let i = 0; i < 100; i++) {
251
+ const h = system.recordCommit('a', `m${i}`, new Map([[`f${i}.ts`, `c`]]), `s${i}`);
252
+ hashes.push(h.short);
253
+ }
254
+
255
+ const start = Date.now();
256
+ for (const hash of hashes) {
257
+ system.getCommit(hash);
258
+ }
259
+ const elapsed = Date.now() - start;
260
+ expect(elapsed).toBeLessThan(100);
261
+ });
262
+ });
263
+
264
+ describe('Real-world Scenarios', () => {
265
+ test('handles agent linear history', () => {
266
+ let parent: string | undefined;
267
+ for (let i = 0; i < 10; i++) {
268
+ const hash = system.recordCommit('agent-a', `commit ${i}`, new Map([[`file.ts`, `v${i}`]]), `sig${i}`, parent);
269
+ parent = hash.full;
270
+ }
271
+
272
+ const stats = system.getStats();
273
+ expect(stats.totalCommits).toBe(10);
274
+ });
275
+
276
+ test('handles branching history', () => {
277
+ const h1 = system.recordCommit('a', 'm1', new Map([['f', 'c']]), 's1');
278
+ const h2a = system.recordCommit('a', 'm2a', new Map([['f', 'c']]), 's2a', h1.full);
279
+ const h2b = system.recordCommit('b', 'm2b', new Map([['g', 'c']]), 's2b', h1.full);
280
+ const h3a = system.recordCommit('a', 'm3a', new Map([['f', 'c']]), 's3a', h2a.full);
281
+ const h3b = system.recordCommit('b', 'm3b', new Map([['g', 'c']]), 's3b', h2b.full);
282
+
283
+ const stats = system.getStats();
284
+ expect(stats.totalCommits).toBe(5);
285
+ expect(stats.branchCount).toBe(1); // One root
286
+ });
287
+ });
288
+ });
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Compression Engine Tests
3
+ * Delta-based storage for efficient versioning
4
+ */
5
+
6
+ import { CompressionEngine } from '../../src/compression';
7
+
8
+ describe('CompressionEngine', () => {
9
+ describe('Delta Calculation', () => {
10
+ test('detects no changes', () => {
11
+ const delta = CompressionEngine.calculateDelta('same content', 'same content');
12
+ expect(delta).toBe('');
13
+ });
14
+
15
+ test('detects line additions', () => {
16
+ const old = 'line 1\nline 2';
17
+ const new_ver = 'line 1\nline 2\nline 3';
18
+ const delta = CompressionEngine.calculateDelta(old, new_ver);
19
+ expect(delta).toContain('+');
20
+ });
21
+
22
+ test('detects line deletions', () => {
23
+ const old = 'line 1\nline 2\nline 3';
24
+ const new_ver = 'line 1\nline 3';
25
+ const delta = CompressionEngine.calculateDelta(old, new_ver);
26
+ expect(delta).toContain('-');
27
+ });
28
+
29
+ test('detects modifications', () => {
30
+ const old = 'original content';
31
+ const new_ver = 'modified content';
32
+ const delta = CompressionEngine.calculateDelta(old, new_ver);
33
+ expect(delta.length).toBeGreaterThan(0);
34
+ });
35
+
36
+ test('handles multi-line changes', () => {
37
+ const old = 'a\nb\nc\nd\ne';
38
+ const new_ver = 'a\nx\nc\ny\ne';
39
+ const delta = CompressionEngine.calculateDelta(old, new_ver);
40
+ expect(delta).toContain('-');
41
+ expect(delta).toContain('+');
42
+ });
43
+ });
44
+
45
+ describe('Compression & Decompression', () => {
46
+ test('compresses content', () => {
47
+ const content = 'x'.repeat(1000);
48
+ const { compressed, ratio } = CompressionEngine.compress(content);
49
+ expect(compressed.length).toBeLessThan(content.length * 2);
50
+ });
51
+
52
+ test('compression ratio > 1 for compressible content', () => {
53
+ const content = 'aaaaaabbbbbbcccccc';
54
+ const { ratio } = CompressionEngine.compress(content);
55
+ expect(ratio).toBeGreaterThan(1);
56
+ });
57
+
58
+ test('decompresses back to original', () => {
59
+ const original = 'test content with some data';
60
+ const { compressed } = CompressionEngine.compress(original);
61
+ const decompressed = CompressionEngine.decompress(compressed);
62
+ expect(decompressed).toBe(original);
63
+ });
64
+
65
+ test('handles binary-like content', () => {
66
+ const content = '\x00\x01\x02\x03';
67
+ const { compressed } = CompressionEngine.compress(content);
68
+ const decompressed = CompressionEngine.decompress(compressed);
69
+ expect(decompressed).toBe(content);
70
+ });
71
+ });
72
+
73
+ describe('Hashing', () => {
74
+ test('produces consistent hash', () => {
75
+ const content = 'test content';
76
+ const hash1 = CompressionEngine.hash(content);
77
+ const hash2 = CompressionEngine.hash(content);
78
+ expect(hash1).toBe(hash2);
79
+ });
80
+
81
+ test('different content produces different hash', () => {
82
+ const hash1 = CompressionEngine.hash('content 1');
83
+ const hash2 = CompressionEngine.hash('content 2');
84
+ expect(hash1).not.toBe(hash2);
85
+ });
86
+
87
+ test('hash is deterministic', () => {
88
+ const hashes = new Set();
89
+ for (let i = 0; i < 10; i++) {
90
+ hashes.add(CompressionEngine.hash('same content'));
91
+ }
92
+ expect(hashes.size).toBe(1);
93
+ });
94
+
95
+ test('produces valid hash format', () => {
96
+ const hash = CompressionEngine.hash('content');
97
+ expect(hash).toMatch(/^[a-z0-9]+$/);
98
+ });
99
+ });
100
+
101
+ describe('Delta Application', () => {
102
+ test('applies simple delta', () => {
103
+ const base = 'line 1\nline 2';
104
+ // Simulate a delta
105
+ const result = CompressionEngine.applyDelta(base, '+2:line 3');
106
+ expect(result).toBeDefined();
107
+ });
108
+
109
+ test('applies multiple changes', () => {
110
+ const base = 'a\nb\nc';
111
+ const delta = '-1:b\n+1:x';
112
+ const result = CompressionEngine.applyDelta(base, delta);
113
+ expect(result).toBeDefined();
114
+ });
115
+ });
116
+
117
+ describe('Delta Creation', () => {
118
+ test('creates delta from files', () => {
119
+ const delta = CompressionEngine.createDelta(
120
+ 'file.ts',
121
+ 'old content',
122
+ 'new content'
123
+ );
124
+ expect(delta.path).toBe('file.ts');
125
+ expect(delta.type).toBe('modify');
126
+ });
127
+
128
+ test('delta includes hashes', () => {
129
+ const delta = CompressionEngine.createDelta(
130
+ 'file.ts',
131
+ 'old',
132
+ 'new'
133
+ );
134
+ expect(delta.hash).toBeDefined();
135
+ expect(delta.baseHash).toBeDefined();
136
+ });
137
+
138
+ test('delta indicates compression', () => {
139
+ const delta = CompressionEngine.createDelta(
140
+ 'file.ts',
141
+ 'short',
142
+ 'very long content that might compress well'
143
+ );
144
+ expect(typeof delta.compressed).toBe('boolean');
145
+ });
146
+ });
147
+
148
+ describe('File Set Compression', () => {
149
+ test('compresses multiple files', () => {
150
+ const files = new Map([
151
+ ['file1.ts', 'content 1'],
152
+ ['file2.ts', 'content 2'],
153
+ ['file3.ts', 'content 3'],
154
+ ]);
155
+ const { deltas, stats } = CompressionEngine.compressFileSet(files);
156
+ expect(deltas).toHaveLength(3);
157
+ expect(stats.deltas).toBe(3);
158
+ });
159
+
160
+ test('calculates compression ratio', () => {
161
+ const files = new Map([
162
+ ['file.txt', 'x'.repeat(100)],
163
+ ]);
164
+ const { stats } = CompressionEngine.compressFileSet(files);
165
+ expect(stats.ratio).toBeGreaterThan(0);
166
+ });
167
+
168
+ test('estimates size savings', () => {
169
+ const files = new Map([
170
+ ['file1.ts', 'a'.repeat(1000)],
171
+ ['file2.ts', 'b'.repeat(1000)],
172
+ ]);
173
+ const { stats } = CompressionEngine.compressFileSet(files);
174
+ expect(stats.originalSize).toBe(2000);
175
+ expect(stats.compressedSize).toBeLessThan(stats.originalSize);
176
+ });
177
+ });
178
+
179
+ describe('Benefit Estimation', () => {
180
+ test('estimates compression benefit', () => {
181
+ const { savings, ratio } = CompressionEngine.estimateBenefit(100, 1000);
182
+ expect(savings).toMatch(/\d+\.\d+%/);
183
+ expect(ratio).toMatch(/\d+\.\d+x/);
184
+ });
185
+
186
+ test('shows significant savings for typical workloads', () => {
187
+ const { savings } = CompressionEngine.estimateBenefit(1000, 10000);
188
+ const savingsNum = parseFloat(savings);
189
+ expect(savingsNum).toBeGreaterThan(30);
190
+ });
191
+ });
192
+
193
+ describe('Performance', () => {
194
+ test('calculates delta for 1000 lines quickly', () => {
195
+ const old = Array(1000).fill('line').join('\n');
196
+ const new_ver = old + '\nnew line';
197
+ const start = Date.now();
198
+ CompressionEngine.calculateDelta(old, new_ver);
199
+ const elapsed = Date.now() - start;
200
+ expect(elapsed).toBeLessThan(50);
201
+ });
202
+
203
+ test('compresses 1MB file reasonably', () => {
204
+ const content = 'x'.repeat(1000000);
205
+ const start = Date.now();
206
+ CompressionEngine.compress(content);
207
+ const elapsed = Date.now() - start;
208
+ expect(elapsed).toBeLessThan(500);
209
+ });
210
+
211
+ test('hashes 1000 files quickly', () => {
212
+ const start = Date.now();
213
+ for (let i = 0; i < 1000; i++) {
214
+ CompressionEngine.hash(`content ${i}`);
215
+ }
216
+ const elapsed = Date.now() - start;
217
+ expect(elapsed).toBeLessThan(100);
218
+ });
219
+ });
220
+ });