pumuki-ast-hooks 5.5.51 → 5.5.52

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 (38) hide show
  1. package/docs/MCP_SERVERS.md +16 -2
  2. package/docs/RELEASE_NOTES.md +34 -0
  3. package/hooks/git-status-monitor.ts +0 -5
  4. package/hooks/notify-macos.ts +0 -1
  5. package/hooks/pre-tool-use-evidence-validator.ts +0 -1
  6. package/package.json +2 -2
  7. package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +48 -0
  8. package/scripts/hooks-system/application/services/guard/GuardConfig.js +2 -4
  9. package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +2 -20
  10. package/scripts/hooks-system/application/services/installation/McpConfigurator.js +9 -205
  11. package/scripts/hooks-system/application/services/installation/mcp/McpGlobalConfigCleaner.js +49 -0
  12. package/scripts/hooks-system/application/services/installation/mcp/McpProjectConfigWriter.js +59 -0
  13. package/scripts/hooks-system/application/services/installation/mcp/McpServerConfigBuilder.js +103 -0
  14. package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +146 -5
  15. package/scripts/hooks-system/infrastructure/ast/ast-core.js +1 -13
  16. package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +3 -2
  17. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +17 -9
  18. package/scripts/hooks-system/infrastructure/ast/backend/detectors/god-class-detector.js +2 -1
  19. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +137 -27
  20. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDChecks.js +385 -0
  21. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDRules.js +38 -408
  22. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseAnalyzer.js +397 -34
  23. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMChecks.js +408 -0
  24. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMRules.js +36 -442
  25. package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenExtractor.js +146 -0
  26. package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenParser.js +22 -190
  27. package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenRunner.js +62 -0
  28. package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +42 -47
  29. package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +0 -16
  30. package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +14 -25
  31. package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +0 -10
  32. package/scripts/hooks-system/application/services/installation/HookAssetsInstaller.js +0 -0
  33. package/scripts/hooks-system/application/services/monitoring/EvidenceRefreshRunner.js +0 -161
  34. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/StagedSwiftFilePreparer.js +0 -59
  35. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/SwiftAstRunner.js +0 -51
  36. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/SwiftToolchainResolver.js +0 -57
  37. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSAstAnalysisOrchestrator.js +0 -32
  38. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseChecks.js +0 -350
@@ -1,12 +1,10 @@
1
1
 
2
2
 
3
- const { exec } = require('child_process');
4
- const util = require('util');
5
3
  const fs = require('fs').promises;
6
4
  const path = require('path');
7
5
  const { DomainError } = require('../../../../domain/errors');
8
-
9
- const execPromise = util.promisify(exec);
6
+ const SourceKittenRunner = require('./SourceKittenRunner');
7
+ const SourceKittenExtractor = require('./SourceKittenExtractor');
10
8
 
11
9
  /**
12
10
  * SourceKittenParser
@@ -17,9 +15,11 @@ const execPromise = util.promisify(exec);
17
15
  * @see https://github.com/jpsim/SourceKitten
18
16
  */
