baseguard 1.0.3 → 1.0.4

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 (167) hide show
  1. package/.baseguardrc.example.json +63 -63
  2. package/.eslintrc.json +24 -24
  3. package/.prettierrc +7 -7
  4. package/CHANGELOG.md +195 -195
  5. package/DEPLOYMENT.md +624 -624
  6. package/DEPLOYMENT_CHECKLIST.md +239 -239
  7. package/DEPLOYMENT_SUMMARY_v1.0.2.md +202 -202
  8. package/QUICK_START.md +134 -134
  9. package/README.md +488 -488
  10. package/RELEASE_NOTES_v1.0.2.md +434 -434
  11. package/bin/base.js +627 -627
  12. package/dist/ai/fix-manager.d.ts.map +1 -1
  13. package/dist/ai/fix-manager.js +1 -1
  14. package/dist/ai/fix-manager.js.map +1 -1
  15. package/dist/ai/gemini-analyzer.d.ts.map +1 -1
  16. package/dist/ai/gemini-analyzer.js +29 -35
  17. package/dist/ai/gemini-analyzer.js.map +1 -1
  18. package/dist/ai/gemini-code-fixer.d.ts.map +1 -1
  19. package/dist/ai/gemini-code-fixer.js +58 -58
  20. package/dist/ai/gemini-code-fixer.js.map +1 -1
  21. package/dist/ai/jules-implementer.d.ts +3 -0
  22. package/dist/ai/jules-implementer.d.ts.map +1 -1
  23. package/dist/ai/jules-implementer.js +63 -32
  24. package/dist/ai/jules-implementer.js.map +1 -1
  25. package/dist/ai/unified-code-fixer.js.map +1 -1
  26. package/dist/commands/check.d.ts.map +1 -1
  27. package/dist/commands/check.js +1 -1
  28. package/dist/commands/check.js.map +1 -1
  29. package/dist/commands/config.js +2 -1
  30. package/dist/commands/config.js.map +1 -1
  31. package/dist/commands/fix.d.ts.map +1 -1
  32. package/dist/commands/fix.js +44 -15
  33. package/dist/commands/fix.js.map +1 -1
  34. package/dist/core/api-key-manager.js +2 -2
  35. package/dist/core/api-key-manager.js.map +1 -1
  36. package/dist/core/baseguard.d.ts +1 -0
  37. package/dist/core/baseguard.d.ts.map +1 -1
  38. package/dist/core/baseguard.js +13 -10
  39. package/dist/core/baseguard.js.map +1 -1
  40. package/dist/core/baseline-checker.d.ts.map +1 -1
  41. package/dist/core/baseline-checker.js +2 -1
  42. package/dist/core/baseline-checker.js.map +1 -1
  43. package/dist/core/configuration-recovery.d.ts.map +1 -1
  44. package/dist/core/configuration-recovery.js +1 -1
  45. package/dist/core/configuration-recovery.js.map +1 -1
  46. package/dist/core/debug-logger.d.ts.map +1 -1
  47. package/dist/core/debug-logger.js +1 -1
  48. package/dist/core/debug-logger.js.map +1 -1
  49. package/dist/core/error-handler.d.ts.map +1 -1
  50. package/dist/core/error-handler.js +2 -1
  51. package/dist/core/error-handler.js.map +1 -1
  52. package/dist/core/gitignore-manager.js +5 -5
  53. package/dist/core/graceful-degradation-manager.d.ts.map +1 -1
  54. package/dist/core/graceful-degradation-manager.js +16 -16
  55. package/dist/core/graceful-degradation-manager.js.map +1 -1
  56. package/dist/core/lazy-loader.d.ts.map +1 -1
  57. package/dist/core/lazy-loader.js +9 -2
  58. package/dist/core/lazy-loader.js.map +1 -1
  59. package/dist/core/memory-manager.d.ts +0 -3
  60. package/dist/core/memory-manager.d.ts.map +1 -1
  61. package/dist/core/memory-manager.js.map +1 -1
  62. package/dist/core/parser-worker.d.ts +2 -0
  63. package/dist/core/parser-worker.d.ts.map +1 -0
  64. package/dist/core/parser-worker.js +19 -0
  65. package/dist/core/parser-worker.js.map +1 -0
  66. package/dist/core/startup-optimizer.d.ts.map +1 -1
  67. package/dist/core/startup-optimizer.js +4 -8
  68. package/dist/core/startup-optimizer.js.map +1 -1
  69. package/dist/core/system-error-handler.d.ts.map +1 -1
  70. package/dist/core/system-error-handler.js.map +1 -1
  71. package/dist/git/automation-engine.d.ts.map +1 -1
  72. package/dist/git/automation-engine.js +5 -4
  73. package/dist/git/automation-engine.js.map +1 -1
  74. package/dist/git/github-manager.d.ts.map +1 -1
  75. package/dist/git/github-manager.js.map +1 -1
  76. package/dist/git/hook-manager.js +5 -5
  77. package/dist/git/hook-manager.js.map +1 -1
  78. package/dist/parsers/parser-manager.d.ts.map +1 -1
  79. package/dist/parsers/parser-manager.js +1 -1
  80. package/dist/parsers/parser-manager.js.map +1 -1
  81. package/dist/parsers/svelte-parser.js +1 -1
  82. package/dist/parsers/svelte-parser.js.map +1 -1
  83. package/dist/parsers/vanilla-parser.d.ts.map +1 -1
  84. package/dist/parsers/vanilla-parser.js.map +1 -1
  85. package/dist/parsers/vue-parser.d.ts.map +1 -1
  86. package/dist/parsers/vue-parser.js.map +1 -1
  87. package/dist/ui/components.d.ts +1 -1
  88. package/dist/ui/components.d.ts.map +1 -1
  89. package/dist/ui/components.js +11 -11
  90. package/dist/ui/components.js.map +1 -1
  91. package/dist/ui/terminal-header.js +14 -14
  92. package/package.json +105 -105
  93. package/src/ai/__tests__/gemini-analyzer.test.ts +180 -180
  94. package/src/ai/agentkit-orchestrator.ts +533 -533
  95. package/src/ai/fix-manager.ts +362 -362
  96. package/src/ai/gemini-analyzer.ts +665 -671
  97. package/src/ai/gemini-code-fixer.ts +539 -540
  98. package/src/ai/index.ts +3 -3
  99. package/src/ai/jules-implementer.ts +504 -460
  100. package/src/ai/unified-code-fixer.ts +347 -347
  101. package/src/commands/automation.ts +343 -343
  102. package/src/commands/check.ts +298 -299
  103. package/src/commands/config.ts +584 -583
  104. package/src/commands/fix.ts +264 -238
  105. package/src/commands/index.ts +6 -6
  106. package/src/commands/init.ts +155 -155
  107. package/src/commands/status.ts +306 -306
  108. package/src/core/api-key-manager.ts +298 -298
  109. package/src/core/baseguard.ts +757 -756
  110. package/src/core/baseline-checker.ts +564 -563
  111. package/src/core/cache-manager.ts +271 -271
  112. package/src/core/configuration-recovery.ts +672 -673
  113. package/src/core/configuration.ts +595 -595
  114. package/src/core/debug-logger.ts +590 -590
  115. package/src/core/directory-filter.ts +420 -420
  116. package/src/core/error-handler.ts +518 -517
  117. package/src/core/file-processor.ts +337 -337
  118. package/src/core/gitignore-manager.ts +168 -168
  119. package/src/core/graceful-degradation-manager.ts +596 -596
  120. package/src/core/index.ts +16 -16
  121. package/src/core/lazy-loader.ts +317 -307
  122. package/src/core/memory-manager.ts +290 -295
  123. package/src/core/parser-worker.ts +33 -0
  124. package/src/core/startup-optimizer.ts +246 -255
  125. package/src/core/system-error-handler.ts +755 -756
  126. package/src/git/automation-engine.ts +361 -361
  127. package/src/git/github-manager.ts +190 -192
  128. package/src/git/hook-manager.ts +210 -210
  129. package/src/git/index.ts +3 -3
  130. package/src/index.ts +7 -7
  131. package/src/parsers/feature-validator.ts +558 -558
  132. package/src/parsers/index.ts +7 -7
  133. package/src/parsers/parser-manager.ts +418 -419
  134. package/src/parsers/parser.ts +25 -25
  135. package/src/parsers/react-parser-optimized.ts +160 -160
  136. package/src/parsers/react-parser.ts +358 -358
  137. package/src/parsers/svelte-parser.ts +510 -510
  138. package/src/parsers/vanilla-parser.ts +685 -686
  139. package/src/parsers/vue-parser.ts +476 -478
  140. package/src/types/index.ts +95 -95
  141. package/src/ui/components.ts +567 -567
  142. package/src/ui/help.ts +192 -192
  143. package/src/ui/index.ts +3 -3
  144. package/src/ui/prompts.ts +680 -680
  145. package/src/ui/terminal-header.ts +58 -58
  146. package/test-build.js +40 -40
  147. package/test-config-commands.js +55 -55
  148. package/test-header-simple.js +32 -32
  149. package/test-terminal-header.js +11 -11
  150. package/test-ui.js +28 -28
  151. package/tests/e2e/baseguard.e2e.test.ts +515 -515
  152. package/tests/e2e/cross-platform.e2e.test.ts +419 -419
  153. package/tests/e2e/git-integration.e2e.test.ts +486 -486
  154. package/tests/fixtures/react-project/package.json +13 -13
  155. package/tests/fixtures/react-project/src/App.css +75 -75
  156. package/tests/fixtures/react-project/src/App.tsx +76 -76
  157. package/tests/fixtures/svelte-project/package.json +10 -10
  158. package/tests/fixtures/svelte-project/src/App.svelte +368 -368
  159. package/tests/fixtures/vanilla-project/index.html +75 -75
  160. package/tests/fixtures/vanilla-project/script.js +330 -330
  161. package/tests/fixtures/vanilla-project/styles.css +358 -358
  162. package/tests/fixtures/vue-project/package.json +11 -11
  163. package/tests/fixtures/vue-project/src/App.vue +215 -215
  164. package/tsconfig.json +34 -34
  165. package/vitest.config.ts +11 -11
  166. package/dist/terminal-header.d.ts +0 -12
  167. package/dist/terminal-header.js +0 -45
