roam-code 12.20__tar.gz → 12.22__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 (618) hide show
  1. {roam_code-12.20/src/roam_code.egg-info → roam_code-12.22}/PKG-INFO +1 -1
  2. {roam_code-12.20 → roam_code-12.22}/pyproject.toml +1 -1
  3. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_context.py +13 -2
  4. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_impact.py +24 -5
  5. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_sbom.py +74 -58
  6. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_watch.py +78 -57
  7. {roam_code-12.20 → roam_code-12.22}/src/roam/competitor_site_data.py +1 -1
  8. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/partition.py +103 -93
  9. {roam_code-12.20 → roam_code-12.22}/src/roam/index/indexer.py +8 -1
  10. {roam_code-12.20 → roam_code-12.22}/src/roam/mcp-server-card.json +1 -1
  11. {roam_code-12.20 → roam_code-12.22/src/roam_code.egg-info}/PKG-INFO +1 -1
  12. {roam_code-12.20 → roam_code-12.22}/src/roam_code.egg-info/SOURCES.txt +2 -0
  13. {roam_code-12.20 → roam_code-12.22}/tests/test_health_gate.py +8 -4
  14. {roam_code-12.20 → roam_code-12.22}/tests/test_mcp_extras.py +15 -1
  15. {roam_code-12.20 → roam_code-12.22}/tests/test_v1216_passes.py +28 -1
  16. roam_code-12.22/tests/test_v1221_query_timeout.py +95 -0
  17. roam_code-12.22/tests/test_v1221_untested_commands.py +100 -0
  18. {roam_code-12.20 → roam_code-12.22}/LICENSE +0 -0
  19. {roam_code-12.20 → roam_code-12.22}/README.md +0 -0
  20. {roam_code-12.20 → roam_code-12.22}/setup.cfg +0 -0
  21. {roam_code-12.20 → roam_code-12.22}/src/roam/__init__.py +0 -0
  22. {roam_code-12.20 → roam_code-12.22}/src/roam/__main__.py +0 -0
  23. {roam_code-12.20 → roam_code-12.22}/src/roam/analysis/__init__.py +0 -0
  24. {roam_code-12.20 → roam_code-12.22}/src/roam/analysis/effects.py +0 -0
  25. {roam_code-12.20 → roam_code-12.22}/src/roam/analysis/taint.py +0 -0
  26. {roam_code-12.20 → roam_code-12.22}/src/roam/api.py +0 -0
  27. {roam_code-12.20 → roam_code-12.22}/src/roam/ask/__init__.py +0 -0
  28. {roam_code-12.20 → roam_code-12.22}/src/roam/ask/classifier.py +0 -0
  29. {roam_code-12.20 → roam_code-12.22}/src/roam/ask/recipes.py +0 -0
  30. {roam_code-12.20 → roam_code-12.22}/src/roam/ask/runner.py +0 -0
  31. {roam_code-12.20 → roam_code-12.22}/src/roam/ask/workflow.py +0 -0
  32. {roam_code-12.20 → roam_code-12.22}/src/roam/attest/__init__.py +0 -0
  33. {roam_code-12.20 → roam_code-12.22}/src/roam/attest/cga.py +0 -0
  34. {roam_code-12.20 → roam_code-12.22}/src/roam/bridges/__init__.py +0 -0
  35. {roam_code-12.20 → roam_code-12.22}/src/roam/bridges/base.py +0 -0
  36. {roam_code-12.20 → roam_code-12.22}/src/roam/bridges/bridge_config.py +0 -0
  37. {roam_code-12.20 → roam_code-12.22}/src/roam/bridges/bridge_django.py +0 -0
  38. {roam_code-12.20 → roam_code-12.22}/src/roam/bridges/bridge_protobuf.py +0 -0
  39. {roam_code-12.20 → roam_code-12.22}/src/roam/bridges/bridge_rest_api.py +0 -0
  40. {roam_code-12.20 → roam_code-12.22}/src/roam/bridges/bridge_salesforce.py +0 -0
  41. {roam_code-12.20 → roam_code-12.22}/src/roam/bridges/bridge_template.py +0 -0
  42. {roam_code-12.20 → roam_code-12.22}/src/roam/bridges/registry.py +0 -0
  43. {roam_code-12.20 → roam_code-12.22}/src/roam/catalog/__init__.py +0 -0
  44. {roam_code-12.20 → roam_code-12.22}/src/roam/catalog/detectors.py +0 -0
  45. {roam_code-12.20 → roam_code-12.22}/src/roam/catalog/fixes.py +0 -0
  46. {roam_code-12.20 → roam_code-12.22}/src/roam/catalog/python_idioms.py +0 -0
  47. {roam_code-12.20 → roam_code-12.22}/src/roam/catalog/smells.py +0 -0
  48. {roam_code-12.20 → roam_code-12.22}/src/roam/catalog/tasks.py +0 -0
  49. {roam_code-12.20 → roam_code-12.22}/src/roam/cli.py +0 -0
  50. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/__init__.py +0 -0
  51. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/changed_files.py +0 -0
  52. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_adrs.py +0 -0
  53. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_adversarial.py +0 -0
  54. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_affected.py +0 -0
  55. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_affected_tests.py +0 -0
  56. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_agent_context.py +0 -0
  57. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_agent_export.py +0 -0
  58. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_agent_plan.py +0 -0
  59. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_ai_ratio.py +0 -0
  60. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_ai_readiness.py +0 -0
  61. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_alerts.py +0 -0
  62. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_annotate.py +0 -0
  63. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_api.py +0 -0
  64. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_api_changes.py +0 -0
  65. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_api_drift.py +0 -0
  66. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_ask.py +0 -0
  67. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_attest.py +0 -0
  68. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_audit.py +0 -0
  69. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_auth_gaps.py +0 -0
  70. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_bisect.py +0 -0
  71. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_breaking.py +0 -0
  72. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_budget.py +0 -0
  73. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_bus_factor.py +0 -0
  74. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_capsule.py +0 -0
  75. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_cga.py +0 -0
  76. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_changelog.py +0 -0
  77. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_check_rules.py +0 -0
  78. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_ci_setup.py +0 -0
  79. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_clean.py +0 -0
  80. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_clones.py +0 -0
  81. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_closure.py +0 -0
  82. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_clusters.py +0 -0
  83. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_codeowners.py +0 -0
  84. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_complexity.py +0 -0
  85. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_config.py +0 -0
  86. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_congestion.py +0 -0
  87. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_conventions.py +0 -0
  88. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_coupling.py +0 -0
  89. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_coverage_gaps.py +0 -0
  90. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_critique.py +0 -0
  91. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_cut.py +0 -0
  92. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_dark_matter.py +0 -0
  93. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_dashboard.py +0 -0
  94. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_dead.py +0 -0
  95. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_debt.py +0 -0
  96. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_deps.py +0 -0
  97. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_describe.py +0 -0
  98. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_dev_profile.py +0 -0
  99. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_diagnose.py +0 -0
  100. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_diff.py +0 -0
  101. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_disambiguate.py +0 -0
  102. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_doc_staleness.py +0 -0
  103. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_docs_coverage.py +0 -0
  104. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_doctor.py +0 -0
  105. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_drift.py +0 -0
  106. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_duplicates.py +0 -0
  107. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_effects.py +0 -0
  108. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_endpoints.py +0 -0
  109. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_entry_points.py +0 -0
  110. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_eval_retrieve.py +0 -0
  111. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_exit_codes.py +0 -0
  112. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_fan.py +0 -0
  113. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_file.py +0 -0
  114. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_fingerprint.py +0 -0
  115. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_fitness.py +0 -0
  116. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_flag_dead.py +0 -0
  117. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_fleet.py +0 -0
  118. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_fn_coupling.py +0 -0
  119. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_forecast.py +0 -0
  120. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_graph_export.py +0 -0
  121. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_graph_stats.py +0 -0
  122. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_grep.py +0 -0
  123. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_guard.py +0 -0
  124. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_health.py +0 -0
  125. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_help_search.py +0 -0
  126. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_hooks.py +0 -0
  127. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_hotspots.py +0 -0
  128. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_hover.py +0 -0
  129. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_index.py +0 -0
  130. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_index_bundle.py +0 -0
  131. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_index_stats.py +0 -0
  132. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_ingest_trace.py +0 -0
  133. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_init.py +0 -0
  134. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_intent.py +0 -0
  135. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_invariants.py +0 -0
  136. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_layers.py +0 -0
  137. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_map.py +0 -0
  138. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_math.py +0 -0
  139. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_mcp_setup.py +0 -0
  140. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_mcp_status.py +0 -0
  141. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_metrics.py +0 -0
  142. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_migration_safety.py +0 -0
  143. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_minimap.py +0 -0
  144. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_missing_index.py +0 -0
  145. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_module.py +0 -0
  146. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_mutate.py +0 -0
  147. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_n1.py +0 -0
  148. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_oracle.py +0 -0
  149. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_orchestrate.py +0 -0
  150. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_orphan_imports.py +0 -0
  151. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_orphan_routes.py +0 -0
  152. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_over_fetch.py +0 -0
  153. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_owner.py +0 -0
  154. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_partition.py +0 -0
  155. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_path_coverage.py +0 -0
  156. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_patterns.py +0 -0
  157. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_plan.py +0 -0
  158. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_plan_refactor.py +0 -0
  159. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_plugins.py +0 -0
  160. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_pr_diff.py +0 -0
  161. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_pr_prep.py +0 -0
  162. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_pr_risk.py +0 -0
  163. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_pre_commit.py +0 -0
  164. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_preflight.py +0 -0
  165. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_py_modern.py +0 -0
  166. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_py_types.py +0 -0
  167. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_pytest_fixtures.py +0 -0
  168. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_recipes.py +0 -0
  169. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_recommend.py +0 -0
  170. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_relate.py +0 -0
  171. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_report.py +0 -0
  172. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_reset.py +0 -0
  173. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_retrieve.py +0 -0
  174. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_risk.py +0 -0
  175. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_rules.py +0 -0
  176. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_safe_delete.py +0 -0
  177. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_safe_zones.py +0 -0
  178. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_schema.py +0 -0
  179. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_search.py +0 -0
  180. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_search_semantic.py +0 -0
  181. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_secrets.py +0 -0
  182. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_semantic_diff.py +0 -0
  183. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_simulate.py +0 -0
  184. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_simulate_departure.py +0 -0
  185. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_sketch.py +0 -0
  186. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_smells.py +0 -0
  187. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_spectral.py +0 -0
  188. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_split.py +0 -0
  189. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_stats.py +0 -0
  190. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
  191. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
  192. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_supply_chain.py +0 -0
  193. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_symbol.py +0 -0
  194. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_syntax_check.py +0 -0
  195. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_taint.py +0 -0
  196. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_telemetry.py +0 -0
  197. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_test_gaps.py +0 -0
  198. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_test_impact.py +0 -0
  199. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_test_pyramid.py +0 -0
  200. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_test_scaffold.py +0 -0
  201. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_testmap.py +0 -0
  202. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_timeline.py +0 -0
  203. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_tour.py +0 -0
  204. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_trace.py +0 -0
  205. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_trends.py +0 -0
  206. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_triage.py +0 -0
  207. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_understand.py +0 -0
  208. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_uses.py +0 -0
  209. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_verify.py +0 -0
  210. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_verify_imports.py +0 -0
  211. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_version.py +0 -0
  212. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_vibe_check.py +0 -0
  213. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_visualize.py +0 -0
  214. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_vuln_map.py +0 -0
  215. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_vuln_reach.py +0 -0
  216. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_vulns.py +0 -0
  217. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_weather.py +0 -0
  218. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_why.py +0 -0
  219. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_why_fail.py +0 -0
  220. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_workflow.py +0 -0
  221. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_ws.py +0 -0
  222. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/cmd_xlang.py +0 -0
  223. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/codeowners_helpers.py +0 -0
  224. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/context_helpers.py +0 -0
  225. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/gate_presets.py +0 -0
  226. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/graph_helpers.py +0 -0
  227. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/metrics_history.py +0 -0
  228. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/next_steps.py +0 -0
  229. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/resolve.py +0 -0
  230. {roam_code-12.20 → roam_code-12.22}/src/roam/commands/suppression.py +0 -0
  231. {roam_code-12.20 → roam_code-12.22}/src/roam/config.py +0 -0
  232. {roam_code-12.20 → roam_code-12.22}/src/roam/coverage_reports.py +0 -0
  233. {roam_code-12.20 → roam_code-12.22}/src/roam/critique/__init__.py +0 -0
  234. {roam_code-12.20 → roam_code-12.22}/src/roam/critique/aggregator.py +0 -0
  235. {roam_code-12.20 → roam_code-12.22}/src/roam/critique/checks.py +0 -0
  236. {roam_code-12.20 → roam_code-12.22}/src/roam/db/__init__.py +0 -0
  237. {roam_code-12.20 → roam_code-12.22}/src/roam/db/connection.py +0 -0
  238. {roam_code-12.20 → roam_code-12.22}/src/roam/db/queries.py +0 -0
  239. {roam_code-12.20 → roam_code-12.22}/src/roam/db/schema.py +0 -0
  240. {roam_code-12.20 → roam_code-12.22}/src/roam/eval/__init__.py +0 -0
  241. {roam_code-12.20 → roam_code-12.22}/src/roam/eval/harness.py +0 -0
  242. {roam_code-12.20 → roam_code-12.22}/src/roam/exit_codes.py +0 -0
  243. {roam_code-12.20 → roam_code-12.22}/src/roam/fleet/__init__.py +0 -0
  244. {roam_code-12.20 → roam_code-12.22}/src/roam/fleet/adapters.py +0 -0
  245. {roam_code-12.20 → roam_code-12.22}/src/roam/fleet/manifest.py +0 -0
  246. {roam_code-12.20 → roam_code-12.22}/src/roam/git_utils.py +0 -0
  247. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/__init__.py +0 -0
  248. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/anomaly.py +0 -0
  249. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/builder.py +0 -0
  250. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/clone_detect.py +0 -0
  251. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/clusters.py +0 -0
  252. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/cycles.py +0 -0
  253. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/dark_matter.py +0 -0
  254. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/diff.py +0 -0
  255. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/fingerprint.py +0 -0
  256. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/layers.py +0 -0
  257. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/pagerank.py +0 -0
  258. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/pathfinding.py +0 -0
  259. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/propagation.py +0 -0
  260. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/simulate.py +0 -0
  261. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/spectral.py +0 -0
  262. {roam_code-12.20 → roam_code-12.22}/src/roam/graph/stats.py +0 -0
  263. {roam_code-12.20 → roam_code-12.22}/src/roam/index/__init__.py +0 -0
  264. {roam_code-12.20 → roam_code-12.22}/src/roam/index/complexity.py +0 -0
  265. {roam_code-12.20 → roam_code-12.22}/src/roam/index/discovery.py +0 -0
  266. {roam_code-12.20 → roam_code-12.22}/src/roam/index/django_post.py +0 -0
  267. {roam_code-12.20 → roam_code-12.22}/src/roam/index/file_roles.py +0 -0
  268. {roam_code-12.20 → roam_code-12.22}/src/roam/index/git_stats.py +0 -0
  269. {roam_code-12.20 → roam_code-12.22}/src/roam/index/gitignore.py +0 -0
  270. {roam_code-12.20 → roam_code-12.22}/src/roam/index/incremental.py +0 -0
  271. {roam_code-12.20 → roam_code-12.22}/src/roam/index/parser.py +0 -0
  272. {roam_code-12.20 → roam_code-12.22}/src/roam/index/pytest_fixtures.py +0 -0
  273. {roam_code-12.20 → roam_code-12.22}/src/roam/index/registry_dispatch.py +0 -0
  274. {roam_code-12.20 → roam_code-12.22}/src/roam/index/relations.py +0 -0
  275. {roam_code-12.20 → roam_code-12.22}/src/roam/index/symbols.py +0 -0
  276. {roam_code-12.20 → roam_code-12.22}/src/roam/index/test_conventions.py +0 -0
  277. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/__init__.py +0 -0
  278. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/apex_lang.py +0 -0
  279. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/aura_lang.py +0 -0
  280. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/base.py +0 -0
  281. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/c_lang.py +0 -0
  282. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/csharp_lang.py +0 -0
  283. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/extractor_schema.py +0 -0
  284. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/extractors/kotlin.yaml +0 -0
  285. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/foxpro_lang.py +0 -0
  286. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/generic_lang.py +0 -0
  287. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/go_lang.py +0 -0
  288. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/hcl_lang.py +0 -0
  289. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/java_lang.py +0 -0
  290. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/javascript_lang.py +0 -0
  291. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/kotlin_lang.py +0 -0
  292. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/php_lang.py +0 -0
  293. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/python_lang.py +0 -0
  294. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/query_engine.py +0 -0
  295. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/registry.py +0 -0
  296. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/ruby_lang.py +0 -0
  297. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/rust_lang.py +0 -0
  298. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/scala_lang.py +0 -0
  299. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/sfxml_lang.py +0 -0
  300. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/sql_lang.py +0 -0
  301. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/swift_lang.py +0 -0
  302. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/typescript_lang.py +0 -0
  303. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/visualforce_lang.py +0 -0
  304. {roam_code-12.20 → roam_code-12.22}/src/roam/languages/yaml_lang.py +0 -0
  305. {roam_code-12.20 → roam_code-12.22}/src/roam/mcp_extras/__init__.py +0 -0
  306. {roam_code-12.20 → roam_code-12.22}/src/roam/mcp_extras/completions.py +0 -0
  307. {roam_code-12.20 → roam_code-12.22}/src/roam/mcp_extras/concurrency.py +0 -0
  308. {roam_code-12.20 → roam_code-12.22}/src/roam/mcp_extras/progress.py +0 -0
  309. {roam_code-12.20 → roam_code-12.22}/src/roam/mcp_extras/sampling.py +0 -0
  310. {roam_code-12.20 → roam_code-12.22}/src/roam/mcp_extras/session.py +0 -0
  311. {roam_code-12.20 → roam_code-12.22}/src/roam/mcp_extras/watcher.py +0 -0
  312. {roam_code-12.20 → roam_code-12.22}/src/roam/mcp_server.py +0 -0
  313. {roam_code-12.20 → roam_code-12.22}/src/roam/observability.py +0 -0
  314. {roam_code-12.20 → roam_code-12.22}/src/roam/output/__init__.py +0 -0
  315. {roam_code-12.20 → roam_code-12.22}/src/roam/output/confidence.py +0 -0
  316. {roam_code-12.20 → roam_code-12.22}/src/roam/output/errors.py +0 -0
  317. {roam_code-12.20 → roam_code-12.22}/src/roam/output/file_role_hints.py +0 -0
  318. {roam_code-12.20 → roam_code-12.22}/src/roam/output/formatter.py +0 -0
  319. {roam_code-12.20 → roam_code-12.22}/src/roam/output/framework_filter.py +0 -0
  320. {roam_code-12.20 → roam_code-12.22}/src/roam/output/mermaid.py +0 -0
  321. {roam_code-12.20 → roam_code-12.22}/src/roam/output/project_shape.py +0 -0
  322. {roam_code-12.20 → roam_code-12.22}/src/roam/output/sarif.py +0 -0
  323. {roam_code-12.20 → roam_code-12.22}/src/roam/output/schema_registry.py +0 -0
  324. {roam_code-12.20 → roam_code-12.22}/src/roam/plugins.py +0 -0
  325. {roam_code-12.20 → roam_code-12.22}/src/roam/refactor/__init__.py +0 -0
  326. {roam_code-12.20 → roam_code-12.22}/src/roam/refactor/codegen.py +0 -0
  327. {roam_code-12.20 → roam_code-12.22}/src/roam/refactor/transforms.py +0 -0
  328. {roam_code-12.20 → roam_code-12.22}/src/roam/retrieve/__init__.py +0 -0
  329. {roam_code-12.20 → roam_code-12.22}/src/roam/retrieve/learned_ranker.py +0 -0
  330. {roam_code-12.20 → roam_code-12.22}/src/roam/retrieve/pipeline.py +0 -0
  331. {roam_code-12.20 → roam_code-12.22}/src/roam/retrieve/rerank.py +0 -0
  332. {roam_code-12.20 → roam_code-12.22}/src/roam/retrieve/seeds.py +0 -0
  333. {roam_code-12.20 → roam_code-12.22}/src/roam/retrieve/semantic.py +0 -0
  334. {roam_code-12.20 → roam_code-12.22}/src/roam/rules/__init__.py +0 -0
  335. {roam_code-12.20 → roam_code-12.22}/src/roam/rules/ast_match.py +0 -0
  336. {roam_code-12.20 → roam_code-12.22}/src/roam/rules/builtin.py +0 -0
  337. {roam_code-12.20 → roam_code-12.22}/src/roam/rules/dataflow.py +0 -0
  338. {roam_code-12.20 → roam_code-12.22}/src/roam/rules/engine.py +0 -0
  339. {roam_code-12.20 → roam_code-12.22}/src/roam/runtime/__init__.py +0 -0
  340. {roam_code-12.20 → roam_code-12.22}/src/roam/runtime/daemon.py +0 -0
  341. {roam_code-12.20 → roam_code-12.22}/src/roam/runtime/graph_backend.py +0 -0
  342. {roam_code-12.20 → roam_code-12.22}/src/roam/runtime/hotspots.py +0 -0
  343. {roam_code-12.20 → roam_code-12.22}/src/roam/runtime/lock_modes.py +0 -0
  344. {roam_code-12.20 → roam_code-12.22}/src/roam/runtime/lockmgr.py +0 -0
  345. {roam_code-12.20 → roam_code-12.22}/src/roam/runtime/trace_ingest.py +0 -0
  346. {roam_code-12.20 → roam_code-12.22}/src/roam/search/__init__.py +0 -0
  347. {roam_code-12.20 → roam_code-12.22}/src/roam/search/framework_packs.py +0 -0
  348. {roam_code-12.20 → roam_code-12.22}/src/roam/search/index_embeddings.py +0 -0
  349. {roam_code-12.20 → roam_code-12.22}/src/roam/search/onnx_embeddings.py +0 -0
  350. {roam_code-12.20 → roam_code-12.22}/src/roam/search/tfidf.py +0 -0
  351. {roam_code-12.20 → roam_code-12.22}/src/roam/security/__init__.py +0 -0
  352. {roam_code-12.20 → roam_code-12.22}/src/roam/security/aibom_extension.py +0 -0
  353. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_classifier.py +0 -0
  354. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_engine.py +0 -0
  355. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/api_error_leak.yaml +0 -0
  356. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/java_fileupload_path_traversal.yaml +0 -0
  357. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/js_insecure_jwt_decode.yaml +0 -0
  358. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/js_localstorage_secrets.yaml +0 -0
  359. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/js_prototype_pollution.yaml +0 -0
  360. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/js_ssrf.yaml +0 -0
  361. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/js_xss.yaml +0 -0
  362. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/python_basic.yaml +0 -0
  363. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/python_deserialization.yaml +0 -0
  364. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/python_path_traversal.yaml +0 -0
  365. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/python_socketio_remote_source.yaml +0 -0
  366. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/python_sqli.yaml +0 -0
  367. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/python_urllib_open_redirect.yaml +0 -0
  368. {roam_code-12.20 → roam_code-12.22}/src/roam/security/taint_rules/vue_v_html.yaml +0 -0
  369. {roam_code-12.20 → roam_code-12.22}/src/roam/security/vuln_reach.py +0 -0
  370. {roam_code-12.20 → roam_code-12.22}/src/roam/security/vuln_store.py +0 -0
  371. {roam_code-12.20 → roam_code-12.22}/src/roam/surface_counts.py +0 -0
  372. {roam_code-12.20 → roam_code-12.22}/src/roam/telemetry.py +0 -0
  373. {roam_code-12.20 → roam_code-12.22}/src/roam/templates/__init__.py +0 -0
  374. {roam_code-12.20 → roam_code-12.22}/src/roam/templates/ci/Jenkinsfile +0 -0
  375. {roam_code-12.20 → roam_code-12.22}/src/roam/templates/ci/__init__.py +0 -0
  376. {roam_code-12.20 → roam_code-12.22}/src/roam/templates/ci/azure-pipelines.yml +0 -0
  377. {roam_code-12.20 → roam_code-12.22}/src/roam/templates/ci/bitbucket-pipelines.yml +0 -0
  378. {roam_code-12.20 → roam_code-12.22}/src/roam/templates/ci/gitlab-ci.yml +0 -0
  379. {roam_code-12.20 → roam_code-12.22}/src/roam/workspace/__init__.py +0 -0
  380. {roam_code-12.20 → roam_code-12.22}/src/roam/workspace/aggregator.py +0 -0
  381. {roam_code-12.20 → roam_code-12.22}/src/roam/workspace/api_scanner.py +0 -0
  382. {roam_code-12.20 → roam_code-12.22}/src/roam/workspace/config.py +0 -0
  383. {roam_code-12.20 → roam_code-12.22}/src/roam/workspace/db.py +0 -0
  384. {roam_code-12.20 → roam_code-12.22}/src/roam_code.egg-info/dependency_links.txt +0 -0
  385. {roam_code-12.20 → roam_code-12.22}/src/roam_code.egg-info/entry_points.txt +0 -0
  386. {roam_code-12.20 → roam_code-12.22}/src/roam_code.egg-info/requires.txt +0 -0
  387. {roam_code-12.20 → roam_code-12.22}/src/roam_code.egg-info/top_level.txt +0 -0
  388. {roam_code-12.20 → roam_code-12.22}/tests/test_adrs.py +0 -0
  389. {roam_code-12.20 → roam_code-12.22}/tests/test_adversarial.py +0 -0
  390. {roam_code-12.20 → roam_code-12.22}/tests/test_affected.py +0 -0
  391. {roam_code-12.20 → roam_code-12.22}/tests/test_agent_export.py +0 -0
  392. {roam_code-12.20 → roam_code-12.22}/tests/test_agent_mode.py +0 -0
  393. {roam_code-12.20 → roam_code-12.22}/tests/test_agent_plan_context.py +0 -0
  394. {roam_code-12.20 → roam_code-12.22}/tests/test_ai_ratio.py +0 -0
  395. {roam_code-12.20 → roam_code-12.22}/tests/test_ai_readiness.py +0 -0
  396. {roam_code-12.20 → roam_code-12.22}/tests/test_alerts_cmd.py +0 -0
  397. {roam_code-12.20 → roam_code-12.22}/tests/test_annotations.py +0 -0
  398. {roam_code-12.20 → roam_code-12.22}/tests/test_anomaly.py +0 -0
  399. {roam_code-12.20 → roam_code-12.22}/tests/test_api_changes.py +0 -0
  400. {roam_code-12.20 → roam_code-12.22}/tests/test_api_drift.py +0 -0
  401. {roam_code-12.20 → roam_code-12.22}/tests/test_ask.py +0 -0
  402. {roam_code-12.20 → roam_code-12.22}/tests/test_attest.py +0 -0
  403. {roam_code-12.20 → roam_code-12.22}/tests/test_auth_gaps.py +0 -0
  404. {roam_code-12.20 → roam_code-12.22}/tests/test_backend_fixes_round2.py +0 -0
  405. {roam_code-12.20 → roam_code-12.22}/tests/test_backend_fixes_round3.py +0 -0
  406. {roam_code-12.20 → roam_code-12.22}/tests/test_basic.py +0 -0
  407. {roam_code-12.20 → roam_code-12.22}/tests/test_batch_mcp.py +0 -0
  408. {roam_code-12.20 → roam_code-12.22}/tests/test_bisect.py +0 -0
  409. {roam_code-12.20 → roam_code-12.22}/tests/test_bridge_django.py +0 -0
  410. {roam_code-12.20 → roam_code-12.22}/tests/test_bridges.py +0 -0
  411. {roam_code-12.20 → roam_code-12.22}/tests/test_bridges_extended.py +0 -0
  412. {roam_code-12.20 → roam_code-12.22}/tests/test_budget.py +0 -0
  413. {roam_code-12.20 → roam_code-12.22}/tests/test_budget_flag.py +0 -0
  414. {roam_code-12.20 → roam_code-12.22}/tests/test_budget_phase2.py +0 -0
  415. {roam_code-12.20 → roam_code-12.22}/tests/test_bus_factor.py +0 -0
  416. {roam_code-12.20 → roam_code-12.22}/tests/test_capsule.py +0 -0
  417. {roam_code-12.20 → roam_code-12.22}/tests/test_cga.py +0 -0
  418. {roam_code-12.20 → roam_code-12.22}/tests/test_check_rules.py +0 -0
  419. {roam_code-12.20 → roam_code-12.22}/tests/test_ci_gate_eval.py +0 -0
  420. {roam_code-12.20 → roam_code-12.22}/tests/test_ci_sarif_guard.py +0 -0
  421. {roam_code-12.20 → roam_code-12.22}/tests/test_ci_setup.py +0 -0
  422. {roam_code-12.20 → roam_code-12.22}/tests/test_clones.py +0 -0
  423. {roam_code-12.20 → roam_code-12.22}/tests/test_closure.py +0 -0
  424. {roam_code-12.20 → roam_code-12.22}/tests/test_codeowners.py +0 -0
  425. {roam_code-12.20 → roam_code-12.22}/tests/test_commands_architecture.py +0 -0
  426. {roam_code-12.20 → roam_code-12.22}/tests/test_commands_exploration.py +0 -0
  427. {roam_code-12.20 → roam_code-12.22}/tests/test_commands_health.py +0 -0
  428. {roam_code-12.20 → roam_code-12.22}/tests/test_commands_refactoring.py +0 -0
  429. {roam_code-12.20 → roam_code-12.22}/tests/test_commands_workflow.py +0 -0
  430. {roam_code-12.20 → roam_code-12.22}/tests/test_competitor_site_data.py +0 -0
  431. {roam_code-12.20 → roam_code-12.22}/tests/test_comprehensive.py +0 -0
  432. {roam_code-12.20 → roam_code-12.22}/tests/test_config.py +0 -0
  433. {roam_code-12.20 → roam_code-12.22}/tests/test_congestion.py +0 -0
  434. {roam_code-12.20 → roam_code-12.22}/tests/test_context_propagation.py +0 -0
  435. {roam_code-12.20 → roam_code-12.22}/tests/test_conventions_cmd.py +0 -0
  436. {roam_code-12.20 → roam_code-12.22}/tests/test_coverage_gaps_cmd.py +0 -0
  437. {roam_code-12.20 → roam_code-12.22}/tests/test_coverage_ingestion.py +0 -0
  438. {roam_code-12.20 → roam_code-12.22}/tests/test_critique.py +0 -0
  439. {roam_code-12.20 → roam_code-12.22}/tests/test_cut.py +0 -0
  440. {roam_code-12.20 → roam_code-12.22}/tests/test_dark_matter.py +0 -0
  441. {roam_code-12.20 → roam_code-12.22}/tests/test_dark_matter_helpers.py +0 -0
  442. {roam_code-12.20 → roam_code-12.22}/tests/test_dashboard.py +0 -0
  443. {roam_code-12.20 → roam_code-12.22}/tests/test_dataflow_dead.py +0 -0
  444. {roam_code-12.20 → roam_code-12.22}/tests/test_dead_aging.py +0 -0
  445. {roam_code-12.20 → roam_code-12.22}/tests/test_defer_loading.py +0 -0
  446. {roam_code-12.20 → roam_code-12.22}/tests/test_demo_gif_asset.py +0 -0
  447. {roam_code-12.20 → roam_code-12.22}/tests/test_describe.py +0 -0
  448. {roam_code-12.20 → roam_code-12.22}/tests/test_detail_flag_hints.py +0 -0
  449. {roam_code-12.20 → roam_code-12.22}/tests/test_detector_precision.py +0 -0
  450. {roam_code-12.20 → roam_code-12.22}/tests/test_deterministic_output.py +0 -0
  451. {roam_code-12.20 → roam_code-12.22}/tests/test_dev_profile.py +0 -0
  452. {roam_code-12.20 → roam_code-12.22}/tests/test_difficulty_scoring.py +0 -0
  453. {roam_code-12.20 → roam_code-12.22}/tests/test_doc_consistency.py +0 -0
  454. {roam_code-12.20 → roam_code-12.22}/tests/test_doc_staleness.py +0 -0
  455. {roam_code-12.20 → roam_code-12.22}/tests/test_docker_assets.py +0 -0
  456. {roam_code-12.20 → roam_code-12.22}/tests/test_docs_coverage.py +0 -0
  457. {roam_code-12.20 → roam_code-12.22}/tests/test_docs_site_quality.py +0 -0
  458. {roam_code-12.20 → roam_code-12.22}/tests/test_doctor.py +0 -0
  459. {roam_code-12.20 → roam_code-12.22}/tests/test_drift.py +0 -0
  460. {roam_code-12.20 → roam_code-12.22}/tests/test_drift_by_team.py +0 -0
  461. {roam_code-12.20 → roam_code-12.22}/tests/test_duplicates.py +0 -0
  462. {roam_code-12.20 → roam_code-12.22}/tests/test_effects.py +0 -0
  463. {roam_code-12.20 → roam_code-12.22}/tests/test_effects_propagation.py +0 -0
  464. {roam_code-12.20 → roam_code-12.22}/tests/test_endpoints.py +0 -0
  465. {roam_code-12.20 → roam_code-12.22}/tests/test_entry_points_cmd.py +0 -0
  466. {roam_code-12.20 → roam_code-12.22}/tests/test_eval_retrieve.py +0 -0
  467. {roam_code-12.20 → roam_code-12.22}/tests/test_except_pass_narrow.py +0 -0
  468. {roam_code-12.20 → roam_code-12.22}/tests/test_exclude_patterns.py +0 -0
  469. {roam_code-12.20 → roam_code-12.22}/tests/test_exit_codes.py +0 -0
  470. {roam_code-12.20 → roam_code-12.22}/tests/test_extractor_grammar_drift.py +0 -0
  471. {roam_code-12.20 → roam_code-12.22}/tests/test_fallback_contracts.py +0 -0
  472. {roam_code-12.20 → roam_code-12.22}/tests/test_file_roles.py +0 -0
  473. {roam_code-12.20 → roam_code-12.22}/tests/test_fingerprint.py +0 -0
  474. {roam_code-12.20 → roam_code-12.22}/tests/test_fixes.py +0 -0
  475. {roam_code-12.20 → roam_code-12.22}/tests/test_flag_dead.py +0 -0
  476. {roam_code-12.20 → roam_code-12.22}/tests/test_fleet.py +0 -0
  477. {roam_code-12.20 → roam_code-12.22}/tests/test_fn_coupling.py +0 -0
  478. {roam_code-12.20 → roam_code-12.22}/tests/test_forecast.py +0 -0
  479. {roam_code-12.20 → roam_code-12.22}/tests/test_formatters.py +0 -0
  480. {roam_code-12.20 → roam_code-12.22}/tests/test_foxpro.py +0 -0
  481. {roam_code-12.20 → roam_code-12.22}/tests/test_framework_detection.py +0 -0
  482. {roam_code-12.20 → roam_code-12.22}/tests/test_gate_presets.py +0 -0
  483. {roam_code-12.20 → roam_code-12.22}/tests/test_git_utils.py +0 -0
  484. {roam_code-12.20 → roam_code-12.22}/tests/test_guard.py +0 -0
  485. {roam_code-12.20 → roam_code-12.22}/tests/test_hooks.py +0 -0
  486. {roam_code-12.20 → roam_code-12.22}/tests/test_hotspots.py +0 -0
  487. {roam_code-12.20 → roam_code-12.22}/tests/test_hover.py +0 -0
  488. {roam_code-12.20 → roam_code-12.22}/tests/test_index.py +0 -0
  489. {roam_code-12.20 → roam_code-12.22}/tests/test_index_bundle.py +0 -0
  490. {roam_code-12.20 → roam_code-12.22}/tests/test_init_cmd.py +0 -0
  491. {roam_code-12.20 → roam_code-12.22}/tests/test_install_check.py +0 -0
  492. {roam_code-12.20 → roam_code-12.22}/tests/test_intent.py +0 -0
  493. {roam_code-12.20 → roam_code-12.22}/tests/test_invariants.py +0 -0
  494. {roam_code-12.20 → roam_code-12.22}/tests/test_json_contracts.py +0 -0
  495. {roam_code-12.20 → roam_code-12.22}/tests/test_json_error_envelope.py +0 -0
  496. {roam_code-12.20 → roam_code-12.22}/tests/test_kotlin_swift_extractors.py +0 -0
  497. {roam_code-12.20 → roam_code-12.22}/tests/test_language_corpus.py +0 -0
  498. {roam_code-12.20 → roam_code-12.22}/tests/test_languages.py +0 -0
  499. {roam_code-12.20 → roam_code-12.22}/tests/test_library_api.py +0 -0
  500. {roam_code-12.20 → roam_code-12.22}/tests/test_math.py +0 -0
  501. {roam_code-12.20 → roam_code-12.22}/tests/test_math_tips.py +0 -0
  502. {roam_code-12.20 → roam_code-12.22}/tests/test_mcp_server.py +0 -0
  503. {roam_code-12.20 → roam_code-12.22}/tests/test_mcp_setup.py +0 -0
  504. {roam_code-12.20 → roam_code-12.22}/tests/test_mermaid.py +0 -0
  505. {roam_code-12.20 → roam_code-12.22}/tests/test_metrics_cmd.py +0 -0
  506. {roam_code-12.20 → roam_code-12.22}/tests/test_migration_safety.py +0 -0
  507. {roam_code-12.20 → roam_code-12.22}/tests/test_minimap.py +0 -0
  508. {roam_code-12.20 → roam_code-12.22}/tests/test_missing_index.py +0 -0
  509. {roam_code-12.20 → roam_code-12.22}/tests/test_mutate.py +0 -0
  510. {roam_code-12.20 → roam_code-12.22}/tests/test_n1.py +0 -0
  511. {roam_code-12.20 → roam_code-12.22}/tests/test_next_steps.py +0 -0
  512. {roam_code-12.20 → roam_code-12.22}/tests/test_onboard.py +0 -0
  513. {roam_code-12.20 → roam_code-12.22}/tests/test_oracle.py +0 -0
  514. {roam_code-12.20 → roam_code-12.22}/tests/test_orchestrate.py +0 -0
  515. {roam_code-12.20 → roam_code-12.22}/tests/test_orphan_routes.py +0 -0
  516. {roam_code-12.20 → roam_code-12.22}/tests/test_oss_bench_harness.py +0 -0
  517. {roam_code-12.20 → roam_code-12.22}/tests/test_over_fetch.py +0 -0
  518. {roam_code-12.20 → roam_code-12.22}/tests/test_pagerank_truncation.py +0 -0
  519. {roam_code-12.20 → roam_code-12.22}/tests/test_partition.py +0 -0
  520. {roam_code-12.20 → roam_code-12.22}/tests/test_path_coverage.py +0 -0
  521. {roam_code-12.20 → roam_code-12.22}/tests/test_patterns_cmd.py +0 -0
  522. {roam_code-12.20 → roam_code-12.22}/tests/test_performance.py +0 -0
  523. {roam_code-12.20 → roam_code-12.22}/tests/test_personalized_pagerank.py +0 -0
  524. {roam_code-12.20 → roam_code-12.22}/tests/test_plan.py +0 -0
  525. {roam_code-12.20 → roam_code-12.22}/tests/test_plugin_discovery.py +0 -0
  526. {roam_code-12.20 → roam_code-12.22}/tests/test_pr_comment_script.py +0 -0
  527. {roam_code-12.20 → roam_code-12.22}/tests/test_pr_diff.py +0 -0
  528. {roam_code-12.20 → roam_code-12.22}/tests/test_pr_risk_author.py +0 -0
  529. {roam_code-12.20 → roam_code-12.22}/tests/test_progress.py +0 -0
  530. {roam_code-12.20 → roam_code-12.22}/tests/test_progressive_disclosure.py +0 -0
  531. {roam_code-12.20 → roam_code-12.22}/tests/test_properties.py +0 -0
  532. {roam_code-12.20 → roam_code-12.22}/tests/test_pytest_fixtures.py +0 -0
  533. {roam_code-12.20 → roam_code-12.22}/tests/test_python_extractor_v2.py +0 -0
  534. {roam_code-12.20 → roam_code-12.22}/tests/test_python_idioms_e2e.py +0 -0
  535. {roam_code-12.20 → roam_code-12.22}/tests/test_python_pivot.py +0 -0
  536. {roam_code-12.20 → roam_code-12.22}/tests/test_readme_surface_consistency.py +0 -0
  537. {roam_code-12.20 → roam_code-12.22}/tests/test_realworld_feedback.py +0 -0
  538. {roam_code-12.20 → roam_code-12.22}/tests/test_refactoring_intelligence.py +0 -0
  539. {roam_code-12.20 → roam_code-12.22}/tests/test_registry_dispatch.py +0 -0
  540. {roam_code-12.20 → roam_code-12.22}/tests/test_relate.py +0 -0
  541. {roam_code-12.20 → roam_code-12.22}/tests/test_report.py +0 -0
  542. {roam_code-12.20 → roam_code-12.22}/tests/test_reset_clean.py +0 -0
  543. {roam_code-12.20 → roam_code-12.22}/tests/test_resolve.py +0 -0
  544. {roam_code-12.20 → roam_code-12.22}/tests/test_retrieve.py +0 -0
  545. {roam_code-12.20 → roam_code-12.22}/tests/test_retrieve_cross_repo.py +0 -0
  546. {roam_code-12.20 → roam_code-12.22}/tests/test_retrieve_seeds.py +0 -0
  547. {roam_code-12.20 → roam_code-12.22}/tests/test_risk.py +0 -0
  548. {roam_code-12.20 → roam_code-12.22}/tests/test_ruby.py +0 -0
  549. {roam_code-12.20 → roam_code-12.22}/tests/test_rule_profiles.py +0 -0
  550. {roam_code-12.20 → roam_code-12.22}/tests/test_rules.py +0 -0
  551. {roam_code-12.20 → roam_code-12.22}/tests/test_rules_ast_match.py +0 -0
  552. {roam_code-12.20 → roam_code-12.22}/tests/test_rules_community_pack.py +0 -0
  553. {roam_code-12.20 → roam_code-12.22}/tests/test_rules_dataflow.py +0 -0
  554. {roam_code-12.20 → roam_code-12.22}/tests/test_rules_symbol_requirements.py +0 -0
  555. {roam_code-12.20 → roam_code-12.22}/tests/test_runtime.py +0 -0
  556. {roam_code-12.20 → roam_code-12.22}/tests/test_runtime_score.py +0 -0
  557. {roam_code-12.20 → roam_code-12.22}/tests/test_salesforce.py +0 -0
  558. {roam_code-12.20 → roam_code-12.22}/tests/test_sarif_flag.py +0 -0
  559. {roam_code-12.20 → roam_code-12.22}/tests/test_sbom.py +0 -0
  560. {roam_code-12.20 → roam_code-12.22}/tests/test_scala.py +0 -0
  561. {roam_code-12.20 → roam_code-12.22}/tests/test_schema_versioning.py +0 -0
  562. {roam_code-12.20 → roam_code-12.22}/tests/test_search_explain.py +0 -0
  563. {roam_code-12.20 → roam_code-12.22}/tests/test_secrets.py +0 -0
  564. {roam_code-12.20 → roam_code-12.22}/tests/test_secrets_v2.py +0 -0
  565. {roam_code-12.20 → roam_code-12.22}/tests/test_semantic_diff.py +0 -0
  566. {roam_code-12.20 → roam_code-12.22}/tests/test_semantic_onnx.py +0 -0
  567. {roam_code-12.20 → roam_code-12.22}/tests/test_semantic_search.py +0 -0
  568. {roam_code-12.20 → roam_code-12.22}/tests/test_simulate.py +0 -0
  569. {roam_code-12.20 → roam_code-12.22}/tests/test_simulate_departure.py +0 -0
  570. {roam_code-12.20 → roam_code-12.22}/tests/test_sketch.py +0 -0
  571. {roam_code-12.20 → roam_code-12.22}/tests/test_smells.py +0 -0
  572. {roam_code-12.20 → roam_code-12.22}/tests/test_smoke.py +0 -0
  573. {roam_code-12.20 → roam_code-12.22}/tests/test_sna_metrics.py +0 -0
  574. {roam_code-12.20 → roam_code-12.22}/tests/test_spectral.py +0 -0
  575. {roam_code-12.20 → roam_code-12.22}/tests/test_split_cmd.py +0 -0
  576. {roam_code-12.20 → roam_code-12.22}/tests/test_sql.py +0 -0
  577. {roam_code-12.20 → roam_code-12.22}/tests/test_suggest_reviewers.py +0 -0
  578. {roam_code-12.20 → roam_code-12.22}/tests/test_supply_chain.py +0 -0
  579. {roam_code-12.20 → roam_code-12.22}/tests/test_surface_counts.py +0 -0
  580. {roam_code-12.20 → roam_code-12.22}/tests/test_syntax_check.py +0 -0
  581. {roam_code-12.20 → roam_code-12.22}/tests/test_taint.py +0 -0
  582. {roam_code-12.20 → roam_code-12.22}/tests/test_taint_analysis.py +0 -0
  583. {roam_code-12.20 → roam_code-12.22}/tests/test_taint_classifier.py +0 -0
  584. {roam_code-12.20 → roam_code-12.22}/tests/test_taint_intraprocedural.py +0 -0
  585. {roam_code-12.20 → roam_code-12.22}/tests/test_test_conventions.py +0 -0
  586. {roam_code-12.20 → roam_code-12.22}/tests/test_test_gaps.py +0 -0
  587. {roam_code-12.20 → roam_code-12.22}/tests/test_test_scaffold.py +0 -0
  588. {roam_code-12.20 → roam_code-12.22}/tests/test_testmap.py +0 -0
  589. {roam_code-12.20 → roam_code-12.22}/tests/test_top_flag_consistency.py +0 -0
  590. {roam_code-12.20 → roam_code-12.22}/tests/test_tour_cmd.py +0 -0
  591. {roam_code-12.20 → roam_code-12.22}/tests/test_trends.py +0 -0
  592. {roam_code-12.20 → roam_code-12.22}/tests/test_trends_cohort.py +0 -0
  593. {roam_code-12.20 → roam_code-12.22}/tests/test_triage.py +0 -0
  594. {roam_code-12.20 → roam_code-12.22}/tests/test_uses_cmd.py +0 -0
  595. {roam_code-12.20 → roam_code-12.22}/tests/test_v1215_passes.py +0 -0
  596. {roam_code-12.20 → roam_code-12.22}/tests/test_v1216_passes_41_50.py +0 -0
  597. {roam_code-12.20 → roam_code-12.22}/tests/test_v1216_passes_51_60.py +0 -0
  598. {roam_code-12.20 → roam_code-12.22}/tests/test_v1217_passes_61_80.py +0 -0
  599. {roam_code-12.20 → roam_code-12.22}/tests/test_v1218_passes_81_90.py +0 -0
  600. {roam_code-12.20 → roam_code-12.22}/tests/test_v1219_passes_91_100.py +0 -0
  601. {roam_code-12.20 → roam_code-12.22}/tests/test_v1220_passes_101_110.py +0 -0
  602. {roam_code-12.20 → roam_code-12.22}/tests/test_v12_2.py +0 -0
  603. {roam_code-12.20 → roam_code-12.22}/tests/test_v6_features.py +0 -0
  604. {roam_code-12.20 → roam_code-12.22}/tests/test_v71_features.py +0 -0
  605. {roam_code-12.20 → roam_code-12.22}/tests/test_v7_features.py +0 -0
  606. {roam_code-12.20 → roam_code-12.22}/tests/test_v82_features.py +0 -0
  607. {roam_code-12.20 → roam_code-12.22}/tests/test_verify.py +0 -0
  608. {roam_code-12.20 → roam_code-12.22}/tests/test_verify_imports.py +0 -0
  609. {roam_code-12.20 → roam_code-12.22}/tests/test_vibe_check.py +0 -0
  610. {roam_code-12.20 → roam_code-12.22}/tests/test_visualize.py +0 -0
  611. {roam_code-12.20 → roam_code-12.22}/tests/test_vuln.py +0 -0
  612. {roam_code-12.20 → roam_code-12.22}/tests/test_vulns_cmd.py +0 -0
  613. {roam_code-12.20 → roam_code-12.22}/tests/test_watch.py +0 -0
  614. {roam_code-12.20 → roam_code-12.22}/tests/test_why.py +0 -0
  615. {roam_code-12.20 → roam_code-12.22}/tests/test_workspace.py +0 -0
  616. {roam_code-12.20 → roam_code-12.22}/tests/test_ws_resolve_fixes.py +0 -0
  617. {roam_code-12.20 → roam_code-12.22}/tests/test_xlang.py +0 -0
  618. {roam_code-12.20 → roam_code-12.22}/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.20
