1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! `function-hotspots` analysis — repo-wide function-level hotspot ranking.
//!
//! Every hotspot-family analysis (`hotspots`, `hotspot-velocity`, `code-health`)
//! is FILE-granularity: a 2000-line file with one genuinely hot function looks
//! identical to one with uniform low-grade churn. This analysis ranks
//! HEAD-live **functions** by the same `revs × cognitive`-style score
//! `hotspots` uses, so the two are comparable in spirit.
//!
//! **Data sources — no tree-sitter reparse.** HEAD function/method spans come
//! from `entities` (`path, name, kind, start_line, end_line`), already
//! populated at ingest; `entities.rev_introduced`/`rev_last_seen` are
//! **degenerate** (always `head_rev` — a single HEAD-only appender), so this
//! answers "hot now," not "was hot historically." Per-function complexity at
//! HEAD comes from `complexity_metrics`. Revision history comes from `hunks`
//! (`rev, path, new_start, new_lines`).
//!
//! **Hunk↔span overlap.** The overlap predicate is the exact one
//! [`crate::analyses::function_xray`] already applies for a single
//! `--target` file, transliterated to SQL so it can run as a repo-wide join
//! instead of a per-target Rust loop: a hunk touching
//! `[new_start, new_start + new_lines)` overlaps a function span
//! `[start_line, end_line]` when `new_start <= end_line AND new_start +
//! new_lines > start_line`; a pure deletion (`new_lines = 0`) attributes to
//! the span whose range contains the anchor line
//! (`start_line <= new_start <= end_line`). See
//! `function_xray::hunk_overlaps` for the canonical Rust form and its unit
//! tests covering every edge case.
//!
//! **Rename limitation (same as `function-xray`).** The join is
//! `hunks.path = entities.path` — the current HEAD-relative path. `hunks` is
//! keyed on the literal path recorded at commit time, and rewriting it
//! through the rename-aware lineage CTE
//! ([`crate::analyses::lineage::rewrite`]) would only affect `changes`/
//! `changes_lineage` references, not `hunks` itself, so opting in would do
//! nothing here — a file's pre-rename history is not attributed, matching
//! `function-xray`'s documented caveat exactly.
//!
//! **Approximate attribution.** Like `function-xray`, hunk line numbers are
//! historical (recorded at commit time) while the span is the function's
//! CURRENT (HEAD) location — line numbers drift as intervening commits
//! insert/remove lines above a function, so attribution is an approximation,
//! not an exact replay. This mirrors `function-xray`'s documented limitation.
//!
//! Score formula — identical shape to [`crate::analyses::hotspots`]:
//!
//! ```text
//! function_hotspot_score(f) = percentile_rank(revs)
//! × percentile_rank(cognitive)
//! × (100 − cognitive_health) / 4
//! ```
//!
//! where `cognitive_health = 100 × (1 − 0.40 × normalize(cognitive))`,
//! `normalize` divides by the max cognitive complexity across all HEAD-live
//! functions. Output range `[0, 10]`, same scale as `hotspots.hotspot_score`
//! — see `hotspots.rs`'s module doc for the full derivation of the `/ 4`
//! divisor and the `[60, 100]` range of `cognitive_health`.
//!
//! Research basis: Gall et al. ICSM 2003 (`HistoryFinder`, function-level
//! churn) + Tornhill 2018 (Software Design X-Rays, the file-level hotspot
//! score this mirrors).
use params;
use crateFactsDb;
use crate::;
/// One HEAD-live function/method, ranked by the `hotspots`-style score.
// Mirrors `hotspots.rs::SQL`'s CTE shape, at function granularity. See the
// module doc for the hunk-overlap predicate (transliterated from
// `function_xray::hunk_overlaps`) and the score formula.
const SQL: &str = "
WITH fn_entities AS (
SELECT path, name AS function, start_line, end_line
FROM entities
WHERE kind IN ('function', 'method')
),
fn_hunk_hits AS (
SELECT e.path, e.function, h.rev
FROM fn_entities e
JOIN hunks h ON h.path = e.path
AND (
(h.new_lines = 0 AND h.new_start >= e.start_line AND h.new_start <= e.end_line)
OR (h.new_lines > 0 AND h.new_start <= e.end_line AND h.new_start + h.new_lines > e.start_line)
)
),
fn_revs AS (
SELECT path, function, COUNT(DISTINCT rev) AS revs
FROM fn_hunk_hits
GROUP BY path, function
HAVING COUNT(DISTINCT rev) >= ?
),
fn_complexity AS (
SELECT cm.path, cm.name AS function, cm.cognitive
FROM complexity_metrics cm
JOIN entities e ON e.path = cm.path AND e.name = cm.name
WHERE e.kind IN ('function', 'method')
),
joined AS (
SELECT
fr.path,
fr.function,
fr.revs,
COALESCE(fc.cognitive, 0) AS cognitive
FROM fn_revs fr
LEFT JOIN fn_complexity fc ON fc.path = fr.path AND fc.function = fr.function
),
ranked AS (
SELECT
path,
function,
revs,
cognitive,
PERCENT_RANK() OVER (ORDER BY revs) AS pr_rev,
PERCENT_RANK() OVER (ORDER BY cognitive) AS pr_cx,
CASE
WHEN MAX(cognitive) OVER () > 0
THEN cognitive / MAX(cognitive) OVER ()
ELSE 0
END AS norm_cx
FROM joined
)
SELECT
path,
function,
revs,
cognitive,
GREATEST(0.0, LEAST(100.0, 100.0 * (1.0 - 0.40 * norm_cx))) AS cognitive_health,
pr_rev * pr_cx * (100.0 - GREATEST(0.0, LEAST(100.0, 100.0 * (1.0 - 0.40 * norm_cx)))) / 4.0 AS function_hotspot_score
FROM ranked
ORDER BY function_hotspot_score DESC, path ASC, function ASC
LIMIT ?
";