pumuki-ast-hooks 5.3.1

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 (567) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1105 -0
  3. package/bin/__tests__/auto-fix-violations.spec.js +132 -0
  4. package/bin/__tests__/auto-restart-guards.spec.js +11 -0
  5. package/bin/__tests__/check-doc-drift.spec.js +11 -0
  6. package/bin/__tests__/check-version.spec.js +240 -0
  7. package/bin/__tests__/cli.spec.js +11 -0
  8. package/bin/__tests__/guard-auto-manager.spec.js +11 -0
  9. package/bin/__tests__/guard-supervisor.spec.js +11 -0
  10. package/bin/__tests__/hook-status.spec.js +11 -0
  11. package/bin/__tests__/install.spec.js +11 -0
  12. package/bin/__tests__/nightly-metrics-report.spec.js +94 -0
  13. package/bin/__tests__/plan-review.spec.js +11 -0
  14. package/bin/__tests__/predictive-hooks.spec.js +11 -0
  15. package/bin/__tests__/run-ast-adapter.spec.js +11 -0
  16. package/bin/__tests__/run-orchestrator.spec.js +11 -0
  17. package/bin/__tests__/run-playbook.spec.js +11 -0
  18. package/bin/__tests__/setup-eslint.spec.js +11 -0
  19. package/bin/__tests__/violations-api.spec.js +11 -0
  20. package/bin/__tests__/watch-hooks.spec.js +11 -0
  21. package/bin/ai-commit.sh +5 -0
  22. package/bin/audit +5 -0
  23. package/bin/audit-library.js +6 -0
  24. package/bin/auto-fix-violations.js +19 -0
  25. package/bin/auto-restart-guards.js +6 -0
  26. package/bin/check-doc-drift.js +6 -0
  27. package/bin/check-version.js +19 -0
  28. package/bin/cleanup-branches.sh +5 -0
  29. package/bin/cli.js +6 -0
  30. package/bin/demo-recording.sh +5 -0
  31. package/bin/demo-violations +5 -0
  32. package/bin/fix-enforcer +5 -0
  33. package/bin/fix-gitflow-enforcement.sh +5 -0
  34. package/bin/generate-progress-report.sh +5 -0
  35. package/bin/git-analyze-pairs.sh +5 -0
  36. package/bin/git-leave-branch-check.sh +5 -0
  37. package/bin/gitflow +5 -0
  38. package/bin/gitflow-shell-integration.sh +5 -0
  39. package/bin/guard-auto-manager.js +6 -0
  40. package/bin/guard-autostart.sh +5 -0
  41. package/bin/guard-env.sh +5 -0
  42. package/bin/guard-supervisor.js +6 -0
  43. package/bin/hook-status.js +6 -0
  44. package/bin/install-git-wrapper.sh +5 -0
  45. package/bin/install.js +6 -0
  46. package/bin/kill-mcp-zombies.sh +5 -0
  47. package/bin/nightly-metrics-report.js +8 -0
  48. package/bin/plan-review.js +6 -0
  49. package/bin/predictive-hooks.js +6 -0
  50. package/bin/pumuki-audit.js +6 -0
  51. package/bin/pumuki-init.js +19 -0
  52. package/bin/pumuki-mcp-server.js +13 -0
  53. package/bin/pumuki-mcp.js +6 -0
  54. package/bin/pumuki-rules.js +6 -0
  55. package/bin/request-no-verify-approval.sh +5 -0
  56. package/bin/run-ast-adapter.js +6 -0
  57. package/bin/run-intelligent-audit.sh +5 -0
  58. package/bin/run-orchestrator.js +6 -0
  59. package/bin/run-playbook.js +6 -0
  60. package/bin/session-loader.sh +5 -0
  61. package/bin/setup-eslint.js +6 -0
  62. package/bin/start-guards.sh +5 -0
  63. package/bin/sync-autonomous-orchestrator.sh +5 -0
  64. package/bin/sync-to-library.sh +5 -0
  65. package/bin/update-evidence.sh +5 -0
  66. package/bin/update-session-context.sh +5 -0
  67. package/bin/verify-no-verify.sh +5 -0
  68. package/bin/violations +5 -0
  69. package/bin/violations-api.js +6 -0
  70. package/bin/watch-hooks.js +6 -0
  71. package/docs/API_REFERENCE.md +161 -0
  72. package/docs/ARCHITECTURE.md +236 -0
  73. package/docs/ARCHITECTURE_DETAILED.md +499 -0
  74. package/docs/BRANCH_PROTECTION_GUIDE.md +236 -0
  75. package/docs/CODE_STANDARDS.md +440 -0
  76. package/docs/CONTRIBUTING.md +246 -0
  77. package/docs/DEPENDENCIES.md +541 -0
  78. package/docs/HOW_IT_WORKS.md +716 -0
  79. package/docs/INSTALLATION.md +784 -0
  80. package/docs/MCP_SERVERS.md +786 -0
  81. package/docs/TESTING.md +423 -0
  82. package/docs/USAGE.md +856 -0
  83. package/docs/images/ast_intelligence_01.png +0 -0
  84. package/docs/images/ast_intelligence_02.png +0 -0
  85. package/docs/images/ast_intelligence_03.png +0 -0
  86. package/docs/images/ast_intelligence_04.png +0 -0
  87. package/docs/images/ast_intelligence_05.png +0 -0
  88. package/hooks/getSkillRulesPath.ts +52 -0
  89. package/hooks/git-status-monitor.ts +160 -0
  90. package/hooks/index.js +5 -0
  91. package/hooks/notify-macos.ts +42 -0
  92. package/hooks/package.json +16 -0
  93. package/hooks/post-tool-use-tracker.sh +89 -0
  94. package/hooks/pre-tool-use-evidence-validator.ts +252 -0
  95. package/hooks/pre-tool-use-guard.ts +151 -0
  96. package/hooks/skill-activation-prompt.sh +8 -0
  97. package/hooks/skill-activation-prompt.ts +307 -0
  98. package/index.js +49 -0
  99. package/package.json +117 -0
  100. package/presentation/cli/audit.sh +24 -0
  101. package/presentation/cli/autonomous-status.sh +92 -0
  102. package/presentation/cli/categorize-violations.sh +179 -0
  103. package/presentation/cli/direct-audit-option2.sh +23 -0
  104. package/presentation/cli/direct-audit.sh +33 -0
  105. package/scripts/hooks-system/.AI_TOKEN_STATUS.txt +16 -0
  106. package/scripts/hooks-system/.audit-reports/auto-recovery.log +1 -0
  107. package/scripts/hooks-system/.audit-reports/install-wizard.log +4 -0
  108. package/scripts/hooks-system/.audit-reports/notifications.log +425 -0
  109. package/scripts/hooks-system/.audit-reports/token-monitor.log +1275 -0
  110. package/scripts/hooks-system/.audit_tmp/intelligent-report.json +44953 -0
  111. package/scripts/hooks-system/.audit_tmp/intelligent-report.txt +1338 -0
  112. package/scripts/hooks-system/.audit_tmp/severity-history.jsonl +1 -0
  113. package/scripts/hooks-system/.audit_tmp/token-usage.jsonl +1 -0
  114. package/scripts/hooks-system/.hook-system/config.json +8 -0
  115. package/scripts/hooks-system/application/CompositionRoot.js +325 -0
  116. package/scripts/hooks-system/application/__tests__/CompositionRoot.spec.js +84 -0
  117. package/scripts/hooks-system/application/commands/index.js +64 -0
  118. package/scripts/hooks-system/application/queries/index.js +60 -0
  119. package/scripts/hooks-system/application/services/AutonomousOrchestrator.js +130 -0
  120. package/scripts/hooks-system/application/services/ContextDetectionEngine.js +181 -0
  121. package/scripts/hooks-system/application/services/DynamicRulesLoader.js +182 -0
  122. package/scripts/hooks-system/application/services/GitFlowService.js +156 -0
  123. package/scripts/hooks-system/application/services/GitTreeState.js +140 -0
  124. package/scripts/hooks-system/application/services/HookSystemScheduler.js +77 -0
  125. package/scripts/hooks-system/application/services/IntelligentCommitAnalyzer.js +151 -0
  126. package/scripts/hooks-system/application/services/IntelligentGitTreeMonitor.js +118 -0
  127. package/scripts/hooks-system/application/services/PlatformAnalysisService.js +173 -0
  128. package/scripts/hooks-system/application/services/PlatformDetectionService.js +168 -0
  129. package/scripts/hooks-system/application/services/PlaybookRunner.js +39 -0
  130. package/scripts/hooks-system/application/services/PredictiveHookAdvisor.js +56 -0
  131. package/scripts/hooks-system/application/services/RealtimeGuardPlugin.js +62 -0
  132. package/scripts/hooks-system/application/services/RealtimeGuardService.js +374 -0
  133. package/scripts/hooks-system/application/services/SmartDirtyTreeAnalyzer.js +63 -0
  134. package/scripts/hooks-system/application/services/__tests__/AutonomousOrchestrator.spec.js +36 -0
  135. package/scripts/hooks-system/application/services/__tests__/ContextDetectionEngine.spec.js +33 -0
  136. package/scripts/hooks-system/application/services/__tests__/DynamicRulesLoader.spec.js +43 -0
  137. package/scripts/hooks-system/application/services/__tests__/GitTreeState.spec.js +163 -0
  138. package/scripts/hooks-system/application/services/__tests__/HookSystemScheduler.spec.js +207 -0
  139. package/scripts/hooks-system/application/services/__tests__/IntelligentCommitAnalyzer.spec.js +365 -0
  140. package/scripts/hooks-system/application/services/__tests__/IntelligentGitTreeMonitor.spec.js +188 -0
  141. package/scripts/hooks-system/application/services/__tests__/PlatformDetectionService.spec.js +28 -0
  142. package/scripts/hooks-system/application/services/__tests__/PlaybookRunner.spec.js +143 -0
  143. package/scripts/hooks-system/application/services/__tests__/PredictiveHookAdvisor.spec.js +181 -0
  144. package/scripts/hooks-system/application/services/__tests__/RealtimeGuardPlugin.spec.js +45 -0
  145. package/scripts/hooks-system/application/services/__tests__/RealtimeGuardService.critical.spec.js +401 -0
  146. package/scripts/hooks-system/application/services/commit/CommitMessageGenerator.js +34 -0
  147. package/scripts/hooks-system/application/services/commit/FeatureDetector.js +101 -0
  148. package/scripts/hooks-system/application/services/evidence/EvidenceContextManager.js +163 -0
  149. package/scripts/hooks-system/application/services/evidence/__tests__/EvidenceContextManager.spec.js +98 -0
  150. package/scripts/hooks-system/application/services/guard/GuardAutoManagerService.js +169 -0
  151. package/scripts/hooks-system/application/services/guard/GuardConfig.js +15 -0
  152. package/scripts/hooks-system/application/services/guard/GuardEventLogger.js +70 -0
  153. package/scripts/hooks-system/application/services/guard/GuardHealthReminder.js +54 -0
  154. package/scripts/hooks-system/application/services/guard/GuardHeartbeatMonitor.js +94 -0
  155. package/scripts/hooks-system/application/services/guard/GuardLockManager.js +72 -0
  156. package/scripts/hooks-system/application/services/guard/GuardMonitorLoop.js +29 -0
  157. package/scripts/hooks-system/application/services/guard/GuardNotificationHandler.js +36 -0
  158. package/scripts/hooks-system/application/services/guard/GuardProcessManager.js +113 -0
  159. package/scripts/hooks-system/application/services/guard/GuardRecoveryService.js +90 -0
  160. package/scripts/hooks-system/application/services/guard/__tests__/GuardAutoManagerService.spec.js +77 -0
  161. package/scripts/hooks-system/application/services/installation/ConfigurationGeneratorService.js +123 -0
  162. package/scripts/hooks-system/application/services/installation/FileSystemInstallerService.js +112 -0
  163. package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +166 -0
  164. package/scripts/hooks-system/application/services/installation/HookInstaller.js +197 -0
  165. package/scripts/hooks-system/application/services/installation/IdeIntegrationService.js +37 -0
  166. package/scripts/hooks-system/application/services/installation/InstallService.js +130 -0
  167. package/scripts/hooks-system/application/services/installation/McpConfigurator.js +172 -0
  168. package/scripts/hooks-system/application/services/installation/PlatformDetectorService.js +36 -0
  169. package/scripts/hooks-system/application/services/installation/VSCodeTaskConfigurator.js +97 -0
  170. package/scripts/hooks-system/application/services/logging/UnifiedLogger.js +142 -0
  171. package/scripts/hooks-system/application/services/logging/__tests__/UnifiedLogger.spec.js +66 -0
  172. package/scripts/hooks-system/application/services/monitoring/ActivityMonitor.js +80 -0
  173. package/scripts/hooks-system/application/services/monitoring/AstMonitor.js +140 -0
  174. package/scripts/hooks-system/application/services/monitoring/DevDocsMonitor.js +85 -0
  175. package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +103 -0
  176. package/scripts/hooks-system/application/services/monitoring/EvidenceMonitorService.js +162 -0
  177. package/scripts/hooks-system/application/services/monitoring/GitTreeMonitor.js +123 -0
  178. package/scripts/hooks-system/application/services/monitoring/GitTreeMonitorService.js +114 -0
  179. package/scripts/hooks-system/application/services/monitoring/HealthCheckProviders.js +153 -0
  180. package/scripts/hooks-system/application/services/monitoring/HealthCheckService.js +118 -0
  181. package/scripts/hooks-system/application/services/monitoring/HeartbeatMonitorService.js +61 -0
  182. package/scripts/hooks-system/application/services/monitoring/TokenMonitor.js +60 -0
  183. package/scripts/hooks-system/application/services/monitoring/__tests__/EvidenceMonitorService.spec.js +107 -0
  184. package/scripts/hooks-system/application/services/monitoring/__tests__/GitTreeMonitorService.spec.js +27 -0
  185. package/scripts/hooks-system/application/services/monitoring/__tests__/HealthCheckProviders.spec.js +68 -0
  186. package/scripts/hooks-system/application/services/monitoring/__tests__/HealthCheckService.spec.js +69 -0
  187. package/scripts/hooks-system/application/services/monitoring/__tests__/HeartbeatMonitorService.spec.js +35 -0
  188. package/scripts/hooks-system/application/services/notification/MacNotificationSender.js +106 -0
  189. package/scripts/hooks-system/application/services/notification/NotificationCenterService.js +221 -0
  190. package/scripts/hooks-system/application/services/notification/NotificationDispatcher.js +42 -0
  191. package/scripts/hooks-system/application/services/notification/__tests__/NotificationCenterService.spec.js +40 -0
  192. package/scripts/hooks-system/application/services/notification/components/NotificationCooldownManager.js +62 -0
  193. package/scripts/hooks-system/application/services/notification/components/NotificationDeduplicator.js +67 -0
  194. package/scripts/hooks-system/application/services/notification/components/NotificationQueue.js +36 -0
  195. package/scripts/hooks-system/application/services/notification/components/NotificationRetryExecutor.js +58 -0
  196. package/scripts/hooks-system/application/services/platform/PlatformHeuristics.js +144 -0
  197. package/scripts/hooks-system/application/services/recovery/AutoRecoveryManager.js +137 -0
  198. package/scripts/hooks-system/application/services/recovery/__tests__/AutoRecoveryManager.spec.js +62 -0
  199. package/scripts/hooks-system/application/services/smart-commit/CommitMessageSuggester.js +97 -0
  200. package/scripts/hooks-system/application/services/smart-commit/FileContextGrouper.js +114 -0
  201. package/scripts/hooks-system/application/services/smart-commit/SmartCommitSummaryBuilder.js +53 -0
  202. package/scripts/hooks-system/application/services/token/CursorTokenService.js +44 -0
  203. package/scripts/hooks-system/application/services/token/TokenMetricsService.js +109 -0
  204. package/scripts/hooks-system/application/services/token/TokenMonitorService.js +160 -0
  205. package/scripts/hooks-system/application/services/token/TokenStatusReporter.js +56 -0
  206. package/scripts/hooks-system/application/services/token/__tests__/CursorTokenService.spec.js +69 -0
  207. package/scripts/hooks-system/application/services/token/__tests__/TokenMonitorService.spec.js +185 -0
  208. package/scripts/hooks-system/application/state/HookSystemStateMachine.js +59 -0
  209. package/scripts/hooks-system/application/state/__tests__/HookSystemStateMachine.spec.js +115 -0
  210. package/scripts/hooks-system/application/use-cases/AnalyzeCodebaseUseCase.js +54 -0
  211. package/scripts/hooks-system/application/use-cases/AnalyzeStagedFilesUseCase.js +61 -0
  212. package/scripts/hooks-system/application/use-cases/AutoExecuteAIStartUseCase.js +123 -0
  213. package/scripts/hooks-system/application/use-cases/BlockCommitUseCase.js +90 -0
  214. package/scripts/hooks-system/application/use-cases/GenerateAuditReportUseCase.js +184 -0
  215. package/scripts/hooks-system/application/use-cases/__tests__/AnalyzeCodebaseUseCase.spec.js +156 -0
  216. package/scripts/hooks-system/application/use-cases/__tests__/AnalyzeStagedFilesUseCase.spec.js +146 -0
  217. package/scripts/hooks-system/application/use-cases/__tests__/AutoExecuteAIStartUseCase.spec.js +89 -0
  218. package/scripts/hooks-system/application/use-cases/__tests__/BlockCommitUseCase.spec.js +171 -0
  219. package/scripts/hooks-system/application/use-cases/__tests__/GenerateAuditReportUseCase.spec.js +207 -0
  220. package/scripts/hooks-system/bin/__tests__/auto-fix-violations.spec.js +132 -0
  221. package/scripts/hooks-system/bin/__tests__/auto-restart-guards.spec.js +11 -0
  222. package/scripts/hooks-system/bin/__tests__/check-doc-drift.spec.js +11 -0
  223. package/scripts/hooks-system/bin/__tests__/check-version.spec.js +240 -0
  224. package/scripts/hooks-system/bin/__tests__/cli.spec.js +11 -0
  225. package/scripts/hooks-system/bin/__tests__/guard-auto-manager.spec.js +11 -0
  226. package/scripts/hooks-system/bin/__tests__/guard-supervisor.spec.js +11 -0
  227. package/scripts/hooks-system/bin/__tests__/hook-status.spec.js +11 -0
  228. package/scripts/hooks-system/bin/__tests__/install.spec.js +11 -0
  229. package/scripts/hooks-system/bin/__tests__/nightly-metrics-report.spec.js +94 -0
  230. package/scripts/hooks-system/bin/__tests__/plan-review.spec.js +11 -0
  231. package/scripts/hooks-system/bin/__tests__/predictive-hooks.spec.js +11 -0
  232. package/scripts/hooks-system/bin/__tests__/run-ast-adapter.spec.js +11 -0
  233. package/scripts/hooks-system/bin/__tests__/run-orchestrator.spec.js +11 -0
  234. package/scripts/hooks-system/bin/__tests__/run-playbook.spec.js +11 -0
  235. package/scripts/hooks-system/bin/__tests__/setup-eslint.spec.js +11 -0
  236. package/scripts/hooks-system/bin/__tests__/violations-api.spec.js +11 -0
  237. package/scripts/hooks-system/bin/__tests__/watch-hooks.spec.js +11 -0
  238. package/scripts/hooks-system/bin/ai-commit.sh +63 -0
  239. package/scripts/hooks-system/bin/audit +463 -0
  240. package/scripts/hooks-system/bin/audit-library.js +54 -0
  241. package/scripts/hooks-system/bin/auto-fix-violations.js +130 -0
  242. package/scripts/hooks-system/bin/auto-restart-guards.js +93 -0
  243. package/scripts/hooks-system/bin/check-doc-drift.js +35 -0
  244. package/scripts/hooks-system/bin/check-version.js +201 -0
  245. package/scripts/hooks-system/bin/cleanup-branches.sh +106 -0
  246. package/scripts/hooks-system/bin/cli.js +208 -0
  247. package/scripts/hooks-system/bin/demo-recording.sh +57 -0
  248. package/scripts/hooks-system/bin/demo-violations +44 -0
  249. package/scripts/hooks-system/bin/fix-enforcer +27 -0
  250. package/scripts/hooks-system/bin/fix-gitflow-enforcement.sh +68 -0
  251. package/scripts/hooks-system/bin/generate-progress-report.sh +129 -0
  252. package/scripts/hooks-system/bin/git-analyze-pairs.sh +0 -0
  253. package/scripts/hooks-system/bin/git-leave-branch-check.sh +73 -0
  254. package/scripts/hooks-system/bin/gitflow +17 -0
  255. package/scripts/hooks-system/bin/gitflow-shell-integration.sh +64 -0
  256. package/scripts/hooks-system/bin/guard-auto-manager.js +44 -0
  257. package/scripts/hooks-system/bin/guard-autostart.sh +158 -0
  258. package/scripts/hooks-system/bin/guard-env.sh +40 -0
  259. package/scripts/hooks-system/bin/guard-supervisor.js +516 -0
  260. package/scripts/hooks-system/bin/hook-status.js +41 -0
  261. package/scripts/hooks-system/bin/install-git-wrapper.sh +53 -0
  262. package/scripts/hooks-system/bin/install.js +10 -0
  263. package/scripts/hooks-system/bin/kill-mcp-zombies.sh +48 -0
  264. package/scripts/hooks-system/bin/nightly-metrics-report.js +138 -0
  265. package/scripts/hooks-system/bin/plan-review.js +31 -0
  266. package/scripts/hooks-system/bin/predictive-hooks.js +18 -0
  267. package/scripts/hooks-system/bin/pumuki-audit.js +113 -0
  268. package/scripts/hooks-system/bin/pumuki-init.js +104 -0
  269. package/scripts/hooks-system/bin/pumuki-mcp.js +74 -0
  270. package/scripts/hooks-system/bin/pumuki-rules.js +74 -0
  271. package/scripts/hooks-system/bin/request-no-verify-approval.sh +116 -0
  272. package/scripts/hooks-system/bin/run-ast-adapter.js +86 -0
  273. package/scripts/hooks-system/bin/run-intelligent-audit.sh +67 -0
  274. package/scripts/hooks-system/bin/run-orchestrator.js +27 -0
  275. package/scripts/hooks-system/bin/run-playbook.js +23 -0
  276. package/scripts/hooks-system/bin/session-loader.sh +264 -0
  277. package/scripts/hooks-system/bin/setup-eslint.js +110 -0
  278. package/scripts/hooks-system/bin/start-guards.sh +190 -0
  279. package/scripts/hooks-system/bin/sync-autonomous-orchestrator.sh +32 -0
  280. package/scripts/hooks-system/bin/sync-to-library.sh +46 -0
  281. package/scripts/hooks-system/bin/update-evidence.sh +1167 -0
  282. package/scripts/hooks-system/bin/update-session-context.sh +261 -0
  283. package/scripts/hooks-system/bin/verify-no-verify.sh +68 -0
  284. package/scripts/hooks-system/bin/violations +20 -0
  285. package/scripts/hooks-system/bin/violations-api.js +345 -0
  286. package/scripts/hooks-system/bin/watch-hooks.js +20 -0
  287. package/scripts/hooks-system/config/project.config.json +36 -0
  288. package/scripts/hooks-system/config/state-map.json +12 -0
  289. package/scripts/hooks-system/domain/entities/AuditResult.js +139 -0
  290. package/scripts/hooks-system/domain/entities/Finding.js +116 -0
  291. package/scripts/hooks-system/domain/entities/SeverityConfig.js +73 -0
  292. package/scripts/hooks-system/domain/entities/SeverityConfig.ts +90 -0
  293. package/scripts/hooks-system/domain/entities/__tests__/AuditResult.spec.js +450 -0
  294. package/scripts/hooks-system/domain/entities/__tests__/Finding.spec.js +335 -0
  295. package/scripts/hooks-system/domain/entities/__tests__/SeverityConfig.spec.js +240 -0
  296. package/scripts/hooks-system/domain/entities/__tests__/entities.spec.js +29 -0
  297. package/scripts/hooks-system/domain/errors/__tests__/DomainErrors.spec.js +59 -0
  298. package/scripts/hooks-system/domain/errors/index.js +169 -0
  299. package/scripts/hooks-system/domain/events/__tests__/DomainEvents.spec.js +60 -0
  300. package/scripts/hooks-system/domain/events/index.js +121 -0
  301. package/scripts/hooks-system/domain/ports/IAstPort.js +67 -0
  302. package/scripts/hooks-system/domain/ports/IEvidencePort.js +86 -0
  303. package/scripts/hooks-system/domain/ports/IGitCommandPort.js +110 -0
  304. package/scripts/hooks-system/domain/ports/IGitPort.js +114 -0
  305. package/scripts/hooks-system/domain/ports/IGitQueryPort.js +93 -0
  306. package/scripts/hooks-system/domain/ports/INotificationPort.js +35 -0
  307. package/scripts/hooks-system/domain/ports/__tests__/ports.spec.js +36 -0
  308. package/scripts/hooks-system/domain/ports/index.js +14 -0
  309. package/scripts/hooks-system/domain/repositories/ICursorTokenRepository.js +13 -0
  310. package/scripts/hooks-system/domain/repositories/IFindingsRepository.js +30 -0
  311. package/scripts/hooks-system/domain/repositories/__tests__/IFindingsRepository.spec.js +18 -0
  312. package/scripts/hooks-system/domain/rules/CommitBlockingRules.js +142 -0
  313. package/scripts/hooks-system/domain/rules/__tests__/CommitBlockingRules.spec.js +18 -0
  314. package/scripts/hooks-system/domain/services/AuditAnalyzer.js +103 -0
  315. package/scripts/hooks-system/domain/services/AuditFilter.js +26 -0
  316. package/scripts/hooks-system/domain/services/AuditResultSerializer.js +35 -0
  317. package/scripts/hooks-system/domain/services/AuditScorer.js +38 -0
  318. package/scripts/hooks-system/domain/values/Severity.js +93 -0
  319. package/scripts/hooks-system/index.js +49 -0
  320. package/scripts/hooks-system/infrastructure/adapters/AstAnalyzerAdapter.js +150 -0
  321. package/scripts/hooks-system/infrastructure/adapters/FileEvidenceAdapter.js +140 -0
  322. package/scripts/hooks-system/infrastructure/adapters/GitCliAdapter.js +16 -0
  323. package/scripts/hooks-system/infrastructure/adapters/GitCommandAdapter.js +68 -0
  324. package/scripts/hooks-system/infrastructure/adapters/GitHubCliAdapter.js +85 -0
  325. package/scripts/hooks-system/infrastructure/adapters/GitQueryAdapter.js +58 -0
  326. package/scripts/hooks-system/infrastructure/adapters/LegacyAnalyzerAdapter.js +61 -0
  327. package/scripts/hooks-system/infrastructure/adapters/MacOSNotificationAdapter.js +99 -0
  328. package/scripts/hooks-system/infrastructure/adapters/__tests__/AstAnalyzerAdapter.spec.js +32 -0
  329. package/scripts/hooks-system/infrastructure/adapters/__tests__/FileEvidenceAdapter.spec.js +31 -0
  330. package/scripts/hooks-system/infrastructure/adapters/__tests__/GitCliAdapter.spec.js +39 -0
  331. package/scripts/hooks-system/infrastructure/adapters/__tests__/MacOSNotificationAdapter.spec.js +33 -0
  332. package/scripts/hooks-system/infrastructure/adapters/git/GitCommandRunner.js +78 -0
  333. package/scripts/hooks-system/infrastructure/adapters/git/GitCommandService.js +67 -0
  334. package/scripts/hooks-system/infrastructure/adapters/git/GitQueryService.js +50 -0
  335. package/scripts/hooks-system/infrastructure/adapters/index.js +14 -0
  336. package/scripts/hooks-system/infrastructure/ast/README.md +198 -0
  337. package/scripts/hooks-system/infrastructure/ast/__tests__/ast-core.spec.js +160 -0
  338. package/scripts/hooks-system/infrastructure/ast/__tests__/ast-intelligence.spec.js +20 -0
  339. package/scripts/hooks-system/infrastructure/ast/android/__tests__/ast-android.spec.js +33 -0
  340. package/scripts/hooks-system/infrastructure/ast/android/__tests__/clean-architecture-analyzer.spec.js +96 -0
  341. package/scripts/hooks-system/infrastructure/ast/android/__tests__/ddd-analyzer.spec.js +113 -0
  342. package/scripts/hooks-system/infrastructure/ast/android/__tests__/detekt-runner.spec.js +36 -0
  343. package/scripts/hooks-system/infrastructure/ast/android/__tests__/feature-first-analyzer.spec.js +80 -0
  344. package/scripts/hooks-system/infrastructure/ast/android/__tests__/native-bridge.spec.js +31 -0
  345. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidASTIntelligentAnalyzer.js +15 -0
  346. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidASTParser.js +157 -0
  347. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidAnalysisOrchestrator.js +164 -0
  348. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidArchitectureDetector.js +334 -0
  349. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidClassAnalyzer.js +162 -0
  350. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidForbiddenLiteralsAnalyzer.js +261 -0
  351. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidSOLIDAnalyzer.js +287 -0
  352. package/scripts/hooks-system/infrastructure/ast/android/analyzers/__tests__/AndroidForbiddenLiteralsAnalyzer.spec.js +58 -0
  353. package/scripts/hooks-system/infrastructure/ast/android/analyzers/__tests__/AndroidSOLIDAnalyzer.spec.js +84 -0
  354. package/scripts/hooks-system/infrastructure/ast/android/ast-android.js +1785 -0
  355. package/scripts/hooks-system/infrastructure/ast/android/clean-architecture-analyzer.js +115 -0
  356. package/scripts/hooks-system/infrastructure/ast/android/ddd-analyzer.js +70 -0
  357. package/scripts/hooks-system/infrastructure/ast/android/detekt-runner.js +81 -0
  358. package/scripts/hooks-system/infrastructure/ast/android/feature-first-analyzer.js +53 -0
  359. package/scripts/hooks-system/infrastructure/ast/android/native-bridge.js +119 -0
  360. package/scripts/hooks-system/infrastructure/ast/archive/README.md +18 -0
  361. package/scripts/hooks-system/infrastructure/ast/archive/ast-intelligence.ts +276 -0
  362. package/scripts/hooks-system/infrastructure/ast/archive/ios-rules.js +329 -0
  363. package/scripts/hooks-system/infrastructure/ast/archive/kotlin-analyzer.js +332 -0
  364. package/scripts/hooks-system/infrastructure/ast/archive/kotlin-parser.js +303 -0
  365. package/scripts/hooks-system/infrastructure/ast/archive/swift-analyzer.js +390 -0
  366. package/scripts/hooks-system/infrastructure/ast/ast-core.js +594 -0
  367. package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +617 -0
  368. package/scripts/hooks-system/infrastructure/ast/backend/__tests__/ast-backend.spec.js +20 -0
  369. package/scripts/hooks-system/infrastructure/ast/backend/__tests__/clean-architecture-analyzer.spec.js +151 -0
  370. package/scripts/hooks-system/infrastructure/ast/backend/__tests__/ddd-analyzer.spec.js +124 -0
  371. package/scripts/hooks-system/infrastructure/ast/backend/__tests__/feature-first-analyzer.spec.js +128 -0
  372. package/scripts/hooks-system/infrastructure/ast/backend/__tests__/forbidden-literals-analyzer.spec.js +95 -0
  373. package/scripts/hooks-system/infrastructure/ast/backend/__tests__/nestjs-patterns-analyzer.spec.js +59 -0
  374. package/scripts/hooks-system/infrastructure/ast/backend/__tests__/solid-analyzer.spec.js +114 -0
  375. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendArchitectureDetector.js +141 -0
  376. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendPatternDetector.js +23 -0
  377. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/__tests__/BackendArchitectureDetector.spec.js +239 -0
  378. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/__tests__/BackendPatternDetector.spec.js +58 -0
  379. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/CQRSDetector.js +41 -0
  380. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/CleanArchitectureDetector.js +52 -0
  381. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/FeatureFirstCleanDetector.js +74 -0
  382. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/LayeredArchitectureDetector.js +25 -0
  383. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/MVCDetector.js +32 -0
  384. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/OnionArchitectureDetector.js +32 -0
  385. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend-clean.js +44 -0
  386. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +2048 -0
  387. package/scripts/hooks-system/infrastructure/ast/backend/clean-architecture-analyzer.js +142 -0
  388. package/scripts/hooks-system/infrastructure/ast/backend/ddd-analyzer.js +256 -0
  389. package/scripts/hooks-system/infrastructure/ast/backend/feature-first-analyzer.js +70 -0
  390. package/scripts/hooks-system/infrastructure/ast/backend/forbidden-literals-analyzer.js +236 -0
  391. package/scripts/hooks-system/infrastructure/ast/backend/nestjs-patterns-analyzer.js +11 -0
  392. package/scripts/hooks-system/infrastructure/ast/backend/solid-analyzer.js +392 -0
  393. package/scripts/hooks-system/infrastructure/ast/common/BDDTDDWorkflowRules.js +52 -0
  394. package/scripts/hooks-system/infrastructure/ast/common/__tests__/BDDTDDWorkflowRules.spec.js +133 -0
  395. package/scripts/hooks-system/infrastructure/ast/common/__tests__/ast-common.spec.js +20 -0
  396. package/scripts/hooks-system/infrastructure/ast/common/__tests__/documentation-analyzer.spec.js +120 -0
  397. package/scripts/hooks-system/infrastructure/ast/common/__tests__/images-backend-analyzer.spec.js +123 -0
  398. package/scripts/hooks-system/infrastructure/ast/common/__tests__/monorepo-health-analyzer.spec.js +118 -0
  399. package/scripts/hooks-system/infrastructure/ast/common/__tests__/network-resilience-analyzer.spec.js +180 -0
  400. package/scripts/hooks-system/infrastructure/ast/common/__tests__/offline-backend-analyzer.spec.js +111 -0
  401. package/scripts/hooks-system/infrastructure/ast/common/__tests__/push-backend-analyzer.spec.js +124 -0
  402. package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +345 -0
  403. package/scripts/hooks-system/infrastructure/ast/common/documentation-analyzer.js +217 -0
  404. package/scripts/hooks-system/infrastructure/ast/common/images-backend-analyzer.js +36 -0
  405. package/scripts/hooks-system/infrastructure/ast/common/monorepo-health-analyzer.js +452 -0
  406. package/scripts/hooks-system/infrastructure/ast/common/network-resilience-analyzer.js +178 -0
  407. package/scripts/hooks-system/infrastructure/ast/common/offline-backend-analyzer.js +53 -0
  408. package/scripts/hooks-system/infrastructure/ast/common/push-backend-analyzer.js +42 -0
  409. package/scripts/hooks-system/infrastructure/ast/common/rules/BDDRules.js +87 -0
  410. package/scripts/hooks-system/infrastructure/ast/common/rules/ImplementationRules.js +83 -0
  411. package/scripts/hooks-system/infrastructure/ast/common/rules/TDDRules.js +109 -0
  412. package/scripts/hooks-system/infrastructure/ast/common/rules/WorkflowRules.js +137 -0
  413. package/scripts/hooks-system/infrastructure/ast/frontend/__tests__/ast-frontend.spec.js +20 -0
  414. package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/FrontendArchitectureDetector.js +289 -0
  415. package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/FrontendForbiddenLiteralsAnalyzer.js +257 -0
  416. package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/FrontendSOLIDAnalyzer.js +274 -0
  417. package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/__tests__/FrontendArchitectureDetector.spec.js +151 -0
  418. package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/__tests__/FrontendForbiddenLiteralsAnalyzer.spec.js +20 -0
  419. package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/__tests__/FrontendSOLIDAnalyzer.spec.js +108 -0
  420. package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend-clean.js +42 -0
  421. package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend.js +2094 -0
  422. package/scripts/hooks-system/infrastructure/ast/frontend/clean-architecture-analyzer.js +88 -0
  423. package/scripts/hooks-system/infrastructure/ast/frontend/ddd-analyzer.js +94 -0
  424. package/scripts/hooks-system/infrastructure/ast/frontend/feature-first-analyzer.js +51 -0
  425. package/scripts/hooks-system/infrastructure/ast/ios/__tests__/ast-ios.spec.js +40 -0
  426. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSArchitectureDetector.spec.js +20 -0
  427. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSArchitectureRules.spec.js +61 -0
  428. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSCICDRules.spec.js +10 -0
  429. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSEnterpriseAnalyzer.spec.js +36 -0
  430. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSForbiddenLiteralsAnalyzer.spec.js +64 -0
  431. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSNetworkingAdvancedRules.spec.js +10 -0
  432. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSPerformanceRules.spec.js +34 -0
  433. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSSPMRules.spec.js +10 -0
  434. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSSwiftUIAdvancedRules.spec.js +10 -0
  435. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +894 -0
  436. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureDetector.js +445 -0
  437. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureRules.js +700 -0
  438. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDRules.js +431 -0
  439. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseAnalyzer.js +580 -0
  440. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSForbiddenLiteralsAnalyzer.js +261 -0
  441. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSNetworkingAdvancedRules.js +177 -0
  442. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSPerformanceRules.js +11 -0
  443. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMRules.js +496 -0
  444. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSwiftUIAdvancedRules.js +333 -0
  445. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSTestingAdvancedRules.js +225 -0
  446. package/scripts/hooks-system/infrastructure/ast/ios/ast-ios.js +2176 -0
  447. package/scripts/hooks-system/infrastructure/ast/ios/native-bridge.js +92 -0
  448. package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenParser.js +471 -0
  449. package/scripts/hooks-system/infrastructure/ast/ios/parsers/__tests__/SourceKittenParser.spec.js +41 -0
  450. package/scripts/hooks-system/infrastructure/ast/text/__tests__/text-scanner.spec.js +20 -0
  451. package/scripts/hooks-system/infrastructure/ast/text/text-scanner.js +1120 -0
  452. package/scripts/hooks-system/infrastructure/cache/CacheService.js +160 -0
  453. package/scripts/hooks-system/infrastructure/cli/__tests__/install-wizard.spec.js +16 -0
  454. package/scripts/hooks-system/infrastructure/cli/install-wizard.js +74 -0
  455. package/scripts/hooks-system/infrastructure/core/GitOperations.js +50 -0
  456. package/scripts/hooks-system/infrastructure/core/GitOperations.ts +112 -0
  457. package/scripts/hooks-system/infrastructure/core/__tests__/GitOperations.spec.js +146 -0
  458. package/scripts/hooks-system/infrastructure/eslint/eslint-integration.sh +75 -0
  459. package/scripts/hooks-system/infrastructure/events/EventListeners.js +143 -0
  460. package/scripts/hooks-system/infrastructure/events/__tests__/events.spec.js +14 -0
  461. package/scripts/hooks-system/infrastructure/external-tools/GitOperations.js +54 -0
  462. package/scripts/hooks-system/infrastructure/external-tools/eslint/backend.config.template.mjs +58 -0
  463. package/scripts/hooks-system/infrastructure/git-hooks/pre-push +35 -0
  464. package/scripts/hooks-system/infrastructure/git-server/pre-receive-hook +253 -0
  465. package/scripts/hooks-system/infrastructure/guards/git-wrapper.sh +32 -0
  466. package/scripts/hooks-system/infrastructure/guards/master-validator.sh +247 -0
  467. package/scripts/hooks-system/infrastructure/guards/prevent-no-verify.sh +34 -0
  468. package/scripts/hooks-system/infrastructure/hooks/__tests__/skill-activation-prompt.spec.js +11 -0
  469. package/scripts/hooks-system/infrastructure/hooks/pre-tool-use-intelligent-enforcer.sh +489 -0
  470. package/scripts/hooks-system/infrastructure/hooks/skill-activation-prompt.js +244 -0
  471. package/scripts/hooks-system/infrastructure/logging/UnifiedLoggerFactory.js +40 -0
  472. package/scripts/hooks-system/infrastructure/logging/__tests__/logging.spec.js +9 -0
  473. package/scripts/hooks-system/infrastructure/mcp/README.md +116 -0
  474. package/scripts/hooks-system/infrastructure/mcp/__tests__/ast-intelligence-automation.spec.js +38 -0
  475. package/scripts/hooks-system/infrastructure/mcp/__tests__/evidence-watcher.spec.js +38 -0
  476. package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +1097 -0
  477. package/scripts/hooks-system/infrastructure/mcp/evidence-watcher.js +128 -0
  478. package/scripts/hooks-system/infrastructure/mcp/package.json +17 -0
  479. package/scripts/hooks-system/infrastructure/mcp/services/EvidenceService.js +87 -0
  480. package/scripts/hooks-system/infrastructure/mcp/services/McpProtocolHandler.js +166 -0
  481. package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +11 -0
  482. package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +353 -0
  483. package/scripts/hooks-system/infrastructure/patterns/pattern-checks.sh +98 -0
  484. package/scripts/hooks-system/infrastructure/reporting/ReportImpactAnalyzer.js +109 -0
  485. package/scripts/hooks-system/infrastructure/reporting/ReportMetricsCalculator.js +114 -0
  486. package/scripts/hooks-system/infrastructure/reporting/ReportPresenter.js +86 -0
  487. package/scripts/hooks-system/infrastructure/reporting/__tests__/reporting.spec.js +15 -0
  488. package/scripts/hooks-system/infrastructure/reporting/report-generator.js +130 -0
  489. package/scripts/hooks-system/infrastructure/reporting/severity-tracker.js +105 -0
  490. package/scripts/hooks-system/infrastructure/repositories/CursorTokenRepository.js +76 -0
  491. package/scripts/hooks-system/infrastructure/repositories/FileFindingsRepository.js +88 -0
  492. package/scripts/hooks-system/infrastructure/repositories/__tests__/repositories.spec.js +20 -0
  493. package/scripts/hooks-system/infrastructure/repositories/datasources/CursorApiDataSource.js +73 -0
  494. package/scripts/hooks-system/infrastructure/repositories/datasources/CursorFileDataSource.js +55 -0
  495. package/scripts/hooks-system/infrastructure/severity/__tests__/severity-evaluator.spec.js +18 -0
  496. package/scripts/hooks-system/infrastructure/severity/analyzers/__tests__/maintainability-analyzer.spec.js +170 -0
  497. package/scripts/hooks-system/infrastructure/severity/analyzers/__tests__/performance-analyzer.spec.js +186 -0
  498. package/scripts/hooks-system/infrastructure/severity/analyzers/__tests__/security-analyzer.spec.js +151 -0
  499. package/scripts/hooks-system/infrastructure/severity/analyzers/__tests__/stability-analyzer.spec.js +143 -0
  500. package/scripts/hooks-system/infrastructure/severity/analyzers/maintainability-analyzer.js +100 -0
  501. package/scripts/hooks-system/infrastructure/severity/analyzers/performance-analyzer.js +109 -0
  502. package/scripts/hooks-system/infrastructure/severity/analyzers/security-analyzer.js +104 -0
  503. package/scripts/hooks-system/infrastructure/severity/analyzers/stability-analyzer.js +85 -0
  504. package/scripts/hooks-system/infrastructure/severity/context/analyzers/CodeClassificationAnalyzer.js +71 -0
  505. package/scripts/hooks-system/infrastructure/severity/context/analyzers/DataAnalyzer.js +64 -0
  506. package/scripts/hooks-system/infrastructure/severity/context/analyzers/ImpactAnalyzer.js +68 -0
  507. package/scripts/hooks-system/infrastructure/severity/context/analyzers/SafetyAnalyzer.js +82 -0
  508. package/scripts/hooks-system/infrastructure/severity/context/context-builder.js +88 -0
  509. package/scripts/hooks-system/infrastructure/severity/generators/RecommendationGenerator.js +153 -0
  510. package/scripts/hooks-system/infrastructure/severity/mappers/SeverityMapper.js +10 -0
  511. package/scripts/hooks-system/infrastructure/severity/policies/gate-policies.js +136 -0
  512. package/scripts/hooks-system/infrastructure/severity/policies/severity-policies.json +206 -0
  513. package/scripts/hooks-system/infrastructure/severity/scorers/ContextMultiplier.js +49 -0
  514. package/scripts/hooks-system/infrastructure/severity/severity-evaluator.js +117 -0
  515. package/scripts/hooks-system/infrastructure/shell/core/constants.sh +26 -0
  516. package/scripts/hooks-system/infrastructure/shell/core/utils.sh +45 -0
  517. package/scripts/hooks-system/infrastructure/shell/gitflow/git-wrapper.sh +646 -0
  518. package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +620 -0
  519. package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-state-manager.sh +235 -0
  520. package/scripts/hooks-system/infrastructure/shell/gitflow-state-manager.sh +225 -0
  521. package/scripts/hooks-system/infrastructure/shell/orchestrators/audit-orchestrator.sh +1106 -0
  522. package/scripts/hooks-system/infrastructure/shell/security/detect-secrets.sh +26 -0
  523. package/scripts/hooks-system/infrastructure/shell/security/detect_secrets.py +182 -0
  524. package/scripts/hooks-system/infrastructure/shell/validate-clean-architecture.sh +254 -0
  525. package/scripts/hooks-system/infrastructure/shell/validators/check-doc-structure.sh +62 -0
  526. package/scripts/hooks-system/infrastructure/shell/validators/ensure-critical-docs.sh +26 -0
  527. package/scripts/hooks-system/infrastructure/shell/validators/validate-ai-protocol.sh +474 -0
  528. package/scripts/hooks-system/infrastructure/shell/validators/validate-clean-architecture.sh +303 -0
  529. package/scripts/hooks-system/infrastructure/shell/validators/validate-conventional-commit.sh +42 -0
  530. package/scripts/hooks-system/infrastructure/storage/file-operations.sh +31 -0
  531. package/scripts/hooks-system/infrastructure/telemetry/TelemetryService.js +165 -0
  532. package/scripts/hooks-system/infrastructure/telemetry/__tests__/telemetry.spec.js +15 -0
  533. package/scripts/hooks-system/infrastructure/telemetry/metrics-logger.js +66 -0
  534. package/scripts/hooks-system/infrastructure/telemetry/metrics-server.js +61 -0
  535. package/scripts/hooks-system/infrastructure/utils/__tests__/utils.spec.js +8 -0
  536. package/scripts/hooks-system/infrastructure/utils/error-utils.js +28 -0
  537. package/scripts/hooks-system/infrastructure/utils/timestamp-helper.sh +106 -0
  538. package/scripts/hooks-system/infrastructure/utils/token-manager.js +121 -0
  539. package/scripts/hooks-system/infrastructure/validators/__tests__/detect-commit-language.spec.js +16 -0
  540. package/scripts/hooks-system/infrastructure/validators/__tests__/enforce-english-literals.spec.js +67 -0
  541. package/scripts/hooks-system/infrastructure/validators/detect-commit-language.js +145 -0
  542. package/scripts/hooks-system/infrastructure/validators/enforce-english-literals.js +202 -0
  543. package/scripts/hooks-system/infrastructure/watchdog/__tests__/.audit-reports/token-monitor.log +18 -0
  544. package/scripts/hooks-system/infrastructure/watchdog/__tests__/auto-recovery.spec.js +14 -0
  545. package/scripts/hooks-system/infrastructure/watchdog/__tests__/token-monitor.spec.js +67 -0
  546. package/scripts/hooks-system/infrastructure/watchdog/__tests__/watchdog.spec.js +22 -0
  547. package/scripts/hooks-system/infrastructure/watchdog/ai-watchdog.sh +278 -0
  548. package/scripts/hooks-system/infrastructure/watchdog/auto-recovery.js +32 -0
  549. package/scripts/hooks-system/infrastructure/watchdog/health-check.js +58 -0
  550. package/scripts/hooks-system/infrastructure/watchdog/token-monitor-loop.sh +20 -0
  551. package/scripts/hooks-system/infrastructure/watchdog/token-monitor.js +69 -0
  552. package/scripts/hooks-system/infrastructure/watchdog/token-tracker.sh +208 -0
  553. package/scripts/hooks-system/presentation/cli/audit.sh +32 -0
  554. package/scripts/hooks-system/presentation/cli/autonomous-status.sh +92 -0
  555. package/scripts/hooks-system/presentation/cli/categorize-violations.sh +179 -0
  556. package/scripts/hooks-system/presentation/cli/direct-audit-option2.sh +23 -0
  557. package/scripts/hooks-system/presentation/cli/direct-audit.sh +33 -0
  558. package/skills/android-guidelines/SKILL.md +475 -0
  559. package/skills/android-guidelines/resources/advanced-topics.md +44 -0
  560. package/skills/android-guidelines/resources/architecture-overview.md +44 -0
  561. package/skills/backend-guidelines/SKILL.md +335 -0
  562. package/skills/backend-guidelines/resources/architecture-overview.md +48 -0
  563. package/skills/frontend-guidelines/SKILL.md +367 -0
  564. package/skills/frontend-guidelines/resources/architecture-overview.md +44 -0
  565. package/skills/ios-guidelines/SKILL.md +406 -0
  566. package/skills/ios-guidelines/resources/architecture-overview.md +47 -0
  567. package/skills/skill-rules.json +334 -0