3
+ Version: 12.22
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.20"
7
+ version = "12.22"
8
8
  description = "Instant codebase comprehension for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -601,6 +601,14 @@ def _render_json(data, budget=0):
601
601
  # ---------------------------------------------------------------------------
602
602
 
603
603
 
604
+ def _table_budget(data) -> int:
605
+ """Pass 118 — read the threaded ``--budget`` so format_table honors it."""
606
+ try:
607
+ return int(data.get("token_budget") or 0)
608
+ except (TypeError, ValueError):
609
+ return 0
610
+
611
+
604
612
  def _render_single_text(data):
605
613
  sym = data["sym"]
606
614
  task = data["task"]
@@ -654,7 +662,7 @@ def _render_single_text(data):
654
662
  ]
655
663
  for cr in non_test_callers[:20]
656
664
  ]
657
- click.echo(format_table(["kind", "name", "location", "edge"], rows))
665
+ click.echo(format_table(["kind", "name", "location", "edge"], rows, budget=_table_budget(data)))
658
666
  if len(non_test_callers) > 20:
659
667
  click.echo(f" (+{len(non_test_callers) - 20} more)")
660
668
  click.echo()
@@ -674,7 +682,7 @@ def _render_single_text(data):
674
682
  ]
675
683
  for ce in callees[:15]
