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,2176 @@
|
|
|
1
|
+
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const glob = require('glob');
|
|
4
|
+
const { pushFinding, mapToLevel, SyntaxKind, isTestFile, platformOf, getRepoRoot } = require(path.join(__dirname, '../ast-core'));
|
|
5
|
+
const { iOSEnterpriseAnalyzer } = require(path.join(__dirname, 'analyzers/iOSEnterpriseAnalyzer'));
|
|
6
|
+
const { iOSArchitectureDetector } = require(path.join(__dirname, 'analyzers/iOSArchitectureDetector'));
|
|
7
|
+
const { iOSArchitectureRules } = require(path.join(__dirname, 'analyzers/iOSArchitectureRules'));
|
|
8
|
+
const { iOSPerformanceRules } = require(path.join(__dirname, 'analyzers/iOSPerformanceRules'));
|
|
9
|
+
const { iOSSwiftUIAdvancedRules } = require(path.join(__dirname, 'analyzers/iOSSwiftUIAdvancedRules'));
|
|
10
|
+
const { iOSSPMRules } = require(path.join(__dirname, 'analyzers/iOSSPMRules'));
|
|
11
|
+
const { iOSTestingAdvancedRules } = require(path.join(__dirname, 'analyzers/iOSTestingAdvancedRules'));
|
|
12
|
+
const { runSwiftLintNative } = require(path.join(__dirname, 'native-bridge'));
|
|
13
|
+
const { iOSNetworkingAdvancedRules } = require(path.join(__dirname, 'analyzers/iOSNetworkingAdvancedRules'));
|
|
14
|
+
const { iOSCICDRules } = require(path.join(__dirname, 'analyzers/iOSCICDRules'));
|
|
15
|
+
const { iOSForbiddenLiteralsAnalyzer } = require(path.join(__dirname, 'analyzers/iOSForbiddenLiteralsAnalyzer'));
|
|
16
|
+
const { iOSASTIntelligentAnalyzer } = require(path.join(__dirname, 'analyzers/iOSASTIntelligentAnalyzer'));
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Run iOS-specific AST intelligence analysis
|
|
20
|
+
* Uses both TypeScript AST (for .ts/.tsx) and SourceKitten (for .swift)
|
|
21
|
+
* @param {Project} project - TypeScript morph project
|
|
22
|
+
* @param {Array} findings - Findings array to populate
|
|
23
|
+
* @param {string} platform - Platform identifier
|
|
24
|
+
*/
|
|
25
|
+
async function runIOSIntelligence(project, findings, platform) {
|
|
26
|
+
|
|
27
|
+
console.error(`[iOS AST Intelligence] Running SourceKitten-based analysis...`);
|
|
28
|
+
const astAnalyzer = new iOSASTIntelligentAnalyzer(findings);
|
|
29
|
+
const root = getRepoRoot();
|
|
30
|
+
const swiftFilesForAST = glob.sync('**/*.swift', {
|
|
31
|
+
cwd: root,
|
|
32
|
+
ignore: ['**/node_modules/**', '**/build/**', '**/Pods/**', '**/.build/**', '**/CustomLintRules/**'],
|
|
33
|
+
absolute: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
for (const swiftFile of swiftFilesForAST) {
|
|
37
|
+
astAnalyzer.analyzeFile(swiftFile);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
astAnalyzer.finalizeGodClassDetection();
|
|
41
|
+
console.error(`[iOS AST Intelligence] Analyzed ${swiftFilesForAST.length} Swift files with SourceKitten AST`);
|
|
42
|
+
|
|
43
|
+
await runSwiftLintNative(findings);
|
|
44
|
+
|
|
45
|
+
project.getSourceFiles().forEach((sf) => {
|
|
46
|
+
if (!sf || typeof sf.getFilePath !== 'function') return;
|
|
47
|
+
const filePath = sf.getFilePath();
|
|
48
|
+
|
|
49
|
+
if (/\/ast-[^/]+\.js$/.test(filePath)) return;
|
|
50
|
+
|
|
51
|
+
if (platformOf(filePath) !== "ios") return;
|
|
52
|
+
|
|
53
|
+
sf.getDescendantsOfKind(SyntaxKind.NonNullExpression).forEach((expr) => {
|
|
54
|
+
pushFinding("ios.force_unwrapping", "high", sf, expr, "Force unwrapping (!) detected - use if let or guard let instead", findings);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const completionHandlerFilePath = sf.getFilePath();
|
|
58
|
+
const isAnalyzer = /infrastructure\/ast\/|analyzers\/|detectors\/|scanner|analyzer|detector/i.test(completionHandlerFilePath);
|
|
59
|
+
const isCompletionTestFile = /\.(spec|test)\.(js|ts|swift)$/i.test(completionHandlerFilePath);
|
|
60
|
+
if (!isAnalyzer && !isCompletionTestFile) {
|
|
61
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
62
|
+
const args = call.getArguments();
|
|
63
|
+
args.forEach((arg) => {
|
|
64
|
+
const argText = typeof arg?.getText === "function" ? arg.getText() : "";
|
|
65
|
+
if (argText.includes("completion:")) {
|
|
66
|
+
pushFinding("ios.completion_handlers", "medium", sf, call, "Completion handler detected - use async/await instead", findings);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const currentFilePath = sf.getFilePath();
|
|
73
|
+
const isAnalyzerForStoryboards = /infrastructure\/ast\/|analyzers\/|detectors\/|scanner|analyzer|detector/i.test(currentFilePath);
|
|
74
|
+
if (!isAnalyzerForStoryboards && (sf.getFullText().includes("storyboard") || sf.getFullText().includes("xib") || sf.getFullText().includes("nib"))) {
|
|
75
|
+
pushFinding("ios.storyboards", "high", sf, sf, "Storyboard/XIB detected - use programmatic UI for better version control", findings);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
79
|
+
const name = cls.getName();
|
|
80
|
+
if (!name || !name.includes("ViewController")) return;
|
|
81
|
+
const lines = cls.getEnd() - cls.getStart();
|
|
82
|
+
if (lines > 300) {
|
|
83
|
+
pushFinding("ios.massive_viewcontrollers", "high", sf, cls, `Massive ViewController detected (${lines} lines) - break down into smaller components`, findings);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const root = getRepoRoot();
|
|
90
|
+
const swiftFiles = glob.sync('**/*.swift', {
|
|
91
|
+
cwd: root,
|
|
92
|
+
ignore: ['**/node_modules/**', '**/build/**', '**/Pods/**', '**/.build/**'],
|
|
93
|
+
absolute: true,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (swiftFiles.length > 0) {
|
|
97
|
+
console.error(`[iOS Enterprise] Analyzing ${swiftFiles.length} Swift files with SourceKitten...`);
|
|
98
|
+
|
|
99
|
+
const repoRoot = getRepoRoot();
|
|
100
|
+
const architectureDetector = new iOSArchitectureDetector(repoRoot);
|
|
101
|
+
const detectedPattern = architectureDetector.detect();
|
|
102
|
+
const detectionSummary = architectureDetector.getDetectionSummary();
|
|
103
|
+
|
|
104
|
+
console.error(`[iOS Architecture] Pattern detected: ${detectedPattern} (confidence: ${detectionSummary.confidence}%)`);
|
|
105
|
+
|
|
106
|
+
if (detectionSummary.warnings.length > 0) {
|
|
107
|
+
detectionSummary.warnings.forEach(warning => {
|
|
108
|
+
pushFinding(findings, {
|
|
109
|
+
ruleId: 'ios.architecture.detection_warning',
|
|
110
|
+
severity: warning.severity.toLowerCase(),
|
|
111
|
+
message: warning.message,
|
|
112
|
+
filePath: 'PROJECT_ROOT',
|
|
113
|
+
line: 1,
|
|
114
|
+
suggestion: warning.recommendation
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const architectureRules = new iOSArchitectureRules(findings, detectedPattern);
|
|
120
|
+
architectureRules.runRules(swiftFiles);
|
|
121
|
+
|
|
122
|
+
console.error(`[iOS Performance] Analyzing SwiftUI performance...`);
|
|
123
|
+
const performanceRules = new iOSPerformanceRules(findings);
|
|
124
|
+
swiftFiles.forEach(swiftFile => {
|
|
125
|
+
performanceRules.analyzeFile(swiftFile, null);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
console.error(`[iOS SwiftUI Advanced] Analyzing advanced patterns...`);
|
|
129
|
+
const swiftUIAdvanced = new iOSSwiftUIAdvancedRules(findings);
|
|
130
|
+
swiftFiles.forEach(swiftFile => {
|
|
131
|
+
swiftUIAdvanced.analyzeFile(swiftFile, null);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
console.error(`[iOS SPM] Analyzing code organization...`);
|
|
135
|
+
const spmRules = new iOSSPMRules(findings, repoRoot);
|
|
136
|
+
spmRules.analyze();
|
|
137
|
+
|
|
138
|
+
console.error(`[iOS Testing] Analyzing testing patterns...`);
|
|
139
|
+
const testingRules = new iOSTestingAdvancedRules(findings, repoRoot);
|
|
140
|
+
testingRules.analyze();
|
|
141
|
+
|
|
142
|
+
console.error(`[iOS Networking] Analyzing networking layer...`);
|
|
143
|
+
const networkingRules = new iOSNetworkingAdvancedRules(findings, repoRoot);
|
|
144
|
+
networkingRules.analyze();
|
|
145
|
+
|
|
146
|
+
console.error(`[iOS CI/CD] Analyzing CI/CD configuration...`);
|
|
147
|
+
const cicdRules = new iOSCICDRules(findings, repoRoot);
|
|
148
|
+
cicdRules.analyze();
|
|
149
|
+
|
|
150
|
+
const analyzer = new iOSEnterpriseAnalyzer();
|
|
151
|
+
|
|
152
|
+
for (const swiftFile of swiftFiles) {
|
|
153
|
+
await analyzer.analyzeFile(swiftFile, findings);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.error(`[iOS Enterprise] Completed Swift analysis`);
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(`[iOS Enterprise] Error during Swift analysis:`, error.message);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ═══════════════════════════════════════════════════════════════
|
|
163
|
+
// ═══════════════════════════════════════════════════════════════
|
|
164
|
+
project.getSourceFiles().forEach((sf) => {
|
|
165
|
+
if (!sf || typeof sf.getFilePath !== 'function') return;
|
|
166
|
+
const filePath = sf.getFilePath();
|
|
167
|
+
if (platformOf(filePath) !== "ios") return;
|
|
168
|
+
if (/\/ast-[^/]+\.js$/.test(filePath)) return;
|
|
169
|
+
|
|
170
|
+
const forbiddenLiteralsAnalyzer = new iOSForbiddenLiteralsAnalyzer();
|
|
171
|
+
forbiddenLiteralsAnalyzer.analyze(sf, findings, pushFinding);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
project.getSourceFiles().forEach((sf) => {
|
|
175
|
+
if (!sf || typeof sf.getFilePath !== 'function') return;
|
|
176
|
+
const filePath = sf.getFilePath();
|
|
177
|
+
if (platformOf(filePath) !== "ios" || !filePath.endsWith('.swift')) return;
|
|
178
|
+
|
|
179
|
+
const content = sf.getFullText();
|
|
180
|
+
|
|
181
|
+
const emptyCatchPattern = /catch\s*\{\s*\}/g;
|
|
182
|
+
let match;
|
|
183
|
+
while ((match = emptyCatchPattern.exec(content)) !== null) {
|
|
184
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
185
|
+
pushFinding(
|
|
186
|
+
"ios.error_handling.empty_catch",
|
|
187
|
+
"high",
|
|
188
|
+
sf,
|
|
189
|
+
sf,
|
|
190
|
+
`Line ${lineNumber}: Empty catch block - handle error with 'catch let error' or propagate with throws`,
|
|
191
|
+
findings
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (content.includes('_ = error') || content.includes('_ = err')) {
|
|
196
|
+
const errorIndex = content.indexOf('_ = error') !== -1 ? content.indexOf('_ = error') : content.indexOf('_ = err');
|
|
197
|
+
const lineNumber = content.substring(0, errorIndex).split('\n').length;
|
|
198
|
+
pushFinding(
|
|
199
|
+
"ios.error_handling.silenced_error",
|
|
200
|
+
"high",
|
|
201
|
+
sf,
|
|
202
|
+
sf,
|
|
203
|
+
`Line ${lineNumber}: NEVER silence errors with '_ = error' - handle with type check or propagate with throws`,
|
|
204
|
+
findings
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const forceTryPattern = /try!\s+/g;
|
|
209
|
+
while ((match = forceTryPattern.exec(content)) !== null) {
|
|
210
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
211
|
+
pushFinding(
|
|
212
|
+
"ios.error_handling.force_try",
|
|
213
|
+
"high",
|
|
214
|
+
sf,
|
|
215
|
+
sf,
|
|
216
|
+
`Line ${lineNumber}: NEVER use 'try!' - use proper do-catch or try? with nil handling`,
|
|
217
|
+
findings
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const anyTypePattern = /:\s*Any\b/g;
|
|
222
|
+
while ((match = anyTypePattern.exec(content)) !== null) {
|
|
223
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
224
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
225
|
+
const varName = lineText.match(/\b(\w+)\s*:\s*Any\b/)?.[1];
|
|
226
|
+
if (varName) {
|
|
227
|
+
const subsequentLines = content.split('\n').slice(lineNumber, lineNumber + 10).join('\n');
|
|
228
|
+
const hasTypeCheck = new RegExp(`${varName}\\s+is\\s+|${varName}\\s+as\\?|if\\s+let.*${varName}`).test(subsequentLines);
|
|
229
|
+
if (!hasTypeCheck) {
|
|
230
|
+
pushFinding(
|
|
231
|
+
"ios.typescript.any_without_guard",
|
|
232
|
+
"high",
|
|
233
|
+
sf,
|
|
234
|
+
sf,
|
|
235
|
+
`Line ${lineNumber}: Variable '${varName}: Any' used without type checking - use 'is', 'as?', or 'if let' guards`,
|
|
236
|
+
findings
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ==========================================
|
|
243
|
+
// ==========================================
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
const delegatePattern = /var\s+(\w*[Dd]elegate\w*)\s*:\s*\w+(?!\?)(?!\s*\?)/g;
|
|
247
|
+
while ((match = delegatePattern.exec(content)) !== null) {
|
|
248
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
249
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
250
|
+
if (!lineText.includes('weak')) {
|
|
251
|
+
pushFinding(
|
|
252
|
+
"ios.memory.delegate_not_weak",
|
|
253
|
+
"high",
|
|
254
|
+
sf,
|
|
255
|
+
sf,
|
|
256
|
+
`Line ${lineNumber}: Delegate '${match[1]}' should be weak to avoid retain cycles`,
|
|
257
|
+
findings
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const closurePattern = /\{\s*(?!\[weak|!\[unowned)[^}]{30,}\bself\b/g;
|
|
263
|
+
while ((match = closurePattern.exec(content)) !== null) {
|
|
264
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
265
|
+
const context = content.substring(match.index - 100, match.index + 100);
|
|
266
|
+
if (context.includes('escaping') || context.includes('@escaping')) {
|
|
267
|
+
pushFinding(
|
|
268
|
+
"ios.memory.closure_retain_cycle",
|
|
269
|
+
"high",
|
|
270
|
+
sf,
|
|
271
|
+
sf,
|
|
272
|
+
`Line ${lineNumber}: Escaping closure captures self - use [weak self] or [unowned self]`,
|
|
273
|
+
findings
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 2. CONCURRENCY
|
|
279
|
+
|
|
280
|
+
if (content.includes('DispatchQueue')) {
|
|
281
|
+
const lines = content.split('\n');
|
|
282
|
+
lines.forEach((line, idx) => {
|
|
283
|
+
if (line.includes('DispatchQueue') && !line.includes('//')) {
|
|
284
|
+
pushFinding(
|
|
285
|
+
"ios.concurrency.dispatch_queue",
|
|
286
|
+
"medium",
|
|
287
|
+
sf,
|
|
288
|
+
sf,
|
|
289
|
+
`Line ${idx + 1}: DispatchQueue detected - prefer async/await and actors for new code`,
|
|
290
|
+
findings
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const uiUpdatePattern = /func\s+\w+.*\{[^}]*\b(self\.|view\.|layer\.|bounds|frame|backgroundColor)/g;
|
|
297
|
+
while ((match = uiUpdatePattern.exec(content)) !== null) {
|
|
298
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
299
|
+
const funcBlock = content.substring(match.index, match.index + 200);
|
|
300
|
+
if (!funcBlock.includes('@MainActor') && funcBlock.includes('async')) {
|
|
301
|
+
pushFinding(
|
|
302
|
+
"ios.concurrency.missing_main_actor",
|
|
303
|
+
"high",
|
|
304
|
+
sf,
|
|
305
|
+
sf,
|
|
306
|
+
`Line ${lineNumber}: Async function with UI updates - add @MainActor annotation`,
|
|
307
|
+
findings
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
const implicitUnwrapPattern = /var\s+\w+\s*:\s*\w+!/g;
|
|
314
|
+
while ((match = implicitUnwrapPattern.exec(content)) !== null) {
|
|
315
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
316
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
317
|
+
if (!lineText.includes('@IBOutlet')) {
|
|
318
|
+
pushFinding(
|
|
319
|
+
"ios.optionals.implicitly_unwrapped",
|
|
320
|
+
"medium",
|
|
321
|
+
sf,
|
|
322
|
+
sf,
|
|
323
|
+
`Line ${lineNumber}: Implicitly unwrapped optional (!) - use regular optional (?) unless IBOutlet`,
|
|
324
|
+
findings
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 4. ARCHITECTURE
|
|
330
|
+
|
|
331
|
+
const singletonPattern = /static\s+(let|var)\s+(shared|instance)\s*=/g;
|
|
332
|
+
while ((match = singletonPattern.exec(content)) !== null) {
|
|
333
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
334
|
+
if (!content.includes('URLSession.shared') && !content.includes('NotificationCenter.default')) {
|
|
335
|
+
pushFinding(
|
|
336
|
+
"ios.architecture.singleton",
|
|
337
|
+
"high",
|
|
338
|
+
sf,
|
|
339
|
+
sf,
|
|
340
|
+
`Line ${lineNumber}: Singleton detected - use Dependency Injection instead`,
|
|
341
|
+
findings
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const viewControllerPattern = /class\s+(\w*ViewController|\w*View)\s*:/g;
|
|
347
|
+
while ((match = viewControllerPattern.exec(content)) !== null) {
|
|
348
|
+
const className = match[1];
|
|
349
|
+
const classStart = match.index;
|
|
350
|
+
const classEnd = content.indexOf('\n}\n', classStart);
|
|
351
|
+
if (classEnd > -1) {
|
|
352
|
+
const lines = content.substring(classStart, classEnd).split('\n').length;
|
|
353
|
+
if (lines > 300) {
|
|
354
|
+
pushFinding(
|
|
355
|
+
"ios.architecture.massive_view",
|
|
356
|
+
"high",
|
|
357
|
+
sf,
|
|
358
|
+
sf,
|
|
359
|
+
`${className} has ${lines} lines - break down into smaller components (max 300)`,
|
|
360
|
+
findings
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (content.includes(': View') || content.includes('SwiftUI.View')) {
|
|
367
|
+
const businessLogicPatterns = ['URLSession', 'Alamofire', 'fetch', 'api.', 'UserDefaults', 'CoreData'];
|
|
368
|
+
businessLogicPatterns.forEach(pattern => {
|
|
369
|
+
if (content.includes(pattern)) {
|
|
370
|
+
const lineNumber = content.indexOf(pattern) ? content.substring(0, content.indexOf(pattern)).split('\n').length : 1;
|
|
371
|
+
pushFinding(
|
|
372
|
+
"ios.architecture.business_logic_in_view",
|
|
373
|
+
"high",
|
|
374
|
+
sf,
|
|
375
|
+
sf,
|
|
376
|
+
`Line ${lineNumber}: Business logic in View - move to ViewModel or UseCase`,
|
|
377
|
+
findings
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 5. SECURITY
|
|
384
|
+
|
|
385
|
+
const secretPatterns = [
|
|
386
|
+
/api[_-]?key\s*=\s*["'][^"']+["']/gi,
|
|
387
|
+
/secret\s*=\s*["'][^"']+["']/gi,
|
|
388
|
+
/password\s*=\s*["'][^"']+["']/gi,
|
|
389
|
+
/token\s*=\s*["'][^"']+["']/gi
|
|
390
|
+
];
|
|
391
|
+
secretPatterns.forEach(pattern => {
|
|
392
|
+
let match;
|
|
393
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
394
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
395
|
+
pushFinding(
|
|
396
|
+
"ios.security.hardcoded_secret",
|
|
397
|
+
"critical",
|
|
398
|
+
sf,
|
|
399
|
+
sf,
|
|
400
|
+
`Line ${lineNumber}: Hardcoded secret detected - use Keychain or environment variables`,
|
|
401
|
+
findings
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
if (content.includes('UserDefaults') && (content.includes('password') || content.includes('token') || content.includes('secret'))) {
|
|
407
|
+
const lineNumber = content.indexOf('UserDefaults') ? content.substring(0, content.indexOf('UserDefaults')).split('\n').length : 1;
|
|
408
|
+
pushFinding(
|
|
409
|
+
"ios.security.userdefaults_sensitive",
|
|
410
|
+
"critical",
|
|
411
|
+
sf,
|
|
412
|
+
sf,
|
|
413
|
+
`Line ${lineNumber}: Sensitive data in UserDefaults - use Keychain instead`,
|
|
414
|
+
findings
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const httpPattern = /http:\/\/(?!localhost|127\.0\.0\.1)/g;
|
|
419
|
+
while ((match = httpPattern.exec(content)) !== null) {
|
|
420
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
421
|
+
pushFinding(
|
|
422
|
+
"ios.security.http_url",
|
|
423
|
+
"high",
|
|
424
|
+
sf,
|
|
425
|
+
sf,
|
|
426
|
+
`Line ${lineNumber}: HTTP URL detected - use HTTPS for security`,
|
|
427
|
+
findings
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// 6. LOCALIZATION
|
|
432
|
+
|
|
433
|
+
const hardcodedStringPattern = /Text\s*\(\s*"[^"]+"\s*\)|\.title\s*=\s*"[^"]+"|\.text\s*=\s*"[^"]+"/g;
|
|
434
|
+
let hardcodedCount = 0;
|
|
435
|
+
while ((match = hardcodedStringPattern.exec(content)) !== null && hardcodedCount < 5) {
|
|
436
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
437
|
+
const stringContent = match[0];
|
|
438
|
+
if (!stringContent.includes('NSLocalizedString') && !stringContent.match(/"(OK|Cancel|Yes|No|Error)"/)) {
|
|
439
|
+
pushFinding(
|
|
440
|
+
"ios.i18n.hardcoded_string",
|
|
441
|
+
"medium",
|
|
442
|
+
sf,
|
|
443
|
+
sf,
|
|
444
|
+
`Line ${lineNumber}: Hardcoded string - use NSLocalizedString for i18n`,
|
|
445
|
+
findings
|
|
446
|
+
);
|
|
447
|
+
hardcodedCount++;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 7. PERFORMANCE
|
|
452
|
+
|
|
453
|
+
if (content.includes('DispatchQueue.global') || content.includes('.background')) {
|
|
454
|
+
const uiPatterns = ['view.', 'layer.', 'backgroundColor', 'addSubview', 'removeFromSuperview'];
|
|
455
|
+
uiPatterns.forEach(pattern => {
|
|
456
|
+
const dispatchIndex = content.indexOf('DispatchQueue.global');
|
|
457
|
+
if (dispatchIndex > -1) {
|
|
458
|
+
const subsequentCode = content.substring(dispatchIndex, dispatchIndex + 500);
|
|
459
|
+
if (subsequentCode.includes(pattern)) {
|
|
460
|
+
const lineNumber = content.substring(0, dispatchIndex).split('\n').length;
|
|
461
|
+
pushFinding(
|
|
462
|
+
"ios.performance.ui_on_background",
|
|
463
|
+
"high",
|
|
464
|
+
sf,
|
|
465
|
+
sf,
|
|
466
|
+
`Line ${lineNumber}: UI update on background thread - wrap in DispatchQueue.main or @MainActor`,
|
|
467
|
+
findings
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
let geometryReaderCount = (content.match(/GeometryReader/g) || []).length;
|
|
476
|
+
if (geometryReaderCount > 3) {
|
|
477
|
+
pushFinding(
|
|
478
|
+
"ios.swiftui.geometry_reader_overuse",
|
|
479
|
+
"medium",
|
|
480
|
+
sf,
|
|
481
|
+
sf,
|
|
482
|
+
`File has ${geometryReaderCount} GeometryReader usages - use sparingly (impacts performance)`,
|
|
483
|
+
findings
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (content.includes('ObservableObject')) {
|
|
488
|
+
const varPattern = /var\s+\w+\s*:/g;
|
|
489
|
+
const publishedPattern = /@Published/g;
|
|
490
|
+
const varCount = (content.match(varPattern) || []).length;
|
|
491
|
+
const publishedCount = (content.match(publishedPattern) || []).length;
|
|
492
|
+
if (varCount > publishedCount + 2) {
|
|
493
|
+
pushFinding(
|
|
494
|
+
"ios.swiftui.missing_published",
|
|
495
|
+
"medium",
|
|
496
|
+
sf,
|
|
497
|
+
sf,
|
|
498
|
+
`ObservableObject with ${varCount - publishedCount} non-@Published vars - state changes won't trigger UI updates`,
|
|
499
|
+
findings
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const stateObjectPattern = /@StateObject.*=.*\(/g;
|
|
505
|
+
while ((match = stateObjectPattern.exec(content)) !== null) {
|
|
506
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
507
|
+
const nextLines = content.split('\n').slice(lineNumber, lineNumber + 3).join('\n');
|
|
508
|
+
if (nextLines.match(/init\s*\(/)) {
|
|
509
|
+
pushFinding(
|
|
510
|
+
"ios.swiftui.stateobject_in_init",
|
|
511
|
+
"high",
|
|
512
|
+
sf,
|
|
513
|
+
sf,
|
|
514
|
+
`Line ${lineNumber}: @StateObject created in init - will recreate on every init, use @ObservedObject instead`,
|
|
515
|
+
findings
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// 9. TESTING
|
|
521
|
+
|
|
522
|
+
if (content.includes('ViewModel') && !filePath.includes('Test') && !filePath.includes('Mock')) {
|
|
523
|
+
const className = content.match(/class\s+(\w+ViewModel)/)?.[1];
|
|
524
|
+
if (className) {
|
|
525
|
+
const testPath = filePath.replace(/\.swift$/, 'Tests.swift');
|
|
526
|
+
pushFinding(
|
|
527
|
+
"ios.testing.missing_viewmodel_tests",
|
|
528
|
+
"medium",
|
|
529
|
+
sf,
|
|
530
|
+
sf,
|
|
531
|
+
`ViewModel '${className}' - ensure test file exists: ${testPath}`,
|
|
532
|
+
findings
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (!filePath.includes('Test') && content.includes('XCTAssert')) {
|
|
538
|
+
const lineNumber = content.indexOf('XCTAssert') ? content.substring(0, content.indexOf('XCTAssert')).split('\n').length : 1;
|
|
539
|
+
pushFinding(
|
|
540
|
+
"ios.testing.xctest_in_production",
|
|
541
|
+
"high",
|
|
542
|
+
sf,
|
|
543
|
+
sf,
|
|
544
|
+
`Line ${lineNumber}: XCTest assertions in production code - move to test files`,
|
|
545
|
+
findings
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
const classInheritancePattern = /class\s+\w+\s*:\s*\w+[^,{]*\{/g;
|
|
551
|
+
while ((match = classInheritancePattern.exec(content)) !== null) {
|
|
552
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
553
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
554
|
+
if (!lineText.includes('Protocol') && !lineText.includes('ObservableObject') &&
|
|
555
|
+
!lineText.includes('UIViewController') && !lineText.includes('UIView') &&
|
|
556
|
+
!lineText.includes('NSObject')) {
|
|
557
|
+
pushFinding(
|
|
558
|
+
"ios.architecture.class_inheritance",
|
|
559
|
+
"medium",
|
|
560
|
+
sf,
|
|
561
|
+
sf,
|
|
562
|
+
`Line ${lineNumber}: Class inheritance detected - prefer protocol composition`,
|
|
563
|
+
findings
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// 11. VALUE TYPES
|
|
569
|
+
|
|
570
|
+
// class when struct would work
|
|
571
|
+
const classPattern = /class\s+(\w+)\s*(?::\s*Codable|:\s*Equatable|:\s*Hashable)?\s*\{/g;
|
|
572
|
+
while ((match = classPattern.exec(content)) !== null) {
|
|
573
|
+
const className = match[1];
|
|
574
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
575
|
+
if (!className.includes('ViewModel') && !className.includes('Controller') &&
|
|
576
|
+
!className.includes('Manager') && !className.includes('Service')) {
|
|
577
|
+
const classBlock = content.substring(match.index, content.indexOf('\n}\n', match.index) || match.index + 500);
|
|
578
|
+
if (!classBlock.includes('deinit') && !classBlock.includes('init?(')) {
|
|
579
|
+
pushFinding(
|
|
580
|
+
"ios.architecture.class_over_struct",
|
|
581
|
+
"low",
|
|
582
|
+
sf,
|
|
583
|
+
sf,
|
|
584
|
+
`Line ${lineNumber}: Class '${className}' might be better as struct - use struct unless you need identity`,
|
|
585
|
+
findings
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
const statePattern = /@State\s+var\s+(\w+)/g;
|
|
593
|
+
while ((match = statePattern.exec(content)) !== null) {
|
|
594
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
595
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
596
|
+
if (!lineText.includes('private')) {
|
|
597
|
+
pushFinding(
|
|
598
|
+
"ios.swiftui.state_not_private",
|
|
599
|
+
"medium",
|
|
600
|
+
sf,
|
|
601
|
+
sf,
|
|
602
|
+
`Line ${lineNumber}: @State var '${match[1]}' should be private - state should not be shared directly`,
|
|
603
|
+
findings
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// 13. NETWORKING
|
|
609
|
+
|
|
610
|
+
if (content.includes('URLSession') || content.includes('Alamofire')) {
|
|
611
|
+
const noErrorHandling = /\.dataTask|\.request.*\{(?!.*catch|.*error)[\s\S]{50,200}\}/g;
|
|
612
|
+
while ((match = noErrorHandling.exec(content)) !== null) {
|
|
613
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
614
|
+
pushFinding(
|
|
615
|
+
"ios.networking.missing_error_handling",
|
|
616
|
+
"high",
|
|
617
|
+
sf,
|
|
618
|
+
sf,
|
|
619
|
+
`Line ${lineNumber}: Network call without error handling - add do-catch or Result type`,
|
|
620
|
+
findings
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
const lineCount = content.split('\n').length;
|
|
627
|
+
if (lineCount > 500 && !filePath.includes('Generated')) {
|
|
628
|
+
pushFinding(
|
|
629
|
+
"ios.organization.file_too_large",
|
|
630
|
+
"medium",
|
|
631
|
+
sf,
|
|
632
|
+
sf,
|
|
633
|
+
`File has ${lineCount} lines - split into smaller files (max 500 lines)`,
|
|
634
|
+
findings
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (lineCount > 150 && !content.includes('MARK:')) {
|
|
639
|
+
pushFinding(
|
|
640
|
+
"ios.organization.missing_marks",
|
|
641
|
+
"low",
|
|
642
|
+
sf,
|
|
643
|
+
sf,
|
|
644
|
+
'Large file without MARK: comments - add section markers for better organization',
|
|
645
|
+
findings
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// 15. MAGIC NUMBERS
|
|
650
|
+
|
|
651
|
+
const magicNumberPattern = /\b\d{3,}\b/g;
|
|
652
|
+
let magicCount = 0;
|
|
653
|
+
while ((match = magicNumberPattern.exec(content)) !== null && magicCount < 5) {
|
|
654
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
655
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
656
|
+
if (!lineText.includes('//') && !lineText.includes('let') && !lineText.includes('case')) {
|
|
657
|
+
pushFinding(
|
|
658
|
+
"ios.code_quality.magic_number",
|
|
659
|
+
"low",
|
|
660
|
+
sf,
|
|
661
|
+
sf,
|
|
662
|
+
`Line ${lineNumber}: Magic number ${match[0]} - use named constant`,
|
|
663
|
+
findings
|
|
664
|
+
);
|
|
665
|
+
magicCount++;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// 16. ACCESSIBILITY
|
|
670
|
+
|
|
671
|
+
if (content.includes('Image(') || content.includes('Button(')) {
|
|
672
|
+
const noAccessibilityPattern = /(Image|Button)\([^)]+\)(?![^.]*\.accessibilityLabel)/g;
|
|
673
|
+
let accessibilityCount = 0;
|
|
674
|
+
while ((match = noAccessibilityPattern.exec(content)) !== null && accessibilityCount < 3) {
|
|
675
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
676
|
+
pushFinding(
|
|
677
|
+
"ios.accessibility.missing_label",
|
|
678
|
+
"medium",
|
|
679
|
+
sf,
|
|
680
|
+
sf,
|
|
681
|
+
`Line ${lineNumber}: ${match[1]} without accessibility label - add .accessibilityLabel()`,
|
|
682
|
+
findings
|
|
683
|
+
);
|
|
684
|
+
accessibilityCount++;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// 17. COMBINE
|
|
689
|
+
|
|
690
|
+
const combinePattern = /\.sink\s*\{|\.assign\s*\(/g;
|
|
691
|
+
let combineCount = (content.match(combinePattern) || []).length;
|
|
692
|
+
if (combineCount > 5 && !content.includes('// Combine required')) {
|
|
693
|
+
pushFinding(
|
|
694
|
+
"ios.combine.overuse",
|
|
695
|
+
"low",
|
|
696
|
+
sf,
|
|
697
|
+
sf,
|
|
698
|
+
`File has ${combineCount} Combine operators - consider async/await for simpler async operations`,
|
|
699
|
+
findings
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (content.includes('.sink') && !content.includes('Set<AnyCancellable>') && !content.includes('AnyCancellable')) {
|
|
704
|
+
const lineNumber = content.indexOf('.sink') ? content.substring(0, content.indexOf('.sink')).split('\n').length : 1;
|
|
705
|
+
pushFinding(
|
|
706
|
+
"ios.combine.missing_cancellable_storage",
|
|
707
|
+
"high",
|
|
708
|
+
sf,
|
|
709
|
+
sf,
|
|
710
|
+
`Line ${lineNumber}: Combine subscription without AnyCancellable storage - will leak memory`,
|
|
711
|
+
findings
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
const manualInstantiationPattern = /=\s*\w+(Service|Repository|Manager|Client|API)\s*\(\)/g;
|
|
717
|
+
while ((match = manualInstantiationPattern.exec(content)) !== null) {
|
|
718
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
719
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
720
|
+
if (!lineText.includes('init') && !lineText.includes('@Injected') && !lineText.includes('Environment')) {
|
|
721
|
+
pushFinding(
|
|
722
|
+
"ios.di.manual_instantiation",
|
|
723
|
+
"high",
|
|
724
|
+
sf,
|
|
725
|
+
sf,
|
|
726
|
+
`Line ${lineNumber}: Manual dependency instantiation - inject via initializer or @EnvironmentObject`,
|
|
727
|
+
findings
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (content.includes('class') && content.match(/init\([^)]{50,}\)/)) {
|
|
733
|
+
const complexInitPattern = /init\([^)]{50,}\)/g;
|
|
734
|
+
while ((match = complexInitPattern.exec(content)) !== null) {
|
|
735
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
736
|
+
pushFinding(
|
|
737
|
+
"ios.di.complex_init",
|
|
738
|
+
"medium",
|
|
739
|
+
sf,
|
|
740
|
+
sf,
|
|
741
|
+
`Line ${lineNumber}: Complex initializer with many parameters - consider Factory pattern`,
|
|
742
|
+
findings
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// 19. PERSISTENCE
|
|
748
|
+
|
|
749
|
+
if (content.includes('NSFetchRequest') && content.includes('NSManagedObject')) {
|
|
750
|
+
const rawFetchPattern = /NSFetchRequest<NSManagedObject>/g;
|
|
751
|
+
while ((match = rawFetchPattern.exec(content)) !== null) {
|
|
752
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
753
|
+
pushFinding(
|
|
754
|
+
"ios.persistence.raw_fetch",
|
|
755
|
+
"medium",
|
|
756
|
+
sf,
|
|
757
|
+
sf,
|
|
758
|
+
`Line ${lineNumber}: Raw NSFetchRequest<NSManagedObject> - use typed fetch NSFetchRequest<YourEntity>`,
|
|
759
|
+
findings
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (content.includes('@Model') || content.includes('SwiftData')) {
|
|
765
|
+
if (!content.includes('@available(iOS 17') && !content.includes('if #available(iOS 17')) {
|
|
766
|
+
const lineNumber = content.indexOf('@Model') || content.indexOf('SwiftData');
|
|
767
|
+
if (lineNumber > -1) {
|
|
768
|
+
pushFinding(
|
|
769
|
+
"ios.persistence.swiftdata_availability",
|
|
770
|
+
"high",
|
|
771
|
+
sf,
|
|
772
|
+
sf,
|
|
773
|
+
`SwiftData requires iOS 17+ - add @available(iOS 17, *) check`,
|
|
774
|
+
findings
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
const storyboardPatterns = [
|
|
782
|
+
/UIStoryboard\s*\(/g,
|
|
783
|
+
/instantiateViewController/g,
|
|
784
|
+
/UINib\s*\(/g,
|
|
785
|
+
/loadNibNamed/g,
|
|
786
|
+
/@IBOutlet.*weak.*UI/g
|
|
787
|
+
];
|
|
788
|
+
storyboardPatterns.forEach(pattern => {
|
|
789
|
+
let match;
|
|
790
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
791
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
792
|
+
pushFinding(
|
|
793
|
+
"ios.uikit.storyboards",
|
|
794
|
+
"medium",
|
|
795
|
+
sf,
|
|
796
|
+
sf,
|
|
797
|
+
`Line ${lineNumber}: Storyboard/XIB usage - prefer programmatic UI (better version control & testing)`,
|
|
798
|
+
findings
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
if (content.includes('UIView') && content.includes('.frame =')) {
|
|
804
|
+
const framePattern = /\.frame\s*=\s*CGRect/g;
|
|
805
|
+
let frameCount = 0;
|
|
806
|
+
while ((match = framePattern.exec(content)) !== null && frameCount < 3) {
|
|
807
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
808
|
+
pushFinding(
|
|
809
|
+
"ios.uikit.frame_layout",
|
|
810
|
+
"medium",
|
|
811
|
+
sf,
|
|
812
|
+
sf,
|
|
813
|
+
`Line ${lineNumber}: Manual frame layout - use Auto Layout or SwiftUI instead`,
|
|
814
|
+
findings
|
|
815
|
+
);
|
|
816
|
+
frameCount++;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
if (filePath.includes('Test') && content.includes('XCTest')) {
|
|
822
|
+
if (!content.includes('makeSUT') && !content.includes('func make')) {
|
|
823
|
+
pushFinding(
|
|
824
|
+
"ios.testing.missing_make_sut",
|
|
825
|
+
"medium",
|
|
826
|
+
sf,
|
|
827
|
+
sf,
|
|
828
|
+
'Test file without makeSUT factory - extract SUT creation for reusability',
|
|
829
|
+
findings
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (!content.includes('trackForMemoryLeaks') && !content.includes('addTeardownBlock')) {
|
|
834
|
+
pushFinding(
|
|
835
|
+
"ios.testing.missing_leak_tracking",
|
|
836
|
+
"medium",
|
|
837
|
+
sf,
|
|
838
|
+
sf,
|
|
839
|
+
'Test file without memory leak tracking - add trackForMemoryLeaks helper',
|
|
840
|
+
findings
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (!filePath.includes('Test') && (content.includes('Mock') || content.includes('Spy') || content.includes('Stub'))) {
|
|
846
|
+
const mockPattern = /\b(Mock|Spy|Stub)\w+/g;
|
|
847
|
+
while ((match = mockPattern.exec(content)) !== null) {
|
|
848
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
849
|
+
pushFinding(
|
|
850
|
+
"ios.testing.mock_in_production",
|
|
851
|
+
"critical",
|
|
852
|
+
sf,
|
|
853
|
+
sf,
|
|
854
|
+
`Line ${lineNumber}: Test double '${match[0]}' in production code - move to test target`,
|
|
855
|
+
findings
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
if (content.includes('Coordinator')) {
|
|
862
|
+
const childCoordinatorPattern = /var\s+\w*[Cc]oordinators?\w*\s*:\s*\[/g;
|
|
863
|
+
while ((match = childCoordinatorPattern.exec(content)) !== null) {
|
|
864
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
865
|
+
const lineText = content.split('\n')[lineNumber - 1];
|
|
866
|
+
if (!lineText.includes('weak')) {
|
|
867
|
+
pushFinding(
|
|
868
|
+
"ios.architecture.coordinator_strong_children",
|
|
869
|
+
"high",
|
|
870
|
+
sf,
|
|
871
|
+
sf,
|
|
872
|
+
`Line ${lineNumber}: Child coordinators array should use weak references to avoid retain cycles`,
|
|
873
|
+
findings
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (content.includes('Presenter') && content.includes('Interactor') && content.includes('Router')) {
|
|
880
|
+
const lineCount = content.split('\n').length;
|
|
881
|
+
if (lineCount < 100) {
|
|
882
|
+
pushFinding(
|
|
883
|
+
"ios.architecture.viper_overkill",
|
|
884
|
+
"low",
|
|
885
|
+
sf,
|
|
886
|
+
sf,
|
|
887
|
+
'VIPER pattern for small feature (<100 lines) - consider simpler MVVM',
|
|
888
|
+
findings
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
if (content.includes('UILabel') || content.includes('UITextView') || content.includes('UITextField')) {
|
|
895
|
+
if (!content.includes('adjustsFontForContentSizeCategory') && !content.includes('.font = .preferredFont')) {
|
|
896
|
+
pushFinding(
|
|
897
|
+
"ios.accessibility.dynamic_type",
|
|
898
|
+
"medium",
|
|
899
|
+
sf,
|
|
900
|
+
sf,
|
|
901
|
+
'UI text elements without Dynamic Type support - use .preferredFont(forTextStyle:)',
|
|
902
|
+
findings
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (content.includes('isAccessibilityElement = true') && !content.includes('accessibilityTraits')) {
|
|
908
|
+
const lineNumber = content.indexOf('isAccessibilityElement = true');
|
|
909
|
+
if (lineNumber > -1) {
|
|
910
|
+
pushFinding(
|
|
911
|
+
"ios.accessibility.missing_traits",
|
|
912
|
+
"medium",
|
|
913
|
+
sf,
|
|
914
|
+
sf,
|
|
915
|
+
'Accessibility element without traits - add .accessibilityTraits for better VoiceOver experience',
|
|
916
|
+
findings
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (content.includes('animate') || content.includes('UIView.transition')) {
|
|
922
|
+
if (!content.includes('UIAccessibility.isReduceMotionEnabled') && !content.includes('@Environment(\\.accessibilityReduceMotion)')) {
|
|
923
|
+
pushFinding(
|
|
924
|
+
"ios.accessibility.reduce_motion",
|
|
925
|
+
"medium",
|
|
926
|
+
sf,
|
|
927
|
+
sf,
|
|
928
|
+
'Animation without Reduce Motion check - respect user preference with UIAccessibility.isReduceMotionEnabled',
|
|
929
|
+
findings
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
if ((content.includes('URLSession') || content.includes('Alamofire')) && content.includes('func')) {
|
|
936
|
+
if (!content.includes('retry') && !content.includes('maxRetries') && !filePath.includes('Mock')) {
|
|
937
|
+
pushFinding(
|
|
938
|
+
"ios.networking.missing_retry",
|
|
939
|
+
"medium",
|
|
940
|
+
sf,
|
|
941
|
+
sf,
|
|
942
|
+
'Network layer without retry logic - add exponential backoff for failed requests',
|
|
943
|
+
findings
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (content.includes('URLSession') && (content.includes('api') || content.includes('backend'))) {
|
|
949
|
+
if (!content.includes('pinning') && !content.includes('URLSessionDelegate')) {
|
|
950
|
+
pushFinding(
|
|
951
|
+
"ios.networking.missing_ssl_pinning",
|
|
952
|
+
"high",
|
|
953
|
+
sf,
|
|
954
|
+
sf,
|
|
955
|
+
'API client without SSL pinning - add certificate pinning for sensitive endpoints',
|
|
956
|
+
findings
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if ((content.includes('URLSession') || content.includes('Alamofire')) && !content.includes('Reachability') && !content.includes('NWPathMonitor')) {
|
|
962
|
+
pushFinding(
|
|
963
|
+
"ios.networking.missing_reachability",
|
|
964
|
+
"medium",
|
|
965
|
+
sf,
|
|
966
|
+
sf,
|
|
967
|
+
'Network layer without reachability check - add Network framework monitoring',
|
|
968
|
+
findings
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
if (content.includes('public') && filePath.includes('Sources/')) {
|
|
974
|
+
const publicPattern = /public\s+(class|struct|enum|func|var|let)/g;
|
|
975
|
+
const publicCount = (content.match(publicPattern) || []).length;
|
|
976
|
+
if (publicCount > 20) {
|
|
977
|
+
pushFinding(
|
|
978
|
+
"ios.spm.excessive_public_api",
|
|
979
|
+
"medium",
|
|
980
|
+
sf,
|
|
981
|
+
sf,
|
|
982
|
+
`Package exposes ${publicCount} public symbols - reduce public API surface, prefer internal`,
|
|
983
|
+
findings
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const isModularProject = content.includes('import ') && (content.match(/import \w+/g) || []).length > 10;
|
|
989
|
+
if (isModularProject && !filePath.includes('Package.swift')) {
|
|
990
|
+
const fs = require('fs');
|
|
991
|
+
const packagePath = path.join(getRepoRoot(), 'Package.swift');
|
|
992
|
+
if (!fs.existsSync(packagePath)) {
|
|
993
|
+
pushFinding(
|
|
994
|
+
"ios.spm.missing_package_swift",
|
|
995
|
+
"low",
|
|
996
|
+
sf,
|
|
997
|
+
sf,
|
|
998
|
+
'Project with many imports - consider SPM modularization with Package.swift',
|
|
999
|
+
findings
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
const forceCastPattern = /as!\s+/g;
|
|
1006
|
+
while ((match = forceCastPattern.exec(content)) !== null) {
|
|
1007
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1008
|
+
pushFinding(
|
|
1009
|
+
"ios.code_quality.force_cast",
|
|
1010
|
+
"high",
|
|
1011
|
+
sf,
|
|
1012
|
+
sf,
|
|
1013
|
+
`Line ${lineNumber}: Force cast 'as!' detected - use 'as?' with nil handling instead`,
|
|
1014
|
+
findings
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if (!filePath.includes('Test')) {
|
|
1019
|
+
const todoPattern = /\/\/\s*(TODO|FIXME):/g;
|
|
1020
|
+
let todoCount = 0;
|
|
1021
|
+
while ((match = todoPattern.exec(content)) !== null && todoCount < 3) {
|
|
1022
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1023
|
+
pushFinding(
|
|
1024
|
+
"ios.code_quality.todo_fixme",
|
|
1025
|
+
"low",
|
|
1026
|
+
sf,
|
|
1027
|
+
sf,
|
|
1028
|
+
`Line ${lineNumber}: ${match[1]} comment - resolve before production release`,
|
|
1029
|
+
findings
|
|
1030
|
+
);
|
|
1031
|
+
todoCount++;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (content.includes('#warning') || content.includes('@available(*, deprecated')) {
|
|
1036
|
+
pushFinding(
|
|
1037
|
+
"ios.code_quality.warnings_present",
|
|
1038
|
+
"medium",
|
|
1039
|
+
sf,
|
|
1040
|
+
sf,
|
|
1041
|
+
'Build warnings present - resolve all warnings before production deployment',
|
|
1042
|
+
findings
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// ==========================================
|
|
1047
|
+
// ==========================================
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
const typePattern = /\b(class|struct|enum|protocol)\s+\w+/g;
|
|
1051
|
+
const classCount = (content.match(typePattern) || []).length;
|
|
1052
|
+
if (classCount > 3 && !filePath.includes('Generated')) {
|
|
1053
|
+
pushFinding(
|
|
1054
|
+
"ios.solid.srp_multiple_types",
|
|
1055
|
+
"high",
|
|
1056
|
+
sf,
|
|
1057
|
+
sf,
|
|
1058
|
+
`File defines ${classCount} types - split into separate files (SRP: one responsibility per file)`,
|
|
1059
|
+
findings
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (content.includes('class') || content.includes('struct')) {
|
|
1064
|
+
const funcPattern = /func\s+\w+/g;
|
|
1065
|
+
const funcCount = (content.match(funcPattern) || []).length;
|
|
1066
|
+
if (funcCount > 20) {
|
|
1067
|
+
pushFinding(
|
|
1068
|
+
"ios.solid.srp_god_class",
|
|
1069
|
+
"critical",
|
|
1070
|
+
sf,
|
|
1071
|
+
sf,
|
|
1072
|
+
`Type has ${funcCount} methods - split responsibilities (SRP: classes should have one reason to change)`,
|
|
1073
|
+
findings
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
// switch/if-else chains that should be polymorphism
|
|
1080
|
+
const switchPattern = /switch\s+\w+\s*\{[^}]{200,}\}/g;
|
|
1081
|
+
let switchCount = 0;
|
|
1082
|
+
while ((match = switchPattern.exec(content)) !== null && switchCount < 3) {
|
|
1083
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1084
|
+
const caseCount = (match[0].match(/case/g) || []).length;
|
|
1085
|
+
if (caseCount > 5) {
|
|
1086
|
+
pushFinding(
|
|
1087
|
+
"ios.solid.ocp_switch_polymorphism",
|
|
1088
|
+
"high",
|
|
1089
|
+
sf,
|
|
1090
|
+
sf,
|
|
1091
|
+
`Line ${lineNumber}: Large switch (${caseCount} cases) - consider protocol + extensions (OCP: open for extension, closed for modification)`,
|
|
1092
|
+
findings
|
|
1093
|
+
);
|
|
1094
|
+
switchCount++;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
if (content.includes('extension') && content.includes('override')) {
|
|
1099
|
+
pushFinding(
|
|
1100
|
+
"ios.solid.ocp_modification",
|
|
1101
|
+
"medium",
|
|
1102
|
+
sf,
|
|
1103
|
+
sf,
|
|
1104
|
+
'Extension with override - prefer protocol extensions or composition (OCP: extend, not modify)',
|
|
1105
|
+
findings
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
const overridePattern = /override\s+func\s+(\w+)[^{]*\{[^}]*throw/g;
|
|
1111
|
+
while ((match = overridePattern.exec(content)) !== null) {
|
|
1112
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1113
|
+
pushFinding(
|
|
1114
|
+
"ios.solid.lsp_throws_violation",
|
|
1115
|
+
"high",
|
|
1116
|
+
sf,
|
|
1117
|
+
sf,
|
|
1118
|
+
`Line ${lineNumber}: Override throws error - ensure parent signature matches (LSP: subtypes must be substitutable)`,
|
|
1119
|
+
findings
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if (content.includes('override') && content.includes('precondition(') || content.includes('assert(')) {
|
|
1124
|
+
const overrideBlocks = content.match(/override[^}]+\{[^}]+\}/g) || [];
|
|
1125
|
+
overrideBlocks.forEach(block => {
|
|
1126
|
+
if (block.includes('precondition') || block.includes('assert')) {
|
|
1127
|
+
const lineNumber = content.indexOf(block) > -1 ? content.substring(0, content.indexOf(block)).split('\n').length : 1;
|
|
1128
|
+
pushFinding(
|
|
1129
|
+
"ios.solid.lsp_precondition",
|
|
1130
|
+
"high",
|
|
1131
|
+
sf,
|
|
1132
|
+
sf,
|
|
1133
|
+
`Line ${lineNumber}: Override adds preconditions - weakens contract (LSP: don't strengthen preconditions)`,
|
|
1134
|
+
findings
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
const protocolPattern = /protocol\s+(\w+)[^{]*\{([^}]+)\}/g;
|
|
1142
|
+
while ((match = protocolPattern.exec(content)) !== null) {
|
|
1143
|
+
const protocolName = match[1];
|
|
1144
|
+
const protocolBody = match[2];
|
|
1145
|
+
const reqCount = (protocolBody.match(/func\s+/g) || []).length + (protocolBody.match(/var\s+/g) || []).length;
|
|
1146
|
+
if (reqCount > 10) {
|
|
1147
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1148
|
+
pushFinding(
|
|
1149
|
+
"ios.solid.isp_fat_protocol",
|
|
1150
|
+
"high",
|
|
1151
|
+
sf,
|
|
1152
|
+
sf,
|
|
1153
|
+
`Line ${lineNumber}: Protocol '${protocolName}' has ${reqCount} requirements - split into smaller protocols (ISP: clients shouldn't depend on unused methods)`,
|
|
1154
|
+
findings
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
if ((content.includes('ViewModel') || content.includes('UseCase')) &&
|
|
1161
|
+
(content.includes('URLSession') || content.includes('UserDefaults') || content.includes('CoreData'))) {
|
|
1162
|
+
const lineNumber = content.indexOf('URLSession') || content.indexOf('UserDefaults') || content.indexOf('CoreData');
|
|
1163
|
+
if (lineNumber > -1) {
|
|
1164
|
+
const actualLine = content.substring(0, lineNumber).split('\n').length;
|
|
1165
|
+
pushFinding(
|
|
1166
|
+
"ios.solid.dip_concrete_dependency",
|
|
1167
|
+
"critical",
|
|
1168
|
+
sf,
|
|
1169
|
+
sf,
|
|
1170
|
+
`Line ${actualLine}: High-level module depends on concrete implementation - inject protocol instead (DIP: depend on abstractions)`,
|
|
1171
|
+
findings
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
if ((content.includes('class') || content.includes('struct')) &&
|
|
1177
|
+
(content.match(/Repository|Service|Manager|Client/) && !content.match(/Protocol/))) {
|
|
1178
|
+
const className = content.match(/(class|struct)\s+(\w+(?:Repository|Service|Manager|Client))/)?.[2];
|
|
1179
|
+
if (className && !content.includes(`protocol ${className.replace(/Impl$/, '')}Protocol`)) {
|
|
1180
|
+
pushFinding(
|
|
1181
|
+
"ios.solid.dip_missing_abstraction",
|
|
1182
|
+
"high",
|
|
1183
|
+
sf,
|
|
1184
|
+
sf,
|
|
1185
|
+
`${className} without protocol abstraction - create protocol for testability (DIP: high-level shouldn't know low-level)`,
|
|
1186
|
+
findings
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// ==========================================
|
|
1192
|
+
// ==========================================
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
if (filePath.includes('/Domain/')) {
|
|
1196
|
+
const forbiddenImports = ['UIKit', 'SwiftUI', 'Alamofire', 'CoreData', 'UserDefaults'];
|
|
1197
|
+
forbiddenImports.forEach(forbidden => {
|
|
1198
|
+
if (content.includes(`import ${forbidden}`)) {
|
|
1199
|
+
pushFinding(
|
|
1200
|
+
"ios.clean_arch.domain_dependency",
|
|
1201
|
+
"critical",
|
|
1202
|
+
sf,
|
|
1203
|
+
sf,
|
|
1204
|
+
`Domain layer imports ${forbidden} - Domain must be framework-agnostic (Clean Arch: dependencies point inward)`,
|
|
1205
|
+
findings
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (filePath.includes('/Application/') && filePath.includes('ViewModel')) {
|
|
1212
|
+
if (content.includes('URLSession') || content.includes('Alamofire') || content.includes('CoreData')) {
|
|
1213
|
+
pushFinding(
|
|
1214
|
+
"ios.clean_arch.application_dependency",
|
|
1215
|
+
"high",
|
|
1216
|
+
sf,
|
|
1217
|
+
sf,
|
|
1218
|
+
'ViewModel depends on infrastructure details - inject repository protocol instead (Clean Arch: Application uses Domain protocols)',
|
|
1219
|
+
findings
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (filePath.includes('/Presentation/') || content.includes(': View') || content.includes(': UIViewController')) {
|
|
1225
|
+
const businessPatterns = ['URLSession', 'fetch(', 'save(', 'delete(', 'update('];
|
|
1226
|
+
businessPatterns.forEach(pattern => {
|
|
1227
|
+
if (content.includes(pattern)) {
|
|
1228
|
+
const lineNumber = content.indexOf(pattern) > -1 ? content.substring(0, content.indexOf(pattern)).split('\n').length : 1;
|
|
1229
|
+
pushFinding(
|
|
1230
|
+
"ios.clean_arch.presentation_business_logic",
|
|
1231
|
+
"critical",
|
|
1232
|
+
sf,
|
|
1233
|
+
sf,
|
|
1234
|
+
`Line ${lineNumber}: Business logic in Presentation - move to UseCase/ViewModel (Clean Arch: Presentation only coordinates)`,
|
|
1235
|
+
findings
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
if (filePath.includes('/Utilities/') || filePath.includes('/Helpers/')) {
|
|
1243
|
+
pushFinding(
|
|
1244
|
+
"ios.clean_arch.forbidden_directory",
|
|
1245
|
+
"critical",
|
|
1246
|
+
sf,
|
|
1247
|
+
sf,
|
|
1248
|
+
'Utilities/Helpers directory forbidden - move to Domain/ or Infrastructure/ (Clean Arch: no utility dumping ground)',
|
|
1249
|
+
findings
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (filePath.endsWith('.swift') && !filePath.includes('/Domain/') && !filePath.includes('/Application/') &&
|
|
1254
|
+
!filePath.includes('/Infrastructure/') && !filePath.includes('/Presentation/') &&
|
|
1255
|
+
!filePath.includes('/Tests/') && !filePath.includes('AppDelegate')) {
|
|
1256
|
+
pushFinding(
|
|
1257
|
+
"ios.clean_arch.root_code",
|
|
1258
|
+
"high",
|
|
1259
|
+
sf,
|
|
1260
|
+
sf,
|
|
1261
|
+
'Swift file in project root - organize into Domain/Application/Infrastructure/Presentation layers',
|
|
1262
|
+
findings
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
if (content.includes('Repository') && !content.includes('Protocol') && !filePath.includes('/Infrastructure/')) {
|
|
1268
|
+
pushFinding(
|
|
1269
|
+
"ios.clean_arch.repository_location",
|
|
1270
|
+
"high",
|
|
1271
|
+
sf,
|
|
1272
|
+
sf,
|
|
1273
|
+
'Repository implementation outside Infrastructure/ - move to Infrastructure/Repositories/',
|
|
1274
|
+
findings
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (content.includes('protocol') && content.includes('Repository') && !filePath.includes('/Domain/')) {
|
|
1279
|
+
pushFinding(
|
|
1280
|
+
"ios.clean_arch.repository_protocol_location",
|
|
1281
|
+
"high",
|
|
1282
|
+
sf,
|
|
1283
|
+
sf,
|
|
1284
|
+
'Repository protocol outside Domain/ - move to Domain/Repositories/',
|
|
1285
|
+
findings
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// ==========================================
|
|
1290
|
+
// ==========================================
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
if (filePath.includes('Test') && content.includes('XCTest')) {
|
|
1294
|
+
const testFunctions = content.match(/func\s+test\w+\(\)/g) || [];
|
|
1295
|
+
testFunctions.forEach(testFunc => {
|
|
1296
|
+
const funcName = testFunc.match(/func\s+(test\w+)/)?.[1];
|
|
1297
|
+
if (funcName && !funcName.includes('_') && funcName.length > 15) {
|
|
1298
|
+
const lineNumber = content.indexOf(testFunc) > -1 ? content.substring(0, content.indexOf(testFunc)).split('\n').length : 1;
|
|
1299
|
+
pushFinding(
|
|
1300
|
+
"ios.bdd.test_naming",
|
|
1301
|
+
"medium",
|
|
1302
|
+
sf,
|
|
1303
|
+
sf,
|
|
1304
|
+
`Line ${lineNumber}: Test '${funcName}' - use BDD naming: test_givenX_whenY_thenZ or testGivenXWhenYThenZ`,
|
|
1305
|
+
findings
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
if (content.includes('Mock') && !content.includes('Spy')) {
|
|
1311
|
+
pushFinding(
|
|
1312
|
+
"ios.bdd.prefer_spies",
|
|
1313
|
+
"low",
|
|
1314
|
+
sf,
|
|
1315
|
+
sf,
|
|
1316
|
+
'Test uses Mocks - prefer Spies (verify real behavior) over Mocks (BDD: test behavior, not implementation)',
|
|
1317
|
+
findings
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// ==========================================
|
|
1323
|
+
// ==========================================
|
|
1324
|
+
|
|
1325
|
+
|
|
1326
|
+
const commentPattern = /\/\/(?!\s*MARK:)(?!\s*TODO:)(?!\s*FIXME:)(?!\s*swiftlint)[^\n]{20,}/g;
|
|
1327
|
+
let commentCount = 0;
|
|
1328
|
+
while ((match = commentPattern.exec(content)) !== null && commentCount < 5) {
|
|
1329
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1330
|
+
const commentText = match[0].substring(0, 50);
|
|
1331
|
+
pushFinding(
|
|
1332
|
+
"ios.code_quality.comment",
|
|
1333
|
+
"medium",
|
|
1334
|
+
sf,
|
|
1335
|
+
sf,
|
|
1336
|
+
`Line ${lineNumber}: Comment detected '${commentText}...' - refactor to self-descriptive code (No comments rule)`,
|
|
1337
|
+
findings
|
|
1338
|
+
);
|
|
1339
|
+
commentCount++;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
const varPattern = /\bvar\s+\w+/g;
|
|
1344
|
+
const letPattern = /\blet\s+\w+/g;
|
|
1345
|
+
const varCount = (content.match(varPattern) || []).length;
|
|
1346
|
+
const letCount = (content.match(letPattern) || []).length;
|
|
1347
|
+
if (varCount > letCount && varCount > 10) {
|
|
1348
|
+
pushFinding(
|
|
1349
|
+
"ios.value_types.prefer_let",
|
|
1350
|
+
"medium",
|
|
1351
|
+
sf,
|
|
1352
|
+
sf,
|
|
1353
|
+
`File has ${varCount} 'var' vs ${letCount} 'let' - prefer immutability with 'let' (Value Types: immutability first)`,
|
|
1354
|
+
findings
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
if (content.includes('struct') && !content.includes('Equatable') && !content.includes('Hashable')) {
|
|
1360
|
+
const structPattern = /struct\s+(\w+)/g;
|
|
1361
|
+
let structCount = 0;
|
|
1362
|
+
while ((match = structPattern.exec(content)) !== null && structCount < 3) {
|
|
1363
|
+
const structName = match[1];
|
|
1364
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1365
|
+
if (!structName.includes('Private') && !structName.includes('Internal')) {
|
|
1366
|
+
pushFinding(
|
|
1367
|
+
"ios.value_types.missing_protocols",
|
|
1368
|
+
"low",
|
|
1369
|
+
sf,
|
|
1370
|
+
sf,
|
|
1371
|
+
`Line ${lineNumber}: struct '${structName}' - add Equatable/Hashable conformance for value semantics`,
|
|
1372
|
+
findings
|
|
1373
|
+
);
|
|
1374
|
+
structCount++;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
const pyramidPattern = /if\s+[^{]+\{[^}]*if\s+[^{]+\{[^}]*if\s+[^{]+\{/g;
|
|
1381
|
+
while ((match = pyramidPattern.exec(content)) !== null) {
|
|
1382
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1383
|
+
pushFinding(
|
|
1384
|
+
"ios.code_quality.pyramid_doom",
|
|
1385
|
+
"high",
|
|
1386
|
+
sf,
|
|
1387
|
+
sf,
|
|
1388
|
+
`Line ${lineNumber}: Nested if statements (pyramid of doom) - use guard clauses for early returns`,
|
|
1389
|
+
findings
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
if ((filePath.includes('/Models/') || filePath.includes('/Views/') || filePath.includes('/Controllers/')) &&
|
|
1395
|
+
!filePath.includes('/Features/') && !filePath.includes('/Domain/')) {
|
|
1396
|
+
pushFinding(
|
|
1397
|
+
"ios.ddd.technical_grouping",
|
|
1398
|
+
"low",
|
|
1399
|
+
sf,
|
|
1400
|
+
sf,
|
|
1401
|
+
'Technical grouping (Models/Views/Controllers) - consider feature-first organization (DDD: group by domain)',
|
|
1402
|
+
findings
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// 41. struct vs class (prefer value types)
|
|
1407
|
+
if (content.includes('class') && content.includes('Codable')) {
|
|
1408
|
+
const classPattern = /class\s+(\w+).*:.*Codable/g;
|
|
1409
|
+
while ((match = classPattern.exec(content)) !== null) {
|
|
1410
|
+
const className = match[1];
|
|
1411
|
+
if (!className.includes('ViewModel') && !className.includes('Controller')) {
|
|
1412
|
+
pushFinding(
|
|
1413
|
+
"ios.value_types.prefer_struct",
|
|
1414
|
+
"medium",
|
|
1415
|
+
sf,
|
|
1416
|
+
sf,
|
|
1417
|
+
`Class '${className}' with Codable - consider struct for value semantics (struct por defecto)`,
|
|
1418
|
+
findings
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
if (content.includes('ObservableObject') && content.includes('var ') && !content.includes('@Published')) {
|
|
1425
|
+
pushFinding(
|
|
1426
|
+
"ios.swiftui.missing_published",
|
|
1427
|
+
"high",
|
|
1428
|
+
sf,
|
|
1429
|
+
sf,
|
|
1430
|
+
'ObservableObject without @Published properties - ViewModels need @Published for reactive updates',
|
|
1431
|
+
findings
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if ((content.includes('UIView') || content.includes('View:')) && content.includes('DispatchQueue.main') && !content.includes('@MainActor')) {
|
|
1436
|
+
pushFinding(
|
|
1437
|
+
"ios.concurrency.missing_main_actor",
|
|
1438
|
+
"high",
|
|
1439
|
+
sf,
|
|
1440
|
+
sf,
|
|
1441
|
+
'UI updates with DispatchQueue.main instead of @MainActor - use Swift Concurrency @MainActor',
|
|
1442
|
+
findings
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// 44. actor keyword usage (thread-safe state)
|
|
1447
|
+
if (content.includes('class') && content.includes('queue') && !content.includes('actor') && !content.includes('@MainActor')) {
|
|
1448
|
+
const hasDispatchQueue = content.includes('DispatchQueue') || content.includes('serialQueue');
|
|
1449
|
+
const hasState = (content.match(/var\s+\w+/g) || []).length > 3;
|
|
1450
|
+
if (hasDispatchQueue && hasState) {
|
|
1451
|
+
pushFinding(
|
|
1452
|
+
"ios.concurrency.use_actor",
|
|
1453
|
+
"medium",
|
|
1454
|
+
sf,
|
|
1455
|
+
sf,
|
|
1456
|
+
'Manual thread synchronization with queues - consider actor for thread-safe state management',
|
|
1457
|
+
findings
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
if (content.includes('async') && content.includes('struct') && !content.includes('Sendable') && !content.includes('@unchecked')) {
|
|
1463
|
+
const asyncPattern = /func\s+\w+\([^)]*\)\s+async/g;
|
|
1464
|
+
if ((content.match(asyncPattern) || []).length > 2) {
|
|
1465
|
+
pushFinding(
|
|
1466
|
+
"ios.concurrency.missing_sendable",
|
|
1467
|
+
"medium",
|
|
1468
|
+
sf,
|
|
1469
|
+
sf,
|
|
1470
|
+
'Async functions without Sendable conformance - add Sendable for thread-safe async operations',
|
|
1471
|
+
findings
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
if (content.includes('import Alamofire') && !content.includes('// justified')) {
|
|
1477
|
+
pushFinding(
|
|
1478
|
+
"ios.networking.prefer_urlsession",
|
|
1479
|
+
"low",
|
|
1480
|
+
sf,
|
|
1481
|
+
sf,
|
|
1482
|
+
'Using Alamofire - prefer native URLSession with async/await unless justified',
|
|
1483
|
+
findings
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if ((content.includes('Response') || content.includes('Request')) && content.includes('struct') && !content.includes('Codable') && !content.includes('Decodable')) {
|
|
1488
|
+
const modelPattern = /struct\s+(\w+(Response|Request|DTO))\s/g;
|
|
1489
|
+
while ((match = modelPattern.exec(content)) !== null) {
|
|
1490
|
+
const modelName = match[1];
|
|
1491
|
+
pushFinding(
|
|
1492
|
+
"ios.networking.missing_codable",
|
|
1493
|
+
"high",
|
|
1494
|
+
sf,
|
|
1495
|
+
sf,
|
|
1496
|
+
`${modelName} struct without Codable - network models need Codable for JSON serialization`,
|
|
1497
|
+
findings
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
if (content.includes('UserDefaults') && (content.includes('password') || content.includes('token') || content.includes('secret'))) {
|
|
1503
|
+
pushFinding(
|
|
1504
|
+
"ios.security.sensitive_data_userdefaults",
|
|
1505
|
+
"critical",
|
|
1506
|
+
sf,
|
|
1507
|
+
sf,
|
|
1508
|
+
'Sensitive data in UserDefaults - use Keychain for passwords/tokens (Security: Keychain for sensitive data)',
|
|
1509
|
+
findings
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
if (content.includes('URLSession') && content.includes('https') && !content.includes('ServerTrustPolicy') && !content.includes('pinning')) {
|
|
1514
|
+
if (content.includes('production') || content.includes('release')) {
|
|
1515
|
+
pushFinding(
|
|
1516
|
+
"ios.security.missing_ssl_pinning",
|
|
1517
|
+
"high",
|
|
1518
|
+
sf,
|
|
1519
|
+
sf,
|
|
1520
|
+
'Production network code without SSL pinning - implement certificate pinning for security',
|
|
1521
|
+
findings
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
if (content.includes('catch') && !content.includes('log') && !content.includes('print') && !content.includes('Logger')) {
|
|
1527
|
+
const catchPattern = /catch\s*\{[^}]{0,50}\}/g;
|
|
1528
|
+
const matches = content.match(catchPattern) || [];
|
|
1529
|
+
if (matches.length > 0) {
|
|
1530
|
+
pushFinding(
|
|
1531
|
+
"ios.error_handling.silent_catch",
|
|
1532
|
+
"high",
|
|
1533
|
+
sf,
|
|
1534
|
+
sf,
|
|
1535
|
+
`${matches.length} catch blocks without logging - errors should be logged for debugging`,
|
|
1536
|
+
findings
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
if ((content.includes('Button') || content.includes('Image') || content.includes('Text')) &&
|
|
1542
|
+
content.includes('View') && !content.includes('accessibility') && !filePath.includes('Preview')) {
|
|
1543
|
+
pushFinding(
|
|
1544
|
+
"ios.accessibility.missing_labels",
|
|
1545
|
+
"medium",
|
|
1546
|
+
sf,
|
|
1547
|
+
sf,
|
|
1548
|
+
'SwiftUI views without accessibility modifiers - add .accessibilityLabel() for VoiceOver support',
|
|
1549
|
+
findings
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
const swiftUIStringPattern = /Text\("(?!.*NSLocalizedString)[^"]{10,}"\)/g;
|
|
1554
|
+
let stringMatches = content.match(swiftUIStringPattern) || [];
|
|
1555
|
+
if (stringMatches.length > 5 && !filePath.includes('Test') && !filePath.includes('Preview')) {
|
|
1556
|
+
pushFinding(
|
|
1557
|
+
"ios.localization.hardcoded_strings",
|
|
1558
|
+
"medium",
|
|
1559
|
+
sf,
|
|
1560
|
+
sf,
|
|
1561
|
+
`${stringMatches.length} hardcoded strings - use NSLocalizedString for i18n support`,
|
|
1562
|
+
findings
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
if (content.includes('NavigationLink') && content.includes('destination:') && !content.includes('Coordinator') && !content.includes('Router')) {
|
|
1567
|
+
const navLinkCount = (content.match(/NavigationLink/g) || []).length;
|
|
1568
|
+
if (navLinkCount > 3) {
|
|
1569
|
+
pushFinding(
|
|
1570
|
+
"ios.architecture.missing_coordinator",
|
|
1571
|
+
"medium",
|
|
1572
|
+
sf,
|
|
1573
|
+
sf,
|
|
1574
|
+
`${navLinkCount} NavigationLinks without Coordinator - consider Coordinator pattern for complex navigation`,
|
|
1575
|
+
findings
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
if (filePath.includes('ViewModel') && (content.includes('import SwiftUI') || content.includes('import UIKit'))) {
|
|
1581
|
+
if (content.includes('Color') || content.includes('Font') || content.includes('Image')) {
|
|
1582
|
+
pushFinding(
|
|
1583
|
+
"ios.mvvm.viewmodel_ui_coupling",
|
|
1584
|
+
"high",
|
|
1585
|
+
sf,
|
|
1586
|
+
sf,
|
|
1587
|
+
'ViewModel importing SwiftUI/UIKit with UI types - ViewModels should be UI-independent',
|
|
1588
|
+
findings
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
if (filePath.includes('Repository') && content.includes('class') && !content.includes('protocol')) {
|
|
1594
|
+
pushFinding(
|
|
1595
|
+
"ios.architecture.missing_repository_protocol",
|
|
1596
|
+
"high",
|
|
1597
|
+
sf,
|
|
1598
|
+
sf,
|
|
1599
|
+
'Repository implementation without protocol - define protocol in Domain layer for testability',
|
|
1600
|
+
findings
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
if (filePath.includes('ViewModel') && (content.match(/func\s+\w+.*async.*throws/g) || []).length > 3) {
|
|
1605
|
+
if (!content.includes('UseCase') && !content.includes('Interactor')) {
|
|
1606
|
+
pushFinding(
|
|
1607
|
+
"ios.architecture.missing_use_case",
|
|
1608
|
+
"medium",
|
|
1609
|
+
sf,
|
|
1610
|
+
sf,
|
|
1611
|
+
'ViewModel with complex async logic - extract to Use Cases for better separation of concerns',
|
|
1612
|
+
findings
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
if (content.includes('Task {') && !content.includes('async let') && !content.includes('withTaskGroup')) {
|
|
1618
|
+
const taskCount = (content.match(/Task\s*\{/g) || []).length;
|
|
1619
|
+
if (taskCount > 3) {
|
|
1620
|
+
pushFinding(
|
|
1621
|
+
"ios.concurrency.unstructured_tasks",
|
|
1622
|
+
"medium",
|
|
1623
|
+
sf,
|
|
1624
|
+
sf,
|
|
1625
|
+
`${taskCount} unstructured Tasks - consider TaskGroup for structured concurrency`,
|
|
1626
|
+
findings
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
if (content.includes('.sink') && !content.includes('AnyCancellable') && !content.includes('store(in:)')) {
|
|
1632
|
+
pushFinding(
|
|
1633
|
+
"ios.combine.missing_cancellable_storage",
|
|
1634
|
+
"high",
|
|
1635
|
+
sf,
|
|
1636
|
+
sf,
|
|
1637
|
+
'Combine subscription without cancellable storage - store in Set<AnyCancellable> to prevent memory leaks',
|
|
1638
|
+
findings
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
if (content.includes('List') && content.includes('ForEach') && !content.includes('Lazy')) {
|
|
1643
|
+
const listCount = (content.match(/List\s*\{/g) || []).length;
|
|
1644
|
+
if (listCount > 0 && content.includes('.id(')) {
|
|
1645
|
+
pushFinding(
|
|
1646
|
+
"ios.performance.use_lazy_stack",
|
|
1647
|
+
"low",
|
|
1648
|
+
sf,
|
|
1649
|
+
sf,
|
|
1650
|
+
'List with many items - consider LazyVStack/LazyHStack for performance',
|
|
1651
|
+
findings
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
if (filePath.includes('Tests') && content.includes('import ') && !content.includes('@testable')) {
|
|
1657
|
+
if (content.includes('XCTest')) {
|
|
1658
|
+
pushFinding(
|
|
1659
|
+
"ios.testing.missing_testable",
|
|
1660
|
+
"low",
|
|
1661
|
+
sf,
|
|
1662
|
+
sf,
|
|
1663
|
+
'Test file without @testable import - use @testable for accessing internal types',
|
|
1664
|
+
findings
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
if (content.includes('protocol') && !content.includes('extension') && (content.match(/func\s+\w+/g) || []).length > 3) {
|
|
1670
|
+
pushFinding(
|
|
1671
|
+
"ios.pop.missing_protocol_extension",
|
|
1672
|
+
"low",
|
|
1673
|
+
sf,
|
|
1674
|
+
sf,
|
|
1675
|
+
'Protocol with many methods - consider protocol extensions for default implementations (Protocol-Oriented Programming)',
|
|
1676
|
+
findings
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
if (content.includes('class') && content.includes(':') && !content.includes('protocol')) {
|
|
1681
|
+
const inheritancePattern = /class\s+\w+\s*:\s*(\w+)(?:\s|,|<)/g;
|
|
1682
|
+
const matches = [];
|
|
1683
|
+
while ((match = inheritancePattern.exec(content)) !== null) {
|
|
1684
|
+
const baseClass = match[1];
|
|
1685
|
+
if (baseClass !== 'NSObject' && baseClass !== 'ObservableObject' && !baseClass.includes('ViewController')) {
|
|
1686
|
+
matches.push(baseClass);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
if (matches.length > 0) {
|
|
1690
|
+
pushFinding(
|
|
1691
|
+
"ios.pop.prefer_protocol_composition",
|
|
1692
|
+
"medium",
|
|
1693
|
+
sf,
|
|
1694
|
+
sf,
|
|
1695
|
+
`Class inheritance from ${matches[0]} - prefer protocol composition over inheritance (POP: Protocols over Inheritance)`,
|
|
1696
|
+
findings
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
if (content.includes('protocol') && content.includes('<') && !content.includes('associatedtype')) {
|
|
1702
|
+
pushFinding(
|
|
1703
|
+
"ios.pop.use_associated_types",
|
|
1704
|
+
"low",
|
|
1705
|
+
sf,
|
|
1706
|
+
sf,
|
|
1707
|
+
'Generic protocol without associatedtype - use associated types for type-safe generics',
|
|
1708
|
+
findings
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
if ((content.match(/protocol\s+\w+/g) || []).length > 2 && !content.includes('&') && !content.includes(',')) {
|
|
1713
|
+
pushFinding(
|
|
1714
|
+
"ios.pop.missing_protocol_composition",
|
|
1715
|
+
"low",
|
|
1716
|
+
sf,
|
|
1717
|
+
sf,
|
|
1718
|
+
'Multiple protocols defined - consider protocol composition (Protocol & AnotherProtocol)',
|
|
1719
|
+
findings
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
if (content.includes('protocol') && content.includes('Delegate') && !content.includes('AnyObject')) {
|
|
1724
|
+
const delegatePattern = /protocol\s+\w+Delegate/g;
|
|
1725
|
+
if ((content.match(delegatePattern) || []).length > 0 && !content.includes(': AnyObject')) {
|
|
1726
|
+
pushFinding(
|
|
1727
|
+
"ios.memory.delegate_not_anyobject",
|
|
1728
|
+
"high",
|
|
1729
|
+
sf,
|
|
1730
|
+
sf,
|
|
1731
|
+
'Delegate protocol without AnyObject conformance - add ": AnyObject" to enable weak references',
|
|
1732
|
+
findings
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
if (content.includes('class') && (content.includes('Timer') || content.includes('NotificationCenter') || content.includes('observer')) && !content.includes('deinit')) {
|
|
1738
|
+
pushFinding(
|
|
1739
|
+
"ios.memory.missing_deinit",
|
|
1740
|
+
"medium",
|
|
1741
|
+
sf,
|
|
1742
|
+
sf,
|
|
1743
|
+
'Class with resources (Timer/NotificationCenter) without deinit - implement deinit for cleanup',
|
|
1744
|
+
findings
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
if (content.includes('struct') && (content.match(/var\s+\w+:\s*\[/g) || []).length > 2) {
|
|
1749
|
+
const structPattern = /struct\s+(\w+)/g;
|
|
1750
|
+
while ((match = structPattern.exec(content)) !== null) {
|
|
1751
|
+
const structName = match[1];
|
|
1752
|
+
if (!content.includes('isKnownUniquelyReferenced')) {
|
|
1753
|
+
pushFinding(
|
|
1754
|
+
"ios.value_types.large_struct_cow",
|
|
1755
|
+
"low",
|
|
1756
|
+
sf,
|
|
1757
|
+
sf,
|
|
1758
|
+
`Struct '${structName}' with collections - consider implementing copy-on-write for performance`,
|
|
1759
|
+
findings
|
|
1760
|
+
);
|
|
1761
|
+
break;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
if (content.includes('enum') && content.includes('Error') && !content.includes(': Error') && !content.includes(': LocalizedError')) {
|
|
1767
|
+
const errorEnumPattern = /enum\s+(\w+Error)/g;
|
|
1768
|
+
while ((match = errorEnumPattern.exec(content)) !== null) {
|
|
1769
|
+
const enumName = match[1];
|
|
1770
|
+
pushFinding(
|
|
1771
|
+
"ios.error_handling.enum_not_error",
|
|
1772
|
+
"high",
|
|
1773
|
+
sf,
|
|
1774
|
+
sf,
|
|
1775
|
+
`Enum '${enumName}' should conform to Error protocol for proper error handling`,
|
|
1776
|
+
findings
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
if (content.includes('import Combine') && content.includes('Publisher') && !content.includes('async')) {
|
|
1782
|
+
const publisherCount = (content.match(/Publisher/g) || []).length;
|
|
1783
|
+
if (publisherCount > 5) {
|
|
1784
|
+
pushFinding(
|
|
1785
|
+
"ios.combine.overuse",
|
|
1786
|
+
"low",
|
|
1787
|
+
sf,
|
|
1788
|
+
sf,
|
|
1789
|
+
`${publisherCount} Publishers - consider async/await for simpler async code (Combine: async/await more simple for single values)`,
|
|
1790
|
+
findings
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
if (content.includes('URLSession') && content.includes('dataTask') && !content.includes('retry') && !content.includes('attempt')) {
|
|
1796
|
+
pushFinding(
|
|
1797
|
+
"ios.networking.missing_retry",
|
|
1798
|
+
"medium",
|
|
1799
|
+
sf,
|
|
1800
|
+
sf,
|
|
1801
|
+
'Network requests without retry logic - implement exponential backoff for failed requests',
|
|
1802
|
+
findings
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
if (content.includes('Keychain') && !content.includes('SecItemAdd') && !content.includes('KeychainSwift')) {
|
|
1807
|
+
pushFinding(
|
|
1808
|
+
"ios.security.keychain_usage",
|
|
1809
|
+
"low",
|
|
1810
|
+
sf,
|
|
1811
|
+
sf,
|
|
1812
|
+
'Keychain mentioned but not using Security framework APIs - use SecItemAdd/SecItemCopyMatching',
|
|
1813
|
+
findings
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
if (content.includes('import CoreData') && content.includes('NSManagedObject') && !content.includes('NSPersistentContainer')) {
|
|
1818
|
+
pushFinding(
|
|
1819
|
+
"ios.persistence.outdated_coredata",
|
|
1820
|
+
"medium",
|
|
1821
|
+
sf,
|
|
1822
|
+
sf,
|
|
1823
|
+
'Core Data without NSPersistentContainer - use modern Core Data stack',
|
|
1824
|
+
findings
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
if (content.includes('import CoreData') && !content.includes('SwiftData') && content.includes('@available(iOS 17')) {
|
|
1829
|
+
pushFinding(
|
|
1830
|
+
"ios.persistence.use_swiftdata",
|
|
1831
|
+
"low",
|
|
1832
|
+
sf,
|
|
1833
|
+
sf,
|
|
1834
|
+
'iOS 17+ with Core Data - consider SwiftData for modern declarative persistence',
|
|
1835
|
+
findings
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
if (content.includes('ObservedObject') && !content.includes('StateObject') && content.includes('ViewModel')) {
|
|
1840
|
+
pushFinding(
|
|
1841
|
+
"ios.swiftui.wrong_property_wrapper",
|
|
1842
|
+
"high",
|
|
1843
|
+
sf,
|
|
1844
|
+
sf,
|
|
1845
|
+
'@ObservedObject for ViewModel - use @StateObject for ownership to prevent recreation',
|
|
1846
|
+
findings
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
if ((content.match(/GeometryReader/g) || []).length > 2) {
|
|
1851
|
+
pushFinding(
|
|
1852
|
+
"ios.swiftui.geometryreader_overuse",
|
|
1853
|
+
"low",
|
|
1854
|
+
sf,
|
|
1855
|
+
sf,
|
|
1856
|
+
'Multiple GeometryReader uses - use with moderation, prefer layout priorities',
|
|
1857
|
+
findings
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
if ((content.match(/\.padding\(\)\.background\(\)\.cornerRadius\(\)/g) || []).length > 2) {
|
|
1862
|
+
pushFinding(
|
|
1863
|
+
"ios.swiftui.extract_view_modifier",
|
|
1864
|
+
"low",
|
|
1865
|
+
sf,
|
|
1866
|
+
sf,
|
|
1867
|
+
'Repeated modifier chains - extract to custom ViewModifier for reusability',
|
|
1868
|
+
findings
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
if (content.includes('UIViewController') && lineCount > 300) {
|
|
1873
|
+
pushFinding(
|
|
1874
|
+
"ios.uikit.massive_viewcontroller",
|
|
1875
|
+
"high",
|
|
1876
|
+
sf,
|
|
1877
|
+
sf,
|
|
1878
|
+
`UIViewController with ${lineCount} lines (limit: 300) - extract logic to ViewModel/Coordinator (MVVM pattern)`,
|
|
1879
|
+
findings
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
if (filePath.includes('ViewController.swift') && !content.includes('loadView') && content.includes('viewDidLoad')) {
|
|
1884
|
+
if (content.includes('storyboard') || content.includes('nib')) {
|
|
1885
|
+
pushFinding(
|
|
1886
|
+
"ios.uikit.storyboard_usage",
|
|
1887
|
+
"medium",
|
|
1888
|
+
sf,
|
|
1889
|
+
sf,
|
|
1890
|
+
'ViewController using Storyboards/XIBs - prefer programmatic UI for better version control',
|
|
1891
|
+
findings
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
if (filePath.includes('.swift') && !filePath.includes('Package.swift') && !filePath.includes('Tests')) {
|
|
1897
|
+
const hasFeature = filePath.includes('/Features/') || filePath.includes('/Modules/');
|
|
1898
|
+
const totalSourceFiles = project?.getSourceFiles()?.length || 0;
|
|
1899
|
+
const isLargeProject = totalSourceFiles > 100;
|
|
1900
|
+
if (isLargeProject && !hasFeature && !filePath.includes('/Shared/')) {
|
|
1901
|
+
pushFinding(
|
|
1902
|
+
"ios.code_organization.missing_modularization",
|
|
1903
|
+
"low",
|
|
1904
|
+
sf,
|
|
1905
|
+
sf,
|
|
1906
|
+
'Large project without SPM modules - consider feature modules (Orders, Users, Auth as packages)',
|
|
1907
|
+
findings
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
if (lineCount > 100 && !content.includes('// MARK:') && !content.includes('MARK: -')) {
|
|
1913
|
+
pushFinding(
|
|
1914
|
+
"ios.code_organization.missing_mark",
|
|
1915
|
+
"low",
|
|
1916
|
+
sf,
|
|
1917
|
+
sf,
|
|
1918
|
+
`File with ${lineCount} lines without MARK: - sections - use MARK: for code organization`,
|
|
1919
|
+
findings
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
const fileName = filePath.split('/').pop();
|
|
1924
|
+
if (fileName && /^[a-z]/.test(fileName) && fileName.endsWith('.swift')) {
|
|
1925
|
+
pushFinding(
|
|
1926
|
+
"ios.code_organization.file_naming",
|
|
1927
|
+
"low",
|
|
1928
|
+
sf,
|
|
1929
|
+
sf,
|
|
1930
|
+
`File name '${fileName}' starts with lowercase - use PascalCase for type files`,
|
|
1931
|
+
findings
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
if (filePath.includes('Tests') && content.includes('func test')) {
|
|
1936
|
+
const testPattern = /func\s+(test\w+)/g;
|
|
1937
|
+
const tests = [];
|
|
1938
|
+
while ((match = testPattern.exec(content)) !== null) {
|
|
1939
|
+
tests.push(match[1]);
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
const badTests = tests.filter(t =>
|
|
1943
|
+
!t.includes('_') &&
|
|
1944
|
+
!t.toLowerCase().includes('given') &&
|
|
1945
|
+
!t.toLowerCase().includes('when') &&
|
|
1946
|
+
!t.toLowerCase().includes('then') &&
|
|
1947
|
+
!t.toLowerCase().includes('should')
|
|
1948
|
+
);
|
|
1949
|
+
|
|
1950
|
+
if (badTests.length > 0) {
|
|
1951
|
+
pushFinding(
|
|
1952
|
+
"ios.testing.test_naming",
|
|
1953
|
+
"low",
|
|
1954
|
+
sf,
|
|
1955
|
+
sf,
|
|
1956
|
+
`${badTests.length} tests with unclear names - use Given_When_Then or should pattern (BDD: test naming)`,
|
|
1957
|
+
findings
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
if (filePath.includes('Tests') && content.includes('XCTest') && !content.includes('makeSUT') && !content.includes('func sut')) {
|
|
1963
|
+
const testCount = (content.match(/func\s+test/g) || []).length;
|
|
1964
|
+
if (testCount > 2) {
|
|
1965
|
+
pushFinding(
|
|
1966
|
+
"ios.testing.missing_makesut",
|
|
1967
|
+
"medium",
|
|
1968
|
+
sf,
|
|
1969
|
+
sf,
|
|
1970
|
+
'Tests without makeSUT factory - use makeSUT pattern for System Under Test creation',
|
|
1971
|
+
findings
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
if (filePath.includes('Tests') && content.includes('deinit') && !content.includes('trackForMemoryLeaks') && !content.includes('addTeardownBlock')) {
|
|
1977
|
+
pushFinding(
|
|
1978
|
+
"ios.testing.missing_memory_leak_tracking",
|
|
1979
|
+
"medium",
|
|
1980
|
+
sf,
|
|
1981
|
+
sf,
|
|
1982
|
+
'Tests checking deinit without trackForMemoryLeaks helper - implement helper for consistent leak detection',
|
|
1983
|
+
findings
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
if (filePath.includes('Tests') && (content.includes('class Mock') || content.includes('class Stub'))) {
|
|
1988
|
+
if (!content.includes('class Spy')) {
|
|
1989
|
+
pushFinding(
|
|
1990
|
+
"ios.testing.prefer_spies",
|
|
1991
|
+
"low",
|
|
1992
|
+
sf,
|
|
1993
|
+
sf,
|
|
1994
|
+
'Using Mocks/Stubs - prefer Spies for verifying real behavior (Spies > Mocks)',
|
|
1995
|
+
findings
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
if (filePath.includes('Tests') && content.includes('sleep') || content.includes('wait')) {
|
|
2001
|
+
pushFinding(
|
|
2002
|
+
"ios.testing.slow_tests",
|
|
2003
|
+
"medium",
|
|
2004
|
+
sf,
|
|
2005
|
+
sf,
|
|
2006
|
+
'Test with sleep/wait - tests should be fast (<10ms unit tests)',
|
|
2007
|
+
findings
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
if (filePath.includes('UITests') && content.includes('XCUIApplication') && !content.includes('identifier')) {
|
|
2012
|
+
pushFinding(
|
|
2013
|
+
"ios.ui_testing.missing_identifiers",
|
|
2014
|
+
"medium",
|
|
2015
|
+
sf,
|
|
2016
|
+
sf,
|
|
2017
|
+
'UI tests without accessibility identifiers - add identifiers for reliable element location',
|
|
2018
|
+
findings
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
if (filePath.includes('UITests') && (content.match(/XCUIElement/g) || []).length > 5 && !content.includes('Page')) {
|
|
2023
|
+
pushFinding(
|
|
2024
|
+
"ios.ui_testing.missing_page_object",
|
|
2025
|
+
"low",
|
|
2026
|
+
sf,
|
|
2027
|
+
sf,
|
|
2028
|
+
'UI test with many XCUIElements - use Page Object Pattern for encapsulation',
|
|
2029
|
+
findings
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
if (content.includes('LocalAuthentication') && !content.includes('LAContext') && !content.includes('biometryType')) {
|
|
2034
|
+
pushFinding(
|
|
2035
|
+
"ios.security.incomplete_biometric",
|
|
2036
|
+
"medium",
|
|
2037
|
+
sf,
|
|
2038
|
+
sf,
|
|
2039
|
+
'LocalAuthentication import without LAContext - implement complete biometric authentication',
|
|
2040
|
+
findings
|
|
2041
|
+
);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
if (content.includes('http://') && !content.includes('localhost') && !filePath.includes('Test')) {
|
|
2045
|
+
pushFinding(
|
|
2046
|
+
"ios.security.http_not_https",
|
|
2047
|
+
"critical",
|
|
2048
|
+
sf,
|
|
2049
|
+
sf,
|
|
2050
|
+
'HTTP URL detected - use HTTPS for App Transport Security (ATS: HTTPS por defecto)',
|
|
2051
|
+
findings
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
if ((content.includes('View:') || content.includes('UIView')) && !content.includes('accessibility') && lineCount > 50) {
|
|
2056
|
+
pushFinding(
|
|
2057
|
+
"ios.accessibility.voiceover_support",
|
|
2058
|
+
"low",
|
|
2059
|
+
sf,
|
|
2060
|
+
sf,
|
|
2061
|
+
'Complex view without accessibility modifiers - test with VoiceOver and add labels/traits',
|
|
2062
|
+
findings
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
if (content.includes('UIFont') && content.includes('systemFont') && !content.includes('preferredFont')) {
|
|
2067
|
+
pushFinding(
|
|
2068
|
+
"ios.accessibility.missing_dynamic_type",
|
|
2069
|
+
"medium",
|
|
2070
|
+
sf,
|
|
2071
|
+
sf,
|
|
2072
|
+
'Using systemFont instead of preferredFont - use preferredFont for Dynamic Type support',
|
|
2073
|
+
findings
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
if ((content.match(/"[^"]{20,}"/g) || []).length > 3 && !content.includes('NSLocalizedString') && !filePath.includes('Test')) {
|
|
2078
|
+
pushFinding(
|
|
2079
|
+
"ios.localization.missing_nslocalizedstring",
|
|
2080
|
+
"medium",
|
|
2081
|
+
sf,
|
|
2082
|
+
sf,
|
|
2083
|
+
'Long strings without NSLocalizedString - use NSLocalizedString for internationalization',
|
|
2084
|
+
findings
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
if (content.includes('Date()') && content.includes('description') && !content.includes('DateFormatter')) {
|
|
2089
|
+
pushFinding(
|
|
2090
|
+
"ios.localization.missing_dateformatter",
|
|
2091
|
+
"medium",
|
|
2092
|
+
sf,
|
|
2093
|
+
sf,
|
|
2094
|
+
'Date.description for display - use DateFormatter for localized dates',
|
|
2095
|
+
findings
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
if (filePath.includes('ViewModel') && content.includes('navigation') && !content.includes('Coordinator')) {
|
|
2100
|
+
pushFinding(
|
|
2101
|
+
"ios.architecture.viewmodel_navigation",
|
|
2102
|
+
"medium",
|
|
2103
|
+
sf,
|
|
2104
|
+
sf,
|
|
2105
|
+
'ViewModel with navigation logic - use Coordinator pattern (MVVM-C) for navigation',
|
|
2106
|
+
findings
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
if (content.includes('struct') && content.includes(': View') && !content.includes(': Equatable') && lineCount > 50) {
|
|
2111
|
+
pushFinding(
|
|
2112
|
+
"ios.swiftui.missing_equatable",
|
|
2113
|
+
"low",
|
|
2114
|
+
sf,
|
|
2115
|
+
sf,
|
|
2116
|
+
'Complex View without Equatable conformance - add Equatable for render optimization',
|
|
2117
|
+
findings
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
if (content.includes('VStack') && content.includes('ForEach') && !content.includes('Lazy')) {
|
|
2122
|
+
const forEachPattern = /ForEach\([^)]*\)/g;
|
|
2123
|
+
const iterations = content.match(forEachPattern) || [];
|
|
2124
|
+
if (iterations.length > 0 && content.includes('.count >')) {
|
|
2125
|
+
pushFinding(
|
|
2126
|
+
"ios.performance.use_lazy_stack",
|
|
2127
|
+
"medium",
|
|
2128
|
+
sf,
|
|
2129
|
+
sf,
|
|
2130
|
+
'VStack with ForEach over large array - use LazyVStack for performance',
|
|
2131
|
+
findings
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
if (content.includes('Image(') && !content.includes('resizable') && !content.includes('frame')) {
|
|
2137
|
+
const imageCount = (content.match(/Image\(/g) || []).length;
|
|
2138
|
+
if (imageCount > 3) {
|
|
2139
|
+
pushFinding(
|
|
2140
|
+
"ios.performance.image_not_optimized",
|
|
2141
|
+
"low",
|
|
2142
|
+
sf,
|
|
2143
|
+
sf,
|
|
2144
|
+
`${imageCount} images without resizable/frame - optimize image rendering with .resizable().frame()`,
|
|
2145
|
+
findings
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
if (content.includes('URLSession') && content.includes('dataTask') && content.includes('DispatchQueue.main')) {
|
|
2151
|
+
pushFinding(
|
|
2152
|
+
"ios.performance.unnecessary_main_dispatch",
|
|
2153
|
+
"low",
|
|
2154
|
+
sf,
|
|
2155
|
+
sf,
|
|
2156
|
+
'Manual DispatchQueue.main with URLSession - network callbacks already on background thread',
|
|
2157
|
+
findings
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
const fastlaneFileName = filePath.split('/').pop();
|
|
2162
|
+
if (fastlaneFileName === 'Fastfile' || content.includes('fastlane')) {
|
|
2163
|
+
pushFinding(
|
|
2164
|
+
"ios.cicd.fastlane_detected",
|
|
2165
|
+
"info",
|
|
2166
|
+
sf,
|
|
2167
|
+
sf,
|
|
2168
|
+
'Fastlane configuration detected - good practice for CI/CD automation',
|
|
2169
|
+
findings
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
module.exports = { runIOSIntelligence };
|