pumuki-ast-hooks 5.5.49 → 5.5.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hooks/git-status-monitor.ts +5 -0
- package/hooks/notify-macos.ts +1 -0
- package/hooks/pre-tool-use-evidence-validator.ts +1 -0
- package/package.json +1 -1
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +56 -0
- package/scripts/hooks-system/application/services/guard/GuardConfig.js +4 -2
- package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +20 -2
- package/scripts/hooks-system/application/services/installation/HookAssetsInstaller.js +0 -0
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +84 -18
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +5 -146
- package/scripts/hooks-system/application/services/monitoring/EvidenceRefreshRunner.js +161 -0
- package/scripts/hooks-system/infrastructure/ast/ast-core.js +13 -1
- package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +2 -3
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +1 -4
- package/scripts/hooks-system/infrastructure/ast/backend/detectors/god-class-detector.js +1 -2
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/StagedSwiftFilePreparer.js +59 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/SwiftAstRunner.js +51 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/SwiftToolchainResolver.js +57 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +27 -137
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSAstAnalysisOrchestrator.js +32 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseAnalyzer.js +34 -397
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseChecks.js +350 -0
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +407 -5
- package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +16 -0
- package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +1 -1
- package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +33 -11
|
@@ -45,6 +45,18 @@ function getRepoRoot() {
|
|
|
45
45
|
*/
|
|
46
46
|
function shouldIgnore(file) {
|
|
47
47
|
const p = file.replace(/\\/g, "/");
|
|
48
|
+
try {
|
|
49
|
+
const configPaths = loadExclusions()?.exclusions?.paths;
|
|
50
|
+
if (configPaths && typeof configPaths === 'object') {
|
|
51
|
+
for (const [key, enabled] of Object.entries(configPaths)) {
|
|
52
|
+
if (enabled && p.includes(key)) return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (process.env.DEBUG) {
|
|
57
|
+
console.debug(`[ast-core] Failed to load exclusions for shouldIgnore: ${error.message || String(error)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
48
60
|
if (p.includes("node_modules/")) return true;
|
|
49
61
|
if (p.includes("/.next/")) return true;
|
|
50
62
|
if (p.includes("/dist/")) return true;
|
|
@@ -125,7 +137,7 @@ let exclusionsConfig = null;
|
|
|
125
137
|
function loadExclusions() {
|
|
126
138
|
if (exclusionsConfig) return exclusionsConfig;
|
|
127
139
|
try {
|
|
128
|
-
const configPath = path.join(
|
|
140
|
+
const configPath = path.join(getRepoRoot(), 'config', 'ast-exclusions.json');
|
|
129
141
|
if (fs.existsSync(configPath)) {
|
|
130
142
|
exclusionsConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
131
143
|
}
|
|
@@ -22,7 +22,7 @@ function formatLocalTimestamp(date = new Date()) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const astModulesPath = __dirname;
|
|
25
|
-
const { createProject, platformOf, mapToLevel } = require(path.join(astModulesPath, "ast-core"));
|
|
25
|
+
const { createProject, platformOf, mapToLevel, shouldIgnore: coreShouldIgnore } = require(path.join(astModulesPath, "ast-core"));
|
|
26
26
|
const MacOSNotificationAdapter = require(path.join(__dirname, '../adapters/MacOSNotificationAdapter'));
|
|
27
27
|
const { runBackendIntelligence } = require(path.join(astModulesPath, "backend/ast-backend"));
|
|
28
28
|
const { runFrontendIntelligence } = require(path.join(astModulesPath, "frontend/ast-frontend"));
|
|
@@ -138,8 +138,6 @@ function runProjectHardcodedThresholdAudit(root, allFiles, findings) {
|
|
|
138
138
|
if (p.includes('/build/')) return true;
|
|
139
139
|
if (p.includes('/coverage/')) return true;
|
|
140
140
|
if (p.includes('/.audit_tmp/')) return true;
|
|
141
|
-
if (p.includes('/infrastructure/ast/')) return true;
|
|
142
|
-
if (p.includes('/scripts/hooks-system/')) return true;
|
|
143
141
|
return false;
|
|
144
142
|
};
|
|
145
143
|
|
|
@@ -706,6 +704,7 @@ function listSourceFiles(root) {
|
|
|
706
704
|
*/
|
|
707
705
|
function shouldIgnore(file) {
|
|
708
706
|
const p = file.replace(/\\/g, "/");
|
|
707
|
+
if (typeof coreShouldIgnore === 'function' && coreShouldIgnore(p)) return true;
|
|
709
708
|
if (p.includes("node_modules/")) return true;
|
|
710
709
|
if (p.includes("/.cursor/")) return true;
|
|
711
710
|
if (/\.bak/i.test(p)) return true;
|
|
@@ -126,13 +126,10 @@ function runBackendIntelligence(project, findings, platform) {
|
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
128
|
// NO excluir archivos AST - la librería debe auto-auditarse
|
|
129
|
-
if (isTestFile(filePath)) return;
|
|
130
|
-
|
|
131
129
|
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
132
130
|
const className = cls.getName() || '';
|
|
133
131
|
const isValueObject = /Metrics|ValueObject|VO$|Dto$|Entity$/.test(className);
|
|
134
|
-
|
|
135
|
-
if (isValueObject || isTestClass) return;
|
|
132
|
+
if (isValueObject) return;
|
|
136
133
|
|
|
137
134
|
const methodsCount = cls.getMethods().length;
|
|
138
135
|
const propertiesCount = cls.getProperties().length;
|
|
@@ -7,8 +7,7 @@ function analyzeGodClasses(sourceFile, findings, { SyntaxKind, pushFinding, godC
|
|
|
7
7
|
sourceFile.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
8
8
|
const className = cls.getName() || '';
|
|
9
9
|
const isValueObject = /Metrics|ValueObject|VO$|Dto$|Entity$/.test(className);
|
|
10
|
-
|
|
11
|
-
if (isValueObject || isTestClass) return;
|
|
10
|
+
if (isValueObject) return;
|
|
12
11
|
|
|
13
12
|
const methodsCount = cls.getMethods().length;
|
|
14
13
|
const propertiesCount = cls.getProperties().length;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
class StagedSwiftFilePreparer {
|
|
7
|
+
constructor({ repoRoot, auditTmpDir }) {
|
|
8
|
+
this.repoRoot = repoRoot;
|
|
9
|
+
this.auditTmpDir = auditTmpDir || path.join(repoRoot, '.audit_tmp');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
safeTempFilePath(displayPath) {
|
|
13
|
+
const hash = crypto.createHash('sha1').update(String(displayPath)).digest('hex').slice(0, 10);
|
|
14
|
+
const base = path.basename(displayPath, '.swift');
|
|
15
|
+
const filename = `${base}.${hash}.staged.swift`;
|
|
16
|
+
return path.join(this.auditTmpDir, filename);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
readStagedFileContent(relPath) {
|
|
20
|
+
try {
|
|
21
|
+
return execSync(`git show :"${relPath}"`, {
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
cwd: this.repoRoot,
|
|
24
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (process.env.DEBUG) {
|
|
28
|
+
console.debug(`[StagedSwiftFilePreparer] Failed to read staged file ${relPath}: ${error.message}`);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
prepare({ filePath, stagedRelPath, stagingOnly }) {
|
|
35
|
+
let parsePath = filePath;
|
|
36
|
+
let contentOverride = null;
|
|
37
|
+
|
|
38
|
+
if (stagingOnly && stagedRelPath) {
|
|
39
|
+
const stagedContent = this.readStagedFileContent(stagedRelPath);
|
|
40
|
+
if (typeof stagedContent === 'string') {
|
|
41
|
+
const tmpPath = this.safeTempFilePath(stagedRelPath);
|
|
42
|
+
try {
|
|
43
|
+
fs.mkdirSync(path.dirname(tmpPath), { recursive: true });
|
|
44
|
+
fs.writeFileSync(tmpPath, stagedContent, 'utf8');
|
|
45
|
+
parsePath = tmpPath;
|
|
46
|
+
contentOverride = stagedContent;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (process.env.DEBUG) {
|
|
49
|
+
console.debug(`[StagedSwiftFilePreparer] Failed to write temp staged file ${tmpPath}: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { parsePath, contentOverride };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { StagedSwiftFilePreparer };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
class SwiftAstRunner {
|
|
4
|
+
constructor({ toolchain }) {
|
|
5
|
+
this.toolchain = toolchain;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
runSwiftSyntax(filePath, displayPath, findings) {
|
|
9
|
+
if (!this.toolchain.swiftSyntaxPath) return;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const result = execSync(`"${this.toolchain.swiftSyntaxPath}" "${filePath}"`, {
|
|
13
|
+
encoding: 'utf8',
|
|
14
|
+
timeout: 30000,
|
|
15
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
16
|
+
});
|
|
17
|
+
const violations = JSON.parse(result);
|
|
18
|
+
violations.forEach(v => {
|
|
19
|
+
findings.push({
|
|
20
|
+
ruleId: v.ruleId,
|
|
21
|
+
severity: String(v.severity || '').toUpperCase(),
|
|
22
|
+
filePath: displayPath || filePath,
|
|
23
|
+
line: v.line,
|
|
24
|
+
column: v.column,
|
|
25
|
+
message: v.message,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('[SwiftAstRunner] Error parsing file with SwiftSyntax:', error.message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
parseSourceKitten(filePath) {
|
|
34
|
+
if (!this.toolchain.checkSourceKitten()) return null;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const result = execSync(
|
|
38
|
+
`${this.toolchain.sourceKittenPath} structure --file "${filePath}"`,
|
|
39
|
+
{ encoding: 'utf8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
40
|
+
);
|
|
41
|
+
return JSON.parse(result);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (process.env.DEBUG) {
|
|
44
|
+
console.debug(`[SwiftAstRunner] SourceKitten parse failed for ${filePath}: ${error.message}`);
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { SwiftAstRunner };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const env = require(path.join(__dirname, '../../../../config/env'));
|
|
5
|
+
|
|
6
|
+
class SwiftToolchainResolver {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.sourceKittenPath = options.sourceKittenPath || '/opt/homebrew/bin/sourcekitten';
|
|
9
|
+
this.projectRoot = options.projectRoot || this.detectRepoRoot();
|
|
10
|
+
this.swiftSyntaxPath = this.findSwiftSyntaxBinary(this.projectRoot);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
detectRepoRoot() {
|
|
14
|
+
try {
|
|
15
|
+
return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
|
|
16
|
+
} catch {
|
|
17
|
+
return process.cwd();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
resolveAuditTmpDir(repoRoot) {
|
|
22
|
+
const configured = String(env.get('AUDIT_TMP', '') || '').trim();
|
|
23
|
+
if (configured.length > 0) {
|
|
24
|
+
return path.isAbsolute(configured) ? configured : path.join(repoRoot, configured);
|
|
25
|
+
}
|
|
26
|
+
return path.join(repoRoot, '.audit_tmp');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
checkSourceKitten() {
|
|
30
|
+
try {
|
|
31
|
+
execSync(`${this.sourceKittenPath} version`, { stdio: 'pipe' });
|
|
32
|
+
return true;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (process.env.DEBUG) {
|
|
35
|
+
console.debug(`[SwiftToolchainResolver] SourceKitten not available at ${this.sourceKittenPath}: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
findSwiftSyntaxBinary(projectRoot) {
|
|
42
|
+
const possiblePaths = [
|
|
43
|
+
path.join(__dirname, '../../../../../CustomLintRules/.build/debug/swift-ast-analyzer'),
|
|
44
|
+
path.join(projectRoot, 'CustomLintRules/.build/debug/swift-ast-analyzer'),
|
|
45
|
+
path.join(projectRoot, '.build/debug/swift-ast-analyzer'),
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (const p of possiblePaths) {
|
|
49
|
+
if (fs.existsSync(p)) {
|
|
50
|
+
return p;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { SwiftToolchainResolver };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const { execSync } = require('child_process');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const crypto = require('crypto');
|
|
5
4
|
const env = require(path.join(__dirname, '../../../../config/env'));
|
|
6
5
|
const {
|
|
7
6
|
resetCollections,
|
|
@@ -9,13 +8,21 @@ const {
|
|
|
9
8
|
analyzeCollectedNodes,
|
|
10
9
|
finalizeGodClassDetection,
|
|
11
10
|
} = require('../detectors/ios-ast-intelligent-strategies');
|
|
11
|
+
const { SwiftToolchainResolver } = require('./SwiftToolchainResolver');
|
|
12
|
+
const { StagedSwiftFilePreparer } = require('./StagedSwiftFilePreparer');
|
|
13
|
+
const { SwiftAstRunner } = require('./SwiftAstRunner');
|
|
14
|
+
const { iOSAstAnalysisOrchestrator } = require('./iOSAstAnalysisOrchestrator');
|
|
12
15
|
|
|
13
16
|
class iOSASTIntelligentAnalyzer {
|
|
14
17
|
constructor(findings) {
|
|
15
18
|
this.findings = findings;
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
+
this.toolchain = new SwiftToolchainResolver();
|
|
20
|
+
this.sourceKittenPath = this.toolchain.sourceKittenPath;
|
|
21
|
+
this.isAvailable = this.toolchain.checkSourceKitten();
|
|
22
|
+
this.swiftSyntaxPath = this.toolchain.swiftSyntaxPath;
|
|
23
|
+
this.hasSwiftSyntax = Boolean(this.swiftSyntaxPath);
|
|
24
|
+
this.swiftRunner = new SwiftAstRunner({ toolchain: this.toolchain });
|
|
25
|
+
this.analysisOrchestrator = new iOSAstAnalysisOrchestrator();
|
|
19
26
|
this.fileContent = '';
|
|
20
27
|
this.currentFilePath = '';
|
|
21
28
|
this.godClassCandidates = [];
|
|
@@ -29,99 +36,6 @@ class iOSASTIntelligentAnalyzer {
|
|
|
29
36
|
this.closures = [];
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
resolveAuditTmpDir(repoRoot) {
|
|
33
|
-
const configured = String(env.get('AUDIT_TMP', '') || '').trim();
|
|
34
|
-
if (configured.length > 0) {
|
|
35
|
-
return path.isAbsolute(configured) ? configured : path.join(repoRoot, configured);
|
|
36
|
-
}
|
|
37
|
-
return path.join(repoRoot, '.audit_tmp');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
safeTempFilePath(repoRoot, displayPath) {
|
|
41
|
-
const tmpDir = this.resolveAuditTmpDir(repoRoot);
|
|
42
|
-
const hash = crypto.createHash('sha1').update(String(displayPath)).digest('hex').slice(0, 10);
|
|
43
|
-
const base = path.basename(displayPath, '.swift');
|
|
44
|
-
const filename = `${base}.${hash}.staged.swift`;
|
|
45
|
-
return path.join(tmpDir, filename);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
readStagedFileContent(repoRoot, relPath) {
|
|
49
|
-
try {
|
|
50
|
-
return execSync(`git show :"${relPath}"`, { encoding: 'utf8', cwd: repoRoot, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
51
|
-
} catch (error) {
|
|
52
|
-
if (process.env.DEBUG) {
|
|
53
|
-
console.debug(`[iOSASTIntelligentAnalyzer] Failed to read staged file ${relPath}: ${error.message}`);
|
|
54
|
-
}
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
checkSourceKitten() {
|
|
60
|
-
try {
|
|
61
|
-
execSync(`${this.sourceKittenPath} version`, { stdio: 'pipe' });
|
|
62
|
-
return true;
|
|
63
|
-
} catch (error) {
|
|
64
|
-
if (process.env.DEBUG) {
|
|
65
|
-
console.debug(`[iOSASTIntelligentAnalyzer] SourceKitten not available at ${this.sourceKittenPath}: ${error.message}`);
|
|
66
|
-
}
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
checkSwiftSyntax() {
|
|
72
|
-
const projectRoot = require('child_process')
|
|
73
|
-
.execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' })
|
|
74
|
-
.trim();
|
|
75
|
-
|
|
76
|
-
const possiblePaths = [
|
|
77
|
-
path.join(__dirname, '../../../../../CustomLintRules/.build/debug/swift-ast-analyzer'),
|
|
78
|
-
path.join(projectRoot, 'CustomLintRules/.build/debug/swift-ast-analyzer'),
|
|
79
|
-
path.join(projectRoot, '.build/debug/swift-ast-analyzer')
|
|
80
|
-
];
|
|
81
|
-
for (const p of possiblePaths) {
|
|
82
|
-
if (fs.existsSync(p)) {
|
|
83
|
-
this.swiftSyntaxPath = p;
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
analyzeWithSwiftSyntax(filePath, displayPath = null) {
|
|
91
|
-
if (!this.swiftSyntaxPath) return;
|
|
92
|
-
try {
|
|
93
|
-
const result = execSync(`"${this.swiftSyntaxPath}" "${filePath}"`, {
|
|
94
|
-
encoding: 'utf8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe']
|
|
95
|
-
});
|
|
96
|
-
const violations = JSON.parse(result);
|
|
97
|
-
for (const v of violations) {
|
|
98
|
-
const reportedPath = displayPath || filePath;
|
|
99
|
-
this.findings.push({
|
|
100
|
-
ruleId: v.ruleId, severity: v.severity, filePath: reportedPath,
|
|
101
|
-
line: v.line, column: v.column, message: v.message
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error('[iOSASTIntelligentAnalyzer] Error parsing file:', error.message);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
parseFile(filePath) {
|
|
110
|
-
if (!this.isAvailable) return null;
|
|
111
|
-
try {
|
|
112
|
-
const result = execSync(
|
|
113
|
-
`${this.sourceKittenPath} structure --file "${filePath}"`,
|
|
114
|
-
{ encoding: 'utf8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
115
|
-
);
|
|
116
|
-
return JSON.parse(result);
|
|
117
|
-
} catch (error) {
|
|
118
|
-
if (process.env.DEBUG) {
|
|
119
|
-
console.debug(`[iOSASTIntelligentAnalyzer] SourceKitten parse failed for ${filePath}: ${error.message}`);
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
39
|
analyzeFile(filePath, options = {}) {
|
|
126
40
|
if (!filePath || !String(filePath).endsWith('.swift')) return;
|
|
127
41
|
|
|
@@ -129,53 +43,29 @@ class iOSASTIntelligentAnalyzer {
|
|
|
129
43
|
const displayPath = options.displayPath || filePath;
|
|
130
44
|
const stagedRelPath = options.stagedRelPath || null;
|
|
131
45
|
const stagingOnly = env.get('STAGING_ONLY_MODE', '0') === '1';
|
|
46
|
+
const auditTmpDir = this.toolchain.resolveAuditTmpDir(repoRoot);
|
|
47
|
+
const stagedPreparer = new StagedSwiftFilePreparer({ repoRoot, auditTmpDir });
|
|
132
48
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (typeof stagedContent === 'string') {
|
|
139
|
-
const tmpPath = this.safeTempFilePath(repoRoot, stagedRelPath);
|
|
140
|
-
try {
|
|
141
|
-
fs.mkdirSync(path.dirname(tmpPath), { recursive: true });
|
|
142
|
-
fs.writeFileSync(tmpPath, stagedContent, 'utf8');
|
|
143
|
-
parsePath = tmpPath;
|
|
144
|
-
contentOverride = stagedContent;
|
|
145
|
-
} catch (error) {
|
|
146
|
-
if (process.env.DEBUG) {
|
|
147
|
-
console.debug(`[iOSASTIntelligentAnalyzer] Failed to write temp staged file ${tmpPath}: ${error.message}`);
|
|
148
|
-
}
|
|
149
|
-
// Fall back to working tree file
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
49
|
+
const { parsePath, contentOverride } = stagedPreparer.prepare({
|
|
50
|
+
filePath,
|
|
51
|
+
stagedRelPath,
|
|
52
|
+
stagingOnly
|
|
53
|
+
});
|
|
153
54
|
|
|
154
55
|
if (this.hasSwiftSyntax) {
|
|
155
|
-
this.
|
|
56
|
+
this.swiftRunner.runSwiftSyntax(parsePath, displayPath, this.findings);
|
|
156
57
|
}
|
|
157
58
|
|
|
158
|
-
const ast = this.
|
|
59
|
+
const ast = this.swiftRunner.parseSourceKitten(parsePath);
|
|
159
60
|
if (!ast) return;
|
|
160
61
|
|
|
161
|
-
this.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
} catch (error) {
|
|
169
|
-
if (process.env.DEBUG) {
|
|
170
|
-
console.debug(`[iOSASTIntelligentAnalyzer] Failed to read file content ${parsePath}: ${error.message}`);
|
|
171
|
-
}
|
|
172
|
-
this.fileContent = '';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const substructure = ast['key.substructure'] || [];
|
|
176
|
-
|
|
177
|
-
collectAllNodes(this, substructure, null);
|
|
178
|
-
analyzeCollectedNodes(this, displayPath);
|
|
62
|
+
this.analysisOrchestrator.run({
|
|
63
|
+
analyzer: this,
|
|
64
|
+
ast,
|
|
65
|
+
parsePath,
|
|
66
|
+
displayPath,
|
|
67
|
+
contentOverride
|
|
68
|
+
});
|
|
179
69
|
}
|
|
180
70
|
|
|
181
71
|
safeStringify(obj) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const {
|
|
3
|
+
resetCollections,
|
|
4
|
+
collectAllNodes,
|
|
5
|
+
analyzeCollectedNodes,
|
|
6
|
+
} = require('../detectors/ios-ast-intelligent-strategies');
|
|
7
|
+
|
|
8
|
+
class iOSAstAnalysisOrchestrator {
|
|
9
|
+
run({ analyzer, ast, parsePath, displayPath, contentOverride }) {
|
|
10
|
+
if (!ast || !analyzer) return;
|
|
11
|
+
|
|
12
|
+
analyzer.currentFilePath = displayPath;
|
|
13
|
+
resetCollections(analyzer);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
analyzer.fileContent = typeof contentOverride === 'string'
|
|
17
|
+
? contentOverride
|
|
18
|
+
: fs.readFileSync(parsePath, 'utf8');
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (process.env.DEBUG) {
|
|
21
|
+
console.debug(`[iOSAstAnalysisOrchestrator] Failed to read file content ${parsePath}: ${error.message}`);
|
|
22
|
+
}
|
|
23
|
+
analyzer.fileContent = '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const substructure = ast['key.substructure'] || [];
|
|
27
|
+
collectAllNodes(analyzer, substructure, null);
|
|
28
|
+
analyzeCollectedNodes(analyzer, displayPath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { iOSAstAnalysisOrchestrator };
|