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,423 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Confidence Gate — scores each finding 0.0–1.0 and routes by tier.
|
|
3
|
+
|
|
4
|
+
HIGH >= 0.7 -> defend (auto-fix / block)
|
|
5
|
+
MEDIUM >= 0.4 -> ai_analyze (Layer 2 AI confirmation)
|
|
6
|
+
LOW < 0.4 -> human_review (if severe) or discard
|
|
7
|
+
|
|
8
|
+
Scoring factors (additive):
|
|
9
|
+
+ method_precision regex=0.3, AST=0.5, taint/flow=0.7, deterministic=0.9
|
|
10
|
+
+ severity_bonus high=+0.1, critical=+0.2
|
|
11
|
+
+ multi_agent_bonus +0.1 per confirming agent, max +0.3
|
|
12
|
+
+ location_precision exact line=+0.1, within 5 lines=+0.05
|
|
13
|
+
- known_fp_penalty -0.3 if matches known false-positive pattern
|
|
14
|
+
- ambiguity_penalty -0.2 if no code snippet or heuristic match
|
|
15
|
+
|
|
16
|
+
Noise controls (config under ``confidence_gate``):
|
|
17
|
+
min_agents_for_defend default 1 — findings confirmed by fewer agents are
|
|
18
|
+
demoted from 'defend' to 'ai_analyze' even at high tier.
|
|
19
|
+
min_agents_to_keep default 0 (off) — findings confirmed by fewer agents
|
|
20
|
+
than this are discarded outright regardless of score.
|
|
21
|
+
ai_weight default 0.0 — blend weight for calibrated AI confidence:
|
|
22
|
+
final = (1-w)*heuristic_score + w*ai_confidence.
|
|
23
|
+
fp_penalty default 0.3 — score penalty for known false positives.
|
|
24
|
+
|
|
25
|
+
False-positive learning loop:
|
|
26
|
+
record_false_positives() persists analyst/AI verdicts to
|
|
27
|
+
.patchi/memory/known_false_positives.json so repeat noise is pre-penalized
|
|
28
|
+
(and, with fp_auto_discard, dropped outright) on every subsequent scan.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
import logging
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
from patchi.core.security.gated_finding import GatedFinding, GatedReport
|
|
38
|
+
from patchi.core.security.orchestrator import CorrelatedFinding, SecurityReport
|
|
39
|
+
|
|
40
|
+
_log = logging.getLogger("patchi.confidence_gate")
|
|
41
|
+
|
|
42
|
+
# ── Method precision lookup ──────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
_METHOD_PRECISION: dict[str, float] = {
|
|
45
|
+
"regex": 0.3,
|
|
46
|
+
"ast": 0.5,
|
|
47
|
+
"flow": 0.7,
|
|
48
|
+
"deterministic": 0.9,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_SEVERITY_BONUS: dict[str, float] = {
|
|
52
|
+
"critical": 0.2,
|
|
53
|
+
"high": 0.1,
|
|
54
|
+
"medium": 0.0,
|
|
55
|
+
"low": 0.0,
|
|
56
|
+
"info": -0.1,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_FP_MEMORY_FILE = ".patchi/memory/known_false_positives.json"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ConfidenceGate:
|
|
63
|
+
"""Scores findings and assigns routing tiers."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, root: Path, config: dict | None = None):
|
|
66
|
+
self.root = root
|
|
67
|
+
self.config = config or {}
|
|
68
|
+
noise_cfg = self.config.get("confidence_gate", {})
|
|
69
|
+
self.min_agents_for_defend: int = int(noise_cfg.get("min_agents_for_defend", 1))
|
|
70
|
+
self.min_agents_to_keep: int = int(noise_cfg.get("min_agents_to_keep", 0))
|
|
71
|
+
self.ai_weight: float = float(noise_cfg.get("ai_weight", 0.3)) # default 0.3, was 0.0
|
|
72
|
+
self.fp_penalty: float = float(noise_cfg.get("fp_penalty", 0.3))
|
|
73
|
+
self.noise_penalty: float = float(noise_cfg.get("noise_penalty", 0.5))
|
|
74
|
+
self.fp_auto_discard: bool = bool(
|
|
75
|
+
noise_cfg.get("fp_auto_discard", True)
|
|
76
|
+
) # default True, was False
|
|
77
|
+
self._known_fps: set[tuple] = set()
|
|
78
|
+
self._load_known_fps()
|
|
79
|
+
# Lazy-load AI validator
|
|
80
|
+
self._ai_validator = None
|
|
81
|
+
|
|
82
|
+
# ── Known-FP memory ─────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def _fp_path(self) -> Path:
|
|
86
|
+
return self.root / _FP_MEMORY_FILE
|
|
87
|
+
|
|
88
|
+
def _load_known_fps(self) -> None:
|
|
89
|
+
if not self._fp_path.exists():
|
|
90
|
+
return
|
|
91
|
+
try:
|
|
92
|
+
data = json.loads(self._fp_path.read_text(encoding="utf-8"))
|
|
93
|
+
for entry in data:
|
|
94
|
+
fp_key = (
|
|
95
|
+
entry.get("file", ""),
|
|
96
|
+
entry.get("type", ""),
|
|
97
|
+
entry.get("line", 0),
|
|
98
|
+
)
|
|
99
|
+
self._known_fps.add(fp_key)
|
|
100
|
+
except Exception as exc: # noqa: BLE001 — corrupt memory = start fresh
|
|
101
|
+
_log.warning("Failed to parse known_false_positives.json: %s", exc)
|
|
102
|
+
|
|
103
|
+
def record_false_positives(self, entries: list[dict]) -> int:
|
|
104
|
+
"""Persist false-positive verdicts and apply them immediately.
|
|
105
|
+
|
|
106
|
+
Each entry needs ``file``, ``type``, ``line``; optional ``reason``
|
|
107
|
+
and ``source`` ("analyst" | "ai") are kept for auditability.
|
|
108
|
+
Returns the number of NEW keys recorded.
|
|
109
|
+
"""
|
|
110
|
+
existing: list[dict] = []
|
|
111
|
+
if self._fp_path.exists():
|
|
112
|
+
try:
|
|
113
|
+
existing = json.loads(self._fp_path.read_text(encoding="utf-8"))
|
|
114
|
+
except Exception: # noqa: BLE001
|
|
115
|
+
existing = []
|
|
116
|
+
|
|
117
|
+
seen = {(e.get("file", ""), e.get("type", ""), e.get("line", 0)) for e in existing}
|
|
118
|
+
added = 0
|
|
119
|
+
for entry in entries:
|
|
120
|
+
key = (entry.get("file", ""), entry.get("type", ""), entry.get("line", 0))
|
|
121
|
+
if key in seen or not any(key):
|
|
122
|
+
continue
|
|
123
|
+
seen.add(key)
|
|
124
|
+
existing.append(entry)
|
|
125
|
+
self._known_fps.add(key)
|
|
126
|
+
added += 1
|
|
127
|
+
|
|
128
|
+
if added:
|
|
129
|
+
self._fp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
130
|
+
tmp = self._fp_path.with_suffix(".json.tmp")
|
|
131
|
+
tmp.write_text(json.dumps(existing, indent=2), encoding="utf-8")
|
|
132
|
+
tmp.replace(self._fp_path)
|
|
133
|
+
_log.info("Recorded %d new false positive(s)", added)
|
|
134
|
+
return added
|
|
135
|
+
|
|
136
|
+
def forget_false_positive(self, file: str, type_: str, line: int) -> bool:
|
|
137
|
+
"""Remove one FP verdict (analyst says it was real after all)."""
|
|
138
|
+
key = (file, type_, line)
|
|
139
|
+
if key not in self._known_fps:
|
|
140
|
+
return False
|
|
141
|
+
self._known_fps.discard(key)
|
|
142
|
+
remaining: list[dict] = []
|
|
143
|
+
if self._fp_path.exists():
|
|
144
|
+
try:
|
|
145
|
+
data = json.loads(self._fp_path.read_text(encoding="utf-8"))
|
|
146
|
+
remaining = [
|
|
147
|
+
e
|
|
148
|
+
for e in data
|
|
149
|
+
if (e.get("file", ""), e.get("type", ""), e.get("line", 0)) != key
|
|
150
|
+
]
|
|
151
|
+
except Exception: # noqa: BLE001
|
|
152
|
+
remaining = []
|
|
153
|
+
tmp = self._fp_path.with_suffix(".json.tmp")
|
|
154
|
+
tmp.write_text(json.dumps(remaining, indent=2), encoding="utf-8")
|
|
155
|
+
tmp.replace(self._fp_path)
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
# ── Gating ──────────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
def gate(
|
|
161
|
+
self,
|
|
162
|
+
cf: CorrelatedFinding,
|
|
163
|
+
ai_confidence: float | None = None,
|
|
164
|
+
) -> GatedFinding:
|
|
165
|
+
"""Score and route one correlated finding.
|
|
166
|
+
|
|
167
|
+
``ai_confidence`` (0.0–1.0, optional) blends a model verdict into the
|
|
168
|
+
heuristic score using the configured ``ai_weight``.
|
|
169
|
+
"""
|
|
170
|
+
score = self._compute_score(cf, ai_confidence=ai_confidence)
|
|
171
|
+
tier = self._assign_tier(score)
|
|
172
|
+
routing = self._assign_routing(tier, cf)
|
|
173
|
+
return GatedFinding(
|
|
174
|
+
finding=cf.finding,
|
|
175
|
+
confidence_score=score,
|
|
176
|
+
confidence_tier=tier,
|
|
177
|
+
routing=routing,
|
|
178
|
+
routing_reason=self._routing_reason(routing, score, cf),
|
|
179
|
+
confirmed_by=cf.confirmed_by,
|
|
180
|
+
composite_score=cf.composite_score,
|
|
181
|
+
owasp_category=cf.owasp_category,
|
|
182
|
+
cwe_ids=cf.cwe_ids,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def gate_all(
|
|
186
|
+
self,
|
|
187
|
+
report: SecurityReport,
|
|
188
|
+
ai_confidences: list[float] | None = None,
|
|
189
|
+
) -> GatedReport:
|
|
190
|
+
gated_list = [
|
|
191
|
+
self.gate(
|
|
192
|
+
cf,
|
|
193
|
+
ai_confidence=(
|
|
194
|
+
ai_confidences[i] if ai_confidences and i < len(ai_confidences) else None
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
for i, cf in enumerate(report.findings)
|
|
198
|
+
]
|
|
199
|
+
return self._build_report(gated_list)
|
|
200
|
+
|
|
201
|
+
def gate_with_ai_validation(
|
|
202
|
+
self,
|
|
203
|
+
report: SecurityReport,
|
|
204
|
+
route_context: dict | None = None,
|
|
205
|
+
) -> GatedReport:
|
|
206
|
+
"""Full harness: score → route → AIConfidenceGate second pass (L2).
|
|
207
|
+
|
|
208
|
+
L2 spec: keep deterministic scanners, add AI as second pass ONLY for
|
|
209
|
+
confidence==low AND severity in {high,critical} OR type in {secret, injection, auth}.
|
|
210
|
+
Schema-validated JSON {verdict: confirmed|fp, reason, exploitability 0-1}
|
|
211
|
+
via call_ai_structured:212. Offline → unverified.
|
|
212
|
+
"""
|
|
213
|
+
from patchi.core.security.ai_validator import AIValidator
|
|
214
|
+
|
|
215
|
+
if self._ai_validator is None:
|
|
216
|
+
self._ai_validator = AIValidator(self.root, self.config)
|
|
217
|
+
|
|
218
|
+
# Step 1: Initial gating
|
|
219
|
+
gated = [self.gate(cf) for cf in report.findings]
|
|
220
|
+
|
|
221
|
+
# L2 targeted second pass: low confidence + high/critical OR sensitive types
|
|
222
|
+
import os as _os
|
|
223
|
+
|
|
224
|
+
if _os.environ.get("PATCHI_OFFLINE"):
|
|
225
|
+
# Offline honors contract — no AI calls, mark as unverified
|
|
226
|
+
for gf in gated:
|
|
227
|
+
if gf.confidence_score < 0.4 or gf.routing == "ai_analyze":
|
|
228
|
+
gf.routing_reason += " [offline unverified]"
|
|
229
|
+
return self._build_report(gated)
|
|
230
|
+
|
|
231
|
+
sensitive_types = ("secret", "injection", "auth", "cred", "token", "password")
|
|
232
|
+
l2_targets: list = []
|
|
233
|
+
for gf in gated:
|
|
234
|
+
is_low = gf.confidence_score < 0.4 or gf.routing in ("ai_analyze", "human_review")
|
|
235
|
+
sev = getattr(gf.finding.severity, "value", str(gf.finding.severity)).lower()
|
|
236
|
+
is_high = sev in ("high", "critical")
|
|
237
|
+
is_sensitive = any(t in (gf.finding.type or "").lower() for t in sensitive_types)
|
|
238
|
+
if (is_low and is_high) or (is_low and is_sensitive) or gf.routing == "ai_analyze":
|
|
239
|
+
# limit tokens: high/critical low OR sensitive types, plus all ai_analyze
|
|
240
|
+
l2_targets.append(gf)
|
|
241
|
+
# cap to bound cost ~10 Findings
|
|
242
|
+
l2_targets = l2_targets[:10]
|
|
243
|
+
if l2_targets:
|
|
244
|
+
_log.info("L2 AIConfidenceGate validating %d findings (low/high+secret/injection/auth) ...", len(l2_targets))
|
|
245
|
+
for gf in l2_targets:
|
|
246
|
+
result = self._ai_validator.validate(gf.finding, route_context=route_context)
|
|
247
|
+
# Schema: is_true_positive + confidence + explanation already
|
|
248
|
+
if result.confidence >= 0.7 and not result.is_true_positive:
|
|
249
|
+
gf.routing = "discard"
|
|
250
|
+
gf.routing_reason = f"L2 fp ({result.confidence:.0%}): {result.explanation} [verdict=fp exploitability~{1-result.confidence:.1f}]"
|
|
251
|
+
elif result.confidence >= 0.6 and result.is_true_positive:
|
|
252
|
+
gf.routing = "defend"
|
|
253
|
+
gf.routing_reason = f"L2 confirmed ({result.confidence:.0%}): {result.explanation} [exploitability={result.confidence:.1f}]"
|
|
254
|
+
else:
|
|
255
|
+
gf.routing = "human_review"
|
|
256
|
+
gf.routing_reason = f"L2 unverified ({result.confidence:.0%}): {result.explanation}"
|
|
257
|
+
ai_w = min(self.ai_weight, 0.5)
|
|
258
|
+
gf.confidence_score = (1.0 - ai_w) * gf.confidence_score + ai_w * (
|
|
259
|
+
1.0 - result.confidence if not result.is_true_positive else result.confidence
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Step 3: Auto-learn from discarded findings
|
|
263
|
+
self.learn_from_dismissed(self._build_report(gated))
|
|
264
|
+
|
|
265
|
+
return self._build_report(gated)
|
|
266
|
+
|
|
267
|
+
def l2_second_pass(self, report: SecurityReport, route_context: dict | None = None) -> GatedReport:
|
|
268
|
+
"""Alias for L2 spec naming — explicit entry point."""
|
|
269
|
+
return self.gate_with_ai_validation(report, route_context=route_context)
|
|
270
|
+
|
|
271
|
+
def _build_report(self, gated_list: list[GatedFinding]) -> GatedReport:
|
|
272
|
+
stats = {
|
|
273
|
+
"total": len(gated_list),
|
|
274
|
+
"defend": sum(1 for g in gated_list if g.routing == "defend"),
|
|
275
|
+
"ai_analyze": sum(1 for g in gated_list if g.routing == "ai_analyze"),
|
|
276
|
+
"human_review": sum(1 for g in gated_list if g.routing == "human_review"),
|
|
277
|
+
"discarded": sum(1 for g in gated_list if g.routing == "discard"),
|
|
278
|
+
}
|
|
279
|
+
return GatedReport(findings=gated_list, stats=stats)
|
|
280
|
+
|
|
281
|
+
def learn_from_dismissed(self, gated: GatedReport) -> int:
|
|
282
|
+
"""Auto-record AI-dismissed + discarded findings as known false positives.
|
|
283
|
+
|
|
284
|
+
This closes the noise loop: anything the pipeline rejects once is
|
|
285
|
+
pre-penalized (or dropped, with ``fp_auto_discard``) on every future
|
|
286
|
+
scan instead of being re-surfaced thousands of times.
|
|
287
|
+
"""
|
|
288
|
+
entries = []
|
|
289
|
+
for gf in gated.discarded:
|
|
290
|
+
f = gf.finding
|
|
291
|
+
entries.append(
|
|
292
|
+
{
|
|
293
|
+
"file": f.file,
|
|
294
|
+
"type": f.type,
|
|
295
|
+
"line": f.line,
|
|
296
|
+
"reason": gf.routing_reason[:200],
|
|
297
|
+
"source": "pipeline",
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
return self.record_false_positives(entries)
|
|
301
|
+
|
|
302
|
+
# ── Scoring internals ───────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
def _compute_score(
|
|
305
|
+
self,
|
|
306
|
+
cf: CorrelatedFinding,
|
|
307
|
+
ai_confidence: float | None = None,
|
|
308
|
+
) -> float:
|
|
309
|
+
score = 0.0
|
|
310
|
+
f = cf.finding
|
|
311
|
+
|
|
312
|
+
# 1. Method precision — infer from finding metadata
|
|
313
|
+
method = "regex"
|
|
314
|
+
if f.code_snippet and len(f.code_snippet) > 50:
|
|
315
|
+
method = "flow"
|
|
316
|
+
elif f.cwe and f.cwe.strip():
|
|
317
|
+
method = "ast"
|
|
318
|
+
if cf.composite_score >= 70:
|
|
319
|
+
method = "deterministic"
|
|
320
|
+
score += _METHOD_PRECISION.get(method, 0.3)
|
|
321
|
+
|
|
322
|
+
# 2. Severity bonus
|
|
323
|
+
score += _SEVERITY_BONUS.get(
|
|
324
|
+
f.severity.value if hasattr(f.severity, "value") else str(f.severity), 0.0
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# 3. Multi-agent confirmation
|
|
328
|
+
confirm_count = len(cf.confirmed_by)
|
|
329
|
+
score += min(confirm_count * 0.1, 0.3)
|
|
330
|
+
|
|
331
|
+
# 4. Location precision
|
|
332
|
+
if f.line > 0:
|
|
333
|
+
score += 0.1
|
|
334
|
+
|
|
335
|
+
# 5. Known false-positive penalty
|
|
336
|
+
fp_key = (f.file, f.type, f.line)
|
|
337
|
+
if fp_key in self._known_fps:
|
|
338
|
+
score -= self.fp_penalty
|
|
339
|
+
|
|
340
|
+
# 6. Ambiguity penalty
|
|
341
|
+
if not f.code_snippet:
|
|
342
|
+
score -= 0.2
|
|
343
|
+
|
|
344
|
+
# 7. Heuristic FP detection — catch common false-positive patterns
|
|
345
|
+
from patchi.core.security.ai_validator import heuristic_pre_filter
|
|
346
|
+
|
|
347
|
+
hp = heuristic_pre_filter(f)
|
|
348
|
+
if hp:
|
|
349
|
+
_, penalty = hp
|
|
350
|
+
score -= penalty * 0.5 # partial penalty (full penalty via AIValidator)
|
|
351
|
+
|
|
352
|
+
# 8. Noise penalty — findings the NoiseFilter capped (tests,
|
|
353
|
+
# lockfiles, generated code, docs) carry ``noise_category``.
|
|
354
|
+
# They may legitimately look dangerous; they must never score
|
|
355
|
+
# high enough to auto-defend.
|
|
356
|
+
if getattr(f, "noise_category", None):
|
|
357
|
+
score -= self.noise_penalty
|
|
358
|
+
|
|
359
|
+
# Clamp heuristic score first…
|
|
360
|
+
score = max(0.0, min(1.0, score))
|
|
361
|
+
|
|
362
|
+
# 9. AI confidence calibration (optional): blend model verdict.
|
|
363
|
+
if ai_confidence is not None and self.ai_weight > 0.0:
|
|
364
|
+
w = min(max(self.ai_weight, 0.0), 1.0)
|
|
365
|
+
score = (1.0 - w) * score + w * max(0.0, min(1.0, ai_confidence))
|
|
366
|
+
|
|
367
|
+
return max(0.0, min(1.0, score))
|
|
368
|
+
|
|
369
|
+
def _assign_tier(self, score: float) -> str:
|
|
370
|
+
if score >= 0.7:
|
|
371
|
+
return "high"
|
|
372
|
+
if score >= 0.4:
|
|
373
|
+
return "medium"
|
|
374
|
+
return "low"
|
|
375
|
+
|
|
376
|
+
def _assign_routing(self, tier: str, cf: CorrelatedFinding) -> str:
|
|
377
|
+
confirm_count = len(cf.confirmed_by)
|
|
378
|
+
f = cf.finding
|
|
379
|
+
|
|
380
|
+
# Hard consensus floor: too few agents => drop entirely (noise kill).
|
|
381
|
+
if self.min_agents_to_keep > 0 and confirm_count < self.min_agents_to_keep:
|
|
382
|
+
return "discard"
|
|
383
|
+
|
|
384
|
+
# Known-FP auto-discard: previously rejected => stay rejected.
|
|
385
|
+
if self.fp_auto_discard:
|
|
386
|
+
fp_key = (f.file, f.type, f.line)
|
|
387
|
+
if fp_key in self._known_fps:
|
|
388
|
+
return "discard"
|
|
389
|
+
|
|
390
|
+
if tier == "high":
|
|
391
|
+
# Structural noise block: capped files never auto-defend,
|
|
392
|
+
# no matter how high they score.
|
|
393
|
+
if getattr(cf.finding, "noise_category", None):
|
|
394
|
+
return "ai_analyze"
|
|
395
|
+
# Consensus ceiling: single-agent regex hits can't auto-defend.
|
|
396
|
+
if confirm_count < self.min_agents_for_defend:
|
|
397
|
+
return "ai_analyze"
|
|
398
|
+
return "defend"
|
|
399
|
+
if tier == "medium":
|
|
400
|
+
return "ai_analyze"
|
|
401
|
+
|
|
402
|
+
sev = f.severity.value if hasattr(f.severity, "value") else str(f.severity)
|
|
403
|
+
if sev in ("critical", "high"):
|
|
404
|
+
return "human_review"
|
|
405
|
+
return "discard"
|
|
406
|
+
|
|
407
|
+
def _routing_reason(self, routing: str, score: float, cf: CorrelatedFinding) -> str:
|
|
408
|
+
n_agents = len(cf.confirmed_by)
|
|
409
|
+
reasons = {
|
|
410
|
+
"defend": f"High confidence ({score:.2f}) — {n_agents} agent(s) confirmed",
|
|
411
|
+
"ai_analyze": (
|
|
412
|
+
f"Confidence {score:.2f} with {n_agents} agent(s) — needs AI confirmation "
|
|
413
|
+
f"(consensus floor: {self.min_agents_for_defend})"
|
|
414
|
+
if n_agents < self.min_agents_for_defend
|
|
415
|
+
else f"Medium confidence ({score:.2f}) — needs AI confirmation"
|
|
416
|
+
),
|
|
417
|
+
"human_review": (
|
|
418
|
+
f"Low confidence ({score:.2f}) but severity is "
|
|
419
|
+
f"{cf.finding.severity} — needs human review"
|
|
420
|
+
),
|
|
421
|
+
"discard": f"Low confidence ({score:.2f}) or below agent consensus — filtered",
|
|
422
|
+
}
|
|
423
|
+
return reasons.get(routing, f"Routed to {routing} (score={score:.2f})")
|