roam-code 8.0.1__tar.gz → 8.1.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 (158) hide show
  1. {roam_code-8.0.1 → roam_code-8.1.0}/PKG-INFO +1 -1
  2. {roam_code-8.0.1 → roam_code-8.1.0}/pyproject.toml +4 -1
  3. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_complexity.py +20 -9
  4. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_dead.py +28 -0
  5. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_health.py +10 -2
  6. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/python_lang.py +72 -0
  7. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam_code.egg-info/PKG-INFO +1 -1
  8. {roam_code-8.0.1 → roam_code-8.1.0}/LICENSE +0 -0
  9. {roam_code-8.0.1 → roam_code-8.1.0}/README.md +0 -0
  10. {roam_code-8.0.1 → roam_code-8.1.0}/setup.cfg +0 -0
  11. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/__init__.py +0 -0
  12. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/__main__.py +0 -0
  13. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/bridges/__init__.py +0 -0
  14. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/bridges/base.py +0 -0
  15. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/bridges/bridge_protobuf.py +0 -0
  16. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/bridges/bridge_salesforce.py +0 -0
  17. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/bridges/registry.py +0 -0
  18. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/cli.py +0 -0
  19. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/__init__.py +0 -0
  20. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/changed_files.py +0 -0
  21. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_affected_tests.py +0 -0
  22. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_alerts.py +0 -0
  23. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_breaking.py +0 -0
  24. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_bus_factor.py +0 -0
  25. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_clusters.py +0 -0
  26. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_context.py +0 -0
  27. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_conventions.py +0 -0
  28. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_coupling.py +0 -0
  29. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_coverage_gaps.py +0 -0
  30. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_debt.py +0 -0
  31. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_deps.py +0 -0
  32. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_describe.py +0 -0
  33. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_diagnose.py +0 -0
  34. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_diff.py +0 -0
  35. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_digest.py +0 -0
  36. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_doc_staleness.py +0 -0
  37. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_entry_points.py +0 -0
  38. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_fan.py +0 -0
  39. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_file.py +0 -0
  40. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_fitness.py +0 -0
  41. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_fn_coupling.py +0 -0
  42. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_grep.py +0 -0
  43. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_impact.py +0 -0
  44. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_index.py +0 -0
  45. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_init.py +0 -0
  46. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_layers.py +0 -0
  47. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_map.py +0 -0
  48. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_module.py +0 -0
  49. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_owner.py +0 -0
  50. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_patterns.py +0 -0
  51. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_pr_risk.py +0 -0
  52. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_preflight.py +0 -0
  53. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_report.py +0 -0
  54. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_risk.py +0 -0
  55. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_safe_delete.py +0 -0
  56. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_safe_zones.py +0 -0
  57. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_search.py +0 -0
  58. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_sketch.py +0 -0
  59. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_snapshot.py +0 -0
  60. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_split.py +0 -0
  61. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_symbol.py +0 -0
  62. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_testmap.py +0 -0
  63. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_tour.py +0 -0
  64. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_trace.py +0 -0
  65. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_trend.py +0 -0
  66. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_understand.py +0 -0
  67. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_uses.py +0 -0
  68. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_visualize.py +0 -0
  69. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_weather.py +0 -0
  70. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_why.py +0 -0
  71. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_ws.py +0 -0
  72. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/cmd_xlang.py +0 -0
  73. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/context_helpers.py +0 -0
  74. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/gate_presets.py +0 -0
  75. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/graph_helpers.py +0 -0
  76. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/metrics_history.py +0 -0
  77. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/commands/resolve.py +0 -0
  78. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/db/__init__.py +0 -0
  79. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/db/connection.py +0 -0
  80. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/db/queries.py +0 -0
  81. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/db/schema.py +0 -0
  82. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/graph/__init__.py +0 -0
  83. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/graph/anomaly.py +0 -0
  84. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/graph/builder.py +0 -0
  85. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/graph/clusters.py +0 -0
  86. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/graph/cycles.py +0 -0
  87. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/graph/layers.py +0 -0
  88. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/graph/pagerank.py +0 -0
  89. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/graph/pathfinding.py +0 -0
  90. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/__init__.py +0 -0
  91. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/complexity.py +0 -0
  92. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/discovery.py +0 -0
  93. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/file_roles.py +0 -0
  94. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/git_stats.py +0 -0
  95. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/incremental.py +0 -0
  96. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/indexer.py +0 -0
  97. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/parser.py +0 -0
  98. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/relations.py +0 -0
  99. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/symbols.py +0 -0
  100. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/index/test_conventions.py +0 -0
  101. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/__init__.py +0 -0
  102. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/apex_lang.py +0 -0
  103. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/aura_lang.py +0 -0
  104. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/base.py +0 -0
  105. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/c_lang.py +0 -0
  106. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/foxpro_lang.py +0 -0
  107. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/generic_lang.py +0 -0
  108. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/go_lang.py +0 -0
  109. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/java_lang.py +0 -0
  110. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/javascript_lang.py +0 -0
  111. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/php_lang.py +0 -0
  112. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/registry.py +0 -0
  113. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/rust_lang.py +0 -0
  114. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/sfxml_lang.py +0 -0
  115. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/typescript_lang.py +0 -0
  116. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/languages/visualforce_lang.py +0 -0
  117. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/mcp_server.py +0 -0
  118. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/output/__init__.py +0 -0
  119. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/output/formatter.py +0 -0
  120. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/output/sarif.py +0 -0
  121. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/workspace/__init__.py +0 -0
  122. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/workspace/aggregator.py +0 -0
  123. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/workspace/api_scanner.py +0 -0
  124. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/workspace/config.py +0 -0
  125. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam/workspace/db.py +0 -0
  126. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam_code.egg-info/SOURCES.txt +0 -0
  127. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam_code.egg-info/dependency_links.txt +0 -0
  128. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam_code.egg-info/entry_points.txt +0 -0
  129. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam_code.egg-info/requires.txt +0 -0
  130. {roam_code-8.0.1 → roam_code-8.1.0}/src/roam_code.egg-info/top_level.txt +0 -0
  131. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_anomaly.py +0 -0
  132. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_basic.py +0 -0
  133. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_bridges.py +0 -0
  134. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_commands_architecture.py +0 -0
  135. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_commands_exploration.py +0 -0
  136. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_commands_health.py +0 -0
  137. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_commands_refactoring.py +0 -0
  138. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_commands_workflow.py +0 -0
  139. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_comprehensive.py +0 -0
  140. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_dead_aging.py +0 -0
  141. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_file_roles.py +0 -0
  142. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_fixes.py +0 -0
  143. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_formatters.py +0 -0
  144. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_foxpro.py +0 -0
  145. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_gate_presets.py +0 -0
  146. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_json_contracts.py +0 -0
  147. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_languages.py +0 -0
  148. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_performance.py +0 -0
  149. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_pr_risk_author.py +0 -0
  150. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_resolve.py +0 -0
  151. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_salesforce.py +0 -0
  152. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_smoke.py +0 -0
  153. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_test_conventions.py +0 -0
  154. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_v6_features.py +0 -0
  155. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_v71_features.py +0 -0
  156. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_v7_features.py +0 -0
  157. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_visualize.py +0 -0
  158. {roam_code-8.0.1 → roam_code-8.1.0}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roam-code
