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,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`p learn` — Learning Brain (Phase 5).
|
|
3
|
+
|
|
4
|
+
Shows what Patchi has learned from your accept/reject patterns and the reusable
|
|
5
|
+
fix patterns it has captured, lets you re-derive conventions from a fresh scan,
|
|
6
|
+
and lets you reset that learning.
|
|
7
|
+
|
|
8
|
+
p learn — show learned accept/reject patterns + which fix types are skipped
|
|
9
|
+
p learn patterns — show captured fix patterns
|
|
10
|
+
p learn --force — re-derive conventions/patterns from the current project
|
|
11
|
+
p learn --reset — forget all learned patterns for this project
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from rich.table import Table
|
|
20
|
+
|
|
21
|
+
from patchi.cli.console import con
|
|
22
|
+
from patchi.core.brain import learning
|
|
23
|
+
from patchi.core.config import require_project_root
|
|
24
|
+
|
|
25
|
+
_log = logging.getLogger("patchi.cli.learn_cmd")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run(
|
|
29
|
+
force: bool = False,
|
|
30
|
+
sub: str | None = None,
|
|
31
|
+
reset: bool = False,
|
|
32
|
+
root: Path | None = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
try:
|
|
35
|
+
r = root or require_project_root()
|
|
36
|
+
except RuntimeError as e:
|
|
37
|
+
con.print(f"[red]{e}[/red]")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
if reset:
|
|
41
|
+
path = r / ".patchi" / "learning.json"
|
|
42
|
+
if path.exists():
|
|
43
|
+
try:
|
|
44
|
+
path.unlink()
|
|
45
|
+
con.print("[#4ADE80]✓[/#4ADE80] Learning data reset for this project.")
|
|
46
|
+
except Exception as e:
|
|
47
|
+
_log.warning("run failed: %s", e)
|
|
48
|
+
con.print("[red]Could not reset learning data.[/red]")
|
|
49
|
+
else:
|
|
50
|
+
con.print("[dim]Nothing to reset.[/dim]")
|
|
51
|
+
con.print()
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if sub == "patterns":
|
|
55
|
+
_show_patterns(r)
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
if force:
|
|
59
|
+
_relearn(r)
|
|
60
|
+
|
|
61
|
+
summary = learning.get_summary(r)
|
|
62
|
+
has_data = summary["acceptances"] or summary["rejections"] or summary.get("agent_trust")
|
|
63
|
+
if not has_data:
|
|
64
|
+
con.print()
|
|
65
|
+
con.print(
|
|
66
|
+
"[dim]No learning data yet. Patchi learns from the fixes you accept or reject "
|
|
67
|
+
"(e.g. [bold]p auto <files> --reject <type>[/bold]).[/dim]"
|
|
68
|
+
)
|
|
69
|
+
con.print()
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
|
|
73
|
+
table.add_column("Fix type", width=20)
|
|
74
|
+
table.add_column("Accepted", width=10)
|
|
75
|
+
table.add_column("Rejected", width=10)
|
|
76
|
+
table.add_column("Status", width=14)
|
|
77
|
+
types = list(summary["acceptances"].keys()) + list(summary["rejections"].keys())
|
|
78
|
+
for t in sorted(set(types)):
|
|
79
|
+
acc = summary["acceptances"].get(t, 0)
|
|
80
|
+
rej = summary["rejections"].get(t, 0)
|
|
81
|
+
status = "[yellow]skipped[/yellow]" if t in summary["skip_types"] else "[dim]active[/dim]"
|
|
82
|
+
table.add_row(t, str(acc), str(rej), status)
|
|
83
|
+
|
|
84
|
+
con.print()
|
|
85
|
+
con.print(table)
|
|
86
|
+
if summary.get("agent_trust"):
|
|
87
|
+
con.print()
|
|
88
|
+
con.print("[dim]Agent trust:[/dim]")
|
|
89
|
+
for agent, score in summary["agent_trust"].items():
|
|
90
|
+
con.print(f" {agent}: {score}")
|
|
91
|
+
con.print()
|
|
92
|
+
con.print(
|
|
93
|
+
"[dim]Run [bold]p learn patterns[/bold] to see captured fix strategies, "
|
|
94
|
+
"[bold]p learn --reset[/bold] to forget.[/dim]"
|
|
95
|
+
)
|
|
96
|
+
con.print()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _show_patterns(r: Path) -> None:
|
|
100
|
+
patterns = learning.list_fix_patterns(r)
|
|
101
|
+
if not patterns:
|
|
102
|
+
con.print()
|
|
103
|
+
con.print("[dim]No fix patterns captured yet.[/dim]")
|
|
104
|
+
con.print()
|
|
105
|
+
return
|
|
106
|
+
table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
|
|
107
|
+
table.add_column("Type", width=18)
|
|
108
|
+
table.add_column("File", width=30)
|
|
109
|
+
table.add_column("Strategy", width=22)
|
|
110
|
+
table.add_column("Summary", width=34)
|
|
111
|
+
for p in patterns:
|
|
112
|
+
table.add_row(
|
|
113
|
+
p.get("finding_type", ""),
|
|
114
|
+
p.get("file_pattern", ""),
|
|
115
|
+
p.get("fix_strategy", ""),
|
|
116
|
+
p.get("fix_summary", ""),
|
|
117
|
+
)
|
|
118
|
+
con.print()
|
|
119
|
+
con.print(table)
|
|
120
|
+
con.print()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _relearn(r: Path) -> None:
|
|
124
|
+
"""Re-derive conventions/patterns from a fresh proactive scan of the project."""
|
|
125
|
+
try:
|
|
126
|
+
from patchi.core.brain.proactive import ProactiveAgent
|
|
127
|
+
from patchi.core.brain.scanner import FileScanner
|
|
128
|
+
|
|
129
|
+
scanner = FileScanner(r)
|
|
130
|
+
file_infos = scanner.scan()
|
|
131
|
+
agent = ProactiveAgent(r)
|
|
132
|
+
captured = 0
|
|
133
|
+
for fi in file_infos:
|
|
134
|
+
fixes = agent.analyze_change([fi.path], file_infos, None, None, include_format=False)
|
|
135
|
+
for f in fixes:
|
|
136
|
+
learning.record_fix_pattern(f.fix_type, f.file, "auto", f.description, r)
|
|
137
|
+
captured += 1
|
|
138
|
+
con.print(f"[dim]Re-learned {captured} fix pattern(s) from the current project.[/dim]")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
con.print(f"[dim]Re-learn skipped: {e}[/dim]")
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
p link — hybrid linking for non-monorepo (auto discovery tag + manual).
|
|
3
|
+
|
|
4
|
+
p link status — show linking config + suggestion
|
|
5
|
+
p link add --frontend ./ --backend ../backend --frontend-url http://localhost:3000 --backend-url http://localhost:5000
|
|
6
|
+
p link confirm — confirm auto suggestion
|
|
7
|
+
p link remove — remove linking
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
|
|
17
|
+
from patchi.cli.console import con
|
|
18
|
+
from patchi.core.config import require_project_root
|
|
19
|
+
|
|
20
|
+
_CFG = ".patchi/config.json"
|
|
21
|
+
_SUG = ".patchi/linking_suggestion.json"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_cfg(root: Path) -> dict:
|
|
25
|
+
p = root / _CFG
|
|
26
|
+
if p.exists():
|
|
27
|
+
try:
|
|
28
|
+
return json.loads(p.read_text(encoding="utf-8"))
|
|
29
|
+
except Exception:
|
|
30
|
+
return {}
|
|
31
|
+
return {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _save_cfg(root: Path, cfg: dict) -> None:
|
|
35
|
+
p = root / _CFG
|
|
36
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
tmp = p.with_suffix(".tmp")
|
|
38
|
+
tmp.write_text(json.dumps(cfg, indent=2), encoding="utf-8")
|
|
39
|
+
tmp.replace(p)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def run(action: str | None = None, root: Path | None = None, **kwargs) -> None:
|
|
43
|
+
try:
|
|
44
|
+
r = root or require_project_root()
|
|
45
|
+
except RuntimeError as e:
|
|
46
|
+
con.print(f"[red]{e}[/red]")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if action in (None, "status"):
|
|
50
|
+
cfg = _load_cfg(r)
|
|
51
|
+
link = cfg.get("linking")
|
|
52
|
+
sug_path = r / _SUG
|
|
53
|
+
if link:
|
|
54
|
+
con.print(Panel(f"[bold]Linking:[/bold] {link}", border_style="#4ADE80"))
|
|
55
|
+
elif sug_path.exists():
|
|
56
|
+
try:
|
|
57
|
+
sug = json.loads(sug_path.read_text(encoding="utf-8"))
|
|
58
|
+
con.print(Panel(f"[yellow]Suggestion:[/yellow] frontend {sug.get('frontend')} ↔ backend {sug.get('backend')}\nRun [bold]p link confirm[/bold] to activate", border_style="#FF8C42"))
|
|
59
|
+
except Exception:
|
|
60
|
+
con.print("[dim]No linking suggestion[/dim]")
|
|
61
|
+
else:
|
|
62
|
+
con.print("[dim]No linking configured — monorepo auto-detected or run p link add[/dim]")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
if action == "add":
|
|
66
|
+
|
|
67
|
+
# Called via registry: kwargs has frontend, backend, frontend_url, backend_url
|
|
68
|
+
front = kwargs.get("frontend") or "./"
|
|
69
|
+
back = kwargs.get("backend")
|
|
70
|
+
furl = kwargs.get("frontend_url") or "http://localhost:3000"
|
|
71
|
+
burl = kwargs.get("backend_url") or "http://localhost:5000"
|
|
72
|
+
if not back:
|
|
73
|
+
con.print("[red]--backend required (e.g. ../backend)[/red]")
|
|
74
|
+
return
|
|
75
|
+
cfg = _load_cfg(r)
|
|
76
|
+
cfg["linking"] = {"mode": "separate", "frontend": front, "backend": back, "frontend_url": furl, "backend_url": burl, "confirmed": True}
|
|
77
|
+
_save_cfg(r, cfg)
|
|
78
|
+
con.print(f"[green]Linking added:[/green] {front} ↔ {back}")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
if action == "confirm":
|
|
82
|
+
sug_path = r / _SUG
|
|
83
|
+
if not sug_path.exists():
|
|
84
|
+
con.print("[yellow]No suggestion to confirm — run p scan first or p link add[/yellow]")
|
|
85
|
+
return
|
|
86
|
+
try:
|
|
87
|
+
sug = json.loads(sug_path.read_text(encoding="utf-8"))
|
|
88
|
+
cfg = _load_cfg(r)
|
|
89
|
+
cfg["linking"] = {**sug, "mode": "separate", "confirmed": True}
|
|
90
|
+
_save_cfg(r, cfg)
|
|
91
|
+
sug_path.unlink(missing_ok=True)
|
|
92
|
+
con.print(f"[green]Link confirmed:[/green] {sug}")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
con.print(f"[red]Confirm failed: {e}[/red]")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
if action == "remove":
|
|
98
|
+
cfg = _load_cfg(r)
|
|
99
|
+
cfg.pop("linking", None)
|
|
100
|
+
_save_cfg(r, cfg)
|
|
101
|
+
con.print("[dim]Linking removed[/dim]")
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
con.print(f"[yellow]Unknown link action {action!r}[/yellow]")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`p log` — Git changelog.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
p log [--since HEAD~N] — show recent commits with changed files
|
|
6
|
+
p log --since HEAD~5 — show last 5 commits
|
|
7
|
+
p log --since 2026-01-01 — show commits since a date
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from patchi.cli.console import con
|
|
17
|
+
from patchi.core.brain.git_aware import changelog
|
|
18
|
+
from patchi.core.config import require_project_root
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run(since: str = "HEAD~10", root: Path | None = None) -> None:
|
|
22
|
+
try:
|
|
23
|
+
r = root or require_project_root()
|
|
24
|
+
except RuntimeError as e:
|
|
25
|
+
con.print(f"[red]{e}[/red]")
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
entries = changelog(r, since)
|
|
29
|
+
|
|
30
|
+
if not entries:
|
|
31
|
+
con.print("[dim]No git history found or not a git repository.[/dim]")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
|
|
35
|
+
table.add_column("Hash", style="bold #F2EDD6", width=10)
|
|
36
|
+
table.add_column("Date", width=12)
|
|
37
|
+
table.add_column("Author", width=18)
|
|
38
|
+
table.add_column("Message", style="dim", width=50)
|
|
39
|
+
table.add_column("Files", style="dim", width=20)
|
|
40
|
+
|
|
41
|
+
for entry in entries:
|
|
42
|
+
files_str = ", ".join(entry.get("files", [])[:3])
|
|
43
|
+
if len(entry.get("files", [])) > 3:
|
|
44
|
+
files_str += f" +{len(entry['files']) - 3} more"
|
|
45
|
+
table.add_row(
|
|
46
|
+
entry.get("hash", "?"),
|
|
47
|
+
entry.get("date", ""),
|
|
48
|
+
entry.get("author", "")[:18],
|
|
49
|
+
entry.get("summary", "")[:50],
|
|
50
|
+
files_str,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
con.print()
|
|
54
|
+
con.print(table)
|
|
55
|
+
con.print()
|
|
56
|
+
con.print(f"[dim]Total: {len(entries)} entries (since {since})[/dim]")
|
|
57
|
+
con.print()
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
from rich.prompt import Confirm, Prompt
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.text import Text
|
|
7
|
+
|
|
8
|
+
from patchi.cli.console import con
|
|
9
|
+
from patchi.core import memory as mem
|
|
10
|
+
from patchi.core.config import require_project_root
|
|
11
|
+
from patchi.core.constants import MemoryCategory
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
`p memory` — view and manage Patchi's memory.
|
|
15
|
+
|
|
16
|
+
Subcommands:
|
|
17
|
+
p memory — show all categories with counts
|
|
18
|
+
p memory show <category> — show full data for a category
|
|
19
|
+
p memory delete <category>— clear a category (with warning)
|
|
20
|
+
p memory delete all — full reset (requires CONFIRM)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def run_show_all(root: Path | None = None) -> None:
|
|
25
|
+
"""p memory — show all categories."""
|
|
26
|
+
try:
|
|
27
|
+
r = root or require_project_root()
|
|
28
|
+
except RuntimeError as e:
|
|
29
|
+
con.print(f"[red]{e}[/red]")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
data = mem.summary(r)
|
|
33
|
+
|
|
34
|
+
table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
|
|
35
|
+
table.add_column("Category", style="bold #F2EDD6", width=16)
|
|
36
|
+
table.add_column("Items", style="#B8A898")
|
|
37
|
+
table.add_column("Description", style="dim")
|
|
38
|
+
|
|
39
|
+
descriptions = {
|
|
40
|
+
"brain": "Brain's understanding of your app",
|
|
41
|
+
"patches": "Applied fix history with diffs",
|
|
42
|
+
"failed": "Patches that failed and were rolled back",
|
|
43
|
+
"scans": "Last results from every scanner agent",
|
|
44
|
+
"issues": "Known unfixed issues",
|
|
45
|
+
"restrictions": "No-touch zones and scan-only paths",
|
|
46
|
+
"tokens": "Dev access token names (values never stored)",
|
|
47
|
+
"layers": "Layered brain (Pillar 1) — module/subsytem/project map",
|
|
48
|
+
"charter": "Project guard-rail charter (Pillar 2)",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for category, info in data.items():
|
|
52
|
+
if "count" in info:
|
|
53
|
+
count_str = str(info["count"])
|
|
54
|
+
elif "keys" in info:
|
|
55
|
+
count_str = f"{len(info['keys'])} sections"
|
|
56
|
+
else:
|
|
57
|
+
count_str = "—"
|
|
58
|
+
|
|
59
|
+
table.add_row(category, count_str, descriptions.get(category, ""))
|
|
60
|
+
|
|
61
|
+
con.print()
|
|
62
|
+
con.print(
|
|
63
|
+
Panel(table, title="[bold #C8621A]Patchi Memory[/bold #C8621A]", border_style="#2A3D28")
|
|
64
|
+
)
|
|
65
|
+
con.print()
|
|
66
|
+
con.print(
|
|
67
|
+
"[dim]Commands: [bold]p memory show <category>[/bold] · [bold]p memory delete <category>[/bold][/dim]"
|
|
68
|
+
)
|
|
69
|
+
con.print()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def run_show(category_str: str, root: Path | None = None) -> None:
|
|
73
|
+
"""p memory show <category>"""
|
|
74
|
+
import json
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
r = root or require_project_root()
|
|
78
|
+
except RuntimeError as e:
|
|
79
|
+
con.print(f"[red]{e}[/red]")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# Special-case: 'scans' gets a formatted view instead of raw JSON
|
|
83
|
+
if category_str.lower() in ("scans", "scan"):
|
|
84
|
+
_show_scans(r)
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
category = _parse_category(category_str)
|
|
89
|
+
except ValueError as e:
|
|
90
|
+
con.print(f"[red]{e}[/red]")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
data = mem.read(category, r)
|
|
94
|
+
con.print()
|
|
95
|
+
con.print(f"[bold #F2EDD6]Memory: {category.value}[/bold #F2EDD6]")
|
|
96
|
+
con.print()
|
|
97
|
+
con.print_json(json.dumps(data, indent=2, default=str))
|
|
98
|
+
con.print()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _show_scans(root: Path) -> None:
|
|
102
|
+
"""Structured view of scan results — one row per agent."""
|
|
103
|
+
from rich.table import Table
|
|
104
|
+
|
|
105
|
+
scans = mem.get_scan_results(root)
|
|
106
|
+
if not scans:
|
|
107
|
+
con.print("[dim]No scan results yet. Run [bold]p scan[/bold] first.[/dim]")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
con.print()
|
|
111
|
+
con.print("[bold #F2EDD6]Last Scan Results by Agent[/bold #F2EDD6]")
|
|
112
|
+
con.print()
|
|
113
|
+
|
|
114
|
+
table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
|
|
115
|
+
table.add_column("Agent", style="bold #F2EDD6", width=26)
|
|
116
|
+
table.add_column("Status", width=10)
|
|
117
|
+
table.add_column("Findings", justify="right", width=10)
|
|
118
|
+
table.add_column("Files", justify="right", width=8)
|
|
119
|
+
table.add_column("Duration", justify="right", width=10)
|
|
120
|
+
|
|
121
|
+
colors = {"done": "#4ADE80", "failed": "#FF4D6D", "skipped": "dim"}
|
|
122
|
+
for agent_name, data in sorted(scans.items()):
|
|
123
|
+
status = data.get("status", "?")
|
|
124
|
+
table.add_row(
|
|
125
|
+
agent_name,
|
|
126
|
+
Text(status, style=colors.get(status, "white")),
|
|
127
|
+
str(data.get("finding_count", 0)),
|
|
128
|
+
str(data.get("files_scanned", 0)),
|
|
129
|
+
f"{data.get('duration_ms', 0)}ms",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
con.print(table)
|
|
133
|
+
con.print()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def run_delete(category_str: str, root: Path | None = None) -> None:
|
|
137
|
+
"""p memory delete <category> — with warning"""
|
|
138
|
+
try:
|
|
139
|
+
r = root or require_project_root()
|
|
140
|
+
except RuntimeError as e:
|
|
141
|
+
con.print(f"[red]{e}[/red]")
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
if category_str.lower() == "all":
|
|
145
|
+
_delete_all(r)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
category = _parse_category(category_str)
|
|
150
|
+
except ValueError as e:
|
|
151
|
+
con.print(f"[red]{e}[/red]")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
consequence = _delete_consequence(category)
|
|
155
|
+
con.print()
|
|
156
|
+
con.print(f"[yellow]Warning:[/yellow] {consequence}")
|
|
157
|
+
con.print()
|
|
158
|
+
|
|
159
|
+
confirmed = Confirm.ask(f"Delete [bold]{category.value}[/bold] memory?", default=False)
|
|
160
|
+
if confirmed:
|
|
161
|
+
mem.delete(category, r)
|
|
162
|
+
con.print(f"[#4ADE80]✓[/#4ADE80] [bold]{category.value}[/bold] memory cleared.")
|
|
163
|
+
else:
|
|
164
|
+
con.print("[dim]Cancelled.[/dim]")
|
|
165
|
+
con.print()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _delete_all(root: Path) -> None:
|
|
169
|
+
"""Full memory reset — requires typing CONFIRM."""
|
|
170
|
+
con.print()
|
|
171
|
+
con.print("[red bold]Full memory reset.[/red bold]\n")
|
|
172
|
+
con.print()
|
|
173
|
+
typed = Prompt.ask("[bold]Type CONFIRM to proceed[/bold]")
|
|
174
|
+
if typed.strip() == "CONFIRM":
|
|
175
|
+
mem.delete_all(root)
|
|
176
|
+
con.print("[#4ADE80]✓[/#4ADE80] All memory cleared. Patchi starts fresh on the next run.")
|
|
177
|
+
else:
|
|
178
|
+
con.print("[dim]Cancelled.[/dim]")
|
|
179
|
+
con.print()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ── Helpers ────────────────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _parse_category(s: str) -> MemoryCategory:
|
|
186
|
+
valid = {c.value: c for c in MemoryCategory}
|
|
187
|
+
# Allow shorthand aliases
|
|
188
|
+
aliases = {
|
|
189
|
+
"patch": MemoryCategory.PATCHES,
|
|
190
|
+
"fail": MemoryCategory.FAILED,
|
|
191
|
+
"scan": MemoryCategory.SCANS,
|
|
192
|
+
"issue": MemoryCategory.ISSUES,
|
|
193
|
+
"restrict": MemoryCategory.RESTRICTIONS,
|
|
194
|
+
"token": MemoryCategory.TOKENS,
|
|
195
|
+
}
|
|
196
|
+
cleaned = s.lower().strip()
|
|
197
|
+
if cleaned in valid:
|
|
198
|
+
return valid[cleaned]
|
|
199
|
+
if cleaned in aliases:
|
|
200
|
+
return aliases[cleaned]
|
|
201
|
+
raise ValueError(f"Unknown category: {s!r}\nValid: {', '.join(valid.keys())}")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _delete_consequence(category: MemoryCategory) -> str:
|
|
205
|
+
consequences = {
|
|
206
|
+
MemoryCategory.BRAIN: "Deleting Brain state means Patchi will re-learn your entire app before the next action.",
|
|
207
|
+
MemoryCategory.PATCHES: "Deleting patch history means you can no longer undo or review past fixes.",
|
|
208
|
+
MemoryCategory.FAILED: "Deleting failed patch history clears all recorded failure reasons.",
|
|
209
|
+
MemoryCategory.SCANS: "Deleting scan results means Patchi will re-scan before the next action.",
|
|
210
|
+
MemoryCategory.ISSUES: "Deleting known issues clears all recorded unfixed findings.",
|
|
211
|
+
MemoryCategory.RESTRICTIONS: "Deleting restrictions removes all no-touch and scan-only zones. "
|
|
212
|
+
"Patchi will have access to all files on next run.",
|
|
213
|
+
MemoryCategory.TOKENS: "Deleting dev tokens removes all stored access token references.",
|
|
214
|
+
}
|
|
215
|
+
return consequences.get(category, "This area will be cleared and rescanned on next run.")
|