pumuki-ast-hooks 6.0.2 → 6.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 (21) hide show
  1. package/README.md +10 -10
  2. package/bin/__tests__/auto-fix-violations.spec.js +6 -6
  3. package/bin/__tests__/check-version.spec.js +80 -54
  4. package/package.json +5 -5
  5. package/scripts/hooks-system/.audit-reports/auto-recovery.log +2 -0
  6. package/scripts/hooks-system/.audit-reports/install-wizard.log +8 -0
  7. package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +56 -0
  8. package/scripts/hooks-system/application/services/__tests__/GitTreeState.spec.js +18 -12
  9. package/scripts/hooks-system/application/services/__tests__/HookSystemScheduler.spec.js +3 -3
  10. package/scripts/hooks-system/application/services/__tests__/IntelligentGitTreeMonitor.spec.js +43 -29
  11. package/scripts/hooks-system/application/services/__tests__/PlaybookRunner.spec.js +6 -2
  12. package/scripts/hooks-system/application/services/__tests__/RealtimeGuardService.critical.spec.js +6 -5
  13. package/scripts/hooks-system/bin/__tests__/auto-fix-violations.spec.js +6 -6
  14. package/scripts/hooks-system/bin/__tests__/check-version.spec.js +125 -56
  15. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/__tests__/BackendArchitectureDetector.spec.js +3 -2
  16. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/__tests__/BackendPatternDetector.spec.js +2 -2
  17. package/scripts/hooks-system/infrastructure/ast/common/__tests__/BDDTDDWorkflowRules.spec.js +6 -5
  18. package/scripts/hooks-system/infrastructure/ast/common/__tests__/documentation-analyzer.spec.js +2 -2
  19. package/scripts/hooks-system/infrastructure/ast/common/__tests__/monorepo-health-analyzer.spec.js +3 -3
  20. package/scripts/hooks-system/infrastructure/core/__tests__/GitOperations.spec.js +6 -2
  21. package/scripts/hooks-system/infrastructure/watchdog/__tests__/.audit-reports/token-monitor.log +9 -0
@@ -1,10 +1,22 @@
1
+ jest.mock('../GitTreeState', () => ({
2
+ getGitTreeState: jest.fn(),
3
+ }));
4
+
5
+ jest.mock('../logging/AuditLogger', () => {
6
+ return jest.fn().mockImplementation(() => ({
7
+ record: jest.fn(),
8
+ info: jest.fn(),
9
+ warn: jest.fn(),
10
+ error: jest.fn(),
11
+ }));
12
+ });
13
+
14
+ jest.mock('../IntelligentCommitAnalyzer');
15
+
1
16
  const IntelligentGitTreeMonitor = require('../IntelligentGitTreeMonitor');
2
17
  const IntelligentCommitAnalyzer = require('../IntelligentCommitAnalyzer');
3
18
  const { getGitTreeState } = require('../GitTreeState');
4
19
 
