@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,382 @@
1
+ /**
2
+ * Advanced Workspace Isolation Tests
3
+ * Complex security scenarios and edge cases
4
+ */
5
+
6
+ import { WorkspaceIsolation } from '../../src/workspace-isolation';
7
+
8
+ describe('WorkspaceIsolation - Advanced Scenarios', () => {
9
+ let isolation: WorkspaceIsolation;
10
+
11
+ beforeEach(() => {
12
+ isolation = new WorkspaceIsolation('/secure/project', true);
13
+ });
14
+
15
+ describe('Nested Path Security', () => {
16
+ test('blocks nested .env files', () => {
17
+ const paths = ['.env', 'config/.env', 'src/.env', 'deploy/.env.production'];
18
+ paths.forEach(p => {
19
+ const result = isolation.isSafeToCommit(p);
20
+ expect(result.safe).toBe(false);
21
+ });
22
+ });
23
+
24
+ test('blocks nested key files', () => {
25
+ const paths = ['keys/agent-a.key', 'config/private.pem', 'certs/cert.pem'];
26
+ paths.forEach(p => {
27
+ const result = isolation.isSafeToCommit(p);
28
+ expect(result.safe).toBe(false);
29
+ });
30
+ });
31
+
32
+ test('allows nested safe files', () => {
33
+ const paths = ['src/utils/helpers.ts', 'tests/unit/main.test.ts', 'docs/api/endpoints.md'];
34
+ paths.forEach(p => {
35
+ const result = isolation.isSafeToCommit(p);
36
+ expect(result.safe).toBe(true);
37
+ });
38
+ });
39
+
40
+ test('blocks deeply nested secrets', () => {
41
+ const result = isolation.isSafeToCommit('a/b/c/d/e/.env.local');
42
+ expect(result.safe).toBe(false);
43
+ });
44
+ });
45
+
46
+ describe('Filename Variations', () => {
47
+ test('blocks all .env variants', () => {
48
+ const variants = [
49
+ '.env',
50
+ '.env.local',
51
+ '.env.production',
52
+ '.env.staging',
53
+ '.env.development',
54
+ '.env.test',
55
+ '.env.custom',
56
+ ];
57
+
58
+ variants.forEach(v => {
59
+ const result = isolation.isSafeToCommit(v);
60
+ expect(result.safe).toBe(false);
61
+ });
62
+ });
63
+
64
+ test('blocks all key variations', () => {
65
+ const keys = [
66
+ 'private.key',
67
+ 'id_rsa.key',
68
+ 'agent-a.key',
69
+ 'agent-b.key',
70
+ 'signing.pem',
71
+ 'certificate.pem',
72
+ ];
73
+
74
+ keys.forEach(k => {
75
+ const result = isolation.isSafeToCommit(k);
76
+ expect(result.safe).toBe(false);
77
+ });
78
+ });
79
+
80
+ test('blocks credential variations', () => {
81
+ const creds = [
82
+ 'secrets.json',
83
+ 'credentials.json',
84
+ 'oauth.json',
85
+ 'api-keys.json',
86
+ 'tokens.json',
87
+ ];
88
+
89
+ creds.forEach(c => {
90
+ const result = isolation.isSafeToCommit(c);
91
+ expect(result.safe).toBe(false);
92
+ });
93
+ });
94
+
95
+ test('warns on sensitive name variations', () => {
96
+ const sensitive = [
97
+ 'password.txt',
98
+ 'passwords.json',
99
+ 'user_passwords.csv',
100
+ 'secret_config.js',
101
+ 'private_data.sql',
102
+ ];
103
+
104
+ sensitive.forEach(s => {
105
+ const result = isolation.isSafeToCommit(s);
106
+ expect(result.violations.length).toBeGreaterThan(0);
107
+ });
108
+ });
109
+ });
110
+
111
+ describe('Case Sensitivity', () => {
112
+ test('blocks uppercase variants', () => {
113
+ const result = isolation.isSafeToCommit('.ENV');
114
+ // Should warn or block depending on case sensitivity rules
115
+ expect(result.violations.length).toBeGreaterThanOrEqual(0);
116
+ });
117
+
118
+ test('blocks mixed case credentials', () => {
119
+ const result = isolation.isSafeToCommit('Secrets.JSON');
120
+ expect(result.violations.length).toBeGreaterThanOrEqual(0);
121
+ });
122
+ });
123
+
124
+ describe('Archive & Backup Files', () => {
125
+ test('blocks backup files', () => {
126
+ const backups = ['app.backup', 'db.bak', 'config.old', 'secrets.tar.gz'];
127
+ backups.forEach(b => {
128
+ const result = isolation.isSafeToCommit(b);
129
+ expect(result.violations.length).toBeGreaterThan(0);
130
+ });
131
+ });
132
+
133
+ test('blocks dump files', () => {
134
+ const dumps = ['database.dump', 'db.sql', 'schema.sql', 'data.dump'];
135
+ dumps.forEach(d => {
136
+ const result = isolation.isSafeToCommit(d);
137
+ expect(result.violations.length).toBeGreaterThan(0);
138
+ });
139
+ });
140
+ });
141
+
142
+ describe('Workspace Directories', () => {
143
+ test('blocks workspace private dirs', () => {
144
+ const dirs = ['.workspace/', 'workspace-private/', 'private/', '.local/'];
145
+ dirs.forEach(d => {
146
+ const result = isolation.isSafeToCommit(d + 'anyfile.txt');
147
+ expect(result.safe).toBe(false);
148
+ });
149
+ });
150
+ });
151
+
152
+ describe('Batch Validation', () => {
153
+ test('validates large batch of files', () => {
154
+ const files = Array.from({ length: 100 }, (_, i) => `src/file${i}.ts`);
155
+ const result = isolation.validateGitPush(files);
156
+ expect(result.canPush).toBe(true);
157
+ expect(result.violations).toHaveLength(0);
158
+ });
159
+
160
+ test('finds all violations in large batch', () => {
161
+ const safe = Array.from({ length: 50 }, (_, i) => `src/file${i}.ts`);
162
+ const unsafe = Array.from({ length: 50 }, (_, i) => `.env.${i}`);
163
+ const all = [...safe, ...unsafe];
164
+
165
+ const result = isolation.validateGitPush(all);
166
+ expect(result.canPush).toBe(false);
167
+ expect(result.violations.length).toBe(50);
168
+ });
169
+ });
170
+
171
+ describe('Custom Rules', () => {
172
+ test('custom allow list overrides', () => {
173
+ isolation.setAllowed(['src/', 'docs/', 'custom-allowed/']);
174
+ const result = isolation.isSafeToCommit('custom-allowed/file.ts');
175
+ expect(result.safe).toBe(true);
176
+ });
177
+
178
+ test('custom block list additions', () => {
179
+ isolation.blockPath('*.sensitive');
180
+ const result = isolation.isSafeToCommit('data.sensitive');
181
+ expect(result.safe).toBe(false);
182
+ });
183
+
184
+ test('multiple custom blocks', () => {
185
+ isolation.blockPath('*.proprietary');
186
+ isolation.blockPath('*_private*');
187
+ isolation.blockPath('classified/*');
188
+
189
+ expect(isolation.isSafeToCommit('source.proprietary')).toEqual(
190
+ expect.objectContaining({ safe: false })
191
+ );
192
+ expect(isolation.isSafeToCommit('my_private_data')).toEqual(
193
+ expect.objectContaining({ safe: false })
194
+ );
195
+ });
196
+ });
197
+
198
+ describe('Error Messages', () => {
199
+ test('provides clear violation messages', () => {
200
+ const result = isolation.isSafeToCommit('.env');
201
+ expect(result.violations.length).toBeGreaterThan(0);
202
+ expect(result.violations[0].reason).toBeDefined();
203
+ expect(result.violations[0].suggestion).toBeDefined();
204
+ });
205
+
206
+ test('differentiates violation severity', () => {
207
+ const env = isolation.isSafeToCommit('.env');
208
+ const warning = isolation.isSafeToCommit('src/password.ts');
209
+
210
+ const envSevere = env.violations.some(v => v.severity === 'critical');
211
+ const warningSevere = warning.violations.some(v => v.severity === 'warning');
212
+
213
+ expect(envSevere).toBe(true);
214
+ expect(warningSevere).toBe(true);
215
+ });
216
+ });
217
+
218
+ describe('Real Production Scenarios', () => {
219
+ test('prevents AWS credentials exposure', () => {
220
+ const awsFiles = ['.aws/credentials', '.aws/config', 'aws-secret.json'];
221
+ awsFiles.forEach(f => {
222
+ const result = isolation.isSafeToCommit(f);
223
+ expect(result.safe).toBe(false);
224
+ });
225
+ });
226
+
227
+ test('prevents GCP/Google Cloud exposure', () => {
228
+ const gcpFiles = [
229
+ 'gcp-key.json',
230
+ 'google-cloud-key.json',
231
+ 'service-account.json',
232
+ ];
233
+ gcpFiles.forEach(f => {
234
+ const result = isolation.isSafeToCommit(f);
235
+ expect(result.safe).toBe(false);
236
+ });
237
+ });
238
+
239
+ test('prevents Docker secrets exposure', () => {
240
+ const dockerFiles = ['.docker/config.json', 'docker-compose.override.yml'];
241
+ dockerFiles.forEach(f => {
242
+ const result = isolation.isSafeToCommit(f);
243
+ expect(result.violations.length).toBeGreaterThan(0);
244
+ });
245
+ });
246
+
247
+ test('prevents API key exposure', () => {
248
+ const apiFiles = ['api-keys.json', 'stripe-secret.key', 'github-token.txt'];
249
+ apiFiles.forEach(f => {
250
+ const result = isolation.isSafeToCommit(f);
251
+ expect(result.safe).toBe(false);
252
+ });
253
+ });
254
+
255
+ test('prevents database password exposure', () => {
256
+ const dbFiles = ['db.password', 'database-url.txt', 'postgres-credentials.env'];
257
+ dbFiles.forEach(f => {
258
+ const result = isolation.isSafeToCommit(f);
259
+ expect(result.violations.length).toBeGreaterThan(0);
260
+ });
261
+ });
262
+
263
+ test('allows safe production files', () => {
264
+ const safeFiles = [
265
+ 'src/database.ts',
266
+ 'src/api/client.ts',
267
+ 'tests/integration.test.ts',
268
+ 'docs/DEPLOYMENT.md',
269
+ 'docker-compose.yml',
270
+ 'Dockerfile',
271
+ 'package.json',
272
+ 'tsconfig.json',
273
+ ];
274
+
275
+ safeFiles.forEach(f => {
276
+ const result = isolation.isSafeToCommit(f);
277
+ expect(result.safe).toBe(true);
278
+ });
279
+ });
280
+ });
281
+
282
+ describe('Performance', () => {
283
+ test('validates 1000 files quickly', () => {
284
+ const files = Array.from({ length: 1000 }, (_, i) => `src/file${i}.ts`);
285
+ const start = Date.now();
286
+ isolation.validateGitPush(files);
287
+ const elapsed = Date.now() - start;
288
+ expect(elapsed).toBeLessThan(500);
289
+ });
290
+
291
+ test('handles mixed safe/unsafe efficiently', () => {
292
+ const mixed = Array.from({ length: 500 }, (_, i) =>
293
+ i % 2 === 0 ? `src/file${i}.ts` : `.env.${i}`
294
+ );
295
+ const start = Date.now();
296
+ isolation.validateGitPush(mixed);
297
+ const elapsed = Date.now() - start;
298
+ expect(elapsed).toBeLessThan(500);
299
+ });
300
+ });
301
+
302
+ describe('Workspace Boundary Enforcement', () => {
303
+ test('rejects files outside workspace root', () => {
304
+ const result = isolation.isSafeToCommit('../../sensitive.key');
305
+ expect(result.safe).toBe(false);
306
+ expect(result.violations.some(v => v.reason.includes('outside'))).toBe(true);
307
+ });
308
+
309
+ test('blocks absolute paths outside workspace', () => {
310
+ const result = isolation.isSafeToCommit('/etc/passwd');
311
+ expect(result.safe).toBe(false);
312
+ });
313
+ });
314
+
315
+ describe('Report Generation', () => {
316
+ test('generates comprehensive security report', () => {
317
+ isolation.isSafeToCommit('.env');
318
+ isolation.isSafeToCommit('secrets.json');
319
+
320
+ const report = isolation.generateReport();
321
+ expect(report).toContain('WORKSPACE SECURITY REPORT');
322
+ expect(report).toContain('ALLOWED PATHS');
323
+ expect(report).toContain('BLOCKED PATHS');
324
+ });
325
+
326
+ test('report includes file counts', () => {
327
+ const report = isolation.generateReport();
328
+ expect(report).toMatch(/\(\d+\)/); // Should have counts like (15)
329
+ });
330
+
331
+ test('report lists all patterns', () => {
332
+ const report = isolation.generateReport();
333
+ expect(report).toContain('.env');
334
+ expect(report).toContain('*.key');
335
+ });
336
+ });
337
+
338
+ describe('Pre-Commit Hook Integration', () => {
339
+ test('generates executable hook script', () => {
340
+ const hook = isolation.getPreCommitHook();
341
+ expect(hook).toMatch(/^#!/bin\/bash/);
342
+ expect(hook).toContain('git diff --cached --name-only');
343
+ expect(hook).toContain('exit 1');
344
+ });
345
+
346
+ test('hook detects .env files', () => {
347
+ const hook = isolation.getPreCommitHook();
348
+ expect(hook).toMatch(/\.env/);
349
+ });
350
+
351
+ test('hook detects key files', () => {
352
+ const hook = isolation.getPreCommitHook();
353
+ expect(hook).toMatch(/\.key.*\|\|.*\.pem/);
354
+ });
355
+
356
+ test('hook script is proper bash', () => {
357
+ const hook = isolation.getPreCommitHook();
358
+ expect(hook).toContain('if [');
359
+ expect(hook).toContain('then');
360
+ expect(hook).toContain('fi');
361
+ });
362
+ });
363
+
364
+ describe('Violation Details', () => {
365
+ test('violations include file path', () => {
366
+ const result = isolation.isSafeToCommit('config/.env');
367
+ expect(result.violations[0].path).toContain('.env');
368
+ });
369
+
370
+ test('violations include reason', () => {
371
+ const result = isolation.isSafeToCommit('.env.production');
372
+ expect(result.violations[0].reason).toBeDefined();
373
+ expect(result.violations[0].reason.length).toBeGreaterThan(0);
374
+ });
375
+
376
+ test('violations include suggestion', () => {
377
+ const result = isolation.isSafeToCommit('secrets.json');
378
+ expect(result.violations[0].suggestion).toBeDefined();
379
+ expect(result.violations[0].suggestion.includes('.gitignore')).toBe(true);
380
+ });
381
+ });
382
+ });
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Workspace Isolation Tests
3
+ * Security boundary enforcement
4
+ */
5
+
6
+ import { WorkspaceIsolation } from '../../src/workspace-isolation';
7
+
8
+ describe('WorkspaceIsolation', () => {
9
+ let isolation: WorkspaceIsolation;
10
+
11
+ beforeEach(() => {
12
+ isolation = new WorkspaceIsolation('/project', true);
13
+ });
14
+
15
+ describe('Safe Files', () => {
16
+ test('allows src files', () => {
17
+ const result = isolation.isSafeToCommit('src/index.ts');
18
+ expect(result.safe).toBe(true);
19
+ });
20
+
21
+ test('allows test files', () => {
22
+ const result = isolation.isSafeToCommit('tests/main.test.ts');
23
+ expect(result.safe).toBe(true);
24
+ });
25
+
26
+ test('allows docs', () => {
27
+ const result = isolation.isSafeToCommit('docs/README.md');
28
+ expect(result.safe).toBe(true);
29
+ });
30
+
31
+ test('allows package.json', () => {
32
+ const result = isolation.isSafeToCommit('package.json');
33
+ expect(result.safe).toBe(true);
34
+ });
35
+ });
36
+
37
+ describe('Blocked Files', () => {
38
+ test('blocks .env files', () => {
39
+ const result = isolation.isSafeToCommit('.env');
40
+ expect(result.safe).toBe(false);
41
+ expect(result.violations).toHaveLength(1);
42
+ });
43
+
44
+ test('blocks .env.local', () => {
45
+ const result = isolation.isSafeToCommit('.env.local');
46
+ expect(result.safe).toBe(false);
47
+ });
48
+
49
+ test('blocks .key files', () => {
50
+ const result = isolation.isSafeToCommit('private.key');
51
+ expect(result.safe).toBe(false);
52
+ });
53
+
54
+ test('blocks .pem files', () => {
55
+ const result = isolation.isSafeToCommit('certificate.pem');
56
+ expect(result.safe).toBe(false);
57
+ });
58
+
59
+ test('blocks secrets.json', () => {
60
+ const result = isolation.isSafeToCommit('secrets.json');
61
+ expect(result.safe).toBe(false);
62
+ });
63
+
64
+ test('blocks credentials.json', () => {
65
+ const result = isolation.isSafeToCommit('credentials.json');
66
+ expect(result.safe).toBe(false);
67
+ });
68
+
69
+ test('blocks agent keys', () => {
70
+ const result = isolation.isSafeToCommit('agent-a.key');
71
+ expect(result.safe).toBe(false);
72
+ });
73
+
74
+ test('blocks password files', () => {
75
+ const result = isolation.isSafeToCommit('passwords.txt');
76
+ expect(result.safe).toBe(false);
77
+ });
78
+ });
79
+
80
+ describe('Sensitive Names', () => {
81
+ test('warns on password files', () => {
82
+ const result = isolation.isSafeToCommit('src/password.ts');
83
+ expect(result.violations.length).toBeGreaterThan(0);
84
+ expect(result.violations[0].severity).toBe('warning');
85
+ });
86
+
87
+ test('warns on secret files', () => {
88
+ const result = isolation.isSafeToCommit('src/secret.ts');
89
+ expect(result.violations.length).toBeGreaterThan(0);
90
+ });
91
+
92
+ test('warns on token files', () => {
93
+ const result = isolation.isSafeToCommit('src/token.ts');
94
+ expect(result.violations.length).toBeGreaterThan(0);
95
+ });
96
+
97
+ test('warns on credential files', () => {
98
+ const result = isolation.isSafeToCommit('src/credentials.ts');
99
+ expect(result.violations.length).toBeGreaterThan(0);
100
+ });
101
+
102
+ test('warns on backup files', () => {
103
+ const result = isolation.isSafeToCommit('src/backup.tar.gz');
104
+ expect(result.violations.length).toBeGreaterThan(0);
105
+ });
106
+ });
107
+
108
+ describe('Git Push Validation', () => {
109
+ test('allows safe push', () => {
110
+ const files = ['src/index.ts', 'tests/main.test.ts', 'README.md'];
111
+ const result = isolation.validateGitPush(files);
112
+ expect(result.canPush).toBe(true);
113
+ });
114
+
115
+ test('blocks push with secrets', () => {
116
+ const files = ['src/index.ts', '.env', 'secrets.json'];
117
+ const result = isolation.validateGitPush(files);
118
+ expect(result.canPush).toBe(false);
119
+ expect(result.violations.length).toBeGreaterThan(0);
120
+ });
121
+
122
+ test('blocks push with keys', () => {
123
+ const files = ['src/index.ts', 'agent-a.key'];
124
+ const result = isolation.validateGitPush(files);
125
+ expect(result.canPush).toBe(false);
126
+ });
127
+
128
+ test('reports multiple violations', () => {
129
+ const files = ['.env', 'secrets.json', 'agent-a.key'];
130
+ const result = isolation.validateGitPush(files);
131
+ expect(result.violations.length).toBeGreaterThanOrEqual(3);
132
+ });
133
+ });
134
+
135
+ describe('Directory Scanning', () => {
136
+ test('scans directory', () => {
137
+ const result = isolation.scanDirectory('/project');
138
+ expect(result.safeFiles).toBeDefined();
139
+ expect(result.violatingFiles).toBeDefined();
140
+ expect(result.violations).toBeDefined();
141
+ });
142
+
143
+ test('identifies safe files in scan', () => {
144
+ const result = isolation.scanDirectory('/project');
145
+ expect(result.safeFiles.length).toBeGreaterThan(0);
146
+ });
147
+
148
+ test('identifies violating files in scan', () => {
149
+ const result = isolation.scanDirectory('/project');
150
+ expect(result.violatingFiles.length).toBeGreaterThan(0);
151
+ });
152
+ });
153
+
154
+ describe('Custom Configuration', () => {
155
+ test('sets custom allowed paths', () => {
156
+ isolation.setAllowed(['src/', 'docs/', 'custom/']);
157
+ const result = isolation.getBoundary();
158
+ expect(result.allowed).toContain('custom/');
159
+ });
160
+
161
+ test('adds blocked path', () => {
162
+ isolation.blockPath('*.secret');
163
+ const result = isolation.getBoundary();
164
+ expect(result.blocked).toContain('*.secret');
165
+ });
166
+
167
+ test('does not duplicate blocked paths', () => {
168
+ isolation.blockPath('*.secret');
169
+ isolation.blockPath('*.secret');
170
+ const result = isolation.getBoundary();
171
+ const count = result.blocked.filter(p => p === '*.secret').length;
172
+ expect(count).toBe(1);
173
+ });
174
+ });
175
+
176
+ describe('Workspace Boundary', () => {
177
+ test('gets boundary info', () => {
178
+ const boundary = isolation.getBoundary();
179
+ expect(boundary.root).toBe('/project');
180
+ expect(boundary.isPublic).toBe(true);
181
+ expect(boundary.allowed).toBeDefined();
182
+ expect(boundary.blocked).toBeDefined();
183
+ });
184
+
185
+ test('marks as private workspace', () => {
186
+ const private_isolation = new WorkspaceIsolation('/private', false);
187
+ const boundary = private_isolation.getBoundary();
188
+ expect(boundary.isPublic).toBe(false);
189
+ });
190
+ });
191
+
192
+ describe('Security Reports', () => {
193
+ test('generates security report', () => {
194
+ const report = isolation.generateReport();
195
+ expect(report).toContain('WORKSPACE SECURITY REPORT');
196
+ expect(report).toContain('ALLOWED PATHS');
197
+ expect(report).toContain('BLOCKED PATHS');
198
+ });
199
+
200
+ test('includes violations in report', () => {
201
+ isolation.isSafeToCommit('.env');
202
+ const report = isolation.generateReport();
203
+ // Note: violations are tracked separately in test
204
+ });
205
+ });
206
+
207
+ describe('Pre-Commit Hook', () => {
208
+ test('generates pre-commit hook script', () => {
209
+ const hook = isolation.getPreCommitHook();
210
+ expect(hook).toContain('#!/bin/bash');
211
+ expect(hook).toContain('Trace Pre-Commit Security Hook');
212
+ expect(hook).toContain('.env');
213
+ expect(hook).toContain('.key');
214
+ });
215
+ });
216
+
217
+ describe('Real-World Scenarios', () => {
218
+ test('prevents .env exposure', () => {
219
+ const files = ['src/main.ts', '.env', '.env.local', '.env.production'];
220
+ const result = isolation.validateGitPush(files);
221
+ expect(result.canPush).toBe(false);
222
+ expect(result.violations.filter(v => v.path.includes('.env')).length).toBe(3);
223
+ });
224
+
225
+ test('prevents credential leak', () => {
226
+ const files = [
227
+ 'src/api.ts',
228
+ 'credentials.json',
229
+ 'oauth.json',
230
+ 'api-keys.json',
231
+ ];
232
+ const result = isolation.validateGitPush(files);
233
+ expect(result.canPush).toBe(false);
234
+ expect(result.violations.length).toBe(3);
235
+ });
236
+
237
+ test('prevents agent key exposure', () => {
238
+ const files = ['src/index.ts', 'agent-a.key', 'agent-b.pem', 'agent-keys/'];
239
+ const result = isolation.validateGitPush(files);
240
+ expect(result.canPush).toBe(false);
241
+ });
242
+
243
+ test('allows safe production push', () => {
244
+ const files = [
245
+ 'src/main.ts',
246
+ 'src/utils.ts',
247
+ 'tests/main.test.ts',
248
+ 'README.md',
249
+ 'package.json',
250
+ 'LICENSE',
251
+ ];
252
+ const result = isolation.validateGitPush(files);
253
+ expect(result.canPush).toBe(true);
254
+ });
255
+ });
256
+
257
+ describe('Path Matching', () => {
258
+ test('matches glob patterns', () => {
259
+ const result = isolation.isSafeToCommit('.env.staging');
260
+ expect(result.safe).toBe(false);
261
+ });
262
+
263
+ test('handles nested paths', () => {
264
+ const result = isolation.isSafeToCommit('config/secrets.json');
265
+ expect(result.safe).toBe(false);
266
+ });
267
+ });
268
+ });