@@ -1,487 +1,487 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { execSync, spawn } from 'child_process';
3
- import { promises as fs } from 'fs';
4
- import path from 'path';
5
- import { fileURLToPath } from 'url';
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
-
10
- const TEST_TIMEOUT = 45000; // 45 seconds for git operations
11
- const TEMP_DIR = path.join(__dirname, '../temp-git');
12
-
13
- // Helper function to run git commands
14
- function runGit(args: string[], cwd: string): string {
15
- try {
16
- return execSync(`git ${args.join(' ')}`, {
17
- cwd,
18
- encoding: 'utf8',
19
- stdio: 'pipe'
20
- });
21
- } catch (error: any) {
22
- throw new Error(`Git command failed: ${error.message}`);
23
- }
24
- }
25
-
26
- // Helper function to run BaseGuard commands
27
- async function runBaseGuard(args: string[], cwd: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {
28
- return new Promise((resolve) => {
29
- const basePath = path.join(__dirname, '../../bin/base.js');
30
- const child = spawn('node', [basePath, ...args], {
31
- cwd,
32
- stdio: 'pipe',
33
- env: { ...process.env, NODE_ENV: 'test' }
34
- });
35
-
36
- let stdout = '';
37
- let stderr = '';
38
-
39
- child.stdout?.on('data', (data) => {
40
- stdout += data.toString();
41
- });
42
-
43
- child.stderr?.on('data', (data) => {
44
- stderr += data.toString();
45
- });
46
-
47
- child.on('close', (code) => {
48
- resolve({
49
- stdout,
50
- stderr,
51
- exitCode: code || 0
52
- });
53
- });
54
-
55
- setTimeout(() => {
56
- child.kill();
57
- resolve({ stdout, stderr, exitCode: 1 });
58
- }, TEST_TIMEOUT);
59
- });
60
- }
61
-
62
- // Helper function to setup git repository
63
- async function setupGitRepo(repoPath: string): Promise<void> {
64
- await fs.mkdir(repoPath, { recursive: true });
65
-
66
- // Initialize git repo
67
- runGit(['init'], repoPath);
68
- runGit(['config', 'user.name', 'Test User'], repoPath);
69
- runGit(['config', 'user.email', 'test@example.com'], repoPath);
70
-
71
- // Create initial commit
72
- await fs.writeFile(path.join(repoPath, 'README.md'), '# Test Project\n');
73
- runGit(['add', 'README.md'], repoPath);
74
- runGit(['commit', '-m', '"Initial commit"'], repoPath);
75
- }
76
-
77
- // Helper function to create test files with violations
78
- async function createTestFiles(repoPath: string): Promise<void> {
79
- // Create a file with modern features that will trigger violations
80
- const jsContent = `
81
- // Modern JavaScript features
82
- const data = { test: 'value' };
83
- const cloned = structuredClone(data);
84
- const observer = new ResizeObserver(() => {});
85
-
86
- // Dialog API usage
87
- const dialog = document.querySelector('dialog');
88
- if (dialog) {
89
- dialog.showModal();
90
- }
91
- `;
92
-
93
- const cssContent = `
94
- .container {
95
- container-type: inline-size;
96
- aspect-ratio: 16/9;
97
- backdrop-filter: blur(10px);
98
- }
99
-
100
- @container (min-width: 400px) {
101
- .container {
102
- font-size: 1.2rem;
103
- }
104
- }
105
-
106
- .modern-selector:has(dialog[open]) {
107
- background: color-mix(in srgb, red 50%, blue);
108
- }
109
- `;
110
-
111
- await fs.writeFile(path.join(repoPath, 'test.js'), jsContent);
112
- await fs.writeFile(path.join(repoPath, 'test.css'), cssContent);
113
- }
114
-
115
- describe('Git Integration End-to-End Tests', () => {
116
- beforeEach(async () => {
117
- try {
118
- await fs.rm(TEMP_DIR, { recursive: true, force: true });
119
- } catch (error) {
120
- // Directory might not exist
121
- }
122
- await fs.mkdir(TEMP_DIR, { recursive: true });
123
- });
124
-
125
- afterEach(async () => {
126
- try {
127
- await fs.rm(TEMP_DIR, { recursive: true, force: true });
128
- } catch (error) {
129
- // Ignore cleanup errors
130
- }
131
- });
132
-
133
- describe('Git Hook Installation', () => {
134
- it('should install pre-commit hooks successfully', async () => {
135
- const repoPath = path.join(TEMP_DIR, 'hook-install-test');
136
- await setupGitRepo(repoPath);
137
-
138
- // Initialize BaseGuard with automation
139
- const initResult = await runBaseGuard(['init'], repoPath);
140
- expect(initResult.exitCode).toBe(0);
141
-
142
- // Install git hooks
143
- const hookResult = await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
144
- expect(hookResult.exitCode).toBe(0);
145
-
146
- // Check if hook files were created
147
- const hookPath = path.join(repoPath, '.husky/pre-commit');
148
- const hookExists = await fs.access(hookPath).then(() => true).catch(() => false);
149
- expect(hookExists).toBe(true);
150
-
151
- if (hookExists) {
152
- const hookContent = await fs.readFile(hookPath, 'utf8');
153
- expect(hookContent).toContain('base automation run');
154
- }
155
- }, TEST_TIMEOUT);
156
-
157
- it('should install pre-push hooks successfully', async () => {
158
- const repoPath = path.join(TEMP_DIR, 'pre-push-test');
159
- await setupGitRepo(repoPath);
160
-
161
- await runBaseGuard(['init'], repoPath);
162
-
163
- const hookResult = await runBaseGuard(['automation', 'enable', '--trigger', 'pre-push'], repoPath);
164
- expect(hookResult.exitCode).toBe(0);
165
-
166
- const hookPath = path.join(repoPath, '.husky/pre-push');
167
- const hookExists = await fs.access(hookPath).then(() => true).catch(() => false);
168
- expect(hookExists).toBe(true);
169
- }, TEST_TIMEOUT);
170
- });
171
-
172
- describe('Pre-commit Hook Behavior', () => {
173
- it('should block commits when violations are found', async () => {
174
- const repoPath = path.join(TEMP_DIR, 'block-commit-test');
175
- await setupGitRepo(repoPath);
176
-
177
- // Initialize BaseGuard and enable automation
178
- await runBaseGuard(['init'], repoPath);
179
-
180
- // Create configuration that will find violations
181
- const config = {
182
- version: '1.0.0',
183
- targets: [{ browser: 'safari', minVersion: '15' }],
184
- automation: {
185
- enabled: true,
186
- trigger: 'pre-commit',
187
- autoAnalyze: false,
188
- autoFix: false,
189
- blockCommit: true
190
- }
191
- };
192
- await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
193
-
194
- await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
195
-
196
- // Create files with violations
197
- await createTestFiles(repoPath);
198
- runGit(['add', '.'], repoPath);
199
-
200
- // Try to commit - should fail
201
- try {
202
- runGit(['commit', '-m', 'Add files with violations'], repoPath);
203
- expect.fail('Commit should have been blocked');
204
- } catch (error: any) {
205
- expect(error.message).toContain('Git command failed');
206
- }
207
- }, TEST_TIMEOUT);
208
-
209
- it('should allow commits when no violations are found', async () => {
210
- const repoPath = path.join(TEMP_DIR, 'allow-commit-test');
211
- await setupGitRepo(repoPath);
212
-
213
- await runBaseGuard(['init'], repoPath);
214
-
215
- // Create configuration targeting very old browsers (everything should pass)
216
- const config = {
217
- version: '1.0.0',
218
- targets: [{ browser: 'chrome', minVersion: '120' }], // Very new browser
219
- automation: {
220
- enabled: true,
221
- trigger: 'pre-commit',
222
- blockCommit: true
223
- }
224
- };
225
- await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
226
-
227
- await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
228
-
229
- // Create files without violations (for very new browsers)
230
- await fs.writeFile(path.join(repoPath, 'safe.js'), 'console.log("Hello World");');
231
- await fs.writeFile(path.join(repoPath, 'safe.css'), '.safe { color: red; }');
232
-
233
- runGit(['add', '.'], repoPath);
234
-
235
- // Commit should succeed
236
- const commitOutput = runGit(['commit', '-m', 'Add safe files'], repoPath);
237
- expect(commitOutput).toContain('Add safe files');
238
- }, TEST_TIMEOUT);
239
-
240
- it('should respect --no-verify flag', async () => {
241
- const repoPath = path.join(TEMP_DIR, 'no-verify-test');
242
- await setupGitRepo(repoPath);
243
-
244
- await runBaseGuard(['init'], repoPath);
245
-
246
- const config = {
247
- version: '1.0.0',
248
- targets: [{ browser: 'safari', minVersion: '15' }],
249
- automation: {
250
- enabled: true,
251
- trigger: 'pre-commit',
252
- blockCommit: true
253
- }
254
- };
255
- await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
256
-
257
- await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
258
-
259
- // Create files with violations
260
- await createTestFiles(repoPath);
261
- runGit(['add', '.'], repoPath);
262
-
263
- // Commit with --no-verify should succeed
264
- const commitOutput = runGit(['commit', '--no-verify', '-m', 'Bypass BaseGuard'], repoPath);
265
- expect(commitOutput).toContain('Bypass BaseGuard');
266
- }, TEST_TIMEOUT);
267
- });
268
-
269
- describe('Auto-fix Mode', () => {
270
- it('should automatically fix violations and include in commit', async () => {
271
- const repoPath = path.join(TEMP_DIR, 'auto-fix-test');
272
- await setupGitRepo(repoPath);
273
-
274
- await runBaseGuard(['init'], repoPath);
275
-
276
- // Enable auto-fix mode (would require API keys in real scenario)
277
- const config = {
278
- version: '1.0.0',
279
- targets: [{ browser: 'safari', minVersion: '15' }],
280
- apiKeys: {
281
- jules: 'test-key',
282
- gemini: 'test-key'
283
- },
284
- automation: {
285
- enabled: true,
286
- trigger: 'pre-commit',
287
- autoAnalyze: true,
288
- autoFix: true,
289
- blockCommit: false
290
- }
291
- };
292
- await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
293
-
294
- await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
295
-
296
- // Create files with violations
297
- await createTestFiles(repoPath);
298
- runGit(['add', '.'], repoPath);
299
-
300
- // Note: This test would require mock API responses in a real scenario
301
- // For now, we test that the automation runs without crashing
302
- try {
303
- runGit(['commit', '-m', 'Test auto-fix'], repoPath);
304
- } catch (error: any) {
305
- // Expected to fail due to missing API keys, but should not crash
306
- expect(error.message).toContain('API') || expect(error.message).toContain('key');
307
- }
308
- }, TEST_TIMEOUT);
309
- });
310
-
311
- describe('Pre-push Hook Behavior', () => {
312
- it('should check violations before push', async () => {
313
- const repoPath = path.join(TEMP_DIR, 'pre-push-test');
314
- await setupGitRepo(repoPath);
315
-
316
- await runBaseGuard(['init'], repoPath);
317
-
318
- const config = {
319
- version: '1.0.0',
320
- targets: [{ browser: 'safari', minVersion: '15' }],
321
- automation: {
322
- enabled: true,
323
- trigger: 'pre-push',
324
- blockCommit: true
325
- }
326
- };
327
- await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
328
-
329
- await runBaseGuard(['automation', 'enable', '--trigger', 'pre-push'], repoPath);
330
-
331
- // Create and commit files with violations (bypassing pre-commit)
332
- await createTestFiles(repoPath);
333
- runGit(['add', '.'], repoPath);
334
- runGit(['commit', '--no-verify', '-m', 'Add files with violations'], repoPath);
335
-
336
- // Create a remote repository simulation
337
- const remoteRepo = path.join(TEMP_DIR, 'remote-repo');
338
- await fs.mkdir(remoteRepo, { recursive: true });
339
- runGit(['init', '--bare'], remoteRepo);
340
- runGit(['remote', 'add', 'origin', remoteRepo], repoPath);
341
-
342
- // Try to push - should fail due to violations
343
- try {
344
- runGit(['push', 'origin', 'main'], repoPath);
345
- expect.fail('Push should have been blocked');
346
- } catch (error: any) {
347
- expect(error.message).toContain('Git command failed');
348
- }
349
- }, TEST_TIMEOUT);
350
- });
351
-
352
- describe('Automation Configuration', () => {
353
- it('should enable and disable automation correctly', async () => {
354
- const repoPath = path.join(TEMP_DIR, 'automation-config-test');
355
- await setupGitRepo(repoPath);
356
-
357
- await runBaseGuard(['init'], repoPath);
358
-
359
- // Enable automation
360
- const enableResult = await runBaseGuard(['automation', 'enable'], repoPath);
361
- expect(enableResult.exitCode).toBe(0);
362
-
363
- // Check configuration
364
- const configPath = path.join(repoPath, '.baseguardrc.json');
365
- const config = JSON.parse(await fs.readFile(configPath, 'utf8'));
366
- expect(config.automation.enabled).toBe(true);
367
-
368
- // Disable automation
369
- const disableResult = await runBaseGuard(['automation', 'disable'], repoPath);
370
- expect(disableResult.exitCode).toBe(0);
371
-
372
- // Check configuration updated
373
- const updatedConfig = JSON.parse(await fs.readFile(configPath, 'utf8'));
374
- expect(updatedConfig.automation.enabled).toBe(false);
375
- }, TEST_TIMEOUT);
376
-
377
- it('should handle different trigger configurations', async () => {
378
- const repoPath = path.join(TEMP_DIR, 'trigger-config-test');
379
- await setupGitRepo(repoPath);
380
-
381
- await runBaseGuard(['init'], repoPath);
382
-
383
- // Test pre-commit trigger
384
- await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
385
- let config = JSON.parse(await fs.readFile(path.join(repoPath, '.baseguardrc.json'), 'utf8'));
386
- expect(config.automation.trigger).toBe('pre-commit');
387
-
388
- // Test pre-push trigger
389
- await runBaseGuard(['automation', 'enable', '--trigger', 'pre-push'], repoPath);
390
- config = JSON.parse(await fs.readFile(path.join(repoPath, '.baseguardrc.json'), 'utf8'));
391
- expect(config.automation.trigger).toBe('pre-push');
392
- }, TEST_TIMEOUT);
393
- });
394
-
395
- describe('Incremental Scanning', () => {
396
- it('should only scan changed files in git workflow', async () => {
397
- const repoPath = path.join(TEMP_DIR, 'incremental-test');
398
- await setupGitRepo(repoPath);
399
-
400
- await runBaseGuard(['init'], repoPath);
401
-
402
- // Create multiple files
403
- await fs.writeFile(path.join(repoPath, 'file1.js'), 'console.log("file1");');
404
- await fs.writeFile(path.join(repoPath, 'file2.js'), 'console.log("file2");');
405
- await createTestFiles(repoPath); // Creates files with violations
406
-
407
- runGit(['add', 'file1.js', 'file2.js'], repoPath);
408
- runGit(['commit', '-m', 'Add safe files'], repoPath);
409
-
410
- // Now stage only the files with violations
411
- runGit(['add', 'test.js', 'test.css'], repoPath);
412
-
413
- const config = {
414
- version: '1.0.0',
415
- targets: [{ browser: 'safari', minVersion: '15' }],
416
- automation: {
417
- enabled: true,
418
- trigger: 'pre-commit',
419
- blockCommit: true
420
- }
421
- };
422
- await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
423
-
424
- await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
425
-
426
- // The automation should only check staged files
427
- try {
428
- runGit(['commit', '-m', 'Add files with violations'], repoPath);
429
- expect.fail('Commit should have been blocked');
430
- } catch (error: any) {
431
- expect(error.message).toContain('Git command failed');
432
- }
433
- }, TEST_TIMEOUT);
434
- });
435
-
436
- describe('Error Recovery', () => {
437
- it('should handle corrupted git hooks gracefully', async () => {
438
- const repoPath = path.join(TEMP_DIR, 'corrupted-hook-test');
439
- await setupGitRepo(repoPath);
440
-
441
- await runBaseGuard(['init'], repoPath);
442
- await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
443
-
444
- // Corrupt the hook file
445
- const hookPath = path.join(repoPath, '.husky/pre-commit');
446
- await fs.writeFile(hookPath, 'invalid shell script content {{{');
447
-
448
- // Create and stage files
449
- await fs.writeFile(path.join(repoPath, 'test.js'), 'console.log("test");');
450
- runGit(['add', '.'], repoPath);
451
-
452
- // Commit should either succeed (hook fails) or provide clear error
453
- try {
454
- const result = runGit(['commit', '-m', 'Test corrupted hook'], repoPath);
455
- // If it succeeds, that's also acceptable behavior
456
- expect(result).toContain('Test corrupted hook');
457
- } catch (error: any) {
458
- // If it fails, error should be about the hook, not BaseGuard crashing
459
- expect(error.message).toContain('hook') || expect(error.message).toContain('script');
460
- }
461
- }, TEST_TIMEOUT);
462
-
463
- it('should handle missing BaseGuard configuration in git workflow', async () => {
464
- const repoPath = path.join(TEMP_DIR, 'missing-config-test');
465
- await setupGitRepo(repoPath);
466
-
467
- // Install hooks without proper BaseGuard initialization
468
- await fs.mkdir(path.join(repoPath, '.husky'), { recursive: true });
469
- await fs.writeFile(
470
- path.join(repoPath, '.husky/pre-commit'),
471
- '#!/bin/sh\n. "$(dirname "$0")/_/husky.sh"\nbase automation run --trigger pre-commit'
472
- );
473
-
474
- // Create and stage files
475
- await fs.writeFile(path.join(repoPath, 'test.js'), 'console.log("test");');
476
- runGit(['add', '.'], repoPath);
477
-
478
- // Should handle missing configuration gracefully
479
- try {
480
- runGit(['commit', '-m', 'Test missing config'], repoPath);
481
- } catch (error: any) {
482
- // Should provide helpful error about missing configuration
483
- expect(error.message).toContain('configuration') || expect(error.message).toContain('init');
484
- }
485
- }, TEST_TIMEOUT);
486
- });
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { execSync, spawn } from 'child_process';
3
+ import { promises as fs } from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const TEST_TIMEOUT = 45000; // 45 seconds for git operations
11
+ const TEMP_DIR = path.join(__dirname, '../temp-git');
12
+
13
+ // Helper function to run git commands
14
+ function runGit(args: string[], cwd: string): string {
15
+ try {
16
+ return execSync(`git ${args.join(' ')}`, {
17
+ cwd,
18
+ encoding: 'utf8',
19
+ stdio: 'pipe'
20
+ });
21
+ } catch (error: any) {
22
+ throw new Error(`Git command failed: ${error.message}`);
23
+ }
24
+ }
25
+
26
+ // Helper function to run BaseGuard commands
27
+ async function runBaseGuard(args: string[], cwd: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {
28
+ return new Promise((resolve) => {
29
+ const basePath = path.join(__dirname, '../../bin/base.js');
30
+ const child = spawn('node', [basePath, ...args], {
31
+ cwd,
32
+ stdio: 'pipe',
33
+ env: { ...process.env, NODE_ENV: 'test' }
34
+ });
35
+
36
+ let stdout = '';
37
+ let stderr = '';
38
+
39
+ child.stdout?.on('data', (data) => {
40
+ stdout += data.toString();
41
+ });
42
+
43
+ child.stderr?.on('data', (data) => {
44
+ stderr += data.toString();
45
+ });
46
+
47
+ child.on('close', (code) => {
48
+ resolve({
49
+ stdout,
50
+ stderr,
51
+ exitCode: code || 0
52
+ });
53
+ });
54
+
55
+ setTimeout(() => {
56
+ child.kill();
57
+ resolve({ stdout, stderr, exitCode: 1 });
58
+ }, TEST_TIMEOUT);
59
+ });
60
+ }
61
+
62
+ // Helper function to setup git repository
63
+ async function setupGitRepo(repoPath: string): Promise<void> {
64
+ await fs.mkdir(repoPath, { recursive: true });
65
+
66
+ // Initialize git repo
67
+ runGit(['init'], repoPath);
68
+ runGit(['config', 'user.name', 'Test User'], repoPath);
69
+ runGit(['config', 'user.email', 'test@example.com'], repoPath);
70
+
71
+ // Create initial commit
72
+ await fs.writeFile(path.join(repoPath, 'README.md'), '# Test Project\n');
73
+ runGit(['add', 'README.md'], repoPath);
74
+ runGit(['commit', '-m', '"Initial commit"'], repoPath);
75
+ }
76
+
77
+ // Helper function to create test files with violations
78
+ async function createTestFiles(repoPath: string): Promise<void> {
79
+ // Create a file with modern features that will trigger violations
80
+ const jsContent = `
81
+ // Modern JavaScript features
82
+ const data = { test: 'value' };
83
+ const cloned = structuredClone(data);
84
+ const observer = new ResizeObserver(() => {});
85
+
86
+ // Dialog API usage
87
+ const dialog = document.querySelector('dialog');
88
+ if (dialog) {
89
+ dialog.showModal();
90
+ }
91
+ `;
92
+
93
+ const cssContent = `
94
+ .container {
95
+ container-type: inline-size;
96
+ aspect-ratio: 16/9;
97
+ backdrop-filter: blur(10px);
98
+ }
99
+
100
+ @container (min-width: 400px) {
101
+ .container {
102
+ font-size: 1.2rem;
103
+ }
104
+ }
105
+
106
+ .modern-selector:has(dialog[open]) {
107
+ background: color-mix(in srgb, red 50%, blue);
108
+ }
109
+ `;
110
+
111
+ await fs.writeFile(path.join(repoPath, 'test.js'), jsContent);
112
+ await fs.writeFile(path.join(repoPath, 'test.css'), cssContent);
113
+ }
114
+
115
+ describe('Git Integration End-to-End Tests', () => {
116
+ beforeEach(async () => {
117
+ try {
118
+ await fs.rm(TEMP_DIR, { recursive: true, force: true });
119
+ } catch (error) {
120
+ // Directory might not exist
121
+ }
122
+ await fs.mkdir(TEMP_DIR, { recursive: true });
123
+ });
124
+
125
+ afterEach(async () => {
126
+ try {
127
+ await fs.rm(TEMP_DIR, { recursive: true, force: true });
128
+ } catch (error) {
129
+ // Ignore cleanup errors
130
+ }
131
+ });
132
+
133
+ describe('Git Hook Installation', () => {
134
+ it('should install pre-commit hooks successfully', async () => {
135
+ const repoPath = path.join(TEMP_DIR, 'hook-install-test');
136
+ await setupGitRepo(repoPath);
137
+
138
+ // Initialize BaseGuard with automation
139
+ const initResult = await runBaseGuard(['init'], repoPath);
140
+ expect(initResult.exitCode).toBe(0);
141
+
142
+ // Install git hooks
143
+ const hookResult = await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
144
+ expect(hookResult.exitCode).toBe(0);
145
+
146
+ // Check if hook files were created
147
+ const hookPath = path.join(repoPath, '.husky/pre-commit');
148
+ const hookExists = await fs.access(hookPath).then(() => true).catch(() => false);
149
+ expect(hookExists).toBe(true);
150
+
151
+ if (hookExists) {
152
+ const hookContent = await fs.readFile(hookPath, 'utf8');
153
+ expect(hookContent).toContain('base automation run');
154
+ }
155
+ }, TEST_TIMEOUT);
156
+
157
+ it('should install pre-push hooks successfully', async () => {
158
+ const repoPath = path.join(TEMP_DIR, 'pre-push-test');
159
+ await setupGitRepo(repoPath);
160
+
161
+ await runBaseGuard(['init'], repoPath);
162
+
163
+ const hookResult = await runBaseGuard(['automation', 'enable', '--trigger', 'pre-push'], repoPath);
164
+ expect(hookResult.exitCode).toBe(0);
165
+
166
+ const hookPath = path.join(repoPath, '.husky/pre-push');
167
+ const hookExists = await fs.access(hookPath).then(() => true).catch(() => false);
168
+ expect(hookExists).toBe(true);
169
+ }, TEST_TIMEOUT);
170
+ });
171
+
172
+ describe('Pre-commit Hook Behavior', () => {
173
+ it('should block commits when violations are found', async () => {
174
+ const repoPath = path.join(TEMP_DIR, 'block-commit-test');
175
+ await setupGitRepo(repoPath);
176
+
177
+ // Initialize BaseGuard and enable automation
178
+ await runBaseGuard(['init'], repoPath);
179
+
180
+ // Create configuration that will find violations
181
+ const config = {
182
+ version: '1.0.0',
183
+ targets: [{ browser: 'safari', minVersion: '15' }],
184
+ automation: {
185
+ enabled: true,
186
+ trigger: 'pre-commit',
187
+ autoAnalyze: false,
188
+ autoFix: false,
189
+ blockCommit: true
190
+ }
191
+ };
192
+ await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
193
+
194
+ await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
195
+
196
+ // Create files with violations
197
+ await createTestFiles(repoPath);
198
+ runGit(['add', '.'], repoPath);
199
+
200
+ // Try to commit - should fail
201
+ try {
202
+ runGit(['commit', '-m', 'Add files with violations'], repoPath);
203
+ expect.fail('Commit should have been blocked');
204
+ } catch (error: any) {
205
+ expect(error.message).toContain('Git command failed');
206
+ }
207
+ }, TEST_TIMEOUT);
208
+
209
+ it('should allow commits when no violations are found', async () => {
210
+ const repoPath = path.join(TEMP_DIR, 'allow-commit-test');
211
+ await setupGitRepo(repoPath);
212
+
213
+ await runBaseGuard(['init'], repoPath);
214
+
215
+ // Create configuration targeting very old browsers (everything should pass)
216
+ const config = {
217
+ version: '1.0.0',
218
+ targets: [{ browser: 'chrome', minVersion: '120' }], // Very new browser
219
+ automation: {
220
+ enabled: true,
221
+ trigger: 'pre-commit',
222
+ blockCommit: true
223
+ }
224
+ };
225
+ await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
226
+
227
+ await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
228
+
229
+ // Create files without violations (for very new browsers)
230
+ await fs.writeFile(path.join(repoPath, 'safe.js'), 'console.log("Hello World");');
231
+ await fs.writeFile(path.join(repoPath, 'safe.css'), '.safe { color: red; }');
232
+
233
+ runGit(['add', '.'], repoPath);
234
+
235
+ // Commit should succeed
236
+ const commitOutput = runGit(['commit', '-m', 'Add safe files'], repoPath);
237
+ expect(commitOutput).toContain('Add safe files');
238
+ }, TEST_TIMEOUT);
239
+
240
+ it('should respect --no-verify flag', async () => {
241
+ const repoPath = path.join(TEMP_DIR, 'no-verify-test');
242
+ await setupGitRepo(repoPath);
243
+
244
+ await runBaseGuard(['init'], repoPath);
245
+
246
+ const config = {
247
+ version: '1.0.0',
248
+ targets: [{ browser: 'safari', minVersion: '15' }],
249
+ automation: {
250
+ enabled: true,
251
+ trigger: 'pre-commit',
252
+ blockCommit: true
253
+ }
254
+ };
255
+ await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
256
+
257
+ await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
258
+
259
+ // Create files with violations
260
+ await createTestFiles(repoPath);
261
+ runGit(['add', '.'], repoPath);
262
+
263
+ // Commit with --no-verify should succeed
264
+ const commitOutput = runGit(['commit', '--no-verify', '-m', 'Bypass BaseGuard'], repoPath);
265
+ expect(commitOutput).toContain('Bypass BaseGuard');
266
+ }, TEST_TIMEOUT);
267
+ });
268
+
269
+ describe('Auto-fix Mode', () => {
270
+ it('should automatically fix violations and include in commit', async () => {
271
+ const repoPath = path.join(TEMP_DIR, 'auto-fix-test');
272
+ await setupGitRepo(repoPath);
273
+
274
+ await runBaseGuard(['init'], repoPath);
275
+
276
+ // Enable auto-fix mode (would require API keys in real scenario)
277
+ const config = {
278
+ version: '1.0.0',
279
+ targets: [{ browser: 'safari', minVersion: '15' }],
280
+ apiKeys: {
281
+ jules: 'test-key',
282
+ gemini: 'test-key'
283
+ },
284
+ automation: {
285
+ enabled: true,
286
+ trigger: 'pre-commit',
287
+ autoAnalyze: true,
288
+ autoFix: true,
289
+ blockCommit: false
290
+ }
291
+ };
292
+ await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
293
+
294
+ await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
295
+
296
+ // Create files with violations
297
+ await createTestFiles(repoPath);
298
+ runGit(['add', '.'], repoPath);
299
+
300
+ // Note: This test would require mock API responses in a real scenario
301
+ // For now, we test that the automation runs without crashing
302
+ try {
303
+ runGit(['commit', '-m', 'Test auto-fix'], repoPath);
304
+ } catch (error: any) {
305
+ // Expected to fail due to missing API keys, but should not crash
306
+ expect(error.message).toContain('API') || expect(error.message).toContain('key');
307
+ }
308
+ }, TEST_TIMEOUT);
309
+ });
310
+
311
+ describe('Pre-push Hook Behavior', () => {
312
+ it('should check violations before push', async () => {
313
+ const repoPath = path.join(TEMP_DIR, 'pre-push-test');
314
+ await setupGitRepo(repoPath);
315
+
316
+ await runBaseGuard(['init'], repoPath);
317
+
318
+ const config = {
319
+ version: '1.0.0',
320
+ targets: [{ browser: 'safari', minVersion: '15' }],
321
+ automation: {
322
+ enabled: true,
323
+ trigger: 'pre-push',
324
+ blockCommit: true
325
+ }
326
+ };
327
+ await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
328
+
329
+ await runBaseGuard(['automation', 'enable', '--trigger', 'pre-push'], repoPath);
330
+
331
+ // Create and commit files with violations (bypassing pre-commit)
332
+ await createTestFiles(repoPath);
333
+ runGit(['add', '.'], repoPath);
334
+ runGit(['commit', '--no-verify', '-m', 'Add files with violations'], repoPath);
335
+
336
+ // Create a remote repository simulation
337
+ const remoteRepo = path.join(TEMP_DIR, 'remote-repo');
338
+ await fs.mkdir(remoteRepo, { recursive: true });
339
+ runGit(['init', '--bare'], remoteRepo);
340
+ runGit(['remote', 'add', 'origin', remoteRepo], repoPath);
341
+
342
+ // Try to push - should fail due to violations
343
+ try {
344
+ runGit(['push', 'origin', 'main'], repoPath);
345
+ expect.fail('Push should have been blocked');
346
+ } catch (error: any) {
347
+ expect(error.message).toContain('Git command failed');
348
+ }
349
+ }, TEST_TIMEOUT);
350
+ });
351
+
352
+ describe('Automation Configuration', () => {
353
+ it('should enable and disable automation correctly', async () => {
354
+ const repoPath = path.join(TEMP_DIR, 'automation-config-test');
355
+ await setupGitRepo(repoPath);
356
+
357
+ await runBaseGuard(['init'], repoPath);
358
+
359
+ // Enable automation
360
+ const enableResult = await runBaseGuard(['automation', 'enable'], repoPath);
361
+ expect(enableResult.exitCode).toBe(0);
362
+
363
+ // Check configuration
364
+ const configPath = path.join(repoPath, '.baseguardrc.json');
365
+ const config = JSON.parse(await fs.readFile(configPath, 'utf8'));
366
+ expect(config.automation.enabled).toBe(true);
367
+
368
+ // Disable automation
369
+ const disableResult = await runBaseGuard(['automation', 'disable'], repoPath);
370
+ expect(disableResult.exitCode).toBe(0);
371
+
372
+ // Check configuration updated
373
+ const updatedConfig = JSON.parse(await fs.readFile(configPath, 'utf8'));
374
+ expect(updatedConfig.automation.enabled).toBe(false);
375
+ }, TEST_TIMEOUT);
376
+
377
+ it('should handle different trigger configurations', async () => {
378
+ const repoPath = path.join(TEMP_DIR, 'trigger-config-test');
379
+ await setupGitRepo(repoPath);
380
+
381
+ await runBaseGuard(['init'], repoPath);
382
+
383
+ // Test pre-commit trigger
384
+ await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
385
+ let config = JSON.parse(await fs.readFile(path.join(repoPath, '.baseguardrc.json'), 'utf8'));
386
+ expect(config.automation.trigger).toBe('pre-commit');
387
+
388
+ // Test pre-push trigger
389
+ await runBaseGuard(['automation', 'enable', '--trigger', 'pre-push'], repoPath);
390
+ config = JSON.parse(await fs.readFile(path.join(repoPath, '.baseguardrc.json'), 'utf8'));
391
+ expect(config.automation.trigger).toBe('pre-push');
392
+ }, TEST_TIMEOUT);
393
+ });
394
+
395
+ describe('Incremental Scanning', () => {
396
+ it('should only scan changed files in git workflow', async () => {
397
+ const repoPath = path.join(TEMP_DIR, 'incremental-test');
398
+ await setupGitRepo(repoPath);
399
+
400
+ await runBaseGuard(['init'], repoPath);
401
+
402
+ // Create multiple files
403
+ await fs.writeFile(path.join(repoPath, 'file1.js'), 'console.log("file1");');
404
+ await fs.writeFile(path.join(repoPath, 'file2.js'), 'console.log("file2");');
405
+ await createTestFiles(repoPath); // Creates files with violations
406
+
407
+ runGit(['add', 'file1.js', 'file2.js'], repoPath);
408
+ runGit(['commit', '-m', 'Add safe files'], repoPath);
409
+
410
+ // Now stage only the files with violations
411
+ runGit(['add', 'test.js', 'test.css'], repoPath);
412
+
413
+ const config = {
414
+ version: '1.0.0',
415
+ targets: [{ browser: 'safari', minVersion: '15' }],
416
+ automation: {
417
+ enabled: true,
418
+ trigger: 'pre-commit',
419
+ blockCommit: true
420
+ }
421
+ };
422
+ await fs.writeFile(path.join(repoPath, '.baseguardrc.json'), JSON.stringify(config, null, 2));
423
+
424
+ await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
425
+
426
+ // The automation should only check staged files
427
+ try {
428
+ runGit(['commit', '-m', 'Add files with violations'], repoPath);
429
+ expect.fail('Commit should have been blocked');
430
+ } catch (error: any) {
431
+ expect(error.message).toContain('Git command failed');
432
+ }
433
+ }, TEST_TIMEOUT);
434
+ });
435
+
436
+ describe('Error Recovery', () => {
437
+ it('should handle corrupted git hooks gracefully', async () => {
438
+ const repoPath = path.join(TEMP_DIR, 'corrupted-hook-test');
439
+ await setupGitRepo(repoPath);
440
+
441
+ await runBaseGuard(['init'], repoPath);
442
+ await runBaseGuard(['automation', 'enable', '--trigger', 'pre-commit'], repoPath);
443
+
444
+ // Corrupt the hook file
445
+ const hookPath = path.join(repoPath, '.husky/pre-commit');
446
+ await fs.writeFile(hookPath, 'invalid shell script content {{{');
447
+
448
+ // Create and stage files
449
+ await fs.writeFile(path.join(repoPath, 'test.js'), 'console.log("test");');
450
+ runGit(['add', '.'], repoPath);
451
+
452
+ // Commit should either succeed (hook fails) or provide clear error
453
+ try {
454
+ const result = runGit(['commit', '-m', 'Test corrupted hook'], repoPath);
455
+ // If it succeeds, that's also acceptable behavior
456
+ expect(result).toContain('Test corrupted hook');
457
+ } catch (error: any) {
458
+ // If it fails, error should be about the hook, not BaseGuard crashing
459
+ expect(error.message).toContain('hook') || expect(error.message).toContain('script');
460
+ }
461
+ }, TEST_TIMEOUT);
462
+
463
+ it('should handle missing BaseGuard configuration in git workflow', async () => {
464
+ const repoPath = path.join(TEMP_DIR, 'missing-config-test');
465
+ await setupGitRepo(repoPath);
466
+
467
+ // Install hooks without proper BaseGuard initialization
468
+ await fs.mkdir(path.join(repoPath, '.husky'), { recursive: true });
469
+ await fs.writeFile(
470
+ path.join(repoPath, '.husky/pre-commit'),
471
+ '#!/bin/sh\n. "$(dirname "$0")/_/husky.sh"\nbase automation run --trigger pre-commit'
472
+ );
473
+
474
+ // Create and stage files
475
+ await fs.writeFile(path.join(repoPath, 'test.js'), 'console.log("test");');
476
+ runGit(['add', '.'], repoPath);
477
+
478
+ // Should handle missing configuration gracefully
479
+ try {
480
+ runGit(['commit', '-m', 'Test missing config'], repoPath);
481
+ } catch (error: any) {
482
+ // Should provide helpful error about missing configuration
483
+ expect(error.message).toContain('configuration') || expect(error.message).toContain('init');
484
+ }
485
+ }, TEST_TIMEOUT);
486
+ });
487
487
  });