roam-code 12.6.0__tar.gz → 12.7.0__tar.gz
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.
- {roam_code-12.6.0/src/roam_code.egg-info → roam_code-12.7.0}/PKG-INFO +1 -1
- {roam_code-12.6.0 → roam_code-12.7.0}/pyproject.toml +1 -1
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/catalog/python_idioms.py +374 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_context.py +35 -0
- {roam_code-12.6.0 → roam_code-12.7.0/src/roam_code.egg-info}/PKG-INFO +1 -1
- {roam_code-12.6.0 → roam_code-12.7.0}/LICENSE +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/README.md +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/setup.cfg +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/__main__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/analysis/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/analysis/effects.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/analysis/taint.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/api.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/ask/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/ask/classifier.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/ask/recipes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/ask/runner.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/attest/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/attest/cga.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/bridges/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/bridges/base.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/bridges/bridge_config.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/bridges/bridge_django.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/bridges/bridge_protobuf.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/bridges/bridge_rest_api.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/bridges/bridge_salesforce.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/bridges/bridge_template.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/bridges/registry.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/catalog/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/catalog/detectors.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/catalog/fixes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/catalog/smells.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/catalog/tasks.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/cli.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/changed_files.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_adrs.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_adversarial.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_affected.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_affected_tests.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_agent_context.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_agent_export.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_agent_plan.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_ai_ratio.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_ai_readiness.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_alerts.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_annotate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_api_changes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_api_drift.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_ask.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_attest.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_auth_gaps.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_bisect.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_breaking.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_budget.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_bus_factor.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_capsule.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_cga.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_check_rules.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_ci_setup.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_clean.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_clones.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_closure.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_clusters.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_codeowners.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_complexity.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_config.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_congestion.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_conventions.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_coupling.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_coverage_gaps.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_critique.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_cut.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_dark_matter.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_dashboard.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_dead.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_debt.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_deps.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_describe.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_dev_profile.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_diagnose.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_diff.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_doc_staleness.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_docs_coverage.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_doctor.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_drift.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_duplicates.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_effects.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_endpoints.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_entry_points.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_eval_retrieve.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_fan.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_file.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_fingerprint.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_fitness.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_flag_dead.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_fleet.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_fn_coupling.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_forecast.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_grep.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_guard.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_health.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_hooks.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_hotspots.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_impact.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_index.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_index_bundle.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_ingest_trace.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_init.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_intent.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_invariants.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_layers.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_map.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_math.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_mcp_setup.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_metrics.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_migration_safety.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_minimap.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_missing_index.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_module.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_mutate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_n1.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_oracle.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_orchestrate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_orphan_routes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_over_fetch.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_owner.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_partition.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_path_coverage.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_patterns.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_plan.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_plan_refactor.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_pr_diff.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_pr_risk.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_preflight.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_py_modern.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_py_types.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_relate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_report.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_reset.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_retrieve.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_risk.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_rules.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_safe_delete.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_safe_zones.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_sbom.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_schema.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_search.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_search_semantic.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_secrets.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_semantic_diff.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_simulate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_simulate_departure.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_sketch.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_smells.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_spectral.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_split.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_supply_chain.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_symbol.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_syntax_check.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_taint.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_test_gaps.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_test_scaffold.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_testmap.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_tour.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_trace.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_trends.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_triage.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_understand.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_uses.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_verify.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_verify_imports.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_vibe_check.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_visualize.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_vuln_map.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_vuln_reach.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_vulns.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_watch.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_weather.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_why.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_ws.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/cmd_xlang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/codeowners_helpers.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/context_helpers.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/gate_presets.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/graph_helpers.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/metrics_history.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/next_steps.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/resolve.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/commands/suppression.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/competitor_site_data.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/config.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/coverage_reports.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/critique/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/critique/aggregator.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/critique/checks.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/db/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/db/connection.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/db/queries.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/db/schema.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/eval/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/eval/harness.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/exit_codes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/fleet/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/fleet/adapters.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/fleet/manifest.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/git_utils.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/anomaly.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/builder.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/clone_detect.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/clusters.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/cycles.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/dark_matter.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/diff.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/fingerprint.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/layers.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/pagerank.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/partition.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/pathfinding.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/propagation.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/simulate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/spectral.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/graph/stats.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/complexity.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/discovery.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/django_post.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/file_roles.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/git_stats.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/gitignore.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/incremental.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/indexer.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/parser.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/relations.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/symbols.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/index/test_conventions.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/apex_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/aura_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/base.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/c_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/csharp_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/extractor_schema.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/foxpro_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/generic_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/go_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/hcl_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/java_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/javascript_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/kotlin_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/php_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/python_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/query_engine.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/registry.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/ruby_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/rust_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/scala_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/sfxml_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/sql_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/swift_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/typescript_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/visualforce_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/languages/yaml_lang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/mcp_extras/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/mcp_extras/completions.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/mcp_extras/progress.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/mcp_extras/sampling.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/mcp_extras/session.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/mcp_extras/watcher.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/mcp_server.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/output/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/output/file_role_hints.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/output/formatter.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/output/mermaid.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/output/sarif.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/output/schema_registry.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/plugins.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/refactor/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/refactor/codegen.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/refactor/transforms.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/retrieve/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/retrieve/learned_ranker.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/retrieve/pipeline.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/retrieve/rerank.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/retrieve/seeds.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/retrieve/semantic.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/rules/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/rules/ast_match.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/rules/builtin.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/rules/dataflow.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/rules/engine.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/runtime/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/runtime/daemon.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/runtime/graph_backend.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/runtime/hotspots.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/runtime/lock_modes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/runtime/lockmgr.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/runtime/trace_ingest.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/search/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/search/framework_packs.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/search/index_embeddings.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/search/onnx_embeddings.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/search/tfidf.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/security/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/security/aibom_extension.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/security/taint_classifier.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/security/taint_engine.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/security/vuln_reach.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/security/vuln_store.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/surface_counts.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/templates/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/templates/ci/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/workspace/__init__.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/workspace/aggregator.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/workspace/api_scanner.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/workspace/config.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam/workspace/db.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam_code.egg-info/SOURCES.txt +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam_code.egg-info/dependency_links.txt +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam_code.egg-info/entry_points.txt +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam_code.egg-info/requires.txt +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/src/roam_code.egg-info/top_level.txt +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_adrs.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_adversarial.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_affected.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_agent_export.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_agent_mode.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_agent_plan_context.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_ai_ratio.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_ai_readiness.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_alerts_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_annotations.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_anomaly.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_api_changes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_api_drift.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_ask.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_attest.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_auth_gaps.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_backend_fixes_round2.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_backend_fixes_round3.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_basic.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_batch_mcp.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_bisect.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_bridge_django.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_bridges.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_bridges_extended.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_budget.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_budget_flag.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_budget_phase2.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_bus_factor.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_capsule.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_cga.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_check_rules.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_ci_gate_eval.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_ci_sarif_guard.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_ci_setup.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_clones.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_closure.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_codeowners.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_commands_architecture.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_commands_exploration.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_commands_health.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_commands_refactoring.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_commands_workflow.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_competitor_site_data.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_comprehensive.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_config.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_congestion.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_context_propagation.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_conventions_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_coverage_gaps_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_coverage_ingestion.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_critique.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_cut.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_dark_matter.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_dark_matter_helpers.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_dashboard.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_dataflow_dead.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_dead_aging.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_defer_loading.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_demo_gif_asset.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_describe.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_detail_flag_hints.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_deterministic_output.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_dev_profile.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_difficulty_scoring.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_doc_staleness.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_docker_assets.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_docs_coverage.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_docs_site_quality.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_doctor.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_drift.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_duplicates.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_effects.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_effects_propagation.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_endpoints.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_entry_points_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_eval_retrieve.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_exclude_patterns.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_exit_codes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_extractor_grammar_drift.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_fallback_contracts.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_file_roles.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_fingerprint.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_fixes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_flag_dead.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_fleet.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_fn_coupling.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_forecast.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_formatters.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_foxpro.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_framework_detection.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_gate_presets.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_git_utils.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_guard.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_health_gate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_hooks.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_hotspots.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_index.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_index_bundle.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_init_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_install_check.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_intent.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_invariants.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_json_contracts.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_json_error_envelope.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_kotlin_swift_extractors.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_language_corpus.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_languages.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_library_api.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_math.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_math_tips.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_mcp_extras.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_mcp_server.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_mcp_setup.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_mermaid.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_metrics_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_migration_safety.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_minimap.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_missing_index.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_mutate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_n1.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_next_steps.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_onboard.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_oracle.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_orchestrate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_orphan_routes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_oss_bench_harness.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_over_fetch.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_pagerank_truncation.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_partition.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_path_coverage.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_patterns_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_performance.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_personalized_pagerank.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_plan.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_plugin_discovery.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_pr_comment_script.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_pr_diff.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_pr_risk_author.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_progress.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_progressive_disclosure.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_properties.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_python_extractor_v2.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_python_idioms_e2e.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_python_pivot.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_readme_surface_consistency.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_refactoring_intelligence.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_relate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_report.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_reset_clean.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_resolve.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_retrieve.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_retrieve_cross_repo.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_retrieve_seeds.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_risk.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_ruby.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_rule_profiles.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_rules.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_rules_ast_match.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_rules_community_pack.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_rules_dataflow.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_rules_symbol_requirements.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_runtime.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_runtime_score.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_salesforce.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_sarif_flag.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_sbom.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_scala.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_schema_versioning.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_search_explain.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_secrets.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_secrets_v2.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_semantic_diff.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_semantic_onnx.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_semantic_search.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_simulate.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_simulate_departure.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_sketch.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_smells.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_smoke.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_sna_metrics.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_spectral.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_split_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_sql.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_suggest_reviewers.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_supply_chain.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_surface_counts.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_syntax_check.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_taint.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_taint_analysis.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_taint_classifier.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_test_conventions.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_test_gaps.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_test_scaffold.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_testmap.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_top_flag_consistency.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_tour_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_trends.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_trends_cohort.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_triage.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_uses_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_v12_2.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_v6_features.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_v71_features.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_v7_features.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_v82_features.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_verify.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_verify_imports.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_vibe_check.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_visualize.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_vuln.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_vulns_cmd.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_watch.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_why.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_workspace.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_ws_resolve_fixes.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_xlang.py +0 -0
- {roam_code-12.6.0 → roam_code-12.7.0}/tests/test_yaml_hcl.py +0 -0
|
@@ -140,6 +140,50 @@ _MAGIC_NUMBER_RE = re.compile(
|
|
|
140
140
|
# function name appears in its own body. Done in detector loop, not
|
|
141
141
|
# regex.
|
|
142
142
|
|
|
143
|
+
# Django ORM N+1: ``.filter(...)`` then ``for x in qs:`` then access
|
|
144
|
+
# of related field (e.g. ``x.author.name``). The simplified detector
|
|
145
|
+
# below catches the canonical pattern: ``.all()`` immediately
|
|
146
|
+
# followed by ``for x in qs`` in the same function body.
|
|
147
|
+
_DJANGO_ALL_THEN_FOR = re.compile(
|
|
148
|
+
r"\.\s*all\s*\(\s*\)[^\n]*\n[^\n]*for\s+\w+\s+in\s+\w+\s*:",
|
|
149
|
+
)
|
|
150
|
+
# Calling ``.objects.filter(...)`` inside a loop — N+1.
|
|
151
|
+
_DJANGO_FILTER_IN_LOOP = re.compile(
|
|
152
|
+
r"^\s+.*\.objects\.\s*(?:filter|get)\s*\(",
|
|
153
|
+
re.MULTILINE,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# SQLAlchemy: ``.all()`` then iterate accessing a relationship attribute.
|
|
157
|
+
# Simplified pattern: ``.all()`` in any function with ``relationship``
|
|
158
|
+
# imported. Caller needs to verify it's worth flagging.
|
|
159
|
+
_SQLALCHEMY_RELATIONSHIP = re.compile(r"=\s*relationship\s*\(")
|
|
160
|
+
_SQLALCHEMY_ALL_THEN_DOT = re.compile(
|
|
161
|
+
r"\.\s*all\s*\(\s*\)[^\n]*\n[^\n]*\bfor\b[^\n]*\n[^\n]*\.\w+",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# FastAPI Depends() — dependency injection chain.
|
|
165
|
+
_FASTAPI_DEPENDS_RE = re.compile(r"\bDepends\s*\(\s*(\w+)\s*\)")
|
|
166
|
+
|
|
167
|
+
# Late-binding closure: ``lambda x: i*x`` inside a loop where ``i``
|
|
168
|
+
# changes per iteration. Pattern: ``for i in ...:\n ... lambda``
|
|
169
|
+
# in same body. Caller verifies same-loop scope.
|
|
170
|
+
_LAMBDA_IN_LOOP_RE = re.compile(
|
|
171
|
+
r"^\s+for\s+(\w+)\s+in\s[^\n]+:\s*\n[\s\S]{0,200}?lambda\b",
|
|
172
|
+
re.MULTILINE,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# ``except SomeError: pass`` — silently swallowing exceptions.
|
|
176
|
+
_EXCEPT_PASS_RE = re.compile(r"^\s*except\b[^:]*:\s*\n\s*pass\s*\n", re.MULTILINE)
|
|
177
|
+
|
|
178
|
+
# ``except Exception:`` (broad) — catches too much. Less severe than
|
|
179
|
+
# bare ``except:`` but still flagged.
|
|
180
|
+
_BROAD_EXCEPT_RE = re.compile(r"^\s*except\s+(?:Exception|BaseException)\s*(?:as\s+\w+\s*)?:", re.MULTILINE)
|
|
181
|
+
|
|
182
|
+
# ``async for`` missing on async iterators (StreamReader, async generators)
|
|
183
|
+
# — heuristic: ``for X in <name>`` where the iterator type hint suggests
|
|
184
|
+
# async (AsyncIterator, AsyncGenerator, AsyncIterable).
|
|
185
|
+
_ASYNC_ITER_HINT = re.compile(r"AsyncIterator|AsyncGenerator|AsyncIterable")
|
|
186
|
+
|
|
143
187
|
|
|
144
188
|
def _project_root_for_conn(conn: sqlite3.Connection) -> str:
|
|
145
189
|
"""Resolve the project root for ``conn`` by inspecting its
|
|
@@ -772,6 +816,329 @@ def detect_async_not_awaited(conn: sqlite3.Connection) -> list[dict]:
|
|
|
772
816
|
return findings
|
|
773
817
|
|
|
774
818
|
|
|
819
|
+
def detect_lambda_in_loop(conn: sqlite3.Connection) -> list[dict]:
|
|
820
|
+
"""Find ``lambda`` in a ``for``-loop body — classic late-binding bug.
|
|
821
|
+
|
|
822
|
+
The lambda captures the loop variable by reference, so all
|
|
823
|
+
lambdas end up bound to the LAST value of the variable. Fix:
|
|
824
|
+
add ``i=i`` default argument or use functools.partial.
|
|
825
|
+
"""
|
|
826
|
+
findings: list[dict] = []
|
|
827
|
+
for file_id, path in _python_files(conn):
|
|
828
|
+
text = _file_text(conn, file_id)
|
|
829
|
+
if text:
|
|
830
|
+
text = _strip_strings_and_comments(text)
|
|
831
|
+
if not text:
|
|
832
|
+
continue
|
|
833
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
834
|
+
for match in _LAMBDA_IN_LOOP_RE.finditer(text):
|
|
835
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
836
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
837
|
+
if sym is None:
|
|
838
|
+
continue
|
|
839
|
+
var = match.group(1)
|
|
840
|
+
findings.append(
|
|
841
|
+
_idiom_finding(
|
|
842
|
+
task_id="py-lambda-in-loop",
|
|
843
|
+
detected_way="late-binding-closure",
|
|
844
|
+
symbol_id=sym[0],
|
|
845
|
+
symbol_name=sym[1],
|
|
846
|
+
file_path=path,
|
|
847
|
+
line_no=line_no,
|
|
848
|
+
reason=(
|
|
849
|
+
f"``lambda`` inside ``for {var} in …`` loop — late binding: "
|
|
850
|
+
f"all lambdas bind to the LAST value of {var}"
|
|
851
|
+
),
|
|
852
|
+
confidence="medium",
|
|
853
|
+
fix=f"lambda x, {var}={var}: ... # capture by default arg",
|
|
854
|
+
)
|
|
855
|
+
)
|
|
856
|
+
return findings
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def detect_except_pass(conn: sqlite3.Connection) -> list[dict]:
|
|
860
|
+
"""Find ``except X: pass`` — silently swallowing exceptions."""
|
|
861
|
+
findings: list[dict] = []
|
|
862
|
+
for file_id, path in _python_files(conn):
|
|
863
|
+
text = _file_text(conn, file_id)
|
|
864
|
+
if text:
|
|
865
|
+
text = _strip_strings_and_comments(text)
|
|
866
|
+
if not text:
|
|
867
|
+
continue
|
|
868
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
869
|
+
for match in _EXCEPT_PASS_RE.finditer(text):
|
|
870
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
871
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
872
|
+
if sym is None:
|
|
873
|
+
continue
|
|
874
|
+
findings.append(
|
|
875
|
+
_idiom_finding(
|
|
876
|
+
task_id="py-except-pass",
|
|
877
|
+
detected_way="silent-swallow",
|
|
878
|
+
symbol_id=sym[0],
|
|
879
|
+
symbol_name=sym[1],
|
|
880
|
+
file_path=path,
|
|
881
|
+
line_no=line_no,
|
|
882
|
+
reason="``except X: pass`` silently swallows the exception — log it or re-raise",
|
|
883
|
+
confidence="high",
|
|
884
|
+
fix="except X as exc:\n logger.warning('...', exc_info=exc)\n raise",
|
|
885
|
+
)
|
|
886
|
+
)
|
|
887
|
+
return findings
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def detect_broad_except(conn: sqlite3.Connection) -> list[dict]:
|
|
891
|
+
"""Find ``except Exception:`` / ``except BaseException:``."""
|
|
892
|
+
findings: list[dict] = []
|
|
893
|
+
for file_id, path in _python_files(conn):
|
|
894
|
+
text = _file_text(conn, file_id)
|
|
895
|
+
if text:
|
|
896
|
+
text = _strip_strings_and_comments(text)
|
|
897
|
+
if not text:
|
|
898
|
+
continue
|
|
899
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
900
|
+
for match in _BROAD_EXCEPT_RE.finditer(text):
|
|
901
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
902
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
903
|
+
if sym is None:
|
|
904
|
+
continue
|
|
905
|
+
findings.append(
|
|
906
|
+
_idiom_finding(
|
|
907
|
+
task_id="py-broad-except",
|
|
908
|
+
detected_way="catch-too-much",
|
|
909
|
+
symbol_id=sym[0],
|
|
910
|
+
symbol_name=sym[1],
|
|
911
|
+
file_path=path,
|
|
912
|
+
line_no=line_no,
|
|
913
|
+
reason="``except Exception:`` catches more than intended — narrow to specific class(es)",
|
|
914
|
+
confidence="low",
|
|
915
|
+
fix="except (ValueError, KeyError): # or whatever the actual cases are",
|
|
916
|
+
)
|
|
917
|
+
)
|
|
918
|
+
return findings
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def detect_django_n1(conn: sqlite3.Connection) -> list[dict]:
|
|
922
|
+
"""Find Django ORM N+1 patterns:
|
|
923
|
+
|
|
924
|
+
* ``.objects.filter(...)`` / ``.get(...)`` *inside* a loop body.
|
|
925
|
+
* ``.all()`` immediately followed by ``for x in qs:`` (suggests
|
|
926
|
+
``.select_related()`` / ``.prefetch_related()``).
|
|
927
|
+
"""
|
|
928
|
+
findings: list[dict] = []
|
|
929
|
+
for file_id, path in _python_files(conn):
|
|
930
|
+
text = _file_text(conn, file_id)
|
|
931
|
+
if text:
|
|
932
|
+
text = _strip_strings_and_comments(text)
|
|
933
|
+
if not text:
|
|
934
|
+
continue
|
|
935
|
+
# Quick reject: skip files with no Django ORM hints
|
|
936
|
+
if ".objects." not in text and ".all()" not in text:
|
|
937
|
+
continue
|
|
938
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
939
|
+
for match in _DJANGO_ALL_THEN_FOR.finditer(text):
|
|
940
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
941
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
942
|
+
if sym is None:
|
|
943
|
+
continue
|
|
944
|
+
findings.append(
|
|
945
|
+
_idiom_finding(
|
|
946
|
+
task_id="py-django-n1",
|
|
947
|
+
detected_way="all-then-for",
|
|
948
|
+
symbol_id=sym[0],
|
|
949
|
+
symbol_name=sym[1],
|
|
950
|
+
file_path=path,
|
|
951
|
+
line_no=line_no,
|
|
952
|
+
reason="``.all()`` then iterate — likely N+1; use ``.select_related()`` / ``.prefetch_related()``",
|
|
953
|
+
confidence="medium",
|
|
954
|
+
fix=".all().select_related('fk_field')",
|
|
955
|
+
)
|
|
956
|
+
)
|
|
957
|
+
# filter/get inside loop: detect by walking lines, tracking
|
|
958
|
+
# indent depth from preceding ``for``/``while``.
|
|
959
|
+
lines = text.splitlines()
|
|
960
|
+
for i, line in enumerate(lines, 1):
|
|
961
|
+
if not _DJANGO_FILTER_IN_LOOP.search("\n" + line):
|
|
962
|
+
continue
|
|
963
|
+
# Look back up to 20 lines for ``for X in`` at lesser indent
|
|
964
|
+
current_indent = len(line) - len(line.lstrip())
|
|
965
|
+
in_loop = False
|
|
966
|
+
for back in range(max(0, i - 20), i - 1):
|
|
967
|
+
prev = lines[back]
|
|
968
|
+
prev_indent = len(prev) - len(prev.lstrip())
|
|
969
|
+
if prev_indent < current_indent and (
|
|
970
|
+
prev.lstrip().startswith("for ") or prev.lstrip().startswith("while ")
|
|
971
|
+
):
|
|
972
|
+
in_loop = True
|
|
973
|
+
break
|
|
974
|
+
if not in_loop:
|
|
975
|
+
continue
|
|
976
|
+
sym = _enclosing_symbol(i, sym_index)
|
|
977
|
+
if sym is None:
|
|
978
|
+
continue
|
|
979
|
+
findings.append(
|
|
980
|
+
_idiom_finding(
|
|
981
|
+
task_id="py-django-n1",
|
|
982
|
+
detected_way="query-in-loop",
|
|
983
|
+
symbol_id=sym[0],
|
|
984
|
+
symbol_name=sym[1],
|
|
985
|
+
file_path=path,
|
|
986
|
+
line_no=i,
|
|
987
|
+
reason="Django ORM query inside loop — N+1; lift the query out or use ``.in_bulk()``",
|
|
988
|
+
confidence="high",
|
|
989
|
+
fix="ids = [x.id for x in items]; lookup = Model.objects.in_bulk(ids)",
|
|
990
|
+
)
|
|
991
|
+
)
|
|
992
|
+
return findings
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
def detect_sqlalchemy_lazy(conn: sqlite3.Connection) -> list[dict]:
|
|
996
|
+
"""Find SQLAlchemy ``.all()`` then iterate-with-attribute patterns.
|
|
997
|
+
|
|
998
|
+
Heuristic: the file has ``relationship(`` (so it defines models)
|
|
999
|
+
AND the ``.all()`` call is immediately followed by a ``for``-loop
|
|
1000
|
+
that accesses an attribute on each item. The accessed attribute
|
|
1001
|
+
is likely a relationship that triggers a lazy SELECT per row.
|
|
1002
|
+
"""
|
|
1003
|
+
findings: list[dict] = []
|
|
1004
|
+
for file_id, path in _python_files(conn):
|
|
1005
|
+
text = _file_text(conn, file_id)
|
|
1006
|
+
if text:
|
|
1007
|
+
text = _strip_strings_and_comments(text)
|
|
1008
|
+
if not text:
|
|
1009
|
+
continue
|
|
1010
|
+
# Quick reject: must have at least one relationship() declaration
|
|
1011
|
+
if not _SQLALCHEMY_RELATIONSHIP.search(text):
|
|
1012
|
+
continue
|
|
1013
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
1014
|
+
for match in _SQLALCHEMY_ALL_THEN_DOT.finditer(text):
|
|
1015
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
1016
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
1017
|
+
if sym is None:
|
|
1018
|
+
continue
|
|
1019
|
+
findings.append(
|
|
1020
|
+
_idiom_finding(
|
|
1021
|
+
task_id="py-sqlalchemy-lazy",
|
|
1022
|
+
detected_way="lazy-load-in-loop",
|
|
1023
|
+
symbol_id=sym[0],
|
|
1024
|
+
symbol_name=sym[1],
|
|
1025
|
+
file_path=path,
|
|
1026
|
+
line_no=line_no,
|
|
1027
|
+
reason=".all() then for-loop accessing attr — likely lazy-load N+1",
|
|
1028
|
+
confidence="medium",
|
|
1029
|
+
fix=".options(selectinload(Model.relationship)).all()",
|
|
1030
|
+
)
|
|
1031
|
+
)
|
|
1032
|
+
return findings
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def detect_fastapi_depends(conn: sqlite3.Connection) -> list[dict]:
|
|
1036
|
+
"""Inventory FastAPI ``Depends(X)`` provider chain.
|
|
1037
|
+
|
|
1038
|
+
Not strictly an anti-pattern; surfaces as info-level findings so
|
|
1039
|
+
agents can discover the dependency graph for FastAPI apps.
|
|
1040
|
+
"""
|
|
1041
|
+
findings: list[dict] = []
|
|
1042
|
+
for file_id, path in _python_files(conn):
|
|
1043
|
+
text = _file_text(conn, file_id)
|
|
1044
|
+
if text:
|
|
1045
|
+
text = _strip_strings_and_comments(text)
|
|
1046
|
+
if not text:
|
|
1047
|
+
continue
|
|
1048
|
+
if "Depends(" not in text:
|
|
1049
|
+
continue
|
|
1050
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
1051
|
+
seen: set[tuple[int, str]] = set()
|
|
1052
|
+
for match in _FASTAPI_DEPENDS_RE.finditer(text):
|
|
1053
|
+
provider = match.group(1)
|
|
1054
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
1055
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
1056
|
+
if sym is None:
|
|
1057
|
+
continue
|
|
1058
|
+
key = (sym[0], provider)
|
|
1059
|
+
if key in seen:
|
|
1060
|
+
continue
|
|
1061
|
+
seen.add(key)
|
|
1062
|
+
findings.append(
|
|
1063
|
+
_idiom_finding(
|
|
1064
|
+
task_id="py-fastapi-depends",
|
|
1065
|
+
detected_way="dependency-provider",
|
|
1066
|
+
symbol_id=sym[0],
|
|
1067
|
+
symbol_name=sym[1],
|
|
1068
|
+
file_path=path,
|
|
1069
|
+
line_no=line_no,
|
|
1070
|
+
reason=f"FastAPI dependency: depends on ``{provider}``",
|
|
1071
|
+
confidence="high",
|
|
1072
|
+
fix="",
|
|
1073
|
+
)
|
|
1074
|
+
)
|
|
1075
|
+
return findings
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
def detect_sync_calls_async_via_graph(conn: sqlite3.Connection) -> list[dict]:
|
|
1079
|
+
"""Use the call graph to find sync functions calling async ones.
|
|
1080
|
+
|
|
1081
|
+
Complement to ``detect_async_not_awaited`` (regex-based on names).
|
|
1082
|
+
This walks ``edges.kind='call'`` and reports edges where source
|
|
1083
|
+
is sync and target is async. Strong-evidence finding because it's
|
|
1084
|
+
backed by the indexed graph not text matching.
|
|
1085
|
+
|
|
1086
|
+
Skips when the source itself is a known wrapper (test runner,
|
|
1087
|
+
``asyncio.run``-style entry point) by name.
|
|
1088
|
+
"""
|
|
1089
|
+
findings: list[dict] = []
|
|
1090
|
+
# Names that are legitimate sync→async entry points and should be
|
|
1091
|
+
# excluded from the scan.
|
|
1092
|
+
skip_source_names = {"main", "cli", "run", "entrypoint", "_main", "__main__"}
|
|
1093
|
+
try:
|
|
1094
|
+
rows = conn.execute(
|
|
1095
|
+
"""
|
|
1096
|
+
SELECT e.source_id, src.name AS src_name, src.is_async AS src_async,
|
|
1097
|
+
e.target_id, tgt.name AS tgt_name,
|
|
1098
|
+
src_f.path AS src_file, src.line_start AS src_line
|
|
1099
|
+
FROM edges e
|
|
1100
|
+
JOIN symbols src ON src.id = e.source_id
|
|
1101
|
+
JOIN symbols tgt ON tgt.id = e.target_id
|
|
1102
|
+
JOIN files src_f ON src_f.id = src.file_id
|
|
1103
|
+
WHERE e.kind = 'call'
|
|
1104
|
+
AND src.kind IN ('function', 'method')
|
|
1105
|
+
AND tgt.kind IN ('function', 'method')
|
|
1106
|
+
AND tgt.is_async = 1
|
|
1107
|
+
AND src.is_async = 0
|
|
1108
|
+
AND COALESCE(src_f.file_role, '') != 'test'
|
|
1109
|
+
"""
|
|
1110
|
+
).fetchall()
|
|
1111
|
+
except Exception:
|
|
1112
|
+
return findings
|
|
1113
|
+
seen: set[tuple[int, int]] = set()
|
|
1114
|
+
for r in rows:
|
|
1115
|
+
sid = int(r["source_id"])
|
|
1116
|
+
tid = int(r["target_id"])
|
|
1117
|
+
key = (sid, tid)
|
|
1118
|
+
if key in seen:
|
|
1119
|
+
continue
|
|
1120
|
+
seen.add(key)
|
|
1121
|
+
if r["src_name"] in skip_source_names:
|
|
1122
|
+
continue
|
|
1123
|
+
findings.append(
|
|
1124
|
+
_idiom_finding(
|
|
1125
|
+
task_id="py-sync-calls-async",
|
|
1126
|
+
detected_way="missing-await-graph",
|
|
1127
|
+
symbol_id=sid,
|
|
1128
|
+
symbol_name=r["src_name"],
|
|
1129
|
+
file_path=r["src_file"],
|
|
1130
|
+
line_no=int(r["src_line"] or 0),
|
|
1131
|
+
reason=(
|
|
1132
|
+
f"sync ``{r['src_name']}`` calls async ``{r['tgt_name']}`` — "
|
|
1133
|
+
f"either ``await`` it (mark caller async) or use ``asyncio.run``"
|
|
1134
|
+
),
|
|
1135
|
+
confidence="medium",
|
|
1136
|
+
fix=f"async def {r['src_name']}(...): await {r['tgt_name']}(...)",
|
|
1137
|
+
)
|
|
1138
|
+
)
|
|
1139
|
+
return findings
|
|
1140
|
+
|
|
1141
|
+
|
|
775
1142
|
def detect_lock_without_with(conn: sqlite3.Connection) -> list[dict]:
|
|
776
1143
|
"""Find ``lock.acquire()`` without ``with``-block — lock leak.
|
|
777
1144
|
|
|
@@ -967,4 +1334,11 @@ PYTHON_IDIOM_DETECTORS = [
|
|
|
967
1334
|
("py-async-with-missing", "async-resource-leak", detect_async_with_missing),
|
|
968
1335
|
("py-type-eq", "type-not-isinstance", detect_type_eq),
|
|
969
1336
|
("py-lock-without-with", "lock-leak", detect_lock_without_with),
|
|
1337
|
+
("py-sync-calls-async", "missing-await-graph", detect_sync_calls_async_via_graph),
|
|
1338
|
+
("py-django-n1", "django-orm", detect_django_n1),
|
|
1339
|
+
("py-sqlalchemy-lazy", "sqla-lazy", detect_sqlalchemy_lazy),
|
|
1340
|
+
("py-fastapi-depends", "fastapi-di", detect_fastapi_depends),
|
|
1341
|
+
("py-lambda-in-loop", "late-binding-closure", detect_lambda_in_loop),
|
|
1342
|
+
("py-except-pass", "silent-swallow", detect_except_pass),
|
|
1343
|
+
("py-broad-except", "catch-too-much", detect_broad_except),
|
|
970
1344
|
]
|
|
@@ -653,6 +653,41 @@ def _render_single_text(data):
|
|
|
653
653
|
click.echo(f" (+{len(siblings) - 10} more)")
|
|
654
654
|
click.echo()
|
|
655
655
|
|
|
656
|
+
# Python pivot v12.7: model-class fields. When the symbol is a
|
|
657
|
+
# Pydantic / dataclass / attrs / TypedDict / NamedTuple class,
|
|
658
|
+
# surface its fields directly (not just as siblings) so an agent
|
|
659
|
+
# working with the class immediately sees its shape.
|
|
660
|
+
if sym_kind == "class":
|
|
661
|
+
try:
|
|
662
|
+
from roam.catalog.python_idioms import is_model_class
|
|
663
|
+
|
|
664
|
+
sig_for_model = sym["signature"] if "signature" in _row_keys else ""
|
|
665
|
+
is_model, _label = is_model_class(sig_for_model, decorators_str)
|
|
666
|
+
except Exception:
|
|
667
|
+
is_model = False
|
|
668
|
+
if is_model:
|
|
669
|
+
from roam.db.connection import open_db as _open_db
|
|
670
|
+
|
|
671
|
+
try:
|
|
672
|
+
with _open_db(readonly=True) as _conn:
|
|
673
|
+
field_rows = _conn.execute(
|
|
674
|
+
"SELECT name, default_value FROM symbols "
|
|
675
|
+
"WHERE parent_id = ? AND kind = 'property' ORDER BY line_start",
|
|
676
|
+
(sym["id"],),
|
|
677
|
+
).fetchall()
|
|
678
|
+
except Exception:
|
|
679
|
+
field_rows = []
|
|
680
|
+
if field_rows:
|
|
681
|
+
click.echo(f"Fields ({len(field_rows)}):")
|
|
682
|
+
for fr in field_rows[:20]:
|
|
683
|
+
fname = fr["name"] if "name" in fr.keys() else fr[0]
|
|
684
|
+
fdef = (fr["default_value"] if "default_value" in fr.keys() else fr[1]) or ""
|
|
685
|
+
badge = f" = {fdef}" if fdef and fdef not in ("None",) else ""
|
|
686
|
+
click.echo(f" {fname}{badge}")
|
|
687
|
+
if len(field_rows) > 20:
|
|
688
|
+
click.echo(f" (+{len(field_rows) - 20} more)")
|
|
689
|
+
click.echo()
|
|
690
|
+
|
|
656
691
|
# Always render all extras
|
|
657
692
|
_render_complexity_text(data.get("complexity"))
|
|
658
693
|
_render_graph_centrality_text(data.get("graph_centrality"))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|