roam-code 12.31__tar.gz → 12.32__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 (655) hide show
  1. {roam_code-12.31 → roam_code-12.32}/PKG-INFO +1 -1
  2. {roam_code-12.31 → roam_code-12.32}/pyproject.toml +1 -1
  3. {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/detectors.py +198 -0
  4. {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/tasks.py +69 -0
  5. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_debt.py +8 -1
  6. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_math.py +38 -1
  7. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_comment_render.py +11 -0
  8. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/finding_suppress.py +52 -2
  9. {roam_code-12.31 → roam_code-12.32}/src/roam/competitor_site_data.py +2 -2
  10. {roam_code-12.31 → roam_code-12.32}/src/roam/mcp-server-card.json +1 -1
  11. {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/PKG-INFO +1 -1
  12. {roam_code-12.31 → roam_code-12.32}/tests/test_math.py +79 -2
  13. {roam_code-12.31 → roam_code-12.32}/tests/test_mcp_server.py +12 -2
  14. {roam_code-12.31 → roam_code-12.32}/LICENSE +0 -0
  15. {roam_code-12.31 → roam_code-12.32}/README.md +0 -0
  16. {roam_code-12.31 → roam_code-12.32}/setup.cfg +0 -0
  17. {roam_code-12.31 → roam_code-12.32}/src/roam/__init__.py +0 -0
  18. {roam_code-12.31 → roam_code-12.32}/src/roam/__main__.py +0 -0
  19. {roam_code-12.31 → roam_code-12.32}/src/roam/analysis/__init__.py +0 -0
  20. {roam_code-12.31 → roam_code-12.32}/src/roam/analysis/effects.py +0 -0
  21. {roam_code-12.31 → roam_code-12.32}/src/roam/analysis/taint.py +0 -0
  22. {roam_code-12.31 → roam_code-12.32}/src/roam/api.py +0 -0
  23. {roam_code-12.31 → roam_code-12.32}/src/roam/ask/__init__.py +0 -0
  24. {roam_code-12.31 → roam_code-12.32}/src/roam/ask/classifier.py +0 -0
  25. {roam_code-12.31 → roam_code-12.32}/src/roam/ask/recipes.py +0 -0
  26. {roam_code-12.31 → roam_code-12.32}/src/roam/ask/runner.py +0 -0
  27. {roam_code-12.31 → roam_code-12.32}/src/roam/ask/workflow.py +0 -0
  28. {roam_code-12.31 → roam_code-12.32}/src/roam/attest/__init__.py +0 -0
  29. {roam_code-12.31 → roam_code-12.32}/src/roam/attest/cga.py +0 -0
  30. {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/__init__.py +0 -0
  31. {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/base.py +0 -0
  32. {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_config.py +0 -0
  33. {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_django.py +0 -0
  34. {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_protobuf.py +0 -0
  35. {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_rest_api.py +0 -0
  36. {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_salesforce.py +0 -0
  37. {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/bridge_template.py +0 -0
  38. {roam_code-12.31 → roam_code-12.32}/src/roam/bridges/registry.py +0 -0
  39. {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/__init__.py +0 -0
  40. {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/fixes.py +0 -0
  41. {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/python_idioms.py +0 -0
  42. {roam_code-12.31 → roam_code-12.32}/src/roam/catalog/smells.py +0 -0
  43. {roam_code-12.31 → roam_code-12.32}/src/roam/cli.py +0 -0
  44. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/__init__.py +0 -0
  45. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/audit_trail_helpers.py +0 -0
  46. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/changed_files.py +0 -0
  47. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_adrs.py +0 -0
  48. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_adversarial.py +0 -0
  49. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_affected.py +0 -0
  50. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_affected_tests.py +0 -0
  51. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_agent_context.py +0 -0
  52. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_agent_export.py +0 -0
  53. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_agent_plan.py +0 -0
  54. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ai_ratio.py +0 -0
  55. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ai_readiness.py +0 -0
  56. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_alerts.py +0 -0
  57. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_annotate.py +0 -0
  58. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_api.py +0 -0
  59. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_api_changes.py +0 -0
  60. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_api_drift.py +0 -0
  61. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ask.py +0 -0
  62. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_attest.py +0 -0
  63. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_audit.py +0 -0
  64. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_audit_trail_conformance.py +0 -0
  65. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_audit_trail_export.py +0 -0
  66. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_audit_trail_verify.py +0 -0
  67. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_auth_gaps.py +0 -0
  68. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_bisect.py +0 -0
  69. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_breaking.py +0 -0
  70. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_budget.py +0 -0
  71. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_bus_factor.py +0 -0
  72. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_capsule.py +0 -0
  73. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_cga.py +0 -0
  74. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_changelog.py +0 -0
  75. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_check_rules.py +0 -0
  76. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ci_setup.py +0 -0
  77. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_clean.py +0 -0
  78. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_clones.py +0 -0
  79. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_closure.py +0 -0
  80. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_clusters.py +0 -0
  81. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_codeowners.py +0 -0
  82. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_complexity.py +0 -0
  83. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_config.py +0 -0
  84. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_congestion.py +0 -0
  85. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_context.py +0 -0
  86. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_conventions.py +0 -0
  87. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_coupling.py +0 -0
  88. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_coverage_gaps.py +0 -0
  89. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_critique.py +0 -0
  90. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_cut.py +0 -0
  91. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dark_matter.py +0 -0
  92. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dashboard.py +0 -0
  93. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dead.py +0 -0
  94. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_deps.py +0 -0
  95. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_describe.py +0 -0
  96. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dev_profile.py +0 -0
  97. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_diagnose.py +0 -0
  98. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_diff.py +0 -0
  99. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_disambiguate.py +0 -0
  100. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_doc_staleness.py +0 -0
  101. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_docs_coverage.py +0 -0
  102. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_doctor.py +0 -0
  103. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_dogfood.py +0 -0
  104. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_drift.py +0 -0
  105. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_duplicates.py +0 -0
  106. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_effects.py +0 -0
  107. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_endpoints.py +0 -0
  108. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_entry_points.py +0 -0
  109. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_eval_retrieve.py +0 -0
  110. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_exit_codes.py +0 -0
  111. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fan.py +0 -0
  112. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_file.py +0 -0
  113. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fingerprint.py +0 -0
  114. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fitness.py +0 -0
  115. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_flag_dead.py +0 -0
  116. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fleet.py +0 -0
  117. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_fn_coupling.py +0 -0
  118. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_forecast.py +0 -0
  119. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_graph_export.py +0 -0
  120. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_graph_stats.py +0 -0
  121. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_grep.py +0 -0
  122. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_guard.py +0 -0
  123. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_health.py +0 -0
  124. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_help_search.py +0 -0
  125. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_hooks.py +0 -0
  126. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_hotspots.py +0 -0
  127. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_hover.py +0 -0
  128. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_impact.py +0 -0
  129. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_index.py +0 -0
  130. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_index_bundle.py +0 -0
  131. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_index_stats.py +0 -0
  132. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ingest_trace.py +0 -0
  133. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_init.py +0 -0
  134. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_intent.py +0 -0
  135. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_invariants.py +0 -0
  136. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_layers.py +0 -0
  137. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_map.py +0 -0
  138. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_mcp_setup.py +0 -0
  139. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_mcp_status.py +0 -0
  140. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_metrics.py +0 -0
  141. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_metrics_push.py +0 -0
  142. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_migration_safety.py +0 -0
  143. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_minimap.py +0 -0
  144. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_missing_index.py +0 -0
  145. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_module.py +0 -0
  146. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_mutate.py +0 -0
  147. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_n1.py +0 -0
  148. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_oracle.py +0 -0
  149. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_orchestrate.py +0 -0
  150. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_orphan_imports.py +0 -0
  151. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_orphan_routes.py +0 -0
  152. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_over_fetch.py +0 -0
  153. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_owner.py +0 -0
  154. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_partition.py +0 -0
  155. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_path_coverage.py +0 -0
  156. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_patterns.py +0 -0
  157. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_plan.py +0 -0
  158. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_plan_refactor.py +0 -0
  159. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_plugins.py +0 -0
  160. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_analyze.py +0 -0
  161. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_diff.py +0 -0
  162. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_prep.py +0 -0
  163. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pr_risk.py +0 -0
  164. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pre_commit.py +0 -0
  165. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_preflight.py +0 -0
  166. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_py_modern.py +0 -0
  167. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_py_types.py +0 -0
  168. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_pytest_fixtures.py +0 -0
  169. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_recipes.py +0 -0
  170. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_recommend.py +0 -0
  171. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_relate.py +0 -0
  172. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_report.py +0 -0
  173. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_reset.py +0 -0
  174. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_retrieve.py +0 -0
  175. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_risk.py +0 -0
  176. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_rules.py +0 -0
  177. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_rules_validate.py +0 -0
  178. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_safe_delete.py +0 -0
  179. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_safe_zones.py +0 -0
  180. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_sbom.py +0 -0
  181. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_schema.py +0 -0
  182. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_search.py +0 -0
  183. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_search_semantic.py +0 -0
  184. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_secrets.py +0 -0
  185. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_semantic_diff.py +0 -0
  186. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_simulate.py +0 -0
  187. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_simulate_departure.py +0 -0
  188. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_sketch.py +0 -0
  189. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_smells.py +0 -0
  190. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_spectral.py +0 -0
  191. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_split.py +0 -0
  192. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_stats.py +0 -0
  193. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
  194. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
  195. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_supply_chain.py +0 -0
  196. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_suppress.py +0 -0
  197. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_symbol.py +0 -0
  198. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_syntax_check.py +0 -0
  199. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_taint.py +0 -0
  200. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_telemetry.py +0 -0
  201. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_test_gaps.py +0 -0
  202. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_test_impact.py +0 -0
  203. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_test_pyramid.py +0 -0
  204. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_test_scaffold.py +0 -0
  205. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_testmap.py +0 -0
  206. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_timeline.py +0 -0
  207. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_tour.py +0 -0
  208. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_trace.py +0 -0
  209. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_trends.py +0 -0
  210. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_triage.py +0 -0
  211. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_understand.py +0 -0
  212. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_uses.py +0 -0
  213. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_verify.py +0 -0
  214. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_verify_imports.py +0 -0
  215. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_version.py +0 -0
  216. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_vibe_check.py +0 -0
  217. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_visualize.py +0 -0
  218. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_vuln_map.py +0 -0
  219. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_vuln_reach.py +0 -0
  220. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_vulns.py +0 -0
  221. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_watch.py +0 -0
  222. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_weather.py +0 -0
  223. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_why.py +0 -0
  224. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_why_fail.py +0 -0
  225. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_workflow.py +0 -0
  226. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_ws.py +0 -0
  227. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/cmd_xlang.py +0 -0
  228. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/codeowners_helpers.py +0 -0
  229. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/context_helpers.py +0 -0
  230. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/gate_presets.py +0 -0
  231. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/git_helpers.py +0 -0
  232. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/graph_helpers.py +0 -0
  233. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/metrics_history.py +0 -0
  234. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/next_steps.py +0 -0
  235. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/pr_analyze/__init__.py +0 -0
  236. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/pr_analyze/audit_trail.py +0 -0
  237. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/pr_analyze/cache.py +0 -0
  238. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/pr_analyze/rules.py +0 -0
  239. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/resolve.py +0 -0
  240. {roam_code-12.31 → roam_code-12.32}/src/roam/commands/suppression.py +0 -0
  241. {roam_code-12.31 → roam_code-12.32}/src/roam/config.py +0 -0
  242. {roam_code-12.31 → roam_code-12.32}/src/roam/coverage_reports.py +0 -0
  243. {roam_code-12.31 → roam_code-12.32}/src/roam/critique/__init__.py +0 -0
  244. {roam_code-12.31 → roam_code-12.32}/src/roam/critique/aggregator.py +0 -0
  245. {roam_code-12.31 → roam_code-12.32}/src/roam/critique/checks.py +0 -0
  246. {roam_code-12.31 → roam_code-12.32}/src/roam/db/__init__.py +0 -0
  247. {roam_code-12.31 → roam_code-12.32}/src/roam/db/connection.py +0 -0
  248. {roam_code-12.31 → roam_code-12.32}/src/roam/db/queries.py +0 -0
  249. {roam_code-12.31 → roam_code-12.32}/src/roam/db/schema.py +0 -0
  250. {roam_code-12.31 → roam_code-12.32}/src/roam/eval/__init__.py +0 -0
  251. {roam_code-12.31 → roam_code-12.32}/src/roam/eval/harness.py +0 -0
  252. {roam_code-12.31 → roam_code-12.32}/src/roam/exit_codes.py +0 -0
  253. {roam_code-12.31 → roam_code-12.32}/src/roam/fleet/__init__.py +0 -0
  254. {roam_code-12.31 → roam_code-12.32}/src/roam/fleet/adapters.py +0 -0
  255. {roam_code-12.31 → roam_code-12.32}/src/roam/fleet/manifest.py +0 -0
  256. {roam_code-12.31 → roam_code-12.32}/src/roam/git_utils.py +0 -0
  257. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/__init__.py +0 -0
  258. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/anomaly.py +0 -0
  259. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/builder.py +0 -0
  260. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/clone_detect.py +0 -0
  261. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/clusters.py +0 -0
  262. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/cycles.py +0 -0
  263. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/dark_matter.py +0 -0
  264. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/diff.py +0 -0
  265. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/fingerprint.py +0 -0
  266. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/layers.py +0 -0
  267. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/pagerank.py +0 -0
  268. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/partition.py +0 -0
  269. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/pathfinding.py +0 -0
  270. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/propagation.py +0 -0
  271. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/simulate.py +0 -0
  272. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/spectral.py +0 -0
  273. {roam_code-12.31 → roam_code-12.32}/src/roam/graph/stats.py +0 -0
  274. {roam_code-12.31 → roam_code-12.32}/src/roam/index/__init__.py +0 -0
  275. {roam_code-12.31 → roam_code-12.32}/src/roam/index/complexity.py +0 -0
  276. {roam_code-12.31 → roam_code-12.32}/src/roam/index/discovery.py +0 -0
  277. {roam_code-12.31 → roam_code-12.32}/src/roam/index/django_post.py +0 -0
  278. {roam_code-12.31 → roam_code-12.32}/src/roam/index/file_roles.py +0 -0
  279. {roam_code-12.31 → roam_code-12.32}/src/roam/index/git_stats.py +0 -0
  280. {roam_code-12.31 → roam_code-12.32}/src/roam/index/gitignore.py +0 -0
  281. {roam_code-12.31 → roam_code-12.32}/src/roam/index/incremental.py +0 -0
  282. {roam_code-12.31 → roam_code-12.32}/src/roam/index/indexer.py +0 -0
  283. {roam_code-12.31 → roam_code-12.32}/src/roam/index/parser.py +0 -0
  284. {roam_code-12.31 → roam_code-12.32}/src/roam/index/pytest_fixtures.py +0 -0
  285. {roam_code-12.31 → roam_code-12.32}/src/roam/index/registry_dispatch.py +0 -0
  286. {roam_code-12.31 → roam_code-12.32}/src/roam/index/relations.py +0 -0
  287. {roam_code-12.31 → roam_code-12.32}/src/roam/index/symbols.py +0 -0
  288. {roam_code-12.31 → roam_code-12.32}/src/roam/index/test_conventions.py +0 -0
  289. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/__init__.py +0 -0
  290. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/apex_lang.py +0 -0
  291. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/aura_lang.py +0 -0
  292. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/base.py +0 -0
  293. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/c_lang.py +0 -0
  294. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/csharp_lang.py +0 -0
  295. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/extractor_schema.py +0 -0
  296. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/extractors/kotlin.yaml +0 -0
  297. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/foxpro_lang.py +0 -0
  298. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/generic_lang.py +0 -0
  299. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/go_lang.py +0 -0
  300. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/hcl_lang.py +0 -0
  301. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/java_lang.py +0 -0
  302. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/javascript_lang.py +0 -0
  303. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/kotlin_lang.py +0 -0
  304. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/php_lang.py +0 -0
  305. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/python_lang.py +0 -0
  306. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/query_engine.py +0 -0
  307. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/registry.py +0 -0
  308. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/ruby_lang.py +0 -0
  309. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/rust_lang.py +0 -0
  310. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/scala_lang.py +0 -0
  311. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/sfxml_lang.py +0 -0
  312. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/sql_lang.py +0 -0
  313. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/swift_lang.py +0 -0
  314. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/typescript_lang.py +0 -0
  315. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/visualforce_lang.py +0 -0
  316. {roam_code-12.31 → roam_code-12.32}/src/roam/languages/yaml_lang.py +0 -0
  317. {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/__init__.py +0 -0
  318. {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/completions.py +0 -0
  319. {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/concurrency.py +0 -0
  320. {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/progress.py +0 -0
  321. {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/sampling.py +0 -0
  322. {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/session.py +0 -0
  323. {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_extras/watcher.py +0 -0
  324. {roam_code-12.31 → roam_code-12.32}/src/roam/mcp_server.py +0 -0
  325. {roam_code-12.31 → roam_code-12.32}/src/roam/observability.py +0 -0
  326. {roam_code-12.31 → roam_code-12.32}/src/roam/output/__init__.py +0 -0
  327. {roam_code-12.31 → roam_code-12.32}/src/roam/output/confidence.py +0 -0
  328. {roam_code-12.31 → roam_code-12.32}/src/roam/output/errors.py +0 -0
  329. {roam_code-12.31 → roam_code-12.32}/src/roam/output/file_role_hints.py +0 -0
  330. {roam_code-12.31 → roam_code-12.32}/src/roam/output/formatter.py +0 -0
  331. {roam_code-12.31 → roam_code-12.32}/src/roam/output/framework_filter.py +0 -0
  332. {roam_code-12.31 → roam_code-12.32}/src/roam/output/mermaid.py +0 -0
  333. {roam_code-12.31 → roam_code-12.32}/src/roam/output/project_shape.py +0 -0
  334. {roam_code-12.31 → roam_code-12.32}/src/roam/output/sarif.py +0 -0
  335. {roam_code-12.31 → roam_code-12.32}/src/roam/output/schema_registry.py +0 -0
  336. {roam_code-12.31 → roam_code-12.32}/src/roam/plugins.py +0 -0
  337. {roam_code-12.31 → roam_code-12.32}/src/roam/refactor/__init__.py +0 -0
  338. {roam_code-12.31 → roam_code-12.32}/src/roam/refactor/codegen.py +0 -0
  339. {roam_code-12.31 → roam_code-12.32}/src/roam/refactor/transforms.py +0 -0
  340. {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/__init__.py +0 -0
  341. {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/learned_ranker.py +0 -0
  342. {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/pipeline.py +0 -0
  343. {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/rerank.py +0 -0
  344. {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/seeds.py +0 -0
  345. {roam_code-12.31 → roam_code-12.32}/src/roam/retrieve/semantic.py +0 -0
  346. {roam_code-12.31 → roam_code-12.32}/src/roam/rules/__init__.py +0 -0
  347. {roam_code-12.31 → roam_code-12.32}/src/roam/rules/ast_match.py +0 -0
  348. {roam_code-12.31 → roam_code-12.32}/src/roam/rules/builtin.py +0 -0
  349. {roam_code-12.31 → roam_code-12.32}/src/roam/rules/dataflow.py +0 -0
  350. {roam_code-12.31 → roam_code-12.32}/src/roam/rules/engine.py +0 -0
  351. {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/__init__.py +0 -0
  352. {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/daemon.py +0 -0
  353. {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/graph_backend.py +0 -0
  354. {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/hotspots.py +0 -0
  355. {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/lock_modes.py +0 -0
  356. {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/lockmgr.py +0 -0
  357. {roam_code-12.31 → roam_code-12.32}/src/roam/runtime/trace_ingest.py +0 -0
  358. {roam_code-12.31 → roam_code-12.32}/src/roam/search/__init__.py +0 -0
  359. {roam_code-12.31 → roam_code-12.32}/src/roam/search/framework_packs.py +0 -0
  360. {roam_code-12.31 → roam_code-12.32}/src/roam/search/index_embeddings.py +0 -0
  361. {roam_code-12.31 → roam_code-12.32}/src/roam/search/onnx_embeddings.py +0 -0
  362. {roam_code-12.31 → roam_code-12.32}/src/roam/search/tfidf.py +0 -0
  363. {roam_code-12.31 → roam_code-12.32}/src/roam/security/__init__.py +0 -0
  364. {roam_code-12.31 → roam_code-12.32}/src/roam/security/aibom_extension.py +0 -0
  365. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_classifier.py +0 -0
  366. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_engine.py +0 -0
  367. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/api_error_leak.yaml +0 -0
  368. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/java_fileupload_path_traversal.yaml +0 -0
  369. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_insecure_jwt_decode.yaml +0 -0
  370. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_localstorage_secrets.yaml +0 -0
  371. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_prototype_pollution.yaml +0 -0
  372. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_ssrf.yaml +0 -0
  373. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/js_xss.yaml +0 -0
  374. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_basic.yaml +0 -0
  375. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_deserialization.yaml +0 -0
  376. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_path_traversal.yaml +0 -0
  377. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_socketio_remote_source.yaml +0 -0
  378. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_sqli.yaml +0 -0
  379. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/python_urllib_open_redirect.yaml +0 -0
  380. {roam_code-12.31 → roam_code-12.32}/src/roam/security/taint_rules/vue_v_html.yaml +0 -0
  381. {roam_code-12.31 → roam_code-12.32}/src/roam/security/vuln_reach.py +0 -0
  382. {roam_code-12.31 → roam_code-12.32}/src/roam/security/vuln_store.py +0 -0
  383. {roam_code-12.31 → roam_code-12.32}/src/roam/surface_counts.py +0 -0
  384. {roam_code-12.31 → roam_code-12.32}/src/roam/telemetry.py +0 -0
  385. {roam_code-12.31 → roam_code-12.32}/src/roam/templates/__init__.py +0 -0
  386. {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/Jenkinsfile +0 -0
  387. {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/__init__.py +0 -0
  388. {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/agent-review.yml +0 -0
  389. {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/azure-pipelines.yml +0 -0
  390. {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/bitbucket-pipelines.yml +0 -0
  391. {roam_code-12.31 → roam_code-12.32}/src/roam/templates/ci/gitlab-ci.yml +0 -0
  392. {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/__init__.py +0 -0
  393. {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/aggregator.py +0 -0
  394. {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/api_scanner.py +0 -0
  395. {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/config.py +0 -0
  396. {roam_code-12.31 → roam_code-12.32}/src/roam/workspace/db.py +0 -0
  397. {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/SOURCES.txt +0 -0
  398. {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/dependency_links.txt +0 -0
  399. {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/entry_points.txt +0 -0
  400. {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/requires.txt +0 -0
  401. {roam_code-12.31 → roam_code-12.32}/src/roam_code.egg-info/top_level.txt +0 -0
  402. {roam_code-12.31 → roam_code-12.32}/tests/test_adrs.py +0 -0
  403. {roam_code-12.31 → roam_code-12.32}/tests/test_adversarial.py +0 -0
  404. {roam_code-12.31 → roam_code-12.32}/tests/test_affected.py +0 -0
  405. {roam_code-12.31 → roam_code-12.32}/tests/test_agent_export.py +0 -0
  406. {roam_code-12.31 → roam_code-12.32}/tests/test_agent_mode.py +0 -0
  407. {roam_code-12.31 → roam_code-12.32}/tests/test_agent_plan_context.py +0 -0
  408. {roam_code-12.31 → roam_code-12.32}/tests/test_ai_ratio.py +0 -0
  409. {roam_code-12.31 → roam_code-12.32}/tests/test_ai_readiness.py +0 -0
  410. {roam_code-12.31 → roam_code-12.32}/tests/test_alerts_cmd.py +0 -0
  411. {roam_code-12.31 → roam_code-12.32}/tests/test_annotations.py +0 -0
  412. {roam_code-12.31 → roam_code-12.32}/tests/test_anomaly.py +0 -0
  413. {roam_code-12.31 → roam_code-12.32}/tests/test_api_changes.py +0 -0
  414. {roam_code-12.31 → roam_code-12.32}/tests/test_api_drift.py +0 -0
  415. {roam_code-12.31 → roam_code-12.32}/tests/test_ask.py +0 -0
  416. {roam_code-12.31 → roam_code-12.32}/tests/test_attest.py +0 -0
  417. {roam_code-12.31 → roam_code-12.32}/tests/test_audit_trail_aggregate.py +0 -0
  418. {roam_code-12.31 → roam_code-12.32}/tests/test_audit_trail_conformance.py +0 -0
  419. {roam_code-12.31 → roam_code-12.32}/tests/test_audit_trail_sequence.py +0 -0
  420. {roam_code-12.31 → roam_code-12.32}/tests/test_audit_trail_verify.py +0 -0
  421. {roam_code-12.31 → roam_code-12.32}/tests/test_auth_gaps.py +0 -0
  422. {roam_code-12.31 → roam_code-12.32}/tests/test_backend_fixes_round2.py +0 -0
  423. {roam_code-12.31 → roam_code-12.32}/tests/test_backend_fixes_round3.py +0 -0
  424. {roam_code-12.31 → roam_code-12.32}/tests/test_basic.py +0 -0
  425. {roam_code-12.31 → roam_code-12.32}/tests/test_batch_mcp.py +0 -0
  426. {roam_code-12.31 → roam_code-12.32}/tests/test_bisect.py +0 -0
  427. {roam_code-12.31 → roam_code-12.32}/tests/test_bridge_django.py +0 -0
  428. {roam_code-12.31 → roam_code-12.32}/tests/test_bridges.py +0 -0
  429. {roam_code-12.31 → roam_code-12.32}/tests/test_bridges_extended.py +0 -0
  430. {roam_code-12.31 → roam_code-12.32}/tests/test_budget.py +0 -0
  431. {roam_code-12.31 → roam_code-12.32}/tests/test_budget_flag.py +0 -0
  432. {roam_code-12.31 → roam_code-12.32}/tests/test_budget_phase2.py +0 -0
  433. {roam_code-12.31 → roam_code-12.32}/tests/test_bus_factor.py +0 -0
  434. {roam_code-12.31 → roam_code-12.32}/tests/test_capsule.py +0 -0
  435. {roam_code-12.31 → roam_code-12.32}/tests/test_cga.py +0 -0
  436. {roam_code-12.31 → roam_code-12.32}/tests/test_check_rules.py +0 -0
  437. {roam_code-12.31 → roam_code-12.32}/tests/test_ci_gate_eval.py +0 -0
  438. {roam_code-12.31 → roam_code-12.32}/tests/test_ci_sarif_guard.py +0 -0
  439. {roam_code-12.31 → roam_code-12.32}/tests/test_ci_setup.py +0 -0
  440. {roam_code-12.31 → roam_code-12.32}/tests/test_clones.py +0 -0
  441. {roam_code-12.31 → roam_code-12.32}/tests/test_closure.py +0 -0
  442. {roam_code-12.31 → roam_code-12.32}/tests/test_codeowners.py +0 -0
  443. {roam_code-12.31 → roam_code-12.32}/tests/test_commands_architecture.py +0 -0
  444. {roam_code-12.31 → roam_code-12.32}/tests/test_commands_exploration.py +0 -0
  445. {roam_code-12.31 → roam_code-12.32}/tests/test_commands_health.py +0 -0
  446. {roam_code-12.31 → roam_code-12.32}/tests/test_commands_refactoring.py +0 -0
  447. {roam_code-12.31 → roam_code-12.32}/tests/test_commands_workflow.py +0 -0
  448. {roam_code-12.31 → roam_code-12.32}/tests/test_competitor_site_data.py +0 -0
  449. {roam_code-12.31 → roam_code-12.32}/tests/test_comprehensive.py +0 -0
  450. {roam_code-12.31 → roam_code-12.32}/tests/test_config.py +0 -0
  451. {roam_code-12.31 → roam_code-12.32}/tests/test_congestion.py +0 -0
  452. {roam_code-12.31 → roam_code-12.32}/tests/test_context_propagation.py +0 -0
  453. {roam_code-12.31 → roam_code-12.32}/tests/test_conventions_cmd.py +0 -0
  454. {roam_code-12.31 → roam_code-12.32}/tests/test_coverage_gaps_cmd.py +0 -0
  455. {roam_code-12.31 → roam_code-12.32}/tests/test_coverage_ingestion.py +0 -0
  456. {roam_code-12.31 → roam_code-12.32}/tests/test_critique.py +0 -0
  457. {roam_code-12.31 → roam_code-12.32}/tests/test_cut.py +0 -0
  458. {roam_code-12.31 → roam_code-12.32}/tests/test_dark_matter.py +0 -0
  459. {roam_code-12.31 → roam_code-12.32}/tests/test_dark_matter_helpers.py +0 -0
  460. {roam_code-12.31 → roam_code-12.32}/tests/test_dashboard.py +0 -0
  461. {roam_code-12.31 → roam_code-12.32}/tests/test_dataflow_dead.py +0 -0
  462. {roam_code-12.31 → roam_code-12.32}/tests/test_dead_aging.py +0 -0
  463. {roam_code-12.31 → roam_code-12.32}/tests/test_defer_loading.py +0 -0
  464. {roam_code-12.31 → roam_code-12.32}/tests/test_demo_gif_asset.py +0 -0
  465. {roam_code-12.31 → roam_code-12.32}/tests/test_describe.py +0 -0
  466. {roam_code-12.31 → roam_code-12.32}/tests/test_detail_flag_hints.py +0 -0
  467. {roam_code-12.31 → roam_code-12.32}/tests/test_detector_precision.py +0 -0
  468. {roam_code-12.31 → roam_code-12.32}/tests/test_deterministic_output.py +0 -0
  469. {roam_code-12.31 → roam_code-12.32}/tests/test_dev_profile.py +0 -0
  470. {roam_code-12.31 → roam_code-12.32}/tests/test_difficulty_scoring.py +0 -0
  471. {roam_code-12.31 → roam_code-12.32}/tests/test_doc_consistency.py +0 -0
  472. {roam_code-12.31 → roam_code-12.32}/tests/test_doc_staleness.py +0 -0
  473. {roam_code-12.31 → roam_code-12.32}/tests/test_docker_assets.py +0 -0
  474. {roam_code-12.31 → roam_code-12.32}/tests/test_docs_coverage.py +0 -0
  475. {roam_code-12.31 → roam_code-12.32}/tests/test_docs_site_quality.py +0 -0
  476. {roam_code-12.31 → roam_code-12.32}/tests/test_doctor.py +0 -0
  477. {roam_code-12.31 → roam_code-12.32}/tests/test_dogfood.py +0 -0
  478. {roam_code-12.31 → roam_code-12.32}/tests/test_drift.py +0 -0
  479. {roam_code-12.31 → roam_code-12.32}/tests/test_drift_by_team.py +0 -0
  480. {roam_code-12.31 → roam_code-12.32}/tests/test_duplicates.py +0 -0
  481. {roam_code-12.31 → roam_code-12.32}/tests/test_effects.py +0 -0
  482. {roam_code-12.31 → roam_code-12.32}/tests/test_effects_propagation.py +0 -0
  483. {roam_code-12.31 → roam_code-12.32}/tests/test_endpoints.py +0 -0
  484. {roam_code-12.31 → roam_code-12.32}/tests/test_entry_points_cmd.py +0 -0
  485. {roam_code-12.31 → roam_code-12.32}/tests/test_eval_retrieve.py +0 -0
  486. {roam_code-12.31 → roam_code-12.32}/tests/test_except_pass_narrow.py +0 -0
  487. {roam_code-12.31 → roam_code-12.32}/tests/test_exclude_patterns.py +0 -0
  488. {roam_code-12.31 → roam_code-12.32}/tests/test_exit_codes.py +0 -0
  489. {roam_code-12.31 → roam_code-12.32}/tests/test_extractor_grammar_drift.py +0 -0
  490. {roam_code-12.31 → roam_code-12.32}/tests/test_fallback_contracts.py +0 -0
  491. {roam_code-12.31 → roam_code-12.32}/tests/test_file_roles.py +0 -0
  492. {roam_code-12.31 → roam_code-12.32}/tests/test_finding_suppress.py +0 -0
  493. {roam_code-12.31 → roam_code-12.32}/tests/test_fingerprint.py +0 -0
  494. {roam_code-12.31 → roam_code-12.32}/tests/test_fixes.py +0 -0
  495. {roam_code-12.31 → roam_code-12.32}/tests/test_flag_dead.py +0 -0
  496. {roam_code-12.31 → roam_code-12.32}/tests/test_fleet.py +0 -0
  497. {roam_code-12.31 → roam_code-12.32}/tests/test_fn_coupling.py +0 -0
  498. {roam_code-12.31 → roam_code-12.32}/tests/test_forecast.py +0 -0
  499. {roam_code-12.31 → roam_code-12.32}/tests/test_formatters.py +0 -0
  500. {roam_code-12.31 → roam_code-12.32}/tests/test_foxpro.py +0 -0
  501. {roam_code-12.31 → roam_code-12.32}/tests/test_framework_detection.py +0 -0
  502. {roam_code-12.31 → roam_code-12.32}/tests/test_gate_presets.py +0 -0
  503. {roam_code-12.31 → roam_code-12.32}/tests/test_git_helpers.py +0 -0
  504. {roam_code-12.31 → roam_code-12.32}/tests/test_git_utils.py +0 -0
  505. {roam_code-12.31 → roam_code-12.32}/tests/test_guard.py +0 -0
  506. {roam_code-12.31 → roam_code-12.32}/tests/test_health_gate.py +0 -0
  507. {roam_code-12.31 → roam_code-12.32}/tests/test_hooks.py +0 -0
  508. {roam_code-12.31 → roam_code-12.32}/tests/test_hotspots.py +0 -0
  509. {roam_code-12.31 → roam_code-12.32}/tests/test_hover.py +0 -0
  510. {roam_code-12.31 → roam_code-12.32}/tests/test_index.py +0 -0
  511. {roam_code-12.31 → roam_code-12.32}/tests/test_index_bundle.py +0 -0
  512. {roam_code-12.31 → roam_code-12.32}/tests/test_init_cmd.py +0 -0
  513. {roam_code-12.31 → roam_code-12.32}/tests/test_install_check.py +0 -0
  514. {roam_code-12.31 → roam_code-12.32}/tests/test_intent.py +0 -0
  515. {roam_code-12.31 → roam_code-12.32}/tests/test_invariants.py +0 -0
  516. {roam_code-12.31 → roam_code-12.32}/tests/test_json_contracts.py +0 -0
  517. {roam_code-12.31 → roam_code-12.32}/tests/test_json_error_envelope.py +0 -0
  518. {roam_code-12.31 → roam_code-12.32}/tests/test_kotlin_swift_extractors.py +0 -0
  519. {roam_code-12.31 → roam_code-12.32}/tests/test_language_corpus.py +0 -0
  520. {roam_code-12.31 → roam_code-12.32}/tests/test_languages.py +0 -0
  521. {roam_code-12.31 → roam_code-12.32}/tests/test_laravel_fp_fixes.py +0 -0
  522. {roam_code-12.31 → roam_code-12.32}/tests/test_library_api.py +0 -0
  523. {roam_code-12.31 → roam_code-12.32}/tests/test_math_fp_fixes.py +0 -0
  524. {roam_code-12.31 → roam_code-12.32}/tests/test_math_tips.py +0 -0
  525. {roam_code-12.31 → roam_code-12.32}/tests/test_mcp_extras.py +0 -0
  526. {roam_code-12.31 → roam_code-12.32}/tests/test_mcp_setup.py +0 -0
  527. {roam_code-12.31 → roam_code-12.32}/tests/test_mermaid.py +0 -0
  528. {roam_code-12.31 → roam_code-12.32}/tests/test_metrics_cmd.py +0 -0
  529. {roam_code-12.31 → roam_code-12.32}/tests/test_metrics_push.py +0 -0
  530. {roam_code-12.31 → roam_code-12.32}/tests/test_migration_safety.py +0 -0
  531. {roam_code-12.31 → roam_code-12.32}/tests/test_minimap.py +0 -0
  532. {roam_code-12.31 → roam_code-12.32}/tests/test_missing_index.py +0 -0
  533. {roam_code-12.31 → roam_code-12.32}/tests/test_mutate.py +0 -0
  534. {roam_code-12.31 → roam_code-12.32}/tests/test_n1.py +0 -0
  535. {roam_code-12.31 → roam_code-12.32}/tests/test_next_steps.py +0 -0
  536. {roam_code-12.31 → roam_code-12.32}/tests/test_onboard.py +0 -0
  537. {roam_code-12.31 → roam_code-12.32}/tests/test_oracle.py +0 -0
  538. {roam_code-12.31 → roam_code-12.32}/tests/test_orchestrate.py +0 -0
  539. {roam_code-12.31 → roam_code-12.32}/tests/test_orphan_routes.py +0 -0
  540. {roam_code-12.31 → roam_code-12.32}/tests/test_oss_bench_harness.py +0 -0
  541. {roam_code-12.31 → roam_code-12.32}/tests/test_over_fetch.py +0 -0
  542. {roam_code-12.31 → roam_code-12.32}/tests/test_pagerank_truncation.py +0 -0
  543. {roam_code-12.31 → roam_code-12.32}/tests/test_partition.py +0 -0
  544. {roam_code-12.31 → roam_code-12.32}/tests/test_path_coverage.py +0 -0
  545. {roam_code-12.31 → roam_code-12.32}/tests/test_patterns_cmd.py +0 -0
  546. {roam_code-12.31 → roam_code-12.32}/tests/test_performance.py +0 -0
  547. {roam_code-12.31 → roam_code-12.32}/tests/test_personalized_pagerank.py +0 -0
  548. {roam_code-12.31 → roam_code-12.32}/tests/test_plan.py +0 -0
  549. {roam_code-12.31 → roam_code-12.32}/tests/test_plugin_discovery.py +0 -0
  550. {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze.py +0 -0
  551. {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze_cache.py +0 -0
  552. {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze_edge_cases.py +0 -0
  553. {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze_helpers.py +0 -0
  554. {roam_code-12.31 → roam_code-12.32}/tests/test_pr_analyze_v2_signals.py +0 -0
  555. {roam_code-12.31 → roam_code-12.32}/tests/test_pr_comment_render.py +0 -0
  556. {roam_code-12.31 → roam_code-12.32}/tests/test_pr_comment_script.py +0 -0
  557. {roam_code-12.31 → roam_code-12.32}/tests/test_pr_diff.py +0 -0
  558. {roam_code-12.31 → roam_code-12.32}/tests/test_pr_risk_author.py +0 -0
  559. {roam_code-12.31 → roam_code-12.32}/tests/test_progress.py +0 -0
  560. {roam_code-12.31 → roam_code-12.32}/tests/test_progressive_disclosure.py +0 -0
  561. {roam_code-12.31 → roam_code-12.32}/tests/test_properties.py +0 -0
  562. {roam_code-12.31 → roam_code-12.32}/tests/test_pytest_fixtures.py +0 -0
  563. {roam_code-12.31 → roam_code-12.32}/tests/test_python_extractor_v2.py +0 -0
  564. {roam_code-12.31 → roam_code-12.32}/tests/test_python_idioms_e2e.py +0 -0
  565. {roam_code-12.31 → roam_code-12.32}/tests/test_python_pivot.py +0 -0
  566. {roam_code-12.31 → roam_code-12.32}/tests/test_readme_surface_consistency.py +0 -0
  567. {roam_code-12.31 → roam_code-12.32}/tests/test_realworld_feedback.py +0 -0
  568. {roam_code-12.31 → roam_code-12.32}/tests/test_refactoring_intelligence.py +0 -0
  569. {roam_code-12.31 → roam_code-12.32}/tests/test_registry_dispatch.py +0 -0
  570. {roam_code-12.31 → roam_code-12.32}/tests/test_regression_fp_corpus.py +0 -0
  571. {roam_code-12.31 → roam_code-12.32}/tests/test_relate.py +0 -0
  572. {roam_code-12.31 → roam_code-12.32}/tests/test_report.py +0 -0
  573. {roam_code-12.31 → roam_code-12.32}/tests/test_reset_clean.py +0 -0
  574. {roam_code-12.31 → roam_code-12.32}/tests/test_resolve.py +0 -0
  575. {roam_code-12.31 → roam_code-12.32}/tests/test_retrieve.py +0 -0
  576. {roam_code-12.31 → roam_code-12.32}/tests/test_retrieve_cross_repo.py +0 -0
  577. {roam_code-12.31 → roam_code-12.32}/tests/test_retrieve_seeds.py +0 -0
  578. {roam_code-12.31 → roam_code-12.32}/tests/test_risk.py +0 -0
  579. {roam_code-12.31 → roam_code-12.32}/tests/test_ruby.py +0 -0
  580. {roam_code-12.31 → roam_code-12.32}/tests/test_rule_profiles.py +0 -0
  581. {roam_code-12.31 → roam_code-12.32}/tests/test_rules.py +0 -0
  582. {roam_code-12.31 → roam_code-12.32}/tests/test_rules_ast_match.py +0 -0
  583. {roam_code-12.31 → roam_code-12.32}/tests/test_rules_community_pack.py +0 -0
  584. {roam_code-12.31 → roam_code-12.32}/tests/test_rules_dataflow.py +0 -0
  585. {roam_code-12.31 → roam_code-12.32}/tests/test_rules_symbol_requirements.py +0 -0
  586. {roam_code-12.31 → roam_code-12.32}/tests/test_rules_validate.py +0 -0
  587. {roam_code-12.31 → roam_code-12.32}/tests/test_runtime.py +0 -0
  588. {roam_code-12.31 → roam_code-12.32}/tests/test_runtime_score.py +0 -0
  589. {roam_code-12.31 → roam_code-12.32}/tests/test_salesforce.py +0 -0
  590. {roam_code-12.31 → roam_code-12.32}/tests/test_sarif_flag.py +0 -0
  591. {roam_code-12.31 → roam_code-12.32}/tests/test_sbom.py +0 -0
  592. {roam_code-12.31 → roam_code-12.32}/tests/test_scala.py +0 -0
  593. {roam_code-12.31 → roam_code-12.32}/tests/test_schema_versioning.py +0 -0
  594. {roam_code-12.31 → roam_code-12.32}/tests/test_search_explain.py +0 -0
  595. {roam_code-12.31 → roam_code-12.32}/tests/test_secrets.py +0 -0
  596. {roam_code-12.31 → roam_code-12.32}/tests/test_secrets_v2.py +0 -0
  597. {roam_code-12.31 → roam_code-12.32}/tests/test_semantic_diff.py +0 -0
  598. {roam_code-12.31 → roam_code-12.32}/tests/test_semantic_onnx.py +0 -0
  599. {roam_code-12.31 → roam_code-12.32}/tests/test_semantic_search.py +0 -0
  600. {roam_code-12.31 → roam_code-12.32}/tests/test_simulate.py +0 -0
  601. {roam_code-12.31 → roam_code-12.32}/tests/test_simulate_departure.py +0 -0
  602. {roam_code-12.31 → roam_code-12.32}/tests/test_sketch.py +0 -0
  603. {roam_code-12.31 → roam_code-12.32}/tests/test_smells.py +0 -0
  604. {roam_code-12.31 → roam_code-12.32}/tests/test_smoke.py +0 -0
  605. {roam_code-12.31 → roam_code-12.32}/tests/test_sna_metrics.py +0 -0
  606. {roam_code-12.31 → roam_code-12.32}/tests/test_spectral.py +0 -0
  607. {roam_code-12.31 → roam_code-12.32}/tests/test_split_cmd.py +0 -0
  608. {roam_code-12.31 → roam_code-12.32}/tests/test_sql.py +0 -0
  609. {roam_code-12.31 → roam_code-12.32}/tests/test_suggest_reviewers.py +0 -0
  610. {roam_code-12.31 → roam_code-12.32}/tests/test_supply_chain.py +0 -0
  611. {roam_code-12.31 → roam_code-12.32}/tests/test_surface_counts.py +0 -0
  612. {roam_code-12.31 → roam_code-12.32}/tests/test_syntax_check.py +0 -0
  613. {roam_code-12.31 → roam_code-12.32}/tests/test_taint.py +0 -0
  614. {roam_code-12.31 → roam_code-12.32}/tests/test_taint_analysis.py +0 -0
  615. {roam_code-12.31 → roam_code-12.32}/tests/test_taint_classifier.py +0 -0
  616. {roam_code-12.31 → roam_code-12.32}/tests/test_taint_intraprocedural.py +0 -0
  617. {roam_code-12.31 → roam_code-12.32}/tests/test_test_conventions.py +0 -0
  618. {roam_code-12.31 → roam_code-12.32}/tests/test_test_gaps.py +0 -0
  619. {roam_code-12.31 → roam_code-12.32}/tests/test_test_scaffold.py +0 -0
  620. {roam_code-12.31 → roam_code-12.32}/tests/test_testmap.py +0 -0
  621. {roam_code-12.31 → roam_code-12.32}/tests/test_top_flag_consistency.py +0 -0
  622. {roam_code-12.31 → roam_code-12.32}/tests/test_tour_cmd.py +0 -0
  623. {roam_code-12.31 → roam_code-12.32}/tests/test_trends.py +0 -0
  624. {roam_code-12.31 → roam_code-12.32}/tests/test_trends_cohort.py +0 -0
  625. {roam_code-12.31 → roam_code-12.32}/tests/test_triage.py +0 -0
  626. {roam_code-12.31 → roam_code-12.32}/tests/test_uses_cmd.py +0 -0
  627. {roam_code-12.31 → roam_code-12.32}/tests/test_v1215_passes.py +0 -0
  628. {roam_code-12.31 → roam_code-12.32}/tests/test_v1216_passes.py +0 -0
  629. {roam_code-12.31 → roam_code-12.32}/tests/test_v1216_passes_41_50.py +0 -0
  630. {roam_code-12.31 → roam_code-12.32}/tests/test_v1216_passes_51_60.py +0 -0
  631. {roam_code-12.31 → roam_code-12.32}/tests/test_v1217_passes_61_80.py +0 -0
  632. {roam_code-12.31 → roam_code-12.32}/tests/test_v1218_passes_81_90.py +0 -0
  633. {roam_code-12.31 → roam_code-12.32}/tests/test_v1219_passes_91_100.py +0 -0
  634. {roam_code-12.31 → roam_code-12.32}/tests/test_v1220_passes_101_110.py +0 -0
  635. {roam_code-12.31 → roam_code-12.32}/tests/test_v1221_query_timeout.py +0 -0
  636. {roam_code-12.31 → roam_code-12.32}/tests/test_v1221_untested_commands.py +0 -0
  637. {roam_code-12.31 → roam_code-12.32}/tests/test_v12_2.py +0 -0
  638. {roam_code-12.31 → roam_code-12.32}/tests/test_v2_edge_cases.py +0 -0
  639. {roam_code-12.31 → roam_code-12.32}/tests/test_v2_integration.py +0 -0
  640. {roam_code-12.31 → roam_code-12.32}/tests/test_v6_features.py +0 -0
  641. {roam_code-12.31 → roam_code-12.32}/tests/test_v71_features.py +0 -0
  642. {roam_code-12.31 → roam_code-12.32}/tests/test_v7_features.py +0 -0
  643. {roam_code-12.31 → roam_code-12.32}/tests/test_v82_features.py +0 -0
  644. {roam_code-12.31 → roam_code-12.32}/tests/test_verify.py +0 -0
  645. {roam_code-12.31 → roam_code-12.32}/tests/test_verify_imports.py +0 -0
  646. {roam_code-12.31 → roam_code-12.32}/tests/test_vibe_check.py +0 -0
  647. {roam_code-12.31 → roam_code-12.32}/tests/test_visualize.py +0 -0
  648. {roam_code-12.31 → roam_code-12.32}/tests/test_vuln.py +0 -0
  649. {roam_code-12.31 → roam_code-12.32}/tests/test_vulns_cmd.py +0 -0
  650. {roam_code-12.31 → roam_code-12.32}/tests/test_watch.py +0 -0
  651. {roam_code-12.31 → roam_code-12.32}/tests/test_why.py +0 -0
  652. {roam_code-12.31 → roam_code-12.32}/tests/test_workspace.py +0 -0
  653. {roam_code-12.31 → roam_code-12.32}/tests/test_ws_resolve_fixes.py +0 -0
  654. {roam_code-12.31 → roam_code-12.32}/tests/test_xlang.py +0 -0
  655. {roam_code-12.31 → roam_code-12.32}/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.31
3
+ Version: 12.32
4
4
  Summary: Instant codebase comprehension for AI coding agents
5
5
  Author: CosmoHac
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "roam-code"
7
- version = "12.31"
7
+ version = "12.32"
8
8
  description = "Instant codebase comprehension for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1723,6 +1723,10 @@ def autodetect_framework_profile() -> str | None:
1723
1723
  tanstack = "@tanstack/vue-query" in deps or "@tanstack/query-core" in deps
1724
1724
  if tanstack and (vue_ver.startswith("^3") or vue_ver.startswith("3") or vue_ver.startswith("~3")):
1725
1725
  return "vue3-tanstack"
1726
+ # Z6 (2026-05-06) — Express/Koa/Fastify don't have framework
1727
+ # profiles yet (they're light frameworks; the I/O patterns are
1728
+ # generic Node fs / http). Return None for now but flag in meta
1729
+ # so future versions can add a profile when one becomes useful.
1726
1730
 
1727
1731
  composer = _read_json(_os.path.join(cwd, "composer.json"))
1728
1732
  if composer:
@@ -2766,6 +2770,197 @@ def detect_chained_collection_walks(conn: sqlite3.Connection) -> list[dict]:
2766
2770
  return results
2767
2771
 
2768
2772
 
2773
+ # Z1 (2026-05-06) — React: `useEffect(() => { ... })` without a dependency
2774
+ # array runs on EVERY render. Almost always a bug — the dev forgot the
2775
+ # second argument. The fix is `useEffect(() => { ... }, [deps])` or
2776
+ # `useEffect(() => { ... }, [])` for mount-only.
2777
+ _RE_USEEFFECT_NO_DEPS = re.compile(
2778
+ r"\buseEffect\s*\(\s*(?:async\s+)?(?:\(\s*\)|function\s*\(\s*\))\s*=>\s*\{[^}]*?\}\s*\)",
2779
+ re.DOTALL,
2780
+ )
2781
+ _RE_USEEFFECT_WITH_DEPS = re.compile(
2782
+ r"\buseEffect\s*\(\s*[^,]+,\s*\[",
2783
+ )
2784
+
2785
+
2786
+ def detect_useeffect_missing_deps(conn: sqlite3.Connection) -> list[dict]:
2787
+ """React: `useEffect(() => {...})` without a dependency array."""
2788
+ try:
2789
+ rows = conn.execute(
2790
+ "SELECT s.id, s.name, s.qualified_name, s.kind, f.path AS file_path, "
2791
+ "s.line_start, s.line_end "
2792
+ "FROM symbols s "
2793
+ "JOIN files f ON s.file_id = f.id "
2794
+ "WHERE s.kind IN ('function', 'method') "
2795
+ "AND f.language IN ('javascript', 'typescript', 'tsx', 'jsx')"
2796
+ ).fetchall()
2797
+ except Exception:
2798
+ return []
2799
+ results = []
2800
+ for r in rows:
2801
+ if _is_test_path(r["file_path"]):
2802
+ continue
2803
+ snippet = _read_symbol_source(r["file_path"], r["line_start"], r["line_end"])
2804
+ if not snippet or "useEffect" not in snippet:
2805
+ continue
2806
+ # Skip if every useEffect call also has a deps array.
2807
+ no_deps = _RE_USEEFFECT_NO_DEPS.search(snippet)
2808
+ if not no_deps:
2809
+ continue
2810
+ # Verify the SAME call doesn't have a deps array (regex catches the
2811
+ # no-deps shape but a more elaborate body could trick it).
2812
+ # Conservative: only fire when there's NO useEffect-with-deps in body.
2813
+ if _RE_USEEFFECT_WITH_DEPS.search(snippet):
2814
+ # Mixed bag — at least one useEffect has deps, can't reliably
2815
+ # tell which one is the problem without a real parser. Skip.
2816
+ continue
2817
+ line_offset = snippet[: no_deps.start()].count("\n")
2818
+ match_line = (r["line_start"] or 1) + line_offset
2819
+ results.append(
2820
+ _finding(
2821
+ "useeffect-missing-deps",
2822
+ "no-deps-array",
2823
+ r,
2824
+ "useEffect without dependency array — runs on every render",
2825
+ "high",
2826
+ match_line=match_line,
2827
+ snippet=snippet,
2828
+ matched_patterns=["useEffect call", "no second-arg dep array"],
2829
+ )
2830
+ )
2831
+ return results
2832
+
2833
+
2834
+ # Z2 (2026-05-06) — `eval()` / `exec()` / `new Function()` / `setTimeout(string)`
2835
+ # in production source. These are arbitrary code execution sinks if the
2836
+ # input is user-derived. Even when "safe" (literal string), they trip
2837
+ # CSP rules and bundler optimisations. Suppress when test path.
2838
+ _RE_EVAL_CALLS = re.compile(
2839
+ r"\b(?:eval|exec|execfile|compile)\s*\("
2840
+ r"|\bnew\s+Function\s*\("
2841
+ r"|\bsetTimeout\s*\(\s*['\"]"
2842
+ r"|\bsetInterval\s*\(\s*['\"]",
2843
+ )
2844
+
2845
+
2846
+ def detect_dangerous_eval(conn: sqlite3.Connection) -> list[dict]:
2847
+ """Detect `eval`, `exec`, `new Function(...)`, `setTimeout(string)` — code-injection sinks."""
2848
+ try:
2849
+ rows = conn.execute(
2850
+ "SELECT s.id, s.name, s.qualified_name, s.kind, f.path AS file_path, "
2851
+ "f.language AS language, s.line_start, s.line_end "
2852
+ "FROM symbols s "
2853
+ "JOIN files f ON s.file_id = f.id "
2854
+ "WHERE s.kind IN ('function', 'method')"
2855
+ ).fetchall()
2856
+ except Exception:
2857
+ return []
2858
+ results = []
2859
+ for r in rows:
2860
+ if _is_test_path(r["file_path"]):
2861
+ continue
2862
+ # Skip non-source roles when we can tell.
2863
+ path = (r["file_path"] or "").replace("\\", "/").lower()
2864
+ if "/migration" in path or "/script" in path or "/cli" in path:
2865
+ # CLI / migration scripts often legitimately use exec/eval.
2866
+ continue
2867
+ snippet = _read_symbol_source(r["file_path"], r["line_start"], r["line_end"])
2868
+ if not snippet:
2869
+ continue
2870
+ m = _RE_EVAL_CALLS.search(snippet)
2871
+ if not m:
2872
+ continue
2873
+ # Skip ast.literal_eval (safe), regex.compile (different "compile"
2874
+ # — won't actually match because it ends in `.compile(` with a dot
2875
+ # before, so prefix isn't word-boundary — but be defensive).
2876
+ if "literal_eval" in snippet[max(0, m.start() - 20) : m.end()]:
2877
+ continue
2878
+ if ".compile(" in snippet[max(0, m.start() - 5) : m.end() + 1]:
2879
+ continue
2880
+ line_offset = snippet[: m.start()].count("\n")
2881
+ match_line = (r["line_start"] or 1) + line_offset
2882
+ called = m.group(0).rstrip("(")
2883
+ results.append(
2884
+ _finding(
2885
+ "dangerous-eval",
2886
+ "eval-or-exec",
2887
+ r,
2888
+ f"Dangerous dynamic execution sink ({called}) — code-injection risk if input is user-derived",
2889
+ "high",
2890
+ match_line=match_line,
2891
+ snippet=snippet,
2892
+ matched_patterns=[f"call: {called}", "not in test/migration/script path"],
2893
+ )
2894
+ )
2895
+ return results
2896
+
2897
+
2898
+ # Z5 (2026-05-06) — JS/TS DOM listener leak: `addEventListener` without
2899
+ # a paired `removeEventListener` keeps references alive after the
2900
+ # component unmounts. Detect ONLY when the function looks like a
2901
+ # component lifecycle (useEffect / componentDidMount / connectedCallback /
2902
+ # constructor) and addEventListener appears without remove.
2903
+ _RE_ADD_LISTENER = re.compile(r"\baddEventListener\s*\(")
2904
+ _RE_REMOVE_LISTENER = re.compile(r"\bremoveEventListener\s*\(")
2905
+ _RE_LIFECYCLE = re.compile(
2906
+ r"\b(?:useEffect|componentDidMount|componentWillMount|connectedCallback|constructor)\b",
2907
+ )
2908
+
2909
+
2910
+ def detect_unremoved_event_listener(conn: sqlite3.Connection) -> list[dict]:
2911
+ """JS/TS: `addEventListener` in a lifecycle without paired `removeEventListener`."""
2912
+ try:
2913
+ rows = conn.execute(
2914
+ "SELECT s.id, s.name, s.qualified_name, s.kind, f.path AS file_path, "
2915
+ "s.line_start, s.line_end "
2916
+ "FROM symbols s "
2917
+ "JOIN files f ON s.file_id = f.id "
2918
+ "WHERE s.kind IN ('function', 'method') "
2919
+ "AND f.language IN ('javascript', 'typescript', 'tsx', 'jsx')"
2920
+ ).fetchall()
2921
+ except Exception:
2922
+ return []
2923
+ results = []
2924
+ for r in rows:
2925
+ if _is_test_path(r["file_path"]):
2926
+ continue
2927
+ snippet = _read_symbol_source(r["file_path"], r["line_start"], r["line_end"])
2928
+ if not snippet:
2929
+ continue
2930
+ if not _RE_ADD_LISTENER.search(snippet):
2931
+ continue
2932
+ # Only fire for lifecycle-ish bodies — outside of components,
2933
+ # listeners are often global and intentionally never removed.
2934
+ if not _RE_LIFECYCLE.search(snippet):
2935
+ continue
2936
+ # Already paired: presence of removeEventListener anywhere in body.
2937
+ if _RE_REMOVE_LISTENER.search(snippet):
2938
+ continue
2939
+ # `useEffect` should also return a cleanup function. Check for one.
2940
+ if "useEffect" in snippet and re.search(r"return\s+(?:\(\s*\)|function)", snippet):
2941
+ continue
2942
+ m = _RE_ADD_LISTENER.search(snippet)
2943
+ line_offset = snippet[: m.start()].count("\n")
2944
+ match_line = (r["line_start"] or 1) + line_offset
2945
+ results.append(
2946
+ _finding(
2947
+ "unremoved-event-listener",
2948
+ "no-cleanup",
2949
+ r,
2950
+ "addEventListener in component lifecycle without removeEventListener — memory leak",
2951
+ "high",
2952
+ match_line=match_line,
2953
+ snippet=snippet,
2954
+ matched_patterns=[
2955
+ "addEventListener call",
2956
+ "lifecycle context (useEffect / componentDidMount / etc.)",
2957
+ "no paired removeEventListener",
2958
+ ],
2959
+ )
2960
+ )
2961
+ return results
2962
+
2963
+
2769
2964
  def detect_loop_lookup(conn: sqlite3.Connection) -> list[dict]:
2770
2965
  """.index(), .indexOf(), .contains(), .includes() called inside a loop.
2771
2966
 
@@ -3736,6 +3931,9 @@ _MATH_DETECTORS = [
3736
3931
  ("spread-accumulator", "spread-rebind", detect_spread_accumulator),
3737
3932
  ("defer-in-loop", "loop-defer", detect_defer_in_loop),
3738
3933
  ("chained-collection-walk", "two-pass-walk", detect_chained_collection_walks),
3934
+ ("useeffect-missing-deps", "no-deps-array", detect_useeffect_missing_deps),
3935
+ ("dangerous-eval", "eval-or-exec", detect_dangerous_eval),
3936
+ ("unremoved-event-listener", "no-cleanup", detect_unremoved_event_listener),
3739
3937
  ("list-prepend", "insert-front", detect_list_prepend),
3740
3938
  ("sort-to-select", "full-sort", detect_sort_to_select),
3741
3939
  ("loop-lookup", "method-scan", detect_loop_lookup),
@@ -400,6 +400,75 @@ CATALOG: dict[str, dict] = {
400
400
  },
401
401
  ],
402
402
  },
403
+ "unremoved-event-listener": {
404
+ "name": "addEventListener without paired removeEventListener (memory leak)",
405
+ "category": "concurrency",
406
+ "kind": "idiom",
407
+ "ways": [
408
+ {
409
+ "id": "with-cleanup",
410
+ "name": "useEffect cleanup / removeEventListener",
411
+ "time": "n/a",
412
+ "space": "n/a",
413
+ "rank": 1,
414
+ "tip": "Return a cleanup function from useEffect: `useEffect(() => { window.addEventListener('x', h); return () => window.removeEventListener('x', h); }, [])`. For class components, pair in componentWillUnmount.",
415
+ },
416
+ {
417
+ "id": "no-cleanup",
418
+ "name": "addEventListener with no cleanup",
419
+ "time": "n/a",
420
+ "space": "n/a",
421
+ "rank": 10,
422
+ "tip": "",
423
+ },
424
+ ],
425
+ },
426
+ "dangerous-eval": {
427
+ "name": "Dynamic execution sink (eval / exec / new Function)",
428
+ "category": "error-handling",
429
+ "kind": "idiom",
430
+ "ways": [
431
+ {
432
+ "id": "narrow-or-remove",
433
+ "name": "Use a parser / template / safer dispatch",
434
+ "time": "n/a",
435
+ "space": "n/a",
436
+ "rank": 1,
437
+ "tip": "Prefer ast.literal_eval, JSON.parse, a real template engine, or a switch/dispatch dict over arbitrary-string execution. Tightens CSP; eliminates injection surface.",
438
+ },
439
+ {
440
+ "id": "eval-or-exec",
441
+ "name": "eval / exec / new Function with dynamic input",
442
+ "time": "n/a",
443
+ "space": "n/a",
444
+ "rank": 10,
445
+ "tip": "",
446
+ },
447
+ ],
448
+ },
449
+ "useeffect-missing-deps": {
450
+ "name": "React useEffect without dependency array",
451
+ "category": "concurrency",
452
+ "kind": "idiom",
453
+ "ways": [
454
+ {
455
+ "id": "with-deps",
456
+ "name": "useEffect with explicit dep array",
457
+ "time": "n/a",
458
+ "space": "n/a",
459
+ "rank": 1,
460
+ "tip": "Add the dependency array as the second argument: `useEffect(() => {...}, [deps])`. Use `[]` for mount-only effects.",
461
+ },
462
+ {
463
+ "id": "no-deps-array",
464
+ "name": "useEffect without deps (runs every render)",
465
+ "time": "n/a",
466
+ "space": "n/a",
467
+ "rank": 10,
468
+ "tip": "",
469
+ },
470
+ ],
471
+ },
403
472
  "chained-collection-walk": {
404
473
  "name": "Chained collection walk (filter+find / map+find / filter+length)",
405
474
  "category": "collections",
@@ -562,10 +562,17 @@ def debt(ctx, limit, by_kind, threshold, roi):
562
562
  _debt_label = (
563
563
  "low debt" if stats["mean_debt"] < 0.1 else "moderate debt" if stats["mean_debt"] < 0.3 else "high debt"
564
564
  )
565
+ # Z14 (2026-05-06) — append top-1 hotspot to the verdict so the
566
+ # one-line summary tells you WHERE to look first, not just IF
567
+ # there's debt.
568
+ top_hint = ""
569
+ if all_items:
570
+ top1 = all_items[0]
571
+ top_hint = f" — top hotspot: {top1['path']} (score={top1['debt_score']})"
565
572
  _debt_verdict = (
566
573
  f"{_debt_label}: {_n_cycles} cycle files, "
567
574
  f"{_n_gods} god components, {_n_hotspots} hotspots "
568
- f"across {stats['total_files']} files"
575
+ f"across {stats['total_files']} files{top_hint}"
569
576
  )
570
577
 
571
578
  roi_summary, roi_by_path = ({}, {})
@@ -147,6 +147,23 @@ def math_cmd(
147
147
  click.echo(name)
148
148
  return
149
149
 
150
+ # Z7 (2026-05-06) — validate --task against the catalog; on typo, show
151
+ # the closest matches by edit distance instead of running 49 detectors
152
+ # silently to find zero results.
153
+ if task_filter:
154
+ from roam.catalog.tasks import CATALOG
155
+
156
+ if task_filter not in CATALOG:
157
+ import difflib
158
+
159
+ close = difflib.get_close_matches(task_filter, list(CATALOG.keys()), n=3, cutoff=0.4)
160
+ hint = f" Did you mean: {', '.join(close)}?" if close else ""
161
+ click.echo(
162
+ f"NOTE: --task '{task_filter}' is not a known task id."
163
+ f" Run `roam math --json` then look at distinct `task_id` values." + hint,
164
+ err=True,
165
+ )
166
+
150
167
  ensure_index()
151
168
 
152
169
  from roam.catalog.detectors import autodetect_framework_profile, run_detectors
@@ -289,7 +306,18 @@ def math_cmd(
289
306
  if top_n >= max(3, total // 2):
290
307
  category_hint = f"; mostly: {top_cat}"
291
308
  if total == 0:
292
- verdict = "No algorithmic issues detected"
309
+ # Z3 (2026-05-06) informative zero-state. When 0 findings,
310
+ # tell the user (a) which profile filter was active, (b) how
311
+ # many detectors ran, (c) what to try next.
312
+ profile_note = ""
313
+ if profile != "balanced":
314
+ profile_note = f" (profile={profile} may be too strict; try --profile balanced)"
315
+ verdict = (
316
+ f"No algorithmic issues detected{profile_note} — "
317
+ f"{detector_meta.get('detectors_executed', 0)} detector(s) ran cleanly. "
318
+ f"Try `roam math --profile aggressive` for more candidates "
319
+ f"or `roam debt --top 10` for refactoring ROI hotspots."
320
+ )
293
321
  elif suppressed_count > 0:
294
322
  verdict = (
295
323
  f"{unsuppressed_total} unsuppressed candidate{'s' if unsuppressed_total != 1 else ''} "
@@ -341,6 +369,15 @@ def math_cmd(
341
369
  [float(f.get("impact_score", 0.0) or 0.0) for f in findings],
342
370
  default=0.0,
343
371
  ),
372
+ # Z13 (2026-05-06) — top_tasks_by_count helps CI
373
+ # dashboards / agents prioritise without iterating
374
+ # every finding. Format: [{task_id, count}, ...].
375
+ "top_tasks_by_count": [
376
+ {"task_id": tid, "count": n}
377
+ for tid, n in __import__("collections")
378
+ .Counter(f.get("task_id", "?") for f in findings)
379
+ .most_common(3)
380
+ ],
344
381
  },
345
382
  findings=findings,
346
383
  )
@@ -287,6 +287,13 @@ def _section_rule_violations(rule_violations: list[dict]) -> list[str]:
287
287
  out = ["### Architecture rule violations", ""]
288
288
  block_v = [v for v in rule_violations if v.get("severity") == "BLOCK"]
289
289
  warn_v = [v for v in rule_violations if v.get("severity") in ("WARN", "WARNING")]
290
+ # Z4 (2026-05-06) — when total violations is huge, wrap the per-item
291
+ # detail in a collapsible <details> block so the comment doesn't
292
+ # dominate the PR thread on noisy diffs.
293
+ use_details = len(rule_violations) >= 12
294
+ if use_details:
295
+ out.append(f"<details><summary>{len(rule_violations)} violation(s) — expand for detail</summary>")
296
+ out.append("")
290
297
  for v in block_v[:5]:
291
298
  out.append(f"- **BLOCK** `{v['rule_id']}`: `{v['file']}` -> `{v['matched_import']}`")
292
299
  if v.get("description"):
@@ -309,6 +316,10 @@ def _section_rule_violations(rule_violations: list[dict]) -> list[str]:
309
316
  chunks = [f"`{rid}` x{n} ({sev})" for (sev, rid), n in counts.most_common(5)]
310
317
  more_total = extra_block + extra_warn
311
318
  out.append(f"- _...{more_total} more violation(s): " + ", ".join(chunks) + "._")
319
+ # Z4 — close the collapsible block when we opened one.
320
+ if use_details:
321
+ out.append("")
322
+ out.append("</details>")
312
323
  out.append("")
313
324
  return out
314
325
 
@@ -91,6 +91,52 @@ def _inline_match(line_text: str, command: str, task_id: str) -> bool:
91
91
  return False
92
92
 
93
93
 
94
+ def _parse_simple_ignore_findings_yaml(text: str) -> dict:
95
+ """Minimal YAML parser for .roamignore-findings — no PyYAML required.
96
+
97
+ Handles the documented shape only:
98
+
99
+ rules:
100
+ - task_id: io-in-loop
101
+ path_glob: "src/composables/**/*.ts"
102
+ reason: "..."
103
+ - task_id: branching-recursion
104
+ path_glob: "src/utils/object-diff.ts"
105
+
106
+ Anything more complex (anchors, multi-line strings, nested lists)
107
+ needs real PyYAML. Returns ``{}`` on shapes we can't recognise so
108
+ callers fall through to a clean empty-rules state.
109
+ """
110
+ rules: list[dict] = []
111
+ current: dict | None = None
112
+ in_rules_block = False
113
+ for raw in text.splitlines():
114
+ line = raw.rstrip()
115
+ stripped = line.strip()
116
+ if not stripped or stripped.startswith("#"):
117
+ continue
118
+ if stripped == "rules:" or stripped.startswith("rules:"):
119
+ in_rules_block = True
120
+ continue
121
+ if not in_rules_block:
122
+ continue
123
+ if stripped.startswith("- "):
124
+ if current:
125
+ rules.append(current)
126
+ current = {}
127
+ stripped = stripped[2:].strip()
128
+ # First key on the same line as `-` is the common shape.
129
+ if ":" in stripped:
130
+ k, _, v = stripped.partition(":")
131
+ current[k.strip()] = v.strip().strip('"').strip("'")
132
+ elif current is not None and ":" in stripped:
133
+ k, _, v = stripped.partition(":")
134
+ current[k.strip()] = v.strip().strip('"').strip("'")
135
+ if current:
136
+ rules.append(current)
137
+ return {"rules": rules} if rules else {}
138
+
139
+
94
140
  def _load_ignore_findings_file(path: Path) -> list[dict]:
95
141
  """Load `.roamignore-findings` from ``path``. Returns ``[]`` on any error.
96
142
 
@@ -116,11 +162,15 @@ def _load_ignore_findings_file(path: Path) -> list[dict]:
116
162
 
117
163
  data = yaml.safe_load(text) or {}
118
164
  except ImportError:
119
- # No PyYAML: assume strict JSON
165
+ # No PyYAML: try strict JSON first, then a minimal-YAML fallback so
166
+ # the .roamignore-findings format works on Python 3.9 / installs
167
+ # without PyYAML (PyYAML is not a project dependency).
120
168
  try:
121
169
  data = _json.loads(text)
122
170
  except _json.JSONDecodeError:
123
- return []
171
+ data = _parse_simple_ignore_findings_yaml(text)
172
+ if not data:
173
+ return []
124
174
  except Exception: # noqa: BLE001 — malformed YAML never crashes the analyser
125
175
  return []
126
176
  rules = data.get("rules") if isinstance(data, dict) else []
@@ -1362,8 +1362,8 @@ MAP_METADATA: dict[str, dict[str, object]] = {
1362
1362
  "relationship": "self",
1363
1363
  "peer": True,
1364
1364
  "graph": "PageRank + Tarjan + Louvain + layers",
1365
- "note": "Graph algorithms (PageRank, SCC, Louvain, Fiedler) on tree-sitter ASTs fused with git history in SQLite. 136 MCP tools, 187 CLI commands. 19 Python idiom detectors (v12.7+). 51 algo detectors (12.31).",
1366
- "version_evaluated": "12.31",
1365
+ "note": "Graph algorithms (PageRank, SCC, Louvain, Fiedler) on tree-sitter ASTs fused with git history in SQLite. 136 MCP tools, 187 CLI commands. 19 Python idiom detectors (v12.7+). 54 algo detectors (12.32).",
1366
+ "version_evaluated": "12.32",
1367
1367
  "repo_url": "https://github.com/Cranot/roam-code",
1368
1368
  },
1369
1369
  "CKB/CodeMCP": {
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://schemas.modelcontextprotocol.io/server-card/v1",
3
3
  "name": "roam-code",
4
4
  "display_name": "roam — instant codebase intelligence",
5
- "version": "12.31",
5
+ "version": "12.32",
6
6
  "description": "Architectural sight for AI agents before they edit. Pre-indexes symbols, call graphs, dependencies, architecture layers, and git history into a local SQLite DB. 136 MCP tools, 10 resources, 5 prompts, 27 languages. 100% local, zero API keys.",
7
7
  "vendor": {
8
8
  "name": "Cranot",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roam-code
3
- Version: 12.31
3
+ Version: 12.32
4
4
  Summary: Instant codebase comprehension for AI coding agents
5
5
  Author: CosmoHac
6
6
  License-Expression: Apache-2.0
@@ -29,8 +29,9 @@ class TestCatalog:
29
29
 
30
30
  # T3 added serial-await-loop; X1-X5 added async-blocking-sleep,
31
31
  # broad-except-swallow, spread-accumulator, defer-in-loop;
32
- # X13 added chained-collection-walk 29.
33
- assert len(CATALOG) == 29, f"Expected 29 tasks, got {len(CATALOG)}"
32
+ # X13 added chained-collection-walk; Z1/Z2/Z5 added useeffect-
33
+ # missing-deps, dangerous-eval, unremoved-event-listener 32.
34
+ assert len(CATALOG) == 32, f"Expected 32 tasks, got {len(CATALOG)}"
34
35
 
35
36
  def test_detector_registry_covers_catalog(self):
36
37
  from roam.catalog.detectors import _MATH_DETECTORS
@@ -1166,6 +1167,82 @@ class TestBroadExceptSwallow:
1166
1167
  assert hits == []
1167
1168
 
1168
1169
 
1170
+ class TestUseEffectMissingDeps:
1171
+ """Z1 (2026-05-06) — React useEffect without deps array."""
1172
+
1173
+ def test_detects_missing_deps(self, project_factory, monkeypatch):
1174
+ proj = project_factory(
1175
+ {
1176
+ "Comp.tsx": (
1177
+ "import { useEffect } from 'react';\n"
1178
+ "export function Comp() {\n"
1179
+ " useEffect(() => { console.log('hi'); });\n"
1180
+ " return null;\n"
1181
+ "}\n"
1182
+ ),
1183
+ }
1184
+ )
1185
+ monkeypatch.chdir(proj)
1186
+ from roam.catalog.detectors import detect_useeffect_missing_deps
1187
+ from roam.db.connection import open_db
1188
+
1189
+ with open_db(readonly=True, project_root=proj) as conn:
1190
+ hits = detect_useeffect_missing_deps(conn)
1191
+ assert len(hits) >= 1
1192
+
1193
+ def test_skips_when_deps_present(self, project_factory, monkeypatch):
1194
+ proj = project_factory(
1195
+ {
1196
+ "Comp.tsx": (
1197
+ "import { useEffect } from 'react';\n"
1198
+ "export function Comp(props: { id: string }) {\n"
1199
+ " useEffect(() => { fetch(props.id); }, [props.id]);\n"
1200
+ " return null;\n"
1201
+ "}\n"
1202
+ ),
1203
+ }
1204
+ )
1205
+ monkeypatch.chdir(proj)
1206
+ from roam.catalog.detectors import detect_useeffect_missing_deps
1207
+ from roam.db.connection import open_db
1208
+
1209
+ with open_db(readonly=True, project_root=proj) as conn:
1210
+ hits = detect_useeffect_missing_deps(conn)
1211
+ assert hits == []
1212
+
1213
+
1214
+ class TestDangerousEval:
1215
+ """Z2 (2026-05-06) — eval / exec / new Function in production source."""
1216
+
1217
+ def test_detects_eval(self, project_factory, monkeypatch):
1218
+ proj = project_factory(
1219
+ {
1220
+ "service.py": ("def evaluate(expr):\n return eval(expr)\n"),
1221
+ }
1222
+ )
1223
+ monkeypatch.chdir(proj)
1224
+ from roam.catalog.detectors import detect_dangerous_eval
1225
+ from roam.db.connection import open_db
1226
+
1227
+ with open_db(readonly=True, project_root=proj) as conn:
1228
+ hits = detect_dangerous_eval(conn)
1229
+ assert len(hits) >= 1
1230
+
1231
+ def test_skips_test_paths(self, project_factory, monkeypatch):
1232
+ proj = project_factory(
1233
+ {
1234
+ "tests/test_eval.py": ("def test_eval():\n return eval('1+1')\n"),
1235
+ }
1236
+ )
1237
+ monkeypatch.chdir(proj)
1238
+ from roam.catalog.detectors import detect_dangerous_eval
1239
+ from roam.db.connection import open_db
1240
+
1241
+ with open_db(readonly=True, project_root=proj) as conn:
1242
+ hits = detect_dangerous_eval(conn)
1243
+ assert hits == []
1244
+
1245
+
1169
1246
  class TestBatchIterationGuard:
1170
1247
  """DF2 (2026-05-06) — `for chunk in _chunked(ids):` is not N+1."""
1171
1248
 
@@ -308,14 +308,24 @@ class TestToolDecorator:
308
308
  "roam_test_impact",
309
309
  "roam_disambiguate",
310
310
  "roam_why_fail",
311
+ # v12.27 / v12.28 — Roam Agent Review v2 layer (8)
312
+ "roam_pr_analyze",
313
+ "roam_pr_comment_render",
314
+ "roam_rules_validate",
315
+ "roam_audit_trail_export",
316
+ "roam_audit_trail_verify",
317
+ "roam_audit_trail_conformance_check",
318
+ "roam_dogfood",
319
+ "roam_metrics_push",
311
320
  }
312
321
  assert _CORE_TOOLS == expected
313
322
 
314
323
  def test_core_tools_count(self):
315
324
  from roam.mcp_server import _CORE_TOOLS
316
325
 
317
- # v12.1: 27 + 6 = 33; v12.6: +2 Python-pivot = 35; v12.16: +1 catalog = 36.
318
- assert len(_CORE_TOOLS) == 41
326
+ # v12.1: 27 + 6 = 33; v12.6: +2 Python-pivot = 35; v12.16: +1 catalog = 36;
327
+ # v12.19: +5 agent wrappers = 41; v12.28: +8 Agent Review v2 = 49.
328
+ assert len(_CORE_TOOLS) == 49
319
329
 
320
330
  def test_required_task_tools_declared(self):
321
331
  from roam.mcp_server import _TASK_REQUIRED_TOOLS
File without changes
File without changes
File without changes