Skip to content

Commit f1101fb

Browse files
CosmoHacclaude
andcommitted
refactor: split collect_symbol_metrics (cc 99 -> ~10)
Seven try/except blocks each pulled different metric tables into the same result dict. Extracted six per-table helpers and a shared _swallow() for the exception logger: - _swallow (centralised observability call) - _populate_symbol_metrics (cognitive_complexity / loc / coverage) - _populate_graph_metrics (pagerank / fan / betweenness / SNA-v2) - _populate_edge_fanout_fallback (raw-edge fallback when graph_metrics empty) - _populate_file_level_metrics (churn / test_files / co_change) - _populate_dead_code_risk (zero fan-in + non-exported gate) The orchestrator collapses to dispatch-then-comprehension. Behavior preserved (58 metrics tests still green). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bc6d3ac commit f1101fb

1 file changed

Lines changed: 120 additions & 126 deletions

File tree

src/roam/commands/cmd_metrics.py

Lines changed: 120 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -68,44 +68,15 @@ def _health_label(metrics: dict) -> str:
6868
# ---------------------------------------------------------------------------
6969

7070

71-
def collect_symbol_metrics(
72-
conn: sqlite3.Connection,
73-
symbol_id: int,
74-
*,
75-
include_comprehension: bool = True,
76-
) -> dict:
77-
"""Gather all available metrics for a single symbol.
71+
def _swallow(tag: str, exc: BaseException) -> None:
72+
"""Centralised swallowed-exception logger to keep the helpers tight."""
73+
from roam.observability import log_swallowed
74+
75+
log_swallowed(tag, exc)
7876

79-
Returns a flat dict with keys: complexity, fan_in, fan_out, pagerank,
80-
betweenness, churn, commits, test_files, layer_depth, dead_code_risk,
81-
loc, co_change_count.
82-
"""
83-
result: dict = {
84-
"complexity": 0,
85-
"fan_in": 0,
86-
"fan_out": 0,
87-
"pagerank": 0.0,
88-
"betweenness": 0.0,
89-
"closeness": 0.0,
90-
"eigenvector": 0.0,
91-
"clustering_coefficient": 0.0,
92-
"debt_score": 0.0,
93-
"churn": 0,
94-
"commits": 0,
95-
"test_files": 0,
96-
"layer_depth": None,
97-
"dead_code_risk": False,
98-
"loc": 0,
99-
"co_change_count": 0,
100-
"information_scatter": 0,
101-
"working_set_size": 0,
102-
"comprehension_difficulty": 0.0,
103-
"coverage_pct": None,
104-
"covered_lines": 0,
105-
"coverable_lines": 0,
106-
}
10777

