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.
Files changed (540) hide show
  1. {roam_code-12.3.0/src/roam_code.egg-info → roam_code-12.4.0}/PKG-INFO +1 -1
  2. {roam_code-12.3.0 → roam_code-12.4.0}/pyproject.toml +1 -1
  3. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/detectors.py +11 -0
  4. roam_code-12.4.0/src/roam/catalog/python_idioms.py +273 -0
  5. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_complexity.py +22 -2
  6. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_context.py +11 -0
  7. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_dead.py +9 -11
  8. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fan.py +7 -15
  9. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_retrieve.py +12 -1
  10. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_search.py +23 -1
  11. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_smells.py +12 -8
  12. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/db/connection.py +3 -0
  13. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/db/schema.py +9 -1
  14. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/indexer.py +5 -2
  15. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/symbols.py +6 -1
  16. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/base.py +4 -0
  17. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/python_lang.py +39 -1
  18. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_server.py +9 -0
  19. roam_code-12.4.0/src/roam/output/file_role_hints.py +102 -0
  20. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/seeds.py +19 -0
  21. {roam_code-12.3.0 → roam_code-12.4.0/src/roam_code.egg-info}/PKG-INFO +1 -1
  22. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/SOURCES.txt +3 -0
  23. roam_code-12.4.0/tests/test_python_pivot.py +199 -0
  24. {roam_code-12.3.0 → roam_code-12.4.0}/LICENSE +0 -0
  25. {roam_code-12.3.0 → roam_code-12.4.0}/README.md +0 -0
  26. {roam_code-12.3.0 → roam_code-12.4.0}/setup.cfg +0 -0
  27. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/__init__.py +0 -0
  28. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/__main__.py +0 -0
  29. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/analysis/__init__.py +0 -0
  30. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/analysis/effects.py +0 -0
  31. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/analysis/taint.py +0 -0
  32. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/api.py +0 -0
  33. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/ask/__init__.py +0 -0
  34. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/ask/classifier.py +0 -0
  35. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/ask/recipes.py +0 -0
  36. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/ask/runner.py +0 -0
  37. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/attest/__init__.py +0 -0
  38. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/attest/cga.py +0 -0
  39. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/__init__.py +0 -0
  40. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/base.py +0 -0
  41. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_config.py +0 -0
  42. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_django.py +0 -0
  43. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_protobuf.py +0 -0
  44. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_rest_api.py +0 -0
  45. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_salesforce.py +0 -0
  46. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/bridge_template.py +0 -0
  47. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/bridges/registry.py +0 -0
  48. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/__init__.py +0 -0
  49. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/fixes.py +0 -0
  50. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/smells.py +0 -0
  51. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/catalog/tasks.py +0 -0
  52. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/cli.py +0 -0
  53. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/__init__.py +0 -0
  54. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/changed_files.py +0 -0
  55. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_adrs.py +0 -0
  56. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_adversarial.py +0 -0
  57. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_affected.py +0 -0
  58. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_affected_tests.py +0 -0
  59. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_agent_context.py +0 -0
  60. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_agent_export.py +0 -0
  61. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_agent_plan.py +0 -0
  62. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ai_ratio.py +0 -0
  63. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ai_readiness.py +0 -0
  64. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_alerts.py +0 -0
  65. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_annotate.py +0 -0
  66. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_api_changes.py +0 -0
  67. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_api_drift.py +0 -0
  68. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ask.py +0 -0
  69. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_attest.py +0 -0
  70. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_auth_gaps.py +0 -0
  71. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_bisect.py +0 -0
  72. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_breaking.py +0 -0
  73. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_budget.py +0 -0
  74. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_bus_factor.py +0 -0
  75. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_capsule.py +0 -0
  76. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_cga.py +0 -0
  77. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_check_rules.py +0 -0
  78. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ci_setup.py +0 -0
  79. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_clean.py +0 -0
  80. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_clones.py +0 -0
  81. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_closure.py +0 -0
  82. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_clusters.py +0 -0
  83. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_codeowners.py +0 -0
  84. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_config.py +0 -0
  85. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_congestion.py +0 -0
  86. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_conventions.py +0 -0
  87. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_coupling.py +0 -0
  88. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_coverage_gaps.py +0 -0
  89. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_critique.py +0 -0
  90. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_cut.py +0 -0
  91. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_dark_matter.py +0 -0
  92. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_dashboard.py +0 -0
  93. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_debt.py +0 -0
  94. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_deps.py +0 -0
  95. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_describe.py +0 -0
  96. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_dev_profile.py +0 -0
  97. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_diagnose.py +0 -0
  98. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_diff.py +0 -0
  99. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_doc_staleness.py +0 -0
  100. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_docs_coverage.py +0 -0
  101. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_doctor.py +0 -0
  102. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_drift.py +0 -0
  103. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_duplicates.py +0 -0
  104. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_effects.py +0 -0
  105. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_endpoints.py +0 -0
  106. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_entry_points.py +0 -0
  107. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_eval_retrieve.py +0 -0
  108. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_file.py +0 -0
  109. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fingerprint.py +0 -0
  110. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fitness.py +0 -0
  111. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_flag_dead.py +0 -0
  112. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fleet.py +0 -0
  113. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_fn_coupling.py +0 -0
  114. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_forecast.py +0 -0
  115. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_grep.py +0 -0
  116. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_guard.py +0 -0
  117. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_health.py +0 -0
  118. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_hooks.py +0 -0
  119. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_hotspots.py +0 -0
  120. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_impact.py +0 -0
  121. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_index.py +0 -0
  122. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_index_bundle.py +0 -0
  123. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ingest_trace.py +0 -0
  124. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_init.py +0 -0
  125. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_intent.py +0 -0
  126. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_invariants.py +0 -0
  127. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_layers.py +0 -0
  128. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_map.py +0 -0
  129. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_math.py +0 -0
  130. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_mcp_setup.py +0 -0
  131. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_metrics.py +0 -0
  132. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_migration_safety.py +0 -0
  133. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_minimap.py +0 -0
  134. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_missing_index.py +0 -0
  135. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_module.py +0 -0
  136. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_mutate.py +0 -0
  137. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_n1.py +0 -0
  138. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_oracle.py +0 -0
  139. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_orchestrate.py +0 -0
  140. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_orphan_routes.py +0 -0
  141. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_over_fetch.py +0 -0
  142. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_owner.py +0 -0
  143. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_partition.py +0 -0
  144. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_path_coverage.py +0 -0
  145. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_patterns.py +0 -0
  146. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_plan.py +0 -0
  147. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_plan_refactor.py +0 -0
  148. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_pr_diff.py +0 -0
  149. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_pr_risk.py +0 -0
  150. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_preflight.py +0 -0
  151. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_relate.py +0 -0
  152. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_report.py +0 -0
  153. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_reset.py +0 -0
  154. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_risk.py +0 -0
  155. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_rules.py +0 -0
  156. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_safe_delete.py +0 -0
  157. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_safe_zones.py +0 -0
  158. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_sbom.py +0 -0
  159. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_schema.py +0 -0
  160. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_search_semantic.py +0 -0
  161. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_secrets.py +0 -0
  162. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_semantic_diff.py +0 -0
  163. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_simulate.py +0 -0
  164. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_simulate_departure.py +0 -0
  165. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_sketch.py +0 -0
  166. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_spectral.py +0 -0
  167. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_split.py +0 -0
  168. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
  169. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
  170. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_supply_chain.py +0 -0
  171. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_symbol.py +0 -0
  172. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_syntax_check.py +0 -0
  173. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_taint.py +0 -0
  174. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_test_gaps.py +0 -0
  175. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_test_scaffold.py +0 -0
  176. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_testmap.py +0 -0
  177. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_tour.py +0 -0
  178. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_trace.py +0 -0
  179. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_trends.py +0 -0
  180. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_triage.py +0 -0
  181. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_understand.py +0 -0
  182. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_uses.py +0 -0
  183. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_verify.py +0 -0
  184. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_verify_imports.py +0 -0
  185. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_vibe_check.py +0 -0
  186. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_visualize.py +0 -0
  187. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_vuln_map.py +0 -0
  188. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_vuln_reach.py +0 -0
  189. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_vulns.py +0 -0
  190. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_watch.py +0 -0
  191. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_weather.py +0 -0
  192. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_why.py +0 -0
  193. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_ws.py +0 -0
  194. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/cmd_xlang.py +0 -0
  195. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/codeowners_helpers.py +0 -0
  196. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/context_helpers.py +0 -0
  197. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/gate_presets.py +0 -0
  198. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/graph_helpers.py +0 -0
  199. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/metrics_history.py +0 -0
  200. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/next_steps.py +0 -0
  201. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/resolve.py +0 -0
  202. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/commands/suppression.py +0 -0
  203. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/competitor_site_data.py +0 -0
  204. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/config.py +0 -0
  205. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/coverage_reports.py +0 -0
  206. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/critique/__init__.py +0 -0
  207. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/critique/aggregator.py +0 -0
  208. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/critique/checks.py +0 -0
  209. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/db/__init__.py +0 -0
  210. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/db/queries.py +0 -0
  211. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/eval/__init__.py +0 -0
  212. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/eval/harness.py +0 -0
  213. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/exit_codes.py +0 -0
  214. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/fleet/__init__.py +0 -0
  215. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/fleet/adapters.py +0 -0
  216. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/fleet/manifest.py +0 -0
  217. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/git_utils.py +0 -0
  218. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/__init__.py +0 -0
  219. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/anomaly.py +0 -0
  220. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/builder.py +0 -0
  221. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/clone_detect.py +0 -0
  222. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/clusters.py +0 -0
  223. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/cycles.py +0 -0
  224. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/dark_matter.py +0 -0
  225. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/diff.py +0 -0
  226. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/fingerprint.py +0 -0
  227. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/layers.py +0 -0
  228. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/pagerank.py +0 -0
  229. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/partition.py +0 -0
  230. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/pathfinding.py +0 -0
  231. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/propagation.py +0 -0
  232. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/simulate.py +0 -0
  233. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/spectral.py +0 -0
  234. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/graph/stats.py +0 -0
  235. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/__init__.py +0 -0
  236. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/complexity.py +0 -0
  237. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/discovery.py +0 -0
  238. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/django_post.py +0 -0
  239. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/file_roles.py +0 -0
  240. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/git_stats.py +0 -0
  241. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/gitignore.py +0 -0
  242. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/incremental.py +0 -0
  243. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/parser.py +0 -0
  244. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/relations.py +0 -0
  245. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/index/test_conventions.py +0 -0
  246. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/__init__.py +0 -0
  247. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/apex_lang.py +0 -0
  248. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/aura_lang.py +0 -0
  249. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/c_lang.py +0 -0
  250. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/csharp_lang.py +0 -0
  251. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/extractor_schema.py +0 -0
  252. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/foxpro_lang.py +0 -0
  253. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/generic_lang.py +0 -0
  254. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/go_lang.py +0 -0
  255. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/hcl_lang.py +0 -0
  256. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/java_lang.py +0 -0
  257. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/javascript_lang.py +0 -0
  258. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/kotlin_lang.py +0 -0
  259. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/php_lang.py +0 -0
  260. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/query_engine.py +0 -0
  261. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/registry.py +0 -0
  262. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/ruby_lang.py +0 -0
  263. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/rust_lang.py +0 -0
  264. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/scala_lang.py +0 -0
  265. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/sfxml_lang.py +0 -0
  266. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/sql_lang.py +0 -0
  267. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/swift_lang.py +0 -0
  268. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/typescript_lang.py +0 -0
  269. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/visualforce_lang.py +0 -0
  270. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/languages/yaml_lang.py +0 -0
  271. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/__init__.py +0 -0
  272. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/completions.py +0 -0
  273. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/progress.py +0 -0
  274. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/sampling.py +0 -0
  275. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/session.py +0 -0
  276. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/mcp_extras/watcher.py +0 -0
  277. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/__init__.py +0 -0
  278. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/formatter.py +0 -0
  279. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/mermaid.py +0 -0
  280. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/sarif.py +0 -0
  281. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/output/schema_registry.py +0 -0
  282. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/plugins.py +0 -0
  283. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/refactor/__init__.py +0 -0
  284. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/refactor/codegen.py +0 -0
  285. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/refactor/transforms.py +0 -0
  286. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/__init__.py +0 -0
  287. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/learned_ranker.py +0 -0
  288. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/pipeline.py +0 -0
  289. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/rerank.py +0 -0
  290. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/retrieve/semantic.py +0 -0
  291. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/__init__.py +0 -0
  292. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/ast_match.py +0 -0
  293. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/builtin.py +0 -0
  294. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/dataflow.py +0 -0
  295. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/rules/engine.py +0 -0
  296. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/__init__.py +0 -0
  297. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/daemon.py +0 -0
  298. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/graph_backend.py +0 -0
  299. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/hotspots.py +0 -0
  300. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/lock_modes.py +0 -0
  301. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/lockmgr.py +0 -0
  302. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/runtime/trace_ingest.py +0 -0
  303. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/__init__.py +0 -0
  304. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/framework_packs.py +0 -0
  305. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/index_embeddings.py +0 -0
  306. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/onnx_embeddings.py +0 -0
  307. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/search/tfidf.py +0 -0
  308. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/__init__.py +0 -0
  309. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/aibom_extension.py +0 -0
  310. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/taint_classifier.py +0 -0
  311. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/taint_engine.py +0 -0
  312. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/vuln_reach.py +0 -0
  313. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/security/vuln_store.py +0 -0
  314. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/surface_counts.py +0 -0
  315. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/templates/__init__.py +0 -0
  316. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/templates/ci/__init__.py +0 -0
  317. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/__init__.py +0 -0
  318. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/aggregator.py +0 -0
  319. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/api_scanner.py +0 -0
  320. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/config.py +0 -0
  321. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam/workspace/db.py +0 -0
  322. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/dependency_links.txt +0 -0
  323. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/entry_points.txt +0 -0
  324. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/requires.txt +0 -0
  325. {roam_code-12.3.0 → roam_code-12.4.0}/src/roam_code.egg-info/top_level.txt +0 -0
  326. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_adrs.py +0 -0
  327. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_adversarial.py +0 -0
  328. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_affected.py +0 -0
  329. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_agent_export.py +0 -0
  330. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_agent_mode.py +0 -0
  331. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_agent_plan_context.py +0 -0
  332. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ai_ratio.py +0 -0
  333. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ai_readiness.py +0 -0
  334. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_alerts_cmd.py +0 -0
  335. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_annotations.py +0 -0
  336. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_anomaly.py +0 -0
  337. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_api_changes.py +0 -0
  338. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_api_drift.py +0 -0
  339. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ask.py +0 -0
  340. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_attest.py +0 -0
  341. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_auth_gaps.py +0 -0
  342. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_backend_fixes_round2.py +0 -0
  343. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_backend_fixes_round3.py +0 -0
  344. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_basic.py +0 -0
  345. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_batch_mcp.py +0 -0
  346. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bisect.py +0 -0
  347. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bridge_django.py +0 -0
  348. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bridges.py +0 -0
  349. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bridges_extended.py +0 -0
  350. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_budget.py +0 -0
  351. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_budget_flag.py +0 -0
  352. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_budget_phase2.py +0 -0
  353. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_bus_factor.py +0 -0
  354. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_capsule.py +0 -0
  355. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_cga.py +0 -0
  356. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_check_rules.py +0 -0
  357. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ci_gate_eval.py +0 -0
  358. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ci_sarif_guard.py +0 -0
  359. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ci_setup.py +0 -0
  360. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_clones.py +0 -0
  361. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_closure.py +0 -0
  362. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_codeowners.py +0 -0
  363. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_architecture.py +0 -0
  364. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_exploration.py +0 -0
  365. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_health.py +0 -0
  366. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_refactoring.py +0 -0
  367. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_commands_workflow.py +0 -0
  368. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_competitor_site_data.py +0 -0
  369. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_comprehensive.py +0 -0
  370. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_config.py +0 -0
  371. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_congestion.py +0 -0
  372. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_context_propagation.py +0 -0
  373. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_conventions_cmd.py +0 -0
  374. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_coverage_gaps_cmd.py +0 -0
  375. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_coverage_ingestion.py +0 -0
  376. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_critique.py +0 -0
  377. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_cut.py +0 -0
  378. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dark_matter.py +0 -0
  379. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dark_matter_helpers.py +0 -0
  380. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dashboard.py +0 -0
  381. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dataflow_dead.py +0 -0
  382. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dead_aging.py +0 -0
  383. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_defer_loading.py +0 -0
  384. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_demo_gif_asset.py +0 -0
  385. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_describe.py +0 -0
  386. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_detail_flag_hints.py +0 -0
  387. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_deterministic_output.py +0 -0
  388. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_dev_profile.py +0 -0
  389. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_difficulty_scoring.py +0 -0
  390. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_doc_staleness.py +0 -0
  391. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_docker_assets.py +0 -0
  392. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_docs_coverage.py +0 -0
  393. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_docs_site_quality.py +0 -0
  394. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_doctor.py +0 -0
  395. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_drift.py +0 -0
  396. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_duplicates.py +0 -0
  397. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_effects.py +0 -0
  398. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_effects_propagation.py +0 -0
  399. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_endpoints.py +0 -0
  400. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_entry_points_cmd.py +0 -0
  401. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_eval_retrieve.py +0 -0
  402. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_exclude_patterns.py +0 -0
  403. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_exit_codes.py +0 -0
  404. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_extractor_grammar_drift.py +0 -0
  405. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fallback_contracts.py +0 -0
  406. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_file_roles.py +0 -0
  407. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fingerprint.py +0 -0
  408. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fixes.py +0 -0
  409. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_flag_dead.py +0 -0
  410. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fleet.py +0 -0
  411. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_fn_coupling.py +0 -0
  412. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_forecast.py +0 -0
  413. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_formatters.py +0 -0
  414. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_foxpro.py +0 -0
  415. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_framework_detection.py +0 -0
  416. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_gate_presets.py +0 -0
  417. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_git_utils.py +0 -0
  418. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_guard.py +0 -0
  419. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_health_gate.py +0 -0
  420. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_hooks.py +0 -0
  421. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_hotspots.py +0 -0
  422. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_index.py +0 -0
  423. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_index_bundle.py +0 -0
  424. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_init_cmd.py +0 -0
  425. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_install_check.py +0 -0
  426. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_intent.py +0 -0
  427. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_invariants.py +0 -0
  428. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_json_contracts.py +0 -0
  429. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_json_error_envelope.py +0 -0
  430. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_kotlin_swift_extractors.py +0 -0
  431. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_language_corpus.py +0 -0
  432. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_languages.py +0 -0
  433. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_library_api.py +0 -0
  434. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_math.py +0 -0
  435. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_math_tips.py +0 -0
  436. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mcp_extras.py +0 -0
  437. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mcp_server.py +0 -0
  438. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mcp_setup.py +0 -0
  439. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mermaid.py +0 -0
  440. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_metrics_cmd.py +0 -0
  441. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_migration_safety.py +0 -0
  442. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_minimap.py +0 -0
  443. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_missing_index.py +0 -0
  444. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_mutate.py +0 -0
  445. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_n1.py +0 -0
  446. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_next_steps.py +0 -0
  447. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_onboard.py +0 -0
  448. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_oracle.py +0 -0
  449. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_orchestrate.py +0 -0
  450. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_orphan_routes.py +0 -0
  451. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_oss_bench_harness.py +0 -0
  452. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_over_fetch.py +0 -0
  453. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_pagerank_truncation.py +0 -0
  454. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_partition.py +0 -0
  455. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_path_coverage.py +0 -0
  456. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_patterns_cmd.py +0 -0
  457. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_performance.py +0 -0
  458. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_personalized_pagerank.py +0 -0
  459. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_plan.py +0 -0
  460. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_plugin_discovery.py +0 -0
  461. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_pr_comment_script.py +0 -0
  462. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_pr_diff.py +0 -0
  463. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_pr_risk_author.py +0 -0
  464. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_progress.py +0 -0
  465. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_progressive_disclosure.py +0 -0
  466. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_properties.py +0 -0
  467. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_python_extractor_v2.py +0 -0
  468. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_readme_surface_consistency.py +0 -0
  469. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_refactoring_intelligence.py +0 -0
  470. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_relate.py +0 -0
  471. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_report.py +0 -0
  472. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_reset_clean.py +0 -0
  473. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_resolve.py +0 -0
  474. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_retrieve.py +0 -0
  475. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_retrieve_cross_repo.py +0 -0
  476. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_retrieve_seeds.py +0 -0
  477. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_risk.py +0 -0
  478. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ruby.py +0 -0
  479. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rule_profiles.py +0 -0
  480. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules.py +0 -0
  481. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules_ast_match.py +0 -0
  482. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules_community_pack.py +0 -0
  483. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules_dataflow.py +0 -0
  484. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_rules_symbol_requirements.py +0 -0
  485. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_runtime.py +0 -0
  486. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_runtime_score.py +0 -0
  487. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_salesforce.py +0 -0
  488. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sarif_flag.py +0 -0
  489. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sbom.py +0 -0
  490. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_scala.py +0 -0
  491. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_schema_versioning.py +0 -0
  492. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_search_explain.py +0 -0
  493. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_secrets.py +0 -0
  494. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_secrets_v2.py +0 -0
  495. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_semantic_diff.py +0 -0
  496. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_semantic_onnx.py +0 -0
  497. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_semantic_search.py +0 -0
  498. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_simulate.py +0 -0
  499. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_simulate_departure.py +0 -0
  500. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sketch.py +0 -0
  501. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_smells.py +0 -0
  502. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_smoke.py +0 -0
  503. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sna_metrics.py +0 -0
  504. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_spectral.py +0 -0
  505. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_split_cmd.py +0 -0
  506. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_sql.py +0 -0
  507. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_suggest_reviewers.py +0 -0
  508. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_supply_chain.py +0 -0
  509. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_surface_counts.py +0 -0
  510. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_syntax_check.py +0 -0
  511. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_taint.py +0 -0
  512. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_taint_analysis.py +0 -0
  513. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_taint_classifier.py +0 -0
  514. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_test_conventions.py +0 -0
  515. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_test_gaps.py +0 -0
  516. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_test_scaffold.py +0 -0
  517. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_testmap.py +0 -0
  518. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_top_flag_consistency.py +0 -0
  519. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_tour_cmd.py +0 -0
  520. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_trends.py +0 -0
  521. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_trends_cohort.py +0 -0
  522. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_triage.py +0 -0
  523. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_uses_cmd.py +0 -0
  524. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v12_2.py +0 -0
  525. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v6_features.py +0 -0
  526. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v71_features.py +0 -0
  527. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v7_features.py +0 -0
  528. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_v82_features.py +0 -0
  529. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_verify.py +0 -0
  530. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_verify_imports.py +0 -0
  531. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_vibe_check.py +0 -0
  532. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_visualize.py +0 -0
  533. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_vuln.py +0 -0
  534. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_vulns_cmd.py +0 -0
  535. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_watch.py +0 -0
  536. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_why.py +0 -0
  537. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_workspace.py +0 -0
  538. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_ws_resolve_fixes.py +0 -0
  539. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_xlang.py +0 -0
  540. {roam_code-12.3.0 → roam_code-12.4.0}/tests/test_yaml_hcl.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roam-code
