pumuki-ast-hooks 5.5.46 → 5.5.48

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.
Files changed (31) hide show
  1. package/docs/CODE_STANDARDS.md +5 -0
  2. package/docs/VIOLATIONS_RESOLUTION_PLAN.md +22 -34
  3. package/package.json +2 -2
  4. package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +36 -0
  5. package/scripts/hooks-system/application/services/installation/FileSystemInstallerService.js +1 -1
  6. package/scripts/hooks-system/application/services/installation/VSCodeTaskConfigurator.js +8 -2
  7. package/scripts/hooks-system/bin/gitflow-cycle.js +0 -0
  8. package/scripts/hooks-system/config/project.config.json +1 -1
  9. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidSOLIDAnalyzer.js +11 -255
  10. package/scripts/hooks-system/infrastructure/ast/android/detectors/android-solid-detectors.js +227 -0
  11. package/scripts/hooks-system/infrastructure/ast/ast-core.js +12 -3
  12. package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +36 -13
  13. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +10 -83
  14. package/scripts/hooks-system/infrastructure/ast/backend/detectors/god-class-detector.js +83 -0
  15. package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +17 -2
  16. package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/FrontendArchitectureDetector.js +12 -142
  17. package/scripts/hooks-system/infrastructure/ast/frontend/detectors/frontend-architecture-strategies.js +126 -0
  18. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +30 -783
  19. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureDetector.js +21 -224
  20. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureRules.js +18 -605
  21. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSModernPracticesRules.js +4 -1
  22. package/scripts/hooks-system/infrastructure/ast/ios/ast-ios.js +4 -1
  23. package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-architecture-rules-strategies.js +595 -0
  24. package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-architecture-strategies.js +192 -0
  25. package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js +789 -0
  26. package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-god-class-detector.js +79 -0
  27. package/scripts/hooks-system/infrastructure/ast/ios/native-bridge.js +4 -1
  28. package/skills/android-guidelines/SKILL.md +1 -0
  29. package/skills/backend-guidelines/SKILL.md +1 -0
  30. package/skills/frontend-guidelines/SKILL.md +1 -0
  31. package/skills/ios-guidelines/SKILL.md +1 -0
@@ -179,6 +179,11 @@ const DEFAULT_TIMEOUT = 5000;
179
179
  const defaultConfig = { timeout: 5000 };
