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,240 @@
|
|
|
1
|
+
"""CICDGeneratorAgent — generates CI/CD configs and pre-commit hooks.
|
|
2
|
+
|
|
3
|
+
Covers §10.1.1-4:
|
|
4
|
+
- Pre-commit hook config (.pre-commit-config.yaml)
|
|
5
|
+
- GitHub Actions workflow
|
|
6
|
+
- GitLab CI template
|
|
7
|
+
- Branch protection gate suggestions
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .base import (
|
|
13
|
+
AgentGroup,
|
|
14
|
+
AgentInput,
|
|
15
|
+
AgentResult,
|
|
16
|
+
AgentStatus,
|
|
17
|
+
BaseAgent,
|
|
18
|
+
Severity,
|
|
19
|
+
make_finding,
|
|
20
|
+
register,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
_PRE_COMMIT_CONFIG = """# Auto-generated by Patchi — pre-commit hooks for instant feedback
|
|
24
|
+
repos:
|
|
25
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
26
|
+
rev: v4.6.0
|
|
27
|
+
hooks:
|
|
28
|
+
- id: trailing-whitespace
|
|
29
|
+
- id: end-of-file-fixer
|
|
30
|
+
- id: check-yaml
|
|
31
|
+
- id: check-json
|
|
32
|
+
- id: check-added-large-files
|
|
33
|
+
- id: detect-private-key
|
|
34
|
+
|
|
35
|
+
- repo: local
|
|
36
|
+
hooks:
|
|
37
|
+
- id: patchi-sast
|
|
38
|
+
name: Patchi fast SAST (Bandit + Semgrep)
|
|
39
|
+
entry: python -m patchi.core.security.sast_gate
|
|
40
|
+
language: system
|
|
41
|
+
types: [python]
|
|
42
|
+
pass_filenames: true
|
|
43
|
+
- id: patchi-scan
|
|
44
|
+
name: Patchi scan
|
|
45
|
+
entry: patchi scan --staged
|
|
46
|
+
language: system
|
|
47
|
+
pass_filenames: false
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
_GITHUB_ACTIONS_WORKFLOW = """# Auto-generated by Patchi — CI workflow with explicit SAST tools
|
|
51
|
+
name: Patchi Security
|
|
52
|
+
on:
|
|
53
|
+
push:
|
|
54
|
+
branches: [main, develop]
|
|
55
|
+
pull_request:
|
|
56
|
+
branches: [main]
|
|
57
|
+
|
|
58
|
+
jobs:
|
|
59
|
+
sast:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v4
|
|
63
|
+
with:
|
|
64
|
+
fetch-depth: 0
|
|
65
|
+
|
|
66
|
+
- uses: actions/setup-python@v5
|
|
67
|
+
with:
|
|
68
|
+
python-version: "3.11"
|
|
69
|
+
|
|
70
|
+
- name: Install SAST tools
|
|
71
|
+
run: pip install bandit semgrep
|
|
72
|
+
|
|
73
|
+
- name: Bandit (SARIF)
|
|
74
|
+
run: bandit -r . -f sarif -o bandit.sarif || true
|
|
75
|
+
|
|
76
|
+
- name: Semgrep (SARIF)
|
|
77
|
+
run: semgrep --sarif --config auto --output semgrep.sarif || true
|
|
78
|
+
|
|
79
|
+
- name: Patchi consensus scan
|
|
80
|
+
run: patchi scan --sarif .patchi/output.sarif || true
|
|
81
|
+
|
|
82
|
+
- name: Upload Bandit SARIF
|
|
83
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
84
|
+
with:
|
|
85
|
+
sarif_file: bandit.sarif
|
|
86
|
+
category: bandit
|
|
87
|
+
|
|
88
|
+
- name: Upload Semgrep SARIF
|
|
89
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
90
|
+
with:
|
|
91
|
+
sarif_file: semgrep.sarif
|
|
92
|
+
category: semgrep
|
|
93
|
+
|
|
94
|
+
- name: Upload Patchi SARIF
|
|
95
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
96
|
+
with:
|
|
97
|
+
sarif_file: .patchi/output.sarif
|
|
98
|
+
category: patchi
|
|
99
|
+
|
|
100
|
+
codeql:
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
permissions:
|
|
103
|
+
security-events: write
|
|
104
|
+
steps:
|
|
105
|
+
- uses: actions/checkout@v4
|
|
106
|
+
- name: Initialize CodeQL
|
|
107
|
+
uses: github/codeql-action/init@v3
|
|
108
|
+
with:
|
|
109
|
+
languages: python
|
|
110
|
+
- name: Perform CodeQL Analysis
|
|
111
|
+
uses: github/codeql-action/analyze@v3
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
_GITLAB_CI_TEMPLATE = """# Auto-generated by Patchi — GitLab CI template with explicit SAST tools
|
|
115
|
+
stages:
|
|
116
|
+
- analyze
|
|
117
|
+
|
|
118
|
+
bandit:
|
|
119
|
+
stage: analyze
|
|
120
|
+
image: python:3.11-slim
|
|
121
|
+
before_script:
|
|
122
|
+
- pip install bandit
|
|
123
|
+
script:
|
|
124
|
+
- bandit -r . -f json -o bandit.json || true
|
|
125
|
+
artifacts:
|
|
126
|
+
reports:
|
|
127
|
+
sast: bandit.json
|
|
128
|
+
|
|
129
|
+
semgrep:
|
|
130
|
+
stage: analyze
|
|
131
|
+
image: python:3.11-slim
|
|
132
|
+
before_script:
|
|
133
|
+
- pip install semgrep
|
|
134
|
+
script:
|
|
135
|
+
- semgrep --json --config auto --output semgrep.json || true
|
|
136
|
+
artifacts:
|
|
137
|
+
reports:
|
|
138
|
+
sast: semgrep.json
|
|
139
|
+
|
|
140
|
+
patchi-scan:
|
|
141
|
+
stage: analyze
|
|
142
|
+
image: python:3.11-slim
|
|
143
|
+
before_script:
|
|
144
|
+
- pip install patchi
|
|
145
|
+
script:
|
|
146
|
+
- patchi scan
|
|
147
|
+
artifacts:
|
|
148
|
+
reports:
|
|
149
|
+
code_quality: .patchi/output.sarif
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@register
|
|
154
|
+
class CICDGeneratorAgent(BaseAgent):
|
|
155
|
+
"""Generates CI/CD configs: pre-commit hooks, GitHub Actions, GitLab CI, branch protection."""
|
|
156
|
+
|
|
157
|
+
group = AgentGroup.GUARD
|
|
158
|
+
name = "CICDGeneratorAgent"
|
|
159
|
+
description = (
|
|
160
|
+
"Generate .pre-commit-config.yaml, GitHub Actions, GitLab CI, and branch protection gates"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
164
|
+
existing_precommit = inp.root / ".pre-commit-config.yaml"
|
|
165
|
+
existing_github = inp.root / ".github" / "workflows" / "patchi-ci.yml"
|
|
166
|
+
existing_gitlab = inp.root / ".gitlab-ci.yml"
|
|
167
|
+
|
|
168
|
+
result.data["generated"] = []
|
|
169
|
+
|
|
170
|
+
if not existing_precommit.exists():
|
|
171
|
+
result.data["generated"].append(".pre-commit-config.yaml")
|
|
172
|
+
result.findings.append(
|
|
173
|
+
make_finding(
|
|
174
|
+
self.name,
|
|
175
|
+
"precommit_config",
|
|
176
|
+
Severity.INFO,
|
|
177
|
+
".pre-commit-config.yaml",
|
|
178
|
+
"Generate pre-commit hook config for instant feedback",
|
|
179
|
+
detail=_PRE_COMMIT_CONFIG[:200],
|
|
180
|
+
suggestion="Run `patchi fix` to write this file",
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if not existing_github.exists():
|
|
185
|
+
result.data["generated"].append(".github/workflows/patchi-ci.yml")
|
|
186
|
+
result.findings.append(
|
|
187
|
+
make_finding(
|
|
188
|
+
self.name,
|
|
189
|
+
"github_actions_workflow",
|
|
190
|
+
Severity.INFO,
|
|
191
|
+
".github/workflows/patchi-ci.yml",
|
|
192
|
+
"Generate GitHub Actions workflow for CI",
|
|
193
|
+
detail=_GITHUB_ACTIONS_WORKFLOW[:200],
|
|
194
|
+
suggestion="Run `patchi fix` to write this file",
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if not existing_gitlab.exists():
|
|
199
|
+
result.data["generated"].append(".gitlab-ci.yml")
|
|
200
|
+
result.findings.append(
|
|
201
|
+
make_finding(
|
|
202
|
+
self.name,
|
|
203
|
+
"gitlab_ci_template",
|
|
204
|
+
Severity.INFO,
|
|
205
|
+
".gitlab-ci.yml",
|
|
206
|
+
"Generate GitLab CI template for CI",
|
|
207
|
+
detail=_GITLAB_CI_TEMPLATE[:200],
|
|
208
|
+
suggestion="Run `patchi fix` to write this file",
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Branch protection gates check
|
|
213
|
+
has_branch_protection = False
|
|
214
|
+
for conf in ["CODEOWNERS", ".github/CODEOWNERS", "CODEOWNERS.txt"]:
|
|
215
|
+
if (inp.root / conf).exists():
|
|
216
|
+
has_branch_protection = True
|
|
217
|
+
break
|
|
218
|
+
|
|
219
|
+
if not has_branch_protection:
|
|
220
|
+
result.findings.append(
|
|
221
|
+
make_finding(
|
|
222
|
+
self.name,
|
|
223
|
+
"branch_protection",
|
|
224
|
+
Severity.MEDIUM,
|
|
225
|
+
"",
|
|
226
|
+
"No branch protection or CODEOWNERS detected",
|
|
227
|
+
detail="Set up branch protection rules requiring Patchi to pass before merge",
|
|
228
|
+
suggestion="Configure branch protection in GitHub/GitLab settings",
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
result.data["templates"] = {
|
|
233
|
+
"precommit": _PRE_COMMIT_CONFIG,
|
|
234
|
+
"github_actions": _GITHUB_ACTIONS_WORKFLOW,
|
|
235
|
+
"gitlab_ci": _GITLAB_CI_TEMPLATE,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
result.files_scanned = 3
|
|
239
|
+
if result.status == AgentStatus.RUNNING:
|
|
240
|
+
result.status = AgentStatus.DONE
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CommentScanner — TODO/FIXME/HACK technical debt markers.
|
|
3
|
+
|
|
4
|
+
Scans for technical debt markers in comments:
|
|
5
|
+
- TODO: Planned features or improvements
|
|
6
|
+
- FIXME: Known issues that need fixing
|
|
7
|
+
- HACK: Workarounds that should be addressed
|
|
8
|
+
- XXX: Critical issues requiring attention
|
|
9
|
+
- NOSONAR: SonarQube suppression markers
|
|
10
|
+
- TEMP: Temporary code that needs removal
|
|
11
|
+
- KLUDGE: Crude solutions that need refinement
|
|
12
|
+
- BUG: Known bug markers
|
|
13
|
+
- REVIEW: Code requiring review
|
|
14
|
+
- OPTIMIZE: Performance improvement opportunities
|
|
15
|
+
|
|
16
|
+
Does NOT call AI.
|
|
17
|
+
Does NOT write to disk.
|
|
18
|
+
Does NOT touch the queue.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from ..brain.languages import DEFAULT_IGNORE_DIRS, Lang, detect_language
|
|
27
|
+
from .base import (
|
|
28
|
+
AgentGroup,
|
|
29
|
+
AgentInput,
|
|
30
|
+
AgentResult,
|
|
31
|
+
AgentStatus,
|
|
32
|
+
BaseAgent,
|
|
33
|
+
Finding,
|
|
34
|
+
Severity,
|
|
35
|
+
make_finding,
|
|
36
|
+
register,
|
|
37
|
+
safe_rglob,
|
|
38
|
+
scope_allows,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@register
|
|
43
|
+
class CommentScanner(BaseAgent):
|
|
44
|
+
"""Scanner for technical debt markers in comments."""
|
|
45
|
+
|
|
46
|
+
group = AgentGroup.SCANNER
|
|
47
|
+
name = "CommentScanner"
|
|
48
|
+
description = "TODO/FIXME/HACK technical debt markers"
|
|
49
|
+
|
|
50
|
+
# Technical debt patterns with severity levels.
|
|
51
|
+
# Comment markers are hygiene, never vulnerabilities: capped at MEDIUM
|
|
52
|
+
# (a "BUG" comment was flagging CRITICAL and drowning the gate).
|
|
53
|
+
TECH_DEBT_PATTERNS = [
|
|
54
|
+
(r"XXX\b", "XXX", Severity.MEDIUM),
|
|
55
|
+
(r"BUG\b", "BUG", Severity.MEDIUM),
|
|
56
|
+
(r"HACK\b", "HACK", Severity.MEDIUM),
|
|
57
|
+
# Important issues
|
|
58
|
+
(r"FIXME\b", "FIXME", Severity.MEDIUM),
|
|
59
|
+
(r"TODO\b", "TODO", Severity.INFO),
|
|
60
|
+
# Less urgent but still noteworthy
|
|
61
|
+
(r"REVIEW\b", "REVIEW", Severity.LOW),
|
|
62
|
+
(r"KLUDGE\b", "KLUDGE", Severity.MEDIUM),
|
|
63
|
+
(r"TEMP\b", "TEMPORARY", Severity.LOW),
|
|
64
|
+
(r"OPTIMIZE\b", "OPTIMIZE", Severity.LOW),
|
|
65
|
+
(r"NOSONAR\b", "NOSONAR", Severity.LOW), # SonarQube suppression
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
69
|
+
"""Scan for technical debt markers in comments."""
|
|
70
|
+
findings = []
|
|
71
|
+
|
|
72
|
+
# Define file patterns for all supported languages
|
|
73
|
+
patterns = [
|
|
74
|
+
"*.js",
|
|
75
|
+
"*.jsx",
|
|
76
|
+
"*.ts",
|
|
77
|
+
"*.tsx",
|
|
78
|
+
"*.py",
|
|
79
|
+
"*.java",
|
|
80
|
+
"*.php",
|
|
81
|
+
"*.rb",
|
|
82
|
+
"*.go",
|
|
83
|
+
"*.rs",
|
|
84
|
+
"*.cpp",
|
|
85
|
+
"*.cxx",
|
|
86
|
+
"*.cc",
|
|
87
|
+
"*.c",
|
|
88
|
+
"*.h",
|
|
89
|
+
"*.hpp",
|
|
90
|
+
"*.cs",
|
|
91
|
+
"*.scala",
|
|
92
|
+
"*.kt",
|
|
93
|
+
"*.swift",
|
|
94
|
+
"*.dart",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
# Search for files
|
|
98
|
+
for pattern in patterns:
|
|
99
|
+
for file_path in safe_rglob(inp.root, pattern):
|
|
100
|
+
if file_path.is_file():
|
|
101
|
+
rel_path = file_path.relative_to(inp.root).as_posix()
|
|
102
|
+
if not scope_allows(inp, rel_path):
|
|
103
|
+
continue
|
|
104
|
+
if not self._should_skip_file(rel_path, inp):
|
|
105
|
+
findings.extend(self._scan_file_comments(file_path, rel_path))
|
|
106
|
+
|
|
107
|
+
# Add summary when technical debt markers exist
|
|
108
|
+
debt_findings = [
|
|
109
|
+
f
|
|
110
|
+
for f in findings
|
|
111
|
+
if any(marker_name in f.title for _, marker_name, _ in self.TECH_DEBT_PATTERNS)
|
|
112
|
+
]
|
|
113
|
+
if debt_findings:
|
|
114
|
+
findings.append(
|
|
115
|
+
make_finding(
|
|
116
|
+
severity=Severity.INFO,
|
|
117
|
+
file="__summary__",
|
|
118
|
+
line_start=0,
|
|
119
|
+
title=f"Technical Debt Markers Found: {len(debt_findings)}",
|
|
120
|
+
description=f"Discovered {len(debt_findings)} technical debt markers in project",
|
|
121
|
+
evidence=f"Markers found in {len({f.file for f in debt_findings})} files",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
by_type: dict[str, int] = {}
|
|
126
|
+
for finding in debt_findings:
|
|
127
|
+
match = re.search(r"Technical Debt Marker: (\w+)", finding.title)
|
|
128
|
+
if match:
|
|
129
|
+
marker = match.group(1)
|
|
130
|
+
by_type[marker] = by_type.get(marker, 0) + 1
|
|
131
|
+
|
|
132
|
+
result.status = AgentStatus.SUCCEEDED
|
|
133
|
+
result.findings = findings
|
|
134
|
+
result.data.update(
|
|
135
|
+
{
|
|
136
|
+
"todo_count": len(
|
|
137
|
+
[
|
|
138
|
+
f
|
|
139
|
+
for f in findings
|
|
140
|
+
if f.type == "technical_debt" and "TODO" in f.message.upper()
|
|
141
|
+
]
|
|
142
|
+
),
|
|
143
|
+
"fixme_count": len(
|
|
144
|
+
[
|
|
145
|
+
f
|
|
146
|
+
for f in findings
|
|
147
|
+
if f.type == "technical_debt" and "FIXME" in f.message.upper()
|
|
148
|
+
]
|
|
149
|
+
),
|
|
150
|
+
"hack_count": len(
|
|
151
|
+
[
|
|
152
|
+
f
|
|
153
|
+
for f in findings
|
|
154
|
+
if f.type == "technical_debt" and "HACK" in f.message.upper()
|
|
155
|
+
]
|
|
156
|
+
),
|
|
157
|
+
"bug_count": len(
|
|
158
|
+
[
|
|
159
|
+
f
|
|
160
|
+
for f in findings
|
|
161
|
+
if f.type == "technical_debt" and "BUG" in f.message.upper()
|
|
162
|
+
]
|
|
163
|
+
),
|
|
164
|
+
"total_markers": len(debt_findings),
|
|
165
|
+
"by_type": by_type,
|
|
166
|
+
"needs_ai": False,
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
def _should_skip_file(self, file_path: str, inp: AgentInput) -> bool:
|
|
172
|
+
"""Check if file should be skipped based on restrictions."""
|
|
173
|
+
# Skip ignored directories (node_modules, .venv, etc.)
|
|
174
|
+
from pathlib import PurePosixPath
|
|
175
|
+
|
|
176
|
+
if any(p in DEFAULT_IGNORE_DIRS for p in PurePosixPath(file_path).parts):
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
from pathlib import PurePosixPath
|
|
180
|
+
|
|
181
|
+
# Check restrictions
|
|
182
|
+
restrictions = inp.config.get("restrictions", [])
|
|
183
|
+
for r in restrictions:
|
|
184
|
+
if r.get("enabled", True):
|
|
185
|
+
path = r["path"]
|
|
186
|
+
if file_path.startswith(path) or PurePosixPath(file_path).match(path):
|
|
187
|
+
if r["type"] == "NO_TOUCH":
|
|
188
|
+
return True
|
|
189
|
+
elif r["type"] == "SCAN_ONLY" and self.__class__.__name__ == "FixAgent":
|
|
190
|
+
return True
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
def _scan_file_comments(self, file_path: Path, rel_path: str) -> list[Finding]:
|
|
194
|
+
"""Scan a file for technical debt markers in comments."""
|
|
195
|
+
findings = []
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
content = file_path.read_text(encoding="utf-8")
|
|
199
|
+
lines = content.splitlines()
|
|
200
|
+
|
|
201
|
+
# Different comment patterns for different languages
|
|
202
|
+
lang = detect_language(file_path)
|
|
203
|
+
comment_patterns = self._get_comment_patterns(lang)
|
|
204
|
+
|
|
205
|
+
for line_num, line in enumerate(lines, 1):
|
|
206
|
+
# Find all comment sections in the line
|
|
207
|
+
for comment_pattern in comment_patterns:
|
|
208
|
+
comment_matches = re.finditer(comment_pattern, line)
|
|
209
|
+
for comment_match in comment_matches:
|
|
210
|
+
comment_text = comment_match.group(1) # The content of the comment
|
|
211
|
+
|
|
212
|
+
# Look for technical debt markers in the comment
|
|
213
|
+
for pattern, marker_name, severity in self.TECH_DEBT_PATTERNS:
|
|
214
|
+
debt_matches = re.finditer(pattern, comment_text, re.IGNORECASE)
|
|
215
|
+
for debt_match in debt_matches:
|
|
216
|
+
# Extract context around the marker
|
|
217
|
+
start_idx = max(0, debt_match.start() - 20)
|
|
218
|
+
end_idx = min(len(comment_text), debt_match.end() + 40)
|
|
219
|
+
context = comment_text[start_idx:end_idx]
|
|
220
|
+
|
|
221
|
+
findings.append(
|
|
222
|
+
make_finding(
|
|
223
|
+
severity=severity,
|
|
224
|
+
finding_type="technical_debt",
|
|
225
|
+
file=rel_path,
|
|
226
|
+
line_start=line_num,
|
|
227
|
+
title=f"Technical Debt Marker: {marker_name}",
|
|
228
|
+
description=f"Found {marker_name} marker in comment",
|
|
229
|
+
evidence=f"Context: {context.strip()}",
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
findings.append(
|
|
235
|
+
make_finding(
|
|
236
|
+
severity=Severity.LOW,
|
|
237
|
+
file=rel_path,
|
|
238
|
+
line_start=0,
|
|
239
|
+
title="Comment file read error",
|
|
240
|
+
description=f"Could not read file {file_path.name} for comment scanning: {str(e)}",
|
|
241
|
+
evidence=str(e),
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return findings
|
|
246
|
+
|
|
247
|
+
def _get_comment_patterns(self, lang: Lang) -> list[str]:
|
|
248
|
+
"""Get appropriate comment patterns for the language."""
|
|
249
|
+
# Use a broad set of comment capture patterns for most languages.
|
|
250
|
+
return [
|
|
251
|
+
r"#(.*)$", # Hash-style comment
|
|
252
|
+
r"//(.*)$", # Double-slash comment
|
|
253
|
+
r"/\*(.*?)\*/", # C-style multi-line comment
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
def _get_language_extensions(self) -> dict[str, Lang]:
|
|
257
|
+
"""Map file extensions to language enums."""
|
|
258
|
+
return {
|
|
259
|
+
".js": Lang.JAVASCRIPT,
|
|
260
|
+
".jsx": Lang.JAVASCRIPT,
|
|
261
|
+
".ts": Lang.TYPESCRIPT,
|
|
262
|
+
".tsx": Lang.TYPESCRIPT,
|
|
263
|
+
".py": Lang.PYTHON,
|
|
264
|
+
".java": Lang.JAVA,
|
|
265
|
+
".php": Lang.PHP,
|
|
266
|
+
".rb": Lang.RUBY,
|
|
267
|
+
".go": Lang.GO,
|
|
268
|
+
".rs": Lang.RUST,
|
|
269
|
+
".cpp": Lang.CPP,
|
|
270
|
+
".cxx": Lang.CPP,
|
|
271
|
+
".cc": Lang.CPP,
|
|
272
|
+
".c": Lang.C,
|
|
273
|
+
".h": Lang.C,
|
|
274
|
+
".hpp": Lang.CPP,
|
|
275
|
+
".cs": Lang.C_SHARP,
|
|
276
|
+
".scala": Lang.SCALA,
|
|
277
|
+
".kt": Lang.KOTLIN,
|
|
278
|
+
".swift": Lang.SWIFT,
|
|
279
|
+
".dart": Lang.DART,
|
|
280
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""ConsoleLoggingAgent §p test — background console/pageerror capture.
|
|
2
|
+
|
|
3
|
+
Runs automatically when `p test` is active (Group.TEST), never under `p scan`.
|
|
4
|
+
Attaches Playwright console + pageerror listeners on every discovered route
|
|
5
|
+
(via shared open_page helper) and tails the app launcher log, so JS runtime
|
|
6
|
+
noise is captured even on pages other testers skip. Findings are console-error
|
|
7
|
+
clusters; raw traces go to result.data for brain/verify.py merging.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from collections import Counter
|
|
14
|
+
|
|
15
|
+
from patchi.core.agents.base import (
|
|
16
|
+
AgentGroup,
|
|
17
|
+
AgentInput,
|
|
18
|
+
AgentResult,
|
|
19
|
+
AgentStatus,
|
|
20
|
+
BaseAgent,
|
|
21
|
+
Severity,
|
|
22
|
+
make_finding,
|
|
23
|
+
register,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
_log = logging.getLogger("patchi.agents.console_logging")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@register
|
|
30
|
+
class ConsoleLoggingAgent(BaseAgent):
|
|
31
|
+
group = AgentGroup.TEST
|
|
32
|
+
name = "ConsoleLoggingAgent"
|
|
33
|
+
description = "Background console/pageerror capture across routes + app log tail"
|
|
34
|
+
timeout = 60
|
|
35
|
+
|
|
36
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
37
|
+
try:
|
|
38
|
+
from playwright.sync_api import sync_playwright # noqa: F401
|
|
39
|
+
except ImportError:
|
|
40
|
+
self.skip(result, "playwright not installed — pip install playwright")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
from patchi.core.testing._browser import discover_routes, find_server, open_page
|
|
44
|
+
|
|
45
|
+
base_url = find_server(inp.root, inp.config, inp.extra)
|
|
46
|
+
if not base_url:
|
|
47
|
+
self.skip(result, "no running server found — start `p web` (default :1612)")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
routes = discover_routes(inp.config, inp.extra)
|
|
51
|
+
if not routes:
|
|
52
|
+
self.skip(result, "no routes discovered")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
from patchi.core.brain.trace_log import trace_agent
|
|
56
|
+
|
|
57
|
+
console_errors: list[str] = []
|
|
58
|
+
page_errors: list[str] = []
|
|
59
|
+
pages_tested = 0
|
|
60
|
+
error_pages = 0
|
|
61
|
+
|
|
62
|
+
with trace_agent(self.name, root=inp.root) as trace:
|
|
63
|
+
with sync_playwright() as pw:
|
|
64
|
+
browser = pw.chromium.launch(headless=True)
|
|
65
|
+
try:
|
|
66
|
+
for page_path in routes[:15]:
|
|
67
|
+
ps = open_page(browser, f"{base_url}{page_path}")
|
|
68
|
+
try:
|
|
69
|
+
if ps.status and ps.status >= 400:
|
|
70
|
+
error_pages += 1
|
|
71
|
+
continue
|
|
72
|
+
for ce in ps.console_errors:
|
|
73
|
+
console_errors.append(f"{page_path} :: {ce[:200]}")
|
|
74
|
+
for pe in ps.page_errors:
|
|
75
|
+
page_errors.append(f"{page_path} :: {pe[:200]}")
|
|
76
|
+
pages_tested += 1
|
|
77
|
+
finally:
|
|
78
|
+
ps.page.close()
|
|
79
|
+
finally:
|
|
80
|
+
browser.close()
|
|
81
|
+
|
|
82
|
+
# Tail the app launcher log for server-side noise
|
|
83
|
+
app_log_lines: list[str] = []
|
|
84
|
+
log_path = inp.root / ".patchi" / "launcher" / "app.log"
|
|
85
|
+
try:
|
|
86
|
+
if log_path.exists():
|
|
87
|
+
text = log_path.read_text(encoding="utf-8", errors="replace")
|
|
88
|
+
app_log_lines = [ln for ln in text.splitlines() if ln.strip()][-50:]
|
|
89
|
+
except OSError as exc:
|
|
90
|
+
_log.debug("app log tail failed: %s", exc)
|
|
91
|
+
|
|
92
|
+
trace.files_scanned = pages_tested
|
|
93
|
+
|
|
94
|
+
# Cluster console errors so one noisy library = one finding
|
|
95
|
+
clusters = Counter()
|
|
96
|
+
for entry in console_errors:
|
|
97
|
+
route, _, msg = entry.partition(" :: ")
|
|
98
|
+
key = (route, msg[:80])
|
|
99
|
+
clusters[key] += 1
|
|
100
|
+
for (route, msg), count in sorted(clusters.items(), key=lambda kv: -kv[1])[:10]:
|
|
101
|
+
text = msg[:160].strip() or "empty error text"
|
|
102
|
+
result.add_finding(
|
|
103
|
+
make_finding(
|
|
104
|
+
agent=self.name,
|
|
105
|
+
finding_type="console_error",
|
|
106
|
+
severity=Severity.LOW if count == 1 else Severity.MEDIUM,
|
|
107
|
+
file=route,
|
|
108
|
+
message=f"Console error ×{count} on {route}: {text}",
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
for entry in page_errors[:10]:
|
|
112
|
+
route, _, msg = entry.partition(" :: ")
|
|
113
|
+
text = msg[:160].strip() or "empty error text"
|
|
114
|
+
result.add_finding(
|
|
115
|
+
make_finding(
|
|
116
|
+
agent=self.name,
|
|
117
|
+
finding_type="page_error",
|
|
118
|
+
severity=Severity.MEDIUM,
|
|
119
|
+
file=route,
|
|
120
|
+
message=f"Uncaught page error on {route}: {text}",
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
if error_pages:
|
|
124
|
+
result.add_finding(
|
|
125
|
+
make_finding(
|
|
126
|
+
agent=self.name,
|
|
127
|
+
finding_type="console_error_pages",
|
|
128
|
+
severity=Severity.HIGH,
|
|
129
|
+
file="(all pages)",
|
|
130
|
+
message=f"{error_pages} page(s) HTTP ≥400 — console capture skipped",
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
if not console_errors and not page_errors and pages_tested > 0:
|
|
134
|
+
result.add_finding(
|
|
135
|
+
make_finding(
|
|
136
|
+
agent=self.name,
|
|
137
|
+
finding_type="console_summary",
|
|
138
|
+
severity=Severity.INFO,
|
|
139
|
+
file="(all pages)",
|
|
140
|
+
message=f"Console clean: {pages_tested} pages, 0 console/page errors",
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
result.data["suite"] = {
|
|
145
|
+
"runner": "console_logging",
|
|
146
|
+
"pages_tested": pages_tested,
|
|
147
|
+
"console_errors": console_errors[:10],
|
|
148
|
+
"page_errors": page_errors[:10],
|
|
149
|
+
"error_pages": error_pages,
|
|
150
|
+
"app_log_tail": app_log_lines[-10:],
|
|
151
|
+
}
|
|
152
|
+
result.files_scanned = pages_tested
|
|
153
|
+
trace.findings = len(result.findings)
|
|
154
|
+
|
|
155
|
+
if result.status == AgentStatus.RUNNING:
|
|
156
|
+
result.status = AgentStatus.DONE
|