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,597 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`p chat` — The Brain. The single intelligence that controls everything.
|
|
3
|
+
|
|
4
|
+
The chat IS the orchestrator. It has direct access to:
|
|
5
|
+
- Read/write any file in the project
|
|
6
|
+
- Run any CLI command
|
|
7
|
+
- Spawn any agent (security, testing, fixing, etc.)
|
|
8
|
+
- Access the full brain/memory state
|
|
9
|
+
- Make decisions based on context
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
p chat — interactive brain session
|
|
13
|
+
p chat "scan this project" — executes security scan
|
|
14
|
+
p chat "what's in src/auth.py" — reads and explains the file
|
|
15
|
+
p chat "fix all critical findings" — scans and fixes
|
|
16
|
+
p chat "run tests and check coverage" — multi-step orchestration
|
|
17
|
+
p chat "write a rate limiter" — creates the file
|
|
18
|
+
p ask "what changed?" — backward-compatible alias
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from rich.markdown import Markdown
|
|
27
|
+
from rich.panel import Panel
|
|
28
|
+
from rich.table import Table
|
|
29
|
+
|
|
30
|
+
from patchi.cli.console import con
|
|
31
|
+
|
|
32
|
+
MAX_HISTORY = 50
|
|
33
|
+
CHAT_HISTORY_FILE = ".patchi/memory/chat_history.json"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_log = logging.getLogger("patchi.cli.chat_cmd")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ── History persistence ────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
def _load_chat_history(root: Path) -> list[dict]:
|
|
42
|
+
path = root / CHAT_HISTORY_FILE
|
|
43
|
+
if path.exists():
|
|
44
|
+
try:
|
|
45
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
46
|
+
except Exception as e:
|
|
47
|
+
_log.warning("_load_chat_history failed: %s", e)
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _save_chat_history(root: Path, history: list[dict]) -> None:
|
|
52
|
+
path = root / CHAT_HISTORY_FILE
|
|
53
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
path.write_text(json.dumps(history[-MAX_HISTORY:], indent=2), encoding="utf-8")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ── System prompt ──────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
BASE_PROMPT = """You are Patchi, the intelligent brain that controls the entire Patchi system.
|
|
60
|
+
|
|
61
|
+
You are NOT just a chat interface — you ARE the orchestrator with direct control over:
|
|
62
|
+
- Reading and writing files in the project
|
|
63
|
+
- Running any CLI command (scan, test, fix, etc.)
|
|
64
|
+
- Spawning specialized agents (security, testing, fixing, etc.)
|
|
65
|
+
- Accessing the full brain/memory state
|
|
66
|
+
- Making decisions based on context
|
|
67
|
+
|
|
68
|
+
When the user asks you to DO something, you should:
|
|
69
|
+
1. Determine what tools/agents are needed
|
|
70
|
+
2. Execute them using the brain's capabilities
|
|
71
|
+
3. Report results with context
|
|
72
|
+
|
|
73
|
+
When the user asks about files, you can:
|
|
74
|
+
- Read the file contents directly
|
|
75
|
+
- Explain what the code does
|
|
76
|
+
- Suggest improvements
|
|
77
|
+
- Write changes if asked
|
|
78
|
+
|
|
79
|
+
You have full control. Use it wisely."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ── Context injection ──────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
def _build_injected_context(brain: dict, message: str, root=None) -> str:
|
|
85
|
+
"""Build rich context from the brain using BrainContext for deeper understanding."""
|
|
86
|
+
m_lower = message.lower()
|
|
87
|
+
parts = []
|
|
88
|
+
|
|
89
|
+
# Try to load BrainContext for richer context
|
|
90
|
+
ctx = None
|
|
91
|
+
if root:
|
|
92
|
+
try:
|
|
93
|
+
from patchi.core.brain.brain_context import get_brain_context
|
|
94
|
+
ctx = get_brain_context(root)
|
|
95
|
+
except Exception as _exc:
|
|
96
|
+
_log.debug('suppressed: %s', _exc)
|
|
97
|
+
|
|
98
|
+
if ctx and ctx.is_loaded():
|
|
99
|
+
# Use BrainContext for rich context injection
|
|
100
|
+
parts.append(ctx.get_context_for_prompt(max_chars=2000))
|
|
101
|
+
else:
|
|
102
|
+
# Fallback to basic brain dict
|
|
103
|
+
file_count = brain.get("file_count", 0)
|
|
104
|
+
framework = brain.get("framework", "Unknown")
|
|
105
|
+
route_count = brain.get("route_count", 0)
|
|
106
|
+
health = brain.get("health_score", {})
|
|
107
|
+
health_total = health.get("total", 0) if isinstance(health, dict) else health
|
|
108
|
+
parts.append(f"Project: {file_count} files, {route_count} routes, framework: {framework}, health: {health_total}/100")
|
|
109
|
+
|
|
110
|
+
# Finding-specific context
|
|
111
|
+
if any(w in m_lower for w in ["finding", "issue", "vulnerability", "bug", "critical", "high", "error"]):
|
|
112
|
+
issues = brain.get("issues", []) or brain.get("findings", [])
|
|
113
|
+
if issues:
|
|
114
|
+
by_sev = {}
|
|
115
|
+
for f in issues:
|
|
116
|
+
if isinstance(f, dict):
|
|
117
|
+
sev = f.get("severity", "info")
|
|
118
|
+
by_sev[sev] = by_sev.get(sev, 0) + 1
|
|
119
|
+
if by_sev:
|
|
120
|
+
sev_str = ", ".join(f"{c} {s}" for s, c in sorted(by_sev.items()))
|
|
121
|
+
parts.append(f"Issues ({len(issues)} total): {sev_str}")
|
|
122
|
+
top = [f for f in issues if isinstance(f, dict) and f.get("severity") in ("critical", "high")][:5]
|
|
123
|
+
for f in top:
|
|
124
|
+
parts.append(f" [{f.get('severity', 'info')}] {f.get('file', '')}:{f.get('line', 0)} -- {f.get('message', '')[:80]}")
|
|
125
|
+
|
|
126
|
+
return "\n".join(parts) if parts else ""
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ── Force routing ──────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
def _check_force_routing(message: str) -> tuple[str, str | None]:
|
|
132
|
+
"""Check for forced routing prefixes. Returns (cleaned_message, force_mode)."""
|
|
133
|
+
msg = message.strip()
|
|
134
|
+
if msg.lower().startswith("reason:"):
|
|
135
|
+
return msg[7:].strip(), "reasoning"
|
|
136
|
+
if msg.lower().startswith("ai:"):
|
|
137
|
+
return msg[3:].strip(), "ai"
|
|
138
|
+
if msg.lower().startswith("tool:"):
|
|
139
|
+
return msg[5:].strip(), "tool"
|
|
140
|
+
return msg, None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ── Explain All ────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
def _show_explain_all() -> None:
|
|
146
|
+
"""Show the full security knowledge base in a formatted table."""
|
|
147
|
+
from rich.table import Table
|
|
148
|
+
|
|
149
|
+
from patchi.cli.commands.explain_cmd import _EXPLANATIONS
|
|
150
|
+
|
|
151
|
+
con.print()
|
|
152
|
+
con.print("[bold #C8621A]Security Knowledge Base[/bold #C8621A]")
|
|
153
|
+
con.print("[dim]Complete reference for all security finding types.[/dim]")
|
|
154
|
+
con.print()
|
|
155
|
+
|
|
156
|
+
table = Table(show_header=True, header_style="bold #C8621A", box=None)
|
|
157
|
+
table.add_column("Type", style="cyan", min_width=25)
|
|
158
|
+
table.add_column("Title", style="white", min_width=25)
|
|
159
|
+
table.add_column("Severity", width=10)
|
|
160
|
+
table.add_column("CWE", width=12)
|
|
161
|
+
table.add_column("What", max_width=50)
|
|
162
|
+
table.add_column("How to Fix", max_width=40)
|
|
163
|
+
|
|
164
|
+
severity_colors = {
|
|
165
|
+
"critical": "red",
|
|
166
|
+
"high": "yellow",
|
|
167
|
+
"medium": "blue",
|
|
168
|
+
"low": "dim",
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for ftype, info in sorted(_EXPLANATIONS.items()):
|
|
172
|
+
sev = info.get("severity", "?")
|
|
173
|
+
color = severity_colors.get(sev, "white")
|
|
174
|
+
table.add_row(
|
|
175
|
+
ftype,
|
|
176
|
+
info.get("title", "?"),
|
|
177
|
+
f"[{color}]{sev}[/{color}]",
|
|
178
|
+
info.get("cwe", "?"),
|
|
179
|
+
info.get("what", "?")[:50],
|
|
180
|
+
info.get("how", "?").split("\n")[0][:40],
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
con.print(table)
|
|
184
|
+
con.print()
|
|
185
|
+
con.print(f"[dim]{len(_EXPLANATIONS)} finding types in knowledge base.[/dim]")
|
|
186
|
+
con.print("[dim]Use `p chat 'explain sql injection'` for detailed info on any type.[/dim]")
|
|
187
|
+
con.print()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ── Process message through the Brain ─────────────────────────────────
|
|
191
|
+
|
|
192
|
+
def _process_message(
|
|
193
|
+
message: str,
|
|
194
|
+
root: Path,
|
|
195
|
+
config: dict,
|
|
196
|
+
brain_state: dict,
|
|
197
|
+
json_output: bool = False,
|
|
198
|
+
history: list[dict] | None = None,
|
|
199
|
+
stream: bool = False,
|
|
200
|
+
) -> str:
|
|
201
|
+
"""Process a message using the Brain as the central intelligence."""
|
|
202
|
+
from patchi.core.ai.client import call_ai
|
|
203
|
+
from patchi.core.ai.orchestrator import Orchestrator
|
|
204
|
+
|
|
205
|
+
clean_msg, force_mode = _check_force_routing(message)
|
|
206
|
+
|
|
207
|
+
# Create brain instance with appropriate progress callback
|
|
208
|
+
if stream:
|
|
209
|
+
# Real-time streaming progress with rich formatting
|
|
210
|
+
_tool_count = [0]
|
|
211
|
+
_step_start = [0]
|
|
212
|
+
|
|
213
|
+
def on_progress(msg):
|
|
214
|
+
import time as _time
|
|
215
|
+
if "Executing" in msg:
|
|
216
|
+
_tool_count[0] += 1
|
|
217
|
+
_step_start[0] = _time.monotonic()
|
|
218
|
+
tool_name = msg.replace("Executing ", "").replace("...", "")
|
|
219
|
+
con.print(f" [bold cyan]⟳ Step {_tool_count[0]}:[/bold cyan] [bold]{tool_name}[/bold]")
|
|
220
|
+
elif "Spawning" in msg:
|
|
221
|
+
con.print(f" [bold magenta]🤖 {msg}[/bold magenta]")
|
|
222
|
+
elif "Planning" in msg or "Understanding" in msg:
|
|
223
|
+
con.print(f" [bold yellow]🧠 {msg}[/bold yellow]")
|
|
224
|
+
else:
|
|
225
|
+
con.print(f" [dim]{msg}[/dim]")
|
|
226
|
+
|
|
227
|
+
def on_event(payload: dict):
|
|
228
|
+
ev = payload.get("event", "")
|
|
229
|
+
data = payload.get("data", {})
|
|
230
|
+
if ev == "orchestrator.planned":
|
|
231
|
+
steps = data.get("steps", 0)
|
|
232
|
+
reasoning = data.get("reasoning", "")
|
|
233
|
+
con.print(f" [bold green]📋 Plan:[/bold green] {steps} steps")
|
|
234
|
+
if reasoning:
|
|
235
|
+
con.print(f" [dim]{reasoning[:100]}{'...' if len(reasoning) > 100 else ''}[/dim]")
|
|
236
|
+
elif ev == "orchestrator.completed":
|
|
237
|
+
success = data.get("success", False)
|
|
238
|
+
findings = data.get("findings", 0)
|
|
239
|
+
icon = "[green]✓[/green]" if success else "[red]✗[/red]"
|
|
240
|
+
con.print(f" {icon} [bold]Orchestration complete[/bold]")
|
|
241
|
+
if findings:
|
|
242
|
+
con.print(f" [dim]Findings: {findings}[/dim]")
|
|
243
|
+
elif ev == "security.finding":
|
|
244
|
+
sev = data.get("severity", "?").upper()
|
|
245
|
+
desc = data.get("description", data.get("type", ""))
|
|
246
|
+
con.print(f" [red][{sev}][/red] {desc[:80]}")
|
|
247
|
+
elif ev == "test.suite.completed":
|
|
248
|
+
passed = data.get("passed", 0)
|
|
249
|
+
failed = data.get("failed", 0)
|
|
250
|
+
con.print(f" [green]Tests: {passed} passed[/green], [red]{failed} failed[/red]")
|
|
251
|
+
elif ev == "agent.progress":
|
|
252
|
+
pct = data.get("progress_pct", 0)
|
|
253
|
+
current = data.get("current_file", "")
|
|
254
|
+
if current and current not in ["planning", "done"]:
|
|
255
|
+
con.print(f" [dim]→ {current} ({pct}%)[/dim]")
|
|
256
|
+
else:
|
|
257
|
+
def on_progress(msg):
|
|
258
|
+
con.print(f" [dim]{msg}[/dim]")
|
|
259
|
+
def on_event(payload: dict):
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
orchestrator = Orchestrator(root, on_progress=on_progress, on_event=on_event)
|
|
263
|
+
|
|
264
|
+
# ── File operations (direct brain access) ──
|
|
265
|
+
|
|
266
|
+
# Check if user wants to read a file
|
|
267
|
+
read_patterns = ["read ", "show ", "cat ", "what's in ", "what is in ", "open ", "display "]
|
|
268
|
+
if any(clean_msg.lower().startswith(p) for p in read_patterns):
|
|
269
|
+
file_path = clean_msg
|
|
270
|
+
for p in read_patterns:
|
|
271
|
+
if file_path.lower().startswith(p):
|
|
272
|
+
file_path = file_path[len(p):].strip()
|
|
273
|
+
break
|
|
274
|
+
|
|
275
|
+
content = orchestrator.read_file(file_path)
|
|
276
|
+
if not content.startswith("ERROR:"):
|
|
277
|
+
con.print(f"\n[bold]📄 {file_path}[/bold]\n")
|
|
278
|
+
con.print(f"```python\n{content}\n```")
|
|
279
|
+
return f"[File: {file_path}]\n{content}"
|
|
280
|
+
else:
|
|
281
|
+
con.print(f"[yellow]{content}[/yellow]")
|
|
282
|
+
return content
|
|
283
|
+
|
|
284
|
+
# Check if user wants to write a file
|
|
285
|
+
write_patterns = ["write ", "create ", "save ", "make "]
|
|
286
|
+
if any(clean_msg.lower().startswith(p) for p in write_patterns):
|
|
287
|
+
# Extract file path and content
|
|
288
|
+
parts = clean_msg.split(":", 1) if ":" in clean_msg else clean_msg.split(" to ", 1)
|
|
289
|
+
if len(parts) == 2:
|
|
290
|
+
file_path = parts[0]
|
|
291
|
+
for p in write_patterns:
|
|
292
|
+
if file_path.lower().startswith(p):
|
|
293
|
+
file_path = file_path[len(p):].strip()
|
|
294
|
+
break
|
|
295
|
+
content = parts[1].strip()
|
|
296
|
+
|
|
297
|
+
result = orchestrator.write_file(file_path, content)
|
|
298
|
+
if result.get("success"):
|
|
299
|
+
con.print(f"\n[green]✓ Written {result['size']} bytes to {file_path}[/green]")
|
|
300
|
+
return f"[Written: {file_path}]"
|
|
301
|
+
else:
|
|
302
|
+
con.print(f"[red]Failed: {result.get('error', 'unknown')}[/red]")
|
|
303
|
+
return f"Failed: {result.get('error', 'unknown')}"
|
|
304
|
+
|
|
305
|
+
# ── Command execution ──
|
|
306
|
+
if clean_msg.lower().startswith("run ") or clean_msg.lower().startswith("exec "):
|
|
307
|
+
command = clean_msg
|
|
308
|
+
for p in ["run ", "exec "]:
|
|
309
|
+
if command.lower().startswith(p):
|
|
310
|
+
command = command[len(p):].strip()
|
|
311
|
+
break
|
|
312
|
+
|
|
313
|
+
result = orchestrator.run_command(command)
|
|
314
|
+
if result.get("success"):
|
|
315
|
+
con.print("\n[green]✓ Command succeeded[/green]")
|
|
316
|
+
if result.get("stdout"):
|
|
317
|
+
con.print(f"```\n{result['stdout']}\n```")
|
|
318
|
+
return result.get("stdout", "Command succeeded")
|
|
319
|
+
else:
|
|
320
|
+
con.print(f"[red]Command failed: {result.get('error', result.get('stderr', 'unknown'))}[/red]")
|
|
321
|
+
return f"Command failed: {result.get('error', result.get('stderr', 'unknown'))}"
|
|
322
|
+
|
|
323
|
+
# ── Agent spawning ──
|
|
324
|
+
spawn_patterns = ["spawn ", "run agent ", "use agent ", "activate "]
|
|
325
|
+
if any(clean_msg.lower().startswith(p) for p in spawn_patterns):
|
|
326
|
+
agent_type = clean_msg
|
|
327
|
+
for p in spawn_patterns:
|
|
328
|
+
if agent_type.lower().startswith(p):
|
|
329
|
+
agent_type = agent_type[len(p):].strip()
|
|
330
|
+
break
|
|
331
|
+
|
|
332
|
+
# Extract agent type from the message
|
|
333
|
+
for atype in ["security", "scanner", "proactive", "council", "smart"]:
|
|
334
|
+
if atype in agent_type.lower():
|
|
335
|
+
con.print(f"\n[bold cyan]🤖 Spawning {atype} agent...[/bold cyan]\n")
|
|
336
|
+
result = orchestrator.spawn_agent(atype, goal=clean_msg)
|
|
337
|
+
if result.get("success"):
|
|
338
|
+
con.print("[green]✓ Agent completed[/green]")
|
|
339
|
+
return json.dumps(result.get("result", {}), indent=2, default=str)[:2000]
|
|
340
|
+
else:
|
|
341
|
+
con.print(f"[red]Agent failed: {result.get('error', 'unknown')}[/red]")
|
|
342
|
+
return f"Agent failed: {result.get('error', 'unknown')}"
|
|
343
|
+
|
|
344
|
+
# ── LLM-powered orchestration (default) ──
|
|
345
|
+
|
|
346
|
+
# Build context
|
|
347
|
+
context = _build_injected_context(brain_state, clean_msg, root=root)
|
|
348
|
+
extra = f"\n\nRelevant context:\n{context}" if context else ""
|
|
349
|
+
|
|
350
|
+
# Build conversation history
|
|
351
|
+
history_text = ""
|
|
352
|
+
if history:
|
|
353
|
+
for msg in history[-10:]:
|
|
354
|
+
role = "Developer" if msg["role"] == "user" else "Brain"
|
|
355
|
+
history_text += f"{role}: {msg['content']}\n"
|
|
356
|
+
|
|
357
|
+
full_prompt = clean_msg
|
|
358
|
+
if history_text:
|
|
359
|
+
full_prompt = f"Previous conversation:\n{history_text}\nCurrent question: {clean_msg}"
|
|
360
|
+
|
|
361
|
+
# Get AI response
|
|
362
|
+
response = call_ai(config, BASE_PROMPT + extra, full_prompt)
|
|
363
|
+
if not response:
|
|
364
|
+
response = "I don't have an AI model configured. Set up with `p key add` or `p model set <model>`."
|
|
365
|
+
|
|
366
|
+
con.print()
|
|
367
|
+
con.print("[bold #4ADE80]Patchi:[/bold #4ADE80]")
|
|
368
|
+
con.print(Markdown(response))
|
|
369
|
+
con.print()
|
|
370
|
+
|
|
371
|
+
return response
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# ── Help commands ──────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
def _print_help():
|
|
377
|
+
"""Print help information."""
|
|
378
|
+
con.print(Panel(
|
|
379
|
+
"[bold]Patchi Chat — The Brain[/bold]\n\n"
|
|
380
|
+
"[bold]Direct File Access:[/bold]\n"
|
|
381
|
+
" [cyan]read[/cyan] src/auth.py — read and explain a file\n"
|
|
382
|
+
" [cyan]write[/cyan] src/utils.py:code — create/overwrite a file\n\n"
|
|
383
|
+
"[bold]Command Execution:[/bold]\n"
|
|
384
|
+
" [cyan]run[/cyan] pytest tests/ — run any shell command\n"
|
|
385
|
+
" [cyan]exec[/cyan] ruff check src/ — execute a command\n\n"
|
|
386
|
+
"[bold]Agent Spawning:[/bold]\n"
|
|
387
|
+
" [cyan]spawn[/cyan] security — run security scan\n"
|
|
388
|
+
" [cyan]spawn[/cyan] council — multi-persona deliberation\n"
|
|
389
|
+
" [cyan]spawn[/cyan] smart — goal-driven smart agent\n\n"
|
|
390
|
+
"[bold]Natural Language:[/bold]\n"
|
|
391
|
+
" [cyan]scan[/cyan] this project — security scan\n"
|
|
392
|
+
" [cyan]fix[/cyan] critical findings — scan + fix pipeline\n"
|
|
393
|
+
" [cyan]test[/cyan] and check coverage — run tests\n"
|
|
394
|
+
" [cyan]what's in[/cyan] src/auth.py — read file\n\n"
|
|
395
|
+
"[bold]Commands:[/bold]\n"
|
|
396
|
+
" [green]tools[/green] — list available tools\n"
|
|
397
|
+
" [green]council[/green] — show council/persona info\n"
|
|
398
|
+
" [green]history[/green] — show chat history\n"
|
|
399
|
+
" [green]help[/green] — this help\n"
|
|
400
|
+
" [green]clear[/green] — clear chat history\n\n"
|
|
401
|
+
"[bold]Forced Routing:[/bold]\n"
|
|
402
|
+
" [yellow]reason:[/yellow] what changed? — force reasoning engine\n"
|
|
403
|
+
" [yellow]ai:[/yellow] explain this code — force AI chat\n"
|
|
404
|
+
" [yellow]tool:[/yellow] scan_vulnerabilities — force tool execution\n",
|
|
405
|
+
border_style="#C8621A",
|
|
406
|
+
))
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _print_tools():
|
|
410
|
+
"""Print available tools."""
|
|
411
|
+
try:
|
|
412
|
+
from patchi.core.ai.tools.registry import get_tool_registry
|
|
413
|
+
registry = get_tool_registry()
|
|
414
|
+
|
|
415
|
+
table = Table(title="Available Tools", box=None, show_lines=False)
|
|
416
|
+
table.add_column("Tool", style="cyan", min_width=20)
|
|
417
|
+
table.add_column("Description", style="white")
|
|
418
|
+
table.add_column("Category", style="yellow")
|
|
419
|
+
|
|
420
|
+
categories = {
|
|
421
|
+
"scan": ["analyze_project", "scan_vulnerabilities", "scan_project"],
|
|
422
|
+
"test": ["run_tests", "generate_tests", "browser_test", "screenshot", "visual_regression"],
|
|
423
|
+
"fix": ["generate_fix", "apply_patch", "verify_fix", "rollback_patch"],
|
|
424
|
+
"attack": ["attack_simulate", "red_team"],
|
|
425
|
+
"stress": ["stress_test"],
|
|
426
|
+
"knowledge": ["ask_brain", "explain_layer", "impact_analysis", "why_file_matters"],
|
|
427
|
+
"compliance": ["check_compliance"],
|
|
428
|
+
"code": ["write_file", "generate_code"],
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
tool_to_cat = {}
|
|
432
|
+
for cat, tools in categories.items():
|
|
433
|
+
for t in tools:
|
|
434
|
+
tool_to_cat[t] = cat
|
|
435
|
+
|
|
436
|
+
for tool in registry.list_tools():
|
|
437
|
+
table.add_row(
|
|
438
|
+
tool.name,
|
|
439
|
+
tool.description[:60] + "..." if len(tool.description) > 60 else tool.description,
|
|
440
|
+
tool_to_cat.get(tool.name, "other"),
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
con.print(table)
|
|
444
|
+
except Exception as e:
|
|
445
|
+
con.print(f"[red]Failed to load tools: {e}[/red]")
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _print_council_info(root: Path):
|
|
449
|
+
"""Print council/persona information."""
|
|
450
|
+
try:
|
|
451
|
+
from patchi.core.brain.personas.base import list_personas
|
|
452
|
+
|
|
453
|
+
personas = list_personas()
|
|
454
|
+
con.print(f"\n[bold]🏛️ Council — {len(personas)} Personas[/bold]\n")
|
|
455
|
+
|
|
456
|
+
table = Table(box=None, show_lines=False)
|
|
457
|
+
table.add_column("Persona", style="cyan", min_width=20)
|
|
458
|
+
table.add_column("Style", style="yellow")
|
|
459
|
+
table.add_column("Focus", style="white")
|
|
460
|
+
|
|
461
|
+
style_names = {
|
|
462
|
+
"aggressive": "🔴 Aggressive",
|
|
463
|
+
"cautious": "🟡 Cautious",
|
|
464
|
+
"balanced": "🟢 Balanced",
|
|
465
|
+
"methodical": "🔵 Methodical",
|
|
466
|
+
"creative": "🟣 Creative",
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
for p in personas:
|
|
470
|
+
table.add_row(
|
|
471
|
+
p.name,
|
|
472
|
+
style_names.get(p.style.value if hasattr(p.style, 'value') else str(p.style), str(p.style)),
|
|
473
|
+
p.focus_domain,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
con.print(table)
|
|
477
|
+
con.print("\n[dim]The Council deliberates on complex issues, combining multiple personas' perspectives.[/dim]")
|
|
478
|
+
con.print("[dim]Ask 'council' or mention 'council' in your message to trigger deliberation.[/dim]\n")
|
|
479
|
+
except Exception as e:
|
|
480
|
+
con.print(f"[red]Failed to load council info: {e}[/red]")
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _print_chat_history(history: list[dict]):
|
|
484
|
+
"""Print chat history."""
|
|
485
|
+
if not history:
|
|
486
|
+
con.print("[dim]No chat history yet.[/dim]")
|
|
487
|
+
return
|
|
488
|
+
|
|
489
|
+
con.print(f"\n[bold]📋 Chat History ({len(history)} messages)[/bold]\n")
|
|
490
|
+
for msg in history[-20:]:
|
|
491
|
+
role = "You" if msg["role"] == "user" else "Brain"
|
|
492
|
+
content = msg["content"][:100] + "..." if len(msg["content"]) > 100 else msg["content"]
|
|
493
|
+
con.print(f" [bold]{role}:[/bold] {content}")
|
|
494
|
+
con.print()
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
# ── Main entry point ───────────────────────────────────────────────────
|
|
498
|
+
|
|
499
|
+
def run(
|
|
500
|
+
message: list[str] | str | None = None,
|
|
501
|
+
json_output: bool = False,
|
|
502
|
+
root: Path | None = None,
|
|
503
|
+
stream: bool = False,
|
|
504
|
+
explain_all: bool = False,
|
|
505
|
+
) -> None:
|
|
506
|
+
"""Entry point for `p chat` and `p ask` (backward compatible).
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
message: Single message or list of words.
|
|
510
|
+
json_output: Output as JSON.
|
|
511
|
+
root: Override project root.
|
|
512
|
+
stream: Show real-time tool execution progress.
|
|
513
|
+
explain_all: Show full security knowledge base.
|
|
514
|
+
"""
|
|
515
|
+
try:
|
|
516
|
+
from patchi.core.config import require_project_root
|
|
517
|
+
r = root or require_project_root()
|
|
518
|
+
except RuntimeError as e:
|
|
519
|
+
con.print(f"[red]{e}[/red]")
|
|
520
|
+
return
|
|
521
|
+
|
|
522
|
+
from patchi.core import config as cfg
|
|
523
|
+
from patchi.core import memory as mem
|
|
524
|
+
|
|
525
|
+
config = cfg.load(r)
|
|
526
|
+
brain_state = mem.get_brain(r)
|
|
527
|
+
|
|
528
|
+
# --explain-all: show full security knowledge base
|
|
529
|
+
if explain_all:
|
|
530
|
+
_show_explain_all()
|
|
531
|
+
return
|
|
532
|
+
|
|
533
|
+
# Single message mode
|
|
534
|
+
if message:
|
|
535
|
+
if isinstance(message, list):
|
|
536
|
+
message = " ".join(message)
|
|
537
|
+
if not message.strip():
|
|
538
|
+
con.print("[red]Error: no message provided[/red]")
|
|
539
|
+
return
|
|
540
|
+
|
|
541
|
+
_process_message(message, r, config, brain_state, json_output=json_output, stream=stream)
|
|
542
|
+
return
|
|
543
|
+
|
|
544
|
+
# Interactive mode
|
|
545
|
+
con.print()
|
|
546
|
+
stream_label = " [green](streaming)[/green]" if stream else ""
|
|
547
|
+
con.print(
|
|
548
|
+
Panel(
|
|
549
|
+
f"[bold]Patchi Chat — The Brain[/bold]{stream_label}\n"
|
|
550
|
+
"[dim]Direct access to files, commands, agents, and intelligence.[/dim]\n"
|
|
551
|
+
"[dim]Type 'quit' to exit, 'help' for commands, 'tools' to list available tools.[/dim]",
|
|
552
|
+
border_style="#C8621A",
|
|
553
|
+
)
|
|
554
|
+
)
|
|
555
|
+
con.print()
|
|
556
|
+
|
|
557
|
+
history = _load_chat_history(r)
|
|
558
|
+
|
|
559
|
+
while True:
|
|
560
|
+
try:
|
|
561
|
+
user_input = con.input("[bold #C8621A]You:[/bold #C8621A] ").strip()
|
|
562
|
+
except (EOFError, KeyboardInterrupt):
|
|
563
|
+
_save_chat_history(r, history)
|
|
564
|
+
con.print("\n[dim]Chat ended.[/dim]")
|
|
565
|
+
return
|
|
566
|
+
|
|
567
|
+
if not user_input:
|
|
568
|
+
continue
|
|
569
|
+
if user_input.lower() in ("quit", "exit", "q"):
|
|
570
|
+
_save_chat_history(r, history)
|
|
571
|
+
con.print("[dim]Chat ended.[/dim]")
|
|
572
|
+
return
|
|
573
|
+
if user_input.lower() == "help":
|
|
574
|
+
_print_help()
|
|
575
|
+
continue
|
|
576
|
+
if user_input.lower() == "tools":
|
|
577
|
+
_print_tools()
|
|
578
|
+
continue
|
|
579
|
+
if user_input.lower() == "council":
|
|
580
|
+
_print_council_info(r)
|
|
581
|
+
continue
|
|
582
|
+
if user_input.lower() == "history":
|
|
583
|
+
_print_chat_history(history)
|
|
584
|
+
continue
|
|
585
|
+
if user_input.lower().startswith("clear"):
|
|
586
|
+
history.clear()
|
|
587
|
+
_save_chat_history(r, history)
|
|
588
|
+
con.print("[dim]Chat history cleared.[/dim]")
|
|
589
|
+
continue
|
|
590
|
+
|
|
591
|
+
# Process the message through the Brain
|
|
592
|
+
response = _process_message(user_input, r, config, brain_state, history=history, stream=stream)
|
|
593
|
+
if response:
|
|
594
|
+
history.append({"role": "user", "content": user_input})
|
|
595
|
+
history.append({"role": "assistant", "content": response})
|
|
596
|
+
history[:] = history[-MAX_HISTORY:]
|
|
597
|
+
_save_chat_history(r, history)
|