3
- Version: 8.0.1
3
+ Version: 8.1.0
4
4
  Summary: Instant codebase comprehension for AI coding agents
5
5
  Author: CosmoHac
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "roam-code"
7
- version = "8.0.1"
7
+ version = "8.1.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,9 @@ where = ["src"]
55
55
  [tool.pytest.ini_options]
56
56
  testpaths = ["tests"]
57
57
  addopts = "-q --tb=short"
58
+ markers = [
59
+ "slow: marks tests as slow (deselect with '-m \"not slow\"')",
60
+ ]
58
61
 
59
62
  [tool.ruff]
60
63
  target-version = "py39"
@@ -5,6 +5,8 @@ functions/methods by cognitive complexity to identify the hardest-to-
5
5
  understand code in the project.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  import click
9
11
 
10
12
  from roam.db.connection import open_db
@@ -12,6 +14,15 @@ from roam.commands.resolve import ensure_index
12
14
  from roam.output.formatter import loc, abbrev_kind, to_json, json_envelope
13
15
 
14
16
 
17
+ def _safe_metric(row, key, default=0.0):
18
+ """Safely access a metric column that may not exist in older DBs."""
19
+ try:
20
+ v = row[key]
21
+ return v if v is not None else default
22
+ except (KeyError, IndexError):
23
+ return default
24
+
25
+
15
26
  def _severity(score: float) -> str:
16
27
  """Map cognitive complexity score to a severity label."""
17
28
  if score >= 25:
@@ -144,11 +155,11 @@ def complexity(ctx, target, limit, threshold, by_file, bumpy_road):
144
155
  "return_count": r["return_count"],
145
156
  "bool_op_count": r["bool_op_count"],
146
157
  "callback_depth": r["callback_depth"],
