@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,227 @@
1
+ /**
2
+ * Agent Checkout System for Trace
3
+ * Restore working directory to specific commit (like git checkout)
4
+ */
5
+
6
+ import { Commit } from './commit-hash-system';
7
+
8
+ export interface CheckoutResult {
9
+ success: boolean;
10
+ hash: string;
11
+ filesRestored: string[];
12
+ filesRemoved: string[];
13
+ message: string;
14
+ timestamp: number;
15
+ }
16
+
17
+ export interface WorkingDirectory {
18
+ currentHash: string;
19
+ currentCommit?: Commit;
20
+ files: Map<string, string>;
21
+ dirty: boolean;
22
+ timestamp: number;
23
+ }
24
+
25
+ export class AgentCheckout {
26
+ private workingDirectory: WorkingDirectory;
27
+ private checkoutHistory: CheckoutResult[] = [];
28
+
29
+ constructor() {
30
+ this.workingDirectory = {
31
+ currentHash: '',
32
+ files: new Map(),
33
+ dirty: false,
34
+ timestamp: Date.now(),
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Checkout commit (restore working directory)
40
+ */
41
+ checkout(commit: Commit, force: boolean = false): CheckoutResult {
42
+ // Check for uncommitted changes
43
+ if (this.workingDirectory.dirty && !force) {
44
+ throw new Error(
45
+ `Working directory has uncommitted changes. Use force=true to discard.`
46
+ );
47
+ }
48
+
49
+ const filesRestored: string[] = [];
50
+ const filesRemoved: string[] = [];
51
+ const oldFiles = new Map(this.workingDirectory.files);
52
+
53
+ // Apply new files
54
+ this.workingDirectory.files.clear();
55
+ for (const [path, content] of commit.files) {
56
+ this.workingDirectory.files.set(path, content);
57
+ if (!oldFiles.has(path)) {
58
+ filesRestored.push(path);
59
+ }
60
+ }
61
+
62
+ // Identify removed files
63
+ for (const path of oldFiles.keys()) {
64
+ if (!this.workingDirectory.files.has(path)) {
65
+ filesRemoved.push(path);
66
+ }
67
+ }
68
+
69
+ this.workingDirectory.currentHash = commit.hash.full;
70
+ this.workingDirectory.currentCommit = commit;
71
+ this.workingDirectory.dirty = false;
72
+ this.workingDirectory.timestamp = Date.now();
73
+
74
+ const result: CheckoutResult = {
75
+ success: true,
76
+ hash: commit.hash.full,
77
+ filesRestored,
78
+ filesRemoved,
79
+ message: `Checked out ${commit.hash.short}: ${commit.message}`,
80
+ timestamp: this.workingDirectory.timestamp,
81
+ };
82
+
83
+ this.checkoutHistory.push(result);
84
+ return result;
85
+ }
86
+
87
+ /**
88
+ * Checkout by hash (full or short)
89
+ */
90
+ checkoutByHash(
91
+ commits: Map<string, Commit>,
92
+ hash: string,
93
+ force: boolean = false
94
+ ): CheckoutResult {
95
+ let commit = commits.get(hash);
96
+
97
+ // Try short hash
98
+ if (!commit) {
99
+ for (const [fullHash, c] of commits) {
100
+ if (fullHash.startsWith(hash)) {
101
+ commit = c;
102
+ break;
103
+ }
104
+ }
105
+ }
106
+
107
+ if (!commit) {
108
+ throw new Error(`Commit ${hash} not found`);
109
+ }
110
+
111
+ return this.checkout(commit, force);
112
+ }
113
+
114
+ /**
115
+ * Get current working directory state
116
+ */
117
+ getWorkingDirectory(): WorkingDirectory {
118
+ return {
119
+ ...this.workingDirectory,
120
+ files: new Map(this.workingDirectory.files),
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Get file at current commit
126
+ */
127
+ getFile(path: string): string | undefined {
128
+ return this.workingDirectory.files.get(path);
129
+ }
130
+
131
+ /**
132
+ * List all files in working directory
133
+ */
134
+ listFiles(): string[] {
135
+ return Array.from(this.workingDirectory.files.keys()).sort();
136
+ }
137
+
138
+ /**
139
+ * Check if working directory is clean
140
+ */
141
+ isClean(): boolean {
142
+ return !this.workingDirectory.dirty;
143
+ }
144
+
145
+ /**
146
+ * Mark working directory as dirty (files changed)
147
+ */
148
+ markDirty(): void {
149
+ this.workingDirectory.dirty = true;
150
+ }
151
+
152
+ /**
153
+ * Get checkout history
154
+ */
155
+ getCheckoutHistory(): CheckoutResult[] {
156
+ return [...this.checkoutHistory];
157
+ }
158
+
159
+ /**
160
+ * Get current commit
161
+ */
162
+ getCurrentCommit(): Commit | undefined {
163
+ return this.workingDirectory.currentCommit;
164
+ }
165
+
166
+ /**
167
+ * Get current hash
168
+ */
169
+ getCurrentHash(): string {
170
+ return this.workingDirectory.currentHash;
171
+ }
172
+
173
+ /**
174
+ * Diff between current and another commit
175
+ */
176
+ diffWithCommit(otherCommit: Commit): {
177
+ added: string[];
178
+ removed: string[];
179
+ modified: string[];
180
+ } {
181
+ const added: string[] = [];
182
+ const removed: string[] = [];
183
+ const modified: string[] = [];
184
+
185
+ // Find added/modified files
186
+ for (const [path, content] of otherCommit.files) {
187
+ if (!this.workingDirectory.files.has(path)) {
188
+ added.push(path);
189
+ } else if (this.workingDirectory.files.get(path) !== content) {
190
+ modified.push(path);
191
+ }
192
+ }
193
+
194
+ // Find removed files
195
+ for (const path of this.workingDirectory.files.keys()) {
196
+ if (!otherCommit.files.has(path)) {
197
+ removed.push(path);
198
+ }
199
+ }
200
+
201
+ return { added, removed, modified };
202
+ }
203
+
204
+ /**
205
+ * Reset to specific file version from commit
206
+ */
207
+ restoreFile(path: string, commit: Commit): boolean {
208
+ if (!commit.files.has(path)) {
209
+ return false;
210
+ }
211
+
212
+ this.workingDirectory.files.set(path, commit.files.get(path)!);
213
+ return true;
214
+ }
215
+
216
+ /**
217
+ * Restore all files from commit
218
+ */
219
+ restoreAll(commit: Commit): void {
220
+ this.workingDirectory.files.clear();
221
+ for (const [path, content] of commit.files) {
222
+ this.workingDirectory.files.set(path, content);
223
+ }
224
+ }
225
+ }
226
+
227
+ export default AgentCheckout;
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Agent Coordination Layer
3
+ * The REAL value proposition: prevent agents from stepping on each other
4
+ * This is what git doesn't solve
5
+ */
6
+
7
+ export interface AgentTask {
8
+ taskId: string;
9
+ agent: string;
10
+ files: string[]; // Files this task will modify
11
+ priority: number; // Higher = goes first
12
+ status: 'pending' | 'assigned' | 'executing' | 'completed' | 'blocked';
13
+ assignedAt?: number;
14
+ completedAt?: number;
15
+ blockedBy?: string[]; // Other task IDs blocking this
16
+ }
17
+
18
+ export interface CoordinationPolicy {
19
+ allowParallel: boolean; // Can tasks run in parallel?
20
+ maxConcurrent: number; // How many tasks at once?
21
+ lockTimeout: number; // How long to hold lock before auto-release?
22
+ conflictStrategy: 'block' | 'queue' | 'merge';
23
+ }
24
+
25
+ export class AgentCoordination {
26
+ private tasks: Map<string, AgentTask> = new Map();
27
+ private fileLocks: Map<string, string> = new Map(); // file -> agentId
28
+ private policy: CoordinationPolicy;
29
+
30
+ constructor(policy: Partial<CoordinationPolicy> = {}) {
31
+ this.policy = {
32
+ allowParallel: true,
33
+ maxConcurrent: 10,
34
+ lockTimeout: 5 * 60 * 1000, // 5 minutes
35
+ conflictStrategy: 'block',
36
+ ...policy,
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Register a task from an agent
42
+ * Returns whether task can start immediately or is blocked
43
+ */
44
+ registerTask(agent: string, files: string[], priority: number = 0): {
45
+ taskId: string;
46
+ canStart: boolean;
47
+ blockedBy?: string[];
48
+ } {
49
+ const taskId = `task-${Date.now()}-${Math.random().toString(36).substring(7)}`;
50
+
51
+ const task: AgentTask = {
52
+ taskId,
53
+ agent,
54
+ files,
55
+ priority,
56
+ status: 'pending',
57
+ assignedAt: Date.now(),
58
+ };
59
+
60
+ // Check for conflicts
61
+ const conflicts = this.detectConflicts(agent, files);
62
+
63
+ if (conflicts.length > 0) {
64
+ if (this.policy.conflictStrategy === 'block') {
65
+ task.status = 'blocked';
66
+ task.blockedBy = conflicts;
67
+ this.tasks.set(taskId, task);
68
+ return { taskId, canStart: false, blockedBy: conflicts };
69
+ }
70
+ }
71
+
72
+ task.status = 'executing';
73
+ this.tasks.set(taskId, task);
74
+ return { taskId, canStart: true };
75
+ }
76
+
77
+ /**
78
+ * Detect if agent's files conflict with existing tasks
79
+ */
80
+ private detectConflicts(agent: string, files: string[]): string[] {
81
+ const conflicts: string[] = [];
82
+
83
+ for (const task of this.tasks.values()) {
84
+ if (task.agent === agent || task.status === 'completed') {
85
+ continue; // Same agent or finished task
86
+ }
87
+
88
+ // Check if any files overlap
89
+ const overlap = task.files.some(f => files.includes(f));
90
+ if (overlap) {
91
+ conflicts.push(task.taskId);
92
+ }
93
+ }
94
+
95
+ return conflicts;
96
+ }
97
+
98
+ /**
99
+ * Request lock on a file for exclusive access
100
+ */
101
+ requestLock(file: string, agent: string): {
102
+ locked: boolean;
103
+ owner?: string;
104
+ waitTime?: number;
105
+ } {
106
+ // Check if already locked by someone else
107
+ const owner = this.fileLocks.get(file);
108
+
109
+ if (owner && owner !== agent) {
110
+ return { locked: false, owner };
111
+ }
112
+
113
+ // Lock it
114
+ this.fileLocks.set(file, agent);
115
+ return { locked: true };
116
+ }
117
+
118
+ /**
119
+ * Release lock
120
+ */
121
+ releaseLock(file: string, agent: string): boolean {
122
+ const owner = this.fileLocks.get(file);
123
+ if (owner !== agent) {
124
+ return false; // Not the owner
125
+ }
126
+
127
+ this.fileLocks.delete(file);
128
+ return true;
129
+ }
130
+
131
+ /**
132
+ * Check what tasks are blocked and why
133
+ */
134
+ getBlockedTasks(): Array<{ taskId: string; agent: string; blockedBy: string[] }> {
135
+ const blocked: Array<{ taskId: string; agent: string; blockedBy: string[] }> = [];
136
+
137
+ for (const task of this.tasks.values()) {
138
+ if (task.status === 'blocked' && task.blockedBy) {
139
+ blocked.push({
140
+ taskId: task.taskId,
141
+ agent: task.agent,
142
+ blockedBy: task.blockedBy,
143
+ });
144
+ }
145
+ }
146
+
147
+ return blocked;
148
+ }
149
+
150
+ /**
151
+ * Get current lock holder for file
152
+ */
153
+ getLockHolder(file: string): string | undefined {
154
+ return this.fileLocks.get(file);
155
+ }
156
+
157
+ /**
158
+ * Complete task and auto-unblock dependent tasks
159
+ */
160
+ completeTask(taskId: string): {
161
+ success: boolean;
162
+ unblocked: string[];
163
+ } {
164
+ const task = this.tasks.get(taskId);
165
+ if (!task) {
166
+ return { success: false, unblocked: [] };
167
+ }
168
+
169
+ task.status = 'completed';
170
+ task.completedAt = Date.now();
171
+
172
+ // Release locks
173
+ for (const file of task.files) {
174
+ this.releaseLock(file, task.agent);
175
+ }
176
+
177
+ // Find blocked tasks and try to unblock
178
+ const unblocked: string[] = [];
179
+ for (const otherTask of this.tasks.values()) {
180
+ if (otherTask.status === 'blocked' && otherTask.blockedBy?.includes(taskId)) {
181
+ const newBlockers = (otherTask.blockedBy || []).filter(b => b !== taskId);
182
+
183
+ if (newBlockers.length === 0) {
184
+ otherTask.status = 'executing';
185
+ unblocked.push(otherTask.taskId);
186
+ } else {
187
+ otherTask.blockedBy = newBlockers;
188
+ }
189
+ }
190
+ }
191
+
192
+ return { success: true, unblocked };
193
+ }
194
+
195
+ /**
196
+ * Get task status
197
+ */
198
+ getTaskStatus(taskId: string): AgentTask | undefined {
199
+ return this.tasks.get(taskId);
200
+ }
201
+
202
+ /**
203
+ * Get all active tasks for agent
204
+ */
205
+ getAgentTasks(agent: string): AgentTask[] {
206
+ return Array.from(this.tasks.values()).filter(t => t.agent === agent);
207
+ }
208
+
209
+ /**
210
+ * Get coordination statistics
211
+ */
212
+ getStats(): {
213
+ totalTasks: number;
214
+ executing: number;
215
+ blocked: number;
216
+ completed: number;
217
+ currentLocks: number;
218
+ avgWaitTime: number;
219
+ } {
220
+ let executing = 0;
221
+ let blocked = 0;
222
+ let completed = 0;
223
+ let totalWait = 0;
224
+
225
+ for (const task of this.tasks.values()) {
226
+ switch (task.status) {
227
+ case 'executing':
228
+ executing++;
229
+ break;
230
+ case 'blocked':
231
+ blocked++;
232
+ if (task.assignedAt) {
233
+ totalWait += Date.now() - task.assignedAt;
234
+ }
235
+ break;
236
+ case 'completed':
237
+ completed++;
238
+ break;
239
+ }
240
+ }
241
+
242
+ return {
243
+ totalTasks: this.tasks.size,
244
+ executing,
245
+ blocked,
246
+ completed,
247
+ currentLocks: this.fileLocks.size,
248
+ avgWaitTime: blocked > 0 ? Math.round(totalWait / blocked) : 0,
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Suggest optimal task ordering
254
+ */
255
+ suggestOrder(): string[] {
256
+ // Sort by priority, then by dependencies
257
+ return Array.from(this.tasks.values())
258
+ .filter(t => t.status !== 'completed')
259
+ .sort((a, b) => {
260
+ // Higher priority first
261
+ if (b.priority !== a.priority) {
262
+ return b.priority - a.priority;
263
+ }
264
+ // Blocked tasks last
265
+ if ((a.blockedBy?.length || 0) !== (b.blockedBy?.length || 0)) {
266
+ return (a.blockedBy?.length || 0) - (b.blockedBy?.length || 0);
267
+ }
268
+ // Older first
269
+ return (a.assignedAt || 0) - (b.assignedAt || 0);
270
+ })
271
+ .map(t => t.taskId);
272
+ }
273
+
274
+ /**
275
+ * Set coordination policy
276
+ */
277
+ setPolicy(policy: Partial<CoordinationPolicy>): void {
278
+ this.policy = { ...this.policy, ...policy };
279
+ }
280
+
281
+ /**
282
+ * Get coordination policy
283
+ */
284
+ getPolicy(): CoordinationPolicy {
285
+ return { ...this.policy };
286
+ }
287
+
288
+ /**
289
+ * Format coordination state for display
290
+ */
291
+ formatState(): string {
292
+ const stats = this.getStats();
293
+ let output = `\n🔀 AGENT COORDINATION STATE\n`;
294
+ output += `Tasks: ${stats.totalTasks} total (executing: ${stats.executing}, blocked: ${stats.blocked}, done: ${stats.completed})\n`;
295
+ output += `File Locks: ${stats.currentLocks}\n`;
296
+ output += `Avg Block Time: ${stats.avgWaitTime}ms\n\n`;
297
+
298
+ const blocked = this.getBlockedTasks();
299
+ if (blocked.length > 0) {
300
+ output += `BLOCKED TASKS:\n`;
301
+ for (const task of blocked) {
302
+ output += ` ${task.taskId} (agent: ${task.agent}) blocked by ${task.blockedBy.join(', ')}\n`;
303
+ }
304
+ }
305
+
306
+ return output;
307
+ }
308
+
309
+ /**
310
+ * Clear all state (for testing)
311
+ */
312
+ clear(): void {
313
+ this.tasks.clear();
314
+ this.fileLocks.clear();
315
+ }
316
+ }
317
+
318
+ export default AgentCoordination;