roam-code 12.1.0__tar.gz → 12.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (534) hide show
  1. {roam_code-12.1.0/src/roam_code.egg-info → roam_code-12.2.0}/PKG-INFO +21 -8
  2. {roam_code-12.1.0 → roam_code-12.2.0}/README.md +11 -7
  3. {roam_code-12.1.0 → roam_code-12.2.0}/pyproject.toml +24 -1
  4. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/attest/cga.py +34 -9
  5. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_cga.py +21 -4
  6. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_mcp_setup.py +39 -2
  7. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_retrieve.py +7 -2
  8. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_sbom.py +26 -2
  9. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/competitor_site_data.py +1 -1
  10. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/config.py +4 -0
  11. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/clusters.py +63 -8
  12. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/mcp_server.py +215 -2
  13. roam_code-12.2.0/src/roam/retrieve/learned_ranker.py +241 -0
  14. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/retrieve/pipeline.py +24 -1
  15. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/retrieve/rerank.py +48 -1
  16. roam_code-12.2.0/src/roam/retrieve/semantic.py +194 -0
  17. roam_code-12.2.0/src/roam/runtime/daemon.py +100 -0
  18. roam_code-12.2.0/src/roam/runtime/graph_backend.py +92 -0
  19. roam_code-12.2.0/src/roam/runtime/lock_modes.py +50 -0
  20. roam_code-12.2.0/src/roam/runtime/lockmgr.py +136 -0
  21. roam_code-12.2.0/src/roam/security/aibom_extension.py +249 -0
  22. {roam_code-12.1.0 → roam_code-12.2.0/src/roam_code.egg-info}/PKG-INFO +21 -8
  23. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam_code.egg-info/SOURCES.txt +8 -0
  24. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam_code.egg-info/requires.txt +13 -0
  25. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_batch_mcp.py +9 -1
  26. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_mcp_server.py +14 -2
  27. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_readme_surface_consistency.py +7 -1
  28. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_retrieve.py +1 -0
  29. roam_code-12.2.0/tests/test_v12_2.py +300 -0
  30. {roam_code-12.1.0 → roam_code-12.2.0}/LICENSE +0 -0
  31. {roam_code-12.1.0 → roam_code-12.2.0}/setup.cfg +0 -0
  32. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/__init__.py +0 -0
  33. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/__main__.py +0 -0
  34. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/analysis/__init__.py +0 -0
  35. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/analysis/effects.py +0 -0
  36. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/analysis/taint.py +0 -0
  37. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/api.py +0 -0
  38. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/ask/__init__.py +0 -0
  39. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/ask/classifier.py +0 -0
  40. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/ask/recipes.py +0 -0
  41. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/ask/runner.py +0 -0
  42. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/attest/__init__.py +0 -0
  43. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/bridges/__init__.py +0 -0
  44. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/bridges/base.py +0 -0
  45. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/bridges/bridge_config.py +0 -0
  46. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/bridges/bridge_django.py +0 -0
  47. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/bridges/bridge_protobuf.py +0 -0
  48. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/bridges/bridge_rest_api.py +0 -0
  49. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/bridges/bridge_salesforce.py +0 -0
  50. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/bridges/bridge_template.py +0 -0
  51. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/bridges/registry.py +0 -0
  52. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/catalog/__init__.py +0 -0
  53. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/catalog/detectors.py +0 -0
  54. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/catalog/fixes.py +0 -0
  55. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/catalog/smells.py +0 -0
  56. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/catalog/tasks.py +0 -0
  57. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/cli.py +0 -0
  58. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/__init__.py +0 -0
  59. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/changed_files.py +0 -0
  60. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_adrs.py +0 -0
  61. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_adversarial.py +0 -0
  62. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_affected.py +0 -0
  63. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_affected_tests.py +0 -0
  64. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_agent_context.py +0 -0
  65. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_agent_export.py +0 -0
  66. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_agent_plan.py +0 -0
  67. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_ai_ratio.py +0 -0
  68. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_ai_readiness.py +0 -0
  69. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_alerts.py +0 -0
  70. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_annotate.py +0 -0
  71. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_api_changes.py +0 -0
  72. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_api_drift.py +0 -0
  73. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_ask.py +0 -0
  74. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_attest.py +0 -0
  75. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_auth_gaps.py +0 -0
  76. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_bisect.py +0 -0
  77. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_breaking.py +0 -0
  78. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_budget.py +0 -0
  79. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_bus_factor.py +0 -0
  80. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_capsule.py +0 -0
  81. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_check_rules.py +0 -0
  82. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_ci_setup.py +0 -0
  83. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_clean.py +0 -0
  84. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_clones.py +0 -0
  85. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_closure.py +0 -0
  86. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_clusters.py +0 -0
  87. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_codeowners.py +0 -0
  88. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_complexity.py +0 -0
  89. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_config.py +0 -0
  90. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_congestion.py +0 -0
  91. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_context.py +0 -0
  92. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_conventions.py +0 -0
  93. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_coupling.py +0 -0
  94. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_coverage_gaps.py +0 -0
  95. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_critique.py +0 -0
  96. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_cut.py +0 -0
  97. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_dark_matter.py +0 -0
  98. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_dashboard.py +0 -0
  99. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_dead.py +0 -0
  100. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_debt.py +0 -0
  101. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_deps.py +0 -0
  102. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_describe.py +0 -0
  103. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_dev_profile.py +0 -0
  104. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_diagnose.py +0 -0
  105. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_diff.py +0 -0
  106. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_doc_staleness.py +0 -0
  107. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_docs_coverage.py +0 -0
  108. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_doctor.py +0 -0
  109. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_drift.py +0 -0
  110. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_duplicates.py +0 -0
  111. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_effects.py +0 -0
  112. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_endpoints.py +0 -0
  113. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_entry_points.py +0 -0
  114. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_eval_retrieve.py +0 -0
  115. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_fan.py +0 -0
  116. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_file.py +0 -0
  117. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_fingerprint.py +0 -0
  118. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_fitness.py +0 -0
  119. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_flag_dead.py +0 -0
  120. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_fleet.py +0 -0
  121. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_fn_coupling.py +0 -0
  122. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_forecast.py +0 -0
  123. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_grep.py +0 -0
  124. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_guard.py +0 -0
  125. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_health.py +0 -0
  126. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_hooks.py +0 -0
  127. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_hotspots.py +0 -0
  128. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_impact.py +0 -0
  129. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_index.py +0 -0
  130. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_index_bundle.py +0 -0
  131. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_ingest_trace.py +0 -0
  132. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_init.py +0 -0
  133. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_intent.py +0 -0
  134. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_invariants.py +0 -0
  135. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_layers.py +0 -0
  136. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_map.py +0 -0
  137. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_math.py +0 -0
  138. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_metrics.py +0 -0
  139. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_migration_safety.py +0 -0
  140. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_minimap.py +0 -0
  141. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_missing_index.py +0 -0
  142. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_module.py +0 -0
  143. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_mutate.py +0 -0
  144. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_n1.py +0 -0
  145. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_oracle.py +0 -0
  146. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_orchestrate.py +0 -0
  147. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_orphan_routes.py +0 -0
  148. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_over_fetch.py +0 -0
  149. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_owner.py +0 -0
  150. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_partition.py +0 -0
  151. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_path_coverage.py +0 -0
  152. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_patterns.py +0 -0
  153. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_plan.py +0 -0
  154. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_plan_refactor.py +0 -0
  155. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_pr_diff.py +0 -0
  156. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_pr_risk.py +0 -0
  157. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_preflight.py +0 -0
  158. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_relate.py +0 -0
  159. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_report.py +0 -0
  160. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_reset.py +0 -0
  161. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_risk.py +0 -0
  162. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_rules.py +0 -0
  163. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_safe_delete.py +0 -0
  164. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_safe_zones.py +0 -0
  165. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_schema.py +0 -0
  166. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_search.py +0 -0
  167. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_search_semantic.py +0 -0
  168. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_secrets.py +0 -0
  169. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_semantic_diff.py +0 -0
  170. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_simulate.py +0 -0
  171. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_simulate_departure.py +0 -0
  172. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_sketch.py +0 -0
  173. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_smells.py +0 -0
  174. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_spectral.py +0 -0
  175. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_split.py +0 -0
  176. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_suggest_refactoring.py +0 -0
  177. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_suggest_reviewers.py +0 -0
  178. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_supply_chain.py +0 -0
  179. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_symbol.py +0 -0
  180. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_syntax_check.py +0 -0
  181. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_taint.py +0 -0
  182. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_test_gaps.py +0 -0
  183. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_test_scaffold.py +0 -0
  184. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_testmap.py +0 -0
  185. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_tour.py +0 -0
  186. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_trace.py +0 -0
  187. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_trends.py +0 -0
  188. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_triage.py +0 -0
  189. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_understand.py +0 -0
  190. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_uses.py +0 -0
  191. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_verify.py +0 -0
  192. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_verify_imports.py +0 -0
  193. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_vibe_check.py +0 -0
  194. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_visualize.py +0 -0
  195. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_vuln_map.py +0 -0
  196. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_vuln_reach.py +0 -0
  197. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_vulns.py +0 -0
  198. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_watch.py +0 -0
  199. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_weather.py +0 -0
  200. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_why.py +0 -0
  201. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_ws.py +0 -0
  202. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/cmd_xlang.py +0 -0
  203. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/codeowners_helpers.py +0 -0
  204. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/context_helpers.py +0 -0
  205. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/gate_presets.py +0 -0
  206. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/graph_helpers.py +0 -0
  207. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/metrics_history.py +0 -0
  208. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/next_steps.py +0 -0
  209. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/resolve.py +0 -0
  210. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/commands/suppression.py +0 -0
  211. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/coverage_reports.py +0 -0
  212. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/critique/__init__.py +0 -0
  213. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/critique/aggregator.py +0 -0
  214. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/critique/checks.py +0 -0
  215. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/db/__init__.py +0 -0
  216. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/db/connection.py +0 -0
  217. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/db/queries.py +0 -0
  218. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/db/schema.py +0 -0
  219. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/eval/__init__.py +0 -0
  220. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/eval/harness.py +0 -0
  221. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/exit_codes.py +0 -0
  222. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/fleet/__init__.py +0 -0
  223. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/fleet/adapters.py +0 -0
  224. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/fleet/manifest.py +0 -0
  225. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/git_utils.py +0 -0
  226. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/__init__.py +0 -0
  227. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/anomaly.py +0 -0
  228. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/builder.py +0 -0
  229. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/clone_detect.py +0 -0
  230. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/cycles.py +0 -0
  231. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/dark_matter.py +0 -0
  232. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/diff.py +0 -0
  233. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/fingerprint.py +0 -0
  234. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/layers.py +0 -0
  235. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/pagerank.py +0 -0
  236. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/partition.py +0 -0
  237. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/pathfinding.py +0 -0
  238. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/propagation.py +0 -0
  239. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/simulate.py +0 -0
  240. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/spectral.py +0 -0
  241. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/graph/stats.py +0 -0
  242. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/__init__.py +0 -0
  243. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/complexity.py +0 -0
  244. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/discovery.py +0 -0
  245. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/django_post.py +0 -0
  246. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/file_roles.py +0 -0
  247. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/git_stats.py +0 -0
  248. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/gitignore.py +0 -0
  249. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/incremental.py +0 -0
  250. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/indexer.py +0 -0
  251. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/parser.py +0 -0
  252. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/relations.py +0 -0
  253. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/symbols.py +0 -0
  254. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/index/test_conventions.py +0 -0
  255. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/__init__.py +0 -0
  256. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/apex_lang.py +0 -0
  257. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/aura_lang.py +0 -0
  258. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/base.py +0 -0
  259. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/c_lang.py +0 -0
  260. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/csharp_lang.py +0 -0
  261. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/extractor_schema.py +0 -0
  262. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/foxpro_lang.py +0 -0
  263. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/generic_lang.py +0 -0
  264. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/go_lang.py +0 -0
  265. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/hcl_lang.py +0 -0
  266. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/java_lang.py +0 -0
  267. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/javascript_lang.py +0 -0
  268. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/kotlin_lang.py +0 -0
  269. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/php_lang.py +0 -0
  270. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/python_lang.py +0 -0
  271. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/query_engine.py +0 -0
  272. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/registry.py +0 -0
  273. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/ruby_lang.py +0 -0
  274. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/rust_lang.py +0 -0
  275. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/scala_lang.py +0 -0
  276. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/sfxml_lang.py +0 -0
  277. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/sql_lang.py +0 -0
  278. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/swift_lang.py +0 -0
  279. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/typescript_lang.py +0 -0
  280. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/visualforce_lang.py +0 -0
  281. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/languages/yaml_lang.py +0 -0
  282. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/mcp_extras/__init__.py +0 -0
  283. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/mcp_extras/completions.py +0 -0
  284. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/mcp_extras/progress.py +0 -0
  285. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/mcp_extras/sampling.py +0 -0
  286. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/mcp_extras/session.py +0 -0
  287. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/mcp_extras/watcher.py +0 -0
  288. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/output/__init__.py +0 -0
  289. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/output/formatter.py +0 -0
  290. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/output/mermaid.py +0 -0
  291. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/output/sarif.py +0 -0
  292. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/output/schema_registry.py +0 -0
  293. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/plugins.py +0 -0
  294. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/refactor/__init__.py +0 -0
  295. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/refactor/codegen.py +0 -0
  296. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/refactor/transforms.py +0 -0
  297. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/retrieve/__init__.py +0 -0
  298. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/retrieve/seeds.py +0 -0
  299. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/rules/__init__.py +0 -0
  300. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/rules/ast_match.py +0 -0
  301. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/rules/builtin.py +0 -0
  302. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/rules/dataflow.py +0 -0
  303. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/rules/engine.py +0 -0
  304. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/runtime/__init__.py +0 -0
  305. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/runtime/hotspots.py +0 -0
  306. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/runtime/trace_ingest.py +0 -0
  307. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/search/__init__.py +0 -0
  308. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/search/framework_packs.py +0 -0
  309. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/search/index_embeddings.py +0 -0
  310. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/search/onnx_embeddings.py +0 -0
  311. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/search/tfidf.py +0 -0
  312. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/security/__init__.py +0 -0
  313. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/security/taint_classifier.py +0 -0
  314. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/security/taint_engine.py +0 -0
  315. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/security/vuln_reach.py +0 -0
  316. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/security/vuln_store.py +0 -0
  317. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/surface_counts.py +0 -0
  318. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/templates/__init__.py +0 -0
  319. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/templates/ci/__init__.py +0 -0
  320. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/workspace/__init__.py +0 -0
  321. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/workspace/aggregator.py +0 -0
  322. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/workspace/api_scanner.py +0 -0
  323. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/workspace/config.py +0 -0
  324. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam/workspace/db.py +0 -0
  325. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam_code.egg-info/dependency_links.txt +0 -0
  326. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam_code.egg-info/entry_points.txt +0 -0
  327. {roam_code-12.1.0 → roam_code-12.2.0}/src/roam_code.egg-info/top_level.txt +0 -0
  328. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_adrs.py +0 -0
  329. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_adversarial.py +0 -0
  330. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_affected.py +0 -0
  331. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_agent_export.py +0 -0
  332. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_agent_mode.py +0 -0
  333. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_agent_plan_context.py +0 -0
  334. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_ai_ratio.py +0 -0
  335. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_ai_readiness.py +0 -0
  336. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_alerts_cmd.py +0 -0
  337. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_annotations.py +0 -0
  338. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_anomaly.py +0 -0
  339. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_api_changes.py +0 -0
  340. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_api_drift.py +0 -0
  341. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_ask.py +0 -0
  342. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_attest.py +0 -0
  343. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_auth_gaps.py +0 -0
  344. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_backend_fixes_round2.py +0 -0
  345. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_backend_fixes_round3.py +0 -0
  346. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_basic.py +0 -0
  347. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_bisect.py +0 -0
  348. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_bridge_django.py +0 -0
  349. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_bridges.py +0 -0
  350. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_bridges_extended.py +0 -0
  351. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_budget.py +0 -0
  352. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_budget_flag.py +0 -0
  353. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_budget_phase2.py +0 -0
  354. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_bus_factor.py +0 -0
  355. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_capsule.py +0 -0
  356. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_cga.py +0 -0
  357. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_check_rules.py +0 -0
  358. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_ci_gate_eval.py +0 -0
  359. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_ci_sarif_guard.py +0 -0
  360. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_ci_setup.py +0 -0
  361. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_clones.py +0 -0
  362. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_closure.py +0 -0
  363. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_codeowners.py +0 -0
  364. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_commands_architecture.py +0 -0
  365. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_commands_exploration.py +0 -0
  366. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_commands_health.py +0 -0
  367. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_commands_refactoring.py +0 -0
  368. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_commands_workflow.py +0 -0
  369. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_competitor_site_data.py +0 -0
  370. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_comprehensive.py +0 -0
  371. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_config.py +0 -0
  372. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_congestion.py +0 -0
  373. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_context_propagation.py +0 -0
  374. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_conventions_cmd.py +0 -0
  375. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_coverage_gaps_cmd.py +0 -0
  376. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_coverage_ingestion.py +0 -0
  377. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_critique.py +0 -0
  378. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_cut.py +0 -0
  379. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_dark_matter.py +0 -0
  380. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_dark_matter_helpers.py +0 -0
  381. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_dashboard.py +0 -0
  382. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_dataflow_dead.py +0 -0
  383. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_dead_aging.py +0 -0
  384. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_defer_loading.py +0 -0
  385. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_demo_gif_asset.py +0 -0
  386. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_describe.py +0 -0
  387. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_detail_flag_hints.py +0 -0
  388. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_deterministic_output.py +0 -0
  389. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_dev_profile.py +0 -0
  390. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_difficulty_scoring.py +0 -0
  391. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_doc_staleness.py +0 -0
  392. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_docker_assets.py +0 -0
  393. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_docs_coverage.py +0 -0
  394. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_docs_site_quality.py +0 -0
  395. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_doctor.py +0 -0
  396. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_drift.py +0 -0
  397. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_duplicates.py +0 -0
  398. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_effects.py +0 -0
  399. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_effects_propagation.py +0 -0
  400. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_endpoints.py +0 -0
  401. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_entry_points_cmd.py +0 -0
  402. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_eval_retrieve.py +0 -0
  403. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_exclude_patterns.py +0 -0
  404. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_exit_codes.py +0 -0
  405. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_file_roles.py +0 -0
  406. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_fingerprint.py +0 -0
  407. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_fixes.py +0 -0
  408. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_flag_dead.py +0 -0
  409. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_fleet.py +0 -0
  410. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_fn_coupling.py +0 -0
  411. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_forecast.py +0 -0
  412. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_formatters.py +0 -0
  413. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_foxpro.py +0 -0
  414. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_framework_detection.py +0 -0
  415. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_gate_presets.py +0 -0
  416. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_git_utils.py +0 -0
  417. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_guard.py +0 -0
  418. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_health_gate.py +0 -0
  419. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_hooks.py +0 -0
  420. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_hotspots.py +0 -0
  421. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_index.py +0 -0
  422. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_index_bundle.py +0 -0
  423. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_init_cmd.py +0 -0
  424. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_install_check.py +0 -0
  425. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_intent.py +0 -0
  426. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_invariants.py +0 -0
  427. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_json_contracts.py +0 -0
  428. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_json_error_envelope.py +0 -0
  429. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_kotlin_swift_extractors.py +0 -0
  430. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_language_corpus.py +0 -0
  431. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_languages.py +0 -0
  432. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_library_api.py +0 -0
  433. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_math.py +0 -0
  434. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_math_tips.py +0 -0
  435. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_mcp_extras.py +0 -0
  436. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_mcp_setup.py +0 -0
  437. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_mermaid.py +0 -0
  438. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_metrics_cmd.py +0 -0
  439. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_migration_safety.py +0 -0
  440. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_minimap.py +0 -0
  441. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_missing_index.py +0 -0
  442. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_mutate.py +0 -0
  443. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_n1.py +0 -0
  444. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_next_steps.py +0 -0
  445. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_onboard.py +0 -0
  446. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_oracle.py +0 -0
  447. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_orchestrate.py +0 -0
  448. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_orphan_routes.py +0 -0
  449. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_oss_bench_harness.py +0 -0
  450. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_over_fetch.py +0 -0
  451. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_pagerank_truncation.py +0 -0
  452. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_partition.py +0 -0
  453. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_path_coverage.py +0 -0
  454. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_patterns_cmd.py +0 -0
  455. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_performance.py +0 -0
  456. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_personalized_pagerank.py +0 -0
  457. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_plan.py +0 -0
  458. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_plugin_discovery.py +0 -0
  459. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_pr_comment_script.py +0 -0
  460. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_pr_diff.py +0 -0
  461. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_pr_risk_author.py +0 -0
  462. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_progress.py +0 -0
  463. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_progressive_disclosure.py +0 -0
  464. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_properties.py +0 -0
  465. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_python_extractor_v2.py +0 -0
  466. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_refactoring_intelligence.py +0 -0
  467. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_relate.py +0 -0
  468. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_report.py +0 -0
  469. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_reset_clean.py +0 -0
  470. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_resolve.py +0 -0
  471. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_retrieve_seeds.py +0 -0
  472. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_risk.py +0 -0
  473. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_ruby.py +0 -0
  474. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_rule_profiles.py +0 -0
  475. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_rules.py +0 -0
  476. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_rules_ast_match.py +0 -0
  477. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_rules_community_pack.py +0 -0
  478. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_rules_dataflow.py +0 -0
  479. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_rules_symbol_requirements.py +0 -0
  480. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_runtime.py +0 -0
  481. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_runtime_score.py +0 -0
  482. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_salesforce.py +0 -0
  483. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_sarif_flag.py +0 -0
  484. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_sbom.py +0 -0
  485. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_scala.py +0 -0
  486. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_schema_versioning.py +0 -0
  487. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_search_explain.py +0 -0
  488. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_secrets.py +0 -0
  489. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_secrets_v2.py +0 -0
  490. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_semantic_diff.py +0 -0
  491. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_semantic_onnx.py +0 -0
  492. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_semantic_search.py +0 -0
  493. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_simulate.py +0 -0
  494. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_simulate_departure.py +0 -0
  495. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_sketch.py +0 -0
  496. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_smells.py +0 -0
  497. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_smoke.py +0 -0
  498. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_sna_metrics.py +0 -0
  499. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_spectral.py +0 -0
  500. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_split_cmd.py +0 -0
  501. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_sql.py +0 -0
  502. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_suggest_reviewers.py +0 -0
  503. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_supply_chain.py +0 -0
  504. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_surface_counts.py +0 -0
  505. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_syntax_check.py +0 -0
  506. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_taint.py +0 -0
  507. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_taint_analysis.py +0 -0
  508. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_taint_classifier.py +0 -0
  509. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_test_conventions.py +0 -0
  510. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_test_gaps.py +0 -0
  511. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_test_scaffold.py +0 -0
  512. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_testmap.py +0 -0
  513. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_top_flag_consistency.py +0 -0
  514. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_tour_cmd.py +0 -0
  515. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_trends.py +0 -0
  516. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_trends_cohort.py +0 -0
  517. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_triage.py +0 -0
  518. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_uses_cmd.py +0 -0
  519. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_v6_features.py +0 -0
  520. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_v71_features.py +0 -0
  521. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_v7_features.py +0 -0
  522. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_v82_features.py +0 -0
  523. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_verify.py +0 -0
  524. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_verify_imports.py +0 -0
  525. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_vibe_check.py +0 -0
  526. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_visualize.py +0 -0
  527. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_vuln.py +0 -0
  528. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_vulns_cmd.py +0 -0
  529. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_watch.py +0 -0
  530. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_why.py +0 -0
  531. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_workspace.py +0 -0
  532. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_ws_resolve_fixes.py +0 -0
  533. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_xlang.py +0 -0
  534. {roam_code-12.1.0 → roam_code-12.2.0}/tests/test_yaml_hcl.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roam-code
