patchi 0.7.5__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.
- patchi/__init__.py +1 -0
- patchi/__main__.py +13 -0
- patchi/cli/__init__.py +0 -0
- patchi/cli/command_families.py +235 -0
- patchi/cli/commands/__init__.py +0 -0
- patchi/cli/commands/access_cmd.py +65 -0
- patchi/cli/commands/agent_stats_cmd.py +273 -0
- patchi/cli/commands/agents_cmd.py +187 -0
- patchi/cli/commands/ai_cmd.py +308 -0
- patchi/cli/commands/assure_cmd.py +538 -0
- patchi/cli/commands/audit_cmd.py +286 -0
- patchi/cli/commands/auto_cmd.py +110 -0
- patchi/cli/commands/blame_cmd.py +53 -0
- patchi/cli/commands/chains_cmd.py +327 -0
- patchi/cli/commands/charter_cmd.py +173 -0
- patchi/cli/commands/chat_cmd.py +597 -0
- patchi/cli/commands/check_cmd.py +232 -0
- patchi/cli/commands/cleanup_cmd.py +328 -0
- patchi/cli/commands/cockpit_cmd.py +208 -0
- patchi/cli/commands/commands_cmd.py +160 -0
- patchi/cli/commands/cross_repo_cmd.py +113 -0
- patchi/cli/commands/deps_cmd.py +110 -0
- patchi/cli/commands/dev_check_cmd.py +342 -0
- patchi/cli/commands/dev_cmd.py +445 -0
- patchi/cli/commands/doctor_cmd.py +457 -0
- patchi/cli/commands/findings_cmd.py +187 -0
- patchi/cli/commands/fix_cmd.py +648 -0
- patchi/cli/commands/fix_review_cmd.py +273 -0
- patchi/cli/commands/heatmap_cmd.py +62 -0
- patchi/cli/commands/help_cmd.py +159 -0
- patchi/cli/commands/hosted_cmd.py +980 -0
- patchi/cli/commands/init.py +456 -0
- patchi/cli/commands/key_cmd.py +571 -0
- patchi/cli/commands/learn_cmd.py +140 -0
- patchi/cli/commands/link_cmd.py +104 -0
- patchi/cli/commands/log_cmd.py +57 -0
- patchi/cli/commands/memory_cmd.py +215 -0
- patchi/cli/commands/model_cmd.py +207 -0
- patchi/cli/commands/notify_cmd.py +213 -0
- patchi/cli/commands/notify_prompts.py +29 -0
- patchi/cli/commands/patch_cmd.py +231 -0
- patchi/cli/commands/plan_cmd.py +128 -0
- patchi/cli/commands/plugins_cmd.py +307 -0
- patchi/cli/commands/queue_cmd.py +152 -0
- patchi/cli/commands/quick_cmd.py +159 -0
- patchi/cli/commands/ready_cmd.py +292 -0
- patchi/cli/commands/report_cmd.py +416 -0
- patchi/cli/commands/restrict_cmd.py +127 -0
- patchi/cli/commands/review_cmd.py +225 -0
- patchi/cli/commands/rules_cmd.py +233 -0
- patchi/cli/commands/scan_cmd.py +1908 -0
- patchi/cli/commands/settings_cmd.py +45 -0
- patchi/cli/commands/status_cmd.py +679 -0
- patchi/cli/commands/test_cmd.py +1052 -0
- patchi/cli/commands/trend_cmd.py +296 -0
- patchi/cli/commands/undo_cmd.py +235 -0
- patchi/cli/commands/update_cmd.py +345 -0
- patchi/cli/commands/verify_cmd.py +56 -0
- patchi/cli/commands/vr_cmd.py +440 -0
- patchi/cli/commands/watch_cmd.py +304 -0
- patchi/cli/commands/web_cmd.py +114 -0
- patchi/cli/console.py +101 -0
- patchi/cli/display/__init__.py +1 -0
- patchi/cli/display/live_audit.py +269 -0
- patchi/cli/display/live_progress.py +169 -0
- patchi/cli/framework.py +241 -0
- patchi/cli/logo.py +128 -0
- patchi/cli/main.py +222 -0
- patchi/cli/registry.py +1171 -0
- patchi/cli/style.py +68 -0
- patchi/cli/themes.py +187 -0
- patchi/cli/ux.py +444 -0
- patchi/core/__init__.py +0 -0
- patchi/core/agents/__init__.py +0 -0
- patchi/core/agents/api_fuzzer_agent.py +141 -0
- patchi/core/agents/attack_agent.py +242 -0
- patchi/core/agents/base.py +1012 -0
- patchi/core/agents/bug_predictor_agent.py +58 -0
- patchi/core/agents/build_tool_validator.py +150 -0
- patchi/core/agents/cache.py +192 -0
- patchi/core/agents/chaos_agent.py +133 -0
- patchi/core/agents/cicd_generator.py +240 -0
- patchi/core/agents/comment_scanner.py +280 -0
- patchi/core/agents/console_logging_agent.py +156 -0
- patchi/core/agents/coordinator.py +858 -0
- patchi/core/agents/core_scanner.py +269 -0
- patchi/core/agents/coverage_prioritizer.py +181 -0
- patchi/core/agents/dead_code_hygiene.py +296 -0
- patchi/core/agents/dead_code_scanner.py +692 -0
- patchi/core/agents/dependency_scanner.py +725 -0
- patchi/core/agents/doc_claim_agent.py +573 -0
- patchi/core/agents/duplicate_scanner.py +568 -0
- patchi/core/agents/env_scanner.py +410 -0
- patchi/core/agents/feature_flag_agent.py +89 -0
- patchi/core/agents/frontend_framework_agent.py +133 -0
- patchi/core/agents/goroutine_agent.py +82 -0
- patchi/core/agents/governor.py +1972 -0
- patchi/core/agents/i18n_agent.py +95 -0
- patchi/core/agents/license_compliance.py +247 -0
- patchi/core/agents/linking_agent.py +251 -0
- patchi/core/agents/memory_profiler_agent.py +102 -0
- patchi/core/agents/modernization_agent.py +84 -0
- patchi/core/agents/mutation_agent.py +174 -0
- patchi/core/agents/nl_query_agent.py +74 -0
- patchi/core/agents/promise_rejection_agent.py +100 -0
- patchi/core/agents/refactoring_agent.py +286 -0
- patchi/core/agents/resource_leak_agent.py +91 -0
- patchi/core/agents/route_graph_scanner.py +951 -0
- patchi/core/agents/run_agent.py +106 -0
- patchi/core/agents/rust_unwrap_agent.py +100 -0
- patchi/core/agents/sbom_generator.py +284 -0
- patchi/core/agents/scanners.py +67 -0
- patchi/core/agents/side/__init__.py +1 -0
- patchi/core/agents/side/build_agent.py +65 -0
- patchi/core/agents/side/format_agent.py +50 -0
- patchi/core/agents/side/install_agent.py +63 -0
- patchi/core/agents/side_file_scanner.py +549 -0
- patchi/core/agents/snapshot_drift_detector.py +146 -0
- patchi/core/agents/spa_route_inventory.py +203 -0
- patchi/core/agents/test_scanner.py +442 -0
- patchi/core/agents/tool_health.py +109 -0
- patchi/core/agents/tool_runner.py +162 -0
- patchi/core/agents/type_scanner.py +168 -0
- patchi/core/agents/ui_scanner.py +558 -0
- patchi/core/ai/__init__.py +20 -0
- patchi/core/ai/agent_audit.py +153 -0
- patchi/core/ai/agent_profiler.py +277 -0
- patchi/core/ai/client.py +499 -0
- patchi/core/ai/cost_tracker.py +107 -0
- patchi/core/ai/model_router.py +318 -0
- patchi/core/ai/orchestrator.py +625 -0
- patchi/core/ai/prompts.py +681 -0
- patchi/core/ai/smart.py +302 -0
- patchi/core/ai/tool_executor.py +24 -0
- patchi/core/ai/tools/__init__.py +10 -0
- patchi/core/ai/tools/executor.py +621 -0
- patchi/core/ai/tools/realize.py +1160 -0
- patchi/core/ai/tools/registry.py +1577 -0
- patchi/core/assurance/__init__.py +33 -0
- patchi/core/assurance/builder.py +211 -0
- patchi/core/assurance/graph.py +342 -0
- patchi/core/assurance/invariants.py +168 -0
- patchi/core/atomic.py +61 -0
- patchi/core/attackers/__init__.py +17 -0
- patchi/core/attackers/api_abuse.py +89 -0
- patchi/core/attackers/auth_bypass.py +73 -0
- patchi/core/attackers/base.py +91 -0
- patchi/core/attackers/business_logic.py +88 -0
- patchi/core/attackers/planner.py +108 -0
- patchi/core/attackers/priv_escalation.py +88 -0
- patchi/core/attackers/recon.py +87 -0
- patchi/core/brain/__init__.py +0 -0
- patchi/core/brain/ast_utils/__init__.py +81 -0
- patchi/core/brain/ast_utils/assignments.py +219 -0
- patchi/core/brain/ast_utils/calls.py +139 -0
- patchi/core/brain/ast_utils/config.py +132 -0
- patchi/core/brain/ast_utils/control_flow.py +74 -0
- patchi/core/brain/ast_utils/dead_symbols.py +205 -0
- patchi/core/brain/ast_utils/decorators.py +108 -0
- patchi/core/brain/ast_utils/helpers.py +144 -0
- patchi/core/brain/ast_utils/imports.py +169 -0
- patchi/core/brain/ast_utils/scan.py +143 -0
- patchi/core/brain/ast_utils/taint.py +310 -0
- patchi/core/brain/audit.py +379 -0
- patchi/core/brain/baseline.py +195 -0
- patchi/core/brain/blast_radius.py +312 -0
- patchi/core/brain/body_tags.py +144 -0
- patchi/core/brain/brain.py +1161 -0
- patchi/core/brain/brain_context.py +393 -0
- patchi/core/brain/brain_watcher.py +340 -0
- patchi/core/brain/charter.py +473 -0
- patchi/core/brain/classifier.py +214 -0
- patchi/core/brain/cockpit.py +350 -0
- patchi/core/brain/code_query.py +710 -0
- patchi/core/brain/contract.py +889 -0
- patchi/core/brain/contract_diff.py +155 -0
- patchi/core/brain/council.py +800 -0
- patchi/core/brain/cross_repo.py +104 -0
- patchi/core/brain/dead_code_tools.py +157 -0
- patchi/core/brain/doc_validator.py +197 -0
- patchi/core/brain/domain_activator.py +2183 -0
- patchi/core/brain/enriched_context.py +220 -0
- patchi/core/brain/file_corpus.py +220 -0
- patchi/core/brain/framework.py +953 -0
- patchi/core/brain/freshness.py +273 -0
- patchi/core/brain/git_aware.py +206 -0
- patchi/core/brain/heatmap.py +104 -0
- patchi/core/brain/ignore_parser.py +153 -0
- patchi/core/brain/import_graph.py +486 -0
- patchi/core/brain/languages.py +397 -0
- patchi/core/brain/layered_brain.py +419 -0
- patchi/core/brain/learning.py +229 -0
- patchi/core/brain/orphaned_endpoints.py +313 -0
- patchi/core/brain/personas/__init__.py +648 -0
- patchi/core/brain/personas/bad_user.py +165 -0
- patchi/core/brain/personas/base.py +349 -0
- patchi/core/brain/proactive.py +807 -0
- patchi/core/brain/project_context.py +121 -0
- patchi/core/brain/project_reader.py +521 -0
- patchi/core/brain/reasoning.py +269 -0
- patchi/core/brain/route_detector/__init__.py +15 -0
- patchi/core/brain/route_detector/base.py +31 -0
- patchi/core/brain/route_detector/csharp.py +209 -0
- patchi/core/brain/route_detector/file_based.py +81 -0
- patchi/core/brain/route_detector/go.py +152 -0
- patchi/core/brain/route_detector/java.py +195 -0
- patchi/core/brain/route_detector/javascript.py +197 -0
- patchi/core/brain/route_detector/php.py +158 -0
- patchi/core/brain/route_detector/python.py +193 -0
- patchi/core/brain/route_detector/registry.py +25 -0
- patchi/core/brain/route_detector/ruby.py +134 -0
- patchi/core/brain/route_detector/rust.py +216 -0
- patchi/core/brain/route_detector/swift.py +118 -0
- patchi/core/brain/route_mapper.py +265 -0
- patchi/core/brain/scanner.py +2136 -0
- patchi/core/brain/secrets.py +280 -0
- patchi/core/brain/symbol_graph.py +1955 -0
- patchi/core/brain/team.py +28 -0
- patchi/core/brain/trace_log.py +132 -0
- patchi/core/brain/type_checker/__init__.py +65 -0
- patchi/core/brain/type_checker/base.py +60 -0
- patchi/core/brain/type_checker/csharp.py +59 -0
- patchi/core/brain/type_checker/dart.py +59 -0
- patchi/core/brain/type_checker/go.py +81 -0
- patchi/core/brain/type_checker/java.py +75 -0
- patchi/core/brain/type_checker/kotlin.py +75 -0
- patchi/core/brain/type_checker/php.py +88 -0
- patchi/core/brain/type_checker/python.py +87 -0
- patchi/core/brain/type_checker/rust.py +81 -0
- patchi/core/brain/type_checker/swift.py +59 -0
- patchi/core/brain/type_checker/typescript.py +298 -0
- patchi/core/brain/understander.py +130 -0
- patchi/core/brain/verify.py +379 -0
- patchi/core/campaigns/__init__.py +14 -0
- patchi/core/campaigns/base.py +62 -0
- patchi/core/campaigns/data_flow_abuse.py +153 -0
- patchi/core/campaigns/orchestrator.py +140 -0
- patchi/core/campaigns/state_transitions.py +178 -0
- patchi/core/ci_bundle.py +104 -0
- patchi/core/config.py +311 -0
- patchi/core/constants.py +245 -0
- patchi/core/debug/__init__.py +25 -0
- patchi/core/debug/adapters/__init__.py +11 -0
- patchi/core/debug/adapters/codelldb.py +379 -0
- patchi/core/debug/adapters/node.py +222 -0
- patchi/core/debug/adapters/powershell.py +210 -0
- patchi/core/debug/adapters/python.py +244 -0
- patchi/core/debug/capture.py +210 -0
- patchi/core/debug/dap_client.py +268 -0
- patchi/core/detector/__init__.py +0 -0
- patchi/core/detector/audit.py +194 -0
- patchi/core/detector/bus.py +226 -0
- patchi/core/detector/dispatcher.py +275 -0
- patchi/core/detector/event.py +254 -0
- patchi/core/detector/sigma_engine.py +378 -0
- patchi/core/detector/triage.py +289 -0
- patchi/core/export/sarif.py +113 -0
- patchi/core/fix/__init__.py +17 -0
- patchi/core/fix/applier.py +574 -0
- patchi/core/fix/base.py +510 -0
- patchi/core/fix/code_fixer.py +169 -0
- patchi/core/fix/dead_code_remover.py +131 -0
- patchi/core/fix/fix_agents.py +775 -0
- patchi/core/fix/patch.py +444 -0
- patchi/core/fix/risk_gate.py +383 -0
- patchi/core/fix/security_fixer.py +93 -0
- patchi/core/fix/verify_loop.py +204 -0
- patchi/core/fuzz/__init__.py +8 -0
- patchi/core/fuzz/corpus.py +137 -0
- patchi/core/fuzz/input_fuzzer.py +256 -0
- patchi/core/fuzz/sequence_fuzzer.py +162 -0
- patchi/core/fuzz/state_fuzzer.py +236 -0
- patchi/core/health.py +407 -0
- patchi/core/hosted/__init__.py +1 -0
- patchi/core/hosted/anomaly.py +298 -0
- patchi/core/hosted/audit_log.py +102 -0
- patchi/core/hosted/compliance_report.py +148 -0
- patchi/core/hosted/ip_reputation.py +169 -0
- patchi/core/hosted/log_parsers.py +251 -0
- patchi/core/hosted/tokens.py +127 -0
- patchi/core/hosted/watchlist.py +164 -0
- patchi/core/hosted/webhooks.py +156 -0
- patchi/core/memory.py +437 -0
- patchi/core/notifications/__init__.py +18 -0
- patchi/core/notifications/channels.py +192 -0
- patchi/core/notifications/digest.py +141 -0
- patchi/core/notifications/escalation.py +158 -0
- patchi/core/notifications/notifier.py +182 -0
- patchi/core/notifications/quiet_hours.py +69 -0
- patchi/core/plugins/__init__.py +48 -0
- patchi/core/plugins/analyzer.py +326 -0
- patchi/core/plugins/analyzers/__init__.py +6 -0
- patchi/core/plugins/analyzers/dead_code_analyzer.py +120 -0
- patchi/core/plugins/analyzers/secret_scanner.py +158 -0
- patchi/core/plugins/analyzers/todo_scanner.py +115 -0
- patchi/core/plugins/registry.py +335 -0
- patchi/core/queue.py +375 -0
- patchi/core/reliability/__init__.py +13 -0
- patchi/core/reliability/chaos.py +163 -0
- patchi/core/reliability/fault_injector.py +172 -0
- patchi/core/reliability/idempotency.py +128 -0
- patchi/core/reliability/recovery.py +147 -0
- patchi/core/runtime/__init__.py +5 -0
- patchi/core/runtime/tracer.py +230 -0
- patchi/core/scan_bus.py +156 -0
- patchi/core/security/__init__.py +1 -0
- patchi/core/security/ai_validator.py +462 -0
- patchi/core/security/app_mapper.py +216 -0
- patchi/core/security/app_profile.py +464 -0
- patchi/core/security/attack_feedback.py +141 -0
- patchi/core/security/attack_scenarios/__init__.py +1 -0
- patchi/core/security/attack_tree.py +176 -0
- patchi/core/security/auth_audit_agent.py +344 -0
- patchi/core/security/authz_agent.py +602 -0
- patchi/core/security/auto_fix_proactive.py +620 -0
- patchi/core/security/auto_fixer.py +660 -0
- patchi/core/security/auto_ticket.py +53 -0
- patchi/core/security/bandit_agent.py +152 -0
- patchi/core/security/blast_radius.py +226 -0
- patchi/core/security/browser_tester.py +333 -0
- patchi/core/security/business_logic_agent.py +263 -0
- patchi/core/security/catch_block_auditor.py +395 -0
- patchi/core/security/cdn_cache_agent.py +357 -0
- patchi/core/security/chain_analyzer.py +321 -0
- patchi/core/security/chain_to_assurance.py +168 -0
- patchi/core/security/charter.py +510 -0
- patchi/core/security/cloud_waf_detector.py +295 -0
- patchi/core/security/codeql_agent.py +233 -0
- patchi/core/security/compliance_agent.py +348 -0
- patchi/core/security/confidence_gate.py +423 -0
- patchi/core/security/container_scanner.py +282 -0
- patchi/core/security/crypto_agent.py +378 -0
- patchi/core/security/cve_monitor.py +262 -0
- patchi/core/security/dast_agent.py +865 -0
- patchi/core/security/defectdojo.py +238 -0
- patchi/core/security/defenders/__init__.py +75 -0
- patchi/core/security/defenders/auth_middleware.py +115 -0
- patchi/core/security/defenders/base.py +97 -0
- patchi/core/security/defenders/block_ip.py +71 -0
- patchi/core/security/defenders/block_ws_origin.py +36 -0
- patchi/core/security/defenders/crypto_fix.py +118 -0
- patchi/core/security/defenders/enforce_rate_limit.py +108 -0
- patchi/core/security/defenders/fix_code.py +93 -0
- patchi/core/security/defenders/invalidate_session.py +40 -0
- patchi/core/security/defenders/patch_config.py +70 -0
- patchi/core/security/defenders/rotate_secret.py +54 -0
- patchi/core/security/defenders/suspend_account.py +41 -0
- patchi/core/security/defenders/update_dependency.py +160 -0
- patchi/core/security/defense_layer.py +217 -0
- patchi/core/security/dependency_vulnerability_agent.py +616 -0
- patchi/core/security/detection_pipeline.py +289 -0
- patchi/core/security/dns_security_agent.py +497 -0
- patchi/core/security/domain_activator_v2.py +913 -0
- patchi/core/security/domain_loader.py +1004 -0
- patchi/core/security/domains/__init__.py +0 -0
- patchi/core/security/email_authentication_agent.py +417 -0
- patchi/core/security/env_var_validator.py +304 -0
- patchi/core/security/evidence.py +100 -0
- patchi/core/security/falco_runtime_agent.py +387 -0
- patchi/core/security/fix-playbooks/__init__.py +0 -0
- patchi/core/security/gated_finding.py +85 -0
- patchi/core/security/git_diff_activator.py +367 -0
- patchi/core/security/governance.py +208 -0
- patchi/core/security/gradual_gate.py +34 -0
- patchi/core/security/history.py +315 -0
- patchi/core/security/iac_scanner.py +250 -0
- patchi/core/security/ignore_expiry.py +111 -0
- patchi/core/security/ignore_learner.py +416 -0
- patchi/core/security/injection_agent.py +319 -0
- patchi/core/security/insecure_randomness_agent.py +288 -0
- patchi/core/security/intent_analyzer.py +326 -0
- patchi/core/security/jwt_agent.py +453 -0
- patchi/core/security/kubernetes_agent.py +638 -0
- patchi/core/security/layer2_orchestrator.py +341 -0
- patchi/core/security/llm_security_agent.py +252 -0
- patchi/core/security/misconfig_agent.py +600 -0
- patchi/core/security/mobile_security_agent.py +474 -0
- patchi/core/security/network_agent.py +376 -0
- patchi/core/security/noise_filter.py +274 -0
- patchi/core/security/orchestrator.py +313 -0
- patchi/core/security/pattern_context.py +125 -0
- patchi/core/security/pattern_loader.py +101 -0
- patchi/core/security/pentest/__init__.py +4 -0
- patchi/core/security/pentest/base.py +58 -0
- patchi/core/security/pentest/dalfox_adapter.py +40 -0
- patchi/core/security/pentest/ffuf_adapter.py +48 -0
- patchi/core/security/pentest/nuclei_adapter.py +50 -0
- patchi/core/security/pentest/registry.py +80 -0
- patchi/core/security/pentest/shannon_adapter.py +107 -0
- patchi/core/security/pentest/sqlmap_adapter.py +56 -0
- patchi/core/security/pentest/zap_adapter.py +52 -0
- patchi/core/security/plan_auditor.py +276 -0
- patchi/core/security/policy_engine.py +272 -0
- patchi/core/security/prechecks.py +172 -0
- patchi/core/security/precommit_hook.py +247 -0
- patchi/core/security/privacy_agent.py +361 -0
- patchi/core/security/push_notification_agent.py +417 -0
- patchi/core/security/pysa_agent.py +143 -0
- patchi/core/security/reasoning.py +442 -0
- patchi/core/security/red_team_agent.py +184 -0
- patchi/core/security/red_team_engine.py +1142 -0
- patchi/core/security/remediation.py +360 -0
- patchi/core/security/request_interceptor.py +294 -0
- patchi/core/security/runtime_validator.py +245 -0
- patchi/core/security/saml_sso_agent.py +401 -0
- patchi/core/security/sast_agent.py +213 -0
- patchi/core/security/sast_gate.py +95 -0
- patchi/core/security/scheduler.py +287 -0
- patchi/core/security/secrets_runtime_agent.py +476 -0
- patchi/core/security/security_agents.py +175 -0
- patchi/core/security/security_config.py +551 -0
- patchi/core/security/security_probe.py +762 -0
- patchi/core/security/security_taint.py +1005 -0
- patchi/core/security/sensitive_data_agent.py +271 -0
- patchi/core/security/service_mesh_agent.py +412 -0
- patchi/core/security/session_management_agent.py +245 -0
- patchi/core/security/ssrf_agent.py +422 -0
- patchi/core/security/supply_chain.py +449 -0
- patchi/core/security/threat_model_generator.py +378 -0
- patchi/core/security/threat_model_updater.py +196 -0
- patchi/core/security/tool_adapters.py +135 -0
- patchi/core/security/tool_verify.py +173 -0
- patchi/core/security/websocket_security_agent.py +188 -0
- patchi/core/snapshot.py +242 -0
- patchi/core/tenant.py +386 -0
- patchi/core/testing/__init__.py +17 -0
- patchi/core/testing/_browser.py +147 -0
- patchi/core/testing/ads_agent.py +141 -0
- patchi/core/testing/api_contract_agent.py +450 -0
- patchi/core/testing/app_discovery_agent.py +641 -0
- patchi/core/testing/app_launcher.py +184 -0
- patchi/core/testing/browser_test_agent.py +516 -0
- patchi/core/testing/e2e_flow_agent.py +354 -0
- patchi/core/testing/flake_detector_agent.py +292 -0
- patchi/core/testing/gate.py +31 -0
- patchi/core/testing/live_test_runner.py +304 -0
- patchi/core/testing/live_v2/__init__.py +1 -0
- patchi/core/testing/live_v2/browser_pool.py +349 -0
- patchi/core/testing/live_v2/browser_test_runner.py +313 -0
- patchi/core/testing/live_v2/runner.py +626 -0
- patchi/core/testing/live_v2/screenshot_manager.py +474 -0
- patchi/core/testing/live_v2/stress_orchestrator.py +523 -0
- patchi/core/testing/live_v2/video_recorder.py +303 -0
- patchi/core/testing/regression_agent.py +302 -0
- patchi/core/testing/security_test_agent.py +253 -0
- patchi/core/testing/stress_test_agent.py +203 -0
- patchi/core/testing/test_agents.py +50 -0
- patchi/core/testing/test_config.py +194 -0
- patchi/core/testing/ui_accessibility_agent.py +324 -0
- patchi/core/testing/ui_button_agent.py +294 -0
- patchi/core/testing/ui_layout_agent.py +330 -0
- patchi/core/testing/unit_test_agent.py +1149 -0
- patchi/core/testing/visual_regression_agent.py +260 -0
- patchi/web/__init__.py +1 -0
- patchi/web/api/__init__.py +1 -0
- patchi/web/api/brain_map.py +114 -0
- patchi/web/api/charts.py +47 -0
- patchi/web/api/chat.py +224 -0
- patchi/web/api/cicd.py +576 -0
- patchi/web/api/dast_evidence.py +49 -0
- patchi/web/api/dev_check.py +320 -0
- patchi/web/api/fix.py +134 -0
- patchi/web/api/guard.py +65 -0
- patchi/web/api/hosted.py +128 -0
- patchi/web/api/hosted_v2.py +234 -0
- patchi/web/api/live_testing.py +699 -0
- patchi/web/api/scan.py +582 -0
- patchi/web/api/smart.py +111 -0
- patchi/web/api/tenant.py +117 -0
- patchi/web/api/tools.py +85 -0
- patchi/web/api_legacy.py +2401 -0
- patchi/web/app.py +310 -0
- patchi/web/events.py +483 -0
- patchi/web/routes/__init__.py +1 -0
- patchi/web/routes/assurance.py +541 -0
- patchi/web/routes/brain.py +36 -0
- patchi/web/routes/charter.py +227 -0
- patchi/web/routes/chat.py +23 -0
- patchi/web/routes/dashboard.py +558 -0
- patchi/web/routes/dashboard_v2.py +402 -0
- patchi/web/routes/findings.py +330 -0
- patchi/web/routes/guard.py +35 -0
- patchi/web/routes/history.py +126 -0
- patchi/web/routes/landing.py +52 -0
- patchi/web/routes/live_testing.py +24 -0
- patchi/web/routes/review.py +57 -0
- patchi/web/routes/self_improvement.py +110 -0
- patchi/web/routes/settings.py +49 -0
- patchi/web/routes/tokens.py +29 -0
- patchi/web/spawn.py +139 -0
- patchi/web/ws.py +141 -0
- patchi-0.7.5.dist-info/METADATA +315 -0
- patchi-0.7.5.dist-info/RECORD +496 -0
- patchi-0.7.5.dist-info/WHEEL +5 -0
- patchi-0.7.5.dist-info/licenses/LICENSE +60 -0
- patchi-0.7.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Team Collaboration §10.3.1-4 — CODEOWNERS routing, knowledge base, gamification.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
def route_to_team(root: Path, finding: dict) -> str:
|
|
10
|
+
co=root/".github/CODEOWNERS"
|
|
11
|
+
if co.exists():
|
|
12
|
+
for line in co.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
13
|
+
if line.strip().startswith("#") or not line.strip(): continue
|
|
14
|
+
parts=line.split()
|
|
15
|
+
if len(parts)>=2 and finding.get("file","").startswith(parts[0].lstrip("/")):
|
|
16
|
+
return parts[-1]
|
|
17
|
+
return "unassigned"
|
|
18
|
+
|
|
19
|
+
def leaderboard(root: Path) -> list[dict]:
|
|
20
|
+
try:
|
|
21
|
+
# count fixes per author via git log
|
|
22
|
+
import subprocess
|
|
23
|
+
out=subprocess.run(["git","log","--pretty=format:%an","--since=1.month.ago"], capture_output=True, text=True, timeout=5, cwd=str(root))
|
|
24
|
+
from collections import Counter
|
|
25
|
+
cnt=Counter(out.stdout.splitlines()) if out.returncode==0 else Counter()
|
|
26
|
+
return [{"author": a, "fixes": c} for a,c in cnt.most_common(5)]
|
|
27
|
+
except Exception:
|
|
28
|
+
return []
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Structured logging for agent execution traces.
|
|
3
|
+
|
|
4
|
+
Provides a lightweight trace logger that records:
|
|
5
|
+
- Agent name, start/end time, duration
|
|
6
|
+
- Input file count, finding count
|
|
7
|
+
- Success/failure status
|
|
8
|
+
- Error messages
|
|
9
|
+
|
|
10
|
+
Uses loguru if available, falls back to stdlib logging.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from patchi.core.brain.trace_log import trace_agent
|
|
14
|
+
with trace_agent("InjectionAgent", root=path) as trace:
|
|
15
|
+
result = AgentResult()
|
|
16
|
+
agent._run(inp, result)
|
|
17
|
+
trace.findings = len(result.findings)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import time
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from loguru import logger as _logger
|
|
30
|
+
|
|
31
|
+
_HAS_LOGURU = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
import logging
|
|
34
|
+
|
|
35
|
+
_logger = logging.getLogger("patchi.trace")
|
|
36
|
+
_HAS_LOGURU = False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class AgentTrace:
|
|
41
|
+
"""Record of a single agent execution."""
|
|
42
|
+
|
|
43
|
+
agent_name: str
|
|
44
|
+
root: Path
|
|
45
|
+
start_time: float = 0.0
|
|
46
|
+
end_time: float = 0.0
|
|
47
|
+
duration_ms: int = 0
|
|
48
|
+
files_scanned: int = 0
|
|
49
|
+
findings: int = 0
|
|
50
|
+
status: str = "ok"
|
|
51
|
+
error: str = ""
|
|
52
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
53
|
+
|
|
54
|
+
def to_dict(self) -> dict:
|
|
55
|
+
return {
|
|
56
|
+
"agent": self.agent_name,
|
|
57
|
+
"root": str(self.root),
|
|
58
|
+
"duration_ms": self.duration_ms,
|
|
59
|
+
"files_scanned": self.files_scanned,
|
|
60
|
+
"findings": self.findings,
|
|
61
|
+
"status": self.status,
|
|
62
|
+
"error": self.error,
|
|
63
|
+
**self.metadata,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
_TRACE_LOG: list[AgentTrace] = []
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_trace_log() -> list[AgentTrace]:
|
|
71
|
+
"""Return all recorded traces."""
|
|
72
|
+
return _TRACE_LOG
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def clear_trace_log() -> None:
|
|
76
|
+
"""Clear the trace log."""
|
|
77
|
+
_TRACE_LOG.clear()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@contextmanager
|
|
81
|
+
def trace_agent(agent_name: str, root: Path):
|
|
82
|
+
"""Context manager that traces an agent execution."""
|
|
83
|
+
trace = AgentTrace(agent_name=agent_name, root=root)
|
|
84
|
+
trace.start_time = time.perf_counter()
|
|
85
|
+
_TRACE_LOG.append(trace)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
yield trace
|
|
89
|
+
except Exception as e:
|
|
90
|
+
trace.status = "error"
|
|
91
|
+
trace.error = str(e)
|
|
92
|
+
raise
|
|
93
|
+
finally:
|
|
94
|
+
trace.end_time = time.perf_counter()
|
|
95
|
+
trace.duration_ms = int((trace.end_time - trace.start_time) * 1000)
|
|
96
|
+
|
|
97
|
+
if _HAS_LOGURU:
|
|
98
|
+
_logger.debug(
|
|
99
|
+
"agent_trace | {agent} | {duration}ms | {findings} findings | {status}",
|
|
100
|
+
agent=trace.agent_name,
|
|
101
|
+
duration=trace.duration_ms,
|
|
102
|
+
findings=trace.findings,
|
|
103
|
+
status=trace.status,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
_logger.debug(
|
|
107
|
+
"agent_trace | %s | %dms | %d findings | %s",
|
|
108
|
+
trace.agent_name,
|
|
109
|
+
trace.duration_ms,
|
|
110
|
+
trace.findings,
|
|
111
|
+
trace.status,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def format_trace_summary() -> str:
|
|
116
|
+
"""Format a summary of all traces as a table string."""
|
|
117
|
+
if not _TRACE_LOG:
|
|
118
|
+
return "No agent traces recorded."
|
|
119
|
+
|
|
120
|
+
lines = ["Agent Trace Summary", "=" * 60]
|
|
121
|
+
total_ms = 0
|
|
122
|
+
total_findings = 0
|
|
123
|
+
for t in _TRACE_LOG:
|
|
124
|
+
total_ms += t.duration_ms
|
|
125
|
+
total_findings += t.findings
|
|
126
|
+
status_icon = "ok" if t.status == "ok" else "FAIL"
|
|
127
|
+
lines.append(
|
|
128
|
+
f" {t.agent_name:25s} {t.duration_ms:6d}ms {t.findings:3d} findings [{status_icon}]"
|
|
129
|
+
)
|
|
130
|
+
lines.append("-" * 60)
|
|
131
|
+
lines.append(f" {'TOTAL':25s} {total_ms:6d}ms {total_findings:3d} findings")
|
|
132
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type checker registry — dispatches to per-language type checkers.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from patchi.core.brain.type_checker import check_types
|
|
6
|
+
findings = check_types(source, lang, file_path)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ..languages import Lang
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_types(source: str, lang: Lang, file_path: str) -> list[dict]:
|
|
17
|
+
"""Run the type checker for the given language. Returns list of findings."""
|
|
18
|
+
checker = _get_checker(lang)
|
|
19
|
+
if checker is None:
|
|
20
|
+
return []
|
|
21
|
+
return checker.check(source, file_path)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_checker(lang: Lang) -> Any | None:
|
|
25
|
+
if lang == Lang.TYPESCRIPT:
|
|
26
|
+
from .typescript import TypeScriptChecker
|
|
27
|
+
|
|
28
|
+
return TypeScriptChecker()
|
|
29
|
+
if lang == Lang.PYTHON:
|
|
30
|
+
from .python import PythonTypeChecker
|
|
31
|
+
|
|
32
|
+
return PythonTypeChecker()
|
|
33
|
+
if lang == Lang.JAVA:
|
|
34
|
+
from .java import JavaTypeChecker
|
|
35
|
+
|
|
36
|
+
return JavaTypeChecker()
|
|
37
|
+
if lang == Lang.GO:
|
|
38
|
+
from .go import GoTypeChecker
|
|
39
|
+
|
|
40
|
+
return GoTypeChecker()
|
|
41
|
+
if lang == Lang.RUST:
|
|
42
|
+
from .rust import RustTypeChecker
|
|
43
|
+
|
|
44
|
+
return RustTypeChecker()
|
|
45
|
+
if lang == Lang.C_SHARP:
|
|
46
|
+
from .csharp import CSharpTypeChecker
|
|
47
|
+
|
|
48
|
+
return CSharpTypeChecker()
|
|
49
|
+
if lang == Lang.KOTLIN:
|
|
50
|
+
from .kotlin import KotlinTypeChecker
|
|
51
|
+
|
|
52
|
+
return KotlinTypeChecker()
|
|
53
|
+
if lang == Lang.SWIFT:
|
|
54
|
+
from .swift import SwiftTypeChecker
|
|
55
|
+
|
|
56
|
+
return SwiftTypeChecker()
|
|
57
|
+
if lang == Lang.PHP:
|
|
58
|
+
from .php import PHPTypeChecker
|
|
59
|
+
|
|
60
|
+
return PHPTypeChecker()
|
|
61
|
+
if lang == Lang.DART:
|
|
62
|
+
from .dart import DartTypeChecker
|
|
63
|
+
|
|
64
|
+
return DartTypeChecker()
|
|
65
|
+
return None
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Base class for per-language type checkers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
_log = logging.getLogger("patchi.brain.base")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseTypeChecker(ABC):
|
|
13
|
+
"""Abstract base for language-specific type checkers."""
|
|
14
|
+
|
|
15
|
+
language: str = ""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def check(self, source: str, file_path: str) -> list[dict]:
|
|
19
|
+
"""Return list of type issue findings.
|
|
20
|
+
Each finding: {
|
|
21
|
+
"type": str, # e.g. "explicit_any", "missing_return_type"
|
|
22
|
+
"file": str,
|
|
23
|
+
"line": int,
|
|
24
|
+
"title": str,
|
|
25
|
+
"description": str,
|
|
26
|
+
"evidence": str,
|
|
27
|
+
"severity": str, # "low", "medium", "high"
|
|
28
|
+
}
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _node_text(source: str, node: Any) -> str:
|
|
34
|
+
"""Extract text from a tree-sitter node using the source string."""
|
|
35
|
+
try:
|
|
36
|
+
buf = source.encode("utf-8")
|
|
37
|
+
return buf[node.start_byte : node.end_byte].decode("utf-8", errors="replace")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
_log.debug("_node_text failed: %s", e)
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def make_finding(
|
|
44
|
+
finding_type: str,
|
|
45
|
+
file: str,
|
|
46
|
+
line: int,
|
|
47
|
+
title: str,
|
|
48
|
+
description: str,
|
|
49
|
+
evidence: str = "",
|
|
50
|
+
severity: str = "medium",
|
|
51
|
+
) -> dict:
|
|
52
|
+
return {
|
|
53
|
+
"type": finding_type,
|
|
54
|
+
"file": file,
|
|
55
|
+
"line": line,
|
|
56
|
+
"title": title,
|
|
57
|
+
"description": description,
|
|
58
|
+
"evidence": evidence,
|
|
59
|
+
"severity": severity,
|
|
60
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""C# type checker — uses tree-sitter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..languages import Lang, get_parser
|
|
9
|
+
from .base import BaseTypeChecker, _node_text, make_finding
|
|
10
|
+
|
|
11
|
+
_log = logging.getLogger("patchi.brain.csharp")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CSharpTypeChecker(BaseTypeChecker):
|
|
15
|
+
language = "c_sharp"
|
|
16
|
+
|
|
17
|
+
def check(self, source: str, file_path: str) -> list[dict]:
|
|
18
|
+
findings: list[dict] = []
|
|
19
|
+
parser = get_parser(Lang.C_SHARP)
|
|
20
|
+
if parser is None:
|
|
21
|
+
return findings
|
|
22
|
+
try:
|
|
23
|
+
tree = parser.parse(source.encode("utf-8"))
|
|
24
|
+
self._walk(tree.root_node, source, file_path, findings)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
_log.warning("CSharpTypeChecker.check failed: %s", e)
|
|
27
|
+
return findings
|
|
28
|
+
|
|
29
|
+
def _walk(self, node: Any, source: str, file_path: str, findings: list[dict]) -> None:
|
|
30
|
+
ntype = node.type
|
|
31
|
+
if ntype == "method_declaration":
|
|
32
|
+
line = node.start_point[0] + 1
|
|
33
|
+
name = (
|
|
34
|
+
_node_text(source, self._child_by_type(node, "identifier"))
|
|
35
|
+
if self._child_by_type(node, "identifier")
|
|
36
|
+
else ""
|
|
37
|
+
)
|
|
38
|
+
return_type = self._child_by_type(node, "predefined_type") or self._child_by_type(
|
|
39
|
+
node, "identifier"
|
|
40
|
+
)
|
|
41
|
+
if return_type and _node_text(source, return_type) in ("object", "dynamic"):
|
|
42
|
+
findings.append(
|
|
43
|
+
make_finding(
|
|
44
|
+
finding_type="untyped_return",
|
|
45
|
+
file=file_path,
|
|
46
|
+
line=line,
|
|
47
|
+
title=f"Method returns '{_node_text(source, return_type)}'",
|
|
48
|
+
description=f"Method '{name}' returns untyped '{_node_text(source, return_type)}' — consider a specific type",
|
|
49
|
+
severity="medium",
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
for child in node.children:
|
|
53
|
+
self._walk(child, source, file_path, findings)
|
|
54
|
+
|
|
55
|
+
def _child_by_type(self, node: Any, ntype: str) -> Any | None:
|
|
56
|
+
for child in node.children:
|
|
57
|
+
if child.type == ntype:
|
|
58
|
+
return child
|
|
59
|
+
return None
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Dart type checker — uses tree-sitter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..languages import Lang, get_parser
|
|
9
|
+
from .base import BaseTypeChecker, _node_text, make_finding
|
|
10
|
+
|
|
11
|
+
_log = logging.getLogger("patchi.brain.dart")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DartTypeChecker(BaseTypeChecker):
|
|
15
|
+
language = "dart"
|
|
16
|
+
|
|
17
|
+
def check(self, source: str, file_path: str) -> list[dict]:
|
|
18
|
+
findings: list[dict] = []
|
|
19
|
+
parser = get_parser(Lang.DART)
|
|
20
|
+
if parser is None:
|
|
21
|
+
return findings
|
|
22
|
+
try:
|
|
23
|
+
tree = parser.parse(source.encode("utf-8"))
|
|
24
|
+
self._walk(tree.root_node, source, file_path, findings)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
_log.warning("DartTypeChecker.check failed: %s", e)
|
|
27
|
+
return findings
|
|
28
|
+
|
|
29
|
+
def _walk(self, node: Any, source: str, file_path: str, findings: list[dict]) -> None:
|
|
30
|
+
ntype = node.type
|
|
31
|
+
if ntype in ("method_declaration", "function_declaration"):
|
|
32
|
+
line = node.start_point[0] + 1
|
|
33
|
+
name = (
|
|
34
|
+
_node_text(source, self._child_by_type(node, "identifier"))
|
|
35
|
+
if self._child_by_type(node, "identifier")
|
|
36
|
+
else ""
|
|
37
|
+
)
|
|
38
|
+
return_type = self._child_by_type(node, "type_identifier") or self._child_by_type(
|
|
39
|
+
node, "generic_type"
|
|
40
|
+
)
|
|
41
|
+
if return_type and _node_text(source, return_type) == "dynamic":
|
|
42
|
+
findings.append(
|
|
43
|
+
make_finding(
|
|
44
|
+
finding_type="dynamic_type",
|
|
45
|
+
file=file_path,
|
|
46
|
+
line=line,
|
|
47
|
+
title="Function returns 'dynamic'",
|
|
48
|
+
description=f"Function '{name}' returns 'dynamic' — prefer a specific type for sound null safety",
|
|
49
|
+
severity="medium",
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
for child in node.children:
|
|
53
|
+
self._walk(child, source, file_path, findings)
|
|
54
|
+
|
|
55
|
+
def _child_by_type(self, node: Any, ntype: str) -> Any | None:
|
|
56
|
+
for child in node.children:
|
|
57
|
+
if child.type == ntype:
|
|
58
|
+
return child
|
|
59
|
+
return None
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Go type checker — uses tree-sitter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..languages import Lang, get_parser
|
|
9
|
+
from .base import BaseTypeChecker, _node_text, make_finding
|
|
10
|
+
|
|
11
|
+
_log = logging.getLogger("patchi.brain.go")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GoTypeChecker(BaseTypeChecker):
|
|
15
|
+
language = "go"
|
|
16
|
+
|
|
17
|
+
def check(self, source: str, file_path: str) -> list[dict]:
|
|
18
|
+
findings: list[dict] = []
|
|
19
|
+
parser = get_parser(Lang.GO)
|
|
20
|
+
if parser is None:
|
|
21
|
+
return findings
|
|
22
|
+
try:
|
|
23
|
+
tree = parser.parse(source.encode("utf-8"))
|
|
24
|
+
self._walk(tree.root_node, source, file_path, findings)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
_log.warning("GoTypeChecker.check failed: %s", e)
|
|
27
|
+
return findings
|
|
28
|
+
|
|
29
|
+
def _walk(self, node: Any, source: str, file_path: str, findings: list[dict]) -> None:
|
|
30
|
+
ntype = node.type
|
|
31
|
+
if ntype == "function_declaration":
|
|
32
|
+
line = node.start_point[0] + 1
|
|
33
|
+
name_node = self._child_by_type(node, "identifier")
|
|
34
|
+
name = _node_text(source, name_node) if name_node else ""
|
|
35
|
+
result = self._child_by_type(node, "result")
|
|
36
|
+
if result is not None:
|
|
37
|
+
for child in result.children:
|
|
38
|
+
if (
|
|
39
|
+
child.type == "type_identifier"
|
|
40
|
+
and _node_text(source, child) == "interface{}"
|
|
41
|
+
):
|
|
42
|
+
findings.append(
|
|
43
|
+
make_finding(
|
|
44
|
+
finding_type="empty_interface",
|
|
45
|
+
file=file_path,
|
|
46
|
+
line=line,
|
|
47
|
+
title="Function returns 'interface{}'",
|
|
48
|
+
description=f"Function '{name}' returns empty interface — consider a concrete type",
|
|
49
|
+
evidence=f"{name}: interface{{}}",
|
|
50
|
+
severity="medium",
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
break
|
|
54
|
+
params = self._child_by_type(node, "parameter_list")
|
|
55
|
+
if params:
|
|
56
|
+
for child in params.children:
|
|
57
|
+
if child.type == "parameter_declaration":
|
|
58
|
+
ptype = (
|
|
59
|
+
self._child_by_type(child, "type_identifier")
|
|
60
|
+
or self._child_by_type(child, "pointer_type")
|
|
61
|
+
or self._child_by_type(child, "generic_type")
|
|
62
|
+
)
|
|
63
|
+
if ptype and _node_text(source, ptype) == "interface{}":
|
|
64
|
+
findings.append(
|
|
65
|
+
make_finding(
|
|
66
|
+
finding_type="empty_interface",
|
|
67
|
+
file=file_path,
|
|
68
|
+
line=line,
|
|
69
|
+
title="Parameter is 'interface{}'",
|
|
70
|
+
description=f"Parameter in '{name}' is empty interface — consider a concrete type",
|
|
71
|
+
severity="medium",
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
for child in node.children:
|
|
75
|
+
self._walk(child, source, file_path, findings)
|
|
76
|
+
|
|
77
|
+
def _child_by_type(self, node: Any, ntype: str) -> Any | None:
|
|
78
|
+
for child in node.children:
|
|
79
|
+
if child.type == ntype:
|
|
80
|
+
return child
|
|
81
|
+
return None
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Java type checker — uses tree-sitter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..languages import Lang, get_parser
|
|
9
|
+
from .base import BaseTypeChecker, _node_text, make_finding
|
|
10
|
+
|
|
11
|
+
_log = logging.getLogger("patchi.brain.java")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JavaTypeChecker(BaseTypeChecker):
|
|
15
|
+
language = "java"
|
|
16
|
+
|
|
17
|
+
def check(self, source: str, file_path: str) -> list[dict]:
|
|
18
|
+
findings: list[dict] = []
|
|
19
|
+
parser = get_parser(Lang.JAVA)
|
|
20
|
+
if parser is None:
|
|
21
|
+
return findings
|
|
22
|
+
try:
|
|
23
|
+
tree = parser.parse(source.encode("utf-8"))
|
|
24
|
+
self._walk(tree.root_node, source, file_path, findings)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
_log.warning("JavaTypeChecker.check failed: %s", e)
|
|
27
|
+
return findings
|
|
28
|
+
|
|
29
|
+
def _walk(self, node: Any, source: str, file_path: str, findings: list[dict]) -> None:
|
|
30
|
+
ntype = node.type
|
|
31
|
+
if ntype == "method_declaration":
|
|
32
|
+
line = node.start_point[0] + 1
|
|
33
|
+
name_node = self._child_by_type(node, "identifier")
|
|
34
|
+
name = _node_text(source, name_node) if name_node else ""
|
|
35
|
+
return_type_node = self._child_by_type(node, "type_identifier") or self._child_by_type(
|
|
36
|
+
node, "generic_type"
|
|
37
|
+
)
|
|
38
|
+
if return_type_node and _node_text(source, return_type_node) == "Object":
|
|
39
|
+
findings.append(
|
|
40
|
+
make_finding(
|
|
41
|
+
finding_type="explicit_object",
|
|
42
|
+
file=file_path,
|
|
43
|
+
line=line,
|
|
44
|
+
title="Method returns 'Object'",
|
|
45
|
+
description=f"Method '{name}' returns raw 'Object' type — consider generics",
|
|
46
|
+
evidence=f"{name}: Object",
|
|
47
|
+
severity="medium",
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
params = self._child_by_type(node, "formal_parameters")
|
|
51
|
+
if params:
|
|
52
|
+
for child in params.children:
|
|
53
|
+
if child.type == "formal_parameter":
|
|
54
|
+
ptype = self._child_by_type(
|
|
55
|
+
child, "type_identifier"
|
|
56
|
+
) or self._child_by_type(child, "generic_type")
|
|
57
|
+
if ptype and _node_text(source, ptype) == "Object":
|
|
58
|
+
findings.append(
|
|
59
|
+
make_finding(
|
|
60
|
+
finding_type="explicit_object",
|
|
61
|
+
file=file_path,
|
|
62
|
+
line=line,
|
|
63
|
+
title="Parameter typed as 'Object'",
|
|
64
|
+
description=f"Parameter in '{name}' is raw 'Object' type",
|
|
65
|
+
severity="medium",
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
for child in node.children:
|
|
69
|
+
self._walk(child, source, file_path, findings)
|
|
70
|
+
|
|
71
|
+
def _child_by_type(self, node: Any, ntype: str) -> Any | None:
|
|
72
|
+
for child in node.children:
|
|
73
|
+
if child.type == ntype:
|
|
74
|
+
return child
|
|
75
|
+
return None
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Kotlin type checker — uses tree-sitter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..languages import Lang, get_parser
|
|
9
|
+
from .base import BaseTypeChecker, _node_text, make_finding
|
|
10
|
+
|
|
11
|
+
_log = logging.getLogger("patchi.brain.kotlin")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class KotlinTypeChecker(BaseTypeChecker):
|
|
15
|
+
language = "kotlin"
|
|
16
|
+
|
|
17
|
+
def check(self, source: str, file_path: str) -> list[dict]:
|
|
18
|
+
findings: list[dict] = []
|
|
19
|
+
parser = get_parser(Lang.KOTLIN)
|
|
20
|
+
if parser is None:
|
|
21
|
+
return findings
|
|
22
|
+
try:
|
|
23
|
+
tree = parser.parse(source.encode("utf-8"))
|
|
24
|
+
self._walk(tree.root_node, source, file_path, findings)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
_log.warning("KotlinTypeChecker.check failed: %s", e)
|
|
27
|
+
return findings
|
|
28
|
+
|
|
29
|
+
def _walk(self, node: Any, source: str, file_path: str, findings: list[dict]) -> None:
|
|
30
|
+
ntype = node.type
|
|
31
|
+
if ntype == "function_declaration":
|
|
32
|
+
line = node.start_point[0] + 1
|
|
33
|
+
name = (
|
|
34
|
+
_node_text(source, self._child_by_type(node, "identifier"))
|
|
35
|
+
if self._child_by_type(node, "identifier")
|
|
36
|
+
else ""
|
|
37
|
+
)
|
|
38
|
+
return_type = self._child_by_type(node, "type_identifier") or self._child_by_type(
|
|
39
|
+
node, "user_type"
|
|
40
|
+
)
|
|
41
|
+
if return_type and _node_text(source, return_type) == "Any":
|
|
42
|
+
findings.append(
|
|
43
|
+
make_finding(
|
|
44
|
+
finding_type="any_type",
|
|
45
|
+
file=file_path,
|
|
46
|
+
line=line,
|
|
47
|
+
title="Function returns 'Any'",
|
|
48
|
+
description=f"Function '{name}' returns 'Any' — consider a specific type",
|
|
49
|
+
severity="medium",
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
# Check for !! non-null assertions
|
|
53
|
+
body = self._child_by_type(node, "function_body") or self._child_by_type(node, "block")
|
|
54
|
+
if body:
|
|
55
|
+
body_text = _node_text(source, body)
|
|
56
|
+
if "!!" in body_text:
|
|
57
|
+
findings.append(
|
|
58
|
+
make_finding(
|
|
59
|
+
finding_type="non_null_assertion",
|
|
60
|
+
file=file_path,
|
|
61
|
+
line=line,
|
|
62
|
+
title="Non-null assertion '!!' used",
|
|
63
|
+
description=f"Function '{name}' uses '!!' — may throw NPE",
|
|
64
|
+
evidence="!!",
|
|
65
|
+
severity="medium",
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
for child in node.children:
|
|
69
|
+
self._walk(child, source, file_path, findings)
|
|
70
|
+
|
|
71
|
+
def _child_by_type(self, node: Any, ntype: str) -> Any | None:
|
|
72
|
+
for child in node.children:
|
|
73
|
+
if child.type == ntype:
|
|
74
|
+
return child
|
|
75
|
+
return None
|