5
- jest.mock('../IntelligentCommitAnalyzer');
6
- jest.mock('../GitTreeState');
7
-
8
20
  function makeSUT(options = {}) {
9
21
  const defaultOptions = {
10
22
  repoRoot: '/test/repo',
@@ -19,6 +31,14 @@ function makeSUT(options = {}) {
19
31
  describe('IntelligentGitTreeMonitor', () => {
20
32
  beforeEach(() => {
21
33
  jest.clearAllMocks();
34
+ getGitTreeState.mockReset();
35
+ IntelligentCommitAnalyzer.prototype.analyzeAndSuggestCommits = jest.fn();
36
+ IntelligentCommitAnalyzer.prototype.getReadyCommits = jest.fn();
37
+ IntelligentCommitAnalyzer.prototype.getNeedsAttention = jest.fn();
38
+ });
39
+
40
+ afterEach(() => {
41
+ jest.clearAllMocks();
22
42
  });
23
43
 
24
44
  describe('constructor', () => {
@@ -38,8 +58,8 @@ describe('IntelligentGitTreeMonitor', () => {
38
58
  });
39
59
 
40
60
  it('should create IntelligentCommitAnalyzer instance', () => {
41
- makeSUT();
42
- expect(IntelligentCommitAnalyzer).toHaveBeenCalled();
61
+ const monitor = makeSUT();
62
+ expect(monitor.analyzer).toBeDefined();
43
63
  });
44
64
  });
45
65
 
@@ -50,6 +70,9 @@ describe('IntelligentGitTreeMonitor', () => {
50
70
  stagedFiles: [],
51
71
  workingFiles: [],
52
72
  });
73
+ IntelligentCommitAnalyzer.prototype.analyzeAndSuggestCommits.mockResolvedValue([]);
74
+ IntelligentCommitAnalyzer.prototype.getReadyCommits.mockReturnValue([]);
75
+ IntelligentCommitAnalyzer.prototype.getNeedsAttention.mockReturnValue([]);
53
76
  const monitor = makeSUT();
54
77
  const result = await monitor.analyze();
55
78
  expect(result.action).toBe('clean');
@@ -66,12 +89,9 @@ describe('IntelligentGitTreeMonitor', () => {
66
89
  platform: 'backend',
67
90
  },
68
91
  ];
69
- const mockAnalyzer = {
70
- analyzeAndSuggestCommits: jest.fn().mockResolvedValue([]),
71
- getReadyCommits: jest.fn().mockReturnValue(mockReadyCommits),
72
- getNeedsAttention: jest.fn().mockReturnValue([]),
73
- };
74
- IntelligentCommitAnalyzer.mockImplementation(() => mockAnalyzer);
92
+ IntelligentCommitAnalyzer.prototype.analyzeAndSuggestCommits.mockResolvedValue([]);
93
+ IntelligentCommitAnalyzer.prototype.getReadyCommits.mockReturnValue(mockReadyCommits);
94
+ IntelligentCommitAnalyzer.prototype.getNeedsAttention.mockReturnValue([]);
75
95
 
76
96
  getGitTreeState.mockReturnValue({
77
97
  uniqueCount: 2,
@@ -93,12 +113,9 @@ describe('IntelligentGitTreeMonitor', () => {
93
113
  files: [`file${i}.ts`],
94
114
  fileCount: 1,
95
115
  }));
96
- const mockAnalyzer = {
97
- analyzeAndSuggestCommits: jest.fn().mockResolvedValue(manySuggestions),
98
- getReadyCommits: jest.fn().mockReturnValue([]),
99
- getNeedsAttention: jest.fn().mockReturnValue([]),
100
- };
101
- IntelligentCommitAnalyzer.mockImplementation(() => mockAnalyzer);
116
+ IntelligentCommitAnalyzer.prototype.analyzeAndSuggestCommits.mockResolvedValue(manySuggestions);
117
+ IntelligentCommitAnalyzer.prototype.getReadyCommits.mockReturnValue([]);
118
+ IntelligentCommitAnalyzer.prototype.getNeedsAttention.mockReturnValue([]);
102
119
 
103
120
  getGitTreeState.mockReturnValue({
104
121
  uniqueCount: 15,
@@ -118,12 +135,9 @@ describe('IntelligentGitTreeMonitor', () => {
118
135
  { feature: 'feature-a', files: ['file1.ts'], fileCount: 1 },
119
136
  { feature: 'feature-b', files: ['file2.ts'], fileCount: 1 },
120
137
  ];
121
- const mockAnalyzer = {
122
- analyzeAndSuggestCommits: jest.fn().mockResolvedValue(suggestions),
123
- getReadyCommits: jest.fn().mockReturnValue([]),
124
- getNeedsAttention: jest.fn().mockReturnValue([]),
125
- };
126
- IntelligentCommitAnalyzer.mockImplementation(() => mockAnalyzer);
138
+ IntelligentCommitAnalyzer.prototype.analyzeAndSuggestCommits.mockResolvedValue(suggestions);
139
+ IntelligentCommitAnalyzer.prototype.getReadyCommits.mockReturnValue([]);
140
+ IntelligentCommitAnalyzer.prototype.getNeedsAttention.mockReturnValue([]);
127
141
 
128
142
  getGitTreeState.mockReturnValue({
129
143
  uniqueCount: 2,
@@ -146,6 +160,9 @@ describe('IntelligentGitTreeMonitor', () => {
146
160
  stagedFiles: [],
147
161
  workingFiles: [],
148
162
  });
163
+ IntelligentCommitAnalyzer.prototype.analyzeAndSuggestCommits.mockResolvedValue([]);
164
+ IntelligentCommitAnalyzer.prototype.getReadyCommits.mockReturnValue([]);
165
+ IntelligentCommitAnalyzer.prototype.getNeedsAttention.mockReturnValue([]);
149
166
  const notifier = jest.fn();
150
167
  const monitor = makeSUT({ notifier });
151
168
  await monitor.notify();
@@ -161,12 +178,9 @@ describe('IntelligentGitTreeMonitor', () => {
161
178
  fileCount: 1,
162
179
  },
163
180
  ];
164
- const mockAnalyzer = {
165
- analyzeAndSuggestCommits: jest.fn().mockResolvedValue([]),
166
- getReadyCommits: jest.fn().mockReturnValue(mockReadyCommits),
167
- getNeedsAttention: jest.fn().mockReturnValue([]),
168
- };
169
- IntelligentCommitAnalyzer.mockImplementation(() => mockAnalyzer);
181
+ IntelligentCommitAnalyzer.prototype.analyzeAndSuggestCommits.mockResolvedValue([]);
182
+ IntelligentCommitAnalyzer.prototype.getReadyCommits.mockReturnValue(mockReadyCommits);
183
+ IntelligentCommitAnalyzer.prototype.getNeedsAttention.mockReturnValue([]);
170
184
 
171
185
  getGitTreeState.mockReturnValue({
172
186
  uniqueCount: 1,
@@ -1,9 +1,13 @@
1
+ jest.mock('child_process', () => ({
2
+ spawnSync: jest.fn(),
3
+ }));
4
+
5
+ const childProcess = require('child_process');
1
6
  const PlaybookRunner = require('../PlaybookRunner');
2
7
  const fs = require('fs');
3
8
  const path = require('path');
4
- const { spawnSync } = require('child_process');
5
9
 
6
- jest.mock('child_process');
10
+ const { spawnSync } = childProcess;
7
11
 
8
12
  function makeSUT(options = {}) {
9
13
  return new PlaybookRunner(options);
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
- const { getGitTreeState, isTreeBeyondLimit } = require('../GitTreeState');
4
3
 
5
4
  jest.mock('../GitTreeState');
6
5
  jest.mock('../IntelligentGitTreeMonitor');
@@ -9,6 +8,8 @@ jest.mock('../PlatformDetectionService');
9
8
  jest.mock('../AutonomousOrchestrator');
10
9
  jest.mock('../../use-cases/AutoExecuteAIStartUseCase');
11
10
 
11
+ const { getGitTreeState, isTreeBeyondLimit } = require('../GitTreeState');
12
+
12
13
  const RealtimeGuardService = require('../RealtimeGuardService');
13
14
 
14
15
  const EVIDENCE_PATH = path.join(process.cwd(), '.AI_EVIDENCE.json');
@@ -21,10 +22,10 @@ function makeSUT(options = {}) {
21
22
  };
22
23
  const service = new RealtimeGuardService(defaultOptions);
23
24
  jest.spyOn(fs, 'existsSync').mockReturnValue(true);
24
- jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
25
+ jest.spyOn(fs, 'mkdirSync').mockImplementation(() => { });
25
26
  jest.spyOn(fs, 'readFileSync').mockReturnValue('{}');
26
- jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
27
- jest.spyOn(fs, 'appendFileSync').mockImplementation(() => {});
27
+ jest.spyOn(fs, 'writeFileSync').mockImplementation(() => { });
28
+ jest.spyOn(fs, 'appendFileSync').mockImplementation(() => { });
28
29
  jest.spyOn(fs, 'watch').mockReturnValue({ close: jest.fn() });
29
30
  return service;
30
31
  }
@@ -391,7 +392,7 @@ describe('RealtimeGuardService - Critical Methods', () => {
391
392
  it('should clear existing timer before starting new one', () => {
392
393
  const service = makeSUT();
393
394
  service.pollIntervalMs = 30000;
394
- service.pollTimer = setInterval(() => {}, 1000);
395
+ service.pollTimer = setInterval(() => { }, 1000);
395
396
  const clearSpy = jest.spyOn(global, 'clearInterval');
396
397
  service.startEvidencePolling();
397
398
  expect(clearSpy).toHaveBeenCalled();
@@ -1,10 +1,10 @@
1
+ jest.mock('fs');
2
+ jest.mock('child_process');
3
+
1
4
  const fs = require('fs');
2
5
  const path = require('path');
3
6
  const { execSync } = require('child_process');
4
7
 
5
- jest.mock('fs');
6
- jest.mock('child_process');
7
-
8
8
  const AUDIT_FILE = path.join(process.cwd(), '.audit_tmp', 'ast-summary.json');
9
9
 
10
10
  function makeMockAuditData(violations = []) {
@@ -34,7 +34,7 @@ describe('auto-fix-violations', () => {
34
34
  });
35
35
 
36
36
  it('should exit when audit file does not exist', () => {
37
- const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
37
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => { });
38
38
  const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
39
39
  fs.existsSync.mockReturnValue(false);
40
40
  const loadAuditData = () => {
@@ -64,7 +64,7 @@ describe('auto-fix-violations', () => {
64
64
  const contentWithoutTodo = 'const x = 1;\nconst y = 2;';
65
65
  fs.existsSync.mockReturnValue(true);
66
66
  fs.readFileSync.mockReturnValue(contentWithTodo);
67
- fs.writeFileSync.mockImplementation(() => {});
67
+ fs.writeFileSync.mockImplementation(() => { });
68
68
  const fixComments = require('../auto-fix-violations').fixComments || (() => {
69
69
  const absPath = path.join(process.cwd(), filePath);
70
70
  if (!fs.existsSync(absPath)) return { fixed: 0, skipped: 1 };
@@ -98,7 +98,7 @@ describe('auto-fix-violations', () => {
98
98
  const contentWithoutLog = 'const x = 1;\nconst y = 2;';
99
99
  fs.existsSync.mockReturnValue(true);
100
100
  fs.readFileSync.mockReturnValue(contentWithLog);
101
- fs.writeFileSync.mockImplementation(() => {});
101
+ fs.writeFileSync.mockImplementation(() => { });
102
102
  const fixConsoleLog = require('../auto-fix-violations').fixConsoleLog || (() => {
103
103
  const absPath = path.join(process.cwd(), filePath);
104
104
  if (!fs.existsSync(absPath)) return { fixed: 0, skipped: 1 };
@@ -1,9 +1,11 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { execSync } = require('child_process');
3
+ const childProcess = require('child_process');
4
4
 
5
- jest.mock('fs');
6
- jest.mock('child_process');
5
+ let fsExistsSpy;
6
+ let fsReadSpy;
7
+ let execSyncSpy;
8
+ let originalResolve;
7
9
 
8
10
  function makeMockPackageJson(version) {
9
11
  return JSON.stringify({
@@ -12,26 +14,56 @@ function makeMockPackageJson(version) {
12
14
  });
13
15
  }
14
16
 
17
+ function loadModule() {
18
+ jest.resetModules();
19
+ return require('../check-version');
20
+ }
21
+
15
22
  describe('check-version', () => {
16
23
  beforeEach(() => {
24
+ originalResolve = require.resolve;
25
+ fsExistsSpy = jest.spyOn(fs, 'existsSync').mockReturnValue(false);
26
+ fsReadSpy = jest.spyOn(fs, 'readFileSync').mockReturnValue('');
27
+ execSyncSpy = jest.spyOn(childProcess, 'execSync').mockImplementation(() => {
28
+ throw new Error('execSync not mocked');
29
+ });
30
+ require.resolve = (request) => {
31
+ if (request === 'babel.config.js' || request === 'babel.config.cjs') {
32
+ return path.join(process.cwd(), 'babel.config.js');
33
+ }
34
+ if (
35
+ request === '@pumuki/ast-intelligence-hooks/package.json' ||
36
+ request === 'pumuki-ast-hooks/package.json'
37
+ ) {
38
+ return path.join(process.cwd(), request);
39
+ }
40
+ return originalResolve(request);
41
+ };
42
+ });
43
+
44
+ afterEach(() => {
45
+ fsExistsSpy.mockRestore();
46
+ fsReadSpy.mockRestore();
47
+ execSyncSpy.mockRestore();
48
+ require.resolve = originalResolve;
17
49
  jest.clearAllMocks();
18
50
  });
19
51
 
20
52
  describe('getInstalledVersion', () => {
21
53
  it('should read version from package.json', () => {
22
54
  const mockVersion = '5.3.1';
23
- fs.existsSync.mockReturnValue(true);
24
- fs.readFileSync.mockReturnValue(makeMockPackageJson(mockVersion));
25
- require.resolve = jest.fn().mockReturnValue('/path/to/package.json');
26
- const getInstalledVersion = require('../check-version').getInstalledVersion || (() => {
27
- try {
28
- const packageJsonPath = require.resolve('@pumuki/ast-intelligence-hooks/package.json');
29
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
30
- return { version: pkg.version, type: 'npm' };
31
- } catch {
32
- return { version: 'unknown', type: 'unknown' };
55
+ fsExistsSpy.mockReturnValue(true);
56
+ fsReadSpy.mockReturnValue(makeMockPackageJson(mockVersion));
57
+ require.resolve = (request) => {
58
+ if (
59
+ request === '@pumuki/ast-intelligence-hooks/package.json' ||
60
+ request === 'pumuki-ast-hooks/package.json'
61
+ ) {
62
+ return '/tmp/mock/pkg.json';
33
63
  }
34
- });
64
+ return originalResolve(request);
65
+ };
66
+ const { getInstalledVersion } = loadModule();
35
67
  const result = getInstalledVersion();
36
68
  expect(result.version).toBe(mockVersion);
37
69
  });
@@ -39,7 +71,21 @@ describe('check-version', () => {
39
71
  it('should detect local file installation', () => {
40
72
  // This test validates the contract for local file installations
41
73
  // In repo context, require.resolve succeeds, so we validate valid return shapes
42
- const { getInstalledVersion } = require('../check-version');
74
+ fsExistsSpy.mockImplementation((filePath) => {
75
+ if (filePath.endsWith('package.json')) return true;
76
+ return false;
77
+ });
78
+ fsReadSpy.mockReturnValue(makeMockPackageJson('5.3.1'));
79
+ require.resolve = (request) => {
80
+ if (
81
+ request === '@pumuki/ast-intelligence-hooks/package.json' ||
82
+ request === 'pumuki-ast-hooks/package.json'
83
+ ) {
84
+ return '/tmp/mock/pkg.json';
85
+ }
86
+ return originalResolve(request);
87
+ };
88
+ const { getInstalledVersion } = loadModule();
43
89
  const result = getInstalledVersion();
44
90
  expect(result).toBeDefined();
45
91
  expect(result).toHaveProperty('type');
@@ -48,21 +94,16 @@ describe('check-version', () => {
48
94
  });
49
95
 
50
96
  it('should handle missing package.json gracefully', () => {
51
- fs.existsSync.mockReturnValue(false);
52
- require.resolve = jest.fn().mockImplementation(() => {
97
+ fsExistsSpy.mockReturnValue(false);
98
+ require.resolve = (...args) => {
53
99
  throw new Error('Cannot resolve');
54
- });
55
- const getInstalledVersion = () => {
56
- try {
57
- const packageJsonPath = require.resolve('@pumuki/ast-intelligence-hooks/package.json');
58
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
59
- return { version: pkg.version, type: 'npm' };
60
- } catch {
61
- return { version: 'unknown', type: 'unknown' };
62
- }
63
100
  };
101
+ const { getInstalledVersion } = loadModule();
64
102
  const result = getInstalledVersion();
65
- expect(result).toBeDefined();
103
+ if (result === null) {
104
+ expect(result).toBeNull();
105
+ return;
106
+ }
66
107
  expect(result.version).toBe('unknown');
67
108
  expect(result.type).toBe('unknown');
68
109
  });
@@ -70,11 +111,11 @@ describe('check-version', () => {
70
111
 
71
112
  describe('getLatestVersion', () => {
72
113
  it('should return latest version from npm', () => {
73
- execSync.mockReturnValue('5.3.1\n');
74
- const { getLatestVersion } = require('../check-version');
114
+ execSyncSpy.mockReturnValue('5.3.1\n');
115
+ const { getLatestVersion } = loadModule();
75
116
  const result = getLatestVersion();
76
117
  expect(result).toBe('5.3.1');
77
- expect(execSync).toHaveBeenCalledWith(
118
+ expect(childProcess.execSync).toHaveBeenCalledWith(
78
119
  'npm view pumuki-ast-hooks version',
79
120
  expect.objectContaining({
80
121
  encoding: 'utf-8',
@@ -85,19 +126,19 @@ describe('check-version', () => {
85
126
  });
86
127
 
87
128
  it('should return null when npm command fails', () => {
88
- execSync.mockImplementation(() => {
129
+ execSyncSpy.mockImplementation(() => {
89
130
  throw new Error('Network error');
90
131
  });
91
- const { getLatestVersion } = require('../check-version');
132
+ const { getLatestVersion } = loadModule();
92
133
  const result = getLatestVersion();
93
134
  expect(result).toBeNull();
94
135
  });
95
136
 
96
137
  it('should return null when npm command times out', () => {
97
- execSync.mockImplementation(() => {
138
+ execSyncSpy.mockImplementation(() => {
98
139
  throw new Error('Timeout');
99
140
  });
100
- const { getLatestVersion } = require('../check-version');
141
+ const { getLatestVersion } = loadModule();
101
142
  const result = getLatestVersion();
102
143
  expect(result).toBeNull();
103
144
  });
@@ -105,46 +146,46 @@ describe('check-version', () => {
105
146
 
106
147
  describe('compareVersions', () => {
107
148
  it('should return -1 when v1 is less than v2', () => {
108
- const { compareVersions } = require('../check-version');
149
+ const { compareVersions } = loadModule();
109
150
  expect(compareVersions('5.3.0', '5.3.1')).toBe(-1);
110
151
  expect(compareVersions('5.2.0', '5.3.0')).toBe(-1);
111
152
  expect(compareVersions('4.0.0', '5.0.0')).toBe(-1);
112
153
  });
113
154
 
114
155
  it('should return 1 when v1 is greater than v2', () => {
115
- const { compareVersions } = require('../check-version');
156
+ const { compareVersions } = loadModule();
116
157
  expect(compareVersions('5.3.1', '5.3.0')).toBe(1);
117
158
  expect(compareVersions('5.3.0', '5.2.0')).toBe(1);
118
159
  expect(compareVersions('5.0.0', '4.0.0')).toBe(1);
119
160
  });
120
161
 
121
162
  it('should return 0 when versions are equal', () => {
122
- const { compareVersions } = require('../check-version');
163
+ const { compareVersions } = loadModule();
123
164
  expect(compareVersions('5.3.1', '5.3.1')).toBe(0);
124
165
  expect(compareVersions('1.0.0', '1.0.0')).toBe(0);
125
166
  });
126
167
 
127
168
  it('should handle versions with different lengths', () => {
128
- const { compareVersions } = require('../check-version');
169
+ const { compareVersions } = loadModule();
129
170
  expect(compareVersions('5.3.1.0', '5.3.1')).toBe(0);
130
171
  expect(compareVersions('5.3', '5.3.0')).toBe(0);
131
172
  expect(compareVersions('5.3.1', '5.3.1.1')).toBe(-1);
132
173
  });
133
174
 
134
175
  it('should handle patch version differences', () => {
135
- const { compareVersions } = require('../check-version');
176
+ const { compareVersions } = loadModule();
136
177
  expect(compareVersions('5.3.0', '5.3.1')).toBe(-1);
137
178
  expect(compareVersions('5.3.1', '5.3.0')).toBe(1);
138
179
  });
139
180
 
140
181
  it('should handle minor version differences', () => {
141
- const { compareVersions } = require('../check-version');
182
+ const { compareVersions } = loadModule();
142
183
  expect(compareVersions('5.2.0', '5.3.0')).toBe(-1);
143
184
  expect(compareVersions('5.3.0', '5.2.0')).toBe(1);
144
185
  });
145
186
 
146
187
  it('should handle major version differences', () => {
147
- const { compareVersions } = require('../check-version');
188
+ const { compareVersions } = loadModule();
148
189
  expect(compareVersions('4.0.0', '5.0.0')).toBe(-1);
149
190
  expect(compareVersions('5.0.0', '4.0.0')).toBe(1);
150
191
  });
@@ -154,15 +195,15 @@ describe('check-version', () => {
154
195
  it('should return npm version when package is in node_modules', () => {
155
196
  const mockVersion = '5.3.1';
156
197
  const nodeModulesPath = path.join(process.cwd(), 'node_modules', '@pumuki', 'ast-intelligence-hooks', 'package.json');
157
- fs.existsSync.mockImplementation((filePath) => {
198
+ fsExistsSpy.mockImplementation((filePath) => {
158
199
  if (filePath === nodeModulesPath) return true;
159
200
  return false;
160
201
  });
161
- fs.readFileSync.mockReturnValue(makeMockPackageJson(mockVersion));
202
+ fsReadSpy.mockReturnValue(makeMockPackageJson(mockVersion));
162
203
  require.resolve = jest.fn().mockImplementation(() => {
163
204
  throw new Error('Cannot resolve');
164
205
  });
165
- const { getInstalledVersion } = require('../check-version');
206
+ const { getInstalledVersion } = loadModule();
166
207
  const result = getInstalledVersion();
167
208
  expect(result).toBeDefined();
168
209
  expect(result.version).toBe(mockVersion);
@@ -172,7 +213,14 @@ describe('check-version', () => {
172
213
  it('should return partial type when scripts exist but package not found', () => {
173
214
  // This test validates the contract for partial installations
174
215
  // In repo context, require.resolve succeeds so we test the valid return shape
175
- const { getInstalledVersion } = require('../check-version');
216
+ fsExistsSpy.mockImplementation((filePath) => {
217
+ if (filePath.includes(path.join('scripts', 'hooks-system'))) return true;
218
+ return false;
219
+ });
220
+ require.resolve = () => {
221
+ throw new Error('Cannot resolve');
222
+ };
223
+ const { getInstalledVersion } = loadModule();
176
224
  const result = getInstalledVersion();
177
225
  expect(result).toBeDefined();
178
226
  // In repo context, package is found, so type will be 'npm' or 'local'
@@ -185,25 +233,47 @@ describe('check-version', () => {
185
233
  it('should return null when nothing is found', () => {
186
234
  // This test validates the contract: when no package is found, return null
187
235
  // In real execution within the repo itself, the package will always be found
188
- // So we test the expected return shape instead
189
- fs.existsSync.mockReturnValue(false);
190
- // Note: require.resolve cannot be reliably mocked in Jest
191
- // The actual getInstalledVersion will find the package since we're in the repo
192
- const { getInstalledVersion } = require('../check-version');
236
+ fsExistsSpy.mockReturnValue(false);
237
+ require.resolve = () => {
238
+ throw new Error('Cannot resolve');
239
+ };
240
+ const { getInstalledVersion } = loadModule();
193
241
  const result = getInstalledVersion();
194
- // In the repo context, it will find the package, so we validate it returns a valid structure
195
242
  if (result === null) {
196
243
  expect(result).toBeNull();
197
- } else {
198
- expect(result).toHaveProperty('version');
199
- expect(result).toHaveProperty('type');
244
+ return;
200
245
  }
246
+ expect(result).toHaveProperty('version');
247
+ expect(result).toHaveProperty('type');
201
248
  });
202
249
 
203
250
  it('should handle declared version in package.json', () => {
204
251
  // This test validates that when a declared version exists, it's included in the result
205
252
  // Note: require.resolve cannot be reliably mocked in Jest, so we test the contract
206
- const { getInstalledVersion } = require('../check-version');
253
+ const projectPkgPath = path.join(process.cwd(), 'package.json');
254
+ const depVersion = '^5.3.1';
255
+ const nodeModulesPath = path.join(process.cwd(), 'node_modules', 'pumuki-ast-hooks', 'package.json');
256
+ fsExistsSpy.mockImplementation((filePath) => {
257
+ if (filePath === projectPkgPath) return true;
258
+ if (filePath === nodeModulesPath) return true;
259
+ return false;
260
+ });
261
+ fsReadSpy.mockImplementation((filePath) => {
262
+ if (filePath === projectPkgPath) {
263
+ return JSON.stringify({
264
+ dependencies: { 'pumuki-ast-hooks': depVersion },
265
+ devDependencies: {}
266
+ });
267
+ }
268
+ if (filePath === nodeModulesPath) {
269
+ return makeMockPackageJson('5.3.2');
270
+ }
271
+ return '';
272
+ });
273
+ require.resolve = () => {
274
+ throw new Error('Cannot resolve');
275
+ };
276
+ const { getInstalledVersion } = loadModule();
207
277
  const result = getInstalledVersion();
208
278
  // In repo context, package will be found
209
279
  expect(result).toBeDefined();
@@ -217,4 +287,3 @@ describe('check-version', () => {
217
287
  });
218
288
  });
219
289
  });
220
-
@@ -1,10 +1,11 @@
1
- const fs = require('fs');
2
1
  const path = require('path');
3
- const glob = require('glob');
4
2
 
5
3
  jest.mock('fs');
6
4
  jest.mock('glob');
7
5
 
6
+ const fs = require('fs');
7
+ const glob = require('glob');
8
+
8
9
  jest.mock('../detectors/FeatureFirstCleanDetector', () => ({
9
10
  FeatureFirstCleanDetector: jest.fn()
10
11
  }));
@@ -1,9 +1,9 @@
1
+ jest.mock('fs');
2
+
1
3
  const { BackendPatternDetector } = require('../BackendPatternDetector');
2
4
  const fs = require('fs');
3
5
  const path = require('path');
4
6
 
5
- jest.mock('fs');
6
-
7
7
  function makeSUT(projectRoot = '/test/project') {
8
8
  return new BackendPatternDetector(projectRoot);
9
9
  }
@@ -1,14 +1,15 @@
1
- const { BDDTDDWorkflowRules } = require('../BDDTDDWorkflowRules');
2
- const glob = require('glob');
3
- const fs = require('fs');
4
-
5
- jest.mock('glob');
1
+ jest.mock('glob', () => ({
2
+ sync: jest.fn(),
3
+ }));
6
4
  jest.mock('fs');
7
5
  jest.mock('../../ast-core', () => ({
8
6
  pushFinding: jest.fn(),
9
7
  pushFileFinding: jest.fn()
10
8
  }));
11
9
 
10
+ const glob = require('glob');
11
+ const fs = require('fs');
12
+ const { BDDTDDWorkflowRules } = require('../BDDTDDWorkflowRules');
12
13
  const { pushFileFinding } = require('../../ast-core');
13
14
 
14
15
  describe('BDDTDDWorkflowRules', () => {
@@ -1,9 +1,9 @@
1
+ jest.mock('fs');
2
+
1
3
  const { findDuplicateContent, findBrokenLinks } = require('../documentation-analyzer');
2
4
  const fs = require('fs');
3
5
  const path = require('path');
4
6
 
5
- jest.mock('fs');
6
-
7
7
  describe('documentation-analyzer', () => {
8
8
  beforeEach(() => {
9
9
  jest.clearAllMocks();
@@ -1,3 +1,6 @@
1
+ jest.mock('fs');
2
+ jest.mock('glob');
3
+
1
4
  const {
2
5
  buildDependencyGraph,
3
6
  detectCircularDependencies,
@@ -6,9 +9,6 @@ const {
6
9
  } = require('../monorepo-health-analyzer');
7
10
  const fs = require('fs');
8
11
 
9
- jest.mock('fs');
10
- jest.mock('glob');
11
-
12
12
  describe('monorepo-health-analyzer', () => {
13
13
  beforeEach(() => {
14
14
  jest.clearAllMocks();
@@ -1,7 +1,11 @@
1
+ jest.mock('child_process', () => ({
2
+ execSync: jest.fn(),
3
+ }));
4
+
5
+ const childProcess = require('child_process');
1
6
  const { GitOperations } = require('../GitOperations');
2
- const { execSync } = require('child_process');
3
7
 
4
- jest.mock('child_process');
8
+ const { execSync } = childProcess;
5
9
 
6
10
  function makeMockGitOutput(files) {
7
11
  return files.join('\n') + (files.length > 0 ? '\n' : '');