3
- Version: 12.1.0
3
+ Version: 12.2.0
4
4
  Summary: Instant codebase comprehension for AI coding agents
5
5
  Author: CosmoHac
6
6
  License-Expression: MIT
@@ -37,6 +37,15 @@ Provides-Extra: semantic
37
37
  Requires-Dist: numpy>=1.24; extra == "semantic"
38
38
  Requires-Dist: onnxruntime>=1.16; extra == "semantic"
39
39
  Requires-Dist: tokenizers>=0.15; extra == "semantic"
40
+ Provides-Extra: leiden
41
+ Requires-Dist: igraph>=0.11; extra == "leiden"
42
+ Requires-Dist: leidenalg>=0.10; extra == "leiden"
43
+ Provides-Extra: graph-fast
44
+ Requires-Dist: rustworkx>=0.14; extra == "graph-fast"
45
+ Provides-Extra: sbom
46
+ Requires-Dist: cyclonedx-python-lib>=8.0; extra == "sbom"
47
+ Provides-Extra: learned
48
+ Requires-Dist: lightgbm>=4.0; extra == "learned"
40
49
  Provides-Extra: dev
41
50
  Requires-Dist: pytest>=7.0; extra == "dev"
42
51
  Requires-Dist: pytest-xdist>=3.0; extra == "dev"
