@zoebuildsai/trace 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +115 -0
- package/.trace/progress.json +22 -0
- package/README.md +466 -0
- package/RELEASE-NOTES-1.5.0.md +410 -0
- package/STATUS.md +245 -0
- package/dist/auto-commit.d.ts +66 -0
- package/dist/auto-commit.d.ts.map +1 -0
- package/dist/auto-commit.js +180 -0
- package/dist/auto-commit.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +246 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +46 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +256 -0
- package/dist/commands.js.map +1 -0
- package/dist/diff.d.ts +23 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +106 -0
- package/dist/diff.js.map +1 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js.map +1 -0
- package/dist/index-cache.d.ts +35 -0
- package/dist/index-cache.d.ts.map +1 -0
- package/dist/index-cache.js +114 -0
- package/dist/index-cache.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +45 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +151 -0
- package/dist/storage.js.map +1 -0
- package/dist/sync.d.ts +60 -0
- package/dist/sync.js +184 -0
- package/dist/tags.d.ts +85 -0
- package/dist/tags.d.ts.map +1 -0
- package/dist/tags.js +219 -0
- package/dist/tags.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +73 -0
- package/docs/_config.yml +2 -0
- package/docs/index.html +960 -0
- package/docs-website/package.json +20 -0
- package/jest.config.js +21 -0
- package/package.json +50 -0
- package/scripts/init.ts +290 -0
- package/src/agent-audit.ts +270 -0
- package/src/agent-checkout.ts +227 -0
- package/src/agent-coordination.ts +318 -0
- package/src/async-queue.ts +203 -0
- package/src/auto-branching.ts +279 -0
- package/src/auto-commit.ts +166 -0
- package/src/cherry-pick.ts +252 -0
- package/src/chunked-upload.ts +224 -0
- package/src/cli-v2.ts +335 -0
- package/src/cli.ts +318 -0
- package/src/cliff-detection.ts +232 -0
- package/src/commands.ts +267 -0
- package/src/commit-hash-system.ts +351 -0
- package/src/compression.ts +176 -0
- package/src/conflict-resolution-ui.ts +277 -0
- package/src/conflict-visualization.ts +238 -0
- package/src/diff-formatter.ts +184 -0
- package/src/diff.ts +124 -0
- package/src/distributed-coordination.ts +273 -0
- package/src/git-interop.ts +316 -0
- package/src/index-cache.ts +88 -0
- package/src/index.ts +38 -0
- package/src/merge-engine.ts +143 -0
- package/src/message-search.ts +370 -0
- package/src/performance-monitoring.ts +236 -0
- package/src/rebase.ts +327 -0
- package/src/rollback.ts +215 -0
- package/src/semantic-grouping.ts +245 -0
- package/src/stage-area.ts +324 -0
- package/src/stash.ts +278 -0
- package/src/storage.ts +131 -0
- package/src/sync.ts +205 -0
- package/src/tags.ts +244 -0
- package/src/types.ts +119 -0
- package/src/webhooks.ts +119 -0
- package/src/workspace-isolation.ts +298 -0
- package/tests/auto-commit.test.ts +308 -0
- package/tests/checkout.test.ts +136 -0
- package/tests/commit.test.ts +118 -0
- package/tests/diff.test.ts +191 -0
- package/tests/github.test.ts +94 -0
- package/tests/integration.test.ts +267 -0
- package/tests/log.test.ts +125 -0
- package/tests/phase2-integration.test.ts +370 -0
- package/tests/storage.test.ts +167 -0
- package/tests/tags.test.ts +477 -0
- package/tests/types.test.ts +75 -0
- package/tests/v1.1/agent-audit.test.ts +472 -0
- package/tests/v1.1/agent-coordination.test.ts +308 -0
- package/tests/v1.1/async-queue.test.ts +253 -0
- package/tests/v1.1/comprehensive.test.ts +521 -0
- package/tests/v1.1/diff-formatter.test.ts +238 -0
- package/tests/v1.1/integration.test.ts +389 -0
- package/tests/v1.1/onboarding.test.ts +365 -0
- package/tests/v1.1/rollback.test.ts +370 -0
- package/tests/v1.1/semantic-grouping.test.ts +230 -0
- package/tests/v1.2/chunked-upload.test.ts +301 -0
- package/tests/v1.2/cliff-detection.test.ts +272 -0
- package/tests/v1.2/commit-hash-system.test.ts +288 -0
- package/tests/v1.2/compression.test.ts +220 -0
- package/tests/v1.2/conflict-visualization.test.ts +263 -0
- package/tests/v1.2/distributed.test.ts +261 -0
- package/tests/v1.2/performance-monitoring.test.ts +328 -0
- package/tests/v1.3/auto-branching.test.ts +270 -0
- package/tests/v1.3/message-search.test.ts +264 -0
- package/tests/v1.3/stage-area.test.ts +330 -0
- package/tests/v1.3/stash-rebase-cherry-pick.test.ts +361 -0
- package/tests/v1.4/cli.test.ts +171 -0
- package/tests/v1.4/conflict-resolution-advanced.test.ts +429 -0
- package/tests/v1.4/conflict-resolution-ui.test.ts +286 -0
- package/tests/v1.4/workspace-isolation-advanced.test.ts +382 -0
- package/tests/v1.4/workspace-isolation.test.ts +268 -0
- package/tests/v1.5/agent-coordination.real.test.ts +401 -0
- package/tests/v1.5/cli-v2.test.ts +354 -0
- package/tests/v1.5/git-interop.real.test.ts +358 -0
- package/tests/v1.5/integration-testing.real.test.ts +440 -0
- package/tsconfig.json +26 -0
package/src/diff.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { TreeObject, DiffResult, DiffChange, DiffStats, LineDiff, DiffOptions } from './types';
|
|
2
|
+
import { Storage } from './storage';
|
|
3
|
+
|
|
4
|
+
export class Differ {
|
|
5
|
+
private storage: Storage;
|
|
6
|
+
|
|
7
|
+
constructor(storage: Storage) {
|
|
8
|
+
this.storage = storage;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compare two tree objects or file maps
|
|
13
|
+
*/
|
|
14
|
+
diff(oldInput: TreeObject | Map<string, import('./types').FileEntry>, newInput: TreeObject | Map<string, import('./types').FileEntry>, options?: DiffOptions): DiffResult {
|
|
15
|
+
const result: DiffResult = {
|
|
16
|
+
added: new Map(),
|
|
17
|
+
modified: new Map(),
|
|
18
|
+
deleted: new Map(),
|
|
19
|
+
stats: {
|
|
20
|
+
filesChanged: 0,
|
|
21
|
+
linesAdded: 0,
|
|
22
|
+
linesRemoved: 0,
|
|
23
|
+
totalChanges: 0,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const oldFiles = oldInput instanceof Map ? oldInput : (oldInput.files || new Map());
|
|
28
|
+
const newFiles = newInput instanceof Map ? newInput : (newInput.files || new Map());
|
|
29
|
+
|
|
30
|
+
// Find modified and deleted
|
|
31
|
+
oldFiles.forEach((oldEntry, path) => {
|
|
32
|
+
const newEntry = newFiles.get(path);
|
|
33
|
+
|
|
34
|
+
if (!newEntry) {
|
|
35
|
+
result.deleted.set(path, oldEntry.hash);
|
|
36
|
+
result.stats.filesChanged++;
|
|
37
|
+
result.stats.totalChanges++;
|
|
38
|
+
} else if (oldEntry.hash !== newEntry.hash) {
|
|
39
|
+
const change: DiffChange = {
|
|
40
|
+
oldHash: oldEntry.hash,
|
|
41
|
+
newHash: newEntry.hash,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (options?.lines) {
|
|
45
|
+
change.lines = this.computeLineDiff(oldEntry.hash, newEntry.hash);
|
|
46
|
+
result.stats.linesAdded += change.lines.filter(l => l.type === 'add').length;
|
|
47
|
+
result.stats.linesRemoved += change.lines.filter(l => l.type === 'remove').length;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
result.modified.set(path, change);
|
|
51
|
+
result.stats.filesChanged++;
|
|
52
|
+
result.stats.totalChanges++;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Find added
|
|
57
|
+
newFiles.forEach((newEntry, path) => {
|
|
58
|
+
if (!oldFiles.has(path)) {
|
|
59
|
+
result.added.set(path, newEntry.hash);
|
|
60
|
+
result.stats.filesChanged++;
|
|
61
|
+
result.stats.totalChanges++;
|
|
62
|
+
|
|
63
|
+
if (options?.lines) {
|
|
64
|
+
const lines = newEntry.size; // Simplified: count as number of bytes
|
|
65
|
+
result.stats.linesAdded += Math.ceil(lines / 50); // Estimate lines
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Compute line-level diff (simplified Myers diff)
|
|
75
|
+
*/
|
|
76
|
+
private computeLineDiff(oldHash: string, newHash: string, context: number = 3): LineDiff[] {
|
|
77
|
+
// Simplified implementation: placeholder
|
|
78
|
+
// In production, would implement full Myers diff algorithm
|
|
79
|
+
return [
|
|
80
|
+
{ type: 'context', content: '...' },
|
|
81
|
+
{ type: 'remove', content: `old content (${oldHash.slice(0, 8)})` },
|
|
82
|
+
{ type: 'add', content: `new content (${newHash.slice(0, 8)})` },
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generate diff stats
|
|
88
|
+
*/
|
|
89
|
+
getStats(diff: DiffResult): DiffStats {
|
|
90
|
+
return diff.stats;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Human-readable diff output
|
|
95
|
+
*/
|
|
96
|
+
format(diff: DiffResult): string {
|
|
97
|
+
const lines: string[] = [];
|
|
98
|
+
|
|
99
|
+
diff.deleted.forEach((hash, path) => {
|
|
100
|
+
lines.push(`deleted: ${path}`);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
diff.added.forEach((hash, path) => {
|
|
104
|
+
lines.push(`new file: ${path}`);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
diff.modified.forEach((change, path) => {
|
|
108
|
+
lines.push(`modified: ${path}`);
|
|
109
|
+
if (change.lines) {
|
|
110
|
+
change.lines.forEach(line => {
|
|
111
|
+
const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' ';
|
|
112
|
+
lines.push(`${prefix} ${line.content}`);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
lines.push('');
|
|
118
|
+
lines.push(
|
|
119
|
+
`${diff.stats.filesChanged} files changed, ${diff.stats.linesAdded} insertions(+), ${diff.stats.linesRemoved} deletions(-)`
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return lines.join('\n');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Distributed Coordination for Trace
|
|
3
|
+
* Enable agents on different machines to safely collaborate
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface RemoteAgent {
|
|
7
|
+
agentId: string;
|
|
8
|
+
endpoint: string; // https://agent-server/api
|
|
9
|
+
publicKey: string;
|
|
10
|
+
lastSeen: number;
|
|
11
|
+
status: 'online' | 'offline' | 'unreachable';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CoordinationMessage {
|
|
15
|
+
type: 'lock-request' | 'lock-grant' | 'lock-release' | 'commit' | 'collision' | 'merge';
|
|
16
|
+
agentId: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
signature: string;
|
|
19
|
+
payload: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SyncState {
|
|
23
|
+
agentId: string;
|
|
24
|
+
commitId: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
filesChanged: number;
|
|
27
|
+
status: 'synced' | 'pending' | 'failed';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class DistributedCoordination {
|
|
31
|
+
private remoteAgents: Map<string, RemoteAgent> = new Map();
|
|
32
|
+
private messageLog: CoordinationMessage[] = [];
|
|
33
|
+
private syncStates: Map<string, SyncState> = new Map();
|
|
34
|
+
private heartbeatInterval: number = 30000; // 30 seconds
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Register remote agent
|
|
38
|
+
*/
|
|
39
|
+
registerRemoteAgent(agentId: string, endpoint: string, publicKey: string): RemoteAgent {
|
|
40
|
+
const agent: RemoteAgent = {
|
|
41
|
+
agentId,
|
|
42
|
+
endpoint,
|
|
43
|
+
publicKey,
|
|
44
|
+
lastSeen: Date.now(),
|
|
45
|
+
status: 'offline',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
this.remoteAgents.set(agentId, agent);
|
|
49
|
+
return agent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Send lock request to remote agent
|
|
54
|
+
*/
|
|
55
|
+
async requestRemoteLock(
|
|
56
|
+
agentId: string,
|
|
57
|
+
filePath: string,
|
|
58
|
+
duration: number,
|
|
59
|
+
signature: string
|
|
60
|
+
): Promise<boolean> {
|
|
61
|
+
const agent = this.remoteAgents.get(agentId);
|
|
62
|
+
if (!agent || agent.status === 'offline') {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const message: CoordinationMessage = {
|
|
67
|
+
type: 'lock-request',
|
|
68
|
+
agentId,
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
signature,
|
|
71
|
+
payload: { filePath, duration },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
this.messageLog.push(message);
|
|
75
|
+
|
|
76
|
+
// In real implementation, would POST to agent.endpoint
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Grant lock to requesting agent
|
|
82
|
+
*/
|
|
83
|
+
grantLock(agentId: string, filePath: string, duration: number, signature: string): boolean {
|
|
84
|
+
const message: CoordinationMessage = {
|
|
85
|
+
type: 'lock-grant',
|
|
86
|
+
agentId,
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
signature,
|
|
89
|
+
payload: { filePath, duration },
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
this.messageLog.push(message);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Release lock
|
|
98
|
+
*/
|
|
99
|
+
releaseLock(agentId: string, filePath: string, signature: string): boolean {
|
|
100
|
+
const message: CoordinationMessage = {
|
|
101
|
+
type: 'lock-release',
|
|
102
|
+
agentId,
|
|
103
|
+
timestamp: Date.now(),
|
|
104
|
+
signature,
|
|
105
|
+
payload: { filePath },
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
this.messageLog.push(message);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Broadcast commit to all agents
|
|
114
|
+
*/
|
|
115
|
+
broadcastCommit(
|
|
116
|
+
agentId: string,
|
|
117
|
+
commitId: string,
|
|
118
|
+
files: string[],
|
|
119
|
+
signature: string
|
|
120
|
+
): number {
|
|
121
|
+
const message: CoordinationMessage = {
|
|
122
|
+
type: 'commit',
|
|
123
|
+
agentId,
|
|
124
|
+
timestamp: Date.now(),
|
|
125
|
+
signature,
|
|
126
|
+
payload: { commitId, files },
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
this.messageLog.push(message);
|
|
130
|
+
|
|
131
|
+
// Update sync state for all agents
|
|
132
|
+
for (const agent of this.remoteAgents.values()) {
|
|
133
|
+
this.syncStates.set(`${agent.agentId}-${commitId}`, {
|
|
134
|
+
agentId: agent.agentId,
|
|
135
|
+
commitId,
|
|
136
|
+
timestamp: Date.now(),
|
|
137
|
+
filesChanged: files.length,
|
|
138
|
+
status: 'pending',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return this.remoteAgents.size;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Detect and report collision across agents
|
|
147
|
+
*/
|
|
148
|
+
reportCollision(agentA: string, agentB: string, filePath: string, signature: string): boolean {
|
|
149
|
+
const message: CoordinationMessage = {
|
|
150
|
+
type: 'collision',
|
|
151
|
+
agentId: agentA,
|
|
152
|
+
timestamp: Date.now(),
|
|
153
|
+
signature,
|
|
154
|
+
payload: { agentB, filePath },
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
this.messageLog.push(message);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Sync state between agents
|
|
163
|
+
*/
|
|
164
|
+
syncState(agentId: string, commitId: string, filesChanged: number): SyncState {
|
|
165
|
+
const state: SyncState = {
|
|
166
|
+
agentId,
|
|
167
|
+
commitId,
|
|
168
|
+
timestamp: Date.now(),
|
|
169
|
+
filesChanged,
|
|
170
|
+
status: 'synced',
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
this.syncStates.set(`${agentId}-${commitId}`, state);
|
|
174
|
+
return state;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check agent heartbeat
|
|
179
|
+
*/
|
|
180
|
+
updateHeartbeat(agentId: string): boolean {
|
|
181
|
+
const agent = this.remoteAgents.get(agentId);
|
|
182
|
+
if (!agent) return false;
|
|
183
|
+
|
|
184
|
+
agent.lastSeen = Date.now();
|
|
185
|
+
agent.status = 'online';
|
|
186
|
+
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check for offline agents
|
|
192
|
+
*/
|
|
193
|
+
detectOfflineAgents(): string[] {
|
|
194
|
+
const now = Date.now();
|
|
195
|
+
const offline: string[] = [];
|
|
196
|
+
|
|
197
|
+
for (const [agentId, agent] of this.remoteAgents) {
|
|
198
|
+
if (now - agent.lastSeen > this.heartbeatInterval * 2) {
|
|
199
|
+
agent.status = 'offline';
|
|
200
|
+
offline.push(agentId);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return offline;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get message history
|
|
209
|
+
*/
|
|
210
|
+
getMessageLog(agentId?: string): CoordinationMessage[] {
|
|
211
|
+
if (!agentId) return [...this.messageLog];
|
|
212
|
+
return this.messageLog.filter(m => m.agentId === agentId);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Verify message signature
|
|
217
|
+
*/
|
|
218
|
+
verifyMessage(message: CoordinationMessage, publicKey: string): boolean {
|
|
219
|
+
// In production, would verify against actual ECDSA/Ed25519 signature
|
|
220
|
+
return message.signature.length > 0 && publicKey.length > 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get remote agent status
|
|
225
|
+
*/
|
|
226
|
+
getAgentStatus(agentId: string): RemoteAgent | undefined {
|
|
227
|
+
return this.remoteAgents.get(agentId);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get all agents
|
|
232
|
+
*/
|
|
233
|
+
getAllAgents(): RemoteAgent[] {
|
|
234
|
+
return Array.from(this.remoteAgents.values());
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get sync status for agent
|
|
239
|
+
*/
|
|
240
|
+
getSyncStatus(agentId: string): SyncState[] {
|
|
241
|
+
return Array.from(this.syncStates.values()).filter(s => s.agentId === agentId);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Resolve merge conflict across agents
|
|
246
|
+
*/
|
|
247
|
+
resolveMergeDistributed(
|
|
248
|
+
file: string,
|
|
249
|
+
agentA: string,
|
|
250
|
+
agentB: string,
|
|
251
|
+
strategy: string
|
|
252
|
+
): { resolved: boolean; winner: string } {
|
|
253
|
+
const message: CoordinationMessage = {
|
|
254
|
+
type: 'merge',
|
|
255
|
+
agentId: agentA,
|
|
256
|
+
timestamp: Date.now(),
|
|
257
|
+
signature: 'distributed-merge',
|
|
258
|
+
payload: { file, agentB, strategy },
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
this.messageLog.push(message);
|
|
262
|
+
|
|
263
|
+
// Simple strategy: most recent agent wins
|
|
264
|
+
const stateA = this.getSyncStatus(agentA)[0];
|
|
265
|
+
const stateB = this.getSyncStatus(agentB)[0];
|
|
266
|
+
|
|
267
|
+
const winner = stateB && stateB.timestamp > stateA?.timestamp ? agentB : agentA;
|
|
268
|
+
|
|
269
|
+
return { resolved: true, winner };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export default DistributedCoordination;
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Interoperability Layer
|
|
3
|
+
* Make Trace backward-compatible with git
|
|
4
|
+
* Agents use git syntax, Trace works behind the scenes
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
export interface GitInteropConfig {
|
|
11
|
+
transparentMode: boolean; // Show as git to agents
|
|
12
|
+
syncBothWays: boolean; // Keep git and Trace in sync
|
|
13
|
+
exportFormat: 'git' | 'trace';
|
|
14
|
+
importFormat: 'git' | 'trace';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class GitInterop {
|
|
18
|
+
private config: GitInteropConfig;
|
|
19
|
+
|
|
20
|
+
constructor(config: Partial<GitInteropConfig> = {}) {
|
|
21
|
+
this.config = {
|
|
22
|
+
transparentMode: true, // Default: agents don't know they're using Trace
|
|
23
|
+
syncBothWays: true, // Keep both in sync
|
|
24
|
+
exportFormat: 'git',
|
|
25
|
+
importFormat: 'git',
|
|
26
|
+
...config,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Wrap git command in Trace
|
|
32
|
+
* Agent runs: git commit -m "feat: x"
|
|
33
|
+
* Trace intercepts and processes
|
|
34
|
+
*/
|
|
35
|
+
wrapGitCommand(command: string, args: string[]): { intercepted: boolean; result?: any } {
|
|
36
|
+
const gitCommands = [
|
|
37
|
+
'clone',
|
|
38
|
+
'init',
|
|
39
|
+
'add',
|
|
40
|
+
'commit',
|
|
41
|
+
'push',
|
|
42
|
+
'pull',
|
|
43
|
+
'fetch',
|
|
44
|
+
'merge',
|
|
45
|
+
'branch',
|
|
46
|
+
'checkout',
|
|
47
|
+
'status',
|
|
48
|
+
'log',
|
|
49
|
+
'diff',
|
|
50
|
+
'tag',
|
|
51
|
+
'reset',
|
|
52
|
+
'rebase',
|
|
53
|
+
'stash',
|
|
54
|
+
'cherry-pick',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
if (!gitCommands.includes(command)) {
|
|
58
|
+
return { intercepted: false };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Intercept and map to Trace equivalent
|
|
62
|
+
const traceCommand = this.mapGitToTrace(command, args);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
intercepted: true,
|
|
66
|
+
result: traceCommand,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Map git command to Trace equivalent
|
|
72
|
+
*/
|
|
73
|
+
private mapGitToTrace(
|
|
74
|
+
gitCmd: string,
|
|
75
|
+
args: string[]
|
|
76
|
+
): { cmd: string; args: string[] } {
|
|
77
|
+
const mapping: Record<string, { cmd: string; args: string[] }> = {
|
|
78
|
+
commit: { cmd: 'trace-commit', args },
|
|
79
|
+
push: { cmd: 'trace-push', args },
|
|
80
|
+
pull: { cmd: 'trace-pull', args },
|
|
81
|
+
branch: { cmd: 'trace-branch', args },
|
|
82
|
+
checkout: { cmd: 'trace-checkout', args },
|
|
83
|
+
merge: { cmd: 'trace-merge', args },
|
|
84
|
+
stash: { cmd: 'trace-stash', args },
|
|
85
|
+
rebase: { cmd: 'trace-rebase', args },
|
|
86
|
+
'cherry-pick': { cmd: 'trace-cherry-pick', args },
|
|
87
|
+
// Passthrough commands (no interception needed)
|
|
88
|
+
clone: { cmd: 'git', args: [gitCmd, ...args] },
|
|
89
|
+
init: { cmd: 'git', args: [gitCmd, ...args] },
|
|
90
|
+
add: { cmd: 'git', args: [gitCmd, ...args] },
|
|
91
|
+
fetch: { cmd: 'git', args: [gitCmd, ...args] },
|
|
92
|
+
status: { cmd: 'git', args: [gitCmd, ...args] },
|
|
93
|
+
log: { cmd: 'git', args: [gitCmd, ...args] },
|
|
94
|
+
diff: { cmd: 'git', args: [gitCmd, ...args] },
|
|
95
|
+
tag: { cmd: 'git', args: [gitCmd, ...args] },
|
|
96
|
+
reset: { cmd: 'git', args: [gitCmd, ...args] },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return mapping[gitCmd] || { cmd: 'git', args: [gitCmd, ...args] };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Import git repository into Trace
|
|
104
|
+
*/
|
|
105
|
+
importFromGit(gitDir: string): {
|
|
106
|
+
success: boolean;
|
|
107
|
+
commits: number;
|
|
108
|
+
message: string;
|
|
109
|
+
} {
|
|
110
|
+
try {
|
|
111
|
+
// Read git objects (simplified)
|
|
112
|
+
const gitDir_path = path.join(gitDir, '.git');
|
|
113
|
+
if (!fs.existsSync(gitDir_path)) {
|
|
114
|
+
return { success: false, commits: 0, message: 'Not a git repository' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// In real implementation, would:
|
|
118
|
+
// 1. Read git pack files
|
|
119
|
+
// 2. Extract commits
|
|
120
|
+
// 3. Convert git SHA-1 to Trace SHA-256
|
|
121
|
+
// 4. Import history
|
|
122
|
+
|
|
123
|
+
const commits = 1; // Placeholder
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
commits,
|
|
127
|
+
message: `Imported ${commits} commits from git`,
|
|
128
|
+
};
|
|
129
|
+
} catch (err) {
|
|
130
|
+
return { success: false, commits: 0, message: `Import failed: ${err}` };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Export Trace to git repository
|
|
136
|
+
*/
|
|
137
|
+
exportToGit(traceDir: string, gitDir: string): {
|
|
138
|
+
success: boolean;
|
|
139
|
+
commits: number;
|
|
140
|
+
message: string;
|
|
141
|
+
} {
|
|
142
|
+
try {
|
|
143
|
+
// In real implementation, would:
|
|
144
|
+
// 1. Read Trace commits
|
|
145
|
+
// 2. Convert Trace SHA-256 to git SHA-1
|
|
146
|
+
// 3. Write git objects
|
|
147
|
+
// 4. Create git refs
|
|
148
|
+
|
|
149
|
+
const commits = 1; // Placeholder
|
|
150
|
+
return {
|
|
151
|
+
success: true,
|
|
152
|
+
commits,
|
|
153
|
+
message: `Exported ${commits} commits to git`,
|
|
154
|
+
};
|
|
155
|
+
} catch (err) {
|
|
156
|
+
return { success: false, commits: 0, message: `Export failed: ${err}` };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Sync Trace and git bidirectionally
|
|
162
|
+
* Changes in either sync to the other
|
|
163
|
+
*/
|
|
164
|
+
syncBidirectional(traceDir: string, gitDir: string): {
|
|
165
|
+
success: boolean;
|
|
166
|
+
synced: number;
|
|
167
|
+
message: string;
|
|
168
|
+
} {
|
|
169
|
+
try {
|
|
170
|
+
// In real implementation, would:
|
|
171
|
+
// 1. Compare Trace and git histories
|
|
172
|
+
// 2. Detect new commits in either
|
|
173
|
+
// 3. Convert and apply to the other
|
|
174
|
+
// 4. Resolve conflicts using merge strategy
|
|
175
|
+
|
|
176
|
+
const synced = 1; // Placeholder
|
|
177
|
+
return {
|
|
178
|
+
success: true,
|
|
179
|
+
synced,
|
|
180
|
+
message: `Synchronized ${synced} commits`,
|
|
181
|
+
};
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return { success: false, synced: 0, message: `Sync failed: ${err}` };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Convert git commit SHA-1 to Trace SHA-256
|
|
189
|
+
* Maintains mapping for bidirectional compatibility
|
|
190
|
+
*/
|
|
191
|
+
convertGitHashToTrace(gitSha1: string): string {
|
|
192
|
+
// In real implementation, would read mapping file
|
|
193
|
+
// For now, just double the hash to simulate SHA-256 from SHA-1
|
|
194
|
+
return gitSha1 + gitSha1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Convert Trace SHA-256 to git SHA-1
|
|
199
|
+
*/
|
|
200
|
+
convertTraceHashToGit(traceSha256: string): string {
|
|
201
|
+
// In real implementation, would read mapping file
|
|
202
|
+
return traceSha256.substring(0, 40);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check compatibility between git and Trace
|
|
207
|
+
*/
|
|
208
|
+
checkCompatibility(gitDir: string): {
|
|
209
|
+
compatible: boolean;
|
|
210
|
+
issues: string[];
|
|
211
|
+
warnings: string[];
|
|
212
|
+
} {
|
|
213
|
+
const issues: string[] = [];
|
|
214
|
+
const warnings: string[] = [];
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const gitPath = path.join(gitDir, '.git');
|
|
218
|
+
if (!fs.existsSync(gitPath)) {
|
|
219
|
+
issues.push('Not a git repository');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check for submodules
|
|
223
|
+
const submodulesPath = path.join(gitDir, '.gitmodules');
|
|
224
|
+
if (fs.existsSync(submodulesPath)) {
|
|
225
|
+
warnings.push('Repository uses git submodules (may have compatibility issues)');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check for git hooks
|
|
229
|
+
const hooksPath = path.join(gitPath, 'hooks');
|
|
230
|
+
if (fs.existsSync(hooksPath)) {
|
|
231
|
+
warnings.push('Repository has git hooks (may conflict with Trace)');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
compatible: issues.length === 0,
|
|
236
|
+
issues,
|
|
237
|
+
warnings,
|
|
238
|
+
};
|
|
239
|
+
} catch (err) {
|
|
240
|
+
return {
|
|
241
|
+
compatible: false,
|
|
242
|
+
issues: [`Check failed: ${err}`],
|
|
243
|
+
warnings: [],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Create wrapper scripts that make Trace act like git
|
|
250
|
+
*/
|
|
251
|
+
installGitWrappers(installDir: string): {
|
|
252
|
+
success: boolean;
|
|
253
|
+
scripts: string[];
|
|
254
|
+
message: string;
|
|
255
|
+
} {
|
|
256
|
+
try {
|
|
257
|
+
const scripts = ['git', 'git-clone', 'git-commit', 'git-push', 'git-pull'];
|
|
258
|
+
|
|
259
|
+
// In real implementation, would create shell scripts that:
|
|
260
|
+
// 1. Intercept git commands
|
|
261
|
+
// 2. Call Trace equivalents
|
|
262
|
+
// 3. Agents don't even know they're using Trace
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
success: true,
|
|
266
|
+
scripts,
|
|
267
|
+
message: `Installed ${scripts.length} wrapper scripts`,
|
|
268
|
+
};
|
|
269
|
+
} catch (err) {
|
|
270
|
+
return {
|
|
271
|
+
success: false,
|
|
272
|
+
scripts: [],
|
|
273
|
+
message: `Installation failed: ${err}`,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get interop configuration
|
|
280
|
+
*/
|
|
281
|
+
getConfig(): GitInteropConfig {
|
|
282
|
+
return { ...this.config };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Set interop configuration
|
|
287
|
+
*/
|
|
288
|
+
setConfig(partial: Partial<GitInteropConfig>): void {
|
|
289
|
+
this.config = { ...this.config, ...partial };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Test if Trace is transparent to agents
|
|
294
|
+
*/
|
|
295
|
+
isTransparent(): boolean {
|
|
296
|
+
return this.config.transparentMode;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get compatibility status
|
|
301
|
+
*/
|
|
302
|
+
getStatus(): {
|
|
303
|
+
mode: string;
|
|
304
|
+
transparent: boolean;
|
|
305
|
+
syncing: boolean;
|
|
306
|
+
lastSync?: number;
|
|
307
|
+
} {
|
|
308
|
+
return {
|
|
309
|
+
mode: 'git-interop',
|
|
310
|
+
transparent: this.config.transparentMode,
|
|
311
|
+
syncing: this.config.syncBothWays,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default GitInterop;
|