3
- Version: 12.3.0
3
+ Version: 12.4.0
4
4
  Summary: Instant codebase comprehension for AI coding agents
5
5
  Author: CosmoHac
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "roam-code"
7
- version = "12.3.0"
7
+ version = "12.4.0"
8
8
  description = "Instant codebase comprehension for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -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 + [limit],
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
- _TOOLING_PATH_HINTS = ("/dev/", "/benchmarks/", "/.github/", "\\dev\\", "\\benchmarks\\", "\\.github\\")
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
- _TOOLING_PATH_HINTS = ("/dev/", "/benchmarks/", "/.github/", "\\dev\\", "\\benchmarks\\", "\\.github\\")
110
+ from roam.output.file_role_hints import is_excluded_path
111
111
 
112
112
 
113
- def _is_tooling_path(path: str) -> bool:
114
- """Return True for paths that belong to tooling/CI/benchmarks/dev.
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
- Default-excluded from headline metrics so first-time users aren't
117
- dominated by ``pr-comment.js`` and ``roam-bench.py`` rows.
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 path:
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
- # Score-based check
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. Per dogfood notes 2026-05-01, the
97
- # top-N critical smells were dominated by .github/scripts,
98
- # benchmarks/, dev/, and generated files none of which are
99
- # source code a user would want to refactor. Their inclusion
100
- # made the verdict ("Needs refactoring: 1689 smells") false-
101
- # alarming. ``--include-tooling`` opts back into the full set.
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 any(hint.replace("\\", "/") in "/" + file_path_only for hint in tooling_path_hints):
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
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
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(