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.
- package/LICENSE +21 -0
- package/README.md +1105 -0
- package/bin/__tests__/auto-fix-violations.spec.js +132 -0
- package/bin/__tests__/auto-restart-guards.spec.js +11 -0
- package/bin/__tests__/check-doc-drift.spec.js +11 -0
- package/bin/__tests__/check-version.spec.js +240 -0
- package/bin/__tests__/cli.spec.js +11 -0
- package/bin/__tests__/guard-auto-manager.spec.js +11 -0
- package/bin/__tests__/guard-supervisor.spec.js +11 -0
- package/bin/__tests__/hook-status.spec.js +11 -0
- package/bin/__tests__/install.spec.js +11 -0
- package/bin/__tests__/nightly-metrics-report.spec.js +94 -0
- package/bin/__tests__/plan-review.spec.js +11 -0
- package/bin/__tests__/predictive-hooks.spec.js +11 -0
- package/bin/__tests__/run-ast-adapter.spec.js +11 -0
- package/bin/__tests__/run-orchestrator.spec.js +11 -0
- package/bin/__tests__/run-playbook.spec.js +11 -0
- package/bin/__tests__/setup-eslint.spec.js +11 -0
- package/bin/__tests__/violations-api.spec.js +11 -0
- package/bin/__tests__/watch-hooks.spec.js +11 -0
- package/bin/ai-commit.sh +5 -0
- package/bin/audit +5 -0
- package/bin/audit-library.js +6 -0
- package/bin/auto-fix-violations.js +19 -0
- package/bin/auto-restart-guards.js +6 -0
- package/bin/check-doc-drift.js +6 -0
- package/bin/check-version.js +19 -0
- package/bin/cleanup-branches.sh +5 -0
- package/bin/cli.js +6 -0
- package/bin/demo-recording.sh +5 -0
- package/bin/demo-violations +5 -0
- package/bin/fix-enforcer +5 -0
- package/bin/fix-gitflow-enforcement.sh +5 -0
- package/bin/generate-progress-report.sh +5 -0
- package/bin/git-analyze-pairs.sh +5 -0
- package/bin/git-leave-branch-check.sh +5 -0
- package/bin/gitflow +5 -0
- package/bin/gitflow-shell-integration.sh +5 -0
- package/bin/guard-auto-manager.js +6 -0
- package/bin/guard-autostart.sh +5 -0
- package/bin/guard-env.sh +5 -0
- package/bin/guard-supervisor.js +6 -0
- package/bin/hook-status.js +6 -0
- package/bin/install-git-wrapper.sh +5 -0
- package/bin/install.js +6 -0
- package/bin/kill-mcp-zombies.sh +5 -0
- package/bin/nightly-metrics-report.js +8 -0
- package/bin/plan-review.js +6 -0
- package/bin/predictive-hooks.js +6 -0
- package/bin/pumuki-audit.js +6 -0
- package/bin/pumuki-init.js +19 -0
- package/bin/pumuki-mcp-server.js +13 -0
- package/bin/pumuki-mcp.js +6 -0
- package/bin/pumuki-rules.js +6 -0
- package/bin/request-no-verify-approval.sh +5 -0
- package/bin/run-ast-adapter.js +6 -0
- package/bin/run-intelligent-audit.sh +5 -0
- package/bin/run-orchestrator.js +6 -0
- package/bin/run-playbook.js +6 -0
- package/bin/session-loader.sh +5 -0
- package/bin/setup-eslint.js +6 -0
- package/bin/start-guards.sh +5 -0
- package/bin/sync-autonomous-orchestrator.sh +5 -0
- package/bin/sync-to-library.sh +5 -0
- package/bin/update-evidence.sh +5 -0
- package/bin/update-session-context.sh +5 -0
- package/bin/verify-no-verify.sh +5 -0
- package/bin/violations +5 -0
- package/bin/violations-api.js +6 -0
- package/bin/watch-hooks.js +6 -0
- package/docs/API_REFERENCE.md +161 -0
- package/docs/ARCHITECTURE.md +236 -0
- package/docs/ARCHITECTURE_DETAILED.md +499 -0
- package/docs/BRANCH_PROTECTION_GUIDE.md +236 -0
- package/docs/CODE_STANDARDS.md +440 -0
- package/docs/CONTRIBUTING.md +246 -0
- package/docs/DEPENDENCIES.md +541 -0
- package/docs/HOW_IT_WORKS.md +716 -0
- package/docs/INSTALLATION.md +784 -0
- package/docs/MCP_SERVERS.md +786 -0
- package/docs/TESTING.md +423 -0
- package/docs/USAGE.md +856 -0
- package/docs/images/ast_intelligence_01.png +0 -0
- package/docs/images/ast_intelligence_02.png +0 -0
- package/docs/images/ast_intelligence_03.png +0 -0
- package/docs/images/ast_intelligence_04.png +0 -0
- package/docs/images/ast_intelligence_05.png +0 -0
- package/hooks/getSkillRulesPath.ts +52 -0
- package/hooks/git-status-monitor.ts +160 -0
- package/hooks/index.js +5 -0
- package/hooks/notify-macos.ts +42 -0
- package/hooks/package.json +16 -0
- package/hooks/post-tool-use-tracker.sh +89 -0
- package/hooks/pre-tool-use-evidence-validator.ts +252 -0
- package/hooks/pre-tool-use-guard.ts +151 -0
- package/hooks/skill-activation-prompt.sh +8 -0
- package/hooks/skill-activation-prompt.ts +307 -0
- package/index.js +49 -0
- package/package.json +117 -0
- package/presentation/cli/audit.sh +24 -0
- package/presentation/cli/autonomous-status.sh +92 -0
- package/presentation/cli/categorize-violations.sh +179 -0
- package/presentation/cli/direct-audit-option2.sh +23 -0
- package/presentation/cli/direct-audit.sh +33 -0
- package/scripts/hooks-system/.AI_TOKEN_STATUS.txt +16 -0
- package/scripts/hooks-system/.audit-reports/auto-recovery.log +1 -0
- package/scripts/hooks-system/.audit-reports/install-wizard.log +4 -0
- package/scripts/hooks-system/.audit-reports/notifications.log +425 -0
- package/scripts/hooks-system/.audit-reports/token-monitor.log +1275 -0
- package/scripts/hooks-system/.audit_tmp/intelligent-report.json +44953 -0
- package/scripts/hooks-system/.audit_tmp/intelligent-report.txt +1338 -0
- package/scripts/hooks-system/.audit_tmp/severity-history.jsonl +1 -0
- package/scripts/hooks-system/.audit_tmp/token-usage.jsonl +1 -0
- package/scripts/hooks-system/.hook-system/config.json +8 -0
- package/scripts/hooks-system/application/CompositionRoot.js +325 -0
- package/scripts/hooks-system/application/__tests__/CompositionRoot.spec.js +84 -0
- package/scripts/hooks-system/application/commands/index.js +64 -0
- package/scripts/hooks-system/application/queries/index.js +60 -0
- package/scripts/hooks-system/application/services/AutonomousOrchestrator.js +130 -0
- package/scripts/hooks-system/application/services/ContextDetectionEngine.js +181 -0
- package/scripts/hooks-system/application/services/DynamicRulesLoader.js +182 -0
- package/scripts/hooks-system/application/services/GitFlowService.js +156 -0
- package/scripts/hooks-system/application/services/GitTreeState.js +140 -0
- package/scripts/hooks-system/application/services/HookSystemScheduler.js +77 -0
- package/scripts/hooks-system/application/services/IntelligentCommitAnalyzer.js +151 -0
- package/scripts/hooks-system/application/services/IntelligentGitTreeMonitor.js +118 -0
- package/scripts/hooks-system/application/services/PlatformAnalysisService.js +173 -0
- package/scripts/hooks-system/application/services/PlatformDetectionService.js +168 -0
- package/scripts/hooks-system/application/services/PlaybookRunner.js +39 -0
- package/scripts/hooks-system/application/services/PredictiveHookAdvisor.js +56 -0
- package/scripts/hooks-system/application/services/RealtimeGuardPlugin.js +62 -0
- package/scripts/hooks-system/application/services/RealtimeGuardService.js +374 -0
- package/scripts/hooks-system/application/services/SmartDirtyTreeAnalyzer.js +63 -0
- package/scripts/hooks-system/application/services/__tests__/AutonomousOrchestrator.spec.js +36 -0
- package/scripts/hooks-system/application/services/__tests__/ContextDetectionEngine.spec.js +33 -0
- package/scripts/hooks-system/application/services/__tests__/DynamicRulesLoader.spec.js +43 -0
- package/scripts/hooks-system/application/services/__tests__/GitTreeState.spec.js +163 -0
- package/scripts/hooks-system/application/services/__tests__/HookSystemScheduler.spec.js +207 -0
- package/scripts/hooks-system/application/services/__tests__/IntelligentCommitAnalyzer.spec.js +365 -0
- package/scripts/hooks-system/application/services/__tests__/IntelligentGitTreeMonitor.spec.js +188 -0
- package/scripts/hooks-system/application/services/__tests__/PlatformDetectionService.spec.js +28 -0
- package/scripts/hooks-system/application/services/__tests__/PlaybookRunner.spec.js +143 -0
- package/scripts/hooks-system/application/services/__tests__/PredictiveHookAdvisor.spec.js +181 -0
- package/scripts/hooks-system/application/services/__tests__/RealtimeGuardPlugin.spec.js +45 -0
- package/scripts/hooks-system/application/services/__tests__/RealtimeGuardService.critical.spec.js +401 -0
- package/scripts/hooks-system/application/services/commit/CommitMessageGenerator.js +34 -0
- package/scripts/hooks-system/application/services/commit/FeatureDetector.js +101 -0
- package/scripts/hooks-system/application/services/evidence/EvidenceContextManager.js +163 -0
- package/scripts/hooks-system/application/services/evidence/__tests__/EvidenceContextManager.spec.js +98 -0
- package/scripts/hooks-system/application/services/guard/GuardAutoManagerService.js +169 -0
- package/scripts/hooks-system/application/services/guard/GuardConfig.js +15 -0
- package/scripts/hooks-system/application/services/guard/GuardEventLogger.js +70 -0
- package/scripts/hooks-system/application/services/guard/GuardHealthReminder.js +54 -0
- package/scripts/hooks-system/application/services/guard/GuardHeartbeatMonitor.js +94 -0
- package/scripts/hooks-system/application/services/guard/GuardLockManager.js +72 -0
- package/scripts/hooks-system/application/services/guard/GuardMonitorLoop.js +29 -0
- package/scripts/hooks-system/application/services/guard/GuardNotificationHandler.js +36 -0
- package/scripts/hooks-system/application/services/guard/GuardProcessManager.js +113 -0
- package/scripts/hooks-system/application/services/guard/GuardRecoveryService.js +90 -0
- package/scripts/hooks-system/application/services/guard/__tests__/GuardAutoManagerService.spec.js +77 -0
- package/scripts/hooks-system/application/services/installation/ConfigurationGeneratorService.js +123 -0
- package/scripts/hooks-system/application/services/installation/FileSystemInstallerService.js +112 -0
- package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +166 -0
- package/scripts/hooks-system/application/services/installation/HookInstaller.js +197 -0
- package/scripts/hooks-system/application/services/installation/IdeIntegrationService.js +37 -0
- package/scripts/hooks-system/application/services/installation/InstallService.js +130 -0
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +172 -0
- package/scripts/hooks-system/application/services/installation/PlatformDetectorService.js +36 -0
- package/scripts/hooks-system/application/services/installation/VSCodeTaskConfigurator.js +97 -0
- package/scripts/hooks-system/application/services/logging/UnifiedLogger.js +142 -0
- package/scripts/hooks-system/application/services/logging/__tests__/UnifiedLogger.spec.js +66 -0
- package/scripts/hooks-system/application/services/monitoring/ActivityMonitor.js +80 -0
- package/scripts/hooks-system/application/services/monitoring/AstMonitor.js +140 -0
- package/scripts/hooks-system/application/services/monitoring/DevDocsMonitor.js +85 -0
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +103 -0
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitorService.js +162 -0
- package/scripts/hooks-system/application/services/monitoring/GitTreeMonitor.js +123 -0
- package/scripts/hooks-system/application/services/monitoring/GitTreeMonitorService.js +114 -0
- package/scripts/hooks-system/application/services/monitoring/HealthCheckProviders.js +153 -0
- package/scripts/hooks-system/application/services/monitoring/HealthCheckService.js +118 -0
- package/scripts/hooks-system/application/services/monitoring/HeartbeatMonitorService.js +61 -0
- package/scripts/hooks-system/application/services/monitoring/TokenMonitor.js +60 -0
- package/scripts/hooks-system/application/services/monitoring/__tests__/EvidenceMonitorService.spec.js +107 -0
- package/scripts/hooks-system/application/services/monitoring/__tests__/GitTreeMonitorService.spec.js +27 -0
- package/scripts/hooks-system/application/services/monitoring/__tests__/HealthCheckProviders.spec.js +68 -0
- package/scripts/hooks-system/application/services/monitoring/__tests__/HealthCheckService.spec.js +69 -0
- package/scripts/hooks-system/application/services/monitoring/__tests__/HeartbeatMonitorService.spec.js +35 -0
- package/scripts/hooks-system/application/services/notification/MacNotificationSender.js +106 -0
- package/scripts/hooks-system/application/services/notification/NotificationCenterService.js +221 -0
- package/scripts/hooks-system/application/services/notification/NotificationDispatcher.js +42 -0
- package/scripts/hooks-system/application/services/notification/__tests__/NotificationCenterService.spec.js +40 -0
- package/scripts/hooks-system/application/services/notification/components/NotificationCooldownManager.js +62 -0
- package/scripts/hooks-system/application/services/notification/components/NotificationDeduplicator.js +67 -0
- package/scripts/hooks-system/application/services/notification/components/NotificationQueue.js +36 -0
- package/scripts/hooks-system/application/services/notification/components/NotificationRetryExecutor.js +58 -0
- package/scripts/hooks-system/application/services/platform/PlatformHeuristics.js +144 -0
- package/scripts/hooks-system/application/services/recovery/AutoRecoveryManager.js +137 -0
- package/scripts/hooks-system/application/services/recovery/__tests__/AutoRecoveryManager.spec.js +62 -0
- package/scripts/hooks-system/application/services/smart-commit/CommitMessageSuggester.js +97 -0
- package/scripts/hooks-system/application/services/smart-commit/FileContextGrouper.js +114 -0
- package/scripts/hooks-system/application/services/smart-commit/SmartCommitSummaryBuilder.js +53 -0
- package/scripts/hooks-system/application/services/token/CursorTokenService.js +44 -0
- package/scripts/hooks-system/application/services/token/TokenMetricsService.js +109 -0
- package/scripts/hooks-system/application/services/token/TokenMonitorService.js +160 -0
- package/scripts/hooks-system/application/services/token/TokenStatusReporter.js +56 -0
- package/scripts/hooks-system/application/services/token/__tests__/CursorTokenService.spec.js +69 -0
- package/scripts/hooks-system/application/services/token/__tests__/TokenMonitorService.spec.js +185 -0
- package/scripts/hooks-system/application/state/HookSystemStateMachine.js +59 -0
- package/scripts/hooks-system/application/state/__tests__/HookSystemStateMachine.spec.js +115 -0
- package/scripts/hooks-system/application/use-cases/AnalyzeCodebaseUseCase.js +54 -0
- package/scripts/hooks-system/application/use-cases/AnalyzeStagedFilesUseCase.js +61 -0
- package/scripts/hooks-system/application/use-cases/AutoExecuteAIStartUseCase.js +123 -0
- package/scripts/hooks-system/application/use-cases/BlockCommitUseCase.js +90 -0
- package/scripts/hooks-system/application/use-cases/GenerateAuditReportUseCase.js +184 -0
- package/scripts/hooks-system/application/use-cases/__tests__/AnalyzeCodebaseUseCase.spec.js +156 -0
- package/scripts/hooks-system/application/use-cases/__tests__/AnalyzeStagedFilesUseCase.spec.js +146 -0
- package/scripts/hooks-system/application/use-cases/__tests__/AutoExecuteAIStartUseCase.spec.js +89 -0
- package/scripts/hooks-system/application/use-cases/__tests__/BlockCommitUseCase.spec.js +171 -0
- package/scripts/hooks-system/application/use-cases/__tests__/GenerateAuditReportUseCase.spec.js +207 -0
- package/scripts/hooks-system/bin/__tests__/auto-fix-violations.spec.js +132 -0
- package/scripts/hooks-system/bin/__tests__/auto-restart-guards.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/check-doc-drift.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/check-version.spec.js +240 -0
- package/scripts/hooks-system/bin/__tests__/cli.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/guard-auto-manager.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/guard-supervisor.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/hook-status.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/install.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/nightly-metrics-report.spec.js +94 -0
- package/scripts/hooks-system/bin/__tests__/plan-review.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/predictive-hooks.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/run-ast-adapter.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/run-orchestrator.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/run-playbook.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/setup-eslint.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/violations-api.spec.js +11 -0
- package/scripts/hooks-system/bin/__tests__/watch-hooks.spec.js +11 -0
- package/scripts/hooks-system/bin/ai-commit.sh +63 -0
- package/scripts/hooks-system/bin/audit +463 -0
- package/scripts/hooks-system/bin/audit-library.js +54 -0
- package/scripts/hooks-system/bin/auto-fix-violations.js +130 -0
- package/scripts/hooks-system/bin/auto-restart-guards.js +93 -0
- package/scripts/hooks-system/bin/check-doc-drift.js +35 -0
- package/scripts/hooks-system/bin/check-version.js +201 -0
- package/scripts/hooks-system/bin/cleanup-branches.sh +106 -0
- package/scripts/hooks-system/bin/cli.js +208 -0
- package/scripts/hooks-system/bin/demo-recording.sh +57 -0
- package/scripts/hooks-system/bin/demo-violations +44 -0
- package/scripts/hooks-system/bin/fix-enforcer +27 -0
- package/scripts/hooks-system/bin/fix-gitflow-enforcement.sh +68 -0
- package/scripts/hooks-system/bin/generate-progress-report.sh +129 -0
- package/scripts/hooks-system/bin/git-analyze-pairs.sh +0 -0
- package/scripts/hooks-system/bin/git-leave-branch-check.sh +73 -0
- package/scripts/hooks-system/bin/gitflow +17 -0
- package/scripts/hooks-system/bin/gitflow-shell-integration.sh +64 -0
- package/scripts/hooks-system/bin/guard-auto-manager.js +44 -0
- package/scripts/hooks-system/bin/guard-autostart.sh +158 -0
- package/scripts/hooks-system/bin/guard-env.sh +40 -0
- package/scripts/hooks-system/bin/guard-supervisor.js +516 -0
- package/scripts/hooks-system/bin/hook-status.js +41 -0
- package/scripts/hooks-system/bin/install-git-wrapper.sh +53 -0
- package/scripts/hooks-system/bin/install.js +10 -0
- package/scripts/hooks-system/bin/kill-mcp-zombies.sh +48 -0
- package/scripts/hooks-system/bin/nightly-metrics-report.js +138 -0
- package/scripts/hooks-system/bin/plan-review.js +31 -0
- package/scripts/hooks-system/bin/predictive-hooks.js +18 -0
- package/scripts/hooks-system/bin/pumuki-audit.js +113 -0
- package/scripts/hooks-system/bin/pumuki-init.js +104 -0
- package/scripts/hooks-system/bin/pumuki-mcp.js +74 -0
- package/scripts/hooks-system/bin/pumuki-rules.js +74 -0
- package/scripts/hooks-system/bin/request-no-verify-approval.sh +116 -0
- package/scripts/hooks-system/bin/run-ast-adapter.js +86 -0
- package/scripts/hooks-system/bin/run-intelligent-audit.sh +67 -0
- package/scripts/hooks-system/bin/run-orchestrator.js +27 -0
- package/scripts/hooks-system/bin/run-playbook.js +23 -0
- package/scripts/hooks-system/bin/session-loader.sh +264 -0
- package/scripts/hooks-system/bin/setup-eslint.js +110 -0
- package/scripts/hooks-system/bin/start-guards.sh +190 -0
- package/scripts/hooks-system/bin/sync-autonomous-orchestrator.sh +32 -0
- package/scripts/hooks-system/bin/sync-to-library.sh +46 -0
- package/scripts/hooks-system/bin/update-evidence.sh +1167 -0
- package/scripts/hooks-system/bin/update-session-context.sh +261 -0
- package/scripts/hooks-system/bin/verify-no-verify.sh +68 -0
- package/scripts/hooks-system/bin/violations +20 -0
- package/scripts/hooks-system/bin/violations-api.js +345 -0
- package/scripts/hooks-system/bin/watch-hooks.js +20 -0
- package/scripts/hooks-system/config/project.config.json +36 -0
- package/scripts/hooks-system/config/state-map.json +12 -0
- package/scripts/hooks-system/domain/entities/AuditResult.js +139 -0
- package/scripts/hooks-system/domain/entities/Finding.js +116 -0
- package/scripts/hooks-system/domain/entities/SeverityConfig.js +73 -0
- package/scripts/hooks-system/domain/entities/SeverityConfig.ts +90 -0
- package/scripts/hooks-system/domain/entities/__tests__/AuditResult.spec.js +450 -0
- package/scripts/hooks-system/domain/entities/__tests__/Finding.spec.js +335 -0
- package/scripts/hooks-system/domain/entities/__tests__/SeverityConfig.spec.js +240 -0
- package/scripts/hooks-system/domain/entities/__tests__/entities.spec.js +29 -0
- package/scripts/hooks-system/domain/errors/__tests__/DomainErrors.spec.js +59 -0
- package/scripts/hooks-system/domain/errors/index.js +169 -0
- package/scripts/hooks-system/domain/events/__tests__/DomainEvents.spec.js +60 -0
- package/scripts/hooks-system/domain/events/index.js +121 -0
- package/scripts/hooks-system/domain/ports/IAstPort.js +67 -0
- package/scripts/hooks-system/domain/ports/IEvidencePort.js +86 -0
- package/scripts/hooks-system/domain/ports/IGitCommandPort.js +110 -0
- package/scripts/hooks-system/domain/ports/IGitPort.js +114 -0
- package/scripts/hooks-system/domain/ports/IGitQueryPort.js +93 -0
- package/scripts/hooks-system/domain/ports/INotificationPort.js +35 -0
- package/scripts/hooks-system/domain/ports/__tests__/ports.spec.js +36 -0
- package/scripts/hooks-system/domain/ports/index.js +14 -0
- package/scripts/hooks-system/domain/repositories/ICursorTokenRepository.js +13 -0
- package/scripts/hooks-system/domain/repositories/IFindingsRepository.js +30 -0
- package/scripts/hooks-system/domain/repositories/__tests__/IFindingsRepository.spec.js +18 -0
- package/scripts/hooks-system/domain/rules/CommitBlockingRules.js +142 -0
- package/scripts/hooks-system/domain/rules/__tests__/CommitBlockingRules.spec.js +18 -0
- package/scripts/hooks-system/domain/services/AuditAnalyzer.js +103 -0
- package/scripts/hooks-system/domain/services/AuditFilter.js +26 -0
- package/scripts/hooks-system/domain/services/AuditResultSerializer.js +35 -0
- package/scripts/hooks-system/domain/services/AuditScorer.js +38 -0
- package/scripts/hooks-system/domain/values/Severity.js +93 -0
- package/scripts/hooks-system/index.js +49 -0
- package/scripts/hooks-system/infrastructure/adapters/AstAnalyzerAdapter.js +150 -0
- package/scripts/hooks-system/infrastructure/adapters/FileEvidenceAdapter.js +140 -0
- package/scripts/hooks-system/infrastructure/adapters/GitCliAdapter.js +16 -0
- package/scripts/hooks-system/infrastructure/adapters/GitCommandAdapter.js +68 -0
- package/scripts/hooks-system/infrastructure/adapters/GitHubCliAdapter.js +85 -0
- package/scripts/hooks-system/infrastructure/adapters/GitQueryAdapter.js +58 -0
- package/scripts/hooks-system/infrastructure/adapters/LegacyAnalyzerAdapter.js +61 -0
- package/scripts/hooks-system/infrastructure/adapters/MacOSNotificationAdapter.js +99 -0
- package/scripts/hooks-system/infrastructure/adapters/__tests__/AstAnalyzerAdapter.spec.js +32 -0
- package/scripts/hooks-system/infrastructure/adapters/__tests__/FileEvidenceAdapter.spec.js +31 -0
- package/scripts/hooks-system/infrastructure/adapters/__tests__/GitCliAdapter.spec.js +39 -0
- package/scripts/hooks-system/infrastructure/adapters/__tests__/MacOSNotificationAdapter.spec.js +33 -0
- package/scripts/hooks-system/infrastructure/adapters/git/GitCommandRunner.js +78 -0
- package/scripts/hooks-system/infrastructure/adapters/git/GitCommandService.js +67 -0
- package/scripts/hooks-system/infrastructure/adapters/git/GitQueryService.js +50 -0
- package/scripts/hooks-system/infrastructure/adapters/index.js +14 -0
- package/scripts/hooks-system/infrastructure/ast/README.md +198 -0
- package/scripts/hooks-system/infrastructure/ast/__tests__/ast-core.spec.js +160 -0
- package/scripts/hooks-system/infrastructure/ast/__tests__/ast-intelligence.spec.js +20 -0
- package/scripts/hooks-system/infrastructure/ast/android/__tests__/ast-android.spec.js +33 -0
- package/scripts/hooks-system/infrastructure/ast/android/__tests__/clean-architecture-analyzer.spec.js +96 -0
- package/scripts/hooks-system/infrastructure/ast/android/__tests__/ddd-analyzer.spec.js +113 -0
- package/scripts/hooks-system/infrastructure/ast/android/__tests__/detekt-runner.spec.js +36 -0
- package/scripts/hooks-system/infrastructure/ast/android/__tests__/feature-first-analyzer.spec.js +80 -0
- package/scripts/hooks-system/infrastructure/ast/android/__tests__/native-bridge.spec.js +31 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidASTIntelligentAnalyzer.js +15 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidASTParser.js +157 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidAnalysisOrchestrator.js +164 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidArchitectureDetector.js +334 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidClassAnalyzer.js +162 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidForbiddenLiteralsAnalyzer.js +261 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidSOLIDAnalyzer.js +287 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/__tests__/AndroidForbiddenLiteralsAnalyzer.spec.js +58 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/__tests__/AndroidSOLIDAnalyzer.spec.js +84 -0
- package/scripts/hooks-system/infrastructure/ast/android/ast-android.js +1785 -0
- package/scripts/hooks-system/infrastructure/ast/android/clean-architecture-analyzer.js +115 -0
- package/scripts/hooks-system/infrastructure/ast/android/ddd-analyzer.js +70 -0
- package/scripts/hooks-system/infrastructure/ast/android/detekt-runner.js +81 -0
- package/scripts/hooks-system/infrastructure/ast/android/feature-first-analyzer.js +53 -0
- package/scripts/hooks-system/infrastructure/ast/android/native-bridge.js +119 -0
- package/scripts/hooks-system/infrastructure/ast/archive/README.md +18 -0
- package/scripts/hooks-system/infrastructure/ast/archive/ast-intelligence.ts +276 -0
- package/scripts/hooks-system/infrastructure/ast/archive/ios-rules.js +329 -0
- package/scripts/hooks-system/infrastructure/ast/archive/kotlin-analyzer.js +332 -0
- package/scripts/hooks-system/infrastructure/ast/archive/kotlin-parser.js +303 -0
- package/scripts/hooks-system/infrastructure/ast/archive/swift-analyzer.js +390 -0
- package/scripts/hooks-system/infrastructure/ast/ast-core.js +594 -0
- package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +617 -0
- package/scripts/hooks-system/infrastructure/ast/backend/__tests__/ast-backend.spec.js +20 -0
- package/scripts/hooks-system/infrastructure/ast/backend/__tests__/clean-architecture-analyzer.spec.js +151 -0
- package/scripts/hooks-system/infrastructure/ast/backend/__tests__/ddd-analyzer.spec.js +124 -0
- package/scripts/hooks-system/infrastructure/ast/backend/__tests__/feature-first-analyzer.spec.js +128 -0
- package/scripts/hooks-system/infrastructure/ast/backend/__tests__/forbidden-literals-analyzer.spec.js +95 -0
- package/scripts/hooks-system/infrastructure/ast/backend/__tests__/nestjs-patterns-analyzer.spec.js +59 -0
- package/scripts/hooks-system/infrastructure/ast/backend/__tests__/solid-analyzer.spec.js +114 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendArchitectureDetector.js +141 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendPatternDetector.js +23 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/__tests__/BackendArchitectureDetector.spec.js +239 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/__tests__/BackendPatternDetector.spec.js +58 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/CQRSDetector.js +41 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/CleanArchitectureDetector.js +52 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/FeatureFirstCleanDetector.js +74 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/LayeredArchitectureDetector.js +25 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/MVCDetector.js +32 -0
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/detectors/OnionArchitectureDetector.js +32 -0
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend-clean.js +44 -0
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +2048 -0
- package/scripts/hooks-system/infrastructure/ast/backend/clean-architecture-analyzer.js +142 -0
- package/scripts/hooks-system/infrastructure/ast/backend/ddd-analyzer.js +256 -0
- package/scripts/hooks-system/infrastructure/ast/backend/feature-first-analyzer.js +70 -0
- package/scripts/hooks-system/infrastructure/ast/backend/forbidden-literals-analyzer.js +236 -0
- package/scripts/hooks-system/infrastructure/ast/backend/nestjs-patterns-analyzer.js +11 -0
- package/scripts/hooks-system/infrastructure/ast/backend/solid-analyzer.js +392 -0
- package/scripts/hooks-system/infrastructure/ast/common/BDDTDDWorkflowRules.js +52 -0
- package/scripts/hooks-system/infrastructure/ast/common/__tests__/BDDTDDWorkflowRules.spec.js +133 -0
- package/scripts/hooks-system/infrastructure/ast/common/__tests__/ast-common.spec.js +20 -0
- package/scripts/hooks-system/infrastructure/ast/common/__tests__/documentation-analyzer.spec.js +120 -0
- package/scripts/hooks-system/infrastructure/ast/common/__tests__/images-backend-analyzer.spec.js +123 -0
- package/scripts/hooks-system/infrastructure/ast/common/__tests__/monorepo-health-analyzer.spec.js +118 -0
- package/scripts/hooks-system/infrastructure/ast/common/__tests__/network-resilience-analyzer.spec.js +180 -0
- package/scripts/hooks-system/infrastructure/ast/common/__tests__/offline-backend-analyzer.spec.js +111 -0
- package/scripts/hooks-system/infrastructure/ast/common/__tests__/push-backend-analyzer.spec.js +124 -0
- package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +345 -0
- package/scripts/hooks-system/infrastructure/ast/common/documentation-analyzer.js +217 -0
- package/scripts/hooks-system/infrastructure/ast/common/images-backend-analyzer.js +36 -0
- package/scripts/hooks-system/infrastructure/ast/common/monorepo-health-analyzer.js +452 -0
- package/scripts/hooks-system/infrastructure/ast/common/network-resilience-analyzer.js +178 -0
- package/scripts/hooks-system/infrastructure/ast/common/offline-backend-analyzer.js +53 -0
- package/scripts/hooks-system/infrastructure/ast/common/push-backend-analyzer.js +42 -0
- package/scripts/hooks-system/infrastructure/ast/common/rules/BDDRules.js +87 -0
- package/scripts/hooks-system/infrastructure/ast/common/rules/ImplementationRules.js +83 -0
- package/scripts/hooks-system/infrastructure/ast/common/rules/TDDRules.js +109 -0
- package/scripts/hooks-system/infrastructure/ast/common/rules/WorkflowRules.js +137 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/__tests__/ast-frontend.spec.js +20 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/FrontendArchitectureDetector.js +289 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/FrontendForbiddenLiteralsAnalyzer.js +257 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/FrontendSOLIDAnalyzer.js +274 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/__tests__/FrontendArchitectureDetector.spec.js +151 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/__tests__/FrontendForbiddenLiteralsAnalyzer.spec.js +20 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/__tests__/FrontendSOLIDAnalyzer.spec.js +108 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend-clean.js +42 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend.js +2094 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/clean-architecture-analyzer.js +88 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/ddd-analyzer.js +94 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/feature-first-analyzer.js +51 -0
- package/scripts/hooks-system/infrastructure/ast/ios/__tests__/ast-ios.spec.js +40 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSArchitectureDetector.spec.js +20 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSArchitectureRules.spec.js +61 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSCICDRules.spec.js +10 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSEnterpriseAnalyzer.spec.js +36 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSForbiddenLiteralsAnalyzer.spec.js +64 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSNetworkingAdvancedRules.spec.js +10 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSPerformanceRules.spec.js +34 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSSPMRules.spec.js +10 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSSwiftUIAdvancedRules.spec.js +10 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +894 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureDetector.js +445 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureRules.js +700 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDRules.js +431 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseAnalyzer.js +580 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSForbiddenLiteralsAnalyzer.js +261 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSNetworkingAdvancedRules.js +177 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSPerformanceRules.js +11 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMRules.js +496 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSwiftUIAdvancedRules.js +333 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSTestingAdvancedRules.js +225 -0
- package/scripts/hooks-system/infrastructure/ast/ios/ast-ios.js +2176 -0
- package/scripts/hooks-system/infrastructure/ast/ios/native-bridge.js +92 -0
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenParser.js +471 -0
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/__tests__/SourceKittenParser.spec.js +41 -0
- package/scripts/hooks-system/infrastructure/ast/text/__tests__/text-scanner.spec.js +20 -0
- package/scripts/hooks-system/infrastructure/ast/text/text-scanner.js +1120 -0
- package/scripts/hooks-system/infrastructure/cache/CacheService.js +160 -0
- package/scripts/hooks-system/infrastructure/cli/__tests__/install-wizard.spec.js +16 -0
- package/scripts/hooks-system/infrastructure/cli/install-wizard.js +74 -0
- package/scripts/hooks-system/infrastructure/core/GitOperations.js +50 -0
- package/scripts/hooks-system/infrastructure/core/GitOperations.ts +112 -0
- package/scripts/hooks-system/infrastructure/core/__tests__/GitOperations.spec.js +146 -0
- package/scripts/hooks-system/infrastructure/eslint/eslint-integration.sh +75 -0
- package/scripts/hooks-system/infrastructure/events/EventListeners.js +143 -0
- package/scripts/hooks-system/infrastructure/events/__tests__/events.spec.js +14 -0
- package/scripts/hooks-system/infrastructure/external-tools/GitOperations.js +54 -0
- package/scripts/hooks-system/infrastructure/external-tools/eslint/backend.config.template.mjs +58 -0
- package/scripts/hooks-system/infrastructure/git-hooks/pre-push +35 -0
- package/scripts/hooks-system/infrastructure/git-server/pre-receive-hook +253 -0
- package/scripts/hooks-system/infrastructure/guards/git-wrapper.sh +32 -0
- package/scripts/hooks-system/infrastructure/guards/master-validator.sh +247 -0
- package/scripts/hooks-system/infrastructure/guards/prevent-no-verify.sh +34 -0
- package/scripts/hooks-system/infrastructure/hooks/__tests__/skill-activation-prompt.spec.js +11 -0
- package/scripts/hooks-system/infrastructure/hooks/pre-tool-use-intelligent-enforcer.sh +489 -0
- package/scripts/hooks-system/infrastructure/hooks/skill-activation-prompt.js +244 -0
- package/scripts/hooks-system/infrastructure/logging/UnifiedLoggerFactory.js +40 -0
- package/scripts/hooks-system/infrastructure/logging/__tests__/logging.spec.js +9 -0
- package/scripts/hooks-system/infrastructure/mcp/README.md +116 -0
- package/scripts/hooks-system/infrastructure/mcp/__tests__/ast-intelligence-automation.spec.js +38 -0
- package/scripts/hooks-system/infrastructure/mcp/__tests__/evidence-watcher.spec.js +38 -0
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +1097 -0
- package/scripts/hooks-system/infrastructure/mcp/evidence-watcher.js +128 -0
- package/scripts/hooks-system/infrastructure/mcp/package.json +17 -0
- package/scripts/hooks-system/infrastructure/mcp/services/EvidenceService.js +87 -0
- package/scripts/hooks-system/infrastructure/mcp/services/McpProtocolHandler.js +166 -0
- package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +11 -0
- package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +353 -0
- package/scripts/hooks-system/infrastructure/patterns/pattern-checks.sh +98 -0
- package/scripts/hooks-system/infrastructure/reporting/ReportImpactAnalyzer.js +109 -0
- package/scripts/hooks-system/infrastructure/reporting/ReportMetricsCalculator.js +114 -0
- package/scripts/hooks-system/infrastructure/reporting/ReportPresenter.js +86 -0
- package/scripts/hooks-system/infrastructure/reporting/__tests__/reporting.spec.js +15 -0
- package/scripts/hooks-system/infrastructure/reporting/report-generator.js +130 -0
- package/scripts/hooks-system/infrastructure/reporting/severity-tracker.js +105 -0
- package/scripts/hooks-system/infrastructure/repositories/CursorTokenRepository.js +76 -0
- package/scripts/hooks-system/infrastructure/repositories/FileFindingsRepository.js +88 -0
- package/scripts/hooks-system/infrastructure/repositories/__tests__/repositories.spec.js +20 -0
- package/scripts/hooks-system/infrastructure/repositories/datasources/CursorApiDataSource.js +73 -0
- package/scripts/hooks-system/infrastructure/repositories/datasources/CursorFileDataSource.js +55 -0
- package/scripts/hooks-system/infrastructure/severity/__tests__/severity-evaluator.spec.js +18 -0
- package/scripts/hooks-system/infrastructure/severity/analyzers/__tests__/maintainability-analyzer.spec.js +170 -0
- package/scripts/hooks-system/infrastructure/severity/analyzers/__tests__/performance-analyzer.spec.js +186 -0
- package/scripts/hooks-system/infrastructure/severity/analyzers/__tests__/security-analyzer.spec.js +151 -0
- package/scripts/hooks-system/infrastructure/severity/analyzers/__tests__/stability-analyzer.spec.js +143 -0
- package/scripts/hooks-system/infrastructure/severity/analyzers/maintainability-analyzer.js +100 -0
- package/scripts/hooks-system/infrastructure/severity/analyzers/performance-analyzer.js +109 -0
- package/scripts/hooks-system/infrastructure/severity/analyzers/security-analyzer.js +104 -0
- package/scripts/hooks-system/infrastructure/severity/analyzers/stability-analyzer.js +85 -0
- package/scripts/hooks-system/infrastructure/severity/context/analyzers/CodeClassificationAnalyzer.js +71 -0
- package/scripts/hooks-system/infrastructure/severity/context/analyzers/DataAnalyzer.js +64 -0
- package/scripts/hooks-system/infrastructure/severity/context/analyzers/ImpactAnalyzer.js +68 -0
- package/scripts/hooks-system/infrastructure/severity/context/analyzers/SafetyAnalyzer.js +82 -0
- package/scripts/hooks-system/infrastructure/severity/context/context-builder.js +88 -0
- package/scripts/hooks-system/infrastructure/severity/generators/RecommendationGenerator.js +153 -0
- package/scripts/hooks-system/infrastructure/severity/mappers/SeverityMapper.js +10 -0
- package/scripts/hooks-system/infrastructure/severity/policies/gate-policies.js +136 -0
- package/scripts/hooks-system/infrastructure/severity/policies/severity-policies.json +206 -0
- package/scripts/hooks-system/infrastructure/severity/scorers/ContextMultiplier.js +49 -0
- package/scripts/hooks-system/infrastructure/severity/severity-evaluator.js +117 -0
- package/scripts/hooks-system/infrastructure/shell/core/constants.sh +26 -0
- package/scripts/hooks-system/infrastructure/shell/core/utils.sh +45 -0
- package/scripts/hooks-system/infrastructure/shell/gitflow/git-wrapper.sh +646 -0
- package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +620 -0
- package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-state-manager.sh +235 -0
- package/scripts/hooks-system/infrastructure/shell/gitflow-state-manager.sh +225 -0
- package/scripts/hooks-system/infrastructure/shell/orchestrators/audit-orchestrator.sh +1106 -0
- package/scripts/hooks-system/infrastructure/shell/security/detect-secrets.sh +26 -0
- package/scripts/hooks-system/infrastructure/shell/security/detect_secrets.py +182 -0
- package/scripts/hooks-system/infrastructure/shell/validate-clean-architecture.sh +254 -0
- package/scripts/hooks-system/infrastructure/shell/validators/check-doc-structure.sh +62 -0
- package/scripts/hooks-system/infrastructure/shell/validators/ensure-critical-docs.sh +26 -0
- package/scripts/hooks-system/infrastructure/shell/validators/validate-ai-protocol.sh +474 -0
- package/scripts/hooks-system/infrastructure/shell/validators/validate-clean-architecture.sh +303 -0
- package/scripts/hooks-system/infrastructure/shell/validators/validate-conventional-commit.sh +42 -0
- package/scripts/hooks-system/infrastructure/storage/file-operations.sh +31 -0
- package/scripts/hooks-system/infrastructure/telemetry/TelemetryService.js +165 -0
- package/scripts/hooks-system/infrastructure/telemetry/__tests__/telemetry.spec.js +15 -0
- package/scripts/hooks-system/infrastructure/telemetry/metrics-logger.js +66 -0
- package/scripts/hooks-system/infrastructure/telemetry/metrics-server.js +61 -0
- package/scripts/hooks-system/infrastructure/utils/__tests__/utils.spec.js +8 -0
- package/scripts/hooks-system/infrastructure/utils/error-utils.js +28 -0
- package/scripts/hooks-system/infrastructure/utils/timestamp-helper.sh +106 -0
- package/scripts/hooks-system/infrastructure/utils/token-manager.js +121 -0
- package/scripts/hooks-system/infrastructure/validators/__tests__/detect-commit-language.spec.js +16 -0
- package/scripts/hooks-system/infrastructure/validators/__tests__/enforce-english-literals.spec.js +67 -0
- package/scripts/hooks-system/infrastructure/validators/detect-commit-language.js +145 -0
- package/scripts/hooks-system/infrastructure/validators/enforce-english-literals.js +202 -0
- package/scripts/hooks-system/infrastructure/watchdog/__tests__/.audit-reports/token-monitor.log +18 -0
- package/scripts/hooks-system/infrastructure/watchdog/__tests__/auto-recovery.spec.js +14 -0
- package/scripts/hooks-system/infrastructure/watchdog/__tests__/token-monitor.spec.js +67 -0
- package/scripts/hooks-system/infrastructure/watchdog/__tests__/watchdog.spec.js +22 -0
- package/scripts/hooks-system/infrastructure/watchdog/ai-watchdog.sh +278 -0
- package/scripts/hooks-system/infrastructure/watchdog/auto-recovery.js +32 -0
- package/scripts/hooks-system/infrastructure/watchdog/health-check.js +58 -0
- package/scripts/hooks-system/infrastructure/watchdog/token-monitor-loop.sh +20 -0
- package/scripts/hooks-system/infrastructure/watchdog/token-monitor.js +69 -0
- package/scripts/hooks-system/infrastructure/watchdog/token-tracker.sh +208 -0
- package/scripts/hooks-system/presentation/cli/audit.sh +32 -0
- package/scripts/hooks-system/presentation/cli/autonomous-status.sh +92 -0
- package/scripts/hooks-system/presentation/cli/categorize-violations.sh +179 -0
- package/scripts/hooks-system/presentation/cli/direct-audit-option2.sh +23 -0
- package/scripts/hooks-system/presentation/cli/direct-audit.sh +33 -0
- package/skills/android-guidelines/SKILL.md +475 -0
- package/skills/android-guidelines/resources/advanced-topics.md +44 -0
- package/skills/android-guidelines/resources/architecture-overview.md +44 -0
- package/skills/backend-guidelines/SKILL.md +335 -0
- package/skills/backend-guidelines/resources/architecture-overview.md +48 -0
- package/skills/frontend-guidelines/SKILL.md +367 -0
- package/skills/frontend-guidelines/resources/architecture-overview.md +44 -0
- package/skills/ios-guidelines/SKILL.md +406 -0
- package/skills/ios-guidelines/resources/architecture-overview.md +47 -0
- package/skills/skill-rules.json +334 -0
|
@@ -0,0 +1,1785 @@
|
|
|
1
|
+
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const glob = require('glob');
|
|
4
|
+
const { pushFinding, mapToLevel, SyntaxKind, platformOf } = require(path.join(__dirname, '../ast-core'));
|
|
5
|
+
const { analyzeAndroidFiles: runDetektAnalysis } = require(path.join(__dirname, './detekt-runner'));
|
|
6
|
+
const { AndroidSOLIDAnalyzer } = require(path.join(__dirname, 'analyzers/AndroidSOLIDAnalyzer'));
|
|
7
|
+
const { AndroidForbiddenLiteralsAnalyzer } = require(path.join(__dirname, 'analyzers/AndroidForbiddenLiteralsAnalyzer'));
|
|
8
|
+
const { AndroidASTIntelligentAnalyzer } = require(path.join(__dirname, 'analyzers/AndroidASTIntelligentAnalyzer'));
|
|
9
|
+
const { AndroidArchitectureDetector } = require(path.join(__dirname, 'analyzers/AndroidArchitectureDetector'));
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run Android-specific AST intelligence analysis
|
|
13
|
+
* @param {Project} project - TypeScript morph project
|
|
14
|
+
* @param {Array} findings - Findings array to populate
|
|
15
|
+
* @param {string} platform - Platform identifier
|
|
16
|
+
*/
|
|
17
|
+
function runAndroidIntelligence(project, findings, platform) {
|
|
18
|
+
console.error(`[Android AST Intelligence] Running Kotlin AST analysis...`);
|
|
19
|
+
const astAnalyzer = new AndroidASTIntelligentAnalyzer(findings);
|
|
20
|
+
const { getRepoRoot } = require(path.join(__dirname, '../ast-core'));
|
|
21
|
+
const root = getRepoRoot();
|
|
22
|
+
const kotlinFiles = glob.sync('**/*.kt', {
|
|
23
|
+
cwd: root,
|
|
24
|
+
ignore: ['**/node_modules/**', '**/build/**', '**/.gradle/**'],
|
|
25
|
+
absolute: true,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
for (const kotlinFile of kotlinFiles) {
|
|
29
|
+
astAnalyzer.analyzeFile(kotlinFile);
|
|
30
|
+
}
|
|
31
|
+
console.error(`[Android AST Intelligence] Analyzed ${kotlinFiles.length} Kotlin files with AST`);
|
|
32
|
+
|
|
33
|
+
if (kotlinFiles.length > 0) {
|
|
34
|
+
try {
|
|
35
|
+
const architectureDetector = new AndroidArchitectureDetector(root);
|
|
36
|
+
const detectedPattern = architectureDetector.detect();
|
|
37
|
+
const detectionSummary = architectureDetector.getDetectionSummary();
|
|
38
|
+
|
|
39
|
+
console.error(`[Android Architecture] Pattern detected: ${detectedPattern} (confidence: ${detectionSummary.confidence}%)`);
|
|
40
|
+
|
|
41
|
+
if (detectionSummary.warnings.length > 0) {
|
|
42
|
+
detectionSummary.warnings.forEach(warning => {
|
|
43
|
+
pushFinding('android.architecture.detection_warning', warning.severity.toLowerCase(), null, null, warning.message + '\n\n' + warning.recommendation, findings);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('[Android Architecture] Error during architecture detection:', error.message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
project.getSourceFiles().forEach((sf) => {
|
|
52
|
+
if (!sf || typeof sf.getFilePath !== 'function') return;
|
|
53
|
+
const filePath = sf.getFilePath();
|
|
54
|
+
|
|
55
|
+
if (platformOf(filePath) !== "android") return;
|
|
56
|
+
|
|
57
|
+
if (/\/ast-[^/]+\.js$/.test(filePath)) return;
|
|
58
|
+
if (/infrastructure\/ast\/|analyzers\/|detectors\//.test(filePath)) return;
|
|
59
|
+
|
|
60
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
61
|
+
const text = call.getExpression().getText();
|
|
62
|
+
|
|
63
|
+
if (text.includes("password") || text.includes("token") || text.includes("secret") || text.includes("key") || text.includes("api_key")) {
|
|
64
|
+
if (!sf.getFullText().includes("BuildConfig") && !sf.getFullText().includes("EncryptedSharedPreferences") && !sf.getFullText().includes("Keystore")) {
|
|
65
|
+
pushFinding("android.hardcoded_secrets", "critical", sf, call, "Hardcoded sensitive data - use EncryptedSharedPreferences, Keystore, or BuildConfig", findings);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (text.includes("!!")) {
|
|
70
|
+
pushFinding("android.force_unwrapping", "critical", sf, call, "Force unwrapping (!) detected - use safe calls or null checks", findings);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (filePath.endsWith(".java") && !filePath.includes("legacy") && !filePath.includes("old")) {
|
|
74
|
+
pushFinding("android.java_code", "critical", sf, sf, "Java file detected - use Kotlin for new development", findings);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (filePath.includes("layout") && filePath.endsWith(".xml")) {
|
|
78
|
+
pushFinding("android.xml_layouts", "critical", sf, sf, "XML layout detected - use Jetpack Compose for new UIs", findings);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (text.includes("Singleton") || text.includes("INSTANCE") || text.includes("getInstance()")) {
|
|
82
|
+
pushFinding("android.singletons", "critical", sf, call, "Singleton pattern detected - use dependency injection instead", findings);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (text.includes("this@") || text.includes("context") && (text.includes("static") || text.includes("companion"))) {
|
|
86
|
+
pushFinding("android.context_leaks", "critical", sf, call, "Potential context leak - avoid storing context in static fields", findings);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (filePath.endsWith(".kt")) {
|
|
90
|
+
// Null safety
|
|
91
|
+
if (text.includes("Any?") || text.includes("Any!") || text.includes("as?")) {
|
|
92
|
+
pushFinding("android.missing_null_safety", "high", sf, call, "Unsafe null handling - leverage Kotlin's null safety", findings);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (text.includes("@Composable") && !sf.getFullText().includes("@Preview")) {
|
|
96
|
+
const hasPreview = sf.getDescendantsOfKind(SyntaxKind.Decorator).some(d =>
|
|
97
|
+
d.expression?.expression?.escapedText === "Preview"
|
|
98
|
+
);
|
|
99
|
+
if (!hasPreview) {
|
|
100
|
+
pushFinding("android.missing_composable", "high", sf, call, "Composable without @Preview - add previews for better development", findings);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (text.includes("@Composable") && (text.includes("LaunchedEffect") || text.includes("DisposableEffect"))) {
|
|
105
|
+
if (!text.includes("key") || text.includes("Unit")) {
|
|
106
|
+
pushFinding("android.side_effects_composable", "medium", sf, call, "Composable with side effects - specify proper keys for LaunchedEffect/DisposableEffect", findings);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Missing entity
|
|
111
|
+
if (text.includes("@Entity") && !sf.getFullText().includes("data class")) {
|
|
112
|
+
pushFinding("android.missing_entity", "medium", sf, call, "Entity not using data class - Room entities should be data classes", findings);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (text.includes("ViewModel") && !sf.getFullText().includes("androidx.lifecycle.ViewModel")) {
|
|
116
|
+
pushFinding("android.missing_viewmodel", "high", sf, call, "Custom ViewModel without extending androidx.lifecycle.ViewModel", findings);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Missing JUnit5
|
|
120
|
+
if (filePath.includes("test") && !sf.getFullText().includes("org.junit.jupiter")) {
|
|
121
|
+
pushFinding("android.missing_junit5", "high", sf, sf, "Test file not using JUnit5 - prefer JUnit5 over JUnit4", findings);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (text.includes("findViewById")) {
|
|
126
|
+
pushFinding("android.findviewbyid", "high", sf, call, "findViewById usage - use View Binding or Compose instead", findings);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (text.includes("AsyncTask")) {
|
|
130
|
+
pushFinding("android.asynctask", "high", sf, call, "AsyncTask usage - deprecated, use Coroutines instead", findings);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (text.includes("startActivity") && !text.includes("Intent")) {
|
|
134
|
+
pushFinding("android.startactivity", "medium", sf, call, "Direct activity navigation - consider Navigation Component", findings);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (text.includes("SharedPreferences") && !text.includes("Encrypted")) {
|
|
138
|
+
pushFinding("android.sharedpreferences", "medium", sf, call, "Plain SharedPreferences - use EncryptedSharedPreferences for sensitive data", findings);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (text.includes("Handler") && text.includes("postDelayed")) {
|
|
142
|
+
pushFinding("android.handler_leak", "medium", sf, call, "Handler potential leak - use weak references or ViewModel scope", findings);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (text.includes("Thread") || text.includes("Runnable")) {
|
|
146
|
+
pushFinding("android.raw_threads", "medium", sf, call, "Raw threads - prefer Coroutines for concurrency", findings);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (text.includes("LiveData") && !text.includes("observe")) {
|
|
150
|
+
pushFinding("android.livedata_observe", "low", sf, call, "LiveData not observed - ensure LiveData is observed in UI", findings);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (text.includes("Flow") && !text.includes("collect")) {
|
|
154
|
+
pushFinding("android.flow_collect", "low", sf, call, "Flow not collected - Flows need to be collected to emit values", findings);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (text.includes("suspend") && !text.includes("viewModelScope") && !text.includes("lifecycleScope")) {
|
|
158
|
+
pushFinding("android.suspend_scope", "medium", sf, call, "Suspend function without proper scope - use viewModelScope or lifecycleScope", findings);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (text.includes("MutableLiveData") && text.includes("public")) {
|
|
162
|
+
pushFinding("android.mutable_exposure", "medium", sf, call, "Mutable LiveData exposed publicly - expose only immutable LiveData", findings);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (text.includes("data class") && text.includes("var")) {
|
|
166
|
+
pushFinding("android.data_class_var", "low", sf, call, "Data class with var properties - prefer val for immutable data", findings);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (text.includes("sealed class") && !text.includes("when")) {
|
|
170
|
+
pushFinding("android.sealed_when", "low", sf, call, "Sealed class without exhaustive when - use when for sealed classes", findings);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (text.includes("enum class") && !text.includes("companion object")) {
|
|
174
|
+
pushFinding("android.enum_companion", "low", sf, call, "Enum without companion object - consider adding utility functions", findings);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (text.includes("inline") && text.includes("reified")) {
|
|
178
|
+
pushFinding("android.inline_reified", "low", sf, call, "Inline reified function - good for type-safe generics", findings);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (text.includes("@SuppressLint")) {
|
|
182
|
+
pushFinding("android.suppress_lint", "medium", sf, call, "@SuppressLint usage - avoid suppressing lint warnings, fix the issues instead", findings);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (text.includes("lateinit") && !text.includes("private")) {
|
|
186
|
+
pushFinding("android.lateinit_visibility", "medium", sf, call, "Public lateinit property - prefer private lateinit with public getter", findings);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (text.includes("by lazy") && text.includes("public")) {
|
|
190
|
+
pushFinding("android.lazy_public", "low", sf, call, "Public lazy property - ensure thread safety if accessed from multiple threads", findings);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (text.includes("companion object") && text.includes("const val")) {
|
|
194
|
+
pushFinding("android.companion_const", "low", sf, call, "Companion object constants - good practice for static constants", findings);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (text.includes("operator fun")) {
|
|
198
|
+
pushFinding("android.operator_overload", "low", sf, call, "Operator overloading - use judiciously for clarity", findings);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (text.includes("init") && text.includes("block")) {
|
|
202
|
+
pushFinding("android.init_block", "low", sf, call, "Init block usage - prefer primary constructor for simple initialization", findings);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (text.includes("typealias")) {
|
|
206
|
+
pushFinding("android.typealias", "low", sf, call, "Typealias usage - good for simplifying complex types", findings);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (text.includes("@Parcelize")) {
|
|
210
|
+
pushFinding("android.parcelize", "low", sf, call, "Parcelize usage - good for passing data between components", findings);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (text.includes("remember") && !text.includes("key")) {
|
|
214
|
+
pushFinding("android.remember_key", "medium", sf, call, "Remember without key - provide keys for proper recomposition", findings);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (text.includes("derivedStateOf") && !text.includes("remember")) {
|
|
218
|
+
pushFinding("android.derived_state", "low", sf, call, "Derived state - good for computed values from state", findings);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (text.includes("LaunchedEffect") && text.includes("Unit")) {
|
|
222
|
+
pushFinding("android.launched_effect", "medium", sf, call, "LaunchedEffect with Unit key - may restart unnecessarily", findings);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (text.includes("DisposableEffect") && !text.includes("onDispose")) {
|
|
226
|
+
pushFinding("android.disposable_effect", "medium", sf, call, "DisposableEffect without onDispose - add cleanup logic", findings);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (text.includes("Modifier") && text.includes("then")) {
|
|
230
|
+
pushFinding("android.modifier_chain", "low", sf, call, "Modifier chaining - good practice for composable styling", findings);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (text.includes("@Preview") && !text.includes("name")) {
|
|
234
|
+
pushFinding("android.preview_name", "low", sf, call, "Preview without name - add descriptive names for better organization", findings);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (text.includes("Scaffold") && !text.includes("content")) {
|
|
238
|
+
pushFinding("android.scaffold_usage", "low", sf, call, "Scaffold usage - good for Material Design structure", findings);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (text.includes("Column") || text.includes("Row")) {
|
|
242
|
+
pushFinding("android.layout_composables", "low", sf, call, "Layout composables - good for structuring UI", findings);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (text.includes("LazyColumn") || text.includes("LazyRow")) {
|
|
246
|
+
pushFinding("android.lazy_lists", "low", sf, call, "Lazy lists - good for performance with large datasets", findings);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (text.includes("ViewModel") && text.includes("factory")) {
|
|
250
|
+
pushFinding("android.viewmodel_factory", "low", sf, call, "ViewModel factory - good for dependency injection in ViewModels", findings);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (text.includes("HiltViewModel")) {
|
|
254
|
+
pushFinding("android.hilt_viewmodel", "low", sf, call, "Hilt ViewModel - good for DI in ViewModels", findings);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (text.includes("@Inject")) {
|
|
258
|
+
pushFinding("android.hilt_injection", "low", sf, call, "Hilt injection - good dependency injection practice", findings);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (text.includes("@Module")) {
|
|
262
|
+
pushFinding("android.hilt_module", "low", sf, call, "Hilt module - good for providing dependencies", findings);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (text.includes("@Provides")) {
|
|
266
|
+
pushFinding("android.provides", "low", sf, call, "@Provides - good for providing third-party or interface implementations", findings);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (text.includes("@Binds")) {
|
|
270
|
+
pushFinding("android.binds", "low", sf, call, "@Binds - good for binding interfaces to implementations", findings);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (text.includes("@Singleton")) {
|
|
274
|
+
pushFinding("android.singleton_scope", "low", sf, call, "Singleton scope - appropriate for application-wide dependencies", findings);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (text.includes("@ViewModelScoped")) {
|
|
278
|
+
pushFinding("android.viewmodel_scoped", "low", sf, call, "ViewModel scoped - good for ViewModel-specific dependencies", findings);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (text.includes("Retrofit")) {
|
|
282
|
+
pushFinding("android.retrofit_usage", "low", sf, call, "Retrofit usage - good REST client", findings);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (text.includes("suspend") && text.includes("Retrofit")) {
|
|
286
|
+
pushFinding("android.suspend_functions", "low", sf, call, "Suspend functions with Retrofit - good for async networking", findings);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (text.includes("OkHttp")) {
|
|
290
|
+
pushFinding("android.okhttp", "low", sf, call, "OkHttp usage - good HTTP client with interceptors", findings);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (text.includes("Moshi") || text.includes("Gson")) {
|
|
294
|
+
pushFinding("android.json_serialization", "low", sf, call, "JSON serialization - good for parsing API responses", findings);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (text.includes("Room")) {
|
|
298
|
+
pushFinding("android.room_usage", "low", sf, call, "Room usage - good type-safe SQLite wrapper", findings);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (text.includes("@Dao")) {
|
|
302
|
+
pushFinding("android.dao_pattern", "low", sf, call, "DAO pattern - good for data access abstraction", findings);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (text.includes("@Query") || text.includes("@Insert") || text.includes("@Update") || text.includes("@Delete")) {
|
|
306
|
+
pushFinding("android.room_annotations", "low", sf, call, "Room annotations - good for defining database operations", findings);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (text.includes("Flow") && text.includes("Room")) {
|
|
310
|
+
pushFinding("android.room_flow", "low", sf, call, "Room with Flow - good for reactive database queries", findings);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (text.includes("WorkManager")) {
|
|
314
|
+
pushFinding("android.work_manager", "low", sf, call, "WorkManager usage - good for background tasks", findings);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (text.includes("StateFlow") || text.includes("SharedFlow")) {
|
|
318
|
+
pushFinding("android.state_flow", "low", sf, call, "State/SharedFlow usage - good for reactive state management", findings);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (text.includes("collectAsState")) {
|
|
322
|
+
pushFinding("android.collect_state", "low", sf, call, "collectAsState - good for observing flows in Compose", findings);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (text.includes("stateIn")) {
|
|
326
|
+
pushFinding("android.state_in", "low", sf, call, "stateIn operator - good for converting cold flows to hot StateFlows", findings);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (text.includes("combine")) {
|
|
330
|
+
pushFinding("android.combine", "low", sf, call, "Combine operator - good for combining multiple flows", findings);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (text.includes("flatMapLatest")) {
|
|
334
|
+
pushFinding("android.flatmap_latest", "low", sf, call, "flatMapLatest - good for switching between flows", findings);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (text.includes("JUnit5") || text.includes("jupiter")) {
|
|
338
|
+
pushFinding("android.junit5", "low", sf, call, "JUnit5 usage - good modern testing framework", findings);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (text.includes("MockK")) {
|
|
342
|
+
pushFinding("android.mockk", "low", sf, call, "MockK usage - good mocking library for Kotlin", findings);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (text.includes("Turbine")) {
|
|
346
|
+
pushFinding("android.turbine", "low", sf, call, "Turbine - good for testing flows", findings);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (text.includes("ComposeTestRule")) {
|
|
350
|
+
pushFinding("android.compose_testing", "low", sf, call, "Compose testing - good for UI testing", findings);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (text.includes("Robolectric")) {
|
|
354
|
+
pushFinding("android.robolectric", "low", sf, call, "Robolectric - good for testing Android framework code", findings);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (text.includes("Truth")) {
|
|
358
|
+
pushFinding("android.truth_assertions", "low", sf, call, "Truth assertions - good for readable test assertions", findings);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (text.includes("Coil")) {
|
|
362
|
+
pushFinding("android.coil", "low", sf, call, "Coil usage - good async image loading for Compose", findings);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (text.includes("Paging")) {
|
|
366
|
+
pushFinding("android.paging", "low", sf, call, "Paging library - good for handling large datasets", findings);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (text.includes("Navigation")) {
|
|
370
|
+
pushFinding("android.navigation", "low", sf, call, "Navigation component - good for app navigation", findings);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (text.includes("DataStore")) {
|
|
374
|
+
pushFinding("android.datastore", "low", sf, call, "DataStore - good modern replacement for SharedPreferences", findings);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (text.includes("BiometricPrompt")) {
|
|
378
|
+
pushFinding("android.biometric", "low", sf, call, "Biometric authentication - good for secure user authentication", findings);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (text.includes("SafetyNet")) {
|
|
382
|
+
pushFinding("android.safetynet", "low", sf, call, "SafetyNet - good for device integrity checks", findings);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (text.includes("LeakCanary")) {
|
|
386
|
+
pushFinding("android.leakcanary", "low", sf, call, "LeakCanary - good for detecting memory leaks", findings);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (text.includes("BaselineProfile")) {
|
|
390
|
+
pushFinding("android.baseline_profile", "low", sf, call, "Baseline profiles - good for app startup optimization", findings);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (text.includes("Kotlin DSL")) {
|
|
394
|
+
pushFinding("android.kotlin_dsl", "low", sf, call, "Kotlin DSL for Gradle - good for type-safe build scripts", findings);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (text.includes("version catalogs")) {
|
|
398
|
+
pushFinding("android.version_catalogs", "low", sf, call, "Version catalogs - good for centralized dependency management", findings);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (text.includes("buildSrc")) {
|
|
402
|
+
pushFinding("android.buildsrc", "low", sf, call, "buildSrc - good for shared build logic", findings);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (text.includes("product flavors")) {
|
|
406
|
+
pushFinding("android.product_flavors", "low", sf, call, "Product flavors - good for different app variants", findings);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (text.includes("build variants")) {
|
|
410
|
+
pushFinding("android.build_variants", "low", sf, call, "Build variants - good for different build configurations", findings);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (text.includes("Detekt")) {
|
|
414
|
+
pushFinding("android.detekt", "low", sf, call, "Detekt - good static analysis for Kotlin", findings);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (text.includes("Firebase App Distribution")) {
|
|
418
|
+
pushFinding("android.firebase_distribution", "low", sf, call, "Firebase App Distribution - good for beta testing", findings);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (text.includes("Play Console")) {
|
|
422
|
+
pushFinding("android.play_console", "low", sf, call, "Play Console - good for production deployments", findings);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (text.includes("Timber")) {
|
|
426
|
+
pushFinding("android.timber", "low", sf, call, "Timber - good logging library", findings);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (text.includes("Crashlytics")) {
|
|
430
|
+
pushFinding("android.crashlytics", "low", sf, call, "Crashlytics - good for crash reporting", findings);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (text.includes("Analytics")) {
|
|
434
|
+
pushFinding("android.analytics", "low", sf, call, "Analytics - good for user behavior tracking", findings);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (text.includes("strings.xml")) {
|
|
438
|
+
pushFinding("android.strings_xml", "low", sf, call, "strings.xml usage - good for localization", findings);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (text.includes("plurals")) {
|
|
442
|
+
pushFinding("android.plurals", "low", sf, call, "Plurals support - good for proper pluralization", findings);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (text.includes("DateFormat")) {
|
|
446
|
+
pushFinding("android.date_format", "low", sf, call, "DateFormat - good for localized date formatting", findings);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (text.includes("NumberFormat")) {
|
|
450
|
+
pushFinding("android.number_format", "low", sf, call, "NumberFormat - good for localized number formatting", findings);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (text.includes("TalkBack")) {
|
|
454
|
+
pushFinding("android.talkback", "low", sf, call, "TalkBack support - good for accessibility", findings);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (text.includes("contentDescription")) {
|
|
458
|
+
pushFinding("android.content_description", "low", sf, call, "Content description - good for screen reader support", findings);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (text.includes("semantics")) {
|
|
462
|
+
pushFinding("android.semantics", "low", sf, call, "Semantics modifier - good for accessibility in Compose", findings);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (text.includes("DTO")) {
|
|
466
|
+
pushFinding("android.dto_sharing", "low", sf, call, "DTO classes - consider sharing with backend via code generation", findings);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (text.includes("Repository")) {
|
|
470
|
+
pushFinding("android.repository_pattern", "low", sf, call, "Repository pattern - good for data abstraction", findings);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (text.includes("UseCase")) {
|
|
474
|
+
pushFinding("android.use_case", "low", sf, call, "Use case pattern - good for business logic encapsulation", findings);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (text.includes("ViewModel")) {
|
|
478
|
+
pushFinding("android.viewmodel_pattern", "low", sf, call, "ViewModel pattern - good for UI state management", findings);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
project.getSourceFiles().forEach((sf) => {
|
|
484
|
+
if (!sf || typeof sf.getFilePath !== 'function') return;
|
|
485
|
+
const filePath = sf.getFilePath();
|
|
486
|
+
if (platformOf(filePath) !== "android" || !filePath.endsWith('.kt')) return;
|
|
487
|
+
|
|
488
|
+
const content = sf.getFullText();
|
|
489
|
+
|
|
490
|
+
const emptyCatchPattern = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
491
|
+
let match;
|
|
492
|
+
while ((match = emptyCatchPattern.exec(content)) !== null) {
|
|
493
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
494
|
+
pushFinding(
|
|
495
|
+
"android.error_handling.empty_catch",
|
|
496
|
+
"high",
|
|
497
|
+
sf,
|
|
498
|
+
sf,
|
|
499
|
+
`Line ${lineNumber}: Empty catch block - handle exception or use specific exception types`,
|
|
500
|
+
findings
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const genericExceptionPattern = /catch\s*\(\s*\w+\s*:\s*Exception\s*\)/g;
|
|
505
|
+
while ((match = genericExceptionPattern.exec(content)) !== null) {
|
|
506
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
507
|
+
const hasSpecific = content.includes('IOException') || content.includes('NetworkException') ||
|
|
508
|
+
content.includes('HttpException') || content.includes('SQLException');
|
|
509
|
+
if (!hasSpecific) {
|
|
510
|
+
pushFinding(
|
|
511
|
+
"android.error_handling.generic_exception",
|
|
512
|
+
"medium",
|
|
513
|
+
sf,
|
|
514
|
+
sf,
|
|
515
|
+
`Line ${lineNumber}: Catch generic Exception - prefer specific exceptions (IOException, HttpException, etc.)`,
|
|
516
|
+
findings
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const forceUnwrapPattern = /!!/g;
|
|
522
|
+
while ((match = forceUnwrapPattern.exec(content)) !== null) {
|
|
523
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
524
|
+
pushFinding(
|
|
525
|
+
"android.error_handling.force_unwrap",
|
|
526
|
+
"high",
|
|
527
|
+
sf,
|
|
528
|
+
sf,
|
|
529
|
+
`Line ${lineNumber}: NEVER use '!!' force unwrap - use safe calls (?.) or requireNotNull() with message`,
|
|
530
|
+
findings
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const anyTypePattern = /:\s*Any\b/g;
|
|
535
|
+
while ((match = anyTypePattern.exec(content)) !== null) {
|
|
536
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
537
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
538
|
+
const varName = lineText.match(/\b(\w+)\s*:\s*Any\b/)?.[1];
|
|
539
|
+
if (varName) {
|
|
540
|
+
const subsequentLines = content.split('\n').slice(lineNumber, lineNumber + 10).join('\n');
|
|
541
|
+
const hasTypeCheck = new RegExp(`${varName}\\s+is\\s+|${varName}\\s+as\\?|when\\s*\\(\\s*${varName}\\s*\\)`).test(subsequentLines);
|
|
542
|
+
if (!hasTypeCheck) {
|
|
543
|
+
pushFinding(
|
|
544
|
+
"android.typescript.any_without_guard",
|
|
545
|
+
"high",
|
|
546
|
+
sf,
|
|
547
|
+
sf,
|
|
548
|
+
`Line ${lineNumber}: Variable '${varName}: Any' used without type checking - use 'is', 'as?', or 'when' guards`,
|
|
549
|
+
findings
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ==========================================
|
|
556
|
+
// ==========================================
|
|
557
|
+
|
|
558
|
+
// 1. JETPACK COMPOSE
|
|
559
|
+
|
|
560
|
+
if (content.includes('findViewById')) {
|
|
561
|
+
const lineNumber = content.indexOf('findViewById') ? content.substring(0, content.indexOf('findViewById')).split('\n').length : 1;
|
|
562
|
+
pushFinding(
|
|
563
|
+
"android.compose.findviewbyid",
|
|
564
|
+
"high",
|
|
565
|
+
sf,
|
|
566
|
+
sf,
|
|
567
|
+
`Line ${lineNumber}: findViewById detected - use Jetpack Compose for new UI`,
|
|
568
|
+
findings
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const composableFunctionPattern = /fun\s+([A-Z]\w+)\s*\([^)]*\)\s*\{[^}]*\b(Text|Button|Column|Row|Box|Card|LazyColumn)\b/g;
|
|
573
|
+
while ((match = composableFunctionPattern.exec(content)) !== null) {
|
|
574
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
575
|
+
const functionBlock = content.substring(match.index - 50, match.index);
|
|
576
|
+
if (!functionBlock.includes('@Composable')) {
|
|
577
|
+
pushFinding(
|
|
578
|
+
"android.compose.missing_annotation",
|
|
579
|
+
"high",
|
|
580
|
+
sf,
|
|
581
|
+
sf,
|
|
582
|
+
`Line ${lineNumber}: Function '${match[1]}' renders Compose UI - add @Composable annotation`,
|
|
583
|
+
findings
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const sideEffectPattern = /@Composable[^{]*\{[^}]*\b(viewModel\.|repository\.|api\.)/g;
|
|
589
|
+
while ((match = sideEffectPattern.exec(content)) !== null) {
|
|
590
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
591
|
+
const block = content.substring(match.index, match.index + 300);
|
|
592
|
+
if (!block.includes('LaunchedEffect') && !block.includes('DisposableEffect')) {
|
|
593
|
+
pushFinding(
|
|
594
|
+
"android.compose.side_effect_without_effect",
|
|
595
|
+
"high",
|
|
596
|
+
sf,
|
|
597
|
+
sf,
|
|
598
|
+
`Line ${lineNumber}: Side effect in Composable without LaunchedEffect - wrap in LaunchedEffect`,
|
|
599
|
+
findings
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
const factoryPattern = /object\s+\w+Factory|class\s+\w+Factory/g;
|
|
606
|
+
while ((match = factoryPattern.exec(content)) !== null) {
|
|
607
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
608
|
+
pushFinding(
|
|
609
|
+
"android.di.manual_factory",
|
|
610
|
+
"medium",
|
|
611
|
+
sf,
|
|
612
|
+
sf,
|
|
613
|
+
`Line ${lineNumber}: Manual factory detected - use Hilt @Inject constructor instead`,
|
|
614
|
+
findings
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const viewModelPattern = /class\s+(\w+ViewModel)\s*\([^)]*\)/g;
|
|
619
|
+
while ((match = viewModelPattern.exec(content)) !== null) {
|
|
620
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
621
|
+
const constructorBlock = content.substring(match.index - 50, match.index);
|
|
622
|
+
if (!constructorBlock.includes('@Inject')) {
|
|
623
|
+
pushFinding(
|
|
624
|
+
"android.di.missing_inject",
|
|
625
|
+
"medium",
|
|
626
|
+
sf,
|
|
627
|
+
sf,
|
|
628
|
+
`Line ${lineNumber}: ViewModel '${match[1]}' without @Inject - use Hilt constructor injection`,
|
|
629
|
+
findings
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// 3. COROUTINES
|
|
635
|
+
|
|
636
|
+
if (content.includes('GlobalScope')) {
|
|
637
|
+
const lineNumber = content.indexOf('GlobalScope') ? content.substring(0, content.indexOf('GlobalScope')).split('\n').length : 1;
|
|
638
|
+
pushFinding(
|
|
639
|
+
"android.coroutines.global_scope",
|
|
640
|
+
"high",
|
|
641
|
+
sf,
|
|
642
|
+
sf,
|
|
643
|
+
`Line ${lineNumber}: GlobalScope detected - use viewModelScope or lifecycleScope for automatic cancellation`,
|
|
644
|
+
findings
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const blockingCallsPattern = /Dispatchers\.Main[^}]*\b(Thread\.sleep|\.get\(\)|\.await\(\))/g;
|
|
649
|
+
while ((match = blockingCallsPattern.exec(content)) !== null) {
|
|
650
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
651
|
+
pushFinding(
|
|
652
|
+
"android.coroutines.blocking_on_main",
|
|
653
|
+
"critical",
|
|
654
|
+
sf,
|
|
655
|
+
sf,
|
|
656
|
+
`Line ${lineNumber}: Blocking call on Main dispatcher - move to Dispatchers.IO`,
|
|
657
|
+
findings
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const suspendFunctionPattern = /suspend\s+fun\s+\w+[^{]*\{[^}]*\b(database|api|file|network)\b/g;
|
|
662
|
+
while ((match = suspendFunctionPattern.exec(content)) !== null) {
|
|
663
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
664
|
+
const functionBlock = content.substring(match.index, match.index + 300);
|
|
665
|
+
if (!functionBlock.includes('withContext')) {
|
|
666
|
+
pushFinding(
|
|
667
|
+
"android.coroutines.missing_withcontext",
|
|
668
|
+
"medium",
|
|
669
|
+
sf,
|
|
670
|
+
sf,
|
|
671
|
+
`Line ${lineNumber}: Suspend function with I/O operations - use withContext(Dispatchers.IO)`,
|
|
672
|
+
findings
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// 4. FLOW
|
|
678
|
+
|
|
679
|
+
if (content.includes('LiveData') && !filePath.includes('legacy') && !filePath.includes('migration')) {
|
|
680
|
+
const lineNumber = content.indexOf('LiveData') ? content.substring(0, content.indexOf('LiveData')).split('\n').length : 1;
|
|
681
|
+
pushFinding(
|
|
682
|
+
"android.flow.livedata_in_new_code",
|
|
683
|
+
"medium",
|
|
684
|
+
sf,
|
|
685
|
+
sf,
|
|
686
|
+
`Line ${lineNumber}: LiveData in new code - prefer Flow/StateFlow for reactive streams`,
|
|
687
|
+
findings
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const flowPattern = /:\s*Flow</g;
|
|
692
|
+
while ((match = flowPattern.exec(content)) !== null) {
|
|
693
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
694
|
+
const varName = content.split('\n')[lineNumber - 1].match(/val\s+(\w+)/)?.[1];
|
|
695
|
+
if (varName) {
|
|
696
|
+
const usagePattern = new RegExp(`${varName}\\.collect`);
|
|
697
|
+
if (!usagePattern.test(content)) {
|
|
698
|
+
pushFinding(
|
|
699
|
+
"android.flow.uncollected_flow",
|
|
700
|
+
"medium",
|
|
701
|
+
sf,
|
|
702
|
+
sf,
|
|
703
|
+
`Line ${lineNumber}: Flow '${varName}' defined but never collected`,
|
|
704
|
+
findings
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// 5. ROOM DATABASE
|
|
711
|
+
|
|
712
|
+
const rawSqlPattern = /database\.execSQL|rawQuery\(/g;
|
|
713
|
+
while ((match = rawSqlPattern.exec(content)) !== null) {
|
|
714
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
715
|
+
pushFinding(
|
|
716
|
+
"android.room.raw_sql",
|
|
717
|
+
"high",
|
|
718
|
+
sf,
|
|
719
|
+
sf,
|
|
720
|
+
`Line ${lineNumber}: Raw SQL query - use Room @Query annotation for type safety`,
|
|
721
|
+
findings
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (content.includes('@Dao')) {
|
|
726
|
+
const daoBlock = content.substring(content.indexOf('@Dao'));
|
|
727
|
+
const hasSuspend = daoBlock.includes('suspend fun');
|
|
728
|
+
if (!hasSuspend && daoBlock.includes('@Insert')) {
|
|
729
|
+
const lineNumber = content.indexOf('@Dao') ? content.substring(0, content.indexOf('@Dao')).split('\n').length : 1;
|
|
730
|
+
pushFinding(
|
|
731
|
+
"android.room.dao_not_suspend",
|
|
732
|
+
"medium",
|
|
733
|
+
sf,
|
|
734
|
+
sf,
|
|
735
|
+
`Line ${lineNumber}: DAO operations should be suspend functions for async execution`,
|
|
736
|
+
findings
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
const mutableStatePattern = /var\s+\w+\s*=\s*mutableListOf|var\s+\w+\s*=\s*mutableMapOf/g;
|
|
743
|
+
while ((match = mutableStatePattern.exec(content)) !== null) {
|
|
744
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
745
|
+
const classContext = content.substring(0, match.index);
|
|
746
|
+
if (classContext.includes('ViewModel')) {
|
|
747
|
+
pushFinding(
|
|
748
|
+
"android.state.mutable_without_stateflow",
|
|
749
|
+
"medium",
|
|
750
|
+
sf,
|
|
751
|
+
sf,
|
|
752
|
+
`Line ${lineNumber}: Mutable state in ViewModel - use StateFlow for observable state`,
|
|
753
|
+
findings
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const directMutationPattern = /\w+\.\w+\s*=\s*/g;
|
|
759
|
+
let mutationCount = 0;
|
|
760
|
+
while ((match = directMutationPattern.exec(content)) !== null && mutationCount < 3) {
|
|
761
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
762
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
763
|
+
if (lineText.includes('state.') && !lineText.includes('copy(') && !lineText.includes('_state')) {
|
|
764
|
+
pushFinding(
|
|
765
|
+
"android.state.direct_mutation",
|
|
766
|
+
"medium",
|
|
767
|
+
sf,
|
|
768
|
+
sf,
|
|
769
|
+
`Line ${lineNumber}: Direct state mutation - use data class .copy() for immutability`,
|
|
770
|
+
findings
|
|
771
|
+
);
|
|
772
|
+
mutationCount++;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
const retrofitCallPattern = /@GET|@POST|@PUT|@DELETE|@PATCH/g;
|
|
778
|
+
while ((match = retrofitCallPattern.exec(content)) !== null) {
|
|
779
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
780
|
+
const nextLines = content.split('\n').slice(lineNumber, lineNumber + 5).join('\n');
|
|
781
|
+
if (!nextLines.includes('suspend') && !nextLines.includes('Call<')) {
|
|
782
|
+
pushFinding(
|
|
783
|
+
"android.networking.sync_call",
|
|
784
|
+
"high",
|
|
785
|
+
sf,
|
|
786
|
+
sf,
|
|
787
|
+
`Line ${lineNumber}: Retrofit endpoint should be suspend function for async execution`,
|
|
788
|
+
findings
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (content.includes('Retrofit.Builder()') && !content.includes('addInterceptor')) {
|
|
794
|
+
const lineNumber = content.indexOf('Retrofit.Builder') ? content.substring(0, content.indexOf('Retrofit.Builder')).split('\n').length : 1;
|
|
795
|
+
pushFinding(
|
|
796
|
+
"android.networking.missing_interceptor",
|
|
797
|
+
"medium",
|
|
798
|
+
sf,
|
|
799
|
+
sf,
|
|
800
|
+
`Line ${lineNumber}: Retrofit without interceptors - add for auth tokens, logging`,
|
|
801
|
+
findings
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// 8. SECURITY
|
|
806
|
+
|
|
807
|
+
if (content.includes('SharedPreferences') && (content.includes('password') || content.includes('token') || content.includes('secret'))) {
|
|
808
|
+
const lineNumber = content.indexOf('SharedPreferences') ? content.substring(0, content.indexOf('SharedPreferences')).split('\n').length : 1;
|
|
809
|
+
if (!content.includes('EncryptedSharedPreferences')) {
|
|
810
|
+
pushFinding(
|
|
811
|
+
"android.security.shared_prefs_sensitive",
|
|
812
|
+
"critical",
|
|
813
|
+
sf,
|
|
814
|
+
sf,
|
|
815
|
+
`Line ${lineNumber}: Sensitive data in SharedPreferences - use EncryptedSharedPreferences`,
|
|
816
|
+
findings
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const apiKeyPattern = /api[_-]?key\s*=\s*"[^"]+"|BuildConfig\.\w*API\w*KEY\w*\s*=\s*"[^"]+"/gi;
|
|
822
|
+
while ((match = apiKeyPattern.exec(content)) !== null) {
|
|
823
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
824
|
+
if (!content.includes('local.properties') && !content.includes('secrets-gradle-plugin')) {
|
|
825
|
+
pushFinding(
|
|
826
|
+
"android.security.hardcoded_api_key",
|
|
827
|
+
"critical",
|
|
828
|
+
sf,
|
|
829
|
+
sf,
|
|
830
|
+
`Line ${lineNumber}: Hardcoded API key - use secrets-gradle-plugin or BuildConfig`,
|
|
831
|
+
findings
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
if (content.includes('RecyclerView') && !filePath.includes('legacy')) {
|
|
838
|
+
const lineNumber = content.indexOf('RecyclerView') ? content.substring(0, content.indexOf('RecyclerView')).split('\n').length : 1;
|
|
839
|
+
pushFinding(
|
|
840
|
+
"android.compose.recyclerview",
|
|
841
|
+
"medium",
|
|
842
|
+
sf,
|
|
843
|
+
sf,
|
|
844
|
+
`Line ${lineNumber}: RecyclerView in new code - use LazyColumn/LazyRow in Compose`,
|
|
845
|
+
findings
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const composablePattern = /@Composable[^{]*\{[^}]*\b(filter|map|sortedBy|groupBy)\b/g;
|
|
850
|
+
while ((match = composablePattern.exec(content)) !== null) {
|
|
851
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
852
|
+
const block = content.substring(match.index, match.index + 200);
|
|
853
|
+
if (!block.includes('remember') && !block.includes('derivedStateOf')) {
|
|
854
|
+
pushFinding(
|
|
855
|
+
"android.compose.missing_remember",
|
|
856
|
+
"medium",
|
|
857
|
+
sf,
|
|
858
|
+
sf,
|
|
859
|
+
`Line ${lineNumber}: Expensive calculation in Composable - wrap in remember or derivedStateOf`,
|
|
860
|
+
findings
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// 10. ARCHITECTURE
|
|
866
|
+
|
|
867
|
+
const activityPattern = /class\s+(\w+Activity)\s*:/g;
|
|
868
|
+
while ((match = activityPattern.exec(content)) !== null) {
|
|
869
|
+
const className = match[1];
|
|
870
|
+
const classStart = match.index;
|
|
871
|
+
const classEnd = content.indexOf('\n}', classStart);
|
|
872
|
+
if (classEnd > -1) {
|
|
873
|
+
const lines = content.substring(classStart, classEnd).split('\n').length;
|
|
874
|
+
if (lines > 500) {
|
|
875
|
+
pushFinding(
|
|
876
|
+
"android.architecture.god_activity",
|
|
877
|
+
"high",
|
|
878
|
+
sf,
|
|
879
|
+
sf,
|
|
880
|
+
`${className} has ${lines} lines - use Single Activity + Composables pattern`,
|
|
881
|
+
findings
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (content.includes('Activity') || content.includes('@Composable')) {
|
|
888
|
+
const businessLogicPatterns = ['repository.', 'database.', 'api.', 'Retrofit'];
|
|
889
|
+
businessLogicPatterns.forEach(pattern => {
|
|
890
|
+
if (content.includes(pattern)) {
|
|
891
|
+
const lineNumber = content.indexOf(pattern) ? content.substring(0, content.indexOf(pattern)).split('\n').length : 1;
|
|
892
|
+
const contextBlock = content.substring(0, match ? match.index : 0);
|
|
893
|
+
if (!contextBlock.includes('ViewModel') && !contextBlock.includes('UseCase')) {
|
|
894
|
+
pushFinding(
|
|
895
|
+
"android.architecture.business_logic_in_ui",
|
|
896
|
+
"high",
|
|
897
|
+
sf,
|
|
898
|
+
sf,
|
|
899
|
+
`Line ${lineNumber}: Business logic in UI layer - move to ViewModel or UseCase`,
|
|
900
|
+
findings
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// 11. TESTING
|
|
908
|
+
|
|
909
|
+
if (content.includes('ViewModel') && !filePath.includes('Test') && !filePath.includes('Fake')) {
|
|
910
|
+
const className = content.match(/class\s+(\w+ViewModel)/)?.[1];
|
|
911
|
+
if (className) {
|
|
912
|
+
pushFinding(
|
|
913
|
+
"android.testing.missing_viewmodel_tests",
|
|
914
|
+
"medium",
|
|
915
|
+
sf,
|
|
916
|
+
sf,
|
|
917
|
+
`ViewModel '${className}' - ensure test file exists with JUnit5`,
|
|
918
|
+
findings
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// 12. LOCALIZATION
|
|
924
|
+
|
|
925
|
+
const hardcodedStringPattern = /Text\s*\(\s*text\s*=\s*"[^"]{5,}"\s*\)|text\s*=\s*"[^"]+"/g;
|
|
926
|
+
let hardcodedCount = 0;
|
|
927
|
+
while ((match = hardcodedStringPattern.exec(content)) !== null && hardcodedCount < 5) {
|
|
928
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
929
|
+
const stringContent = match[0];
|
|
930
|
+
if (!stringContent.includes('stringResource') && !stringContent.match(/"(OK|Cancel|Error)"/)) {
|
|
931
|
+
pushFinding(
|
|
932
|
+
"android.i18n.hardcoded_string",
|
|
933
|
+
"medium",
|
|
934
|
+
sf,
|
|
935
|
+
sf,
|
|
936
|
+
`Line ${lineNumber}: Hardcoded string - use stringResource(R.string.xxx)`,
|
|
937
|
+
findings
|
|
938
|
+
);
|
|
939
|
+
hardcodedCount++;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// left/right instead of start/end
|
|
944
|
+
if (content.includes('Modifier.padding') && (content.includes('paddingLeft') || content.includes('paddingRight'))) {
|
|
945
|
+
const lineNumber = content.indexOf('paddingLeft') || content.indexOf('paddingRight') ?
|
|
946
|
+
content.substring(0, content.indexOf('paddingLeft') || content.indexOf('paddingRight')).split('\n').length : 1;
|
|
947
|
+
pushFinding(
|
|
948
|
+
"android.i18n.left_right_padding",
|
|
949
|
+
"medium",
|
|
950
|
+
sf,
|
|
951
|
+
sf,
|
|
952
|
+
`Line ${lineNumber}: Using left/right padding - use start/end for RTL support`,
|
|
953
|
+
findings
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// 13. ACCESSIBILITY
|
|
958
|
+
|
|
959
|
+
if (content.includes('Image(') && !content.includes('contentDescription')) {
|
|
960
|
+
const imagePattern = /Image\s*\(/g;
|
|
961
|
+
let imageCount = 0;
|
|
962
|
+
while ((match = imagePattern.exec(content)) !== null && imageCount < 3) {
|
|
963
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
964
|
+
const nextLines = content.split('\n').slice(lineNumber - 1, lineNumber + 3).join('\n');
|
|
965
|
+
if (!nextLines.includes('contentDescription')) {
|
|
966
|
+
pushFinding(
|
|
967
|
+
"android.accessibility.missing_content_description",
|
|
968
|
+
"medium",
|
|
969
|
+
sf,
|
|
970
|
+
sf,
|
|
971
|
+
`Line ${lineNumber}: Image without contentDescription - add for TalkBack support`,
|
|
972
|
+
findings
|
|
973
|
+
);
|
|
974
|
+
imageCount++;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const modifierPattern = /Modifier\.size\s*\(\s*(\d+)\.dp\s*\)/g;
|
|
980
|
+
while ((match = modifierPattern.exec(content)) !== null) {
|
|
981
|
+
const size = parseInt(match[1]);
|
|
982
|
+
if (size < 48) {
|
|
983
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
984
|
+
pushFinding(
|
|
985
|
+
"android.accessibility.touch_target_small",
|
|
986
|
+
"medium",
|
|
987
|
+
sf,
|
|
988
|
+
sf,
|
|
989
|
+
`Line ${lineNumber}: Touch target ${size}dp - minimum 48dp for accessibility`,
|
|
990
|
+
findings
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
const lineCount = content.split('\n').length;
|
|
997
|
+
if (lineCount > 500 && !filePath.includes('Generated')) {
|
|
998
|
+
pushFinding(
|
|
999
|
+
"android.organization.file_too_large",
|
|
1000
|
+
"medium",
|
|
1001
|
+
sf,
|
|
1002
|
+
sf,
|
|
1003
|
+
`File has ${lineCount} lines - split into smaller files (max 500)`,
|
|
1004
|
+
findings
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// 15. ANTI-PATTERNS
|
|
1009
|
+
|
|
1010
|
+
if (content.includes('AsyncTask')) {
|
|
1011
|
+
const lineNumber = content.indexOf('AsyncTask') ? content.substring(0, content.indexOf('AsyncTask')).split('\n').length : 1;
|
|
1012
|
+
pushFinding(
|
|
1013
|
+
"android.antipattern.async_task",
|
|
1014
|
+
"high",
|
|
1015
|
+
sf,
|
|
1016
|
+
sf,
|
|
1017
|
+
`Line ${lineNumber}: AsyncTask is deprecated - use Kotlin Coroutines`,
|
|
1018
|
+
findings
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (content.includes('Observable<') || content.includes('Single<') || content.includes('Flowable<')) {
|
|
1023
|
+
if (!filePath.includes('legacy') && !filePath.includes('migration')) {
|
|
1024
|
+
const lineNumber = content.indexOf('Observable') || content.indexOf('Single') || content.indexOf('Flowable') ?
|
|
1025
|
+
content.substring(0, content.indexOf('Observable') || content.indexOf('Single') || content.indexOf('Flowable')).split('\n').length : 1;
|
|
1026
|
+
pushFinding(
|
|
1027
|
+
"android.antipattern.rxjava",
|
|
1028
|
+
"medium",
|
|
1029
|
+
sf,
|
|
1030
|
+
sf,
|
|
1031
|
+
`Line ${lineNumber}: RxJava in new code - use Kotlin Flow instead`,
|
|
1032
|
+
findings
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const contextLeakPattern = /class\s+\w+\s*\([^)]*context:\s*Context\s*\)/g;
|
|
1038
|
+
while ((match = contextLeakPattern.exec(content)) !== null) {
|
|
1039
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1040
|
+
const classBlock = content.substring(match.index, match.index + 100);
|
|
1041
|
+
if (classBlock.includes('@Singleton') || classBlock.includes('object ')) {
|
|
1042
|
+
pushFinding(
|
|
1043
|
+
"android.memory.context_leak",
|
|
1044
|
+
"critical",
|
|
1045
|
+
sf,
|
|
1046
|
+
sf,
|
|
1047
|
+
`Line ${lineNumber}: Context reference in Singleton - use ApplicationContext to avoid leaks`,
|
|
1048
|
+
findings
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// 16. LOGGING
|
|
1054
|
+
|
|
1055
|
+
const logPattern = /Log\.[dewi]\s*\(/g;
|
|
1056
|
+
while ((match = logPattern.exec(content)) !== null) {
|
|
1057
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1058
|
+
const surroundingLines = content.split('\n').slice(Math.max(0, lineNumber - 3), lineNumber + 2).join('\n');
|
|
1059
|
+
if (!surroundingLines.includes('BuildConfig.DEBUG') && !surroundingLines.includes('Timber')) {
|
|
1060
|
+
pushFinding(
|
|
1061
|
+
"android.logging.production_logs",
|
|
1062
|
+
"medium",
|
|
1063
|
+
sf,
|
|
1064
|
+
sf,
|
|
1065
|
+
`Line ${lineNumber}: Log in production - wrap with if (BuildConfig.DEBUG) or use Timber`,
|
|
1066
|
+
findings
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// 17. SEALED CLASSES
|
|
1072
|
+
|
|
1073
|
+
if (content.includes('State') && content.includes('data class') && !content.includes('sealed')) {
|
|
1074
|
+
const stateClassPattern = /data\s+class\s+(\w*State)\s*\(/g;
|
|
1075
|
+
while ((match = stateClassPattern.exec(content)) !== null) {
|
|
1076
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1077
|
+
pushFinding(
|
|
1078
|
+
"android.architecture.missing_sealed_state",
|
|
1079
|
+
"low",
|
|
1080
|
+
sf,
|
|
1081
|
+
sf,
|
|
1082
|
+
`Line ${lineNumber}: State class '${match[1]}' - consider sealed class for Loading/Success/Error states`,
|
|
1083
|
+
findings
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const hasJavaFile = filePath.endsWith('.java');
|
|
1089
|
+
if (hasJavaFile && !filePath.includes('build/') && !filePath.includes('generated/')) {
|
|
1090
|
+
pushFinding(
|
|
1091
|
+
"android.kotlin.java_detected",
|
|
1092
|
+
"critical",
|
|
1093
|
+
sf,
|
|
1094
|
+
sf,
|
|
1095
|
+
'Java file detected - use Kotlin 100% for new code (NO Java in new code)',
|
|
1096
|
+
findings
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
if (!content.includes('fun ') || (!content.includes('fun String.') && !content.includes('fun Int.') && !content.includes('fun List.'))) {
|
|
1101
|
+
if ((content.match(/fun\s+\w+/g) || []).length > 10 && !filePath.includes('ViewModel')) {
|
|
1102
|
+
pushFinding(
|
|
1103
|
+
"android.kotlin.missing_extensions",
|
|
1104
|
+
"low",
|
|
1105
|
+
sf,
|
|
1106
|
+
sf,
|
|
1107
|
+
'File with many functions - consider extension functions for better organization',
|
|
1108
|
+
findings
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const hasScopeFunctions = content.includes('.let {') || content.includes('.run {') || content.includes('.apply {');
|
|
1114
|
+
const hasNullChecks = (content.match(/if\s*\(\s*\w+\s*!=\s*null\s*\)/g) || []).length;
|
|
1115
|
+
if (hasNullChecks > 3 && !hasScopeFunctions) {
|
|
1116
|
+
pushFinding(
|
|
1117
|
+
"android.kotlin.missing_scope_functions",
|
|
1118
|
+
"medium",
|
|
1119
|
+
sf,
|
|
1120
|
+
sf,
|
|
1121
|
+
`${hasNullChecks} null checks without scope functions - use let, run, apply for cleaner code`,
|
|
1122
|
+
findings
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if ((content.includes('Response') || content.includes('Request') || content.includes('DTO')) &&
|
|
1127
|
+
content.includes('class') && !content.includes('data class')) {
|
|
1128
|
+
pushFinding(
|
|
1129
|
+
"android.kotlin.missing_data_class",
|
|
1130
|
+
"high",
|
|
1131
|
+
sf,
|
|
1132
|
+
sf,
|
|
1133
|
+
'Response/Request/DTO without data class - use data class for DTOs and models',
|
|
1134
|
+
findings
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const hasXMLLayout = filePath.includes('/res/layout/') && filePath.endsWith('.xml');
|
|
1139
|
+
if (hasXMLLayout && !filePath.includes('navigation.xml')) {
|
|
1140
|
+
pushFinding(
|
|
1141
|
+
"android.compose.xml_layout_detected",
|
|
1142
|
+
"high",
|
|
1143
|
+
sf,
|
|
1144
|
+
sf,
|
|
1145
|
+
'XML layout detected - use Jetpack Compose 100% for new UI code',
|
|
1146
|
+
findings
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (content.includes('@Composable') && !content.includes('fun ')) {
|
|
1151
|
+
pushFinding(
|
|
1152
|
+
"android.compose.composable_not_function",
|
|
1153
|
+
"critical",
|
|
1154
|
+
sf,
|
|
1155
|
+
sf,
|
|
1156
|
+
'@Composable must be a function - Composable functions pattern',
|
|
1157
|
+
findings
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (content.includes('@Composable') && content.includes('var ') && content.includes('mutableStateOf')) {
|
|
1162
|
+
if (!content.includes('remember') && !content.includes('rememberSaveable')) {
|
|
1163
|
+
pushFinding(
|
|
1164
|
+
"android.compose.missing_remember",
|
|
1165
|
+
"high",
|
|
1166
|
+
sf,
|
|
1167
|
+
sf,
|
|
1168
|
+
'mutableStateOf without remember - state will be lost on recomposition',
|
|
1169
|
+
findings
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
if (content.includes('remember {') && content.includes('mutableStateOf') && !content.includes('rememberSaveable')) {
|
|
1175
|
+
if (filePath.includes('Screen') || filePath.includes('Activity')) {
|
|
1176
|
+
pushFinding(
|
|
1177
|
+
"android.compose.use_remembersaveable",
|
|
1178
|
+
"medium",
|
|
1179
|
+
sf,
|
|
1180
|
+
sf,
|
|
1181
|
+
'Screen-level state with remember - use rememberSaveable to survive process death',
|
|
1182
|
+
findings
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
if (content.includes('@Composable') && (content.includes('viewModel') || content.includes('repository'))) {
|
|
1188
|
+
if (content.includes('.collect') && !content.includes('LaunchedEffect')) {
|
|
1189
|
+
pushFinding(
|
|
1190
|
+
"android.compose.missing_launched_effect",
|
|
1191
|
+
"high",
|
|
1192
|
+
sf,
|
|
1193
|
+
sf,
|
|
1194
|
+
'Flow.collect in Composable without LaunchedEffect - use LaunchedEffect for side effects with lifecycle',
|
|
1195
|
+
findings
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
if (content.includes('@Composable') && (content.includes('Observer') || content.includes('Listener') || content.includes('register'))) {
|
|
1201
|
+
if (!content.includes('DisposableEffect')) {
|
|
1202
|
+
pushFinding(
|
|
1203
|
+
"android.compose.missing_disposable_effect",
|
|
1204
|
+
"high",
|
|
1205
|
+
sf,
|
|
1206
|
+
sf,
|
|
1207
|
+
'Resource registration without DisposableEffect - implement cleanup when Composable leaves composition',
|
|
1208
|
+
findings
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if (content.includes('Modifier') && content.includes('.background(') && content.includes('.padding(')) {
|
|
1214
|
+
const modifierChainPattern = /\.background\([^)]+\)\.padding\(/g;
|
|
1215
|
+
if (modifierChainPattern.test(content)) {
|
|
1216
|
+
pushFinding(
|
|
1217
|
+
"android.compose.wrong_modifier_order",
|
|
1218
|
+
"medium",
|
|
1219
|
+
sf,
|
|
1220
|
+
sf,
|
|
1221
|
+
'Modifier.background().padding() - wrong order, use .padding().background() (padding before background)',
|
|
1222
|
+
findings
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (content.includes('@Composable') && !content.includes('@Preview') && !filePath.includes('ViewModel')) {
|
|
1228
|
+
const composableCount = (content.match(/@Composable/g) || []).length;
|
|
1229
|
+
if (composableCount > 0 && composableCount < 5) {
|
|
1230
|
+
pushFinding(
|
|
1231
|
+
"android.compose.missing_preview",
|
|
1232
|
+
"low",
|
|
1233
|
+
sf,
|
|
1234
|
+
sf,
|
|
1235
|
+
'Composable without @Preview - add @Preview for development visualization',
|
|
1236
|
+
findings
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if (content.includes('import androidx.compose.material.') && !content.includes('material3')) {
|
|
1242
|
+
pushFinding(
|
|
1243
|
+
"android.material.use_material3",
|
|
1244
|
+
"medium",
|
|
1245
|
+
sf,
|
|
1246
|
+
sf,
|
|
1247
|
+
'Using Material 2 components - migrate to Material 3 (androidx.compose.material3)',
|
|
1248
|
+
findings
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
if (content.includes('@Composable') && content.includes('Color(') && !content.includes('isSystemInDarkTheme')) {
|
|
1253
|
+
if ((content.match(/Color\(0x/g) || []).length > 3) {
|
|
1254
|
+
pushFinding(
|
|
1255
|
+
"android.material.missing_dark_theme",
|
|
1256
|
+
"medium",
|
|
1257
|
+
sf,
|
|
1258
|
+
sf,
|
|
1259
|
+
'Hardcoded colors without dark theme support - use isSystemInDarkTheme() for adaptive theming',
|
|
1260
|
+
findings
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (content.includes('@Composable') && (content.includes('Column') || content.includes('Row')) &&
|
|
1266
|
+
!content.includes('WindowSizeClass') && lineCount > 100) {
|
|
1267
|
+
pushFinding(
|
|
1268
|
+
"android.material.missing_adaptive_layout",
|
|
1269
|
+
"low",
|
|
1270
|
+
sf,
|
|
1271
|
+
sf,
|
|
1272
|
+
'Complex layout without WindowSizeClass - implement responsive design for tablets',
|
|
1273
|
+
findings
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
if (content.includes('class') && content.includes('Activity') && !content.includes('MainActivity')) {
|
|
1278
|
+
const activityPattern = /class\s+(\w+Activity)\s*:/g;
|
|
1279
|
+
const activities = [];
|
|
1280
|
+
while ((match = activityPattern.exec(content)) !== null) {
|
|
1281
|
+
if (match[1] !== 'MainActivity') {
|
|
1282
|
+
activities.push(match[1]);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
if (activities.length > 0) {
|
|
1286
|
+
pushFinding(
|
|
1287
|
+
"android.architecture.multiple_activities",
|
|
1288
|
+
"medium",
|
|
1289
|
+
sf,
|
|
1290
|
+
sf,
|
|
1291
|
+
`Multiple Activities detected (${activities.join(', ')}) - use Single Activity + Composables pattern`,
|
|
1292
|
+
findings
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if (content.includes('NavHost') && !content.includes('androidx.navigation.compose')) {
|
|
1298
|
+
pushFinding(
|
|
1299
|
+
"android.navigation.use_compose_navigation",
|
|
1300
|
+
"high",
|
|
1301
|
+
sf,
|
|
1302
|
+
sf,
|
|
1303
|
+
'NavHost without Compose Navigation - use androidx.navigation:navigation-compose',
|
|
1304
|
+
findings
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
if (content.includes('ViewModel') && content.includes('LiveData') && !content.includes('StateFlow')) {
|
|
1309
|
+
pushFinding(
|
|
1310
|
+
"android.architecture.use_stateflow",
|
|
1311
|
+
"high",
|
|
1312
|
+
sf,
|
|
1313
|
+
sf,
|
|
1314
|
+
'ViewModel using LiveData - migrate to StateFlow for Kotlin Flow support',
|
|
1315
|
+
findings
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if (filePath.includes('ViewModel') && !content.includes('@HiltViewModel') && content.includes('ViewModel()')) {
|
|
1320
|
+
pushFinding(
|
|
1321
|
+
"android.di.missing_hilt_viewmodel",
|
|
1322
|
+
"high",
|
|
1323
|
+
sf,
|
|
1324
|
+
sf,
|
|
1325
|
+
'ViewModel without @HiltViewModel - use Hilt for dependency injection',
|
|
1326
|
+
findings
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
if (content.includes('class') && (content.includes('Repository') || content.includes('Service')) &&
|
|
1331
|
+
!content.includes('@Inject') && content.includes('constructor')) {
|
|
1332
|
+
pushFinding(
|
|
1333
|
+
"android.di.missing_inject",
|
|
1334
|
+
"high",
|
|
1335
|
+
sf,
|
|
1336
|
+
sf,
|
|
1337
|
+
'Repository/Service constructor without @Inject - use constructor injection with Hilt',
|
|
1338
|
+
findings
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
if (content.includes('@Module') && !content.includes('@InstallIn')) {
|
|
1343
|
+
pushFinding(
|
|
1344
|
+
"android.di.missing_installin",
|
|
1345
|
+
"critical",
|
|
1346
|
+
sf,
|
|
1347
|
+
sf,
|
|
1348
|
+
'@Module without @InstallIn - specify component scope (SingletonComponent, ViewModelComponent, etc.)',
|
|
1349
|
+
findings
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (content.includes('ViewModel') && content.includes('launch {') && !content.includes('viewModelScope')) {
|
|
1354
|
+
pushFinding(
|
|
1355
|
+
"android.coroutines.use_viewmodelscope",
|
|
1356
|
+
"high",
|
|
1357
|
+
sf,
|
|
1358
|
+
sf,
|
|
1359
|
+
'ViewModel launch without viewModelScope - use viewModelScope for automatic cancellation',
|
|
1360
|
+
findings
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
if (content.includes('suspend fun') && (content.includes('Room') || content.includes('Retrofit') || content.includes('File'))) {
|
|
1365
|
+
if (!content.includes('Dispatchers.IO') && !content.includes('withContext')) {
|
|
1366
|
+
pushFinding(
|
|
1367
|
+
"android.coroutines.missing_io_dispatcher",
|
|
1368
|
+
"medium",
|
|
1369
|
+
sf,
|
|
1370
|
+
sf,
|
|
1371
|
+
'Network/Disk operations without Dispatchers.IO - use appropriate dispatcher',
|
|
1372
|
+
findings
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
if (content.includes('async {') && !content.includes('supervisorScope') && (content.match(/async\s*\{/g) || []).length > 2) {
|
|
1378
|
+
pushFinding(
|
|
1379
|
+
"android.coroutines.use_supervisor_scope",
|
|
1380
|
+
"medium",
|
|
1381
|
+
sf,
|
|
1382
|
+
sf,
|
|
1383
|
+
'Multiple async without supervisorScope - errors will cancel all jobs, use supervisorScope',
|
|
1384
|
+
findings
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
if (content.includes('interface') && content.includes('@GET') && !content.includes('suspend fun')) {
|
|
1389
|
+
pushFinding(
|
|
1390
|
+
"android.networking.retrofit_not_suspend",
|
|
1391
|
+
"high",
|
|
1392
|
+
sf,
|
|
1393
|
+
sf,
|
|
1394
|
+
'Retrofit API without suspend functions - use suspend for coroutine support',
|
|
1395
|
+
findings
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
if (content.includes('Retrofit') && content.includes('Builder') && !content.includes('Interceptor')) {
|
|
1400
|
+
pushFinding(
|
|
1401
|
+
"android.networking.missing_interceptors",
|
|
1402
|
+
"medium",
|
|
1403
|
+
sf,
|
|
1404
|
+
sf,
|
|
1405
|
+
'Retrofit without interceptors - add logging/auth interceptors for debugging and authentication',
|
|
1406
|
+
findings
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// 165. Moshi over Gson
|
|
1411
|
+
if (content.includes('Gson') && !content.includes('Moshi')) {
|
|
1412
|
+
pushFinding(
|
|
1413
|
+
"android.networking.prefer_moshi",
|
|
1414
|
+
"low",
|
|
1415
|
+
sf,
|
|
1416
|
+
sf,
|
|
1417
|
+
'Using Gson - prefer Moshi for better Kotlin support and performance',
|
|
1418
|
+
findings
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
if (content.includes('OkHttpClient') && content.includes('https') && !content.includes('CertificatePinner')) {
|
|
1423
|
+
if (content.includes('production') || content.includes('release')) {
|
|
1424
|
+
pushFinding(
|
|
1425
|
+
"android.networking.missing_cert_pinning",
|
|
1426
|
+
"critical",
|
|
1427
|
+
sf,
|
|
1428
|
+
sf,
|
|
1429
|
+
'Production network without certificate pinning - implement SSL pinning for security',
|
|
1430
|
+
findings
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if (content.includes('@Dao') && content.includes('fun ') && !content.includes('suspend fun')) {
|
|
1436
|
+
pushFinding(
|
|
1437
|
+
"android.persistence.dao_not_suspend",
|
|
1438
|
+
"high",
|
|
1439
|
+
sf,
|
|
1440
|
+
sf,
|
|
1441
|
+
'@Dao methods without suspend - use suspend functions for coroutine support',
|
|
1442
|
+
findings
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
if (content.includes('@Dao') && content.includes('@Query') && !content.includes('Flow<')) {
|
|
1447
|
+
const queryCount = (content.match(/@Query/g) || []).length;
|
|
1448
|
+
if (queryCount > 0) {
|
|
1449
|
+
pushFinding(
|
|
1450
|
+
"android.persistence.use_flow_queries",
|
|
1451
|
+
"medium",
|
|
1452
|
+
sf,
|
|
1453
|
+
sf,
|
|
1454
|
+
'Room queries without Flow - use Flow<T> for observable queries',
|
|
1455
|
+
findings
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
if (content.includes('@Entity') && (content.includes('Date') || content.includes('List<') || content.includes('Map<'))) {
|
|
1461
|
+
if (!content.includes('@TypeConverter')) {
|
|
1462
|
+
pushFinding(
|
|
1463
|
+
"android.persistence.missing_type_converter",
|
|
1464
|
+
"high",
|
|
1465
|
+
sf,
|
|
1466
|
+
sf,
|
|
1467
|
+
'Entity with complex types (Date, List, Map) without @TypeConverter',
|
|
1468
|
+
findings
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
if (content.includes('@Dao') && (content.match(/@Query|@Insert|@Update|@Delete/g) || []).length > 2) {
|
|
1474
|
+
if (!content.includes('@Transaction')) {
|
|
1475
|
+
pushFinding(
|
|
1476
|
+
"android.persistence.missing_transaction",
|
|
1477
|
+
"medium",
|
|
1478
|
+
sf,
|
|
1479
|
+
sf,
|
|
1480
|
+
'DAO with multiple operations - use @Transaction for atomic operations',
|
|
1481
|
+
findings
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
if (filePath.includes('ViewModel') && content.includes('data class') && content.includes('State')) {
|
|
1487
|
+
if (content.includes('var ') && !content.includes('private set')) {
|
|
1488
|
+
pushFinding(
|
|
1489
|
+
"android.architecture.mutable_state",
|
|
1490
|
+
"high",
|
|
1491
|
+
sf,
|
|
1492
|
+
sf,
|
|
1493
|
+
'Mutable state in ViewModel - use immutable data class with copy() pattern',
|
|
1494
|
+
findings
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
if (content.includes('ViewModel') && (content.match(/StateFlow|LiveData/g) || []).length > 5) {
|
|
1500
|
+
pushFinding(
|
|
1501
|
+
"android.architecture.too_many_state_sources",
|
|
1502
|
+
"medium",
|
|
1503
|
+
sf,
|
|
1504
|
+
sf,
|
|
1505
|
+
'ViewModel with many state sources - consolidate into single UiState sealed class',
|
|
1506
|
+
findings
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
if (content.includes('Glide') && content.includes('@Composable')) {
|
|
1511
|
+
pushFinding(
|
|
1512
|
+
"android.images.use_coil",
|
|
1513
|
+
"low",
|
|
1514
|
+
sf,
|
|
1515
|
+
sf,
|
|
1516
|
+
'Using Glide in Compose - prefer Coil for Jetpack Compose integration',
|
|
1517
|
+
findings
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
if (filePath.includes('Test.kt') && content.includes('import org.junit.Test') && !content.includes('jupiter')) {
|
|
1522
|
+
pushFinding(
|
|
1523
|
+
"android.testing.use_junit5",
|
|
1524
|
+
"medium",
|
|
1525
|
+
sf,
|
|
1526
|
+
sf,
|
|
1527
|
+
'Using JUnit4 - migrate to JUnit5 (Jupiter) for modern testing features',
|
|
1528
|
+
findings
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
if (filePath.includes('Test.kt') && content.includes('mock') && !content.includes('MockK') && !content.includes('mockk')) {
|
|
1533
|
+
pushFinding(
|
|
1534
|
+
"android.testing.use_mockk",
|
|
1535
|
+
"medium",
|
|
1536
|
+
sf,
|
|
1537
|
+
sf,
|
|
1538
|
+
'Mocking without MockK - use MockK for Kotlin-first mocking',
|
|
1539
|
+
findings
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
if (filePath.includes('Test.kt') && content.includes('Flow<') && !content.includes('turbine')) {
|
|
1544
|
+
pushFinding(
|
|
1545
|
+
"android.testing.use_turbine",
|
|
1546
|
+
"low",
|
|
1547
|
+
sf,
|
|
1548
|
+
sf,
|
|
1549
|
+
'Testing Flows without Turbine - use Turbine for Flow testing',
|
|
1550
|
+
findings
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
if (filePath.includes('Test.kt') && content.includes('assert') && !content.includes('assertThat')) {
|
|
1555
|
+
pushFinding(
|
|
1556
|
+
"android.testing.use_truth",
|
|
1557
|
+
"low",
|
|
1558
|
+
sf,
|
|
1559
|
+
sf,
|
|
1560
|
+
'Using JUnit assertions - consider Truth library for more readable assertions',
|
|
1561
|
+
findings
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (filePath.includes('Test.kt') && content.includes('suspend fun') && !content.includes('runTest')) {
|
|
1566
|
+
pushFinding(
|
|
1567
|
+
"android.testing.use_runtest",
|
|
1568
|
+
"high",
|
|
1569
|
+
sf,
|
|
1570
|
+
sf,
|
|
1571
|
+
'Testing suspend functions without runTest - use runTest for coroutine testing',
|
|
1572
|
+
findings
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
if (content.includes('SharedPreferences') && !content.includes('Encrypted') &&
|
|
1577
|
+
(content.includes('token') || content.includes('password') || content.includes('secret'))) {
|
|
1578
|
+
pushFinding(
|
|
1579
|
+
"android.security.use_encrypted_prefs",
|
|
1580
|
+
"critical",
|
|
1581
|
+
sf,
|
|
1582
|
+
sf,
|
|
1583
|
+
'Sensitive data in SharedPreferences - use EncryptedSharedPreferences for security',
|
|
1584
|
+
findings
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
if (filePath.includes('build.gradle') && content.includes('release') && !content.includes('minifyEnabled true')) {
|
|
1589
|
+
pushFinding(
|
|
1590
|
+
"android.security.missing_proguard",
|
|
1591
|
+
"high",
|
|
1592
|
+
sf,
|
|
1593
|
+
sf,
|
|
1594
|
+
'Release build without ProGuard/R8 - enable code obfuscation for security',
|
|
1595
|
+
findings
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
if (content.includes('Column') && content.includes('items(') && !content.includes('LazyColumn')) {
|
|
1600
|
+
pushFinding(
|
|
1601
|
+
"android.performance.use_lazy_column",
|
|
1602
|
+
"high",
|
|
1603
|
+
sf,
|
|
1604
|
+
sf,
|
|
1605
|
+
'Column with items() - use LazyColumn for virtualized lists',
|
|
1606
|
+
findings
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (content.includes('LazyColumn') && content.includes('items(') && !content.includes('Paging')) {
|
|
1611
|
+
if ((content.match(/items\(/g) || []).length > 0 && content.includes('repository')) {
|
|
1612
|
+
pushFinding(
|
|
1613
|
+
"android.performance.use_paging",
|
|
1614
|
+
"medium",
|
|
1615
|
+
sf,
|
|
1616
|
+
sf,
|
|
1617
|
+
'LazyColumn with repository data - consider Paging 3 for large datasets',
|
|
1618
|
+
findings
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
if (filePath.includes('build.gradle') && content.includes('compose') && !content.includes('baselineProfile')) {
|
|
1624
|
+
pushFinding(
|
|
1625
|
+
"android.performance.missing_baseline_profile",
|
|
1626
|
+
"low",
|
|
1627
|
+
sf,
|
|
1628
|
+
sf,
|
|
1629
|
+
'Compose app without Baseline Profiles - add for startup optimization',
|
|
1630
|
+
findings
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
if (content.includes('data class') && content.includes('@Composable') && !content.includes('@Stable') && !content.includes('@Immutable')) {
|
|
1635
|
+
if ((content.match(/data\s+class/g) || []).length > 2) {
|
|
1636
|
+
pushFinding(
|
|
1637
|
+
"android.compose_perf.missing_stability",
|
|
1638
|
+
"high",
|
|
1639
|
+
sf,
|
|
1640
|
+
sf,
|
|
1641
|
+
'Data classes in Composable without @Stable/@Immutable - causes unnecessary recompositions',
|
|
1642
|
+
findings
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
if (content.includes('StateFlow<List<') || content.includes('State<List<')) {
|
|
1648
|
+
if (!content.includes('kotlinx.collections.immutable')) {
|
|
1649
|
+
pushFinding(
|
|
1650
|
+
"android.compose_perf.use_immutable_collections",
|
|
1651
|
+
"medium",
|
|
1652
|
+
sf,
|
|
1653
|
+
sf,
|
|
1654
|
+
'State with List - use ImmutableList from kotlinx.collections.immutable for stability',
|
|
1655
|
+
findings
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
if (content.includes('@Composable') && (content.includes('Image') || content.includes('Icon')) &&
|
|
1661
|
+
!content.includes('contentDescription')) {
|
|
1662
|
+
const imageCount = (content.match(/Image\(|Icon\(/g) || []).length;
|
|
1663
|
+
if (imageCount > 2) {
|
|
1664
|
+
pushFinding(
|
|
1665
|
+
"android.accessibility.missing_content_description",
|
|
1666
|
+
"high",
|
|
1667
|
+
sf,
|
|
1668
|
+
sf,
|
|
1669
|
+
`${imageCount} images/icons without contentDescription - add for TalkBack accessibility`,
|
|
1670
|
+
findings
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
if (content.includes('Modifier') && content.includes('.size(') && !content.includes('.minimumInteractiveComponentSize()')) {
|
|
1676
|
+
const sizePattern = /\.size\((\d+)\.dp\)/g;
|
|
1677
|
+
while ((match = sizePattern.exec(content)) !== null) {
|
|
1678
|
+
const size = parseInt(match[1]);
|
|
1679
|
+
if (size < 48) {
|
|
1680
|
+
pushFinding(
|
|
1681
|
+
"android.accessibility.small_touch_target",
|
|
1682
|
+
"medium",
|
|
1683
|
+
sf,
|
|
1684
|
+
sf,
|
|
1685
|
+
`Touch target ${size}dp < 48dp minimum - increase size for accessibility`,
|
|
1686
|
+
findings
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
// 188. strings.xml for i18n
|
|
1693
|
+
if (content.includes('Text(') && (content.match(/Text\("[^"]{15,}"\)/g) || []).length > 3) {
|
|
1694
|
+
if (!content.includes('stringResource') && !filePath.includes('Preview')) {
|
|
1695
|
+
pushFinding(
|
|
1696
|
+
"android.localization.hardcoded_strings",
|
|
1697
|
+
"medium",
|
|
1698
|
+
sf,
|
|
1699
|
+
sf,
|
|
1700
|
+
'Hardcoded strings in Text - use stringResource(R.string.xxx) for internationalization',
|
|
1701
|
+
findings
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
if (filePath.endsWith('build.gradle') && !filePath.endsWith('.kts')) {
|
|
1707
|
+
pushFinding(
|
|
1708
|
+
"android.gradle.use_kotlin_dsl",
|
|
1709
|
+
"low",
|
|
1710
|
+
sf,
|
|
1711
|
+
sf,
|
|
1712
|
+
'Groovy Gradle script - migrate to Kotlin DSL (build.gradle.kts) for type safety',
|
|
1713
|
+
findings
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
if (filePath.includes('build.gradle') && content.includes('implementation') && !content.includes('libs.')) {
|
|
1718
|
+
const depCount = (content.match(/implementation\s*\(/g) || []).length;
|
|
1719
|
+
if (depCount > 5) {
|
|
1720
|
+
pushFinding(
|
|
1721
|
+
"android.gradle.use_version_catalogs",
|
|
1722
|
+
"low",
|
|
1723
|
+
sf,
|
|
1724
|
+
sf,
|
|
1725
|
+
`${depCount} dependencies without version catalog - use libs.versions.toml for centralized management`,
|
|
1726
|
+
findings
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1734
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1735
|
+
project.getSourceFiles().forEach((sf) => {
|
|
1736
|
+
const filePath = sf.getFilePath();
|
|
1737
|
+
if (platformOf(filePath) !== "android") return;
|
|
1738
|
+
if (/\/ast-[^/]+\.js$/.test(filePath)) return;
|
|
1739
|
+
|
|
1740
|
+
const solidAnalyzer = new AndroidSOLIDAnalyzer();
|
|
1741
|
+
solidAnalyzer.analyze(sf, findings, pushFinding);
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1745
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1746
|
+
project.getSourceFiles().forEach((sf) => {
|
|
1747
|
+
const filePath = sf.getFilePath();
|
|
1748
|
+
if (platformOf(filePath) !== "android") return;
|
|
1749
|
+
if (/\/ast-[^/]+\.js$/.test(filePath)) return;
|
|
1750
|
+
|
|
1751
|
+
const forbiddenLiteralsAnalyzer = new AndroidForbiddenLiteralsAnalyzer();
|
|
1752
|
+
forbiddenLiteralsAnalyzer.analyze(sf, findings, pushFinding);
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1756
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1757
|
+
try {
|
|
1758
|
+
const kotlinFiles = project.getSourceFiles()
|
|
1759
|
+
.map(sf => sf.getFilePath())
|
|
1760
|
+
.filter(f => platformOf(f) === "android" && (f.endsWith('.kt') || f.endsWith('.kts')));
|
|
1761
|
+
|
|
1762
|
+
if (kotlinFiles.length > 0) {
|
|
1763
|
+
const detektFindings = runDetektAnalysis(kotlinFiles);
|
|
1764
|
+
|
|
1765
|
+
for (const finding of detektFindings) {
|
|
1766
|
+
findings.push({
|
|
1767
|
+
rule_id: finding.rule_id,
|
|
1768
|
+
severity: finding.severity,
|
|
1769
|
+
file: finding.file,
|
|
1770
|
+
line: finding.line,
|
|
1771
|
+
column: finding.column,
|
|
1772
|
+
message: finding.message,
|
|
1773
|
+
category: finding.category || 'detekt',
|
|
1774
|
+
debt: finding.debt || '10min'
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
console.error('Error running detekt analysis:', error.message);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
module.exports = {
|
|
1784
|
+
runAndroidIntelligence,
|
|
1785
|
+
};
|