pumuki-ast-hooks 5.5.50 → 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/package.json +1 -1
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +48 -0
- 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/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/mcp/ast-intelligence-automation.js +407 -5
- package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +21 -67
|
@@ -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 };
|