pumuki-ast-hooks 6.2.2 → 6.2.4
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 +2 -2
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +4 -0
- package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +10 -52
- package/scripts/hooks-system/infrastructure/ast/common/rules/WorkflowRules.js +1 -21
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseChecks.js +1 -1
- package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js +4 -8
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +1 -7
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.spec.js +0 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki-ast-hooks",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.4",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -136,4 +136,4 @@
|
|
|
136
136
|
"./skills": "./skills/skill-rules.json",
|
|
137
137
|
"./hooks": "./hooks/index.js"
|
|
138
138
|
}
|
|
139
|
-
}
|
|
139
|
+
}
|
|
@@ -1506,3 +1506,7 @@
|
|
|
1506
1506
|
{"timestamp":1769006619004,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1507
1507
|
{"timestamp":1769006619004,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1508
1508
|
{"timestamp":1769006619004,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1509
|
+
{"timestamp":1769296106437,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1510
|
+
{"timestamp":1769296106437,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1511
|
+
{"timestamp":1769296106437,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1512
|
+
{"timestamp":1769296106437,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const { execSync } = require('child_process');
|
|
4
3
|
const { pushFinding, SyntaxKind, platformOf, getRepoRoot } = require(path.join(__dirname, '../ast-core'));
|
|
5
4
|
const { BDDTDDWorkflowRules } = require(path.join(__dirname, 'BDDTDDWorkflowRules'));
|
|
6
5
|
|
|
@@ -72,56 +71,6 @@ function hasTrackForMemoryLeaksEvidence(content) {
|
|
|
72
71
|
return hasTrackForMemoryLeaks(content) || hasMakeSUT(content);
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
function loadTestFileContent(filePath) {
|
|
76
|
-
try {
|
|
77
|
-
const diskContent = fs.readFileSync(filePath, 'utf8');
|
|
78
|
-
if (diskContent && diskContent.trim().length > 0) {
|
|
79
|
-
return diskContent;
|
|
80
|
-
}
|
|
81
|
-
} catch (error) {
|
|
82
|
-
if (process.env.DEBUG) {
|
|
83
|
-
console.debug(`[ast-common] Failed to read test file content for ${filePath}: ${error.message}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function resolveTestFileContent(filePath, currentContent) {
|
|
90
|
-
const diskContent = loadTestFileContent(filePath);
|
|
91
|
-
if (diskContent) {
|
|
92
|
-
return diskContent;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const repoRoot = getRepoRoot();
|
|
96
|
-
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(repoRoot, filePath);
|
|
97
|
-
if (resolvedPath !== filePath) {
|
|
98
|
-
const resolvedContent = loadTestFileContent(resolvedPath);
|
|
99
|
-
if (resolvedContent) {
|
|
100
|
-
return resolvedContent;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const relPath = path.relative(repoRoot, resolvedPath).replace(/\\/g, '/');
|
|
106
|
-
if (relPath && !relPath.startsWith('..')) {
|
|
107
|
-
const stagedContent = execSync(`git show :${relPath}`, {
|
|
108
|
-
encoding: 'utf8',
|
|
109
|
-
cwd: repoRoot,
|
|
110
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
111
|
-
});
|
|
112
|
-
if (stagedContent && stagedContent.trim().length > 0) {
|
|
113
|
-
return stagedContent;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
} catch (error) {
|
|
117
|
-
if (process.env.DEBUG) {
|
|
118
|
-
console.debug(`[ast-common] Failed to read staged content for ${filePath}: ${error.message}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return currentContent;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
74
|
/**
|
|
126
75
|
* Detect if a test file is a simple value type test that doesn't need makeSUT/trackForMemoryLeaks.
|
|
127
76
|
* Simple tests typically:
|
|
@@ -326,7 +275,16 @@ function runCommonIntelligence(project, findings) {
|
|
|
326
275
|
const isSwiftOrKotlinTest = ext === '.swift' || ext === '.kt' || ext === '.kts';
|
|
327
276
|
|
|
328
277
|
if (isSwiftOrKotlinTest) {
|
|
329
|
-
|
|
278
|
+
try {
|
|
279
|
+
const diskContent = fs.readFileSync(filePath, 'utf8');
|
|
280
|
+
if (diskContent && diskContent.trim().length > 0) {
|
|
281
|
+
content = diskContent;
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if (process.env.DEBUG) {
|
|
285
|
+
console.debug(`[ast-common] Failed to read test file content for ${filePath}: ${error.message}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
330
288
|
}
|
|
331
289
|
|
|
332
290
|
if (isSwiftOrKotlinTest) {
|
|
@@ -76,7 +76,7 @@ class WorkflowRules {
|
|
|
76
76
|
const featureName = this.extractFeatureName(content);
|
|
77
77
|
|
|
78
78
|
if (featureName) {
|
|
79
|
-
|
|
79
|
+
const testFiles = glob.sync(`**/*${featureName}*.{test,spec}.{ts,tsx,swift,kt}`, {
|
|
80
80
|
cwd: this.projectRoot,
|
|
81
81
|
absolute: true,
|
|
82
82
|
nocase: true
|
|
@@ -89,17 +89,6 @@ class WorkflowRules {
|
|
|
89
89
|
nocase: true
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
-
if (testFiles.length === 0) {
|
|
93
|
-
const tokens = this.splitFeatureName(featureName);
|
|
94
|
-
if (tokens.length > 1) {
|
|
95
|
-
testFiles = tokens.flatMap(token => glob.sync(`**/*${token}*.{test,spec}.{ts,tsx,swift,kt}`, {
|
|
96
|
-
cwd: this.projectRoot,
|
|
97
|
-
absolute: true,
|
|
98
|
-
nocase: true
|
|
99
|
-
}));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
92
|
if (testFiles.length === 0) {
|
|
104
93
|
pushFileFinding(
|
|
105
94
|
'workflow.triad.feature_without_tests',
|
|
@@ -139,15 +128,6 @@ class WorkflowRules {
|
|
|
139
128
|
});
|
|
140
129
|
}
|
|
141
130
|
|
|
142
|
-
splitFeatureName(name) {
|
|
143
|
-
const parts = String(name).split(/And|&|_/).map(part => part.trim()).filter(Boolean);
|
|
144
|
-
if (parts.length > 1) {
|
|
145
|
-
return parts;
|
|
146
|
-
}
|
|
147
|
-
const camelParts = String(name).match(/[A-Z][a-z0-9]+/g) || [name];
|
|
148
|
-
return camelParts.length > 0 ? camelParts : [name];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
131
|
extractFeatureName(content) {
|
|
152
132
|
const match = content.match(/Feature:\s*(.+)/);
|
|
153
133
|
return match ? match[1].trim().replace(/\s+/g, '') : null;
|
|
@@ -146,7 +146,7 @@ function analyzeMemoryManagement({ content, filePath, addFinding }) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
function analyzeOptionals({ content, filePath, addFinding }) {
|
|
149
|
-
const forceUnwraps = content.match(
|
|
149
|
+
const forceUnwraps = content.match(/(\w+)\s*!/g);
|
|
150
150
|
if (forceUnwraps && forceUnwraps.length > 0) {
|
|
151
151
|
const nonIBOutlets = forceUnwraps.filter(match => !content.includes(`@IBOutlet`));
|
|
152
152
|
if (nonIBOutlets.length > 0) {
|
package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js
CHANGED
|
@@ -451,7 +451,7 @@ function analyzeFunctionAST(analyzer, node, filePath) {
|
|
|
451
451
|
const ifStatements = countStatementsOfType(substructure, 'stmt.if');
|
|
452
452
|
const guardStatements = countStatementsOfType(substructure, 'stmt.guard');
|
|
453
453
|
|
|
454
|
-
const hasEarlyReturns = guardStatements > 0;
|
|
454
|
+
const hasEarlyReturns = (analyzer.fileContent || '').includes('return') && guardStatements > 0;
|
|
455
455
|
const nestingLevel = calculateNestingDepth(substructure);
|
|
456
456
|
|
|
457
457
|
if (nestingLevel >= 3 && !hasEarlyReturns) {
|
|
@@ -496,6 +496,8 @@ function analyzePropertyAST(analyzer, node, filePath) {
|
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
function analyzeClosuresAST(analyzer, filePath) {
|
|
499
|
+
const isStruct = analyzer.structs.length > 0;
|
|
500
|
+
|
|
499
501
|
for (const closure of analyzer.closures) {
|
|
500
502
|
const closureText = analyzer.safeStringify(closure);
|
|
501
503
|
const hasSelfReference = closureText.includes('"self"') || closureText.includes('key.name":"self');
|
|
@@ -503,13 +505,7 @@ function analyzeClosuresAST(analyzer, filePath) {
|
|
|
503
505
|
const parentFunc = closure._parent;
|
|
504
506
|
const isEscaping = parentFunc && (parentFunc['key.typename'] || '').includes('@escaping');
|
|
505
507
|
|
|
506
|
-
|
|
507
|
-
while (container && !['source.lang.swift.decl.class', 'source.lang.swift.decl.struct'].includes(container['key.kind'])) {
|
|
508
|
-
container = container._parent;
|
|
509
|
-
}
|
|
510
|
-
const isContainerStruct = container && container['key.kind'] === 'source.lang.swift.decl.struct';
|
|
511
|
-
|
|
512
|
-
if (hasSelfReference && isEscaping && !isContainerStruct) {
|
|
508
|
+
if (hasSelfReference && isEscaping && !isStruct) {
|
|
513
509
|
const offset = closure['key.offset'] || 0;
|
|
514
510
|
const length = closure['key.length'] || 100;
|
|
515
511
|
const closureCode = (analyzer.fileContent || '').substring(offset, offset + length);
|
|
@@ -2504,18 +2504,12 @@ if (require.main === module) {
|
|
|
2504
2504
|
* Called ONLY after MCP handshake is complete
|
|
2505
2505
|
*/
|
|
2506
2506
|
function startPollingLoops() {
|
|
2507
|
-
const evidenceMonitor = getCompositionRoot().getEvidenceMonitor();
|
|
2508
|
-
evidenceMonitor.start();
|
|
2509
|
-
|
|
2510
|
-
if (process.env.DEBUG) {
|
|
2511
|
-
process.stderr.write('[MCP] EvidenceMonitorService started with 3-min auto-refresh\n');
|
|
2512
|
-
}
|
|
2513
|
-
|
|
2514
2507
|
setInterval(async () => {
|
|
2515
2508
|
try {
|
|
2516
2509
|
const now = Date.now();
|
|
2517
2510
|
const gitFlowService = getCompositionRoot().getGitFlowService();
|
|
2518
2511
|
const gitQuery = getCompositionRoot().getGitQueryAdapter();
|
|
2512
|
+
const evidenceMonitor = getCompositionRoot().getEvidenceMonitor();
|
|
2519
2513
|
const orchestrator = getCompositionRoot().getOrchestrator();
|
|
2520
2514
|
|
|
2521
2515
|
const currentBranch = gitFlowService.getCurrentBranch();
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const { startPollingLoops } = require('./ast-intelligence-automation');
|
|
2
|
-
const { getCompositionRoot } = require('../../../application/composition-root');
|
|
3
|
-
|
|
4
|
-
jest.mock('../../../application/composition-root');
|
|
5
|
-
|
|
6
|
-
describe('Polling Loops', () => {
|
|
7
|
-
let mockEvidenceMonitor;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
mockEvidenceMonitor = {
|
|
11
|
-
start: jest.fn(),
|
|
12
|
-
refresh: jest.fn(),
|
|
13
|
-
isStale: jest.fn().mockReturnValue(true)
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
getCompositionRoot.mockReturnValue({
|
|
17
|
-
getEvidenceMonitor: () => mockEvidenceMonitor,
|
|
18
|
-
getGitFlowService: jest.fn(),
|
|
19
|
-
getGitQueryAdapter: jest.fn(),
|
|
20
|
-
getOrchestrator: jest.fn()
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should configure evidence monitor refresh interval to 3 minutes', () => {
|
|
25
|
-
const originalSetInterval = setInterval;
|
|
26
|
-
let intervalCallback;
|
|
27
|
-
let intervalTime;
|
|
28
|
-
|
|
29
|
-
global.setInterval = (callback, time) => {
|
|
30
|
-
intervalCallback = callback;
|
|
31
|
-
intervalTime = time;
|
|
32
|
-
return originalSetInterval(callback, time);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
startPollingLoops();
|
|
36
|
-
|
|
37
|
-
expect(intervalTime).toBe(180000);
|
|
38
|
-
expect(mockEvidenceMonitor.start).toHaveBeenCalled();
|
|
39
|
-
|
|
40
|
-
intervalCallback();
|
|
41
|
-
expect(mockEvidenceMonitor.refresh).toHaveBeenCalled();
|
|
42
|
-
|
|
43
|
-
global.setInterval = originalSetInterval;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should handle evidence refresh errors with retry', () => {
|
|
47
|
-
mockEvidenceMonitor.refresh.mockRejectedValue(new Error('Refresh failed'));
|
|
48
|
-
|
|
49
|
-
startPollingLoops();
|
|
50
|
-
|
|
51
|
-
const intervalCallback = setInterval.mock.calls[0][0];
|
|
52
|
-
intervalCallback();
|
|
53
|
-
|
|
54
|
-
expect(mockEvidenceMonitor.refresh).toHaveBeenCalled();
|
|
55
|
-
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 30000);
|
|
56
|
-
});
|
|
57
|
-
});
|