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,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
|
|
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.
|
|
22
|
-
this.
|
|
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
|
|
32
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
979
|
-
|
|
980
|
-
.
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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:
|
|
987
|
-
criticalRules: rulesData.criticalRules,
|
|
988
|
-
rulesLoaded: Object.keys(rulesData.rules),
|
|
989
|
-
|
|
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 (
|
|
1008
|
+
} catch (err) {
|
|
994
1009
|
if (process.env.DEBUG) {
|
|
995
|
-
process.stderr.write(`[MCP]
|
|
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:
|
|
1013
|
+
platforms: platformsToLoad,
|
|
1002
1014
|
criticalRules: [],
|
|
1003
1015
|
rulesLoaded: [],
|
|
1004
|
-
|
|
1005
|
-
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:
|
|
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:
|
|
1027
|
-
|
|
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. ${
|
|
1032
|
-
instructions:
|
|
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
|
-
:
|
|
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
|
};
|
package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
File without changes
|