pumuki-ast-hooks 6.1.11 → 6.1.13

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/README.md CHANGED
@@ -13,7 +13,7 @@ Portable, project‑agnostic, multi‑platform enterprise framework to govern AI
13
13
 
14
14
  ---
15
15
 
16
- ## Inicio rápido (30–60s)
16
+ ## Quick start (30–60s)
17
17
 
18
18
  ```bash
19
19
  git init
@@ -22,9 +22,9 @@ npx ast-install
22
22
  npx ast-hooks audit
23
23
  ```
24
24
 
25
- Modo de instalación por defecto: `npm-runtime`.
25
+ Default installation mode: `npm-runtime`.
26
26
 
27
- Para usar runtime embebido (modo `vendored`):
27
+ To use the embedded runtime (`vendored` mode):
28
28
 
29
29
  ```bash
30
30
  HOOK_INSTALL_MODE=vendored npx ast-install
@@ -360,55 +360,55 @@ The Git Flow cycle includes branch validation, commits, push, PR, merge (by poli
360
360
 
361
361
  ---
362
362
 
363
- ## 9. Sección de comandos (OBLIGATORIO – NO CAMBIAR)
363
+ ## 9. Commands (required)
364
364
 
365
- ### Instalación (dev)
365
+ ### Installation (dev)
366
366
 
367
367
  ```bash
368
368
  npm install --save-dev pumuki-ast-hooks
369
369
  npx ast-install
370
370
  ```
371
371
 
372
- ### Instalación (legacy)
372
+ ### Installation (legacy)
373
373
 
374
374
  ```bash
375
375
  npm install --save-dev pumuki-ast-hooks
376
376
  npx ast-install
377
377
  ```
378
378
 
379
- ### Actualización
379
+ ### Update
380
380
 
381
381
  ```bash
382
382
  npm install --save-dev pumuki-ast-hooks@latest
383
383
  npx ast-install
384
384
  ```
385
385
 
386
- ### Desinstalación
386
+ ### Uninstall
387
387
 
388
388
  ```bash
389
389
  npx ast-uninstall
390
390
  npm uninstall pumuki-ast-hooks
391
391
  ```
392
392
 
393
- ### Instalar hooks
393
+ ### Install hooks
394
394
 
395
395
  ```bash
396
396
  npm run install-hooks
397
397
  ```
398
398
 
399
- ### Menú interactivo
399
+ ### Interactive menu
400
400
 
401
401
  ```bash
402
402
  npx ast-hooks
403
403
  ```
404
404
 
405
- ### Verificación de versión
405
+ ### Version check
406
406
 
407
407
  ```bash
408
408
  npm run ast:check-version
409
409
  ```
410
410
 
411
- ### Auditoría
411
+ ### Audit
412
412
 
413
413
  ```bash
414
414
  npm run audit
@@ -435,7 +435,7 @@ npm run ast:guard:status
435
435
  npm run ast:guard:logs
436
436
  ```
437
437
 
438
- ### Refresco de evidencia
438
+ ### Evidence refresh
439
439
 
440
440
  ```bash
441
441
  npm run ast:refresh
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "name": "pumuki-ast-hooks",
3
- "version": "6.1.11",
3
+ "version": "6.1.13",
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": {
7
- "pumuki-ast-hooks": "./bin/pumuki-mcp-server.js",
8
- "audit": "./bin/audit",
9
- "ast-hooks": "./bin/cli.js",
10
- "ast-install": "./bin/install.js",
11
- "ast-uninstall": "./bin/uninstall.js",
12
- "ast-violations": "./bin/violations-api.js",
13
- "ast-check-version": "./bin/check-version.js",
14
- "ast-gitflow": "./scripts/hooks-system/bin/gitflow-cycle.js",
15
- "ai-commit": "./bin/ai-commit.sh",
16
- "hook-run-orchestrator": "./bin/run-orchestrator.js",
17
- "hook-watch": "./bin/watch-hooks.js",
18
- "hook-status": "./bin/hook-status.js",
19
- "hook-predict": "./bin/predictive-hooks.js",
20
- "hook-playbook": "./bin/run-playbook.js",
21
- "hook-doc-drift": "./bin/check-doc-drift.js",
22
- "hook-plan-review": "./bin/plan-review.js"
7
+ "pumuki-ast-hooks": "bin/pumuki-mcp-server.js",
8
+ "audit": "bin/audit",
9
+ "ast-hooks": "bin/cli.js",
10
+ "ast-install": "bin/install.js",
11
+ "ast-uninstall": "bin/uninstall.js",
12
+ "ast-violations": "bin/violations-api.js",
13
+ "ast-check-version": "bin/check-version.js",
14
+ "ast-gitflow": "scripts/hooks-system/bin/gitflow-cycle.js",
15
+ "ai-commit": "bin/ai-commit.sh",
16
+ "hook-run-orchestrator": "bin/run-orchestrator.js",
17
+ "hook-watch": "bin/watch-hooks.js",
18
+ "hook-status": "bin/hook-status.js",
19
+ "hook-predict": "bin/predictive-hooks.js",
20
+ "hook-playbook": "bin/run-playbook.js",
21
+ "hook-doc-drift": "bin/check-doc-drift.js",
22
+ "hook-plan-review": "bin/plan-review.js"
23
23
  },
