roam-code 12.3.0__tar.gz → 12.4.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.3.0/src/roam_code.egg-info → roam_code-12.4.0}/PKG-INFO +1 -1
- {roam_code-12.3.0 → roam_code-12.4.0}/pyproject.toml +1 -1
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/detectors.py +11 -0
- roam_code-12.4.0/src/roam/catalog/python_idioms.py +273 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_complexity.py +22 -2
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_context.py +11 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_dead.py +9 -11
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fan.py +7 -15
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_retrieve.py +12 -1
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_search.py +23 -1
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_smells.py +12 -8
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/db/connection.py +3 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/db/schema.py +9 -1
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/indexer.py +5 -2
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/symbols.py +6 -1
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/base.py +4 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/python_lang.py +39 -1
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_server.py +9 -0
- roam_code-12.4.0/src/roam/output/file_role_hints.py +102 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/seeds.py +19 -0
- {roam_code-12.3.0 → roam_code-12.4.0/src/roam_code.egg-info}/PKG-INFO +1 -1
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/SOURCES.txt +3 -0
- roam_code-12.4.0/tests/test_python_pivot.py +199 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/LICENSE +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/README.md +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/setup.cfg +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/__main__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/analysis/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/analysis/effects.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/analysis/taint.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/api.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/ask/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/ask/classifier.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/ask/recipes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/ask/runner.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/attest/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/attest/cga.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/base.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_config.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_django.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_protobuf.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_rest_api.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_salesforce.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_template.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/registry.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/fixes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/smells.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/tasks.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/cli.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/changed_files.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_adrs.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_adversarial.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_affected.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_affected_tests.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_agent_context.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_agent_export.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_agent_plan.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ai_ratio.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ai_readiness.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_alerts.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_annotate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_api_changes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_api_drift.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ask.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_attest.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_auth_gaps.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_bisect.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_breaking.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_budget.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_bus_factor.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_capsule.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_cga.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_check_rules.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ci_setup.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_clean.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_clones.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_closure.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_clusters.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_codeowners.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_config.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_congestion.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_conventions.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_coupling.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_coverage_gaps.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_critique.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_cut.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_dark_matter.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_dashboard.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_debt.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_deps.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_describe.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_dev_profile.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_diagnose.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_diff.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_doc_staleness.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_docs_coverage.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_doctor.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_drift.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_duplicates.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_effects.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_endpoints.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_entry_points.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_eval_retrieve.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_file.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fingerprint.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fitness.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_flag_dead.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fleet.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fn_coupling.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_forecast.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_grep.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_guard.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_health.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_hooks.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_hotspots.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_impact.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_index.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_index_bundle.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ingest_trace.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_init.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_intent.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_invariants.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_layers.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_map.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_math.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_mcp_setup.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_metrics.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_migration_safety.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_minimap.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_missing_index.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_module.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_mutate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_n1.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_oracle.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_orchestrate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_orphan_routes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_over_fetch.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_owner.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_partition.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_path_coverage.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_patterns.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_plan.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_plan_refactor.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_pr_diff.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_pr_risk.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_preflight.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_relate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_report.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_reset.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_risk.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_rules.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_safe_delete.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_safe_zones.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_sbom.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_schema.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_search_semantic.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_secrets.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_semantic_diff.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_simulate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_simulate_departure.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_sketch.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_spectral.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_split.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_supply_chain.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_symbol.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_syntax_check.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_taint.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_test_gaps.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_test_scaffold.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_testmap.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_tour.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_trace.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_trends.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_triage.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_understand.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_uses.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_verify.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_verify_imports.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_vibe_check.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_visualize.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_vuln_map.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_vuln_reach.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_vulns.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_watch.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_weather.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_why.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ws.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_xlang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/codeowners_helpers.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/context_helpers.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/gate_presets.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/graph_helpers.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/metrics_history.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/next_steps.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/resolve.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/suppression.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/competitor_site_data.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/config.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/coverage_reports.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/critique/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/critique/aggregator.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/critique/checks.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/db/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/db/queries.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/eval/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/eval/harness.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/exit_codes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/fleet/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/fleet/adapters.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/fleet/manifest.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/git_utils.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/anomaly.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/builder.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/clone_detect.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/clusters.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/cycles.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/dark_matter.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/diff.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/fingerprint.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/layers.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/pagerank.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/partition.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/pathfinding.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/propagation.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/simulate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/spectral.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/stats.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/complexity.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/discovery.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/django_post.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/file_roles.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/git_stats.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/gitignore.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/incremental.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/parser.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/relations.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/test_conventions.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/apex_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/aura_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/c_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/csharp_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/extractor_schema.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/foxpro_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/generic_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/go_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/hcl_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/java_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/javascript_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/kotlin_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/php_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/query_engine.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/registry.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/ruby_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/rust_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/scala_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/sfxml_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/sql_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/swift_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/typescript_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/visualforce_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/yaml_lang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/completions.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/progress.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/sampling.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/session.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/watcher.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/formatter.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/mermaid.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/sarif.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/schema_registry.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/plugins.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/refactor/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/refactor/codegen.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/refactor/transforms.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/learned_ranker.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/pipeline.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/rerank.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/semantic.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/ast_match.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/builtin.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/dataflow.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/engine.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/daemon.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/graph_backend.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/hotspots.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/lock_modes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/lockmgr.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/trace_ingest.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/framework_packs.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/index_embeddings.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/onnx_embeddings.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/tfidf.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/aibom_extension.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/taint_classifier.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/taint_engine.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/vuln_reach.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/vuln_store.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/surface_counts.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/templates/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/templates/ci/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/__init__.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/aggregator.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/api_scanner.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/config.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/db.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/dependency_links.txt +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/entry_points.txt +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/requires.txt +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/top_level.txt +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_adrs.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_adversarial.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_affected.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_agent_export.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_agent_mode.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_agent_plan_context.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ai_ratio.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ai_readiness.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_alerts_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_annotations.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_anomaly.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_api_changes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_api_drift.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ask.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_attest.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_auth_gaps.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_backend_fixes_round2.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_backend_fixes_round3.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_basic.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_batch_mcp.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bisect.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bridge_django.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bridges.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bridges_extended.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_budget.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_budget_flag.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_budget_phase2.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bus_factor.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_capsule.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_cga.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_check_rules.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ci_gate_eval.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ci_sarif_guard.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ci_setup.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_clones.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_closure.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_codeowners.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_architecture.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_exploration.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_health.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_refactoring.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_workflow.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_competitor_site_data.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_comprehensive.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_config.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_congestion.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_context_propagation.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_conventions_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_coverage_gaps_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_coverage_ingestion.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_critique.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_cut.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dark_matter.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dark_matter_helpers.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dashboard.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dataflow_dead.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dead_aging.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_defer_loading.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_demo_gif_asset.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_describe.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_detail_flag_hints.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_deterministic_output.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dev_profile.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_difficulty_scoring.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_doc_staleness.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_docker_assets.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_docs_coverage.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_docs_site_quality.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_doctor.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_drift.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_duplicates.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_effects.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_effects_propagation.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_endpoints.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_entry_points_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_eval_retrieve.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_exclude_patterns.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_exit_codes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_extractor_grammar_drift.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fallback_contracts.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_file_roles.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fingerprint.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fixes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_flag_dead.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fleet.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fn_coupling.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_forecast.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_formatters.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_foxpro.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_framework_detection.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_gate_presets.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_git_utils.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_guard.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_health_gate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_hooks.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_hotspots.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_index.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_index_bundle.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_init_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_install_check.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_intent.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_invariants.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_json_contracts.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_json_error_envelope.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_kotlin_swift_extractors.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_language_corpus.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_languages.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_library_api.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_math.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_math_tips.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mcp_extras.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mcp_server.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mcp_setup.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mermaid.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_metrics_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_migration_safety.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_minimap.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_missing_index.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mutate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_n1.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_next_steps.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_onboard.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_oracle.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_orchestrate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_orphan_routes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_oss_bench_harness.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_over_fetch.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_pagerank_truncation.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_partition.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_path_coverage.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_patterns_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_performance.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_personalized_pagerank.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_plan.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_plugin_discovery.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_pr_comment_script.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_pr_diff.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_pr_risk_author.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_progress.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_progressive_disclosure.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_properties.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_python_extractor_v2.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_readme_surface_consistency.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_refactoring_intelligence.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_relate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_report.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_reset_clean.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_resolve.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_retrieve.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_retrieve_cross_repo.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_retrieve_seeds.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_risk.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ruby.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rule_profiles.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules_ast_match.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules_community_pack.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules_dataflow.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules_symbol_requirements.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_runtime.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_runtime_score.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_salesforce.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sarif_flag.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sbom.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_scala.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_schema_versioning.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_search_explain.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_secrets.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_secrets_v2.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_semantic_diff.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_semantic_onnx.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_semantic_search.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_simulate.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_simulate_departure.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sketch.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_smells.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_smoke.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sna_metrics.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_spectral.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_split_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sql.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_suggest_reviewers.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_supply_chain.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_surface_counts.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_syntax_check.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_taint.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_taint_analysis.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_taint_classifier.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_test_conventions.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_test_gaps.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_test_scaffold.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_testmap.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_top_flag_consistency.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_tour_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_trends.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_trends_cohort.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_triage.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_uses_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v12_2.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v6_features.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v71_features.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v7_features.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v82_features.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_verify.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_verify_imports.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_vibe_check.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_visualize.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_vuln.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_vulns_cmd.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_watch.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_why.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_workspace.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ws_resolve_fixes.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_xlang.py +0 -0
- {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_yaml_hcl.py +0 -0
|
@@ -2300,6 +2300,17 @@ def _iter_registered_detectors():
|
|
|
2300
2300
|
for det in _MATH_DETECTORS:
|
|
2301
2301
|
yield det
|
|
2302
2302
|
|
|
2303
|
+
# Python pivot v12.4 — language-specific idiom detectors. Wrapped
|
|
2304
|
+
# in try/except so a regex bug in one detector can't block the
|
|
2305
|
+
# algorithm pass.
|
|
2306
|
+
try:
|
|
2307
|
+
from roam.catalog.python_idioms import PYTHON_IDIOM_DETECTORS
|
|
2308
|
+
|
|
2309
|
+
for det in PYTHON_IDIOM_DETECTORS:
|
|
2310
|
+
yield det
|
|
2311
|
+
except Exception:
|
|
2312
|
+
pass
|
|
2313
|
+
|
|
2303
2314
|
try:
|
|
2304
2315
|
from roam.plugins import get_plugin_detectors
|
|
2305
2316
|
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Python-specific anti-pattern detectors.
|
|
2
|
+
|
|
3
|
+
Surfaced in v12.4 from the Python-pivot dogfood (2026-05-02). The
|
|
4
|
+
existing ``catalog/detectors.py`` covers language-agnostic algorithm
|
|
5
|
+
patterns (O(n²) string concat, sort-to-take, IO-in-loop). This module
|
|
6
|
+
adds Python-canonical anti-patterns that don't generalise to other
|
|
7
|
+
languages and would muddy the detector registry there.
|
|
8
|
+
|
|
9
|
+
Each detector returns a list of ``(symbol_id, pattern_id, severity,
|
|
10
|
+
description, fix_hint)`` tuples — same shape as the algorithm
|
|
11
|
+
detectors so they plug into the same ``roam math`` / ``roam smells``
|
|
12
|
+
plumbing.
|
|
13
|
+
|
|
14
|
+
Initial detectors (most-cited Python footguns):
|
|
15
|
+
|
|
16
|
+
1. **Mutable default argument** — ``def foo(x=[])`` / ``def bar(d={})``.
|
|
17
|
+
The list/dict is created *once* at definition time and shared
|
|
18
|
+
across calls. Classic source of "why does my list keep growing?".
|
|
19
|
+
2. **Bare except** — ``except:`` with no exception type. Catches
|
|
20
|
+
``SystemExit`` and ``KeyboardInterrupt``, masking critical
|
|
21
|
+
shutdown signals. PEP 8 explicitly discourages this.
|
|
22
|
+
3. **Comparison to None with ``==``** — should use ``is None``.
|
|
23
|
+
Not just style: ``__eq__`` overrides can do anything.
|
|
24
|
+
4. **f-string in logger calls** — ``logger.info(f"x={x}")`` evaluates
|
|
25
|
+
the format string even if the log level is below INFO. Use
|
|
26
|
+
``logger.info("x=%s", x)`` for lazy evaluation.
|
|
27
|
+
|
|
28
|
+
Detectors are line-anchored regex over the source text rather than
|
|
29
|
+
AST queries because Python's tree-sitter grammar exposes default
|
|
30
|
+
argument values as a deeply-nested chain that's brittle to query.
|
|
31
|
+
The regex approach is "good enough" for these specific patterns and
|
|
32
|
+
ports trivially to YAML/Jupyter notebooks if we extend later.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import re
|
|
38
|
+
import sqlite3
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Pattern regexes (compiled once)
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
# def foo(x=[]) — also matches dict, set literal, function calls like list()
|
|
45
|
+
# Skips ``=None`` and immutable literals (``=0``, ``=""``, ``=()``).
|
|
46
|
+
_MUTABLE_DEFAULT_RE = re.compile(
|
|
47
|
+
r"def\s+\w+\s*\([^)]*?(\w+)\s*=\s*"
|
|
48
|
+
r"(\[\s*\]|\{\s*\}|\{\s*[^}:]+\s*\}|list\(\s*\)|dict\(\s*\)|set\(\s*\))",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# bare except — matches ``except:`` with optional whitespace,
|
|
52
|
+
# but NOT ``except SomeError:`` or ``except (A, B):``.
|
|
53
|
+
_BARE_EXCEPT_RE = re.compile(r"^\s*except\s*:", re.MULTILINE)
|
|
54
|
+
|
|
55
|
+
# == None or != None at end-of-expression positions. The leading
|
|
56
|
+
# ``\b`` doesn't apply (operators aren't word chars); we anchor on
|
|
57
|
+
# the comparison operator and require ``None`` followed by a non-word
|
|
58
|
+
# character to avoid matching ``Nonetype`` etc.
|
|
59
|
+
_NONE_EQ_RE = re.compile(r"(==|!=)\s*None\b")
|
|
60
|
+
|
|
61
|
+
# logger.<level>(f"..."): logger / log / logging variants
|
|
62
|
+
_LOGGER_FSTRING_RE = re.compile(
|
|
63
|
+
r"\b(?:logger|log|logging|self\.logger|self\.log)\.(?:debug|info|warning|warn|error|critical|exception)\s*\(\s*f[\"']",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _file_text(conn: sqlite3.Connection, file_id: int) -> str | None:
|
|
68
|
+
"""Read the source text of a file via roam.index — but the index
|
|
69
|
+
doesn't store source. Instead we read from disk via the file path.
|
|
70
|
+
Fast (mmap) and safe to no-op on read errors.
|
|
71
|
+
"""
|
|
72
|
+
row = conn.execute("SELECT path FROM files WHERE id = ?", (file_id,)).fetchone()
|
|
73
|
+
if row is None:
|
|
74
|
+
return None
|
|
75
|
+
path = row[0]
|
|
76
|
+
try:
|
|
77
|
+
with open(path, encoding="utf-8", errors="replace") as f:
|
|
78
|
+
return f.read()
|
|
79
|
+
except OSError:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _python_files(conn: sqlite3.Connection) -> list[tuple[int, str]]:
|
|
84
|
+
"""Return ``(file_id, path)`` for every Python file in the index."""
|
|
85
|
+
return [(int(r[0]), r[1]) for r in conn.execute("SELECT id, path FROM files WHERE language = 'python'").fetchall()]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _line_to_symbol(conn: sqlite3.Connection, file_id: int) -> list[tuple[int, int, int, str]]:
|
|
89
|
+
"""``(symbol_id, line_start, line_end, name)`` for symbols in
|
|
90
|
+
``file_id`` ordered by line. Used to attribute regex matches to
|
|
91
|
+
the enclosing symbol."""
|
|
92
|
+
return [
|
|
93
|
+
(int(r[0]), int(r[1] or 0), int(r[2] or 0), r[3])
|
|
94
|
+
for r in conn.execute(
|
|
95
|
+
"SELECT id, line_start, line_end, name FROM symbols WHERE file_id = ? ORDER BY line_start",
|
|
96
|
+
(file_id,),
|
|
97
|
+
).fetchall()
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _enclosing_symbol(line_no: int, sym_index: list[tuple[int, int, int, str]]) -> tuple[int, str] | None:
|
|
102
|
+
"""Return the innermost ``(symbol_id, name)`` whose line range
|
|
103
|
+
contains ``line_no``, or ``None``. Innermost = max line_start."""
|
|
104
|
+
best: tuple[int, str] | None = None
|
|
105
|
+
best_start = -1
|
|
106
|
+
for sid, start, end, name in sym_index:
|
|
107
|
+
if start <= line_no <= end and start > best_start:
|
|
108
|
+
best = (sid, name)
|
|
109
|
+
best_start = start
|
|
110
|
+
return best
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# Detectors
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _idiom_finding(
|
|
119
|
+
*,
|
|
120
|
+
task_id: str,
|
|
121
|
+
detected_way: str,
|
|
122
|
+
symbol_id: int,
|
|
123
|
+
symbol_name: str,
|
|
124
|
+
file_path: str,
|
|
125
|
+
line_no: int,
|
|
126
|
+
reason: str,
|
|
127
|
+
confidence: str = "high",
|
|
128
|
+
fix: str | None = None,
|
|
129
|
+
) -> dict:
|
|
130
|
+
"""Build a finding dict in the shape ``catalog.detectors._finding``
|
|
131
|
+
produces, so the same downstream calibration / display works."""
|
|
132
|
+
return {
|
|
133
|
+
"task_id": task_id,
|
|
134
|
+
"detected_way": detected_way,
|
|
135
|
+
"suggested_way": detected_way,
|
|
136
|
+
"symbol_id": symbol_id,
|
|
137
|
+
"symbol_name": symbol_name,
|
|
138
|
+
"kind": "function",
|
|
139
|
+
"location": f"{file_path}:{line_no}",
|
|
140
|
+
"confidence": confidence,
|
|
141
|
+
"reason": reason,
|
|
142
|
+
"fix": fix or "",
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def detect_mutable_default_arg(conn: sqlite3.Connection) -> list[dict]:
|
|
147
|
+
"""Find ``def foo(x=[])`` / ``def foo(d={})`` patterns."""
|
|
148
|
+
findings: list[dict] = []
|
|
149
|
+
for file_id, path in _python_files(conn):
|
|
150
|
+
text = _file_text(conn, file_id)
|
|
151
|
+
if not text:
|
|
152
|
+
continue
|
|
153
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
154
|
+
for match in _MUTABLE_DEFAULT_RE.finditer(text):
|
|
155
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
156
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
157
|
+
if sym is None:
|
|
158
|
+
continue
|
|
159
|
+
param = match.group(1)
|
|
160
|
+
findings.append(
|
|
161
|
+
_idiom_finding(
|
|
162
|
+
task_id="py-mutable-default-arg",
|
|
163
|
+
detected_way="default-mutable",
|
|
164
|
+
symbol_id=sym[0],
|
|
165
|
+
symbol_name=sym[1],
|
|
166
|
+
file_path=path,
|
|
167
|
+
line_no=line_no,
|
|
168
|
+
reason=(f"Mutable default arg: ``{param}={match.group(2)}`` is shared across calls"),
|
|
169
|
+
confidence="high",
|
|
170
|
+
fix=f"def fn({param}=None): ...; if {param} is None: {param} = [] / {{}} / set()",
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
return findings
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def detect_bare_except(conn: sqlite3.Connection) -> list[dict]:
|
|
177
|
+
"""Find ``except:`` (no exception type)."""
|
|
178
|
+
findings: list[dict] = []
|
|
179
|
+
for file_id, path in _python_files(conn):
|
|
180
|
+
text = _file_text(conn, file_id)
|
|
181
|
+
if not text:
|
|
182
|
+
continue
|
|
183
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
184
|
+
for match in _BARE_EXCEPT_RE.finditer(text):
|
|
185
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
186
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
187
|
+
if sym is None:
|
|
188
|
+
continue
|
|
189
|
+
findings.append(
|
|
190
|
+
_idiom_finding(
|
|
191
|
+
task_id="py-bare-except",
|
|
192
|
+
detected_way="catch-all",
|
|
193
|
+
symbol_id=sym[0],
|
|
194
|
+
symbol_name=sym[1],
|
|
195
|
+
file_path=path,
|
|
196
|
+
line_no=line_no,
|
|
197
|
+
reason="bare ``except:`` catches SystemExit/KeyboardInterrupt — shutdown signals masked",
|
|
198
|
+
confidence="high",
|
|
199
|
+
fix="except Exception: # or the specific class you mean to handle",
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
return findings
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def detect_none_eq(conn: sqlite3.Connection) -> list[dict]:
|
|
206
|
+
"""Find ``x == None`` / ``x != None``."""
|
|
207
|
+
findings: list[dict] = []
|
|
208
|
+
for file_id, path in _python_files(conn):
|
|
209
|
+
text = _file_text(conn, file_id)
|
|
210
|
+
if not text:
|
|
211
|
+
continue
|
|
212
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
213
|
+
for match in _NONE_EQ_RE.finditer(text):
|
|
214
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
215
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
216
|
+
if sym is None:
|
|
217
|
+
continue
|
|
218
|
+
op = match.group(1)
|
|
219
|
+
replacement = "is" if op == "==" else "is not"
|
|
220
|
+
findings.append(
|
|
221
|
+
_idiom_finding(
|
|
222
|
+
task_id="py-none-eq",
|
|
223
|
+
detected_way="eq-not-is",
|
|
224
|
+
symbol_id=sym[0],
|
|
225
|
+
symbol_name=sym[1],
|
|
226
|
+
file_path=path,
|
|
227
|
+
line_no=line_no,
|
|
228
|
+
reason=f"``{op} None`` invokes ``__eq__``; use ``{replacement} None`` (idiomatic, faster)",
|
|
229
|
+
confidence="medium",
|
|
230
|
+
fix=f"x {replacement} None",
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
return findings
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def detect_logger_fstring(conn: sqlite3.Connection) -> list[dict]:
|
|
237
|
+
"""Find ``logger.info(f"...")`` — eager-format anti-pattern."""
|
|
238
|
+
findings: list[dict] = []
|
|
239
|
+
for file_id, path in _python_files(conn):
|
|
240
|
+
text = _file_text(conn, file_id)
|
|
241
|
+
if not text:
|
|
242
|
+
continue
|
|
243
|
+
sym_index = _line_to_symbol(conn, file_id)
|
|
244
|
+
for match in _LOGGER_FSTRING_RE.finditer(text):
|
|
245
|
+
line_no = text.count("\n", 0, match.start()) + 1
|
|
246
|
+
sym = _enclosing_symbol(line_no, sym_index)
|
|
247
|
+
if sym is None:
|
|
248
|
+
continue
|
|
249
|
+
findings.append(
|
|
250
|
+
_idiom_finding(
|
|
251
|
+
task_id="py-logger-fstring",
|
|
252
|
+
detected_way="eager-format",
|
|
253
|
+
symbol_id=sym[0],
|
|
254
|
+
symbol_name=sym[1],
|
|
255
|
+
file_path=path,
|
|
256
|
+
line_no=line_no,
|
|
257
|
+
reason="f-string in logger call evaluates even when level discards the message",
|
|
258
|
+
confidence="high",
|
|
259
|
+
fix='logger.info("x=%s", x)',
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
return findings
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# Detector registry — same shape ``cmd_math`` expects from ``_MATH_DETECTORS``
|
|
266
|
+
# (task_id, pattern_id, detect_fn). Re-exported so registration is one
|
|
267
|
+
# import line elsewhere.
|
|
268
|
+
PYTHON_IDIOM_DETECTORS = [
|
|
269
|
+
("py-mutable-default-arg", "default-mutable", detect_mutable_default_arg),
|
|
270
|
+
("py-bare-except", "catch-all", detect_bare_except),
|
|
271
|
+
("py-none-eq", "eq-not-is", detect_none_eq),
|
|
272
|
+
("py-logger-fstring", "eager-format", detect_logger_fstring),
|
|
273
|
+
]
|
|
@@ -67,8 +67,19 @@ def _severity_icon(sev: str) -> str:
|
|
|
67
67
|
is_flag=True,
|
|
68
68
|
help="Detect bumpy-road pattern: files with multiple medium-complexity functions",
|
|
69
69
|
)
|
|
70
|
+
@click.option(
|
|
71
|
+
"--include-tooling",
|
|
72
|
+
is_flag=True,
|
|
73
|
+
default=False,
|
|
74
|
+
help=(
|
|
75
|
+
"Include CI scripts, examples, generated code, vendor, and "
|
|
76
|
+
"workspaces directories. Excluded by default — high complexity "
|
|
77
|
+
"in tooling/codegen is expected and uninteresting (Python pivot "
|
|
78
|
+
"dogfood 2026-05-02 found agent-generated workspaces dominating)."
|
|
79
|
+
),
|
|
80
|
+
)
|
|
70
81
|
@click.pass_context
|
|
71
|
-
def complexity(ctx, target, limit, threshold, by_file, bumpy_road):
|
|
82
|
+
def complexity(ctx, target, limit, threshold, by_file, bumpy_road, include_tooling):
|
|
72
83
|
"""Show cognitive complexity metrics for functions and methods.
|
|
73
84
|
|
|
74
85
|
Unlike ``health`` (which scores the whole codebase) and ``debt`` (which
|
|
@@ -117,6 +128,10 @@ def complexity(ctx, target, limit, threshold, by_file, bumpy_road):
|
|
|
117
128
|
|
|
118
129
|
where_clause = " AND ".join(where_parts) if where_parts else "1=1"
|
|
119
130
|
|
|
131
|
+
# Pull more rows than ``limit`` when default-excluding tooling
|
|
132
|
+
# so the displayed top-N still has the requested count after
|
|
133
|
+
# filtering. 5x is comfortable for typical exclusion shares.
|
|
134
|
+
fetch_limit = limit * 5 if not include_tooling else limit
|
|
120
135
|
rows = conn.execute(
|
|
121
136
|
f"""SELECT sm.*, s.name, s.qualified_name, s.kind,
|
|
122
137
|
s.line_start, s.line_end, f.path as file_path
|
|
@@ -126,8 +141,13 @@ def complexity(ctx, target, limit, threshold, by_file, bumpy_road):
|
|
|
126
141
|
WHERE {where_clause}
|
|
127
142
|
ORDER BY sm.cognitive_complexity DESC
|
|
128
143
|
LIMIT ?""",
|
|
129
|
-
params + [
|
|
144
|
+
params + [fetch_limit],
|
|
130
145
|
).fetchall()
|
|
146
|
+
if not include_tooling:
|
|
147
|
+
from roam.output.file_role_hints import is_excluded_path
|
|
148
|
+
|
|
149
|
+
rows = [r for r in rows if not is_excluded_path(r["file_path"] or "")]
|
|
150
|
+
rows = rows[:limit]
|
|
131
151
|
|
|
132
152
|
if not rows:
|
|
133
153
|
if sarif_mode:
|
|
@@ -523,6 +523,17 @@ def _render_single_text(data):
|
|
|
523
523
|
click.echo(f"VERDICT: {verdict}")
|
|
524
524
|
click.echo()
|
|
525
525
|
click.echo(f"=== Context for: {sym['name']}{task_suffix} ===")
|
|
526
|
+
# Python pivot v12.4: surface async + decorators above the
|
|
527
|
+
# signature so agents reading context know coroutine semantics
|
|
528
|
+
# without scanning source. ``sym`` is a sqlite3.Row which doesn't
|
|
529
|
+
# expose ``.get`` — guard each access with a key check.
|
|
530
|
+
_row_keys = sym.keys() if hasattr(sym, "keys") else []
|
|
531
|
+
if "is_async" in _row_keys and sym["is_async"]:
|
|
532
|
+
click.echo(" [async coroutine]")
|
|
533
|
+
decorators_str = (sym["decorators"] if "decorators" in _row_keys else "") or ""
|
|
534
|
+
if decorators_str:
|
|
535
|
+
for d in decorators_str.split(",")[:5]:
|
|
536
|
+
click.echo(f" {d.strip()}")
|
|
526
537
|
click.echo(
|
|
527
538
|
f"{abbrev_kind(sym['kind'])} "
|
|
528
539
|
f"{sym['qualified_name'] or sym['name']}"
|
|
@@ -358,17 +358,7 @@ def _group_dead(dead_items, by):
|
|
|
358
358
|
# ---------------------------------------------------------------------------
|
|
359
359
|
|
|
360
360
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def _is_tooling_path(path: str) -> bool:
|
|
365
|
-
"""Same hint set as cmd_fan / cmd_smells. Excluded from dead-code
|
|
366
|
-
headline by default (dogfood notes 2026-05-01) — CI scripts are
|
|
367
|
-
expected to have unreferenced helper functions."""
|
|
368
|
-
if not path:
|
|
369
|
-
return False
|
|
370
|
-
p = "/" + path.replace("\\", "/")
|
|
371
|
-
return any(hint.replace("\\", "/") in p for hint in _TOOLING_PATH_HINTS)
|
|
361
|
+
from roam.output.file_role_hints import is_excluded_path as _is_tooling_path
|
|
372
362
|
|
|
373
363
|
|
|
374
364
|
def _analyze_dead(conn):
|
|
@@ -1114,6 +1104,7 @@ def dead(
|
|
|
1114
1104
|
return
|
|
1115
1105
|
if json_mode:
|
|
1116
1106
|
summary = {
|
|
1107
|
+
"verdict": "no dead exports",
|
|
1117
1108
|
"safe": 0,
|
|
1118
1109
|
"review": 0,
|
|
1119
1110
|
"intentional": 0,
|
|
@@ -1281,7 +1272,14 @@ def dead(
|
|
|
1281
1272
|
d["decay_score"] = ext["decay_score"]
|
|
1282
1273
|
return d
|
|
1283
1274
|
|
|
1275
|
+
total = n_safe + n_review + n_intent
|
|
1276
|
+
verdict = (
|
|
1277
|
+
f"{total} dead export(s): {n_safe} safe, {n_review} review, {n_intent} intentional"
|
|
1278
|
+
if total
|
|
1279
|
+
else "no dead exports"
|
|
1280
|
+
)
|
|
1284
1281
|
summary = {
|
|
1282
|
+
"verdict": verdict,
|
|
1285
1283
|
"safe": n_safe,
|
|
1286
1284
|
"review": n_review,
|
|
1287
1285
|
"intentional": n_intent,
|
|
@@ -107,25 +107,17 @@ _FRAMEWORK_NAMES = frozenset(
|
|
|
107
107
|
)
|
|
108
108
|
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
from roam.output.file_role_hints import is_excluded_path
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
def
|
|
114
|
-
"""
|
|
113
|
+
def _filter_tooling_rows(rows):
|
|
114
|
+
"""Filter out rows whose ``file_path`` is in a default-excluded
|
|
115
|
+
location (tooling, generated, examples, vendor, workspaces, etc.).
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Matches the hint set used by ``cmd_smells._file_role_lookup``.
|
|
117
|
+
Uses the shared ``output.file_role_hints`` set so all headline
|
|
118
|
+
commands stay in sync.
|
|
119
119
|
"""
|
|
120
|
-
if not
|
|
121
|
-
return False
|
|
122
|
-
p = "/" + path.replace("\\", "/")
|
|
123
|
-
return any(hint.replace("\\", "/") in p for hint in _TOOLING_PATH_HINTS)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def _filter_tooling_rows(rows):
|
|
127
|
-
"""Filter out rows whose ``file_path`` is in a tooling location."""
|
|
128
|
-
return [r for r in rows if not _is_tooling_path(r["file_path"] or "")]
|
|
120
|
+
return [r for r in rows if not is_excluded_path(r["file_path"] or "")]
|
|
129
121
|
|
|
130
122
|
|
|
131
123
|
@click.command()
|
|
@@ -49,8 +49,19 @@ def _retrieve_confidence(candidates: list[dict], task: str = "") -> str:
|
|
|
49
49
|
if not scores:
|
|
50
50
|
return "ok"
|
|
51
51
|
|
|
52
|
-
#
|
|
52
|
+
# High-confidence override (dogfood R14 2026-05-01): a top-1 that
|
|
53
|
+
# significantly outranks the 2nd hit (gap ≥ 0.30 in normalised
|
|
54
|
+
# space) signals a unique winner — the structural reranker found
|
|
55
|
+
# one strong answer rather than many equal candidates. Skip the
|
|
56
|
+
# token-coverage check in that case.
|
|
57
|
+
# Distinguishes the email query ("where is email sending" →
|
|
58
|
+
# send_welcome at 0.900, 2nd at 0.275 → gap 0.625, real answer)
|
|
59
|
+
# from the trace query ("trace the login flow" → 1.014, 2nd 0.942
|
|
60
|
+
# → gap 0.072, all matching one common word).
|
|
53
61
|
top = scores[0]
|
|
62
|
+
second = scores[1] if len(scores) > 1 else 0.0
|
|
63
|
+
if top - second >= 0.30:
|
|
64
|
+
return "ok"
|
|
54
65
|
fifth = scores[min(4, len(scores) - 1)]
|
|
55
66
|
if top < 0.30 and (top - fifth) < 0.10:
|
|
56
67
|
return "low"
|
|
@@ -160,9 +160,25 @@ def _format_explanation_text(expl: dict) -> list[str]:
|
|
|
160
160
|
default=None,
|
|
161
161
|
help="Filter by symbol kind (fn, cls, meth, var, iface, etc.)",
|
|
162
162
|
)
|
|
163
|
+
@click.option(
|
|
164
|
+
"--async",
|
|
165
|
+
"async_only",
|
|
166
|
+
is_flag=True,
|
|
167
|
+
help="Show only async functions/methods (requires Python pivot v12.4 schema).",
|
|
168
|
+
)
|
|
169
|
+
@click.option(
|
|
170
|
+
"--decorator",
|
|
171
|
+
"decorator_filter",
|
|
172
|
+
default=None,
|
|
173
|
+
help=(
|
|
174
|
+
"Filter to symbols carrying a decorator matching this substring "
|
|
175
|
+
"(case-insensitive). E.g. ``--decorator pytest.fixture`` finds all "
|
|
176
|
+
"fixtures. ``--decorator app.route`` finds Flask/FastAPI routes."
|
|
177
|
+
),
|
|
178
|
+
)
|
|
163
179
|
@click.option("--explain", is_flag=True, help="Show score breakdown for each result")
|
|
164
180
|
@click.pass_context
|
|
165
|
-
def search(ctx, pattern, full, kind_filter, explain):
|
|
181
|
+
def search(ctx, pattern, full, kind_filter, async_only, decorator_filter, explain):
|
|
166
182
|
"""Find symbols matching a name substring (case-insensitive).
|
|
167
183
|
|
|
168
184
|
Unlike ``grep`` (which searches file contents) and ``search-semantic``
|
|
@@ -180,6 +196,12 @@ def search(ctx, pattern, full, kind_filter, explain):
|
|
|
180
196
|
abbrev_to_kind = {v: k for k, v in KIND_ABBREV.items()}
|
|
181
197
|
full_kind = abbrev_to_kind.get(kind_filter, kind_filter)
|
|
182
198
|
rows = [r for r in rows if r["kind"] == full_kind]
|
|
199
|
+
# Python pivot v12.4 — filter by async/decorator
|
|
200
|
+
if async_only:
|
|
201
|
+
rows = [r for r in rows if (r["is_async"] if "is_async" in r.keys() else 0)]
|
|
202
|
+
if decorator_filter:
|
|
203
|
+
needle = decorator_filter.lower()
|
|
204
|
+
rows = [r for r in rows if needle in (r["decorators"] or "").lower()]
|
|
183
205
|
|
|
184
206
|
if not rows:
|
|
185
207
|
suffix = f" of kind '{kind_filter}'" if kind_filter else ""
|
|
@@ -93,16 +93,20 @@ def smells(ctx, file_path, min_severity, include_tooling):
|
|
|
93
93
|
with open_db(readonly=True) as conn:
|
|
94
94
|
findings = run_all_detectors(conn)
|
|
95
95
|
|
|
96
|
-
# Default: exclude tooling
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
96
|
+
# Default: exclude tooling, generated, examples, vendor, workspaces,
|
|
97
|
+
# docs. Per dogfood notes 2026-05-01 + 2026-05-02 (Python pivot),
|
|
98
|
+
# the top-N critical smells were dominated by paths the user
|
|
99
|
+
# didn't write or doesn't want to refactor (``dev/``,
|
|
100
|
+
# ``.github/scripts/``, ``examples/``, ``workspaces/`` agent
|
|
101
|
+
# artifacts, vendored packages, codegen output). The shared
|
|
102
|
+
# path-hint set lives in ``roam.output.file_role_hints`` so all
|
|
103
|
+
# headline commands stay in sync. ``--include-tooling`` opts
|
|
104
|
+
# back into the full set.
|
|
105
|
+
from roam.output.file_role_hints import is_excluded_path
|
|
106
|
+
|
|
102
107
|
excluded_tooling = 0
|
|
103
108
|
if not include_tooling:
|
|
104
109
|
tooling_roles = {"ci", "scripts", "build", "generated"}
|
|
105
|
-
tooling_path_hints = ("/dev/", "/benchmarks/", "/.github/", "\\dev\\", "\\benchmarks\\", "\\.github\\")
|
|
106
110
|
tooling_roles_per_file = _file_role_lookup(conn)
|
|
107
111
|
kept: list[dict] = []
|
|
108
112
|
for f in findings:
|
|
@@ -112,7 +116,7 @@ def smells(ctx, file_path, min_severity, include_tooling):
|
|
|
112
116
|
if role in tooling_roles:
|
|
113
117
|
excluded_tooling += 1
|
|
114
118
|
continue
|
|
115
|
-
if
|
|
119
|
+
if is_excluded_path(file_path_only):
|
|
116
120
|
excluded_tooling += 1
|
|
117
121
|
continue
|
|
118
122
|
kept.append(f)
|
|
@@ -146,6 +146,9 @@ def ensure_schema(conn: sqlite3.Connection):
|
|
|
146
146
|
|
|
147
147
|
# Migrations for columns added after initial schema
|
|
148
148
|
_safe_alter(conn, "symbols", "default_value", "TEXT")
|
|
149
|
+
# Python pivot v12.4: is_async + decorators on symbols
|
|
150
|
+
_safe_alter(conn, "symbols", "is_async", "INTEGER DEFAULT 0")
|
|
151
|
+
_safe_alter(conn, "symbols", "decorators", "TEXT DEFAULT ''")
|
|
149
152
|
_safe_alter(conn, "file_stats", "health_score", "REAL")
|
|
150
153
|
_safe_alter(conn, "file_stats", "cochange_entropy", "REAL")
|
|
151
154
|
_safe_alter(conn, "file_stats", "cognitive_load", "REAL")
|
|
@@ -24,7 +24,15 @@ CREATE TABLE IF NOT EXISTS symbols (
|
|
|
24
24
|
visibility TEXT DEFAULT 'public',
|
|
25
25
|
is_exported INTEGER DEFAULT 1,
|
|
26
26
|
parent_id INTEGER REFERENCES symbols(id) ON DELETE SET NULL,
|
|
27
|
-
default_value TEXT
|
|
27
|
+
default_value TEXT,
|
|
28
|
+
-- Python pivot v12.4: is_async marks ``async def`` functions/methods.
|
|
29
|
+
-- Lets agents distinguish coroutines from sync calls without
|
|
30
|
+
-- regex'ing the source. Used by retrieve, context, search.
|
|
31
|
+
is_async INTEGER DEFAULT 0,
|
|
32
|
+
-- Decorators applied to the symbol, comma-joined ("@property,@cached").
|
|
33
|
+
-- Empty when none. Captured by the Python extractor; other languages
|
|
34
|
+
-- may populate when the concept maps (TypeScript decorators).
|
|
35
|
+
decorators TEXT DEFAULT ''
|
|
28
36
|
);
|
|
29
37
|
|
|
30
38
|
CREATE TABLE IF NOT EXISTS edges (
|
|
@@ -429,8 +429,9 @@ def _store_symbols(conn, file_id, rel_path, symbols, all_symbol_rows):
|
|
|
429
429
|
"""INSERT INTO symbols
|
|
430
430
|
(file_id, name, qualified_name, kind, signature,
|
|
431
431
|
line_start, line_end, docstring, visibility,
|
|
432
|
-
is_exported, parent_id, default_value
|
|
433
|
-
|
|
432
|
+
is_exported, parent_id, default_value,
|
|
433
|
+
is_async, decorators)
|
|
434
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
434
435
|
(
|
|
435
436
|
file_id,
|
|
436
437
|
sym["name"],
|
|
@@ -444,6 +445,8 @@ def _store_symbols(conn, file_id, rel_path, symbols, all_symbol_rows):
|
|
|
444
445
|
1 if sym["is_exported"] else 0,
|
|
445
446
|
parent_id,
|
|
446
447
|
sym.get("default_value"),
|
|
448
|
+
1 if sym.get("is_async") else 0,
|
|
449
|
+
sym.get("decorators") or "",
|
|
447
450
|
),
|
|
448
451
|
)
|
|
449
452
|
row = conn.execute("SELECT last_insert_rowid()").fetchone()
|
|
@@ -20,7 +20,10 @@ def extract_symbols(tree, source: bytes, file_path: str, extractor) -> list[dict
|
|
|
20
20
|
except Exception:
|
|
21
21
|
return []
|
|
22
22
|
|
|
23
|
-
# Ensure every symbol dict has all required keys with defaults
|
|
23
|
+
# Ensure every symbol dict has all required keys with defaults.
|
|
24
|
+
# Python pivot v12.4 added ``is_async`` and ``decorators`` — they
|
|
25
|
+
# must pass through this normalisation or the indexer never sees
|
|
26
|
+
# them (caught by dogfood notes 2026-05-02 R3).
|
|
24
27
|
normalised = []
|
|
25
28
|
for sym in symbols:
|
|
26
29
|
normalised.append(
|
|
@@ -36,6 +39,8 @@ def extract_symbols(tree, source: bytes, file_path: str, extractor) -> list[dict
|
|
|
36
39
|
"is_exported": sym.get("is_exported", True),
|
|
37
40
|
"parent_name": sym.get("parent_name"),
|
|
38
41
|
"default_value": sym.get("default_value"),
|
|
42
|
+
"is_async": bool(sym.get("is_async", False)),
|
|
43
|
+
"decorators": sym.get("decorators") or "",
|
|
39
44
|
}
|
|
40
45
|
)
|
|
41
46
|
return normalised
|
|
@@ -74,6 +74,8 @@ class LanguageExtractor(ABC):
|
|
|
74
74
|
is_exported: bool = False,
|
|
75
75
|
parent_name: str | None = None,
|
|
76
76
|
default_value: str | None = None,
|
|
77
|
+
is_async: bool = False,
|
|
78
|
+
decorators: str = "",
|
|
77
79
|
) -> dict:
|
|
78
80
|
return {
|
|
79
81
|
"name": name,
|
|
@@ -87,6 +89,8 @@ class LanguageExtractor(ABC):
|
|
|
87
89
|
"is_exported": is_exported,
|
|
88
90
|
"parent_name": parent_name,
|
|
89
91
|
"default_value": default_value,
|
|
92
|
+
"is_async": bool(is_async),
|
|
93
|
+
"decorators": decorators or "",
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
def _make_reference(
|