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,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CrossRepo §11.3 — multi-repo index for shared library drift.
|
|
3
|
+
|
|
4
|
+
Indexes multiple repo roots (siblings or explicit list) and compares dependency versions + API usage.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import re
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
_log = logging.getLogger("patchi.brain.cross_repo")
|
|
15
|
+
|
|
16
|
+
def index_repos(roots: list[Path]) -> dict:
|
|
17
|
+
idx={}
|
|
18
|
+
for r in roots:
|
|
19
|
+
try:
|
|
20
|
+
from patchi.core.brain.file_corpus import FileCorpus
|
|
21
|
+
corpus=FileCorpus(r)
|
|
22
|
+
idx[str(r)]={"files": len(list(corpus.files())), "deps": _deps(r)}
|
|
23
|
+
except Exception:
|
|
24
|
+
idx[str(r)]={"files":0,"deps":{}}
|
|
25
|
+
return idx
|
|
26
|
+
|
|
27
|
+
def _deps(root: Path) -> dict:
|
|
28
|
+
"""Parsed {package: version} per manifest (not raw text)."""
|
|
29
|
+
deps: dict[str, dict[str, str]] = {}
|
|
30
|
+
try:
|
|
31
|
+
p = root / "package.json"
|
|
32
|
+
if p.exists():
|
|
33
|
+
pkg = json.loads(p.read_text(encoding="utf-8", errors="replace"))
|
|
34
|
+
merged = {}
|
|
35
|
+
for section in ("dependencies", "devDependencies", "peerDependencies"):
|
|
36
|
+
for name, ver in (pkg.get(section) or {}).items():
|
|
37
|
+
merged[str(name)] = str(ver)
|
|
38
|
+
if merged:
|
|
39
|
+
deps["package.json"] = merged
|
|
40
|
+
except Exception as _exc:
|
|
41
|
+
_log.debug("package.json parse failed: %s", _exc)
|
|
42
|
+
try:
|
|
43
|
+
p = root / "requirements.txt"
|
|
44
|
+
if p.exists():
|
|
45
|
+
parsed = {}
|
|
46
|
+
for line in p.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
47
|
+
line = line.strip()
|
|
48
|
+
if not line or line.startswith(("#", "-", " ")):
|
|
49
|
+
continue
|
|
50
|
+
m = re.match(r"^([A-Za-z0-9_.\-]+)\s*(?:==|>=|<=|~=|!=|>|<)?\s*([^;\s]*)", line)
|
|
51
|
+
if m:
|
|
52
|
+
parsed[m.group(1).lower()] = m.group(2) or "unpinned"
|
|
53
|
+
if parsed:
|
|
54
|
+
deps["requirements.txt"] = parsed
|
|
55
|
+
except Exception as _exc:
|
|
56
|
+
_log.debug("requirements parse failed: %s", _exc)
|
|
57
|
+
try:
|
|
58
|
+
p = root / "go.mod"
|
|
59
|
+
if p.exists():
|
|
60
|
+
parsed = {}
|
|
61
|
+
for line in p.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
62
|
+
m = re.match(r"^\s*([a-z0-9./\-_]+)\s+(v[\w.+\-]+)", line.strip(), re.I)
|
|
63
|
+
if m and "module" not in line and "go " != line[:3]:
|
|
64
|
+
parsed[m.group(1)] = m.group(2)
|
|
65
|
+
if parsed:
|
|
66
|
+
deps["go.mod"] = parsed
|
|
67
|
+
except Exception as _exc:
|
|
68
|
+
_log.debug("go.mod parse failed: %s", _exc)
|
|
69
|
+
try:
|
|
70
|
+
p = root / "Cargo.toml"
|
|
71
|
+
if p.exists():
|
|
72
|
+
parsed = {}
|
|
73
|
+
in_deps = False
|
|
74
|
+
for line in p.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
75
|
+
s = line.strip()
|
|
76
|
+
if s.startswith("["):
|
|
77
|
+
in_deps = "dependencies" in s
|
|
78
|
+
continue
|
|
79
|
+
if in_deps:
|
|
80
|
+
m = re.match(r"^([A-Za-z0-9_\-]+)\s*=\s*\"?([^\"]*)\"?", s)
|
|
81
|
+
if m:
|
|
82
|
+
parsed[m.group(1)] = m.group(2) or "unpinned"
|
|
83
|
+
if parsed:
|
|
84
|
+
deps["Cargo.toml"] = parsed
|
|
85
|
+
except Exception as _exc:
|
|
86
|
+
_log.debug("Cargo.toml parse failed: %s", _exc)
|
|
87
|
+
return deps
|
|
88
|
+
|
|
89
|
+
def drift_report(roots: list[Path]) -> dict:
|
|
90
|
+
idx=index_repos(roots)
|
|
91
|
+
# real version drift: same package, different versions across repos
|
|
92
|
+
seen: dict[str, dict[str, list]] = {}
|
|
93
|
+
for r, data in idx.items():
|
|
94
|
+
for pkgs in data.get("deps", {}).values():
|
|
95
|
+
if not isinstance(pkgs, dict):
|
|
96
|
+
continue
|
|
97
|
+
for name, ver in pkgs.items():
|
|
98
|
+
seen.setdefault(name, {}).setdefault(str(ver), []).append(r)
|
|
99
|
+
drift=[]
|
|
100
|
+
for name, versions in seen.items():
|
|
101
|
+
if len(versions) > 1:
|
|
102
|
+
drift.append({"package": name[:80],
|
|
103
|
+
"versions": {v: rs[:3] for v, rs in versions.items()}})
|
|
104
|
+
return {"index": idx, "drift": drift[:10]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
External tool runners for symbol-level dead code detection.
|
|
3
|
+
|
|
4
|
+
Each runner invokes a language-specific tool and normalizes its output
|
|
5
|
+
into a list of dicts with keys: symbol, file, line, tool, confidence.
|
|
6
|
+
|
|
7
|
+
Tools:
|
|
8
|
+
- vulture (Python) — already a dependency
|
|
9
|
+
- ts-prune (JS/TS) — npm package, optional
|
|
10
|
+
- deadcode (Python) — alternative to vulture
|
|
11
|
+
- ruff (Python) — unused imports via F401
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from patchi.core.brain.file_corpus import FileCorpus
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_vulture(root: Path, min_confidence: int = 60) -> list[dict]:
|
|
27
|
+
"""Run vulture on a Python project and return unused symbols."""
|
|
28
|
+
if not shutil.which("vulture"):
|
|
29
|
+
return []
|
|
30
|
+
try:
|
|
31
|
+
proc = subprocess.run(
|
|
32
|
+
["vulture", str(root), "--min-confidence", str(min_confidence), "--json"],
|
|
33
|
+
capture_output=True,
|
|
34
|
+
text=True,
|
|
35
|
+
timeout=30,
|
|
36
|
+
)
|
|
37
|
+
if proc.returncode not in (0, 1):
|
|
38
|
+
return []
|
|
39
|
+
results = []
|
|
40
|
+
for line in proc.stdout.splitlines():
|
|
41
|
+
line = line.strip()
|
|
42
|
+
if not line:
|
|
43
|
+
continue
|
|
44
|
+
try:
|
|
45
|
+
entry = json.loads(line)
|
|
46
|
+
results.append(
|
|
47
|
+
{
|
|
48
|
+
"symbol": entry.get("name", ""),
|
|
49
|
+
"file": entry.get("filename", ""),
|
|
50
|
+
"line": entry.get("lineno", 0),
|
|
51
|
+
"tool": "vulture",
|
|
52
|
+
"confidence": entry.get("confidence", min_confidence),
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
except json.JSONDecodeError:
|
|
56
|
+
continue
|
|
57
|
+
return results
|
|
58
|
+
except (subprocess.TimeoutExpired, Exception):
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def run_ruff_unused_imports(root: Path) -> list[dict]:
|
|
63
|
+
"""Run ruff to find unused imports (F401) across the project."""
|
|
64
|
+
if not shutil.which("ruff"):
|
|
65
|
+
return []
|
|
66
|
+
try:
|
|
67
|
+
proc = subprocess.run(
|
|
68
|
+
["ruff", "check", str(root), "--select=F401", "--output-format=json"],
|
|
69
|
+
capture_output=True,
|
|
70
|
+
text=True,
|
|
71
|
+
timeout=30,
|
|
72
|
+
)
|
|
73
|
+
results = []
|
|
74
|
+
for line in proc.stdout.splitlines():
|
|
75
|
+
line = line.strip()
|
|
76
|
+
if not line:
|
|
77
|
+
continue
|
|
78
|
+
try:
|
|
79
|
+
entry = json.loads(line)
|
|
80
|
+
if entry.get("code") == "F401":
|
|
81
|
+
results.append(
|
|
82
|
+
{
|
|
83
|
+
"symbol": entry.get("message", "").split("'")[1]
|
|
84
|
+
if "'" in entry.get("message", "")
|
|
85
|
+
else "",
|
|
86
|
+
"file": entry.get("filename", ""),
|
|
87
|
+
"line": entry.get("location", {}).get("row", 0),
|
|
88
|
+
"tool": "ruff",
|
|
89
|
+
"confidence": 90,
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
except (json.JSONDecodeError, IndexError):
|
|
93
|
+
continue
|
|
94
|
+
return results
|
|
95
|
+
except (subprocess.TimeoutExpired, Exception):
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def run_ts_prune(root: Path) -> list[dict]:
|
|
100
|
+
"""Run ts-prune on a TypeScript project to find unused exports."""
|
|
101
|
+
if not shutil.which("npx"):
|
|
102
|
+
return []
|
|
103
|
+
pkg_json = root / "package.json"
|
|
104
|
+
if not pkg_json.exists():
|
|
105
|
+
return []
|
|
106
|
+
try:
|
|
107
|
+
proc = subprocess.run(
|
|
108
|
+
["npx", "--yes", "ts-prune", "--json"],
|
|
109
|
+
capture_output=True,
|
|
110
|
+
text=True,
|
|
111
|
+
timeout=60,
|
|
112
|
+
cwd=str(root),
|
|
113
|
+
)
|
|
114
|
+
results = []
|
|
115
|
+
for line in proc.stdout.splitlines():
|
|
116
|
+
line = line.strip()
|
|
117
|
+
if not line:
|
|
118
|
+
continue
|
|
119
|
+
try:
|
|
120
|
+
entry = json.loads(line)
|
|
121
|
+
if entry.get("isDefault", False):
|
|
122
|
+
continue
|
|
123
|
+
results.append(
|
|
124
|
+
{
|
|
125
|
+
"symbol": entry.get("symbol", ""),
|
|
126
|
+
"file": entry.get("file", ""),
|
|
127
|
+
"line": 0,
|
|
128
|
+
"tool": "ts-prune",
|
|
129
|
+
"confidence": 70,
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
except json.JSONDecodeError:
|
|
133
|
+
continue
|
|
134
|
+
return results
|
|
135
|
+
except (subprocess.TimeoutExpired, Exception):
|
|
136
|
+
return []
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def detect_and_run(root: Path, corpus: FileCorpus | None = None) -> list[dict]:
|
|
140
|
+
"""Auto-detect project type and run all applicable dead code tools."""
|
|
141
|
+
results: list[dict] = []
|
|
142
|
+
|
|
143
|
+
has_python = any(corpus.by_ext(".py")) if corpus else any(root.rglob("*.py"))
|
|
144
|
+
has_ts = (
|
|
145
|
+
any(corpus.by_ext(".ts", ".tsx"))
|
|
146
|
+
if corpus
|
|
147
|
+
else any(root.rglob("*.ts")) or any(root.rglob("*.tsx"))
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if has_python:
|
|
151
|
+
results.extend(run_vulture(root))
|
|
152
|
+
results.extend(run_ruff_unused_imports(root))
|
|
153
|
+
|
|
154
|
+
if has_ts:
|
|
155
|
+
results.extend(run_ts_prune(root))
|
|
156
|
+
|
|
157
|
+
return results
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Documentation claim validator.
|
|
3
|
+
|
|
4
|
+
Parses README and documentation files for factual claims about the project
|
|
5
|
+
(code features, API endpoints, supported formats, etc.), cross-references
|
|
6
|
+
each claim against the actual scanned code, and stores validated claims in
|
|
7
|
+
the brain for use by fix agents and contract inference.
|
|
8
|
+
|
|
9
|
+
This prevents the classic problem of docs saying "supports X" when X was
|
|
10
|
+
removed months ago.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from patchi.core.brain.languages import DEFAULT_IGNORE_DIRS
|
|
20
|
+
from patchi.core.brain.route_mapper import RouteInfo
|
|
21
|
+
from patchi.core.brain.scanner import FileInfo
|
|
22
|
+
|
|
23
|
+
# ── Claim extraction ────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
# Patterns that look like documented features in README files
|
|
26
|
+
_CLAIM_PATTERNS = [
|
|
27
|
+
re.compile(
|
|
28
|
+
r"(?:supports?|handles?|manages?|provides?|offers?|features?)\s+([^.,]+)", re.IGNORECASE
|
|
29
|
+
),
|
|
30
|
+
re.compile(r"- \[x\]\s*(.+)", re.IGNORECASE), # checkbox done
|
|
31
|
+
re.compile(r"\*\*(.+?)\*\*\s*(?:—|–|-|:)\s*.+", re.IGNORECASE), # **Feature** — description
|
|
32
|
+
re.compile(
|
|
33
|
+
r"(?:API|endpoint|route)\s*[`:/]\s*([A-Za-z0-9_/{}]+)", re.IGNORECASE
|
|
34
|
+
), # inline API mentions
|
|
35
|
+
re.compile(
|
|
36
|
+
r"(?:CLI|command)\s*[`:]\s*([a-z][a-z0-9_-]+)", re.IGNORECASE
|
|
37
|
+
), # inline CLI mentions
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def extract_claims(doc_path: Path) -> list[str]:
|
|
42
|
+
"""Parse a documentation file and return a list of raw claim strings."""
|
|
43
|
+
if not doc_path.exists():
|
|
44
|
+
return []
|
|
45
|
+
try:
|
|
46
|
+
text = doc_path.read_text(encoding="utf-8", errors="replace")
|
|
47
|
+
except OSError:
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
claims: list[str] = []
|
|
51
|
+
for pattern in _CLAIM_PATTERNS:
|
|
52
|
+
for m in pattern.finditer(text):
|
|
53
|
+
claim = m.group(1).strip()
|
|
54
|
+
if len(claim) > 15 and claim not in claims:
|
|
55
|
+
claims.append(claim)
|
|
56
|
+
return claims
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _tokenize(text: str) -> set[str]:
|
|
60
|
+
"""Split text into lowercase tokens for overlap matching."""
|
|
61
|
+
return set(re.findall(r"[a-z][a-z0-9_]{2,}", text.lower()))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _code_vocabulary(file_infos: list[FileInfo], routes: list[RouteInfo]) -> dict[str, list[str]]:
|
|
65
|
+
"""Build a token→evidence map from the actual codebase."""
|
|
66
|
+
vocab: dict[str, list[str]] = {}
|
|
67
|
+
for fi in file_infos:
|
|
68
|
+
for token in _tokenize(fi.path):
|
|
69
|
+
vocab.setdefault(token, []).append(f"file: {fi.path}")
|
|
70
|
+
for func in fi.functions:
|
|
71
|
+
for token in _tokenize(func.name):
|
|
72
|
+
vocab.setdefault(token, []).append(f"file: {fi.path} function: {func.name}")
|
|
73
|
+
for cls in fi.classes:
|
|
74
|
+
for token in _tokenize(cls.name):
|
|
75
|
+
vocab.setdefault(token, []).append(f"file: {fi.path} class: {cls.name}")
|
|
76
|
+
for route in routes:
|
|
77
|
+
for token in _tokenize(route.path):
|
|
78
|
+
vocab.setdefault(token, []).append(f"route: {route.method} {route.path}")
|
|
79
|
+
return vocab
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def cross_reference_claim(
|
|
83
|
+
claim: str,
|
|
84
|
+
file_infos: list[FileInfo],
|
|
85
|
+
routes: list[RouteInfo],
|
|
86
|
+
) -> tuple[bool, list[str]]:
|
|
87
|
+
"""
|
|
88
|
+
Check if a claim is backed by actual code.
|
|
89
|
+
Uses token-overlap matching instead of hardcoded keyword lists.
|
|
90
|
+
Returns (verified, evidence_list).
|
|
91
|
+
"""
|
|
92
|
+
claim_tokens = _tokenize(claim)
|
|
93
|
+
if not claim_tokens:
|
|
94
|
+
return False, []
|
|
95
|
+
|
|
96
|
+
vocab = _code_vocabulary(file_infos, routes)
|
|
97
|
+
evidence: list[str] = []
|
|
98
|
+
scored: dict[str, float] = {}
|
|
99
|
+
|
|
100
|
+
for token in claim_tokens:
|
|
101
|
+
for ev in vocab.get(token, []):
|
|
102
|
+
scored[ev] = scored.get(ev, 0) + 1
|
|
103
|
+
|
|
104
|
+
# Sort by overlap count, take top matches
|
|
105
|
+
for ev, _ in sorted(scored.items(), key=lambda x: -x[1]):
|
|
106
|
+
if ev not in evidence:
|
|
107
|
+
evidence.append(ev)
|
|
108
|
+
if len(evidence) >= 5:
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
verified = len(evidence) >= 1
|
|
112
|
+
return verified, evidence[:5]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ── Main validation entry point ────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def validate_project_docs(
|
|
119
|
+
root: Path,
|
|
120
|
+
file_infos: list[FileInfo],
|
|
121
|
+
routes: list[RouteInfo],
|
|
122
|
+
ai_config: dict | None = None,
|
|
123
|
+
) -> dict[str, Any]:
|
|
124
|
+
"""
|
|
125
|
+
Validate all documentation files in a project against scanned code.
|
|
126
|
+
|
|
127
|
+
Returns a dict with:
|
|
128
|
+
- validated_claims: list of {claim, evidence, verified}
|
|
129
|
+
- stale_claims: list of {claim, reason} (claims that could not be verified)
|
|
130
|
+
- summary: plain-English summary of doc health
|
|
131
|
+
"""
|
|
132
|
+
# Find doc files (skip .venv, node_modules, __pycache__)
|
|
133
|
+
_EXCLUDED_DIRS = DEFAULT_IGNORE_DIRS | {"files 6", ".vscode", ".idea"}
|
|
134
|
+
|
|
135
|
+
doc_files = []
|
|
136
|
+
for pattern in ("README*", "*.md", "docs/**/*.md"):
|
|
137
|
+
if "*" in pattern:
|
|
138
|
+
matched = list(root.glob(pattern))
|
|
139
|
+
# Filter out excluded directories
|
|
140
|
+
matched = [
|
|
141
|
+
p
|
|
142
|
+
for p in matched
|
|
143
|
+
if not any(part in _EXCLUDED_DIRS for part in p.relative_to(root).parts)
|
|
144
|
+
]
|
|
145
|
+
else:
|
|
146
|
+
p = root / pattern
|
|
147
|
+
if p.exists():
|
|
148
|
+
matched = [p]
|
|
149
|
+
else:
|
|
150
|
+
matched = []
|
|
151
|
+
doc_files.extend(matched)
|
|
152
|
+
doc_files = sorted(set(doc_files))
|
|
153
|
+
|
|
154
|
+
all_claims: list[tuple[str, Path]] = []
|
|
155
|
+
for doc_path in doc_files:
|
|
156
|
+
for claim in extract_claims(doc_path):
|
|
157
|
+
all_claims.append((claim, doc_path))
|
|
158
|
+
|
|
159
|
+
result = {
|
|
160
|
+
"validated_claims": [],
|
|
161
|
+
"stale_claims": [],
|
|
162
|
+
"summary": "",
|
|
163
|
+
"doc_files_found": [str(p) for p in doc_files],
|
|
164
|
+
"total_claims": 0,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for claim, doc_path in all_claims:
|
|
168
|
+
verified, evidence = cross_reference_claim(claim, file_infos, routes)
|
|
169
|
+
entry = {
|
|
170
|
+
"claim": claim,
|
|
171
|
+
"source": str(doc_path),
|
|
172
|
+
"verified": verified,
|
|
173
|
+
"evidence": evidence,
|
|
174
|
+
}
|
|
175
|
+
if verified:
|
|
176
|
+
result["validated_claims"].append(entry)
|
|
177
|
+
else:
|
|
178
|
+
result["stale_claims"].append(entry)
|
|
179
|
+
|
|
180
|
+
result["total_claims"] = len(all_claims)
|
|
181
|
+
result["summary"] = _build_summary(result)
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _build_summary(result: dict) -> str:
|
|
186
|
+
total = result["total_claims"]
|
|
187
|
+
validated = len(result["validated_claims"])
|
|
188
|
+
stale = len(result["stale_claims"])
|
|
189
|
+
doc_count = len(result["doc_files_found"])
|
|
190
|
+
|
|
191
|
+
if total == 0:
|
|
192
|
+
return f"Found {doc_count} doc file(s) but no verifiable claims."
|
|
193
|
+
|
|
194
|
+
parts = [f"Verified {validated}/{total} claims across {doc_count} doc file(s)."]
|
|
195
|
+
if stale:
|
|
196
|
+
parts.append(f"{stale} claim(s) could not be verified against code")
|
|
197
|
+
return " ".join(parts)
|