24
24
  "scripts": {
25
25
  "install-hooks": "node bin/install.js",
@@ -81,7 +81,7 @@
81
81
  "license": "MIT",
82
82
  "repository": {
83
83
  "type": "git",
84
- "url": "https://github.com/SwiftEnProfundidad/ast-intelligence-hooks.git"
84
+ "url": "git+https://github.com/SwiftEnProfundidad/ast-intelligence-hooks.git"
85
85
  },
86
86
  "bugs": {
87
87
  "url": "https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/issues"
@@ -46,3 +46,5 @@
46
46
  {"timestamp":"2026-01-21T13:16:57.700Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
47
47
  {"timestamp":"2026-01-21T14:08:08.597Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
48
48
  {"timestamp":"2026-01-21T14:14:43.580Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
49
+ {"timestamp":"2026-01-21T14:19:14.000Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
50
+ {"timestamp":"2026-01-21T14:43:15.797Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
@@ -194,3 +194,11 @@
194
194
  {"timestamp":"2026-01-21T14:14:44.277Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
195
195
  {"timestamp":"2026-01-21T14:14:44.277Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
196
196
  {"timestamp":"2026-01-21T14:14:44.277Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
197
+ {"timestamp":"2026-01-21T14:19:14.062Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_START","data":{"repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"},"context":{}}
198
+ {"timestamp":"2026-01-21T14:19:14.069Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
199
+ {"timestamp":"2026-01-21T14:19:14.070Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
200
+ {"timestamp":"2026-01-21T14:19:14.070Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
201
+ {"timestamp":"2026-01-21T14:43:15.954Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_START","data":{"repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"},"context":{}}
202
+ {"timestamp":"2026-01-21T14:43:15.961Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
203
+ {"timestamp":"2026-01-21T14:43:15.961Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
204
+ {"timestamp":"2026-01-21T14:43:15.961Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
@@ -1450,3 +1450,59 @@
1450
1450
  {"timestamp":1769004905775,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1451
1451
  {"timestamp":1769004905775,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1452
1452
  {"timestamp":1769004905775,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1453
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1454
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1455
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1456
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1457
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1458
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1459
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1460
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1461
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1462
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1463
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1464
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1465
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1466
+ {"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1467
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1468
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1469
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1470
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1471
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1472
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1473
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1474
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1475
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1476
+ {"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1477
+ {"timestamp":1769005445389,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1478
+ {"timestamp":1769005445389,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1479
+ {"timestamp":1769005445390,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1480
+ {"timestamp":1769005445390,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1481
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1482
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1483
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1484
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1485
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1486
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1487
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1488
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1489
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1490
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1491
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1492
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1493
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1494
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1495
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1496
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1497
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1498
+ {"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1499
+ {"timestamp":1769006595797,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1500
+ {"timestamp":1769006595797,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1501
+ {"timestamp":1769006595797,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1502
+ {"timestamp":1769006595797,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1503
+ {"timestamp":1769006595797,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1504
+ {"timestamp":1769006595797,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1505
+ {"timestamp":1769006619003,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
1506
+ {"timestamp":1769006619004,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
1507
+ {"timestamp":1769006619004,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
1508
+ {"timestamp":1769006619004,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
@@ -0,0 +1,62 @@
1
+ const { summarizeSwiftTypes, evaluateMultipleTypeGroups, resolveSrpSeverity, isThinWrapperSummary } = require('../utils/ios-srp-helpers');
2
+
3
+ describe('ios srp heuristics', () => {
4
+ it('flags multiple unrelated types by group', () => {
5
+ const content = `
6
+ struct User { let id: String }
7
+ struct Order { let id: String }
8
+ struct Product { let id: String }
9
+ `;
10
+ const summaries = summarizeSwiftTypes(content);
11
+ const result = evaluateMultipleTypeGroups(summaries);
12
+ expect(result.shouldFlag).toBe(true);
13
+ expect(result.distinctGroups).toBeGreaterThan(1);
14
+ });
15
+
16
+ it('does not flag multiple types with shared prefix', () => {
17
+ const content = `
18
+ struct UserView { var body: some View { Text("x") } }
19
+ final class UserViewModel { func load() {} }
20
+ final class UserRouter { func route() {} }
21
+ `;
22
+ const summaries = summarizeSwiftTypes(content);
23
+ const result = evaluateMultipleTypeGroups(summaries);
24
+ expect(result.shouldFlag).toBe(false);
25
+ });
26
+
27
+ it('counts methods without nested type members', () => {
28
+ const content = `
29
+ struct Wrapper {
30
+ struct Nested { func inner() {} }
31
+ func outer() {}
32
+ }
33
+ `;
34
+ const summaries = summarizeSwiftTypes(content);
35
+ const wrapper = summaries.find((item) => item.name === 'Wrapper');
36
+ expect(wrapper.methodsCount).toBe(1);
37
+ });
38
+
39
+ it('detects thin wrappers', () => {
40
+ const content = `
41
+ struct TokenSpyStorageState {
42
+ var savedTokens: [String]
43
+ var deletedTokensCount: Int
44
+ }
45
+
46
+ final class AccessTokenSpyStorage {
47
+ var state: TokenSpyStorageState
48
+ }
49
+ `;
50
+ const summaries = summarizeSwiftTypes(content);
51
+ const wrapper = summaries.find((item) => item.name === 'AccessTokenSpyStorage');
52
+ expect(isThinWrapperSummary(wrapper)).toBe(true);
53
+ });
54
+
55
+ it('resolves severity by layer', () => {
56
+ const config = { coreSeverity: 'critical', defaultSeverity: 'high', testSeverity: 'low' };
57
+ expect(resolveSrpSeverity('Apps/Domain/User.swift', config)).toBe('critical');
58
+ expect(resolveSrpSeverity('Apps/Application/UserUseCase.swift', config)).toBe('critical');
59
+ expect(resolveSrpSeverity('Apps/Tests/UserTests.swift', config)).toBe('low');
60
+ expect(resolveSrpSeverity('Apps/Presentation/UserView.swift', config)).toBe('high');
61
+ });
62
+ });
@@ -17,6 +17,7 @@ const { iOSCICDRules } = require(path.join(__dirname, 'analyzers/iOSCICDRules'))
17
17
  const { iOSForbiddenLiteralsAnalyzer } = require(path.join(__dirname, 'analyzers/iOSForbiddenLiteralsAnalyzer'));
18
18
  const { iOSASTIntelligentAnalyzer } = require(path.join(__dirname, 'analyzers/iOSASTIntelligentAnalyzer'));
19
19
  const { iOSModernPracticesRules } = require(path.join(__dirname, 'analyzers/iOSModernPracticesRules'));
20
+ const { summarizeSwiftTypes, evaluateMultipleTypeGroups, resolveSrpSeverity, isThinWrapperSummary } = require(path.join(__dirname, 'utils/ios-srp-helpers'));
20
21
 
21
22
  function detectForbiddenTestableImport({ filePath, content }) {
22
23
  if (!filePath || !content) return null;
@@ -1134,29 +1135,42 @@ async function runIOSIntelligence(project, findings, platform) {
1134
1135
  // ==========================================
1135
1136
 
1136
1137
 
1137
- const typePattern = /\b(class|struct|enum|protocol)\s+\w+/g;
1138
- const classCount = (content.match(typePattern) || []).length;
1139
- if (classCount > 3 && !filePath.includes('Generated')) {
1138
+ const typeSummaries = summarizeSwiftTypes(content);
1139
+ const typeGroups = evaluateMultipleTypeGroups(typeSummaries);
1140
+ if (typeGroups.shouldFlag && !filePath.includes('Generated')) {
1141
+ const severity = resolveSrpSeverity(filePath, {
1142
+ coreSeverity: 'high',
1143
+ defaultSeverity: 'medium',
1144
+ testSeverity: 'low'
1145
+ });
1140
1146
  pushFinding(
1141
1147
  "ios.solid.srp_multiple_types",
1142
- "high",
1148
+ severity,
1143
1149
  sf,
1144
1150
  sf,
1145
- `File defines ${classCount} types - split into separate files (SRP: one responsibility per file)`,
1151
+ `File defines ${typeGroups.totalTypes} types across ${typeGroups.distinctGroups} domains - split by responsibility (SRP)`,
1146
1152
  findings
1147
1153
  );
1148
1154
  }
1149
1155
 
1150
- if (content.includes('class') || content.includes('struct')) {
1151
- const funcPattern = /func\s+\w+/g;
1152
- const funcCount = (content.match(funcPattern) || []).length;
1153
- if (funcCount > 20) {
1156
+ const godClassSeverityConfig = {
1157
+ coreSeverity: 'critical',
1158
+ defaultSeverity: 'high',
1159
+ testSeverity: 'low'
1160
+ };
1161
+ const godClassThreshold = 20;
1162
+ for (const summary of typeSummaries) {
1163
+ if (isThinWrapperSummary(summary)) {
1164
+ continue;
1165
+ }
1166
+ if (summary.methodsCount > godClassThreshold) {
1167
+ const severity = resolveSrpSeverity(filePath, godClassSeverityConfig);
1154
1168
  pushFinding(
1155
1169
  "ios.solid.srp_god_class",
1156
- "critical",
1170
+ severity,
1157
1171
  sf,
1158
1172
  sf,
1159
- `Type has ${funcCount} methods - split responsibilities (SRP: classes should have one reason to change)`,
1173
+ `Type '${summary.name}' has ${summary.methodsCount} methods - split responsibilities (SRP: one reason to change)`,
1160
1174
  findings
1161
1175
  );
1162
1176
  }
@@ -1,5 +1,6 @@
1
1
  const path = require('path');
2
2
  const DIValidationService = require('../../../../application/DIValidationService');
3
+ const { resolveSrpSeverity, isThinWrapperSummary } = require('../utils/ios-srp-helpers');
3
4
 
4
5
  const diValidationService = new DIValidationService();
5
6
 
@@ -168,16 +169,22 @@ async function analyzeClassAST(analyzer, node, filePath) {
168
169
 
169
170
  if (name && !/Spec$|Test$|Mock/.test(name) && !name.includes('Coordinator')) {
170
171
  const complexity = calculateComplexityAST(substructure);
171
- analyzer.godClassCandidates.push({
172
- name,
173
- filePath,
174
- line,
175
- methodsCount: methods.length,
176
- significantMethodsCount: significantMethods.length,
172
+ const isThinWrapper = isThinWrapperSummary({
173
+ methodsCount: significantMethods.length,
177
174
  propertiesCount: properties.length,
178
- bodyLength,
179
- complexity,
180
175
  });
176
+ if (!isThinWrapper) {
177
+ analyzer.godClassCandidates.push({
178
+ name,
179
+ filePath,
180
+ line,
181
+ methodsCount: methods.length,
182
+ significantMethodsCount: significantMethods.length,
183
+ propertiesCount: properties.length,
184
+ bodyLength,
185
+ complexity,
186
+ });
187
+ }
181
188
  }
182
189
 
183
190
  if (name.includes('ViewController')) {
@@ -803,9 +810,14 @@ function finalizeGodClassDetection(analyzer) {
803
810
  const signalCount = [sizeOutlier, complexityOutlier].filter(Boolean).length;
804
811
 
805
812
  if (extremeOutlier || signalCount >= 2) {
813
+ const severity = resolveSrpSeverity(c.filePath, {
814
+ coreSeverity: 'critical',
815
+ defaultSeverity: 'high',
816
+ testSeverity: 'low',
817
+ });
806
818
  analyzer.pushFinding(
807
819
  'ios.solid.srp.god_class',
808
- 'critical',
820
+ severity,
809
821
  c.filePath,
810
822
  c.line,
811
823
  `God class '${c.name}': ${c.methodsCount} methods (z=${methodsZ.toFixed(2)}), ${c.propertiesCount} properties (z=${propsZ.toFixed(2)}), body ${c.bodyLength} (z=${bodyZ.toFixed(2)}), complexity ${c.complexity} (z=${complexityZ.toFixed(2)}) - VIOLATES SRP`
@@ -0,0 +1,162 @@
1
+ function resolveSrpSeverity(filePath, { coreSeverity, defaultSeverity, testSeverity }) {
2
+ const normalized = String(filePath || '').replace(/\\/g, '/');
3
+ const isTest = /Tests|Test|Spec/.test(normalized);
4
+ if (isTest) return testSeverity || defaultSeverity;
5
+ const isCore = normalized.includes('/Domain/') || normalized.includes('/Application/');
6
+ if (isCore) return coreSeverity || defaultSeverity;
7
+ return defaultSeverity;
8
+ }
9
+
10
+ function findMatchingBraceIndex(content, startIndex) {
11
+ let depth = 0;
12
+ let inString = false;
13
+ let stringChar = '';
14
+ let inLineComment = false;
15
+ let inBlockComment = false;
16
+
17
+ for (let i = startIndex; i < content.length; i += 1) {
18
+ const current = content[i];
19
+ const next = content[i + 1];
20
+
21
+ if (inLineComment) {
22
+ if (current === '\n') inLineComment = false;
23
+ continue;
24
+ }
25
+
26
+ if (inBlockComment) {
27
+ if (current === '*' && next === '/') {
28
+ inBlockComment = false;
29
+ i += 1;
30
+ }
31
+ continue;
32
+ }
33
+
34
+ if (inString) {
35
+ if (current === '\\') {
36
+ i += 1;
37
+ continue;
38
+ }
39
+ if (current === stringChar) {
40
+ inString = false;
41
+ }
42
+ continue;
43
+ }
44
+
45
+ if (current === '/' && next === '/') {
46
+ inLineComment = true;
47
+ i += 1;
48
+ continue;
49
+ }
50
+
51
+ if (current === '/' && next === '*') {
52
+ inBlockComment = true;
53
+ i += 1;
54
+ continue;
55
+ }
56
+
57
+ if (current === '"' || current === "'") {
58
+ inString = true;
59
+ stringChar = current;
60
+ continue;
61
+ }
62
+
63
+ if (current === '{') depth += 1;
64
+ if (current === '}') {
65
+ depth -= 1;
66
+ if (depth === 0) return i;
67
+ }
68
+ }
69
+
70
+ return -1;
71
+ }
72
+
73
+ function extractSwiftTypeBlocks(content) {
74
+ const blocks = [];
75
+ const pattern = /(^|\n)\s*(?:public|internal|fileprivate|private|open)?\s*(?:final\s+)?(class|struct|enum|protocol)\s+([A-Za-z_][A-Za-z0-9_]*)[^\n{]*\{/g;
76
+ let match = pattern.exec(content);
77
+ while (match) {
78
+ const name = match[3];
79
+ const braceIndex = match.index + match[0].lastIndexOf('{');
80
+ const endIndex = findMatchingBraceIndex(content, braceIndex);
81
+ if (endIndex !== -1) {
82
+ blocks.push({
83
+ name,
84
+ openIndex: braceIndex,
85
+ closeIndex: endIndex,
86
+ body: content.slice(braceIndex + 1, endIndex)
87
+ });
88
+ pattern.lastIndex = endIndex + 1;
89
+ }
90
+ match = pattern.exec(content);
91
+ }
92
+ return blocks;
93
+ }
94
+
95
+ function stripNestedTypeBlocks(content) {
96
+ const nestedBlocks = extractSwiftTypeBlocks(content);
97
+ if (nestedBlocks.length === 0) return content;
98
+ const sorted = nestedBlocks.slice().sort((a, b) => b.openIndex - a.openIndex);
99
+ let output = content;
100
+ for (const block of sorted) {
101
+ const length = block.closeIndex - block.openIndex + 1;
102
+ output = `${output.slice(0, block.openIndex)}${' '.repeat(length)}${output.slice(block.closeIndex + 1)}`;
103
+ }
104
+ return output;
105
+ }
106
+
107
+ function countFunctions(content) {
108
+ const matches = content.match(/\bfunc\s+[A-Za-z_][A-Za-z0-9_]*/g) || [];
109
+ return matches.length;
110
+ }
111
+
112
+ function countProperties(content) {
113
+ const matches = content.match(/(^|\n)\s*(?:@[A-Za-z0-9_.]+\s*)*(var|let)\s+[A-Za-z_][A-Za-z0-9_]*/g) || [];
114
+ return matches.length;
115
+ }
116
+
117
+ function splitTypeName(name) {
118
+ return String(name).match(/[A-Z]+(?![a-z])|[A-Z]?[a-z]+|[0-9]+/g) || [name];
119
+ }
120
+
121
+ function getTypeGroupKey(name) {
122
+ const parts = splitTypeName(name);
123
+ return parts[0] || name;
124
+ }
125
+
126
+ function summarizeSwiftTypes(content) {
127
+ const blocks = extractSwiftTypeBlocks(String(content || ''));
128
+ return blocks.map((block) => {
129
+ const sanitizedBody = stripNestedTypeBlocks(block.body);
130
+ return {
131
+ name: block.name,
132
+ methodsCount: countFunctions(sanitizedBody),
133
+ propertiesCount: countProperties(sanitizedBody),
134
+ groupKey: getTypeGroupKey(block.name)
135
+ };
136
+ });
137
+ }
138
+
139
+ function evaluateMultipleTypeGroups(summaries) {
140
+ const groups = new Set((summaries || []).map((item) => item.groupKey).filter(Boolean));
141
+ const totalTypes = (summaries || []).length;
142
+ const distinctGroups = groups.size;
143
+ const shouldFlag = totalTypes >= 3 && distinctGroups >= 2;
144
+ return {
145
+ totalTypes,
146
+ distinctGroups,
147
+ shouldFlag,
148
+ groups: Array.from(groups)
149
+ };
150
+ }
151
+
152
+ function isThinWrapperSummary(summary) {
153
+ if (!summary) return false;
154
+ return summary.methodsCount === 0 && summary.propertiesCount <= 1;
155
+ }
156
+
157
+ module.exports = {
158
+ summarizeSwiftTypes,
159
+ evaluateMultipleTypeGroups,
160
+ resolveSrpSeverity,
161
+ isThinWrapperSummary
162
+ };
@@ -142,3 +142,9 @@
142
142
  {"timestamp":"2026-01-21T14:14:45.047Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"ok","percentUsed":10,"tokensUsed":100000,"maxTokens":1000000,"source":"realtime","stale":false},"context":{"message":"Result level=ok percent=10% used=100000/1000000 source=realtime"}}
143
143
  {"timestamp":"2026-01-21T14:14:45.048Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"warning","percentUsed":91,"tokensUsed":910000,"maxTokens":1000000,"source":"fallback","stale":false},"context":{"message":"Result level=warning percent=91% used=910000/1000000 source=fallback"}}
144
144
  {"timestamp":"2026-01-21T14:14:45.048Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"critical","percentUsed":98,"tokensUsed":980000,"maxTokens":1000000,"source":"realtime","stale":true},"context":{"message":"Result level=critical percent=98% used=980000/1000000 source=realtime (stale)"}}
145
+ {"timestamp":"2026-01-21T14:19:16.416Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"ok","percentUsed":10,"tokensUsed":100000,"maxTokens":1000000,"source":"realtime","stale":false},"context":{"message":"Result level=ok percent=10% used=100000/1000000 source=realtime"}}
146
+ {"timestamp":"2026-01-21T14:19:16.417Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"warning","percentUsed":91,"tokensUsed":910000,"maxTokens":1000000,"source":"fallback","stale":false},"context":{"message":"Result level=warning percent=91% used=910000/1000000 source=fallback"}}
147
+ {"timestamp":"2026-01-21T14:19:16.418Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"critical","percentUsed":98,"tokensUsed":980000,"maxTokens":1000000,"source":"realtime","stale":true},"context":{"message":"Result level=critical percent=98% used=980000/1000000 source=realtime (stale)"}}
148
+ {"timestamp":"2026-01-21T14:43:17.747Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"ok","percentUsed":10,"tokensUsed":100000,"maxTokens":1000000,"source":"realtime","stale":false},"context":{"message":"Result level=ok percent=10% used=100000/1000000 source=realtime"}}
149
+ {"timestamp":"2026-01-21T14:43:17.748Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"warning","percentUsed":91,"tokensUsed":910000,"maxTokens":1000000,"source":"fallback","stale":false},"context":{"message":"Result level=warning percent=91% used=910000/1000000 source=fallback"}}
150
+ {"timestamp":"2026-01-21T14:43:17.749Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"critical","percentUsed":98,"tokensUsed":980000,"maxTokens":1000000,"source":"realtime","stale":true},"context":{"message":"Result level=critical percent=98% used=980000/1000000 source=realtime (stale)"}}