roam-code 12.27__tar.gz → 12.28__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 (650) hide show
  1. {roam_code-12.27/src/roam_code.egg-info → roam_code-12.28}/PKG-INFO +5 -4
  2. {roam_code-12.27 → roam_code-12.28}/README.md +4 -3
  3. {roam_code-12.27 → roam_code-12.28}/pyproject.toml +1 -1
  4. {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/detectors.py +298 -7
  5. {roam_code-12.27 → roam_code-12.28}/src/roam/cli.py +2 -0
  6. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_auth_gaps.py +90 -11
  7. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_math.py +34 -6
  8. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_missing_index.py +82 -3
  9. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_over_fetch.py +86 -17
  10. roam_code-12.28/src/roam/commands/cmd_suppress.py +138 -0
  11. roam_code-12.28/src/roam/commands/finding_suppress.py +258 -0
  12. {roam_code-12.27 → roam_code-12.28/src/roam_code.egg-info}/PKG-INFO +5 -4
  13. {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/SOURCES.txt +5 -0
  14. roam_code-12.28/tests/test_finding_suppress.py +220 -0
  15. roam_code-12.28/tests/test_laravel_fp_fixes.py +169 -0
  16. roam_code-12.28/tests/test_math_fp_fixes.py +243 -0
  17. {roam_code-12.27 → roam_code-12.28}/LICENSE +0 -0
  18. {roam_code-12.27 → roam_code-12.28}/setup.cfg +0 -0
  19. {roam_code-12.27 → roam_code-12.28}/src/roam/__init__.py +0 -0
  20. {roam_code-12.27 → roam_code-12.28}/src/roam/__main__.py +0 -0
  21. {roam_code-12.27 → roam_code-12.28}/src/roam/analysis/__init__.py +0 -0
  22. {roam_code-12.27 → roam_code-12.28}/src/roam/analysis/effects.py +0 -0
  23. {roam_code-12.27 → roam_code-12.28}/src/roam/analysis/taint.py +0 -0
  24. {roam_code-12.27 → roam_code-12.28}/src/roam/api.py +0 -0
  25. {roam_code-12.27 → roam_code-12.28}/src/roam/ask/__init__.py +0 -0
  26. {roam_code-12.27 → roam_code-12.28}/src/roam/ask/classifier.py +0 -0
  27. {roam_code-12.27 → roam_code-12.28}/src/roam/ask/recipes.py +0 -0
  28. {roam_code-12.27 → roam_code-12.28}/src/roam/ask/runner.py +0 -0
  29. {roam_code-12.27 → roam_code-12.28}/src/roam/ask/workflow.py +0 -0
  30. {roam_code-12.27 → roam_code-12.28}/src/roam/attest/__init__.py +0 -0
  31. {roam_code-12.27 → roam_code-12.28}/src/roam/attest/cga.py +0 -0
  32. {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/__init__.py +0 -0
  33. {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/base.py +0 -0
  34. {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_config.py +0 -0
  35. {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_django.py +0 -0
  36. {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_protobuf.py +0 -0
  37. {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_rest_api.py +0 -0
  38. {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_salesforce.py +0 -0
  39. {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/bridge_template.py +0 -0
  40. {roam_code-12.27 → roam_code-12.28}/src/roam/bridges/registry.py +0 -0
  41. {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/__init__.py +0 -0
  42. {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/fixes.py +0 -0
  43. {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/python_idioms.py +0 -0
  44. {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/smells.py +0 -0
  45. {roam_code-12.27 → roam_code-12.28}/src/roam/catalog/tasks.py +0 -0
  46. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/__init__.py +0 -0
  47. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/audit_trail_helpers.py +0 -0
  48. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/changed_files.py +0 -0
  49. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_adrs.py +0 -0
  50. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_adversarial.py +0 -0
  51. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_affected.py +0 -0
  52. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_affected_tests.py +0 -0
  53. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_agent_context.py +0 -0
  54. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_agent_export.py +0 -0
  55. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_agent_plan.py +0 -0
  56. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ai_ratio.py +0 -0
  57. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ai_readiness.py +0 -0
  58. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_alerts.py +0 -0
  59. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_annotate.py +0 -0
  60. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_api.py +0 -0
  61. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_api_changes.py +0 -0
  62. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_api_drift.py +0 -0
  63. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ask.py +0 -0
  64. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_attest.py +0 -0
  65. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_audit.py +0 -0
  66. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_audit_trail_conformance.py +0 -0
  67. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_audit_trail_export.py +0 -0
  68. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_audit_trail_verify.py +0 -0
  69. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_bisect.py +0 -0
  70. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_breaking.py +0 -0
  71. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_budget.py +0 -0
  72. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_bus_factor.py +0 -0
  73. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_capsule.py +0 -0
  74. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_cga.py +0 -0
  75. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_changelog.py +0 -0
  76. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_check_rules.py +0 -0
  77. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ci_setup.py +0 -0
  78. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_clean.py +0 -0
  79. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_clones.py +0 -0
  80. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_closure.py +0 -0
  81. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_clusters.py +0 -0
  82. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_codeowners.py +0 -0
  83. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_complexity.py +0 -0
  84. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_config.py +0 -0
  85. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_congestion.py +0 -0
  86. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_context.py +0 -0
  87. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_conventions.py +0 -0
  88. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_coupling.py +0 -0
  89. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_coverage_gaps.py +0 -0
  90. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_critique.py +0 -0
  91. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_cut.py +0 -0
  92. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dark_matter.py +0 -0
  93. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dashboard.py +0 -0
  94. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dead.py +0 -0
  95. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_debt.py +0 -0
  96. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_deps.py +0 -0
  97. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_describe.py +0 -0
  98. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dev_profile.py +0 -0
  99. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_diagnose.py +0 -0
  100. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_diff.py +0 -0
  101. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_disambiguate.py +0 -0
  102. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_doc_staleness.py +0 -0
  103. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_docs_coverage.py +0 -0
  104. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_doctor.py +0 -0
  105. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_dogfood.py +0 -0
  106. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_drift.py +0 -0
  107. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_duplicates.py +0 -0
  108. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_effects.py +0 -0
  109. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_endpoints.py +0 -0
  110. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_entry_points.py +0 -0
  111. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_eval_retrieve.py +0 -0
  112. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_exit_codes.py +0 -0
  113. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fan.py +0 -0
  114. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_file.py +0 -0
  115. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fingerprint.py +0 -0
  116. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fitness.py +0 -0
  117. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_flag_dead.py +0 -0
  118. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fleet.py +0 -0
  119. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_fn_coupling.py +0 -0
  120. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_forecast.py +0 -0
  121. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_graph_export.py +0 -0
  122. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_graph_stats.py +0 -0
  123. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_grep.py +0 -0
  124. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_guard.py +0 -0
  125. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_health.py +0 -0
  126. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_help_search.py +0 -0
  127. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_hooks.py +0 -0
  128. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_hotspots.py +0 -0
  129. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_hover.py +0 -0
  130. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_impact.py +0 -0
  131. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_index.py +0 -0
  132. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_index_bundle.py +0 -0
  133. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_index_stats.py +0 -0
  134. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ingest_trace.py +0 -0
  135. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_init.py +0 -0
  136. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_intent.py +0 -0
  137. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_invariants.py +0 -0
  138. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_layers.py +0 -0
  139. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_map.py +0 -0
  140. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_mcp_setup.py +0 -0
  141. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_mcp_status.py +0 -0
  142. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_metrics.py +0 -0
  143. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_metrics_push.py +0 -0
  144. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_migration_safety.py +0 -0
  145. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_minimap.py +0 -0
  146. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_module.py +0 -0
  147. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_mutate.py +0 -0
  148. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_n1.py +0 -0
  149. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_oracle.py +0 -0
  150. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_orchestrate.py +0 -0
  151. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_orphan_imports.py +0 -0
  152. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_orphan_routes.py +0 -0
  153. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_owner.py +0 -0
  154. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_partition.py +0 -0
  155. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_path_coverage.py +0 -0
  156. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_patterns.py +0 -0
  157. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_plan.py +0 -0
  158. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_plan_refactor.py +0 -0
  159. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_plugins.py +0 -0
  160. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_analyze.py +0 -0
  161. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_comment_render.py +0 -0
  162. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_diff.py +0 -0
  163. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_prep.py +0 -0
  164. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pr_risk.py +0 -0
  165. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pre_commit.py +0 -0
  166. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_preflight.py +0 -0
  167. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_py_modern.py +0 -0
  168. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_py_types.py +0 -0
  169. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_pytest_fixtures.py +0 -0
  170. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_recipes.py +0 -0
  171. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_recommend.py +0 -0
  172. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_relate.py +0 -0
  173. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_report.py +0 -0
  174. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_reset.py +0 -0
  175. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_retrieve.py +0 -0
  176. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_risk.py +0 -0
  177. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_rules.py +0 -0
  178. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_rules_validate.py +0 -0
  179. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_safe_delete.py +0 -0
  180. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_safe_zones.py +0 -0
  181. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_sbom.py +0 -0
  182. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_schema.py +0 -0
  183. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_search.py +0 -0
  184. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_search_semantic.py +0 -0
  185. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_secrets.py +0 -0
  186. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_semantic_diff.py +0 -0
  187. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_simulate.py +0 -0
  188. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_simulate_departure.py +0 -0
  189. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_sketch.py +0 -0
  190. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_smells.py +0 -0
  191. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_spectral.py +0 -0
  192. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_split.py +0 -0
  193. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_stats.py +0 -0
  194. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
  195. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
  196. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_supply_chain.py +0 -0
  197. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_symbol.py +0 -0
  198. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_syntax_check.py +0 -0
  199. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_taint.py +0 -0
  200. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_telemetry.py +0 -0
  201. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_test_gaps.py +0 -0
  202. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_test_impact.py +0 -0
  203. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_test_pyramid.py +0 -0
  204. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_test_scaffold.py +0 -0
  205. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_testmap.py +0 -0
  206. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_timeline.py +0 -0
  207. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_tour.py +0 -0
  208. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_trace.py +0 -0
  209. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_trends.py +0 -0
  210. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_triage.py +0 -0
  211. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_understand.py +0 -0
  212. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_uses.py +0 -0
  213. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_verify.py +0 -0
  214. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_verify_imports.py +0 -0
  215. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_version.py +0 -0
  216. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_vibe_check.py +0 -0
  217. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_visualize.py +0 -0
  218. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_vuln_map.py +0 -0
  219. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_vuln_reach.py +0 -0
  220. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_vulns.py +0 -0
  221. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_watch.py +0 -0
  222. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_weather.py +0 -0
  223. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_why.py +0 -0
  224. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_why_fail.py +0 -0
  225. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_workflow.py +0 -0
  226. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_ws.py +0 -0
  227. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/cmd_xlang.py +0 -0
  228. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/codeowners_helpers.py +0 -0
  229. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/context_helpers.py +0 -0
  230. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/gate_presets.py +0 -0
  231. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/git_helpers.py +0 -0
  232. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/graph_helpers.py +0 -0
  233. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/metrics_history.py +0 -0
  234. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/next_steps.py +0 -0
  235. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/resolve.py +0 -0
  236. {roam_code-12.27 → roam_code-12.28}/src/roam/commands/suppression.py +0 -0
  237. {roam_code-12.27 → roam_code-12.28}/src/roam/competitor_site_data.py +0 -0
  238. {roam_code-12.27 → roam_code-12.28}/src/roam/config.py +0 -0
  239. {roam_code-12.27 → roam_code-12.28}/src/roam/coverage_reports.py +0 -0
  240. {roam_code-12.27 → roam_code-12.28}/src/roam/critique/__init__.py +0 -0
  241. {roam_code-12.27 → roam_code-12.28}/src/roam/critique/aggregator.py +0 -0
  242. {roam_code-12.27 → roam_code-12.28}/src/roam/critique/checks.py +0 -0
  243. {roam_code-12.27 → roam_code-12.28}/src/roam/db/__init__.py +0 -0
  244. {roam_code-12.27 → roam_code-12.28}/src/roam/db/connection.py +0 -0
  245. {roam_code-12.27 → roam_code-12.28}/src/roam/db/queries.py +0 -0
  246. {roam_code-12.27 → roam_code-12.28}/src/roam/db/schema.py +0 -0
  247. {roam_code-12.27 → roam_code-12.28}/src/roam/eval/__init__.py +0 -0
  248. {roam_code-12.27 → roam_code-12.28}/src/roam/eval/harness.py +0 -0
  249. {roam_code-12.27 → roam_code-12.28}/src/roam/exit_codes.py +0 -0
  250. {roam_code-12.27 → roam_code-12.28}/src/roam/fleet/__init__.py +0 -0
  251. {roam_code-12.27 → roam_code-12.28}/src/roam/fleet/adapters.py +0 -0
  252. {roam_code-12.27 → roam_code-12.28}/src/roam/fleet/manifest.py +0 -0
  253. {roam_code-12.27 → roam_code-12.28}/src/roam/git_utils.py +0 -0
  254. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/__init__.py +0 -0
  255. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/anomaly.py +0 -0
  256. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/builder.py +0 -0
  257. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/clone_detect.py +0 -0
  258. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/clusters.py +0 -0
  259. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/cycles.py +0 -0
  260. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/dark_matter.py +0 -0
  261. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/diff.py +0 -0
  262. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/fingerprint.py +0 -0
  263. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/layers.py +0 -0
  264. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/pagerank.py +0 -0
  265. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/partition.py +0 -0
  266. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/pathfinding.py +0 -0
  267. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/propagation.py +0 -0
  268. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/simulate.py +0 -0
  269. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/spectral.py +0 -0
  270. {roam_code-12.27 → roam_code-12.28}/src/roam/graph/stats.py +0 -0
  271. {roam_code-12.27 → roam_code-12.28}/src/roam/index/__init__.py +0 -0
  272. {roam_code-12.27 → roam_code-12.28}/src/roam/index/complexity.py +0 -0
  273. {roam_code-12.27 → roam_code-12.28}/src/roam/index/discovery.py +0 -0
  274. {roam_code-12.27 → roam_code-12.28}/src/roam/index/django_post.py +0 -0
  275. {roam_code-12.27 → roam_code-12.28}/src/roam/index/file_roles.py +0 -0
  276. {roam_code-12.27 → roam_code-12.28}/src/roam/index/git_stats.py +0 -0
  277. {roam_code-12.27 → roam_code-12.28}/src/roam/index/gitignore.py +0 -0
  278. {roam_code-12.27 → roam_code-12.28}/src/roam/index/incremental.py +0 -0
  279. {roam_code-12.27 → roam_code-12.28}/src/roam/index/indexer.py +0 -0
  280. {roam_code-12.27 → roam_code-12.28}/src/roam/index/parser.py +0 -0
  281. {roam_code-12.27 → roam_code-12.28}/src/roam/index/pytest_fixtures.py +0 -0
  282. {roam_code-12.27 → roam_code-12.28}/src/roam/index/registry_dispatch.py +0 -0
  283. {roam_code-12.27 → roam_code-12.28}/src/roam/index/relations.py +0 -0
  284. {roam_code-12.27 → roam_code-12.28}/src/roam/index/symbols.py +0 -0
  285. {roam_code-12.27 → roam_code-12.28}/src/roam/index/test_conventions.py +0 -0
  286. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/__init__.py +0 -0
  287. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/apex_lang.py +0 -0
  288. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/aura_lang.py +0 -0
  289. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/base.py +0 -0
  290. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/c_lang.py +0 -0
  291. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/csharp_lang.py +0 -0
  292. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/extractor_schema.py +0 -0
  293. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/extractors/kotlin.yaml +0 -0
  294. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/foxpro_lang.py +0 -0
  295. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/generic_lang.py +0 -0
  296. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/go_lang.py +0 -0
  297. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/hcl_lang.py +0 -0
  298. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/java_lang.py +0 -0
  299. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/javascript_lang.py +0 -0
  300. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/kotlin_lang.py +0 -0
  301. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/php_lang.py +0 -0
  302. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/python_lang.py +0 -0
  303. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/query_engine.py +0 -0
  304. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/registry.py +0 -0
  305. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/ruby_lang.py +0 -0
  306. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/rust_lang.py +0 -0
  307. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/scala_lang.py +0 -0
  308. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/sfxml_lang.py +0 -0
  309. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/sql_lang.py +0 -0
  310. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/swift_lang.py +0 -0
  311. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/typescript_lang.py +0 -0
  312. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/visualforce_lang.py +0 -0
  313. {roam_code-12.27 → roam_code-12.28}/src/roam/languages/yaml_lang.py +0 -0
  314. {roam_code-12.27 → roam_code-12.28}/src/roam/mcp-server-card.json +0 -0
  315. {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/__init__.py +0 -0
  316. {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/completions.py +0 -0
  317. {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/concurrency.py +0 -0
  318. {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/progress.py +0 -0
  319. {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/sampling.py +0 -0
  320. {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/session.py +0 -0
  321. {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_extras/watcher.py +0 -0
  322. {roam_code-12.27 → roam_code-12.28}/src/roam/mcp_server.py +0 -0
  323. {roam_code-12.27 → roam_code-12.28}/src/roam/observability.py +0 -0
  324. {roam_code-12.27 → roam_code-12.28}/src/roam/output/__init__.py +0 -0
  325. {roam_code-12.27 → roam_code-12.28}/src/roam/output/confidence.py +0 -0
  326. {roam_code-12.27 → roam_code-12.28}/src/roam/output/errors.py +0 -0
  327. {roam_code-12.27 → roam_code-12.28}/src/roam/output/file_role_hints.py +0 -0
  328. {roam_code-12.27 → roam_code-12.28}/src/roam/output/formatter.py +0 -0
  329. {roam_code-12.27 → roam_code-12.28}/src/roam/output/framework_filter.py +0 -0
  330. {roam_code-12.27 → roam_code-12.28}/src/roam/output/mermaid.py +0 -0
  331. {roam_code-12.27 → roam_code-12.28}/src/roam/output/project_shape.py +0 -0
  332. {roam_code-12.27 → roam_code-12.28}/src/roam/output/sarif.py +0 -0
  333. {roam_code-12.27 → roam_code-12.28}/src/roam/output/schema_registry.py +0 -0
  334. {roam_code-12.27 → roam_code-12.28}/src/roam/plugins.py +0 -0
  335. {roam_code-12.27 → roam_code-12.28}/src/roam/refactor/__init__.py +0 -0
  336. {roam_code-12.27 → roam_code-12.28}/src/roam/refactor/codegen.py +0 -0
  337. {roam_code-12.27 → roam_code-12.28}/src/roam/refactor/transforms.py +0 -0
  338. {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/__init__.py +0 -0
  339. {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/learned_ranker.py +0 -0
  340. {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/pipeline.py +0 -0
  341. {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/rerank.py +0 -0
  342. {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/seeds.py +0 -0
  343. {roam_code-12.27 → roam_code-12.28}/src/roam/retrieve/semantic.py +0 -0
  344. {roam_code-12.27 → roam_code-12.28}/src/roam/rules/__init__.py +0 -0
  345. {roam_code-12.27 → roam_code-12.28}/src/roam/rules/ast_match.py +0 -0
  346. {roam_code-12.27 → roam_code-12.28}/src/roam/rules/builtin.py +0 -0
  347. {roam_code-12.27 → roam_code-12.28}/src/roam/rules/dataflow.py +0 -0
  348. {roam_code-12.27 → roam_code-12.28}/src/roam/rules/engine.py +0 -0
  349. {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/__init__.py +0 -0
  350. {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/daemon.py +0 -0
  351. {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/graph_backend.py +0 -0
  352. {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/hotspots.py +0 -0
  353. {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/lock_modes.py +0 -0
  354. {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/lockmgr.py +0 -0
  355. {roam_code-12.27 → roam_code-12.28}/src/roam/runtime/trace_ingest.py +0 -0
  356. {roam_code-12.27 → roam_code-12.28}/src/roam/search/__init__.py +0 -0
  357. {roam_code-12.27 → roam_code-12.28}/src/roam/search/framework_packs.py +0 -0
  358. {roam_code-12.27 → roam_code-12.28}/src/roam/search/index_embeddings.py +0 -0
  359. {roam_code-12.27 → roam_code-12.28}/src/roam/search/onnx_embeddings.py +0 -0
  360. {roam_code-12.27 → roam_code-12.28}/src/roam/search/tfidf.py +0 -0
  361. {roam_code-12.27 → roam_code-12.28}/src/roam/security/__init__.py +0 -0
  362. {roam_code-12.27 → roam_code-12.28}/src/roam/security/aibom_extension.py +0 -0
  363. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_classifier.py +0 -0
  364. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_engine.py +0 -0
  365. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/api_error_leak.yaml +0 -0
  366. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/java_fileupload_path_traversal.yaml +0 -0
  367. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_insecure_jwt_decode.yaml +0 -0
  368. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_localstorage_secrets.yaml +0 -0
  369. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_prototype_pollution.yaml +0 -0
  370. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_ssrf.yaml +0 -0
  371. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/js_xss.yaml +0 -0
  372. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_basic.yaml +0 -0
  373. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_deserialization.yaml +0 -0
  374. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_path_traversal.yaml +0 -0
  375. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_socketio_remote_source.yaml +0 -0
  376. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_sqli.yaml +0 -0
  377. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/python_urllib_open_redirect.yaml +0 -0
  378. {roam_code-12.27 → roam_code-12.28}/src/roam/security/taint_rules/vue_v_html.yaml +0 -0
  379. {roam_code-12.27 → roam_code-12.28}/src/roam/security/vuln_reach.py +0 -0
  380. {roam_code-12.27 → roam_code-12.28}/src/roam/security/vuln_store.py +0 -0
  381. {roam_code-12.27 → roam_code-12.28}/src/roam/surface_counts.py +0 -0
  382. {roam_code-12.27 → roam_code-12.28}/src/roam/telemetry.py +0 -0
  383. {roam_code-12.27 → roam_code-12.28}/src/roam/templates/__init__.py +0 -0
  384. {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/Jenkinsfile +0 -0
  385. {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/__init__.py +0 -0
  386. {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/agent-review.yml +0 -0
  387. {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/azure-pipelines.yml +0 -0
  388. {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/bitbucket-pipelines.yml +0 -0
  389. {roam_code-12.27 → roam_code-12.28}/src/roam/templates/ci/gitlab-ci.yml +0 -0
  390. {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/__init__.py +0 -0
  391. {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/aggregator.py +0 -0
  392. {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/api_scanner.py +0 -0
  393. {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/config.py +0 -0
  394. {roam_code-12.27 → roam_code-12.28}/src/roam/workspace/db.py +0 -0
  395. {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/dependency_links.txt +0 -0
  396. {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/entry_points.txt +0 -0
  397. {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/requires.txt +0 -0
  398. {roam_code-12.27 → roam_code-12.28}/src/roam_code.egg-info/top_level.txt +0 -0
  399. {roam_code-12.27 → roam_code-12.28}/tests/test_adrs.py +0 -0
  400. {roam_code-12.27 → roam_code-12.28}/tests/test_adversarial.py +0 -0
  401. {roam_code-12.27 → roam_code-12.28}/tests/test_affected.py +0 -0
  402. {roam_code-12.27 → roam_code-12.28}/tests/test_agent_export.py +0 -0
  403. {roam_code-12.27 → roam_code-12.28}/tests/test_agent_mode.py +0 -0
  404. {roam_code-12.27 → roam_code-12.28}/tests/test_agent_plan_context.py +0 -0
  405. {roam_code-12.27 → roam_code-12.28}/tests/test_ai_ratio.py +0 -0
  406. {roam_code-12.27 → roam_code-12.28}/tests/test_ai_readiness.py +0 -0
  407. {roam_code-12.27 → roam_code-12.28}/tests/test_alerts_cmd.py +0 -0
  408. {roam_code-12.27 → roam_code-12.28}/tests/test_annotations.py +0 -0
  409. {roam_code-12.27 → roam_code-12.28}/tests/test_anomaly.py +0 -0
  410. {roam_code-12.27 → roam_code-12.28}/tests/test_api_changes.py +0 -0
  411. {roam_code-12.27 → roam_code-12.28}/tests/test_api_drift.py +0 -0
  412. {roam_code-12.27 → roam_code-12.28}/tests/test_ask.py +0 -0
  413. {roam_code-12.27 → roam_code-12.28}/tests/test_attest.py +0 -0
  414. {roam_code-12.27 → roam_code-12.28}/tests/test_audit_trail_aggregate.py +0 -0
  415. {roam_code-12.27 → roam_code-12.28}/tests/test_audit_trail_conformance.py +0 -0
  416. {roam_code-12.27 → roam_code-12.28}/tests/test_audit_trail_sequence.py +0 -0
  417. {roam_code-12.27 → roam_code-12.28}/tests/test_audit_trail_verify.py +0 -0
  418. {roam_code-12.27 → roam_code-12.28}/tests/test_auth_gaps.py +0 -0
  419. {roam_code-12.27 → roam_code-12.28}/tests/test_backend_fixes_round2.py +0 -0
  420. {roam_code-12.27 → roam_code-12.28}/tests/test_backend_fixes_round3.py +0 -0
  421. {roam_code-12.27 → roam_code-12.28}/tests/test_basic.py +0 -0
  422. {roam_code-12.27 → roam_code-12.28}/tests/test_batch_mcp.py +0 -0
  423. {roam_code-12.27 → roam_code-12.28}/tests/test_bisect.py +0 -0
  424. {roam_code-12.27 → roam_code-12.28}/tests/test_bridge_django.py +0 -0
  425. {roam_code-12.27 → roam_code-12.28}/tests/test_bridges.py +0 -0
  426. {roam_code-12.27 → roam_code-12.28}/tests/test_bridges_extended.py +0 -0
  427. {roam_code-12.27 → roam_code-12.28}/tests/test_budget.py +0 -0
  428. {roam_code-12.27 → roam_code-12.28}/tests/test_budget_flag.py +0 -0
  429. {roam_code-12.27 → roam_code-12.28}/tests/test_budget_phase2.py +0 -0
  430. {roam_code-12.27 → roam_code-12.28}/tests/test_bus_factor.py +0 -0
  431. {roam_code-12.27 → roam_code-12.28}/tests/test_capsule.py +0 -0
  432. {roam_code-12.27 → roam_code-12.28}/tests/test_cga.py +0 -0
  433. {roam_code-12.27 → roam_code-12.28}/tests/test_check_rules.py +0 -0
  434. {roam_code-12.27 → roam_code-12.28}/tests/test_ci_gate_eval.py +0 -0
  435. {roam_code-12.27 → roam_code-12.28}/tests/test_ci_sarif_guard.py +0 -0
  436. {roam_code-12.27 → roam_code-12.28}/tests/test_ci_setup.py +0 -0
  437. {roam_code-12.27 → roam_code-12.28}/tests/test_clones.py +0 -0
  438. {roam_code-12.27 → roam_code-12.28}/tests/test_closure.py +0 -0
  439. {roam_code-12.27 → roam_code-12.28}/tests/test_codeowners.py +0 -0
  440. {roam_code-12.27 → roam_code-12.28}/tests/test_commands_architecture.py +0 -0
  441. {roam_code-12.27 → roam_code-12.28}/tests/test_commands_exploration.py +0 -0
  442. {roam_code-12.27 → roam_code-12.28}/tests/test_commands_health.py +0 -0
  443. {roam_code-12.27 → roam_code-12.28}/tests/test_commands_refactoring.py +0 -0
  444. {roam_code-12.27 → roam_code-12.28}/tests/test_commands_workflow.py +0 -0
  445. {roam_code-12.27 → roam_code-12.28}/tests/test_competitor_site_data.py +0 -0
  446. {roam_code-12.27 → roam_code-12.28}/tests/test_comprehensive.py +0 -0
  447. {roam_code-12.27 → roam_code-12.28}/tests/test_config.py +0 -0
  448. {roam_code-12.27 → roam_code-12.28}/tests/test_congestion.py +0 -0
  449. {roam_code-12.27 → roam_code-12.28}/tests/test_context_propagation.py +0 -0
  450. {roam_code-12.27 → roam_code-12.28}/tests/test_conventions_cmd.py +0 -0
  451. {roam_code-12.27 → roam_code-12.28}/tests/test_coverage_gaps_cmd.py +0 -0
  452. {roam_code-12.27 → roam_code-12.28}/tests/test_coverage_ingestion.py +0 -0
  453. {roam_code-12.27 → roam_code-12.28}/tests/test_critique.py +0 -0
  454. {roam_code-12.27 → roam_code-12.28}/tests/test_cut.py +0 -0
  455. {roam_code-12.27 → roam_code-12.28}/tests/test_dark_matter.py +0 -0
  456. {roam_code-12.27 → roam_code-12.28}/tests/test_dark_matter_helpers.py +0 -0
  457. {roam_code-12.27 → roam_code-12.28}/tests/test_dashboard.py +0 -0
  458. {roam_code-12.27 → roam_code-12.28}/tests/test_dataflow_dead.py +0 -0
  459. {roam_code-12.27 → roam_code-12.28}/tests/test_dead_aging.py +0 -0
  460. {roam_code-12.27 → roam_code-12.28}/tests/test_defer_loading.py +0 -0
  461. {roam_code-12.27 → roam_code-12.28}/tests/test_demo_gif_asset.py +0 -0
  462. {roam_code-12.27 → roam_code-12.28}/tests/test_describe.py +0 -0
  463. {roam_code-12.27 → roam_code-12.28}/tests/test_detail_flag_hints.py +0 -0
  464. {roam_code-12.27 → roam_code-12.28}/tests/test_detector_precision.py +0 -0
  465. {roam_code-12.27 → roam_code-12.28}/tests/test_deterministic_output.py +0 -0
  466. {roam_code-12.27 → roam_code-12.28}/tests/test_dev_profile.py +0 -0
  467. {roam_code-12.27 → roam_code-12.28}/tests/test_difficulty_scoring.py +0 -0
  468. {roam_code-12.27 → roam_code-12.28}/tests/test_doc_consistency.py +0 -0
  469. {roam_code-12.27 → roam_code-12.28}/tests/test_doc_staleness.py +0 -0
  470. {roam_code-12.27 → roam_code-12.28}/tests/test_docker_assets.py +0 -0
  471. {roam_code-12.27 → roam_code-12.28}/tests/test_docs_coverage.py +0 -0
  472. {roam_code-12.27 → roam_code-12.28}/tests/test_docs_site_quality.py +0 -0
  473. {roam_code-12.27 → roam_code-12.28}/tests/test_doctor.py +0 -0
  474. {roam_code-12.27 → roam_code-12.28}/tests/test_dogfood.py +0 -0
  475. {roam_code-12.27 → roam_code-12.28}/tests/test_drift.py +0 -0
  476. {roam_code-12.27 → roam_code-12.28}/tests/test_drift_by_team.py +0 -0
  477. {roam_code-12.27 → roam_code-12.28}/tests/test_duplicates.py +0 -0
  478. {roam_code-12.27 → roam_code-12.28}/tests/test_effects.py +0 -0
  479. {roam_code-12.27 → roam_code-12.28}/tests/test_effects_propagation.py +0 -0
  480. {roam_code-12.27 → roam_code-12.28}/tests/test_endpoints.py +0 -0
  481. {roam_code-12.27 → roam_code-12.28}/tests/test_entry_points_cmd.py +0 -0
  482. {roam_code-12.27 → roam_code-12.28}/tests/test_eval_retrieve.py +0 -0
  483. {roam_code-12.27 → roam_code-12.28}/tests/test_except_pass_narrow.py +0 -0
  484. {roam_code-12.27 → roam_code-12.28}/tests/test_exclude_patterns.py +0 -0
  485. {roam_code-12.27 → roam_code-12.28}/tests/test_exit_codes.py +0 -0
  486. {roam_code-12.27 → roam_code-12.28}/tests/test_extractor_grammar_drift.py +0 -0
  487. {roam_code-12.27 → roam_code-12.28}/tests/test_fallback_contracts.py +0 -0
  488. {roam_code-12.27 → roam_code-12.28}/tests/test_file_roles.py +0 -0
  489. {roam_code-12.27 → roam_code-12.28}/tests/test_fingerprint.py +0 -0
  490. {roam_code-12.27 → roam_code-12.28}/tests/test_fixes.py +0 -0
  491. {roam_code-12.27 → roam_code-12.28}/tests/test_flag_dead.py +0 -0
  492. {roam_code-12.27 → roam_code-12.28}/tests/test_fleet.py +0 -0
  493. {roam_code-12.27 → roam_code-12.28}/tests/test_fn_coupling.py +0 -0
  494. {roam_code-12.27 → roam_code-12.28}/tests/test_forecast.py +0 -0
  495. {roam_code-12.27 → roam_code-12.28}/tests/test_formatters.py +0 -0
  496. {roam_code-12.27 → roam_code-12.28}/tests/test_foxpro.py +0 -0
  497. {roam_code-12.27 → roam_code-12.28}/tests/test_framework_detection.py +0 -0
  498. {roam_code-12.27 → roam_code-12.28}/tests/test_gate_presets.py +0 -0
  499. {roam_code-12.27 → roam_code-12.28}/tests/test_git_helpers.py +0 -0
  500. {roam_code-12.27 → roam_code-12.28}/tests/test_git_utils.py +0 -0
  501. {roam_code-12.27 → roam_code-12.28}/tests/test_guard.py +0 -0
  502. {roam_code-12.27 → roam_code-12.28}/tests/test_health_gate.py +0 -0
  503. {roam_code-12.27 → roam_code-12.28}/tests/test_hooks.py +0 -0
  504. {roam_code-12.27 → roam_code-12.28}/tests/test_hotspots.py +0 -0
  505. {roam_code-12.27 → roam_code-12.28}/tests/test_hover.py +0 -0
  506. {roam_code-12.27 → roam_code-12.28}/tests/test_index.py +0 -0
  507. {roam_code-12.27 → roam_code-12.28}/tests/test_index_bundle.py +0 -0
  508. {roam_code-12.27 → roam_code-12.28}/tests/test_init_cmd.py +0 -0
  509. {roam_code-12.27 → roam_code-12.28}/tests/test_install_check.py +0 -0
  510. {roam_code-12.27 → roam_code-12.28}/tests/test_intent.py +0 -0
  511. {roam_code-12.27 → roam_code-12.28}/tests/test_invariants.py +0 -0
  512. {roam_code-12.27 → roam_code-12.28}/tests/test_json_contracts.py +0 -0
  513. {roam_code-12.27 → roam_code-12.28}/tests/test_json_error_envelope.py +0 -0
  514. {roam_code-12.27 → roam_code-12.28}/tests/test_kotlin_swift_extractors.py +0 -0
  515. {roam_code-12.27 → roam_code-12.28}/tests/test_language_corpus.py +0 -0
  516. {roam_code-12.27 → roam_code-12.28}/tests/test_languages.py +0 -0
  517. {roam_code-12.27 → roam_code-12.28}/tests/test_library_api.py +0 -0
  518. {roam_code-12.27 → roam_code-12.28}/tests/test_math.py +0 -0
  519. {roam_code-12.27 → roam_code-12.28}/tests/test_math_tips.py +0 -0
  520. {roam_code-12.27 → roam_code-12.28}/tests/test_mcp_extras.py +0 -0
  521. {roam_code-12.27 → roam_code-12.28}/tests/test_mcp_server.py +0 -0
  522. {roam_code-12.27 → roam_code-12.28}/tests/test_mcp_setup.py +0 -0
  523. {roam_code-12.27 → roam_code-12.28}/tests/test_mermaid.py +0 -0
  524. {roam_code-12.27 → roam_code-12.28}/tests/test_metrics_cmd.py +0 -0
  525. {roam_code-12.27 → roam_code-12.28}/tests/test_metrics_push.py +0 -0
  526. {roam_code-12.27 → roam_code-12.28}/tests/test_migration_safety.py +0 -0
  527. {roam_code-12.27 → roam_code-12.28}/tests/test_minimap.py +0 -0
  528. {roam_code-12.27 → roam_code-12.28}/tests/test_missing_index.py +0 -0
  529. {roam_code-12.27 → roam_code-12.28}/tests/test_mutate.py +0 -0
  530. {roam_code-12.27 → roam_code-12.28}/tests/test_n1.py +0 -0
  531. {roam_code-12.27 → roam_code-12.28}/tests/test_next_steps.py +0 -0
  532. {roam_code-12.27 → roam_code-12.28}/tests/test_onboard.py +0 -0
  533. {roam_code-12.27 → roam_code-12.28}/tests/test_oracle.py +0 -0
  534. {roam_code-12.27 → roam_code-12.28}/tests/test_orchestrate.py +0 -0
  535. {roam_code-12.27 → roam_code-12.28}/tests/test_orphan_routes.py +0 -0
  536. {roam_code-12.27 → roam_code-12.28}/tests/test_oss_bench_harness.py +0 -0
  537. {roam_code-12.27 → roam_code-12.28}/tests/test_over_fetch.py +0 -0
  538. {roam_code-12.27 → roam_code-12.28}/tests/test_pagerank_truncation.py +0 -0
  539. {roam_code-12.27 → roam_code-12.28}/tests/test_partition.py +0 -0
  540. {roam_code-12.27 → roam_code-12.28}/tests/test_path_coverage.py +0 -0
  541. {roam_code-12.27 → roam_code-12.28}/tests/test_patterns_cmd.py +0 -0
  542. {roam_code-12.27 → roam_code-12.28}/tests/test_performance.py +0 -0
  543. {roam_code-12.27 → roam_code-12.28}/tests/test_personalized_pagerank.py +0 -0
  544. {roam_code-12.27 → roam_code-12.28}/tests/test_plan.py +0 -0
  545. {roam_code-12.27 → roam_code-12.28}/tests/test_plugin_discovery.py +0 -0
  546. {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze.py +0 -0
  547. {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze_cache.py +0 -0
  548. {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze_edge_cases.py +0 -0
  549. {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze_helpers.py +0 -0
  550. {roam_code-12.27 → roam_code-12.28}/tests/test_pr_analyze_v2_signals.py +0 -0
  551. {roam_code-12.27 → roam_code-12.28}/tests/test_pr_comment_render.py +0 -0
  552. {roam_code-12.27 → roam_code-12.28}/tests/test_pr_comment_script.py +0 -0
  553. {roam_code-12.27 → roam_code-12.28}/tests/test_pr_diff.py +0 -0
  554. {roam_code-12.27 → roam_code-12.28}/tests/test_pr_risk_author.py +0 -0
  555. {roam_code-12.27 → roam_code-12.28}/tests/test_progress.py +0 -0
  556. {roam_code-12.27 → roam_code-12.28}/tests/test_progressive_disclosure.py +0 -0
  557. {roam_code-12.27 → roam_code-12.28}/tests/test_properties.py +0 -0
  558. {roam_code-12.27 → roam_code-12.28}/tests/test_pytest_fixtures.py +0 -0
  559. {roam_code-12.27 → roam_code-12.28}/tests/test_python_extractor_v2.py +0 -0
  560. {roam_code-12.27 → roam_code-12.28}/tests/test_python_idioms_e2e.py +0 -0
  561. {roam_code-12.27 → roam_code-12.28}/tests/test_python_pivot.py +0 -0
  562. {roam_code-12.27 → roam_code-12.28}/tests/test_readme_surface_consistency.py +0 -0
  563. {roam_code-12.27 → roam_code-12.28}/tests/test_realworld_feedback.py +0 -0
  564. {roam_code-12.27 → roam_code-12.28}/tests/test_refactoring_intelligence.py +0 -0
  565. {roam_code-12.27 → roam_code-12.28}/tests/test_registry_dispatch.py +0 -0
  566. {roam_code-12.27 → roam_code-12.28}/tests/test_relate.py +0 -0
  567. {roam_code-12.27 → roam_code-12.28}/tests/test_report.py +0 -0
  568. {roam_code-12.27 → roam_code-12.28}/tests/test_reset_clean.py +0 -0
  569. {roam_code-12.27 → roam_code-12.28}/tests/test_resolve.py +0 -0
  570. {roam_code-12.27 → roam_code-12.28}/tests/test_retrieve.py +0 -0
  571. {roam_code-12.27 → roam_code-12.28}/tests/test_retrieve_cross_repo.py +0 -0
  572. {roam_code-12.27 → roam_code-12.28}/tests/test_retrieve_seeds.py +0 -0
  573. {roam_code-12.27 → roam_code-12.28}/tests/test_risk.py +0 -0
  574. {roam_code-12.27 → roam_code-12.28}/tests/test_ruby.py +0 -0
  575. {roam_code-12.27 → roam_code-12.28}/tests/test_rule_profiles.py +0 -0
  576. {roam_code-12.27 → roam_code-12.28}/tests/test_rules.py +0 -0
  577. {roam_code-12.27 → roam_code-12.28}/tests/test_rules_ast_match.py +0 -0
  578. {roam_code-12.27 → roam_code-12.28}/tests/test_rules_community_pack.py +0 -0
  579. {roam_code-12.27 → roam_code-12.28}/tests/test_rules_dataflow.py +0 -0
  580. {roam_code-12.27 → roam_code-12.28}/tests/test_rules_symbol_requirements.py +0 -0
  581. {roam_code-12.27 → roam_code-12.28}/tests/test_rules_validate.py +0 -0
  582. {roam_code-12.27 → roam_code-12.28}/tests/test_runtime.py +0 -0
  583. {roam_code-12.27 → roam_code-12.28}/tests/test_runtime_score.py +0 -0
  584. {roam_code-12.27 → roam_code-12.28}/tests/test_salesforce.py +0 -0
  585. {roam_code-12.27 → roam_code-12.28}/tests/test_sarif_flag.py +0 -0
  586. {roam_code-12.27 → roam_code-12.28}/tests/test_sbom.py +0 -0
  587. {roam_code-12.27 → roam_code-12.28}/tests/test_scala.py +0 -0
  588. {roam_code-12.27 → roam_code-12.28}/tests/test_schema_versioning.py +0 -0
  589. {roam_code-12.27 → roam_code-12.28}/tests/test_search_explain.py +0 -0
  590. {roam_code-12.27 → roam_code-12.28}/tests/test_secrets.py +0 -0
  591. {roam_code-12.27 → roam_code-12.28}/tests/test_secrets_v2.py +0 -0
  592. {roam_code-12.27 → roam_code-12.28}/tests/test_semantic_diff.py +0 -0
  593. {roam_code-12.27 → roam_code-12.28}/tests/test_semantic_onnx.py +0 -0
  594. {roam_code-12.27 → roam_code-12.28}/tests/test_semantic_search.py +0 -0
  595. {roam_code-12.27 → roam_code-12.28}/tests/test_simulate.py +0 -0
  596. {roam_code-12.27 → roam_code-12.28}/tests/test_simulate_departure.py +0 -0
  597. {roam_code-12.27 → roam_code-12.28}/tests/test_sketch.py +0 -0
  598. {roam_code-12.27 → roam_code-12.28}/tests/test_smells.py +0 -0
  599. {roam_code-12.27 → roam_code-12.28}/tests/test_smoke.py +0 -0
  600. {roam_code-12.27 → roam_code-12.28}/tests/test_sna_metrics.py +0 -0
  601. {roam_code-12.27 → roam_code-12.28}/tests/test_spectral.py +0 -0
  602. {roam_code-12.27 → roam_code-12.28}/tests/test_split_cmd.py +0 -0
  603. {roam_code-12.27 → roam_code-12.28}/tests/test_sql.py +0 -0
  604. {roam_code-12.27 → roam_code-12.28}/tests/test_suggest_reviewers.py +0 -0
  605. {roam_code-12.27 → roam_code-12.28}/tests/test_supply_chain.py +0 -0
  606. {roam_code-12.27 → roam_code-12.28}/tests/test_surface_counts.py +0 -0
  607. {roam_code-12.27 → roam_code-12.28}/tests/test_syntax_check.py +0 -0
  608. {roam_code-12.27 → roam_code-12.28}/tests/test_taint.py +0 -0
  609. {roam_code-12.27 → roam_code-12.28}/tests/test_taint_analysis.py +0 -0
  610. {roam_code-12.27 → roam_code-12.28}/tests/test_taint_classifier.py +0 -0
  611. {roam_code-12.27 → roam_code-12.28}/tests/test_taint_intraprocedural.py +0 -0
  612. {roam_code-12.27 → roam_code-12.28}/tests/test_test_conventions.py +0 -0
  613. {roam_code-12.27 → roam_code-12.28}/tests/test_test_gaps.py +0 -0
  614. {roam_code-12.27 → roam_code-12.28}/tests/test_test_scaffold.py +0 -0
  615. {roam_code-12.27 → roam_code-12.28}/tests/test_testmap.py +0 -0
  616. {roam_code-12.27 → roam_code-12.28}/tests/test_top_flag_consistency.py +0 -0
  617. {roam_code-12.27 → roam_code-12.28}/tests/test_tour_cmd.py +0 -0
  618. {roam_code-12.27 → roam_code-12.28}/tests/test_trends.py +0 -0
  619. {roam_code-12.27 → roam_code-12.28}/tests/test_trends_cohort.py +0 -0
  620. {roam_code-12.27 → roam_code-12.28}/tests/test_triage.py +0 -0
  621. {roam_code-12.27 → roam_code-12.28}/tests/test_uses_cmd.py +0 -0
  622. {roam_code-12.27 → roam_code-12.28}/tests/test_v1215_passes.py +0 -0
  623. {roam_code-12.27 → roam_code-12.28}/tests/test_v1216_passes.py +0 -0
  624. {roam_code-12.27 → roam_code-12.28}/tests/test_v1216_passes_41_50.py +0 -0
  625. {roam_code-12.27 → roam_code-12.28}/tests/test_v1216_passes_51_60.py +0 -0
  626. {roam_code-12.27 → roam_code-12.28}/tests/test_v1217_passes_61_80.py +0 -0
  627. {roam_code-12.27 → roam_code-12.28}/tests/test_v1218_passes_81_90.py +0 -0
  628. {roam_code-12.27 → roam_code-12.28}/tests/test_v1219_passes_91_100.py +0 -0
  629. {roam_code-12.27 → roam_code-12.28}/tests/test_v1220_passes_101_110.py +0 -0
  630. {roam_code-12.27 → roam_code-12.28}/tests/test_v1221_query_timeout.py +0 -0
  631. {roam_code-12.27 → roam_code-12.28}/tests/test_v1221_untested_commands.py +0 -0
  632. {roam_code-12.27 → roam_code-12.28}/tests/test_v12_2.py +0 -0
  633. {roam_code-12.27 → roam_code-12.28}/tests/test_v2_edge_cases.py +0 -0
  634. {roam_code-12.27 → roam_code-12.28}/tests/test_v2_integration.py +0 -0
  635. {roam_code-12.27 → roam_code-12.28}/tests/test_v6_features.py +0 -0
  636. {roam_code-12.27 → roam_code-12.28}/tests/test_v71_features.py +0 -0
  637. {roam_code-12.27 → roam_code-12.28}/tests/test_v7_features.py +0 -0
  638. {roam_code-12.27 → roam_code-12.28}/tests/test_v82_features.py +0 -0
  639. {roam_code-12.27 → roam_code-12.28}/tests/test_verify.py +0 -0
  640. {roam_code-12.27 → roam_code-12.28}/tests/test_verify_imports.py +0 -0
  641. {roam_code-12.27 → roam_code-12.28}/tests/test_vibe_check.py +0 -0
  642. {roam_code-12.27 → roam_code-12.28}/tests/test_visualize.py +0 -0
  643. {roam_code-12.27 → roam_code-12.28}/tests/test_vuln.py +0 -0
  644. {roam_code-12.27 → roam_code-12.28}/tests/test_vulns_cmd.py +0 -0
  645. {roam_code-12.27 → roam_code-12.28}/tests/test_watch.py +0 -0
  646. {roam_code-12.27 → roam_code-12.28}/tests/test_why.py +0 -0
  647. {roam_code-12.27 → roam_code-12.28}/tests/test_workspace.py +0 -0
  648. {roam_code-12.27 → roam_code-12.28}/tests/test_ws_resolve_fixes.py +0 -0
  649. {roam_code-12.27 → roam_code-12.28}/tests/test_xlang.py +0 -0
  650. {roam_code-12.27 → roam_code-12.28}/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.27
3
+ Version: 12.28
4
4
  Summary: Instant codebase comprehension for AI coding agents
5
5
  Author: CosmoHac
6
6
  License-Expression: Apache-2.0
@@ -63,9 +63,9 @@ Dynamic: license-file
63
63
 
64
64
  **Architectural sight for AI coding agents — before they edit.**
65
65
 
66
- 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 181 specialised commands are advanced surface for specialised workflows.
66
+ 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 182 specialised commands are advanced surface for specialised workflows.
67
67
 
68
- *186 commands · 136 MCP tools · 27 languages · 100% local · zero API keys*
68
+ *187 commands · 136 MCP tools · 27 languages · 100% local · zero API keys*
69
69
 
70
70
  [![PyPI version](https://img.shields.io/pypi/v/roam-code?style=flat-square&color=blue)](https://pypi.org/project/roam-code/)
71
71
  [![GitHub stars](https://img.shields.io/github/stars/Cranot/roam-code?style=flat-square)](https://github.com/Cranot/roam-code/stargazers)
@@ -333,7 +333,7 @@ roam health
333
333
 
334
334
  ## Commands
335
335
 
336
- **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 181 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 **186 commands organised into 7 categories** (plus 6 aliases for muscle memory: `algo` → `math`, `weather` → `churn`, `digest` / `snapshot` / `trend` → `trends`, `onboard` → `understand`), but you don't need to know that to start.
336
+ **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 182 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 **187 commands organised into 7 categories** (plus 6 aliases for muscle memory: `algo` → `math`, `weather` → `churn`, `digest` / `snapshot` / `trend` → `trends`, `onboard` → `understand`), but you don't need to know that to start.
337
337
 
338
338
  <details>
339
339
  <summary><strong>Full command reference</strong></summary>
@@ -383,6 +383,7 @@ roam health
383
383
  | `roam audit-trail-conformance-check [--retention-days N] [--gate]` | Score the audit trail against an EU AI Act Article 12 checklist (chain integrity, timestamps, actors, reproducibility, retention) |
384
384
  | `roam rules-validate [PATH] [--against DIFF] [--strict] [--gate] [--explain]` | Lint a `.roam/rules.yml` for typos, schema mistakes, unknown patterns, duplicate IDs; optional dry-run against a sample diff |
385
385
  | `roam dogfood [--no-audit] [--no-pr-analyze] [--no-audit-trail]` | One-shot v2 stack runner: audit + pr-analyze + audit-trail + Article 12 conformance — first-touch demo for any repo |
386
+ | `roam suppress <finding-id> --reason "…"` | Suppress a math / over-fetch / missing-index / auth-gaps false positive with audit-trail-friendly record (`.roam/suppressions.json`); `--list` / `--remove` complete the workflow |
386
387
  | `roam why-fail <test>` | Find recently-changed symbols transitively reachable from a failing test |
387
388
  | `roam recommend <symbol>` | Surface related symbols using call-graph + co-change + clone signals |
388
389
  | `roam graph-stats` | Graph-level invariants: density, weak components, non-trivial cycles, top inbound symbols |
@@ -4,9 +4,9 @@
4
4
 
5
5
  **Architectural sight for AI coding agents — before they edit.**
6
6
 
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 181 specialised commands are advanced surface for specialised workflows.
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 182 specialised commands are advanced surface for specialised workflows.
8
8
 
9
- *186 commands · 136 MCP tools · 27 languages · 100% local · zero API keys*
9
+ *187 commands · 136 MCP tools · 27 languages · 100% local · zero API keys*
10
10
 
11
11
  [![PyPI version](https://img.shields.io/pypi/v/roam-code?style=flat-square&color=blue)](https://pypi.org/project/roam-code/)
12
12
  [![GitHub stars](https://img.shields.io/github/stars/Cranot/roam-code?style=flat-square)](https://github.com/Cranot/roam-code/stargazers)
@@ -274,7 +274,7 @@ roam health
274
274
 
275
275
  ## Commands
276
276
 
277
- **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 181 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 **186 commands organised into 7 categories** (plus 6 aliases for muscle memory: `algo` → `math`, `weather` → `churn`, `digest` / `snapshot` / `trend` → `trends`, `onboard` → `understand`), but you don't need to know that to start.
277
+ **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 182 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 **187 commands organised into 7 categories** (plus 6 aliases for muscle memory: `algo` → `math`, `weather` → `churn`, `digest` / `snapshot` / `trend` → `trends`, `onboard` → `understand`), but you don't need to know that to start.
278
278
 
279
279
  <details>
280
280
  <summary><strong>Full command reference</strong></summary>
@@ -324,6 +324,7 @@ roam health
324
324
  | `roam audit-trail-conformance-check [--retention-days N] [--gate]` | Score the audit trail against an EU AI Act Article 12 checklist (chain integrity, timestamps, actors, reproducibility, retention) |
325
325
  | `roam rules-validate [PATH] [--against DIFF] [--strict] [--gate] [--explain]` | Lint a `.roam/rules.yml` for typos, schema mistakes, unknown patterns, duplicate IDs; optional dry-run against a sample diff |
326
326
  | `roam dogfood [--no-audit] [--no-pr-analyze] [--no-audit-trail]` | One-shot v2 stack runner: audit + pr-analyze + audit-trail + Article 12 conformance — first-touch demo for any repo |
327
+ | `roam suppress <finding-id> --reason "…"` | Suppress a math / over-fetch / missing-index / auth-gaps false positive with audit-trail-friendly record (`.roam/suppressions.json`); `--list` / `--remove` complete the workflow |
327
328
  | `roam why-fail <test>` | Find recently-changed symbols transitively reachable from a failing test |
328
329
  | `roam recommend <symbol>` | Surface related symbols using call-graph + co-change + clone signals |
329
330
  | `roam graph-stats` | Graph-level invariants: density, weak components, non-trivial cycles, top inbound symbols |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "roam-code"
7
- version = "12.27"
7
+ version = "12.28"
8
8
  description = "Instant codebase comprehension for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -48,8 +48,19 @@ def _finding(
48
48
  *,
49
49
  evidence=None,
50
50
  fix=None,
51
+ match_line=None,
51
52
  ):
53
+ """Build a finding dict.
54
+
55
+ M1 (2026-05-06): when ``match_line`` is supplied, the finding's
56
+ ``location`` field points at the exact AST node where the pattern
57
+ matched (e.g. the line containing the .sort() call) — not the
58
+ enclosing function declaration. The function-start line is
59
+ preserved as ``symbol_line`` so callers needing both have access.
60
+ """
52
61
  bw = best_way(task_id)
62
+ sym_line = sym["line_start"]
63
+ actual_line = match_line if match_line is not None else sym_line
53
64
  finding = {
54
65
  "task_id": task_id,
55
66
  "detected_way": detected_way,
@@ -57,7 +68,8 @@ def _finding(
57
68
  "symbol_id": sym["id"],
58
69
  "symbol_name": sym["qualified_name"] or sym["name"],
59
70
  "kind": sym["kind"],
60
- "location": _loc(sym["file_path"], sym["line_start"]),
71
+ "location": _loc(sym["file_path"], actual_line),
72
+ "symbol_line": sym_line,
61
73
  "confidence": confidence,
62
74
  "reason": reason,
63
75
  }
@@ -68,6 +80,78 @@ def _finding(
68
80
  return finding
69
81
 
70
82
 
83
+ def _find_match_line(snippet: str, pattern, sym_line_start: int | None) -> int | None:
84
+ """Walk the snippet line by line to find the first line matching ``pattern``.
85
+
86
+ Returns the absolute line number (sym_line_start + offset). When
87
+ snippet doesn't contain the match, returns sym_line_start unchanged
88
+ so callers can blindly substitute.
89
+
90
+ Used by sort-to-select / IO-in-loop / regex-in-loop detectors to
91
+ pin findings at the exact match site.
92
+ """
93
+ if not snippet or sym_line_start is None:
94
+ return sym_line_start
95
+ for offset, line in enumerate(snippet.splitlines()):
96
+ if pattern.search(line):
97
+ return sym_line_start + offset
98
+ return sym_line_start
99
+
100
+
101
+ # M4 — recognise DEV-only / DEBUG-only gates so production-impact
102
+ # detectors don't fire on code that's stripped from production builds.
103
+ # Real-world examples (from the prior round of FPs):
104
+ # - if (import.meta.env.DEV) { ... heavy diagnostics ... }
105
+ # - if (process.env.NODE_ENV !== 'production') { ... }
106
+ # - if (__DEV__) { ... }
107
+ # - if (DEBUG) { ... }
108
+ # - console.assert(...) — short-circuits in production
109
+ _DEV_GATE_RE = re.compile(
110
+ # No trailing \b — alternatives ending in `'production'` or `__DEV__` have
111
+ # non-word chars after them, so the global `\b` would never fire there.
112
+ r"\bif\s*\([^)]*?\b(?:"
113
+ r"import\.meta\.env\.(?:DEV\b|MODE\s*[!=]==?\s*['\"]production['\"])|"
114
+ r"process\.env\.NODE_ENV\s*[!=]==?\s*['\"]production['\"]|"
115
+ r"__DEV__|"
116
+ r"DEBUG\b"
117
+ r")",
118
+ re.IGNORECASE,
119
+ )
120
+ _CONSOLE_ASSERT_RE = re.compile(r"\bconsole\.assert\s*\(")
121
+
122
+
123
+ def _is_dev_only_block(snippet: str, match_line_offset: int | None = None) -> bool:
124
+ """Heuristic: does the snippet (or the lines around the match) sit inside
125
+ a DEV-only conditional?
126
+
127
+ Conservative matcher: returns True only when an obvious DEV gate is
128
+ visible *before* the match line in the snippet. Won't catch every
129
+ case (e.g. a flag set in a parent function), but catches the common
130
+ Vue 3 / React / Next.js / Vite patterns where heavy diagnostics live
131
+ behind import.meta.env.DEV.
132
+ """
133
+ if not snippet:
134
+ return False
135
+ if _DEV_GATE_RE.search(snippet) or _CONSOLE_ASSERT_RE.search(snippet):
136
+ return True
137
+ return False
138
+
139
+
140
+ def _find_first_keyword_line(snippet: str, keywords: tuple[str, ...], sym_line_start: int | None) -> int | None:
141
+ """First line containing any of the keywords (case-insensitive substring).
142
+
143
+ Cheaper than regex; used for self-call detection in branching recursion.
144
+ """
145
+ if not snippet or sym_line_start is None:
146
+ return sym_line_start
147
+ lows = [k.lower() for k in keywords]
148
+ for offset, line in enumerate(snippet.splitlines()):
149
+ ll = line.lower()
150
+ if any(k in ll for k in lows):
151
+ return sym_line_start + offset
152
+ return sym_line_start
153
+
154
+
71
155
  def _row_value(row, key, default=None):
72
156
  """Safely read a sqlite row key with a fallback."""
73
157
  try:
@@ -1125,25 +1209,62 @@ _IO_RECEIVER_HINTS = {
1125
1209
  "requests",
1126
1210
  "urllib",
1127
1211
  }
1212
+ # M3 expansion: when a call is IN this set OR matches one of the IN_MEMORY_LEAVES
1213
+ # attached to a recognised receiver, treat as an in-memory cache read NOT I/O.
1214
+ # Real-world FP that drove the expansion: roam math flagged
1215
+ # `queryClient.getQueryData` inside a TanStack Query factory as N+1
1216
+ # round-trips when those are sync cache reads.
1128
1217
  _IN_MEMORY_EXACT = {
1129
1218
  "queryclient.setquerydata",
1130
1219
  "queryclient.getquerydata",
1131
1220
  "queryclient.getqueriesdata",
1132
1221
  "queryclient.setqueriesdata",
1222
+ "queryclient.invalidatequeries",
1223
+ "queryclient.removequeries",
1224
+ "queryclient.cancelqueries",
1225
+ "queryclient.refetchqueries",
1133
1226
  "qc.setquerydata",
1134
1227
  "qc.getquerydata",
1135
1228
  "qc.getqueriesdata",
1136
1229
  "qc.setqueriesdata",
1230
+ "qc.invalidatequeries",
1137
1231
  "redux.dispatch",
1138
1232
  "store.dispatch",
1233
+ # SWR + RTK Query + Apollo cache equivalents
1234
+ "mutate", # SWR's mutate() (cache-only)
1235
+ "cache.read",
1236
+ "cache.write",
1237
+ "cache.evict",
1238
+ "cache.modify", # Apollo
1239
+ "client.readquery", # Apollo
1240
+ "client.writequery", # Apollo
1241
+ "client.cache.evict", # Apollo
1139
1242
  }
1140
1243
  _IN_MEMORY_LEAVES = {
1141
1244
  "setquerydata",
1142
1245
  "getquerydata",
1143
1246
  "setqueriesdata",
1144
1247
  "getqueriesdata",
1248
+ "invalidatequeries",
1249
+ "removequeries",
1250
+ "cancelqueries",
1251
+ "refetchqueries",
1145
1252
  "dispatch",
1146
1253
  "setstate",
1254
+ # M3 — generic Map/Set/WeakMap operations are NOT I/O even in loops.
1255
+ # Without these, `mapInst.get(k)` inside a loop falsely fires as N+1.
1256
+ "has",
1257
+ "set",
1258
+ "delete",
1259
+ "clear",
1260
+ "peek",
1261
+ # SWR + Apollo
1262
+ "readquery",
1263
+ "writequery",
1264
+ "writefragment",
1265
+ "readfragment",
1266
+ "evict",
1267
+ "modify",
1147
1268
  }
1148
1269
  _IN_MEMORY_RECEIVER_HINTS = {
1149
1270
  "queryclient",
@@ -1153,6 +1274,17 @@ _IN_MEMORY_RECEIVER_HINTS = {
1153
1274
  "cache",
1154
1275
  "state",
1155
1276
  "router",
1277
+ # M3 expansion: more cache-library + native-collection receivers
1278
+ "map",
1279
+ "set",
1280
+ "weakmap",
1281
+ "weakset",
1282
+ "dict",
1283
+ "lookup",
1284
+ "registry",
1285
+ "client", # Apollo client.cache.evict
1286
+ "session",
1287
+ "pinia", # Vue 3 store
1156
1288
  }
1157
1289
  _IO_WRAPPER_NAMES = {
1158
1290
  "batch",
@@ -1264,9 +1396,41 @@ def _io_emit_finding(
1264
1396
  guard_applies: bool,
1265
1397
  evidence_io_calls: list[str],
1266
1398
  r,
1399
+ *,
1400
+ dev_gated: bool = False,
1401
+ match_line: int | None = None,
1402
+ snippet: str | None = None,
1267
1403
  ) -> dict:
1268
- """Build the finding dict for one of high/medium/ambiguous result branches."""
1404
+ """Build the finding dict for one of high/medium/ambiguous result branches.
1405
+
1406
+ M1 (match_line): pin location at the first I/O call site in the snippet
1407
+ rather than the function declaration.
1408
+ M4 (dev_gated): note when the body sits inside a DEV gate.
1409
+ M6 (suppress hint): every emitted finding carries `to_suppress` evidence.
1410
+ """
1269
1411
  fix_text = "; ".join(sorted(fixes)) if fixes else None
1412
+ # M1: try to find the line of the first I/O call inside the snippet.
1413
+ derived_match_line = match_line
1414
+ if derived_match_line is None and snippet and (high_calls or medium_calls or ambiguous_calls):
1415
+ first_call = (high_calls + medium_calls + ambiguous_calls)[0]
1416
+ leaf = _call_leaf(first_call) or first_call
1417
+ # Use a loose substring match — call shapes vary across extractors.
1418
+ if leaf:
1419
+ for offset, line in enumerate(snippet.splitlines()):
1420
+ if leaf in line:
1421
+ derived_match_line = (r["line_start"] or 0) + offset
1422
+ break
1423
+ common_evidence_extras = {}
1424
+ if dev_gated:
1425
+ common_evidence_extras["dev_gated"] = True
1426
+ common_evidence_extras["dev_gated_note"] = (
1427
+ "loop body sits inside a DEV-only conditional (import.meta.env.DEV / __DEV__ / "
1428
+ "process.env.NODE_ENV); production-stripped, so the N+1 cost is not paid in prod"
1429
+ )
1430
+ suppress_hint = (
1431
+ "wrap the loop in a batch/eager guard (e.g. `with()` or `map()`+`Promise.all`), OR "
1432
+ "add `# roam: ignore-math[io-in-loop]` on the function line if the call is intentional"
1433
+ )
1270
1434
  if level == "high":
1271
1435
  reason_calls = _dedupe(high_calls)[:2]
1272
1436
  reason_suffix = f"; frameworks: {', '.join(sorted(frameworks))}" if frameworks else ""
@@ -1284,8 +1448,11 @@ def _io_emit_finding(
1284
1448
  "io_calls": evidence_io_calls,
1285
1449
  "frameworks": sorted(frameworks),
1286
1450
  "guard_hints": guard_hints,
1451
+ "to_suppress": suppress_hint,
1452
+ **common_evidence_extras,
1287
1453
  },
1288
1454
  fix=fix_text,
1455
+ match_line=derived_match_line,
1289
1456
  )
1290
1457
  if level == "medium":
1291
1458
  reason_calls = _dedupe(medium_calls)[:2]
@@ -1304,8 +1471,11 @@ def _io_emit_finding(
1304
1471
  "io_calls": evidence_io_calls,
1305
1472
  "frameworks": sorted(frameworks),
1306
1473
  "guard_hints": guard_hints,
1474
+ "to_suppress": suppress_hint,
1475
+ **common_evidence_extras,
1307
1476
  },
1308
1477
  fix=fix_text,
1478
+ match_line=derived_match_line,
1309
1479
  )
1310
1480
  # Ambiguous bare calls (get/find/query) — weak evidence, low confidence.
1311
1481
  reason_calls = _dedupe(ambiguous_calls)[:2]
@@ -1321,8 +1491,11 @@ def _io_emit_finding(
1321
1491
  "ambiguous_io_only": True,
1322
1492
  "frameworks": sorted(frameworks),
1323
1493
  "guard_hints": guard_hints,
1494
+ "to_suppress": suppress_hint,
1495
+ **common_evidence_extras,
1324
1496
  },
1325
1497
  fix=fix_text,
1498
+ match_line=derived_match_line,
1326
1499
  )
1327
1500
  finding["precision"] = "low"
1328
1501
  return finding
@@ -1397,6 +1570,10 @@ def detect_io_in_loop(conn: sqlite3.Connection) -> list[dict]:
1397
1570
  if any(kw in name_lower for kw in _IO_WRAPPER_NAMES):
1398
1571
  continue
1399
1572
 
1573
+ # M4: skip DEV-gated bodies — production stripped, so loop overhead
1574
+ # is irrelevant. (Conservative: only skip when the gate is *literally*
1575
+ # in this function's body.)
1576
+ dev_gated = _is_dev_only_block(snippet)
1400
1577
  guard_applies = bool(frameworks and guard_hints)
1401
1578
  evidence_io_calls = _dedupe(high_calls + medium_calls + ambiguous_calls)[:6]
1402
1579
  if high_calls:
@@ -1405,6 +1582,14 @@ def detect_io_in_loop(conn: sqlite3.Connection) -> list[dict]:
1405
1582
  level = "medium"
1406
1583
  else:
1407
1584
  level = "ambiguous"
1585
+ if dev_gated:
1586
+ # Don't drop entirely — a real production-bound issue could still
1587
+ # exist outside the gate. But demote two tiers so it sinks to the
1588
+ # bottom of the verdict list.
1589
+ if level == "high":
1590
+ level = "medium"
1591
+ elif level == "medium":
1592
+ level = "ambiguous"
1408
1593
  results.append(
1409
1594
  _io_emit_finding(
1410
1595
  level,
@@ -1417,6 +1602,8 @@ def detect_io_in_loop(conn: sqlite3.Connection) -> list[dict]:
1417
1602
  guard_applies,
1418
1603
  evidence_io_calls,
1419
1604
  r,
1605
+ dev_gated=dev_gated,
1606
+ snippet=snippet,
1420
1607
  )
1421
1608
  )
1422
1609
  return results
@@ -1512,6 +1699,15 @@ def detect_sort_to_select(conn: sqlite3.Connection) -> list[dict]:
1512
1699
  r"\bsort(?:ed)?\s*\([^)]*\).*?\[\s*(?:-?\d+|:\s*[^]\n]+)\s*\]",
1513
1700
  re.DOTALL,
1514
1701
  )
1702
+ # M1: per-line sort detector for pinpointing the match line
1703
+ sort_call_line_re = re.compile(r"\bsort(?:ed)?\s*\(")
1704
+
1705
+ # M5 — fallback false-positive guard. Skip when the sort result is also
1706
+ # iterated/returned in full (sorted then map/forEach/return — display order,
1707
+ # not a min/max selection).
1708
+ # The check is conservative: if we see ANY iteration of the sort target
1709
+ # alongside the index access, demote the finding rather than skip outright.
1710
+ iteration_after_sort_re = re.compile(r"\bsort(?:ed)?\s*\(.*?\b(map|forEach|filter|reduce|return)\b", re.DOTALL)
1515
1711
 
1516
1712
  results = []
1517
1713
  for r in rows:
@@ -1521,28 +1717,44 @@ def detect_sort_to_select(conn: sqlite3.Connection) -> list[dict]:
1521
1717
  if not snippet:
1522
1718
  continue
1523
1719
 
1720
+ match_line = _find_match_line(snippet, sort_call_line_re, r["line_start"])
1721
+ sort_iterated = bool(iteration_after_sort_re.search(snippet))
1722
+
1524
1723
  # Strong patterns: sorted(...)[0], sorted(... )[:k], arr.sort(); arr[0]
1525
1724
  if sorted_index_re.search(snippet) or inplace_sort_index_re.search(snippet):
1725
+ # M5: when the sort result is also iterated, downgrade — the
1726
+ # subscript may be incidental (e.g. logging the first item of a
1727
+ # display-ordered list).
1728
+ confidence = "medium" if sort_iterated else "high"
1729
+ reason = "Sort used only for first/last/top-k selection"
1730
+ if sort_iterated:
1731
+ reason += " (note: result is also iterated — may be incidental subscript)"
1526
1732
  results.append(
1527
1733
  _finding(
1528
1734
  "sort-to-select",
1529
1735
  "full-sort",
1530
1736
  r,
1531
- "Sort used only for first/last/top-k selection",
1532
- "high",
1737
+ reason,
1738
+ confidence,
1739
+ match_line=match_line,
1533
1740
  )
1534
1741
  )
1535
1742
  continue
1536
1743
 
1537
1744
  # Fallback pattern for other languages (sort(...) then index/slice).
1538
1745
  if generic_sort_index_re.search(snippet):
1746
+ confidence = "low" if sort_iterated else "medium"
1747
+ reason = "Potential full sort followed by index/slice selection"
1748
+ if sort_iterated:
1749
+ reason += " (note: result is also iterated — may be incidental subscript)"
1539
1750
  results.append(
1540
1751
  _finding(
1541
1752
  "sort-to-select",
1542
1753
  "full-sort",
1543
1754
  r,
1544
- "Potential full sort followed by index/slice selection",
1545
- "medium",
1755
+ reason,
1756
+ confidence,
1757
+ match_line=match_line,
1546
1758
  )
1547
1759
  )
1548
1760
  return results
@@ -1646,6 +1858,11 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
1646
1858
 
1647
1859
  results = []
1648
1860
 
1861
+ # M2 fix: detect the SECOND form of depth guard — early-return when
1862
+ # depth EXCEEDS limit ("if depth > 10 return"). The original regex only
1863
+ # recognised "depth < limit ⇒ continue" patterns; the negation form was
1864
+ # silently mis-flagged as O(2^n). Real-world FP: deepEqual flagged
1865
+ # despite line+2 having `if (depth > 10) return false`.
1649
1866
  def _has_explicit_depth_guard(language: str | None, snippet: str) -> bool:
1650
1867
  """Return True for bounded-recursion guards that cap traversal depth."""
1651
1868
  if not snippet:
@@ -1656,9 +1873,30 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
1656
1873
  # Python: len(path.split(".")) < 5
1657
1874
  if re.search(r"len\s*\(\s*[^)]*\.split\s*\([^)]*\)\s*\)\s*(?:<|<=)\s*\d+", snippet):
1658
1875
  return True
1876
+ # Form 1: continue if depth/level/budget BELOW limit.
1659
1877
  # Common explicit counters: depth < maxDepth, level <= 4, etc.
1660
1878
  if re.search(
1661
- r"\b(?:depth|level|currentDepth|current_depth)\b\s*(?:<|<=)\s*(?:\d+|maxDepth|max_depth)",
1879
+ r"\b(?:depth|level|budget|remaining|hops|currentDepth|current_depth|max_depth|maxDepth)\b"
1880
+ r"\s*(?:<|<=)\s*(?:\d+|maxDepth|max_depth|MAX_DEPTH|max_recursion|MAX_RECURSION)",
1881
+ snippet,
1882
+ ):
1883
+ return True
1884
+ # M2 — Form 2: early-return if depth/level/budget EXCEEDS limit.
1885
+ # Matches `if (depth > 10) return ...`, `if (level >= MAX_DEPTH) raise`,
1886
+ # `if (budget < 1) return`, `if (--budget <= 0) return`.
1887
+ # Generous on the "return / raise / throw / break" side since
1888
+ # short-circuit-and-bail is the universal early-exit shape.
1889
+ if re.search(
1890
+ r"\b(?:depth|level|budget|remaining|hops|recursion_count|recursionDepth)\b"
1891
+ r"\s*(?:>|>=|<|<=)\s*(?:\d+|maxDepth|max_depth|MAX_DEPTH|max_recursion|MAX_RECURSION|0)\s*\)?\s*"
1892
+ r"\s*[:{]?\s*(?:return|raise|throw|break)",
1893
+ snippet,
1894
+ ):
1895
+ return True
1896
+ # Decrement-then-check: `if (--budget <= 0) return`
1897
+ if re.search(
1898
+ r"--?\s*\b(?:depth|budget|remaining|hops)\b\s*[<>]=?\s*\d+\s*\)?\s*[:{]?\s*"
1899
+ r"(?:return|raise|throw|break)",
1662
1900
  snippet,
1663
1901
  ):
1664
1902
  return True
@@ -1673,6 +1911,22 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
1673
1911
  return True
1674
1912
  return False
1675
1913
 
1914
+ # M2 — Set/Map/WeakSet/WeakMap parameter or local: signals the function
1915
+ # already implements its own memoization / cycle-tracking, so the
1916
+ # branching-recursion warning would be a FP.
1917
+ def _has_memo_collection(snippet: str) -> bool:
1918
+ if not snippet:
1919
+ return False
1920
+ if re.search(r"\b(?:Set|Map|WeakSet|WeakMap)\s*<", snippet): # TS generic
1921
+ return True
1922
+ if re.search(r"\bnew\s+(?:Set|Map|WeakSet|WeakMap)\b", snippet): # JS literal
1923
+ return True
1924
+ if re.search(r"\b(?:visited|seen|memo|cache|memoised|memoized)\b\s*[:=]", snippet):
1925
+ return True
1926
+ if re.search(r"@(?:lru_cache|cache|memoize|memoise|functools\.lru_cache)\b", snippet):
1927
+ return True
1928
+ return False
1929
+
1676
1930
  for r in rows:
1677
1931
  if _is_test_path(r["file_path"]):
1678
1932
  continue
@@ -1715,6 +1969,14 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
1715
1969
  )
1716
1970
  if _has_explicit_depth_guard(_row_value(r, "language", ""), snippet):
1717
1971
  continue
1972
+ if _has_memo_collection(snippet):
1973
+ # M2: function carries its own Set/Map/WeakSet — already memoised.
1974
+ continue
1975
+ # M1: pin the location at the first self-call line if we can find it,
1976
+ # not the function declaration. The detector flagged because
1977
+ # self_call_count >= 2; the first occurrence of `name(` (or shorthand
1978
+ # `name(`) inside the body is the most informative anchor.
1979
+ match_line = _find_first_keyword_line(snippet, (r["name"] + "(",), r["line_start"])
1718
1980
  results.append(
1719
1981
  _finding(
1720
1982
  "branching-recursion",
@@ -1722,6 +1984,16 @@ def detect_branching_recursion(conn: sqlite3.Connection) -> list[dict]:
1722
1984
  r,
1723
1985
  f"Branching recursion ({r['self_call_count']} self-calls) without memoization",
1724
1986
  "high",
1987
+ evidence={
1988
+ "self_call_count": r["self_call_count"],
1989
+ "guard_check": "no depth/budget guard found in body, no memo Set/Map detected",
1990
+ "to_suppress": (
1991
+ "add `if (depth > N) return` early-return guard, OR pass a "
1992
+ "Set/Map/WeakSet for memoisation/cycle-tracking, OR add "
1993
+ "`# roam: ignore-math[branching-recursion]` to the function line"
1994
+ ),
1995
+ },
1996
+ match_line=match_line,
1725
1997
  )
1726
1998
  )
1727
1999
  return results
@@ -2332,6 +2604,25 @@ def _calibrate_finding(finding: dict, context: dict | None) -> dict:
2332
2604
  finding["confidence"] = _lower_confidence(finding["confidence"])
2333
2605
  finding["reason"] += " (bounded loop)"
2334
2606
 
2607
+ # M8 — confidence calibration floor.
2608
+ # Categories where the FP-fix is heuristic-only (no AST-level proof)
2609
+ # cap at 'medium' regardless of caller-count or runtime boost. Real-world
2610
+ # calibration on union-web showed "high confidence" branching-recursion
2611
+ # was 0/1 true positive; same pattern likely on sort-then-subscript when
2612
+ # the result is also iterated.
2613
+ _MEDIUM_FLOOR_TASKS = {"branching-recursion", "sort-to-select"}
2614
+ if finding.get("task_id") in _MEDIUM_FLOOR_TASKS and finding.get("confidence") == "high":
2615
+ # Only floor when there's no strong runtime signal — runtime-hot code
2616
+ # earns the high-confidence boost.
2617
+ if not runtime_hot:
2618
+ finding["confidence"] = "medium"
2619
+ evidence_dict = finding.setdefault("evidence", {}) if isinstance(finding.get("evidence"), dict) else {}
2620
+ if isinstance(evidence_dict, dict):
2621
+ evidence_dict["calibration_floor"] = (
2622
+ "category capped at 'medium' — heuristic-only detection has high FP rate; "
2623
+ "use --confidence high to skip these"
2624
+ )
2625
+
2335
2626
  return finding
2336
2627
 
2337
2628
 
@@ -131,6 +131,7 @@ _COMMANDS = {
131
131
  ),
132
132
  "rules-validate": ("roam.commands.cmd_rules_validate", "rules_validate"),
133
133
  "dogfood": ("roam.commands.cmd_dogfood", "dogfood"),
134
+ "suppress": ("roam.commands.cmd_suppress", "suppress"),
134
135
  "stats": ("roam.commands.cmd_stats", "stats"),
135
136
  "why-fail": ("roam.commands.cmd_why_fail", "why_fail"),
136
137
  "recommend": ("roam.commands.cmd_recommend", "recommend"),
@@ -284,6 +285,7 @@ _CATEGORIES = {
284
285
  "audit-trail-export",
285
286
  "audit-trail-conformance-check",
286
287
  "dogfood",
288
+ "suppress",
287
289
  "pr-diff",
288
290
  "api-changes",
289
291
  "semantic-diff",