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,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BrainWatcher — incremental Layered Brain updates (Phase 1 of the super-agent plan).
|
|
3
|
+
|
|
4
|
+
The full :func:`build_layers` rebuild is cheap, but re-summarising every module
|
|
5
|
+
on every scan is wasteful for large repos. The BrainWatcher reuses cached layer
|
|
6
|
+
summaries for modules whose files did **not** change, and recomputes *only* the
|
|
7
|
+
layers touched by a file change — propagating the invalidation up one level
|
|
8
|
+
(module → subsystem → project).
|
|
9
|
+
|
|
10
|
+
Change detection is content-hash based (mirrors the scanner's own per-file cache,
|
|
11
|
+
so unchanged files are never re-parsed either). The watcher also records *which*
|
|
12
|
+
layers are stale so a downstream scan can skip or rebuild them selectively.
|
|
13
|
+
|
|
14
|
+
This module is additive: it sits on top of :mod:`layered_brain` and never alters
|
|
15
|
+
the scan pipeline's behaviour when no prior state exists.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from collections.abc import Iterable
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from patchi.core.brain.framework import StackInfo
|
|
27
|
+
from patchi.core.brain.import_graph import ImportGraph
|
|
28
|
+
from patchi.core.brain.route_mapper import RouteInfo
|
|
29
|
+
from patchi.core.brain.scanner import FileInfo
|
|
30
|
+
|
|
31
|
+
# ── Change tracking ────────────────────────────────────────────────────────────
|
|
32
|
+
import logging
|
|
33
|
+
|
|
34
|
+
from patchi.core.brain.layered_brain import (
|
|
35
|
+
Layer,
|
|
36
|
+
_classify_subsystem,
|
|
37
|
+
_dominant_language,
|
|
38
|
+
_module_of,
|
|
39
|
+
_module_summary,
|
|
40
|
+
_populate_dependencies,
|
|
41
|
+
)
|
|
42
|
+
from patchi.core.brain.layered_brain import build_layers as _full_build
|
|
43
|
+
|
|
44
|
+
_log = logging.getLogger("patchi.brain.brain_watcher")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ChangeSet:
|
|
49
|
+
"""The set of files that changed between two snapshots."""
|
|
50
|
+
|
|
51
|
+
changed: set[str] = field(default_factory=set)
|
|
52
|
+
added: set[str] = field(default_factory=set)
|
|
53
|
+
removed: set[str] = field(default_factory=set)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def any(self) -> bool:
|
|
57
|
+
return bool(self.changed or self.added or self.removed)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def all_paths(self) -> set[str]:
|
|
61
|
+
return self.changed | self.added | self.removed
|
|
62
|
+
|
|
63
|
+
def to_dict(self) -> dict:
|
|
64
|
+
return {
|
|
65
|
+
"changed": sorted(self.changed),
|
|
66
|
+
"added": sorted(self.added),
|
|
67
|
+
"removed": sorted(self.removed),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_dict(cls, d: dict) -> ChangeSet:
|
|
72
|
+
return cls(
|
|
73
|
+
changed=set(d.get("changed", [])),
|
|
74
|
+
added=set(d.get("added", [])),
|
|
75
|
+
removed=set(d.get("removed", [])),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def file_snapshot(file_infos: Iterable[FileInfo], root: Path) -> dict[str, str]:
|
|
80
|
+
"""Map each file path to its content hash for change detection."""
|
|
81
|
+
from patchi.core.brain.scanner import _file_content_hash
|
|
82
|
+
|
|
83
|
+
root = Path(root)
|
|
84
|
+
snap: dict[str, str] = {}
|
|
85
|
+
for fi in file_infos:
|
|
86
|
+
try:
|
|
87
|
+
snap[fi.path] = _file_content_hash(root / fi.path)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
_log.warning("file_snapshot failed: %s", e)
|
|
90
|
+
snap[fi.path] = ""
|
|
91
|
+
return snap
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def diff_snapshots(old: dict[str, str], new: dict[str, str]) -> ChangeSet:
|
|
95
|
+
"""Compare two file-hash snapshots and return what changed."""
|
|
96
|
+
changed = {p for p in new if p in old and old[p] != new[p]}
|
|
97
|
+
added = {p for p in new if p not in old}
|
|
98
|
+
removed = {p for p in old if p not in new}
|
|
99
|
+
return ChangeSet(changed=changed, added=added, removed=removed)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ── Stale marking ──────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def mark_layers_stale(layers: dict[str, Layer], names: Iterable[str]) -> None:
|
|
106
|
+
"""Flag the given layer names (and nothing else) as stale/invalidated."""
|
|
107
|
+
name_set = set(names)
|
|
108
|
+
for name, layer in layers.items():
|
|
109
|
+
layer.stale = name in name_set
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def clear_stale(layers: dict[str, Layer]) -> None:
|
|
113
|
+
"""Clear the stale flag on every layer."""
|
|
114
|
+
for layer in layers.values():
|
|
115
|
+
layer.stale = False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def affected_layers(changes: ChangeSet, old_layers: dict[str, Layer]) -> set[str]:
|
|
119
|
+
"""Return every layer touched by a change, walking the parent chain up.
|
|
120
|
+
|
|
121
|
+
A changed file invalidates its module (L1), its subsystem (L2), and the
|
|
122
|
+
project layer (L4). Anything not in this set can be left untouched.
|
|
123
|
+
"""
|
|
124
|
+
names: set[str] = set()
|
|
125
|
+
for p in changes.all_paths:
|
|
126
|
+
cur = _module_of(p)
|
|
127
|
+
while cur is not None:
|
|
128
|
+
names.add(cur)
|
|
129
|
+
layer = old_layers.get(cur)
|
|
130
|
+
cur = layer.parent_layer if layer else None
|
|
131
|
+
return names
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ── Incremental rebuild ────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def update_layers(
|
|
138
|
+
old_layers: dict[str, Layer],
|
|
139
|
+
file_infos: list[FileInfo],
|
|
140
|
+
graph: ImportGraph | None = None,
|
|
141
|
+
routes: list[RouteInfo] | None = None,
|
|
142
|
+
stack: StackInfo | None = None,
|
|
143
|
+
changes: ChangeSet | None = None,
|
|
144
|
+
) -> tuple[dict[str, Layer], list[str]]:
|
|
145
|
+
"""Incrementally rebuild layers, reusing cached summaries where possible.
|
|
146
|
+
|
|
147
|
+
Unchanged modules keep their exact previous :class:`Layer` object (no
|
|
148
|
+
re-summarisation). Only modules touched by ``changes`` (plus their
|
|
149
|
+
subsystem/project ancestors) are recomputed. Returns the new layer dict and
|
|
150
|
+
the list of layer names that were actually rebuilt.
|
|
151
|
+
"""
|
|
152
|
+
routes = routes or []
|
|
153
|
+
fi_map = {fi.path: fi for fi in file_infos}
|
|
154
|
+
|
|
155
|
+
modules: dict[str, list[str]] = {}
|
|
156
|
+
for fi in file_infos:
|
|
157
|
+
modules.setdefault(_module_of(fi.path), []).append(fi.path)
|
|
158
|
+
|
|
159
|
+
changed_modules: set[str] = set()
|
|
160
|
+
if changes is not None:
|
|
161
|
+
for p in list(changes.changed) + list(changes.added) + list(changes.removed):
|
|
162
|
+
changed_modules.add(_module_of(p))
|
|
163
|
+
|
|
164
|
+
new_layers: dict[str, Layer] = {}
|
|
165
|
+
rebuilt: list[str] = []
|
|
166
|
+
|
|
167
|
+
# ── Level 1: modules (reuse cached summary unless changed) ──────────────────
|
|
168
|
+
for mod, files in modules.items():
|
|
169
|
+
if mod in old_layers and mod not in changed_modules:
|
|
170
|
+
new_layers[mod] = old_layers[mod] # no-op: reuse cached summary
|
|
171
|
+
else:
|
|
172
|
+
summary, purpose, api = _module_summary(mod, files, fi_map, routes)
|
|
173
|
+
layer = Layer(
|
|
174
|
+
name=mod,
|
|
175
|
+
level=1,
|
|
176
|
+
path=mod,
|
|
177
|
+
language=_dominant_language(files, fi_map),
|
|
178
|
+
summary=summary,
|
|
179
|
+
purpose=purpose,
|
|
180
|
+
files=sorted(files),
|
|
181
|
+
public_api=sorted(set(api)),
|
|
182
|
+
)
|
|
183
|
+
layer.validity_hash = layer.compute_hash()
|
|
184
|
+
new_layers[mod] = layer
|
|
185
|
+
rebuilt.append(mod)
|
|
186
|
+
|
|
187
|
+
# ── Level 2: subsystems (recompute only if a child module changed) ───────────
|
|
188
|
+
subsystem_modules: dict[str, list[str]] = {}
|
|
189
|
+
module_to_subsystem: dict[str, str] = {}
|
|
190
|
+
for mod in modules:
|
|
191
|
+
sub = _classify_subsystem(mod)
|
|
192
|
+
subsystem_modules.setdefault(sub, []).append(mod)
|
|
193
|
+
module_to_subsystem[mod] = sub
|
|
194
|
+
|
|
195
|
+
changed_subsystems = {
|
|
196
|
+
module_to_subsystem[m] for m in changed_modules if m in module_to_subsystem
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for sub, mods in subsystem_modules.items():
|
|
200
|
+
all_children_present = all(m in new_layers for m in mods)
|
|
201
|
+
if sub in old_layers and sub not in changed_subsystems and all_children_present:
|
|
202
|
+
new_layers[sub] = old_layers[sub] # no-op: reuse cached summary
|
|
203
|
+
else:
|
|
204
|
+
sub_files: list[str] = []
|
|
205
|
+
sub_api: list[str] = []
|
|
206
|
+
for m in mods:
|
|
207
|
+
sub_files.extend(new_layers[m].files)
|
|
208
|
+
sub_api.extend(new_layers[m].public_api)
|
|
209
|
+
sub_summary = (
|
|
210
|
+
f"Subsystem '{sub}' — {len(mods)} module(s), "
|
|
211
|
+
f"{len(sub_files)} file(s). Modules: {', '.join(mods[:8])}."
|
|
212
|
+
)
|
|
213
|
+
sub_layer = Layer(
|
|
214
|
+
name=sub,
|
|
215
|
+
level=2,
|
|
216
|
+
path=sub,
|
|
217
|
+
language=_dominant_language(sub_files, fi_map),
|
|
218
|
+
summary=sub_summary,
|
|
219
|
+
purpose=f"Groups {len(mods)} module(s) under the '{sub}' concern.",
|
|
220
|
+
files=sorted(set(sub_files)),
|
|
221
|
+
child_layers=sorted(mods),
|
|
222
|
+
public_api=sorted(set(sub_api)),
|
|
223
|
+
)
|
|
224
|
+
sub_layer.validity_hash = sub_layer.compute_hash()
|
|
225
|
+
new_layers[sub] = sub_layer
|
|
226
|
+
rebuilt.append(sub)
|
|
227
|
+
for m in mods:
|
|
228
|
+
new_layers[m].parent_layer = sub
|
|
229
|
+
|
|
230
|
+
# ── Inter-layer dependencies (cheap; derived from the existing graph) ────────
|
|
231
|
+
if graph is not None:
|
|
232
|
+
_populate_dependencies(new_layers, graph, module_to_subsystem)
|
|
233
|
+
|
|
234
|
+
# ── Level 4: project (recompute only if the module set changed) ──────────────
|
|
235
|
+
old_module_names = {n for n, lay in old_layers.items() if lay.level == 1}
|
|
236
|
+
module_set_changed = (set(modules) != old_module_names) or bool(changed_modules)
|
|
237
|
+
if "__project__" in old_layers and not module_set_changed:
|
|
238
|
+
new_layers["__project__"] = old_layers["__project__"] # no-op
|
|
239
|
+
else:
|
|
240
|
+
fw_names = (
|
|
241
|
+
", ".join(f.name for f in stack.frameworks[:5])
|
|
242
|
+
if stack and stack.frameworks
|
|
243
|
+
else "unknown"
|
|
244
|
+
)
|
|
245
|
+
runtime = stack.runtime if stack else "unknown"
|
|
246
|
+
project_summary = (
|
|
247
|
+
f"Project — {len(file_infos)} file(s) across {len(modules)} module(s) "
|
|
248
|
+
f"and {len(subsystem_modules)} subsystem(s). "
|
|
249
|
+
f"Stack: {fw_names} (runtime: {runtime})."
|
|
250
|
+
)
|
|
251
|
+
project_layer = Layer(
|
|
252
|
+
name="__project__",
|
|
253
|
+
level=4,
|
|
254
|
+
path="",
|
|
255
|
+
language=runtime,
|
|
256
|
+
summary=project_summary,
|
|
257
|
+
purpose="The whole project — top of the layer hierarchy.",
|
|
258
|
+
files=sorted(fi.path for fi in file_infos),
|
|
259
|
+
child_layers=sorted(subsystem_modules.keys()),
|
|
260
|
+
public_api=sorted({a for lay in new_layers.values() for a in lay.public_api}),
|
|
261
|
+
)
|
|
262
|
+
project_layer.validity_hash = project_layer.compute_hash()
|
|
263
|
+
new_layers["__project__"] = project_layer
|
|
264
|
+
rebuilt.append("__project__")
|
|
265
|
+
for sub in subsystem_modules:
|
|
266
|
+
new_layers[sub].parent_layer = "__project__"
|
|
267
|
+
|
|
268
|
+
clear_stale(new_layers)
|
|
269
|
+
return new_layers, rebuilt
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def build_or_update(
|
|
273
|
+
old_layers: dict[str, Layer],
|
|
274
|
+
file_infos: list[FileInfo],
|
|
275
|
+
graph: ImportGraph | None = None,
|
|
276
|
+
routes: list[RouteInfo] | None = None,
|
|
277
|
+
stack: StackInfo | None = None,
|
|
278
|
+
old_snapshot: dict[str, str] | None = None,
|
|
279
|
+
root: Path | None = None,
|
|
280
|
+
) -> tuple[dict[str, Layer], list[str], ChangeSet]:
|
|
281
|
+
"""High-level entry: reuse cached layers when nothing changed, else rebuild.
|
|
282
|
+
|
|
283
|
+
Returns ``(layers, rebuilt_names, changes)``. When there is prior state and
|
|
284
|
+
no file changed, the existing layers are returned verbatim (true no-op) and
|
|
285
|
+
``rebuilt_names`` is empty.
|
|
286
|
+
"""
|
|
287
|
+
new_snapshot = file_snapshot(file_infos, root) if root is not None else {}
|
|
288
|
+
changes = (
|
|
289
|
+
diff_snapshots(old_snapshot or {}, new_snapshot)
|
|
290
|
+
if old_snapshot is not None
|
|
291
|
+
else ChangeSet()
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if old_layers and not changes.any:
|
|
295
|
+
return old_layers, [], changes
|
|
296
|
+
if old_layers and changes.any:
|
|
297
|
+
layers, rebuilt = update_layers(
|
|
298
|
+
old_layers, file_infos, graph, routes, stack, changes=changes
|
|
299
|
+
)
|
|
300
|
+
return layers, rebuilt, changes
|
|
301
|
+
# No prior state — full build.
|
|
302
|
+
layers = _full_build(file_infos, graph, routes, stack)
|
|
303
|
+
return layers, list(layers.keys()), changes
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# ── Watcher (stateful helper around the above) ─────────────────────────────────
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class BrainWatcher:
|
|
310
|
+
"""Stateful helper: tracks file-snapshot history in memory and reports drift."""
|
|
311
|
+
|
|
312
|
+
def __init__(self, root: Path):
|
|
313
|
+
self.root = Path(root)
|
|
314
|
+
|
|
315
|
+
# ── snapshot persistence ────────────────────────────────────────────────────
|
|
316
|
+
def load_snapshot(self) -> dict[str, str]:
|
|
317
|
+
from patchi.core import memory as mem
|
|
318
|
+
|
|
319
|
+
data = mem.get_layers(self.root) or {}
|
|
320
|
+
return data.get("file_snapshot", {})
|
|
321
|
+
|
|
322
|
+
def save_snapshot(self, snapshot: dict[str, str]) -> None:
|
|
323
|
+
from patchi.core import memory as mem
|
|
324
|
+
|
|
325
|
+
data = mem.get_layers(self.root) or {}
|
|
326
|
+
data["file_snapshot"] = snapshot
|
|
327
|
+
mem.save_layers(data, self.root)
|
|
328
|
+
|
|
329
|
+
# ── diffing ──────────────────────────────────────────────────────────────────
|
|
330
|
+
def diff(self, old: dict[str, str], new: dict[str, str]) -> ChangeSet:
|
|
331
|
+
return diff_snapshots(old, new)
|
|
332
|
+
|
|
333
|
+
def affected(self, changes: ChangeSet, old_layers: dict[str, Layer]) -> set[str]:
|
|
334
|
+
return affected_layers(changes, old_layers)
|
|
335
|
+
|
|
336
|
+
def mark_stale(self, layers: dict[str, Layer], changes: ChangeSet) -> set[str]:
|
|
337
|
+
"""Mark the layers touched by ``changes`` as stale and return their names."""
|
|
338
|
+
names = affected_layers(changes, layers)
|
|
339
|
+
mark_layers_stale(layers, names)
|
|
340
|
+
return names
|