@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
|
@@ -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
|
+
}
|
package/scripts/init.ts
ADDED
|
@@ -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;
|