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,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST utilities — generic tree-sitter node helpers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ast as _py_ast
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from patchi.core.brain.languages import Lang, get_parser
|
|
12
|
+
|
|
13
|
+
_log = logging.getLogger("patchi.brain.helpers")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _py_call_name(node: _py_ast.Call) -> str:
|
|
17
|
+
"""Full dotted name of a Python ``Call`` node's function.
|
|
18
|
+
|
|
19
|
+
Single source of truth shared by ``calls.find_calls`` and the cached
|
|
20
|
+
single-pass scan (``scan.scan_python``) so name resolution can never
|
|
21
|
+
drift between the two.
|
|
22
|
+
"""
|
|
23
|
+
if isinstance(node.func, _py_ast.Name):
|
|
24
|
+
return node.func.id
|
|
25
|
+
if isinstance(node.func, _py_ast.Attribute):
|
|
26
|
+
parts = []
|
|
27
|
+
cur = node.func
|
|
28
|
+
while isinstance(cur, _py_ast.Attribute):
|
|
29
|
+
parts.append(cur.attr)
|
|
30
|
+
cur = cur.value
|
|
31
|
+
if isinstance(cur, _py_ast.Name):
|
|
32
|
+
parts.append(cur.id)
|
|
33
|
+
return ".".join(reversed(parts))
|
|
34
|
+
return ""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _py_assign_target_name(node: _py_ast.AST) -> str:
|
|
38
|
+
"""Dotted name of a Python assignment target.
|
|
39
|
+
|
|
40
|
+
Single source of truth shared by ``assignments.find_assignments`` and the
|
|
41
|
+
cached single-pass scan.
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(node, _py_ast.Name):
|
|
44
|
+
return node.id
|
|
45
|
+
if isinstance(node, _py_ast.Attribute):
|
|
46
|
+
parts = []
|
|
47
|
+
cur = node
|
|
48
|
+
while isinstance(cur, _py_ast.Attribute):
|
|
49
|
+
parts.append(cur.attr)
|
|
50
|
+
cur = cur.value
|
|
51
|
+
if isinstance(cur, _py_ast.Name):
|
|
52
|
+
parts.append(cur.id)
|
|
53
|
+
return ".".join(reversed(parts))
|
|
54
|
+
return ""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def parse_source(content: str, lang: Lang) -> Any | None:
|
|
58
|
+
"""Parse `content` with the tree-sitter parser for `lang`.
|
|
59
|
+
|
|
60
|
+
Returns the parse tree (with `.root_node`) or None if no parser is
|
|
61
|
+
available or parsing fails. All languages use tree-sitter so callers
|
|
62
|
+
can walk a uniform node interface.
|
|
63
|
+
"""
|
|
64
|
+
parser = get_parser(lang)
|
|
65
|
+
if not parser:
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
return parser.parse(content.encode("utf-8"))
|
|
69
|
+
except Exception as e:
|
|
70
|
+
_log.debug("parse_source failed: %s", e)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def node_text(node: Any) -> str:
|
|
75
|
+
try:
|
|
76
|
+
raw = getattr(node, "text", b"")
|
|
77
|
+
if isinstance(raw, bytes):
|
|
78
|
+
return raw.decode("utf-8", errors="replace")
|
|
79
|
+
return str(raw)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
_log.debug("node_text failed: %s", e)
|
|
82
|
+
return ""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def child_by_field(node: Any, field: str) -> Any | None:
|
|
86
|
+
if hasattr(node, "child_by_field_name"):
|
|
87
|
+
return node.child_by_field_name(field)
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def children(node: Any) -> list[Any]:
|
|
92
|
+
try:
|
|
93
|
+
return list(node.children) if hasattr(node, "children") else []
|
|
94
|
+
except Exception as e:
|
|
95
|
+
_log.debug("children failed: %s", e)
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def named_children(node: Any) -> list[Any]:
|
|
100
|
+
try:
|
|
101
|
+
return list(node.named_children) if hasattr(node, "named_children") else []
|
|
102
|
+
except Exception as e:
|
|
103
|
+
_log.debug("named_children failed: %s", e)
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _leaf_name(fn: str) -> str:
|
|
108
|
+
"""Extract the leaf symbol from a possibly dotted / scoped name.
|
|
109
|
+
|
|
110
|
+
Handles both `.` (Python, JS, Java) and `::` (Rust, C++) separators.
|
|
111
|
+
"""
|
|
112
|
+
for sep in (".", "::"):
|
|
113
|
+
if sep in fn:
|
|
114
|
+
return fn.split(sep)[-1]
|
|
115
|
+
return fn
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_call_arg(call_full_text: str, position: int = 0) -> str:
|
|
119
|
+
"""Extract the positional argument at `position` from a call text."""
|
|
120
|
+
try:
|
|
121
|
+
paren = call_full_text.index("(")
|
|
122
|
+
rest = call_full_text[paren + 1 :]
|
|
123
|
+
depth = 0
|
|
124
|
+
arg_start = 0
|
|
125
|
+
arg_idx = 0
|
|
126
|
+
for i, ch in enumerate(rest):
|
|
127
|
+
if ch in ("(", "[", "{"):
|
|
128
|
+
depth += 1
|
|
129
|
+
elif ch in (")", "]", "}"):
|
|
130
|
+
depth -= 1
|
|
131
|
+
if depth < 0:
|
|
132
|
+
if arg_idx == position:
|
|
133
|
+
return rest[arg_start:i].strip()
|
|
134
|
+
break
|
|
135
|
+
elif ch == "," and depth == 0:
|
|
136
|
+
if arg_idx == position:
|
|
137
|
+
return rest[arg_start:i].strip()
|
|
138
|
+
arg_start = i + 1
|
|
139
|
+
arg_idx += 1
|
|
140
|
+
if arg_idx == position:
|
|
141
|
+
return rest[arg_start:].strip().rstrip(")")
|
|
142
|
+
except (ValueError, IndexError):
|
|
143
|
+
pass
|
|
144
|
+
return ""
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST utilities — import statement finding across languages.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ast as _py_ast
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from patchi.core.brain.languages import Lang, get_parser
|
|
12
|
+
|
|
13
|
+
from .config import IMPORT_NODE_TYPES
|
|
14
|
+
from .helpers import child_by_field, children, node_text
|
|
15
|
+
|
|
16
|
+
_log = logging.getLogger("patchi.brain.imports")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def find_imports(content: str, lang: Lang, names: set[str]) -> list[dict]:
|
|
20
|
+
"""
|
|
21
|
+
Find all import statements matching one of `names`.
|
|
22
|
+
Returns list of dicts with keys: name, line, full_text.
|
|
23
|
+
"""
|
|
24
|
+
if lang == Lang.PYTHON:
|
|
25
|
+
return _find_imports_python(content, names)
|
|
26
|
+
parser = get_parser(lang)
|
|
27
|
+
if not parser:
|
|
28
|
+
return []
|
|
29
|
+
try:
|
|
30
|
+
tree = parser.parse(content.encode("utf-8"))
|
|
31
|
+
except Exception as e:
|
|
32
|
+
_log.warning("find_imports failed: %s", e)
|
|
33
|
+
return []
|
|
34
|
+
import_types = IMPORT_NODE_TYPES.get(lang, set())
|
|
35
|
+
results: list[dict] = []
|
|
36
|
+
_walk_imports(tree.root_node, import_types, names, results, lang)
|
|
37
|
+
return results
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _find_imports_python(content: str, names: set[str]) -> list[dict]:
|
|
41
|
+
results: list[dict] = []
|
|
42
|
+
try:
|
|
43
|
+
tree = _py_ast.parse(content)
|
|
44
|
+
except SyntaxError:
|
|
45
|
+
return results
|
|
46
|
+
for node in _py_ast.walk(tree):
|
|
47
|
+
if isinstance(node, _py_ast.Import):
|
|
48
|
+
for alias in node.names:
|
|
49
|
+
if alias.name in names:
|
|
50
|
+
results.append(
|
|
51
|
+
{
|
|
52
|
+
"name": alias.name,
|
|
53
|
+
"line": getattr(node, "lineno", 0),
|
|
54
|
+
"full_text": _ast_text(node, content),
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
elif isinstance(node, _py_ast.ImportFrom):
|
|
58
|
+
if node.module and node.module in names:
|
|
59
|
+
results.append(
|
|
60
|
+
{
|
|
61
|
+
"name": node.module,
|
|
62
|
+
"line": getattr(node, "lineno", 0),
|
|
63
|
+
"full_text": _ast_text(node, content),
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
return results
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _ast_text(node: _py_ast.AST, source: str) -> str:
|
|
70
|
+
try:
|
|
71
|
+
lines = source.splitlines()
|
|
72
|
+
start = getattr(node, "lineno", 1) - 1
|
|
73
|
+
end = getattr(node, "end_lineno", start + 1) - 1
|
|
74
|
+
return "\n".join(lines[start : end + 1])
|
|
75
|
+
except Exception as e:
|
|
76
|
+
_log.debug("_ast_text failed: %s", e)
|
|
77
|
+
return ""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _walk_imports(
|
|
81
|
+
node: Any, import_types: set[str], names: set[str], results: list[dict], lang: Lang
|
|
82
|
+
) -> None:
|
|
83
|
+
try:
|
|
84
|
+
ntype = node.type if hasattr(node, "type") else ""
|
|
85
|
+
except Exception as e:
|
|
86
|
+
_log.warning("_walk_imports failed: %s", e)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
if ntype in import_types:
|
|
90
|
+
imp_name = _extract_import_name(node, lang)
|
|
91
|
+
if imp_name and any(n in imp_name for n in names):
|
|
92
|
+
try:
|
|
93
|
+
line = node.start_point[0] + 1 if node.start_point else 0
|
|
94
|
+
except Exception as e:
|
|
95
|
+
_log.warning("_walk_imports failed: %s", e)
|
|
96
|
+
line = 0
|
|
97
|
+
results.append(
|
|
98
|
+
{
|
|
99
|
+
"name": imp_name,
|
|
100
|
+
"line": line,
|
|
101
|
+
"full_text": node_text(node),
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
for child in children(node):
|
|
106
|
+
_walk_imports(child, import_types, names, results, lang)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _extract_import_name(node: Any, lang: Lang) -> str:
|
|
110
|
+
if lang in (Lang.JAVASCRIPT, Lang.TYPESCRIPT):
|
|
111
|
+
source = child_by_field(node, "source")
|
|
112
|
+
if source:
|
|
113
|
+
return node_text(source).strip("\"'")
|
|
114
|
+
return ""
|
|
115
|
+
if lang in (Lang.RUST, Lang.JAVA, Lang.GO, Lang.SWIFT, Lang.C_SHARP, Lang.KOTLIN, Lang.DART):
|
|
116
|
+
# Try named fields first
|
|
117
|
+
src = (
|
|
118
|
+
child_by_field(node, "source")
|
|
119
|
+
or child_by_field(node, "name")
|
|
120
|
+
or child_by_field(node, "alias")
|
|
121
|
+
)
|
|
122
|
+
if src:
|
|
123
|
+
return node_text(src).strip("\"'<>")
|
|
124
|
+
# Walk children for identifier / scoped_identifier / string
|
|
125
|
+
for child in children(node):
|
|
126
|
+
ct = child.type
|
|
127
|
+
if ct == "scoped_identifier":
|
|
128
|
+
return node_text(child)
|
|
129
|
+
if ct in ("identifier", "name"):
|
|
130
|
+
return node_text(child)
|
|
131
|
+
if lang == Lang.GO and ct == "import_spec":
|
|
132
|
+
for c2 in children(child):
|
|
133
|
+
if c2.type == "interpreted_string_literal":
|
|
134
|
+
return node_text(c2).strip('"')
|
|
135
|
+
if lang in (Lang.C_SHARP,) and ct == "string_literal":
|
|
136
|
+
return node_text(child).strip("\"'")
|
|
137
|
+
if lang == Lang.C_SHARP and ct == "alias_qualified_name":
|
|
138
|
+
for c2 in children(child):
|
|
139
|
+
if c2.type == "identifier":
|
|
140
|
+
return node_text(c2)
|
|
141
|
+
return ""
|
|
142
|
+
if lang in (Lang.C, Lang.CPP):
|
|
143
|
+
for child in children(node):
|
|
144
|
+
if child.type == "string_literal":
|
|
145
|
+
return node_text(child).strip('"<>')
|
|
146
|
+
return ""
|
|
147
|
+
if lang == Lang.RUBY:
|
|
148
|
+
for child in children(node):
|
|
149
|
+
if child.type in ("string", "simple_symbol"):
|
|
150
|
+
return node_text(child).strip("\"':")
|
|
151
|
+
if child.type == "argument_list":
|
|
152
|
+
for c2 in children(child):
|
|
153
|
+
if c2.type in ("string", "simple_symbol"):
|
|
154
|
+
return node_text(c2).strip("\"':")
|
|
155
|
+
return ""
|
|
156
|
+
if lang == Lang.PHP:
|
|
157
|
+
name_node = child_by_field(node, "name")
|
|
158
|
+
if name_node:
|
|
159
|
+
return node_text(name_node)
|
|
160
|
+
for child in children(node):
|
|
161
|
+
ct = child.type
|
|
162
|
+
if ct == "namespace_use_clause":
|
|
163
|
+
for c2 in children(child):
|
|
164
|
+
if c2.type == "qualified_name":
|
|
165
|
+
return node_text(c2)
|
|
166
|
+
if ct in ("namespace_name", "name", "qualified_name"):
|
|
167
|
+
return node_text(child)
|
|
168
|
+
return ""
|
|
169
|
+
return ""
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST scanning utilities for Patchi.
|
|
3
|
+
Provides tree-sitter based code scanning capabilities.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _slice(node: Any, source: str) -> str:
|
|
14
|
+
"""Extract source code slice for a tree-sitter node."""
|
|
15
|
+
try:
|
|
16
|
+
return source[node.start_byte : node.end_byte]
|
|
17
|
+
except Exception:
|
|
18
|
+
return ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def scan_python(
|
|
22
|
+
source: str,
|
|
23
|
+
file_path: Path,
|
|
24
|
+
) -> dict[str, Any]:
|
|
25
|
+
"""
|
|
26
|
+
Scan Python source code and extract structured information.
|
|
27
|
+
|
|
28
|
+
Returns a dictionary with:
|
|
29
|
+
- functions: list of function definitions
|
|
30
|
+
- classes: list of class definitions
|
|
31
|
+
- imports: list of imports
|
|
32
|
+
- calls: function/method calls
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
import ast
|
|
36
|
+
except ImportError:
|
|
37
|
+
logger.warning("ast module not available")
|
|
38
|
+
return {"functions": [], "classes": [], "imports": [], "calls": []}
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
tree = ast.parse(source)
|
|
42
|
+
except SyntaxError as e:
|
|
43
|
+
logger.warning(f"Syntax error in {file_path}: {e}")
|
|
44
|
+
return {"functions": [], "classes": [], "imports": [], "calls": []}
|
|
45
|
+
|
|
46
|
+
functions = []
|
|
47
|
+
classes = []
|
|
48
|
+
imports = []
|
|
49
|
+
calls = []
|
|
50
|
+
|
|
51
|
+
for node in ast.walk(tree):
|
|
52
|
+
if isinstance(node, ast.FunctionDef):
|
|
53
|
+
functions.append(
|
|
54
|
+
{
|
|
55
|
+
"name": node.name,
|
|
56
|
+
"line": node.lineno,
|
|
57
|
+
"end_line": getattr(node, "end_lineno", node.lineno),
|
|
58
|
+
"args": [a.arg for a in node.args.args],
|
|
59
|
+
"returns": ast.unparse(node.returns) if node.returns else None,
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
elif isinstance(node, ast.ClassDef):
|
|
63
|
+
classes.append(
|
|
64
|
+
{
|
|
65
|
+
"name": node.name,
|
|
66
|
+
"line": node.lineno,
|
|
67
|
+
"end_line": getattr(node, "end_lineno", node.lineno),
|
|
68
|
+
"bases": [ast.unparse(b) for b in node.bases],
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
elif isinstance(node, ast.Import):
|
|
72
|
+
for alias in node.names:
|
|
73
|
+
imports.append(
|
|
74
|
+
{
|
|
75
|
+
"name": alias.name,
|
|
76
|
+
"alias": alias.asname,
|
|
77
|
+
"line": node.lineno,
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
elif isinstance(node, ast.ImportFrom):
|
|
81
|
+
for alias in node.names:
|
|
82
|
+
imports.append(
|
|
83
|
+
{
|
|
84
|
+
"name": f"{node.module}.{alias.name}" if node.module else alias.name,
|
|
85
|
+
"alias": alias.asname,
|
|
86
|
+
"line": node.lineno,
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
elif isinstance(node, ast.Call):
|
|
90
|
+
# Full dotted name via the shared helper (single source of truth
|
|
91
|
+
# with calls.find_calls) — leaf-only names broke deep dotted
|
|
92
|
+
# matching like a.b.c.execute().
|
|
93
|
+
from .helpers import _py_call_name
|
|
94
|
+
|
|
95
|
+
func_name = _py_call_name(node)
|
|
96
|
+
calls.append(
|
|
97
|
+
{
|
|
98
|
+
"function": func_name,
|
|
99
|
+
"line": node.lineno,
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
"functions": functions,
|
|
105
|
+
"classes": classes,
|
|
106
|
+
"imports": imports,
|
|
107
|
+
"calls": calls,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def scan_generic(source: str, file_path: Path) -> dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
Generic scanner for non-Python files.
|
|
114
|
+
Returns basic structural information.
|
|
115
|
+
"""
|
|
116
|
+
return {
|
|
117
|
+
"functions": [],
|
|
118
|
+
"classes": [],
|
|
119
|
+
"imports": [],
|
|
120
|
+
"calls": [],
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def scan_file(file_path: Path) -> dict[str, Any]:
|
|
125
|
+
"""
|
|
126
|
+
Scan a file and return structured information.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
source = file_path.read_text(encoding="utf-8", errors="ignore")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.warning(f"Failed to read {file_path}: {e}")
|
|
132
|
+
return {"functions": [], "classes": [], "imports": [], "calls": []}
|
|
133
|
+
|
|
134
|
+
suffix = file_path.suffix.lower()
|
|
135
|
+
if suffix == ".py":
|
|
136
|
+
return scan_python(source, file_path)
|
|
137
|
+
else:
|
|
138
|
+
return scan_generic(source, file_path)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def scan_python_file(file_path: Path) -> dict[str, Any]:
|
|
142
|
+
"""Convenience function to scan a Python file."""
|
|
143
|
+
return scan_python(file_path.read_text(encoding="utf-8"), file_path)
|