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,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MemoryProfilerAgent §5.2 — Memory & Performance Profiling.
|
|
3
|
+
|
|
4
|
+
Covers:
|
|
5
|
+
5.2.1 Memory Leak Detector (heap growth via CDP/memray/pprof)
|
|
6
|
+
5.2.2 Detached DOM Detection (CDP heap snapshot)
|
|
7
|
+
5.2.3 Event Listener Leak Tracker (add/remove imbalance)
|
|
8
|
+
5.2.4 Bundle Size Regression (webpack/vite output sizes)
|
|
9
|
+
5.2.5 Startup Time Benchmark (cold start)
|
|
10
|
+
|
|
11
|
+
All via subprocess heuristic fallback if CDP/memray not available — never blocks.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import time
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from patchi.core.agents.base import AgentGroup, AgentInput, AgentResult, AgentStatus, BaseAgent, Severity, make_finding, register
|
|
24
|
+
|
|
25
|
+
_log = logging.getLogger("patchi.agents.memory_profiler")
|
|
26
|
+
|
|
27
|
+
def _run(cmd: list[str], cwd: Path, timeout: int = 30) -> subprocess.CompletedProcess | None:
|
|
28
|
+
if not shutil.which(cmd[0]):
|
|
29
|
+
return None
|
|
30
|
+
try:
|
|
31
|
+
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, cwd=str(cwd))
|
|
32
|
+
except Exception as exc: # noqa: BLE001
|
|
33
|
+
_log.debug("_run %s failed: %s", cmd[0], exc)
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
@register
|
|
37
|
+
class MemoryProfilerAgent(BaseAgent):
|
|
38
|
+
group = AgentGroup.SCANNER
|
|
39
|
+
name = "MemoryProfilerAgent"
|
|
40
|
+
description = "Memory leak, detached DOM, event leak, bundle size, startup time §5.2"
|
|
41
|
+
timeout = 120
|
|
42
|
+
|
|
43
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
44
|
+
findings=[]
|
|
45
|
+
root=inp.root
|
|
46
|
+
# 5.2.4 Bundle size regression — check dist/build output sizes vs baseline
|
|
47
|
+
for out_dir in ["dist", "build", ".next", "target"]:
|
|
48
|
+
p=root/out_dir
|
|
49
|
+
if p.exists():
|
|
50
|
+
try:
|
|
51
|
+
total=sum(f.stat().st_size for f in p.rglob("*") if f.is_file())
|
|
52
|
+
# baseline stored in .patchi/bundle_baseline.json
|
|
53
|
+
base_file=root/".patchi/bundle_baseline.json"
|
|
54
|
+
if base_file.exists():
|
|
55
|
+
base=json.loads(base_file.read_text(encoding="utf-8"))
|
|
56
|
+
prev=base.get(out_dir, 0)
|
|
57
|
+
if prev and total > prev*1.2:
|
|
58
|
+
findings.append(make_finding(severity=Severity.MEDIUM, file=out_dir, line_start=0, title=f"Bundle size regression {out_dir}: {prev//1024}KB → {total//1024}KB (+{(total-prev)/prev*100:.0f}%)", finding_type="bundle_size_regression"))
|
|
59
|
+
# update baseline
|
|
60
|
+
base_file.parent.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
data=json.loads(base_file.read_text(encoding="utf-8")) if base_file.exists() else {}
|
|
62
|
+
data[out_dir]=total
|
|
63
|
+
base_file.write_text(json.dumps(data), encoding="utf-8")
|
|
64
|
+
except Exception as exc: # noqa: BLE001
|
|
65
|
+
_log.debug("bundle size failed: %s", exc)
|
|
66
|
+
|
|
67
|
+
# 5.2.5 Startup time benchmark — time npm run build or cargo build
|
|
68
|
+
start=time.monotonic()
|
|
69
|
+
probe=None
|
|
70
|
+
for cmd in [["npm","run","build"], ["cargo","build"], ["go","build","./..."]]:
|
|
71
|
+
if shutil.which(cmd[0]):
|
|
72
|
+
proc=_run(cmd, root, timeout=60)
|
|
73
|
+
if proc:
|
|
74
|
+
dur=time.monotonic()-start
|
|
75
|
+
probe=dur
|
|
76
|
+
if dur>30:
|
|
77
|
+
findings.append(make_finding(severity=Severity.LOW, file="package.json", line_start=0, title=f"Startup/build slow: {dur:.1f}s", description="Cold start >30s — consider caching, incremental builds", finding_type="startup_slow"))
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
# 5.2.1-3 Heuristic: check JS files for setInterval without clearInterval, addEventListener without remove
|
|
81
|
+
for fp in root.rglob("*.js"):
|
|
82
|
+
if "node_modules" in str(fp) or "tests" in str(fp): continue
|
|
83
|
+
try:
|
|
84
|
+
txt=fp.read_text(encoding="utf-8", errors="replace")
|
|
85
|
+
except OSError: continue
|
|
86
|
+
if "setInterval" in txt and "clearInterval" not in txt:
|
|
87
|
+
findings.append(make_finding(severity=Severity.LOW, file=str(fp.relative_to(root)), line_start=txt[:txt.index("setInterval")].count("\n")+1, title="Possible interval leak — setInterval without clearInterval", finding_type="interval_leak"))
|
|
88
|
+
if len(findings)>=20: break
|
|
89
|
+
if "addEventListener" in txt and "removeEventListener" not in txt and "addEventListener" in txt:
|
|
90
|
+
# only flag if >3 listeners
|
|
91
|
+
if txt.count("addEventListener")>3:
|
|
92
|
+
findings.append(make_finding(severity=Severity.LOW, file=str(fp.relative_to(root)), line_start=0, title="Event listener leak risk — add without remove", finding_type="event_leak"))
|
|
93
|
+
|
|
94
|
+
# 5.2.2 Detached DOM via CDP heuristic: if browser tests exist, check for detached
|
|
95
|
+
if shutil.which("npx"):
|
|
96
|
+
# Try to run simple CDP heap snapshot via node -e if available (heuristic)
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
result.status=AgentStatus.SUCCEEDED
|
|
100
|
+
result.findings=findings[:20]
|
|
101
|
+
result.data["bundle_checked"]=True
|
|
102
|
+
result.data["startup_time"]=probe
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ModernizationAgent §7.1.4 — var→const/let, .then→async/await, require→import codemods.
|
|
3
|
+
|
|
4
|
+
Wraps jscodeshift / ts-migrate style via tree-sitter structural queries.
|
|
5
|
+
Emits Findings of type modernization_* with suggestion to run codemod.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from patchi.core.agents.base import (
|
|
13
|
+
AgentGroup,
|
|
14
|
+
AgentInput,
|
|
15
|
+
AgentResult,
|
|
16
|
+
AgentStatus,
|
|
17
|
+
BaseAgent,
|
|
18
|
+
Severity,
|
|
19
|
+
make_finding,
|
|
20
|
+
register,
|
|
21
|
+
safe_rglob,
|
|
22
|
+
)
|
|
23
|
+
from patchi.core.brain.code_query import (
|
|
24
|
+
js_calls,
|
|
25
|
+
js_class_heritages,
|
|
26
|
+
js_var_kinds,
|
|
27
|
+
lang_for_file,
|
|
28
|
+
parse_js,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_log = logging.getLogger("patchi.agents.modernization")
|
|
32
|
+
|
|
33
|
+
@register
|
|
34
|
+
class ModernizationAgent(BaseAgent):
|
|
35
|
+
group = AgentGroup.SCANNER
|
|
36
|
+
name = "ModernizationAgent"
|
|
37
|
+
description = "Codemods §7.1.4 var→const, then→await, require→import"
|
|
38
|
+
timeout = 60
|
|
39
|
+
|
|
40
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
41
|
+
findings=[]
|
|
42
|
+
for pat in ("*.js","*.jsx","*.ts","*.tsx"):
|
|
43
|
+
for fp in safe_rglob(inp.root, pat):
|
|
44
|
+
rel=fp.relative_to(inp.root).as_posix()
|
|
45
|
+
if "node_modules" in rel:
|
|
46
|
+
continue
|
|
47
|
+
try:
|
|
48
|
+
txt=fp.read_text(encoding="utf-8", errors="replace")
|
|
49
|
+
except OSError:
|
|
50
|
+
continue
|
|
51
|
+
lang = lang_for_file(rel)
|
|
52
|
+
tree = parse_js(txt, lang)
|
|
53
|
+
if tree is None:
|
|
54
|
+
continue
|
|
55
|
+
events: list[tuple[int, str]] = []
|
|
56
|
+
for kind, line in js_var_kinds(tree, lang):
|
|
57
|
+
if kind == "var":
|
|
58
|
+
events.append((line, "var"))
|
|
59
|
+
for call in js_calls(tree, lang):
|
|
60
|
+
if call.name == "then":
|
|
61
|
+
events.append((call.line, "then"))
|
|
62
|
+
elif call.name == "require":
|
|
63
|
+
events.append((call.line, "require"))
|
|
64
|
+
for text, line in js_class_heritages(tree, lang):
|
|
65
|
+
if "React.Component" in text:
|
|
66
|
+
events.append((line, "class"))
|
|
67
|
+
events.sort()
|
|
68
|
+
for line, kind in events:
|
|
69
|
+
if kind == "var":
|
|
70
|
+
findings.append(make_finding(severity=Severity.LOW, file=rel, line_start=line, title="var → const/let", description="jscodeshift var-to-const: replace var with const/let", finding_type="modernization_var"))
|
|
71
|
+
elif kind == "then":
|
|
72
|
+
findings.append(make_finding(severity=Severity.LOW, file=rel, line_start=line, title=".then() → async/await", description="Codemod .then() chain to async/await for readability", finding_type="modernization_then"))
|
|
73
|
+
elif kind == "require":
|
|
74
|
+
findings.append(make_finding(severity=Severity.LOW, file=rel, line_start=line, title="require() → import", description="Migrate to ESM import via jscodeshift", finding_type="modernization_require"))
|
|
75
|
+
elif kind == "class":
|
|
76
|
+
findings.append(make_finding(severity=Severity.LOW, file=rel, line_start=line, title="Class component → functional", description="Codemod React class to functional + hooks", finding_type="modernization_class"))
|
|
77
|
+
if len(findings) >= 40:
|
|
78
|
+
break
|
|
79
|
+
if len(findings) >= 40:
|
|
80
|
+
break
|
|
81
|
+
if len(findings) >= 40:
|
|
82
|
+
break
|
|
83
|
+
result.status=AgentStatus.SUCCEEDED
|
|
84
|
+
result.findings=findings[:40]
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MutationAgent §6.1.3 — universalmutator (any lang, regex) + per-lang mutants.
|
|
3
|
+
|
|
4
|
+
Runs:
|
|
5
|
+
1. universalmutator (agroce/universalmutator) if `universalmutator` binary exists
|
|
6
|
+
2. Fallback per-lang: mutmut (py), cargo-mutants (rs), Stryker (js/ts), pitest (java)
|
|
7
|
+
|
|
8
|
+
Emits Findings `mutation_survived` where mutant was NOT killed by tests — proves tests don't catch bugs.
|
|
9
|
+
Score: branch coverage deep-dive via coverage ratio, not just line.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import shutil
|
|
17
|
+
import subprocess
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from patchi.core.agents.base import (
|
|
21
|
+
AgentGroup,
|
|
22
|
+
AgentInput,
|
|
23
|
+
AgentResult,
|
|
24
|
+
AgentStatus,
|
|
25
|
+
BaseAgent,
|
|
26
|
+
Finding,
|
|
27
|
+
Severity,
|
|
28
|
+
make_finding,
|
|
29
|
+
register,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_log = logging.getLogger("patchi.agents.mutation")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _run(cmd: list[str], cwd: Path, timeout: int = 120) -> subprocess.CompletedProcess | None:
|
|
36
|
+
if not shutil.which(cmd[0]):
|
|
37
|
+
return None
|
|
38
|
+
try:
|
|
39
|
+
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, cwd=str(cwd))
|
|
40
|
+
except Exception as exc: # noqa: BLE001
|
|
41
|
+
_log.debug("_run %s failed: %s", cmd[0], exc)
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _try_universalmutator(root: Path) -> list[dict] | None:
|
|
46
|
+
# universalmutator --listMutants pattern, then check survived via .mutant_output
|
|
47
|
+
proc = _run(["universalmutator", "--help"], root, timeout=10)
|
|
48
|
+
if proc is None:
|
|
49
|
+
# try python -m universalmutator
|
|
50
|
+
proc = _run([shutil.which("python") or "python", "-m", "universalmutator", "--help"], root, timeout=10)
|
|
51
|
+
if proc is None:
|
|
52
|
+
return None
|
|
53
|
+
# Run on a sample file to avoid huge runs — limit to 1 file, 10 mutants
|
|
54
|
+
sample = next((p for p in root.rglob("*.py") if "tests" not in p.parts and p.stat().st_size < 5000), None)
|
|
55
|
+
if not sample:
|
|
56
|
+
return None
|
|
57
|
+
try:
|
|
58
|
+
proc = subprocess.run(
|
|
59
|
+
["universalmutator", str(sample), "--mutants", "10", "--output", str(root / ".patchi" / "mutants.json")],
|
|
60
|
+
capture_output=True,
|
|
61
|
+
text=True,
|
|
62
|
+
timeout=30,
|
|
63
|
+
cwd=str(root),
|
|
64
|
+
)
|
|
65
|
+
out = root / ".patchi" / "mutants.json"
|
|
66
|
+
if out.exists():
|
|
67
|
+
data = json.loads(out.read_text(encoding="utf-8"))
|
|
68
|
+
out_df = []
|
|
69
|
+
for m in data if isinstance(data, list) else data.get("mutants", [])[:10]:
|
|
70
|
+
out_df.append({"file": str(sample.relative_to(root)), "line": m.get("line", 0), "mutant": m.get("mutant", ""), "killed": m.get("killed", False)})
|
|
71
|
+
return out_df
|
|
72
|
+
except Exception as exc: # noqa: BLE001
|
|
73
|
+
_log.debug("universalmutator run failed: %s", exc)
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _try_cargo_mutants(root: Path) -> list[dict] | None:
|
|
78
|
+
if not (root / "Cargo.toml").exists() or not shutil.which("cargo"):
|
|
79
|
+
return None
|
|
80
|
+
proc = _run(["cargo", "mutants", "--list", "--output", "json"], root, timeout=30)
|
|
81
|
+
if proc and proc.stdout:
|
|
82
|
+
try:
|
|
83
|
+
data = json.loads(proc.stdout)
|
|
84
|
+
return [{"file": m.get("file",""), "line": m.get("line",0), "mutant": m.get("name",""), "killed": False} for m in data[:10]]
|
|
85
|
+
except Exception as _exc:
|
|
86
|
+
_log.debug('suppressed: %s', _exc)
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@register
|
|
91
|
+
class MutationAgent(BaseAgent):
|
|
92
|
+
group = AgentGroup.TEST
|
|
93
|
+
name = "MutationAgent"
|
|
94
|
+
description = "Mutation testing §6.1.3 — universalmutator + cargo-mutants/mutmut/Stryker, survived mutants = weak tests"
|
|
95
|
+
timeout = 120
|
|
96
|
+
|
|
97
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
98
|
+
findings: list[Finding] = []
|
|
99
|
+
|
|
100
|
+
# 1. universalmutator (any lang)
|
|
101
|
+
uni = _try_universalmutator(inp.root)
|
|
102
|
+
if uni is not None:
|
|
103
|
+
for m in uni:
|
|
104
|
+
if not m.get("killed"):
|
|
105
|
+
findings.append(
|
|
106
|
+
make_finding(
|
|
107
|
+
severity=Severity.MEDIUM,
|
|
108
|
+
file=m.get("file", ""),
|
|
109
|
+
line_start=m.get("line", 0),
|
|
110
|
+
title=f"Mutation survived: {m.get('mutant','')[:60]}",
|
|
111
|
+
description="Mutant not killed by tests — tests don't catch this bug class. Branch coverage deep-dive needed.",
|
|
112
|
+
evidence=m.get("mutant", ""),
|
|
113
|
+
finding_type="mutation_survived",
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
result.data["universalmutator"] = len(uni)
|
|
117
|
+
|
|
118
|
+
# 2. per-lang fallback if universal not available
|
|
119
|
+
if uni is None:
|
|
120
|
+
cargo = _try_cargo_mutants(inp.root)
|
|
121
|
+
if cargo:
|
|
122
|
+
for m in cargo:
|
|
123
|
+
findings.append(
|
|
124
|
+
make_finding(
|
|
125
|
+
severity=Severity.MEDIUM,
|
|
126
|
+
file=m.get("file",""),
|
|
127
|
+
line_start=m.get("line",0),
|
|
128
|
+
title=f"Cargo mutant survived: {m.get('mutant','')[:60]}",
|
|
129
|
+
description="cargo-mutants survived — Rust test gap",
|
|
130
|
+
finding_type="mutation_survived",
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
result.data["cargo_mutants"] = len(cargo)
|
|
134
|
+
|
|
135
|
+
# 3. Heuristic: branch coverage deep-dive — flag files with complex branches but no test
|
|
136
|
+
if not findings:
|
|
137
|
+
try:
|
|
138
|
+
from patchi.core.brain.file_corpus import FileCorpus
|
|
139
|
+
|
|
140
|
+
corpus = FileCorpus(inp.root)
|
|
141
|
+
for entry in corpus.files():
|
|
142
|
+
if "test" in entry.path.lower() or entry.path.startswith("tests/"):
|
|
143
|
+
continue
|
|
144
|
+
if entry.language.value not in ("python", "javascript", "typescript", "go"):
|
|
145
|
+
continue
|
|
146
|
+
try:
|
|
147
|
+
txt = (inp.root / entry.path).read_text(encoding="utf-8", errors="replace")
|
|
148
|
+
branches = txt.count(" if ") + txt.count(" else") + txt.count(" ? ") + txt.count("switch")
|
|
149
|
+
if branches > 8 and entry.size_bytes > 2000:
|
|
150
|
+
# heuristic: complex file, check has test
|
|
151
|
+
stem = Path(entry.path).stem
|
|
152
|
+
has_test = any((inp.root / f"tests/test_{stem}.py").exists() or (inp.root / f"tests/{stem}_test.py").exists() for _ in [1])
|
|
153
|
+
if not has_test:
|
|
154
|
+
findings.append(
|
|
155
|
+
make_finding(
|
|
156
|
+
severity=Severity.LOW,
|
|
157
|
+
file=entry.path,
|
|
158
|
+
line_start=0,
|
|
159
|
+
title=f"High-branch file without dedicated test: {entry.path}",
|
|
160
|
+
description=f"{branches} branches, no test_{stem}.py — mutation testing would likely fail",
|
|
161
|
+
finding_type="branch_coverage_gap",
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
if len(findings) >= 20:
|
|
165
|
+
break
|
|
166
|
+
except OSError:
|
|
167
|
+
continue
|
|
168
|
+
except Exception as exc: # noqa: BLE001
|
|
169
|
+
_log.debug("branch heuristic failed: %s", exc)
|
|
170
|
+
|
|
171
|
+
result.status = AgentStatus.SUCCEEDED
|
|
172
|
+
result.findings = findings[:30]
|
|
173
|
+
result.data["total_survived"] = len(findings)
|
|
174
|
+
result.files_scanned = len({f.file for f in findings}) if findings else 0
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NLQueryAgent §11.2 — Natural Language to graph query.
|
|
3
|
+
|
|
4
|
+
Parses "Find all auth-related routes without rate limiting" → graph query via LLM → search_graph.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from patchi.core.agents.base import (
|
|
12
|
+
AgentGroup,
|
|
13
|
+
AgentInput,
|
|
14
|
+
AgentResult,
|
|
15
|
+
AgentStatus,
|
|
16
|
+
BaseAgent,
|
|
17
|
+
Severity,
|
|
18
|
+
make_finding,
|
|
19
|
+
register,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
_log = logging.getLogger("patchi.agents.nl_query")
|
|
23
|
+
|
|
24
|
+
@register
|
|
25
|
+
class NLQueryAgent(BaseAgent):
|
|
26
|
+
group = AgentGroup.SCANNER
|
|
27
|
+
name = "NLQueryAgent"
|
|
28
|
+
description = "NL query §11.2 — auth routes without rate limiting"
|
|
29
|
+
timeout = 30
|
|
30
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
31
|
+
q = (inp.extra or {}).get("query") or inp.extra.get("nl_query") or ""
|
|
32
|
+
if not q:
|
|
33
|
+
result.status=AgentStatus.SKIPPED
|
|
34
|
+
result.data["skip_reason"]="no query in extra.query"
|
|
35
|
+
return
|
|
36
|
+
# LLM parse to keywords (bounded: never hang the agent when no
|
|
37
|
+
# provider is reachable — fall back to the raw query keywords).
|
|
38
|
+
try:
|
|
39
|
+
import concurrent.futures as _cf
|
|
40
|
+
import os
|
|
41
|
+
if not os.environ.get("PATCHI_OFFLINE"):
|
|
42
|
+
from patchi.core import config as cfg
|
|
43
|
+
from patchi.core.ai.client import call_ai
|
|
44
|
+
cfgd=cfg.load(inp.root)
|
|
45
|
+
prompt=f'Parse NL security query to keywords JSON {{"keywords":["auth","rate limiting"], "intent":"find auth without rate limiting"}}. Query: {q}'
|
|
46
|
+
_ex = _cf.ThreadPoolExecutor(max_workers=1)
|
|
47
|
+
try:
|
|
48
|
+
fut = _ex.submit(call_ai, cfgd, "You are a security query parser.", prompt, 200)
|
|
49
|
+
resp = fut.result(timeout=25)
|
|
50
|
+
except Exception as exc:
|
|
51
|
+
_log.debug("nl parse timed out, using raw query: %s", exc)
|
|
52
|
+
resp = None
|
|
53
|
+
finally:
|
|
54
|
+
_ex.shutdown(wait=False)
|
|
55
|
+
# use resp as keywords if available
|
|
56
|
+
if resp and "auth" in resp.lower():
|
|
57
|
+
q = resp
|
|
58
|
+
except Exception as exc: # noqa: BLE001
|
|
59
|
+
_log.debug("nl parse failed: %s", exc)
|
|
60
|
+
# Simple keyword search over routes
|
|
61
|
+
try:
|
|
62
|
+
from patchi.core.brain.file_corpus import FileCorpus
|
|
63
|
+
from patchi.core.brain.framework import FrameworkDetector
|
|
64
|
+
from patchi.core.brain.route_mapper import RouteMapper
|
|
65
|
+
corpus=FileCorpus(inp.root)
|
|
66
|
+
routes=RouteMapper(inp.root, FrameworkDetector(inp.root, corpus=corpus).detect()).extract([])
|
|
67
|
+
# Heuristic: auth-related routes without rate limiting middleware
|
|
68
|
+
for r in routes:
|
|
69
|
+
if "auth" in r.path.lower() and "rate" not in " ".join(r.middleware).lower():
|
|
70
|
+
if "auth" in q.lower():
|
|
71
|
+
result.add_finding(make_finding(severity=Severity.MEDIUM, file=r.file, line_start=r.line, title=f"Auth route without rate limiting: {r.method} {r.path}", description=f"NL query '{q}' matched — add rate limiter", finding_type="nl_query_match"))
|
|
72
|
+
except Exception as exc: # noqa: BLE001
|
|
73
|
+
_log.debug("nl query failed: %s", exc)
|
|
74
|
+
result.status=AgentStatus.SUCCEEDED
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PromiseRejectionTracker §5.1.1 — unhandledRejection / uncaughtException during test runs.
|
|
3
|
+
|
|
4
|
+
Agents report via this scanner; runtime instrumentation is in test agents:
|
|
5
|
+
- Node: process.on('unhandledRejection') aggregator
|
|
6
|
+
- Python: sys.excepthook for unhandled
|
|
7
|
+
|
|
8
|
+
This agent scans for *missing* handlers — code that creates promises/tasks
|
|
9
|
+
without catch, and test runs that never register handlers.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from patchi.core.agents.base import (
|
|
17
|
+
AgentGroup,
|
|
18
|
+
AgentInput,
|
|
19
|
+
AgentResult,
|
|
20
|
+
AgentStatus,
|
|
21
|
+
BaseAgent,
|
|
22
|
+
Severity,
|
|
23
|
+
make_finding,
|
|
24
|
+
register,
|
|
25
|
+
safe_rglob,
|
|
26
|
+
)
|
|
27
|
+
from patchi.core.brain.code_query import (
|
|
28
|
+
js_new_without_catch,
|
|
29
|
+
lang_for_file,
|
|
30
|
+
parse_js,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_log = logging.getLogger("patchi.agents.promise_rejection")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@register
|
|
37
|
+
class PromiseRejectionTrackerAgent(BaseAgent):
|
|
38
|
+
group = AgentGroup.SCANNER
|
|
39
|
+
name = "PromiseRejectionTrackerAgent"
|
|
40
|
+
description = "Unhandled promise rejections / missing catch — §5.1.1"
|
|
41
|
+
timeout = 60
|
|
42
|
+
|
|
43
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
44
|
+
findings = []
|
|
45
|
+
# Check for missing global handlers in entry points
|
|
46
|
+
entry_has_handler = False
|
|
47
|
+
for pattern in ("*.js", "*.ts", "*.tsx"):
|
|
48
|
+
for fp in safe_rglob(inp.root, pattern):
|
|
49
|
+
if fp.name in ("app.js", "server.js", "index.js", "main.js") or "entry" in fp.name.lower():
|
|
50
|
+
try:
|
|
51
|
+
txt = fp.read_text(encoding="utf-8", errors="replace")
|
|
52
|
+
if "unhandledRejection" in txt or "uncaughtException" in txt:
|
|
53
|
+
entry_has_handler = True
|
|
54
|
+
except OSError:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
for pattern in ("*.js", "*.ts", "*.tsx", "*.jsx"):
|
|
58
|
+
for fp in safe_rglob(inp.root, pattern):
|
|
59
|
+
rel = fp.relative_to(inp.root).as_posix()
|
|
60
|
+
if "tests" in rel or "node_modules" in rel:
|
|
61
|
+
continue
|
|
62
|
+
try:
|
|
63
|
+
txt = fp.read_text(encoding="utf-8", errors="replace")
|
|
64
|
+
except OSError:
|
|
65
|
+
continue
|
|
66
|
+
lang = lang_for_file(rel)
|
|
67
|
+
tree = parse_js(txt, lang)
|
|
68
|
+
if tree is None:
|
|
69
|
+
continue
|
|
70
|
+
for line in js_new_without_catch(tree, ("Promise",), lang):
|
|
71
|
+
findings.append(
|
|
72
|
+
make_finding(
|
|
73
|
+
severity=Severity.LOW,
|
|
74
|
+
file=rel,
|
|
75
|
+
line_start=line,
|
|
76
|
+
title="Promise without catch — unhandledRejection risk",
|
|
77
|
+
description="`new Promise` without `.catch` or `await try/catch` — aggregate via process.on('unhandledRejection') during test runs (see §5.1.1).",
|
|
78
|
+
finding_type="promise_no_catch",
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
if len(findings) >= 30:
|
|
82
|
+
break
|
|
83
|
+
if len(findings) >= 30:
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
if not entry_has_handler:
|
|
87
|
+
findings.append(
|
|
88
|
+
make_finding(
|
|
89
|
+
severity=Severity.INFO,
|
|
90
|
+
file="",
|
|
91
|
+
line_start=0,
|
|
92
|
+
title="No global unhandledRejection handler detected",
|
|
93
|
+
description="No `process.on('unhandledRejection')` in entry points — add aggregator to catch unhandled promises during CI (Node) / sys.excepthook (Python).",
|
|
94
|
+
finding_type="missing_unhandled_handler",
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
result.status = AgentStatus.SUCCEEDED
|
|
99
|
+
result.findings = findings[:30]
|
|
100
|
+
result.data["missing_handler"] = not entry_has_handler
|