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.
- package/docs/MCP_SERVERS.md +16 -2
- package/docs/RELEASE_NOTES.md +34 -0
- package/hooks/git-status-monitor.ts +0 -5
- package/hooks/notify-macos.ts +0 -1
- package/hooks/pre-tool-use-evidence-validator.ts +0 -1
- package/package.json +2 -2
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +48 -0
- package/scripts/hooks-system/application/services/guard/GuardConfig.js +2 -4
- package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +2 -20
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +9 -205
- package/scripts/hooks-system/application/services/installation/mcp/McpGlobalConfigCleaner.js +49 -0
- package/scripts/hooks-system/application/services/installation/mcp/McpProjectConfigWriter.js +59 -0
- package/scripts/hooks-system/application/services/installation/mcp/McpServerConfigBuilder.js +103 -0
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +146 -5
- package/scripts/hooks-system/infrastructure/ast/ast-core.js +1 -13
- package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +3 -2
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +17 -9
- package/scripts/hooks-system/infrastructure/ast/backend/detectors/god-class-detector.js +2 -1
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +137 -27
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDChecks.js +385 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDRules.js +38 -408
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseAnalyzer.js +397 -34
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMChecks.js +408 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMRules.js +36 -442
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenExtractor.js +146 -0
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenParser.js +22 -190
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenRunner.js +62 -0
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +42 -47
- package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +0 -16
- package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +14 -25
- package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +0 -10
- package/scripts/hooks-system/application/services/installation/HookAssetsInstaller.js +0 -0
- package/scripts/hooks-system/application/services/monitoring/EvidenceRefreshRunner.js +0 -161
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/StagedSwiftFilePreparer.js +0 -59
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/SwiftAstRunner.js +0 -51
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/SwiftToolchainResolver.js +0 -57
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSAstAnalysisOrchestrator.js +0 -32
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseChecks.js +0 -350
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { spawn } = require('child_process');
|
|
4
|
-
const { ConfigurationError, DomainError } = require('../../../domain/errors');
|
|
5
|
-
|
|
6
|
-
class EvidenceRefreshRunner {
|
|
7
|
-
constructor(repoRoot, options = {}) {
|
|
8
|
-
this.repoRoot = repoRoot;
|
|
9
|
-
this.tempDir = path.join(repoRoot, '.audit_tmp');
|
|
10
|
-
this.refreshLockFile = path.join(this.tempDir, 'evidence-refresh.lock');
|
|
11
|
-
this.refreshTimeoutMs = options.refreshTimeoutMs || 120000;
|
|
12
|
-
this.refreshInFlight = false;
|
|
13
|
-
this.updateScript = this.resolveUpdateEvidenceScript();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
resolveUpdateEvidenceScript() {
|
|
17
|
-
const candidates = [
|
|
18
|
-
path.join(this.repoRoot, 'node_modules/@pumuki/ast-intelligence-hooks/bin/update-evidence.sh'),
|
|
19
|
-
path.join(this.repoRoot, 'scripts/hooks-system/bin/update-evidence.sh'),
|
|
20
|
-
path.join(this.repoRoot, 'bin/update-evidence.sh')
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
for (const candidate of candidates) {
|
|
24
|
-
if (fs.existsSync(candidate)) {
|
|
25
|
-
return candidate;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
isPidRunning(pid) {
|
|
33
|
-
if (!pid || !Number.isFinite(pid) || pid <= 0) return false;
|
|
34
|
-
try {
|
|
35
|
-
process.kill(pid, 0);
|
|
36
|
-
return true;
|
|
37
|
-
} catch {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
acquireRefreshLock() {
|
|
43
|
-
try {
|
|
44
|
-
fs.mkdirSync(this.tempDir, { recursive: true });
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.warn('[EvidenceRefreshRunner] Failed to ensure temp dir:', error.message);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const fd = fs.openSync(this.refreshLockFile, 'wx');
|
|
51
|
-
const payload = JSON.stringify({ pid: process.pid, timestamp: new Date().toISOString() });
|
|
52
|
-
fs.writeFileSync(fd, payload, { encoding: 'utf8' });
|
|
53
|
-
fs.closeSync(fd);
|
|
54
|
-
return { acquired: true };
|
|
55
|
-
} catch (error) {
|
|
56
|
-
if (error && error.code !== 'EEXIST') {
|
|
57
|
-
return { acquired: false, reason: 'error', error };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
const raw = String(fs.readFileSync(this.refreshLockFile, 'utf8') || '').trim();
|
|
62
|
-
const data = raw ? JSON.parse(raw) : null;
|
|
63
|
-
const lockPid = data && Number(data.pid);
|
|
64
|
-
if (lockPid && this.isPidRunning(lockPid)) {
|
|
65
|
-
return { acquired: false, reason: 'locked', pid: lockPid };
|
|
66
|
-
}
|
|
67
|
-
} catch (readError) {
|
|
68
|
-
console.warn('[EvidenceRefreshRunner] Failed to read refresh lock file:', readError.message);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
fs.unlinkSync(this.refreshLockFile);
|
|
73
|
-
} catch (removeError) {
|
|
74
|
-
console.warn('[EvidenceRefreshRunner] Failed to remove stale refresh lock:', removeError.message);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
const fd = fs.openSync(this.refreshLockFile, 'wx');
|
|
79
|
-
const payload = JSON.stringify({ pid: process.pid, timestamp: new Date().toISOString() });
|
|
80
|
-
fs.writeFileSync(fd, payload, { encoding: 'utf8' });
|
|
81
|
-
fs.closeSync(fd);
|
|
82
|
-
return { acquired: true };
|
|
83
|
-
} catch (retryError) {
|
|
84
|
-
return { acquired: false, reason: 'locked', error: retryError };
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
releaseRefreshLock() {
|
|
90
|
-
try {
|
|
91
|
-
if (!fs.existsSync(this.refreshLockFile)) return;
|
|
92
|
-
const raw = String(fs.readFileSync(this.refreshLockFile, 'utf8') || '').trim();
|
|
93
|
-
const data = raw ? JSON.parse(raw) : null;
|
|
94
|
-
const lockPid = data && Number(data.pid);
|
|
95
|
-
if (lockPid === process.pid) {
|
|
96
|
-
fs.unlinkSync(this.refreshLockFile);
|
|
97
|
-
}
|
|
98
|
-
} catch (error) {
|
|
99
|
-
console.warn('[EvidenceRefreshRunner] Failed to release refresh lock:', error.message);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async refresh() {
|
|
104
|
-
if (!this.updateScript) {
|
|
105
|
-
throw new ConfigurationError('Update evidence script not found', 'updateScript');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (this.refreshInFlight) {
|
|
109
|
-
return '';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const lock = this.acquireRefreshLock();
|
|
113
|
-
if (!lock.acquired) {
|
|
114
|
-
return '';
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
this.refreshInFlight = true;
|
|
118
|
-
|
|
119
|
-
return new Promise((resolve, reject) => {
|
|
120
|
-
const child = spawn('bash', [this.updateScript, '--auto', '--refresh-only'], {
|
|
121
|
-
cwd: this.repoRoot,
|
|
122
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
let output = '';
|
|
126
|
-
child.stdout.on('data', (data) => {
|
|
127
|
-
output += data.toString();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const timeoutId = setTimeout(() => {
|
|
131
|
-
try {
|
|
132
|
-
child.kill('SIGKILL');
|
|
133
|
-
} catch (error) {
|
|
134
|
-
console.warn('[EvidenceRefreshRunner] Failed to kill timed-out refresh process:', error.message);
|
|
135
|
-
}
|
|
136
|
-
}, this.refreshTimeoutMs);
|
|
137
|
-
|
|
138
|
-
const finalize = () => {
|
|
139
|
-
clearTimeout(timeoutId);
|
|
140
|
-
this.refreshInFlight = false;
|
|
141
|
-
this.releaseRefreshLock();
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
child.on('close', (code) => {
|
|
145
|
-
finalize();
|
|
146
|
-
if (code === 0) {
|
|
147
|
-
resolve(output);
|
|
148
|
-
} else {
|
|
149
|
-
reject(new DomainError(`Evidence refresh failed with code ${code}`, 'EVIDENCE_REFRESH_FAILED'));
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
child.on('error', (err) => {
|
|
154
|
-
finalize();
|
|
155
|
-
reject(err);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
module.exports = { EvidenceRefreshRunner };
|
|
@@ -1,59 +0,0 @@
|
|
|
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 };
|
|
@@ -1,51 +0,0 @@
|
|
|
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 };
|
|
@@ -1,57 +0,0 @@
|
|
|
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,32 +0,0 @@
|
|
|
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 };
|