tapps-agents 3.5.40__py3-none-any.whl → 3.6.0__py3-none-any.whl
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.
- tapps_agents/__init__.py +2 -2
- tapps_agents/agents/__init__.py +22 -22
- tapps_agents/agents/analyst/__init__.py +5 -5
- tapps_agents/agents/architect/__init__.py +5 -5
- tapps_agents/agents/architect/agent.py +1033 -1033
- tapps_agents/agents/architect/pattern_detector.py +75 -75
- tapps_agents/agents/cleanup/__init__.py +7 -7
- tapps_agents/agents/cleanup/agent.py +445 -445
- tapps_agents/agents/debugger/__init__.py +7 -7
- tapps_agents/agents/debugger/agent.py +310 -310
- tapps_agents/agents/debugger/error_analyzer.py +437 -437
- tapps_agents/agents/designer/__init__.py +5 -5
- tapps_agents/agents/designer/agent.py +786 -786
- tapps_agents/agents/designer/visual_designer.py +638 -638
- tapps_agents/agents/documenter/__init__.py +7 -7
- tapps_agents/agents/documenter/agent.py +531 -531
- tapps_agents/agents/documenter/doc_generator.py +472 -472
- tapps_agents/agents/documenter/doc_validator.py +393 -393
- tapps_agents/agents/documenter/framework_doc_updater.py +493 -493
- tapps_agents/agents/enhancer/__init__.py +7 -7
- tapps_agents/agents/evaluator/__init__.py +7 -7
- tapps_agents/agents/evaluator/agent.py +443 -443
- tapps_agents/agents/evaluator/priority_evaluator.py +641 -641
- tapps_agents/agents/evaluator/quality_analyzer.py +147 -147
- tapps_agents/agents/evaluator/report_generator.py +344 -344
- tapps_agents/agents/evaluator/usage_analyzer.py +192 -192
- tapps_agents/agents/evaluator/workflow_analyzer.py +189 -189
- tapps_agents/agents/implementer/__init__.py +7 -7
- tapps_agents/agents/implementer/agent.py +798 -798
- tapps_agents/agents/implementer/auto_fix.py +1119 -1119
- tapps_agents/agents/implementer/code_generator.py +73 -73
- tapps_agents/agents/improver/__init__.py +1 -1
- tapps_agents/agents/improver/agent.py +753 -753
- tapps_agents/agents/ops/__init__.py +1 -1
- tapps_agents/agents/ops/agent.py +619 -619
- tapps_agents/agents/ops/dependency_analyzer.py +600 -600
- tapps_agents/agents/orchestrator/__init__.py +5 -5
- tapps_agents/agents/orchestrator/agent.py +522 -522
- tapps_agents/agents/planner/__init__.py +7 -7
- tapps_agents/agents/planner/agent.py +1127 -1127
- tapps_agents/agents/reviewer/__init__.py +24 -24
- tapps_agents/agents/reviewer/agent.py +3513 -3513
- tapps_agents/agents/reviewer/aggregator.py +213 -213
- tapps_agents/agents/reviewer/batch_review.py +448 -448
- tapps_agents/agents/reviewer/cache.py +443 -443
- tapps_agents/agents/reviewer/context7_enhancer.py +630 -630
- tapps_agents/agents/reviewer/context_detector.py +203 -203
- tapps_agents/agents/reviewer/docker_compose_validator.py +158 -158
- tapps_agents/agents/reviewer/dockerfile_validator.py +176 -176
- tapps_agents/agents/reviewer/error_handling.py +126 -126
- tapps_agents/agents/reviewer/feedback_generator.py +490 -490
- tapps_agents/agents/reviewer/influxdb_validator.py +316 -316
- tapps_agents/agents/reviewer/issue_tracking.py +169 -169
- tapps_agents/agents/reviewer/library_detector.py +295 -295
- tapps_agents/agents/reviewer/library_patterns.py +268 -268
- tapps_agents/agents/reviewer/maintainability_scorer.py +593 -593
- tapps_agents/agents/reviewer/metric_strategies.py +276 -276
- tapps_agents/agents/reviewer/mqtt_validator.py +160 -160
- tapps_agents/agents/reviewer/output_enhancer.py +105 -105
- tapps_agents/agents/reviewer/pattern_detector.py +241 -241
- tapps_agents/agents/reviewer/performance_scorer.py +357 -357
- tapps_agents/agents/reviewer/phased_review.py +516 -516
- tapps_agents/agents/reviewer/progressive_review.py +435 -435
- tapps_agents/agents/reviewer/react_scorer.py +331 -331
- tapps_agents/agents/reviewer/score_constants.py +228 -228
- tapps_agents/agents/reviewer/score_validator.py +507 -507
- tapps_agents/agents/reviewer/scorer_registry.py +373 -373
- tapps_agents/agents/reviewer/scoring.py +1566 -1566
- tapps_agents/agents/reviewer/service_discovery.py +534 -534
- tapps_agents/agents/reviewer/tools/__init__.py +41 -41
- tapps_agents/agents/reviewer/tools/parallel_executor.py +581 -581
- tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -250
- tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -284
- tapps_agents/agents/reviewer/typescript_scorer.py +1142 -1142
- tapps_agents/agents/reviewer/validation.py +208 -208
- tapps_agents/agents/reviewer/websocket_validator.py +132 -132
- tapps_agents/agents/tester/__init__.py +7 -7
- tapps_agents/agents/tester/accessibility_auditor.py +309 -309
- tapps_agents/agents/tester/agent.py +1080 -1080
- tapps_agents/agents/tester/batch_generator.py +54 -54
- tapps_agents/agents/tester/context_learner.py +51 -51
- tapps_agents/agents/tester/coverage_analyzer.py +386 -386
- tapps_agents/agents/tester/coverage_test_generator.py +290 -290
- tapps_agents/agents/tester/debug_enhancer.py +238 -238
- tapps_agents/agents/tester/device_emulator.py +241 -241
- tapps_agents/agents/tester/integration_generator.py +62 -62
- tapps_agents/agents/tester/network_recorder.py +300 -300
- tapps_agents/agents/tester/performance_monitor.py +320 -320
- tapps_agents/agents/tester/test_fixer.py +316 -316
- tapps_agents/agents/tester/test_generator.py +632 -632
- tapps_agents/agents/tester/trace_manager.py +234 -234
- tapps_agents/agents/tester/visual_regression.py +291 -291
- tapps_agents/analysis/pattern_detector.py +36 -36
- tapps_agents/beads/hydration.py +213 -213
- tapps_agents/beads/parse.py +32 -32
- tapps_agents/beads/specs.py +206 -206
- tapps_agents/cli/__init__.py +9 -9
- tapps_agents/cli/__main__.py +8 -8
- tapps_agents/cli/base.py +478 -478
- tapps_agents/cli/command_classifier.py +72 -72
- tapps_agents/cli/commands/__init__.py +2 -2
- tapps_agents/cli/commands/analyst.py +173 -173
- tapps_agents/cli/commands/architect.py +109 -109
- tapps_agents/cli/commands/cleanup_agent.py +92 -92
- tapps_agents/cli/commands/common.py +126 -126
- tapps_agents/cli/commands/debugger.py +90 -90
- tapps_agents/cli/commands/designer.py +112 -112
- tapps_agents/cli/commands/documenter.py +136 -136
- tapps_agents/cli/commands/enhancer.py +110 -110
- tapps_agents/cli/commands/evaluator.py +255 -255
- tapps_agents/cli/commands/health.py +665 -665
- tapps_agents/cli/commands/implementer.py +301 -301
- tapps_agents/cli/commands/improver.py +91 -91
- tapps_agents/cli/commands/knowledge.py +111 -111
- tapps_agents/cli/commands/learning.py +172 -172
- tapps_agents/cli/commands/observability.py +283 -283
- tapps_agents/cli/commands/ops.py +135 -135
- tapps_agents/cli/commands/orchestrator.py +116 -116
- tapps_agents/cli/commands/planner.py +237 -237
- tapps_agents/cli/commands/reviewer.py +1872 -1872
- tapps_agents/cli/commands/status.py +285 -285
- tapps_agents/cli/commands/task.py +227 -219
- tapps_agents/cli/commands/tester.py +191 -191
- tapps_agents/cli/commands/top_level.py +3586 -3586
- tapps_agents/cli/feedback.py +936 -936
- tapps_agents/cli/formatters.py +608 -608
- tapps_agents/cli/help/__init__.py +7 -7
- tapps_agents/cli/help/static_help.py +425 -425
- tapps_agents/cli/network_detection.py +110 -110
- tapps_agents/cli/output_compactor.py +274 -274
- tapps_agents/cli/parsers/__init__.py +2 -2
- tapps_agents/cli/parsers/analyst.py +186 -186
- tapps_agents/cli/parsers/architect.py +167 -167
- tapps_agents/cli/parsers/cleanup_agent.py +228 -228
- tapps_agents/cli/parsers/debugger.py +116 -116
- tapps_agents/cli/parsers/designer.py +182 -182
- tapps_agents/cli/parsers/documenter.py +134 -134
- tapps_agents/cli/parsers/enhancer.py +113 -113
- tapps_agents/cli/parsers/evaluator.py +213 -213
- tapps_agents/cli/parsers/implementer.py +168 -168
- tapps_agents/cli/parsers/improver.py +132 -132
- tapps_agents/cli/parsers/ops.py +159 -159
- tapps_agents/cli/parsers/orchestrator.py +98 -98
- tapps_agents/cli/parsers/planner.py +145 -145
- tapps_agents/cli/parsers/reviewer.py +462 -462
- tapps_agents/cli/parsers/tester.py +124 -124
- tapps_agents/cli/progress_heartbeat.py +254 -254
- tapps_agents/cli/streaming_progress.py +336 -336
- tapps_agents/cli/utils/__init__.py +6 -6
- tapps_agents/cli/utils/agent_lifecycle.py +48 -48
- tapps_agents/cli/utils/error_formatter.py +82 -82
- tapps_agents/cli/utils/error_recovery.py +188 -188
- tapps_agents/cli/utils/output_handler.py +59 -59
- tapps_agents/cli/utils/prompt_enhancer.py +319 -319
- tapps_agents/cli/validators/__init__.py +9 -9
- tapps_agents/cli/validators/command_validator.py +81 -81
- tapps_agents/context7/__init__.py +112 -112
- tapps_agents/context7/agent_integration.py +869 -869
- tapps_agents/context7/analytics.py +382 -382
- tapps_agents/context7/analytics_dashboard.py +299 -299
- tapps_agents/context7/async_cache.py +681 -681
- tapps_agents/context7/backup_client.py +958 -958
- tapps_agents/context7/cache_locking.py +194 -194
- tapps_agents/context7/cache_metadata.py +214 -214
- tapps_agents/context7/cache_prewarm.py +488 -488
- tapps_agents/context7/cache_structure.py +168 -168
- tapps_agents/context7/cache_warming.py +604 -604
- tapps_agents/context7/circuit_breaker.py +376 -376
- tapps_agents/context7/cleanup.py +461 -461
- tapps_agents/context7/commands.py +858 -858
- tapps_agents/context7/credential_validation.py +276 -276
- tapps_agents/context7/cross_reference_resolver.py +168 -168
- tapps_agents/context7/cross_references.py +424 -424
- tapps_agents/context7/doc_manager.py +225 -225
- tapps_agents/context7/fuzzy_matcher.py +369 -369
- tapps_agents/context7/kb_cache.py +404 -404
- tapps_agents/context7/language_detector.py +219 -219
- tapps_agents/context7/library_detector.py +725 -725
- tapps_agents/context7/lookup.py +738 -738
- tapps_agents/context7/metadata.py +258 -258
- tapps_agents/context7/refresh_queue.py +300 -300
- tapps_agents/context7/security.py +373 -373
- tapps_agents/context7/staleness_policies.py +278 -278
- tapps_agents/context7/tiles_integration.py +47 -47
- tapps_agents/continuous_bug_fix/__init__.py +20 -20
- tapps_agents/continuous_bug_fix/bug_finder.py +306 -306
- tapps_agents/continuous_bug_fix/bug_fix_coordinator.py +177 -177
- tapps_agents/continuous_bug_fix/commit_manager.py +178 -178
- tapps_agents/continuous_bug_fix/continuous_bug_fixer.py +322 -322
- tapps_agents/continuous_bug_fix/proactive_bug_finder.py +285 -285
- tapps_agents/core/__init__.py +298 -298
- tapps_agents/core/adaptive_cache_config.py +432 -432
- tapps_agents/core/agent_base.py +647 -647
- tapps_agents/core/agent_cache.py +466 -466
- tapps_agents/core/agent_learning.py +1865 -1865
- tapps_agents/core/analytics_dashboard.py +563 -563
- tapps_agents/core/analytics_enhancements.py +597 -597
- tapps_agents/core/anonymization.py +274 -274
- tapps_agents/core/artifact_context_builder.py +293 -0
- tapps_agents/core/ast_parser.py +228 -228
- tapps_agents/core/async_file_ops.py +402 -402
- tapps_agents/core/best_practice_consultant.py +299 -299
- tapps_agents/core/brownfield_analyzer.py +299 -299
- tapps_agents/core/brownfield_review.py +541 -541
- tapps_agents/core/browser_controller.py +513 -513
- tapps_agents/core/capability_registry.py +418 -418
- tapps_agents/core/change_impact_analyzer.py +190 -190
- tapps_agents/core/checkpoint_manager.py +377 -377
- tapps_agents/core/code_generator.py +329 -329
- tapps_agents/core/code_validator.py +276 -276
- tapps_agents/core/command_registry.py +327 -327
- tapps_agents/core/config.py +33 -0
- tapps_agents/core/context_gathering/__init__.py +2 -2
- tapps_agents/core/context_gathering/repository_explorer.py +28 -28
- tapps_agents/core/context_intelligence/__init__.py +2 -2
- tapps_agents/core/context_intelligence/relevance_scorer.py +24 -24
- tapps_agents/core/context_intelligence/token_budget_manager.py +27 -27
- tapps_agents/core/context_manager.py +240 -240
- tapps_agents/core/cursor_feedback_monitor.py +146 -146
- tapps_agents/core/cursor_verification.py +290 -290
- tapps_agents/core/customization_loader.py +280 -280
- tapps_agents/core/customization_schema.py +260 -260
- tapps_agents/core/customization_template.py +238 -238
- tapps_agents/core/debug_logger.py +124 -124
- tapps_agents/core/design_validator.py +298 -298
- tapps_agents/core/diagram_generator.py +226 -226
- tapps_agents/core/docker_utils.py +232 -232
- tapps_agents/core/document_generator.py +617 -617
- tapps_agents/core/domain_detector.py +30 -30
- tapps_agents/core/error_envelope.py +454 -454
- tapps_agents/core/error_handler.py +270 -270
- tapps_agents/core/estimation_tracker.py +189 -189
- tapps_agents/core/eval_prompt_engine.py +116 -116
- tapps_agents/core/evaluation_base.py +119 -119
- tapps_agents/core/evaluation_models.py +320 -320
- tapps_agents/core/evaluation_orchestrator.py +225 -225
- tapps_agents/core/evaluators/__init__.py +7 -7
- tapps_agents/core/evaluators/architectural_evaluator.py +205 -205
- tapps_agents/core/evaluators/behavioral_evaluator.py +160 -160
- tapps_agents/core/evaluators/performance_profile_evaluator.py +160 -160
- tapps_agents/core/evaluators/security_posture_evaluator.py +148 -148
- tapps_agents/core/evaluators/spec_compliance_evaluator.py +181 -181
- tapps_agents/core/exceptions.py +107 -107
- tapps_agents/core/expert_config_generator.py +293 -293
- tapps_agents/core/export_schema.py +202 -202
- tapps_agents/core/external_feedback_models.py +102 -102
- tapps_agents/core/external_feedback_storage.py +213 -213
- tapps_agents/core/fallback_strategy.py +314 -314
- tapps_agents/core/feedback_analyzer.py +162 -162
- tapps_agents/core/feedback_collector.py +178 -178
- tapps_agents/core/git_operations.py +445 -445
- tapps_agents/core/hardware_profiler.py +151 -151
- tapps_agents/core/instructions.py +324 -324
- tapps_agents/core/io_guardrails.py +69 -69
- tapps_agents/core/issue_manifest.py +249 -249
- tapps_agents/core/issue_schema.py +139 -139
- tapps_agents/core/json_utils.py +128 -128
- tapps_agents/core/knowledge_graph.py +446 -446
- tapps_agents/core/language_detector.py +296 -296
- tapps_agents/core/learning_confidence.py +242 -242
- tapps_agents/core/learning_dashboard.py +246 -246
- tapps_agents/core/learning_decision.py +384 -384
- tapps_agents/core/learning_explainability.py +578 -578
- tapps_agents/core/learning_export.py +287 -287
- tapps_agents/core/learning_integration.py +228 -228
- tapps_agents/core/llm_behavior.py +232 -232
- tapps_agents/core/long_duration_support.py +786 -786
- tapps_agents/core/mcp_setup.py +106 -106
- tapps_agents/core/memory_integration.py +396 -396
- tapps_agents/core/meta_learning.py +666 -666
- tapps_agents/core/module_path_sanitizer.py +199 -199
- tapps_agents/core/multi_agent_orchestrator.py +382 -382
- tapps_agents/core/network_errors.py +125 -125
- tapps_agents/core/nfr_validator.py +336 -336
- tapps_agents/core/offline_mode.py +158 -158
- tapps_agents/core/output_contracts.py +300 -300
- tapps_agents/core/output_formatter.py +300 -300
- tapps_agents/core/path_normalizer.py +174 -174
- tapps_agents/core/path_validator.py +322 -322
- tapps_agents/core/pattern_library.py +250 -250
- tapps_agents/core/performance_benchmark.py +301 -301
- tapps_agents/core/performance_monitor.py +184 -184
- tapps_agents/core/playwright_mcp_controller.py +771 -771
- tapps_agents/core/policy_loader.py +135 -135
- tapps_agents/core/progress.py +166 -166
- tapps_agents/core/project_profile.py +354 -354
- tapps_agents/core/project_type_detector.py +454 -454
- tapps_agents/core/prompt_base.py +223 -223
- tapps_agents/core/prompt_learning/__init__.py +2 -2
- tapps_agents/core/prompt_learning/learning_loop.py +24 -24
- tapps_agents/core/prompt_learning/project_prompt_store.py +25 -25
- tapps_agents/core/prompt_learning/skills_prompt_analyzer.py +35 -35
- tapps_agents/core/prompt_optimization/__init__.py +6 -6
- tapps_agents/core/prompt_optimization/ab_tester.py +114 -114
- tapps_agents/core/prompt_optimization/correlation_analyzer.py +160 -160
- tapps_agents/core/prompt_optimization/progressive_refiner.py +129 -129
- tapps_agents/core/prompt_optimization/prompt_library.py +37 -37
- tapps_agents/core/requirements_evaluator.py +431 -431
- tapps_agents/core/resource_aware_executor.py +449 -449
- tapps_agents/core/resource_monitor.py +343 -343
- tapps_agents/core/resume_handler.py +298 -298
- tapps_agents/core/retry_handler.py +197 -197
- tapps_agents/core/review_checklists.py +479 -479
- tapps_agents/core/role_loader.py +201 -201
- tapps_agents/core/role_template_loader.py +201 -201
- tapps_agents/core/runtime_mode.py +60 -60
- tapps_agents/core/security_scanner.py +342 -342
- tapps_agents/core/skill_agent_registry.py +194 -194
- tapps_agents/core/skill_integration.py +208 -208
- tapps_agents/core/skill_loader.py +492 -492
- tapps_agents/core/skill_template.py +341 -341
- tapps_agents/core/skill_validator.py +478 -478
- tapps_agents/core/stack_analyzer.py +35 -35
- tapps_agents/core/startup.py +174 -174
- tapps_agents/core/storage_manager.py +397 -397
- tapps_agents/core/storage_models.py +166 -166
- tapps_agents/core/story_evaluator.py +410 -410
- tapps_agents/core/subprocess_utils.py +170 -170
- tapps_agents/core/task_duration.py +296 -296
- tapps_agents/core/task_memory.py +582 -582
- tapps_agents/core/task_state.py +226 -226
- tapps_agents/core/tech_stack_priorities.py +208 -208
- tapps_agents/core/temp_directory.py +194 -194
- tapps_agents/core/template_merger.py +600 -600
- tapps_agents/core/template_selector.py +280 -280
- tapps_agents/core/test_generator.py +286 -286
- tapps_agents/core/tiered_context.py +253 -253
- tapps_agents/core/token_monitor.py +345 -345
- tapps_agents/core/traceability.py +254 -254
- tapps_agents/core/trajectory_tracker.py +50 -50
- tapps_agents/core/unicode_safe.py +143 -143
- tapps_agents/core/unified_cache_config.py +170 -170
- tapps_agents/core/unified_state.py +324 -324
- tapps_agents/core/validate_cursor_setup.py +237 -237
- tapps_agents/core/validation_registry.py +136 -136
- tapps_agents/core/validators/__init__.py +4 -4
- tapps_agents/core/validators/python_validator.py +87 -87
- tapps_agents/core/verification_agent.py +90 -90
- tapps_agents/core/visual_feedback.py +644 -644
- tapps_agents/core/workflow_validator.py +197 -197
- tapps_agents/core/worktree.py +367 -367
- tapps_agents/docker/__init__.py +10 -10
- tapps_agents/docker/analyzer.py +186 -186
- tapps_agents/docker/debugger.py +229 -229
- tapps_agents/docker/error_patterns.py +216 -216
- tapps_agents/epic/__init__.py +22 -22
- tapps_agents/epic/beads_sync.py +115 -115
- tapps_agents/epic/markdown_sync.py +105 -105
- tapps_agents/epic/models.py +96 -96
- tapps_agents/experts/__init__.py +163 -163
- tapps_agents/experts/agent_integration.py +243 -243
- tapps_agents/experts/auto_generator.py +331 -331
- tapps_agents/experts/base_expert.py +536 -536
- tapps_agents/experts/builtin_registry.py +261 -261
- tapps_agents/experts/business_metrics.py +565 -565
- tapps_agents/experts/cache.py +266 -266
- tapps_agents/experts/confidence_breakdown.py +306 -306
- tapps_agents/experts/confidence_calculator.py +336 -336
- tapps_agents/experts/confidence_metrics.py +236 -236
- tapps_agents/experts/domain_config.py +311 -311
- tapps_agents/experts/domain_detector.py +550 -550
- tapps_agents/experts/domain_utils.py +84 -84
- tapps_agents/experts/expert_config.py +113 -113
- tapps_agents/experts/expert_engine.py +465 -465
- tapps_agents/experts/expert_registry.py +744 -744
- tapps_agents/experts/expert_synthesizer.py +70 -70
- tapps_agents/experts/governance.py +197 -197
- tapps_agents/experts/history_logger.py +312 -312
- tapps_agents/experts/knowledge/README.md +180 -180
- tapps_agents/experts/knowledge/accessibility/accessible-forms.md +331 -331
- tapps_agents/experts/knowledge/accessibility/aria-patterns.md +344 -344
- tapps_agents/experts/knowledge/accessibility/color-contrast.md +285 -285
- tapps_agents/experts/knowledge/accessibility/keyboard-navigation.md +332 -332
- tapps_agents/experts/knowledge/accessibility/screen-readers.md +282 -282
- tapps_agents/experts/knowledge/accessibility/semantic-html.md +355 -355
- tapps_agents/experts/knowledge/accessibility/testing-accessibility.md +369 -369
- tapps_agents/experts/knowledge/accessibility/wcag-2.1.md +296 -296
- tapps_agents/experts/knowledge/accessibility/wcag-2.2.md +211 -211
- tapps_agents/experts/knowledge/agent-learning/best-practices.md +715 -715
- tapps_agents/experts/knowledge/agent-learning/pattern-extraction.md +282 -282
- tapps_agents/experts/knowledge/agent-learning/prompt-optimization.md +320 -320
- tapps_agents/experts/knowledge/ai-frameworks/model-optimization.md +90 -90
- tapps_agents/experts/knowledge/ai-frameworks/openvino-patterns.md +260 -260
- tapps_agents/experts/knowledge/api-design-integration/api-gateway-patterns.md +309 -309
- tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +521 -521
- tapps_agents/experts/knowledge/api-design-integration/api-versioning.md +421 -421
- tapps_agents/experts/knowledge/api-design-integration/async-protocol-patterns.md +61 -61
- tapps_agents/experts/knowledge/api-design-integration/contract-testing.md +221 -221
- tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +489 -489
- tapps_agents/experts/knowledge/api-design-integration/fastapi-patterns.md +360 -360
- tapps_agents/experts/knowledge/api-design-integration/fastapi-testing.md +262 -262
- tapps_agents/experts/knowledge/api-design-integration/graphql-patterns.md +582 -582
- tapps_agents/experts/knowledge/api-design-integration/grpc-best-practices.md +499 -499
- tapps_agents/experts/knowledge/api-design-integration/mqtt-patterns.md +455 -455
- tapps_agents/experts/knowledge/api-design-integration/rate-limiting.md +507 -507
- tapps_agents/experts/knowledge/api-design-integration/restful-api-design.md +618 -618
- tapps_agents/experts/knowledge/api-design-integration/websocket-patterns.md +480 -480
- tapps_agents/experts/knowledge/cloud-infrastructure/cloud-native-patterns.md +175 -175
- tapps_agents/experts/knowledge/cloud-infrastructure/container-health-checks.md +261 -261
- tapps_agents/experts/knowledge/cloud-infrastructure/containerization.md +222 -222
- tapps_agents/experts/knowledge/cloud-infrastructure/cost-optimization.md +122 -122
- tapps_agents/experts/knowledge/cloud-infrastructure/disaster-recovery.md +153 -153
- tapps_agents/experts/knowledge/cloud-infrastructure/dockerfile-patterns.md +285 -285
- tapps_agents/experts/knowledge/cloud-infrastructure/infrastructure-as-code.md +187 -187
- tapps_agents/experts/knowledge/cloud-infrastructure/kubernetes-patterns.md +253 -253
- tapps_agents/experts/knowledge/cloud-infrastructure/multi-cloud-strategies.md +155 -155
- tapps_agents/experts/knowledge/cloud-infrastructure/serverless-architecture.md +200 -200
- tapps_agents/experts/knowledge/code-quality-analysis/README.md +16 -16
- tapps_agents/experts/knowledge/code-quality-analysis/code-metrics.md +137 -137
- tapps_agents/experts/knowledge/code-quality-analysis/complexity-analysis.md +181 -181
- tapps_agents/experts/knowledge/code-quality-analysis/technical-debt-patterns.md +191 -191
- tapps_agents/experts/knowledge/data-privacy-compliance/anonymization.md +313 -313
- tapps_agents/experts/knowledge/data-privacy-compliance/ccpa.md +255 -255
- tapps_agents/experts/knowledge/data-privacy-compliance/consent-management.md +282 -282
- tapps_agents/experts/knowledge/data-privacy-compliance/data-minimization.md +275 -275
- tapps_agents/experts/knowledge/data-privacy-compliance/data-retention.md +297 -297
- tapps_agents/experts/knowledge/data-privacy-compliance/data-subject-rights.md +383 -383
- tapps_agents/experts/knowledge/data-privacy-compliance/encryption-privacy.md +285 -285
- tapps_agents/experts/knowledge/data-privacy-compliance/gdpr.md +344 -344
- tapps_agents/experts/knowledge/data-privacy-compliance/hipaa.md +385 -385
- tapps_agents/experts/knowledge/data-privacy-compliance/privacy-by-design.md +280 -280
- tapps_agents/experts/knowledge/database-data-management/acid-vs-cap.md +164 -164
- tapps_agents/experts/knowledge/database-data-management/backup-and-recovery.md +182 -182
- tapps_agents/experts/knowledge/database-data-management/data-modeling.md +172 -172
- tapps_agents/experts/knowledge/database-data-management/database-design.md +187 -187
- tapps_agents/experts/knowledge/database-data-management/flux-query-optimization.md +342 -342
- tapps_agents/experts/knowledge/database-data-management/influxdb-connection-patterns.md +432 -432
- tapps_agents/experts/knowledge/database-data-management/influxdb-patterns.md +442 -442
- tapps_agents/experts/knowledge/database-data-management/migration-strategies.md +216 -216
- tapps_agents/experts/knowledge/database-data-management/nosql-patterns.md +259 -259
- tapps_agents/experts/knowledge/database-data-management/scalability-patterns.md +184 -184
- tapps_agents/experts/knowledge/database-data-management/sql-optimization.md +175 -175
- tapps_agents/experts/knowledge/database-data-management/time-series-modeling.md +444 -444
- tapps_agents/experts/knowledge/development-workflow/README.md +16 -16
- tapps_agents/experts/knowledge/development-workflow/automation-best-practices.md +216 -216
- tapps_agents/experts/knowledge/development-workflow/build-strategies.md +198 -198
- tapps_agents/experts/knowledge/development-workflow/deployment-patterns.md +205 -205
- tapps_agents/experts/knowledge/development-workflow/git-workflows.md +205 -205
- tapps_agents/experts/knowledge/documentation-knowledge-management/README.md +16 -16
- tapps_agents/experts/knowledge/documentation-knowledge-management/api-documentation-patterns.md +231 -231
- tapps_agents/experts/knowledge/documentation-knowledge-management/documentation-standards.md +191 -191
- tapps_agents/experts/knowledge/documentation-knowledge-management/knowledge-management.md +171 -171
- tapps_agents/experts/knowledge/documentation-knowledge-management/technical-writing-guide.md +192 -192
- tapps_agents/experts/knowledge/observability-monitoring/alerting-patterns.md +461 -461
- tapps_agents/experts/knowledge/observability-monitoring/apm-tools.md +459 -459
- tapps_agents/experts/knowledge/observability-monitoring/distributed-tracing.md +367 -367
- tapps_agents/experts/knowledge/observability-monitoring/logging-strategies.md +478 -478
- tapps_agents/experts/knowledge/observability-monitoring/metrics-and-monitoring.md +510 -510
- tapps_agents/experts/knowledge/observability-monitoring/observability-best-practices.md +492 -492
- tapps_agents/experts/knowledge/observability-monitoring/open-telemetry.md +573 -573
- tapps_agents/experts/knowledge/observability-monitoring/slo-sli-sla.md +419 -419
- tapps_agents/experts/knowledge/performance/anti-patterns.md +284 -284
- tapps_agents/experts/knowledge/performance/api-performance.md +256 -256
- tapps_agents/experts/knowledge/performance/caching.md +327 -327
- tapps_agents/experts/knowledge/performance/database-performance.md +252 -252
- tapps_agents/experts/knowledge/performance/optimization-patterns.md +327 -327
- tapps_agents/experts/knowledge/performance/profiling.md +297 -297
- tapps_agents/experts/knowledge/performance/resource-management.md +293 -293
- tapps_agents/experts/knowledge/performance/scalability.md +306 -306
- tapps_agents/experts/knowledge/security/owasp-top10.md +209 -209
- tapps_agents/experts/knowledge/security/secure-coding-practices.md +207 -207
- tapps_agents/experts/knowledge/security/threat-modeling.md +220 -220
- tapps_agents/experts/knowledge/security/vulnerability-patterns.md +342 -342
- tapps_agents/experts/knowledge/software-architecture/docker-compose-patterns.md +314 -314
- tapps_agents/experts/knowledge/software-architecture/microservices-patterns.md +379 -379
- tapps_agents/experts/knowledge/software-architecture/service-communication.md +316 -316
- tapps_agents/experts/knowledge/testing/best-practices.md +310 -310
- tapps_agents/experts/knowledge/testing/coverage-analysis.md +293 -293
- tapps_agents/experts/knowledge/testing/mocking.md +256 -256
- tapps_agents/experts/knowledge/testing/test-automation.md +276 -276
- tapps_agents/experts/knowledge/testing/test-data.md +271 -271
- tapps_agents/experts/knowledge/testing/test-design-patterns.md +280 -280
- tapps_agents/experts/knowledge/testing/test-maintenance.md +236 -236
- tapps_agents/experts/knowledge/testing/test-strategies.md +311 -311
- tapps_agents/experts/knowledge/user-experience/information-architecture.md +325 -325
- tapps_agents/experts/knowledge/user-experience/interaction-design.md +363 -363
- tapps_agents/experts/knowledge/user-experience/prototyping.md +293 -293
- tapps_agents/experts/knowledge/user-experience/usability-heuristics.md +337 -337
- tapps_agents/experts/knowledge/user-experience/usability-testing.md +311 -311
- tapps_agents/experts/knowledge/user-experience/user-journeys.md +296 -296
- tapps_agents/experts/knowledge/user-experience/user-research.md +373 -373
- tapps_agents/experts/knowledge/user-experience/ux-principles.md +340 -340
- tapps_agents/experts/knowledge_freshness.py +321 -321
- tapps_agents/experts/knowledge_ingestion.py +438 -438
- tapps_agents/experts/knowledge_need_detector.py +93 -93
- tapps_agents/experts/knowledge_validator.py +382 -382
- tapps_agents/experts/observability.py +440 -440
- tapps_agents/experts/passive_notifier.py +238 -238
- tapps_agents/experts/proactive_orchestrator.py +32 -32
- tapps_agents/experts/rag_chunker.py +205 -205
- tapps_agents/experts/rag_embedder.py +152 -152
- tapps_agents/experts/rag_evaluation.py +299 -299
- tapps_agents/experts/rag_index.py +303 -303
- tapps_agents/experts/rag_metrics.py +293 -293
- tapps_agents/experts/rag_safety.py +263 -263
- tapps_agents/experts/report_generator.py +296 -296
- tapps_agents/experts/setup_wizard.py +441 -441
- tapps_agents/experts/simple_rag.py +431 -431
- tapps_agents/experts/vector_rag.py +354 -354
- tapps_agents/experts/weight_distributor.py +304 -304
- tapps_agents/health/__init__.py +24 -24
- tapps_agents/health/base.py +75 -75
- tapps_agents/health/checks/__init__.py +22 -22
- tapps_agents/health/checks/automation.py +127 -127
- tapps_agents/health/checks/context7_cache.py +210 -210
- tapps_agents/health/checks/environment.py +116 -116
- tapps_agents/health/checks/execution.py +170 -170
- tapps_agents/health/checks/knowledge_base.py +187 -187
- tapps_agents/health/checks/outcomes.py +324 -324
- tapps_agents/health/collector.py +280 -280
- tapps_agents/health/dashboard.py +137 -137
- tapps_agents/health/metrics.py +151 -151
- tapps_agents/health/orchestrator.py +271 -271
- tapps_agents/health/registry.py +166 -166
- tapps_agents/hooks/__init__.py +33 -33
- tapps_agents/hooks/config.py +140 -140
- tapps_agents/hooks/events.py +135 -135
- tapps_agents/hooks/executor.py +128 -128
- tapps_agents/hooks/manager.py +143 -143
- tapps_agents/integration/__init__.py +8 -8
- tapps_agents/integration/service_integrator.py +121 -121
- tapps_agents/integrations/__init__.py +10 -10
- tapps_agents/integrations/clawdbot.py +525 -525
- tapps_agents/integrations/memory_bridge.py +356 -356
- tapps_agents/mcp/__init__.py +18 -18
- tapps_agents/mcp/gateway.py +112 -112
- tapps_agents/mcp/servers/__init__.py +13 -13
- tapps_agents/mcp/servers/analysis.py +204 -204
- tapps_agents/mcp/servers/context7.py +198 -198
- tapps_agents/mcp/servers/filesystem.py +218 -218
- tapps_agents/mcp/servers/git.py +201 -201
- tapps_agents/mcp/tool_registry.py +115 -115
- tapps_agents/quality/__init__.py +54 -54
- tapps_agents/quality/coverage_analyzer.py +379 -379
- tapps_agents/quality/enforcement.py +82 -82
- tapps_agents/quality/gates/__init__.py +37 -37
- tapps_agents/quality/gates/approval_gate.py +255 -255
- tapps_agents/quality/gates/base.py +84 -84
- tapps_agents/quality/gates/exceptions.py +43 -43
- tapps_agents/quality/gates/policy_gate.py +195 -195
- tapps_agents/quality/gates/registry.py +239 -239
- tapps_agents/quality/gates/security_gate.py +156 -156
- tapps_agents/quality/quality_gates.py +369 -369
- tapps_agents/quality/secret_scanner.py +335 -335
- tapps_agents/session/__init__.py +19 -19
- tapps_agents/session/manager.py +256 -256
- tapps_agents/simple_mode/__init__.py +66 -66
- tapps_agents/simple_mode/agent_contracts.py +357 -357
- tapps_agents/simple_mode/beads_hooks.py +151 -151
- tapps_agents/simple_mode/code_snippet_handler.py +382 -382
- tapps_agents/simple_mode/documentation_manager.py +395 -395
- tapps_agents/simple_mode/documentation_reader.py +187 -187
- tapps_agents/simple_mode/file_inference.py +292 -292
- tapps_agents/simple_mode/framework_change_detector.py +268 -268
- tapps_agents/simple_mode/intent_parser.py +510 -510
- tapps_agents/simple_mode/learning_progression.py +358 -358
- tapps_agents/simple_mode/nl_handler.py +700 -700
- tapps_agents/simple_mode/onboarding.py +253 -253
- tapps_agents/simple_mode/orchestrators/__init__.py +38 -38
- tapps_agents/simple_mode/orchestrators/base.py +185 -185
- tapps_agents/simple_mode/orchestrators/breakdown_orchestrator.py +49 -49
- tapps_agents/simple_mode/orchestrators/brownfield_orchestrator.py +135 -135
- tapps_agents/simple_mode/orchestrators/build_orchestrator.py +2700 -2667
- tapps_agents/simple_mode/orchestrators/deliverable_checklist.py +349 -349
- tapps_agents/simple_mode/orchestrators/enhance_orchestrator.py +53 -53
- tapps_agents/simple_mode/orchestrators/epic_orchestrator.py +122 -122
- tapps_agents/simple_mode/orchestrators/explore_orchestrator.py +184 -184
- tapps_agents/simple_mode/orchestrators/fix_orchestrator.py +723 -723
- tapps_agents/simple_mode/orchestrators/plan_analysis_orchestrator.py +206 -206
- tapps_agents/simple_mode/orchestrators/pr_orchestrator.py +237 -237
- tapps_agents/simple_mode/orchestrators/refactor_orchestrator.py +222 -222
- tapps_agents/simple_mode/orchestrators/requirements_tracer.py +262 -262
- tapps_agents/simple_mode/orchestrators/resume_orchestrator.py +210 -210
- tapps_agents/simple_mode/orchestrators/review_orchestrator.py +161 -161
- tapps_agents/simple_mode/orchestrators/test_orchestrator.py +82 -82
- tapps_agents/simple_mode/output_aggregator.py +340 -340
- tapps_agents/simple_mode/result_formatters.py +598 -598
- tapps_agents/simple_mode/step_dependencies.py +382 -382
- tapps_agents/simple_mode/step_results.py +276 -276
- tapps_agents/simple_mode/streaming.py +388 -388
- tapps_agents/simple_mode/variations.py +129 -129
- tapps_agents/simple_mode/visual_feedback.py +238 -238
- tapps_agents/simple_mode/zero_config.py +274 -274
- tapps_agents/suggestions/__init__.py +8 -8
- tapps_agents/suggestions/inline_suggester.py +52 -52
- tapps_agents/templates/__init__.py +8 -8
- tapps_agents/templates/microservice_generator.py +274 -274
- tapps_agents/utils/env_validator.py +291 -291
- tapps_agents/workflow/__init__.py +171 -171
- tapps_agents/workflow/acceptance_verifier.py +132 -132
- tapps_agents/workflow/agent_handlers/__init__.py +41 -41
- tapps_agents/workflow/agent_handlers/analyst_handler.py +75 -75
- tapps_agents/workflow/agent_handlers/architect_handler.py +107 -107
- tapps_agents/workflow/agent_handlers/base.py +84 -84
- tapps_agents/workflow/agent_handlers/debugger_handler.py +100 -100
- tapps_agents/workflow/agent_handlers/designer_handler.py +110 -110
- tapps_agents/workflow/agent_handlers/documenter_handler.py +94 -94
- tapps_agents/workflow/agent_handlers/implementer_handler.py +235 -235
- tapps_agents/workflow/agent_handlers/ops_handler.py +62 -62
- tapps_agents/workflow/agent_handlers/orchestrator_handler.py +43 -43
- tapps_agents/workflow/agent_handlers/planner_handler.py +98 -98
- tapps_agents/workflow/agent_handlers/registry.py +119 -119
- tapps_agents/workflow/agent_handlers/reviewer_handler.py +119 -119
- tapps_agents/workflow/agent_handlers/tester_handler.py +69 -69
- tapps_agents/workflow/analytics_accessor.py +337 -337
- tapps_agents/workflow/analytics_alerts.py +416 -416
- tapps_agents/workflow/analytics_dashboard_cursor.py +281 -281
- tapps_agents/workflow/analytics_dual_write.py +103 -103
- tapps_agents/workflow/analytics_integration.py +119 -119
- tapps_agents/workflow/analytics_query_parser.py +278 -278
- tapps_agents/workflow/analytics_visualizer.py +259 -259
- tapps_agents/workflow/artifact_helper.py +204 -204
- tapps_agents/workflow/audit_logger.py +263 -263
- tapps_agents/workflow/auto_execution_config.py +340 -340
- tapps_agents/workflow/auto_progression.py +586 -586
- tapps_agents/workflow/branch_cleanup.py +349 -349
- tapps_agents/workflow/checkpoint.py +256 -256
- tapps_agents/workflow/checkpoint_manager.py +178 -178
- tapps_agents/workflow/code_artifact.py +179 -179
- tapps_agents/workflow/common_enums.py +96 -96
- tapps_agents/workflow/confirmation_handler.py +130 -130
- tapps_agents/workflow/context_analyzer.py +222 -222
- tapps_agents/workflow/context_artifact.py +230 -230
- tapps_agents/workflow/cursor_chat.py +94 -94
- tapps_agents/workflow/cursor_executor.py +2337 -2196
- tapps_agents/workflow/cursor_skill_helper.py +516 -516
- tapps_agents/workflow/dependency_resolver.py +244 -244
- tapps_agents/workflow/design_artifact.py +156 -156
- tapps_agents/workflow/detector.py +751 -751
- tapps_agents/workflow/direct_execution_fallback.py +301 -301
- tapps_agents/workflow/docs_artifact.py +168 -168
- tapps_agents/workflow/enforcer.py +389 -389
- tapps_agents/workflow/enhancement_artifact.py +142 -142
- tapps_agents/workflow/error_recovery.py +806 -806
- tapps_agents/workflow/event_bus.py +183 -183
- tapps_agents/workflow/event_log.py +612 -612
- tapps_agents/workflow/events.py +63 -63
- tapps_agents/workflow/exceptions.py +43 -43
- tapps_agents/workflow/execution_graph.py +498 -498
- tapps_agents/workflow/execution_plan.py +126 -126
- tapps_agents/workflow/file_utils.py +186 -186
- tapps_agents/workflow/gate_evaluator.py +182 -182
- tapps_agents/workflow/gate_integration.py +200 -200
- tapps_agents/workflow/graph_visualizer.py +130 -130
- tapps_agents/workflow/health_checker.py +206 -206
- tapps_agents/workflow/logging_helper.py +243 -243
- tapps_agents/workflow/manifest.py +582 -582
- tapps_agents/workflow/marker_writer.py +250 -250
- tapps_agents/workflow/message_formatter.py +188 -188
- tapps_agents/workflow/messaging.py +325 -325
- tapps_agents/workflow/metadata_models.py +91 -91
- tapps_agents/workflow/metrics_integration.py +226 -226
- tapps_agents/workflow/migration_utils.py +116 -116
- tapps_agents/workflow/models.py +148 -111
- tapps_agents/workflow/nlp_config.py +198 -198
- tapps_agents/workflow/nlp_error_handler.py +207 -207
- tapps_agents/workflow/nlp_executor.py +163 -163
- tapps_agents/workflow/nlp_parser.py +528 -528
- tapps_agents/workflow/observability_dashboard.py +451 -451
- tapps_agents/workflow/observer.py +170 -170
- tapps_agents/workflow/ops_artifact.py +257 -257
- tapps_agents/workflow/output_passing.py +214 -214
- tapps_agents/workflow/parallel_executor.py +463 -463
- tapps_agents/workflow/planning_artifact.py +179 -179
- tapps_agents/workflow/preset_loader.py +285 -285
- tapps_agents/workflow/preset_recommender.py +270 -270
- tapps_agents/workflow/progress_logger.py +145 -145
- tapps_agents/workflow/progress_manager.py +303 -303
- tapps_agents/workflow/progress_monitor.py +186 -186
- tapps_agents/workflow/progress_updates.py +423 -423
- tapps_agents/workflow/quality_artifact.py +158 -158
- tapps_agents/workflow/quality_loopback.py +101 -101
- tapps_agents/workflow/recommender.py +387 -387
- tapps_agents/workflow/remediation_loop.py +166 -166
- tapps_agents/workflow/result_aggregator.py +300 -300
- tapps_agents/workflow/review_artifact.py +185 -185
- tapps_agents/workflow/schema_validator.py +522 -522
- tapps_agents/workflow/session_handoff.py +178 -178
- tapps_agents/workflow/skill_invoker.py +648 -648
- tapps_agents/workflow/state_manager.py +756 -756
- tapps_agents/workflow/state_persistence_config.py +331 -331
- tapps_agents/workflow/status_monitor.py +449 -449
- tapps_agents/workflow/step_checkpoint.py +314 -314
- tapps_agents/workflow/step_details.py +201 -201
- tapps_agents/workflow/story_models.py +147 -147
- tapps_agents/workflow/streaming.py +416 -416
- tapps_agents/workflow/suggestion_engine.py +552 -552
- tapps_agents/workflow/testing_artifact.py +186 -186
- tapps_agents/workflow/timeline.py +158 -158
- tapps_agents/workflow/token_integration.py +209 -209
- tapps_agents/workflow/validation.py +217 -217
- tapps_agents/workflow/visual_feedback.py +391 -391
- tapps_agents/workflow/workflow_chain.py +95 -95
- tapps_agents/workflow/workflow_summary.py +219 -219
- tapps_agents/workflow/worktree_manager.py +724 -724
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/METADATA +672 -672
- tapps_agents-3.6.0.dist-info/RECORD +758 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/licenses/LICENSE +22 -22
- tapps_agents/health/checks/outcomes.backup_20260204_064058.py +0 -324
- tapps_agents/health/checks/outcomes.backup_20260204_064256.py +0 -324
- tapps_agents/health/checks/outcomes.backup_20260204_064600.py +0 -324
- tapps_agents-3.5.40.dist-info/RECORD +0 -760
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/WHEEL +0 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/entry_points.txt +0 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/top_level.txt +0 -0
|
@@ -1,1142 +1,1142 @@
|
|
|
1
|
-
"""
|
|
2
|
-
TypeScript Scorer - Code quality scoring for TypeScript and JavaScript files
|
|
3
|
-
|
|
4
|
-
Phase 6.4.4: TypeScript & JavaScript Support
|
|
5
|
-
Phase 1.2: Enhanced maintainability scoring
|
|
6
|
-
Phase 7.1: Security Analysis & Score Explanations (Evaluation Enhancement)
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
import re
|
|
11
|
-
import shutil
|
|
12
|
-
import subprocess # nosec B404 - used with fixed args, no shell
|
|
13
|
-
from dataclasses import dataclass, asdict
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
from typing import Any
|
|
16
|
-
|
|
17
|
-
from ...core.subprocess_utils import wrap_windows_cmd_shim
|
|
18
|
-
from .scoring import BaseScorer
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _js_coverage_from_reports(project_root: Path, file_path: Path) -> float | None:
|
|
22
|
-
"""Parse coverage from lcov or coverage-summary.json. §3.6. Returns 0-10 or None."""
|
|
23
|
-
import json as _json
|
|
24
|
-
|
|
25
|
-
root = Path(project_root)
|
|
26
|
-
try:
|
|
27
|
-
rel = str(file_path.resolve().relative_to(root.resolve())).replace("\\", "/")
|
|
28
|
-
except ValueError:
|
|
29
|
-
rel = file_path.name
|
|
30
|
-
for cov_path in (root / "coverage" / "coverage-summary.json",):
|
|
31
|
-
if not cov_path.exists():
|
|
32
|
-
continue
|
|
33
|
-
try:
|
|
34
|
-
data = _json.loads(cov_path.read_text(encoding="utf-8", errors="replace"))
|
|
35
|
-
if isinstance(data, dict) and "total" in data and isinstance(data["total"], dict):
|
|
36
|
-
pct = (data["total"].get("lines") or {}).get("pct")
|
|
37
|
-
if pct is not None:
|
|
38
|
-
return min(10.0, max(0.0, float(pct) / 10.0))
|
|
39
|
-
for k, v in (data if isinstance(data, dict) else {}).items():
|
|
40
|
-
if k != "total" and isinstance(v, dict) and (rel in k or file_path.name in k):
|
|
41
|
-
pct = (v.get("lines") or {}).get("pct")
|
|
42
|
-
if pct is not None:
|
|
43
|
-
return min(10.0, max(0.0, float(pct) / 10.0))
|
|
44
|
-
except Exception:
|
|
45
|
-
pass
|
|
46
|
-
for lcov in (root / "coverage" / "lcov.info", root / "lcov.info"):
|
|
47
|
-
if not lcov.exists():
|
|
48
|
-
continue
|
|
49
|
-
try:
|
|
50
|
-
text = lcov.read_text(encoding="utf-8", errors="replace")
|
|
51
|
-
cur_lh = cur_lf = 0
|
|
52
|
-
in_file = False
|
|
53
|
-
for line in text.splitlines():
|
|
54
|
-
if line.startswith("SF:"):
|
|
55
|
-
p = line[3:].replace("\\", "/")
|
|
56
|
-
in_file = rel in p or file_path.name in p
|
|
57
|
-
cur_lh = cur_lf = 0
|
|
58
|
-
elif in_file and line.startswith("LF:"):
|
|
59
|
-
cur_lf = int(line[3:].strip())
|
|
60
|
-
elif in_file and line.startswith("LH:"):
|
|
61
|
-
cur_lh = int(line[3:].strip())
|
|
62
|
-
elif in_file and line == "end_of_record" and cur_lf > 0:
|
|
63
|
-
return min(10.0, 10.0 * cur_lh / cur_lf)
|
|
64
|
-
except Exception:
|
|
65
|
-
pass
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _apply_npm_audit_to_security(scores: dict[str, Any], file_path: Path) -> None:
|
|
70
|
-
"""Fold npm audit into security_score. MCP_SYSTEMS_IMPROVEMENT_RECOMMENDATIONS §3.3."""
|
|
71
|
-
root = BaseScorer._find_project_root(file_path)
|
|
72
|
-
if not root or not (root / "package.json").exists():
|
|
73
|
-
return
|
|
74
|
-
try:
|
|
75
|
-
from ...agents.ops.dependency_analyzer import DependencyAnalyzer
|
|
76
|
-
|
|
77
|
-
da = DependencyAnalyzer(project_root=root)
|
|
78
|
-
audit = da.run_npm_audit(project_root=root)
|
|
79
|
-
if not audit or audit.get("vulnerability_count", 0) <= 0:
|
|
80
|
-
return
|
|
81
|
-
sb = audit.get("severity_breakdown") or {}
|
|
82
|
-
penalty = (
|
|
83
|
-
sb.get("critical", 0) * 3.0
|
|
84
|
-
+ sb.get("high", 0) * 2.0
|
|
85
|
-
+ sb.get("medium", 0) * 1.0
|
|
86
|
-
+ sb.get("low", 0) * 0.5
|
|
87
|
-
)
|
|
88
|
-
scores["security_score"] = max(0.0, float(scores.get("security_score", 10.0)) - penalty)
|
|
89
|
-
except Exception:
|
|
90
|
-
pass
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
# Security patterns for JavaScript/TypeScript code analysis
|
|
94
|
-
# Phase 7.1: Comprehensive security pattern detection
|
|
95
|
-
DANGEROUS_PATTERNS: dict[str, dict[str, Any]] = {
|
|
96
|
-
"eval": {
|
|
97
|
-
"pattern": r"\beval\s*\(",
|
|
98
|
-
"severity": "HIGH",
|
|
99
|
-
"message": "eval() can execute arbitrary code",
|
|
100
|
-
"recommendation": "Use JSON.parse() for JSON, or safer alternatives like Function constructors with validation",
|
|
101
|
-
"cwe_id": "CWE-95",
|
|
102
|
-
},
|
|
103
|
-
"innerHTML": {
|
|
104
|
-
"pattern": r"\.innerHTML\s*=",
|
|
105
|
-
"severity": "MEDIUM",
|
|
106
|
-
"message": "innerHTML can lead to XSS vulnerabilities",
|
|
107
|
-
"recommendation": "Use textContent for plain text, or sanitize input with DOMPurify",
|
|
108
|
-
"cwe_id": "CWE-79",
|
|
109
|
-
},
|
|
110
|
-
"outerHTML": {
|
|
111
|
-
"pattern": r"\.outerHTML\s*=",
|
|
112
|
-
"severity": "MEDIUM",
|
|
113
|
-
"message": "outerHTML can lead to XSS vulnerabilities",
|
|
114
|
-
"recommendation": "Use DOM manipulation methods instead",
|
|
115
|
-
"cwe_id": "CWE-79",
|
|
116
|
-
},
|
|
117
|
-
"document.write": {
|
|
118
|
-
"pattern": r"\bdocument\.write\s*\(",
|
|
119
|
-
"severity": "MEDIUM",
|
|
120
|
-
"message": "document.write can be exploited for XSS",
|
|
121
|
-
"recommendation": "Use DOM manipulation methods like appendChild instead",
|
|
122
|
-
"cwe_id": "CWE-79",
|
|
123
|
-
},
|
|
124
|
-
"Function constructor": {
|
|
125
|
-
"pattern": r"\bnew\s+Function\s*\(",
|
|
126
|
-
"severity": "HIGH",
|
|
127
|
-
"message": "Function constructor can execute arbitrary code",
|
|
128
|
-
"recommendation": "Use arrow functions or regular function declarations",
|
|
129
|
-
"cwe_id": "CWE-95",
|
|
130
|
-
},
|
|
131
|
-
"setTimeout string": {
|
|
132
|
-
"pattern": r"\bsetTimeout\s*\(\s*['\"`]",
|
|
133
|
-
"severity": "MEDIUM",
|
|
134
|
-
"message": "setTimeout with string argument can execute arbitrary code",
|
|
135
|
-
"recommendation": "Use function reference instead of string",
|
|
136
|
-
"cwe_id": "CWE-95",
|
|
137
|
-
},
|
|
138
|
-
"setInterval string": {
|
|
139
|
-
"pattern": r"\bsetInterval\s*\(\s*['\"`]",
|
|
140
|
-
"severity": "MEDIUM",
|
|
141
|
-
"message": "setInterval with string argument can execute arbitrary code",
|
|
142
|
-
"recommendation": "Use function reference instead of string",
|
|
143
|
-
"cwe_id": "CWE-95",
|
|
144
|
-
},
|
|
145
|
-
"insertAdjacentHTML": {
|
|
146
|
-
"pattern": r"\.insertAdjacentHTML\s*\(",
|
|
147
|
-
"severity": "MEDIUM",
|
|
148
|
-
"message": "insertAdjacentHTML can lead to XSS vulnerabilities",
|
|
149
|
-
"recommendation": "Sanitize input before insertion or use safe DOM methods",
|
|
150
|
-
"cwe_id": "CWE-79",
|
|
151
|
-
},
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
# React-specific security patterns
|
|
155
|
-
REACT_SECURITY_PATTERNS: dict[str, dict[str, Any]] = {
|
|
156
|
-
"dangerouslySetInnerHTML": {
|
|
157
|
-
"pattern": r"dangerouslySetInnerHTML",
|
|
158
|
-
"severity": "HIGH",
|
|
159
|
-
"message": "dangerouslySetInnerHTML can lead to XSS vulnerabilities",
|
|
160
|
-
"recommendation": "Sanitize content with DOMPurify or avoid using if possible",
|
|
161
|
-
"cwe_id": "CWE-79",
|
|
162
|
-
},
|
|
163
|
-
"javascript: URL": {
|
|
164
|
-
"pattern": r"href\s*=\s*[{'\"`]javascript:",
|
|
165
|
-
"severity": "HIGH",
|
|
166
|
-
"message": "javascript: URLs can execute arbitrary code",
|
|
167
|
-
"recommendation": "Use onClick handlers instead of javascript: URLs",
|
|
168
|
-
"cwe_id": "CWE-79",
|
|
169
|
-
},
|
|
170
|
-
"target _blank": {
|
|
171
|
-
"pattern": r"target\s*=\s*['\"`]_blank['\"`](?!.*rel\s*=)",
|
|
172
|
-
"severity": "LOW",
|
|
173
|
-
"message": "Links with target='_blank' without rel='noopener' can be exploited",
|
|
174
|
-
"recommendation": "Add rel='noopener noreferrer' to external links",
|
|
175
|
-
"cwe_id": "CWE-1022",
|
|
176
|
-
},
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
@dataclass
|
|
181
|
-
class SecurityIssue:
|
|
182
|
-
"""Represents a security issue found in code."""
|
|
183
|
-
|
|
184
|
-
pattern: str
|
|
185
|
-
severity: str # "HIGH", "MEDIUM", "LOW"
|
|
186
|
-
line: int
|
|
187
|
-
column: int | None
|
|
188
|
-
message: str
|
|
189
|
-
recommendation: str
|
|
190
|
-
cwe_id: str | None
|
|
191
|
-
|
|
192
|
-
def to_dict(self) -> dict[str, Any]:
|
|
193
|
-
"""Convert to dictionary."""
|
|
194
|
-
return asdict(self)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
@dataclass
|
|
198
|
-
class ScoreExplanation:
|
|
199
|
-
"""Explanation for a code quality score."""
|
|
200
|
-
|
|
201
|
-
score: float
|
|
202
|
-
reason: str
|
|
203
|
-
issues: list[str]
|
|
204
|
-
recommendations: list[str]
|
|
205
|
-
tool_status: str # "available", "unavailable", "error", "pattern_based"
|
|
206
|
-
tool_name: str | None = None
|
|
207
|
-
|
|
208
|
-
def to_dict(self) -> dict[str, Any]:
|
|
209
|
-
"""Convert to dictionary."""
|
|
210
|
-
return asdict(self)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
class TypeScriptScorer(BaseScorer):
|
|
214
|
-
"""
|
|
215
|
-
Calculate code quality scores for TypeScript and JavaScript files.
|
|
216
|
-
|
|
217
|
-
Phase 6.4.4: TypeScript & JavaScript Support
|
|
218
|
-
|
|
219
|
-
Supports:
|
|
220
|
-
- TypeScript files: .ts, .tsx
|
|
221
|
-
- JavaScript files: .js, .jsx
|
|
222
|
-
"""
|
|
223
|
-
|
|
224
|
-
def __init__(
|
|
225
|
-
self, eslint_config: str | None = None, tsconfig_path: str | None = None
|
|
226
|
-
):
|
|
227
|
-
"""
|
|
228
|
-
Initialize TypeScript scorer.
|
|
229
|
-
|
|
230
|
-
Args:
|
|
231
|
-
eslint_config: Path to ESLint config file (optional)
|
|
232
|
-
tsconfig_path: Path to tsconfig.json (optional)
|
|
233
|
-
"""
|
|
234
|
-
self.eslint_config = eslint_config
|
|
235
|
-
self.tsconfig_path = tsconfig_path
|
|
236
|
-
|
|
237
|
-
# Check for tool availability
|
|
238
|
-
self.has_tsc = self._check_tsc_available()
|
|
239
|
-
self.has_eslint = self._check_eslint_available()
|
|
240
|
-
self.has_npm = shutil.which("npm") is not None
|
|
241
|
-
self.has_npx = shutil.which("npx") is not None
|
|
242
|
-
|
|
243
|
-
def _check_tsc_available(self) -> bool:
|
|
244
|
-
"""Check if TypeScript compiler (tsc) is available."""
|
|
245
|
-
if shutil.which("tsc"):
|
|
246
|
-
return True
|
|
247
|
-
npx_path = shutil.which("npx")
|
|
248
|
-
if npx_path:
|
|
249
|
-
try:
|
|
250
|
-
result = subprocess.run( # nosec B603 - fixed args
|
|
251
|
-
wrap_windows_cmd_shim([npx_path, "--yes", "tsc", "--version"]),
|
|
252
|
-
capture_output=True,
|
|
253
|
-
timeout=5,
|
|
254
|
-
check=False,
|
|
255
|
-
)
|
|
256
|
-
return result.returncode == 0
|
|
257
|
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
258
|
-
return False
|
|
259
|
-
return False
|
|
260
|
-
|
|
261
|
-
def _check_eslint_available(self) -> bool:
|
|
262
|
-
"""Check if ESLint is available."""
|
|
263
|
-
if shutil.which("eslint"):
|
|
264
|
-
return True
|
|
265
|
-
npx_path = shutil.which("npx")
|
|
266
|
-
if npx_path:
|
|
267
|
-
try:
|
|
268
|
-
result = subprocess.run( # nosec B603 - fixed args
|
|
269
|
-
wrap_windows_cmd_shim([npx_path, "--yes", "eslint", "--version"]),
|
|
270
|
-
capture_output=True,
|
|
271
|
-
timeout=5,
|
|
272
|
-
check=False,
|
|
273
|
-
)
|
|
274
|
-
return result.returncode == 0
|
|
275
|
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
276
|
-
return False
|
|
277
|
-
return False
|
|
278
|
-
|
|
279
|
-
def score_file(self, file_path: Path, code: str) -> dict[str, Any]:
|
|
280
|
-
"""
|
|
281
|
-
Score a TypeScript/JavaScript file.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
file_path: Path to the file
|
|
285
|
-
code: File content (for complexity analysis)
|
|
286
|
-
|
|
287
|
-
Returns:
|
|
288
|
-
Dictionary with scores:
|
|
289
|
-
{
|
|
290
|
-
"complexity_score": float (0-10),
|
|
291
|
-
"security_score": float (0-10),
|
|
292
|
-
"maintainability_score": float (0-10),
|
|
293
|
-
"test_coverage_score": float (0-10),
|
|
294
|
-
"performance_score": float (0-10),
|
|
295
|
-
"linting_score": float (0-10),
|
|
296
|
-
"type_checking_score": float (0-10),
|
|
297
|
-
"overall_score": float (0-100),
|
|
298
|
-
"metrics": {...}
|
|
299
|
-
}
|
|
300
|
-
"""
|
|
301
|
-
metrics: dict[str, float] = {}
|
|
302
|
-
scores: dict[str, Any] = {
|
|
303
|
-
"complexity_score": 0.0,
|
|
304
|
-
"security_score": 0.0,
|
|
305
|
-
"maintainability_score": 0.0,
|
|
306
|
-
"test_coverage_score": 0.0,
|
|
307
|
-
"performance_score": 5.0,
|
|
308
|
-
"structure_score": 0.0, # 7-category §3.2
|
|
309
|
-
"devex_score": 0.0, # 7-category §3.2
|
|
310
|
-
"linting_score": 0.0,
|
|
311
|
-
"type_checking_score": 0.0,
|
|
312
|
-
"metrics": metrics,
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
# Complexity Score (0-10, lower is better)
|
|
316
|
-
scores["complexity_score"] = self._calculate_complexity(code)
|
|
317
|
-
metrics["complexity"] = float(scores["complexity_score"])
|
|
318
|
-
|
|
319
|
-
# Linting Score (0-10, higher is better) - ESLint
|
|
320
|
-
scores["linting_score"] = self._calculate_linting_score(file_path)
|
|
321
|
-
metrics["linting"] = float(scores["linting_score"])
|
|
322
|
-
|
|
323
|
-
# Security Score (0-10, higher is better) - Phase 7.1 + npm audit §3.3
|
|
324
|
-
security_result = self._calculate_security_score(code, file_path)
|
|
325
|
-
scores["security_score"] = security_result["score"]
|
|
326
|
-
scores["_security_issues"] = security_result.get("issues", [])
|
|
327
|
-
# npm audit: fold into security (MCP_SYSTEMS_IMPROVEMENT_RECOMMENDATIONS §3.3)
|
|
328
|
-
_apply_npm_audit_to_security(scores, file_path)
|
|
329
|
-
metrics["security"] = float(scores["security_score"])
|
|
330
|
-
|
|
331
|
-
# Type Checking Score (0-10, higher is better) - TypeScript compiler
|
|
332
|
-
if file_path.suffix in [".ts", ".tsx"]:
|
|
333
|
-
scores["type_checking_score"] = self._calculate_type_checking_score(
|
|
334
|
-
file_path
|
|
335
|
-
)
|
|
336
|
-
else:
|
|
337
|
-
scores["type_checking_score"] = 5.0 # Neutral for JavaScript
|
|
338
|
-
metrics["type_checking"] = float(scores["type_checking_score"])
|
|
339
|
-
|
|
340
|
-
# Maintainability Score (0-10, higher is better)
|
|
341
|
-
# Phase 3.1: Use context-aware maintainability scorer
|
|
342
|
-
from .maintainability_scorer import MaintainabilityScorer
|
|
343
|
-
from ...core.language_detector import Language
|
|
344
|
-
|
|
345
|
-
maintainability_scorer = MaintainabilityScorer()
|
|
346
|
-
language = (
|
|
347
|
-
Language.TYPESCRIPT
|
|
348
|
-
if file_path and file_path.suffix in [".ts", ".tsx"]
|
|
349
|
-
else Language.JAVASCRIPT
|
|
350
|
-
)
|
|
351
|
-
scores["maintainability_score"] = maintainability_scorer.calculate(
|
|
352
|
-
code, language, file_path, context=None
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
# Phase 3.2: Use context-aware performance scorer
|
|
356
|
-
from .performance_scorer import PerformanceScorer
|
|
357
|
-
|
|
358
|
-
performance_scorer = PerformanceScorer()
|
|
359
|
-
scores["performance_score"] = performance_scorer.calculate(
|
|
360
|
-
code, language, file_path, context=None
|
|
361
|
-
)
|
|
362
|
-
metrics["maintainability"] = float(scores["maintainability_score"])
|
|
363
|
-
|
|
364
|
-
# Test Coverage Score (0-10, higher is better)
|
|
365
|
-
scores["test_coverage_score"] = self._calculate_test_coverage(file_path)
|
|
366
|
-
metrics["test_coverage"] = float(scores["test_coverage_score"])
|
|
367
|
-
|
|
368
|
-
# Structure and DevEx (0-10, higher is better) - 7-category §3.2
|
|
369
|
-
scores["structure_score"] = self._calculate_structure_score(file_path)
|
|
370
|
-
scores["devex_score"] = self._calculate_devex_score(file_path)
|
|
371
|
-
metrics["structure"] = float(scores["structure_score"])
|
|
372
|
-
metrics["devex"] = float(scores["devex_score"])
|
|
373
|
-
|
|
374
|
-
# Overall Score (weighted average, 7-category)
|
|
375
|
-
# complexity 18%, security 13%, maintainability 23%, test 13%, perf 8%, lint 8%, type 5%, structure 6%, devex 6%
|
|
376
|
-
scores["overall_score"] = (
|
|
377
|
-
(10 - scores["complexity_score"]) * 0.18
|
|
378
|
-
+ scores["security_score"] * 0.13
|
|
379
|
-
+ scores["maintainability_score"] * 0.23
|
|
380
|
-
+ scores["test_coverage_score"] * 0.13
|
|
381
|
-
+ scores["performance_score"] * 0.08
|
|
382
|
-
+ scores["linting_score"] * 0.08
|
|
383
|
-
+ scores["type_checking_score"] * 0.05
|
|
384
|
-
+ scores["structure_score"] * 0.06
|
|
385
|
-
+ scores["devex_score"] * 0.06
|
|
386
|
-
) * 10 # Scale to 0-100
|
|
387
|
-
|
|
388
|
-
# Phase 3.3: Validate all scores before returning
|
|
389
|
-
from .score_validator import ScoreValidator
|
|
390
|
-
from ...core.language_detector import Language
|
|
391
|
-
|
|
392
|
-
validator = ScoreValidator()
|
|
393
|
-
language = (
|
|
394
|
-
Language.TYPESCRIPT
|
|
395
|
-
if file_path and file_path.suffix in [".ts", ".tsx"]
|
|
396
|
-
else Language.JAVASCRIPT
|
|
397
|
-
)
|
|
398
|
-
validation_results = validator.validate_all_scores(
|
|
399
|
-
scores, language=language, context=None
|
|
400
|
-
)
|
|
401
|
-
|
|
402
|
-
# Update scores with validated/clamped values and add explanations
|
|
403
|
-
validated_scores = {}
|
|
404
|
-
score_explanations = {}
|
|
405
|
-
for category, result in validation_results.items():
|
|
406
|
-
if result.valid and result.calibrated_score is not None:
|
|
407
|
-
validated_scores[category] = result.calibrated_score
|
|
408
|
-
if result.explanation:
|
|
409
|
-
score_explanations[category] = {
|
|
410
|
-
"explanation": result.explanation,
|
|
411
|
-
"suggestions": result.suggestions,
|
|
412
|
-
}
|
|
413
|
-
else:
|
|
414
|
-
validated_scores[category] = scores.get(category, 0.0)
|
|
415
|
-
|
|
416
|
-
# Merge validated scores back into scores
|
|
417
|
-
for key, value in validated_scores.items():
|
|
418
|
-
if key != "_explanations":
|
|
419
|
-
scores[key] = value
|
|
420
|
-
|
|
421
|
-
# Add explanations to result if any
|
|
422
|
-
if score_explanations:
|
|
423
|
-
scores["_explanations"] = score_explanations
|
|
424
|
-
|
|
425
|
-
# Phase 7.1: Generate comprehensive explanations
|
|
426
|
-
enhanced_explanations = self._generate_explanations(
|
|
427
|
-
scores,
|
|
428
|
-
scores.get("_security_issues", []),
|
|
429
|
-
self.has_eslint,
|
|
430
|
-
self.has_tsc
|
|
431
|
-
)
|
|
432
|
-
if enhanced_explanations:
|
|
433
|
-
# Merge with existing explanations
|
|
434
|
-
if "_explanations" not in scores:
|
|
435
|
-
scores["_explanations"] = {}
|
|
436
|
-
scores["_explanations"].update(enhanced_explanations)
|
|
437
|
-
|
|
438
|
-
# Add tool status
|
|
439
|
-
scores["_tool_status"] = {
|
|
440
|
-
"eslint": "available" if self.has_eslint else "unavailable",
|
|
441
|
-
"tsc": "available" if self.has_tsc else "unavailable",
|
|
442
|
-
"security_scanner": "pattern_based",
|
|
443
|
-
"npm": "available" if self.has_npm else "unavailable",
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return scores
|
|
447
|
-
|
|
448
|
-
def _calculate_complexity(self, code: str) -> float:
|
|
449
|
-
"""
|
|
450
|
-
Calculate cyclomatic complexity for TypeScript/JavaScript.
|
|
451
|
-
|
|
452
|
-
Simple heuristic-based complexity analysis.
|
|
453
|
-
"""
|
|
454
|
-
try:
|
|
455
|
-
# Count decision points
|
|
456
|
-
decision_keywords = [
|
|
457
|
-
"if",
|
|
458
|
-
"else",
|
|
459
|
-
"for",
|
|
460
|
-
"while",
|
|
461
|
-
"do",
|
|
462
|
-
"switch",
|
|
463
|
-
"case",
|
|
464
|
-
"catch",
|
|
465
|
-
"&&",
|
|
466
|
-
"||",
|
|
467
|
-
"?",
|
|
468
|
-
":",
|
|
469
|
-
"try",
|
|
470
|
-
]
|
|
471
|
-
|
|
472
|
-
complexity = 1 # Base complexity
|
|
473
|
-
lines = code.split("\n")
|
|
474
|
-
|
|
475
|
-
for line in lines:
|
|
476
|
-
stripped = line.strip()
|
|
477
|
-
# Skip comments
|
|
478
|
-
if (
|
|
479
|
-
stripped.startswith("//")
|
|
480
|
-
or stripped.startswith("*")
|
|
481
|
-
or stripped.startswith("/*")
|
|
482
|
-
):
|
|
483
|
-
continue
|
|
484
|
-
|
|
485
|
-
for keyword in decision_keywords:
|
|
486
|
-
# Count occurrences (rough estimate)
|
|
487
|
-
if f" {keyword} " in f" {stripped} ":
|
|
488
|
-
complexity += 1
|
|
489
|
-
elif f" {keyword}(" in f" {stripped} ":
|
|
490
|
-
complexity += 1
|
|
491
|
-
|
|
492
|
-
# Scale to 0-10 (max complexity ~50 = 10)
|
|
493
|
-
return min(complexity / 5.0, 10.0)
|
|
494
|
-
|
|
495
|
-
except Exception:
|
|
496
|
-
return 5.0 # Neutral on error
|
|
497
|
-
|
|
498
|
-
def _calculate_linting_score(self, file_path: Path) -> float:
|
|
499
|
-
"""
|
|
500
|
-
Calculate linting score using ESLint (0-10 scale, higher is better).
|
|
501
|
-
|
|
502
|
-
Returns:
|
|
503
|
-
Linting score (0-10)
|
|
504
|
-
"""
|
|
505
|
-
if not self.has_eslint:
|
|
506
|
-
return 5.0 # Neutral score if ESLint not available
|
|
507
|
-
|
|
508
|
-
if file_path.suffix not in [".ts", ".tsx", ".js", ".jsx"]:
|
|
509
|
-
return 10.0 # Perfect score for unsupported file types
|
|
510
|
-
|
|
511
|
-
try:
|
|
512
|
-
# Build ESLint command
|
|
513
|
-
npx_path = shutil.which("npx")
|
|
514
|
-
if not npx_path:
|
|
515
|
-
return 5.0 # Neutral if npx not available
|
|
516
|
-
command = [npx_path, "--yes", "eslint", str(file_path), "--format", "json"]
|
|
517
|
-
|
|
518
|
-
# Add config if provided
|
|
519
|
-
if self.eslint_config:
|
|
520
|
-
command.extend(["--config", self.eslint_config])
|
|
521
|
-
|
|
522
|
-
result = subprocess.run( # nosec B603 - fixed args
|
|
523
|
-
wrap_windows_cmd_shim(command),
|
|
524
|
-
capture_output=True,
|
|
525
|
-
text=True,
|
|
526
|
-
encoding="utf-8",
|
|
527
|
-
errors="replace",
|
|
528
|
-
timeout=30,
|
|
529
|
-
cwd=file_path.parent if file_path.parent.exists() else None,
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
# ESLint returns non-zero exit code if there are errors/warnings
|
|
533
|
-
if result.returncode != 0 and result.stdout:
|
|
534
|
-
try:
|
|
535
|
-
# Parse JSON output
|
|
536
|
-
eslint_output = json.loads(result.stdout)
|
|
537
|
-
|
|
538
|
-
if not eslint_output:
|
|
539
|
-
return 10.0 # No output = no issues
|
|
540
|
-
|
|
541
|
-
# Count errors and warnings
|
|
542
|
-
total_errors = 0
|
|
543
|
-
total_warnings = 0
|
|
544
|
-
|
|
545
|
-
for file_result in eslint_output:
|
|
546
|
-
messages = file_result.get("messages", [])
|
|
547
|
-
for message in messages:
|
|
548
|
-
severity = message.get("severity", 1)
|
|
549
|
-
if severity == 2: # Error
|
|
550
|
-
total_errors += 1
|
|
551
|
-
elif severity == 1: # Warning
|
|
552
|
-
total_warnings += 1
|
|
553
|
-
|
|
554
|
-
# Score: 10 - (errors * 2 + warnings * 1), minimum 0
|
|
555
|
-
score = 10.0 - (total_errors * 2.0 + total_warnings * 1.0)
|
|
556
|
-
return max(0.0, min(10.0, score))
|
|
557
|
-
|
|
558
|
-
except json.JSONDecodeError:
|
|
559
|
-
# If JSON parsing fails, assume no issues if exit code is 0
|
|
560
|
-
if result.returncode == 0:
|
|
561
|
-
return 10.0
|
|
562
|
-
return 5.0 # Neutral on parsing error
|
|
563
|
-
|
|
564
|
-
# Exit code 0 = no issues
|
|
565
|
-
return 10.0
|
|
566
|
-
|
|
567
|
-
except subprocess.TimeoutExpired:
|
|
568
|
-
return 5.0 # Neutral on timeout
|
|
569
|
-
except FileNotFoundError:
|
|
570
|
-
return 5.0 # ESLint not found
|
|
571
|
-
except Exception:
|
|
572
|
-
return 5.0 # Any other error
|
|
573
|
-
|
|
574
|
-
def _calculate_type_checking_score(self, file_path: Path) -> float:
|
|
575
|
-
"""
|
|
576
|
-
Calculate type checking score using TypeScript compiler (tsc).
|
|
577
|
-
|
|
578
|
-
Returns:
|
|
579
|
-
Type checking score (0-10, higher is better)
|
|
580
|
-
"""
|
|
581
|
-
if not self.has_tsc:
|
|
582
|
-
return 5.0 # Neutral score if tsc not available
|
|
583
|
-
|
|
584
|
-
if file_path.suffix not in [".ts", ".tsx"]:
|
|
585
|
-
return 5.0 # Neutral for JavaScript files
|
|
586
|
-
|
|
587
|
-
try:
|
|
588
|
-
# Build tsc command
|
|
589
|
-
npx_path = shutil.which("npx")
|
|
590
|
-
if not npx_path:
|
|
591
|
-
return 5.0 # Neutral if npx not available
|
|
592
|
-
command = [npx_path, "--yes", "tsc", "--noEmit", "--pretty", "false"]
|
|
593
|
-
|
|
594
|
-
# Add tsconfig if provided
|
|
595
|
-
if self.tsconfig_path:
|
|
596
|
-
command.extend(["--project", self.tsconfig_path])
|
|
597
|
-
|
|
598
|
-
# Add file to check
|
|
599
|
-
command.append(str(file_path))
|
|
600
|
-
|
|
601
|
-
result = subprocess.run( # nosec B603 - fixed args
|
|
602
|
-
wrap_windows_cmd_shim(command),
|
|
603
|
-
capture_output=True,
|
|
604
|
-
text=True,
|
|
605
|
-
encoding="utf-8",
|
|
606
|
-
errors="replace",
|
|
607
|
-
timeout=30,
|
|
608
|
-
cwd=file_path.parent if file_path.parent.exists() else None,
|
|
609
|
-
)
|
|
610
|
-
|
|
611
|
-
# tsc returns non-zero exit code if there are type errors
|
|
612
|
-
if result.returncode != 0:
|
|
613
|
-
# Count errors (lines with "error TS")
|
|
614
|
-
error_count = result.stderr.count("error TS") + result.stdout.count(
|
|
615
|
-
"error TS"
|
|
616
|
-
)
|
|
617
|
-
|
|
618
|
-
# Score: 10 - (error_count * 0.5), minimum 0
|
|
619
|
-
score = 10.0 - (error_count * 0.5)
|
|
620
|
-
return max(0.0, min(10.0, score))
|
|
621
|
-
|
|
622
|
-
# Exit code 0 = no type errors
|
|
623
|
-
return 10.0
|
|
624
|
-
|
|
625
|
-
except subprocess.TimeoutExpired:
|
|
626
|
-
return 5.0 # Neutral on timeout
|
|
627
|
-
except FileNotFoundError:
|
|
628
|
-
return 5.0 # tsc not found
|
|
629
|
-
except Exception:
|
|
630
|
-
return 5.0 # Any other error
|
|
631
|
-
|
|
632
|
-
def _calculate_maintainability(
|
|
633
|
-
self, code: str, file_path: Path | None = None
|
|
634
|
-
) -> float:
|
|
635
|
-
"""
|
|
636
|
-
Calculate maintainability score for TypeScript/JavaScript.
|
|
637
|
-
|
|
638
|
-
Enhanced heuristic-based maintainability analysis with context-aware scoring.
|
|
639
|
-
"""
|
|
640
|
-
try:
|
|
641
|
-
lines = code.split("\n")
|
|
642
|
-
total_lines = len(lines)
|
|
643
|
-
|
|
644
|
-
if total_lines == 0:
|
|
645
|
-
return 5.0
|
|
646
|
-
|
|
647
|
-
# Base score - start higher for TypeScript (type safety helps)
|
|
648
|
-
is_typescript = file_path and file_path.suffix in [".ts", ".tsx"]
|
|
649
|
-
score = 6.0 if is_typescript else 5.0
|
|
650
|
-
|
|
651
|
-
# Factors that improve maintainability
|
|
652
|
-
has_comments = sum(
|
|
653
|
-
1 for line in lines if line.strip().startswith("//") or "/*" in line
|
|
654
|
-
)
|
|
655
|
-
has_javadoc = sum(1 for line in lines if "/**" in line or "* @" in line)
|
|
656
|
-
|
|
657
|
-
# Enhanced type safety detection
|
|
658
|
-
has_types = sum(
|
|
659
|
-
1
|
|
660
|
-
for line in lines
|
|
661
|
-
if ": " in line
|
|
662
|
-
and (
|
|
663
|
-
"string" in line
|
|
664
|
-
or "number" in line
|
|
665
|
-
or "boolean" in line
|
|
666
|
-
or "object" in line
|
|
667
|
-
or "Array<" in line
|
|
668
|
-
or "Promise<" in line
|
|
669
|
-
or "Record<" in line
|
|
670
|
-
)
|
|
671
|
-
)
|
|
672
|
-
|
|
673
|
-
# Check for interfaces and type aliases (TypeScript)
|
|
674
|
-
has_interfaces = len(re.findall(r"interface\s+\w+", code))
|
|
675
|
-
has_type_aliases = len(re.findall(r"type\s+\w+\s*=", code))
|
|
676
|
-
has_generics = len(re.findall(r"<\w+>", code))
|
|
677
|
-
|
|
678
|
-
# Check for proper exports/imports structure
|
|
679
|
-
has_exports = len(re.findall(r"export\s+(const|function|class|interface|type)", code))
|
|
680
|
-
has_imports = len(re.findall(r"import\s+.*from\s+['\"]", code))
|
|
681
|
-
|
|
682
|
-
# Error handling patterns
|
|
683
|
-
has_error_handling = bool(
|
|
684
|
-
re.search(r"try\s*\{|catch\s*\(|throw\s+new\s+Error", code)
|
|
685
|
-
)
|
|
686
|
-
|
|
687
|
-
# Documentation patterns
|
|
688
|
-
has_jsdoc = bool(re.search(r"/\*\*[\s\S]*?\*/", code))
|
|
689
|
-
|
|
690
|
-
# Factors that reduce maintainability
|
|
691
|
-
long_lines = sum(1 for line in lines if len(line) > 120)
|
|
692
|
-
# Better nesting calculation
|
|
693
|
-
nesting_depth = self._calculate_nesting_depth(code)
|
|
694
|
-
|
|
695
|
-
# Function/class complexity
|
|
696
|
-
function_count = len(re.findall(r"(function|const\s+\w+\s*=\s*\(|=>\s*\{)", code))
|
|
697
|
-
avg_function_length = total_lines / max(function_count, 1)
|
|
698
|
-
|
|
699
|
-
# Positive factors
|
|
700
|
-
if has_comments > 0:
|
|
701
|
-
score += min(has_comments / total_lines * 2, 2.0)
|
|
702
|
-
if has_javadoc > 0 or has_jsdoc:
|
|
703
|
-
score += min((has_javadoc + (1 if has_jsdoc else 0)) / total_lines * 1.5, 1.5)
|
|
704
|
-
if is_typescript:
|
|
705
|
-
# TypeScript-specific bonuses
|
|
706
|
-
if has_types > 0:
|
|
707
|
-
score += min(has_types / total_lines * 2.5, 2.5)
|
|
708
|
-
if has_interfaces > 0:
|
|
709
|
-
score += min(has_interfaces * 0.3, 1.0)
|
|
710
|
-
if has_type_aliases > 0:
|
|
711
|
-
score += min(has_type_aliases * 0.2, 0.5)
|
|
712
|
-
if has_generics > 0:
|
|
713
|
-
score += min(has_generics * 0.1, 0.5)
|
|
714
|
-
|
|
715
|
-
# Code organization
|
|
716
|
-
if has_exports > 0:
|
|
717
|
-
score += min(has_exports * 0.1, 0.5)
|
|
718
|
-
if has_imports > 0:
|
|
719
|
-
score += min(has_imports * 0.05, 0.3)
|
|
720
|
-
|
|
721
|
-
# Error handling
|
|
722
|
-
if has_error_handling:
|
|
723
|
-
score += 0.5
|
|
724
|
-
|
|
725
|
-
# Negative factors
|
|
726
|
-
if long_lines > 0:
|
|
727
|
-
score -= min(long_lines / total_lines * 1.5, 1.5)
|
|
728
|
-
if nesting_depth > 3:
|
|
729
|
-
score -= min((nesting_depth - 3) * 0.5, 2.0)
|
|
730
|
-
if avg_function_length > 50:
|
|
731
|
-
score -= min((avg_function_length - 50) / 50 * 1.0, 1.5)
|
|
732
|
-
|
|
733
|
-
return max(0.0, min(10.0, score))
|
|
734
|
-
|
|
735
|
-
except Exception:
|
|
736
|
-
return 5.0 # Neutral on error
|
|
737
|
-
|
|
738
|
-
def _calculate_nesting_depth(self, code: str) -> int:
|
|
739
|
-
"""Calculate maximum nesting depth in code."""
|
|
740
|
-
max_depth = 0
|
|
741
|
-
current_depth = 0
|
|
742
|
-
|
|
743
|
-
for char in code:
|
|
744
|
-
if char == '{':
|
|
745
|
-
current_depth += 1
|
|
746
|
-
max_depth = max(max_depth, current_depth)
|
|
747
|
-
elif char == '}':
|
|
748
|
-
current_depth = max(0, current_depth - 1)
|
|
749
|
-
|
|
750
|
-
return max_depth
|
|
751
|
-
|
|
752
|
-
def _calculate_test_coverage(self, file_path: Path) -> float:
|
|
753
|
-
"""
|
|
754
|
-
Test coverage for JS/TS. §3.6: reuse coverage from Vitest/Jest/c8/nyc (lcov, coverage-summary.json).
|
|
755
|
-
Fallback: heuristic from test file presence.
|
|
756
|
-
"""
|
|
757
|
-
root = BaseScorer._find_project_root(file_path)
|
|
758
|
-
if root:
|
|
759
|
-
v = _js_coverage_from_reports(root, file_path)
|
|
760
|
-
if v is not None:
|
|
761
|
-
return float(v)
|
|
762
|
-
try:
|
|
763
|
-
test_patterns = [
|
|
764
|
-
file_path.stem + ".test" + file_path.suffix,
|
|
765
|
-
file_path.stem + ".spec" + file_path.suffix,
|
|
766
|
-
file_path.name.replace(".ts", ".test.ts").replace(".js", ".test.js"),
|
|
767
|
-
]
|
|
768
|
-
parent_dir = file_path.parent
|
|
769
|
-
test_files_found = 0
|
|
770
|
-
for pattern in test_patterns:
|
|
771
|
-
if (parent_dir / pattern).exists():
|
|
772
|
-
test_files_found += 1
|
|
773
|
-
break
|
|
774
|
-
if (parent_dir / "__tests__").exists():
|
|
775
|
-
test_files_found += 1
|
|
776
|
-
return min(10.0, 5.0 + test_files_found * 2.5)
|
|
777
|
-
except Exception:
|
|
778
|
-
return 5.0
|
|
779
|
-
|
|
780
|
-
def get_eslint_issues(self, file_path: Path) -> dict[str, Any]:
|
|
781
|
-
"""
|
|
782
|
-
Get ESLint issues for a file.
|
|
783
|
-
|
|
784
|
-
Returns:
|
|
785
|
-
Dictionary with ESLint results
|
|
786
|
-
"""
|
|
787
|
-
if not self.has_eslint:
|
|
788
|
-
return {"available": False, "error": "ESLint not available"}
|
|
789
|
-
|
|
790
|
-
try:
|
|
791
|
-
npx_path = shutil.which("npx")
|
|
792
|
-
if not npx_path:
|
|
793
|
-
return {"available": False, "error": "npx not available"}
|
|
794
|
-
command = [npx_path, "--yes", "eslint", str(file_path), "--format", "json"]
|
|
795
|
-
|
|
796
|
-
if self.eslint_config:
|
|
797
|
-
command.extend(["--config", self.eslint_config])
|
|
798
|
-
|
|
799
|
-
result = subprocess.run( # nosec B603 - fixed args
|
|
800
|
-
wrap_windows_cmd_shim(command),
|
|
801
|
-
capture_output=True,
|
|
802
|
-
text=True,
|
|
803
|
-
encoding="utf-8",
|
|
804
|
-
errors="replace",
|
|
805
|
-
timeout=30,
|
|
806
|
-
cwd=file_path.parent if file_path.parent.exists() else None,
|
|
807
|
-
)
|
|
808
|
-
|
|
809
|
-
if result.returncode == 0:
|
|
810
|
-
return {"available": True, "issues": []}
|
|
811
|
-
|
|
812
|
-
try:
|
|
813
|
-
eslint_output = json.loads(result.stdout)
|
|
814
|
-
return {"available": True, "issues": eslint_output}
|
|
815
|
-
except json.JSONDecodeError:
|
|
816
|
-
return {
|
|
817
|
-
"available": True,
|
|
818
|
-
"issues": [],
|
|
819
|
-
"error": "Failed to parse ESLint output",
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
except Exception as e:
|
|
823
|
-
return {"available": False, "error": str(e)}
|
|
824
|
-
|
|
825
|
-
def get_type_errors(self, file_path: Path) -> dict[str, Any]:
|
|
826
|
-
"""
|
|
827
|
-
Get TypeScript type errors for a file.
|
|
828
|
-
|
|
829
|
-
Returns:
|
|
830
|
-
Dictionary with type checking results
|
|
831
|
-
"""
|
|
832
|
-
if not self.has_tsc:
|
|
833
|
-
return {"available": False, "error": "TypeScript compiler not available"}
|
|
834
|
-
|
|
835
|
-
if file_path.suffix not in [".ts", ".tsx"]:
|
|
836
|
-
return {"available": False, "error": "File is not TypeScript"}
|
|
837
|
-
|
|
838
|
-
try:
|
|
839
|
-
npx_path = shutil.which("npx")
|
|
840
|
-
if not npx_path:
|
|
841
|
-
return {"available": False, "error": "npx not available"}
|
|
842
|
-
command = [npx_path, "--yes", "tsc", "--noEmit", "--pretty", "false"]
|
|
843
|
-
|
|
844
|
-
if self.tsconfig_path:
|
|
845
|
-
command.extend(["--project", self.tsconfig_path])
|
|
846
|
-
|
|
847
|
-
command.append(str(file_path))
|
|
848
|
-
|
|
849
|
-
result = subprocess.run( # nosec B603 - fixed args
|
|
850
|
-
wrap_windows_cmd_shim(command),
|
|
851
|
-
capture_output=True,
|
|
852
|
-
text=True,
|
|
853
|
-
encoding="utf-8",
|
|
854
|
-
errors="replace",
|
|
855
|
-
timeout=30,
|
|
856
|
-
cwd=file_path.parent if file_path.parent.exists() else None,
|
|
857
|
-
)
|
|
858
|
-
|
|
859
|
-
errors = []
|
|
860
|
-
if result.returncode != 0:
|
|
861
|
-
# Parse errors from output
|
|
862
|
-
for line in (result.stderr + result.stdout).split("\n"):
|
|
863
|
-
if "error TS" in line:
|
|
864
|
-
errors.append(line.strip())
|
|
865
|
-
|
|
866
|
-
return {
|
|
867
|
-
"available": True,
|
|
868
|
-
"errors": errors,
|
|
869
|
-
"error_count": len(errors),
|
|
870
|
-
"passed": len(errors) == 0,
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
except Exception as e:
|
|
874
|
-
return {"available": False, "error": str(e)}
|
|
875
|
-
|
|
876
|
-
# ========================================================================
|
|
877
|
-
# Phase 7.1: Security Analysis Enhancement
|
|
878
|
-
# ========================================================================
|
|
879
|
-
|
|
880
|
-
def _calculate_security_score(
|
|
881
|
-
self, code: str, file_path: Path
|
|
882
|
-
) -> dict[str, Any]:
|
|
883
|
-
"""
|
|
884
|
-
Calculate security score based on dangerous patterns.
|
|
885
|
-
|
|
886
|
-
Phase 7.1: Security Analysis Enhancement
|
|
887
|
-
|
|
888
|
-
Args:
|
|
889
|
-
code: Source code content
|
|
890
|
-
file_path: Path to the file (for React detection)
|
|
891
|
-
|
|
892
|
-
Returns:
|
|
893
|
-
Dictionary with:
|
|
894
|
-
- score: Security score from 0.0 to 10.0 (higher is better)
|
|
895
|
-
- issues: List of SecurityIssue dictionaries
|
|
896
|
-
- high_count, medium_count, low_count: Issue counts by severity
|
|
897
|
-
"""
|
|
898
|
-
issues = self._detect_dangerous_patterns(code, file_path)
|
|
899
|
-
|
|
900
|
-
# Calculate score based on issues
|
|
901
|
-
# Base score: 10.0
|
|
902
|
-
# HIGH severity: -2.0 each
|
|
903
|
-
# MEDIUM severity: -1.0 each
|
|
904
|
-
# LOW severity: -0.5 each
|
|
905
|
-
base_score = 10.0
|
|
906
|
-
high_count = sum(1 for i in issues if i.severity == "HIGH")
|
|
907
|
-
medium_count = sum(1 for i in issues if i.severity == "MEDIUM")
|
|
908
|
-
low_count = sum(1 for i in issues if i.severity == "LOW")
|
|
909
|
-
|
|
910
|
-
penalty = (high_count * 2.0) + (medium_count * 1.0) + (low_count * 0.5)
|
|
911
|
-
score = max(0.0, min(10.0, base_score - penalty))
|
|
912
|
-
|
|
913
|
-
return {
|
|
914
|
-
"score": score,
|
|
915
|
-
"issues": [i.to_dict() for i in issues],
|
|
916
|
-
"high_count": high_count,
|
|
917
|
-
"medium_count": medium_count,
|
|
918
|
-
"low_count": low_count,
|
|
919
|
-
"total_issues": len(issues),
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
def _detect_dangerous_patterns(
|
|
923
|
-
self, code: str, file_path: Path | None = None
|
|
924
|
-
) -> list[SecurityIssue]:
|
|
925
|
-
"""
|
|
926
|
-
Detect dangerous JavaScript/TypeScript patterns.
|
|
927
|
-
|
|
928
|
-
Phase 7.1: Security Analysis Enhancement
|
|
929
|
-
|
|
930
|
-
Args:
|
|
931
|
-
code: Source code to analyze
|
|
932
|
-
file_path: Optional path to detect React files
|
|
933
|
-
|
|
934
|
-
Returns:
|
|
935
|
-
List of SecurityIssue objects
|
|
936
|
-
"""
|
|
937
|
-
issues: list[SecurityIssue] = []
|
|
938
|
-
lines = code.split("\n")
|
|
939
|
-
|
|
940
|
-
# Check if this is a React file
|
|
941
|
-
is_react = False
|
|
942
|
-
if file_path and file_path.suffix in [".tsx", ".jsx"]:
|
|
943
|
-
is_react = True
|
|
944
|
-
elif "react" in code.lower() or "import React" in code:
|
|
945
|
-
is_react = True
|
|
946
|
-
|
|
947
|
-
# Detect general JavaScript/TypeScript patterns
|
|
948
|
-
for pattern_name, pattern_info in DANGEROUS_PATTERNS.items():
|
|
949
|
-
regex = re.compile(pattern_info["pattern"])
|
|
950
|
-
for line_num, line in enumerate(lines, 1):
|
|
951
|
-
# Skip comments
|
|
952
|
-
stripped = line.strip()
|
|
953
|
-
if stripped.startswith("//") or stripped.startswith("*"):
|
|
954
|
-
continue
|
|
955
|
-
|
|
956
|
-
matches = list(regex.finditer(line))
|
|
957
|
-
for match in matches:
|
|
958
|
-
issues.append(SecurityIssue(
|
|
959
|
-
pattern=pattern_name,
|
|
960
|
-
severity=pattern_info["severity"],
|
|
961
|
-
line=line_num,
|
|
962
|
-
column=match.start() + 1,
|
|
963
|
-
message=pattern_info["message"],
|
|
964
|
-
recommendation=pattern_info["recommendation"],
|
|
965
|
-
cwe_id=pattern_info.get("cwe_id"),
|
|
966
|
-
))
|
|
967
|
-
|
|
968
|
-
# Detect React-specific patterns
|
|
969
|
-
if is_react:
|
|
970
|
-
for pattern_name, pattern_info in REACT_SECURITY_PATTERNS.items():
|
|
971
|
-
regex = re.compile(pattern_info["pattern"])
|
|
972
|
-
for line_num, line in enumerate(lines, 1):
|
|
973
|
-
# Skip comments
|
|
974
|
-
stripped = line.strip()
|
|
975
|
-
if stripped.startswith("//") or stripped.startswith("*"):
|
|
976
|
-
continue
|
|
977
|
-
|
|
978
|
-
matches = list(regex.finditer(line))
|
|
979
|
-
for match in matches:
|
|
980
|
-
issues.append(SecurityIssue(
|
|
981
|
-
pattern=pattern_name,
|
|
982
|
-
severity=pattern_info["severity"],
|
|
983
|
-
line=line_num,
|
|
984
|
-
column=match.start() + 1,
|
|
985
|
-
message=pattern_info["message"],
|
|
986
|
-
recommendation=pattern_info["recommendation"],
|
|
987
|
-
cwe_id=pattern_info.get("cwe_id"),
|
|
988
|
-
))
|
|
989
|
-
|
|
990
|
-
return issues
|
|
991
|
-
|
|
992
|
-
def get_security_issues(
|
|
993
|
-
self, code: str, file_path: Path
|
|
994
|
-
) -> dict[str, Any]:
|
|
995
|
-
"""
|
|
996
|
-
Get detailed security issues for external access.
|
|
997
|
-
|
|
998
|
-
Phase 7.1: Security Analysis Enhancement
|
|
999
|
-
|
|
1000
|
-
Args:
|
|
1001
|
-
code: Source code content
|
|
1002
|
-
file_path: Path to the file
|
|
1003
|
-
|
|
1004
|
-
Returns:
|
|
1005
|
-
Dictionary with security analysis results
|
|
1006
|
-
"""
|
|
1007
|
-
result = self._calculate_security_score(code, file_path)
|
|
1008
|
-
result["available"] = True
|
|
1009
|
-
return result
|
|
1010
|
-
|
|
1011
|
-
def _generate_explanations(
|
|
1012
|
-
self,
|
|
1013
|
-
scores: dict[str, Any],
|
|
1014
|
-
security_issues: list[dict[str, Any]],
|
|
1015
|
-
eslint_available: bool,
|
|
1016
|
-
tsc_available: bool,
|
|
1017
|
-
) -> dict[str, dict[str, Any]]:
|
|
1018
|
-
"""
|
|
1019
|
-
Generate explanations for each score.
|
|
1020
|
-
|
|
1021
|
-
Phase 7.1: Score Explanation Enhancement
|
|
1022
|
-
|
|
1023
|
-
Args:
|
|
1024
|
-
scores: Calculated scores dictionary
|
|
1025
|
-
security_issues: List of security issues found
|
|
1026
|
-
eslint_available: Whether ESLint is available
|
|
1027
|
-
tsc_available: Whether TypeScript compiler is available
|
|
1028
|
-
|
|
1029
|
-
Returns:
|
|
1030
|
-
Dictionary of explanations keyed by score name
|
|
1031
|
-
"""
|
|
1032
|
-
explanations: dict[str, dict[str, Any]] = {}
|
|
1033
|
-
|
|
1034
|
-
# Security score explanation
|
|
1035
|
-
security_score = scores.get("security_score", 5.0)
|
|
1036
|
-
if security_issues:
|
|
1037
|
-
high_issues = [i for i in security_issues if i.get("severity") == "HIGH"]
|
|
1038
|
-
medium_issues = [i for i in security_issues if i.get("severity") == "MEDIUM"]
|
|
1039
|
-
low_issues = [i for i in security_issues if i.get("severity") == "LOW"]
|
|
1040
|
-
|
|
1041
|
-
issue_summary = []
|
|
1042
|
-
if high_issues:
|
|
1043
|
-
issue_summary.append(f"{len(high_issues)} HIGH severity")
|
|
1044
|
-
if medium_issues:
|
|
1045
|
-
issue_summary.append(f"{len(medium_issues)} MEDIUM severity")
|
|
1046
|
-
if low_issues:
|
|
1047
|
-
issue_summary.append(f"{len(low_issues)} LOW severity")
|
|
1048
|
-
|
|
1049
|
-
explanations["security_score"] = ScoreExplanation(
|
|
1050
|
-
score=security_score,
|
|
1051
|
-
reason=f"{len(security_issues)} security issue(s) detected: {', '.join(issue_summary)}",
|
|
1052
|
-
issues=[f"{i.get('pattern')} at line {i.get('line')}: {i.get('message')}" for i in security_issues[:5]],
|
|
1053
|
-
recommendations=[i.get("recommendation", "") for i in security_issues[:3] if i.get("recommendation")],
|
|
1054
|
-
tool_status="pattern_based",
|
|
1055
|
-
tool_name="TypeScriptSecurityScanner",
|
|
1056
|
-
).to_dict()
|
|
1057
|
-
else:
|
|
1058
|
-
explanations["security_score"] = ScoreExplanation(
|
|
1059
|
-
score=security_score,
|
|
1060
|
-
reason="No security issues detected",
|
|
1061
|
-
issues=[],
|
|
1062
|
-
recommendations=["Code appears secure. Continue following security best practices."],
|
|
1063
|
-
tool_status="pattern_based",
|
|
1064
|
-
tool_name="TypeScriptSecurityScanner",
|
|
1065
|
-
).to_dict()
|
|
1066
|
-
|
|
1067
|
-
# Linting score explanation
|
|
1068
|
-
linting_score = scores.get("linting_score", 5.0)
|
|
1069
|
-
if not eslint_available:
|
|
1070
|
-
explanations["linting_score"] = ScoreExplanation(
|
|
1071
|
-
score=linting_score,
|
|
1072
|
-
reason="ESLint not available - using neutral score",
|
|
1073
|
-
issues=["ESLint is not installed or not accessible via npx"],
|
|
1074
|
-
recommendations=["Install ESLint: npm install -g eslint", "Or install locally: npm install --save-dev eslint"],
|
|
1075
|
-
tool_status="unavailable",
|
|
1076
|
-
tool_name="ESLint",
|
|
1077
|
-
).to_dict()
|
|
1078
|
-
elif linting_score < 7.0:
|
|
1079
|
-
explanations["linting_score"] = ScoreExplanation(
|
|
1080
|
-
score=linting_score,
|
|
1081
|
-
reason=f"ESLint found issues (score: {linting_score:.1f}/10)",
|
|
1082
|
-
issues=["Multiple linting violations detected"],
|
|
1083
|
-
recommendations=["Run 'npx eslint --fix' to auto-fix issues", "Review ESLint configuration"],
|
|
1084
|
-
tool_status="available",
|
|
1085
|
-
tool_name="ESLint",
|
|
1086
|
-
).to_dict()
|
|
1087
|
-
|
|
1088
|
-
# Type checking score explanation
|
|
1089
|
-
type_checking_score = scores.get("type_checking_score", 5.0)
|
|
1090
|
-
if not tsc_available:
|
|
1091
|
-
explanations["type_checking_score"] = ScoreExplanation(
|
|
1092
|
-
score=type_checking_score,
|
|
1093
|
-
reason="TypeScript compiler not available - using neutral score",
|
|
1094
|
-
issues=["TypeScript is not installed or not accessible via npx"],
|
|
1095
|
-
recommendations=["Install TypeScript: npm install -g typescript", "Or install locally: npm install --save-dev typescript"],
|
|
1096
|
-
tool_status="unavailable",
|
|
1097
|
-
tool_name="TypeScript Compiler (tsc)",
|
|
1098
|
-
).to_dict()
|
|
1099
|
-
elif type_checking_score < 7.0:
|
|
1100
|
-
explanations["type_checking_score"] = ScoreExplanation(
|
|
1101
|
-
score=type_checking_score,
|
|
1102
|
-
reason=f"TypeScript compiler found type errors (score: {type_checking_score:.1f}/10)",
|
|
1103
|
-
issues=["Type checking errors detected"],
|
|
1104
|
-
recommendations=["Fix type errors reported by tsc", "Enable strict mode in tsconfig.json for better type safety"],
|
|
1105
|
-
tool_status="available",
|
|
1106
|
-
tool_name="TypeScript Compiler (tsc)",
|
|
1107
|
-
).to_dict()
|
|
1108
|
-
|
|
1109
|
-
# Complexity score explanation
|
|
1110
|
-
complexity_score = scores.get("complexity_score", 5.0)
|
|
1111
|
-
if complexity_score > 7.0:
|
|
1112
|
-
explanations["complexity_score"] = ScoreExplanation(
|
|
1113
|
-
score=complexity_score,
|
|
1114
|
-
reason=f"High cyclomatic complexity detected (score: {complexity_score:.1f}/10)",
|
|
1115
|
-
issues=["Code has many decision points (if/else, loops, ternary operators)"],
|
|
1116
|
-
recommendations=[
|
|
1117
|
-
"Extract complex logic into smaller functions",
|
|
1118
|
-
"Consider using early returns to reduce nesting",
|
|
1119
|
-
"Apply the single responsibility principle",
|
|
1120
|
-
],
|
|
1121
|
-
tool_status="available",
|
|
1122
|
-
tool_name="Complexity Analyzer",
|
|
1123
|
-
).to_dict()
|
|
1124
|
-
|
|
1125
|
-
# Maintainability score explanation
|
|
1126
|
-
maintainability_score = scores.get("maintainability_score", 5.0)
|
|
1127
|
-
if maintainability_score < 6.0:
|
|
1128
|
-
explanations["maintainability_score"] = ScoreExplanation(
|
|
1129
|
-
score=maintainability_score,
|
|
1130
|
-
reason=f"Low maintainability score (score: {maintainability_score:.1f}/10)",
|
|
1131
|
-
issues=["Code may be difficult to maintain"],
|
|
1132
|
-
recommendations=[
|
|
1133
|
-
"Add JSDoc comments to functions and classes",
|
|
1134
|
-
"Use TypeScript interfaces for type definitions",
|
|
1135
|
-
"Break down large functions into smaller ones",
|
|
1136
|
-
"Follow consistent naming conventions",
|
|
1137
|
-
],
|
|
1138
|
-
tool_status="available",
|
|
1139
|
-
tool_name="Maintainability Analyzer",
|
|
1140
|
-
).to_dict()
|
|
1141
|
-
|
|
1142
|
-
return explanations
|
|
1
|
+
"""
|
|
2
|
+
TypeScript Scorer - Code quality scoring for TypeScript and JavaScript files
|
|
3
|
+
|
|
4
|
+
Phase 6.4.4: TypeScript & JavaScript Support
|
|
5
|
+
Phase 1.2: Enhanced maintainability scoring
|
|
6
|
+
Phase 7.1: Security Analysis & Score Explanations (Evaluation Enhancement)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess # nosec B404 - used with fixed args, no shell
|
|
13
|
+
from dataclasses import dataclass, asdict
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from ...core.subprocess_utils import wrap_windows_cmd_shim
|
|
18
|
+
from .scoring import BaseScorer
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _js_coverage_from_reports(project_root: Path, file_path: Path) -> float | None:
|
|
22
|
+
"""Parse coverage from lcov or coverage-summary.json. §3.6. Returns 0-10 or None."""
|
|
23
|
+
import json as _json
|
|
24
|
+
|
|
25
|
+
root = Path(project_root)
|
|
26
|
+
try:
|
|
27
|
+
rel = str(file_path.resolve().relative_to(root.resolve())).replace("\\", "/")
|
|
28
|
+
except ValueError:
|
|
29
|
+
rel = file_path.name
|
|
30
|
+
for cov_path in (root / "coverage" / "coverage-summary.json",):
|
|
31
|
+
if not cov_path.exists():
|
|
32
|
+
continue
|
|
33
|
+
try:
|
|
34
|
+
data = _json.loads(cov_path.read_text(encoding="utf-8", errors="replace"))
|
|
35
|
+
if isinstance(data, dict) and "total" in data and isinstance(data["total"], dict):
|
|
36
|
+
pct = (data["total"].get("lines") or {}).get("pct")
|
|
37
|
+
if pct is not None:
|
|
38
|
+
return min(10.0, max(0.0, float(pct) / 10.0))
|
|
39
|
+
for k, v in (data if isinstance(data, dict) else {}).items():
|
|
40
|
+
if k != "total" and isinstance(v, dict) and (rel in k or file_path.name in k):
|
|
41
|
+
pct = (v.get("lines") or {}).get("pct")
|
|
42
|
+
if pct is not None:
|
|
43
|
+
return min(10.0, max(0.0, float(pct) / 10.0))
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
for lcov in (root / "coverage" / "lcov.info", root / "lcov.info"):
|
|
47
|
+
if not lcov.exists():
|
|
48
|
+
continue
|
|
49
|
+
try:
|
|
50
|
+
text = lcov.read_text(encoding="utf-8", errors="replace")
|
|
51
|
+
cur_lh = cur_lf = 0
|
|
52
|
+
in_file = False
|
|
53
|
+
for line in text.splitlines():
|
|
54
|
+
if line.startswith("SF:"):
|
|
55
|
+
p = line[3:].replace("\\", "/")
|
|
56
|
+
in_file = rel in p or file_path.name in p
|
|
57
|
+
cur_lh = cur_lf = 0
|
|
58
|
+
elif in_file and line.startswith("LF:"):
|
|
59
|
+
cur_lf = int(line[3:].strip())
|
|
60
|
+
elif in_file and line.startswith("LH:"):
|
|
61
|
+
cur_lh = int(line[3:].strip())
|
|
62
|
+
elif in_file and line == "end_of_record" and cur_lf > 0:
|
|
63
|
+
return min(10.0, 10.0 * cur_lh / cur_lf)
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _apply_npm_audit_to_security(scores: dict[str, Any], file_path: Path) -> None:
|
|
70
|
+
"""Fold npm audit into security_score. MCP_SYSTEMS_IMPROVEMENT_RECOMMENDATIONS §3.3."""
|
|
71
|
+
root = BaseScorer._find_project_root(file_path)
|
|
72
|
+
if not root or not (root / "package.json").exists():
|
|
73
|
+
return
|
|
74
|
+
try:
|
|
75
|
+
from ...agents.ops.dependency_analyzer import DependencyAnalyzer
|
|
76
|
+
|
|
77
|
+
da = DependencyAnalyzer(project_root=root)
|
|
78
|
+
audit = da.run_npm_audit(project_root=root)
|
|
79
|
+
if not audit or audit.get("vulnerability_count", 0) <= 0:
|
|
80
|
+
return
|
|
81
|
+
sb = audit.get("severity_breakdown") or {}
|
|
82
|
+
penalty = (
|
|
83
|
+
sb.get("critical", 0) * 3.0
|
|
84
|
+
+ sb.get("high", 0) * 2.0
|
|
85
|
+
+ sb.get("medium", 0) * 1.0
|
|
86
|
+
+ sb.get("low", 0) * 0.5
|
|
87
|
+
)
|
|
88
|
+
scores["security_score"] = max(0.0, float(scores.get("security_score", 10.0)) - penalty)
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Security patterns for JavaScript/TypeScript code analysis
|
|
94
|
+
# Phase 7.1: Comprehensive security pattern detection
|
|
95
|
+
DANGEROUS_PATTERNS: dict[str, dict[str, Any]] = {
|
|
96
|
+
"eval": {
|
|
97
|
+
"pattern": r"\beval\s*\(",
|
|
98
|
+
"severity": "HIGH",
|
|
99
|
+
"message": "eval() can execute arbitrary code",
|
|
100
|
+
"recommendation": "Use JSON.parse() for JSON, or safer alternatives like Function constructors with validation",
|
|
101
|
+
"cwe_id": "CWE-95",
|
|
102
|
+
},
|
|
103
|
+
"innerHTML": {
|
|
104
|
+
"pattern": r"\.innerHTML\s*=",
|
|
105
|
+
"severity": "MEDIUM",
|
|
106
|
+
"message": "innerHTML can lead to XSS vulnerabilities",
|
|
107
|
+
"recommendation": "Use textContent for plain text, or sanitize input with DOMPurify",
|
|
108
|
+
"cwe_id": "CWE-79",
|
|
109
|
+
},
|
|
110
|
+
"outerHTML": {
|
|
111
|
+
"pattern": r"\.outerHTML\s*=",
|
|
112
|
+
"severity": "MEDIUM",
|
|
113
|
+
"message": "outerHTML can lead to XSS vulnerabilities",
|
|
114
|
+
"recommendation": "Use DOM manipulation methods instead",
|
|
115
|
+
"cwe_id": "CWE-79",
|
|
116
|
+
},
|
|
117
|
+
"document.write": {
|
|
118
|
+
"pattern": r"\bdocument\.write\s*\(",
|
|
119
|
+
"severity": "MEDIUM",
|
|
120
|
+
"message": "document.write can be exploited for XSS",
|
|
121
|
+
"recommendation": "Use DOM manipulation methods like appendChild instead",
|
|
122
|
+
"cwe_id": "CWE-79",
|
|
123
|
+
},
|
|
124
|
+
"Function constructor": {
|
|
125
|
+
"pattern": r"\bnew\s+Function\s*\(",
|
|
126
|
+
"severity": "HIGH",
|
|
127
|
+
"message": "Function constructor can execute arbitrary code",
|
|
128
|
+
"recommendation": "Use arrow functions or regular function declarations",
|
|
129
|
+
"cwe_id": "CWE-95",
|
|
130
|
+
},
|
|
131
|
+
"setTimeout string": {
|
|
132
|
+
"pattern": r"\bsetTimeout\s*\(\s*['\"`]",
|
|
133
|
+
"severity": "MEDIUM",
|
|
134
|
+
"message": "setTimeout with string argument can execute arbitrary code",
|
|
135
|
+
"recommendation": "Use function reference instead of string",
|
|
136
|
+
"cwe_id": "CWE-95",
|
|
137
|
+
},
|
|
138
|
+
"setInterval string": {
|
|
139
|
+
"pattern": r"\bsetInterval\s*\(\s*['\"`]",
|
|
140
|
+
"severity": "MEDIUM",
|
|
141
|
+
"message": "setInterval with string argument can execute arbitrary code",
|
|
142
|
+
"recommendation": "Use function reference instead of string",
|
|
143
|
+
"cwe_id": "CWE-95",
|
|
144
|
+
},
|
|
145
|
+
"insertAdjacentHTML": {
|
|
146
|
+
"pattern": r"\.insertAdjacentHTML\s*\(",
|
|
147
|
+
"severity": "MEDIUM",
|
|
148
|
+
"message": "insertAdjacentHTML can lead to XSS vulnerabilities",
|
|
149
|
+
"recommendation": "Sanitize input before insertion or use safe DOM methods",
|
|
150
|
+
"cwe_id": "CWE-79",
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# React-specific security patterns
|
|
155
|
+
REACT_SECURITY_PATTERNS: dict[str, dict[str, Any]] = {
|
|
156
|
+
"dangerouslySetInnerHTML": {
|
|
157
|
+
"pattern": r"dangerouslySetInnerHTML",
|
|
158
|
+
"severity": "HIGH",
|
|
159
|
+
"message": "dangerouslySetInnerHTML can lead to XSS vulnerabilities",
|
|
160
|
+
"recommendation": "Sanitize content with DOMPurify or avoid using if possible",
|
|
161
|
+
"cwe_id": "CWE-79",
|
|
162
|
+
},
|
|
163
|
+
"javascript: URL": {
|
|
164
|
+
"pattern": r"href\s*=\s*[{'\"`]javascript:",
|
|
165
|
+
"severity": "HIGH",
|
|
166
|
+
"message": "javascript: URLs can execute arbitrary code",
|
|
167
|
+
"recommendation": "Use onClick handlers instead of javascript: URLs",
|
|
168
|
+
"cwe_id": "CWE-79",
|
|
169
|
+
},
|
|
170
|
+
"target _blank": {
|
|
171
|
+
"pattern": r"target\s*=\s*['\"`]_blank['\"`](?!.*rel\s*=)",
|
|
172
|
+
"severity": "LOW",
|
|
173
|
+
"message": "Links with target='_blank' without rel='noopener' can be exploited",
|
|
174
|
+
"recommendation": "Add rel='noopener noreferrer' to external links",
|
|
175
|
+
"cwe_id": "CWE-1022",
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass
|
|
181
|
+
class SecurityIssue:
|
|
182
|
+
"""Represents a security issue found in code."""
|
|
183
|
+
|
|
184
|
+
pattern: str
|
|
185
|
+
severity: str # "HIGH", "MEDIUM", "LOW"
|
|
186
|
+
line: int
|
|
187
|
+
column: int | None
|
|
188
|
+
message: str
|
|
189
|
+
recommendation: str
|
|
190
|
+
cwe_id: str | None
|
|
191
|
+
|
|
192
|
+
def to_dict(self) -> dict[str, Any]:
|
|
193
|
+
"""Convert to dictionary."""
|
|
194
|
+
return asdict(self)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class ScoreExplanation:
|
|
199
|
+
"""Explanation for a code quality score."""
|
|
200
|
+
|
|
201
|
+
score: float
|
|
202
|
+
reason: str
|
|
203
|
+
issues: list[str]
|
|
204
|
+
recommendations: list[str]
|
|
205
|
+
tool_status: str # "available", "unavailable", "error", "pattern_based"
|
|
206
|
+
tool_name: str | None = None
|
|
207
|
+
|
|
208
|
+
def to_dict(self) -> dict[str, Any]:
|
|
209
|
+
"""Convert to dictionary."""
|
|
210
|
+
return asdict(self)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class TypeScriptScorer(BaseScorer):
|
|
214
|
+
"""
|
|
215
|
+
Calculate code quality scores for TypeScript and JavaScript files.
|
|
216
|
+
|
|
217
|
+
Phase 6.4.4: TypeScript & JavaScript Support
|
|
218
|
+
|
|
219
|
+
Supports:
|
|
220
|
+
- TypeScript files: .ts, .tsx
|
|
221
|
+
- JavaScript files: .js, .jsx
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def __init__(
|
|
225
|
+
self, eslint_config: str | None = None, tsconfig_path: str | None = None
|
|
226
|
+
):
|
|
227
|
+
"""
|
|
228
|
+
Initialize TypeScript scorer.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
eslint_config: Path to ESLint config file (optional)
|
|
232
|
+
tsconfig_path: Path to tsconfig.json (optional)
|
|
233
|
+
"""
|
|
234
|
+
self.eslint_config = eslint_config
|
|
235
|
+
self.tsconfig_path = tsconfig_path
|
|
236
|
+
|
|
237
|
+
# Check for tool availability
|
|
238
|
+
self.has_tsc = self._check_tsc_available()
|
|
239
|
+
self.has_eslint = self._check_eslint_available()
|
|
240
|
+
self.has_npm = shutil.which("npm") is not None
|
|
241
|
+
self.has_npx = shutil.which("npx") is not None
|
|
242
|
+
|
|
243
|
+
def _check_tsc_available(self) -> bool:
|
|
244
|
+
"""Check if TypeScript compiler (tsc) is available."""
|
|
245
|
+
if shutil.which("tsc"):
|
|
246
|
+
return True
|
|
247
|
+
npx_path = shutil.which("npx")
|
|
248
|
+
if npx_path:
|
|
249
|
+
try:
|
|
250
|
+
result = subprocess.run( # nosec B603 - fixed args
|
|
251
|
+
wrap_windows_cmd_shim([npx_path, "--yes", "tsc", "--version"]),
|
|
252
|
+
capture_output=True,
|
|
253
|
+
timeout=5,
|
|
254
|
+
check=False,
|
|
255
|
+
)
|
|
256
|
+
return result.returncode == 0
|
|
257
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
258
|
+
return False
|
|
259
|
+
return False
|
|
260
|
+
|
|
261
|
+
def _check_eslint_available(self) -> bool:
|
|
262
|
+
"""Check if ESLint is available."""
|
|
263
|
+
if shutil.which("eslint"):
|
|
264
|
+
return True
|
|
265
|
+
npx_path = shutil.which("npx")
|
|
266
|
+
if npx_path:
|
|
267
|
+
try:
|
|
268
|
+
result = subprocess.run( # nosec B603 - fixed args
|
|
269
|
+
wrap_windows_cmd_shim([npx_path, "--yes", "eslint", "--version"]),
|
|
270
|
+
capture_output=True,
|
|
271
|
+
timeout=5,
|
|
272
|
+
check=False,
|
|
273
|
+
)
|
|
274
|
+
return result.returncode == 0
|
|
275
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
276
|
+
return False
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
def score_file(self, file_path: Path, code: str) -> dict[str, Any]:
|
|
280
|
+
"""
|
|
281
|
+
Score a TypeScript/JavaScript file.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
file_path: Path to the file
|
|
285
|
+
code: File content (for complexity analysis)
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Dictionary with scores:
|
|
289
|
+
{
|
|
290
|
+
"complexity_score": float (0-10),
|
|
291
|
+
"security_score": float (0-10),
|
|
292
|
+
"maintainability_score": float (0-10),
|
|
293
|
+
"test_coverage_score": float (0-10),
|
|
294
|
+
"performance_score": float (0-10),
|
|
295
|
+
"linting_score": float (0-10),
|
|
296
|
+
"type_checking_score": float (0-10),
|
|
297
|
+
"overall_score": float (0-100),
|
|
298
|
+
"metrics": {...}
|
|
299
|
+
}
|
|
300
|
+
"""
|
|
301
|
+
metrics: dict[str, float] = {}
|
|
302
|
+
scores: dict[str, Any] = {
|
|
303
|
+
"complexity_score": 0.0,
|
|
304
|
+
"security_score": 0.0,
|
|
305
|
+
"maintainability_score": 0.0,
|
|
306
|
+
"test_coverage_score": 0.0,
|
|
307
|
+
"performance_score": 5.0,
|
|
308
|
+
"structure_score": 0.0, # 7-category §3.2
|
|
309
|
+
"devex_score": 0.0, # 7-category §3.2
|
|
310
|
+
"linting_score": 0.0,
|
|
311
|
+
"type_checking_score": 0.0,
|
|
312
|
+
"metrics": metrics,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
# Complexity Score (0-10, lower is better)
|
|
316
|
+
scores["complexity_score"] = self._calculate_complexity(code)
|
|
317
|
+
metrics["complexity"] = float(scores["complexity_score"])
|
|
318
|
+
|
|
319
|
+
# Linting Score (0-10, higher is better) - ESLint
|
|
320
|
+
scores["linting_score"] = self._calculate_linting_score(file_path)
|
|
321
|
+
metrics["linting"] = float(scores["linting_score"])
|
|
322
|
+
|
|
323
|
+
# Security Score (0-10, higher is better) - Phase 7.1 + npm audit §3.3
|
|
324
|
+
security_result = self._calculate_security_score(code, file_path)
|
|
325
|
+
scores["security_score"] = security_result["score"]
|
|
326
|
+
scores["_security_issues"] = security_result.get("issues", [])
|
|
327
|
+
# npm audit: fold into security (MCP_SYSTEMS_IMPROVEMENT_RECOMMENDATIONS §3.3)
|
|
328
|
+
_apply_npm_audit_to_security(scores, file_path)
|
|
329
|
+
metrics["security"] = float(scores["security_score"])
|
|
330
|
+
|
|
331
|
+
# Type Checking Score (0-10, higher is better) - TypeScript compiler
|
|
332
|
+
if file_path.suffix in [".ts", ".tsx"]:
|
|
333
|
+
scores["type_checking_score"] = self._calculate_type_checking_score(
|
|
334
|
+
file_path
|
|
335
|
+
)
|
|
336
|
+
else:
|
|
337
|
+
scores["type_checking_score"] = 5.0 # Neutral for JavaScript
|
|
338
|
+
metrics["type_checking"] = float(scores["type_checking_score"])
|
|
339
|
+
|
|
340
|
+
# Maintainability Score (0-10, higher is better)
|
|
341
|
+
# Phase 3.1: Use context-aware maintainability scorer
|
|
342
|
+
from .maintainability_scorer import MaintainabilityScorer
|
|
343
|
+
from ...core.language_detector import Language
|
|
344
|
+
|
|
345
|
+
maintainability_scorer = MaintainabilityScorer()
|
|
346
|
+
language = (
|
|
347
|
+
Language.TYPESCRIPT
|
|
348
|
+
if file_path and file_path.suffix in [".ts", ".tsx"]
|
|
349
|
+
else Language.JAVASCRIPT
|
|
350
|
+
)
|
|
351
|
+
scores["maintainability_score"] = maintainability_scorer.calculate(
|
|
352
|
+
code, language, file_path, context=None
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Phase 3.2: Use context-aware performance scorer
|
|
356
|
+
from .performance_scorer import PerformanceScorer
|
|
357
|
+
|
|
358
|
+
performance_scorer = PerformanceScorer()
|
|
359
|
+
scores["performance_score"] = performance_scorer.calculate(
|
|
360
|
+
code, language, file_path, context=None
|
|
361
|
+
)
|
|
362
|
+
metrics["maintainability"] = float(scores["maintainability_score"])
|
|
363
|
+
|
|
364
|
+
# Test Coverage Score (0-10, higher is better)
|
|
365
|
+
scores["test_coverage_score"] = self._calculate_test_coverage(file_path)
|
|
366
|
+
metrics["test_coverage"] = float(scores["test_coverage_score"])
|
|
367
|
+
|
|
368
|
+
# Structure and DevEx (0-10, higher is better) - 7-category §3.2
|
|
369
|
+
scores["structure_score"] = self._calculate_structure_score(file_path)
|
|
370
|
+
scores["devex_score"] = self._calculate_devex_score(file_path)
|
|
371
|
+
metrics["structure"] = float(scores["structure_score"])
|
|
372
|
+
metrics["devex"] = float(scores["devex_score"])
|
|
373
|
+
|
|
374
|
+
# Overall Score (weighted average, 7-category)
|
|
375
|
+
# complexity 18%, security 13%, maintainability 23%, test 13%, perf 8%, lint 8%, type 5%, structure 6%, devex 6%
|
|
376
|
+
scores["overall_score"] = (
|
|
377
|
+
(10 - scores["complexity_score"]) * 0.18
|
|
378
|
+
+ scores["security_score"] * 0.13
|
|
379
|
+
+ scores["maintainability_score"] * 0.23
|
|
380
|
+
+ scores["test_coverage_score"] * 0.13
|
|
381
|
+
+ scores["performance_score"] * 0.08
|
|
382
|
+
+ scores["linting_score"] * 0.08
|
|
383
|
+
+ scores["type_checking_score"] * 0.05
|
|
384
|
+
+ scores["structure_score"] * 0.06
|
|
385
|
+
+ scores["devex_score"] * 0.06
|
|
386
|
+
) * 10 # Scale to 0-100
|
|
387
|
+
|
|
388
|
+
# Phase 3.3: Validate all scores before returning
|
|
389
|
+
from .score_validator import ScoreValidator
|
|
390
|
+
from ...core.language_detector import Language
|
|
391
|
+
|
|
392
|
+
validator = ScoreValidator()
|
|
393
|
+
language = (
|
|
394
|
+
Language.TYPESCRIPT
|
|
395
|
+
if file_path and file_path.suffix in [".ts", ".tsx"]
|
|
396
|
+
else Language.JAVASCRIPT
|
|
397
|
+
)
|
|
398
|
+
validation_results = validator.validate_all_scores(
|
|
399
|
+
scores, language=language, context=None
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Update scores with validated/clamped values and add explanations
|
|
403
|
+
validated_scores = {}
|
|
404
|
+
score_explanations = {}
|
|
405
|
+
for category, result in validation_results.items():
|
|
406
|
+
if result.valid and result.calibrated_score is not None:
|
|
407
|
+
validated_scores[category] = result.calibrated_score
|
|
408
|
+
if result.explanation:
|
|
409
|
+
score_explanations[category] = {
|
|
410
|
+
"explanation": result.explanation,
|
|
411
|
+
"suggestions": result.suggestions,
|
|
412
|
+
}
|
|
413
|
+
else:
|
|
414
|
+
validated_scores[category] = scores.get(category, 0.0)
|
|
415
|
+
|
|
416
|
+
# Merge validated scores back into scores
|
|
417
|
+
for key, value in validated_scores.items():
|
|
418
|
+
if key != "_explanations":
|
|
419
|
+
scores[key] = value
|
|
420
|
+
|
|
421
|
+
# Add explanations to result if any
|
|
422
|
+
if score_explanations:
|
|
423
|
+
scores["_explanations"] = score_explanations
|
|
424
|
+
|
|
425
|
+
# Phase 7.1: Generate comprehensive explanations
|
|
426
|
+
enhanced_explanations = self._generate_explanations(
|
|
427
|
+
scores,
|
|
428
|
+
scores.get("_security_issues", []),
|
|
429
|
+
self.has_eslint,
|
|
430
|
+
self.has_tsc
|
|
431
|
+
)
|
|
432
|
+
if enhanced_explanations:
|
|
433
|
+
# Merge with existing explanations
|
|
434
|
+
if "_explanations" not in scores:
|
|
435
|
+
scores["_explanations"] = {}
|
|
436
|
+
scores["_explanations"].update(enhanced_explanations)
|
|
437
|
+
|
|
438
|
+
# Add tool status
|
|
439
|
+
scores["_tool_status"] = {
|
|
440
|
+
"eslint": "available" if self.has_eslint else "unavailable",
|
|
441
|
+
"tsc": "available" if self.has_tsc else "unavailable",
|
|
442
|
+
"security_scanner": "pattern_based",
|
|
443
|
+
"npm": "available" if self.has_npm else "unavailable",
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return scores
|
|
447
|
+
|
|
448
|
+
def _calculate_complexity(self, code: str) -> float:
|
|
449
|
+
"""
|
|
450
|
+
Calculate cyclomatic complexity for TypeScript/JavaScript.
|
|
451
|
+
|
|
452
|
+
Simple heuristic-based complexity analysis.
|
|
453
|
+
"""
|
|
454
|
+
try:
|
|
455
|
+
# Count decision points
|
|
456
|
+
decision_keywords = [
|
|
457
|
+
"if",
|
|
458
|
+
"else",
|
|
459
|
+
"for",
|
|
460
|
+
"while",
|
|
461
|
+
"do",
|
|
462
|
+
"switch",
|
|
463
|
+
"case",
|
|
464
|
+
"catch",
|
|
465
|
+
"&&",
|
|
466
|
+
"||",
|
|
467
|
+
"?",
|
|
468
|
+
":",
|
|
469
|
+
"try",
|
|
470
|
+
]
|
|
471
|
+
|
|
472
|
+
complexity = 1 # Base complexity
|
|
473
|
+
lines = code.split("\n")
|
|
474
|
+
|
|
475
|
+
for line in lines:
|
|
476
|
+
stripped = line.strip()
|
|
477
|
+
# Skip comments
|
|
478
|
+
if (
|
|
479
|
+
stripped.startswith("//")
|
|
480
|
+
or stripped.startswith("*")
|
|
481
|
+
or stripped.startswith("/*")
|
|
482
|
+
):
|
|
483
|
+
continue
|
|
484
|
+
|
|
485
|
+
for keyword in decision_keywords:
|
|
486
|
+
# Count occurrences (rough estimate)
|
|
487
|
+
if f" {keyword} " in f" {stripped} ":
|
|
488
|
+
complexity += 1
|
|
489
|
+
elif f" {keyword}(" in f" {stripped} ":
|
|
490
|
+
complexity += 1
|
|
491
|
+
|
|
492
|
+
# Scale to 0-10 (max complexity ~50 = 10)
|
|
493
|
+
return min(complexity / 5.0, 10.0)
|
|
494
|
+
|
|
495
|
+
except Exception:
|
|
496
|
+
return 5.0 # Neutral on error
|
|
497
|
+
|
|
498
|
+
def _calculate_linting_score(self, file_path: Path) -> float:
|
|
499
|
+
"""
|
|
500
|
+
Calculate linting score using ESLint (0-10 scale, higher is better).
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
Linting score (0-10)
|
|
504
|
+
"""
|
|
505
|
+
if not self.has_eslint:
|
|
506
|
+
return 5.0 # Neutral score if ESLint not available
|
|
507
|
+
|
|
508
|
+
if file_path.suffix not in [".ts", ".tsx", ".js", ".jsx"]:
|
|
509
|
+
return 10.0 # Perfect score for unsupported file types
|
|
510
|
+
|
|
511
|
+
try:
|
|
512
|
+
# Build ESLint command
|
|
513
|
+
npx_path = shutil.which("npx")
|
|
514
|
+
if not npx_path:
|
|
515
|
+
return 5.0 # Neutral if npx not available
|
|
516
|
+
command = [npx_path, "--yes", "eslint", str(file_path), "--format", "json"]
|
|
517
|
+
|
|
518
|
+
# Add config if provided
|
|
519
|
+
if self.eslint_config:
|
|
520
|
+
command.extend(["--config", self.eslint_config])
|
|
521
|
+
|
|
522
|
+
result = subprocess.run( # nosec B603 - fixed args
|
|
523
|
+
wrap_windows_cmd_shim(command),
|
|
524
|
+
capture_output=True,
|
|
525
|
+
text=True,
|
|
526
|
+
encoding="utf-8",
|
|
527
|
+
errors="replace",
|
|
528
|
+
timeout=30,
|
|
529
|
+
cwd=file_path.parent if file_path.parent.exists() else None,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# ESLint returns non-zero exit code if there are errors/warnings
|
|
533
|
+
if result.returncode != 0 and result.stdout:
|
|
534
|
+
try:
|
|
535
|
+
# Parse JSON output
|
|
536
|
+
eslint_output = json.loads(result.stdout)
|
|
537
|
+
|
|
538
|
+
if not eslint_output:
|
|
539
|
+
return 10.0 # No output = no issues
|
|
540
|
+
|
|
541
|
+
# Count errors and warnings
|
|
542
|
+
total_errors = 0
|
|
543
|
+
total_warnings = 0
|
|
544
|
+
|
|
545
|
+
for file_result in eslint_output:
|
|
546
|
+
messages = file_result.get("messages", [])
|
|
547
|
+
for message in messages:
|
|
548
|
+
severity = message.get("severity", 1)
|
|
549
|
+
if severity == 2: # Error
|
|
550
|
+
total_errors += 1
|
|
551
|
+
elif severity == 1: # Warning
|
|
552
|
+
total_warnings += 1
|
|
553
|
+
|
|
554
|
+
# Score: 10 - (errors * 2 + warnings * 1), minimum 0
|
|
555
|
+
score = 10.0 - (total_errors * 2.0 + total_warnings * 1.0)
|
|
556
|
+
return max(0.0, min(10.0, score))
|
|
557
|
+
|
|
558
|
+
except json.JSONDecodeError:
|
|
559
|
+
# If JSON parsing fails, assume no issues if exit code is 0
|
|
560
|
+
if result.returncode == 0:
|
|
561
|
+
return 10.0
|
|
562
|
+
return 5.0 # Neutral on parsing error
|
|
563
|
+
|
|
564
|
+
# Exit code 0 = no issues
|
|
565
|
+
return 10.0
|
|
566
|
+
|
|
567
|
+
except subprocess.TimeoutExpired:
|
|
568
|
+
return 5.0 # Neutral on timeout
|
|
569
|
+
except FileNotFoundError:
|
|
570
|
+
return 5.0 # ESLint not found
|
|
571
|
+
except Exception:
|
|
572
|
+
return 5.0 # Any other error
|
|
573
|
+
|
|
574
|
+
def _calculate_type_checking_score(self, file_path: Path) -> float:
|
|
575
|
+
"""
|
|
576
|
+
Calculate type checking score using TypeScript compiler (tsc).
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Type checking score (0-10, higher is better)
|
|
580
|
+
"""
|
|
581
|
+
if not self.has_tsc:
|
|
582
|
+
return 5.0 # Neutral score if tsc not available
|
|
583
|
+
|
|
584
|
+
if file_path.suffix not in [".ts", ".tsx"]:
|
|
585
|
+
return 5.0 # Neutral for JavaScript files
|
|
586
|
+
|
|
587
|
+
try:
|
|
588
|
+
# Build tsc command
|
|
589
|
+
npx_path = shutil.which("npx")
|
|
590
|
+
if not npx_path:
|
|
591
|
+
return 5.0 # Neutral if npx not available
|
|
592
|
+
command = [npx_path, "--yes", "tsc", "--noEmit", "--pretty", "false"]
|
|
593
|
+
|
|
594
|
+
# Add tsconfig if provided
|
|
595
|
+
if self.tsconfig_path:
|
|
596
|
+
command.extend(["--project", self.tsconfig_path])
|
|
597
|
+
|
|
598
|
+
# Add file to check
|
|
599
|
+
command.append(str(file_path))
|
|
600
|
+
|
|
601
|
+
result = subprocess.run( # nosec B603 - fixed args
|
|
602
|
+
wrap_windows_cmd_shim(command),
|
|
603
|
+
capture_output=True,
|
|
604
|
+
text=True,
|
|
605
|
+
encoding="utf-8",
|
|
606
|
+
errors="replace",
|
|
607
|
+
timeout=30,
|
|
608
|
+
cwd=file_path.parent if file_path.parent.exists() else None,
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
# tsc returns non-zero exit code if there are type errors
|
|
612
|
+
if result.returncode != 0:
|
|
613
|
+
# Count errors (lines with "error TS")
|
|
614
|
+
error_count = result.stderr.count("error TS") + result.stdout.count(
|
|
615
|
+
"error TS"
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
# Score: 10 - (error_count * 0.5), minimum 0
|
|
619
|
+
score = 10.0 - (error_count * 0.5)
|
|
620
|
+
return max(0.0, min(10.0, score))
|
|
621
|
+
|
|
622
|
+
# Exit code 0 = no type errors
|
|
623
|
+
return 10.0
|
|
624
|
+
|
|
625
|
+
except subprocess.TimeoutExpired:
|
|
626
|
+
return 5.0 # Neutral on timeout
|
|
627
|
+
except FileNotFoundError:
|
|
628
|
+
return 5.0 # tsc not found
|
|
629
|
+
except Exception:
|
|
630
|
+
return 5.0 # Any other error
|
|
631
|
+
|
|
632
|
+
def _calculate_maintainability(
|
|
633
|
+
self, code: str, file_path: Path | None = None
|
|
634
|
+
) -> float:
|
|
635
|
+
"""
|
|
636
|
+
Calculate maintainability score for TypeScript/JavaScript.
|
|
637
|
+
|
|
638
|
+
Enhanced heuristic-based maintainability analysis with context-aware scoring.
|
|
639
|
+
"""
|
|
640
|
+
try:
|
|
641
|
+
lines = code.split("\n")
|
|
642
|
+
total_lines = len(lines)
|
|
643
|
+
|
|
644
|
+
if total_lines == 0:
|
|
645
|
+
return 5.0
|
|
646
|
+
|
|
647
|
+
# Base score - start higher for TypeScript (type safety helps)
|
|
648
|
+
is_typescript = file_path and file_path.suffix in [".ts", ".tsx"]
|
|
649
|
+
score = 6.0 if is_typescript else 5.0
|
|
650
|
+
|
|
651
|
+
# Factors that improve maintainability
|
|
652
|
+
has_comments = sum(
|
|
653
|
+
1 for line in lines if line.strip().startswith("//") or "/*" in line
|
|
654
|
+
)
|
|
655
|
+
has_javadoc = sum(1 for line in lines if "/**" in line or "* @" in line)
|
|
656
|
+
|
|
657
|
+
# Enhanced type safety detection
|
|
658
|
+
has_types = sum(
|
|
659
|
+
1
|
|
660
|
+
for line in lines
|
|
661
|
+
if ": " in line
|
|
662
|
+
and (
|
|
663
|
+
"string" in line
|
|
664
|
+
or "number" in line
|
|
665
|
+
or "boolean" in line
|
|
666
|
+
or "object" in line
|
|
667
|
+
or "Array<" in line
|
|
668
|
+
or "Promise<" in line
|
|
669
|
+
or "Record<" in line
|
|
670
|
+
)
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
# Check for interfaces and type aliases (TypeScript)
|
|
674
|
+
has_interfaces = len(re.findall(r"interface\s+\w+", code))
|
|
675
|
+
has_type_aliases = len(re.findall(r"type\s+\w+\s*=", code))
|
|
676
|
+
has_generics = len(re.findall(r"<\w+>", code))
|
|
677
|
+
|
|
678
|
+
# Check for proper exports/imports structure
|
|
679
|
+
has_exports = len(re.findall(r"export\s+(const|function|class|interface|type)", code))
|
|
680
|
+
has_imports = len(re.findall(r"import\s+.*from\s+['\"]", code))
|
|
681
|
+
|
|
682
|
+
# Error handling patterns
|
|
683
|
+
has_error_handling = bool(
|
|
684
|
+
re.search(r"try\s*\{|catch\s*\(|throw\s+new\s+Error", code)
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
# Documentation patterns
|
|
688
|
+
has_jsdoc = bool(re.search(r"/\*\*[\s\S]*?\*/", code))
|
|
689
|
+
|
|
690
|
+
# Factors that reduce maintainability
|
|
691
|
+
long_lines = sum(1 for line in lines if len(line) > 120)
|
|
692
|
+
# Better nesting calculation
|
|
693
|
+
nesting_depth = self._calculate_nesting_depth(code)
|
|
694
|
+
|
|
695
|
+
# Function/class complexity
|
|
696
|
+
function_count = len(re.findall(r"(function|const\s+\w+\s*=\s*\(|=>\s*\{)", code))
|
|
697
|
+
avg_function_length = total_lines / max(function_count, 1)
|
|
698
|
+
|
|
699
|
+
# Positive factors
|
|
700
|
+
if has_comments > 0:
|
|
701
|
+
score += min(has_comments / total_lines * 2, 2.0)
|
|
702
|
+
if has_javadoc > 0 or has_jsdoc:
|
|
703
|
+
score += min((has_javadoc + (1 if has_jsdoc else 0)) / total_lines * 1.5, 1.5)
|
|
704
|
+
if is_typescript:
|
|
705
|
+
# TypeScript-specific bonuses
|
|
706
|
+
if has_types > 0:
|
|
707
|
+
score += min(has_types / total_lines * 2.5, 2.5)
|
|
708
|
+
if has_interfaces > 0:
|
|
709
|
+
score += min(has_interfaces * 0.3, 1.0)
|
|
710
|
+
if has_type_aliases > 0:
|
|
711
|
+
score += min(has_type_aliases * 0.2, 0.5)
|
|
712
|
+
if has_generics > 0:
|
|
713
|
+
score += min(has_generics * 0.1, 0.5)
|
|
714
|
+
|
|
715
|
+
# Code organization
|
|
716
|
+
if has_exports > 0:
|
|
717
|
+
score += min(has_exports * 0.1, 0.5)
|
|
718
|
+
if has_imports > 0:
|
|
719
|
+
score += min(has_imports * 0.05, 0.3)
|
|
720
|
+
|
|
721
|
+
# Error handling
|
|
722
|
+
if has_error_handling:
|
|
723
|
+
score += 0.5
|
|
724
|
+
|
|
725
|
+
# Negative factors
|
|
726
|
+
if long_lines > 0:
|
|
727
|
+
score -= min(long_lines / total_lines * 1.5, 1.5)
|
|
728
|
+
if nesting_depth > 3:
|
|
729
|
+
score -= min((nesting_depth - 3) * 0.5, 2.0)
|
|
730
|
+
if avg_function_length > 50:
|
|
731
|
+
score -= min((avg_function_length - 50) / 50 * 1.0, 1.5)
|
|
732
|
+
|
|
733
|
+
return max(0.0, min(10.0, score))
|
|
734
|
+
|
|
735
|
+
except Exception:
|
|
736
|
+
return 5.0 # Neutral on error
|
|
737
|
+
|
|
738
|
+
def _calculate_nesting_depth(self, code: str) -> int:
|
|
739
|
+
"""Calculate maximum nesting depth in code."""
|
|
740
|
+
max_depth = 0
|
|
741
|
+
current_depth = 0
|
|
742
|
+
|
|
743
|
+
for char in code:
|
|
744
|
+
if char == '{':
|
|
745
|
+
current_depth += 1
|
|
746
|
+
max_depth = max(max_depth, current_depth)
|
|
747
|
+
elif char == '}':
|
|
748
|
+
current_depth = max(0, current_depth - 1)
|
|
749
|
+
|
|
750
|
+
return max_depth
|
|
751
|
+
|
|
752
|
+
def _calculate_test_coverage(self, file_path: Path) -> float:
|
|
753
|
+
"""
|
|
754
|
+
Test coverage for JS/TS. §3.6: reuse coverage from Vitest/Jest/c8/nyc (lcov, coverage-summary.json).
|
|
755
|
+
Fallback: heuristic from test file presence.
|
|
756
|
+
"""
|
|
757
|
+
root = BaseScorer._find_project_root(file_path)
|
|
758
|
+
if root:
|
|
759
|
+
v = _js_coverage_from_reports(root, file_path)
|
|
760
|
+
if v is not None:
|
|
761
|
+
return float(v)
|
|
762
|
+
try:
|
|
763
|
+
test_patterns = [
|
|
764
|
+
file_path.stem + ".test" + file_path.suffix,
|
|
765
|
+
file_path.stem + ".spec" + file_path.suffix,
|
|
766
|
+
file_path.name.replace(".ts", ".test.ts").replace(".js", ".test.js"),
|
|
767
|
+
]
|
|
768
|
+
parent_dir = file_path.parent
|
|
769
|
+
test_files_found = 0
|
|
770
|
+
for pattern in test_patterns:
|
|
771
|
+
if (parent_dir / pattern).exists():
|
|
772
|
+
test_files_found += 1
|
|
773
|
+
break
|
|
774
|
+
if (parent_dir / "__tests__").exists():
|
|
775
|
+
test_files_found += 1
|
|
776
|
+
return min(10.0, 5.0 + test_files_found * 2.5)
|
|
777
|
+
except Exception:
|
|
778
|
+
return 5.0
|
|
779
|
+
|
|
780
|
+
def get_eslint_issues(self, file_path: Path) -> dict[str, Any]:
|
|
781
|
+
"""
|
|
782
|
+
Get ESLint issues for a file.
|
|
783
|
+
|
|
784
|
+
Returns:
|
|
785
|
+
Dictionary with ESLint results
|
|
786
|
+
"""
|
|
787
|
+
if not self.has_eslint:
|
|
788
|
+
return {"available": False, "error": "ESLint not available"}
|
|
789
|
+
|
|
790
|
+
try:
|
|
791
|
+
npx_path = shutil.which("npx")
|
|
792
|
+
if not npx_path:
|
|
793
|
+
return {"available": False, "error": "npx not available"}
|
|
794
|
+
command = [npx_path, "--yes", "eslint", str(file_path), "--format", "json"]
|
|
795
|
+
|
|
796
|
+
if self.eslint_config:
|
|
797
|
+
command.extend(["--config", self.eslint_config])
|
|
798
|
+
|
|
799
|
+
result = subprocess.run( # nosec B603 - fixed args
|
|
800
|
+
wrap_windows_cmd_shim(command),
|
|
801
|
+
capture_output=True,
|
|
802
|
+
text=True,
|
|
803
|
+
encoding="utf-8",
|
|
804
|
+
errors="replace",
|
|
805
|
+
timeout=30,
|
|
806
|
+
cwd=file_path.parent if file_path.parent.exists() else None,
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
if result.returncode == 0:
|
|
810
|
+
return {"available": True, "issues": []}
|
|
811
|
+
|
|
812
|
+
try:
|
|
813
|
+
eslint_output = json.loads(result.stdout)
|
|
814
|
+
return {"available": True, "issues": eslint_output}
|
|
815
|
+
except json.JSONDecodeError:
|
|
816
|
+
return {
|
|
817
|
+
"available": True,
|
|
818
|
+
"issues": [],
|
|
819
|
+
"error": "Failed to parse ESLint output",
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
except Exception as e:
|
|
823
|
+
return {"available": False, "error": str(e)}
|
|
824
|
+
|
|
825
|
+
def get_type_errors(self, file_path: Path) -> dict[str, Any]:
|
|
826
|
+
"""
|
|
827
|
+
Get TypeScript type errors for a file.
|
|
828
|
+
|
|
829
|
+
Returns:
|
|
830
|
+
Dictionary with type checking results
|
|
831
|
+
"""
|
|
832
|
+
if not self.has_tsc:
|
|
833
|
+
return {"available": False, "error": "TypeScript compiler not available"}
|
|
834
|
+
|
|
835
|
+
if file_path.suffix not in [".ts", ".tsx"]:
|
|
836
|
+
return {"available": False, "error": "File is not TypeScript"}
|
|
837
|
+
|
|
838
|
+
try:
|
|
839
|
+
npx_path = shutil.which("npx")
|
|
840
|
+
if not npx_path:
|
|
841
|
+
return {"available": False, "error": "npx not available"}
|
|
842
|
+
command = [npx_path, "--yes", "tsc", "--noEmit", "--pretty", "false"]
|
|
843
|
+
|
|
844
|
+
if self.tsconfig_path:
|
|
845
|
+
command.extend(["--project", self.tsconfig_path])
|
|
846
|
+
|
|
847
|
+
command.append(str(file_path))
|
|
848
|
+
|
|
849
|
+
result = subprocess.run( # nosec B603 - fixed args
|
|
850
|
+
wrap_windows_cmd_shim(command),
|
|
851
|
+
capture_output=True,
|
|
852
|
+
text=True,
|
|
853
|
+
encoding="utf-8",
|
|
854
|
+
errors="replace",
|
|
855
|
+
timeout=30,
|
|
856
|
+
cwd=file_path.parent if file_path.parent.exists() else None,
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
errors = []
|
|
860
|
+
if result.returncode != 0:
|
|
861
|
+
# Parse errors from output
|
|
862
|
+
for line in (result.stderr + result.stdout).split("\n"):
|
|
863
|
+
if "error TS" in line:
|
|
864
|
+
errors.append(line.strip())
|
|
865
|
+
|
|
866
|
+
return {
|
|
867
|
+
"available": True,
|
|
868
|
+
"errors": errors,
|
|
869
|
+
"error_count": len(errors),
|
|
870
|
+
"passed": len(errors) == 0,
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
except Exception as e:
|
|
874
|
+
return {"available": False, "error": str(e)}
|
|
875
|
+
|
|
876
|
+
# ========================================================================
|
|
877
|
+
# Phase 7.1: Security Analysis Enhancement
|
|
878
|
+
# ========================================================================
|
|
879
|
+
|
|
880
|
+
def _calculate_security_score(
|
|
881
|
+
self, code: str, file_path: Path
|
|
882
|
+
) -> dict[str, Any]:
|
|
883
|
+
"""
|
|
884
|
+
Calculate security score based on dangerous patterns.
|
|
885
|
+
|
|
886
|
+
Phase 7.1: Security Analysis Enhancement
|
|
887
|
+
|
|
888
|
+
Args:
|
|
889
|
+
code: Source code content
|
|
890
|
+
file_path: Path to the file (for React detection)
|
|
891
|
+
|
|
892
|
+
Returns:
|
|
893
|
+
Dictionary with:
|
|
894
|
+
- score: Security score from 0.0 to 10.0 (higher is better)
|
|
895
|
+
- issues: List of SecurityIssue dictionaries
|
|
896
|
+
- high_count, medium_count, low_count: Issue counts by severity
|
|
897
|
+
"""
|
|
898
|
+
issues = self._detect_dangerous_patterns(code, file_path)
|
|
899
|
+
|
|
900
|
+
# Calculate score based on issues
|
|
901
|
+
# Base score: 10.0
|
|
902
|
+
# HIGH severity: -2.0 each
|
|
903
|
+
# MEDIUM severity: -1.0 each
|
|
904
|
+
# LOW severity: -0.5 each
|
|
905
|
+
base_score = 10.0
|
|
906
|
+
high_count = sum(1 for i in issues if i.severity == "HIGH")
|
|
907
|
+
medium_count = sum(1 for i in issues if i.severity == "MEDIUM")
|
|
908
|
+
low_count = sum(1 for i in issues if i.severity == "LOW")
|
|
909
|
+
|
|
910
|
+
penalty = (high_count * 2.0) + (medium_count * 1.0) + (low_count * 0.5)
|
|
911
|
+
score = max(0.0, min(10.0, base_score - penalty))
|
|
912
|
+
|
|
913
|
+
return {
|
|
914
|
+
"score": score,
|
|
915
|
+
"issues": [i.to_dict() for i in issues],
|
|
916
|
+
"high_count": high_count,
|
|
917
|
+
"medium_count": medium_count,
|
|
918
|
+
"low_count": low_count,
|
|
919
|
+
"total_issues": len(issues),
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
def _detect_dangerous_patterns(
|
|
923
|
+
self, code: str, file_path: Path | None = None
|
|
924
|
+
) -> list[SecurityIssue]:
|
|
925
|
+
"""
|
|
926
|
+
Detect dangerous JavaScript/TypeScript patterns.
|
|
927
|
+
|
|
928
|
+
Phase 7.1: Security Analysis Enhancement
|
|
929
|
+
|
|
930
|
+
Args:
|
|
931
|
+
code: Source code to analyze
|
|
932
|
+
file_path: Optional path to detect React files
|
|
933
|
+
|
|
934
|
+
Returns:
|
|
935
|
+
List of SecurityIssue objects
|
|
936
|
+
"""
|
|
937
|
+
issues: list[SecurityIssue] = []
|
|
938
|
+
lines = code.split("\n")
|
|
939
|
+
|
|
940
|
+
# Check if this is a React file
|
|
941
|
+
is_react = False
|
|
942
|
+
if file_path and file_path.suffix in [".tsx", ".jsx"]:
|
|
943
|
+
is_react = True
|
|
944
|
+
elif "react" in code.lower() or "import React" in code:
|
|
945
|
+
is_react = True
|
|
946
|
+
|
|
947
|
+
# Detect general JavaScript/TypeScript patterns
|
|
948
|
+
for pattern_name, pattern_info in DANGEROUS_PATTERNS.items():
|
|
949
|
+
regex = re.compile(pattern_info["pattern"])
|
|
950
|
+
for line_num, line in enumerate(lines, 1):
|
|
951
|
+
# Skip comments
|
|
952
|
+
stripped = line.strip()
|
|
953
|
+
if stripped.startswith("//") or stripped.startswith("*"):
|
|
954
|
+
continue
|
|
955
|
+
|
|
956
|
+
matches = list(regex.finditer(line))
|
|
957
|
+
for match in matches:
|
|
958
|
+
issues.append(SecurityIssue(
|
|
959
|
+
pattern=pattern_name,
|
|
960
|
+
severity=pattern_info["severity"],
|
|
961
|
+
line=line_num,
|
|
962
|
+
column=match.start() + 1,
|
|
963
|
+
message=pattern_info["message"],
|
|
964
|
+
recommendation=pattern_info["recommendation"],
|
|
965
|
+
cwe_id=pattern_info.get("cwe_id"),
|
|
966
|
+
))
|
|
967
|
+
|
|
968
|
+
# Detect React-specific patterns
|
|
969
|
+
if is_react:
|
|
970
|
+
for pattern_name, pattern_info in REACT_SECURITY_PATTERNS.items():
|
|
971
|
+
regex = re.compile(pattern_info["pattern"])
|
|
972
|
+
for line_num, line in enumerate(lines, 1):
|
|
973
|
+
# Skip comments
|
|
974
|
+
stripped = line.strip()
|
|
975
|
+
if stripped.startswith("//") or stripped.startswith("*"):
|
|
976
|
+
continue
|
|
977
|
+
|
|
978
|
+
matches = list(regex.finditer(line))
|
|
979
|
+
for match in matches:
|
|
980
|
+
issues.append(SecurityIssue(
|
|
981
|
+
pattern=pattern_name,
|
|
982
|
+
severity=pattern_info["severity"],
|
|
983
|
+
line=line_num,
|
|
984
|
+
column=match.start() + 1,
|
|
985
|
+
message=pattern_info["message"],
|
|
986
|
+
recommendation=pattern_info["recommendation"],
|
|
987
|
+
cwe_id=pattern_info.get("cwe_id"),
|
|
988
|
+
))
|
|
989
|
+
|
|
990
|
+
return issues
|
|
991
|
+
|
|
992
|
+
def get_security_issues(
|
|
993
|
+
self, code: str, file_path: Path
|
|
994
|
+
) -> dict[str, Any]:
|
|
995
|
+
"""
|
|
996
|
+
Get detailed security issues for external access.
|
|
997
|
+
|
|
998
|
+
Phase 7.1: Security Analysis Enhancement
|
|
999
|
+
|
|
1000
|
+
Args:
|
|
1001
|
+
code: Source code content
|
|
1002
|
+
file_path: Path to the file
|
|
1003
|
+
|
|
1004
|
+
Returns:
|
|
1005
|
+
Dictionary with security analysis results
|
|
1006
|
+
"""
|
|
1007
|
+
result = self._calculate_security_score(code, file_path)
|
|
1008
|
+
result["available"] = True
|
|
1009
|
+
return result
|
|
1010
|
+
|
|
1011
|
+
def _generate_explanations(
|
|
1012
|
+
self,
|
|
1013
|
+
scores: dict[str, Any],
|
|
1014
|
+
security_issues: list[dict[str, Any]],
|
|
1015
|
+
eslint_available: bool,
|
|
1016
|
+
tsc_available: bool,
|
|
1017
|
+
) -> dict[str, dict[str, Any]]:
|
|
1018
|
+
"""
|
|
1019
|
+
Generate explanations for each score.
|
|
1020
|
+
|
|
1021
|
+
Phase 7.1: Score Explanation Enhancement
|
|
1022
|
+
|
|
1023
|
+
Args:
|
|
1024
|
+
scores: Calculated scores dictionary
|
|
1025
|
+
security_issues: List of security issues found
|
|
1026
|
+
eslint_available: Whether ESLint is available
|
|
1027
|
+
tsc_available: Whether TypeScript compiler is available
|
|
1028
|
+
|
|
1029
|
+
Returns:
|
|
1030
|
+
Dictionary of explanations keyed by score name
|
|
1031
|
+
"""
|
|
1032
|
+
explanations: dict[str, dict[str, Any]] = {}
|
|
1033
|
+
|
|
1034
|
+
# Security score explanation
|
|
1035
|
+
security_score = scores.get("security_score", 5.0)
|
|
1036
|
+
if security_issues:
|
|
1037
|
+
high_issues = [i for i in security_issues if i.get("severity") == "HIGH"]
|
|
1038
|
+
medium_issues = [i for i in security_issues if i.get("severity") == "MEDIUM"]
|
|
1039
|
+
low_issues = [i for i in security_issues if i.get("severity") == "LOW"]
|
|
1040
|
+
|
|
1041
|
+
issue_summary = []
|
|
1042
|
+
if high_issues:
|
|
1043
|
+
issue_summary.append(f"{len(high_issues)} HIGH severity")
|
|
1044
|
+
if medium_issues:
|
|
1045
|
+
issue_summary.append(f"{len(medium_issues)} MEDIUM severity")
|
|
1046
|
+
if low_issues:
|
|
1047
|
+
issue_summary.append(f"{len(low_issues)} LOW severity")
|
|
1048
|
+
|
|
1049
|
+
explanations["security_score"] = ScoreExplanation(
|
|
1050
|
+
score=security_score,
|
|
1051
|
+
reason=f"{len(security_issues)} security issue(s) detected: {', '.join(issue_summary)}",
|
|
1052
|
+
issues=[f"{i.get('pattern')} at line {i.get('line')}: {i.get('message')}" for i in security_issues[:5]],
|
|
1053
|
+
recommendations=[i.get("recommendation", "") for i in security_issues[:3] if i.get("recommendation")],
|
|
1054
|
+
tool_status="pattern_based",
|
|
1055
|
+
tool_name="TypeScriptSecurityScanner",
|
|
1056
|
+
).to_dict()
|
|
1057
|
+
else:
|
|
1058
|
+
explanations["security_score"] = ScoreExplanation(
|
|
1059
|
+
score=security_score,
|
|
1060
|
+
reason="No security issues detected",
|
|
1061
|
+
issues=[],
|
|
1062
|
+
recommendations=["Code appears secure. Continue following security best practices."],
|
|
1063
|
+
tool_status="pattern_based",
|
|
1064
|
+
tool_name="TypeScriptSecurityScanner",
|
|
1065
|
+
).to_dict()
|
|
1066
|
+
|
|
1067
|
+
# Linting score explanation
|
|
1068
|
+
linting_score = scores.get("linting_score", 5.0)
|
|
1069
|
+
if not eslint_available:
|
|
1070
|
+
explanations["linting_score"] = ScoreExplanation(
|
|
1071
|
+
score=linting_score,
|
|
1072
|
+
reason="ESLint not available - using neutral score",
|
|
1073
|
+
issues=["ESLint is not installed or not accessible via npx"],
|
|
1074
|
+
recommendations=["Install ESLint: npm install -g eslint", "Or install locally: npm install --save-dev eslint"],
|
|
1075
|
+
tool_status="unavailable",
|
|
1076
|
+
tool_name="ESLint",
|
|
1077
|
+
).to_dict()
|
|
1078
|
+
elif linting_score < 7.0:
|
|
1079
|
+
explanations["linting_score"] = ScoreExplanation(
|
|
1080
|
+
score=linting_score,
|
|
1081
|
+
reason=f"ESLint found issues (score: {linting_score:.1f}/10)",
|
|
1082
|
+
issues=["Multiple linting violations detected"],
|
|
1083
|
+
recommendations=["Run 'npx eslint --fix' to auto-fix issues", "Review ESLint configuration"],
|
|
1084
|
+
tool_status="available",
|
|
1085
|
+
tool_name="ESLint",
|
|
1086
|
+
).to_dict()
|
|
1087
|
+
|
|
1088
|
+
# Type checking score explanation
|
|
1089
|
+
type_checking_score = scores.get("type_checking_score", 5.0)
|
|
1090
|
+
if not tsc_available:
|
|
1091
|
+
explanations["type_checking_score"] = ScoreExplanation(
|
|
1092
|
+
score=type_checking_score,
|
|
1093
|
+
reason="TypeScript compiler not available - using neutral score",
|
|
1094
|
+
issues=["TypeScript is not installed or not accessible via npx"],
|
|
1095
|
+
recommendations=["Install TypeScript: npm install -g typescript", "Or install locally: npm install --save-dev typescript"],
|
|
1096
|
+
tool_status="unavailable",
|
|
1097
|
+
tool_name="TypeScript Compiler (tsc)",
|
|
1098
|
+
).to_dict()
|
|
1099
|
+
elif type_checking_score < 7.0:
|
|
1100
|
+
explanations["type_checking_score"] = ScoreExplanation(
|
|
1101
|
+
score=type_checking_score,
|
|
1102
|
+
reason=f"TypeScript compiler found type errors (score: {type_checking_score:.1f}/10)",
|
|
1103
|
+
issues=["Type checking errors detected"],
|
|
1104
|
+
recommendations=["Fix type errors reported by tsc", "Enable strict mode in tsconfig.json for better type safety"],
|
|
1105
|
+
tool_status="available",
|
|
1106
|
+
tool_name="TypeScript Compiler (tsc)",
|
|
1107
|
+
).to_dict()
|
|
1108
|
+
|
|
1109
|
+
# Complexity score explanation
|
|
1110
|
+
complexity_score = scores.get("complexity_score", 5.0)
|
|
1111
|
+
if complexity_score > 7.0:
|
|
1112
|
+
explanations["complexity_score"] = ScoreExplanation(
|
|
1113
|
+
score=complexity_score,
|
|
1114
|
+
reason=f"High cyclomatic complexity detected (score: {complexity_score:.1f}/10)",
|
|
1115
|
+
issues=["Code has many decision points (if/else, loops, ternary operators)"],
|
|
1116
|
+
recommendations=[
|
|
1117
|
+
"Extract complex logic into smaller functions",
|
|
1118
|
+
"Consider using early returns to reduce nesting",
|
|
1119
|
+
"Apply the single responsibility principle",
|
|
1120
|
+
],
|
|
1121
|
+
tool_status="available",
|
|
1122
|
+
tool_name="Complexity Analyzer",
|
|
1123
|
+
).to_dict()
|
|
1124
|
+
|
|
1125
|
+
# Maintainability score explanation
|
|
1126
|
+
maintainability_score = scores.get("maintainability_score", 5.0)
|
|
1127
|
+
if maintainability_score < 6.0:
|
|
1128
|
+
explanations["maintainability_score"] = ScoreExplanation(
|
|
1129
|
+
score=maintainability_score,
|
|
1130
|
+
reason=f"Low maintainability score (score: {maintainability_score:.1f}/10)",
|
|
1131
|
+
issues=["Code may be difficult to maintain"],
|
|
1132
|
+
recommendations=[
|
|
1133
|
+
"Add JSDoc comments to functions and classes",
|
|
1134
|
+
"Use TypeScript interfaces for type definitions",
|
|
1135
|
+
"Break down large functions into smaller ones",
|
|
1136
|
+
"Follow consistent naming conventions",
|
|
1137
|
+
],
|
|
1138
|
+
tool_status="available",
|
|
1139
|
+
tool_name="Maintainability Analyzer",
|
|
1140
|
+
).to_dict()
|
|
1141
|
+
|
|
1142
|
+
return explanations
|