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,573 @@
|
|
|
1
|
+
"""DocClaimAgent — LLM-powered documentation claim miner.
|
|
2
|
+
|
|
3
|
+
Reads all project documentation files (README, docs/, .md, .txt, .rst, .adoc),
|
|
4
|
+
extracts structured verifiable claims via LLM, and hands them to the Brain
|
|
5
|
+
for structural cross-referencing against the actual codebase.
|
|
6
|
+
|
|
7
|
+
Usage (via Brain.scan):
|
|
8
|
+
agent = DocClaimAgent()
|
|
9
|
+
result = agent.run(AgentInput(root=root, config=config, ...))
|
|
10
|
+
claims = result.data["claims"] # list[Claim]
|
|
11
|
+
|
|
12
|
+
Fallback (--offline mode):
|
|
13
|
+
from patchi.core.brain.doc_validator import validate_project_docs
|
|
14
|
+
result = validate_project_docs(...) # regex-based heuristic
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from loguru import logger
|
|
25
|
+
|
|
26
|
+
from patchi.core.brain.languages import DEFAULT_IGNORE_DIRS
|
|
27
|
+
|
|
28
|
+
from .base import (
|
|
29
|
+
AgentGroup,
|
|
30
|
+
AgentInput,
|
|
31
|
+
AgentResult,
|
|
32
|
+
BaseAgent,
|
|
33
|
+
register,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Doc file discovery patterns — covers every common project doc format
|
|
37
|
+
_DOC_PATTERNS = [
|
|
38
|
+
"README*",
|
|
39
|
+
"CONTRIBUTING*",
|
|
40
|
+
"CHANGELOG*",
|
|
41
|
+
"CHANGES*",
|
|
42
|
+
"HISTORY*",
|
|
43
|
+
"SECURITY*",
|
|
44
|
+
"CODE_OF_CONDUCT*",
|
|
45
|
+
"SUPPORT*",
|
|
46
|
+
"AUTHORS*",
|
|
47
|
+
"MAINTAINERS*",
|
|
48
|
+
"GOVERNANCE*",
|
|
49
|
+
"ROADMAP*",
|
|
50
|
+
"VISION*",
|
|
51
|
+
"GOALS*",
|
|
52
|
+
"MILESTONES*",
|
|
53
|
+
"LICENSE*",
|
|
54
|
+
"COPYING*",
|
|
55
|
+
"NOTICE*",
|
|
56
|
+
"*.md",
|
|
57
|
+
"*.MD",
|
|
58
|
+
"*.txt",
|
|
59
|
+
"*.TXT",
|
|
60
|
+
"*.rst",
|
|
61
|
+
"*.RST",
|
|
62
|
+
"*.adoc",
|
|
63
|
+
"*.ADOC",
|
|
64
|
+
"*.asciidoc",
|
|
65
|
+
"*.mdown",
|
|
66
|
+
"docs/**/*",
|
|
67
|
+
"documentation/**/*",
|
|
68
|
+
"wiki/**/*",
|
|
69
|
+
"man/**/*",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
_EXCLUDED_DIRS = DEFAULT_IGNORE_DIRS | {"files 6", ".vscode", ".idea"}
|
|
73
|
+
|
|
74
|
+
_CLAIM_EXTRACTION_PROMPT = """\
|
|
75
|
+
You are reading a documentation file from a software project.
|
|
76
|
+
Extract every concrete, verifiable claim about the project into a JSON array.
|
|
77
|
+
|
|
78
|
+
A claim is a statement that CAN be checked against source code — something that is either true or false based on what the code actually does.
|
|
79
|
+
|
|
80
|
+
INCLUDE claims about:
|
|
81
|
+
- Features the project supports ("supports JWT", "handles file uploads")
|
|
82
|
+
- CLI commands and flags ("`p scan --deep` runs deep scan")
|
|
83
|
+
- API endpoints and methods ("POST /api/v1/users")
|
|
84
|
+
- Configuration keys and options ("`debug: true` enables debug mode")
|
|
85
|
+
- Supported formats, platforms, protocols ("exports to CSV", "runs on Linux/macOS")
|
|
86
|
+
- Dependencies and integrations ("uses Redis for caching", "integrates with Slack")
|
|
87
|
+
- Architecture patterns ("uses MVC architecture", "plugin-based")
|
|
88
|
+
- Security measures ("audit logging", "rate limiting")
|
|
89
|
+
- Test capabilities ("supports browser tests via Playwright")
|
|
90
|
+
|
|
91
|
+
EXCLUDE:
|
|
92
|
+
- Opinions, intros, thank-yous ("we love contributors", "built with care")
|
|
93
|
+
- License text, copyright notices
|
|
94
|
+
- Contribution guidelines, setup instructions for contributors
|
|
95
|
+
- Hypotheticals, future plans ("we plan to add", "coming soon")
|
|
96
|
+
- Generic meta-commentary about the project
|
|
97
|
+
- Things that are self-evident from reading ("this is a README file")
|
|
98
|
+
|
|
99
|
+
Output ONLY a JSON array of objects with this structure:
|
|
100
|
+
[
|
|
101
|
+
{{
|
|
102
|
+
"claim": "The exact sentence from the doc",
|
|
103
|
+
"category": "feature|cli_command|api_endpoint|config_key|dependency|architecture|format_support|security|test_capability",
|
|
104
|
+
"evidence_hints": [
|
|
105
|
+
"route: POST /api/v1/users",
|
|
106
|
+
"function: verify_jwt_token",
|
|
107
|
+
"class: JwtMiddleware",
|
|
108
|
+
"cli: p scan --deep",
|
|
109
|
+
"import: redis",
|
|
110
|
+
"file_match: config/**/*.yaml",
|
|
111
|
+
"config_key: debug",
|
|
112
|
+
"package: pytest",
|
|
113
|
+
"symbol: JWT_SECRET"
|
|
114
|
+
]
|
|
115
|
+
}}
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
If there are zero verifiable claims, output: []
|
|
119
|
+
Do NOT output anything else — no explanations, no markdown.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
_log = logging.getLogger("patchi.agents.doc_claim_agent")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class Claim:
|
|
128
|
+
"""A single verifiable claim extracted from documentation."""
|
|
129
|
+
|
|
130
|
+
text: str
|
|
131
|
+
source_file: str
|
|
132
|
+
category: str # feature, cli_command, api_endpoint, config_key, dependency, architecture, format_support, security, test_capability
|
|
133
|
+
evidence_hints: list[str] # hints for the brain to verify structurally
|
|
134
|
+
verified: bool = False # set by the brain after cross-referencing
|
|
135
|
+
evidence: list[str] = field(default_factory=list) # actual verification evidence
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@register
|
|
139
|
+
class DocClaimAgent(BaseAgent):
|
|
140
|
+
"""LLM-powered documentation claim miner.
|
|
141
|
+
|
|
142
|
+
Discovers all project documentation files, sends each to the LLM
|
|
143
|
+
with a structured extraction prompt, and returns structured claims
|
|
144
|
+
with evidence hints for the brain to verify against the codebase.
|
|
145
|
+
|
|
146
|
+
Falls back gracefully: if LLM is unavailable, returns empty claims
|
|
147
|
+
and the caller should fall back to doc_validator heuristic.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
group = AgentGroup.SCANNER
|
|
151
|
+
name = "DocClaimAgent"
|
|
152
|
+
timeout = 120 # LLM calls can take time
|
|
153
|
+
|
|
154
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
155
|
+
import os
|
|
156
|
+
|
|
157
|
+
if os.environ.get("PATCHI_OFFLINE"):
|
|
158
|
+
logger.info("Offline mode — DocClaimAgent skipped")
|
|
159
|
+
result.data["claims"] = []
|
|
160
|
+
result.data["doc_files_found"] = []
|
|
161
|
+
result.data["offline"] = True
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
doc_files = self._discover_docs(inp.root)
|
|
165
|
+
if not doc_files:
|
|
166
|
+
logger.info("No documentation files found")
|
|
167
|
+
result.data["claims"] = []
|
|
168
|
+
result.data["doc_files_found"] = []
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
result.data["doc_files_found"] = [str(p) for p in doc_files]
|
|
172
|
+
|
|
173
|
+
all_claims: list[dict] = []
|
|
174
|
+
ai_config = inp.config.get("ai", {})
|
|
175
|
+
has_ai = bool(ai_config.get("keys") or ai_config.get("local_model_name"))
|
|
176
|
+
|
|
177
|
+
if not has_ai:
|
|
178
|
+
logger.info(
|
|
179
|
+
"No AI configured — DocClaimAgent returns no claims (fallback to heuristic)"
|
|
180
|
+
)
|
|
181
|
+
result.data["claims"] = []
|
|
182
|
+
result.data["ai_unavailable"] = True
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
max_workers = min(len(doc_files), 4)
|
|
186
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
187
|
+
future_to_doc = {}
|
|
188
|
+
for doc_path in doc_files:
|
|
189
|
+
try:
|
|
190
|
+
text = doc_path.read_text(encoding="utf-8", errors="replace")
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.warning(f"Cannot read {doc_path}: {e}")
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
if len(text.strip()) < 20:
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
future = executor.submit(
|
|
199
|
+
self._extract_claims, text, doc_path, result, inp.config, ai_config
|
|
200
|
+
)
|
|
201
|
+
future_to_doc[future] = doc_path
|
|
202
|
+
|
|
203
|
+
for future in as_completed(future_to_doc):
|
|
204
|
+
doc_path = future_to_doc[future]
|
|
205
|
+
try:
|
|
206
|
+
claims = future.result()
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.warning(f"Claim extraction failed for {doc_path.name}: {e}")
|
|
209
|
+
result.errors.append(f"{doc_path.name}: {e}")
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
for c in claims:
|
|
213
|
+
c["source_file"] = str(doc_path.relative_to(inp.root))
|
|
214
|
+
all_claims.append(c)
|
|
215
|
+
|
|
216
|
+
result.ai_calls_made += 1
|
|
217
|
+
|
|
218
|
+
result.data["claims"] = all_claims
|
|
219
|
+
result.data["total_claims"] = len(all_claims)
|
|
220
|
+
logger.info(f"DocClaimAgent: {len(all_claims)} claims from {len(doc_files)} files")
|
|
221
|
+
|
|
222
|
+
def _discover_docs(self, root: Path) -> list[Path]:
|
|
223
|
+
"""Discover all documentation files in the project tree."""
|
|
224
|
+
found: list[Path] = []
|
|
225
|
+
seen: set[Path] = set()
|
|
226
|
+
|
|
227
|
+
for pattern in _DOC_PATTERNS:
|
|
228
|
+
for path in root.glob(pattern):
|
|
229
|
+
resolved = path.resolve()
|
|
230
|
+
if resolved in seen:
|
|
231
|
+
continue
|
|
232
|
+
# Skip excluded dirs
|
|
233
|
+
rel = resolved.relative_to(root).as_posix()
|
|
234
|
+
parts = rel.replace("\\", "/").split("/")
|
|
235
|
+
if any(p in _EXCLUDED_DIRS for p in parts):
|
|
236
|
+
continue
|
|
237
|
+
# Skip binary/irrelevant files by extension check
|
|
238
|
+
if resolved.suffix.lower() not in (
|
|
239
|
+
".md",
|
|
240
|
+
".txt",
|
|
241
|
+
".rst",
|
|
242
|
+
".adoc",
|
|
243
|
+
".asciidoc",
|
|
244
|
+
".mdown",
|
|
245
|
+
".mdx",
|
|
246
|
+
) and resolved.name not in (
|
|
247
|
+
"README",
|
|
248
|
+
"CONTRIBUTING",
|
|
249
|
+
"CHANGELOG",
|
|
250
|
+
"CHANGES",
|
|
251
|
+
"HISTORY",
|
|
252
|
+
"SECURITY",
|
|
253
|
+
"CODE_OF_CONDUCT",
|
|
254
|
+
"LICENSE",
|
|
255
|
+
"COPYING",
|
|
256
|
+
"NOTICE",
|
|
257
|
+
"SUPPORT",
|
|
258
|
+
"AUTHORS",
|
|
259
|
+
"MAINTAINERS",
|
|
260
|
+
"GOVERNANCE",
|
|
261
|
+
"ROADMAP",
|
|
262
|
+
"VISION",
|
|
263
|
+
"GOALS",
|
|
264
|
+
"MILESTONES",
|
|
265
|
+
):
|
|
266
|
+
# Skip non-text files matched by docs/**/* (binary, images, etc.)
|
|
267
|
+
if resolved.suffix in (
|
|
268
|
+
".png",
|
|
269
|
+
".jpg",
|
|
270
|
+
".gif",
|
|
271
|
+
".svg",
|
|
272
|
+
".ico",
|
|
273
|
+
".pdf",
|
|
274
|
+
".zip",
|
|
275
|
+
".gz",
|
|
276
|
+
".tar",
|
|
277
|
+
".bin",
|
|
278
|
+
".exe",
|
|
279
|
+
".dll",
|
|
280
|
+
".so",
|
|
281
|
+
".dylib",
|
|
282
|
+
".pyc",
|
|
283
|
+
".woff",
|
|
284
|
+
".woff2",
|
|
285
|
+
".ttf",
|
|
286
|
+
".eot",
|
|
287
|
+
):
|
|
288
|
+
continue
|
|
289
|
+
seen.add(resolved)
|
|
290
|
+
found.append(resolved)
|
|
291
|
+
|
|
292
|
+
found.sort(key=lambda p: p.as_posix())
|
|
293
|
+
return found
|
|
294
|
+
|
|
295
|
+
def _extract_claims(
|
|
296
|
+
self,
|
|
297
|
+
text: str,
|
|
298
|
+
doc_path: Path,
|
|
299
|
+
result: AgentResult,
|
|
300
|
+
config: dict,
|
|
301
|
+
ai_config: dict,
|
|
302
|
+
) -> list[dict]:
|
|
303
|
+
"""Send a single doc file to the LLM and parse the claim JSON."""
|
|
304
|
+
from patchi.core.ai.client import call_ai
|
|
305
|
+
|
|
306
|
+
# Truncate very large doc files to avoid token blowout
|
|
307
|
+
text = text[:15000]
|
|
308
|
+
|
|
309
|
+
prompt = _CLAIM_EXTRACTION_PROMPT + "\n\n--- DOCUMENT ---\n" + text
|
|
310
|
+
system_prompt = "You are a documentation analyser. Output only valid JSON."
|
|
311
|
+
|
|
312
|
+
max_retries = 2
|
|
313
|
+
for attempt in range(max_retries):
|
|
314
|
+
try:
|
|
315
|
+
response = call_ai(
|
|
316
|
+
config=config,
|
|
317
|
+
system_prompt=system_prompt,
|
|
318
|
+
user_prompt=prompt,
|
|
319
|
+
max_tokens=4096,
|
|
320
|
+
temperature=0.1,
|
|
321
|
+
)
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.warning(
|
|
324
|
+
f"LLM extraction failed for {doc_path.name} (attempt {attempt + 1}): {e}"
|
|
325
|
+
)
|
|
326
|
+
if attempt < max_retries - 1:
|
|
327
|
+
continue
|
|
328
|
+
result.errors.append(f"{doc_path.name}: LLM error — {e}")
|
|
329
|
+
return []
|
|
330
|
+
|
|
331
|
+
if not response:
|
|
332
|
+
if attempt < max_retries - 1:
|
|
333
|
+
continue
|
|
334
|
+
return []
|
|
335
|
+
|
|
336
|
+
claims = self._parse_claims(response, doc_path)
|
|
337
|
+
if not claims and attempt < max_retries - 1:
|
|
338
|
+
logger.debug(f"Empty claims for {doc_path.name}, retrying...")
|
|
339
|
+
continue
|
|
340
|
+
|
|
341
|
+
return claims
|
|
342
|
+
|
|
343
|
+
return []
|
|
344
|
+
|
|
345
|
+
def _parse_claims(self, response: str, doc_path: Path) -> list[dict]:
|
|
346
|
+
"""Parse the LLM response into a list of claim dicts."""
|
|
347
|
+
text = response.strip()
|
|
348
|
+
|
|
349
|
+
# Strip markdown code fences if present
|
|
350
|
+
if text.startswith("```"):
|
|
351
|
+
lines = text.splitlines()
|
|
352
|
+
if len(lines) >= 2:
|
|
353
|
+
lines = lines[1:] if lines[0].startswith("```") else lines
|
|
354
|
+
if lines and lines[-1].strip().startswith("```"):
|
|
355
|
+
lines = lines[:-1]
|
|
356
|
+
text = "\n".join(lines).strip()
|
|
357
|
+
|
|
358
|
+
# Try to extract JSON array if embedded in other text
|
|
359
|
+
start = text.find("[")
|
|
360
|
+
end = text.rfind("]")
|
|
361
|
+
if start >= 0 and end > start:
|
|
362
|
+
text = text[start : end + 1]
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
claims = json.loads(text)
|
|
366
|
+
if not isinstance(claims, list):
|
|
367
|
+
logger.warning(f"LLM returned non-array for {doc_path.name}")
|
|
368
|
+
return []
|
|
369
|
+
except (json.JSONDecodeError, ValueError) as e:
|
|
370
|
+
logger.warning(f"Failed to parse LLM claims from {doc_path.name}: {e}")
|
|
371
|
+
return []
|
|
372
|
+
|
|
373
|
+
# Validate structure
|
|
374
|
+
valid = []
|
|
375
|
+
for c in claims:
|
|
376
|
+
if isinstance(c, dict) and "claim" in c and "category" in c:
|
|
377
|
+
if not isinstance(c.get("evidence_hints"), list):
|
|
378
|
+
c["evidence_hints"] = []
|
|
379
|
+
valid.append(c)
|
|
380
|
+
|
|
381
|
+
return valid
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# ── Brain integration helper ──────────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def verify_claims_against_code(
|
|
388
|
+
claims: list[dict],
|
|
389
|
+
file_infos: list,
|
|
390
|
+
routes: list,
|
|
391
|
+
symbol_graph=None,
|
|
392
|
+
import_graph=None,
|
|
393
|
+
config: dict | None = None,
|
|
394
|
+
) -> list[dict]:
|
|
395
|
+
"""Cross-reference each claim's evidence_hints against the codebase.
|
|
396
|
+
|
|
397
|
+
Returns the claims list with verified=True/False and evidence populated.
|
|
398
|
+
"""
|
|
399
|
+
if not claims:
|
|
400
|
+
return claims
|
|
401
|
+
|
|
402
|
+
# Build lookup sets from scan results
|
|
403
|
+
route_set: set[str] = set()
|
|
404
|
+
for r in routes:
|
|
405
|
+
if hasattr(r, "method"):
|
|
406
|
+
route_set.add(r.method.upper() + " " + r.path)
|
|
407
|
+
route_set.add((r.method.upper() + " " + r.path).lower())
|
|
408
|
+
route_set.add(r.path.lower())
|
|
409
|
+
elif isinstance(r, dict):
|
|
410
|
+
method = r.get("method", "GET")
|
|
411
|
+
path = r.get("path", "/")
|
|
412
|
+
route_set.add(method.upper() + " " + path)
|
|
413
|
+
route_set.add((method.upper() + " " + path).lower())
|
|
414
|
+
route_set.add(path.lower())
|
|
415
|
+
|
|
416
|
+
file_paths: set[str] = set()
|
|
417
|
+
file_names: set[str] = set()
|
|
418
|
+
func_names: set[str] = set()
|
|
419
|
+
class_names: set[str] = set()
|
|
420
|
+
import_names: set[str] = set()
|
|
421
|
+
symbols: set[str] = set()
|
|
422
|
+
|
|
423
|
+
for fi in file_infos:
|
|
424
|
+
if hasattr(fi, "path"):
|
|
425
|
+
rel = fi.path
|
|
426
|
+
elif isinstance(fi, dict):
|
|
427
|
+
rel = fi.get("path", "")
|
|
428
|
+
else:
|
|
429
|
+
continue
|
|
430
|
+
file_paths.add(rel.lower())
|
|
431
|
+
file_names.add(Path(rel).stem.lower())
|
|
432
|
+
funcs = fi.functions if hasattr(fi, "functions") else fi.get("functions", [])
|
|
433
|
+
classes = fi.classes if hasattr(fi, "classes") else fi.get("classes", [])
|
|
434
|
+
imports = fi.imports if hasattr(fi, "imports") else fi.get("imports", [])
|
|
435
|
+
if funcs:
|
|
436
|
+
func_names.update(
|
|
437
|
+
f.name.lower() if hasattr(f, "name") else f.get("name", "").lower() for f in funcs
|
|
438
|
+
)
|
|
439
|
+
if classes:
|
|
440
|
+
class_names.update(
|
|
441
|
+
c.name.lower() if hasattr(c, "name") else c.get("name", "").lower() for c in classes
|
|
442
|
+
)
|
|
443
|
+
if imports:
|
|
444
|
+
import_names.update(
|
|
445
|
+
i.source.lower() if hasattr(i, "source") else i.get("source", "").lower()
|
|
446
|
+
for i in imports
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
if symbol_graph:
|
|
450
|
+
try:
|
|
451
|
+
for sym in symbol_graph.get_all_symbols():
|
|
452
|
+
symbols.add(sym.name.lower())
|
|
453
|
+
symbols.add(sym.qualified_name.lower())
|
|
454
|
+
except Exception as e:
|
|
455
|
+
_log.warning("verify_claims_against_code failed: %s", e)
|
|
456
|
+
|
|
457
|
+
cli_flags: set[str] = set()
|
|
458
|
+
if config:
|
|
459
|
+
cli_parser = config.get("_cli_parser")
|
|
460
|
+
if cli_parser:
|
|
461
|
+
for action in cli_parser._actions:
|
|
462
|
+
for opt in action.option_strings:
|
|
463
|
+
cli_flags.add(opt.lower())
|
|
464
|
+
|
|
465
|
+
# Build import graph node set for dependency verification
|
|
466
|
+
ig_nodes: set[str] = set()
|
|
467
|
+
if import_graph:
|
|
468
|
+
try:
|
|
469
|
+
if hasattr(import_graph, "graph") and hasattr(import_graph.graph, "nodes"):
|
|
470
|
+
ig_nodes = set(import_graph.graph.nodes())
|
|
471
|
+
elif hasattr(import_graph, "nodes"):
|
|
472
|
+
ig_nodes = set(import_graph.nodes())
|
|
473
|
+
except Exception as e:
|
|
474
|
+
_log.warning("verify_claims_against_code failed: %s", e)
|
|
475
|
+
|
|
476
|
+
for claim in claims:
|
|
477
|
+
hints = claim.get("evidence_hints", [])
|
|
478
|
+
evidence: list[str] = []
|
|
479
|
+
|
|
480
|
+
for hint in hints:
|
|
481
|
+
hint_lower = hint.lower()
|
|
482
|
+
|
|
483
|
+
# Route check
|
|
484
|
+
if hint_lower.startswith("route:"):
|
|
485
|
+
target = hint_lower.replace("route:", "").strip()
|
|
486
|
+
if target in route_set:
|
|
487
|
+
evidence.append(f"matched_route:{hint}")
|
|
488
|
+
|
|
489
|
+
# Function/class/symbol check
|
|
490
|
+
elif hint_lower.startswith("function:"):
|
|
491
|
+
target = hint_lower.replace("function:", "").strip()
|
|
492
|
+
if target in func_names:
|
|
493
|
+
evidence.append(f"matched_function:{hint}")
|
|
494
|
+
|
|
495
|
+
elif hint_lower.startswith("class:"):
|
|
496
|
+
target = hint_lower.replace("class:", "").strip()
|
|
497
|
+
if target in class_names:
|
|
498
|
+
evidence.append(f"matched_class:{hint}")
|
|
499
|
+
|
|
500
|
+
elif hint_lower.startswith("symbol:"):
|
|
501
|
+
target = hint_lower.replace("symbol:", "").strip()
|
|
502
|
+
if target in symbols:
|
|
503
|
+
evidence.append(f"matched_symbol:{hint}")
|
|
504
|
+
|
|
505
|
+
# CLI flag check
|
|
506
|
+
elif hint_lower.startswith("cli:"):
|
|
507
|
+
target = hint_lower.replace("cli:", "").strip()
|
|
508
|
+
if target in cli_flags:
|
|
509
|
+
evidence.append(f"matched_cli:{hint}")
|
|
510
|
+
|
|
511
|
+
# Import/package check (now also uses import_graph)
|
|
512
|
+
elif hint_lower.startswith("import:"):
|
|
513
|
+
target = hint_lower.replace("import:", "").strip()
|
|
514
|
+
if target in import_names:
|
|
515
|
+
evidence.append(f"matched_import:{hint}")
|
|
516
|
+
elif ig_nodes:
|
|
517
|
+
for node in ig_nodes:
|
|
518
|
+
if target in node.lower():
|
|
519
|
+
evidence.append(f"matched_import_graph:{hint}")
|
|
520
|
+
break
|
|
521
|
+
|
|
522
|
+
elif hint_lower.startswith("package:"):
|
|
523
|
+
target = hint_lower.replace("package:", "").strip()
|
|
524
|
+
if target in import_names or target in file_names:
|
|
525
|
+
evidence.append(f"matched_package:{hint}")
|
|
526
|
+
elif ig_nodes:
|
|
527
|
+
for node in ig_nodes:
|
|
528
|
+
if target in node.lower():
|
|
529
|
+
evidence.append(f"matched_import_graph:{hint}")
|
|
530
|
+
break
|
|
531
|
+
|
|
532
|
+
# Config key check
|
|
533
|
+
elif hint_lower.startswith("config_key:"):
|
|
534
|
+
target = hint_lower.replace("config_key:", "").strip()
|
|
535
|
+
if config and target in str(config).lower():
|
|
536
|
+
evidence.append(f"matched_config:{hint}")
|
|
537
|
+
|
|
538
|
+
# File match check
|
|
539
|
+
elif hint_lower.startswith("file_match:"):
|
|
540
|
+
target = hint_lower.replace("file_match:", "").strip()
|
|
541
|
+
if any(target in fp for fp in file_paths):
|
|
542
|
+
evidence.append(f"matched_file:{hint}")
|
|
543
|
+
elif ig_nodes:
|
|
544
|
+
for node in ig_nodes:
|
|
545
|
+
if target in node.lower():
|
|
546
|
+
evidence.append(f"matched_file_via_import_graph:{hint}")
|
|
547
|
+
break
|
|
548
|
+
|
|
549
|
+
# Dependency verification via import graph
|
|
550
|
+
elif hint_lower.startswith("dependency:"):
|
|
551
|
+
target = hint_lower.replace("dependency:", "").strip()
|
|
552
|
+
if ig_nodes:
|
|
553
|
+
for node in ig_nodes:
|
|
554
|
+
if target in node.lower():
|
|
555
|
+
evidence.append(f"matched_import_graph_dep:{hint}")
|
|
556
|
+
break
|
|
557
|
+
if target in import_names:
|
|
558
|
+
evidence.append(f"matched_import:{hint}")
|
|
559
|
+
|
|
560
|
+
# General keyword search (fallback)
|
|
561
|
+
else:
|
|
562
|
+
if hint_lower in func_names or hint_lower in class_names or hint_lower in symbols:
|
|
563
|
+
evidence.append(f"matched_symbol:{hint}")
|
|
564
|
+
elif ig_nodes:
|
|
565
|
+
for node in ig_nodes:
|
|
566
|
+
if hint_lower in node.lower():
|
|
567
|
+
evidence.append(f"matched_import_graph:{hint}")
|
|
568
|
+
break
|
|
569
|
+
|
|
570
|
+
claim["verified"] = len(evidence) > 0
|
|
571
|
+
claim["evidence"] = evidence
|
|
572
|
+
|
|
573
|
+
return claims
|