147
- "cyclomatic_density": r["cyclomatic_density"] or 0,
148
- "halstead_volume": r["halstead_volume"] or 0,
149
- "halstead_difficulty": r["halstead_difficulty"] or 0,
150
- "halstead_effort": r["halstead_effort"] or 0,
151
- "halstead_bugs": r["halstead_bugs"] or 0,
158
+ "cyclomatic_density": _safe_metric(r, "cyclomatic_density"),
159
+ "halstead_volume": _safe_metric(r, "halstead_volume"),
160
+ "halstead_difficulty": _safe_metric(r, "halstead_difficulty"),
161
+ "halstead_effort": _safe_metric(r, "halstead_effort"),
162
+ "halstead_bugs": _safe_metric(r, "halstead_bugs"),
152
163
  "severity": _severity(r["cognitive_complexity"]),
153
164
  }
154
165
  for r in rows
@@ -181,11 +192,11 @@ def complexity(ctx, target, limit, threshold, by_file, bumpy_road):
181
192
  factors.append(f"params={r['param_count']}")
182
193
  if r["return_count"] >= 4:
183
194
  factors.append(f"ret={r['return_count']}")
184
- cd = r["cyclomatic_density"]
185
- if cd and cd > 0.15:
195
+ cd = _safe_metric(r, "cyclomatic_density")
196
+ if cd > 0.15:
186
197
  factors.append(f"density={cd:.2f}")
187
- hv = r["halstead_volume"]
188
- if hv and hv > 500:
198
+ hv = _safe_metric(r, "halstead_volume")
199
+ if hv > 500:
189
200
  factors.append(f"H.vol={hv:.0f}")
190
201
 
191
202
  factor_str = f" ({', '.join(factors)})" if factors else ""
@@ -40,6 +40,20 @@ _API_PREFIXES = ("get", "use", "create", "validate", "fetch", "update",
40
40
  "delete", "find", "check", "make", "build", "parse")
41
41
 
42
42
 
43
+ _ABC_METHOD_NAMES = frozenset({
44
+ "language_name", "file_extensions", "extract_symbols", "extract_references",
45
+ "get_docstring", "get_signature", "node_text",
46
+ "detect", "supported_bridges",
47
+ "resolve_cross_language", "get_bridge_edges",
48
+ })
49
+
50
+
51
+ def _is_test_path(file_path):
52
+ """Check if a file is a test file (discovered by pytest, not imported)."""
53
+ base = os.path.basename(file_path).lower()
54
+ return base.startswith("test_") or base.endswith("_test.py")
55
+
56
+
43
57
  def _dead_action(r, file_imported):
44
58
  """Compute actionable verdict and confidence % for a dead symbol.
45
59
 
@@ -62,6 +76,18 @@ def _dead_action(r, file_imported):
62
76
  except (KeyError, IndexError):
63
77
  kind = ""
64
78
 
79
+ # Test file symbols — discovered by pytest, never imported directly
80
+ if _is_test_path(r["file_path"]):
81
+ return "INTENTIONAL", 10
82
+
83
+ # CLI command functions — loaded dynamically via LazyGroup/importlib
84
+ if base.startswith("cmd_") and kind == "function":
85
+ return "INTENTIONAL", 20
86
+
87
+ # ABC method overrides — called polymorphically, not by direct import
88
+ if kind == "method" and name in _ABC_METHOD_NAMES:
89
+ return "INTENTIONAL", 10
90
+
65
91
  # Entry point / lifecycle hooks (check original case for camelCase hooks)
66
92
  if name in _ENTRY_NAMES or name_lower in _ENTRY_NAMES:
67
93
  return "INTENTIONAL", 60
@@ -251,6 +277,8 @@ def _analyze_dead(conn):
251
277
  Returns (high, low, imported_files) where high/low are lists of Row objects.
252
278
  """
253
279
  rows = conn.execute(UNREFERENCED_EXPORTS).fetchall()
280
+ # Exclude test files — their symbols are discovered by pytest, not imported
281
+ rows = [r for r in rows if not _is_test_path(r["file_path"])]
254
282
  if not rows:
255
283
  return [], [], set()
256
284
 