19
17
  class SourceKittenParser {
20
- constructor() {
21
- this.sourceKittenPath = '/opt/homebrew/bin/sourcekitten';
22
- this.timeout = 30000;
18
+ constructor({ runner = null, timeout = 30000 } = {}) {
19
+ this.runner = runner || new SourceKittenRunner({ defaultTimeoutMs: timeout });
20
+ this.sourceKittenPath = this.runner.binaryPath;
21
+ this.timeout = timeout;
22
+ this.extractor = new SourceKittenExtractor();
23
23
  }
24
24
 
25
25
  /**
@@ -28,10 +28,8 @@ class SourceKittenParser {
28
28
  */
29
29
  async isInstalled() {
30
30
  try {
31
- const { stdout } = await execPromise(`${this.sourceKittenPath} version`, {
32
- timeout: 5000,
33
- });
34
- return stdout.includes('SourceKitten');
31
+ const { stdout } = await this.runner.version();
32
+ return Boolean(stdout) && stdout.includes('SourceKitten');
35
33
  } catch (error) {
36
34
  console.error('[SourceKitten] Not installed. Run: brew install sourcekitten');
37
35
  return false;
@@ -49,16 +47,13 @@ class SourceKittenParser {
49
47
 
50
48
  await fs.access(absolutePath);
51
49
 
52
- const { stdout, stderr } = await execPromise(
53
- `${this.sourceKittenPath} structure --file "${absolutePath}"`,
54
- { timeout: this.timeout }
55
- );
50
+ const { stdout, stderr } = await this.runner.structure(absolutePath);
56
51
 
57
52
  if (stderr && stderr.includes('error')) {
58
53
  throw new DomainError(`SourceKitten parse error: ${stderr}`, 'PARSE_ERROR');
59
54
  }
60
55
 
61
- const ast = JSON.parse(stdout);
56
+ const ast = stdout;
62
57
 
63
58
  return {
64
59
  filePath: absolutePath,
@@ -89,18 +84,14 @@ class SourceKittenParser {
89
84
  async parseProject(projectPath, moduleName, scheme) {
90
85
  try {
91
86
  const isWorkspace = projectPath.endsWith('.xcworkspace');
92
- const projectFlag = isWorkspace ? '-workspace' : '-project';
93
87
 
94
- const { stdout, stderr } = await execPromise(
95
- `${this.sourceKittenPath} doc ${projectFlag} "${projectPath}" -scheme "${scheme}"`,
96
- { timeout: 120000 }
97
- );
88
+ const { stdout, stderr } = await this.runner.projectDoc(projectPath, scheme, isWorkspace);
98
89
 
99
90
  if (stderr && stderr.includes('error')) {
100
91
  throw new DomainError(`SourceKitten project parse error: ${stderr}`, 'PARSE_ERROR');
101
92
  }
102
93
 
103
- const projectAST = JSON.parse(stdout);
94
+ const projectAST = stdout;
104
95
 
105
96
  return {
106
97
  projectPath,
@@ -132,12 +123,8 @@ class SourceKittenParser {
132
123
  try {
133
124
  const absolutePath = path.resolve(filePath);
134
125
 
135
- const { stdout } = await execPromise(
136
- `${this.sourceKittenPath} syntax --file "${absolutePath}"`,
137
- { timeout: this.timeout }
138
- );
139
-
140
- const syntaxMap = JSON.parse(stdout);
126
+ const { stdout } = await this.runner.syntax(absolutePath);
127
+ const syntaxMap = stdout;
141
128
 
142
129
  return {
143
130
  filePath: absolutePath,
@@ -175,33 +162,7 @@ class SourceKittenParser {
175
162
  * @returns {ClassNode[]}
176
163
  */
177
164
  extractClasses(ast) {
178
- const classes = [];
179
-
180
- const traverse = (nodes) => {
181
- if (!Array.isArray(nodes)) return;
182
-
183
- nodes.forEach(node => {
184
- const kind = node['key.kind'];
185
-
186
- if (kind === 'source.lang.swift.decl.class') {
187
- classes.push({
188
- name: node['key.name'],
189
- line: node['key.line'],
190
- column: node['key.column'],
191
- accessibility: node['key.accessibility'],
192
- inheritedTypes: node['key.inheritedtypes'] || [],
193
- substructure: node['key.substructure'] || [],
194
- });
195
- }
196
-
197
- if (node['key.substructure']) {
198
- traverse(node['key.substructure']);
199
- }
200
- });
201
- };
202
-
203
- traverse(ast.substructure);
204
- return classes;
165
+ return this.extractor.extractClasses(ast);
205
166
  }
206
167
 
207
168
  /**
@@ -210,39 +171,7 @@ class SourceKittenParser {
210
171
  * @returns {FunctionNode[]}
211
172
  */
212
173
  extractFunctions(ast) {
213
- const functions = [];
214
-
215
- const traverse = (nodes) => {
216
- if (!Array.isArray(nodes)) return;
217
-
218
- nodes.forEach(node => {
219
- const kind = node['key.kind'];
220
-
221
- if (kind === 'source.lang.swift.decl.function.method.instance' ||
222
- kind === 'source.lang.swift.decl.function.method.class' ||
223
- kind === 'source.lang.swift.decl.function.method.static' ||
224
- kind === 'source.lang.swift.decl.function.free') {
225
-
226
- functions.push({
227
- name: node['key.name'],
228
- line: node['key.line'],
229
- column: node['key.column'],
230
- kind,
231
- accessibility: node['key.accessibility'],
232
- typename: node['key.typename'],
233
- length: node['key.length'],
234
- bodyLength: node['key.bodylength'],
235
- });
236
- }
237
-
238
- if (node['key.substructure']) {
239
- traverse(node['key.substructure']);
240
- }
241
- });
242
- };
243
-
244
- traverse(ast.substructure);
245
- return functions;
174
+ return this.extractor.extractFunctions(ast);
246
175
  }
247
176
 
248
177
  /**
@@ -251,36 +180,7 @@ class SourceKittenParser {
251
180
  * @returns {PropertyNode[]}
252
181
  */
253
182
  extractProperties(ast) {
254
- const properties = [];
255
-
256
- const traverse = (nodes) => {
257
- if (!Array.isArray(nodes)) return;
258
-
259
- nodes.forEach(node => {
260
- const kind = node['key.kind'];
261
-
262
- if (kind === 'source.lang.swift.decl.var.instance' ||
263
- kind === 'source.lang.swift.decl.var.class' ||
264
- kind === 'source.lang.swift.decl.var.static') {
265
-
266
- properties.push({
267
- name: node['key.name'],
268
- line: node['key.line'],
269
- column: node['key.column'],
270
- kind,
271
- typename: node['key.typename'],
272
- accessibility: node['key.accessibility'],
273
- });
274
- }
275
-
276
- if (node['key.substructure']) {
277
- traverse(node['key.substructure']);
278
- }
279
- });
280
- };
281
-
282
- traverse(ast.substructure);
283
- return properties;
183
+ return this.extractor.extractProperties(ast);
284
184
  }
285
185
 
286
186
  /**
@@ -289,33 +189,7 @@ class SourceKittenParser {
289
189
  * @returns {ProtocolNode[]}
290
190
  */
291
191
  extractProtocols(ast) {
292
- const protocols = [];
293
-
294
- const traverse = (nodes) => {
295
- if (!Array.isArray(nodes)) return;
296
-
297
- nodes.forEach(node => {
298
- const kind = node['key.kind'];
299
-
300
- if (kind === 'source.lang.swift.decl.protocol') {
301
- protocols.push({
302
- name: node['key.name'],
303
- line: node['key.line'],
304
- column: node['key.column'],
305
- accessibility: node['key.accessibility'],
306
- inheritedTypes: node['key.inheritedtypes'] || [],
307
- substructure: node['key.substructure'] || [],
308
- });
309
- }
310
-
311
- if (node['key.substructure']) {
312
- traverse(node['key.substructure']);
313
- }
314
- });
315
- };
316
-
317
- traverse(ast.substructure);
318
- return protocols;
192
+ return this.extractor.extractProtocols(ast);
319
193
  }
320
194
 
321
195
  /**
@@ -324,19 +198,7 @@ class SourceKittenParser {
324
198
  * @returns {boolean}
325
199
  */
326
200
  usesSwiftUI(ast) {
327
- const hasViewProtocol = (nodes) => {
328
- if (!Array.isArray(nodes)) return false;
329
-
330
- return nodes.some(node => {
331
- const inheritedTypes = node['key.inheritedtypes'] || [];
332
- if (inheritedTypes.some(t => t['key.name'] === 'View')) {
333
- return true;
334
- }
335
- return hasViewProtocol(node['key.substructure'] || []);
336
- });
337
- };
338
-
339
- return hasViewProtocol(ast.substructure);
201
+ return this.extractor.usesSwiftUI(ast);
340
202
  }
341
203
 
342
204
  /**
@@ -345,22 +207,7 @@ class SourceKittenParser {
345
207
  * @returns {boolean}
346
208
  */
347
209
  usesUIKit(ast) {
348
- const hasUIKitBase = (nodes) => {
349
- if (!Array.isArray(nodes)) return false;
350
-
351
- return nodes.some(node => {
352
- const inheritedTypes = node['key.inheritedtypes'] || [];
353
- if (inheritedTypes.some(t =>
354
- t['key.name'] === 'UIViewController' ||
355
- t['key.name'] === 'UIView'
356
- )) {
357
- return true;
358
- }
359
- return hasUIKitBase(node['key.substructure'] || []);
360
- });
361
- };
362
-
363
- return hasUIKitBase(ast.substructure);
210
+ return this.extractor.usesUIKit(ast);
364
211
  }
365
212
 
366
213
  /**
@@ -370,22 +217,7 @@ class SourceKittenParser {
370
217
  * @returns {ForceUnwrap[]}
371
218
  */
372
219
  detectForceUnwraps(syntaxMap, fileContent) {
373
- const forceUnwraps = [];
374
- const lines = fileContent.split('\n');
375
-
376
- lines.forEach((line, index) => {
377
- const matches = [...line.matchAll(/(\w+)\s*!/g)];
378
- matches.forEach(match => {
379
- forceUnwraps.push({
380
- line: index + 1,
381
- column: match.index + 1,
382
- variable: match[1],
383
- context: line.trim(),
384
- });
385
- });
386
- });
387
-
388
- return forceUnwraps;
220
+ return this.extractor.detectForceUnwraps(syntaxMap, fileContent);
389
221
  }
390
222
  }
391
223
 
@@ -0,0 +1,62 @@
1
+ const { exec } = require('child_process');
2
+ const util = require('util');
3
+ const path = require('path');
4
+
5
+ const execPromise = util.promisify(exec);
6
+
7
+ class SourceKittenRunner {
8
+ constructor({
9
+ binaryPath = '/opt/homebrew/bin/sourcekitten',
10
+ defaultTimeoutMs = 30000,
11
+ logger = console
12
+ } = {}) {
13
+ this.binaryPath = binaryPath;
14
+ this.defaultTimeoutMs = defaultTimeoutMs;
15
+ this.logger = logger;
16
+ }
17
+
18
+ async version() {
19
+ return this.execRaw(`${this.binaryPath} version`, 5000);
20
+ }
21
+
22
+ async structure(filePath) {
23
+ const cmd = `${this.binaryPath} structure --file "${path.resolve(filePath)}"`;
24
+ return this.execJson(cmd, this.defaultTimeoutMs);
25
+ }
26
+
27
+ async syntax(filePath) {
28
+ const cmd = `${this.binaryPath} syntax --file "${path.resolve(filePath)}"`;
29
+ return this.execJson(cmd, this.defaultTimeoutMs);
30
+ }
31
+
32
+ async projectDoc(projectPath, scheme, isWorkspace = false) {
33
+ const projectFlag = isWorkspace ? '-workspace' : '-project';
34
+ const cmd = `${this.binaryPath} doc ${projectFlag} "${projectPath}" -scheme "${scheme}"`;
35
+ return this.execJson(cmd, 120000);
36
+ }
37
+
38
+ async execJson(command, timeoutMs) {
39
+ const { stdout, stderr } = await this.execRaw(command, timeoutMs);
40
+ return { stdout: this.safeJson(stdout), stderr };
41
+ }
42
+
43
+ async execRaw(command, timeoutMs) {
44
+ try {
45
+ return await execPromise(command, { timeout: timeoutMs });
46
+ } catch (error) {
47
+ const msg = error && error.message ? error.message : String(error);
48
+ this.logger?.debug?.('[SourceKittenRunner] exec error', { command, error: msg });
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ safeJson(text) {
54
+ try {
55
+ return JSON.parse(text);
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+ }
61
+
62
+ module.exports = SourceKittenRunner;
@@ -967,7 +967,6 @@ async function aiGateCheck() {
967
967
  }
968
968
  }
969
969
 
970
- let mandatoryRules = null;
971
970
  let detectedPlatforms = [];
972
971
  try {
973
972
  const orchestrator = getCompositionRoot().getOrchestrator();
@@ -975,63 +974,66 @@ async function aiGateCheck() {
975
974
  if (contextDecision && contextDecision.platforms) {
976
975
  detectedPlatforms = contextDecision.platforms.map(p => p.platform || p);
977
976
  }
978
- const fallbackPlatforms = ['backend', 'frontend', 'ios', 'android'];
979
- const platformsForRules = (detectedPlatforms.length > 0 ? detectedPlatforms : fallbackPlatforms)
980
- .filter(Boolean);
981
- const normalizedPlatforms = Array.from(new Set(platformsForRules));
982
- const rulesData = await loadPlatformRules(normalizedPlatforms);
983
- const rulesSample = rulesData.criticalRules.slice(0, 5).map(r => r.rule || r);
984
- const rulesCount = rulesData.criticalRules.length;
977
+ } catch (err) {
978
+ if (process.env.DEBUG) {
979
+ process.stderr.write(`[MCP] analyzeContext failed, using fallback: ${err.message}\n`);
980
+ }
981
+ }
982
+
983
+ if (detectedPlatforms.length === 0) {
984
+ try {
985
+ const PlatformDetectionService = require('../../application/services/PlatformDetectionService');
986
+ const detector = new PlatformDetectionService();
987
+ detectedPlatforms = await detector.detectPlatforms(REPO_ROOT);
988
+ } catch (err) {
989
+ if (process.env.DEBUG) {
990
+ process.stderr.write(`[MCP] PlatformDetectionService failed: ${err.message}\n`);
991
+ }
992
+ }
993
+ }
994
+
995
+ const platformsToLoad = detectedPlatforms.length > 0
996
+ ? detectedPlatforms.map(p => String(p).toLowerCase())
997
+ : ['backend', 'frontend', 'ios', 'android'];
998
+
999
+ let mandatoryRules = null;
1000
+ try {
1001
+ const rulesData = await loadPlatformRules(platformsToLoad);
985
1002
  mandatoryRules = {
986
- platforms: normalizedPlatforms,
987
- criticalRules: rulesData.criticalRules,
988
- rulesLoaded: Object.keys(rulesData.rules),
989
- totalRulesCount: rulesCount,
990
- rulesSample: rulesSample,
991
- proofOfRead: `✅ VERIFIED: ${rulesCount} critical rules loaded from ${Object.keys(rulesData.rules).join(', ')}`
1003
+ platforms: platformsToLoad,
1004
+ criticalRules: rulesData.criticalRules || [],
1005
+ rulesLoaded: Object.keys(rulesData.rules || {}),
1006
+ warning: '⚠️ AI MUST read and follow these rules before ANY code generation or modification'
992
1007
  };
993
- } catch (error) {
1008
+ } catch (err) {
994
1009
  if (process.env.DEBUG) {
995
- process.stderr.write(`[MCP] Failed to load mandatory rules: ${error.message}\n`);
1010
+ process.stderr.write(`[MCP] loadPlatformRules failed: ${err.message}\n`);
996
1011
  }
997
-
998
- const fallbackPlatforms = ['backend', 'frontend', 'ios', 'android'];
999
- const normalizedPlatforms = Array.from(new Set((detectedPlatforms.length > 0 ? detectedPlatforms : fallbackPlatforms).filter(Boolean)));
1000
1012
  mandatoryRules = {
1001
- platforms: normalizedPlatforms,
1013
+ platforms: platformsToLoad,
1002
1014
  criticalRules: [],
1003
1015
  rulesLoaded: [],
1004
- status: 'FAILED_TO_LOAD',
1005
- error: `Failed to load rules content: ${error && error.message ? error.message : String(error)}`
1016
+ warning: '⚠️ Rules loading failed but AI MUST still follow project conventions',
1017
+ error: err.message
1006
1018
  };
1007
1019
  }
1008
1020
 
1009
- const rulesLoadedSuccessfully = mandatoryRules &&
1010
- mandatoryRules.criticalRules &&
1011
- mandatoryRules.criticalRules.length > 0;
1012
-
1013
- if (!rulesLoadedSuccessfully) {
1014
- violations.push('❌ RULES_NOT_LOADED: Critical platform rules could not be loaded. AI cannot proceed without reading mandatory rules.');
1015
- }
1016
-
1017
- const finalBlocked = isBlocked || !rulesLoadedSuccessfully;
1018
-
1019
1021
  return {
1020
- status: finalBlocked ? 'BLOCKED' : 'ALLOWED',
1022
+ status: isBlocked ? 'BLOCKED' : 'ALLOWED',
1021
1023
  timestamp: new Date().toISOString(),
1022
1024
  branch: currentBranch,
1023
1025
  violations,
1024
1026
  warnings,
1025
1027
  autoFixes,
1026
- mandatory_rules: rulesLoadedSuccessfully
1027
- ? { ...mandatoryRules, status: 'LOADED_OK' }
1028
- : mandatoryRules,
1029
- summary: finalBlocked
1028
+ mandatory_rules: mandatoryRules,
1029
+ summary: isBlocked
1030
1030
  ? `🚫 BLOCKED: ${violations.length} violation(s). Fix before proceeding.`
1031
- : `🚦 ALLOWED: Gate passed. ${mandatoryRules.totalRulesCount} critical rules loaded and verified.`,
1032
- instructions: finalBlocked
1031
+ : `🚦 ALLOWED: Gate passed.${warnings.length > 0 ? ` ${warnings.length} warning(s).` : ''}`,
1032
+ instructions: isBlocked
1033
1033
  ? 'DO NOT proceed with user task. Announce violations and fix them first.'
1034
- : `✅ ${mandatoryRules.totalRulesCount} RULES LOADED. Sample: ${mandatoryRules.rulesSample.slice(0, 2).join(' | ')}... Review ALL rules in mandatory_rules.criticalRules before ANY code generation.`
1034
+ : mandatoryRules
1035
+ ? `You may proceed with user task. CRITICAL: Review mandatory_rules.criticalRules BEFORE generating ANY code.`
1036
+ : 'You may proceed with user task.'
1035
1037
  };
1036
1038
  };
1037
1039
 
@@ -1048,13 +1050,6 @@ async function aiGateCheck() {
1048
1050
  violations: ['❌ GATE_TIMEOUT: AI gate check timed out. Retry or run ai-start manually.'],
1049
1051
  warnings: [],
1050
1052
  autoFixes: [],
1051
- mandatory_rules: {
1052
- platforms: ['backend', 'frontend', 'ios', 'android'],
1053
- criticalRules: [],
1054
- rulesLoaded: [],
1055
- warning: '⚠️ AI MUST read and follow these rules before ANY code generation or modification',
1056
- error: 'Rules could not be loaded due to timeout'
1057
- },
1058
1053
  summary: '🚫 BLOCKED: Gate check timed out.',
1059
1054
  instructions: 'DO NOT proceed with user task. Retry the gate check.'
1060
1055
  };
@@ -11,22 +11,6 @@ describe('intelligent-audit', () => {
11
11
  const mod = require('../intelligent-audit');
12
12
  expect(typeof mod.runIntelligentAudit).toBe('function');
13
13
  });
14
-
15
- it('should filter staged violations strictly (no substring matches, no .audit_tmp)', () => {
16
- const mod = require('../intelligent-audit');
17
-
18
- expect(typeof mod.isViolationInStagedFiles).toBe('function');
19
-
20
- const stagedSet = new Set([
21
- 'apps/ios/Application/AppCoordinator.swift'
22
- ]);
23
-
24
- expect(mod.isViolationInStagedFiles('apps/ios/Application/AppCoordinator.swift', stagedSet)).toBe(true);
25
- expect(mod.isViolationInStagedFiles('apps/ios/Application/AppCoordinator.swift.backup', stagedSet)).toBe(false);
26
- expect(mod.isViolationInStagedFiles('.audit_tmp/AppCoordinator.123.staged.swift', stagedSet)).toBe(false);
27
- expect(mod.isViolationInStagedFiles('some/dir/.audit_tmp/AppCoordinator.123.staged.swift', stagedSet)).toBe(false);
28
- expect(mod.isViolationInStagedFiles('apps/ios/Application/AppCoordinator', stagedSet)).toBe(false);
29
- });
30
14
  });
31
15
 
32
16
  describe('AI_EVIDENCE.json structure validation', () => {
@@ -235,28 +235,6 @@ function toRepoRelativePath(filePath) {
235
235
  return normalized;
236
236
  }
237
237
 
238
- function isAuditTmpPath(repoRelativePath) {
239
- const normalized = normalizePathForMatch(repoRelativePath);
240
- return normalized.startsWith('.audit_tmp/') || normalized.includes('/.audit_tmp/');
241
- }
242
-
243
- function isViolationInStagedFiles(violationPath, stagedSet) {
244
- if (!violationPath) {
245
- return false;
246
- }
247
-
248
- const repoRelative = toRepoRelativePath(violationPath);
249
- if (!repoRelative) {
250
- return false;
251
- }
252
-
253
- if (isAuditTmpPath(repoRelative)) {
254
- return false;
255
- }
256
-
257
- return stagedSet.has(repoRelative);
258
- }
259
-
260
238
  function resolveAuditTmpDir() {
261
239
  const configured = (env.get('AUDIT_TMP', '') || '').trim();
262
240
  if (configured.length > 0) {
@@ -295,7 +273,18 @@ async function runIntelligentAudit() {
295
273
 
296
274
  const stagedViolations = rawViolations.filter(v => {
297
275
  const violationPath = toRepoRelativePath(v.filePath || v.file || '');
298
- return isViolationInStagedFiles(violationPath, stagedSet);
276
+ if (!violationPath) {
277
+ return false;
278
+ }
279
+ if (stagedSet.has(violationPath)) {
280
+ return true;
281
+ }
282
+ for (const sf of stagedSet) {
283
+ if (sf && (violationPath === sf || violationPath.endsWith('/' + sf) || violationPath.includes('/' + sf))) {
284
+ return true;
285
+ }
286
+ }
287
+ return false;
299
288
  });
300
289
 
301
290
  console.log(`[Intelligent Audit] Gate scope: STAGING (${stagedFiles.length} files)`);
@@ -561,7 +550,7 @@ async function updateAIEvidence(violations, gateResult, tokenUsage) {
561
550
  file: v.filePath || v.file || 'unknown',
562
551
  line: v.line || null,
563
552
  severity: v.severity,
564
- rule_id: ruleId,
553
+ rule: ruleId,
565
554
  message: v.message || v.description || '',
566
555
  category: v.category || deriveCategoryFromRuleId(ruleId),
567
556
  intelligent_evaluation: v.intelligentEvaluation || false,
@@ -694,4 +683,4 @@ if (require.main === module) {
694
683
  });
695
684
  }
696
685
 
697
- module.exports = { runIntelligentAudit, isViolationInStagedFiles, toRepoRelativePath };
686
+ module.exports = { runIntelligentAudit };
@@ -198,16 +198,6 @@ verify_atomic_commit() {
198
198
  done
199
199
 
200
200
  if (( root_count > 1 )); then
201
- local has_scripts=0
202
- local has_tests=0
203
- for root in $roots_list; do
204
- [[ "$root" == "scripts" ]] && has_scripts=1
205
- [[ "$root" == "tests" ]] && has_tests=1
206
- done
207
- if [[ $has_scripts -eq 1 && $has_tests -eq 1 && $root_count -eq 2 ]]; then
208
- printf "${GREEN}✅ Commit %s toca scripts + tests (permitido para bugfixes/features con tests).${NC}\n" "$commit"
209
- return 0
210
- fi
211
201
  printf "${RED}❌ Commit %s toca múltiples raíces (%s). Divide los cambios en commits atómicos.${NC}\n" "$commit" "$roots_list"
212
202
  return 1
213
203
  fi