@@ -0,0 +1,1120 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { pushFileFinding, platformOf } = require(path.join(__dirname, '../ast-core'));
4
+
5
+ function walk(dir, acc) {
6
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
7
+ for (const e of entries) {
8
+ const p = path.join(dir, e.name);
9
+ if (e.isDirectory()) {
10
+ if (/node_modules|dist|\.next|\.turbo|build|out|coverage|\.git/.test(p)) continue;
11
+ walk(p, acc);
12
+ } else {
13
+ acc.push(p);
14
+ }
15
+ }
16
+ }
17
+
18
+ function runTextScanner(root, findings) {
19
+ const files = [];
20
+ let iosAnchorFile = null;
21
+ let iosHasBiometric = false;
22
+ let iosHasJailbreakCheck = false;
23
+ let iosHasVoiceOverRef = false;
24
+ let iosHasStringsDict = false;
25
+ let iosHasRtlSupport = false;
26
+ let iosHasPinningIndicator = false;
27
+ let iosHasNetworkingUsage = false;
28
+ let iosHasPreviewProvider = false;
29
+ let iosHasPreviewMultiple = false;
30
+ let iosPreviewHasDark = false;
31
+ let iosPreviewHasLight = false;
32
+ let iosHasCoreAnimationRef = false;
33
+ let iosHasAnimationUsage = false;
34
+ let iosHasMemoryWarningObserver = false;
35
+ let iosHasAnyExtension = false;
36
+ let iosHasExtensionsFolder = false;
37
+ let iosPublicApiFound = false;
38
+ let iosPublicApiDocsFound = false;
39
+ let iosTaskCount = 0;
40
+ let iosTaskGroupFound = false;
41
+ let iosHasFastfile = false;
42
+ let iosCiHasXcodeTests = false;
43
+ let iosHasDomainFolder = false;
44
+ let iosHasApplicationFolder = false;
45
+ let iosHasInfrastructureFolder = false;
46
+ let iosHasPresentationFolder = false;
47
+ walk(root, files);
48
+ for (const file of files) {
49
+ const ext = path.extname(file).toLowerCase();
50
+ if (!['.kt', '.kts', '.swift', '.java', '.xml', '.plist', '.stringsdict', '.yml', '.yaml'].includes(ext)) continue;
51
+ let content = '';
52
+ try {
53
+ content = fs.readFileSync(file, 'utf-8');
54
+ } catch (error) {
55
+ continue;
56
+ }
57
+ const plat = platformOf(file) || (ext === '.swift' ? 'ios' : (ext === '.kt' || ext === '.kts' || ext === '.java' || ext === '.xml') ? 'android' : (ext === '.plist' && path.basename(file).toLowerCase().includes('info')) ? 'ios' : (ext === '.stringsdict' ? 'ios' : (ext === '.yml' || ext === '.yaml') ? 'ios' : 'other'));
58
+
59
+ if (plat === 'android') {
60
+ if (ext === '.xml' && /<layout|<LinearLayout|<RelativeLayout|<ConstraintLayout/.test(content)) {
61
+ pushFileFinding('android.xml_layouts', 'critical', file, 1, 1, 'XML layout detected - prefer Compose for new UI', findings);
62
+ }
63
+ if (/findViewById\(/.test(content)) {
64
+ pushFileFinding('android.findviewbyid', 'high', file, 1, 1, 'findViewById usage detected', findings);
65
+ }
66
+ if (/AsyncTask\b/.test(content)) {
67
+ pushFileFinding('android.asynctask', 'high', file, 1, 1, 'AsyncTask usage detected', findings);
68
+ }
69
+ if (/!!/.test(content)) {
70
+ pushFileFinding('android.force_unwrapping', 'critical', file, 1, 1, 'Force unwrapping detected', findings);
71
+ }
72
+ if (/class\s+\w+\s*:\s*Application\b/.test(content) && !/@HiltAndroidApp\b/.test(content)) {
73
+ pushFileFinding('android.di.missing_hilt_app', 'high', file, 1, 1, 'Application without @HiltAndroidApp', findings);
74
+ }
75
+ if (/(class\s+\w+\s*:\s*\w*Activity\b|class\s+\w+\s*:\s*\w*Fragment\b)/.test(content) && !/@AndroidEntryPoint\b/.test(content)) {
76
+ pushFileFinding('android.di.missing_android_entry_point', 'high', file, 1, 1, 'Activity/Fragment without @AndroidEntryPoint', findings);
77
+ }
78
+ if (/@Module\b/.test(content) && !/@InstallIn\b/.test(content)) {
79
+ pushFileFinding('android.di.missing_module_install_in', 'medium', file, 1, 1, 'Hilt @Module without @InstallIn', findings);
80
+ }
81
+ if (/@Module\b/.test(content) && !/@Provides\b/.test(content)) {
82
+ pushFileFinding('android.di.missing_provides', 'medium', file, 1, 1, 'Hilt @Module without @Provides methods', findings);
83
+ }
84
+ if (/@Module\b/.test(content) && /abstract\s+class\b/.test(content) && !/@Binds\b/.test(content)) {
85
+ pushFileFinding('android.di.missing_binds', 'medium', file, 1, 1, 'Abstract Hilt module without @Binds', findings);
86
+ }
87
+ if (/@Provides\b/.test(content) && !/@Singleton\b/.test(content)) {
88
+ pushFileFinding('android.di.missing_singleton', 'low', file, 1, 1, 'Provider without @Singleton where appropriate', findings);
89
+ }
90
+ if (/@Provides\b/.test(content) && /ViewModel\b/.test(content) && !/@ViewModelScoped\b/.test(content)) {
91
+ pushFileFinding('android.di.missing_viewmodel_scoped', 'low', file, 1, 1, 'ViewModel provider without @ViewModelScoped', findings);
92
+ }
93
+ if (/(class\s+\w+ViewModel\b)/.test(content) && !/StateFlow\b|SharedFlow\b/.test(content)) {
94
+ pushFileFinding('android.architecture.missing_stateflow', 'medium', file, 1, 1, 'ViewModel without StateFlow/SharedFlow', findings);
95
+ }
96
+ if (/(class\s+\w+\s*:\s*\w*Activity\b|class\s+\w+\s*:\s*\w*Fragment\b)/.test(content) && !/ViewModel\b/.test(content)) {
97
+ pushFileFinding('android.architecture.missing_mvvm', 'medium', file, 1, 1, 'UI component without ViewModel reference', findings);
98
+ }
99
+ if (/(setContent\s*\(|NavController\b)/.test(content) && !/NavHost\b/.test(content)) {
100
+ pushFileFinding('android.architecture.missing_navigation', 'low', file, 1, 1, 'Compose setup without Navigation/NavHost', findings);
101
+ }
102
+ if (/(class\s+\w+Repository\b|class\s+\w+Service\b)/.test(content) && /constructor\s*\(/.test(content) && !/@Inject\s+constructor\b/.test(content)) {
103
+ pushFileFinding('android.di.missing_inject_constructor', 'medium', file, 1, 1, 'Repository/Service without @Inject constructor', findings);
104
+ }
105
+ if (/interface\s+\w+\s*\{[\s\S]*(@GET|@POST|@PUT|@DELETE)\b/.test(content) && !/suspend\s+fun\s+/m.test(content)) {
106
+ pushFileFinding('android.networking.missing_suspend', 'high', file, 1, 1, 'Retrofit interface methods without suspend', findings);
107
+ }
108
+ if (/OkHttpClient\.Builder\(\)/.test(content) && !/addInterceptor\(/.test(content)) {
109
+ pushFileFinding('android.networking.missing_interceptors', 'medium', file, 1, 1, 'OkHttpClient without interceptors', findings);
110
+ }
111
+ if (/(enqueue\(|execute\()/.test(content) && !/try\s*\{/.test(content)) {
112
+ pushFileFinding('android.networking.missing_error_handling', 'medium', file, 1, 1, 'Network call without try/catch', findings);
113
+ }
114
+ if (/OkHttpClient\.Builder\([\s\S]*\)\s*\.build\(\)/.test(content) && !/certificatePinner\(/.test(content)) {
115
+ pushFileFinding('android.networking.missing_certificate_pinning', 'low', file, 1, 1, 'Missing certificate pinning on OkHttpClient', findings);
116
+ }
117
+ if (/class\s+\w+ViewModel\b/.test(content) && !/viewModelScope\b/.test(content)) {
118
+ pushFileFinding('android.coroutines.missing_viewmodel_scope', 'medium', file, 1, 1, 'ViewModel without viewModelScope', findings);
119
+ }
120
+ if (/(class\s+\w+\s*:\s*\w*Activity\b|class\s+\w+\s*:\s*\w*Fragment\b)/.test(content) && !/lifecycleScope\b/.test(content)) {
121
+ pushFileFinding('android.coroutines.missing_lifecycle_scope', 'medium', file, 1, 1, 'Activity/Fragment without lifecycleScope', findings);
122
+ }
123
+ if (/(launch\s*\{|async\s*\()/m.test(content) && !/try\s*\{/.test(content)) {
124
+ pushFileFinding('android.coroutines.missing_try_catch', 'medium', file, 1, 1, 'Coroutine without try/catch handling', findings);
125
+ }
126
+ if (/async\s*\(/.test(content) && !/supervisorScope\s*\{/.test(content)) {
127
+ pushFileFinding('android.coroutines.missing_supervisor_scope', 'low', file, 1, 1, 'Async used without supervisorScope', findings);
128
+ }
129
+ if (/class\s+\w+ViewModel\b/.test(content) && !/SharedFlow\b|MutableSharedFlow\b/.test(content)) {
130
+ pushFileFinding('android.flow.missing_sharedflow', 'low', file, 1, 1, 'ViewModel without SharedFlow for events', findings);
131
+ }
132
+ if (/Flow<[^>]+>/.test(content) && !/(\.map\(|\.filter\(|combine\(|flatMapLatest\(|catch\()/ .test(content)) {
133
+ pushFileFinding('android.flow.missing_operators', 'low', file, 1, 1, 'Flow used without operators', findings);
134
+ }
135
+ if (/@Composable\b/.test(content) && /StateFlow\b/.test(content) && !/collectAsState\b/.test(content)) {
136
+ pushFileFinding('android.flow.missing_collect_as_state', 'medium', file, 1, 1, 'StateFlow in Compose without collectAsState', findings);
137
+ }
138
+ if (/interface\s+\w+Dao\b/.test(content) && !/@Dao\b/.test(content)) {
139
+ pushFileFinding('android.room.missing_dao', 'medium', file, 1, 1, 'DAO interface without @Dao', findings);
140
+ }
141
+ if (/@Query\s*\([\s\S]*\)\s*\n\s*fun\s+\w+\s*\([^)]*\)\s*:\s*(?!Flow<)/m.test(content)) {
142
+ pushFileFinding('android.room.missing_flow', 'medium', file, 1, 1, 'Room query without Flow return type', findings);
143
+ }
144
+ if (/class\s+\w+\s*:\s*RoomDatabase\b/.test(content) && !/@Database\b/.test(content)) {
145
+ pushFileFinding('android.room.missing_database', 'medium', file, 1, 1, 'Room database class without @Database', findings);
146
+ }
147
+ if (/(RAW_QUERY|execSQL\(|SELECT\s+.+FROM\s+)/i.test(content)) {
148
+ pushFileFinding('android.room.raw_sql', 'high', file, 1, 1, 'Raw SQL detected', findings);
149
+ }
150
+ if (/class\s+\w+Database\b/.test(content) && /@Database\b/.test(content) && !/@TypeConverters\b/.test(content)) {
151
+ pushFileFinding('android.room.missing_typeconverter', 'medium', file, 1, 1, 'Room Database without @TypeConverters', findings);
152
+ }
153
+ if (/@Database\s*\([\s\S]*version\s*=\s*(\d+)/.test(content) && !/Migration\b/.test(content)) {
154
+ pushFileFinding('android.room.missing_migrations', 'medium', file, 1, 1, 'Room Database with version but no Migration found', findings);
155
+ }
156
+ if (/@Dao\b/.test(content) && !/@Transaction\b/.test(content)) {
157
+ pushFileFinding('android.room.missing_transaction', 'low', file, 1, 1, 'DAO without @Transaction annotations (multi-query safety)', findings);
158
+ }
159
+ if (/Flow<[^>]+>/.test(content) && !/stateIn\s*\(/.test(content)) {
160
+ pushFileFinding('android.flow.missing_state_in', 'low', file, 1, 1, 'Flow without stateIn conversion where appropriate', findings);
161
+ }
162
+ if (path.basename(file) === 'AndroidManifest.xml' && !/android:networkSecurityConfig=/.test(content)) {
163
+ pushFileFinding('android.networking.missing_network_security_config', 'high', file, 1, 1, 'AndroidManifest without networkSecurityConfig', findings);
164
+ }
165
+ // Architecture
166
+ if (/class\s+\w+Activity\b/.test(content) && content.split(/class\s+\w+Activity\b/).length > 3) {
167
+ pushFileFinding('android.architecture.multiple_activities', 'medium', file, 1, 1, 'Multiple Activities detected - prefer Single Activity + Composables', findings);
168
+ }
169
+ if (/class\s+\w+Repository\b/.test(content) && !/interface\s+\w+Repository\b/.test(content) && !/:\s*\w+Repository/.test(content)) {
170
+ pushFileFinding('android.architecture.missing_repository', 'medium', file, 1, 1, 'Repository class without interface - implement repository pattern', findings);
171
+ }
172
+ if (/class\s+\w+ViewModel\b/.test(content) && !/(class\s+\w+UseCase\b|fun\s+\w+UseCase\()/m.test(content)) {
173
+ pushFileFinding('android.architecture.missing_use_cases', 'low', file, 1, 1, 'ViewModel without use cases - encapsulate business logic', findings);
174
+ }
175
+ if (!/domain\/|data\/|presentation\//i.test(file)) {
176
+ pushFileFinding('android.architecture.missing_clean_layers', 'info', file, 1, 1, 'File not in Clean Architecture layers (domain/data/presentation)', findings);
177
+ }
178
+
179
+ if (file.includes('/domain/') && /(import\s+androidx\.|import\s+android\.)/.test(content)) {
180
+ pushFileFinding('android.clean.domain_layer', 'high', file, 1, 1, 'Domain layer with Android framework dependencies - must be pure Kotlin', findings);
181
+ }
182
+ if (file.includes('/data/') && !/(interface\s+\w+Repository\b|class\s+\w+RepositoryImpl\b|@Dao\b)/.test(content)) {
183
+ pushFileFinding('android.clean.data_layer', 'low', file, 1, 1, 'Data layer file without Repository implementation or DAO', findings);
184
+ }
185
+ if (file.includes('/presentation/') && !/(@Composable\b|class\s+\w+ViewModel\b|@HiltViewModel\b)/.test(content)) {
186
+ pushFileFinding('android.clean.presentation_layer', 'low', file, 1, 1, 'Presentation layer without Composables or ViewModels', findings);
187
+ }
188
+ if (file.includes('/presentation/') && /(import\s+com\.example\.\w+\.domain\.\w+\.repository)/.test(content)) {
189
+ pushFileFinding('android.clean.dependency_direction', 'medium', file, 1, 1, 'Presentation importing domain repository directly - use repository interface', findings);
190
+ }
191
+
192
+ // DI
193
+ if (!/@HiltAndroidApp\b/.test(content) && /class\s+\w+Application\b/.test(content)) {
194
+ pushFileFinding('android.di.missing_hilt', 'high', file, 1, 1, 'Application class without @HiltAndroidApp', findings);
195
+ }
196
+
197
+ // Coroutines
198
+ if (/suspend\s+fun\s+\w+\s*\([^)]*\)\s*\{[\s\S]{0,500}\}/.test(content) && !/Dispatchers\.(IO|Default|Main)/.test(content)) {
199
+ pushFileFinding('android.coroutines.missing_dispatchers', 'medium', file, 1, 1, 'Suspend function without explicit Dispatcher', findings);
200
+ }
201
+ if (/Dispatchers\.(IO|Default)\b/.test(content) && !/withContext\s*\(/.test(content)) {
202
+ pushFileFinding('android.coroutines.missing_withcontext', 'low', file, 1, 1, 'Dispatcher used without withContext for switching', findings);
203
+ }
204
+ if (/launch\s*\{[\s\S]{0,300}\}/.test(content) && !/async\s*\{/.test(content) && /\n[\s\S]{100,}/.test(content)) {
205
+ pushFileFinding('android.coroutines.missing_async_await', 'low', file, 1, 1, 'Consider async/await for parallel operations', findings);
206
+ }
207
+ if (/completion:\s*\(|callback:\s*\(/.test(content) && /suspend\s+fun\s+/.test(content)) {
208
+ pushFileFinding('android.coroutines.callbacks_instead_coroutines', 'medium', file, 1, 1, 'Using callbacks with suspend functions - prefer coroutines', findings);
209
+ }
210
+
211
+ // Flow
212
+ if (/Flow<[^>]+>/.test(content) && !/flow\s*\{/.test(content) && !/flowOf\(|asFlow\(\)/.test(content)) {
213
+ pushFileFinding('android.flow.missing_flow_builders', 'low', file, 1, 1, 'Flow type without flow builders (flow{}, flowOf(), asFlow())', findings);
214
+ }
215
+ if (/Flow<[^>]+>/.test(content) && !/\.collect\s*\{/.test(content) && !/\.collect/.test(content)) {
216
+ pushFileFinding('android.flow.missing_collect', 'medium', file, 1, 1, 'Flow declared but never collected', findings);
217
+ }
218
+ if (/Flow<[^>]+>/.test(content) && !/\.catch\s*\{/.test(content)) {
219
+ pushFileFinding('android.flow.missing_error_handling', 'medium', file, 1, 1, 'Flow without .catch() error handling', findings);
220
+ }
221
+ if (/RxJava|Observable<|Flowable<|Single</.test(content)) {
222
+ pushFileFinding('android.flow.rxjava_instead_flow', 'medium', file, 1, 1, 'RxJava detected - migrate to Kotlin Flow', findings);
223
+ }
224
+
225
+ // Networking
226
+ if (/https?:\/\//.test(content) && !/(Retrofit\.Builder\(|interface\s+\w+Api\b)/.test(content) && !/Retrofit|OkHttp/.test(content)) {
227
+ pushFileFinding('android.networking.missing_retrofit', 'high', file, 1, 1, 'HTTP URLs without Retrofit/OkHttp', findings);
228
+ }
229
+ if (/Retrofit\.Builder\(\)/.test(content) && !/OkHttpClient\(/.test(content)) {
230
+ pushFileFinding('android.networking.missing_okhttp', 'medium', file, 1, 1, 'Retrofit without OkHttpClient configuration', findings);
231
+ }
232
+ if (/Retrofit\.Builder\(\)/.test(content) && !/MoshiConverterFactory|GsonConverterFactory/.test(content)) {
233
+ pushFileFinding('android.networking.missing_moshi_gson', 'medium', file, 1, 1, 'Retrofit without Moshi/Gson converter', findings);
234
+ }
235
+ if (/Retrofit\.Builder\(\)/.test(content) && !/retryOnConnectionFailure|Interceptor/.test(content)) {
236
+ pushFileFinding('android.networking.missing_retry_logic', 'low', file, 1, 1, 'Retrofit without retry logic', findings);
237
+ }
238
+ if (/BiometricPrompt\b/.test(content)) {
239
+ } else if (/(password|auth|login)/.test(content.toLowerCase()) && !/BiometricPrompt/.test(content)) {
240
+ pushFileFinding('android.networking.missing_biometric_auth', 'low', file, 1, 1, 'Auth without BiometricPrompt API consideration', findings);
241
+ }
242
+
243
+ // Room
244
+ if (/class\s+\w+(Entity|Model)\b/.test(content) && file.includes('/data/') && !/@Entity\b/.test(content)) {
245
+ pushFileFinding('android.room.missing_room', 'medium', file, 1, 1, 'Data entity without @Entity annotation', findings);
246
+ }
247
+ if (/@Entity\b/.test(content) && !/@Index\(/.test(content) && /@ColumnInfo\b/.test(content)) {
248
+ pushFileFinding('android.room.missing_indices', 'low', file, 1, 1, 'Room Entity without @Index on queried columns', findings);
249
+ }
250
+ if (/@Entity\b/.test(content) && !/@Relation\(|@Embedded\b/.test(content) && /@ColumnInfo.*id/.test(content)) {
251
+ pushFileFinding('android.room.missing_relations', 'low', file, 1, 1, 'Room Entity with foreign keys but no @Relation/@Embedded', findings);
252
+ }
253
+ if (/@Query\([^)]{200,}\)/.test(content)) {
254
+ pushFileFinding('android.room.performance_issues', 'medium', file, 1, 1, 'Complex Room query - optimize with indices', findings);
255
+ }
256
+
257
+ if (/@Composable\b/.test(content) && !/ViewModel\b/.test(content) && /var\s+\w+\s*=/.test(content)) {
258
+ pushFileFinding('android.state.missing_viewmodel', 'medium', file, 1, 1, 'Composable with var state without ViewModel', findings);
259
+ }
260
+ if (/class\s+\w+ViewModel\b/.test(content) && !/StateFlow\b|MutableStateFlow\b/.test(content)) {
261
+ pushFileFinding('android.state.missing_stateflow', 'medium', file, 1, 1, 'ViewModel without StateFlow for state', findings);
262
+ }
263
+ if (/class\s+\w+ViewModel\b/.test(content) && !/sealed\s+(class|interface)\s+\w*UiState\b/.test(content)) {
264
+ pushFileFinding('android.state.missing_uistate_sealed', 'low', file, 1, 1, 'ViewModel without UiState sealed class (Loading, Success, Error)', findings);
265
+ }
266
+ if (/@Composable\b/.test(content) && /StateFlow\b/.test(content) && !/collectAsState\b/.test(content)) {
267
+ pushFileFinding('android.state.missing_single_source', 'medium', file, 1, 1, 'StateFlow not collected in Composable - ViewModel should be source of truth', findings);
268
+ }
269
+ if (/var\s+\w+State\s*=\s*mutableListOf|var\s+\w+\s*=\s*mutableMapOf/.test(content) && !/data\s+class\b/.test(content)) {
270
+ pushFileFinding('android.state.missing_immutable_state', 'medium', file, 1, 1, 'Mutable collections as state - use data class + copy()', findings);
271
+ }
272
+ if (/@Composable\b/.test(content) && /var\s+/.test(content) && !/remember\s*\{/.test(content)) {
273
+ pushFileFinding('android.state.missing_state_hoisting', 'medium', file, 1, 1, 'State not hoisted in Composable', findings);
274
+ }
275
+ if (/class\s+\w+ViewModel\b/.test(content) && !/SavedStateHandle/.test(content)) {
276
+ pushFileFinding('android.state.missing_savedstate', 'low', file, 1, 1, 'ViewModel without SavedStateHandle for process death', findings);
277
+ }
278
+ if (/(var\s+\w+\s*=.*StateFlow|val\s+\w+\s*=.*MutableStateFlow)/.test(content) && content.split(/StateFlow/).length > 3) {
279
+ pushFileFinding('android.state.multiple_sources', 'low', file, 1, 1, 'Multiple StateFlows - consider single source of truth', findings);
280
+ }
281
+
282
+ // Navigation
283
+ if (/@Composable\b/.test(content) && /Screen\b/.test(content) && !/NavHost\b/.test(content) && !/NavController/.test(content)) {
284
+ pushFileFinding('android.navigation.missing_compose_navigation', 'medium', file, 1, 1, 'Composable screens without Navigation Compose', findings);
285
+ }
286
+ if (/NavController\b/.test(content) && !/NavHost\b/.test(content)) {
287
+ pushFileFinding('android.navigation.missing_navhost', 'medium', file, 1, 1, 'NavController without NavHost container', findings);
288
+ }
289
+ if (/NavHost\b/.test(content) && !/rememberNavController\(\)/.test(content)) {
290
+ pushFileFinding('android.navigation.missing_navcontroller', 'medium', file, 1, 1, 'NavHost without NavController', findings);
291
+ }
292
+ if (/NavHost\b/.test(content) && !/route\s*=\s*["']/.test(content)) {
293
+ pushFileFinding('android.navigation.missing_routes', 'medium', file, 1, 1, 'NavHost without route strings', findings);
294
+ }
295
+ if (/navigate\s*\(["'][^"']+["']\)/.test(content) && !/arguments\s*=/.test(content) && /\{/.test(content)) {
296
+ pushFileFinding('android.navigation.missing_arguments', 'low', file, 1, 1, 'Navigation without arguments for passing data', findings);
297
+ }
298
+ if (/NavHost\b/.test(content) && !/deepLinks\s*=/.test(content)) {
299
+ pushFileFinding('android.navigation.missing_deep_links', 'low', file, 1, 1, 'Navigation without deep links support', findings);
300
+ }
301
+ if (/@Composable\b/.test(content) && /BottomNavigation\b|NavigationBar\b/.test(content)) {
302
+ // Good
303
+ } else if (/@Composable\b/.test(content) && /Scaffold\b/.test(content) && !/BottomNavigation|NavigationBar/.test(content)) {
304
+ pushFileFinding('android.navigation.missing_bottom_navigation', 'info', file, 1, 1, 'Scaffold without bottom navigation consideration', findings);
305
+ }
306
+ if (/navigate\s*\([^)]{100,}\)/.test(content)) {
307
+ pushFileFinding('android.navigation.complex_navigation', 'low', file, 1, 1, 'Complex navigation logic - consider navigation component architecture', findings);
308
+ }
309
+
310
+ // Image Loading
311
+ if (/Image\s*\(/.test(content) && !/Coil|AsyncImage|rememberImagePainter/.test(content)) {
312
+ pushFileFinding('android.images.missing_coil', 'medium', file, 1, 1, 'Image loading without Coil async loader', findings);
313
+ }
314
+ if (/Glide\.with\(/.test(content)) {
315
+ } else if (/ImageView\b/.test(content) && !/Coil|Glide/.test(content)) {
316
+ pushFileFinding('android.images.missing_glide', 'low', file, 1, 1, 'ImageView without Coil/Glide - consider async loading', findings);
317
+ }
318
+ if (/AsyncImage\b|rememberImagePainter\b/.test(content) && !/\.diskCachePolicy|\.memoryCachePolicy/.test(content)) {
319
+ pushFileFinding('android.images.missing_cache', 'low', file, 1, 1, 'Image loading without explicit cache policy', findings);
320
+ }
321
+ if (/AsyncImage\b/.test(content) && !/transformations\s*=/.test(content)) {
322
+ pushFileFinding('android.images.missing_transformations', 'info', file, 1, 1, 'Consider image transformations (resize, crop, blur)', findings);
323
+ }
324
+ if (/AsyncImage\b/.test(content) && !/placeholder\s*=/.test(content)) {
325
+ pushFileFinding('android.images.missing_placeholders', 'low', file, 1, 1, 'AsyncImage without placeholder while loading', findings);
326
+ }
327
+ if (/AsyncImage\b/.test(content) && !/error\s*=/.test(content)) {
328
+ pushFileFinding('android.images.missing_error_handling', 'medium', file, 1, 1, 'AsyncImage without error fallback image', findings);
329
+ }
330
+ if (/ImageView\b/.test(content) && /setImageResource\(R\.drawable\./.test(content)) {
331
+ pushFileFinding('android.images.raw_image_views', 'low', file, 1, 1, 'Raw ImageView.setImageResource - consider Coil for consistency', findings);
332
+ }
333
+
334
+ // Testing
335
+ if (file.includes('Test.kt') && !/@Test\b/.test(content)) {
336
+ pushFileFinding('android.testing.missing_junit5', 'medium', file, 1, 1, 'Test file without @Test annotations', findings);
337
+ }
338
+ if (file.includes('Test.kt') && !/mockk|MockK/.test(content) && /mock|Mock/.test(content)) {
339
+ pushFileFinding('android.testing.missing_mockk', 'low', file, 1, 1, 'Mocking without MockK - use MockK for Kotlin', findings);
340
+ }
341
+ if (file.includes('Test.kt') && /Flow<[^>]+>/.test(content) && !/turbine|test\(\)/.test(content)) {
342
+ pushFileFinding('android.testing.missing_turbine', 'low', file, 1, 1, 'Testing Flows without Turbine library', findings);
343
+ }
344
+ if (file.includes('Test.kt') && /@Composable\b/.test(content) && !/composeTestRule|createComposeRule/.test(content)) {
345
+ pushFileFinding('android.testing.missing_compose_ui_test', 'medium', file, 1, 1, 'Testing Composables without Compose UI Test', findings);
346
+ }
347
+ if (file.includes('Test.kt') && /Fragment\b/.test(content) && !/Espresso|onView\(/.test(content)) {
348
+ pushFileFinding('android.testing.missing_espresso', 'medium', file, 1, 1, 'Fragment testing without Espresso', findings);
349
+ }
350
+ if (file.includes('Test.kt') && /android\./.test(content) && !/Robolectric|@RunWith\(AndroidJUnit4/.test(content)) {
351
+ pushFileFinding('android.testing.missing_robolectric', 'low', file, 1, 1, 'Android framework testing without Robolectric', findings);
352
+ }
353
+ if (file.includes('Test.kt') && /assert/.test(content) && !/Truth|assertThat\(/.test(content)) {
354
+ pushFileFinding('android.testing.missing_truth', 'info', file, 1, 1, 'Assertions without Truth library - consider for readability', findings);
355
+ }
356
+ if (file.includes('Test.kt') && /launch\s*\{|async\s*\{/.test(content) && !/runTest\s*\{|TestDispatcher/.test(content)) {
357
+ pushFileFinding('android.testing.missing_coroutines_test', 'medium', file, 1, 1, 'Testing coroutines without runTest/TestDispatcher', findings);
358
+ }
359
+ if (file.includes('Test.kt') && !/(\/\/ Arrange|\/\/ Act|\/\/ Assert)/.test(content) && /@Test\b/.test(content)) {
360
+ pushFileFinding('android.testing.missing_aaa_pattern', 'info', file, 1, 1, 'Test without AAA pattern comments (Arrange-Act-Assert)', findings);
361
+ }
362
+ if (file.includes('Test.kt') && !/(\/\/ Given|\/\/ When|\/\/ Then)/.test(content) && /@Test\b/.test(content)) {
363
+ pushFileFinding('android.testing.missing_given_when_then', 'info', file, 1, 1, 'Test without Given-When-Then BDD style', findings);
364
+ }
365
+
366
+ // Security
367
+ if (/SharedPreferences\b/.test(content) && !/EncryptedSharedPreferences/.test(content) && /(password|token|key|secret)/.test(content.toLowerCase())) {
368
+ pushFileFinding('android.security.missing_encrypted_prefs', 'critical', file, 1, 1, 'Sensitive data in SharedPreferences - use EncryptedSharedPreferences', findings);
369
+ }
370
+ if (/(SecretKey|KeyGenerator)\b/.test(content) && !/KeyStore/.test(content)) {
371
+ pushFileFinding('android.security.missing_keystore', 'high', file, 1, 1, 'Cryptographic keys without Android KeyStore', findings);
372
+ }
373
+ if (/package\s+com\.\w+/.test(content) && !/SafetyNet|PlayIntegrity/.test(content) && file.includes('MainActivity')) {
374
+ pushFileFinding('android.security.missing_safetynet', 'low', file, 1, 1, 'Consider SafetyNet/Play Integrity for device verification', findings);
375
+ }
376
+ if (/class\s+\w+Application\b/.test(content) && !/RootBeer|isDeviceRooted/.test(content)) {
377
+ pushFileFinding('android.security.missing_root_detection', 'low', file, 1, 1, 'Application without root detection', findings);
378
+ }
379
+ if (path.basename(file) === 'build.gradle.kts' && !/proguard|r8/.test(content.toLowerCase())) {
380
+ pushFileFinding('android.security.missing_proguard_r8', 'medium', file, 1, 1, 'Release build without ProGuard/R8 obfuscation', findings);
381
+ }
382
+ if (/BiometricPrompt\b/.test(content)) {
383
+ } else if (/BiometricManager\b/.test(content)) {
384
+ } else if (/(password|auth|login)/.test(content.toLowerCase()) && file.includes('Activity')) {
385
+ pushFileFinding('android.security.missing_biometric_auth', 'medium', file, 1, 1, 'Auth screen without BiometricPrompt consideration', findings);
386
+ }
387
+
388
+ // Performance
389
+ if (/@Composable\b/.test(content) && /List\(|ArrayList\(/.test(content) && !/LazyColumn|LazyRow/.test(content) && content.length > 500) {
390
+ pushFileFinding('android.performance.missing_lazycolumn', 'high', file, 1, 1, 'Large list without LazyColumn/LazyRow virtualization', findings);
391
+ }
392
+ if (/@Dao\b/.test(content) && /@Query\b/.test(content) && !/PagingSource|@androidx\.paging/.test(content) && /LIMIT\s+\d{3,}/.test(content)) {
393
+ pushFileFinding('android.performance.missing_paging', 'medium', file, 1, 1, 'Large dataset query without Paging 3', findings);
394
+ }
395
+ if (/(doInBackground|AsyncTask|Thread\(|Runnable)/.test(content) && !/(OneTimeWorkRequest|PeriodicWorkRequest)\b/.test(content)) {
396
+ pushFileFinding('android.performance.missing_workmanager', 'medium', file, 1, 1, 'Background work without WorkManager', findings);
397
+ }
398
+ if (path.basename(file) === 'build.gradle.kts' && !/baselineProfile/.test(content)) {
399
+ pushFileFinding('android.performance.missing_baseline_profiles', 'info', file, 1, 1, 'Consider Baseline Profiles for app startup optimization', findings);
400
+ }
401
+ if (/class\s+\w+Application\b/.test(content) && !/androidx\.startup/.test(content) && /initialize/.test(content)) {
402
+ pushFileFinding('android.performance.missing_app_startup', 'low', file, 1, 1, 'App initialization without androidx.startup for lazy init', findings);
403
+ }
404
+ if (path.basename(file) === 'build.gradle.kts' && !/debugImplementation.*leakcanary/.test(content)) {
405
+ pushFileFinding('android.performance.missing_leakcanary', 'low', file, 1, 1, 'Debug build without LeakCanary for memory leak detection', findings);
406
+ }
407
+ if (/@Composable\b/.test(content) && !/remember\s*\{/.test(content) && /\w+\s*\(.*\)\s*\{[\s\S]{50,}\}/.test(content)) {
408
+ pushFileFinding('android.performance.missing_remember', 'medium', file, 1, 1, 'Composable recreating objects - use remember{}', findings);
409
+ }
410
+ if (/@Composable\b/.test(content) && /\.filter\(|\.map\(/.test(content) && !/derivedStateOf\s*\{/.test(content)) {
411
+ pushFileFinding('android.performance.derived_state_missing', 'low', file, 1, 1, 'Expensive calculations without derivedStateOf', findings);
412
+ }
413
+ if (/LaunchedEffect\s*\([^)]*\)/.test(content) && !/LaunchedEffect\s*\(.*,.*\)/.test(content)) {
414
+ pushFileFinding('android.performance.launched_effect_keys', 'low', file, 1, 1, 'LaunchedEffect without keys - may re-launch unnecessarily', findings);
415
+ }
416
+
417
+ // Accessibility
418
+ if (ext === '.xml' && /<ImageView|<ImageButton/.test(content) && !/android:contentDescription=/.test(content)) {
419
+ pushFileFinding('android.accessibility.missing_contentdescription', 'high', file, 1, 1, 'ImageView/ImageButton without contentDescription for TalkBack', findings);
420
+ }
421
+ if (/@Composable\b/.test(content) && /Image\s*\(/.test(content) && !/contentDescription\s*=/.test(content)) {
422
+ pushFileFinding('android.accessibility.missing_semantics', 'high', file, 1, 1, 'Compose Image without contentDescription', findings);
423
+ }
424
+ if (ext === '.xml' && /(Button|ImageButton|clickable="true")/.test(content) && /layout_width="[^"]*"/.test(content)) {
425
+ const widthMatch = content.match(/layout_width="(\d+)dp"/);
426
+ if (widthMatch && parseInt(widthMatch[1]) < 48) {
427
+ pushFileFinding('android.accessibility.missing_touch_targets', 'medium', file, 1, 1, `Touch target <48dp (found ${widthMatch[1]}dp) - minimum 48dp required`, findings);
428
+ }
429
+ }
430
+ if (/@Composable\b/.test(content) && /textSize\s*=\s*\d+\.sp/.test(content) && !/with\(LocalDensity\.current\)/.test(content)) {
431
+ pushFileFinding('android.accessibility.missing_text_scaling', 'medium', file, 1, 1, 'Fixed text size without density/scaling support', findings);
432
+ }
433
+
434
+ // Localization
435
+ if (ext === '.kt' && /Text\s*\(["'][^"']+["']\)/.test(content) && !/@Composable\b/.test(content)) {
436
+ pushFileFinding('android.i18n.hardcoded_strings', 'medium', file, 1, 1, 'Hardcoded strings - use stringResource()', findings);
437
+ }
438
+ if (ext === '.xml' && /<string\s+name=/.test(content) && !/<plurals\s+name=/.test(content) && file.includes('values/')) {
439
+ pushFileFinding('android.i18n.missing_quantity_strings', 'low', file, 1, 1, 'strings.xml without plurals - consider for countable items', findings);
440
+ }
441
+ if (ext === '.xml' && /(android:layout_marginLeft|android:layout_marginRight)/.test(content)) {
442
+ pushFileFinding('android.i18n.missing_rtl_support', 'medium', file, 1, 1, 'Using marginLeft/marginRight instead of marginStart/marginEnd - breaks RTL', findings);
443
+ }
444
+ if (/@Composable\b/.test(content) && /Text\s*\([^)]*\$/.test(content) && !/stringResource\(/.test(content)) {
445
+ pushFileFinding('android.i18n.missing_string_formatting', 'medium', file, 1, 1, 'String interpolation without stringResource() formatting', findings);
446
+ }
447
+ if (/SimpleDateFormat\(/.test(content) && !/Locale\./.test(content)) {
448
+ pushFileFinding('android.i18n.missing_dateformat', 'medium', file, 1, 1, 'SimpleDateFormat without Locale - use DateTimeFormatter', findings);
449
+ }
450
+ if (/String\.format\([^)]*\d/.test(content) && !/Locale\./.test(content)) {
451
+ pushFileFinding('android.i18n.missing_numberformat', 'medium', file, 1, 1, 'Number formatting without Locale - use NumberFormat', findings);
452
+ }
453
+
454
+ // Gradle
455
+ if (path.basename(file) === 'build.gradle' && ext === '') {
456
+ pushFileFinding('android.gradle.missing_kotlin_dsl', 'low', file, 1, 1, 'Using Groovy build.gradle - prefer Kotlin DSL (build.gradle.kts)', findings);
457
+ }
458
+ if (path.basename(file) === 'build.gradle.kts' && !/libs\.versions\.toml/.test(content) && /implementation\(/.test(content)) {
459
+ pushFileFinding('android.gradle.missing_version_catalogs', 'low', file, 1, 1, 'Dependencies without version catalog (libs.versions.toml)', findings);
460
+ }
461
+ if (path.basename(file) === 'build.gradle.kts' && /buildTypes\s*\{/.test(content) && content.split(/buildTypes/).length < 3) {
462
+ pushFileFinding('android.gradle.missing_build_types', 'info', file, 1, 1, 'Only one build type - consider debug/release/staging', findings);
463
+ }
464
+
465
+ // Logging
466
+ if (/Log\.(d|i|w|e)\(/.test(content) && !/Timber/.test(content)) {
467
+ pushFileFinding('android.logging.missing_timber', 'low', file, 1, 1, 'Using Log directly - prefer Timber for logging', findings);
468
+ }
469
+ if (/Timber\.(d|i)\(/.test(content) && !/if\s*\(BuildConfig\.DEBUG\)/.test(content)) {
470
+ pushFileFinding('android.logging.debug_in_production', 'medium', file, 1, 1, 'Debug logs without BuildConfig.DEBUG check', findings);
471
+ }
472
+ if (/Log\.(d|i|w|e)\([^)]*password|token|secret/.test(content.toLowerCase())) {
473
+ pushFileFinding('android.logging.sensitive_data', 'critical', file, 1, 1, 'Logging sensitive data (password/token)', findings);
474
+ }
475
+
476
+ // Configuration
477
+ if (/const\s+val\s+API_KEY\s*=\s*["']/.test(content) && !(path.basename(file) === 'build.gradle.kts' && /buildConfigField/.test(content))) {
478
+ pushFileFinding('android.config.hardcoded_config', 'high', file, 1, 1, 'API keys hardcoded - use BuildConfig or gradle.properties', findings);
479
+ }
480
+
481
+ // Anti-patterns
482
+ if (/object\s+\w+(Repository|Manager|Helper)\b/.test(content)) {
483
+ pushFileFinding('android.antipattern.singleton', 'high', file, 1, 1, 'Singleton object detected - use Hilt DI instead', findings);
484
+ }
485
+ if (/class\s+\w+Activity\b[\s\S]{0,2000}fun\s+/.test(content) && content.split(/fun\s+\w+\s*\(/).length > 30) {
486
+ pushFileFinding('android.antipattern.god_activity', 'high', file, 1, 1, 'God Activity with 30+ methods - break down into ViewModels', findings);
487
+ }
488
+ if (/findViewById\s*</.test(content)) {
489
+ pushFileFinding('android.antipattern.findviewbyid', 'medium', file, 1, 1, 'Using findViewById - use View Binding or Compose', findings);
490
+ }
491
+ if (/AsyncTask\b/.test(content)) {
492
+ pushFileFinding('android.antipattern.asynctask', 'high', file, 1, 1, 'AsyncTask is deprecated - use Coroutines', findings);
493
+ }
494
+ if (/Handler\(\)\.post/.test(content) && !/@SuppressLint\("HandlerLeak"\)/.test(content)) {
495
+ pushFileFinding('android.antipattern.handler_leak', 'high', file, 1, 1, 'Handler without weak reference - potential memory leak', findings);
496
+ }
497
+ }
498
+
499
+ if (plat === 'ios') {
500
+ if (ext === '.swift' && !iosAnchorFile) iosAnchorFile = file;
501
+ if (ext === '.stringsdict') iosHasStringsDict = true;
502
+ if (file.split(path.sep).some(seg => /^Extensions$/i.test(seg))) iosHasExtensionsFolder = true;
503
+ if (file.split(path.sep).some(seg => /^Domain$/i.test(seg))) iosHasDomainFolder = true;
504
+ if (file.split(path.sep).some(seg => /^Application$/i.test(seg))) iosHasApplicationFolder = true;
505
+ if (file.split(path.sep).some(seg => /^Infrastructure$/i.test(seg))) iosHasInfrastructureFolder = true;
506
+ if (file.split(path.sep).some(seg => /^Presentation$/i.test(seg))) iosHasPresentationFolder = true;
507
+ if (path.basename(file) === 'Fastfile') iosHasFastfile = true;
508
+ if ((ext === '.yml' || ext === '.yaml') && /xcodebuild\s+test|fastlane\s+scan/.test(content)) iosCiHasXcodeTests = true;
509
+ if (/\bextension\s+[A-Za-z_][A-Za-z0-9_]*\s*\{/.test(content)) iosHasAnyExtension = true;
510
+ if (/\bpublic\s+(class|struct|enum|protocol|func|var)\b/.test(content)) iosPublicApiFound = true;
511
+ if (/^\s*\/\/\/[^\n]*\n\s*public\s+(class|struct|enum|protocol|func|var)\b/m.test(content)) iosPublicApiDocsFound = true;
512
+ if (path.basename(file) === 'Package.swift') {
513
+ const targetCount = (content.match(/target\s*\(/g) || []).length + (content.match(/\.target\(/g) || []).length;
514
+ if (targetCount < 2) {
515
+ pushFileFinding('ios.spm.modular_architecture', 'low', file, 1, 1, 'Package.swift without multiple targets (weak modular architecture)', findings);
516
+ }
517
+ if (!/Swinject|Needle|Resolver|Factory\b/.test(content)) {
518
+ pushFileFinding('ios.spm.dependency_injection', 'low', file, 1, 1, 'No DI library hint found in Package.swift', findings);
519
+ }
520
+ }
521
+ if (/storyboard|\.xib\b|\.nib\b/i.test(content)) {
522
+ pushFileFinding('ios.storyboards', 'high', file, 1, 1, 'Storyboard/XIB usage detected', findings);
523
+ }
524
+ const isAnalyzer = /infrastructure\/ast\/|analyzers\/|detectors\/|scanner|analyzer|detector/i.test(file);
525
+ const isTestFile = /\.(spec|test)\.(js|ts|swift)$/i.test(file);
526
+ if (!isAnalyzer && !isTestFile && /completion:\s*\(/.test(content)) {
527
+ pushFileFinding('ios.completion_handlers', 'medium', file, 1, 1, 'Completion handler usage detected', findings);
528
+ }
529
+ if (/import UIKit\b/.test(content) && /import SwiftUI\b/.test(content)) {
530
+ pushFileFinding('ios.uikit_unnecessary', 'low', file, 1, 1, 'UIKit and SwiftUI imported together', findings);
531
+ }
532
+ if (/\bDispatchQueue\./.test(content)) {
533
+ pushFileFinding('ios.dispatchqueue_old', 'low', file, 1, 1, 'DispatchQueue usage detected (prefer async/await)', findings);
534
+ }
535
+ if (/class\s+[A-Za-z0-9_]+ViewController\b/.test(content) && /(URLSession|Alamofire|NSPersistentContainer|NSManagedObjectContext)/.test(content)) {
536
+ pushFileFinding('ios.uikit.viewmodel_delegation', 'medium', file, 1, 1, 'ViewController contains data/network logic (delegate to ViewModel)', findings);
537
+ }
538
+ if (/[A-Za-z0-9_]\s*!\b/.test(content) && !/@IBOutlet\b/.test(content)) {
539
+ pushFileFinding('ios.force_unwrapping', 'high', file, 1, 1, 'Force unwrapping detected', findings);
540
+ }
541
+ if (/\[[ ]*(weak|unowned)[ ]+self[ ]*\]/.test(content) === false && /self\./.test(content) && /\{[^\n]*in/.test(content)) {
542
+ pushFileFinding('ios.weak_self', 'medium', file, 1, 1, 'Closure referencing self without [weak self]', findings);
543
+ }
544
+ if (/\[\s*unowned\s+self\s*\]/.test(content)) {
545
+ pushFileFinding('ios.unowned_self_missing', 'medium', file, 1, 1, 'unowned self used where weak may be safer', findings);
546
+ }
547
+ if (/self\./.test(content) && /\{[^\n]*in/.test(content) && !/\[[^\]]+\]/.test(content)) {
548
+ pushFileFinding('ios.capture_lists_missing', 'low', file, 1, 1, 'Closure without capture list', findings);
549
+ }
550
+ if (/\bclass\s+\w+\s*\{/.test(content) && !/\bdeinit\b/.test(content)) {
551
+ pushFileFinding('ios.deinit_missing', 'low', file, 1, 1, 'Class without deinit', findings);
552
+ }
553
+ if (/\bif\s+[A-Za-z_][A-Za-z0-9_]*\s*!=\s*nil\s*\{/.test(content)) {
554
+ pushFileFinding('ios.optionals.optional_binding', 'medium', file, 1, 1, 'Optional check != nil (prefer optional binding)', findings);
555
+ }
556
+ if (/:\s*Any\b|\bas\s*Any\b/.test(content)) {
557
+ pushFileFinding('ios.optionals.type_safety', 'medium', file, 1, 1, 'Usage of Any reduces type safety', findings);
558
+ }
559
+ if (/\.sink\s*\(/.test(content) && !/receiveCompletion\s*:\s*/.test(content)) {
560
+ pushFileFinding('ios.combine.error_handling', 'medium', file, 1, 1, 'Combine sink without receiveCompletion handler', findings);
561
+ }
562
+ if (/\.sink\s*\(|\.assign\s*\(/.test(content) && !/store\s*\(in\s*:\s*/.test(content)) {
563
+ pushFileFinding('ios.combine.memory_management', 'medium', file, 1, 1, 'Combine subscription without store(in:)', findings);
564
+ }
565
+ if (/\bTask\s*\{/.test(content) && !/withTaskCancellationHandler\s*\(/.test(content)) {
566
+ pushFileFinding('ios.concurrency.task_cancellation', 'low', file, 1, 1, 'Task without cancellation handling', findings);
567
+ }
568
+ iosTaskCount += (content.match(/\bTask\s*\{/g) || []).length;
569
+ if (/withTaskGroup\(|\bTaskGroup\b/.test(content)) iosTaskGroupFound = true;
570
+ if (/(UIImage\s*\(named:|Image\(\s*\"[^\"]+\")/.test(content) && !/resize|resizable|scaledTo/.test(content)) {
571
+ pushFileFinding('ios.performance.image_optimization', 'medium', file, 1, 1, 'Image usage without explicit optimization (resizable/resize/scaledTo)', findings);
572
+ }
573
+ if (/(sleep\(|usleep\(|Thread\.sleep|CFRunLoopRunInMode)/.test(content)) {
574
+ pushFileFinding('ios.performance.blocking_main_thread', 'high', file, 1, 1, 'Potential main-thread blocking call detected', findings);
575
+ }
576
+ if (/\b(var|let)\s+\w*delegate\w*\s*:\s*[A-Za-z_][A-Za-z0-9_]*Delegate\??/.test(content) && !/\bweak\s+var\s+\w*delegate/.test(content)) {
577
+ pushFileFinding('ios.memory.leaks', 'medium', file, 1, 1, 'Delegate property not weak (potential retain cycle)', findings);
578
+ }
579
+ if (/if\s+([A-Za-z_]\w*)\s*!=\s*nil\s*\{[\s\S]{0,120}?\1!\./.test(content)) {
580
+ pushFileFinding('ios.optionals.missing_optional_chaining', 'medium', file, 1, 1, 'Use optional chaining instead of if != nil and force unwrap', findings);
581
+ }
582
+ if (/([A-Za-z_]\w*)\s*!=\s*nil\s*\?\s*\1!\s*:\s*[^\n;]+/.test(content)) {
583
+ pushFileFinding('ios.optionals.missing_nil_coalescing', 'medium', file, 1, 1, 'Prefer ?? over ternary with force unwrap', findings);
584
+ }
585
+ if (/Text\(\s*\"[^\"]+\"\s*\)/.test(content) && !/NSLocalizedString|\.localized/.test(content)) {
586
+ pushFileFinding('ios.i18n.hardcoded_strings', 'medium', file, 1, 1, 'Hardcoded string in Text() without localization', findings);
587
+ }
588
+ if (/Date\(/.test(content) && !/DateFormatter/.test(content)) {
589
+ pushFileFinding('ios.i18n.missing_date_formatter', 'medium', file, 1, 1, 'Date rendered without DateFormatter', findings);
590
+ }
591
+ if (/(String\s*\(\s*format\s*:\s*\".*%[0-9]*[fd].*\"|NumberFormatter)/.test(content) === false && /%[0-9]*[fd]/.test(content)) {
592
+ pushFileFinding('ios.i18n.missing_number_formatter', 'medium', file, 1, 1, 'Numeric formatting without NumberFormatter', findings);
593
+ }
594
+ if (/(Data\([^)]*contentsOf|write\(to\:)/.test(content) && !/FileManager\.default/.test(content)) {
595
+ pushFileFinding('ios.persistence.missing_filemanager', 'medium', file, 1, 1, 'File operations without FileManager usage', findings);
596
+ }
597
+ if (/(NSPersistentContainer|NSPersistentStoreCoordinator)/.test(content) && !/NSMigratePersistentStoresAutomaticallyOption|NSInferMappingModelAutomaticallyOption/.test(content)) {
598
+ pushFileFinding('ios.persistence.migration', 'medium', file, 1, 1, 'Core Data without automatic migration options', findings);
599
+ }
600
+ if (/NSFetchRequest\s*<[^>]+>/.test(content) && !/fetchBatchSize|predicate\s*=/.test(content)) {
601
+ pushFileFinding('ios.persistence.performance', 'low', file, 1, 1, 'NSFetchRequest without predicate/batch size hints', findings);
602
+ }
603
+ if (path.extname(file).toLowerCase() === '.plist' && /NSAppTransportSecurity/.test(content)) {
604
+ if (/NSAllowsArbitraryLoads\s*<true\/>/.test(content)) {
605
+ pushFileFinding('ios.security.missing_ats', 'high', file, 1, 'ATS allows arbitrary loads - enable HTTPS by default', findings);
606
+ }
607
+ }
608
+ if (/URLSession\b|Alamofire\b/.test(content)) iosHasNetworkingUsage = true;
609
+ if (/URLSession\b/.test(content) && !/SecTrust|evaluateServerTrust|pinned|certificate/i.test(content)) {
610
+ pushFileFinding('ios.security.missing_ssl_pinning', 'high', file, 1, 1, 'Networking without SSL pinning indicators', findings);
611
+ }
612
+ if (/ServerTrustManager|pinnedCertificates|SecTrustEvaluate|SecPolicyCreateSSL|evaluators\s*:\s*\[|AFServerTrustPolicy/.test(content)) iosHasPinningIndicator = true;
613
+ if (/UserDefaults\.standard\.(set|setValue)\([^,]+,\s*forKey\s*:\s*\"(password|token|secret|apiKey)/i.test(content)) {
614
+ pushFileFinding('ios.security.missing_keychain', 'high', file, 1, 1, 'Sensitive data in UserDefaults - use Keychain', findings);
615
+ }
616
+ if (/(SecItemAdd|SecItemUpdate|SecKeyCreateRandomKey)\(/.test(content) && !/kSecAttrTokenIDSecureEnclave/.test(content)) {
617
+ pushFileFinding('ios.security.missing_secure_enclave', 'high', file, 1, 1, 'Keychain/SecKey usage without Secure Enclave token', findings);
618
+ }
619
+ if (/\bLAContext\b|evaluatePolicy\(/.test(content)) {
620
+ iosHasBiometric = true;
621
+ }
622
+ if (/(Cydia|Substrate|DYLD_INSERT_LIBRARIES|jailbreak|apt\.saurik|MobileSubstrate|libsubstitute|cydia:\/\/)/i.test(content)) {
623
+ iosHasJailbreakCheck = true;
624
+ }
625
+ if (/UIAccessibility\.isVoiceOverRunning|UIAccessibilityPostNotification|accessibilityVoiceOver/.test(content)) iosHasVoiceOverRef = true;
626
+ if (/semanticContentAttribute\s*=\s*\.forceRightToLeft|UISemanticContentAttribute\.(forceRightToLeft|playback)/.test(content)) iosHasRtlSupport = true;
627
+ if (/struct\s+\w+_Previews\s*:\s*PreviewProvider|:\s*PreviewProvider\b/.test(content)) iosHasPreviewProvider = true;
628
+ if (/\.previewDevice\(/.test(content)) iosHasPreviewMultiple = true;
629
+ if (/\.preferredColorScheme\(\.dark\)/.test(content)) iosPreviewHasDark = true;
630
+ if (/\.preferredColorScheme\(\.light\)/.test(content)) iosPreviewHasLight = true;
631
+ if (/import\s+QuartzCore\b|CABasicAnimation|CASpringAnimation|CAKeyframeAnimation|UIViewPropertyAnimator/.test(content)) iosHasCoreAnimationRef = true;
632
+ if (/withAnimation\(|\.animation\(/.test(content)) iosHasAnimationUsage = true;
633
+ if (/class\s+\w+UITests\b/.test(content) && !/XCUIApplication\(\)/.test(content)) {
634
+ pushFileFinding('ios.uitesting.missing_xcuitest', 'medium', file, 1, 1, 'UI tests missing XCUIApplication usage', findings);
635
+ }
636
+ if (/class\s+\w+UITests\b/.test(content) && /app\.(buttons|staticTexts|textFields|images)/.test(content) && !/accessibilityIdentifier/.test(content)) {
637
+ pushFileFinding('ios.uitesting.missing_accessibility', 'medium', file, 1, 1, 'UI tests querying elements without accessibility identifiers', findings);
638
+ }
639
+ if (/class\s+\w+UITests\b/.test(content)) {
640
+ const tapCount = (content.match(/\.tap\(\)/g) || []).length;
641
+ if (/(sleep\(|usleep\(|Thread\.sleep)/.test(content)) {
642
+ pushFileFinding('ios.ui_testing.flaky_tests', 'medium', file, 1, 1, 'UITest contains sleep calls leading to flakiness', findings);
643
+ }
644
+ if (tapCount >= 20) {
645
+ pushFileFinding('ios.ui_testing.test_recording', 'low', file, 1, 1, 'UITest looks like recording artifact (excessive .tap())', findings);
646
+ }
647
+ }
648
+ if ((/Image\(/.test(content) || /Button\(/.test(content) || /TextField\(/.test(content)) && !/\.accessibilityLabel\(/.test(content)) {
649
+ pushFileFinding('ios.accessibility.missing_labels', 'medium', file, 1, 1, 'UI elements without accessibilityLabel', findings);
650
+ }
651
+ if ((/Image\(/.test(content) || /Button\(/.test(content) || /TextField\(/.test(content)) && !/\.accessibilityAddTraits\(/.test(content)) {
652
+ pushFileFinding('ios.accessibility.missing_traits', 'low', file, 1, 1, 'UI elements without accessibilityAddTraits', findings);
653
+ }
654
+ if (/\.font\(\.system\(size\s*:\s*\d+/.test(content)) {
655
+ pushFileFinding('ios.accessibility.missing_dynamic_type', 'low', file, 1, 1, 'Fixed font size without Dynamic Type considerations', findings);
656
+ }
657
+ if (/\.animation\(/.test(content) && !/accessibilityReduceMotion|UIAccessibility\.isReduceMotionEnabled/.test(content)) {
658
+ pushFileFinding('ios.accessibility.missing_reduce_motion', 'low', file, 1, 1, 'Animations without reduce motion handling', findings);
659
+ }
660
+ if (/GeometryReader\s*\{/.test(content)) {
661
+ pushFileFinding('ios.swiftui.geometryreader_moderation', 'low', file, 1, 1, 'GeometryReader usage detected (use with moderation)', findings);
662
+ }
663
+ if (/NotificationCenter\.default\.addObserver\([\s\S]*didReceiveMemoryWarning/.test(content) || /UIApplication\.didReceiveMemoryWarningNotification/.test(content)) {
664
+ iosHasMemoryWarningObserver = true;
665
+ }
666
+ if (/(UITableView|UICollectionView)/.test(content) && !/dequeueReusable(Cell|SupplementaryView)/.test(content)) {
667
+ pushFileFinding('ios.performance.cell_reuse', 'medium', file, 1, 1, 'Collection/Table view usage without dequeueReusable*', findings);
668
+ }
669
+ if (/var\s+body\s*:\s*some\s+View[\s\S]*\{[\s\S]*\}/m.test(content) && /(Data\([^)]*contentsOf|JSONSerialization|Date\(\)|UUID\(\)|FileManager\.default)/.test(content)) {
670
+ pushFileFinding('ios.performance.missing_memoization', 'low', file, 1, 1, 'Potential heavy operation inside SwiftUI body without memoization', findings);
671
+ }
672
+ const vStackCount = (content.match(/\bVStack\s*\(/g) || []).length + (content.match(/\bHStack\s*\(/g) || []).length + (content.match(/\bZStack\s*\(/g) || []).length;
673
+ if (vStackCount > 20) {
674
+ pushFileFinding('ios.performance.view_hierarchy', 'low', file, 1, 1, 'Large number of nested stacks detected (consider simplifying hierarchy)', findings);
675
+ }
676
+ if (/CADisplayLink\s*\(/.test(content) || /CADisplayLink\.(add|init)/.test(content)) {
677
+ pushFileFinding('ios.performance.energy_impact', 'low', file, 1, 1, 'CADisplayLink usage may impact energy', findings);
678
+ }
679
+ if (/Timer\.(scheduledTimer|publish)\([^)]*timeInterval\s*:\s*0\.(0?[0-4])/m.test(content)) {
680
+ pushFileFinding('ios.performance.energy_impact', 'low', file, 1, 1, 'High-frequency timer detected (< 0.05s)', findings);
681
+ }
682
+ if (/\.repeatForever\(/.test(content)) {
683
+ pushFileFinding('ios.performance.energy_impact', 'low', file, 1, 1, 'Infinite animation repeat may impact energy', findings);
684
+ }
685
+ if (/while\s*\(\s*true\s*\)/.test(content)) {
686
+ pushFileFinding('ios.performance.energy_impact', 'medium', file, 1, 1, 'Potential infinite loop detected', findings);
687
+ }
688
+ if (!/\.focusable\(|UIKeyCommand\b/.test(content)) {
689
+ if (/(Button\(|TextField\(|List\(|ScrollView\()/.test(content)) {
690
+ pushFileFinding('ios.accessibility.keyboard_navigation', 'low', file, 1, 1, 'Interactive elements without keyboard navigation hints', findings);
691
+ }
692
+ }
693
+ if (!/\.focused\(|becomeFirstResponder\(\)/.test(content) && /(TextField\(|UITextField\b)/.test(content)) {
694
+ pushFileFinding('ios.accessibility.focus_management', 'low', file, 1, 1, 'Input elements without explicit focus management', findings);
695
+ }
696
+ if (/\bclass\s+([A-Z][A-Za-z0-9_]*)\s*\{/.test(content) && !/:\s*[A-Za-z0-9_]+/.test(content) && !/(UIView|UIViewController|CALayer|NSObject)/.test(content)) {
697
+ pushFileFinding('ios.values.reference_types_when_value', 'low', file, 1, 1, 'Class without inheritance – consider struct for value semantics', findings);
698
+ pushFileFinding('ios.values.classes_instead_structs', 'low', file, 1, 1, 'Class without inheritance – prefer struct', findings);
699
+ }
700
+ if (/\bstruct\s+[A-Z][A-Za-z0-9_]*\b/.test(content)) {
701
+ const varCount = (content.match(/\bvar\b/g) || []).length;
702
+ const letCount = (content.match(/\blet\b/g) || []).length;
703
+ if (varCount > letCount + 3) {
704
+ pushFileFinding('ios.values.mutability', 'low', file, 1, 1, 'Struct with excessive mutable properties (prefer let)', findings);
705
+ }
706
+ }
707
+ if (/NotificationCenter\.default\.addObserver\(/.test(content) && !/NotificationCenter\.default\.removeObserver\(/.test(content)) {
708
+ pushFileFinding('ios.memory.zombies', 'medium', file, 1, 1, 'Observer added without corresponding removeObserver', findings);
709
+ }
710
+ if (/addObserver\([^)]*forKeyPath\s*:\s*"[^"]+"/.test(content) && !/removeObserver\(/.test(content)) {
711
+ pushFileFinding('ios.memory.zombies', 'medium', file, 1, 1, 'KVO addObserver without removeObserver', findings);
712
+ }
713
+ if (/Data\(count\s*:\s*(\d{7,})\)/.test(content) || /Array\(repeating\s*:\s*[^,]+,\s*count\s*:\s*(\d{6,})\)/.test(content)) {
714
+ pushFileFinding('ios.memory.allocations', 'low', file, 1, 1, 'Large in-memory allocation detected', findings);
715
+ }
716
+ // i18n: locale aware formatting
717
+ if (/(DateFormatter|NumberFormatter)\(\)/.test(content) && !/\.locale\s*=\s*Locale\b/.test(content)) {
718
+ pushFileFinding('ios.i18n.locale_aware', 'low', file, 1, 1, 'Formatter without explicit Locale configured', findings);
719
+ }
720
+ }
721
+ }
722
+ if (iosAnchorFile) {
723
+ if (!iosHasBiometric) {
724
+ pushFileFinding('ios.security.missing_biometric', 'high', iosAnchorFile, 1, 1, 'LocalAuthentication (LAContext) not detected in project', findings);
725
+ }
726
+ if (!iosHasJailbreakCheck) {
727
+ pushFileFinding('ios.security.missing_jailbreak_detection', 'medium', iosAnchorFile, 1, 1, 'No jailbreak detection heuristics detected in project', findings);
728
+ }
729
+ if (!iosHasVoiceOverRef) {
730
+ pushFileFinding('ios.accessibility.missing_voiceover', 'low', iosAnchorFile, 1, 1, 'No VoiceOver usage detected in project', findings);
731
+ }
732
+ if (!iosHasStringsDict) {
733
+ pushFileFinding('ios.i18n.missing_stringsdict', 'medium', iosAnchorFile, 1, 1, 'No .stringsdict files detected in project', findings);
734
+ }
735
+ if (!iosHasRtlSupport) {
736
+ pushFileFinding('ios.i18n.missing_rtl', 'low', iosAnchorFile, 1, 1, 'No RTL semanticContentAttribute usage detected', findings);
737
+ }
738
+ if (iosHasNetworkingUsage && !iosHasPinningIndicator) {
739
+ pushFileFinding('ios.security.certificate_pinning', 'critical', iosAnchorFile, 1, 1, 'Networking used without explicit certificate pinning configuration', findings);
740
+ }
741
+ if (!iosHasPreviewProvider) {
742
+ pushFileFinding('ios.swiftui.preview_provider', 'low', iosAnchorFile, 1, 1, 'No PreviewProvider found in project', findings);
743
+ }
744
+ if (!(iosHasPreviewMultiple || (iosPreviewHasDark && iosPreviewHasLight))) {
745
+ pushFileFinding('ios.swiftui.preview_multiple_devices', 'low', iosAnchorFile, 1, 1, 'No multiple device/theme previews detected', findings);
746
+ }
747
+ if (iosHasAnimationUsage && !iosHasCoreAnimationRef) {
748
+ pushFileFinding('ios.performance.core_animation', 'low', iosAnchorFile, 1, 1, 'Animations used without Core Animation/Animator references', findings);
749
+ }
750
+ if (!iosHasMemoryWarningObserver) {
751
+ pushFileFinding('ios.memory.memory_pressure', 'low', iosAnchorFile, 1, 1, 'No memory pressure handling (didReceiveMemoryWarning) detected', findings);
752
+ }
753
+ if (!iosHasAnyExtension) {
754
+ pushFileFinding('ios.pop.missing_extensions', 'low', iosAnchorFile, 1, 1, 'No Swift extensions detected across project', findings);
755
+ }
756
+ if (!iosHasExtensionsFolder) {
757
+ pushFileFinding('ios.organization.grouping', 'low', iosAnchorFile, 1, 1, 'No Extensions folder detected for logical grouping', findings);
758
+ }
759
+ if (iosPublicApiFound && !iosPublicApiDocsFound) {
760
+ pushFileFinding('ios.organization.documentation', 'low', iosAnchorFile, 1, 1, 'Public APIs without minimal doc comments (///)', findings);
761
+ }
762
+ if (iosTaskCount > 3 && !iosTaskGroupFound) {
763
+ pushFileFinding('ios.concurrency.structured_concurrency', 'low', iosAnchorFile, 1, 1, 'Multiple Task blocks without TaskGroup usage', findings);
764
+ }
765
+ if (!iosHasFastfile) {
766
+ pushFileFinding('ios.cicd.code_signing', 'low', iosAnchorFile, 1, 1, 'Fastfile not found; code signing automation likely missing', findings);
767
+ }
768
+ if (!iosCiHasXcodeTests) {
769
+ pushFileFinding('ios.cicd.test_automation', 'low', iosAnchorFile, 1, 1, 'CI pipeline without xcodebuild test/fastlane scan', findings);
770
+ }
771
+ if (!(iosHasDomainFolder && iosHasApplicationFolder && iosHasInfrastructureFolder && iosHasPresentationFolder)) {
772
+ pushFileFinding('ios.arch.clean_architecture', 'low', iosAnchorFile, 1, 1, 'Clean Architecture folders (Domain/Application/Infrastructure/Presentation) not fully present', findings);
773
+ }
774
+ const iosSwiftFiles = files.filter(f => f.endsWith('.swift'));
775
+ const iosAnyAnimation = iosSwiftFiles.some(f => f.includes('Animation') || f.includes('transition'));
776
+ const iosEnergyHeuristics = iosSwiftFiles.some(f => f.includes('Timer') || f.includes('Location'));
777
+ if (iosAnyAnimation || iosEnergyHeuristics) {
778
+ pushFileFinding('ios.performance.instruments', 'low', iosAnchorFile, 1, 1, 'Consider Instruments profiling for performance/energy hotspots', findings);
779
+ }
780
+ }
781
+
782
+ // ============================================
783
+ // ============================================
784
+ const hasKotlinFiles = files.some(f => f.endsWith('.kt'));
785
+ if (hasKotlinFiles) {
786
+ const androidAnchorFile = files.find(f => f.endsWith('.kt')) || files[0];
787
+
788
+ const kotlinFiles = files.filter(f => f.endsWith('.kt'));
789
+ const allContent = kotlinFiles.map(f => {
790
+ try {
791
+ return fs.readFileSync(f, 'utf8');
792
+ } catch (error) {
793
+ return '';
794
+ }
795
+ }).join('\n');
796
+
797
+ const hasStableAnnotation = /import\s+androidx\.compose\.runtime\.Stable|@Stable/.test(allContent);
798
+ const hasImmutableAnnotation = /import\s+androidx\.compose\.runtime\.Immutable|@Immutable/.test(allContent);
799
+ const hasComplexDataClasses = /data\s+class\s+\w+.*\{[\s\S]{100,}?\}/.test(allContent);
800
+
801
+ if (hasComplexDataClasses && !hasStableAnnotation && !hasImmutableAnnotation) {
802
+ pushFileFinding('android.compose_perf.missing_stable_annotation', 'high', androidAnchorFile, 1, 1,
803
+ 'Data classes complejas sin @Stable/@Immutable causan re-compositions innecesarias', findings);
804
+ }
805
+
806
+ // 2. remember optimization
807
+ const hasComposable = /@Composable/.test(allContent);
808
+ const hasRemember = /remember\s*\{/.test(allContent);
809
+ const hasComplexCalculations = /\.map\s*\{|\.filter\s*\{|\.reduce\s*\{/.test(allContent);
810
+
811
+ if (hasComposable && hasComplexCalculations && !hasRemember) {
812
+ pushFileFinding('android.compose_perf.missing_remember', 'high', androidAnchorFile, 1, 1,
813
+ 'Cálculos complejos en Composable sin remember{} causan recálculos en cada recomposition', findings);
814
+ }
815
+
816
+ // 3. derivedStateOf usage
817
+ const hasDerivedStateOf = /derivedStateOf\s*\{/.test(allContent);
818
+ const hasExpensiveComputations = allContent.split('\n').some(line =>
819
+ (line.includes('.sortedBy') || line.includes('.groupBy')) &&
820
+ line.includes('val ') &&
821
+ !line.includes('remember')
822
+ );
823
+
824
+ if (hasExpensiveComputations && !hasDerivedStateOf) {
825
+ pushFileFinding('android.compose_perf.missing_derived_state', 'high', androidAnchorFile, 1, 1,
826
+ 'Cálculos costosos (sortedBy, groupBy) sin derivedStateOf. Solo recalcular cuando cambien inputs', findings);
827
+ }
828
+
829
+ const launchedEffectMatches = allContent.match(/LaunchedEffect\([^)]*\)/g) || [];
830
+ const launchedEffectWithoutKey = launchedEffectMatches.filter(le =>
831
+ le === 'LaunchedEffect(true)' || le === 'LaunchedEffect(Unit)'
832
+ );
833
+
834
+ if (launchedEffectWithoutKey.length > 0) {
835
+ pushFileFinding('android.compose_perf.launched_effect_keys', 'medium', androidAnchorFile, 1, 1,
836
+ `${launchedEffectWithoutKey.length} LaunchedEffect sin keys apropiadas. Usar keys para controlar cuándo se relanza`, findings);
837
+ }
838
+
839
+ // 5. kotlinx.collections.immutable
840
+ const hasImmutableCollections = /import\s+kotlinx\.collections\.immutable/.test(allContent);
841
+ const hasListStateFlow = /StateFlow<List<|MutableStateFlow<List</.test(allContent);
842
+
843
+ if (hasListStateFlow && !hasImmutableCollections) {
844
+ pushFileFinding('android.compose_perf.missing_immutable_collections', 'medium', androidAnchorFile, 1, 1,
845
+ 'StateFlow<List<T>> sin kotlinx.collections.immutable puede causar re-renders innecesarios', findings);
846
+ }
847
+
848
+ const composableFunctions = allContent.match(/@Composable[\s\S]*?fun\s+\w+\([^)]*\)/g) || [];
849
+ const mutableParams = composableFunctions.filter(func =>
850
+ func.includes('var ') || (func.includes(': MutableList') || func.includes(': ArrayList'))
851
+ );
852
+
853
+ if (mutableParams.length > 0) {
854
+ pushFileFinding('android.compose_perf.skip_recomposition', 'high', androidAnchorFile, 1, 1,
855
+ `${mutableParams.length} Composables con parámetros mutables causan re-renders. Usar parámetros inmutables`, findings);
856
+ }
857
+
858
+ const dataClassesUsedInComposables = allContent.match(/data\s+class\s+(\w+)/g) || [];
859
+
860
+ if (dataClassesUsedInComposables.length > 5 && !hasStableAnnotation) {
861
+ pushFileFinding('android.compose_perf.missing_composable_stability', 'medium', androidAnchorFile, 1, 1,
862
+ `${dataClassesUsedInComposables.length} data classes sin @Stable annotation. Marcar para estabilidad`, findings);
863
+ }
864
+
865
+ const functionsWithClosureParams = composableFunctions.filter(func =>
866
+ func.includes('() ->') || func.includes('(') && func.includes(') ->')
867
+ );
868
+
869
+ if (functionsWithClosureParams.length > 3) {
870
+ pushFileFinding('android.compose_perf.unstable_parameters', 'medium', androidAnchorFile, 1, 1,
871
+ `${functionsWithClosureParams.length} Composables con closures como parámetros (inestables). Considerar remember o stable wrappers`, findings);
872
+ }
873
+
874
+ // ============================================
875
+ // ============================================
876
+ const hasSettingsGradle = files.some(f => f.endsWith('settings.gradle.kts') || f.endsWith('settings.gradle'));
877
+ const settingsContent = hasSettingsGradle ?
878
+ fs.readFileSync(files.find(f => f.endsWith('settings.gradle.kts') || f.endsWith('settings.gradle')), 'utf-8') : '';
879
+
880
+ const featureModules = (settingsContent.match(/include\(":feature:\w+"\)/g) || []).length;
881
+ const coreModules = (settingsContent.match(/include\(":core:\w+"\)/g) || []).length;
882
+ const hasAppModule = /include\(":app"\)/.test(settingsContent);
883
+
884
+ // 1. Feature modules
885
+ if (hasSettingsGradle && featureModules === 0 && files.filter(f => f.endsWith('.kt')).length > 100) {
886
+ pushFileFinding('android.multimodule.missing_feature_modules', 'medium', androidAnchorFile, 1, 1,
887
+ 'Proyecto grande sin feature modules. Modularizar en :feature:orders, :feature:users, etc.', findings);
888
+ }
889
+
890
+ // 2. Core modules
891
+ if (hasSettingsGradle && coreModules === 0 && featureModules > 0) {
892
+ pushFileFinding('android.multimodule.missing_core_modules', 'medium', androidAnchorFile, 1, 1,
893
+ 'Feature modules sin core modules. Crear :core:network, :core:database, :core:ui', findings);
894
+ }
895
+
896
+ // 3. App module
897
+ if (hasSettingsGradle && !hasAppModule && featureModules > 0) {
898
+ pushFileFinding('android.multimodule.missing_app_module', 'high', androidAnchorFile, 1, 1,
899
+ ':app module faltante. Debe orquestar todos los feature modules', findings);
900
+ }
901
+
902
+ const buildGradleFiles = files.filter(f => f.endsWith('build.gradle.kts') || f.endsWith('build.gradle'));
903
+ buildGradleFiles.forEach(buildFile => {
904
+ const buildContent = fs.readFileSync(buildFile, 'utf-8');
905
+
906
+ if (buildFile.includes('feature/') && buildContent.includes('project(":feature:')) {
907
+ pushFileFinding('android.multimodule.wrong_dependencies', 'high', buildFile, 1, 1,
908
+ 'Feature module depende de otro Feature. Extraer código compartido a :core', findings);
909
+ }
910
+ });
911
+
912
+ if (hasSettingsGradle && !settingsContent.includes('dynamicFeatures')) {
913
+ pushFileFinding('android.multimodule.missing_dynamic_features', 'low', androidAnchorFile, 1, 1,
914
+ 'Sin dynamic features. Considerar para app bundles grandes (>150MB)', findings);
915
+ }
916
+
917
+ const sharedFolders = files.filter(f => f.includes('/shared/') || f.includes('/common/'));
918
+ if (sharedFolders.length > 10 && coreModules === 0) {
919
+ pushFileFinding('android.multimodule.shared_code', 'medium', androidAnchorFile, 1, 1,
920
+ 'Código en /shared o /common sin modularización. Migrar a :core modules', findings);
921
+ }
922
+
923
+ // 7. API modules
924
+ const hasApiFolder = files.some(f => f.includes('/api/'));
925
+ if (hasApiFolder && !settingsContent.includes(':api')) {
926
+ pushFileFinding('android.multimodule.missing_api_modules', 'low', androidAnchorFile, 1, 1,
927
+ 'Código API sin módulo separado. Crear :api module para exposed APIs', findings);
928
+ }
929
+
930
+ // ============================================
931
+ // ============================================
932
+ const hasGitHubActions = files.some(f => f.includes('.github/workflows'));
933
+ const hasGradleTasks = /task\s+\w+|tasks\.register/.test(allContent);
934
+ const hasLintConfig = files.some(f => f.endsWith('lint.xml'));
935
+ const hasDetekt = /detekt\s*\{|id\("io\.gitlab\.arturbosch\.detekt"\)/.test(allContent);
936
+
937
+ // 1. GitHub Actions
938
+ if (!hasGitHubActions) {
939
+ pushFileFinding('android.cicd.missing_github_actions', 'medium', androidAnchorFile, 1, 1,
940
+ 'Proyecto sin GitHub Actions. Automatizar CI/CD con workflows', findings);
941
+ }
942
+
943
+ // 2. Gradle tasks
944
+ if (!hasGradleTasks && buildGradleFiles.length > 0) {
945
+ pushFileFinding('android.cicd.missing_gradle_tasks', 'low', androidAnchorFile, 1, 1,
946
+ 'Sin custom Gradle tasks. Crear tasks para assembleDebug, test, etc.', findings);
947
+ }
948
+
949
+ if (!hasLintConfig) {
950
+ pushFileFinding('android.cicd.missing_lint', 'medium', androidAnchorFile, 1, 1,
951
+ 'Sin lint.xml configuration. Lint warnings deben tratarse como errores', findings);
952
+ }
953
+
954
+ // 4. Detekt
955
+ if (!hasDetekt && files.filter(f => f.endsWith('.kt')).length > 20) {
956
+ pushFileFinding('android.cicd.missing_detekt', 'medium', androidAnchorFile, 1, 1,
957
+ 'Sin Detekt para static analysis de Kotlin. Añadir para detectar code smells', findings);
958
+ }
959
+
960
+ const hasFastlane = files.some(f => f.includes('fastlane/Fastfile'));
961
+ if (hasFastlane) {
962
+ const fastlaneContent = fs.readFileSync(files.find(f => f.includes('fastlane/Fastfile')), 'utf-8');
963
+
964
+ if (!fastlaneContent.includes('firebase_app_distribution')) {
965
+ pushFileFinding('android.cicd.missing_firebase_distribution', 'low', androidAnchorFile, 1, 1,
966
+ 'Fastlane sin Firebase App Distribution. Útil para beta testing', findings);
967
+ }
968
+ }
969
+
970
+ // 6. Play Console
971
+ if (hasFastlane) {
972
+ const fastlaneContent = fs.readFileSync(files.find(f => f.includes('fastlane/Fastfile')), 'utf-8');
973
+
974
+ if (!fastlaneContent.includes('upload_to_play_store') && !fastlaneContent.includes('supply')) {
975
+ pushFileFinding('android.cicd.missing_play_console', 'low', androidAnchorFile, 1, 1,
976
+ 'Fastlane sin deployment automático a Play Console', findings);
977
+ }
978
+ }
979
+
980
+ if (hasGitHubActions) {
981
+ const workflowFiles = files.filter(f => f.includes('.github/workflows') && f.endsWith('.yml'));
982
+ let hasTestsInWorkflow = false;
983
+
984
+ workflowFiles.forEach(wf => {
985
+ const wfContent = fs.readFileSync(wf, 'utf-8');
986
+ if (wfContent.includes('./gradlew test') || wfContent.includes('./gradlew check')) {
987
+ hasTestsInWorkflow = true;
988
+ }
989
+ });
990
+
991
+ if (!hasTestsInWorkflow) {
992
+ pushFileFinding('android.cicd.missing_automated_testing', 'high', androidAnchorFile, 1, 1,
993
+ 'GitHub Actions sin tests automatizados. Añadir ./gradlew test a workflow', findings);
994
+ }
995
+ }
996
+
997
+ // ============================================
998
+ // ============================================
999
+ const gradleFiles = buildGradleFiles.map(f => {
1000
+ try {
1001
+ return { path: f, content: fs.readFileSync(f, 'utf-8') };
1002
+ } catch (error) {
1003
+ return null;
1004
+ }
1005
+ }).filter(Boolean);
1006
+
1007
+ const allGradleContent = gradleFiles.map(g => g.content).join('\n');
1008
+
1009
+ const jetpackDeps = {
1010
+ 'lifecycle-viewmodel-ktx': 'android.jetpack.missing_viewmodel',
1011
+ 'navigation-compose': 'android.jetpack.missing_navigation',
1012
+ 'room-ktx': 'android.jetpack.missing_room',
1013
+ 'work-runtime-ktx': 'android.jetpack.missing_workmanager',
1014
+ 'paging-compose': 'android.jetpack.missing_paging',
1015
+ 'datastore-preferences': 'android.jetpack.missing_datastore',
1016
+ 'hilt-android': 'android.jetpack.missing_hilt',
1017
+ 'compose-bom': 'android.jetpack.missing_compose_bom'
1018
+ };
1019
+
1020
+ Object.entries(jetpackDeps).forEach(([dep, ruleId]) => {
1021
+ if (!allGradleContent.includes(dep) && files.filter(f => f.endsWith('.kt')).length > 20) {
1022
+ const depName = dep.replace(/-/g, ' ');
1023
+ pushFileFinding(ruleId, 'medium', androidAnchorFile, 1, 1,
1024
+ `Dependencia ${depName} no encontrada en build.gradle. Añadir para funcionalidad Jetpack`, findings);
1025
+ }
1026
+ });
1027
+
1028
+ const versionMatches = allGradleContent.match(/androidx\.\w+:\w+:(\d+\.\d+\.\d+)/g) || [];
1029
+ const oldVersions = versionMatches.filter(v => {
1030
+ const version = v.match(/(\d+\.\d+\.\d+)/)?.[1];
1031
+ return version && parseFloat(version) < 1.0;
1032
+ });
1033
+
1034
+ if (oldVersions.length > 3) {
1035
+ pushFileFinding('android.jetpack.outdated_versions', 'medium', androidAnchorFile, 1, 1,
1036
+ `${oldVersions.length} dependencias Jetpack desactualizadas. Actualizar a versiones recientes`, findings);
1037
+ }
1038
+
1039
+ if (hasComposable && !allGradleContent.includes('androidx.compose.compiler:compiler')) {
1040
+ pushFileFinding('android.jetpack.missing_compose_compiler', 'low', androidAnchorFile, 1, 1,
1041
+ 'Compose sin compiler reports. Añadir para métricas de estabilidad', findings);
1042
+ }
1043
+
1044
+ // ============================================
1045
+ // ============================================
1046
+ const hasOrdersModule = files.some(f => f.includes('/orders/') || f.includes('OrdersRepository'));
1047
+ const hasUsersModule = files.some(f => f.includes('/users/') || f.includes('UsersRepository'));
1048
+
1049
+ // 1. DTO codegen
1050
+ const hasDTOs = files.some(f => f.includes('/dto/') || f.includes('Dto.kt'));
1051
+ const hasCodegen = files.some(f => f.includes('openapi') || f.includes('swagger-codegen'));
1052
+
1053
+ if (hasDTOs && !hasCodegen && files.length > 50) {
1054
+ pushFileFinding('android.rural.dto_codegen', 'medium', androidAnchorFile, 1, 1,
1055
+ 'DTOs manuales sin codegen. Considerar generar desde TypeScript backend con quicktype/OpenAPI', findings);
1056
+ }
1057
+
1058
+ if (hasOrdersModule || hasUsersModule) {
1059
+ const hasRepositoryInterface = /interface\s+\w+Repository/.test(allContent);
1060
+ const hasRepositoryImpl = /class\s+\w+Repository.*:\s*\w+Repository/.test(allContent);
1061
+
1062
+ if (!hasRepositoryInterface || !hasRepositoryImpl) {
1063
+ pushFileFinding('android.rural.repository_pattern', 'high', androidAnchorFile, 1, 1,
1064
+ 'Falta Repository pattern (interface + implementación). Implementar para abstracción de datos', findings);
1065
+ }
1066
+ }
1067
+
1068
+ // 3. Use Cases
1069
+ const hasUseCases = files.some(f => f.includes('UseCase.kt') || /class\s+\w+UseCase/.test(allContent));
1070
+
1071
+ if ((hasOrdersModule || hasUsersModule) && !hasUseCases) {
1072
+ pushFileFinding('android.rural.use_cases', 'high', androidAnchorFile, 1, 1,
1073
+ 'Falta Use Cases (CreateOrderUseCase, UpdateOrderStatusUseCase). Encapsular lógica de negocio', findings);
1074
+ }
1075
+
1076
+ const hasViewModels = /class\s+\w+ViewModel.*:\s*ViewModel/.test(allContent);
1077
+ const hasSpecificViewModels =
1078
+ /OrdersListViewModel|OrderDetailViewModel|UsersViewModel/.test(allContent);
1079
+
1080
+ if (hasOrdersModule && !hasSpecificViewModels) {
1081
+ pushFileFinding('android.rural.viewmodels', 'medium', androidAnchorFile, 1, 1,
1082
+ 'Falta ViewModels específicos (OrdersListViewModel, OrderDetailViewModel)', findings);
1083
+ }
1084
+
1085
+ const hasHiltApp = /@HiltAndroidApp/.test(allContent);
1086
+ const hasHiltModules = /@Module/.test(allContent) && /@InstallIn/.test(allContent);
1087
+
1088
+ if (files.filter(f => f.endsWith('.kt')).length > 50 && (!hasHiltApp || !hasHiltModules)) {
1089
+ pushFileFinding('android.rural.hilt_di', 'high', androidAnchorFile, 1, 1,
1090
+ 'Hilt DI no implementado en toda la app. Usar Hilt para dependency injection completo', findings);
1091
+ }
1092
+
1093
+ // 6. 100% Jetpack Compose
1094
+ const hasXMLLayouts = files.some(f => f.endsWith('.xml') && f.includes('/layout/'));
1095
+
1096
+ if (hasXMLLayouts && hasComposable) {
1097
+ pushFileFinding('android.rural.compose_ui', 'medium', androidAnchorFile, 1, 1,
1098
+ 'Mix de XML layouts y Compose. Migrar 100% a Jetpack Compose', findings);
1099
+ }
1100
+
1101
+ const hasRoom = /@Entity|@Dao|@Database/.test(allContent);
1102
+ const hasNetworkCalls = /Retrofit|OkHttp|URLConnection/.test(allContent);
1103
+
1104
+ if (hasNetworkCalls && !hasRoom) {
1105
+ pushFileFinding('android.rural.offline_first', 'medium', androidAnchorFile, 1, 1,
1106
+ 'Network calls sin Room. Implementar offline-first con cache local', findings);
1107
+ }
1108
+
1109
+ const hasMaterial3 = /import\s+androidx\.compose\.material3/.test(allContent);
1110
+ const hasTheme = /\w+Theme\s*\{/.test(allContent);
1111
+ const hasDarkTheme = /isSystemInDarkTheme|darkColorScheme/.test(allContent);
1112
+
1113
+ if (hasComposable && (!hasMaterial3 || !hasTheme || !hasDarkTheme)) {
1114
+ pushFileFinding('android.rural.material3_theme', 'medium', androidAnchorFile, 1, 1,
1115
+ 'Falta Material 3 theme completo con dark mode. Implementar desde día 1', findings);
1116
+ }
1117
+ }
1118
+ }
1119
+
1120
+ module.exports = { runTextScanner };