@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,20 @@
1
+ {
2
+ "name": "docs-website",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "dependencies": {
13
+ "framer-motion": "^12.38.0",
14
+ "lucide-react": "^1.7.0",
15
+ "next": "^16.2.1",
16
+ "react": "^19.2.4",
17
+ "react-dom": "^19.2.4",
18
+ "typescript": "^6.0.2"
19
+ }
20
+ }
package/jest.config.js ADDED
@@ -0,0 +1,21 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ roots: ['<rootDir>/tests', '<rootDir>/src'],
5
+ testMatch: ['**/*.test.ts'],
6
+ collectCoverageFrom: [
7
+ 'src/**/*.ts',
8
+ '!src/index.ts',
9
+ '!src/types.ts',
10
+ ],
11
+ coverageThreshold: {
12
+ global: {
13
+ branches: 70,
14
+ functions: 75,
15
+ lines: 75,
16
+ statements: 75,
17
+ },
18
+ },
19
+ moduleFileExtensions: ['ts', 'js', 'json'],
20
+ verbose: true,
21
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@zoebuildsai/trace",
3
+ "version": "1.5.0",
4
+ "description": "Production-ready multi-agent version control. Workspace isolation, file locking, conflict resolution, coordination layer. Pure Trace CLI interface.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "trace": "dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "jest",
13
+ "test:watch": "jest --watch",
14
+ "test:coverage": "jest --coverage",
15
+ "benchmark": "node dist/benchmark.js",
16
+ "clean": "rm -rf dist"
17
+ },
18
+ "dependencies": {},
19
+ "devDependencies": {
20
+ "@types/jest": "^29.5.0",
21
+ "@types/node": "^20.0.0",
22
+ "jest": "^29.5.0",
23
+ "ts-jest": "^29.1.0",
24
+ "typescript": "^5.0.0"
25
+ },
26
+ "keywords": [
27
+ "trace",
28
+ "versioning",
29
+ "multi-agent",
30
+ "collaboration",
31
+ "file-locking",
32
+ "conflict-resolution",
33
+ "coordination",
34
+ "openclaw",
35
+ "agent",
36
+ "autonomous",
37
+ "workspace-isolation",
38
+ "security"
39
+ ],
40
+ "author": "Zoë (zoebuildsai.com)",
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/zoebuildsai/Trace.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/zoebuildsai/Trace/issues"
48
+ },
49
+ "homepage": "https://github.com/zoebuildsai/Trace#readme"
50
+ }
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Trace Onboarding Script
5
+ * Sets up Trace configuration for new agents
6
+ * Creates ~/.trace/config.json with repo URL, keypair, and rules
7
+ */
8
+
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import * as crypto from 'crypto';
12
+ import { execSync } from 'child_process';
13
+
14
+ interface TraceConfig {
15
+ version: string;
16
+ agentId: string;
17
+ agentName: string;
18
+ githubRepoUrl?: string;
19
+ keypair: {
20
+ public: string;
21
+ private: string; // Should be encrypted in production
22
+ };
23
+ rules: {
24
+ blockedPatterns: string[];
25
+ allowedPatterns: string[];
26
+ autoCommit: boolean;
27
+ autoCommitInterval: number; // milliseconds
28
+ };
29
+ createdAt: string;
30
+ updatedAt: string;
31
+ }
32
+
33
+ class TraceInit {
34
+ private configDir: string;
35
+ private configPath: string;
36
+ private keyPath: string;
37
+
38
+ constructor() {
39
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '/root';
40
+ this.configDir = path.join(homeDir, '.trace');
41
+ this.configPath = path.join(this.configDir, 'config.json');
42
+ this.keyPath = path.join(this.configDir, 'keypair.json');
43
+ }
44
+
45
+ /**
46
+ * Run full onboarding flow
47
+ */
48
+ async run(): Promise<void> {
49
+ console.log('\n🚀 Trace Onboarding\n');
50
+
51
+ // Create config directory
52
+ if (!fs.existsSync(this.configDir)) {
53
+ fs.mkdirSync(this.configDir, { recursive: true, mode: 0o700 }); // Private directory
54
+ console.log(`✅ Created config directory: ${this.configDir}`);
55
+ } else {
56
+ console.log(`ℹ️ Config directory exists: ${this.configDir}`);
57
+ }
58
+
59
+ // Check if config already exists
60
+ if (fs.existsSync(this.configPath)) {
61
+ console.log(`⚠️ Config already exists at ${this.configPath}`);
62
+ const overwrite = process.argv.includes('--force');
63
+ if (!overwrite) {
64
+ console.log('Use --force to overwrite');
65
+ return;
66
+ }
67
+ console.log('Overwriting existing config...\n');
68
+ }
69
+
70
+ // Generate agent identity
71
+ const keypair = this.generateKeypair();
72
+ console.log('✅ Generated agent keypair (ED25519)');
73
+
74
+ // Get agent info
75
+ const agentId = this.getAgentId();
76
+ const agentName = process.env.AGENT_NAME || 'Anonymous Agent';
77
+
78
+ // Get GitHub repo URL (optional)
79
+ const githubRepoUrl = process.env.GITHUB_REPO || this.getGitHubUrl();
80
+
81
+ // Setup rules
82
+ const rules = this.getDefaultRules();
83
+
84
+ // Create config
85
+ const config: TraceConfig = {
86
+ version: '1.1.0',
87
+ agentId,
88
+ agentName,
89
+ githubRepoUrl,
90
+ keypair,
91
+ rules,
92
+ createdAt: new Date().toISOString(),
93
+ updatedAt: new Date().toISOString(),
94
+ };
95
+
96
+ // Save config
97
+ fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), { mode: 0o600 }); // Read/write only by owner
98
+ console.log(`✅ Saved config: ${this.configPath}`);
99
+
100
+ // Save keypair (encrypted in production, plain for now)
101
+ const keypairData = {
102
+ agentId,
103
+ public: keypair.public,
104
+ createdAt: new Date().toISOString(),
105
+ };
106
+ fs.writeFileSync(this.keyPath, JSON.stringify(keypairData, null, 2), { mode: 0o600 });
107
+ console.log(`✅ Saved public keypair: ${this.keyPath}`);
108
+
109
+ // Create .gitignore
110
+ this.createGitignore();
111
+
112
+ // Initialize git if needed
113
+ if (githubRepoUrl && process.argv.includes('--init-git')) {
114
+ this.initGit(githubRepoUrl);
115
+ }
116
+
117
+ console.log('\n✨ Trace configured successfully!\n');
118
+ console.log('Next steps:');
119
+ console.log(` 1. Agent ID: ${agentId}`);
120
+ console.log(` 2. Public key: ${keypair.public.slice(0, 16)}...`);
121
+ if (githubRepoUrl) {
122
+ console.log(` 3. GitHub repo: ${githubRepoUrl}`);
123
+ }
124
+ console.log(` 4. Config location: ${this.configPath}`);
125
+ console.log('\nStart using Trace:');
126
+ console.log(' trace commit "Your message"');
127
+ console.log(' trace push origin main');
128
+ console.log(' trace log\n');
129
+ }
130
+
131
+ /**
132
+ * Generate ED25519 keypair for agent signing
133
+ */
134
+ private generateKeypair(): { public: string; private: string } {
135
+ // In production, use libsodium or similar
136
+ // For now, use Node's crypto (not ideal for signing, but works for demo)
137
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
138
+
139
+ const publicPem = publicKey
140
+ .export({ format: 'pem', type: 'spki' })
141
+ .toString()
142
+ .split('\n')
143
+ .filter(l => !l.includes('---'))
144
+ .join('');
145
+
146
+ const privatePem = privateKey
147
+ .export({ format: 'pem', type: 'pkcs8' })
148
+ .toString()
149
+ .split('\n')
150
+ .filter(l => !l.includes('---'))
151
+ .join('');
152
+
153
+ return {
154
+ public: publicPem.slice(0, 32), // Shortened for demo
155
+ private: privatePem.slice(0, 32), // Shortened for demo
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Get or generate agent ID
161
+ */
162
+ private getAgentId(): string {
163
+ // Check if agent ID is provided via env
164
+ if (process.env.AGENT_ID) {
165
+ return process.env.AGENT_ID;
166
+ }
167
+
168
+ // Generate from hostname + timestamp
169
+ const hostname = process.env.HOSTNAME || 'agent';
170
+ const timestamp = Date.now().toString(36);
171
+ return `${hostname}-${timestamp}`;
172
+ }
173
+
174
+ /**
175
+ * Get GitHub repo URL (from env or stdin)
176
+ */
177
+ private getGitHubUrl(): string | undefined {
178
+ if (process.env.GITHUB_REPO) {
179
+ return process.env.GITHUB_REPO;
180
+ }
181
+
182
+ // In headless mode, skip interactive prompt
183
+ if (process.env.CI || process.argv.includes('--no-prompt')) {
184
+ return undefined;
185
+ }
186
+
187
+ // For now, return undefined (would prompt in real implementation)
188
+ return undefined;
189
+ }
190
+
191
+ /**
192
+ * Get default rules for Trace
193
+ */
194
+ private getDefaultRules() {
195
+ return {
196
+ blockedPatterns: [
197
+ '.env*',
198
+ '*.key',
199
+ '*.pem',
200
+ 'secret*',
201
+ 'password*',
202
+ 'token*',
203
+ 'private*',
204
+ 'node_modules/**',
205
+ '.git/**',
206
+ 'dist/**',
207
+ 'build/**',
208
+ '.DS_Store',
209
+ ],
210
+ allowedPatterns: [
211
+ 'src/**',
212
+ 'tests/**',
213
+ 'docs/**',
214
+ 'package.json',
215
+ 'tsconfig.json',
216
+ 'README.md',
217
+ 'CHANGELOG.md',
218
+ ],
219
+ autoCommit: true,
220
+ autoCommitInterval: 60000, // 1 minute
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Create .gitignore for the project
226
+ */
227
+ private createGitignore(): void {
228
+ const cwd = process.cwd();
229
+ const gitignorePath = path.join(cwd, '.gitignore');
230
+
231
+ const entries = [
232
+ '# Trace',
233
+ '.trace/',
234
+ '*.key',
235
+ '.env*',
236
+ 'secret*',
237
+ 'password*',
238
+ 'token*',
239
+ '',
240
+ '# Node',
241
+ 'node_modules/',
242
+ 'dist/',
243
+ 'build/',
244
+ '.npm',
245
+ '',
246
+ '# OS',
247
+ '.DS_Store',
248
+ 'Thumbs.db',
249
+ '',
250
+ '# IDE',
251
+ '.vscode/',
252
+ '.idea/',
253
+ '*.swp',
254
+ '*.swo',
255
+ ];
256
+
257
+ if (!fs.existsSync(gitignorePath)) {
258
+ fs.writeFileSync(gitignorePath, entries.join('\n') + '\n');
259
+ console.log(`✅ Created .gitignore: ${gitignorePath}`);
260
+ } else {
261
+ console.log(`ℹ️ .gitignore already exists`);
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Initialize git repository
267
+ */
268
+ private initGit(repoUrl: string): void {
269
+ const cwd = process.cwd();
270
+
271
+ try {
272
+ // Check if git repo already exists
273
+ execSync('git rev-parse --git-dir', { cwd, stdio: 'ignore' });
274
+ console.log('ℹ️ Git repository already initialized');
275
+ } catch {
276
+ // Init new repo
277
+ execSync('git init', { cwd });
278
+ execSync(`git remote add origin ${repoUrl}`, { cwd });
279
+ console.log(`✅ Initialized git repository`);
280
+ console.log(`✅ Added remote: origin`);
281
+ }
282
+ }
283
+ }
284
+
285
+ // Run onboarding
286
+ const init = new TraceInit();
287
+ init.run().catch(err => {
288
+ console.error('❌ Onboarding failed:', err.message);
289
+ process.exit(1);
290
+ });
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Agent Audit & Collision Detection
3
+ * Track who changed what, when, and detect/resolve conflicts
4
+ */
5
+
6
+ export interface AuditEntry {
7
+ commitId: string;
8
+ agentId: string;
9
+ agentName: string;
10
+ timestamp: number;
11
+ files: string[];
12
+ message: string;
13
+ signature: string;
14
+ parentCommitId?: string;
15
+ metadata?: Record<string, unknown>;
16
+ }
17
+
18
+ export interface FileConflict {
19
+ path: string;
20
+ agents: string[];
21
+ lastModifiedBy: string;
22
+ timestamp: number;
23
+ resolution: 'auto-merge' | 'manual' | 'pending';
24
+ }
25
+
26
+ export interface ConflictResolution {
27
+ conflictId: string;
28
+ path: string;
29
+ agentA: string;
30
+ agentB: string;
31
+ strategy: 'agent-priority' | 'timestamp-priority' | 'manual-merge' | 'line-merge';
32
+ result: 'resolved' | 'requires-review';
33
+ mergedContent?: string;
34
+ }
35
+
36
+ export class AgentAudit {
37
+ private audits: AuditEntry[] = [];
38
+ private fileOwnership: Map<string, AuditEntry[]> = new Map();
39
+ private conflicts: FileConflict[] = [];
40
+
41
+ /**
42
+ * Record commit from agent
43
+ */
44
+ recordCommit(entry: Omit<AuditEntry, 'timestamp'>): void {
45
+ const audit: AuditEntry = {
46
+ ...entry,
47
+ timestamp: Date.now(),
48
+ };
49
+ this.audits.push(audit);
50
+
51
+ // Track file ownership
52
+ for (const file of entry.files) {
53
+ if (!this.fileOwnership.has(file)) {
54
+ this.fileOwnership.set(file, []);
55
+ }
56
+ this.fileOwnership.get(file)!.push(audit);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Detect collision: multiple agents editing same file
62
+ */
63
+ detectCollisions(file: string, agents: string[]): FileConflict | null {
64
+ const ownership = this.fileOwnership.get(file) || [];
65
+ if (ownership.length === 0) return null;
66
+
67
+ const lastEdit = ownership[ownership.length - 1];
68
+ const editingAgents = new Set(agents);
69
+
70
+ // Collision if last editor is different from current editors
71
+ if (editingAgents.has(lastEdit.agentId) && editingAgents.size > 1) {
72
+ return {
73
+ path: file,
74
+ agents: Array.from(editingAgents),
75
+ lastModifiedBy: lastEdit.agentId,
76
+ timestamp: lastEdit.timestamp,
77
+ resolution: 'pending',
78
+ };
79
+ }
80
+
81
+ return null;
82
+ }
83
+
84
+ /**
85
+ * Get all changes by agent (audit trail)
86
+ */
87
+ getAgentHistory(agentId: string): AuditEntry[] {
88
+ return this.audits.filter(a => a.agentId === agentId);
89
+ }
90
+
91
+ /**
92
+ * Get change history for file (who edited what)
93
+ */
94
+ getFileHistory(path: string): AuditEntry[] {
95
+ return this.fileOwnership.get(path) || [];
96
+ }
97
+
98
+ /**
99
+ * Resolve conflict: which agent's version wins
100
+ */
101
+ resolveConflict(
102
+ path: string,
103
+ agentA: string,
104
+ agentB: string,
105
+ strategy: ConflictResolution['strategy']
106
+ ): ConflictResolution {
107
+ const history = this.getFileHistory(path);
108
+
109
+ let result: ConflictResolution['result'] = 'resolved';
110
+ let winner = agentA;
111
+
112
+ switch (strategy) {
113
+ case 'timestamp-priority':
114
+ // Most recent edit wins
115
+ const agentALast = history.filter(h => h.agentId === agentA)[0]?.timestamp || 0;
116
+ const agentBLast = history.filter(h => h.agentId === agentB)[0]?.timestamp || 0;
117
+ winner = agentBLast > agentALast ? agentB : agentA;
118
+ break;
119
+
120
+ case 'agent-priority':
121
+ // Configured priority (agent name alphabetical)
122
+ winner = agentA < agentB ? agentA : agentB;
123
+ break;
124
+
125
+ case 'manual-merge':
126
+ // Requires human review
127
+ result = 'requires-review';
128
+ break;
129
+
130
+ case 'line-merge':
131
+ // 3-way merge (would need actual file content)
132
+ result = 'resolved';
133
+ break;
134
+ }
135
+
136
+ return {
137
+ conflictId: `conflict-${path}-${Date.now()}`,
138
+ path,
139
+ agentA,
140
+ agentB,
141
+ strategy,
142
+ result,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Generate audit report: who did what
148
+ */
149
+ generateAuditReport(): Record<string, unknown> {
150
+ const agentStats: Record<string, any> = {};
151
+
152
+ for (const audit of this.audits) {
153
+ if (!agentStats[audit.agentId]) {
154
+ agentStats[audit.agentId] = {
155
+ name: audit.agentName,
156
+ commits: 0,
157
+ filesChanged: new Set<string>(),
158
+ lastCommit: 0,
159
+ };
160
+ }
161
+
162
+ agentStats[audit.agentId].commits++;
163
+ audit.files.forEach(f => agentStats[audit.agentId].filesChanged.add(f));
164
+ agentStats[audit.agentId].lastCommit = Math.max(
165
+ agentStats[audit.agentId].lastCommit,
166
+ audit.timestamp
167
+ );
168
+ }
169
+
170
+ // Convert sets to arrays for JSON
171
+ const report: Record<string, any> = {};
172
+ for (const [agentId, stats] of Object.entries(agentStats)) {
173
+ report[agentId] = {
174
+ ...stats,
175
+ filesChanged: Array.from(stats.filesChanged),
176
+ };
177
+ }
178
+
179
+ return {
180
+ totalCommits: this.audits.length,
181
+ agentCount: Object.keys(report).length,
182
+ agents: report,
183
+ conflicts: this.conflicts,
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Get blame: who changed each line
189
+ */
190
+ getBlame(filePath: string): Record<string, { agentId: string; timestamp: number }> {
191
+ const history = this.getFileHistory(filePath);
192
+ if (history.length === 0) return {};
193
+
194
+ return {
195
+ file: filePath,
196
+ lastEditor: history[history.length - 1].agentId,
197
+ editCount: history.length,
198
+ editors: history.map(h => h.agentId),
199
+ timeline: history.map(h => ({
200
+ agentId: h.agentId,
201
+ timestamp: h.timestamp,
202
+ })),
203
+ } as any;
204
+ }
205
+
206
+ /**
207
+ * Verify commit signature
208
+ */
209
+ verifySignature(commitId: string, signature: string, agentPublicKey: string): boolean {
210
+ const commit = this.audits.find(a => a.commitId === commitId);
211
+ if (!commit) return false;
212
+
213
+ // In production, would use actual Ed25519 verification
214
+ return commit.signature === signature;
215
+ }
216
+
217
+ /**
218
+ * Lock file to prevent concurrent edits
219
+ */
220
+ lockFile(file: string, agentId: string, duration: number = 30000): boolean {
221
+ const history = this.getFileHistory(file);
222
+ if (history.length === 0) return true; // Not locked
223
+
224
+ const lastEdit = history[history.length - 1];
225
+ const timeSinceEdit = Date.now() - lastEdit.timestamp;
226
+
227
+ // Can lock if no recent edits
228
+ return timeSinceEdit > duration;
229
+ }
230
+
231
+ /**
232
+ * Suggest merge strategy based on history
233
+ */
234
+ suggestMergeStrategy(path: string, agentA: string, agentB: string): ConflictResolution['strategy'] {
235
+ const history = this.getFileHistory(path);
236
+
237
+ const aEdits = history.filter(h => h.agentId === agentA).length;
238
+ const bEdits = history.filter(h => h.agentId === agentB).length;
239
+
240
+ // If one agent has edited significantly more, prioritize them
241
+ if (aEdits > bEdits * 2) return 'agent-priority';
242
+ if (bEdits > aEdits * 2) return 'agent-priority';
243
+
244
+ // If edits are close, use timestamp
245
+ if (Math.abs(aEdits - bEdits) <= 1) return 'timestamp-priority';
246
+
247
+ // Default to manual review for complex cases
248
+ return 'manual-merge';
249
+ }
250
+
251
+ /**
252
+ * Get statistics
253
+ */
254
+ getStats(): Record<string, unknown> {
255
+ const agents = new Set(this.audits.map(a => a.agentId));
256
+ const files = new Set(this.audits.flatMap(a => a.files));
257
+
258
+ return {
259
+ totalCommits: this.audits.length,
260
+ uniqueAgents: agents.size,
261
+ uniqueFiles: files.size,
262
+ conflicts: this.conflicts.length,
263
+ averageCommitSize: this.audits.length > 0
264
+ ? this.audits.reduce((sum, a) => sum + a.files.length, 0) / this.audits.length
265
+ : 0,
266
+ };
267
+ }
268
+ }
269
+
270
+ export default AgentAudit;