use fallow_engine::module_graph::FocusFileFactsPaths;
pub use fallow_output::{ConfidenceFlag, FocusLabel, FocusMap, FocusScore, FocusUnit};
const REVIEW_HERE_THRESHOLD: u32 = 3;
const FAN_IN_WEIGHT: u32 = 2;
const FAN_OUT_WEIGHT: u32 = 1;
const FAN_CAP: u32 = 5;
const RISK_ZONE_WEIGHT: u32 = 2;
const CHANGE_SHAPE_WEIGHT: u32 = 2;
const SECURITY_TAINT_WEIGHT: u32 = 3;
const RUNTIME_HOT_FLOOR: u32 = 3;
const RUNTIME_HOT_WARM: u32 = 4;
const RUNTIME_HOT_BLAZING: u32 = 6;
const RUNTIME_WARM_INVOCATIONS: u64 = 100;
const RUNTIME_BLAZING_INVOCATIONS: u64 = 1_000;
fn runtime_weight(invocations: u64) -> u32 {
if invocations >= RUNTIME_BLAZING_INVOCATIONS {
RUNTIME_HOT_BLAZING
} else if invocations >= RUNTIME_WARM_INVOCATIONS {
RUNTIME_HOT_WARM
} else {
RUNTIME_HOT_FLOOR
}
}
#[derive(Debug, Clone)]
pub struct BoundaryZoneFile {
pub from_file: String,
}
#[derive(Debug, Clone)]
pub struct RuntimeHotFile {
pub file: String,
pub invocations: u64,
}
#[derive(Debug, Clone, Default)]
pub struct RuntimeFocus {
pub hot_files: Vec<RuntimeHotFile>,
pub cold_files: Vec<String>,
}
pub struct FocusInputs<'a> {
pub graph_facts: &'a [FocusFileFactsPaths],
pub boundary_files: &'a [BoundaryZoneFile],
pub public_api_added: &'a [String],
pub coordination_changed_files: &'a [String],
pub taint_touched_files: &'a [String],
pub runtime: Option<&'a RuntimeFocus>,
}
fn file_in_public_api(file: &str, public_api_added: &[String]) -> bool {
public_api_added
.iter()
.any(|key| key.split("::").next() == Some(file))
}
fn score_unit(facts: &FocusFileFactsPaths, inputs: &FocusInputs<'_>) -> FocusScore {
let fan_io =
facts.fan_in.min(FAN_CAP) * FAN_IN_WEIGHT + facts.fan_out.min(FAN_CAP) * FAN_OUT_WEIGHT;
let taint_touched = inputs.taint_touched_files.iter().any(|f| f == &facts.file);
let security_taint = if taint_touched {
SECURITY_TAINT_WEIGHT
} else {
0
};
let in_boundary = inputs
.boundary_files
.iter()
.any(|b| b.from_file == facts.file);
let in_public_api = file_in_public_api(&facts.file, inputs.public_api_added);
let zones = u32::from(in_boundary) + u32::from(in_public_api) + u32::from(taint_touched);
let risk_zone = zones * RISK_ZONE_WEIGHT;
let new_export = in_public_api;
let sig_change = inputs
.coordination_changed_files
.iter()
.any(|f| f == &facts.file);
let shapes = u32::from(new_export) + u32::from(sig_change);
let change_shape = shapes * CHANGE_SHAPE_WEIGHT;
let runtime = inputs.runtime.map_or(0, |rt| {
rt.hot_files
.iter()
.find(|hot| hot.file == facts.file)
.map_or(0, |hot| runtime_weight(hot.invocations))
});
let total = fan_io + security_taint + risk_zone + change_shape + runtime;
FocusScore {
fan_io,
security_taint,
risk_zone,
change_shape,
runtime,
total,
}
}
fn is_safe_skip(facts: &FocusFileFactsPaths, score: &FocusScore, inputs: &FocusInputs<'_>) -> bool {
inputs.runtime.is_some_and(|rt| {
score.total == 0
&& !facts.dynamic_dispatch
&& !facts.re_export_indirection
&& rt.cold_files.iter().any(|file| file == &facts.file)
})
}
fn build_reason(
facts: &FocusFileFactsPaths,
score: &FocusScore,
inputs: &FocusInputs<'_>,
) -> String {
let mut parts: Vec<String> = Vec::new();
if let Some(rt) = inputs.runtime {
if let Some(hot) = rt.hot_files.iter().find(|hot| hot.file == facts.file) {
parts.push(format!(
"hot path ({} invocation{})",
hot.invocations,
if hot.invocations == 1 { "" } else { "s" }
));
} else if rt.cold_files.iter().any(|file| file == &facts.file) {
parts.push("runtime-cold (no hot path)".to_string());
}
}
if facts.fan_in > 0 {
parts.push(format!(
"high fan-in ({} importer{})",
facts.fan_in,
if facts.fan_in == 1 { "" } else { "s" }
));
}
if facts.fan_out > 0 {
parts.push(format!("fan-out {}", facts.fan_out));
}
if score.security_taint > 0 {
parts.push("on a security taint path".to_string());
}
if inputs
.boundary_files
.iter()
.any(|b| b.from_file == facts.file)
{
parts.push("introduces a cross-zone edge".to_string());
}
if file_in_public_api(&facts.file, inputs.public_api_added) {
parts.push("widens the public API".to_string());
}
if inputs
.coordination_changed_files
.iter()
.any(|f| f == &facts.file)
{
parts.push("changes a contract consumed outside the diff".to_string());
}
if parts.is_empty() {
"isolated change, no blast beyond the diff".to_string()
} else {
parts.join(", ")
}
}
fn confidence_flags(facts: &FocusFileFactsPaths) -> Vec<ConfidenceFlag> {
let mut flags: Vec<ConfidenceFlag> = Vec::new();
if facts.dynamic_dispatch {
flags.push(ConfidenceFlag::DynamicDispatch);
}
if facts.re_export_indirection {
flags.push(ConfidenceFlag::ReExportIndirection);
}
flags
}
#[must_use]
pub fn build_focus_map(inputs: &FocusInputs<'_>) -> FocusMap {
let mut units: Vec<FocusUnit> = inputs
.graph_facts
.iter()
.map(|facts| {
let score = score_unit(facts, inputs);
let label = if is_safe_skip(facts, &score, inputs) {
FocusLabel::Skip
} else if score.total >= REVIEW_HERE_THRESHOLD {
FocusLabel::ReviewHere
} else {
FocusLabel::NotPrioritized
};
let reason = build_reason(facts, &score, inputs);
FocusUnit {
file: facts.file.clone(),
score,
label,
reason,
confidence: confidence_flags(facts),
}
})
.collect();
units.sort_by(|a, b| {
b.score
.total
.cmp(&a.score.total)
.then_with(|| a.file.cmp(&b.file))
});
let mut review_here: Vec<FocusUnit> = Vec::new();
let mut deprioritized: Vec<FocusUnit> = Vec::new();
for unit in units {
match unit.label {
FocusLabel::ReviewHere => review_here.push(unit),
FocusLabel::NotPrioritized | FocusLabel::Skip => deprioritized.push(unit),
}
}
deprioritized.sort_by(|a, b| a.file.cmp(&b.file));
FocusMap {
review_here,
deprioritized,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn facts(
file: &str,
fan_in: u32,
fan_out: u32,
dynamic: bool,
re_export: bool,
) -> FocusFileFactsPaths {
FocusFileFactsPaths {
file: file.to_string(),
fan_in,
fan_out,
dynamic_dispatch: dynamic,
re_export_indirection: re_export,
}
}
fn inputs<'a>(
graph_facts: &'a [FocusFileFactsPaths],
boundary_files: &'a [BoundaryZoneFile],
public_api_added: &'a [String],
coordination_changed_files: &'a [String],
taint_touched_files: &'a [String],
) -> FocusInputs<'a> {
FocusInputs {
graph_facts,
boundary_files,
public_api_added,
coordination_changed_files,
taint_touched_files,
runtime: None,
}
}
fn runtime(hot: &[(&str, u64)], cold: &[&str]) -> RuntimeFocus {
RuntimeFocus {
hot_files: hot
.iter()
.map(|(file, invocations)| RuntimeHotFile {
file: (*file).to_string(),
invocations: *invocations,
})
.collect(),
cold_files: cold.iter().map(|file| (*file).to_string()).collect(),
}
}
fn inputs_rt<'a>(
graph_facts: &'a [FocusFileFactsPaths],
public_api_added: &'a [String],
taint_touched_files: &'a [String],
runtime: &'a RuntimeFocus,
) -> FocusInputs<'a> {
FocusInputs {
graph_facts,
boundary_files: &[],
public_api_added,
coordination_changed_files: &[],
taint_touched_files,
runtime: Some(runtime),
}
}
#[test]
fn no_skip_label_ever_emitted_in_free_mode() {
let gf = vec![
facts("src/hot.ts", 12, 3, false, false), facts("src/iso.ts", 0, 0, false, false), ];
let map = build_focus_map(&inputs(&gf, &[], &[], &[], &[]));
let all_units: Vec<&FocusUnit> = map
.review_here
.iter()
.chain(map.deprioritized.iter())
.collect();
assert!(!all_units.is_empty());
for unit in all_units {
let token = unit.label.token();
assert_ne!(token, "skip", "free mode must never emit a skip label");
assert!(
token == "review-here" || token == "not-prioritized",
"unexpected label token {token}"
);
}
let json = serde_json::to_string(&map).expect("serialize");
assert!(
!json.contains("\"skip\""),
"serialized focus map leaked a skip label: {json}"
);
}
#[test]
fn escape_hatch_enumerates_every_deprioritized_unit() {
let gf = vec![
facts("src/a.ts", 12, 4, false, false), facts("src/b.ts", 0, 0, false, false), facts("src/c.ts", 1, 0, false, false), facts("src/d.ts", 8, 0, false, false), ];
let map = build_focus_map(&inputs(&gf, &[], &[], &[], &[]));
assert_eq!(
map.total_units(),
gf.len(),
"every unit must be reachable via review-here OR deprioritized"
);
assert!(!map.deprioritized.is_empty());
for d in &map.deprioritized {
assert!(
!map.review_here.iter().any(|r| r.file == d.file),
"{} is in both lists",
d.file
);
}
}
#[test]
fn dynamic_and_re_export_units_carry_low_confidence_flags() {
let gf = vec![
facts("src/dyn.ts", 0, 0, true, false),
facts("src/barrel.ts", 0, 0, false, true),
facts("src/both.ts", 0, 0, true, true),
];
let map = build_focus_map(&inputs(&gf, &[], &[], &[], &[]));
let all: Vec<&FocusUnit> = map
.review_here
.iter()
.chain(map.deprioritized.iter())
.collect();
let find = |file: &str| all.iter().find(|u| u.file == file).expect("unit present");
let dyn_unit = find("src/dyn.ts");
assert!(
dyn_unit
.confidence
.contains(&ConfidenceFlag::DynamicDispatch),
"dynamic unit must carry the dynamic-dispatch flag"
);
assert_eq!(
ConfidenceFlag::DynamicDispatch.message(),
"low: dynamic dispatch detected"
);
let barrel = find("src/barrel.ts");
assert!(
barrel
.confidence
.contains(&ConfidenceFlag::ReExportIndirection),
"barrel unit must carry the re-export-indirection flag"
);
assert_eq!(
ConfidenceFlag::ReExportIndirection.message(),
"low: re-export indirection"
);
let both = find("src/both.ts");
assert_eq!(both.confidence.len(), 2, "both flags present");
}
#[test]
fn confidence_flag_never_lowers_the_score() {
let plain = facts("src/plain.ts", 5, 0, false, false);
let flagged = facts("src/flagged.ts", 5, 0, true, true);
let plain_map = build_focus_map(&inputs(&[plain], &[], &[], &[], &[]));
let flagged_map = build_focus_map(&inputs(&[flagged], &[], &[], &[], &[]));
let plain_total = plain_map
.review_here
.iter()
.chain(plain_map.deprioritized.iter())
.next()
.unwrap()
.score
.total;
let flagged_total = flagged_map
.review_here
.iter()
.chain(flagged_map.deprioritized.iter())
.next()
.unwrap()
.score
.total;
assert_eq!(
plain_total, flagged_total,
"flags are advisory, not a penalty"
);
}
#[test]
fn risk_zone_and_change_shape_signals_raise_the_score() {
let gf = vec![facts("src/api.ts", 0, 0, false, false)];
let public_api = vec!["src/api.ts::Widget".to_string()];
let map = build_focus_map(&inputs(&gf, &[], &public_api, &[], &[]));
let unit = map
.review_here
.iter()
.chain(map.deprioritized.iter())
.next()
.unwrap();
assert_eq!(unit.score.risk_zone, RISK_ZONE_WEIGHT);
assert_eq!(unit.score.change_shape, CHANGE_SHAPE_WEIGHT);
assert_eq!(unit.label, FocusLabel::ReviewHere);
assert!(unit.reason.contains("public API"));
}
#[test]
fn security_taint_seam_is_zero_with_empty_findings_and_lights_up_with_a_touch() {
let gf = vec![facts("src/sink.ts", 0, 0, false, false)];
let no_taint = build_focus_map(&inputs(&gf, &[], &[], &[], &[]));
let no_taint_unit = no_taint
.review_here
.iter()
.chain(no_taint.deprioritized.iter())
.next()
.unwrap();
assert_eq!(no_taint_unit.score.security_taint, 0);
assert_eq!(no_taint_unit.label, FocusLabel::NotPrioritized);
let touched = vec!["src/sink.ts".to_string()];
let with_taint = build_focus_map(&inputs(&gf, &[], &[], &[], &touched));
let taint_unit = with_taint
.review_here
.iter()
.chain(with_taint.deprioritized.iter())
.next()
.unwrap();
assert_eq!(taint_unit.score.security_taint, SECURITY_TAINT_WEIGHT);
assert_eq!(taint_unit.score.risk_zone, RISK_ZONE_WEIGHT);
assert_eq!(taint_unit.label, FocusLabel::ReviewHere);
}
#[test]
fn coordination_gap_drives_signature_change_shape() {
let gf = vec![facts("src/core.ts", 0, 0, false, false)];
let coordination = vec!["src/core.ts".to_string()];
let map = build_focus_map(&inputs(&gf, &[], &[], &coordination, &[]));
let unit = map
.review_here
.iter()
.chain(map.deprioritized.iter())
.next()
.unwrap();
assert_eq!(unit.score.change_shape, CHANGE_SHAPE_WEIGHT);
assert!(unit.reason.contains("contract consumed outside the diff"));
}
#[test]
fn focus_map_is_byte_identical_across_runs() {
let gf = vec![
facts("src/a.ts", 5, 2, true, false),
facts("src/b.ts", 0, 0, false, true),
facts("src/c.ts", 3, 1, false, false),
];
let boundary = vec![BoundaryZoneFile {
from_file: "src/a.ts".to_string(),
}];
let public_api = vec!["src/c.ts::Thing".to_string()];
let first = build_focus_map(&inputs(&gf, &boundary, &public_api, &[], &[]));
let second = build_focus_map(&inputs(&gf, &boundary, &public_api, &[], &[]));
let s1 = serde_json::to_string_pretty(&first).unwrap();
let s2 = serde_json::to_string_pretty(&second).unwrap();
assert_eq!(s1, s2);
}
#[test]
fn review_here_is_ranked_by_score_descending() {
let gf = vec![
facts("src/low.ts", 2, 0, false, false), facts("src/high.ts", 12, 5, false, false), ];
let public_api = vec!["src/low.ts::X".to_string()];
let map = build_focus_map(&inputs(&gf, &[], &public_api, &[], &[]));
assert_eq!(map.review_here.len(), 2);
assert!(map.review_here[0].score.total >= map.review_here[1].score.total);
assert_eq!(map.review_here[0].file, "src/high.ts");
}
#[test]
fn focus_map_inputs_have_no_symbol_chain_or_trace_field() {
let empty_facts: &[FocusFileFactsPaths] = &[];
let empty_boundary: &[BoundaryZoneFile] = &[];
let empty_strings: &[String] = &[];
let FocusInputs {
graph_facts: _,
boundary_files: _,
public_api_added: _,
coordination_changed_files: _,
taint_touched_files: _,
runtime: _,
} = inputs(
empty_facts,
empty_boundary,
empty_strings,
empty_strings,
empty_strings,
);
let gf = vec![facts("src/x.ts", 4, 2, false, false)];
let map = build_focus_map(&inputs(&gf, &[], &[], &[], &[]));
let unit = map
.review_here
.iter()
.chain(map.deprioritized.iter())
.next()
.unwrap();
let score = &unit.score;
assert_eq!(
score.total,
score.fan_io
+ score.security_taint
+ score.risk_zone
+ score.change_shape
+ score.runtime,
"the focus total must be the documented components only -- no symbol-chain term"
);
assert_eq!(score.runtime, 0, "free mode adds no runtime weight");
}
#[test]
fn hot_unit_outranks_cold_with_runtime_data() {
let gf = vec![
facts("src/hot.ts", 2, 0, false, false),
facts("src/cold.ts", 2, 0, false, false),
];
let rt = runtime(&[("src/hot.ts", 500)], &["src/cold.ts"]);
let map = build_focus_map(&inputs_rt(&gf, &[], &[], &rt));
let find = |file: &str| {
map.review_here
.iter()
.chain(map.deprioritized.iter())
.find(|unit| unit.file == file)
.unwrap_or_else(|| panic!("{file} present"))
};
let hot = find("src/hot.ts");
let cold = find("src/cold.ts");
assert!(hot.score.runtime > 0, "hot unit carries a runtime weight");
assert_eq!(cold.score.runtime, 0, "cold unit carries no runtime weight");
assert!(
hot.score.total > cold.score.total,
"hot ({}) must outrank cold ({})",
hot.score.total,
cold.score.total
);
assert!(hot.reason.contains("hot path (500 invocations)"));
}
#[test]
fn runtime_weight_is_invocation_bucketed() {
assert_eq!(runtime_weight(0), RUNTIME_HOT_FLOOR);
assert_eq!(runtime_weight(50), RUNTIME_HOT_FLOOR);
assert_eq!(runtime_weight(100), RUNTIME_HOT_WARM);
assert_eq!(runtime_weight(1_000), RUNTIME_HOT_BLAZING);
}
#[test]
fn safe_skip_only_with_runtime_evidence_and_zero_risk() {
let isolated = vec![facts("src/dead.ts", 0, 0, false, false)];
let rt = runtime(&[], &["src/dead.ts"]);
let map = build_focus_map(&inputs_rt(&isolated, &[], &[], &rt));
let unit = map
.review_here
.iter()
.chain(map.deprioritized.iter())
.next()
.expect("unit present");
assert_eq!(unit.label, FocusLabel::Skip);
assert_eq!(unit.label.token(), "skip");
assert!(unit.reason.contains("runtime-cold"));
assert!(map.deprioritized.iter().any(|u| u.file == "src/dead.ts"));
let public_api = vec!["src/dead.ts::Widget".to_string()];
let with_risk = build_focus_map(&inputs_rt(&isolated, &public_api, &[], &rt));
let risky = with_risk
.review_here
.iter()
.chain(with_risk.deprioritized.iter())
.next()
.expect("unit present");
assert_ne!(
risky.label,
FocusLabel::Skip,
"a risk signal blocks safe-skip"
);
}
#[test]
fn confidence_flag_blocks_safe_skip_even_when_runtime_cold() {
let dyn_cold = vec![facts("src/dyn.ts", 0, 0, true, false)];
let rt = runtime(&[], &["src/dyn.ts"]);
let map = build_focus_map(&inputs_rt(&dyn_cold, &[], &[], &rt));
let unit = map
.review_here
.iter()
.chain(map.deprioritized.iter())
.next()
.expect("unit present");
assert_ne!(
unit.label,
FocusLabel::Skip,
"dynamic-dispatch reachability uncertainty blocks safe-skip"
);
assert!(unit.confidence.contains(&ConfidenceFlag::DynamicDispatch));
}
#[test]
fn no_runtime_data_is_byte_identical_to_e7() {
let gf = vec![
facts("src/a.ts", 12, 3, false, false),
facts("src/b.ts", 0, 0, false, false),
facts("src/c.ts", 2, 0, false, true),
];
let public_api = vec!["src/c.ts::Thing".to_string()];
let e7 = build_focus_map(&inputs(&gf, &[], &public_api, &[], &[]));
let json = serde_json::to_string_pretty(&e7).expect("serialize");
assert!(!json.contains("\"skip\""), "free mode emits no skip label");
assert!(
!json.contains("\"runtime\""),
"free mode omits the runtime component from the wire"
);
for unit in e7.review_here.iter().chain(e7.deprioritized.iter()) {
assert_eq!(unit.score.runtime, 0);
assert_ne!(unit.label, FocusLabel::Skip);
}
}
#[test]
fn runtime_focus_map_is_byte_identical_across_runs() {
let gf = vec![
facts("src/hot.ts", 4, 2, false, false),
facts("src/cold.ts", 0, 0, false, false),
facts("src/warm.ts", 1, 0, false, false),
];
let rt = runtime(
&[("src/hot.ts", 2_000), ("src/warm.ts", 120)],
&["src/cold.ts"],
);
let first = build_focus_map(&inputs_rt(&gf, &[], &[], &rt));
let second = build_focus_map(&inputs_rt(&gf, &[], &[], &rt));
assert_eq!(
serde_json::to_string_pretty(&first).unwrap(),
serde_json::to_string_pretty(&second).unwrap()
);
}
}