use std::collections::{HashMap, HashSet};
use crate::analyses::code_health::CodeHealthRow;
use crate::analyses::{effort_exposure, lineage, query};
use crate::facts::FactsDb;
use crate::repo::Repo;
use crate::{Options, Result};
#[derive(Debug, Clone, Default)]
pub struct NewCodeScope {
pub window_start_present: bool,
pub born: Vec<(String, f64)>,
pub touched: Vec<(String, f64)>,
}
pub fn run_new_code_scope<R: Repo>(
db: &FactsDb,
repo: &R,
opts: &Options,
window_days: u32,
health: &[CodeHealthRow],
) -> Result<NewCodeScope> {
let Some(window_start) = effort_exposure::window_start_rev(db, window_days)? else {
return Ok(NewCodeScope::default());
};
let scores: HashMap<&str, f64> = health.iter().map(|r| (r.path.as_str(), r.score)).collect();
lineage::materialize_if_needed(db, opts)?;
let src = lineage::source_table(opts);
let mut born = Vec::new();
let mut touched_paths: HashSet<String> = HashSet::new();
for (path, born_in_window, touched_in_window) in born_touched_flags(db, src, window_days)? {
let Some(&score) = scores.get(path.as_str()) else {
continue; };
if born_in_window {
born.push((path, score));
} else if touched_in_window {
touched_paths.insert(path);
}
}
let net = effort_exposure::window_net_movement(
db,
repo,
&touched_paths,
Some(window_start.as_str()),
)?;
let mut touched: Vec<(String, f64)> = net.into_iter().collect();
born.sort_by(|a, b| a.0.cmp(&b.0));
touched.sort_by(|a, b| a.0.cmp(&b.0));
Ok(NewCodeScope {
window_start_present: true,
born,
touched,
})
}
fn born_touched_flags(db: &FactsDb, src: &str, wd: u32) -> Result<Vec<(String, bool, bool)>> {
let now_anchor = crate::analyses::query::clamped_now_anchor("date");
let sql = format!(
"SELECT c.path,
(MIN(ci.date) >= (SELECT {now_anchor} FROM commits) - INTERVAL '{wd} days') AS born,
(MAX(ci.date) >= (SELECT {now_anchor} FROM commits) - INTERVAL '{wd} days') AS touched
FROM {src} c
JOIN commits ci ON ci.rev = c.rev
GROUP BY c.path"
);
query::query_map_collect(db, &sql, [], "new-code born/touched partition", |r| {
Ok((
r.get::<_, String>(0)?,
r.get::<_, bool>(1)?,
r.get::<_, bool>(2)?,
))
})
}
#[cfg(test)]
mod tests {
use super::born_touched_flags;
use crate::analyses::effort_exposure::window_start_rev;
use crate::facts::FactsDb;
use std::collections::HashMap;
fn seed_commit(db: &FactsDb, rev: &str, date: &str) {
db.conn()
.execute(
&format!(
"INSERT INTO commits (rev, author_email, author_name, committer_email, \
canonical_author, date, committer_date, message, is_merge, parent_count) \
VALUES ('{rev}', 'a@b.com', 'A', 'a@b.com', 'A', \
TIMESTAMP '{date}', TIMESTAMP '{date}', 'm', false, 1)"
),
[],
)
.expect("insert commit");
}
fn seed_change(db: &FactsDb, rev: &str, path: &str, kind: &str) {
db.conn()
.execute(
&format!(
"INSERT INTO changes (rev, path, change_type, loc_added, loc_deleted) \
VALUES ('{rev}', '{path}', '{kind}', 10, 0)"
),
[],
)
.expect("insert change");
}
#[test]
fn born_touched_partition_splits_by_first_and_last_touch() {
let db = FactsDb::new_in_memory().expect("db");
seed_commit(&db, "c1", "2026-01-01");
seed_commit(&db, "c2", "2026-02-01");
seed_commit(&db, "c3", "2026-05-01");
seed_commit(&db, "c4", "2026-06-01");
seed_change(&db, "c1", "legacy.rs", "added");
seed_change(&db, "c3", "legacy.rs", "modified");
seed_change(&db, "c4", "fresh.rs", "added");
seed_change(&db, "c1", "ancient.rs", "added");
seed_change(&db, "c2", "ancient.rs", "modified");
let flags: HashMap<String, (bool, bool)> = born_touched_flags(&db, "changes", 90)
.expect("partition")
.into_iter()
.map(|(p, born, touched)| (p, (born, touched)))
.collect();
assert_eq!(flags["legacy.rs"], (false, true), "touched but not born");
assert_eq!(flags["fresh.rs"], (true, true), "born ⊂ touched");
assert_eq!(flags["ancient.rs"], (false, false), "untouched legacy");
}
#[test]
fn shallow_history_has_no_window_start() {
let db = FactsDb::new_in_memory().expect("db");
seed_commit(&db, "c1", "2026-05-20");
seed_commit(&db, "c2", "2026-06-01");
assert!(
window_start_rev(&db, 90).expect("query").is_none(),
"history shallower than the window has no pre-window rev"
);
seed_commit(&db, "c0", "2026-01-01");
assert_eq!(
window_start_rev(&db, 90).expect("query").as_deref(),
Some("c0"),
"the newest pre-window commit anchors the window start"
);
}
fn ts_offset_days(days: i64) -> String {
let t = time::OffsetDateTime::now_utc() + time::Duration::days(days);
crate::facts::ingest::consumer::format_timestamp(t)
}
#[test]
fn born_touched_anchor_ignores_a_future_dated_commit_as_now() {
let db = FactsDb::new_in_memory().expect("db");
seed_commit(&db, "recent", &ts_offset_days(-10));
seed_commit(&db, "old", &ts_offset_days(-200));
seed_commit(&db, "future", "2099-01-01 00:00:00");
seed_change(&db, "recent", "recent.rs", "modified");
seed_change(&db, "old", "old.rs", "modified");
seed_change(&db, "future", "future.rs", "added");
let flags: HashMap<String, (bool, bool)> = born_touched_flags(&db, "changes", 90)
.expect("partition")
.into_iter()
.map(|(p, born, touched)| (p, (born, touched)))
.collect();
assert_eq!(
flags["recent.rs"],
(true, true),
"the recent file stays in-window despite the future-dated commit"
);
assert_eq!(
flags["old.rs"],
(false, false),
"200 days back is outside the 90-day window"
);
assert_eq!(
flags["future.rs"],
(true, true),
"the future row is still its own born/touched; the clamp guards the anchor, not membership"
);
}
#[test]
fn window_start_rev_anchors_on_the_wall_clock_not_a_future_commit() {
let db = FactsDb::new_in_memory().expect("db");
seed_commit(&db, "baseline", &ts_offset_days(-120));
seed_commit(&db, "inwindow", &ts_offset_days(-10));
seed_commit(&db, "future", "2099-01-01 00:00:00");
assert_eq!(
window_start_rev(&db, 90).expect("query").as_deref(),
Some("baseline"),
"a raw MAX(date) anchor would instead pick the 10-day-old commit"
);
}
}