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,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`p cockpit` — the live cockpit (a single glanceable dashboard for a coding session).
|
|
3
|
+
|
|
4
|
+
p cockpit live dashboard, refreshes as you save files
|
|
5
|
+
p cockpit --area src/api scope the fix list to a subtree
|
|
6
|
+
p cockpit --once render one frame and exit (CI / smoke test)
|
|
7
|
+
p cockpit --interval 30 seconds between full health/drift refreshes
|
|
8
|
+
|
|
9
|
+
It is a *view layer only*: every number comes from signals Patchi already
|
|
10
|
+
computes (health score, Plan-vs-Built drift, Prioritized Fix List, blast radius,
|
|
11
|
+
secrets sweep). No new analysis lives here.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from rich.console import Group
|
|
20
|
+
from rich.layout import Layout
|
|
21
|
+
from rich.live import Live
|
|
22
|
+
from rich.panel import Panel
|
|
23
|
+
from rich.table import Table
|
|
24
|
+
from rich.text import Text
|
|
25
|
+
|
|
26
|
+
from patchi.cli.console import con
|
|
27
|
+
from patchi.core.brain import cockpit as ck
|
|
28
|
+
from patchi.core.config import require_project_root
|
|
29
|
+
|
|
30
|
+
_FIX_LABEL = {
|
|
31
|
+
"charter_violation": ("Charter", "#FF4D6D"),
|
|
32
|
+
"signature_callers": ("Breaking", "#FF8C42"),
|
|
33
|
+
"missing_import": ("Missing import", "#FACC15"),
|
|
34
|
+
"dead_code": ("Dead code", "#9AA0A6"),
|
|
35
|
+
"unused_import": ("Unused import", "#9AA0A6"),
|
|
36
|
+
"format": ("Format", "#86EFAC"),
|
|
37
|
+
}
|
|
38
|
+
_LEVEL_STYLE = {"info": "#86EFAC", "warn": "#FACC15", "crit": "#FF4D6D"}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ── Panels ───────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _header(state: ck.CockpitState) -> Panel:
|
|
45
|
+
t = Table.grid(expand=True)
|
|
46
|
+
t.add_column(justify="left")
|
|
47
|
+
t.add_column(justify="right")
|
|
48
|
+
|
|
49
|
+
if state.health_total is None:
|
|
50
|
+
health = Text("Health — (run `p scan`)", style="#9AA0A6")
|
|
51
|
+
else:
|
|
52
|
+
health = Text.assemble(
|
|
53
|
+
("Health ", "#F2EDD6"),
|
|
54
|
+
(f"{state.health_total}", f"bold {state.health_color}"),
|
|
55
|
+
(f" {state.health_grade}", state.health_color),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
drift = state.drift or {}
|
|
59
|
+
if not drift.get("has_plan"):
|
|
60
|
+
plan = Text("Plan no baseline (`p audit save`)", style="#9AA0A6")
|
|
61
|
+
elif drift.get("clean"):
|
|
62
|
+
plan = Text("Plan ✓ ON PLAN", style="bold #86EFAC")
|
|
63
|
+
else:
|
|
64
|
+
sd = drift.get("scope_diff", {}) or {}
|
|
65
|
+
delta = sd.get("added_count", 0) + sd.get("removed_count", 0) + sd.get("modified_count", 0)
|
|
66
|
+
nf = drift.get("new_findings", 0)
|
|
67
|
+
plan = Text.assemble(
|
|
68
|
+
("Plan ⚠ DRIFT", "bold #FF4D6D"),
|
|
69
|
+
(f" ({delta} file Δ, +{nf} findings)", "#FF8C42"),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
t.add_row(health, plan)
|
|
73
|
+
return Panel(t, border_style="#2A3D28", padding=(0, 1))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _fix_panel(state: ck.CockpitState) -> Panel:
|
|
77
|
+
if not state.fixes:
|
|
78
|
+
if state.fixes_computing or state.fixes_at == 0:
|
|
79
|
+
body = Text("Computing fix list…", style="#FACC15")
|
|
80
|
+
else:
|
|
81
|
+
body = Text("No fixes proposed — clean, or run `p scan` first.", style="#9AA0A6")
|
|
82
|
+
return Panel(
|
|
83
|
+
body, title="[bold #C8621A]Prioritized Fix List[/bold #C8621A]", border_style="#2A3D28"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
tbl = Table(show_header=True, header_style="dim", box=None, expand=True, pad_edge=False)
|
|
87
|
+
tbl.add_column("#", justify="right", width=2, style="dim")
|
|
88
|
+
tbl.add_column("Type", width=14)
|
|
89
|
+
tbl.add_column("Where", overflow="ellipsis", no_wrap=True)
|
|
90
|
+
tbl.add_column("", justify="right", width=6)
|
|
91
|
+
|
|
92
|
+
for i, f in enumerate(state.fixes[:12], 1):
|
|
93
|
+
label, color = _FIX_LABEL.get(f["fix_type"], (f["fix_type"], "#F2EDD6"))
|
|
94
|
+
where = f["file"] + (f" · {f['name']}" if f["name"] else "")
|
|
95
|
+
tag = Text("safe", style="#86EFAC") if f["safe"] else Text("review", style="#FF8C42")
|
|
96
|
+
tbl.add_row(str(i), Text(label, style=color), where, tag)
|
|
97
|
+
|
|
98
|
+
extra = len(state.fixes) - 12
|
|
99
|
+
title = f"[bold #C8621A]Prioritized Fix List[/bold #C8621A] [dim]({len(state.fixes)})[/dim]"
|
|
100
|
+
grp = Group(tbl, Text(f" +{extra} more…", style="dim")) if extra > 0 else tbl
|
|
101
|
+
return Panel(grp, title=title, border_style="#2A3D28")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _blast_panel(state: ck.CockpitState) -> Panel:
|
|
105
|
+
if not state.last_file:
|
|
106
|
+
body = Text("Save a file to see its blast radius.", style="#9AA0A6")
|
|
107
|
+
return Panel(
|
|
108
|
+
body, title="[bold #C8621A]Blast Radius[/bold #C8621A]", border_style="#2A3D28"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
lines: list = [Text(state.blast_summary or "", style="#F2EDD6"), Text("")]
|
|
112
|
+
if state.blast_affected:
|
|
113
|
+
lines.append(Text("Directly affected:", style="bold #F2EDD6"))
|
|
114
|
+
lines += [Text(f" • {n}", style="#F2EDD6") for n in state.blast_affected]
|
|
115
|
+
if state.blast_impacted:
|
|
116
|
+
lines.append(Text("Downstream (blast):", style="bold #FF8C42"))
|
|
117
|
+
lines += [Text(f" • {n}", style="#FF8C42") for n in state.blast_impacted]
|
|
118
|
+
elif state.blast_affected:
|
|
119
|
+
lines.append(Text("No downstream dependents.", style="dim"))
|
|
120
|
+
|
|
121
|
+
title = f"[bold #C8621A]Blast Radius[/bold #C8621A] [dim]{state.last_file}[/dim]"
|
|
122
|
+
return Panel(Group(*lines), title=title, border_style="#2A3D28")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _events_panel(state: ck.CockpitState) -> Panel:
|
|
126
|
+
if not state.events:
|
|
127
|
+
body = Text("Watching…", style="#9AA0A6")
|
|
128
|
+
else:
|
|
129
|
+
rows = []
|
|
130
|
+
for ev in list(state.events)[-8:]:
|
|
131
|
+
style = _LEVEL_STYLE.get(ev.level, "#F2EDD6")
|
|
132
|
+
mark = {"info": "·", "warn": "▲", "crit": "✖"}.get(ev.level, "·")
|
|
133
|
+
rows.append(
|
|
134
|
+
Text.assemble(
|
|
135
|
+
(f"{ev.stamp()} ", "dim"),
|
|
136
|
+
(f"{mark} ", style),
|
|
137
|
+
(ev.text, style),
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
body = Group(*rows)
|
|
141
|
+
sec = f" · [#FF4D6D]{len(state.secrets)} secret(s)[/#FF4D6D]" if state.secrets else ""
|
|
142
|
+
return Panel(body, title=f"[bold #C8621A]Events[/bold #C8621A]{sec}", border_style="#2A3D28")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ── Layout ─────────────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _build_layout() -> Layout:
|
|
149
|
+
root = Layout()
|
|
150
|
+
root.split_column(
|
|
151
|
+
Layout(name="header", size=3),
|
|
152
|
+
Layout(name="body", ratio=1),
|
|
153
|
+
Layout(name="events", size=10),
|
|
154
|
+
)
|
|
155
|
+
root["body"].split_row(Layout(name="fixes"), Layout(name="blast"))
|
|
156
|
+
return root
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _render(layout: Layout, state: ck.CockpitState) -> None:
|
|
160
|
+
layout["header"].update(_header(state))
|
|
161
|
+
layout["fixes"].update(_fix_panel(state))
|
|
162
|
+
layout["blast"].update(_blast_panel(state))
|
|
163
|
+
layout["events"].update(_events_panel(state))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ── Entry point ────────────────────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def run(
|
|
170
|
+
root: Path | None = None,
|
|
171
|
+
interval: float = 15.0,
|
|
172
|
+
area: str | None = None,
|
|
173
|
+
once: bool = False,
|
|
174
|
+
poll: float = 1.0,
|
|
175
|
+
scan_secrets: bool = False,
|
|
176
|
+
) -> None:
|
|
177
|
+
try:
|
|
178
|
+
r = root or require_project_root()
|
|
179
|
+
except RuntimeError as e:
|
|
180
|
+
con.print(f"[red]{e}[/red]")
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
state = ck.gather_full(r, area=area, scan_secrets=scan_secrets, fixes_sync=once)
|
|
184
|
+
layout = _build_layout()
|
|
185
|
+
_render(layout, state)
|
|
186
|
+
|
|
187
|
+
if once:
|
|
188
|
+
con.print(layout)
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
watcher = ck.SourceWatcher(r)
|
|
192
|
+
con.print("[dim]Cockpit live — press Ctrl+C to exit.[/dim]")
|
|
193
|
+
try:
|
|
194
|
+
with Live(layout, console=con, refresh_per_second=4, screen=True):
|
|
195
|
+
last_full = time.time()
|
|
196
|
+
while True:
|
|
197
|
+
changed = watcher.poll()
|
|
198
|
+
if changed:
|
|
199
|
+
ck.refresh_on_change(state, changed)
|
|
200
|
+
ck.refresh_fixes_async(state)
|
|
201
|
+
if time.time() - last_full >= interval:
|
|
202
|
+
ck.update_metrics(state)
|
|
203
|
+
ck.refresh_fixes_async(state)
|
|
204
|
+
last_full = time.time()
|
|
205
|
+
_render(layout, state)
|
|
206
|
+
time.sleep(poll)
|
|
207
|
+
except KeyboardInterrupt:
|
|
208
|
+
con.print("[dim]Cockpit closed.[/dim]")
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Commands command — shows command families and their subcommands.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
p commands → lists all families with descriptions
|
|
6
|
+
p commands --flat → alphabetical list of all commands
|
|
7
|
+
p commands --search <q> → search commands by name/description
|
|
8
|
+
p <family> commands → shows subcommands in that family
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
from patchi.cli.command_families import (
|
|
18
|
+
FAMILIES,
|
|
19
|
+
format_all_families,
|
|
20
|
+
format_family_help,
|
|
21
|
+
get_family,
|
|
22
|
+
list_families,
|
|
23
|
+
)
|
|
24
|
+
from patchi.cli.console import con
|
|
25
|
+
from patchi.cli.ux import format_header, colored_status
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run(args=None):
|
|
29
|
+
"""Handle the commands command."""
|
|
30
|
+
if args is None:
|
|
31
|
+
args = sys.argv[1:]
|
|
32
|
+
|
|
33
|
+
# Parse arguments
|
|
34
|
+
flat = "--flat" in args
|
|
35
|
+
search = None
|
|
36
|
+
for i, arg in enumerate(args):
|
|
37
|
+
if arg == "--search" and i + 1 < len(args):
|
|
38
|
+
search = args[i + 1]
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
if flat:
|
|
42
|
+
_print_flat_commands()
|
|
43
|
+
elif search:
|
|
44
|
+
_search_commands(search)
|
|
45
|
+
else:
|
|
46
|
+
_print_families()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _print_families():
|
|
50
|
+
"""Print all families with descriptions."""
|
|
51
|
+
con.print()
|
|
52
|
+
con.print(format_header("Patchi Command Families"))
|
|
53
|
+
con.print()
|
|
54
|
+
|
|
55
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
56
|
+
table.add_column("Family", style="cyan", ratio=1)
|
|
57
|
+
table.add_column("Description", ratio=3)
|
|
58
|
+
table.add_column("Subcommands", ratio=2)
|
|
59
|
+
|
|
60
|
+
for family in list_families():
|
|
61
|
+
cmds = ", ".join(family.commands[:5]) if family.commands else "—"
|
|
62
|
+
if len(family.commands) > 5:
|
|
63
|
+
cmds += f" (+{len(family.commands) - 5} more)"
|
|
64
|
+
table.add_row(
|
|
65
|
+
f"p {family.name}",
|
|
66
|
+
family.description,
|
|
67
|
+
f"[dim]{cmds}[/dim]",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
con.print(table)
|
|
71
|
+
con.print()
|
|
72
|
+
con.print("[dim]Use 'p <family> commands' to see all subcommands in a family.[/dim]")
|
|
73
|
+
con.print()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _print_flat_commands():
|
|
77
|
+
"""Print all commands in alphabetical order."""
|
|
78
|
+
all_commands = []
|
|
79
|
+
for family in list_families():
|
|
80
|
+
all_commands.append((family.name, family.description))
|
|
81
|
+
for cmd in family.commands:
|
|
82
|
+
all_commands.append((f"{family.name} {cmd}", family.description))
|
|
83
|
+
|
|
84
|
+
all_commands.sort(key=lambda x: x[0])
|
|
85
|
+
|
|
86
|
+
con.print()
|
|
87
|
+
con.print(format_header("All Patchi Commands"))
|
|
88
|
+
con.print()
|
|
89
|
+
|
|
90
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
91
|
+
table.add_column("Command", style="cyan", ratio=1)
|
|
92
|
+
table.add_column("Description", ratio=3)
|
|
93
|
+
|
|
94
|
+
for cmd, desc in all_commands:
|
|
95
|
+
table.add_row(f"p {cmd}", desc)
|
|
96
|
+
|
|
97
|
+
con.print(table)
|
|
98
|
+
con.print()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _search_commands(query: str):
|
|
102
|
+
"""Search commands by name or description."""
|
|
103
|
+
query = query.lower()
|
|
104
|
+
results = []
|
|
105
|
+
|
|
106
|
+
for family in list_families():
|
|
107
|
+
# Check family name and description
|
|
108
|
+
if query in family.name.lower() or query in family.description.lower():
|
|
109
|
+
results.append((family.name, family.description, "family"))
|
|
110
|
+
|
|
111
|
+
# Check subcommands
|
|
112
|
+
for cmd in family.commands:
|
|
113
|
+
if query in cmd.lower():
|
|
114
|
+
results.append((f"{family.name} {cmd}", family.description, "subcommand"))
|
|
115
|
+
|
|
116
|
+
con.print()
|
|
117
|
+
if results:
|
|
118
|
+
con.print(format_header(f"Commands matching '{query}'"))
|
|
119
|
+
con.print()
|
|
120
|
+
|
|
121
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
122
|
+
table.add_column("Command", style="cyan", ratio=1)
|
|
123
|
+
table.add_column("Description", ratio=2)
|
|
124
|
+
table.add_column("Type", ratio=1)
|
|
125
|
+
|
|
126
|
+
for cmd, desc, cmd_type in results:
|
|
127
|
+
type_style = "blue" if cmd_type == "family" else "dim"
|
|
128
|
+
table.add_row(f"p {cmd}", desc, f"[{type_style}]{cmd_type}[/{type_style}]")
|
|
129
|
+
|
|
130
|
+
con.print(table)
|
|
131
|
+
else:
|
|
132
|
+
con.print(f"[yellow]No commands found matching '{query}'[/yellow]")
|
|
133
|
+
|
|
134
|
+
con.print()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def run_family_commands(family_name: str):
|
|
138
|
+
"""Show commands for a specific family."""
|
|
139
|
+
family = get_family(family_name)
|
|
140
|
+
if family:
|
|
141
|
+
con.print()
|
|
142
|
+
con.print(format_header(f"p {family.name}", family.description))
|
|
143
|
+
con.print()
|
|
144
|
+
|
|
145
|
+
if family.commands:
|
|
146
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
147
|
+
table.add_column("Command", style="cyan", ratio=1)
|
|
148
|
+
table.add_column("Full Command", ratio=2)
|
|
149
|
+
|
|
150
|
+
for cmd in family.commands:
|
|
151
|
+
table.add_row(cmd, f"p {family.name} {cmd}")
|
|
152
|
+
|
|
153
|
+
con.print(table)
|
|
154
|
+
else:
|
|
155
|
+
con.print("[dim]No subcommands — use 'p {family.name}' directly.[/dim]")
|
|
156
|
+
|
|
157
|
+
con.print()
|
|
158
|
+
else:
|
|
159
|
+
con.print(f"[red]Unknown family: {family_name}[/red]")
|
|
160
|
+
con.print(f"[dim]Available families: {', '.join(sorted(FAMILIES.keys()))}[/dim]")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cross-repo CLI command.
|
|
3
|
+
|
|
4
|
+
Runs cross-repository intelligence analysis to find
|
|
5
|
+
Routes, Channels, and HTTP calls across multiple indexed projects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run(args):
|
|
12
|
+
"""Entry point for the cross-repo command."""
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
# Accept either the main.py subparser shape (action / --target) or the
|
|
20
|
+
# module's own register() shape (projects / --all / --mode).
|
|
21
|
+
target_projects = list(getattr(args, "projects", None) or [])
|
|
22
|
+
if getattr(args, "target", None):
|
|
23
|
+
target_projects = [args.target]
|
|
24
|
+
mode = getattr(args, "mode", None) or "cross-repo-intelligence"
|
|
25
|
+
action = getattr(args, "action", None)
|
|
26
|
+
if action == "health":
|
|
27
|
+
mode = "health"
|
|
28
|
+
elif action == "fix":
|
|
29
|
+
mode = "fix"
|
|
30
|
+
all_flag = getattr(args, "all", False)
|
|
31
|
+
|
|
32
|
+
if not target_projects:
|
|
33
|
+
console.print("[yellow]No target projects specified.[/yellow]")
|
|
34
|
+
console.print("Usage: p cross-repo project1 project2 [project3 ...]")
|
|
35
|
+
console.print("Or: p cross-repo --all (analyze all indexed projects)")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
console.print(
|
|
39
|
+
Panel(
|
|
40
|
+
f"[bold]Cross-Repository Intelligence[/bold]\n"
|
|
41
|
+
f"Mode: {mode}\n"
|
|
42
|
+
f"Targets: {', '.join(target_projects)}",
|
|
43
|
+
border_style="blue",
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
from patchi.codebase_memory import index_repository, list_projects
|
|
49
|
+
|
|
50
|
+
if all_flag:
|
|
51
|
+
all_projects = list_projects()
|
|
52
|
+
target_projects = [p["name"] for p in all_projects if p.get("name")]
|
|
53
|
+
console.print(f"[dim]Found {len(target_projects)} indexed projects[/dim]")
|
|
54
|
+
|
|
55
|
+
if len(target_projects) < 2:
|
|
56
|
+
console.print("[red]Need at least 2 projects for cross-repo analysis.[/red]")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
console.print(
|
|
60
|
+
f"\n[bold]Running cross-repo intelligence on {len(target_projects)} projects...[/bold]\n"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
results = index_repository(
|
|
64
|
+
repo_path=".",
|
|
65
|
+
mode="cross-repo-intelligence",
|
|
66
|
+
target_projects=target_projects,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if not results:
|
|
70
|
+
console.print("[yellow]No cross-repo links found.[/yellow]")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
table = Table(title="Cross-Repository Links", border_style="green")
|
|
74
|
+
table.add_column("Source", style="cyan")
|
|
75
|
+
table.add_column("Target", style="magenta")
|
|
76
|
+
table.add_column("Type", style="yellow")
|
|
77
|
+
table.add_column("Detail", style="white")
|
|
78
|
+
|
|
79
|
+
for link in results if isinstance(results, list) else []:
|
|
80
|
+
source = str(link.get("source", link.get("from", "")))
|
|
81
|
+
target = str(link.get("target", link.get("to", "")))
|
|
82
|
+
link_type = str(link.get("type", link.get("edge_type", "")))
|
|
83
|
+
detail = str(link.get("detail", link.get("label", "")))[:80]
|
|
84
|
+
table.add_row(source, target, link_type, detail)
|
|
85
|
+
|
|
86
|
+
console.print(table)
|
|
87
|
+
console.print(
|
|
88
|
+
f"\n[green]Found {len(results) if isinstance(results, list) else 0} cross-repo links.[/green]"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
except ImportError:
|
|
92
|
+
console.print("[red]codebase-memory module not available.[/red]")
|
|
93
|
+
console.print("Install it with: pip install codebase-memory")
|
|
94
|
+
except Exception as e:
|
|
95
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def register(subparsers):
|
|
99
|
+
"""Register the cross-repo subcommand."""
|
|
100
|
+
parser = subparsers.add_parser(
|
|
101
|
+
"cross-repo",
|
|
102
|
+
help="Cross-repository intelligence analysis",
|
|
103
|
+
description="Find Routes, Channels, and HTTP calls across multiple indexed projects.",
|
|
104
|
+
)
|
|
105
|
+
parser.add_argument("projects", nargs="*", help="Target project names to analyze")
|
|
106
|
+
parser.add_argument("--all", action="store_true", help="Analyze all indexed projects")
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
"--mode",
|
|
109
|
+
default="cross-repo-intelligence",
|
|
110
|
+
help="Analysis mode (default: cross-repo-intelligence)",
|
|
111
|
+
)
|
|
112
|
+
parser.set_defaults(func=run)
|
|
113
|
+
return parser
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`p deps` — Supply chain security scan.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
p deps — run all dependency checks
|
|
6
|
+
p deps --sbom — generate SBOM
|
|
7
|
+
p deps --licenses — check license compliance
|
|
8
|
+
p deps --outdated — check for outdated packages
|
|
9
|
+
p deps --cve — check for known CVEs
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from patchi.cli.console import con
|
|
17
|
+
from patchi.cli.display.live_progress import LiveProgress
|
|
18
|
+
from patchi.core.agents.base import AgentGroup, AgentInput, list_agents
|
|
19
|
+
from patchi.core.config import require_project_root
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run(
|
|
23
|
+
sbom: bool = False,
|
|
24
|
+
licenses: bool = False,
|
|
25
|
+
outdated: bool = False,
|
|
26
|
+
cve: bool = False,
|
|
27
|
+
json_output: bool = False,
|
|
28
|
+
root: Path | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
try:
|
|
31
|
+
r = root or require_project_root()
|
|
32
|
+
except RuntimeError as e:
|
|
33
|
+
con.print(f"[red]{e}[/red]")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
from patchi.core import config as cfg_mod
|
|
37
|
+
from patchi.core import memory as mem
|
|
38
|
+
|
|
39
|
+
brain = mem.get_brain(r)
|
|
40
|
+
config = cfg_mod.load(r)
|
|
41
|
+
|
|
42
|
+
all_agents = {a.name: a for a in list_agents(AgentGroup.SECURITY)}
|
|
43
|
+
target_agents = []
|
|
44
|
+
|
|
45
|
+
no_flags = not any([sbom, licenses, outdated, cve])
|
|
46
|
+
if cve or no_flags:
|
|
47
|
+
target_agents.append("CVEMonitorAgent")
|
|
48
|
+
target_agents.append("DependencyCVEChecker")
|
|
49
|
+
if sbom or licenses or no_flags:
|
|
50
|
+
target_agents.append("SupplyChainAgent")
|
|
51
|
+
if outdated or no_flags:
|
|
52
|
+
target_agents.append("DependencyVulnerabilityAgent")
|
|
53
|
+
|
|
54
|
+
target_agents = list(dict.fromkeys(target_agents)) # dedup
|
|
55
|
+
|
|
56
|
+
results = []
|
|
57
|
+
lp = LiveProgress(con, title="Dependency scan")
|
|
58
|
+
lp.start()
|
|
59
|
+
for name in target_agents:
|
|
60
|
+
cls = all_agents.get(name)
|
|
61
|
+
if not cls:
|
|
62
|
+
lp.log(f" [yellow]Agent {name} not found.[/yellow]")
|
|
63
|
+
lp.update()
|
|
64
|
+
continue
|
|
65
|
+
lp.set_progress(len(results) + 1, len(target_agents), name)
|
|
66
|
+
lp.log(f" {name}…")
|
|
67
|
+
lp.update()
|
|
68
|
+
inp = AgentInput(
|
|
69
|
+
root=r,
|
|
70
|
+
scope=[],
|
|
71
|
+
brain=brain,
|
|
72
|
+
config=config,
|
|
73
|
+
on_message=lambda n, msg, s: (lp.log(f" {msg}", s), lp.update()),
|
|
74
|
+
)
|
|
75
|
+
try:
|
|
76
|
+
result = cls().run(inp)
|
|
77
|
+
results.append(result)
|
|
78
|
+
total_findings = sum(r.finding_count for r in results)
|
|
79
|
+
lp.set_findings(total_findings)
|
|
80
|
+
lp.log(f" [#4ADE80]v[/#4ADE80] {result.finding_count} findings", "")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
lp.log(f" [red]x {name} failed: {e}[/red]", "red")
|
|
83
|
+
lp.update()
|
|
84
|
+
|
|
85
|
+
total_findings = sum(r.finding_count for r in results)
|
|
86
|
+
lp.stop(summary=f"{len(results)} agents - {total_findings} findings")
|
|
87
|
+
|
|
88
|
+
if json_output:
|
|
89
|
+
import json as _json
|
|
90
|
+
|
|
91
|
+
con.print(
|
|
92
|
+
_json.dumps(
|
|
93
|
+
{
|
|
94
|
+
"agents": [
|
|
95
|
+
{
|
|
96
|
+
"agent": getattr(res, "agent_name", ""),
|
|
97
|
+
"status": getattr(getattr(res, "status", None), "value", ""),
|
|
98
|
+
"findings": [
|
|
99
|
+
f.to_dict() if hasattr(f, "to_dict") else dict(f)
|
|
100
|
+
for f in (getattr(res, "findings", []) or [])
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
for res in results
|
|
104
|
+
],
|
|
105
|
+
"total_findings": total_findings,
|
|
106
|
+
},
|
|
107
|
+
indent=2,
|
|
108
|
+
default=str,
|
|
109
|
+
)
|
|
110
|
+
)
|