108-
# -- symbol_metrics (cognitive complexity, line_count) --
78+
def _populate_symbol_metrics(conn, symbol_id: int, result: dict) -> None:
79+
"""Pull cognitive complexity + LOC + (optional) coverage columns."""
10980
try:
11081
sm = conn.execute(
11182
"SELECT cognitive_complexity, line_count FROM symbol_metrics WHERE symbol_id = ?",
@@ -114,11 +85,8 @@ def collect_symbol_metrics(
11485
if sm:
11586
result["complexity"] = sm["cognitive_complexity"] or 0
11687
result["loc"] = sm["line_count"] or 0
117-
except Exception as _exc: # noqa: BLE001 — defensive
118-
from roam.observability import log_swallowed
119-
120-
log_swallowed("cmd_metrics:metric_query", _exc)
121-
# Optional imported coverage columns (safe on older DB schemas)
88+
except Exception as exc: # noqa: BLE001 — defensive
89+
_swallow("cmd_metrics:metric_query", exc)
12290
try:
12391
cov = conn.execute(
12492
"SELECT coverage_pct, covered_lines, coverable_lines FROM symbol_metrics WHERE symbol_id = ?",
@@ -128,12 +96,12 @@ def collect_symbol_metrics(
12896
result["coverage_pct"] = cov["coverage_pct"]
12997
result["covered_lines"] = cov["covered_lines"] or 0
13098
result["coverable_lines"] = cov["coverable_lines"] or 0
131-
except Exception as _exc: # noqa: BLE001 — defensive
132-
from roam.observability import log_swallowed
99+
except Exception as exc: # noqa: BLE001 — defensive
100+
_swallow("cmd_metrics:metric_query", exc)
133101

134-
log_swallowed("cmd_metrics:metric_query", _exc)
135102

136-
# -- graph_metrics (pagerank, in_degree, out_degree, betweenness) --
103+
def _populate_graph_metrics(conn, symbol_id: int, result: dict) -> None:
104+
"""Pull pagerank/fan-in/fan-out/betweenness + (optional) SNA-v2 cols."""
137105
try:
138106
gm = conn.execute(
139107
"SELECT pagerank, in_degree, out_degree, betweenness FROM graph_metrics WHERE symbol_id = ?",
@@ -144,11 +112,8 @@ def collect_symbol_metrics(
144112
result["fan_in"] = gm["in_degree"] or 0
145113
result["fan_out"] = gm["out_degree"] or 0
146114
result["betweenness"] = gm["betweenness"] or 0.0
147-
except Exception as _exc: # noqa: BLE001 — defensive
148-
from roam.observability import log_swallowed
149-
150-
log_swallowed("cmd_metrics:metric_query", _exc)
151-
# Optional SNA v2 columns (safe on older DB schemas)
115+
except Exception as exc: # noqa: BLE001 — defensive
116+
_swallow("cmd_metrics:metric_query", exc)
152117
try:
153118
extra = conn.execute(
154119
"SELECT closeness, eigenvector, clustering_coefficient, debt_score FROM graph_metrics WHERE symbol_id = ?",
@@ -159,89 +124,118 @@ def collect_symbol_metrics(
159124
result["eigenvector"] = extra["eigenvector"] or 0.0
160125
result["clustering_coefficient"] = extra["clustering_coefficient"] or 0.0
161126
result["debt_score"] = extra["debt_score"] or 0.0
162-
except Exception as _exc: # noqa: BLE001 — defensive
163-
from roam.observability import log_swallowed
127+
except Exception as exc: # noqa: BLE001 — defensive
128+
_swallow("cmd_metrics:metric_query", exc)
129+
130+
131+
def _populate_edge_fanout_fallback(conn, symbol_id: int, result: dict) -> None:
132+
"""When graph_metrics returned 0/0, fall back to raw edge counts."""
133+
if result["fan_in"] != 0 or result["fan_out"] != 0:
134+
return
135+
try:
136+
fi = conn.execute("SELECT COUNT(*) FROM edges WHERE target_id = ?", (symbol_id,)).fetchone()
137+
fo = conn.execute("SELECT COUNT(*) FROM edges WHERE source_id = ?", (symbol_id,)).fetchone()
138+
result["fan_in"] = fi[0] if fi else 0
139+
result["fan_out"] = fo[0] if fo else 0
140+
except Exception as exc: # noqa: BLE001 — defensive
141+
_swallow("cmd_metrics:nested_query", exc)
164142

165-
log_swallowed("cmd_metrics:metric_query", _exc)
166143

167-
# -- edges (fallback fan-in / fan-out from raw edges) --
168-
if result["fan_in"] == 0 and result["fan_out"] == 0:
169-
try:
170-
fi = conn.execute(
171-
"SELECT COUNT(*) FROM edges WHERE target_id = ?",
172-
(symbol_id,),
173-
).fetchone()
174-
fo = conn.execute(
175-
"SELECT COUNT(*) FROM edges WHERE source_id = ?",
176-
(symbol_id,),
177-
).fetchone()
178-
result["fan_in"] = fi[0] if fi else 0
179-
result["fan_out"] = fo[0] if fo else 0
180-
except Exception as _exc: # noqa: BLE001 — defensive
181-
from roam.observability import log_swallowed
182-
183-
log_swallowed("cmd_metrics:nested_query", _exc)
184-
185-
# -- dead_code_risk: fan_in == 0 for non-entry-point symbols --
144+
def _populate_file_level_metrics(conn, file_id: int, result: dict) -> None:
145+
"""Pull churn/commits, test-file count, and co-change count for a file."""
146+
try:
147+
fs = conn.execute(
148+
"SELECT commit_count, total_churn FROM file_stats WHERE file_id = ?",
149+
(file_id,),
150+
).fetchone()
151+
if fs:
152+
result["commits"] = fs["commit_count"] or 0
153+
result["churn"] = fs["total_churn"] or 0
154+
except Exception as exc: # noqa: BLE001 — defensive
155+
_swallow("cmd_metrics:nested_query", exc)
156+
try:
157+
tf = conn.execute(
158+
"SELECT COUNT(DISTINCT fe.source_file_id) "
159+
"FROM file_edges fe "
160+
"JOIN files f ON fe.source_file_id = f.id "
161+
"WHERE fe.target_file_id = ? AND f.file_role = 'test'",
162+
(file_id,),
163+
).fetchone()
164+
result["test_files"] = tf[0] if tf else 0
165+
except Exception as exc: # noqa: BLE001 — defensive
166+
_swallow("cmd_metrics:nested_query", exc)
167+
try:
168+
cc_row = conn.execute(
169+
"SELECT SUM(cochange_count) AS total FROM git_cochange WHERE file_id_a = ? OR file_id_b = ?",
170+
(file_id, file_id),
171+
).fetchone()
172+
result["co_change_count"] = cc_row["total"] or 0 if cc_row else 0
173+
except Exception as exc: # noqa: BLE001 — defensive
174+
_swallow("cmd_metrics:nested_query", exc)
175+
176+
177+
def _populate_dead_code_risk(sym_row, result: dict) -> None:
178+
"""Mark symbols with zero fan-in as dead-code risks unless they're
179+
exported (entry points)."""
180+
if result["fan_in"] != 0:
181+
return
182+
kind = sym_row["kind"] or ""
183+
if kind in ("function", "method", "class") and not sym_row["is_exported"]:
184+
result["dead_code_risk"] = True
185+
186+
187+
def collect_symbol_metrics(
188+
conn: sqlite3.Connection,
189+
symbol_id: int,
190+
*,
191+
include_comprehension: bool = True,
192+
) -> dict:
193+
"""Gather all available metrics for a single symbol.
194+
195+
Returns a flat dict with keys: complexity, fan_in, fan_out, pagerank,
196+
betweenness, churn, commits, test_files, layer_depth, dead_code_risk,
197+
loc, co_change_count.
198+
"""
199+
result: dict = {
200+
"complexity": 0,
201+
"fan_in": 0,
202+
"fan_out": 0,
203+
"pagerank": 0.0,
204+
"betweenness": 0.0,
205+
"closeness": 0.0,
206+
"eigenvector": 0.0,
207+
"clustering_coefficient": 0.0,
208+
"debt_score": 0.0,
209+
"churn": 0,
210+
"commits": 0,
211+
"test_files": 0,
212+
"layer_depth": None,
213+
"dead_code_risk": False,
214+
"loc": 0,
215+
"co_change_count": 0,
216+
"information_scatter": 0,
217+
"working_set_size": 0,
218+
"comprehension_difficulty": 0.0,
219+
"coverage_pct": None,
220+
"covered_lines": 0,
221+
"coverable_lines": 0,
222+
}
223+
224+
_populate_symbol_metrics(conn, symbol_id, result)
225+
_populate_graph_metrics(conn, symbol_id, result)
226+
_populate_edge_fanout_fallback(conn, symbol_id, result)
227+
186228
sym_row = conn.execute(
187229
"SELECT kind, is_exported, file_id FROM symbols WHERE id = ?",
188230
(symbol_id,),
189231
).fetchone()
190232
if sym_row:
191-
kind = sym_row["kind"] or ""
192-
is_exported = sym_row["is_exported"]
193-
if result["fan_in"] == 0 and kind in ("function", "method", "class"):
194-
# Entry points (exported, main, __init__) are not dead code
195-
if not is_exported:
196-
result["dead_code_risk"] = True
197-
198-
# -- churn / commits from git_file_stats via file_id --
199-
file_id = sym_row["file_id"]
200-
try:
201-
fs = conn.execute(
202-
"SELECT commit_count, total_churn FROM file_stats WHERE file_id = ?",
203-
(file_id,),
204-
).fetchone()
205-
if fs:
206-
result["commits"] = fs["commit_count"] or 0
207-
result["churn"] = fs["total_churn"] or 0
208-
except Exception as _exc: # noqa: BLE001 — defensive
209-
from roam.observability import log_swallowed
210-
211-
log_swallowed("cmd_metrics:nested_query", _exc)
212-
213-
# -- test files: count files with file_role='test' that reference
214-
# the same file via file_edges --
215-
try:
216-
tf = conn.execute(
217-
"SELECT COUNT(DISTINCT fe.source_file_id) "
218-
"FROM file_edges fe "
219-
"JOIN files f ON fe.source_file_id = f.id "
220-
"WHERE fe.target_file_id = ? AND f.file_role = 'test'",
221-
(file_id,),
222-
).fetchone()
223-
result["test_files"] = tf[0] if tf else 0
224-
except Exception as _exc: # noqa: BLE001 — defensive
225-
from roam.observability import log_swallowed
226-
227-
log_swallowed("cmd_metrics:nested_query", _exc)
228-
229-
# -- co_change_count --
230-
try:
231-
cc_row = conn.execute(
232-
"SELECT SUM(cochange_count) AS total FROM git_cochange WHERE file_id_a = ? OR file_id_b = ?",
233-
(file_id, file_id),
234-
).fetchone()
235-
result["co_change_count"] = cc_row["total"] or 0 if cc_row else 0
236-
except Exception as _exc: # noqa: BLE001 — defensive
237-
from roam.observability import log_swallowed
238-
239-
log_swallowed("cmd_metrics:nested_query", _exc)
240-
241-
# Comprehension difficulty metrics (#71):
242-
# - information scatter: distinct files in 2-hop closure
243-
# - working set size: symbols in 2-hop closure
244-
# - composite score from fan-out, scatter, working set, complexity
233+
_populate_dead_code_risk(sym_row, result)
234+
_populate_file_level_metrics(conn, sym_row["file_id"], result)
235+
236+
# Comprehension difficulty metrics (#71): information scatter
237+
# (distinct files in 2-hop closure) + working set size + composite
238+
# score from fan-out, scatter, working set, complexity.
245239
if include_comprehension:
246240
scatter, working_set = _comprehension_neighborhood(conn, symbol_id)
247241
result["information_scatter"] = scatter

0 commit comments

Comments
 (0)