@@ -52,13 +52,21 @@ _FRAMEWORK_NAMES = frozenset({
52
52
  _UTILITY_PATH_PATTERNS = (
53
53
  "composables/", "utils/", "services/", "lib/", "helpers/",
54
54
  "shared/", "config/", "core/", "hooks/", "stores/",
55
+ "output/", "db/", "common/", "internal/", "infra/",
56
+ )
57
+
58
+ _UTILITY_FILE_PATTERNS = (
59
+ "resolve.py", "helpers.py", "common.py", "base.py",
55
60
  )
56
61
 
57
62
 
58
63
  def _is_utility_path(file_path):
59
- """Check if a file is in a utility/infrastructure directory."""
64
+ """Check if a file is in a utility/infrastructure directory or is a known utility file."""
60
65
  p = file_path.replace("\\", "/").lower()
61
- return any(pat in p for pat in _UTILITY_PATH_PATTERNS)
66
+ if any(pat in p for pat in _UTILITY_PATH_PATTERNS):
67
+ return True
68
+ basename = p.rsplit("/", 1)[-1] if "/" in p else p
69
+ return basename in _UTILITY_FILE_PATTERNS
62
70
 
63
71
 
64
72
  def _percentile(sorted_values, pct):
@@ -333,6 +333,9 @@ class PythonExtractor(LanguageExtractor):
333
333
  self._extract_from_import(child, source, refs, scope_name)
334
334
  elif child.type == "call":
335
335
  self._extract_call(child, source, refs, scope_name)
336
+ elif child.type == "decorated_definition":
337
+ self._extract_decorator_refs(child, source, refs, scope_name)
338
+ self._walk_refs(child, source, file_path, refs, scope_name)
336
339
  else:
337
340
  # Recurse, updating scope for classes/functions
338
341
  new_scope = scope_name
@@ -341,8 +344,77 @@ class PythonExtractor(LanguageExtractor):
341
344
  if n:
342
345
  fname = self.node_text(n, source)
343
346
  new_scope = f"{scope_name}.{fname}" if scope_name else fname
347
+ # Extract type annotation refs from function parameters and return
348
+ if child.type == "function_definition":
349
+ self._extract_type_refs(child, source, refs, new_scope)
344
350
  self._walk_refs(child, source, file_path, refs, new_scope)
345
351
 
352
+ def _extract_decorator_refs(self, decorated_node, source, refs, scope_name):
353
+ """Extract references from decorators (e.g., @cache, @app.route)."""
354
+ for child in decorated_node.children:
355
+ if child.type == "decorator":
356
+ # The decorator content is after the '@'
357
+ for sub in child.children:
358
+ if sub.type == "identifier":
359
+ name = self.node_text(sub, source)
360
+ refs.append(self._make_reference(
361
+ target_name=name,
362
+ kind="call",
363
+ line=sub.start_point[0] + 1,
364
+ source_name=scope_name,
365
+ ))
366
+ elif sub.type == "attribute":
367
+ name = self.node_text(sub, source)
368
+ refs.append(self._make_reference(
369
+ target_name=name,
370
+ kind="call",
371
+ line=sub.start_point[0] + 1,
372
+ source_name=scope_name,
373
+ ))
374
+ elif sub.type == "call":
375
+ self._extract_call(sub, source, refs, scope_name)
376
+
377
+ def _extract_type_refs(self, func_node, source, refs, scope_name):
378
+ """Extract references from type annotations in function signatures."""
379
+ # Parameter type annotations
380
+ params = func_node.child_by_field_name("parameters")
381
+ if params:
382
+ for param in params.children:
383
+ type_node = param.child_by_field_name("type")
384
+ if type_node:
385
+ self._walk_type_node(type_node, source, refs, scope_name)
386
+
387
+ # Return type annotation
388
+ ret = func_node.child_by_field_name("return_type")
389
+ if ret:
390
+ self._walk_type_node(ret, source, refs, scope_name)
391
+
392
+ def _walk_type_node(self, node, source, refs, scope_name):
393
+ """Walk a type annotation node and extract type references."""
394
+ if node.type == "identifier":
395
+ name = self.node_text(node, source)
396
+ # Skip builtins that don't create real references
397
+ if name not in ("int", "str", "float", "bool", "bytes", "None",
398
+ "list", "dict", "set", "tuple", "type", "object"):
399
+ refs.append(self._make_reference(
400
+ target_name=name,
401
+ kind="type_ref",
402
+ line=node.start_point[0] + 1,
403
+ source_name=scope_name,
404
+ ))
405
+ elif node.type == "attribute":
406
+ name = self.node_text(node, source)
407
+ refs.append(self._make_reference(
408
+ target_name=name,
409
+ kind="type_ref",
410
+ line=node.start_point[0] + 1,
411
+ source_name=scope_name,
412
+ ))
413
+ else:
414
+ # Recurse into generic types like List[Item], Optional[str], etc.
415
+ for child in node.children:
416
+ self._walk_type_node(child, source, refs, scope_name)
417
+
346
418
  def _extract_import(self, node, source, refs, scope_name):
347
419
  # import x, import x.y, import x as y
348
420
  for child in node.children:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roam-code
3
- Version: 8.0.1
3
+ Version: 8.1.0
4
4
  Summary: Instant codebase comprehension for AI coding agents
5
5
  Author: CosmoHac
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes