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,2048 @@
|
|
|
1
|
+
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const {
|
|
4
|
+
pushFinding,
|
|
5
|
+
mapToLevel,
|
|
6
|
+
SyntaxKind,
|
|
7
|
+
isTestFile,
|
|
8
|
+
platformOf,
|
|
9
|
+
hasImport,
|
|
10
|
+
hasDecorator,
|
|
11
|
+
findStringLiterals,
|
|
12
|
+
findIdentifiers,
|
|
13
|
+
findCallExpressions,
|
|
14
|
+
classHasMethod,
|
|
15
|
+
classHasProperty,
|
|
16
|
+
getClasses,
|
|
17
|
+
getFunctions,
|
|
18
|
+
getArrowFunctions,
|
|
19
|
+
getRepoRoot,
|
|
20
|
+
} = require(path.join(__dirname, '../ast-core'));
|
|
21
|
+
const { BackendArchitectureDetector } = require(path.join(__dirname, 'analyzers/BackendArchitectureDetector'));
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run Backend-specific AST intelligence analysis
|
|
25
|
+
* @param {Project} project - TypeScript morph project
|
|
26
|
+
* @param {Array} findings - Findings array to populate
|
|
27
|
+
* @param {string} platform - Platform identifier
|
|
28
|
+
*/
|
|
29
|
+
function runBackendIntelligence(project, findings, platform) {
|
|
30
|
+
try {
|
|
31
|
+
const root = getRepoRoot();
|
|
32
|
+
const architectureDetector = new BackendArchitectureDetector(root);
|
|
33
|
+
const detectedPattern = architectureDetector.detect();
|
|
34
|
+
const detectionSummary = architectureDetector.getDetectionSummary();
|
|
35
|
+
|
|
36
|
+
console.error(`[Backend Architecture] Pattern detected: ${detectedPattern} (confidence: ${detectionSummary.confidence}%)`);
|
|
37
|
+
|
|
38
|
+
if (detectionSummary.warnings.length > 0) {
|
|
39
|
+
detectionSummary.warnings.forEach(warning => {
|
|
40
|
+
pushFinding('backend.architecture.detection_warning', warning.severity.toLowerCase(), null, null, warning.message + '\n\n' + warning.recommendation, findings);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('[Backend Architecture] Error during architecture detection:', error.message);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hasGlobalCors = project.getSourceFiles().some((sourceFile) => {
|
|
48
|
+
const text = sourceFile.getFullText();
|
|
49
|
+
return text.includes("app.enableCors(") ||
|
|
50
|
+
text.includes("app.use(cors(") ||
|
|
51
|
+
text.includes("Access-Control-Allow-Origin");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const godClassBaseline = (() => {
|
|
55
|
+
const quantile = (values, p) => {
|
|
56
|
+
if (!values || values.length === 0) return 0;
|
|
57
|
+
const sorted = [...values].filter((v) => Number.isFinite(v)).sort((a, b) => a - b);
|
|
58
|
+
if (sorted.length === 0) return 0;
|
|
59
|
+
const idx = Math.max(0, Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1));
|
|
60
|
+
return sorted[idx];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const median = (values) => {
|
|
64
|
+
if (!values || values.length === 0) return 0;
|
|
65
|
+
const sorted = [...values].filter((v) => Number.isFinite(v)).sort((a, b) => a - b);
|
|
66
|
+
if (sorted.length === 0) return 0;
|
|
67
|
+
const mid = Math.floor(sorted.length / 2);
|
|
68
|
+
if (sorted.length % 2 === 0) return (sorted[mid - 1] + sorted[mid]) / 2;
|
|
69
|
+
return sorted[mid];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const mad = (values) => {
|
|
73
|
+
const med = median(values);
|
|
74
|
+
const deviations = (values || []).map((v) => Math.abs(v - med));
|
|
75
|
+
return median(deviations);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const robustZ = (x, med, madValue) => {
|
|
79
|
+
if (!Number.isFinite(x) || !Number.isFinite(med) || !Number.isFinite(madValue) || madValue === 0) return 0;
|
|
80
|
+
return 0.6745 * (x - med) / madValue;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const concernCountOf = (cls) => {
|
|
84
|
+
const text = cls.getFullText();
|
|
85
|
+
const concerns = new Set();
|
|
86
|
+
|
|
87
|
+
if (/\bfs\.|\bfs\.promises\b|readFileSync|writeFileSync|mkdirSync|unlinkSync|readdirSync/.test(text)) concerns.add('io');
|
|
88
|
+
if (/\bpath\.|join\(|resolve\(|dirname\(|basename\(/.test(text)) concerns.add('path');
|
|
89
|
+
if (/execSync\(|spawnSync\(|spawn\(|child_process/.test(text)) concerns.add('process');
|
|
90
|
+
if (/\bfetch\b|axios\b|http\.|https\.|request\(/.test(text)) concerns.add('network');
|
|
91
|
+
if (/\bcrypto\b|encrypt|decrypt|hash|jwt|bearer|token/i.test(text)) concerns.add('security');
|
|
92
|
+
if (/setTimeout\(|setInterval\(|clearInterval\(|cron|schedule/i.test(text)) concerns.add('scheduling');
|
|
93
|
+
if (/\brepo\b|repository|prisma|typeorm|mongoose|sequelize|knex|\bdb\b|database|sql/i.test(text)) concerns.add('persistence');
|
|
94
|
+
if (/notification|notifier|terminal-notifier|osascript/i.test(text)) concerns.add('notifications');
|
|
95
|
+
if (/\bgit\b|rev-parse|git diff|git status|git log/i.test(text)) concerns.add('git');
|
|
96
|
+
|
|
97
|
+
return concerns.size;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const complexityOf = (cls) => {
|
|
101
|
+
const decisionKinds = [
|
|
102
|
+
SyntaxKind.IfStatement,
|
|
103
|
+
SyntaxKind.ForStatement,
|
|
104
|
+
SyntaxKind.ForInStatement,
|
|
105
|
+
SyntaxKind.ForOfStatement,
|
|
106
|
+
SyntaxKind.WhileStatement,
|
|
107
|
+
SyntaxKind.DoStatement,
|
|
108
|
+
SyntaxKind.SwitchStatement,
|
|
109
|
+
SyntaxKind.ConditionalExpression,
|
|
110
|
+
SyntaxKind.TryStatement,
|
|
111
|
+
SyntaxKind.CatchClause
|
|
112
|
+
];
|
|
113
|
+
return decisionKinds.reduce((acc, kind) => acc + cls.getDescendantsOfKind(kind).length, 0);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const metrics = [];
|
|
117
|
+
project.getSourceFiles().forEach((sf) => {
|
|
118
|
+
if (!sf || typeof sf.getFilePath !== 'function') return;
|
|
119
|
+
const filePath = sf.getFilePath();
|
|
120
|
+
if (platformOf(filePath) !== 'backend') return;
|
|
121
|
+
if (/\/ast-[^/]+\.js$/.test(filePath)) return;
|
|
122
|
+
if (process.env.AUDIT_LIBRARY !== 'true') {
|
|
123
|
+
if (/scripts\/hooks-system\/infrastructure\/ast\//i.test(filePath) || /\/infrastructure\/ast\//i.test(filePath)) return;
|
|
124
|
+
}
|
|
125
|
+
if (isTestFile(filePath)) return;
|
|
126
|
+
|
|
127
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
128
|
+
const className = cls.getName() || '';
|
|
129
|
+
const isValueObject = /Metrics|ValueObject|VO$|Dto$|Entity$/.test(className);
|
|
130
|
+
const isTestClass = /Spec$|Test$|Mock/.test(className);
|
|
131
|
+
if (isValueObject || isTestClass) return;
|
|
132
|
+
|
|
133
|
+
const methodsCount = cls.getMethods().length;
|
|
134
|
+
const propertiesCount = cls.getProperties().length;
|
|
135
|
+
const startLine = cls.getStartLineNumber();
|
|
136
|
+
const endLine = cls.getEndLineNumber();
|
|
137
|
+
const lineCount = Math.max(0, endLine - startLine);
|
|
138
|
+
const complexity = complexityOf(cls);
|
|
139
|
+
const concerns = concernCountOf(cls);
|
|
140
|
+
|
|
141
|
+
metrics.push({ methodsCount, propertiesCount, lineCount, complexity, concerns });
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (metrics.length === 0) return null;
|
|
146
|
+
|
|
147
|
+
const pOutlier = Number(process.env.AST_GODCLASS_P_OUTLIER || 90);
|
|
148
|
+
const pExtreme = Number(process.env.AST_GODCLASS_P_EXTREME || 97);
|
|
149
|
+
|
|
150
|
+
const methods = metrics.map(m => m.methodsCount);
|
|
151
|
+
const props = metrics.map(m => m.propertiesCount);
|
|
152
|
+
const lines = metrics.map(m => m.lineCount);
|
|
153
|
+
const complexities = metrics.map(m => m.complexity);
|
|
154
|
+
const concerns = metrics.map(m => m.concerns);
|
|
155
|
+
|
|
156
|
+
const med = {
|
|
157
|
+
methodsCount: median(methods),
|
|
158
|
+
propertiesCount: median(props),
|
|
159
|
+
lineCount: median(lines),
|
|
160
|
+
complexity: median(complexities)
|
|
161
|
+
};
|
|
162
|
+
const madValue = {
|
|
163
|
+
methodsCount: mad(methods),
|
|
164
|
+
propertiesCount: mad(props),
|
|
165
|
+
lineCount: mad(lines),
|
|
166
|
+
complexity: mad(complexities)
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const z = {
|
|
170
|
+
methodsCount: methods.map(v => robustZ(v, med.methodsCount, madValue.methodsCount)),
|
|
171
|
+
propertiesCount: props.map(v => robustZ(v, med.propertiesCount, madValue.propertiesCount)),
|
|
172
|
+
lineCount: lines.map(v => robustZ(v, med.lineCount, madValue.lineCount)),
|
|
173
|
+
complexity: complexities.map(v => robustZ(v, med.complexity, madValue.complexity))
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
thresholds: {
|
|
178
|
+
outlier: {
|
|
179
|
+
methodsCountZ: quantile(z.methodsCount, pOutlier),
|
|
180
|
+
propertiesCountZ: quantile(z.propertiesCount, pOutlier),
|
|
181
|
+
lineCountZ: quantile(z.lineCount, pOutlier),
|
|
182
|
+
complexityZ: quantile(z.complexity, pOutlier),
|
|
183
|
+
concerns: quantile(concerns, pOutlier)
|
|
184
|
+
},
|
|
185
|
+
extreme: {
|
|
186
|
+
methodsCountZ: quantile(z.methodsCount, pExtreme),
|
|
187
|
+
propertiesCountZ: quantile(z.propertiesCount, pExtreme),
|
|
188
|
+
lineCountZ: quantile(z.lineCount, pExtreme),
|
|
189
|
+
complexityZ: quantile(z.complexity, pExtreme)
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
med,
|
|
193
|
+
mad: madValue,
|
|
194
|
+
robustZ
|
|
195
|
+
};
|
|
196
|
+
})();
|
|
197
|
+
|
|
198
|
+
project.getSourceFiles().forEach((sf) => {
|
|
199
|
+
if (!sf || typeof sf.getFilePath !== 'function') return;
|
|
200
|
+
const filePath = sf.getFilePath();
|
|
201
|
+
|
|
202
|
+
if (platformOf(filePath) !== "backend") return;
|
|
203
|
+
|
|
204
|
+
if (/\/ast-[^/]+\.js$/.test(filePath)) return;
|
|
205
|
+
if (process.env.AUDIT_LIBRARY !== 'true') {
|
|
206
|
+
if (/scripts\/hooks-system\/infrastructure\/ast\//i.test(filePath) || /\/infrastructure\/ast\//i.test(filePath)) return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const fullText = sf.getFullText();
|
|
210
|
+
const insightsEnabled = process.env.AST_INSIGHTS === '1';
|
|
211
|
+
const isSpecFile = /\.(spec|test)\.(ts|tsx|js|jsx)$/.test(filePath);
|
|
212
|
+
const secretPattern = /(password|secret|key|token)\s*[:=]\s*['"`]([^'"]{8,})['"`]/gi;
|
|
213
|
+
const matches = Array.from(fullText.matchAll(secretPattern));
|
|
214
|
+
|
|
215
|
+
for (const match of matches) {
|
|
216
|
+
const fullMatch = match[0];
|
|
217
|
+
const secretValue = match[2];
|
|
218
|
+
|
|
219
|
+
const matchIndex = match.index || 0;
|
|
220
|
+
const lineStart = fullText.lastIndexOf('\n', matchIndex) + 1;
|
|
221
|
+
const lineEnd = fullText.indexOf('\n', matchIndex);
|
|
222
|
+
const fullLine = fullText.substring(lineStart, lineEnd === -1 ? undefined : lineEnd);
|
|
223
|
+
|
|
224
|
+
const isEnvVar = /process\.env\.|env\.|config\.|from.*env/i.test(fullMatch);
|
|
225
|
+
|
|
226
|
+
const isPlaceholderPattern = /^(placeholder|example|test-|mock-|fake-|dummy-|your-|xxx|abc|000|123|bearer\s)/i.test(secretValue);
|
|
227
|
+
const hasObviousTestWords = /(valid|invalid|wrong|expired|reset|sample|demo|user-\d|customer-\d|store-\d)/i.test(secretValue);
|
|
228
|
+
const isShortRepeating = secretValue.length <= 20 && /^(.)\1+$/.test(secretValue);
|
|
229
|
+
const isPlaceholder = isPlaceholderPattern || hasObviousTestWords || isShortRepeating;
|
|
230
|
+
|
|
231
|
+
const isComment = fullLine.includes('//') || fullLine.includes('/*');
|
|
232
|
+
const isTestContext = isSpecFile && /mock|jest\.fn|describe|it\(|beforeEach|afterEach/.test(fullText);
|
|
233
|
+
const isTestFilePath = isSpecFile || /\/(tests?|__tests__|e2e|spec|playwright)\//i.test(filePath);
|
|
234
|
+
const hasStorageContext = (
|
|
235
|
+
/localStorage|sessionStorage|AsyncStorage|getItem|setItem|removeItem/i.test(fullLine) ||
|
|
236
|
+
/const\s+\w*(KEY|STORAGE|CACHE|Token|Key|Storage)\s*=/i.test(fullLine)
|
|
237
|
+
);
|
|
238
|
+
const hasKeyNamingPattern = /_(?:key|token|storage|cache|slots)$/i.test(secretValue);
|
|
239
|
+
const hasDescriptivePrefix = /^(?:admin|user|auth|session|cache|storage|local|temp|ruralgo)_/i.test(secretValue);
|
|
240
|
+
const isStorageKey = (hasStorageContext || hasKeyNamingPattern || hasDescriptivePrefix) &&
|
|
241
|
+
!/^eyJ/.test(secretValue) && secretValue.length < 50;
|
|
242
|
+
|
|
243
|
+
const isCacheKey = secretValue.includes(':') || /^(?:products|orders|users|stores|cache|metrics|session):/i.test(secretValue);
|
|
244
|
+
|
|
245
|
+
const secretEntropyPattern = new RegExp(
|
|
246
|
+
'^(eyJ|sk_|pk_|live_|prod_|' +
|
|
247
|
+
'[a-f0-9]{' + '32,}' +
|
|
248
|
+
'|\\$2[aby]\\$)'
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const matchesSecretEntropyPattern = secretEntropyPattern.test(secretValue);
|
|
252
|
+
|
|
253
|
+
const isConstantKey = /(?:const|let|var)\s+\w*(?:KEY|TOKEN|STORAGE)\s*=/i.test(fullLine) &&
|
|
254
|
+
secretValue.length < 30 &&
|
|
255
|
+
!matchesSecretEntropyPattern;
|
|
256
|
+
const isRolesDecorator = /ROLES_KEY\s*=\s*['"`]roles['"`]/.test(fullLine);
|
|
257
|
+
|
|
258
|
+
const isTestData = isTestFilePath && secretValue.length < 50 && !matchesSecretEntropyPattern;
|
|
259
|
+
|
|
260
|
+
if (!isEnvVar && !isPlaceholder && !isComment && !isTestContext && !isStorageKey && !isCacheKey && !isConstantKey && !isRolesDecorator && !isTestData && secretValue.length >= 8) {
|
|
261
|
+
pushFinding("backend.config.secrets_in_code", "critical", sf, sf, "Hardcoded secret detected - replace with environment variable (process.env)", findings);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const hasEnvSpecific = sf.getFullText().includes("process.env.NODE_ENV") ||
|
|
266
|
+
sf.getFullText().includes("app.get('env')") ||
|
|
267
|
+
sf.getFullText().includes("ConfigService");
|
|
268
|
+
const hasConfigUsage = sf.getFullText().includes("config") || sf.getFullText().includes("env");
|
|
269
|
+
if (!hasEnvSpecific && hasConfigUsage && !isTestFile(filePath)) {
|
|
270
|
+
pushFinding("backend.config.missing_env_separation", "warning", sf, sf, "Missing environment-specific configuration - consider NODE_ENV or ConfigService", findings);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const hasConfigValidation = sf.getFullText().includes("joi") ||
|
|
274
|
+
sf.getFullText().includes("class-validator") ||
|
|
275
|
+
sf.getFullText().includes("@nestjs/config");
|
|
276
|
+
if (!hasConfigValidation && sf.getFullText().includes("process.env")) {
|
|
277
|
+
pushFinding("backend.config.missing_validation", "warning", sf, sf, "Environment variables without validation - consider Joi or class-validator", findings);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (godClassBaseline) {
|
|
281
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
282
|
+
const className = cls.getName() || '';
|
|
283
|
+
const isValueObject = /Metrics|ValueObject|VO$|Dto$|Entity$/.test(className);
|
|
284
|
+
const isTestClass = /Spec$|Test$|Mock/.test(className);
|
|
285
|
+
if (isValueObject || isTestClass) return;
|
|
286
|
+
|
|
287
|
+
const methodsCount = cls.getMethods().length;
|
|
288
|
+
const propertiesCount = cls.getProperties().length;
|
|
289
|
+
const startLine = cls.getStartLineNumber();
|
|
290
|
+
const endLine = cls.getEndLineNumber();
|
|
291
|
+
const lineCount = Math.max(0, endLine - startLine);
|
|
292
|
+
|
|
293
|
+
const decisionKinds = [
|
|
294
|
+
SyntaxKind.IfStatement,
|
|
295
|
+
SyntaxKind.ForStatement,
|
|
296
|
+
SyntaxKind.ForInStatement,
|
|
297
|
+
SyntaxKind.ForOfStatement,
|
|
298
|
+
SyntaxKind.WhileStatement,
|
|
299
|
+
SyntaxKind.DoStatement,
|
|
300
|
+
SyntaxKind.SwitchStatement,
|
|
301
|
+
SyntaxKind.ConditionalExpression,
|
|
302
|
+
SyntaxKind.TryStatement,
|
|
303
|
+
SyntaxKind.CatchClause
|
|
304
|
+
];
|
|
305
|
+
const complexity = decisionKinds.reduce((acc, kind) => acc + cls.getDescendantsOfKind(kind).length, 0);
|
|
306
|
+
|
|
307
|
+
const clsText = cls.getFullText();
|
|
308
|
+
const concerns = new Set();
|
|
309
|
+
if (/\bfs\.|\bfs\.promises\b|readFileSync|writeFileSync|mkdirSync|unlinkSync|readdirSync/.test(clsText)) concerns.add('io');
|
|
310
|
+
if (/\bpath\.|join\(|resolve\(|dirname\(|basename\(/.test(clsText)) concerns.add('path');
|
|
311
|
+
if (/execSync\(|spawnSync\(|spawn\(|child_process/.test(clsText)) concerns.add('process');
|
|
312
|
+
if (/\bfetch\b|axios\b|http\.|https\.|request\(/.test(clsText)) concerns.add('network');
|
|
313
|
+
if (/\bcrypto\b|encrypt|decrypt|hash|jwt|bearer|token/i.test(clsText)) concerns.add('security');
|
|
314
|
+
if (/setTimeout\(|setInterval\(|clearInterval\(|cron|schedule/i.test(clsText)) concerns.add('scheduling');
|
|
315
|
+
if (/\brepo\b|repository|prisma|typeorm|mongoose|sequelize|knex|\bdb\b|database|sql/i.test(clsText)) concerns.add('persistence');
|
|
316
|
+
if (/notification|notifier|terminal-notifier|osascript/i.test(clsText)) concerns.add('notifications');
|
|
317
|
+
if (/\bgit\b|rev-parse|git diff|git status|git log/i.test(clsText)) concerns.add('git');
|
|
318
|
+
const concernCount = concerns.size;
|
|
319
|
+
|
|
320
|
+
const methodsZ = godClassBaseline.robustZ(methodsCount, godClassBaseline.med.methodsCount, godClassBaseline.mad.methodsCount);
|
|
321
|
+
const propsZ = godClassBaseline.robustZ(propertiesCount, godClassBaseline.med.propertiesCount, godClassBaseline.mad.propertiesCount);
|
|
322
|
+
const linesZ = godClassBaseline.robustZ(lineCount, godClassBaseline.med.lineCount, godClassBaseline.mad.lineCount);
|
|
323
|
+
const complexityZ = godClassBaseline.robustZ(complexity, godClassBaseline.med.complexity, godClassBaseline.mad.complexity);
|
|
324
|
+
|
|
325
|
+
const sizeOutlier =
|
|
326
|
+
methodsZ >= godClassBaseline.thresholds.outlier.methodsCountZ ||
|
|
327
|
+
propsZ >= godClassBaseline.thresholds.outlier.propertiesCountZ ||
|
|
328
|
+
linesZ >= godClassBaseline.thresholds.outlier.lineCountZ;
|
|
329
|
+
const complexityOutlier = complexityZ >= godClassBaseline.thresholds.outlier.complexityZ;
|
|
330
|
+
const concernOutlier = concernCount >= godClassBaseline.thresholds.outlier.concerns;
|
|
331
|
+
|
|
332
|
+
const isAbsoluteGod = lineCount > 600 && methodsCount > 30 && complexity > 80;
|
|
333
|
+
const isUnderThreshold = lineCount < 400 && methodsCount < 25 && complexity < 50;
|
|
334
|
+
|
|
335
|
+
let signalCount = 0;
|
|
336
|
+
if (sizeOutlier) signalCount++;
|
|
337
|
+
if (complexityOutlier) signalCount++;
|
|
338
|
+
if (concernOutlier) signalCount++;
|
|
339
|
+
|
|
340
|
+
if (!isUnderThreshold && (signalCount >= 2 || isAbsoluteGod)) {
|
|
341
|
+
pushFinding("backend.antipattern.god_classes", "critical", sf, cls,
|
|
342
|
+
`God class detected: ${methodsCount} methods, ${propertiesCount} properties, ${lineCount} lines, complexity ${complexity}, concerns ${concernCount} - VIOLATES SRP`,
|
|
343
|
+
findings
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
350
|
+
const name = cls.getName();
|
|
351
|
+
if (name && /Entity|Model|Domain/.test(name)) {
|
|
352
|
+
// Exclude Errors, Exceptions, and Events from Anemic Domain check
|
|
353
|
+
if (/Error$|Exception$|Event$/.test(name)) return;
|
|
354
|
+
|
|
355
|
+
// Exclude files in domain/errors or domain/events directories
|
|
356
|
+
const filePath = sf.getFilePath().replace(/\\/g, '/');
|
|
357
|
+
if (/\/domain\/errors\//.test(filePath) || /\/domain\/events\//.test(filePath)) return;
|
|
358
|
+
|
|
359
|
+
const methods = cls.getMethods();
|
|
360
|
+
const hasBusinessLogic = methods.some((method) => {
|
|
361
|
+
const methodName = method.getName();
|
|
362
|
+
return /calculate|validate|process|compute/.test(methodName);
|
|
363
|
+
});
|
|
364
|
+
if (!hasBusinessLogic && methods.length <= 2) { // Solo getters/setters
|
|
365
|
+
pushFinding("backend.antipattern.anemic_domain", "medium", sf, cls, `Anemic domain model: ${name} lacks business logic`, findings);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const hasCors = sf.getFullText().includes("cors") || sf.getFullText().includes("CORS") || sf.getFullText().includes("@CrossOrigin");
|
|
371
|
+
const missingCorsSeverity = hasGlobalCors ? "low" : "high";
|
|
372
|
+
if (!hasCors && (sf.getFullText().includes("controller") || sf.getFullText().includes("Controller"))) {
|
|
373
|
+
pushFinding("backend.auth.missing_cors", missingCorsSeverity, sf, sf, "Missing CORS configuration in controller - consider @CrossOrigin or global CORS config", findings);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
377
|
+
const expr = call.getExpression();
|
|
378
|
+
if (!expr) return;
|
|
379
|
+
const exprText = expr.getText();
|
|
380
|
+
if (/log|Log|logger|Logger|console\./.test(exprText)) {
|
|
381
|
+
const args = call.getArguments();
|
|
382
|
+
args.forEach((arg) => {
|
|
383
|
+
const argText = arg.getText();
|
|
384
|
+
const argLower = argText.toLowerCase();
|
|
385
|
+
|
|
386
|
+
const isSensitiveKeyword = /password|token|secret|apikey|api_key/.test(argLower);
|
|
387
|
+
if (!isSensitiveKeyword) return;
|
|
388
|
+
|
|
389
|
+
const isSafeUsage =
|
|
390
|
+
/\.length/.test(argText) ||
|
|
391
|
+
/\.substring/.test(argText) ||
|
|
392
|
+
/\.slice/.test(argText) ||
|
|
393
|
+
/for user/.test(argLower) ||
|
|
394
|
+
/failed/.test(argLower) ||
|
|
395
|
+
/insufficient/.test(argLower) ||
|
|
396
|
+
/\$\{[^}]*\.length\}/.test(argText) ||
|
|
397
|
+
/template.*literal.*without.*direct.*value/.test(argLower);
|
|
398
|
+
|
|
399
|
+
const isActualValue =
|
|
400
|
+
/^\w+(Password|Token|Secret|Key|ApiKey)$/.test(argText) ||
|
|
401
|
+
/^\w+\.(password|token|secret|key|apiKey)$/.test(argText) ||
|
|
402
|
+
argText.match(/^(password|token|secret|key|apiKey)$/i);
|
|
403
|
+
|
|
404
|
+
if (isActualValue && !isSafeUsage) {
|
|
405
|
+
pushFinding("backend.logging.sensitive_data", "critical", sf, call, "Logging sensitive data detected - never log passwords, tokens, or secrets directly", findings);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const filePathNormalizedForMetrics = filePath.replace(/\\/g, "/");
|
|
412
|
+
const filePathNormalizedForMetricsLower = filePathNormalizedForMetrics.toLowerCase();
|
|
413
|
+
const isInternalAstToolingFileForMetrics = filePathNormalizedForMetricsLower.includes("/infrastructure/ast/") || filePathNormalizedForMetricsLower.includes("infrastructure/ast/") || filePathNormalizedForMetricsLower.includes("/scripts/hooks-system/infrastructure/ast/");
|
|
414
|
+
|
|
415
|
+
const fullTextLower = fullText.toLowerCase();
|
|
416
|
+
const hasMetrics = fullTextLower.includes("micrometer") || fullTextLower.includes("prometheus") ||
|
|
417
|
+
fullTextLower.includes("actuator") || fullTextLower.includes("metrics");
|
|
418
|
+
const looksLikeServiceOrController = fullTextLower.includes("controller") || fullTextLower.includes("service");
|
|
419
|
+
|
|
420
|
+
if (!isInternalAstToolingFileForMetrics && !hasMetrics && looksLikeServiceOrController) {
|
|
421
|
+
pushFinding("backend.metrics.missing_prometheus", "low", sf, sf, "Missing application metrics - consider Spring Boot Actuator or Micrometer for monitoring", findings);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (isTestFile(filePath)) {
|
|
425
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
426
|
+
const expr = call.getExpression();
|
|
427
|
+
if (!expr) return;
|
|
428
|
+
const exprText = expr.getText();
|
|
429
|
+
if (/Thread\.sleep|await|delay/.test(exprText)) {
|
|
430
|
+
pushFinding("backend.testing.slow_tests", "medium", sf, call, "Test with sleep/delay detected - slow tests impact CI/CD performance", findings);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
436
|
+
const name = cls.getName();
|
|
437
|
+
if (name && /Repository/.test(name) && !name.includes("Impl")) {
|
|
438
|
+
const hasInterface = sf.getDescendantsOfKind(SyntaxKind.InterfaceDeclaration).some((iface) => {
|
|
439
|
+
return iface.getName() === name.replace("Repository", "Repository");
|
|
440
|
+
});
|
|
441
|
+
if (!hasInterface) {
|
|
442
|
+
pushFinding("backend.repository.missing_interface", "medium", sf, cls, `Repository ${name} should implement an interface for testability`, findings);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
448
|
+
const name = cls.getName();
|
|
449
|
+
if (name && /Repository/.test(name)) {
|
|
450
|
+
const methods = cls.getMethods();
|
|
451
|
+
methods.forEach((method) => {
|
|
452
|
+
const methodName = method.getName();
|
|
453
|
+
if (/calculate|validate|process|compute|business/.test(methodName)) {
|
|
454
|
+
pushFinding("backend.repository.business_logic", "high", sf, method, `Repository ${name}.${methodName} contains business logic - move to service layer`, findings);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
461
|
+
const name = cls.getName();
|
|
462
|
+
if (name && /Repository/.test(name)) {
|
|
463
|
+
const methods = cls.getMethods();
|
|
464
|
+
const hasMultipleOperations = methods.some((method) => {
|
|
465
|
+
const body = method.getBody();
|
|
466
|
+
if (body) {
|
|
467
|
+
const calls = body.getDescendantsOfKind(SyntaxKind.CallExpression).length;
|
|
468
|
+
return calls > 3; // Multiple operations
|
|
469
|
+
}
|
|
470
|
+
return false;
|
|
471
|
+
});
|
|
472
|
+
if (hasMultipleOperations) {
|
|
473
|
+
const hasTransaction = sf.getFullText().includes("@Transactional") || sf.getFullText().includes("@Transaction");
|
|
474
|
+
if (!hasTransaction) {
|
|
475
|
+
pushFinding("backend.repository.transaction_missing", "medium", sf, cls, `Repository ${name} performs multiple operations without @Transactional`, findings);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
482
|
+
const name = cls.getName();
|
|
483
|
+
if (name && /UseCase|UseCaseImpl/.test(name)) {
|
|
484
|
+
pushFinding("backend.usecase.explicit", "low", sf, cls, `Use case pattern detected: ${name} - good practice for business logic encapsulation`, findings);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
489
|
+
const name = cls.getName();
|
|
490
|
+
if (!name || !/UseCase/.test(name)) return;
|
|
491
|
+
const methods = cls.getMethods();
|
|
492
|
+
methods.forEach((method) => {
|
|
493
|
+
const rtNode = method.getReturnTypeNode();
|
|
494
|
+
if (!rtNode) return;
|
|
495
|
+
const returnTypeText = rtNode.getText();
|
|
496
|
+
if (returnTypeText.includes("Entity") && !returnTypeText.includes("DTO")) {
|
|
497
|
+
pushFinding("backend.usecase.returns", "medium", sf, method, `Use case ${name}.${method.getName()} returns Entity directly - return DTOs to avoid exposing domain objects`, findings);
|
|
498
|
+
pushFinding("backend.usecase.returns_entity", "medium", sf, method, `Use case ${name}.${method.getName()} returns Entity directly - return DTOs to avoid exposing domain objects`, findings);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
504
|
+
const name = cls.getName();
|
|
505
|
+
if (name && /Dto|DTO|Request|Response/.test(name)) {
|
|
506
|
+
const hasValidation = sf.getFullText().includes("@IsString") ||
|
|
507
|
+
sf.getFullText().includes("@IsEmail") ||
|
|
508
|
+
sf.getFullText().includes("@IsNotEmpty") ||
|
|
509
|
+
sf.getFullText().includes("class-validator");
|
|
510
|
+
if (!hasValidation) {
|
|
511
|
+
pushFinding("backend.dto.validation", "high", sf, cls, `DTO ${name} without validation decorators - use class-validator for input validation`, findings);
|
|
512
|
+
pushFinding("backend.dto.missing_validation", "high", sf, cls, `DTO ${name} without validation decorators - use class-validator for input validation`, findings);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
518
|
+
const name = cls.getName();
|
|
519
|
+
if (name && /Dto|DTO/.test(name)) {
|
|
520
|
+
const hasTransform = sf.getFullText().includes("@Transform") ||
|
|
521
|
+
sf.getFullText().includes("@Expose") ||
|
|
522
|
+
sf.getFullText().includes("@Exclude") ||
|
|
523
|
+
sf.getFullText().includes("class-transformer");
|
|
524
|
+
if (!hasTransform) {
|
|
525
|
+
pushFinding("backend.dto.transformation", "low", sf, cls, `DTO ${name} without transformation - consider class-transformer for serialization control`, findings);
|
|
526
|
+
pushFinding("backend.dto.missing_transformer", "low", sf, cls, `DTO ${name} without transformation - consider class-transformer for serialization control`, findings);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
sf.getDescendantsOfKind(SyntaxKind.ThrowStatement).forEach((throwStmt) => {
|
|
532
|
+
const expr = throwStmt.getExpression();
|
|
533
|
+
if (!expr) return;
|
|
534
|
+
const exprText = expr.getText();
|
|
535
|
+
if (exprText.includes("Error(") || exprText.includes("Exception(")) {
|
|
536
|
+
const isCustom = exprText.includes("Exception") &&
|
|
537
|
+
(exprText.includes("Validation") ||
|
|
538
|
+
exprText.includes("NotFound") ||
|
|
539
|
+
exprText.includes("Unauthorized") ||
|
|
540
|
+
exprText.includes("Forbidden"));
|
|
541
|
+
if (!isCustom) {
|
|
542
|
+
pushFinding("backend.error.custom_exceptions", "medium", sf, throwStmt, "Generic Error/Exception thrown - create custom exception classes for better error handling", findings);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
sf.getDescendantsOfKind(SyntaxKind.CatchClause).forEach((catchClause) => {
|
|
548
|
+
const block = catchClause.getBlock();
|
|
549
|
+
if (block && block.getText().includes("error") || block.getText().includes("err")) {
|
|
550
|
+
const exposesStack = block.getText().includes("stack") || block.getText().includes("stackTrace");
|
|
551
|
+
if (exposesStack) {
|
|
552
|
+
pushFinding("backend.error.exposes", "high", sf, catchClause, "Error handler exposes stack trace - never expose internal errors to clients", findings);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
sf.getDescendantsOfKind(SyntaxKind.CatchClause).forEach((catchClause) => {
|
|
558
|
+
const block = catchClause.getBlock();
|
|
559
|
+
if (!block) return;
|
|
560
|
+
const blockText = block.getText().trim();
|
|
561
|
+
const isEmpty = blockText === '{}' || /^\{\s*\/\/[^\n]*\s*\}$/.test(blockText) || /^\{\s*\/\*[\s\S]*?\*\/\s*\}$/.test(blockText);
|
|
562
|
+
if (isEmpty) {
|
|
563
|
+
pushFinding("backend.error.empty_catch", "high", sf, catchClause, "Empty catch block detected - handle errors properly or rethrow", findings);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
if (filePath.endsWith('.ts') && !filePath.endsWith('.d.ts')) {
|
|
568
|
+
sf.getDescendantsOfKind(SyntaxKind.CatchClause).forEach((catchClause) => {
|
|
569
|
+
const varDecl = catchClause.getVariableDeclaration();
|
|
570
|
+
if (varDecl) {
|
|
571
|
+
const typeNode = varDecl.getTypeNode();
|
|
572
|
+
if (!typeNode) {
|
|
573
|
+
pushFinding(
|
|
574
|
+
"backend.error_handling.untyped_catch",
|
|
575
|
+
"high",
|
|
576
|
+
sf,
|
|
577
|
+
catchClause,
|
|
578
|
+
"Catch parameter MUST be typed as ': unknown' - use type guards (error instanceof HttpException/Error)",
|
|
579
|
+
findings
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
sf.getDescendantsOfKind(SyntaxKind.ExpressionStatement).forEach((stmt) => {
|
|
587
|
+
const text = stmt.getText().trim();
|
|
588
|
+
if (/^void\s+(err|error|e)\s*;?\s*$/.test(text)) {
|
|
589
|
+
pushFinding(
|
|
590
|
+
"backend.error_handling.void_error",
|
|
591
|
+
"high",
|
|
592
|
+
sf,
|
|
593
|
+
stmt,
|
|
594
|
+
"NEVER use 'void err' - throw custom exceptions (NotFoundException, BadRequestException) or log properly",
|
|
595
|
+
findings
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
sf.getDescendantsOfKind(SyntaxKind.VariableDeclaration).forEach((varDecl) => {
|
|
601
|
+
const typeNode = varDecl.getTypeNode();
|
|
602
|
+
if (typeNode && typeNode.getText() === 'unknown') {
|
|
603
|
+
const parent = varDecl.getParent();
|
|
604
|
+
const scope = varDecl.getFirstAncestorByKind(SyntaxKind.Block);
|
|
605
|
+
if (scope) {
|
|
606
|
+
const scopeText = scope.getText();
|
|
607
|
+
const varName = varDecl.getName();
|
|
608
|
+
const hasTypeGuard = new RegExp(`${varName}\\s+instanceof|typeof\\s+${varName}`).test(scopeText);
|
|
609
|
+
if (!hasTypeGuard) {
|
|
610
|
+
pushFinding(
|
|
611
|
+
"backend.typescript.unknown_without_guard",
|
|
612
|
+
"high",
|
|
613
|
+
sf,
|
|
614
|
+
varDecl,
|
|
615
|
+
`Variable '${varName}: unknown' used without type guards - add instanceof/typeof checks before use`,
|
|
616
|
+
findings
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
624
|
+
const expr = call.getExpression().getText();
|
|
625
|
+
if (/Promise\.|\.then\(|\.catch\(/.test(expr)) {
|
|
626
|
+
const hasAwait = sf.getFullText().includes("await ") ||
|
|
627
|
+
sf.getDescendantsOfKind(SyntaxKind.AwaitExpression).some((awaitExpr) =>
|
|
628
|
+
awaitExpr.getExpression() === call
|
|
629
|
+
);
|
|
630
|
+
if (!hasAwait) {
|
|
631
|
+
pushFinding("backend.async.missing_await", "high", sf, call, "Async operation without await - ensure proper async/await usage", findings);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
sf.getDescendantsOfKind(SyntaxKind.AwaitExpression).forEach((awaitExpr) => {
|
|
637
|
+
const filePath = sf.getFilePath();
|
|
638
|
+
const ancestors = awaitExpr.getAncestors();
|
|
639
|
+
|
|
640
|
+
const insideTryCatch = ancestors.some(ancestor =>
|
|
641
|
+
ancestor.getKind() === SyntaxKind.TryStatement
|
|
642
|
+
);
|
|
643
|
+
if (insideTryCatch) return;
|
|
644
|
+
|
|
645
|
+
const containingFunction = ancestors.find(ancestor =>
|
|
646
|
+
ancestor.getKind() === SyntaxKind.FunctionDeclaration ||
|
|
647
|
+
ancestor.getKind() === SyntaxKind.ArrowFunction ||
|
|
648
|
+
ancestor.getKind() === SyntaxKind.MethodDeclaration
|
|
649
|
+
);
|
|
650
|
+
if (containingFunction) {
|
|
651
|
+
const funcText = containingFunction.getText();
|
|
652
|
+
const funcName = funcText.match(/(?:function\s+|const\s+|let\s+|var\s+)(\w+)/)?.[1] || '';
|
|
653
|
+
if (/catch|error|reject|handle|rescue/i.test(funcName)) return;
|
|
654
|
+
|
|
655
|
+
const isDirectReturn = awaitExpr.getParent()?.getKind() === SyntaxKind.ReturnStatement;
|
|
656
|
+
if (isDirectReturn && containingFunction.getKind() === SyntaxKind.ArrowFunction) {
|
|
657
|
+
const funcBody = funcText.trim();
|
|
658
|
+
const isPureWrapper = /^[^{]*=>\s*await\s+/.test(funcBody) ||
|
|
659
|
+
/^[^{]*=>\s*\{\s*return\s+await\s+[^;]+;\s*\}$/.test(funcBody);
|
|
660
|
+
if (isPureWrapper) return;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (/\/(main|index|server|app)\.ts$/.test(filePath)) {
|
|
665
|
+
const isTopLevel = !ancestors.some(ancestor =>
|
|
666
|
+
ancestor.getKind() === SyntaxKind.FunctionDeclaration ||
|
|
667
|
+
ancestor.getKind() === SyntaxKind.ArrowFunction ||
|
|
668
|
+
ancestor.getKind() === SyntaxKind.MethodDeclaration
|
|
669
|
+
);
|
|
670
|
+
if (isTopLevel) return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const statement = awaitExpr.getParent();
|
|
674
|
+
if (statement) {
|
|
675
|
+
const statementText = statement.getText();
|
|
676
|
+
if (/\.catch\s*\(/.test(statementText)) return; // Has .catch() handler
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (/middleware|guard|interceptor|filter/i.test(filePath)) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (/\/(scripts?|migrations?|seeders?|fixtures?)\//i.test(filePath)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
pushFinding("backend.async.error_handling", "medium", sf, awaitExpr, "Await expression without try/catch - handle async errors properly", findings);
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
691
|
+
const expr = call.getExpression().getText();
|
|
692
|
+
if (expr.includes("emit") || expr.includes("publish")) {
|
|
693
|
+
pushFinding("backend.event.emitter", "low", sf, call, "Event emission detected - good practice for decoupled communication", findings);
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
698
|
+
const expr = call.getExpression().getText();
|
|
699
|
+
if (expr.includes("on(") || expr.includes("subscribe(")) {
|
|
700
|
+
pushFinding("backend.event.handler", "low", sf, call, "Event handler detected - ensure idempotent processing", findings);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
705
|
+
const expr = call.getExpression().getText();
|
|
706
|
+
if (expr.includes(".find(") || expr.includes(".query(") || expr.includes("supabase.from(")) {
|
|
707
|
+
const inLoop = call.getAncestors().some((ancestor) =>
|
|
708
|
+
ancestor.getKind() === SyntaxKind.ForStatement ||
|
|
709
|
+
ancestor.getKind() === SyntaxKind.ForOfStatement ||
|
|
710
|
+
ancestor.getKind() === SyntaxKind.WhileStatement ||
|
|
711
|
+
ancestor.getKind() === SyntaxKind.ForInStatement
|
|
712
|
+
);
|
|
713
|
+
if (inLoop) {
|
|
714
|
+
pushFinding("backend.performance.nplus1", "high", sf, call, "Database query in loop detected - potential N+1 query problem", findings);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
720
|
+
const expr = call.getExpression().getText();
|
|
721
|
+
if (expr.includes(".find(") || expr.includes("supabase.from(")) {
|
|
722
|
+
const hasLimit = sf.getFullText().includes(".limit(") ||
|
|
723
|
+
sf.getFullText().includes(".take(") ||
|
|
724
|
+
sf.getFullText().includes("LIMIT ") ||
|
|
725
|
+
sf.getFullText().includes("range(");
|
|
726
|
+
if (!hasLimit) {
|
|
727
|
+
pushFinding("backend.performance.pagination", "medium", sf, call, "Query without pagination - consider adding limit/range for performance", findings);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
sf.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((str) => {
|
|
733
|
+
const text = str.getLiteralValue();
|
|
734
|
+
const sqlKeywords = ['SEL' + 'ECT ', 'INS' + 'ERT ', 'UPD' + 'ATE ', 'DEL' + 'ETE ', 'DR' + 'OP ', 'AL' + 'TER ', 'TRUN' + 'CATE '];
|
|
735
|
+
const sqlPattern = new RegExp(sqlKeywords.join('|'), 'i');
|
|
736
|
+
if (sqlPattern.test(text)) {
|
|
737
|
+
pushFinding("backend.database.raw_sql", "medium", sf, str, "Raw SQL detected - prefer ORM queries for type safety and security", findings);
|
|
738
|
+
if (/[\"'`].*\+.*[\"'`]/.test(text) || /\$\{.*\}/.test(text)) {
|
|
739
|
+
pushFinding("backend.db.query_not_parameterized", "high", sf, str, "Query appears to be built via string concatenation/interpolation - use parameterized queries", findings);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
const isOrmRepositoryFile = /\/(repositories?)\//i.test(filePath) && (
|
|
745
|
+
fullText.includes("TypeOrmModule") ||
|
|
746
|
+
fullText.includes("Repository<") ||
|
|
747
|
+
fullText.includes("@Entity(")
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
if (isOrmRepositoryFile) {
|
|
751
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
752
|
+
const expr = call.getExpression().getText();
|
|
753
|
+
if (expr.includes(".save(") || expr.includes(".update(") || expr.includes(".delete(")) {
|
|
754
|
+
const multipleOps = sf.getDescendantsOfKind(SyntaxKind.CallExpression).filter((c) =>
|
|
755
|
+
c.getExpression().getText().includes(".save(") ||
|
|
756
|
+
c.getExpression().getText().includes(".update(") ||
|
|
757
|
+
c.getExpression().getText().includes(".delete(")
|
|
758
|
+
).length > 1;
|
|
759
|
+
|
|
760
|
+
if (multipleOps) {
|
|
761
|
+
const hasTransaction = sf.getFullText().includes("@Transactional") ||
|
|
762
|
+
sf.getFullText().includes("transaction(") ||
|
|
763
|
+
sf.getFullText().includes("beginTransaction");
|
|
764
|
+
if (!hasTransaction) {
|
|
765
|
+
pushFinding("backend.database.transaction", "high", sf, call, "Multiple database operations without transaction - ensure atomicity", findings);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
sf.getDescendantsOfKind(SyntaxKind.Decorator).forEach((decorator) => {
|
|
773
|
+
const expr = decorator.getExpression();
|
|
774
|
+
if (expr && expr.getText().includes("@Get") || expr.getText().includes("@Post") ||
|
|
775
|
+
expr.getText().includes("@Put") || expr.getText().includes("@Delete")) {
|
|
776
|
+
pushFinding("backend.api.restful", "low", sf, decorator, "REST endpoint detected - ensure follows RESTful conventions", findings);
|
|
777
|
+
const text = expr.getText();
|
|
778
|
+
if (!/\/(api|v[0-9]+)/i.test(text)) {
|
|
779
|
+
pushFinding("backend.api.missing_versioning", "medium", sf, decorator, "Route without API versioning (expected /api/vN)", findings);
|
|
780
|
+
}
|
|
781
|
+
const methodName = decorator.getParent()?.getName?.() || "";
|
|
782
|
+
if ((/create|add/i.test(methodName) && /@Get/.test(text)) || (/delete|remove/i.test(methodName) && /@Get/.test(text))) {
|
|
783
|
+
pushFinding("backend.api.bad_http_methods", "medium", sf, decorator, `Method ${methodName} may use incorrect HTTP verb`, findings);
|
|
784
|
+
}
|
|
785
|
+
if (/@Post/.test(text)) {
|
|
786
|
+
pushFinding("backend.api.missing_idempotency", "low", sf, decorator, "POST endpoint should consider idempotency key or safeguards", findings);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
const swaggerFilePath = sf.getFilePath();
|
|
792
|
+
const isAnalyzer = /infrastructure\/ast\/|analyzers\/|detectors\/|scanner|analyzer|detector/i.test(swaggerFilePath);
|
|
793
|
+
const isSwaggerTestFile = /\.(spec|test)\.(js|ts)$/i.test(swaggerFilePath);
|
|
794
|
+
if (!isAnalyzer && !isSwaggerTestFile && sf.getFullText().includes("@Controller") && !sf.getFullText().includes("@nestjs/swagger") && !sf.getFullText().includes("@Api")) {
|
|
795
|
+
pushFinding("backend.api.missing_swagger", "medium", sf, sf, "Controller without Swagger decorators/imports", findings);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
sf.getDescendantsOfKind(SyntaxKind.ReturnStatement).forEach((ret) => {
|
|
799
|
+
const expr = ret.getExpression();
|
|
800
|
+
if (!expr) return;
|
|
801
|
+
const exprText = expr.getText();
|
|
802
|
+
if (exprText.includes("status(") || exprText.includes("HttpStatus.")) {
|
|
803
|
+
if (exprText.includes("200") || exprText.includes("OK")) {
|
|
804
|
+
pushFinding("backend.api.status_codes", "low", sf, ret, "HTTP status code usage - ensure semantically correct status codes", findings);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
const isControllerFile = /\/(controllers?)\//i.test(filePath) || sf.getFullText().includes("@Controller");
|
|
810
|
+
if (isControllerFile) {
|
|
811
|
+
sf.getDescendantsOfKind(SyntaxKind.MethodDeclaration).forEach((method) => {
|
|
812
|
+
const decorators = method.getDecorators();
|
|
813
|
+
const hasValidationPipe = decorators.some((d) =>
|
|
814
|
+
d.getExpression().getText().includes("UsePipes") ||
|
|
815
|
+
d.getExpression().getText().includes("ValidationPipe")
|
|
816
|
+
);
|
|
817
|
+
if (!hasValidationPipe && method.getParameters().length > 0) {
|
|
818
|
+
pushFinding("backend.api.validation", "high", sf, method, "API method without validation - use ValidationPipe for automatic input validation", findings);
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (insightsEnabled) {
|
|
824
|
+
if (sf.getFullText().includes("@nestjs/jwt") || sf.getFullText().includes("passport-jwt")) {
|
|
825
|
+
pushFinding("backend.auth.jwt", "low", sf, sf, "JWT authentication detected - ensure proper token validation and refresh strategy", findings);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (isControllerFile) {
|
|
830
|
+
sf.getDescendantsOfKind(SyntaxKind.MethodDeclaration).forEach((method) => {
|
|
831
|
+
const decorators = method.getDecorators();
|
|
832
|
+
const hasGuard = decorators.some((d) =>
|
|
833
|
+
d.getExpression().getText().includes("@UseGuards") ||
|
|
834
|
+
d.getExpression().getText().includes("JwtAuthGuard")
|
|
835
|
+
);
|
|
836
|
+
const methodName = method.getName();
|
|
837
|
+
if (!hasGuard && (methodName.includes("create") || methodName.includes("update") || methodName.includes("delete"))) {
|
|
838
|
+
pushFinding("backend.auth.guards", "high", sf, method, "Protected operation without auth guard - ensure proper authentication", findings);
|
|
839
|
+
pushFinding("backend.auth.missing_guard", "high", sf, method, "Protected operation without auth guard - ensure proper authentication", findings);
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
sf.getDescendantsOfKind(SyntaxKind.Decorator).forEach((decorator) => {
|
|
845
|
+
if (decorator.getExpression().getText().includes("@Roles")) {
|
|
846
|
+
if (insightsEnabled) {
|
|
847
|
+
pushFinding("backend.auth.rbac", "low", sf, decorator, "Role-based access control detected - ensure proper role validation", findings);
|
|
848
|
+
}
|
|
849
|
+
} else if (sf.getFullText().includes("@Controller") && !sf.getFullText().includes("@Roles")) {
|
|
850
|
+
pushFinding("backend.auth.missing_roles", "medium", sf, decorator, "Controller without @Roles annotations for RBAC", findings);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
if (insightsEnabled) {
|
|
855
|
+
if (sf.getFullText().includes("@nestjs/throttler") || sf.getFullText().includes("rate-limit")) {
|
|
856
|
+
pushFinding("backend.security.rate_limiting", "low", sf, sf, "Rate limiting detected - good practice for API protection", findings);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (sf.getFullText().includes("express()") && !sf.getFullText().includes("helmet")) {
|
|
861
|
+
pushFinding("backend.security.missing_helmet", "high", sf, sf, "Missing Helmet security headers middleware", findings);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const isBusinessLogic = /\/(controllers?|services?|use-?cases?|handlers?)\//i.test(filePath) ||
|
|
865
|
+
/(controller|service|usecase|handler)\.ts$/i.test(filePath);
|
|
866
|
+
if (isBusinessLogic && !sf.getFullText().includes("winston") && !sf.getFullText().includes("audit") && !sf.getFullText().includes("Logger")) {
|
|
867
|
+
pushFinding("backend.security.missing_audit_logging", "medium", sf, sf, "Audit logging not detected - add structured audit logs", findings);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
871
|
+
const expr = call.getExpression().getText();
|
|
872
|
+
if (expr.includes("req.body") || expr.includes("req.query") || expr.includes("req.params")) {
|
|
873
|
+
const hasSanitization = sf.getFullText().includes("sanitize") ||
|
|
874
|
+
sf.getFullText().includes("escape") ||
|
|
875
|
+
sf.getFullText().includes("validator");
|
|
876
|
+
if (!hasSanitization) {
|
|
877
|
+
pushFinding("backend.security.input_sanitization", "medium", sf, call, "User input without sanitization - prevent XSS and injection attacks", findings);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
if (insightsEnabled) {
|
|
883
|
+
if (sf.getFullText().includes("redis") || sf.getFullText().includes("ioredis")) {
|
|
884
|
+
pushFinding("backend.caching.redis", "low", sf, sf, "Redis caching detected - ensure proper cache invalidation strategy", findings);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (insightsEnabled) {
|
|
889
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
890
|
+
const expr = call.getExpression().getText();
|
|
891
|
+
if (expr.includes("cache.get") || expr.includes("redis.get")) {
|
|
892
|
+
pushFinding("backend.caching.pattern", "low", sf, call, "Cache access detected - ensure cache-aside pattern implementation", findings);
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (insightsEnabled) {
|
|
898
|
+
if (sf.getFullText().includes("health") || sf.getFullText().includes("readiness") || sf.getFullText().includes("liveness")) {
|
|
899
|
+
pushFinding("backend.health.checks", "low", sf, sf, "Health checks detected - ensure proper liveness/readiness probes", findings);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
if (insightsEnabled) {
|
|
904
|
+
if (sf.getFullText().includes("winston") || sf.getFullText().includes("pino")) {
|
|
905
|
+
pushFinding("backend.logging.structured", "low", sf, sf, "Structured logging detected - ensure correlation IDs and proper log levels", findings);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
910
|
+
const txt = call.getText();
|
|
911
|
+
if (/bcrypt\.hash\(/.test(txt)) {
|
|
912
|
+
const args = call.getArguments().map(a => a.getText());
|
|
913
|
+
const salt = args[1] || "";
|
|
914
|
+
if (/^[0-9]+$/.test(salt) && parseInt(salt, 10) < 10) {
|
|
915
|
+
pushFinding("backend.auth.weak_password_hashing", "high", sf, call, "bcrypt salt rounds < 10 detected", findings);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (/md5\(|sha1\(/i.test(txt)) {
|
|
919
|
+
pushFinding("backend.auth.weak_password_hashing", "high", sf, call, "Weak hash function (MD5/SHA1) detected for passwords", findings);
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
if (isTestFile(filePath)) {
|
|
924
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
925
|
+
const expr = call.getExpression().getText();
|
|
926
|
+
if (expr.includes("jest.mock") || expr.includes("mock(")) {
|
|
927
|
+
pushFinding("backend.testing.mocks", "low", sf, call, "Mock usage in tests - prefer spies over mocks when possible", findings);
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (insightsEnabled) {
|
|
933
|
+
if (isTestFile(filePath) && sf.getFullText().includes("supertest") || sf.getFullText().includes("TestContainer")) {
|
|
934
|
+
pushFinding("backend.testing.integration", "low", sf, sf, "Integration testing detected - ensure proper test isolation and cleanup", findings);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (insightsEnabled) {
|
|
939
|
+
if (sf.getFullText().includes("@Module") && sf.getFullText().includes("@nestjs/common")) {
|
|
940
|
+
pushFinding("backend.architecture.module", "info", sf, sf, "NestJS module detected - ensure proper separation of concerns", findings);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const isServiceOrRepo = /Service|Repository|Controller|Provider/.test(sf.getBaseName());
|
|
945
|
+
if (isServiceOrRepo) {
|
|
946
|
+
sf.getDescendantsOfKind(SyntaxKind.Constructor).forEach((ctor) => {
|
|
947
|
+
const params = ctor.getParameters();
|
|
948
|
+
const classDecl = ctor.getParent();
|
|
949
|
+
if (!classDecl || params.length === 0) return;
|
|
950
|
+
|
|
951
|
+
const depNames = [];
|
|
952
|
+
params.forEach((param) => {
|
|
953
|
+
const nameNode = typeof param.getNameNode === 'function' ? param.getNameNode() : null;
|
|
954
|
+
if (nameNode && typeof nameNode.getKind === 'function' && nameNode.getKind() === SyntaxKind.ObjectBindingPattern) {
|
|
955
|
+
const elements = typeof nameNode.getElements === 'function' ? nameNode.getElements() : [];
|
|
956
|
+
elements.forEach((el) => {
|
|
957
|
+
const elNameNode = typeof el.getNameNode === 'function' ? el.getNameNode() : null;
|
|
958
|
+
const depName = elNameNode ? elNameNode.getText() : null;
|
|
959
|
+
if (depName) depNames.push(depName);
|
|
960
|
+
});
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const depName = param.getName();
|
|
965
|
+
if (depName && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(depName)) {
|
|
966
|
+
depNames.push(depName);
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
if (depNames.length === 0) return;
|
|
971
|
+
|
|
972
|
+
const ctorBody = typeof ctor.getBody === 'function' ? ctor.getBody() : null;
|
|
973
|
+
const depsSet = new Set(depNames);
|
|
974
|
+
|
|
975
|
+
const assignedPropsByDep = new Map();
|
|
976
|
+
const recordAssignedProp = (depName, propName) => {
|
|
977
|
+
if (!assignedPropsByDep.has(depName)) assignedPropsByDep.set(depName, new Set());
|
|
978
|
+
assignedPropsByDep.get(depName).add(propName);
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
if (ctorBody) {
|
|
982
|
+
const binaries = ctorBody.getDescendantsOfKind(SyntaxKind.BinaryExpression);
|
|
983
|
+
binaries.forEach((bin) => {
|
|
984
|
+
const op = typeof bin.getOperatorToken === 'function' ? bin.getOperatorToken().getText() : null;
|
|
985
|
+
if (op !== '=') return;
|
|
986
|
+
const left = typeof bin.getLeft === 'function' ? bin.getLeft() : null;
|
|
987
|
+
const right = typeof bin.getRight === 'function' ? bin.getRight() : null;
|
|
988
|
+
if (!left || !right) return;
|
|
989
|
+
|
|
990
|
+
if (left.getKind() === SyntaxKind.PropertyAccessExpression && right.getKind() === SyntaxKind.Identifier) {
|
|
991
|
+
const leftExpr = left.getExpression();
|
|
992
|
+
if (leftExpr && leftExpr.getKind() === SyntaxKind.ThisExpression) {
|
|
993
|
+
const propName = left.getName();
|
|
994
|
+
const depName = right.getText();
|
|
995
|
+
if (depsSet.has(depName) && propName) {
|
|
996
|
+
recordAssignedProp(depName, propName);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const isThisPropWrite = (node) => {
|
|
1004
|
+
const parent = node.getParent();
|
|
1005
|
+
if (!parent) return false;
|
|
1006
|
+
|
|
1007
|
+
if (parent.getKind && parent.getKind() === SyntaxKind.BinaryExpression) {
|
|
1008
|
+
const op = parent.getOperatorToken?.().getText?.();
|
|
1009
|
+
const left = parent.getLeft?.();
|
|
1010
|
+
return op === '=' && left === node;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (parent.getKind && parent.getKind() === SyntaxKind.PostfixUnaryExpression) return true;
|
|
1014
|
+
if (parent.getKind && parent.getKind() === SyntaxKind.PrefixUnaryExpression) return true;
|
|
1015
|
+
return false;
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
const allThisPropAccesses = classDecl
|
|
1019
|
+
.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)
|
|
1020
|
+
.filter((pa) => pa.getExpression()?.getKind?.() === SyntaxKind.ThisExpression);
|
|
1021
|
+
|
|
1022
|
+
const readProps = new Set(
|
|
1023
|
+
allThisPropAccesses
|
|
1024
|
+
.filter((pa) => !isThisPropWrite(pa))
|
|
1025
|
+
.map((pa) => pa.getName())
|
|
1026
|
+
.filter(Boolean)
|
|
1027
|
+
);
|
|
1028
|
+
|
|
1029
|
+
const directDepUseCount = new Map();
|
|
1030
|
+
depNames.forEach((depName) => directDepUseCount.set(depName, 0));
|
|
1031
|
+
|
|
1032
|
+
if (ctorBody) {
|
|
1033
|
+
const identifiers = ctorBody.getDescendantsOfKind(SyntaxKind.Identifier);
|
|
1034
|
+
identifiers.forEach((id) => {
|
|
1035
|
+
const depName = id.getText();
|
|
1036
|
+
if (!depsSet.has(depName)) return;
|
|
1037
|
+
|
|
1038
|
+
const bin = id.getFirstAncestorByKind(SyntaxKind.BinaryExpression);
|
|
1039
|
+
if (bin && bin.getRight?.() === id && bin.getOperatorToken?.().getText?.() === '=') {
|
|
1040
|
+
const left = bin.getLeft?.();
|
|
1041
|
+
if (left && left.getKind?.() === SyntaxKind.PropertyAccessExpression && left.getExpression?.().getKind?.() === SyntaxKind.ThisExpression) {
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
directDepUseCount.set(depName, (directDepUseCount.get(depName) || 0) + 1);
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const unusedDeps = [];
|
|
1051
|
+
depNames.forEach((depName) => {
|
|
1052
|
+
const assignedProps = assignedPropsByDep.get(depName);
|
|
1053
|
+
const hasReadProp = assignedProps ? Array.from(assignedProps).some((p) => readProps.has(p)) : false;
|
|
1054
|
+
const hasDirectUse = (directDepUseCount.get(depName) || 0) > 0;
|
|
1055
|
+
if (!hasReadProp && !hasDirectUse) {
|
|
1056
|
+
unusedDeps.push(depName);
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
if (unusedDeps.length > 0) {
|
|
1061
|
+
pushFinding(
|
|
1062
|
+
"backend.architecture.di",
|
|
1063
|
+
"high",
|
|
1064
|
+
sf,
|
|
1065
|
+
ctor,
|
|
1066
|
+
`Unused/underused dependencies: ${unusedDeps.join(', ')} - remove or use them (ISP violation)`,
|
|
1067
|
+
findings
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const filePathNormalized = filePath.replace(/\\/g, "/");
|
|
1074
|
+
const filePathNormalizedLower = filePathNormalized.toLowerCase();
|
|
1075
|
+
if (insightsEnabled) {
|
|
1076
|
+
if (filePathNormalized.includes("/domain/")) {
|
|
1077
|
+
pushFinding("backend.clean.domain", "low", sf, sf, "Domain layer file - ensure contains only business logic and entities", findings);
|
|
1078
|
+
}
|
|
1079
|
+
if (filePathNormalized.includes("/application/")) {
|
|
1080
|
+
pushFinding("backend.clean.application", "low", sf, sf, "Application layer file - ensure contains use cases and application logic", findings);
|
|
1081
|
+
}
|
|
1082
|
+
const isInternalAstToolingFile = filePathNormalizedLower.includes("/infrastructure/ast/") || filePathNormalizedLower.includes("/scripts/hooks-system/infrastructure/ast/");
|
|
1083
|
+
if (filePathNormalized.includes("/infrastructure/") && !isInternalAstToolingFile) {
|
|
1084
|
+
pushFinding("backend.clean.infrastructure", "low", sf, sf, "Infrastructure layer file - ensure contains external concerns and implementations", findings);
|
|
1085
|
+
}
|
|
1086
|
+
if (filePathNormalized.includes("/presentation/")) {
|
|
1087
|
+
pushFinding("backend.clean.presentation", "low", sf, sf, "Presentation layer file - ensure contains controllers and DTOs", findings);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
sf.getDescendantsOfKind(SyntaxKind.InterfaceDeclaration).forEach((iface) => {
|
|
1092
|
+
const name = iface.getName();
|
|
1093
|
+
if (name && name.includes("Repository")) {
|
|
1094
|
+
if (insightsEnabled) {
|
|
1095
|
+
pushFinding("backend.repository.pattern", "low", sf, iface, "Repository interface detected - good abstraction for data access", findings);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
1101
|
+
const name = cls.getName();
|
|
1102
|
+
if (name && name.includes("Service") && !name.includes("Repository")) {
|
|
1103
|
+
if (insightsEnabled) {
|
|
1104
|
+
pushFinding("backend.service.layer", "low", sf, cls, "Service class detected - ensure orchestrates business logic without data access", findings);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
1110
|
+
const name = cls.getName();
|
|
1111
|
+
if (name && name.includes("Controller")) {
|
|
1112
|
+
if (insightsEnabled) {
|
|
1113
|
+
pushFinding("backend.controller.layer", "low", sf, cls, "Controller detected - ensure thin layer focused on HTTP concerns", findings);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
1119
|
+
const name = cls.getName();
|
|
1120
|
+
if (name && (name.includes("Dto") || name.includes("DTO") || name.includes("Request") || name.includes("Response"))) {
|
|
1121
|
+
if (insightsEnabled) {
|
|
1122
|
+
pushFinding("backend.dto.pattern", "low", sf, cls, "DTO detected - ensure used for data transfer between layers", findings);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
1128
|
+
const name = cls.getName();
|
|
1129
|
+
if (name && name.includes("Mapper")) {
|
|
1130
|
+
if (insightsEnabled) {
|
|
1131
|
+
pushFinding("backend.mapper.pattern", "low", sf, cls, "Mapper detected - ensure handles conversion between domain objects and DTOs", findings);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
1137
|
+
const name = cls.getName();
|
|
1138
|
+
if (name && name.includes("Factory")) {
|
|
1139
|
+
if (insightsEnabled) {
|
|
1140
|
+
pushFinding("backend.factory.pattern", "low", sf, cls, "Factory detected - good for complex object creation", findings);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
sf.getDescendantsOfKind(SyntaxKind.VariableDeclaration).forEach((varDecl) => {
|
|
1146
|
+
const text = varDecl.getText();
|
|
1147
|
+
if (text.includes("static") && text.includes("INSTANCE")) {
|
|
1148
|
+
pushFinding("backend.singleton.pattern", "medium", sf, varDecl, "Singleton pattern detected - consider dependency injection instead", findings);
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
1153
|
+
const expr = call.getExpression().getText();
|
|
1154
|
+
if (expr.includes("subscribe") || expr.includes("on(") || expr.includes("emit")) {
|
|
1155
|
+
if (insightsEnabled) {
|
|
1156
|
+
pushFinding("backend.observer.pattern", "low", sf, call, "Observer pattern usage detected - good for event-driven architecture", findings);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
sf.getDescendantsOfKind(SyntaxKind.InterfaceDeclaration).forEach((iface) => {
|
|
1162
|
+
const methods = iface.getMethods();
|
|
1163
|
+
if (methods.length === 1) {
|
|
1164
|
+
if (insightsEnabled) {
|
|
1165
|
+
pushFinding("backend.strategy.pattern", "low", sf, iface, "Single-method interface detected - potential strategy pattern", findings);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
|
|
1171
|
+
const methods = cls.getMethods();
|
|
1172
|
+
const hasAbstract = methods.some((m) => m.getText().includes("abstract"));
|
|
1173
|
+
const hasOverride = methods.some((m) => m.getText().includes("override"));
|
|
1174
|
+
if (hasAbstract && hasOverride) {
|
|
1175
|
+
if (insightsEnabled) {
|
|
1176
|
+
pushFinding("backend.template.pattern", "low", sf, cls, "Template method pattern detected - good for algorithm customization", findings);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
sf.getDescendantsOfKind(SyntaxKind.Decorator).forEach((decorator) => {
|
|
1182
|
+
const expr = decorator.getExpression().getText();
|
|
1183
|
+
if (expr.includes("@") && !expr.includes("@nestjs")) {
|
|
1184
|
+
if (insightsEnabled) {
|
|
1185
|
+
pushFinding("backend.decorator.pattern", "low", sf, decorator, "Custom decorator detected - ensure follows decorator pattern principles", findings);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
1191
|
+
const expr = call.getExpression().getText();
|
|
1192
|
+
if (expr.includes("use(") && (expr.includes("middleware") || expr.includes("Middleware"))) {
|
|
1193
|
+
if (insightsEnabled) {
|
|
1194
|
+
pushFinding("backend.middleware.pattern", "low", sf, call, "Middleware usage detected - ensure proper request/response processing", findings);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
1200
|
+
const expr = call.getExpression().getText();
|
|
1201
|
+
if (expr.includes("pipe(") || expr.includes("pipeline")) {
|
|
1202
|
+
if (insightsEnabled) {
|
|
1203
|
+
pushFinding("backend.pipeline.pattern", "low", sf, call, "Pipeline pattern detected - good for data processing chains", findings);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
if (insightsEnabled) {
|
|
1209
|
+
if (sf.getFullText().includes("circuit") || sf.getFullText().includes("breaker")) {
|
|
1210
|
+
pushFinding("backend.circuit_breaker", "low", sf, sf, "Circuit breaker pattern detected - good for fault tolerance", findings);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
if (insightsEnabled) {
|
|
1215
|
+
if (sf.getFullText().includes("bulkhead") || sf.getFullText().includes("isolation")) {
|
|
1216
|
+
pushFinding("backend.bulkhead.pattern", "low", sf, sf, "Bulkhead pattern detected - good for resource isolation", findings);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (sf.getFullText().includes("saga") || sf.getFullText().includes("compensation")) {
|
|
1220
|
+
pushFinding("backend.saga.pattern", "low", sf, sf, "Saga pattern detected - good for distributed transactions", findings);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (sf.getFullText().includes("Command") && sf.getFullText().includes("Query")) {
|
|
1224
|
+
pushFinding("backend.cqrs.pattern", "low", sf, sf, "CQRS pattern detected - ensure proper separation of read/write models", findings);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (sf.getFullText().includes("EventStore") || sf.getFullText().includes("event sourcing")) {
|
|
1228
|
+
pushFinding("backend.event_sourcing", "low", sf, sf, "Event sourcing detected - ensure proper event versioning and replay", findings);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (sf.getFullText().includes("axios") || sf.getFullText().includes("HttpService")) {
|
|
1232
|
+
pushFinding("backend.microservices.comm", "low", sf, sf, "Inter-service communication detected - ensure proper error handling and timeouts", findings);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
sf.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((str) => {
|
|
1236
|
+
const text = str.getLiteralValue();
|
|
1237
|
+
if (/\/api\/v\d+\//.test(text)) {
|
|
1238
|
+
pushFinding("backend.api.versioning", "low", sf, str, "API versioning detected - ensure proper version management", findings);
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
if (sf.getFullText().includes("@nestjs/swagger") || sf.getFullText().includes("swagger")) {
|
|
1243
|
+
pushFinding("backend.api.documentation", "low", sf, sf, "API documentation detected - ensure complete and accurate OpenAPI specs", findings);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
if (sf.getFullText().includes("@nestjs/graphql") || sf.getFullText().includes("graphql")) {
|
|
1247
|
+
pushFinding("backend.graphql.usage", "low", sf, sf, "GraphQL usage detected - ensure proper schema design and resolvers", findings);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if (sf.getFullText().includes("@nestjs/websockets") || sf.getFullText().includes("socket.io")) {
|
|
1251
|
+
pushFinding("backend.websocket.usage", "low", sf, sf, "WebSocket usage detected - ensure proper connection management", findings);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
1255
|
+
const expr = call.getExpression().getText();
|
|
1256
|
+
if (expr.includes("multer") || expr.includes("upload")) {
|
|
1257
|
+
pushFinding("backend.file.upload", "low", sf, call, "File upload detected - ensure proper validation and security checks", findings);
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
if (sf.getFullText().includes("@nestjs/schedule") || sf.getFullText().includes("cron")) {
|
|
1262
|
+
pushFinding("backend.scheduled.tasks", "low", sf, sf, "Scheduled tasks detected - ensure proper error handling and logging", findings);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (sf.getFullText().includes("i18n") || sf.getFullText().includes("i18next")) {
|
|
1266
|
+
pushFinding("backend.i18n.support", "low", sf, sf, "Internationalization detected - ensure proper message translation and locale handling", findings);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (sf.getFullText().includes("feature.flag") || sf.getFullText().includes("toggle")) {
|
|
1270
|
+
pushFinding("backend.feature.flags", "low", sf, sf, "Feature flags detected - ensure proper flag management and cleanup", findings);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
if (sf.getFullText().includes("experiment") || sf.getFullText().includes("ab.test")) {
|
|
1274
|
+
pushFinding("backend.ab.testing", "low", sf, sf, "A/B testing detected - ensure proper experiment tracking and analysis", findings);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (insightsEnabled) {
|
|
1279
|
+
if (sf.getFullText().includes("analytics") || sf.getFullText().includes("tracking")) {
|
|
1280
|
+
pushFinding("backend.analytics.tracking", "low", sf, sf, "Analytics tracking detected - ensure GDPR compliance and proper data handling", findings);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (sf.getFullText().includes("gdpr") || sf.getFullText().includes("consent")) {
|
|
1284
|
+
pushFinding("backend.gdpr.compliance", "low", sf, sf, "GDPR compliance detected - ensure proper data protection measures", findings);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
if (insightsEnabled) {
|
|
1289
|
+
if (sf.getFullText().includes("audit") || sf.getFullText().includes("audit_log")) {
|
|
1290
|
+
pushFinding("backend.audit.logging", "low", sf, sf, "Audit logging detected - ensure tamper-proof and comprehensive logging", findings);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (sf.getFullText().includes("encrypt") || sf.getFullText().includes("crypto")) {
|
|
1294
|
+
pushFinding("backend.data.encryption", "low", sf, sf, "Data encryption detected - ensure proper key management and algorithm selection", findings);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
if (insightsEnabled) {
|
|
1299
|
+
if (sf.getFullText().includes("backup") || sf.getFullText().includes("snapshot")) {
|
|
1300
|
+
pushFinding("backend.backup.strategy", "low", sf, sf, "Backup strategy detected - ensure regular and tested backups", findings);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
if (sf.getFullText().includes("alert") || sf.getFullText().includes("notification")) {
|
|
1304
|
+
pushFinding("backend.monitoring.alerts", "low", sf, sf, "Monitoring alerts detected - ensure actionable and non-spammy alerts", findings);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if (sf.getFullText().includes("profiler") || sf.getFullText().includes("benchmark")) {
|
|
1308
|
+
pushFinding("backend.performance.profiling", "low", sf, sf, "Performance profiling detected - ensure regular performance monitoring", findings);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (sf.getFullText().includes("heap") || sf.getFullText().includes("garbage")) {
|
|
1312
|
+
pushFinding("backend.memory.management", "low", sf, sf, "Memory management detected - ensure proper resource cleanup", findings);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
if (sf.getFullText().includes("synchronized") || sf.getFullText().includes("mutex")) {
|
|
1316
|
+
pushFinding("backend.thread.safety", "low", sf, sf, "Thread safety measures detected - ensure proper synchronization", findings);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if (sf.getFullText().includes("pool") || sf.getFullText().includes("connection.pool")) {
|
|
1320
|
+
pushFinding("backend.connection.pooling", "low", sf, sf, "Connection pooling detected - ensure proper pool sizing and monitoring", findings);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
1324
|
+
const expr = call.getExpression().getText();
|
|
1325
|
+
if (expr.includes("timeout") || expr.includes("setTimeout")) {
|
|
1326
|
+
pushFinding("backend.timeout.management", "low", sf, call, "Timeout management detected - ensure reasonable timeout values", findings);
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
if (sf.getFullText().includes("retry") || sf.getFullText().includes("backoff")) {
|
|
1331
|
+
pushFinding("backend.retry.mechanism", "low", sf, sf, "Retry mechanism detected - ensure exponential backoff and circuit breaker", findings);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
if (sf.getFullText().includes("shutdown") || sf.getFullText().includes("SIGTERM")) {
|
|
1335
|
+
pushFinding("backend.graceful.shutdown", "low", sf, sf, "Graceful shutdown detected - ensure proper cleanup and request draining", findings);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
if (sf.getFullText().includes("vault") || sf.getFullText().includes("secrets")) {
|
|
1339
|
+
pushFinding("backend.secrets.management", "low", sf, sf, "Secrets management detected - ensure secure key rotation and access control", findings);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
if (sf.getFullText().includes("docker") || sf.getFullText().includes("Dockerfile")) {
|
|
1343
|
+
pushFinding("backend.containerization", "low", sf, sf, "Containerization detected - ensure proper image optimization and security", findings);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
if (sf.getFullText().includes("kubernetes") || sf.getFullText().includes("k8s")) {
|
|
1347
|
+
pushFinding("backend.orchestration", "low", sf, sf, "Container orchestration detected - ensure proper resource limits and health checks", findings);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
if (sf.getFullText().includes("pipeline") || sf.getFullText().includes("workflow")) {
|
|
1351
|
+
pushFinding("backend.cicd.pipelines", "low", sf, sf, "CI/CD pipelines detected - ensure automated testing and deployment", findings);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
if (sf.getFullText().includes("blue.green") || sf.getFullText().includes("canary")) {
|
|
1355
|
+
pushFinding("backend.deployment.strategy", "low", sf, sf, "Advanced deployment strategy detected - ensure proper rollback procedures", findings);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
if (sf.getFullText().includes("chaos") || sf.getFullText().includes("fault.injection")) {
|
|
1359
|
+
pushFinding("backend.chaos.engineering", "low", sf, sf, "Chaos engineering detected - ensure systematic testing of failure scenarios", findings);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
if (sf.getFullText().includes("istio") || sf.getFullText().includes("linkerd")) {
|
|
1363
|
+
pushFinding("backend.service.mesh", "low", sf, sf, "Service mesh detected - ensure proper traffic management and observability", findings);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
if (sf.getFullText().includes("gateway") || sf.getFullText().includes("kong")) {
|
|
1367
|
+
pushFinding("backend.api.gateway", "low", sf, sf, "API gateway detected - ensure proper routing and security policies", findings);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
if (sf.getFullText().includes("rabbitmq") || sf.getFullText().includes("kafka")) {
|
|
1371
|
+
pushFinding("backend.message.queues", "low", sf, sf, "Message queue detected - ensure proper message handling and dead letter queues", findings);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
if (sf.getFullText().includes("stream") || sf.getFullText().includes("reactive")) {
|
|
1375
|
+
pushFinding("backend.stream.processing", "low", sf, sf, "Stream processing detected - ensure proper backpressure handling", findings);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (sf.getFullText().includes("warehouse") || sf.getFullText().includes("redshift")) {
|
|
1379
|
+
pushFinding("backend.data.warehousing", "low", sf, sf, "Data warehousing detected - ensure proper ETL processes and data quality", findings);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
if (sf.getFullText().includes("tensorflow") || sf.getFullText().includes("pytorch")) {
|
|
1383
|
+
pushFinding("backend.ml.integration", "low", sf, sf, "Machine learning integration detected - ensure proper model versioning and monitoring", findings);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
if (sf.getFullText().includes("web3") || sf.getFullText().includes("blockchain")) {
|
|
1387
|
+
pushFinding("backend.blockchain.integration", "low", sf, sf, "Blockchain integration detected - ensure proper smart contract interactions", findings);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
if (sf.getFullText().includes("mqtt") || sf.getFullText().includes("iot")) {
|
|
1391
|
+
pushFinding("backend.iot.integration", "low", sf, sf, "IoT integration detected - ensure proper device management and security", findings);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
// ==========================================
|
|
1395
|
+
// ==========================================
|
|
1396
|
+
|
|
1397
|
+
const rawSQLPattern = /query\s*\(\s*`[^`]*\$\{[^}]+\}|execut(e|eRaw)\s*\(\s*`[^`]*\$\{/gi;
|
|
1398
|
+
let sqlMatch;
|
|
1399
|
+
|
|
1400
|
+
while ((sqlMatch = rawSQLPattern.exec(fullText)) !== null) {
|
|
1401
|
+
const lineNumber = fullText.substring(0, sqlMatch.index).split('\n').length;
|
|
1402
|
+
pushFinding(
|
|
1403
|
+
"backend.security.sql_injection",
|
|
1404
|
+
"critical",
|
|
1405
|
+
sf,
|
|
1406
|
+
sf,
|
|
1407
|
+
`🚨 CRITICAL SQL Injection Risk (line ${lineNumber}): Raw SQL with template literals. Use parameterized queries: query('SELECT * FROM users WHERE id = $1', [userId]). Never: query(\`SELECT * FROM users WHERE id = \${userId}\`). Prevents: Data breach, unauthorized access, data loss`,
|
|
1408
|
+
findings
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
const loopQueryPattern = /for\s*\([^)]+\)[^{]*\{[^}]*\.(findOne|findById|query|execute)\(/g;
|
|
1413
|
+
if (loopQueryPattern.test(fullText)) {
|
|
1414
|
+
pushFinding(
|
|
1415
|
+
"backend.performance.n_plus_one",
|
|
1416
|
+
"critical",
|
|
1417
|
+
sf,
|
|
1418
|
+
sf,
|
|
1419
|
+
'🚨 CRITICAL N+1 Query: Database query inside loop. Use: findByIds([ids]) or JOIN. Example: const users = await repo.findByIds(orderIds); instead of: for(order of orders) { user = await repo.findById(order.userId); }. Impact: 1000 queries = 10s response time',
|
|
1420
|
+
findings
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const plainPasswordPattern = /password\s*[:=]\s*[^b][^c][^r][^y][^p][^t]/i;
|
|
1425
|
+
if (plainPasswordPattern.test(fullText) && !fullText.includes('bcrypt') && !fullText.includes('argon2') && !fullText.includes('hash')) {
|
|
1426
|
+
pushFinding(
|
|
1427
|
+
"backend.security.plain_password",
|
|
1428
|
+
"critical",
|
|
1429
|
+
sf,
|
|
1430
|
+
sf,
|
|
1431
|
+
'🚨 CRITICAL Security: Password not hashed. Use bcrypt: import * as bcrypt from \'bcrypt\'; const hash = await bcrypt.hash(password, 10); Never store plain passwords. Prevents: Account compromise, compliance violation (GDPR, PCI-DSS)',
|
|
1432
|
+
findings
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
if (filePath.includes('/auth/') || filePath.includes('/guards/')) {
|
|
1437
|
+
const hasJWT = fullText.includes('@nestjs/jwt') || fullText.includes('JwtService') || fullText.includes('JwtStrategy');
|
|
1438
|
+
const hasAuth = fullText.includes('@UseGuards') || fullText.includes('AuthGuard');
|
|
1439
|
+
|
|
1440
|
+
if (!hasJWT && hasAuth) {
|
|
1441
|
+
pushFinding(
|
|
1442
|
+
"backend.security.missing_jwt",
|
|
1443
|
+
"critical",
|
|
1444
|
+
sf,
|
|
1445
|
+
sf,
|
|
1446
|
+
'🚨 CRITICAL Auth: Auth guard without JWT strategy. Install: @nestjs/jwt @nestjs/passport passport-jwt. Implement JwtStrategy extends PassportStrategy. Use: @UseGuards(JwtAuthGuard). Prevents: Unauthorized access, session hijacking',
|
|
1447
|
+
findings
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
if (filePath.includes('main.ts')) {
|
|
1453
|
+
const hasHelmet = fullText.includes('helmet') || fullText.includes('helmet()');
|
|
1454
|
+
|
|
1455
|
+
if (!hasHelmet) {
|
|
1456
|
+
pushFinding(
|
|
1457
|
+
"backend.security.missing_helmet",
|
|
1458
|
+
"critical",
|
|
1459
|
+
sf,
|
|
1460
|
+
sf,
|
|
1461
|
+
'🚨 CRITICAL Security Headers: Missing Helmet. Install: npm i helmet. In main.ts: app.use(helmet()). Sets 15 security headers: X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security, etc. Prevents: Clickjacking, MIME sniffing, XSS',
|
|
1462
|
+
findings
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
if (filePath.includes('main.ts')) {
|
|
1468
|
+
const hasCORS = fullText.includes('enableCors') || fullText.includes('cors()');
|
|
1469
|
+
const hasWildcard = fullText.includes("origin: '*'") || fullText.includes('origin: true');
|
|
1470
|
+
|
|
1471
|
+
if (!hasCORS) {
|
|
1472
|
+
pushFinding(
|
|
1473
|
+
"backend.security.missing_cors",
|
|
1474
|
+
"critical",
|
|
1475
|
+
sf,
|
|
1476
|
+
sf,
|
|
1477
|
+
'🚨 CRITICAL CORS: Missing CORS configuration. In main.ts: app.enableCors({ origin: [\'https://yourdomain.com\'], credentials: true }). Never use origin: \'*\' in production. Prevents: Unauthorized domain access, CSRF attacks',
|
|
1478
|
+
findings
|
|
1479
|
+
);
|
|
1480
|
+
} else if (hasWildcard) {
|
|
1481
|
+
pushFinding(
|
|
1482
|
+
"backend.security.cors_wildcard",
|
|
1483
|
+
"critical",
|
|
1484
|
+
sf,
|
|
1485
|
+
sf,
|
|
1486
|
+
'🚨 CRITICAL CORS: Wildcard origin (*) in production. Allows ANY domain to access your API. Use specific origins: origin: [\'https://yourdomain.com\']. Prevents: CSRF, unauthorized access, data theft',
|
|
1487
|
+
findings
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
if (filePath.includes('.dto.ts') || filePath.endsWith('Dto.ts')) {
|
|
1493
|
+
const hasValidation = fullText.includes('@IsString') ||
|
|
1494
|
+
fullText.includes('@IsEmail') ||
|
|
1495
|
+
fullText.includes('@IsNumber') ||
|
|
1496
|
+
fullText.includes('class-validator');
|
|
1497
|
+
|
|
1498
|
+
const hasClass = fullText.includes('export class ') && fullText.includes('Dto');
|
|
1499
|
+
|
|
1500
|
+
if (hasClass && !hasValidation) {
|
|
1501
|
+
pushFinding(
|
|
1502
|
+
"backend.security.missing_dto_validation",
|
|
1503
|
+
"critical",
|
|
1504
|
+
sf,
|
|
1505
|
+
sf,
|
|
1506
|
+
'🚨 CRITICAL Input Validation: DTO without class-validator decorators. Install: class-validator class-transformer. Use: @IsString(), @IsEmail(), @Min(), @Max(). Enable in main.ts: app.useGlobalPipes(new ValidationPipe({ whitelist: true })). Prevents: Injection attacks, invalid data, crashes',
|
|
1507
|
+
findings
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
const isDomain = filePath.includes('/domain/') && !filePath.includes('Impl');
|
|
1513
|
+
if (isDomain) {
|
|
1514
|
+
const forbiddenImports = ['@nestjs/', 'express', 'fastify', 'typeorm', 'mongoose', 'prisma'];
|
|
1515
|
+
|
|
1516
|
+
forbiddenImports.forEach(forbidden => {
|
|
1517
|
+
if (fullText.includes(`from '${forbidden}`) || fullText.includes(`from "${forbidden}`)) {
|
|
1518
|
+
pushFinding(
|
|
1519
|
+
"backend.architecture.domain_purity",
|
|
1520
|
+
"critical",
|
|
1521
|
+
sf,
|
|
1522
|
+
sf,
|
|
1523
|
+
`🚨 CRITICAL Architecture: Domain layer importing ${forbidden}. Domain must be framework-agnostic. Move to infrastructure/. Use interfaces in domain/, implementations in infrastructure/. Prevents: Tight coupling, untestable code`,
|
|
1524
|
+
findings
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
const importPaths = [];
|
|
1531
|
+
sf.getImportDeclarations().forEach(imp => {
|
|
1532
|
+
const moduleSpec = imp.getModuleSpecifierValue();
|
|
1533
|
+
if (moduleSpec.startsWith('.') || moduleSpec.startsWith('/')) {
|
|
1534
|
+
importPaths.push(moduleSpec);
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
const currentModule = filePath.split('/').slice(-2)[0];
|
|
1539
|
+
importPaths.forEach(importPath => {
|
|
1540
|
+
if (importPath.includes(currentModule)) {
|
|
1541
|
+
pushFinding(
|
|
1542
|
+
"backend.architecture.circular_dependency",
|
|
1543
|
+
"critical",
|
|
1544
|
+
sf,
|
|
1545
|
+
sf,
|
|
1546
|
+
`🚨 CRITICAL Circular Dependency: ${currentModule} imports module that imports it back. Causes: Runtime errors, initialization failures, module.exports undefined. Refactor: Extract shared code to separate module, use dependency injection. NestJS will throw: "Circular dependency between modules"`,
|
|
1547
|
+
findings
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
if (filePath.includes('/filters/') || filePath.includes('/exception')) {
|
|
1553
|
+
const hasStackTrace = fullText.includes('stack:') || fullText.includes('error.stack');
|
|
1554
|
+
const hasEnvCheck = fullText.includes('NODE_ENV') || fullText.includes('process.env');
|
|
1555
|
+
|
|
1556
|
+
if (hasStackTrace && !hasEnvCheck) {
|
|
1557
|
+
pushFinding(
|
|
1558
|
+
"backend.security.stack_trace_exposure",
|
|
1559
|
+
"critical",
|
|
1560
|
+
sf,
|
|
1561
|
+
sf,
|
|
1562
|
+
'🚨 CRITICAL Info Disclosure: Stack traces exposed in production. Add: if (process.env.NODE_ENV !== \'production\') { response.stack = error.stack; }. Production should return generic error. Prevents: Path disclosure, framework version exposure, attack surface mapping',
|
|
1563
|
+
findings
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// ==========================================
|
|
1569
|
+
// ==========================================
|
|
1570
|
+
|
|
1571
|
+
if (filePath.includes('.controller.ts')) {
|
|
1572
|
+
const lines = fullText.split('\\n');
|
|
1573
|
+
const methodLines = lines.filter(l => l.trim().startsWith('async ') || l.trim().startsWith('public ') || l.trim().startsWith('private '));
|
|
1574
|
+
|
|
1575
|
+
if (methodLines.length > 100) {
|
|
1576
|
+
pushFinding(
|
|
1577
|
+
"backend.architecture.fat_controller",
|
|
1578
|
+
"high",
|
|
1579
|
+
sf,
|
|
1580
|
+
sf,
|
|
1581
|
+
`🚨 HIGH: Fat Controller (${methodLines.length} methods/lines). Controllers should only route + validate. Move business logic to services/use-cases. NestJS: Controllers are thin, Services are thick. Max recommended: 50 lines per controller method.`,
|
|
1582
|
+
findings
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
if (fullText.includes('.save(') && fullText.includes('.update(') && !fullText.includes('@Transaction()')) {
|
|
1588
|
+
const isSameFunction = fullText.match(/async\s+\w+\([^)]*\)[^{]*\{[^}]*\.save\([^}]*\.update\(/);
|
|
1589
|
+
|
|
1590
|
+
if (isSameFunction) {
|
|
1591
|
+
pushFinding(
|
|
1592
|
+
"backend.database.missing_transaction",
|
|
1593
|
+
"high",
|
|
1594
|
+
sf,
|
|
1595
|
+
sf,
|
|
1596
|
+
'🚨 HIGH: Multiple DB operations without @Transaction(). Data inconsistency risk. Wrap in transaction: @Transaction() async method() { await repo1.save(); await repo2.update(); }. Ensures: All succeed or all rollback.',
|
|
1597
|
+
findings
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
if (fullText.includes('extends HttpException') || fullText.includes('extends Error')) {
|
|
1603
|
+
const hasFilter = fullText.includes('@Catch(');
|
|
1604
|
+
|
|
1605
|
+
if (!hasFilter && filePath.includes('/exceptions/')) {
|
|
1606
|
+
pushFinding(
|
|
1607
|
+
"backend.error_handling.missing_exception_filter",
|
|
1608
|
+
"high",
|
|
1609
|
+
sf,
|
|
1610
|
+
sf,
|
|
1611
|
+
'🚨 HIGH: Custom exception without @Catch filter. Create: @Catch(MyException) export class MyExceptionFilter implements ExceptionFilter. Consistent error responses required.',
|
|
1612
|
+
findings
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
if (filePath.includes('Repository.ts') && !filePath.includes('IRepository') && !filePath.includes('Protocol')) {
|
|
1618
|
+
const hasInterface = fullText.includes('export interface') || fullText.includes('export abstract class');
|
|
1619
|
+
|
|
1620
|
+
if (!hasInterface && fullText.includes('export class')) {
|
|
1621
|
+
pushFinding(
|
|
1622
|
+
"backend.architecture.repository_no_interface",
|
|
1623
|
+
"high",
|
|
1624
|
+
sf,
|
|
1625
|
+
sf,
|
|
1626
|
+
'🚨 HIGH: Repository without interface. Create IRepository interface in domain/. Concrete implementation in infrastructure/. Dependency Inversion: Depend on abstractions. Testability: Mock interface, not class.',
|
|
1627
|
+
findings
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
const isAppOrDomainFile = /\/(domain|application)\//i.test(filePath);
|
|
1633
|
+
const isServiceFile = /\/(services?|use-?cases?)\//i.test(filePath) || /Service/.test(sf.getBaseName());
|
|
1634
|
+
const isDomainServiceFile = isAppOrDomainFile && isServiceFile && !/Repository/.test(filePath);
|
|
1635
|
+
if (isDomainServiceFile && !fullText.includes('eventEmitter.emit') && !fullText.includes('@OnEvent')) {
|
|
1636
|
+
const persistenceReceiverRegex = /(repo|repository|prisma|typeorm|mongoose|model|collection|db|database|entitymanager|manager|client|knex|sequelize|supabase)/i;
|
|
1637
|
+
const persistenceWriteMethods = new Set([
|
|
1638
|
+
'create',
|
|
1639
|
+
'update',
|
|
1640
|
+
'delete',
|
|
1641
|
+
'insert',
|
|
1642
|
+
'save',
|
|
1643
|
+
'upsert',
|
|
1644
|
+
'remove',
|
|
1645
|
+
'destroy'
|
|
1646
|
+
]);
|
|
1647
|
+
|
|
1648
|
+
const hasPersistenceWrite = sf
|
|
1649
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
1650
|
+
.some((call) => {
|
|
1651
|
+
const expr = call.getExpression();
|
|
1652
|
+
if (!expr || expr.getKind() !== SyntaxKind.PropertyAccessExpression) return false;
|
|
1653
|
+
const method = expr.getName();
|
|
1654
|
+
if (!persistenceWriteMethods.has(method)) return false;
|
|
1655
|
+
const receiverText = expr.getExpression()?.getText?.() || '';
|
|
1656
|
+
return persistenceReceiverRegex.test(receiverText);
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
if (hasPersistenceWrite) {
|
|
1660
|
+
pushFinding(
|
|
1661
|
+
"backend.architecture.missing_domain_event",
|
|
1662
|
+
"high",
|
|
1663
|
+
sf,
|
|
1664
|
+
sf,
|
|
1665
|
+
'🚨 HIGH: State change without domain event. Emit events for audit/integration: this.eventEmitter.emit(\'user.created\', new UserCreatedEvent(user)). Benefits: Audit trail, microservices integration, async processing.',
|
|
1666
|
+
findings
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
const isAnalyzerForPII = /infrastructure\/ast\/|analyzers\/|detectors\/|scanner|analyzer|detector/i.test(filePath);
|
|
1672
|
+
if (!isAnalyzerForPII && !isTestFile(filePath)) {
|
|
1673
|
+
const sensitiveKeywords = [
|
|
1674
|
+
'password',
|
|
1675
|
+
'secret',
|
|
1676
|
+
'ssn',
|
|
1677
|
+
'creditcard',
|
|
1678
|
+
'credit_card',
|
|
1679
|
+
'authorization',
|
|
1680
|
+
'bearer',
|
|
1681
|
+
'jwt',
|
|
1682
|
+
'accesstoken',
|
|
1683
|
+
'access_token',
|
|
1684
|
+
'refreshtoken',
|
|
1685
|
+
'refresh_token',
|
|
1686
|
+
'idtoken',
|
|
1687
|
+
'id_token',
|
|
1688
|
+
'apikey',
|
|
1689
|
+
'api_key'
|
|
1690
|
+
];
|
|
1691
|
+
const redactionKeywords = ['redact', 'mask', 'sanitize', 'obfuscate'];
|
|
1692
|
+
const logCallRegex = /^(console|logger|this\.logger)\.(log|info|debug|warn|error)$/;
|
|
1693
|
+
|
|
1694
|
+
const calls = sf.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
1695
|
+
for (const call of calls) {
|
|
1696
|
+
const exprText = call.getExpression().getText();
|
|
1697
|
+
if (!logCallRegex.test(exprText)) continue;
|
|
1698
|
+
|
|
1699
|
+
const args = call.getArguments();
|
|
1700
|
+
if (!args || args.length === 0) continue;
|
|
1701
|
+
|
|
1702
|
+
const relevantArgs = args.filter((a) => {
|
|
1703
|
+
const kind = typeof a.getKind === 'function' ? a.getKind() : null;
|
|
1704
|
+
if (kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral) return false;
|
|
1705
|
+
return true;
|
|
1706
|
+
});
|
|
1707
|
+
if (relevantArgs.length === 0) continue;
|
|
1708
|
+
|
|
1709
|
+
const argsText = relevantArgs.map(a => a.getText()).join(' ').toLowerCase();
|
|
1710
|
+
if (!sensitiveKeywords.some(k => argsText.includes(k))) continue;
|
|
1711
|
+
if (redactionKeywords.some(k => argsText.includes(k))) continue;
|
|
1712
|
+
|
|
1713
|
+
pushFinding(
|
|
1714
|
+
"backend.security.pii_in_logs",
|
|
1715
|
+
"high",
|
|
1716
|
+
sf,
|
|
1717
|
+
call,
|
|
1718
|
+
'🚨 HIGH: Potential PII in logs. Never log: passwords, tokens, SSN, credit cards. Sanitize: logger.info({ userId, action }) - don\'t include sensitive fields. GDPR violation risk.',
|
|
1719
|
+
findings
|
|
1720
|
+
);
|
|
1721
|
+
break;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
if (filePath.includes('.service.ts') && !filePath.includes('.spec.ts')) {
|
|
1726
|
+
const testFilePath = filePath.replace('.service.ts', '.service.spec.ts');
|
|
1727
|
+
const fs = require('fs');
|
|
1728
|
+
|
|
1729
|
+
if (!fs.existsSync(testFilePath)) {
|
|
1730
|
+
pushFinding(
|
|
1731
|
+
"backend.testing.missing_service_tests",
|
|
1732
|
+
"high",
|
|
1733
|
+
sf,
|
|
1734
|
+
sf,
|
|
1735
|
+
`🚨 HIGH: Service without tests. Create: ${testFilePath}. Test coverage required for business logic. Use makeSUT pattern, AAA (Arrange-Act-Assert). Target: >95% coverage.`,
|
|
1736
|
+
findings
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
if (filePath.includes('.controller.ts')) {
|
|
1742
|
+
const hasListEndpoint = fullText.includes('findAll') || fullText.includes('getAll') || fullText.includes('list');
|
|
1743
|
+
const hasPagination = fullText.includes('page') || fullText.includes('limit') || fullText.includes('offset') || fullText.includes('@Query');
|
|
1744
|
+
|
|
1745
|
+
if (hasListEndpoint && !hasPagination) {
|
|
1746
|
+
pushFinding(
|
|
1747
|
+
"backend.api.missing_pagination",
|
|
1748
|
+
"high",
|
|
1749
|
+
sf,
|
|
1750
|
+
sf,
|
|
1751
|
+
'🚨 HIGH: List endpoint without pagination. Add: @Query(\'page\') page: number, @Query(\'limit\') limit: number. Return: { data: items, total, page, limit }. Prevents: Memory issues, slow responses, timeout.',
|
|
1752
|
+
findings
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
if (filePath.includes('.controller.ts')) {
|
|
1758
|
+
const swaggerIsAnalyzer = /infrastructure\/ast\/|analyzers\/|detectors\/|scanner|analyzer|detector/i.test(filePath);
|
|
1759
|
+
const swaggerIsTestFile = /\.(spec|test)\.(js|ts)$/i.test(filePath);
|
|
1760
|
+
if (!swaggerIsAnalyzer && !swaggerIsTestFile) {
|
|
1761
|
+
const hasSwaggerDecorators = fullText.includes('@ApiTags') || fullText.includes('@ApiOperation');
|
|
1762
|
+
const hasEndpoints = fullText.includes('@Get') || fullText.includes('@Post') || fullText.includes('@Put');
|
|
1763
|
+
|
|
1764
|
+
if (hasEndpoints && !hasSwaggerDecorators) {
|
|
1765
|
+
pushFinding(
|
|
1766
|
+
"backend.api.missing_swagger",
|
|
1767
|
+
"medium",
|
|
1768
|
+
sf,
|
|
1769
|
+
sf,
|
|
1770
|
+
'Controller without Swagger documentation. Add: @ApiTags(\'users\'), @ApiOperation({ summary: \'...\' }), @ApiResponse(). Benefits: Auto-generated API docs, TypeScript types for frontend.',
|
|
1771
|
+
findings
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
if (filePath.includes('.controller.ts')) {
|
|
1778
|
+
const hasVersioning = fullText.includes('@Version(') || fullText.includes('/v1/') || fullText.includes('/v2/');
|
|
1779
|
+
const isMainController = fullText.includes('@Controller(') && !filePath.includes('.spec.ts');
|
|
1780
|
+
|
|
1781
|
+
if (isMainController && !hasVersioning) {
|
|
1782
|
+
pushFinding(
|
|
1783
|
+
"backend.api.missing_versioning",
|
|
1784
|
+
"medium",
|
|
1785
|
+
sf,
|
|
1786
|
+
sf,
|
|
1787
|
+
'Controller without API versioning. Add: @Controller(\'v1/users\'). Or enable global versioning in main.ts. Prevents breaking changes for existing clients.',
|
|
1788
|
+
findings
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
if (filePath.includes('health') || fullText.includes('HealthCheck')) {
|
|
1794
|
+
const hasDatabase = fullText.includes('database') || fullText.includes('TypeOrmHealthIndicator');
|
|
1795
|
+
const hasMemory = fullText.includes('memory') || fullText.includes('MemoryHealthIndicator');
|
|
1796
|
+
const hasDisk = fullText.includes('disk') || fullText.includes('DiskHealthIndicator');
|
|
1797
|
+
|
|
1798
|
+
if (!hasDatabase || !hasMemory) {
|
|
1799
|
+
pushFinding(
|
|
1800
|
+
"backend.monitoring.incomplete_health_check",
|
|
1801
|
+
"medium",
|
|
1802
|
+
sf,
|
|
1803
|
+
sf,
|
|
1804
|
+
'Incomplete health check. Add indicators: database, memory, disk. Use @nestjs/terminus. Kubernetes needs /health liveness and /health/ready readiness probes.',
|
|
1805
|
+
findings
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
if (filePath.includes('middleware') || filePath.includes('interceptor')) {
|
|
1811
|
+
const hasRequestId = fullText.includes('requestId') || fullText.includes('x-request-id') || fullText.includes('correlationId');
|
|
1812
|
+
|
|
1813
|
+
if (!hasRequestId && fullText.includes('logger')) {
|
|
1814
|
+
pushFinding(
|
|
1815
|
+
"backend.observability.missing_request_id",
|
|
1816
|
+
"medium",
|
|
1817
|
+
sf,
|
|
1818
|
+
sf,
|
|
1819
|
+
'Logger without request ID. Generate: const requestId = uuidv4(); Add to headers: res.set(\'X-Request-ID\', requestId). Include in all logs. Traces requests across microservices.',
|
|
1820
|
+
findings
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
if (filePath.includes('main.ts')) {
|
|
1826
|
+
const hasCompression = fullText.includes('compression(') || fullText.includes('@nestjs/platform-fastify');
|
|
1827
|
+
|
|
1828
|
+
if (!hasCompression) {
|
|
1829
|
+
pushFinding(
|
|
1830
|
+
"backend.performance.missing_compression",
|
|
1831
|
+
"medium",
|
|
1832
|
+
sf,
|
|
1833
|
+
sf,
|
|
1834
|
+
'Missing GZIP compression. Install: npm i compression. Add in main.ts: app.use(compression()). Reduces response size by 70-90%. Faster API responses.',
|
|
1835
|
+
findings
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
if (filePath.includes('config') || filePath.includes('env')) {
|
|
1841
|
+
const hasValidation = fullText.includes('Joi') || fullText.includes('class-validator') || fullText.includes('validate');
|
|
1842
|
+
const hasEnvVars = fullText.includes('process.env');
|
|
1843
|
+
|
|
1844
|
+
if (hasEnvVars && !hasValidation) {
|
|
1845
|
+
pushFinding(
|
|
1846
|
+
"backend.config.missing_env_validation",
|
|
1847
|
+
"medium",
|
|
1848
|
+
sf,
|
|
1849
|
+
sf,
|
|
1850
|
+
'Environment variables without validation. Use Joi: validationSchema: Joi.object({ NODE_ENV: Joi.string().valid(\'dev\', \'prod\').required() }). Fails fast on missing/invalid config.',
|
|
1851
|
+
findings
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
|
|
1857
|
+
if (fullText.includes('metrics') && !fullText.includes('prom-client') && !fullText.includes('@Prometheus')) {
|
|
1858
|
+
pushFinding(
|
|
1859
|
+
"backend.observability.missing_prometheus",
|
|
1860
|
+
"low",
|
|
1861
|
+
sf,
|
|
1862
|
+
sf,
|
|
1863
|
+
'Metrics without Prometheus. Install: npm i prom-client @willsoto/nestjs-prometheus. Expose: /metrics endpoint. Monitor: request_duration, error_rate, throughput.',
|
|
1864
|
+
findings
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
if (filePath.includes('prometheus') || filePath.includes('metrics')) {
|
|
1869
|
+
const hasDashboardComment = fullText.includes('Grafana') || fullText.includes('dashboard');
|
|
1870
|
+
|
|
1871
|
+
if (!hasDashboardComment) {
|
|
1872
|
+
pushFinding(
|
|
1873
|
+
"backend.observability.missing_grafana_setup",
|
|
1874
|
+
"low",
|
|
1875
|
+
sf,
|
|
1876
|
+
sf,
|
|
1877
|
+
'Prometheus metrics without Grafana dashboard. Export metrics.json. Import to Grafana. Create: API latency, error rate, throughput dashboards. Visualize production health.',
|
|
1878
|
+
findings
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
if (filePath.includes('monitoring') || filePath.includes('alert')) {
|
|
1884
|
+
const hasAlertManager = fullText.includes('AlertManager') || fullText.includes('webhook') || fullText.includes('notification');
|
|
1885
|
+
|
|
1886
|
+
if (!hasAlertManager) {
|
|
1887
|
+
pushFinding(
|
|
1888
|
+
"backend.observability.missing_alerting",
|
|
1889
|
+
"low",
|
|
1890
|
+
sf,
|
|
1891
|
+
sf,
|
|
1892
|
+
'Monitoring without alerts. Configure Prometheus AlertManager or PagerDuty. Alert on: error_rate > 5%, latency_p99 > 1s, cpu > 80%. Get notified BEFORE users complain.',
|
|
1893
|
+
findings
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
if (filePath.includes('logger') && !fullText.includes('elasticsearch') && !fullText.includes('logstash')) {
|
|
1899
|
+
const hasStructuredLogging = fullText.includes('JSON.stringify') || fullText.includes('winston');
|
|
1900
|
+
|
|
1901
|
+
if (hasStructuredLogging) {
|
|
1902
|
+
pushFinding(
|
|
1903
|
+
"backend.observability.missing_log_aggregation",
|
|
1904
|
+
"low",
|
|
1905
|
+
sf,
|
|
1906
|
+
sf,
|
|
1907
|
+
'Structured logs without aggregation. Send to: ELK Stack (Elasticsearch, Logstash, Kibana) or Datadog. Search logs across servers. Debug production issues faster.',
|
|
1908
|
+
findings
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
if (filePath.includes('main.ts') && !fullText.includes('newrelic') && !fullText.includes('datadog') && !fullText.includes('@sentry/node')) {
|
|
1914
|
+
pushFinding(
|
|
1915
|
+
"backend.observability.missing_apm",
|
|
1916
|
+
"low",
|
|
1917
|
+
sf,
|
|
1918
|
+
sf,
|
|
1919
|
+
'No APM integration. Install: New Relic, Datadog, or Elastic APM. Track: slow queries, external API calls, memory leaks. Find performance bottlenecks.',
|
|
1920
|
+
findings
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
if (filePath.includes('e2e') || filePath.includes('test')) {
|
|
1925
|
+
const hasLoadTest = fullText.includes('artillery') || fullText.includes('k6') || fullText.includes('jmeter');
|
|
1926
|
+
|
|
1927
|
+
if (!hasLoadTest && filePath.includes('e2e')) {
|
|
1928
|
+
pushFinding(
|
|
1929
|
+
"backend.testing.missing_load_tests",
|
|
1930
|
+
"low",
|
|
1931
|
+
sf,
|
|
1932
|
+
sf,
|
|
1933
|
+
'E2E tests without load testing. Add: artillery or k6. Test: 100 concurrent users, 1000 requests/second. Find: rate limits, connection pool issues, memory leaks.',
|
|
1934
|
+
findings
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
if (filePath.includes('database') || filePath.includes('typeorm')) {
|
|
1940
|
+
const hasBackup = fullText.includes('backup') || fullText.includes('pg_dump') || fullText.includes('snapshot');
|
|
1941
|
+
|
|
1942
|
+
if (!hasBackup && fullText.includes('createConnection')) {
|
|
1943
|
+
pushFinding(
|
|
1944
|
+
"backend.reliability.missing_backup_strategy",
|
|
1945
|
+
"low",
|
|
1946
|
+
sf,
|
|
1947
|
+
sf,
|
|
1948
|
+
'Database connection without backup strategy. Configure: Daily automated backups, 30-day retention, test restore monthly. Use: pg_dump, AWS RDS snapshots, or backup service.',
|
|
1949
|
+
findings
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
if (filePath.includes('config') && fullText.includes('database')) {
|
|
1955
|
+
const hasReplication = fullText.includes('replication') || fullText.includes('replica') || fullText.includes('standby');
|
|
1956
|
+
|
|
1957
|
+
if (!hasReplication) {
|
|
1958
|
+
pushFinding(
|
|
1959
|
+
"backend.reliability.missing_dr_plan",
|
|
1960
|
+
"low",
|
|
1961
|
+
sf,
|
|
1962
|
+
sf,
|
|
1963
|
+
'Database config without disaster recovery. Setup: Read replicas, failover automation, cross-region backup. Target: RTO < 1 hour, RPO < 15 minutes. Survive data center outage.',
|
|
1964
|
+
findings
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
if (filePath.includes('main.ts') || filePath.includes('app.module')) {
|
|
1970
|
+
const hasThrottler = fullText.includes('@nestjs/throttler') || fullText.includes('ThrottlerModule');
|
|
1971
|
+
|
|
1972
|
+
if (!hasThrottler) {
|
|
1973
|
+
pushFinding(
|
|
1974
|
+
"backend.security.missing_rate_limiting",
|
|
1975
|
+
"low",
|
|
1976
|
+
sf,
|
|
1977
|
+
sf,
|
|
1978
|
+
'No rate limiting. Install: @nestjs/throttler. Configure: 10 requests/second per IP. Prevents: DDoS, brute force, API abuse. Saves infrastructure costs.',
|
|
1979
|
+
findings
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
if (fullText.includes('axios') || fullText.includes('fetch') || fullText.includes('HttpService')) {
|
|
1985
|
+
const hasCircuitBreaker = fullText.includes('opossum') || fullText.includes('CircuitBreaker');
|
|
1986
|
+
|
|
1987
|
+
if (!hasCircuitBreaker && filePath.includes('.service.ts')) {
|
|
1988
|
+
pushFinding(
|
|
1989
|
+
"backend.reliability.missing_circuit_breaker",
|
|
1990
|
+
"low",
|
|
1991
|
+
sf,
|
|
1992
|
+
sf,
|
|
1993
|
+
'External API call without circuit breaker. Install: opossum. Prevents: Cascading failures, timeout avalanche. Opens circuit after 5 failures, retries after 30s.',
|
|
1994
|
+
findings
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
if (fullText.includes('async') && fullText.includes('await') && fullText.includes('for')) {
|
|
2000
|
+
const hasBulkhead = fullText.includes('Promise.all') || fullText.includes('p-limit');
|
|
2001
|
+
|
|
2002
|
+
if (!hasBulkhead) {
|
|
2003
|
+
pushFinding(
|
|
2004
|
+
"backend.reliability.missing_bulkhead",
|
|
2005
|
+
"low",
|
|
2006
|
+
sf,
|
|
2007
|
+
sf,
|
|
2008
|
+
'Parallel async operations without bulkhead. Use: p-limit to control concurrency. const limit = pLimit(10). Prevents: Thread pool exhaustion, connection pool depletion.',
|
|
2009
|
+
findings
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
if ((fullText.includes('axios') || fullText.includes('fetch')) && !fullText.includes('retry')) {
|
|
2015
|
+
const isExternalCall = fullText.includes('http://') || fullText.includes('https://');
|
|
2016
|
+
|
|
2017
|
+
if (isExternalCall) {
|
|
2018
|
+
pushFinding(
|
|
2019
|
+
"backend.reliability.missing_retry_policy",
|
|
2020
|
+
"low",
|
|
2021
|
+
sf,
|
|
2022
|
+
sf,
|
|
2023
|
+
'External API call without retry. Use: axios-retry or custom exponential backoff. Retry 3 times with delays: 1s, 2s, 4s. Handles transient network failures.',
|
|
2024
|
+
findings
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
if (filePath.includes('Dockerfile') || filePath.includes('.yml') || filePath.includes('deploy')) {
|
|
2030
|
+
const hasStrategy = fullText.includes('blue-green') || fullText.includes('canary') || fullText.includes('rolling');
|
|
2031
|
+
|
|
2032
|
+
if (!hasStrategy && (fullText.includes('deploy') || fullText.includes('kubernetes'))) {
|
|
2033
|
+
pushFinding(
|
|
2034
|
+
"backend.devops.missing_deployment_strategy",
|
|
2035
|
+
"low",
|
|
2036
|
+
sf,
|
|
2037
|
+
sf,
|
|
2038
|
+
'Deployment config without strategy. Use: Blue-green (zero downtime), Canary (gradual rollout), or Rolling update. Kubernetes: set strategy.type, maxSurge, maxUnavailable.',
|
|
2039
|
+
findings
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
module.exports = {
|
|
2047
|
+
runBackendIntelligence,
|
|
2048
|
+
};
|