676
684
  ]
677
- click.echo(format_table(["kind", "name", "location", "edge"], rows))
685
+ click.echo(format_table(["kind", "name", "location", "edge"], rows, budget=_table_budget(data)))
678
686
  if len(callees) > 15:
679
687
  click.echo(f" (+{len(callees) - 15} more)")
680
688
  click.echo()
@@ -1263,4 +1271,7 @@ def context(ctx, names, task, for_file, session_hint, recent_symbols, no_propaga
1263
1271
  # Pass 67 — pass inline-mode flag through to the renderer.
1264
1272
  if inline_mode:
1265
1273
  data["inline_mode"] = True
1274
+ # Pass 118 — thread the global --budget through to renderers so
1275
+ # format_table calls in cmd_context can honor it.
1276
+ data["token_budget"] = token_budget
1266
1277
  _render_json(data, budget=token_budget) if json_mode else _render_text(data)
@@ -191,11 +191,30 @@ def impact(ctx, name, hops):
191
191
 
192
192
  G = build_symbol_graph(conn)
193
193
  if sym_id not in G:
194
- click.echo(
195
- f"Symbol '{name}' exists in the index but is not in the dependency graph.\n"
196
- f" Tip: Run `roam index` to rebuild the graph, or use `roam symbol {name}`"
197
- " to view raw symbol data."
198
- )
194
+ verdict = f"Symbol '{name}' exists in the index but is not in the dependency graph."
195
+ tip = f"Run `roam index` to rebuild the graph, or use `roam symbol {name}` to view raw symbol data."
196
+ if json_mode:
197
+ click.echo(
198
+ to_json(
199
+ json_envelope(
200
+ "impact",
201
+ budget=token_budget,
202
+ summary={
203
+ "verdict": verdict,
204
+ "affected_symbols": 0,
205
+ "affected_files": 0,
206
+ "in_graph": False,
207
+ },
208
+ symbol=sym["qualified_name"] or sym["name"],
209
+ tip=tip,
210
+ direct_dependents={},
211
+ affected_file_list=[],
212
+ indirect_refs=[],
213
+ )
214
+ )
215
+ )
216
+ else:
217
+ click.echo(f"{verdict}\n Tip: {tip}")
199
218
  return
200
219
 
201
220
  RG = G.reverse()
@@ -47,87 +47,103 @@ def _normalize_dep_name(name: str) -> str:
47
47
  return name.lower().replace("-", "_").replace(".", "_")
48
48
 
49
49
 
50
+ def _node_match_keys(data) -> tuple[str, str, str]:
51
+ """Pass 114 — pre-normalise the three node fields used for dep matching."""
52
+ qname = (data.get("qualified_name") or "").lower().replace("-", "_").replace(".", "_")
53
+ name_lower = (data.get("name") or "").lower().replace("-", "_").replace(".", "_")
54
+ file_path = (data.get("file_path") or "").lower().replace("-", "_").replace(".", "_")
55
+ return qname, name_lower, file_path
56
+
57
+
58
+ def _matches_dep(qname: str, name_lower: str, file_path: str, norm: str) -> bool:
59
+ """Pass 114 — predicate version of the inner dep-match check."""
60
+ if qname and (qname.startswith(norm + "_") or qname.startswith(norm + "/") or qname == norm):
61
+ return True
62
+ if norm in file_path:
63
+ return True
64
+ if name_lower == norm:
65
+ return True
66
+ return False
67
+
68
+
69
+ def _trace_entry_reach(G, entries, nid):
70
+ """Pass 114 — return the entry-point node IDs that can reach ``nid``."""
71
+ import networkx as nx
72
+
73
+ reach: list = []
74
+ for eid in entries:
75
+ try:
76
+ if nx.has_path(G, eid, nid):
77
+ reach.append(eid)
78
+ except (nx.NetworkXError, nx.NodeNotFound):
79
+ continue
80
+ return reach
81
+
82
+
83
+ def _build_norm_lookup(dep_names: list[str]) -> dict[str, list[str]]:
84
+ """Pass 114 — group orig dep names by their normalised key."""
85
+ norm_to_dep: dict[str, list[str]] = {}
86
+ for dep in dep_names:
87
+ norm = _normalize_dep_name(dep)
88
+ if norm:
89
+ norm_to_dep.setdefault(norm, []).append(dep)
90
+ return norm_to_dep
91
+
92
+
93
+ def _record_match(info: dict, display_name: str, G, entries, nid) -> None:
94
+ """Pass 114 — update a single dep's reachability record."""
95
+ if display_name not in info["matched_symbols"]:
96
+ info["matched_symbols"].append(display_name)
97
+ if info["reachable"]:
98
+ return
99
+ for eid in _trace_entry_reach(G, entries, nid):
100
+ info["reachable"] = True
101
+ entry_name = G.nodes[eid].get("qualified_name") or G.nodes[eid].get("name", str(eid))
102
+ if entry_name not in info["entry_points"]:
103
+ info["entry_points"].append(entry_name)
104
+
105
+
50
106
  def _compute_reachability(conn, dep_names: list[str]) -> dict[str, dict]:
51
107
  """Check which dependencies are referenced in the codebase symbol graph.
52
108
 
53
- For each dependency, look for import references or qualified-name matches
54
- in the ``edges`` / ``symbols`` tables. When a match is found, trace
55
- entry points (in-degree 0) that can reach the matched symbol.
109
+ For each dependency, look for import references or qualified-name
110
+ matches in the ``edges`` / ``symbols`` tables. When a match is
111
+ found, trace entry points (in-degree 0) that can reach the matched
112
+ symbol.
56
113
 
57
- Returns ``{dep_name: {"reachable": bool, "entry_points": [str, ...], "matched_symbols": [str, ...]}}``
58
- """
59
- import networkx as nx
114
+ Returns ``{dep_name: {"reachable": bool, "entry_points": [...],
115
+ "matched_symbols": [...]}}``.
60
116
 
117
+ Pass 114 — orchestrator only. Pre-Pass 114 this function had cc=150
118
+ and nesting depth 8 (the deepest in the repo). Per-symbol logic now
119
+ lives in ``_node_match_keys``, ``_matches_dep``,
120
+ ``_trace_entry_reach``, ``_build_norm_lookup``, ``_record_match``.
121
+ """
61
122
  from roam.graph.builder import build_symbol_graph
62
123
 
63
- result: dict[str, dict] = {}
64
-
65
- # Pre-populate with defaults
66
- for dep in dep_names:
67
- result[dep] = {"reachable": False, "entry_points": [], "matched_symbols": []}
68
-
124
+ result: dict[str, dict] = {
125
+ dep: {"reachable": False, "entry_points": [], "matched_symbols": []} for dep in dep_names
126
+ }
69
127
  if not dep_names:
70
128
  return result
71
-
72
- # Build graph once
73
129
  try:
74
130
  G = build_symbol_graph(conn)
75
131
  except Exception:
76
132
  return result
77
-
78
133
  if not G.nodes:
79
134
  return result
80
135
 
81
- # Compute entry points (in-degree 0)
82
136
  entries = [n for n in G.nodes() if G.in_degree(n) == 0]
137
+ norm_to_dep = _build_norm_lookup(dep_names)
83
138
 
84
- # Build a lookup: normalized name fragment -> list of node IDs
85
- # We match dependency names against import targets and qualified names.
86
- norm_to_dep: dict[str, list[str]] = {}
87
- for dep in dep_names:
88
- norm = _normalize_dep_name(dep)
89
- if norm:
90
- norm_to_dep.setdefault(norm, []).append(dep)
91
-
92
- # Scan symbols for matches
93
139
  for nid, data in G.nodes(data=True):
94
- qname = (data.get("qualified_name") or "").lower().replace("-", "_").replace(".", "_")
95
- name_lower = (data.get("name") or "").lower().replace("-", "_").replace(".", "_")
96
- file_path = (data.get("file_path") or "").lower().replace("-", "_").replace(".", "_")
97
-
140
+ qname, name_lower, file_path = _node_match_keys(data)
98
141
  for norm, orig_deps in norm_to_dep.items():
99
- # Match if the normalized dep name appears as a prefix in qualified
100
- # name, or in the file path (e.g., node_modules/lodash/...).
101
- matched = False
102
- if qname and (qname.startswith(norm + "_") or qname.startswith(norm + "/") or qname == norm):
103
- matched = True
104
- elif norm in file_path:
105
- matched = True
106
- elif name_lower == norm:
107
- matched = True
108
-
109
- if not matched:
142
+ if not _matches_dep(qname, name_lower, file_path, norm):
110
143
  continue
111
-
112
144
  display_name = data.get("qualified_name") or data.get("name", str(nid))
113
-
114
145
  for dep_name in orig_deps:
115
- info = result[dep_name]
116
- if display_name not in info["matched_symbols"]:
117
- info["matched_symbols"].append(display_name)
118
-
119
- if not info["reachable"]:
120
- # Check if any entry point can reach this node
121
- for eid in entries:
122
- try:
123
- if nx.has_path(G, eid, nid):
124
- info["reachable"] = True
125
- entry_name = G.nodes[eid].get("qualified_name") or G.nodes[eid].get("name", str(eid))
126
- if entry_name not in info["entry_points"]:
127
- info["entry_points"].append(entry_name)
128
- except (nx.NetworkXError, nx.NodeNotFound):
129
- continue
130
-
146
+ _record_match(result[dep_name], display_name, G, entries, nid)
131
147
  return result
132
148
 
133
149
 
@@ -454,6 +454,76 @@ class DebounceAccumulator:
454
454
  return result
455
455
 
456
456
 
457
+ def _need_force(webhook_events: list, webhook_force: bool) -> bool:
458
+ """Pass 115 — does the current event batch require a forced re-index?"""
459
+ has_explicit = any(bool(evt.get("force")) for evt in webhook_events if isinstance(evt, dict))
460
+ return has_explicit or (webhook_force and bool(webhook_events))
461
+
462
+
463
+ def _scan_disk_changes(_discover, project_root: Path, tracked: dict, quiet: bool):
464
+ """Pass 115 — discover + diff disk state, optionally print per-file lines."""
465
+ current_paths = _discover()
466
+ current_disk = scan_disk_mtimes(current_paths, project_root)
467
+ added, modified, removed = detect_changes(tracked, current_disk)
468
+ changed = added + modified + removed
469
+ if changed and not quiet:
470
+ for path in added:
471
+ click.echo(f" + {path}")
472
+ for path in modified:
473
+ click.echo(f" ~ {path}")
474
+ for path in removed:
475
+ click.echo(f" - {path}")
476
+ return tracked, changed
477
+
478
+
479
+ def _label_webhook_events(webhook_events: list, quiet: bool) -> list[str]:
480
+ """Pass 115 — turn webhook event dicts into ``<webhook:name>`` labels."""
481
+ if not webhook_events:
482
+ return []
483
+ labels = [
484
+ f"<webhook:{str(evt.get('event', 'webhook')).strip() or 'webhook'}>"
485
+ for evt in webhook_events
486
+ if isinstance(evt, dict)
487
+ ]
488
+ if labels and not quiet:
489
+ for label in labels:
490
+ click.echo(f" * {label}")
491
+ return labels
492
+
493
+
494
+ def _refresh_tracked_after_reindex(project_root: Path, prev_file_count: int, quiet: bool):
495
+ """Pass 115 — reload tracked-files state and emit a status line."""
496
+ tracked = load_tracked_files(project_root)
497
+ new_count = len(tracked)
498
+ if not quiet:
499
+ click.echo(f"Re-index complete. Watching {new_count} files.")
500
+ elif new_count != prev_file_count:
501
+ click.echo(f"Re-indexed. Watching {new_count} files.")
502
+ return tracked, new_count
503
+
504
+
505
+ def _run_guardian_step(_guardian_collect, _guardian_write, guardian_report: str, quiet: bool) -> None:
506
+ """Pass 115 — collect + write a guardian snapshot, log the verdict."""
507
+ try:
508
+ payload = _guardian_collect()
509
+ if guardian_report:
510
+ _guardian_write(payload)
511
+ if quiet:
512
+ return
513
+ gate_state = "PASS" if payload.get("gates", {}).get("health_gate_pass") else "FAIL"
514
+ click.echo(
515
+ "Guardian: health={} trend={} drift={} ({})".format(
516
+ payload.get("current", {}).get("health_score", "n/a"),
517
+ payload.get("trend", {}).get("verdict", "n/a"),
518
+ payload.get("drift", {}).get("drift_files", "n/a"),
519
+ gate_state,
520
+ )
521
+ )
522
+ except Exception as exc:
523
+ if not quiet:
524
+ click.echo(f"Guardian update failed: {exc}")
525
+
526
+
457
527
  def poll_loop(
458
528
  project_root: Path,
459
529
  interval: float,
@@ -530,77 +600,28 @@ def poll_loop(
530
600
  while True:
531
601
  _sleep(interval)
532
602
 
533
- changed: list[str] = []
534
603
  webhook_events = _external_events() or []
535
- force_needed = any(bool(evt.get("force")) for evt in webhook_events if isinstance(evt, dict))
536
- force_needed = force_needed or (webhook_force and bool(webhook_events))
537
- pending_force = pending_force or force_needed
604
+ pending_force = pending_force or _need_force(webhook_events, webhook_force)
538
605
 
606
+ changed: list[str] = []
539
607
  if not webhook_only:
540
- # Discover current files on disk
541
- current_paths = _discover()
542
- current_disk = scan_disk_mtimes(current_paths, project_root)
543
-
544
- added, modified, removed = detect_changes(tracked, current_disk)
545
- changed.extend(added + modified + removed)
546
-
547
- if changed and not quiet:
548
- for path in added:
549
- click.echo(f" + {path}")
550
- for path in modified:
551
- click.echo(f" ~ {path}")
552
- for path in removed:
553
- click.echo(f" - {path}")
554
-
555
- if webhook_events:
556
- webhook_labels = [
557
- f"<webhook:{str(evt.get('event', 'webhook')).strip() or 'webhook'}>"
558
- for evt in webhook_events
559
- if isinstance(evt, dict)
560
- ]
561
- changed.extend(webhook_labels)
562
- if not quiet:
563
- for label in webhook_labels:
564
- click.echo(f" * {label}")
608
+ tracked, disk_changes = _scan_disk_changes(_discover, project_root, tracked, quiet)
609
+ changed.extend(disk_changes)
610
+ changed.extend(_label_webhook_events(webhook_events, quiet))
565
611
 
566
612
  if changed:
567
613
  acc.add(changed)
568
614
 
569
- now = time.monotonic()
570
- if acc.should_fire(now):
615
+ if acc.should_fire(time.monotonic()):
571
616
  batch = acc.flush()
572
617
  if not quiet:
573
618
  mode_label = "force re-indexing" if pending_force else "re-indexing"
574
619
  click.echo(f"Changed: {len(batch)} event(s) -- {mode_label}...")
575
620
  _reindex(force=pending_force)
576
621
  pending_force = False
577
- # Refresh tracked state from DB after re-index
578
- tracked = load_tracked_files(project_root)
579
- new_count = len(tracked)
580
- if not quiet:
581
- click.echo(f"Re-index complete. Watching {new_count} files.")
582
- elif new_count != file_count:
583
- click.echo(f"Re-indexed. Watching {new_count} files.")
584
- file_count = new_count
585
-
622
+ tracked, file_count = _refresh_tracked_after_reindex(project_root, file_count, quiet)
586
623
  if guardian or guardian_report:
587
- try:
588
- guard_payload = _guardian_collect()
589
- if guardian_report:
590
- _guardian_write(guard_payload)
591
- if not quiet:
592
- gate_state = "PASS" if guard_payload.get("gates", {}).get("health_gate_pass") else "FAIL"
593
- click.echo(
594
- "Guardian: health={} trend={} drift={} ({})".format(
595
- guard_payload.get("current", {}).get("health_score", "n/a"),
596
- guard_payload.get("trend", {}).get("verdict", "n/a"),
597
- guard_payload.get("drift", {}).get("drift_files", "n/a"),
598
- gate_state,
599
- )
600
- )
601
- except Exception as exc:
602
- if not quiet:
603
- click.echo(f"Guardian update failed: {exc}")
624
+ _run_guardian_step(_guardian_collect, _guardian_write, guardian_report, quiet)
604
625
 
605
626
 
606
627
  @click.command("watch")
@@ -1364,7 +1364,7 @@ MAP_METADATA: dict[str, dict[str, object]] = {
1364
1364
  "peer": True,
1365
1365
  "graph": "PageRank + Tarjan + Louvain + layers",
1366
1366
  "note": "Graph algorithms (PageRank, SCC, Louvain, Fiedler) on tree-sitter ASTs fused with git history in SQLite. 128 MCP tools, 178 CLI commands. 19 Python idiom detectors (v12.7+).",
1367
- "version_evaluated": "12.20",
1367
+ "version_evaluated": "12.22",
1368
1368
  "repo_url": "https://github.com/Cranot/roam-code",
1369
1369
  },
1370
1370
  "CKB/CodeMCP": {
@@ -255,61 +255,118 @@ def _adjust_cluster_count(
255
255
  return partitions
256
256
 
257
257
 
258
- def _build_agent_descriptors(
259
- G: nx.DiGraph,
260
- conn: sqlite3.Connection,
261
- partitions: list[dict[str, set[int]]],
262
- ) -> list[dict]:
263
- """Build per-agent descriptor dicts.
264
-
265
- File ownership is determined by majority vote: each file is assigned
266
- exclusively to the partition that owns the most of its symbols.
267
- This guarantees no write overlap between agents.
268
- """
269
- # Build node -> partition index
258
+ def _node_partition_index(partitions: list[dict[str, set[int]]]) -> dict[int, int]:
259
+ """Pass 120 — flatten partitions into ``{node_id: partition_index}``."""
270
260
  node_part: dict[int, int] = {}
271
261
  for idx, p in enumerate(partitions):
272
262
  for n in p["nodes"]:
273
263
  node_part[n] = idx
264
+ return node_part
265
+
274
266
 
275
- # Fetch file paths for all nodes
276
- all_node_ids = list(node_part.keys())
267
+ def _fetch_node_metadata(conn: sqlite3.Connection, node_ids: list[int]):
268
+ """Pass 120 — load (file, name, signature) for every node in one batch."""
277
269
  node_to_file: dict[int, str] = {}
278
270
  node_to_name: dict[int, str] = {}
279
271
  node_to_sig: dict[int, str] = {}
280
- if all_node_ids:
281
- rows = batched_in(
282
- conn,
283
- "SELECT s.id, s.name, s.signature, f.path "
284
- "FROM symbols s JOIN files f ON s.file_id = f.id "
285
- "WHERE s.id IN ({ph})",
286
- all_node_ids,
287
- )
288
- for r in rows:
289
- node_to_file[r["id"]] = r["path"].replace("\\", "/")
290
- node_to_name[r["id"]] = r["name"]
291
- node_to_sig[r["id"]] = r["signature"] or r["name"]
292
-
293
- # ── Determine exclusive file ownership by majority vote ──────
294
- # Count how many symbols each partition contributes to each file
272
+ if not node_ids:
273
+ return node_to_file, node_to_name, node_to_sig
274
+ rows = batched_in(
275
+ conn,
276
+ "SELECT s.id, s.name, s.signature, f.path FROM symbols s JOIN files f ON s.file_id = f.id WHERE s.id IN ({ph})",
277
+ node_ids,
278
+ )
279
+ for r in rows:
280
+ node_to_file[r["id"]] = r["path"].replace("\\", "/")
281
+ node_to_name[r["id"]] = r["name"]
282
+ node_to_sig[r["id"]] = r["signature"] or r["name"]
283
+ return node_to_file, node_to_name, node_to_sig
284
+
285
+
286
+ def _file_majority_owners(node_part: dict[int, int], node_to_file: dict[int, str]) -> dict[int, set[str]]:
287
+ """Pass 120 — assign each file exclusively to its majority-vote partition."""
295
288
  file_partition_counts: dict[str, Counter] = defaultdict(Counter)
296
289
  for n, pidx in node_part.items():
297
290
  fp = node_to_file.get(n)
298
291
  if fp:
299
292
  file_partition_counts[fp][pidx] += 1
300
-
301
- # Assign each file to the partition with the most symbols in it
302
- file_owner: dict[str, int] = {}
293
+ partition_write_files: dict[int, set[str]] = defaultdict(set)
303
294
  for fp, counts in file_partition_counts.items():
304
- file_owner[fp] = counts.most_common(1)[0][0]
295
+ owner = counts.most_common(1)[0][0]
296
+ partition_write_files[owner].add(fp)
297
+ return partition_write_files
298
+
299
+
300
+ def _read_only_files_for(
301
+ G, nodes: set[int], node_part: dict[int, int], node_to_file: dict[int, str], write_files: set[str]
302
+ ) -> set[str]:
303
+ """Pass 120 — files with cross-partition edges but not owned by this agent."""
304
+ read_only: set[str] = set()
305
+ for n in nodes:
306
+ for neighbour in (*G.predecessors(n), *G.successors(n)):
307
+ if neighbour in nodes or neighbour not in node_part:
308
+ continue
309
+ fp = node_to_file.get(neighbour)
310
+ if fp and fp not in write_files:
311
+ read_only.add(fp)
312
+ return read_only
313
+
314
+
315
+ def _boundary_contracts(
316
+ G, nodes: set[int], node_part: dict[int, int], node_to_name: dict[int, str], node_to_sig: dict[int, str]
317
+ ) -> list[str]:
318
+ """Pass 120 — list cross-partition symbols this agent must not change."""
319
+ contracts: list[str] = []
320
+ seen: set[str] = set()
321
+ for n in nodes:
322
+ for neighbour in (*G.successors(n), *G.predecessors(n)):
323
+ if neighbour in nodes or neighbour not in node_part:
324
+ continue
325
+ name = node_to_name.get(neighbour)
326
+ sig = node_to_sig.get(neighbour)
327
+ if not (name and sig):
328
+ continue
329
+ contract = f"do NOT modify {sig} signature"
330
+ if contract not in seen:
331
+ seen.add(contract)
332
+ contracts.append(contract)
333
+ return contracts
334
+
335
+
336
+ def _cluster_label_for(nodes: set[int], node_to_file: dict[int, str], idx: int) -> str:
337
+ """Pass 120 — directory-majority label, with sensible fallbacks."""
338
+ dirs = [os.path.dirname(node_to_file.get(n, "")).replace("\\", "/") for n in nodes if n in node_to_file]
339
+ counts = Counter(dirs) if dirs else Counter()
340
+ if not counts:
341
+ return f"partition-{idx + 1}"
342
+ label = counts.most_common(1)[0][0]
343
+ if not label:
344
+ return f"root-{idx + 1}"
345
+ return label.rstrip("/").rsplit("/", 1)[-1] or f"partition-{idx + 1}"
305
346
 
306
- # Build per-partition write file sets (exclusive, no overlap)
307
- partition_write_files: dict[int, set[str]] = defaultdict(set)
308
- for fp, pidx in file_owner.items():
309
- partition_write_files[pidx].add(fp)
310
347
 
311
- # ── Build agent descriptors ──────────────────────────────────
312
- agents = []
348
+ def _build_agent_descriptors(
349
+ G: nx.DiGraph,
350
+ conn: sqlite3.Connection,
351
+ partitions: list[dict[str, set[int]]],
352
+ ) -> list[dict]:
353
+ """Build per-agent descriptor dicts.
354
+
355
+ Pass 120 — orchestrator only. Per-step logic lives in
356
+ ``_node_partition_index``, ``_fetch_node_metadata``,
357
+ ``_file_majority_owners``, ``_read_only_files_for``,
358
+ ``_boundary_contracts``, ``_cluster_label_for``.
359
+ Cognitive complexity dropped from 161 to ~10.
360
+
361
+ File ownership is determined by majority vote: each file is
362
+ assigned exclusively to the partition that owns the most of its
363
+ symbols. This guarantees no write overlap between agents.
364
+ """
365
+ node_part = _node_partition_index(partitions)
366
+ node_to_file, node_to_name, node_to_sig = _fetch_node_metadata(conn, list(node_part.keys()))
367
+ partition_write_files = _file_majority_owners(node_part, node_to_file)
368
+
369
+ agents: list[dict] = []
313
370
  for idx, p in enumerate(partitions):
314
371
  nodes = p["nodes"]
315
372
  if not nodes:
@@ -324,66 +381,19 @@ def _build_agent_descriptors(
324
381
  }
325
382
  )
326
383
  continue
327
-
328
- write_files_set = partition_write_files.get(idx, set())
329
-
330
- # Read-only files: files with edges into/from this partition
331
- # but owned by another partition
332
- read_only_set: set[str] = set()
333
- for n in nodes:
334
- for pred in G.predecessors(n):
335
- if pred not in nodes and pred in node_part:
336
- fp = node_to_file.get(pred)
337
- if fp and fp not in write_files_set:
338
- read_only_set.add(fp)
339
- for succ in G.successors(n):
340
- if succ not in nodes and succ in node_part:
341
- fp = node_to_file.get(succ)
342
- if fp and fp not in write_files_set:
343
- read_only_set.add(fp)
344
-
345
- # Contracts: symbols at boundaries that this agent must NOT modify
346
- contracts = []
347
- for n in nodes:
348
- for succ in G.successors(n):
349
- if succ not in nodes and succ in node_part:
350
- name = node_to_name.get(succ)
351
- sig = node_to_sig.get(succ)
352
- if name and sig:
353
- contract = f"do NOT modify {sig} signature"
354
- if contract not in contracts:
355
- contracts.append(contract)
356
- for pred in G.predecessors(n):
357
- if pred not in nodes and pred in node_part:
358
- name = node_to_name.get(pred)
359
- sig = node_to_sig.get(pred)
360
- if name and sig:
361
- contract = f"do NOT modify {sig} signature"
362
- if contract not in contracts:
363
- contracts.append(contract)
364
-
365
- # Cluster label from directory majority
366
- dirs = [os.path.dirname(node_to_file.get(n, "")).replace("\\", "/") for n in nodes if n in node_to_file]
367
- dir_counts = Counter(dirs) if dirs else Counter()
368
- if dir_counts:
369
- label = dir_counts.most_common(1)[0][0]
370
- label = label.rstrip("/").rsplit("/", 1)[-1] if label else f"partition-{idx + 1}"
371
- if not label:
372
- label = f"root-{idx + 1}"
373
- else:
374
- label = f"partition-{idx + 1}"
375
-
384
+ write_files = partition_write_files.get(idx, set())
385
+ read_only = _read_only_files_for(G, nodes, node_part, node_to_file, write_files)
386
+ contracts = _boundary_contracts(G, nodes, node_part, node_to_name, node_to_sig)
376
387
  agents.append(
377
388
  {
378
389
  "id": idx + 1,
379
- "write_files": sorted(write_files_set),
380
- "read_only_files": sorted(read_only_set),
390
+ "write_files": sorted(write_files),
391
+ "read_only_files": sorted(read_only),
381
392
  "symbols_owned": len(nodes),
382
- "contracts": contracts[:10], # cap at 10
383
- "cluster_label": label,
393
+ "contracts": contracts[:10],
394
+ "cluster_label": _cluster_label_for(nodes, node_to_file, idx),
384
395
  }
385
396
  )
386
-
387
397
  return agents
388
398
 
389
399