@@ -51,7 +60,7 @@ Dynamic: license-file
51
60
 
52
61
  **The architectural intelligence layer for AI coding agents. Structural graph, architecture governance, multi-agent orchestration, vulnerability mapping, runtime analysis -- one CLI, zero API keys.**
53
62
 
54
- *150 commands · 112 MCP tools · 27 languages · 100% local*
63
+ *150 commands · 116 MCP tools · 27 languages · 100% local*
55
64
 
56
65
  [![PyPI version](https://img.shields.io/pypi/v/roam-code?style=flat-square&color=blue)](https://pypi.org/project/roam-code/)
57
66
  [![GitHub stars](https://img.shields.io/github/stars/Cranot/roam-code?style=flat-square)](https://github.com/Cranot/roam-code/stargazers)
@@ -128,7 +137,7 @@ $ roam diff # blast radius of uncommitted changes
128
137
  - **`personalized_pagerank()`** in `graph/pagerank.py`: NetworkX `personalization=` wrapper with empty-seed fallback to global PR; biases ranking toward query-relevant nodes for the retrieve reranker.
129
138
  - **`.roam/config.toml`** (new): zero-dep TOML loader (stdlib `tomllib` → `tomli` → in-tree subset parser). Tunable retrieve weights (`alpha`/`beta`/`gamma`/`delta`/`epsilon`), `tokens_per_line`, `lexical_baseline`, `first_stage_token_cap`, `default_budget`, `default_k`, `default_rerank`.
130
139
  - **DX corrections from dogfood pass**: `roam --detail <cmd>` is the canonical group-level flag; misleading "use --detail" hints in 7 commands rewritten to point users at `roam --detail <cmd>`. `--top N` aliased on `complexity`/`algo`/`rules` (`--top 0` means unlimited on `rules`). `roam fingerprint` no longer refuses graphs ≥5,000 symbols (new soft-warn threshold 20k, hard cap 100k).
131
- - **150 CLI commands, 112 MCP tools** (`fleet`, `ask`, `taint`, `cga`, `eval-retrieve` remain CLI-only; v12 exposes `roam_retrieve`, `roam_critique`, `roam_fleet_plan`, plus 5 v12.1 boolean oracles (`roam_oracle_*`) and `roam_taint_classify` as MCP tools). 33-tool `core` preset is the default for token-budget-conscious clients.
140
+ - **150 CLI commands, 116 MCP tools** (`fleet`, `ask`, `taint`, `cga`, `eval-retrieve` remain CLI-only; v12 exposes `roam_retrieve`, `roam_critique`, `roam_fleet_plan`, plus 5 v12.1 boolean oracles (`roam_oracle_*`) and `roam_taint_classify` as MCP tools). 33-tool `core` preset is the default for token-budget-conscious clients.
132
141
 
133
142
  ## What's New in v11
134
143
 
@@ -159,7 +168,7 @@ $ roam diff # blast radius of uncommitted changes
159
168
  - MCP token overhead for default core context dropped from ~36K to <3K tokens (about 92% reduction).
160
169
 
161
170
  ### Performance and Retrieval
162
- - Symbol search moved to SQLite FTS5/BM25: typical search moved from seconds to milliseconds (about 1000x on benchmarked paths).
171
+ - Symbol search moved to SQLite FTS5/BM25: typical search moved from seconds to tens of milliseconds on the indexed cohort (mileage varies by repo size and query selectivity — see `bench/retrieve/` for the methodology).
163
172
  - Incremental indexing shifted from O(N) full-edge rebuild behavior to O(changed) updates.
164
173
  - DB/runtime optimizations (`mmap_size`, safer large-graph guards, batched writes) reduce first-run and reindex friction on larger repos.
165
174
 
@@ -299,7 +308,7 @@ roam health
299
308
 
300
309
  ## Commands
301
310
 
302
- The [5 core commands](#core-commands) shown above cover ~80% of agent workflows. All 150 commands are organized into 7 categories.
311
+ **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 145 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 **150 commands organised into 7 categories**, but you don't need to know that to start.
303
312
 
304
313
  <details>
305
314
  <summary><strong>Full command reference</strong></summary>
@@ -937,7 +946,7 @@ ROAM_MCP_LITE=0 roam mcp
937
946
  Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`, `roam_complete`, `roam_complexity_report`, `roam_context`, `roam_dead_code`, `roam_deps`, `roam_diagnose`, `roam_diagnose_issue`, `roam_diff`, `roam_expand_toolset`, `roam_explore`, `roam_file_info`, `roam_health`, `roam_impact`, `roam_pr_risk`, `roam_preflight`, `roam_prepare_change`, `roam_review_change`, `roam_search_symbol`, `roam_syntax_check`, `roam_trace`, `roam_understand`, `roam_uses`.
938
947
 
939
948
  <details>
940
- <summary><strong>MCP tool list (all 112)</strong></summary>
949
+ <summary><strong>MCP tool list (all 116)</strong></summary>
941
950
 
942
951
  | Tool | Description |
943
952
  |------|-------------|
@@ -957,6 +966,10 @@ Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`,
957
966
  | `roam_oracle_is_reachable_from_entry` | Boolean oracle: can BFS reach this symbol from any entry-point? |
958
967
  | `roam_oracle_is_clone_of` | Boolean oracle: does this symbol participate in a persisted clone cluster? |
959
968
  | `roam_taint_classify` | LLM-augmented taint classification (IDOR/AUTHZ/SQLI/...) via MCP sampling |
969
+ | `roam_taint` | Static taint analysis — graph-reach BFS with OpenVEX-correct findings |
970
+ | `roam_sbom` | CycloneDX 1.7 / SPDX 2.3 SBOM emit with optional AIBOM extension (EU AI Act) |
971
+ | `roam_cga_emit` | Emit a Code Graph Attestation (in-toto v1) with optional cosign signing |
972
+ | `roam_cga_verify` | Verify a CGA statement — re-derives Merkle + edge digest, checks cosign signature |
960
973
  | `roam_trace` | Dependency path between two symbols |
961
974
  | `roam_impact` | Blast radius of changing a symbol |
962
975
  | `roam_file_info` | File skeleton with all definitions |
@@ -1568,7 +1581,7 @@ roam-code/
1568
1581
  ├── src/roam/
1569
1582
  │ ├── __init__.py # Version (from pyproject.toml)
1570
1583
  │ ├── cli.py # Click CLI (150 commands)
1571
- │ ├── mcp_server.py # MCP server (112 tools, 10 resources, 5 prompts)
1584
+ │ ├── mcp_server.py # MCP server (116 tools, 10 resources, 5 prompts)
1572
1585
  │ ├── db/
1573
1586
  │ │ ├── connection.py # SQLite (WAL, pragmas, batched IN)
1574
1587
  │ │ ├── schema.py # Tables, indexes, migrations
@@ -1662,7 +1675,7 @@ Optional: Local semantic ONNX stack (`numpy`, `onnxruntime`, `tokenizers`) via `
1662
1675
  ### Shipped
1663
1676
 
1664
1677
  - [x] MCP v2 agent surface: in-process execution, compound operations, presets, schemas, annotations, and compatibility profiles.
1665
- - [x] Full command and MCP inventory parity in docs: 150 CLI commands and 112 MCP tools.
1678
+ - [x] Full command and MCP inventory parity in docs: 150 CLI commands and 116 MCP tools.
1666
1679
  - [x] CI hardening: composite action, changed-only mode, trend-aware gates, sticky PR updater, and SARIF guardrails.
1667
1680
  - [x] Performance foundation: FTS5/BM25 search, O(changed) incremental indexing, DB/index optimizations.
1668
1681
  - [x] Agent governance suite: `vibe-check`, `ai-readiness`, `verify`, `ai-ratio`, `duplicates`, advanced `algo` scoring/SARIF.
@@ -4,7 +4,7 @@
4
4
 
5
5
  **The architectural intelligence layer for AI coding agents. Structural graph, architecture governance, multi-agent orchestration, vulnerability mapping, runtime analysis -- one CLI, zero API keys.**
6
6
 
7
- *150 commands · 112 MCP tools · 27 languages · 100% local*
7
+ *150 commands · 116 MCP tools · 27 languages · 100% local*
8
8
 
9
9
  [![PyPI version](https://img.shields.io/pypi/v/roam-code?style=flat-square&color=blue)](https://pypi.org/project/roam-code/)
10
10
  [![GitHub stars](https://img.shields.io/github/stars/Cranot/roam-code?style=flat-square)](https://github.com/Cranot/roam-code/stargazers)
@@ -81,7 +81,7 @@ $ roam diff # blast radius of uncommitted changes
81
81
  - **`personalized_pagerank()`** in `graph/pagerank.py`: NetworkX `personalization=` wrapper with empty-seed fallback to global PR; biases ranking toward query-relevant nodes for the retrieve reranker.
82
82
  - **`.roam/config.toml`** (new): zero-dep TOML loader (stdlib `tomllib` → `tomli` → in-tree subset parser). Tunable retrieve weights (`alpha`/`beta`/`gamma`/`delta`/`epsilon`), `tokens_per_line`, `lexical_baseline`, `first_stage_token_cap`, `default_budget`, `default_k`, `default_rerank`.
83
83
  - **DX corrections from dogfood pass**: `roam --detail <cmd>` is the canonical group-level flag; misleading "use --detail" hints in 7 commands rewritten to point users at `roam --detail <cmd>`. `--top N` aliased on `complexity`/`algo`/`rules` (`--top 0` means unlimited on `rules`). `roam fingerprint` no longer refuses graphs ≥5,000 symbols (new soft-warn threshold 20k, hard cap 100k).
84
- - **150 CLI commands, 112 MCP tools** (`fleet`, `ask`, `taint`, `cga`, `eval-retrieve` remain CLI-only; v12 exposes `roam_retrieve`, `roam_critique`, `roam_fleet_plan`, plus 5 v12.1 boolean oracles (`roam_oracle_*`) and `roam_taint_classify` as MCP tools). 33-tool `core` preset is the default for token-budget-conscious clients.
84
+ - **150 CLI commands, 116 MCP tools** (`fleet`, `ask`, `taint`, `cga`, `eval-retrieve` remain CLI-only; v12 exposes `roam_retrieve`, `roam_critique`, `roam_fleet_plan`, plus 5 v12.1 boolean oracles (`roam_oracle_*`) and `roam_taint_classify` as MCP tools). 33-tool `core` preset is the default for token-budget-conscious clients.
85
85
 
86
86
  ## What's New in v11
87
87
 
@@ -112,7 +112,7 @@ $ roam diff # blast radius of uncommitted changes
112
112
  - MCP token overhead for default core context dropped from ~36K to <3K tokens (about 92% reduction).
113
113
 
114
114
  ### Performance and Retrieval
115
- - Symbol search moved to SQLite FTS5/BM25: typical search moved from seconds to milliseconds (about 1000x on benchmarked paths).
115
+ - Symbol search moved to SQLite FTS5/BM25: typical search moved from seconds to tens of milliseconds on the indexed cohort (mileage varies by repo size and query selectivity — see `bench/retrieve/` for the methodology).
116
116
  - Incremental indexing shifted from O(N) full-edge rebuild behavior to O(changed) updates.
117
117
  - DB/runtime optimizations (`mmap_size`, safer large-graph guards, batched writes) reduce first-run and reindex friction on larger repos.
118
118
 
@@ -252,7 +252,7 @@ roam health
252
252
 
253
253
  ## Commands
254
254
 
255
- The [5 core commands](#core-commands) shown above cover ~80% of agent workflows. All 150 commands are organized into 7 categories.
255
+ **Lead with the 5 verbs.** The [5 core commands](#core-commands) cover ~80% of agent workflows: `understand`, `context`, `retrieve`, `preflight`, `critique`. The remaining 145 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 **150 commands organised into 7 categories**, but you don't need to know that to start.
256
256
 
257
257
  <details>
258
258
  <summary><strong>Full command reference</strong></summary>
@@ -890,7 +890,7 @@ ROAM_MCP_LITE=0 roam mcp
890
890
  Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`, `roam_complete`, `roam_complexity_report`, `roam_context`, `roam_dead_code`, `roam_deps`, `roam_diagnose`, `roam_diagnose_issue`, `roam_diff`, `roam_expand_toolset`, `roam_explore`, `roam_file_info`, `roam_health`, `roam_impact`, `roam_pr_risk`, `roam_preflight`, `roam_prepare_change`, `roam_review_change`, `roam_search_symbol`, `roam_syntax_check`, `roam_trace`, `roam_understand`, `roam_uses`.
891
891
 
892
892
  <details>
893
- <summary><strong>MCP tool list (all 112)</strong></summary>
893
+ <summary><strong>MCP tool list (all 116)</strong></summary>
894
894
 
895
895
  | Tool | Description |
896
896
  |------|-------------|
@@ -910,6 +910,10 @@ Core preset tools: `roam_affected_tests`, `roam_batch_get`, `roam_batch_search`,
910
910
  | `roam_oracle_is_reachable_from_entry` | Boolean oracle: can BFS reach this symbol from any entry-point? |
911
911
  | `roam_oracle_is_clone_of` | Boolean oracle: does this symbol participate in a persisted clone cluster? |
912
912
  | `roam_taint_classify` | LLM-augmented taint classification (IDOR/AUTHZ/SQLI/...) via MCP sampling |
913
+ | `roam_taint` | Static taint analysis — graph-reach BFS with OpenVEX-correct findings |
914
+ | `roam_sbom` | CycloneDX 1.7 / SPDX 2.3 SBOM emit with optional AIBOM extension (EU AI Act) |
915
+ | `roam_cga_emit` | Emit a Code Graph Attestation (in-toto v1) with optional cosign signing |
916
+ | `roam_cga_verify` | Verify a CGA statement — re-derives Merkle + edge digest, checks cosign signature |
913
917
  | `roam_trace` | Dependency path between two symbols |
914
918
  | `roam_impact` | Blast radius of changing a symbol |
915
919
  | `roam_file_info` | File skeleton with all definitions |
@@ -1521,7 +1525,7 @@ roam-code/
1521
1525
  ├── src/roam/
1522
1526
  │ ├── __init__.py # Version (from pyproject.toml)
1523
1527
  │ ├── cli.py # Click CLI (150 commands)
1524
- │ ├── mcp_server.py # MCP server (112 tools, 10 resources, 5 prompts)
1528
+ │ ├── mcp_server.py # MCP server (116 tools, 10 resources, 5 prompts)
1525
1529
  │ ├── db/
1526
1530
  │ │ ├── connection.py # SQLite (WAL, pragmas, batched IN)
1527
1531
  │ │ ├── schema.py # Tables, indexes, migrations
@@ -1615,7 +1619,7 @@ Optional: Local semantic ONNX stack (`numpy`, `onnxruntime`, `tokenizers`) via `
1615
1619
  ### Shipped
1616
1620
 
1617
1621
  - [x] MCP v2 agent surface: in-process execution, compound operations, presets, schemas, annotations, and compatibility profiles.
1618
- - [x] Full command and MCP inventory parity in docs: 150 CLI commands and 112 MCP tools.
1622
+ - [x] Full command and MCP inventory parity in docs: 150 CLI commands and 116 MCP tools.
1619
1623
  - [x] CI hardening: composite action, changed-only mode, trend-aware gates, sticky PR updater, and SARIF guardrails.
1620
1624
  - [x] Performance foundation: FTS5/BM25 search, O(changed) incremental indexing, DB/index optimizations.
1621
1625
  - [x] Agent governance suite: `vibe-check`, `ai-readiness`, `verify`, `ai-ratio`, `duplicates`, advanced `algo` scoring/SARIF.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "roam-code"
7
- version = "12.1.0"
7
+ version = "12.2.0"
8
8
  description = "Instant codebase comprehension for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -55,6 +55,29 @@ semantic = [
55
55
  "onnxruntime>=1.16",
56
56
  "tokenizers>=0.15",
57
57
  ]
58
+ leiden = [
59
+ # Strictly better community detection than Louvain (no badly-connected
60
+ # communities). Auto-detected at runtime; falls back to seeded Louvain
61
+ # when these aren't installed.
62
+ "igraph>=0.11",
63
+ "leidenalg>=0.10",
64
+ ]
65
+ graph-fast = [
66
+ # rustworkx is a Rust-backed drop-in for many NetworkX algorithms;
67
+ # 3-100× speedup at >250k nodes per the v12.2 architecture review.
68
+ # Auto-detected via ``ROAM_GRAPH_BACKEND=rustworkx``.
69
+ "rustworkx>=0.14",
70
+ ]
71
+ sbom = [
72
+ # CycloneDX 1.7 SBOM emit (with AIBOM extension) for `roam sbom`.
73
+ # Falls back to in-tree minimal emitter when not installed.
74
+ "cyclonedx-python-lib>=8.0",
75
+ ]
76
+ learned = [
77
+ # LightGBM LambdaMART distillation for `roam retrieve --rerank learned`.
78
+ # Optional — current `--rerank fast` blend stays the default.
79
+ "lightgbm>=4.0",
80
+ ]
58
81
  dev = [
59
82
  "pytest>=7.0",
60
83
  "pytest-xdist>=3.0",
@@ -31,6 +31,11 @@ from typing import Any
31
31
  from roam.security.taint_engine import OPENVEX_JUSTIFICATIONS, OPENVEX_STATUSES
32
32
 
33
33
  PREDICATE_TYPE = "https://roam-code.dev/CodeGraph/v1"
34
+ # v12.2: fused CodeGraph + AIBOM predicate. Owns the "structurally bound
35
+ # AI authorship for tamper-evident codebases" lane that SLSA + SPDX +
36
+ # CycloneDX 1.7 + OpenVEX leave gapped. Reference impl candidate for
37
+ # the in-toto attestation registry.
38
+ PREDICATE_TYPE_AIBOM = "https://roam-code.dev/CodeGraph-AIBOM/v1"
34
39
  STATEMENT_TYPE = "https://in-toto.io/Statement/v1"
35
40
  SCHEMA_VERSION = "1"
36
41
 
@@ -218,6 +223,7 @@ def build_cga_statement(
218
223
  project_root: Path,
219
224
  tool_version: str | None = None,
220
225
  taint_findings: list | None = None,
226
+ include_aibom: bool = False,
221
227
  ) -> dict[str, Any]:
222
228
  """Build the full in-toto v1 Statement wrapping the CGA predicate.
223
229
 
@@ -225,9 +231,15 @@ def build_cga_statement(
225
231
  {
226
232
  "_type": "https://in-toto.io/Statement/v1",
227
233
  "predicateType": "https://roam-code.dev/CodeGraph/v1",
234
+ # → CodeGraph-AIBOM/v1 when include_aibom=True
228
235
  "subject": [{"name": "...", "digest": {...}}],
229
236
  "predicate": {...}
230
237
  }
238
+
239
+ With ``include_aibom=True``, the predicate type promotes to
240
+ ``CodeGraph-AIBOM/v1`` and embeds an ``aibom`` block binding
241
+ AI-authored commits to the indexed symbols they touched. Required
242
+ for EU AI Act Art. 50 disclosure (effective 2026-08-02).
231
243
  """
232
244
  sha = _git_commit_sha(project_root) or "unknown"
233
245
  remote = _git_remote_url(project_root)
@@ -236,16 +248,26 @@ def build_cga_statement(
236
248
  "name": subject_name,
237
249
  "digest": {"git_commit_sha1": sha},
238
250
  }
251
+ predicate = build_cga_predicate(
252
+ conn,
253
+ project_root=project_root,
254
+ tool_version=tool_version,
255
+ taint_findings=taint_findings,
256
+ )
257
+ predicate_type = PREDICATE_TYPE
258
+ if include_aibom:
259
+ try:
260
+ from roam.security.aibom_extension import build_aibom_block
261
+
262
+ predicate["aibom"] = build_aibom_block(project_root, conn)
263
+ predicate_type = PREDICATE_TYPE_AIBOM
264
+ except Exception as exc:
265
+ predicate["aibom_error"] = str(exc)
239
266
  return {
240
267
  "_type": STATEMENT_TYPE,
241
- "predicateType": PREDICATE_TYPE,
268
+ "predicateType": predicate_type,
242
269
  "subject": [subject],
243
- "predicate": build_cga_predicate(
244
- conn,
245
- project_root=project_root,
246
- tool_version=tool_version,
247
- taint_findings=taint_findings,
248
- ),
270
+ "predicate": predicate,
249
271
  }
250
272
 
251
273
 
@@ -270,8 +292,11 @@ def verify_cga_statement(
270
292
  errors: list[str] = []
271
293
  if statement.get("_type") != STATEMENT_TYPE:
272
294
  errors.append(f"_type mismatch: got {statement.get('_type')!r}, expected {STATEMENT_TYPE!r}")
273
- if statement.get("predicateType") != PREDICATE_TYPE:
274
- errors.append(f"predicateType mismatch: got {statement.get('predicateType')!r}, expected {PREDICATE_TYPE!r}")
295
+ accepted_types = (PREDICATE_TYPE, PREDICATE_TYPE_AIBOM)
296
+ if statement.get("predicateType") not in accepted_types:
297
+ errors.append(
298
+ f"predicateType mismatch: got {statement.get('predicateType')!r}, expected one of {accepted_types!r}"
299
+ )
275
300
  predicate = statement.get("predicate") or {}
276
301
  if not isinstance(predicate, dict):
277
302
  return False, errors + ["predicate is not a JSON object"]
@@ -110,9 +110,25 @@ def _default_output_path(project_root: Path, statement: dict) -> Path:
110
110
  "browser flow."
111
111
  ),
112
112
  )
113
+ @click.option(
114
+ "--aibom",
115
+ is_flag=True,
116
+ help=(
117
+ "Promote the predicate to ``CodeGraph-AIBOM/v1`` and embed an "
118
+ "AIBOM block binding AI-authored commits to the indexed symbols "
119
+ "they touched. Required for EU AI Act Art. 50 disclosure (effective "
120
+ "2026-08-02) and the GPAI Code of Practice provenance mandate. "
121
+ "Reference impl candidate for the in-toto attestation registry."
122
+ ),
123
+ )
113
124
  @click.pass_context
114
- def cga_emit(ctx, output_path, no_write, include_taint, taint_rules_dir, sign, key_path, keyless):
115
- """Emit a Code Graph Attestation (in-toto v1, optionally cosign-signed)."""
125
+ def cga_emit(ctx, output_path, no_write, include_taint, taint_rules_dir, sign, key_path, keyless, aibom):
126
+ """Emit a Code Graph Attestation (in-toto v1, optionally cosign-signed).
127
+
128
+ With ``--aibom`` the predicate type promotes to
129
+ ``roam-code.dev/CodeGraph-AIBOM/v1`` and the predicate gains an
130
+ ``aibom`` block binding AI-authored commits to indexed symbols.
131
+ """
116
132
  json_mode = ctx.obj.get("json") if ctx.obj else False
117
133
  token_budget = ctx.obj.get("budget", 0) if ctx.obj else 0
118
134
 
@@ -137,6 +153,7 @@ def cga_emit(ctx, output_path, no_write, include_taint, taint_rules_dir, sign, k
137
153
  conn,
138
154
  project_root=project_root,
139
155
  taint_findings=taint_findings,
156
+ include_aibom=aibom,
140
157
  )
141
158
 
142
159
  canonical = serialize_statement(statement)
@@ -198,7 +215,7 @@ def cga_emit(ctx, output_path, no_write, include_taint, taint_rules_dir, sign, k
198
215
  "edge_bundle_digest": pred["edge_bundle_digest"],
199
216
  "symbol_count": pred["symbol_count"],
200
217
  "edge_count": pred["edge_count"],
201
- "predicate_type": PREDICATE_TYPE,
218
+ "predicate_type": statement.get("predicateType", PREDICATE_TYPE),
202
219
  "statement_type": STATEMENT_TYPE,
203
220
  "written_to": written_to,
204
221
  "signed": bool(sign_result and sign_result.get("signed")),
@@ -212,7 +229,7 @@ def cga_emit(ctx, output_path, no_write, include_taint, taint_rules_dir, sign, k
212
229
  return
213
230
 
214
231
  click.echo(f"VERDICT: {verdict}")
215
- click.echo(f"Predicate type: {PREDICATE_TYPE}")
232
+ click.echo(f"Predicate type: {statement.get('predicateType', PREDICATE_TYPE)}")
216
233
  click.echo(f"Statement type: {STATEMENT_TYPE}")
217
234
  click.echo(f"Languages: {', '.join(pred['languages']) or '(none)'}")
218
235
  if written_to:
@@ -60,8 +60,19 @@ _CONFIGS = {
60
60
 
61
61
  @click.command("mcp-setup")
62
62
  @click.argument("platform", type=click.Choice(sorted(_CONFIGS.keys())), required=False)
63
+ @click.option(
64
+ "--preset",
65
+ type=click.Choice(["core", "review", "refactor", "debug", "architecture", "compliance", "full"]),
66
+ default=None,
67
+ help=(
68
+ "Pre-fill the generated config with ``ROAM_MCP_PRESET=<preset>``. "
69
+ "Default = no env var (uses 'core'). v12.2 adds the 'compliance' "
70
+ "preset for the EU AI Act / NIS2 buyer wedge — 14 tools covering "
71
+ "preflight, taint, sbom, cga emit/verify."
72
+ ),
73
+ )
63
74
  @click.pass_context
64
- def mcp_setup(ctx, platform):
75
+ def mcp_setup(ctx, platform, preset):
65
76
  """Generate MCP server config for AI coding platforms.
66
77
 
67
78
  Prints the exact JSON config block to paste into your AI coding tool.
@@ -77,10 +88,17 @@ def mcp_setup(ctx, platform):
77
88
  gemini-cli Gemini CLI
78
89
  codex-cli OpenAI Codex CLI
79
90
 
91
+ \b
92
+ Presets (v12.2):
93
+ core 33 tools — default, balanced for daily agent use
94
+ compliance 14 tools — EU AI Act / NIS2 audit (taint, sbom, cga, …)
95
+ full 116 tools — every tool exposed
96
+ review/refactor/debug/architecture — task-specific subsets
97
+
80
98
  \b
81
99
  Examples:
82
100
  roam mcp-setup claude-code
83
- roam mcp-setup cursor
101
+ roam mcp-setup cursor --preset compliance
84
102
  roam --json mcp-setup vscode
85
103
  """
86
104
  json_mode = ctx.obj.get("json") if ctx.obj else False
@@ -106,6 +124,25 @@ def mcp_setup(ctx, platform):
106
124
 
107
125
  cfg = _CONFIGS[platform]
108
126
 
127
+ # v12.2: when --preset is supplied, deep-copy the config and inject the
128
+ # ROAM_MCP_PRESET env var into the server block. Each platform stores
129
+ # its server entry under a slightly different key shape (mcpServers vs
130
+ # servers) — handle both. Mutates a copy, not the module-level dict.
131
+ if preset:
132
+ import copy
133
+
134
+ cfg = copy.deepcopy(cfg)
135
+ jc = cfg.get("json_config") or {}
136
+ # Walk to the first server entry and add an "env" block.
137
+ for outer_key in ("mcpServers", "servers"):
138
+ if outer_key not in jc:
139
+ continue
140
+ for server_name, server_block in jc[outer_key].items():
141
+ env = server_block.setdefault("env", {})
142
+ env["ROAM_MCP_PRESET"] = preset
143
+ cfg["json_config"] = jc
144
+ cfg["preset"] = preset
145
+
109
146
  if json_mode:
110
147
  click.echo(
111
148
  to_json(
@@ -38,9 +38,14 @@ from roam.retrieve.pipeline import run_retrieve
38
38
  )
39
39
  @click.option(
40
40
  "--rerank",
41
- type=click.Choice(["fast", "off"], case_sensitive=False),
41
+ type=click.Choice(["fast", "off", "learned"], case_sensitive=False),
42
42
  default=None,
43
- help="'fast' = structural rerank with personalized PageRank (default). 'off' = lexical only.",
43
+ help=(
44
+ "'fast' = structural rerank (default). 'off' = lexical only. "
45
+ "'learned' = LightGBM LambdaMART trained on your bench (requires "
46
+ '``pip install "roam-code[learned]"`` + a trained model at '
47
+ "``$ROAM_LEARNED_MODEL``; falls back to 'fast' when unavailable)."
48
+ ),
44
49
  )
45
50
  @click.option(
46
51
  "--seed-files",
@@ -270,7 +270,7 @@ def _generate_cyclonedx(
270
270
 
271
271
  bom: dict = {
272
272
  "bomFormat": "CycloneDX",
273
- "specVersion": "1.5",
273
+ "specVersion": "1.7",
274
274
  "version": 1,
275
275
  "serialNumber": f"urn:uuid:{serial}",
276
276
  "metadata": metadata,
@@ -413,8 +413,19 @@ def _generate_spdx(
413
413
  default=False,
414
414
  help="Skip call-graph reachability analysis (faster)",
415
415
  )
416
+ @click.option(
417
+ "--aibom",
418
+ is_flag=True,
419
+ default=False,
420
+ help=(
421
+ "Embed the AIBOM extension (CycloneDX only) — bind AI-authored "
422
+ "commits (mined via committer email + Co-Authored-By trailers + "
423
+ "AI-keyword scan) to the indexed symbols they touched. Required "
424
+ "for EU AI Act Art. 50 disclosure (effective 2026-08-02)."
425
+ ),
426
+ )
416
427
  @click.pass_context
417
- def sbom(ctx, fmt, output_path, no_reachability):
428
+ def sbom(ctx, fmt, output_path, no_reachability, aibom):
418
429
  """Generate a Software Bill of Materials (SBOM) enriched with call-graph reachability.
419
430
 
420
431
  Produces CycloneDX 1.5 or SPDX 2.3 JSON output. Each dependency is
@@ -468,6 +479,19 @@ def sbom(ctx, fmt, output_path, no_reachability):
468
479
  else:
469
480
  sbom_data = _generate_cyclonedx(project_name, deps, reachability)
470
481
 
482
+ # AIBOM extension (CycloneDX 1.7 only) — bind AI-authored commits to
483
+ # indexed symbols. Required for EU AI Act Art. 50 disclosure.
484
+ if aibom and fmt.lower() == "cyclonedx":
485
+ try:
486
+ from roam.security.aibom_extension import build_aibom_block
487
+
488
+ ensure_index()
489
+ with open_db(readonly=True) as conn:
490
+ aibom_block = build_aibom_block(project_root, conn)
491
+ sbom_data["aibom"] = aibom_block
492
+ except Exception as exc:
493
+ sbom_data["aibom"] = {"error": str(exc), "version": "0.1"}
494
+
471
495
  # Build summary for verdict / JSON envelope
472
496
  total_deps = len(deps)
473
497
  reachable_count = 0
@@ -1360,7 +1360,7 @@ MAP_METADATA: dict[str, dict[str, object]] = {
1360
1360
  "relationship": "self",
1361
1361
  "peer": True,
1362
1362
  "graph": "PageRank + Tarjan + Louvain + layers",
1363
- "note": "Graph algorithms (PageRank, SCC, Louvain, Fiedler) on tree-sitter ASTs fused with git history in SQLite. 112 MCP tools, 150 CLI commands.",
1363
+ "note": "Graph algorithms (PageRank, SCC, Louvain, Fiedler) on tree-sitter ASTs fused with git history in SQLite. 116 MCP tools, 150 CLI commands.",
1364
1364
  "version_evaluated": "11.1.2",
1365
1365
  "repo_url": "https://github.com/Cranot/roam-code",
1366
1366
  },
@@ -46,6 +46,10 @@ DEFAULT_RETRIEVE_WEIGHTS: dict[str, float] = {
46
46
  "gamma": 0.15, # inverse layer distance
47
47
  "delta": 0.15, # runtime hotspot signal
48
48
  "epsilon": 0.05, # clone-canonical boost
49
+ "zeta": 0.20, # v12.2: semantic similarity (bge-small + sqlite-vec)
50
+ # Activates only when [semantic] extras are installed AND the
51
+ # symbol-embedding table is populated; otherwise contributes 0 and
52
+ # the original blend is preserved exactly.
49
53
  }
50
54
 
51
55
  DEFAULT_RETRIEVE: dict[str, Any] = {
@@ -1,4 +1,4 @@
1
- """Louvain community detection for the symbol graph."""
1
+ """Community detection for the symbol graph (Leiden → Louvain → greedy)."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -11,10 +11,56 @@ import networkx as nx
11
11
  from roam.db.connection import batched_in
12
12
 
13
13
 
14
+ def _try_leiden_communities(undirected: nx.Graph, out: list[set[int]]) -> bool:
15
+ """Run Leiden via the optional ``leidenalg`` + ``igraph`` extras.
16
+
17
+ Returns ``True`` and fills *out* in place when Leiden succeeded, else
18
+ ``False`` so the caller can fall back to NetworkX Louvain. Leiden is
19
+ strictly better than Louvain per Traag et al. 2019 (Nature Sci. Rep.) —
20
+ no badly-connected communities, fully deterministic with the seed.
21
+
22
+ Install with::
23
+
24
+ pip install "roam-code[leiden]"
25
+
26
+ Skip with ``ROAM_LEIDEN=0`` (e.g., for offline / ABI-pin environments
27
+ where the C extension fails to load).
28
+ """
29
+ if os.environ.get("ROAM_LEIDEN", "1") == "0":
30
+ return False
31
+ try:
32
+ import igraph as ig # type: ignore
33
+ import leidenalg # type: ignore
34
+ except ImportError:
35
+ return False
36
+ try:
37
+ # Build an igraph from the undirected NetworkX graph. Map node ids
38
+ # so we can translate back. Edge weights default to 1.0.
39
+ node_list = list(undirected.nodes())
40
+ index_for: dict[int, int] = {n: i for i, n in enumerate(node_list)}
41
+ edges = [(index_for[u], index_for[v]) for u, v in undirected.edges() if u in index_for and v in index_for]
42
+ ig_graph = ig.Graph(n=len(node_list), edges=edges, directed=False)
43
+ partition = leidenalg.find_partition(
44
+ ig_graph,
45
+ leidenalg.ModularityVertexPartition,
46
+ seed=42,
47
+ )
48
+ for community in partition:
49
+ out.append({node_list[idx] for idx in community})
50
+ return True
51
+ except Exception:
52
+ # leidenalg / igraph ABI mismatch or memory pressure — fall back.
53
+ out.clear()
54
+ return False
55
+
56
+
14
57
  def detect_clusters(G: nx.DiGraph) -> dict[int, int]:
15
- """Detect communities using Louvain on the undirected projection of *G*.
58
+ """Detect communities on the undirected projection of *G*.
59
+
60
+ Algorithm preference: **Leiden** (via optional ``[leiden]`` extra) →
61
+ seeded **Louvain** (NetworkX) → **greedy modularity** (NetworkX
62
+ classic, last-resort).
16
63
 
17
- Falls back to greedy modularity if Louvain is unavailable.
18
64
  Returns ``{node_id: cluster_id}``.
19
65
  """
20
66
  if len(G) == 0:
@@ -24,12 +70,21 @@ def detect_clusters(G: nx.DiGraph) -> dict[int, int]:
24
70
 
25
71
  # Remove isolates -- community detection on disconnected singletons just
26
72
  # assigns each its own community, which is not useful.
73
+ #
74
+ # Algorithm preference (best → fallback):
75
+ # 1. Leiden (via leidenalg + igraph extras): no badly-connected
76
+ # communities, fully deterministic with seed. Strictly better than
77
+ # Louvain per Traag et al. 2019 (Nature Sci. Rep.).
78
+ # 2. Louvain (NetworkX) with seed=42: deterministic but may produce
79
+ # badly-connected communities — the Leiden paper's main critique.
80
+ # 3. Greedy modularity: very old NetworkX fallback for offline envs.
27
81
  communities: list[set[int]] = []
28
- try:
29
- communities = list(nx.community.louvain_communities(undirected, seed=42))
30
- except (AttributeError, TypeError):
31
- # NetworkX < 3.1 or missing optional dependency
32
- communities = list(nx.community.greedy_modularity_communities(undirected))
82
+ if not _try_leiden_communities(undirected, communities):
83
+ try:
84
+ communities = list(nx.community.louvain_communities(undirected, seed=42))
85
+ except (AttributeError, TypeError):
86
+ # NetworkX < 3.1 or missing optional dependency
87
+ communities = list(nx.community.greedy_modularity_communities(undirected))
33
88
 
34
89
  mapping: dict[int, int] = {}
35
90
  for cluster_id, members in enumerate(communities):