180
180
  ```
181
181
 
182
+ ### Error Handling
183
+
184
+ - **No empty catch blocks**: Never write `catch {}` or `catch (e) {}` with an empty body. Always handle the error (log, rethrow, wrap, or return a Result).
185
+ - **AST enforcement**: `common.error.empty_catch` (CRITICAL)
186
+
182
187
  ### Files
183
188
 
184
189
  - **kebab-case** for file names
@@ -10,11 +10,11 @@
10
10
  ---
11
11
 
12
12
  ## 📊 Executive Summary
13
- - **Current status:** ⚠️ Action required (1 critical, 1 high, 41 medium, 602 low)
14
- - **Progress:** 673 → 645 violaciones (-28 tras correcciones en analizadores)
15
- - **Branch:** `feature/fix-critical-high-violations`
13
+ - **Current status:** ⚠️ Acción requerida (0 critical, 0 high, 33 medium, 515 low)
14
+ - **Progress:** 673 → 548 violaciones (-125 tras correcciones y exclusiones afinadas)
15
+ - **Branch:** `develop`
16
16
  - **Start date:** 2026-01-05 — **Overall ETA:** 2026-01-15
17
- - **Goal:** Reducir CRITICAL a 0 y mantener gate ALLOWED.
17
+ - **Goal:** Mantener CRITICAL/HIGH en 0 y reducir MEDIUM/LOW priorizando impacto.
18
18
  - **Risks:**
19
19
  1) Cambiar severities no elimina violaciones existentes, solo previene nuevas; 2) Mantener estabilidad de librería; 3) Evitar regressions en AST analysis.
20
20
 
@@ -65,44 +65,32 @@ gantt
65
65
  ## 🔴 Phase 1: BLOCKER Violations (CRITICAL + HIGH)
66
66
  | Status | Severity | Count | Owner | DOD (Definition of Done) | Source |
67
67
  |--------|-----------|-------|-------------|--------------------------|--------|
68
- | 🚧 | CRITICAL | 21 | Backend | Eliminar CRITICAL hasta 0 | Audit actual |
69
- | 🚧 | HIGH | 3 | Backend | Reducir HIGH hasta 0 | Audit actual |
68
+ | | CRITICAL | 0 | Backend | Mantener CRITICAL en 0 | Audit 2026-01-06 13:56 |
69
+ | | HIGH | 0 | Backend | Mantener HIGH en 0 | Audit 2026-01-06 13:56 |
70
70
 
71
- **Top CRITICAL violations:**
72
- - `backend.security.missing_audit_logging`: 22 violaciones
73
- - `backend.antipattern.god_classes`: 15 violaciones
74
- - `shell.antipattern.god_script`: 1 violación
75
- - `shell.maintainability.large_script`: 1 violación
71
+ **CRITICAL/HIGH actuales:** Ninguna. Mantener vigilancia en nuevas reglas.
76
72
 
77
- **Top HIGH violations:**
78
- - `backend.auth.missing_cors`: 25 violaciones (actualmente HIGH)
79
- - `backend.types.any`: 3 violaciones
80
-
81
- **Fixes aplicados (cambio de severities para evitar CRITICAL con AUDIT_STRICT=1):**
82
- - `backend.testing.mocks` (40): Ya estaba en 'info' (falso positivo en tests)
83
- - `backend.error.custom_exceptions` (104): Cambiado de 'low' a 'info'
84
- - `backend.config.missing_env_separation` (79): Cambiado de 'low' a 'info'
85
- - `backend.security.missing_audit_logging` (22): Cambiado de 'low' a 'info'
86
-
87
- **Resultado esperado:** CRITICAL = 0 después de estos cambios.
73
+ **Fixes previos destacados:**
74
+ - Detección de god classes y god scripts en analizadores/shell.
75
+ - Ajustes de severidad para falsas alarmas (mocks en tests, custom_exceptions info, env_separation info, audit_logging info).
88
76
 
89
77
  ---
90
78
 
91
- ## 🟠 Phase 2: MEDIUM Violations (138)
79
+ ## 🟠 Phase 2: MEDIUM Violations (33)
92
80
  | Status | Violation | Count | Owner | DOD | Doc |
93
81
  |--------|-----------|-------|-------------|-----|-----|
94
- | ⏳ | MEDIUM | 47 | Backend | Reducir MEDIUM priorizando reglas de testing y observabilidad | [Medium violations](../docs/medium-violations.md) |
82
+ | ⏳ | MEDIUM | 33 | Backend | Reducir MEDIUM priorizando reglas de testing y observabilidad | [Medium violations](../docs/medium-violations.md) |
95
83
 
96
84
  **Top MEDIUM violations:**
97
- - `backend.error.custom_exceptions`: 104 violaciones
98
- - `backend.config.missing_env_separation`: 81 violaciones
99
- - `backend.metrics.missing_prometheus`: 78 violaciones
100
- - `backend.reliability.missing_bulkhead`: 50 violaciones
85
+ - `backend.error.custom_exceptions`: 108 violaciones
86
+ - `backend.config.missing_env_separation`: 112 violaciones
87
+ - `backend.metrics.missing_prometheus`: 79 violaciones
88
+ - `backend.reliability.missing_bulkhead`: 57 violaciones
101
89
  - `backend.testing.mocks`: 40 violaciones
102
90
 
103
91
  ---
104
92
 
105
- ## 🔵 Phase 3: LOW Violations (246)
93
+ ## 🔵 Phase 3: LOW Violations (602)
106
94
  | Status | Violation | Count | Owner | DOD | Doc |
107
95
  |--------|-----------|-------|-------------|-----|-----|
108
96
  | ⏳ | LOW | 602 | Backend | Reducir LOW con foco en patrones de desarrollo y documentación | [Low violations](../docs/low-violations.md) |
@@ -121,17 +109,17 @@ gantt
121
109
  | P1 | backend.observability.missing_prometheus | 37 | Métricas de observabilidad |
122
110
  | P1 | backend.auth.missing_cors | 25 | CORS no configurado |
123
111
  | P1 | backend.security.missing_audit_logging | 22 | Logging de auditoría faltante |
124
- | P1 | backend.antipattern.god_classes | 15 | God classes detectadas |
112
+ | P1 | shell.maintainability.large_script | 1 | Script extenso en infraestructura |
125
113
 
126
114
  ---
127
115
 
128
116
  ## 📈 Progress Metrics
129
117
  | Phase | Total | Completed | % |
130
118
  |------|-------|------------|---|
131
- | BLOCKERS (CRITICAL + HIGH) | 24 | 0 | 0% |
132
- | MEDIUM | 47 | 0 | 0% |
133
- | LOW | 602 | 0 | 0% |
134
- | **TOTAL** | **673** | **0** | **0%** |
119
+ | BLOCKERS (CRITICAL + HIGH) | 0 | 0 | 0% |
120
+ | MEDIUM | 33 | 0 | 0% |
121
+ | LOW | 515 | 0 | 0% |
122
+ | **TOTAL** | **548** | **0** | **0%** |
135
123
 
136
124
  **Updated risks:**
137
125
  1) MEDIUM/LOW pueden requerir cambios más invasivos en la arquitectura; 2) Mantener compatibilidad backward en librería; 3) Evitar impacto en performance de análisis AST.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki-ast-hooks",
3
- "version": "5.5.46",
3
+ "version": "5.5.48",
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": {
@@ -134,4 +134,4 @@
134
134
  "./skills": "./skills/skill-rules.json",
135
135
  "./hooks": "./hooks/index.js"
136
136
  }
137
- }
137
+ }
@@ -6,3 +6,39 @@
6
6
  {"timestamp":1767651940178,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
7
7
  {"timestamp":1767651940178,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
8
8
  {"timestamp":1767651940178,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
9
+ {"timestamp":1767704104971,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
10
+ {"timestamp":1767704104972,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
11
+ {"timestamp":1767704104972,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
12
+ {"timestamp":1767704104972,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
13
+ {"timestamp":1767716313724,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
14
+ {"timestamp":1767716313724,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
15
+ {"timestamp":1767716313724,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
16
+ {"timestamp":1767716313724,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
17
+ {"timestamp":1767725525265,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
18
+ {"timestamp":1767725525265,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
19
+ {"timestamp":1767725525265,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
20
+ {"timestamp":1767725525265,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
21
+ {"timestamp":1767725979141,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
22
+ {"timestamp":1767725979141,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
23
+ {"timestamp":1767725979141,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
24
+ {"timestamp":1767725979141,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
25
+ {"timestamp":1767726402642,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
26
+ {"timestamp":1767726402642,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
27
+ {"timestamp":1767726402642,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
28
+ {"timestamp":1767726402642,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
29
+ {"timestamp":1767726935328,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
30
+ {"timestamp":1767726935328,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
31
+ {"timestamp":1767726935328,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
32
+ {"timestamp":1767726935328,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
33
+ {"timestamp":1767728590260,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
34
+ {"timestamp":1767728590260,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
35
+ {"timestamp":1767728590261,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
36
+ {"timestamp":1767728590261,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
37
+ {"timestamp":1767729164115,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
38
+ {"timestamp":1767729164116,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
39
+ {"timestamp":1767729164116,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
40
+ {"timestamp":1767729164116,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
41
+ {"timestamp":1767729940607,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
42
+ {"timestamp":1767729940607,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
43
+ {"timestamp":1767729940607,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
44
+ {"timestamp":1767729940607,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
@@ -56,7 +56,7 @@ class FileSystemInstallerService {
56
56
  this.copyRecursiveExcluding(source, dest, ['scripts']);
57
57
  } else if (item === 'bin/') {
58
58
  // Exclude wrapper scripts that would cause fork bombs
59
- this.copyRecursiveExcluding(source, dest, ['session-loader.sh', 'cli.js', 'install.js']);
59
+ this.copyRecursiveExcluding(source, dest, ['cli.js', 'install.js']);
60
60
  } else {
61
61
  this.copyRecursive(source, dest);
62
62
  }
@@ -63,16 +63,20 @@ class VSCodeTaskConfigurator {
63
63
  [
64
64
  'ROOT="${workspaceFolder}"',
65
65
  'PRIMARY="$ROOT/scripts/hooks-system/bin/session-loader.sh"',
66
- 'FALLBACK="$ROOT/node_modules/@pumuki/ast-intelligence-hooks/bin/session-loader.sh"',
66
+ 'FALLBACK="$ROOT/node_modules/pumuki-ast-hooks/scripts/hooks-system/bin/session-loader.sh"',
67
+ 'FALLBACK2="$ROOT/node_modules/@pumuki/ast-intelligence-hooks/bin/session-loader.sh"',
67
68
  'if [ -f "$PRIMARY" ]; then',
68
69
  ' exec bash "$PRIMARY"',
69
70
  'elif [ -f "$FALLBACK" ]; then',
70
71
  ' exec bash "$FALLBACK"',
72
+ 'elif [ -f "$FALLBACK2" ]; then',
73
+ ' exec bash "$FALLBACK2"',
71
74
  'else',
72
75
  ' echo "AST Session Loader not found." >&2',
73
76
  ' echo "Tried:" >&2',
74
77
  ' echo " - $PRIMARY" >&2',
75
78
  ' echo " - $FALLBACK" >&2',
79
+ ' echo " - $FALLBACK2" >&2',
76
80
  ' exit 127',
77
81
  'fi'
78
82
  ].join('\n')
@@ -83,7 +87,9 @@ class VSCodeTaskConfigurator {
83
87
  },
84
88
  presentation: {
85
89
  reveal: 'always',
86
- panel: 'new'
90
+ panel: 'new',
91
+ clear: true,
92
+ showReuseMessage: false
87
93
  },
88
94
  identifier: 'ast-session-loader'
89
95
  };
File without changes
@@ -5,7 +5,7 @@
5
5
  "platforms": [
6
6
  "backend"
7
7
  ],
8
- "created": "2026-01-04T19:10:44.947Z"
8
+ "created": "2026-01-06T11:37:47.558Z"
9
9
  },
10
10
  "architecture": {
11
11
  "pattern": "FEATURE_FIRST_CLEAN_DDD",
@@ -1,6 +1,12 @@
1
1
 
2
2
  const path = require('path');
3
3
  const { pushFinding, SyntaxKind } = require(path.join(__dirname, '../../ast-core'));
4
+ const {
5
+ analyzeOCP,
6
+ analyzeDIP,
7
+ analyzeSRP,
8
+ analyzeISP,
9
+ } = require('../detectors/android-solid-detectors');
4
10
 
5
11
  /**
6
12
  * AndroidSOLIDAnalyzer
@@ -27,261 +33,11 @@ class AndroidSOLIDAnalyzer {
27
33
  this.findings = findings;
28
34
  this.pushFinding = pushFinding;
29
35
 
30
- this.analyzeOCP(sf);
31
- this.analyzeDIP(sf);
32
- this.analyzeSRP(sf);
33
- this.analyzeISP(sf);
34
- }
35
-
36
-
37
- analyzeOCP(sf) {
38
- const filePath = sf.getFilePath();
39
- const fileName = filePath.split('/').pop() || 'unknown';
40
-
41
- const functions = sf.getFunctions();
42
- const arrowFunctions = sf.getVariableDeclarations().filter(vd => {
43
- const init = vd.getInitializer();
44
- return init && init.getKind() === SyntaxKind.ArrowFunction;
45
- });
46
-
47
- const classes = sf.getClasses();
48
-
49
- const allNodes = [
50
- ...functions.map(f => ({ type: 'function', node: f, name: f.getName() || 'anonymous' })),
51
- ...arrowFunctions.map(af => ({ type: 'arrow', node: af, name: af.getName() || 'anonymous' })),
52
- ...classes.map(c => ({ type: 'class', node: c, name: c.getName() || 'AnonymousClass' })),
53
- ];
54
-
55
- allNodes.forEach(({ type, node, name }) => {
56
- this.analyzeNodeForOCP(node, name, fileName, sf);
57
- });
58
- }
59
-
60
- analyzeNodeForOCP(node, nodeName, fileName, sf) {
61
- let body;
62
-
63
- if (node.getKind() === SyntaxKind.FunctionDeclaration ||
64
- node.getKind() === SyntaxKind.FunctionExpression) {
65
- body = node.getBody();
66
- } else if (node.getKind() === SyntaxKind.VariableDeclaration) {
67
- const init = node.getInitializer();
68
- if (init && init.getKind() === SyntaxKind.ArrowFunction) {
69
- body = init.getBody();
70
- }
71
- } else if (node.getKind() === SyntaxKind.ClassDeclaration) {
72
- const methods = node.getMethods();
73
- methods.forEach(method => {
74
- const methodBody = method.getBody();
75
- if (methodBody) {
76
- this.analyzeBodyForOCP(methodBody, `${nodeName}.${method.getName()}`, fileName, sf);
77
- }
78
- });
79
- return;
80
- } else {
81
- return;
82
- }
83
-
84
- if (!body) return;
85
- this.analyzeBodyForOCP(body, nodeName, fileName, sf);
86
- }
87
-
88
- analyzeBodyForOCP(body, nodeName, fileName, sf) {
89
- const switches = body.getDescendantsOfKind(SyntaxKind.SwitchStatement);
90
-
91
- switches.forEach(switchStmt => {
92
- const switchExpr = switchStmt.getExpression();
93
- const exprText = switchExpr.getText();
94
- const cases = switchStmt.getCaseClauses();
95
-
96
- if (cases.length >= 3) {
97
- const isDomainValue = /status|type|priority|role|kind|category|state|mode/i.test(exprText);
98
-
99
- if (isDomainValue) {
100
- const message = `OCP VIOLATION in ${fileName}::${nodeName}: switch on '${exprText}' with ${cases.length} cases - use Strategy/Map pattern with centralized constants`;
101
- this.pushFinding('solid.ocp.switch_statement', 'critical', sf, switchStmt, message, this.findings);
102
- } else if (cases.length >= 5) {
103
- const message = `OCP VIOLATION in ${fileName}::${nodeName}: large switch (${cases.length} cases) on '${exprText}' - consider Strategy/Factory pattern`;
104
- this.pushFinding('solid.ocp.switch_statement', 'high', sf, switchStmt, message, this.findings);
105
- }
106
- }
107
- });
108
-
109
- const ifStatements = body.getDescendantsOfKind(SyntaxKind.IfStatement);
110
- const typeIfChains = this.detectTypeIfChains(ifStatements);
111
-
112
- if (typeIfChains.length >= 3) {
113
- const chainVars = new Set(typeIfChains.map(chain => chain.variable));
114
- if (chainVars.size === 1) {
115
- const varName = Array.from(chainVars)[0];
116
- const message = `OCP VIOLATION in ${fileName}::${nodeName}: ${typeIfChains.length} if-else checking '${varName}' - use Strategy pattern or polymorphism`;
117
- this.pushFinding('solid.ocp.if_type_chain', 'critical', sf, typeIfChains[0].node, message, this.findings);
118
- }
119
- }
120
-
121
- switches.forEach(switchStmt => {
122
- const nestedSwitches = switchStmt.getDescendantsOfKind(SyntaxKind.SwitchStatement);
123
- if (nestedSwitches.length > 0) {
124
- const message = `OCP VIOLATION in ${fileName}::${nodeName}: nested switch statements - use Strategy/Map pattern with composition`;
125
- this.pushFinding('solid.ocp.nested_switch', 'critical', sf, switchStmt, message, this.findings);
126
- }
127
- });
128
- }
129
-
130
- detectTypeIfChains(ifStatements) {
131
- const chains = [];
132
-
133
- ifStatements.forEach(ifStmt => {
134
- const condition = ifStmt.getExpression();
135
- const conditionText = condition.getText();
136
-
137
- const typeCheckPattern = /(\w+)\s*(===|==|!==|!=)\s*['"]([\w\s]+)['"]|typeof\s+(\w+)|(\w+)\s+instanceof/i;
138
- const match = conditionText.match(typeCheckPattern);
139
-
140
- if (match) {
141
- const variable = match[1] || match[4] || match[5];
142
- chains.push({ variable, node: ifStmt });
143
- }
144
- });
145
-
146
- return chains;
147
- }
148
-
149
-
150
- analyzeDIP(sf) {
151
- const filePath = sf.getFilePath();
152
- const fileName = filePath.split('/').pop() || 'unknown';
153
-
154
- const isDomain = /\/domain\//i.test(filePath);
155
-
156
- if (isDomain) {
157
- const imports = sf.getImportDeclarations();
158
-
159
- imports.forEach(imp => {
160
- const importPath = imp.getModuleSpecifierValue();
161
-
162
- if (/\/infrastructure\//i.test(importPath) ||
163
- /androidx|kotlinx|retrofit|room|hilt/i.test(importPath)) {
164
- const message = `DIP VIOLATION in ${fileName}: Domain layer importing from Infrastructure/Framework: ${importPath} - Domain should depend only on abstractions`;
165
- this.pushFinding('solid.dip.domain_depends_infrastructure', 'critical', sf, imp, message, this.findings);
166
- }
167
- });
168
- }
169
-
170
- const isPresentation = /\/presentation\//i.test(filePath);
171
-
172
- if (isPresentation) {
173
- const imports = sf.getImportDeclarations();
174
-
175
- imports.forEach(imp => {
176
- const importPath = imp.getModuleSpecifierValue();
177
-
178
- if (/\/infrastructure\//i.test(importPath) &&
179
- !/\/infrastructure\/repositories\/|\/infrastructure\/config\//i.test(importPath)) {
180
- const message = `DIP VIOLATION in ${fileName}: Presentation layer importing from Infrastructure: ${importPath} - use repository interfaces or abstractions`;
181
- this.pushFinding('solid.dip.presentation_infrastructure', 'critical', sf, imp, message, this.findings);
182
- }
183
- });
184
- }
185
-
186
- const classes = sf.getClasses();
187
- classes.forEach(cls => {
188
- const className = cls.getName() || 'AnonymousClass';
189
-
190
- if (/ViewModel|UseCase/i.test(className)) {
191
- const imports = sf.getImportDeclarations();
192
-
193
- imports.forEach(imp => {
194
- const importPath = imp.getModuleSpecifierValue();
195
-
196
- if (/Repository|Service|Client/i.test(importPath) &&
197
- !/interface|protocol|Repository.*Protocol/i.test(importPath)) {
198
- const message = `DIP VIOLATION in ${fileName}::${className}: depends on concrete implementation '${importPath}' - inject interface/abstraction`;
199
- this.pushFinding('solid.dip.concrete_dependency', 'critical', sf, imp, message, this.findings);
200
- }
201
- });
202
- }
203
- });
204
- }
205
-
206
-
207
- analyzeSRP(sf) {
208
- const filePath = sf.getFilePath();
209
- const fileName = filePath.split('/').pop() || 'unknown';
210
-
211
- const functions = sf.getFunctions();
212
- const arrowFunctions = sf.getVariableDeclarations().filter(vd => {
213
- const init = vd.getInitializer();
214
- return init && init.getKind() === SyntaxKind.ArrowFunction;
215
- });
216
-
217
- [...functions, ...arrowFunctions].forEach(func => {
218
- const funcName = func.getName?.() || 'anonymous';
219
- const body = func.getBody?.() || func.getInitializer()?.getBody();
220
-
221
- if (!body) return;
222
-
223
- const statements = body.getStatements();
224
- const ifStatements = body.getDescendantsOfKind(SyntaxKind.IfStatement);
225
- const switchStatements = body.getDescendantsOfKind(SyntaxKind.SwitchStatement);
226
- const loops = body.getDescendantsOfKind(SyntaxKind.ForStatement)
227
- .concat(body.getDescendantsOfKind(SyntaxKind.ForInStatement))
228
- .concat(body.getDescendantsOfKind(SyntaxKind.WhileStatement));
229
-
230
- if (statements.length > 30 || ifStatements.length > 10 || switchStatements.length > 2 || loops.length > 5) {
231
- const message = `SRP VIOLATION in ${fileName}::${funcName}: high complexity (${statements.length} statements, ${ifStatements.length} ifs, ${switchStatements.length} switches) - extract responsibilities`;
232
- this.pushFinding('solid.srp.high_complexity', 'critical', sf, func, message, this.findings);
233
- }
234
- });
235
-
236
- const classes = sf.getClasses();
237
- classes.forEach(cls => {
238
- const className = cls.getName() || 'AnonymousClass';
239
- const methods = cls.getMethods();
240
-
241
- if (methods.length > 20) {
242
- const message = `SRP VIOLATION in ${fileName}::${className}: God class with ${methods.length} methods - split into focused classes`;
243
- this.pushFinding('solid.srp.god_class', 'critical', sf, cls, message, this.findings);
244
- }
245
- });
246
- }
247
-
248
-
249
- analyzeISP(sf) {
250
- const interfaces = sf.getInterfaces();
251
-
252
- interfaces.forEach(iface => {
253
- const interfaceName = iface.getName();
254
- const properties = iface.getProperties();
255
- const methods = iface.getMethods();
256
-
257
- if (properties.length > 10) {
258
- const message = `ISP VIOLATION: ${interfaceName} has ${properties.length} properties - split into focused interfaces`;
259
- this.pushFinding('solid.isp.fat_interface', 'critical', sf, iface, message, this.findings);
260
- }
261
-
262
- if (methods.length > 0) {
263
- const methodConcerns = methods.map(m => this.detectMethodConcern(m.getName()));
264
- const uniqueConcerns = new Set(methodConcerns.filter(c => c !== 'unknown'));
265
-
266
- if (uniqueConcerns.size >= 3) {
267
- const message = `ISP VIOLATION: ${interfaceName} mixes ${uniqueConcerns.size} concerns (${Array.from(uniqueConcerns).join(', ')}) - segregate into focused interfaces`;
268
- this.pushFinding('solid.isp.multiple_concerns', 'critical', sf, iface, message, this.findings);
269
- }
270
- }
271
- });
272
- }
273
-
274
- detectMethodConcern(methodName) {
275
- const name = methodName.toLowerCase();
276
-
277
- if (/get|fetch|load|read|find|query/i.test(name)) return 'data-access';
278
- if (/set|save|create|update|delete|remove/i.test(name)) return 'data-mutation';
279
- if (/validate|check|verify/i.test(name)) return 'validation';
280
- if (/format|parse|transform/i.test(name)) return 'transformation';
281
- if (/render|display|show/i.test(name)) return 'rendering';
282
-
283
- return 'unknown';
36
+ analyzeOCP(sf, findings, pushFinding);
37
+ analyzeDIP(sf, findings, pushFinding);
38
+ analyzeSRP(sf, findings, pushFinding);
39
+ analyzeISP(sf, findings, pushFinding);
284
40
  }
285
41
  }
286
42
 
287
- module.exports = { AndroidSOLIDAnalyzer };
43
+ module.exports = AndroidSOLIDAnalyzer;