@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,351 @@
1
+ /**
2
+ * Commit Hash System for Trace
3
+ * Git-like hashing for agent checkout capability
4
+ * Each commit is uniquely addressable by SHA-256 hash
5
+ */
6
+
7
+ import * as crypto from 'crypto';
8
+
9
+ export interface CommitHash {
10
+ full: string; // Full SHA-256 hash
11
+ short: string; // First 7 characters (like git)
12
+ commitId: string; // Unique identifier
13
+ }
14
+
15
+ export interface Commit {
16
+ hash: CommitHash;
17
+ parent?: string; // Parent commit hash
18
+ author: string; // Agent ID
19
+ message: string;
20
+ timestamp: number;
21
+ files: Map<string, string>; // filename -> content hash
22
+ signature: string; // Agent signature
23
+ }
24
+
25
+ export interface CommitIndex {
26
+ hash: string;
27
+ parent?: string;
28
+ author: string;
29
+ message: string;
30
+ timestamp: number;
31
+ fileCount: number;
32
+ }
33
+
34
+ export class CommitHashSystem {
35
+ private commits: Map<string, Commit> = new Map();
36
+ private hashIndex: Map<string, CommitIndex> = new Map();
37
+ private parentChild: Map<string, string[]> = new Map(); // parent -> [children]
38
+ private authCommits: Map<string, string[]> = new Map(); // author -> [commits]
39
+
40
+ /**
41
+ * Create commit hash from content
42
+ */
43
+ static createHash(
44
+ author: string,
45
+ message: string,
46
+ files: Map<string, string>,
47
+ parent?: string
48
+ ): CommitHash {
49
+ // Git-like hash: tree + parent + author + message + timestamp
50
+ const content = [
51
+ author,
52
+ message,
53
+ parent || 'root',
54
+ Array.from(files.entries())
55
+ .sort(([a], [b]) => a.localeCompare(b))
56
+ .map(([f, h]) => `${f}:${h}`)
57
+ .join('|'),
58
+ Date.now().toString(),
59
+ ].join('\n');
60
+
61
+ const full = crypto.createHash('sha256').update(content).digest('hex');
62
+ const short = full.substring(0, 7);
63
+
64
+ return {
65
+ full,
66
+ short,
67
+ commitId: short,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Record commit in system
73
+ */
74
+ recordCommit(
75
+ author: string,
76
+ message: string,
77
+ files: Map<string, string>,
78
+ signature: string,
79
+ parent?: string
80
+ ): CommitHash {
81
+ const hash = CommitHashSystem.createHash(author, message, files, parent);
82
+
83
+ const commit: Commit = {
84
+ hash,
85
+ parent,
86
+ author,
87
+ message,
88
+ timestamp: Date.now(),
89
+ files: new Map(files),
90
+ signature,
91
+ };
92
+
93
+ this.commits.set(hash.full, commit);
94
+
95
+ // Index for fast lookup
96
+ this.hashIndex.set(hash.full, {
97
+ hash: hash.full,
98
+ parent,
99
+ author,
100
+ message,
101
+ timestamp: commit.timestamp,
102
+ fileCount: files.size,
103
+ });
104
+
105
+ // Parent-child tracking
106
+ if (parent) {
107
+ if (!this.parentChild.has(parent)) {
108
+ this.parentChild.set(parent, []);
109
+ }
110
+ this.parentChild.get(parent)!.push(hash.full);
111
+ }
112
+
113
+ // Author tracking
114
+ if (!this.authCommits.has(author)) {
115
+ this.authCommits.set(author, []);
116
+ }
117
+ this.authCommits.get(author)!.push(hash.full);
118
+
119
+ return hash;
120
+ }
121
+
122
+ /**
123
+ * Get commit by hash (full or short)
124
+ */
125
+ getCommit(hash: string): Commit | undefined {
126
+ // Try full hash first
127
+ if (this.commits.has(hash)) {
128
+ return this.commits.get(hash);
129
+ }
130
+
131
+ // Try short hash
132
+ for (const [fullHash, commit] of this.commits) {
133
+ if (fullHash.startsWith(hash)) {
134
+ return commit;
135
+ }
136
+ }
137
+
138
+ return undefined;
139
+ }
140
+
141
+ /**
142
+ * Get commit index (lightweight lookup)
143
+ */
144
+ getCommitIndex(hash: string): CommitIndex | undefined {
145
+ if (this.hashIndex.has(hash)) {
146
+ return this.hashIndex.get(hash);
147
+ }
148
+
149
+ // Try short hash
150
+ for (const [fullHash, index] of this.hashIndex) {
151
+ if (fullHash.startsWith(hash)) {
152
+ return index;
153
+ }
154
+ }
155
+
156
+ return undefined;
157
+ }
158
+
159
+ /**
160
+ * Resolve short hash to full hash
161
+ */
162
+ resolveHash(shortHash: string): string | undefined {
163
+ for (const fullHash of this.commits.keys()) {
164
+ if (fullHash.startsWith(shortHash)) {
165
+ return fullHash;
166
+ }
167
+ }
168
+ return undefined;
169
+ }
170
+
171
+ /**
172
+ * Get commit chain (ancestry)
173
+ */
174
+ getChain(hash: string): Commit[] {
175
+ const chain: Commit[] = [];
176
+ let current = this.getCommit(hash);
177
+
178
+ while (current) {
179
+ chain.push(current);
180
+ if (current.parent) {
181
+ current = this.getCommit(current.parent);
182
+ } else {
183
+ break;
184
+ }
185
+ }
186
+
187
+ return chain;
188
+ }
189
+
190
+ /**
191
+ * Get children of commit (for branching)
192
+ */
193
+ getChildren(hash: string): Commit[] {
194
+ const fullHash = this.resolveHash(hash) || hash;
195
+ const childHashes = this.parentChild.get(fullHash) || [];
196
+ return childHashes
197
+ .map(h => this.getCommit(h))
198
+ .filter((c): c is Commit => c !== undefined);
199
+ }
200
+
201
+ /**
202
+ * Get all commits by author
203
+ */
204
+ getCommitsByAuthor(author: string): Commit[] {
205
+ const hashes = this.authCommits.get(author) || [];
206
+ return hashes
207
+ .map(h => this.getCommit(h))
208
+ .filter((c): c is Commit => c !== undefined);
209
+ }
210
+
211
+ /**
212
+ * Verify commit integrity
213
+ */
214
+ verifyCommit(hash: string, expectedSignature: string): boolean {
215
+ const commit = this.getCommit(hash);
216
+ if (!commit) return false;
217
+
218
+ // Verify hash matches content
219
+ const recomputed = CommitHashSystem.createHash(
220
+ commit.author,
221
+ commit.message,
222
+ commit.files,
223
+ commit.parent
224
+ );
225
+
226
+ return recomputed.full === (this.resolveHash(hash) || hash) && commit.signature === expectedSignature;
227
+ }
228
+
229
+ /**
230
+ * Check if commit is ancestor of another
231
+ */
232
+ isAncestor(potentialAncestor: string, commit: string): boolean {
233
+ const chain = this.getChain(commit);
234
+ const ancestorFullHash = this.resolveHash(potentialAncestor) || potentialAncestor;
235
+ return chain.some(c => c.hash.full === ancestorFullHash);
236
+ }
237
+
238
+ /**
239
+ * Get commit range (for diff, log, etc)
240
+ */
241
+ getRange(fromHash: string, toHash: string): Commit[] {
242
+ const fromChain = this.getChain(fromHash);
243
+ const toChain = this.getChain(toHash);
244
+
245
+ // Find common ancestor
246
+ const toSet = new Set(toChain.map(c => c.hash.full));
247
+ let commonAncestor = null;
248
+ for (const commit of fromChain) {
249
+ if (toSet.has(commit.hash.full)) {
250
+ commonAncestor = commit.hash.full;
251
+ break;
252
+ }
253
+ }
254
+
255
+ if (!commonAncestor) return [];
256
+
257
+ // Get commits from commonAncestor to toHash
258
+ const result: Commit[] = [];
259
+ let current = this.getCommit(toHash);
260
+ while (current && current.hash.full !== commonAncestor) {
261
+ result.push(current);
262
+ current = current.parent ? this.getCommit(current.parent) : null;
263
+ }
264
+
265
+ return result.reverse();
266
+ }
267
+
268
+ /**
269
+ * Get all commits (for log)
270
+ */
271
+ getAllCommits(): CommitIndex[] {
272
+ return Array.from(this.hashIndex.values());
273
+ }
274
+
275
+ /**
276
+ * Get statistics
277
+ */
278
+ getStats(): {
279
+ totalCommits: number;
280
+ uniqueAuthors: number;
281
+ branchCount: number;
282
+ oldestCommit?: CommitIndex;
283
+ newestCommit?: CommitIndex;
284
+ } {
285
+ const commits = Array.from(this.hashIndex.values());
286
+ const authors = new Set(commits.map(c => c.author));
287
+ const rootCommits = commits.filter(c => !c.parent);
288
+
289
+ let oldest = commits[0];
290
+ let newest = commits[0];
291
+
292
+ for (const commit of commits) {
293
+ if (commit.timestamp < oldest.timestamp) oldest = commit;
294
+ if (commit.timestamp > newest.timestamp) newest = commit;
295
+ }
296
+
297
+ return {
298
+ totalCommits: commits.length,
299
+ uniqueAuthors: authors.size,
300
+ branchCount: rootCommits.length,
301
+ oldestCommit: oldest,
302
+ newestCommit: newest,
303
+ };
304
+ }
305
+
306
+ /**
307
+ * Validate entire history
308
+ */
309
+ validateHistory(): { valid: boolean; errors: string[] } {
310
+ const errors: string[] = [];
311
+
312
+ for (const [hash, commit] of this.commits) {
313
+ // Verify parent exists
314
+ if (commit.parent) {
315
+ if (!this.commits.has(commit.parent)) {
316
+ errors.push(`Commit ${hash} has missing parent ${commit.parent}`);
317
+ }
318
+ }
319
+
320
+ // Verify hash consistency
321
+ const recomputed = CommitHashSystem.createHash(
322
+ commit.author,
323
+ commit.message,
324
+ commit.files,
325
+ commit.parent
326
+ );
327
+ if (recomputed.full !== hash) {
328
+ errors.push(`Commit ${hash} has inconsistent hash`);
329
+ }
330
+ }
331
+
332
+ // Verify parent-child index
333
+ for (const [parent, children] of this.parentChild) {
334
+ if (!this.commits.has(parent)) {
335
+ errors.push(`Parent index references non-existent commit ${parent}`);
336
+ }
337
+ for (const child of children) {
338
+ if (!this.commits.has(child)) {
339
+ errors.push(`Child index references non-existent commit ${child}`);
340
+ }
341
+ }
342
+ }
343
+
344
+ return {
345
+ valid: errors.length === 0,
346
+ errors,
347
+ };
348
+ }
349
+ }
350
+
351
+ export default CommitHashSystem;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Compression Engine for Trace
3
+ * Delta-based storage for fast commits and minimal file size
4
+ */
5
+
6
+ export interface Delta {
7
+ type: 'add' | 'modify' | 'delete';
8
+ path: string;
9
+ hash: string;
10
+ baseHash?: string;
11
+ size: number;
12
+ compressed: boolean;
13
+ }
14
+
15
+ export interface CompressionStats {
16
+ originalSize: number;
17
+ compressedSize: number;
18
+ ratio: number;
19
+ deltas: number;
20
+ }
21
+
22
+ export class CompressionEngine {
23
+ /**
24
+ * Calculate delta between two file versions
25
+ */
26
+ static calculateDelta(oldContent: string, newContent: string): string {
27
+ if (oldContent === newContent) return '';
28
+
29
+ const oldLines = oldContent.split('\n');
30
+ const newLines = newContent.split('\n');
31
+ const delta: string[] = [];
32
+
33
+ // Simple line-based diff (production would use Levenshtein or similar)
34
+ for (let i = 0; i < Math.max(oldLines.length, newLines.length); i++) {
35
+ const oldLine = oldLines[i];
36
+ const newLine = newLines[i];
37
+
38
+ if (oldLine !== newLine) {
39
+ if (oldLine !== undefined) delta.push(`-${i}:${oldLine}`);
40
+ if (newLine !== undefined) delta.push(`+${i}:${newLine}`);
41
+ }
42
+ }
43
+
44
+ return delta.join('\n');
45
+ }
46
+
47
+ /**
48
+ * Compress content using deflate-like algorithm (mock)
49
+ */
50
+ static compress(content: string): { compressed: string; ratio: number } {
51
+ // In production, use zlib or similar
52
+ const compressed = Buffer.from(content).toString('base64');
53
+ const ratio = compressed.length > 0 ? content.length / compressed.length : 1;
54
+
55
+ return { compressed, ratio };
56
+ }
57
+
58
+ /**
59
+ * Decompress content
60
+ */
61
+ static decompress(compressed: string): string {
62
+ try {
63
+ return Buffer.from(compressed, 'base64').toString('utf-8');
64
+ } catch {
65
+ return compressed;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Calculate hash (simple checksum)
71
+ */
72
+ static hash(content: string): string {
73
+ let hash = 0;
74
+ for (let i = 0; i < content.length; i++) {
75
+ const char = content.charCodeAt(i);
76
+ hash = (hash << 5) - hash + char;
77
+ hash = hash & hash; // Convert to 32bit integer
78
+ }
79
+ return Math.abs(hash).toString(36);
80
+ }
81
+
82
+ /**
83
+ * Create delta from two files
84
+ */
85
+ static createDelta(path: string, oldContent: string, newContent: string): Delta {
86
+ const delta = this.calculateDelta(oldContent, newContent);
87
+ const { compressed, ratio } = this.compress(delta);
88
+
89
+ return {
90
+ type: 'modify',
91
+ path,
92
+ hash: this.hash(newContent),
93
+ baseHash: this.hash(oldContent),
94
+ size: newContent.length,
95
+ compressed: ratio > 1,
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Apply delta to reconstruct file
101
+ */
102
+ static applyDelta(baseContent: string, delta: string): string {
103
+ const lines = baseContent.split('\n');
104
+ const changes = delta.split('\n');
105
+
106
+ for (const change of changes) {
107
+ if (change.startsWith('-')) {
108
+ const [, lineNumStr, ...rest] = change.split(':');
109
+ const lineNum = parseInt(lineNumStr, 10);
110
+ if (lineNum >= 0 && lineNum < lines.length) {
111
+ lines[lineNum] = '';
112
+ }
113
+ } else if (change.startsWith('+')) {
114
+ const [, lineNumStr, ...rest] = change.split(':');
115
+ const lineNum = parseInt(lineNumStr, 10);
116
+ const newLine = rest.join(':');
117
+ if (lineNum >= 0) {
118
+ lines[lineNum] = newLine;
119
+ }
120
+ }
121
+ }
122
+
123
+ return lines.join('\n');
124
+ }
125
+
126
+ /**
127
+ * Compress file set
128
+ */
129
+ static compressFileSet(files: Map<string, string>): { deltas: Delta[]; stats: CompressionStats } {
130
+ const deltas: Delta[] = [];
131
+ let originalSize = 0;
132
+ let compressedSize = 0;
133
+
134
+ for (const [path, content] of files) {
135
+ originalSize += content.length;
136
+
137
+ const { compressed } = this.compress(content);
138
+ compressedSize += compressed.length;
139
+
140
+ const hash = this.hash(content);
141
+
142
+ deltas.push({
143
+ type: 'add',
144
+ path,
145
+ hash,
146
+ size: content.length,
147
+ compressed: compressed.length < content.length,
148
+ });
149
+ }
150
+
151
+ return {
152
+ deltas,
153
+ stats: {
154
+ originalSize,
155
+ compressedSize,
156
+ ratio: originalSize > 0 ? originalSize / compressedSize : 1,
157
+ deltas: deltas.length,
158
+ },
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Estimate compression benefit
164
+ */
165
+ static estimateBenefit(fileCount: number, avgFileSize: number): { savings: string; ratio: string } {
166
+ const originalSize = fileCount * avgFileSize;
167
+ // Typical compression ratio ~1.5-2x
168
+ const estimatedCompressed = originalSize / 1.7;
169
+ const savings = `${((originalSize - estimatedCompressed) / originalSize * 100).toFixed(1)}%`;
170
+ const ratio = `${(originalSize / estimatedCompressed).toFixed(2)}x`;
171
+
172
+ return { savings, ratio };
173
+ }
174
+ }
175
+
176
+ export default CompressionEngine;