roam-code 12.7.0__tar.gz → 12.8.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 (550) hide show
  1. {roam_code-12.7.0/src/roam_code.egg-info → roam_code-12.8.0}/PKG-INFO +29 -8
  2. {roam_code-12.7.0 → roam_code-12.8.0}/README.md +28 -7
  3. {roam_code-12.7.0 → roam_code-12.8.0}/pyproject.toml +1 -2
  4. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/ask/recipes.py +22 -0
  5. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/catalog/python_idioms.py +81 -10
  6. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/cli.py +4 -0
  7. roam_code-12.8.0/src/roam/commands/cmd_hover.py +130 -0
  8. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_preflight.py +14 -1
  9. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_py_modern.py +8 -0
  10. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_py_types.py +27 -0
  11. roam_code-12.8.0/src/roam/commands/cmd_pytest_fixtures.py +352 -0
  12. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_search.py +0 -1
  13. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_supply_chain.py +6 -0
  14. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_taint.py +8 -0
  15. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_tour.py +12 -3
  16. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_ws.py +0 -2
  17. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/resolve.py +42 -3
  18. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/competitor_site_data.py +6 -3
  19. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/indexer.py +12 -0
  20. roam_code-12.8.0/src/roam/index/pytest_fixtures.py +281 -0
  21. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/relations.py +44 -4
  22. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/mcp_extras/progress.py +0 -1
  23. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/mcp_server.py +49 -0
  24. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/output/sarif.py +192 -0
  25. {roam_code-12.7.0 → roam_code-12.8.0/src/roam_code.egg-info}/PKG-INFO +29 -8
  26. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam_code.egg-info/SOURCES.txt +7 -0
  27. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_ask.py +5 -3
  28. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_batch_mcp.py +0 -1
  29. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_conventions_cmd.py +1 -2
  30. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_defer_loading.py +3 -0
  31. roam_code-12.8.0/tests/test_detector_precision.py +162 -0
  32. roam_code-12.8.0/tests/test_doc_consistency.py +273 -0
  33. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_doctor.py +2 -3
  34. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_duplicates.py +1 -2
  35. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_foxpro.py +0 -2
  36. roam_code-12.8.0/tests/test_hover.py +84 -0
  37. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_math.py +0 -1
  38. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_next_steps.py +3 -1
  39. roam_code-12.8.0/tests/test_pytest_fixtures.py +375 -0
  40. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_rule_profiles.py +0 -1
  41. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_suggest_reviewers.py +0 -5
  42. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_trends.py +0 -1
  43. {roam_code-12.7.0 → roam_code-12.8.0}/LICENSE +0 -0
  44. {roam_code-12.7.0 → roam_code-12.8.0}/setup.cfg +0 -0
  45. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/__init__.py +0 -0
  46. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/__main__.py +0 -0
  47. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/analysis/__init__.py +0 -0
  48. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/analysis/effects.py +0 -0
  49. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/analysis/taint.py +0 -0
  50. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/api.py +0 -0
  51. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/ask/__init__.py +0 -0
  52. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/ask/classifier.py +0 -0
  53. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/ask/runner.py +0 -0
  54. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/attest/__init__.py +0 -0
  55. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/attest/cga.py +0 -0
  56. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/bridges/__init__.py +0 -0
  57. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/bridges/base.py +0 -0
  58. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/bridges/bridge_config.py +0 -0
  59. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/bridges/bridge_django.py +0 -0
  60. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/bridges/bridge_protobuf.py +0 -0
  61. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/bridges/bridge_rest_api.py +0 -0
  62. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/bridges/bridge_salesforce.py +0 -0
  63. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/bridges/bridge_template.py +0 -0
  64. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/bridges/registry.py +0 -0
  65. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/catalog/__init__.py +0 -0
  66. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/catalog/detectors.py +0 -0
  67. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/catalog/fixes.py +0 -0
  68. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/catalog/smells.py +0 -0
  69. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/catalog/tasks.py +0 -0
  70. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/__init__.py +0 -0
  71. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/changed_files.py +0 -0
  72. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_adrs.py +0 -0
  73. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_adversarial.py +0 -0
  74. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_affected.py +0 -0
  75. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_affected_tests.py +0 -0
  76. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_agent_context.py +0 -0
  77. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_agent_export.py +0 -0
  78. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_agent_plan.py +0 -0
  79. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_ai_ratio.py +0 -0
  80. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_ai_readiness.py +0 -0
  81. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_alerts.py +0 -0
  82. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_annotate.py +0 -0
  83. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_api_changes.py +0 -0
  84. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_api_drift.py +0 -0
  85. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_ask.py +0 -0
  86. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_attest.py +0 -0
  87. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_auth_gaps.py +0 -0
  88. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_bisect.py +0 -0
  89. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_breaking.py +0 -0
  90. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_budget.py +0 -0
  91. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_bus_factor.py +0 -0
  92. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_capsule.py +0 -0
  93. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_cga.py +0 -0
  94. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_check_rules.py +0 -0
  95. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_ci_setup.py +0 -0
  96. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_clean.py +0 -0
  97. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_clones.py +0 -0
  98. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_closure.py +0 -0
  99. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_clusters.py +0 -0
  100. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_codeowners.py +0 -0
  101. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_complexity.py +0 -0
  102. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_config.py +0 -0
  103. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_congestion.py +0 -0
  104. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_context.py +0 -0
  105. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_conventions.py +0 -0
  106. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_coupling.py +0 -0
  107. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_coverage_gaps.py +0 -0
  108. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_critique.py +0 -0
  109. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_cut.py +0 -0
  110. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_dark_matter.py +0 -0
  111. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_dashboard.py +0 -0
  112. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_dead.py +0 -0
  113. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_debt.py +0 -0
  114. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_deps.py +0 -0
  115. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_describe.py +0 -0
  116. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_dev_profile.py +0 -0
  117. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_diagnose.py +0 -0
  118. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_diff.py +0 -0
  119. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_doc_staleness.py +0 -0
  120. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_docs_coverage.py +0 -0
  121. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_doctor.py +0 -0
  122. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_drift.py +0 -0
  123. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_duplicates.py +0 -0
  124. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_effects.py +0 -0
  125. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_endpoints.py +0 -0
  126. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_entry_points.py +0 -0
  127. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_eval_retrieve.py +0 -0
  128. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_fan.py +0 -0
  129. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_file.py +0 -0
  130. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_fingerprint.py +0 -0
  131. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_fitness.py +0 -0
  132. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_flag_dead.py +0 -0
  133. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_fleet.py +0 -0
  134. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_fn_coupling.py +0 -0
  135. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_forecast.py +0 -0
  136. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_grep.py +0 -0
  137. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_guard.py +0 -0
  138. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_health.py +0 -0
  139. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_hooks.py +0 -0
  140. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_hotspots.py +0 -0
  141. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_impact.py +0 -0
  142. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_index.py +0 -0
  143. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_index_bundle.py +0 -0
  144. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_ingest_trace.py +0 -0
  145. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_init.py +0 -0
  146. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_intent.py +0 -0
  147. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_invariants.py +0 -0
  148. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_layers.py +0 -0
  149. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_map.py +0 -0
  150. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_math.py +0 -0
  151. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_mcp_setup.py +0 -0
  152. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_metrics.py +0 -0
  153. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_migration_safety.py +0 -0
  154. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_minimap.py +0 -0
  155. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_missing_index.py +0 -0
  156. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_module.py +0 -0
  157. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_mutate.py +0 -0
  158. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_n1.py +0 -0
  159. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_oracle.py +0 -0
  160. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_orchestrate.py +0 -0
  161. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_orphan_routes.py +0 -0
  162. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_over_fetch.py +0 -0
  163. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_owner.py +0 -0
  164. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_partition.py +0 -0
  165. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_path_coverage.py +0 -0
  166. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_patterns.py +0 -0
  167. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_plan.py +0 -0
  168. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_plan_refactor.py +0 -0
  169. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_pr_diff.py +0 -0
  170. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_pr_risk.py +0 -0
  171. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_relate.py +0 -0
  172. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_report.py +0 -0
  173. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_reset.py +0 -0
  174. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_retrieve.py +0 -0
  175. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_risk.py +0 -0
  176. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_rules.py +0 -0
  177. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_safe_delete.py +0 -0
  178. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_safe_zones.py +0 -0
  179. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_sbom.py +0 -0
  180. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_schema.py +0 -0
  181. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_search_semantic.py +0 -0
  182. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_secrets.py +0 -0
  183. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_semantic_diff.py +0 -0
  184. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_simulate.py +0 -0
  185. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_simulate_departure.py +0 -0
  186. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_sketch.py +0 -0
  187. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_smells.py +0 -0
  188. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_spectral.py +0 -0
  189. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_split.py +0 -0
  190. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
  191. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
  192. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_symbol.py +0 -0
  193. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_syntax_check.py +0 -0
  194. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_test_gaps.py +0 -0
  195. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_test_scaffold.py +0 -0
  196. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_testmap.py +0 -0
  197. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_trace.py +0 -0
  198. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_trends.py +0 -0
  199. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_triage.py +0 -0
  200. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_understand.py +0 -0
  201. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_uses.py +0 -0
  202. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_verify.py +0 -0
  203. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_verify_imports.py +0 -0
  204. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_vibe_check.py +0 -0
  205. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_visualize.py +0 -0
  206. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_vuln_map.py +0 -0
  207. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_vuln_reach.py +0 -0
  208. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_vulns.py +0 -0
  209. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_watch.py +0 -0
  210. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_weather.py +0 -0
  211. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_why.py +0 -0
  212. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/cmd_xlang.py +0 -0
  213. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/codeowners_helpers.py +0 -0
  214. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/context_helpers.py +0 -0
  215. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/gate_presets.py +0 -0
  216. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/graph_helpers.py +0 -0
  217. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/metrics_history.py +0 -0
  218. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/next_steps.py +0 -0
  219. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/commands/suppression.py +0 -0
  220. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/config.py +0 -0
  221. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/coverage_reports.py +0 -0
  222. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/critique/__init__.py +0 -0
  223. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/critique/aggregator.py +0 -0
  224. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/critique/checks.py +0 -0
  225. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/db/__init__.py +0 -0
  226. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/db/connection.py +0 -0
  227. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/db/queries.py +0 -0
  228. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/db/schema.py +0 -0
  229. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/eval/__init__.py +0 -0
  230. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/eval/harness.py +0 -0
  231. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/exit_codes.py +0 -0
  232. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/fleet/__init__.py +0 -0
  233. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/fleet/adapters.py +0 -0
  234. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/fleet/manifest.py +0 -0
  235. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/git_utils.py +0 -0
  236. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/__init__.py +0 -0
  237. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/anomaly.py +0 -0
  238. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/builder.py +0 -0
  239. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/clone_detect.py +0 -0
  240. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/clusters.py +0 -0
  241. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/cycles.py +0 -0
  242. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/dark_matter.py +0 -0
  243. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/diff.py +0 -0
  244. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/fingerprint.py +0 -0
  245. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/layers.py +0 -0
  246. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/pagerank.py +0 -0
  247. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/partition.py +0 -0
  248. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/pathfinding.py +0 -0
  249. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/propagation.py +0 -0
  250. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/simulate.py +0 -0
  251. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/spectral.py +0 -0
  252. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/graph/stats.py +0 -0
  253. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/__init__.py +0 -0
  254. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/complexity.py +0 -0
  255. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/discovery.py +0 -0
  256. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/django_post.py +0 -0
  257. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/file_roles.py +0 -0
  258. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/git_stats.py +0 -0
  259. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/gitignore.py +0 -0
  260. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/incremental.py +0 -0
  261. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/parser.py +0 -0
  262. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/symbols.py +0 -0
  263. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/index/test_conventions.py +0 -0
  264. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/__init__.py +0 -0
  265. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/apex_lang.py +0 -0
  266. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/aura_lang.py +0 -0
  267. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/base.py +0 -0
  268. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/c_lang.py +0 -0
  269. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/csharp_lang.py +0 -0
  270. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/extractor_schema.py +0 -0
  271. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/foxpro_lang.py +0 -0
  272. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/generic_lang.py +0 -0
  273. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/go_lang.py +0 -0
  274. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/hcl_lang.py +0 -0
  275. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/java_lang.py +0 -0
  276. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/javascript_lang.py +0 -0
  277. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/kotlin_lang.py +0 -0
  278. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/php_lang.py +0 -0
  279. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/python_lang.py +0 -0
  280. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/query_engine.py +0 -0
  281. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/registry.py +0 -0
  282. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/ruby_lang.py +0 -0
  283. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/rust_lang.py +0 -0
  284. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/scala_lang.py +0 -0
  285. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/sfxml_lang.py +0 -0
  286. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/sql_lang.py +0 -0
  287. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/swift_lang.py +0 -0
  288. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/typescript_lang.py +0 -0
  289. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/visualforce_lang.py +0 -0
  290. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/languages/yaml_lang.py +0 -0
  291. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/mcp_extras/__init__.py +0 -0
  292. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/mcp_extras/completions.py +0 -0
  293. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/mcp_extras/sampling.py +0 -0
  294. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/mcp_extras/session.py +0 -0
  295. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/mcp_extras/watcher.py +0 -0
  296. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/output/__init__.py +0 -0
  297. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/output/file_role_hints.py +0 -0
  298. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/output/formatter.py +0 -0
  299. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/output/mermaid.py +0 -0
  300. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/output/schema_registry.py +0 -0
  301. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/plugins.py +0 -0
  302. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/refactor/__init__.py +0 -0
  303. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/refactor/codegen.py +0 -0
  304. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/refactor/transforms.py +0 -0
  305. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/retrieve/__init__.py +0 -0
  306. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/retrieve/learned_ranker.py +0 -0
  307. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/retrieve/pipeline.py +0 -0
  308. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/retrieve/rerank.py +0 -0
  309. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/retrieve/seeds.py +0 -0
  310. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/retrieve/semantic.py +0 -0
  311. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/rules/__init__.py +0 -0
  312. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/rules/ast_match.py +0 -0
  313. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/rules/builtin.py +0 -0
  314. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/rules/dataflow.py +0 -0
  315. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/rules/engine.py +0 -0
  316. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/runtime/__init__.py +0 -0
  317. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/runtime/daemon.py +0 -0
  318. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/runtime/graph_backend.py +0 -0
  319. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/runtime/hotspots.py +0 -0
  320. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/runtime/lock_modes.py +0 -0
  321. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/runtime/lockmgr.py +0 -0
  322. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/runtime/trace_ingest.py +0 -0
  323. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/search/__init__.py +0 -0
  324. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/search/framework_packs.py +0 -0
  325. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/search/index_embeddings.py +0 -0
  326. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/search/onnx_embeddings.py +0 -0
  327. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/search/tfidf.py +0 -0
  328. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/security/__init__.py +0 -0
  329. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/security/aibom_extension.py +0 -0
  330. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/security/taint_classifier.py +0 -0
  331. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/security/taint_engine.py +0 -0
  332. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/security/vuln_reach.py +0 -0
  333. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/security/vuln_store.py +0 -0
  334. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/surface_counts.py +0 -0
  335. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/templates/__init__.py +0 -0
  336. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/templates/ci/__init__.py +0 -0
  337. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/workspace/__init__.py +0 -0
  338. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/workspace/aggregator.py +0 -0
  339. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/workspace/api_scanner.py +0 -0
  340. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/workspace/config.py +0 -0
  341. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam/workspace/db.py +0 -0
  342. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam_code.egg-info/dependency_links.txt +0 -0
  343. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam_code.egg-info/entry_points.txt +0 -0
  344. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam_code.egg-info/requires.txt +0 -0
  345. {roam_code-12.7.0 → roam_code-12.8.0}/src/roam_code.egg-info/top_level.txt +0 -0
  346. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_adrs.py +0 -0
  347. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_adversarial.py +0 -0
  348. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_affected.py +0 -0
  349. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_agent_export.py +0 -0
  350. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_agent_mode.py +0 -0
  351. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_agent_plan_context.py +0 -0
  352. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_ai_ratio.py +0 -0
  353. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_ai_readiness.py +0 -0
  354. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_alerts_cmd.py +0 -0
  355. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_annotations.py +0 -0
  356. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_anomaly.py +0 -0
  357. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_api_changes.py +0 -0
  358. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_api_drift.py +0 -0
  359. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_attest.py +0 -0
  360. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_auth_gaps.py +0 -0
  361. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_backend_fixes_round2.py +0 -0
  362. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_backend_fixes_round3.py +0 -0
  363. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_basic.py +0 -0
  364. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_bisect.py +0 -0
  365. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_bridge_django.py +0 -0
  366. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_bridges.py +0 -0
  367. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_bridges_extended.py +0 -0
  368. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_budget.py +0 -0
  369. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_budget_flag.py +0 -0
  370. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_budget_phase2.py +0 -0
  371. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_bus_factor.py +0 -0
  372. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_capsule.py +0 -0
  373. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_cga.py +0 -0
  374. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_check_rules.py +0 -0
  375. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_ci_gate_eval.py +0 -0
  376. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_ci_sarif_guard.py +0 -0
  377. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_ci_setup.py +0 -0
  378. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_clones.py +0 -0
  379. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_closure.py +0 -0
  380. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_codeowners.py +0 -0
  381. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_commands_architecture.py +0 -0
  382. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_commands_exploration.py +0 -0
  383. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_commands_health.py +0 -0
  384. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_commands_refactoring.py +0 -0
  385. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_commands_workflow.py +0 -0
  386. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_competitor_site_data.py +0 -0
  387. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_comprehensive.py +0 -0
  388. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_config.py +0 -0
  389. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_congestion.py +0 -0
  390. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_context_propagation.py +0 -0
  391. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_coverage_gaps_cmd.py +0 -0
  392. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_coverage_ingestion.py +0 -0
  393. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_critique.py +0 -0
  394. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_cut.py +0 -0
  395. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_dark_matter.py +0 -0
  396. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_dark_matter_helpers.py +0 -0
  397. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_dashboard.py +0 -0
  398. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_dataflow_dead.py +0 -0
  399. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_dead_aging.py +0 -0
  400. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_demo_gif_asset.py +0 -0
  401. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_describe.py +0 -0
  402. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_detail_flag_hints.py +0 -0
  403. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_deterministic_output.py +0 -0
  404. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_dev_profile.py +0 -0
  405. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_difficulty_scoring.py +0 -0
  406. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_doc_staleness.py +0 -0
  407. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_docker_assets.py +0 -0
  408. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_docs_coverage.py +0 -0
  409. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_docs_site_quality.py +0 -0
  410. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_drift.py +0 -0
  411. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_effects.py +0 -0
  412. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_effects_propagation.py +0 -0
  413. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_endpoints.py +0 -0
  414. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_entry_points_cmd.py +0 -0
  415. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_eval_retrieve.py +0 -0
  416. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_exclude_patterns.py +0 -0
  417. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_exit_codes.py +0 -0
  418. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_extractor_grammar_drift.py +0 -0
  419. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_fallback_contracts.py +0 -0
  420. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_file_roles.py +0 -0
  421. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_fingerprint.py +0 -0
  422. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_fixes.py +0 -0
  423. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_flag_dead.py +0 -0
  424. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_fleet.py +0 -0
  425. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_fn_coupling.py +0 -0
  426. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_forecast.py +0 -0
  427. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_formatters.py +0 -0
  428. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_framework_detection.py +0 -0
  429. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_gate_presets.py +0 -0
  430. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_git_utils.py +0 -0
  431. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_guard.py +0 -0
  432. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_health_gate.py +0 -0
  433. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_hooks.py +0 -0
  434. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_hotspots.py +0 -0
  435. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_index.py +0 -0
  436. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_index_bundle.py +0 -0
  437. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_init_cmd.py +0 -0
  438. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_install_check.py +0 -0
  439. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_intent.py +0 -0
  440. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_invariants.py +0 -0
  441. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_json_contracts.py +0 -0
  442. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_json_error_envelope.py +0 -0
  443. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_kotlin_swift_extractors.py +0 -0
  444. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_language_corpus.py +0 -0
  445. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_languages.py +0 -0
  446. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_library_api.py +0 -0
  447. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_math_tips.py +0 -0
  448. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_mcp_extras.py +0 -0
  449. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_mcp_server.py +0 -0
  450. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_mcp_setup.py +0 -0
  451. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_mermaid.py +0 -0
  452. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_metrics_cmd.py +0 -0
  453. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_migration_safety.py +0 -0
  454. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_minimap.py +0 -0
  455. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_missing_index.py +0 -0
  456. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_mutate.py +0 -0
  457. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_n1.py +0 -0
  458. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_onboard.py +0 -0
  459. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_oracle.py +0 -0
  460. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_orchestrate.py +0 -0
  461. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_orphan_routes.py +0 -0
  462. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_oss_bench_harness.py +0 -0
  463. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_over_fetch.py +0 -0
  464. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_pagerank_truncation.py +0 -0
  465. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_partition.py +0 -0
  466. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_path_coverage.py +0 -0
  467. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_patterns_cmd.py +0 -0
  468. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_performance.py +0 -0
  469. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_personalized_pagerank.py +0 -0
  470. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_plan.py +0 -0
  471. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_plugin_discovery.py +0 -0
  472. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_pr_comment_script.py +0 -0
  473. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_pr_diff.py +0 -0
  474. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_pr_risk_author.py +0 -0
  475. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_progress.py +0 -0
  476. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_progressive_disclosure.py +0 -0
  477. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_properties.py +0 -0
  478. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_python_extractor_v2.py +0 -0
  479. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_python_idioms_e2e.py +0 -0
  480. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_python_pivot.py +0 -0
  481. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_readme_surface_consistency.py +0 -0
  482. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_refactoring_intelligence.py +0 -0
  483. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_relate.py +0 -0
  484. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_report.py +0 -0
  485. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_reset_clean.py +0 -0
  486. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_resolve.py +0 -0
  487. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_retrieve.py +0 -0
  488. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_retrieve_cross_repo.py +0 -0
  489. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_retrieve_seeds.py +0 -0
  490. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_risk.py +0 -0
  491. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_ruby.py +0 -0
  492. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_rules.py +0 -0
  493. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_rules_ast_match.py +0 -0
  494. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_rules_community_pack.py +0 -0
  495. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_rules_dataflow.py +0 -0
  496. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_rules_symbol_requirements.py +0 -0
  497. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_runtime.py +0 -0
  498. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_runtime_score.py +0 -0
  499. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_salesforce.py +0 -0
  500. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_sarif_flag.py +0 -0
  501. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_sbom.py +0 -0
  502. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_scala.py +0 -0
  503. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_schema_versioning.py +0 -0
  504. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_search_explain.py +0 -0
  505. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_secrets.py +0 -0
  506. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_secrets_v2.py +0 -0
  507. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_semantic_diff.py +0 -0
  508. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_semantic_onnx.py +0 -0
  509. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_semantic_search.py +0 -0
  510. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_simulate.py +0 -0
  511. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_simulate_departure.py +0 -0
  512. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_sketch.py +0 -0
  513. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_smells.py +0 -0
  514. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_smoke.py +0 -0
  515. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_sna_metrics.py +0 -0
  516. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_spectral.py +0 -0
  517. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_split_cmd.py +0 -0
  518. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_sql.py +0 -0
  519. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_supply_chain.py +0 -0
  520. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_surface_counts.py +0 -0
  521. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_syntax_check.py +0 -0
  522. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_taint.py +0 -0
  523. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_taint_analysis.py +0 -0
  524. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_taint_classifier.py +0 -0
  525. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_test_conventions.py +0 -0
  526. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_test_gaps.py +0 -0
  527. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_test_scaffold.py +0 -0
  528. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_testmap.py +0 -0
  529. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_top_flag_consistency.py +0 -0
  530. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_tour_cmd.py +0 -0
  531. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_trends_cohort.py +0 -0
  532. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_triage.py +0 -0
  533. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_uses_cmd.py +0 -0
  534. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_v12_2.py +0 -0
  535. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_v6_features.py +0 -0
  536. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_v71_features.py +0 -0
  537. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_v7_features.py +0 -0
  538. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_v82_features.py +0 -0
  539. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_verify.py +0 -0
  540. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_verify_imports.py +0 -0
  541. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_vibe_check.py +0 -0
  542. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_visualize.py +0 -0
  543. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_vuln.py +0 -0
  544. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_vulns_cmd.py +0 -0
  545. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_watch.py +0 -0
  546. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_why.py +0 -0
  547. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_workspace.py +0 -0
  548. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_ws_resolve_fixes.py +0 -0
  549. {roam_code-12.7.0 → roam_code-12.8.0}/tests/test_xlang.py +0 -0
  550. {roam_code-12.7.0 → roam_code-12.8.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.7.0
3
+ Version: 12.8.0
4
4
  Summary: Instant codebase comprehension for AI coding agents
5
5
  Author: CosmoHac
6
6
  License-Expression: MIT
@@ -60,9 +60,11 @@ Dynamic: license-file
60
60
 
61
61
  # roam-code
62
62
 
63
- **The architectural intelligence layer for AI coding agents. Structural graph, architecture governance, multi-agent orchestration, vulnerability mapping, runtime analysis -- one CLI, zero API keys.**
63
+ **Architectural sight for AI coding agents before they edit.**
64
64
 
65
- *152 commands · 118 MCP tools · 27 languages · 100% local*
65
+ A local code graph (SQLite + tree-sitter + git history) that gives any agent — Claude Code, Cursor, Aider, Continue, your own — five high-leverage verbs: `understand`, `retrieve`, `context`, `preflight`, `critique`. The other 148 specialised commands are advanced surface for specialised workflows.
66
+
67
+ *154 commands · 120 MCP tools · 27 languages · 100% local · zero API keys*
66
68
 
67
69
  [![PyPI version](https://img.shields.io/pypi/v/roam-code?style=flat-square&color=blue)](https://pypi.org/project/roam-code/)
68
70
  [![GitHub stars](https://img.shields.io/github/stars/Cranot/roam-code?style=flat-square)](https://github.com/Cranot/roam-code/stargazers)
@@ -81,7 +83,7 @@ Roam is a structural intelligence engine for software. It pre-indexes your codeb
81
83
  Unlike LSPs (editor-bound, language-specific) or Sourcegraph (hosted search), Roam provides architecture-level graph queries -- offline, cross-language, and compact. It goes beyond comprehension: Roam governs architecture through budget gates, simulates refactoring outcomes, orchestrates multi-agent swarms with zero-conflict guarantees, maps vulnerability reachability paths, and enables graph-level code editing without syntax errors.
82
84
 
83
85
  ```
84
- Codebase ──> [Index] ──> Semantic Graph ──> 139 Commands ──> AI Agent
86
+ Codebase ──> [Index] ──> Semantic Graph ──> 152 Commands ──> AI Agent
85
87
  │ │ │
86
88
  tree-sitter symbols comprehend
87
89
  27 languages + edges govern
@@ -89,6 +91,21 @@ Codebase ──> [Index] ──> Semantic Graph ──> 139 Commands ──> AI
89
91
  runtime traces + architecture orchestrate
90
92
  ```
91
93
 
94
+ ### Start here — the 5 verbs that cover ~80% of agent workflows
95
+
96
+ ```bash
97
+ pip install roam-code
98
+
99
+ cd your-repo/
100
+ roam understand # 1. landing pad — what is this codebase?
101
+ roam retrieve "where is auth?" # 2. graph-aware retrieval for free-form tasks
102
+ roam context AuthService # 3. exact files+lines to read before changing
103
+ roam preflight AuthService # 4. blast radius + tests + complexity check
104
+ git diff | roam critique # 5. patch verifier — clones-not-edited, hot-path
105
+ ```
106
+
107
+ That's the full mental model. The other CLI surface — `taint`, `fleet`, `cga`, `simulate`, `mutate`, `partition`, `attest`, `eval-retrieve`, `oracle`, `py-types`, `py-modern`, `dark-matter`, `clones`, `propagation`, `fingerprint`, etc. — is advanced surface for specialised workflows; you'll never need most of them.
108
+
92
109
  ### The problem
93
110
 
94
111
  Coding agents explore codebases inefficiently: dozens of grep/read cycles, high token cost, no structural understanding. Roam replaces this with one graph query:
@@ -139,7 +156,7 @@ $ roam diff # blast radius of uncommitted changes
139
156
  - **`personalized_pagerank()`** in `graph/pagerank.py`: NetworkX `personalization=` wrapper with empty-seed fallback to global PR; biases ranking toward query-relevant nodes for the retrieve reranker.
140
157
  - **`.roam/config.toml`** (new): zero-dep TOML loader (stdlib `tomllib` → `tomli` → in-tree subset parser). Tunable retrieve weights (`alpha`/`beta`/`gamma`/`delta`/`epsilon`), `tokens_per_line`, `lexical_baseline`, `first_stage_token_cap`, `default_budget`, `default_k`, `default_rerank`.
141
158
  - **DX corrections from dogfood pass**: `roam --detail <cmd>` is the canonical group-level flag; misleading "use --detail" hints in 7 commands rewritten to point users at `roam --detail <cmd>`. `--top N` aliased on `complexity`/`algo`/`rules` (`--top 0` means unlimited on `rules`). `roam fingerprint` no longer refuses graphs ≥5,000 symbols (new soft-warn threshold 20k, hard cap 100k).
142
- - **152 CLI commands, 118 MCP tools** (`fleet`, `ask`, `taint`, `cga`, `eval-retrieve` remain CLI-only; v12 exposes `roam_retrieve`, `roam_critique`, `roam_fleet_plan`, plus 5 v12.1 boolean oracles (`roam_oracle_*`) and `roam_taint_classify` as MCP tools). 35-tool `core` preset is the default for token-budget-conscious clients.
159
+ - **154 CLI commands, 120 MCP tools** (`fleet`, `ask`, `cga`, `eval-retrieve` remain CLI-only; v12 exposes `roam_retrieve`, `roam_critique`, `roam_fleet_plan`, plus 5 v12.1 boolean oracles (`roam_oracle_*`), `roam_taint_classify`, `roam_pytest_fixtures`, and `roam_hover` as MCP tools). 35-tool `core` preset is the default for token-budget-conscious clients.
143
160
 
144
161
  ## What's New in v11
145
162
 
@@ -310,7 +327,7 @@ roam health
310
327
 
311
328
  ## Commands
312
329
 
313
- **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 147 commands are detail surface for specialised workflows (taint, fleet, cga, oracle, eval, …) — they're called by agents on demand, not memorised. This is intentional design; under the hood the canonical surface is **152 commands organised into 7 categories**, but you don't need to know that to start.
330
+ **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 149 commands are detail surface for specialised workflows (taint, fleet, cga, oracle, eval, …) — they're called by agents on demand, not memorised. This is intentional design; under the hood the canonical surface is **154 commands organised into 7 categories**, but you don't need to know that to start.
314
331
 
315
332
  <details>
316
333
  <summary><strong>Full command reference</strong></summary>
@@ -349,6 +366,7 @@ roam health
349
366
  | `roam file <path> [--full] [--changed] [--deps-of PATH]` | File skeleton: all definitions with signatures, cognitive load index, health score |
350
367
  | `roam symbol <name> [--full]` | Symbol definition + callers + callees + metrics. Supports `file:symbol` disambiguation |
351
368
  | `roam context <symbol> [--task MODE] [--for-file PATH]` | AI-optimized context: definition + callers + callees + files-to-read with line ranges |
369
+ | `roam hover <symbol>` | One-line architectural summary: kind, location, blast-radius bucket, top caller, top callee. Bounded at ~200 tokens for IDE hover panels |
352
370
  | `roam retrieve <task> [--budget N] [--k N] [--seed-files PATH]` | Graph-aware context for free-form tasks: FTS5 + structural rerank (PageRank + clones) + token budget |
353
371
  | `roam critique [--input DIFF] [--intent TEXT] [--high-callers N]` | Verify a patch against the graph: clones-not-edited + blast radius + intent-vs-semantic-diff. Pipe `git diff` in. Exit 5 on high severity. |
354
372
  | `roam fleet plan <goal> [--n-agents N] [--adapter raw\|composio\|copilot]` | Graph-aware planner: Louvain partition + co-change + PageRank anchors → `.roam-fleet.json` for Composio/Copilot CLI/raw. |
@@ -402,6 +420,7 @@ roam health
402
420
  | `roam complexity [--bumpy-road] [--include-tooling]` | Per-function cognitive complexity (SonarSource-compatible, triangular nesting penalty) + Halstead metrics (volume, difficulty, effort, bugs) + cyclomatic density |
403
421
  | `roam py-types [--detail] [--include-tests] [--ci --min-coverage N]` | Python type-annotation health: % of public functions with full annotations, ``Any`` usage, legacy ``typing.Optional/Dict/List`` (PEP 585/604 modernisation candidates), per-file worst offenders. CI-gateable via ``--ci --min-coverage N`` (exit 5 below threshold). Default-excludes test files |
404
422
  | `roam py-modern [--detail]` | Modern-Python adoption signal: counts walrus operator (PEP 572), match statements (PEP 634), PEP 604 ``X \| None``, PEP 585 ``dict[…]``, PEP 695 type aliases, f-strings vs ``.format()``. Reports type-modernisation % and f-string adoption % to gauge migration progress |
423
+ | `roam pytest-fixtures [SYMBOL] [--max-depth N]` | Inventory pytest fixture chains. With no SYMBOL, prints the project-wide fixture count and the top fixtures by dependent count. With a fixture or test name, walks the implicit fixture-parameter dependency graph to show what each test transitively requires. Resolves through ``conftest.py`` chains |
405
424
  | `roam algo [--task T] [--confidence C] [--profile P]` | Algorithm anti-pattern detection: 23-pattern catalog detects suboptimal algorithms (O(n^2) loops, N+1 queries, quadratic string building, branching recursion, loop-invariant calls) and suggests better approaches with Big-O improvements. Confidence calibration via caller-count + runtime traces, evidence paths, impact scoring, framework-aware N+1 packs, and language-aware fix templates. Alias: `roam math` |
406
425
  | `roam n1 [--confidence C] [--verbose]` | Implicit N+1 I/O detection: finds ORM model computed properties (`$appends`/accessors) that trigger lazy-loaded DB queries in collection contexts. Cross-references with eager loading config. Supports Laravel, Django, Rails, SQLAlchemy, JPA |
407
426
  | `roam over-fetch [--threshold N] [--confidence C]` | Detect models serializing too many fields: large `$fillable` without `$hidden`/`$visible`, direct controller returns bypassing API Resources, poor exposed-to-hidden ratio |
@@ -654,7 +673,7 @@ The sentinel pair `<!-- roam:minimap -->` / `<!-- /roam:minimap -->` is replaced
654
673
  |--------|-------------|
655
674
  | `roam --json <command>` | Structured JSON output with consistent envelope |
656
675
  | `roam --compact <command>` | Token-efficient output: TSV tables, minimal JSON envelope |
657
- | `roam --sarif <command>` | SARIF 2.1.0 output for dead, health, complexity, rules, secrets, and algo (GitHub/CI integration) |
676
+ | `roam --sarif <command>` | SARIF 2.1.0 output for dead, health, complexity, rules, secrets, algo, py-types, py-modern (GitHub/CI integration) |
658
677
  | `roam health --gate` | CI quality gate. Reads `.roam-gates.yml` thresholds. Exit code 5 on failure |
659
678
 
660
679
  </details>
@@ -950,7 +969,7 @@ ROAM_MCP_LITE=0 roam mcp
950
969
  Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`, `roam_complete`, `roam_complexity_report`, `roam_context`, `roam_dead_code`, `roam_deps`, `roam_diagnose`, `roam_diagnose_issue`, `roam_diff`, `roam_expand_toolset`, `roam_explore`, `roam_file_info`, `roam_health`, `roam_impact`, `roam_pr_risk`, `roam_preflight`, `roam_prepare_change`, `roam_review_change`, `roam_search_symbol`, `roam_syntax_check`, `roam_trace`, `roam_understand`, `roam_uses`.
951
970
 
952
971
  <details>
953
- <summary><strong>MCP tool list (all 118)</strong></summary>
972
+ <summary><strong>MCP tool list (all 120)</strong></summary>
954
973
 
955
974
  | Tool | Description |
956
975
  |------|-------------|
@@ -961,6 +980,7 @@ Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`,
961
980
  | `roam_search_symbol` | Find symbols by name |
962
981
  | `roam_complete` | Prefix completion for symbols/paths/commands (FTS5-backed) |
963
982
  | `roam_context` | Files-to-read for modifying a symbol |
983
+ | `roam_hover` | Single-line architectural summary — kind, blast-radius bucket, top caller, top callee |
964
984
  | `roam_retrieve` | Graph-aware context for free-form tasks (FTS5 + structural rerank + token budget) |
965
985
  | `roam_critique` | Verify a patch against the graph (clones-not-edited + blast radius) |
966
986
  | `roam_fleet_plan` | Plan a multi-agent fleet — graph-aware partition emits .roam-fleet.json |
@@ -984,6 +1004,7 @@ Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`,
984
1004
  | `roam_complexity_report` | Per-symbol cognitive complexity |
985
1005
  | `roam_py_types` | Python type-annotation health (% public typed, Any, legacy typing) |
986
1006
  | `roam_py_modern` | Modern-Python adoption (walrus, match, PEP 604/585/695, f-strings) |
1007
+ | `roam_pytest_fixtures` | pytest fixture chain — top fixtures by dependent count, or per-symbol dependency walk |
987
1008
  | `roam_tour` | Auto-generated onboarding guide |
988
1009
  | `roam_diagnose` | Root cause analysis for debugging |
989
1010
  | `roam_visualize` | Generate Mermaid or DOT architecture diagrams |
@@ -2,9 +2,11 @@
2
2
 
3
3
  # roam-code
4
4
 
5
- **The architectural intelligence layer for AI coding agents. Structural graph, architecture governance, multi-agent orchestration, vulnerability mapping, runtime analysis -- one CLI, zero API keys.**
5
+ **Architectural sight for AI coding agents before they edit.**
6
6
 
7
- *152 commands · 118 MCP tools · 27 languages · 100% local*
7
+ A local code graph (SQLite + tree-sitter + git history) that gives any agent — Claude Code, Cursor, Aider, Continue, your own — five high-leverage verbs: `understand`, `retrieve`, `context`, `preflight`, `critique`. The other 148 specialised commands are advanced surface for specialised workflows.
8
+
9
+ *154 commands · 120 MCP tools · 27 languages · 100% local · zero API keys*
8
10
 
9
11
  [![PyPI version](https://img.shields.io/pypi/v/roam-code?style=flat-square&color=blue)](https://pypi.org/project/roam-code/)
10
12
  [![GitHub stars](https://img.shields.io/github/stars/Cranot/roam-code?style=flat-square)](https://github.com/Cranot/roam-code/stargazers)
@@ -23,7 +25,7 @@ Roam is a structural intelligence engine for software. It pre-indexes your codeb
23
25
  Unlike LSPs (editor-bound, language-specific) or Sourcegraph (hosted search), Roam provides architecture-level graph queries -- offline, cross-language, and compact. It goes beyond comprehension: Roam governs architecture through budget gates, simulates refactoring outcomes, orchestrates multi-agent swarms with zero-conflict guarantees, maps vulnerability reachability paths, and enables graph-level code editing without syntax errors.
24
26
 
25
27
  ```
26
- Codebase ──> [Index] ──> Semantic Graph ──> 139 Commands ──> AI Agent
28
+ Codebase ──> [Index] ──> Semantic Graph ──> 152 Commands ──> AI Agent
27
29
  │ │ │
28
30
  tree-sitter symbols comprehend
29
31
  27 languages + edges govern
@@ -31,6 +33,21 @@ Codebase ──> [Index] ──> Semantic Graph ──> 139 Commands ──> AI
31
33
  runtime traces + architecture orchestrate
32
34
  ```
33
35
 
36
+ ### Start here — the 5 verbs that cover ~80% of agent workflows
37
+
38
+ ```bash
39
+ pip install roam-code
40
+
41
+ cd your-repo/
42
+ roam understand # 1. landing pad — what is this codebase?
43
+ roam retrieve "where is auth?" # 2. graph-aware retrieval for free-form tasks
44
+ roam context AuthService # 3. exact files+lines to read before changing
45
+ roam preflight AuthService # 4. blast radius + tests + complexity check
46
+ git diff | roam critique # 5. patch verifier — clones-not-edited, hot-path
47
+ ```
48
+
49
+ That's the full mental model. The other CLI surface — `taint`, `fleet`, `cga`, `simulate`, `mutate`, `partition`, `attest`, `eval-retrieve`, `oracle`, `py-types`, `py-modern`, `dark-matter`, `clones`, `propagation`, `fingerprint`, etc. — is advanced surface for specialised workflows; you'll never need most of them.
50
+
34
51
  ### The problem
35
52
 
36
53
  Coding agents explore codebases inefficiently: dozens of grep/read cycles, high token cost, no structural understanding. Roam replaces this with one graph query:
@@ -81,7 +98,7 @@ $ roam diff # blast radius of uncommitted changes
81
98
  - **`personalized_pagerank()`** in `graph/pagerank.py`: NetworkX `personalization=` wrapper with empty-seed fallback to global PR; biases ranking toward query-relevant nodes for the retrieve reranker.
82
99
  - **`.roam/config.toml`** (new): zero-dep TOML loader (stdlib `tomllib` → `tomli` → in-tree subset parser). Tunable retrieve weights (`alpha`/`beta`/`gamma`/`delta`/`epsilon`), `tokens_per_line`, `lexical_baseline`, `first_stage_token_cap`, `default_budget`, `default_k`, `default_rerank`.
83
100
  - **DX corrections from dogfood pass**: `roam --detail <cmd>` is the canonical group-level flag; misleading "use --detail" hints in 7 commands rewritten to point users at `roam --detail <cmd>`. `--top N` aliased on `complexity`/`algo`/`rules` (`--top 0` means unlimited on `rules`). `roam fingerprint` no longer refuses graphs ≥5,000 symbols (new soft-warn threshold 20k, hard cap 100k).
84
- - **152 CLI commands, 118 MCP tools** (`fleet`, `ask`, `taint`, `cga`, `eval-retrieve` remain CLI-only; v12 exposes `roam_retrieve`, `roam_critique`, `roam_fleet_plan`, plus 5 v12.1 boolean oracles (`roam_oracle_*`) and `roam_taint_classify` as MCP tools). 35-tool `core` preset is the default for token-budget-conscious clients.
101
+ - **154 CLI commands, 120 MCP tools** (`fleet`, `ask`, `cga`, `eval-retrieve` remain CLI-only; v12 exposes `roam_retrieve`, `roam_critique`, `roam_fleet_plan`, plus 5 v12.1 boolean oracles (`roam_oracle_*`), `roam_taint_classify`, `roam_pytest_fixtures`, and `roam_hover` as MCP tools). 35-tool `core` preset is the default for token-budget-conscious clients.
85
102
 
86
103
  ## What's New in v11
87
104
 
@@ -252,7 +269,7 @@ roam health
252
269
 
253
270
  ## Commands
254
271
 
255
- **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 147 commands are detail surface for specialised workflows (taint, fleet, cga, oracle, eval, …) — they're called by agents on demand, not memorised. This is intentional design; under the hood the canonical surface is **152 commands organised into 7 categories**, but you don't need to know that to start.
272
+ **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 149 commands are detail surface for specialised workflows (taint, fleet, cga, oracle, eval, …) — they're called by agents on demand, not memorised. This is intentional design; under the hood the canonical surface is **154 commands organised into 7 categories**, but you don't need to know that to start.
256
273
 
257
274
  <details>
258
275
  <summary><strong>Full command reference</strong></summary>
@@ -291,6 +308,7 @@ roam health
291
308
  | `roam file <path> [--full] [--changed] [--deps-of PATH]` | File skeleton: all definitions with signatures, cognitive load index, health score |
292
309
  | `roam symbol <name> [--full]` | Symbol definition + callers + callees + metrics. Supports `file:symbol` disambiguation |
293
310
  | `roam context <symbol> [--task MODE] [--for-file PATH]` | AI-optimized context: definition + callers + callees + files-to-read with line ranges |
311
+ | `roam hover <symbol>` | One-line architectural summary: kind, location, blast-radius bucket, top caller, top callee. Bounded at ~200 tokens for IDE hover panels |
294
312
  | `roam retrieve <task> [--budget N] [--k N] [--seed-files PATH]` | Graph-aware context for free-form tasks: FTS5 + structural rerank (PageRank + clones) + token budget |
295
313
  | `roam critique [--input DIFF] [--intent TEXT] [--high-callers N]` | Verify a patch against the graph: clones-not-edited + blast radius + intent-vs-semantic-diff. Pipe `git diff` in. Exit 5 on high severity. |
296
314
  | `roam fleet plan <goal> [--n-agents N] [--adapter raw\|composio\|copilot]` | Graph-aware planner: Louvain partition + co-change + PageRank anchors → `.roam-fleet.json` for Composio/Copilot CLI/raw. |
@@ -344,6 +362,7 @@ roam health
344
362
  | `roam complexity [--bumpy-road] [--include-tooling]` | Per-function cognitive complexity (SonarSource-compatible, triangular nesting penalty) + Halstead metrics (volume, difficulty, effort, bugs) + cyclomatic density |
345
363
  | `roam py-types [--detail] [--include-tests] [--ci --min-coverage N]` | Python type-annotation health: % of public functions with full annotations, ``Any`` usage, legacy ``typing.Optional/Dict/List`` (PEP 585/604 modernisation candidates), per-file worst offenders. CI-gateable via ``--ci --min-coverage N`` (exit 5 below threshold). Default-excludes test files |
346
364
  | `roam py-modern [--detail]` | Modern-Python adoption signal: counts walrus operator (PEP 572), match statements (PEP 634), PEP 604 ``X \| None``, PEP 585 ``dict[…]``, PEP 695 type aliases, f-strings vs ``.format()``. Reports type-modernisation % and f-string adoption % to gauge migration progress |
365
+ | `roam pytest-fixtures [SYMBOL] [--max-depth N]` | Inventory pytest fixture chains. With no SYMBOL, prints the project-wide fixture count and the top fixtures by dependent count. With a fixture or test name, walks the implicit fixture-parameter dependency graph to show what each test transitively requires. Resolves through ``conftest.py`` chains |
347
366
  | `roam algo [--task T] [--confidence C] [--profile P]` | Algorithm anti-pattern detection: 23-pattern catalog detects suboptimal algorithms (O(n^2) loops, N+1 queries, quadratic string building, branching recursion, loop-invariant calls) and suggests better approaches with Big-O improvements. Confidence calibration via caller-count + runtime traces, evidence paths, impact scoring, framework-aware N+1 packs, and language-aware fix templates. Alias: `roam math` |
348
367
  | `roam n1 [--confidence C] [--verbose]` | Implicit N+1 I/O detection: finds ORM model computed properties (`$appends`/accessors) that trigger lazy-loaded DB queries in collection contexts. Cross-references with eager loading config. Supports Laravel, Django, Rails, SQLAlchemy, JPA |
349
368
  | `roam over-fetch [--threshold N] [--confidence C]` | Detect models serializing too many fields: large `$fillable` without `$hidden`/`$visible`, direct controller returns bypassing API Resources, poor exposed-to-hidden ratio |
@@ -596,7 +615,7 @@ The sentinel pair `<!-- roam:minimap -->` / `<!-- /roam:minimap -->` is replaced
596
615
  |--------|-------------|
597
616
  | `roam --json <command>` | Structured JSON output with consistent envelope |
598
617
  | `roam --compact <command>` | Token-efficient output: TSV tables, minimal JSON envelope |
599
- | `roam --sarif <command>` | SARIF 2.1.0 output for dead, health, complexity, rules, secrets, and algo (GitHub/CI integration) |
618
+ | `roam --sarif <command>` | SARIF 2.1.0 output for dead, health, complexity, rules, secrets, algo, py-types, py-modern (GitHub/CI integration) |
600
619
  | `roam health --gate` | CI quality gate. Reads `.roam-gates.yml` thresholds. Exit code 5 on failure |
601
620
 
602
621
  </details>
@@ -892,7 +911,7 @@ ROAM_MCP_LITE=0 roam mcp
892
911
  Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`, `roam_complete`, `roam_complexity_report`, `roam_context`, `roam_dead_code`, `roam_deps`, `roam_diagnose`, `roam_diagnose_issue`, `roam_diff`, `roam_expand_toolset`, `roam_explore`, `roam_file_info`, `roam_health`, `roam_impact`, `roam_pr_risk`, `roam_preflight`, `roam_prepare_change`, `roam_review_change`, `roam_search_symbol`, `roam_syntax_check`, `roam_trace`, `roam_understand`, `roam_uses`.
893
912
 
894
913
  <details>
895
- <summary><strong>MCP tool list (all 118)</strong></summary>
914
+ <summary><strong>MCP tool list (all 120)</strong></summary>
896
915
 
897
916
  | Tool | Description |
898
917
  |------|-------------|
@@ -903,6 +922,7 @@ Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`,
903
922
  | `roam_search_symbol` | Find symbols by name |
904
923
  | `roam_complete` | Prefix completion for symbols/paths/commands (FTS5-backed) |
905
924
  | `roam_context` | Files-to-read for modifying a symbol |
925
+ | `roam_hover` | Single-line architectural summary — kind, blast-radius bucket, top caller, top callee |
906
926
  | `roam_retrieve` | Graph-aware context for free-form tasks (FTS5 + structural rerank + token budget) |
907
927
  | `roam_critique` | Verify a patch against the graph (clones-not-edited + blast radius) |
908
928
  | `roam_fleet_plan` | Plan a multi-agent fleet — graph-aware partition emits .roam-fleet.json |
@@ -926,6 +946,7 @@ Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`,
926
946
  | `roam_complexity_report` | Per-symbol cognitive complexity |
927
947
  | `roam_py_types` | Python type-annotation health (% public typed, Any, legacy typing) |
928
948
  | `roam_py_modern` | Modern-Python adoption (walrus, match, PEP 604/585/695, f-strings) |
949
+ | `roam_pytest_fixtures` | pytest fixture chain — top fixtures by dependent count, or per-symbol dependency walk |
929
950
  | `roam_tour` | Auto-generated onboarding guide |
930
951
  | `roam_diagnose` | Root cause analysis for debugging |
931
952
  | `roam_visualize` | Generate Mermaid or DOT architecture diagrams |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "roam-code"
7
- version = "12.7.0"
7
+ version = "12.8.0"
8
8
  description = "Instant codebase comprehension for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -130,7 +130,6 @@ ignore = [
130
130
  "E501", # line-too-long — handled by ruff format; remaining are long strings/SQL
131
131
  "E731", # lambda-assignment — used for concise dispatch tables
132
132
  "E741", # ambiguous variable names (G, l, etc.) — common in graph code
133
- "F841", # unused-variable — often intentional destructuring
134
133
  ]
135
134
 
136
135
  [tool.ruff.lint.per-file-ignores]
@@ -240,6 +240,28 @@ RECIPES: list[Recipe] = [
240
240
  "attack-surface review. Failures gate-able in CI via exit 5."
241
241
  ),
242
242
  ),
243
+ Recipe(
244
+ name="fixture-impact",
245
+ intent=(
246
+ "Show what tests / fixtures break if a pytest fixture is renamed, removed, or has its return shape changed"
247
+ ),
248
+ examples=(
249
+ "if I rename cli_runner what tests break",
250
+ "what depends on the cli_runner fixture",
251
+ "who uses indexed_project",
252
+ "blast radius of indexed_project",
253
+ "find tests that consume mock_db_session",
254
+ ),
255
+ keywords=("fixture", "fixtures", "pytest", "conftest"),
256
+ commands=(("pytest-fixtures", ("{symbol}", "--reverse")),),
257
+ summary=(
258
+ "Walks the implicit pytest fixture-parameter dependency graph "
259
+ "in reverse — fixtures and tests that consume the named fixture "
260
+ "transitively. Pass the fixture name as an identifier (snake_case "
261
+ "or PascalCase). ``--json`` for the full list when output is "
262
+ "capped."
263
+ ),
264
+ ),
243
265
  Recipe(
244
266
  name="dead-code-sweep",
245
267
  intent="Find unused or unreachable symbols ready for deletion",
@@ -210,19 +210,46 @@ def _project_root_for_conn(conn: sqlite3.Connection) -> str:
210
210
  return parent
211
211
 
212
212
 
213
+ # Per-process cache of file-text reads. With 19 detectors each calling
214
+ # ``_file_text`` per file, an uncached implementation does 19×N disk
215
+ # reads. Cache keyed by ``(id(conn), file_id)`` so distinct DB
216
+ # connections don't collide. Cleared at module unload (or via
217
+ # ``_clear_file_text_cache()`` in tests). Bounded in size at 4096
218
+ # entries to avoid unbounded growth on huge repos; LRU-evicted.
219
+ from collections import OrderedDict as _OrderedDict
220
+
221
+ _FILE_TEXT_CACHE: _OrderedDict = _OrderedDict()
222
+ _FILE_TEXT_CACHE_MAX = 4096
223
+
224
+
225
+ def _clear_file_text_cache() -> None:
226
+ """Clear the file-text cache. Call from tests that want clean state."""
227
+ _FILE_TEXT_CACHE.clear()
228
+
229
+
213
230
  def _file_text(conn: sqlite3.Connection, file_id: int) -> str | None:
214
231
  """Read the source text of a file via roam.index — but the index
215
232
  doesn't store source. Instead we read from disk via the file path
216
233
  resolved against the project root (paths in the index are
217
234
  project-relative, so a bare ``open(path)`` fails when the caller
218
235
  isn't sitting at the project root).
219
- Fast (mmap) and safe to no-op on read errors.
236
+
237
+ Caches results across calls within a process so all 19+ detectors
238
+ pay the disk read once per file rather than 19+ times.
220
239
  """
240
+ cache_key = (id(conn), file_id)
241
+ cached = _FILE_TEXT_CACHE.get(cache_key)
242
+ if cached is not None:
243
+ # LRU bump
244
+ _FILE_TEXT_CACHE.move_to_end(cache_key)
245
+ return cached if cached != "" else None # sentinel for "tried, failed"
221
246
  row = conn.execute("SELECT path FROM files WHERE id = ?", (file_id,)).fetchone()
222
247
  if row is None:
248
+ _FILE_TEXT_CACHE[cache_key] = ""
223
249
  return None
224
250
  path = row[0]
225
251
  if not path:
252
+ _FILE_TEXT_CACHE[cache_key] = ""
226
253
  return None
227
254
  # Resolve project-relative path via the DB's location.
228
255
  root = _project_root_for_conn(conn)
@@ -233,9 +260,15 @@ def _file_text(conn: sqlite3.Connection, file_id: int) -> str | None:
233
260
  path = _osp.join(root, path)
234
261
  try:
235
262
  with open(path, encoding="utf-8", errors="replace") as f:
236
- return f.read()
263
+ text = f.read()
237
264
  except OSError:
265
+ _FILE_TEXT_CACHE[cache_key] = ""
238
266
  return None
267
+ _FILE_TEXT_CACHE[cache_key] = text
268
+ # LRU eviction
269
+ if len(_FILE_TEXT_CACHE) > _FILE_TEXT_CACHE_MAX:
270
+ _FILE_TEXT_CACHE.popitem(last=False)
271
+ return text
239
272
 
240
273
 
241
274
  # Triple-quoted strings (greedy across newlines), single/double quoted
@@ -250,27 +283,39 @@ _SINGLE_QUOTE_RE = re.compile(r'("(?:\\.|[^"\\\n])*"|\'(?:\\.|[^\'\\\n])*\')')
250
283
  _COMMENT_RE = re.compile(r"#[^\n]*")
251
284
 
252
285
 
286
+ _STRIP_CACHE: _OrderedDict = _OrderedDict()
287
+ _STRIP_CACHE_MAX = 4096
288
+
289
+
253
290
  def _strip_strings_and_comments(text: str) -> str:
254
291
  """Replace strings + comments with same-length whitespace so the
255
292
  detector regexes don't false-match inside docstrings or comments.
256
293
 
257
294
  Length-preserving so ``text.count("\n", 0, match.start())`` still
258
- yields the original line number. Test the dogfood case:
259
- ``r"def\\s+\\w+\\s*\\(...."`` regex source no longer trips
260
- ``_MUTABLE_DEFAULT_RE`` when it scans python_idioms.py itself.
295
+ yields the original line number. Cached because all detectors
296
+ that strip apply the same transform — without caching we re-strip
297
+ once per detector per file.
261
298
  """
262
299
  if not text:
263
300
  return text
301
+ # Cache key: id() of the original string (CPython gives unique
302
+ # ids while alive). Combined with len() to catch the rare case
303
+ # of id-reuse after string GC.
304
+ key = (id(text), len(text))
305
+ cached = _STRIP_CACHE.get(key)
306
+ if cached is not None:
307
+ _STRIP_CACHE.move_to_end(key)
308
+ return cached
264
309
 
265
310
  def _blank(match: re.Match) -> str:
266
- # Preserve newlines — re-blanking with spaces would collapse
267
- # multi-line docstrings into one logical line for downstream
268
- # regexes that use ``^`` / ``$`` anchors.
269
311
  return "".join(" " if c != "\n" else "\n" for c in match.group(0))
270
312
 
271
313
  out = _TRIPLE_QUOTE_RE.sub(_blank, text)
272
314
  out = _SINGLE_QUOTE_RE.sub(_blank, out)
273
315
  out = _COMMENT_RE.sub(_blank, out)
316
+ _STRIP_CACHE[key] = out
317
+ if len(_STRIP_CACHE) > _STRIP_CACHE_MAX:
318
+ _STRIP_CACHE.popitem(last=False)
274
319
  return out
275
320
 
276
321
 
@@ -932,12 +977,25 @@ def detect_django_n1(conn: sqlite3.Connection) -> list[dict]:
932
977
  text = _strip_strings_and_comments(text)
933
978
  if not text:
934
979
  continue
935
- # Quick reject: skip files with no Django ORM hints
936
- if ".objects." not in text and ".all()" not in text:
980
+ # Require a Django ORM hint anywhere in the file. Without
981
+ # ``.objects.`` (the manager indicator) or an explicit Django
982
+ # import, a ``.all()`` shape might be a custom collection or a
983
+ # different ORM — firing would be a false positive.
984
+ has_django_hint = ".objects." in text or "from django" in text or "import django" in text
985
+ if not has_django_hint:
937
986
  continue
938
987
  sym_index = _line_to_symbol(conn, file_id)
939
988
  for match in _DJANGO_ALL_THEN_FOR.finditer(text):
940
989
  line_no = text.count("\n", 0, match.start()) + 1
990
+ # Suppress when the queryset chain already eager-loads via
991
+ # select_related or prefetch_related. Look back from the
992
+ # ``.all()`` call to the assignment (or 200 chars max) for
993
+ # those calls. They defuse the N+1 — firing here would be a
994
+ # false positive.
995
+ chain_start = max(0, match.start() - 200)
996
+ chain = text[chain_start : match.start()]
997
+ if "select_related(" in chain or "prefetch_related(" in chain:
998
+ continue
941
999
  sym = _enclosing_symbol(line_no, sym_index)
942
1000
  if sym is None:
943
1001
  continue
@@ -1013,6 +1071,19 @@ def detect_sqlalchemy_lazy(conn: sqlite3.Connection) -> list[dict]:
1013
1071
  sym_index = _line_to_symbol(conn, file_id)
1014
1072
  for match in _SQLALCHEMY_ALL_THEN_DOT.finditer(text):
1015
1073
  line_no = text.count("\n", 0, match.start()) + 1
1074
+ # Suppress when the query expression already eager-loads via
1075
+ # joinedload / selectinload / contains_eager. Look back from
1076
+ # the ``.all()`` call to the start of the statement (or 200
1077
+ # chars, whichever is shorter).
1078
+ chain_start = max(0, match.start() - 200)
1079
+ chain = text[chain_start : match.start()]
1080
+ if (
1081
+ "joinedload(" in chain
1082
+ or "selectinload(" in chain
1083
+ or "contains_eager(" in chain
1084
+ or "subqueryload(" in chain
1085
+ ):
1086
+ continue
1016
1087
  sym = _enclosing_symbol(line_no, sym_index)
1017
1088
  if sym is None:
1018
1089
  continue
@@ -56,6 +56,8 @@ _COMMANDS = {
56
56
  "complexity": ("roam.commands.cmd_complexity", "complexity"),
57
57
  "py-types": ("roam.commands.cmd_py_types", "py_types"),
58
58
  "py-modern": ("roam.commands.cmd_py_modern", "py_modern"),
59
+ "pytest-fixtures": ("roam.commands.cmd_pytest_fixtures", "pytest_fixtures"),
60
+ "hover": ("roam.commands.cmd_hover", "hover"),
59
61
  "debt": ("roam.commands.cmd_debt", "debt"),
60
62
  "conventions": ("roam.commands.cmd_conventions", "conventions"),
61
63
  "bus-factor": ("roam.commands.cmd_bus_factor", "bus_factor"),
@@ -219,6 +221,7 @@ _CATEGORIES = {
219
221
  "verify-imports",
220
222
  "diff",
221
223
  "context",
224
+ "hover",
222
225
  "retrieve",
223
226
  "critique",
224
227
  "fleet",
@@ -245,6 +248,7 @@ _CATEGORIES = {
245
248
  "complexity",
246
249
  "py-types",
247
250
  "py-modern",
251
+ "pytest-fixtures",
248
252
  "algo",
249
253
  "n1",
250
254
  "over-fetch",
@@ -0,0 +1,130 @@
1
+ """``roam hover`` — single-line architectural summary suitable for an
2
+ IDE hover panel or chat-inline reference.
3
+
4
+ Designed to fit in ~200 tokens regardless of the symbol. Where ``roam
5
+ context`` returns a full briefing (signature, callers, related files,
6
+ tests, model fields, etc.), ``roam hover`` returns the minimum useful
7
+ gloss: kind, qualified name, location, blast-radius bucket, top
8
+ caller, top callee. Pairs with a hover-on-symbol IDE plugin.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import click
14
+
15
+ from roam.commands.resolve import ensure_index, find_symbol, symbol_not_found
16
+ from roam.db.connection import open_db
17
+ from roam.output.formatter import abbrev_kind, json_envelope, loc, to_json
18
+
19
+
20
+ def _blast_bucket(in_degree: int) -> str:
21
+ """Coarse classifier: how nervous should an editor be about
22
+ changing this symbol? Buckets match ``roam impact`` thresholds."""
23
+ if in_degree >= 50:
24
+ return "large"
25
+ if in_degree >= 10:
26
+ return "moderate"
27
+ if in_degree >= 1:
28
+ return "small"
29
+ return "none"
30
+
31
+
32
+ def _top_neighbour(conn, sym_id: int, *, direction: str) -> dict | None:
33
+ """Highest-PageRank caller (direction='in') or callee (direction='out')."""
34
+ if direction == "in":
35
+ edge_clause = "e.target_id = ? AND s.id = e.source_id"
36
+ else:
37
+ edge_clause = "e.source_id = ? AND s.id = e.target_id"
38
+ rows = conn.execute(
39
+ f"""
40
+ SELECT s.id, s.name, s.qualified_name, s.kind, f.path AS file_path,
41
+ s.line_start, COALESCE(gm.pagerank, 0) AS pr
42
+ FROM edges e
43
+ JOIN symbols s ON {edge_clause}
44
+ JOIN files f ON s.file_id = f.id
45
+ LEFT JOIN graph_metrics gm ON gm.symbol_id = s.id
46
+ WHERE e.kind IN ('call', 'inherits', 'imports')
47
+ ORDER BY pr DESC
48
+ LIMIT 1
49
+ """,
50
+ (sym_id,),
51
+ ).fetchall()
52
+ if not rows:
53
+ return None
54
+ r = rows[0]
55
+ return {
56
+ "name": r["qualified_name"] or r["name"],
57
+ "kind": r["kind"],
58
+ "file_path": r["file_path"],
59
+ "line_start": r["line_start"],
60
+ }
61
+
62
+
63
+ @click.command()
64
+ @click.argument("symbol")
65
+ @click.pass_context
66
+ def hover(ctx, symbol: str):
67
+ """Show a one-line architectural summary for SYMBOL.
68
+
69
+ Output is bounded at ~200 tokens regardless of the symbol — kind,
70
+ qualified name, location, blast-radius bucket, top caller, top
71
+ callee. Designed for IDE hover panels and chat-inline references
72
+ where ``roam context`` is too verbose.
73
+ """
74
+ json_mode = ctx.obj.get("json") if ctx.obj else False
75
+ ensure_index()
76
+
77
+ with open_db(readonly=True) as conn:
78
+ sym = find_symbol(conn, symbol)
79
+ if sym is None:
80
+ click.echo(symbol_not_found(conn, symbol, json_mode=json_mode))
81
+ raise SystemExit(1)
82
+ sym_id = sym["id"]
83
+
84
+ metrics = conn.execute(
85
+ "SELECT in_degree, out_degree, pagerank FROM graph_metrics WHERE symbol_id = ?",
86
+ (sym_id,),
87
+ ).fetchone()
88
+ in_d = metrics["in_degree"] if metrics else 0
89
+ out_d = metrics["out_degree"] if metrics else 0
90
+ pr = float(metrics["pagerank"] or 0) if metrics else 0.0
91
+
92
+ bucket = _blast_bucket(in_d)
93
+ top_caller = _top_neighbour(conn, sym_id, direction="in")
94
+ top_callee = _top_neighbour(conn, sym_id, direction="out")
95
+
96
+ qn = sym["qualified_name"] or sym["name"]
97
+ file_loc = loc(sym["file_path"], sym["line_start"])
98
+ kind_short = abbrev_kind(sym["kind"])
99
+
100
+ if json_mode:
101
+ click.echo(
102
+ to_json(
103
+ json_envelope(
104
+ "hover",
105
+ summary={
106
+ "verdict": (f"{kind_short} {qn} — {bucket} blast radius ({in_d} in, {out_d} out)"),
107
+ "kind": sym["kind"],
108
+ "qualified_name": qn,
109
+ "file_path": sym["file_path"],
110
+ "line_start": sym["line_start"],
111
+ "in_degree": in_d,
112
+ "out_degree": out_d,
113
+ "pagerank": round(pr, 6),
114
+ "blast_bucket": bucket,
115
+ },
116
+ top_caller=top_caller,
117
+ top_callee=top_callee,
118
+ )
119
+ )
120
+ )
121
+ return
122
+
123
+ click.echo(f"{kind_short} {qn} {file_loc}")
124
+ click.echo(f" blast radius: {bucket} ({in_d} callers, {out_d} callees, pr={pr:.4f})")
125
+ if top_caller:
126
+ c_loc = loc(top_caller["file_path"], top_caller["line_start"])
127
+ click.echo(f" top caller: {top_caller['name']} {c_loc}")
128
+ if top_callee:
129
+ c_loc = loc(top_callee["file_path"], top_callee["line_start"])
130
+ click.echo(f" top callee: {top_callee['name']} {c_loc}")
@@ -196,6 +196,12 @@ def _check_blast_radius(conn, sym_ids, file_paths):
196
196
  # ---------------------------------------------------------------------------
197
197
 
198
198
 
199
+ # Once the blast radius dumps every test file in the repo, the
200
+ # "Suggested tests:" line stops being a suggestion and becomes a wall
201
+ # of text. This cap is the boundary between actionable and unactionable.
202
+ _MAX_SUGGESTED_TEST_FILES = 15
203
+
204
+
199
205
  def _check_affected_tests(conn, sym_ids, file_paths):
200
206
  """Find tests that need to run."""
201
207
  results = _gather_affected_tests(conn, sym_ids, file_paths)
@@ -212,7 +218,13 @@ def _check_affected_tests(conn, sym_ids, file_paths):
212
218
  seen.add(r["file"])
213
219
  test_files.append(r["file"])
214
220
 
215
- pytest_cmd = "pytest " + " ".join(test_files) if test_files else ""
221
+ truncated_files = len(test_files) > _MAX_SUGGESTED_TEST_FILES
222
+ if truncated_files:
223
+ suggested = test_files[:_MAX_SUGGESTED_TEST_FILES]
224
+ suffix = f" # (+{len(test_files) - _MAX_SUGGESTED_TEST_FILES} more)"
225
+ pytest_cmd = "pytest " + " ".join(suggested) + suffix
226
+ else:
227
+ pytest_cmd = "pytest " + " ".join(test_files) if test_files else ""
216
228
  severity = _test_severity(direct, transitive, colocated)
217
229
 
218
230
  return {
@@ -222,6 +234,7 @@ def _check_affected_tests(conn, sym_ids, file_paths):
222
234
  "total": len(results),
223
235
  "test_files": test_files,
224
236
  "pytest_command": pytest_cmd,
237
+ "pytest_command_truncated": truncated_files,
225
238
  "severity": severity,
226
239
  }
227
240