use std::process::ExitCode;
pub use fallow_output::{
CoordinationGapFact, DiffTriage, GraphFacts, ImpactClosureFacts, PartitionFacts,
ReviewBriefSchemaVersion, ReviewBriefSubtractSections, ReviewDeltas, ReviewEffort,
ReviewUnitFact, RiskClass,
};
use fallow_types::results::AnalysisResults;
use rustc_hash::FxHashSet;
use crate::audit::AuditResult;
use crate::report::sink::outln;
pub type ReviewBriefOutput = fallow_output::StandardReviewBriefOutput;
const RISK_HIGH_FILES: usize = 20;
const RISK_HIGH_LINES: i64 = 500;
const RISK_MEDIUM_FILES: usize = 5;
const RISK_MEDIUM_LINES: i64 = 100;
const COORDINATION_GAP_NOTE: &str = "syntactic attention pointer, not a correctness proof";
#[must_use]
#[allow(
clippy::implicit_hasher,
reason = "callers always pass the audit FxHashSet key sets; generalizing the hasher adds noise"
)]
pub fn build_review_deltas(
head_boundary: &FxHashSet<String>,
base_boundary: &FxHashSet<String>,
head_cycles: &FxHashSet<String>,
base_cycles: &FxHashSet<String>,
head_public_api: &FxHashSet<String>,
base_public_api: &FxHashSet<String>,
) -> ReviewDeltas {
use crate::audit::review_deltas::introduced_keys;
ReviewDeltas {
boundary_introduced: introduced_keys(head_boundary, base_boundary),
cycle_introduced: introduced_keys(head_cycles, base_cycles),
public_api_added: introduced_keys(head_public_api, base_public_api),
}
}
#[must_use]
pub fn classify_risk(files: usize, net_lines: Option<i64>) -> RiskClass {
let lines = net_lines.unwrap_or(0).abs();
if files >= RISK_HIGH_FILES || lines >= RISK_HIGH_LINES {
RiskClass::High
} else if files >= RISK_MEDIUM_FILES || lines >= RISK_MEDIUM_LINES {
RiskClass::Medium
} else {
RiskClass::Low
}
}
#[must_use]
pub fn review_effort_for(risk: RiskClass) -> ReviewEffort {
match risk {
RiskClass::Low => ReviewEffort::Glance,
RiskClass::Medium => ReviewEffort::Review,
RiskClass::High => ReviewEffort::DeepDive,
}
}
#[must_use]
pub fn build_triage(result: &AuditResult) -> DiffTriage {
let files = result.changed_files_count;
let hunks = None;
let net_lines = None;
let risk_class = classify_risk(files, net_lines);
DiffTriage {
files,
hunks,
net_lines,
risk_class,
review_effort: review_effort_for(risk_class),
}
}
#[must_use]
pub fn derive_graph_facts(
results: &AnalysisResults,
closure: Option<&fallow_engine::module_graph::ImpactClosurePaths>,
) -> GraphFacts {
let mut zones: FxHashSet<String> = FxHashSet::default();
for finding in &results.boundary_violations {
zones.insert(finding.violation.from_zone.clone());
zones.insert(finding.violation.to_zone.clone());
}
let mut boundaries_touched: Vec<String> = zones.into_iter().collect();
boundaries_touched.sort();
let reachable_from = closure
.map(|c| c.affected_not_shown.clone())
.unwrap_or_default();
GraphFacts {
exports_added: 0,
api_width_delta: 0,
reachable_from,
boundaries_touched,
}
}
#[must_use]
fn build_impact_closure_facts(result: &AuditResult) -> ImpactClosureFacts {
let Some(closure) = result
.check
.as_ref()
.and_then(|c| c.impact_closure.as_ref())
else {
return ImpactClosureFacts::default();
};
let coordination_gap = closure
.coordination_gap
.iter()
.map(|gap| CoordinationGapFact {
changed_file: gap.changed_file.clone(),
consumer_file: gap.consumer_file.clone(),
consumed_symbols: gap.consumed_symbols.clone(),
note: COORDINATION_GAP_NOTE.to_string(),
})
.collect();
ImpactClosureFacts {
affected_not_shown: closure.affected_not_shown.clone(),
coordination_gap,
}
}
#[must_use]
fn build_partition_facts(result: &AuditResult) -> PartitionFacts {
let Some(partition) = result
.check
.as_ref()
.and_then(|c| c.partition_order.as_ref())
else {
return PartitionFacts::default();
};
let units = partition
.units
.iter()
.map(|unit| ReviewUnitFact {
module_dir: unit.module_dir.clone(),
files: unit.files.clone(),
})
.collect();
PartitionFacts {
units,
order: partition.order.clone(),
}
}
#[must_use]
fn build_focus_map(result: &AuditResult, deltas: &ReviewDeltas) -> crate::audit_focus::FocusMap {
use crate::audit_focus::{BoundaryZoneFile, FocusInputs, build_focus_map};
let Some(check) = result.check.as_ref() else {
return crate::audit_focus::FocusMap::default();
};
let Some(graph_facts) = check.focus_facts.as_ref() else {
return crate::audit_focus::FocusMap::default();
};
let root = &check.config.root;
let mut seen_pairs: FxHashSet<String> = FxHashSet::default();
let mut boundary_files: Vec<BoundaryZoneFile> = Vec::new();
for finding in &check.results.boundary_violations {
let key = crate::audit::review_deltas::boundary_edge_key(finding);
if !deltas.boundary_introduced.contains(&key) || !seen_pairs.insert(key) {
continue;
}
boundary_files.push(BoundaryZoneFile {
from_file: crate::audit::keys::relative_key_path(&finding.violation.from_path, root),
});
}
let coordination_changed_files: Vec<String> = check
.impact_closure
.as_ref()
.map(|c| {
let mut files: Vec<String> = c
.coordination_gap
.iter()
.map(|gap| gap.changed_file.clone())
.collect();
files.sort_unstable();
files.dedup();
files
})
.unwrap_or_default();
let taint_touched_files = taint_touched_files(result.check.as_ref());
let runtime_focus = build_runtime_focus(result, root);
build_focus_map(&FocusInputs {
graph_facts,
boundary_files: &boundary_files,
public_api_added: &deltas.public_api_added,
coordination_changed_files: &coordination_changed_files,
taint_touched_files: &taint_touched_files,
runtime: runtime_focus.as_ref(),
})
}
fn build_runtime_focus(
result: &AuditResult,
root: &std::path::Path,
) -> Option<crate::audit_focus::RuntimeFocus> {
let report = result.health.as_ref()?.report.runtime_coverage.as_ref()?;
let hot_pairs: Vec<(String, u64)> = report
.hot_paths
.iter()
.map(|hot| {
(
crate::audit::keys::relative_key_path(&hot.path, root),
hot.invocations,
)
})
.collect();
let mut safe_to_delete: FxHashSet<String> = FxHashSet::default();
let mut other_verdict: FxHashSet<String> = FxHashSet::default();
for finding in &report.findings {
let file = crate::audit::keys::relative_key_path(&finding.path, root);
if matches!(
finding.verdict,
fallow_output::RuntimeCoverageVerdict::SafeToDelete
) {
safe_to_delete.insert(file);
} else {
other_verdict.insert(file);
}
}
reconcile_runtime_focus(hot_pairs, &safe_to_delete, &other_verdict)
}
fn reconcile_runtime_focus(
hot_pairs: Vec<(String, u64)>,
safe_to_delete: &FxHashSet<String>,
other_verdict: &FxHashSet<String>,
) -> Option<crate::audit_focus::RuntimeFocus> {
use crate::audit_focus::{RuntimeFocus, RuntimeHotFile};
let mut hot_by_file: rustc_hash::FxHashMap<String, u64> = rustc_hash::FxHashMap::default();
for (file, invocations) in hot_pairs {
let entry = hot_by_file.entry(file).or_insert(0);
*entry = (*entry).max(invocations);
}
let mut hot_files: Vec<RuntimeHotFile> = hot_by_file
.into_iter()
.map(|(file, invocations)| RuntimeHotFile { file, invocations })
.collect();
hot_files.sort_by(|a, b| a.file.cmp(&b.file));
let hot_set: FxHashSet<&str> = hot_files.iter().map(|hot| hot.file.as_str()).collect();
let mut cold_files: Vec<String> = safe_to_delete
.iter()
.filter(|file| !other_verdict.contains(*file) && !hot_set.contains(file.as_str()))
.cloned()
.collect();
cold_files.sort();
if hot_files.is_empty() && cold_files.is_empty() {
return None;
}
Some(RuntimeFocus {
hot_files,
cold_files,
})
}
fn taint_touched_files(check: Option<&crate::check::CheckResult>) -> Vec<String> {
let Some(check) = check else {
return Vec::new();
};
let root = &check.config.root;
let mut touched: FxHashSet<String> = FxHashSet::default();
for finding in &check.results.security_findings {
touched.insert(crate::audit::keys::relative_key_path(&finding.path, root));
for hop in &finding.trace {
touched.insert(crate::audit::keys::relative_key_path(&hop.path, root));
}
}
let mut files: Vec<String> = touched.into_iter().collect();
files.sort();
files
}
#[must_use]
pub fn build_brief_output(result: &AuditResult) -> ReviewBriefOutput {
let triage = build_triage(result);
let closure = result
.check
.as_ref()
.and_then(|c| c.impact_closure.as_ref());
let deltas = result.review_deltas.clone().unwrap_or_default();
let mut graph_facts = result.check.as_ref().map_or_else(
|| GraphFacts {
exports_added: 0,
api_width_delta: 0,
reachable_from: Vec::new(),
boundaries_touched: Vec::new(),
},
|check| derive_graph_facts(&check.results, closure),
);
let added = deltas.public_api_added.len();
graph_facts.exports_added = added;
graph_facts.api_width_delta = i64::try_from(added).unwrap_or(i64::MAX);
let partition = build_partition_facts(result);
let impact_closure = build_impact_closure_facts(result);
let focus = build_focus_map(result, &deltas);
ReviewBriefOutput {
schema_version: ReviewBriefSchemaVersion::default(),
version: env!("CARGO_PKG_VERSION").to_string(),
command: "audit-brief".to_string(),
triage,
graph_facts,
partition,
impact_closure,
focus,
deltas,
weakening: result.weakening_signals.clone(),
routing: result.routing.clone().unwrap_or_default(),
decisions: result.decision_surface.clone().unwrap_or_default(),
}
}
fn build_brief_subtract_sections(
result: &AuditResult,
) -> Result<ReviewBriefSubtractSections, ExitCode> {
let mut obj = serde_json::Map::new();
if let Some(ref check) = result.check {
crate::audit::insert_audit_dead_code_json(&mut obj, result, check)?;
}
if let Some(ref dupes) = result.dupes {
crate::audit::insert_audit_duplication_json(&mut obj, result, dupes)?;
}
if let Some(ref health) = result.health {
crate::audit::insert_audit_health_json(&mut obj, result, health)?;
}
Ok(ReviewBriefSubtractSections {
dead_code: obj.remove("dead_code"),
duplication: obj.remove("duplication"),
complexity: obj.remove("complexity"),
})
}
fn build_brief_json(result: &AuditResult) -> Result<serde_json::Value, ExitCode> {
let brief = build_brief_output(result);
let audit_header = fallow_api::build_audit_header_map(crate::audit::audit_json_header_input(
result,
))
.map_err(|err| {
crate::error::emit_error(
&format!("JSON serialization error: {err}"),
2,
fallow_config::OutputFormat::Json,
)
})?;
let subtract = build_brief_subtract_sections(result)?;
fallow_output::build_review_brief_json_output(&brief, audit_header, subtract).map_err(|err| {
crate::error::emit_error(
&format!("JSON serialization error: {err}"),
2,
fallow_config::OutputFormat::Json,
)
})
}
fn print_brief_json(result: &AuditResult) -> ExitCode {
match build_brief_json(result) {
Ok(output) => {
let Ok(output) = fallow_output::serialize_review_brief_json_output(
output,
crate::output_runtime::current_root_envelope_mode(),
crate::output_runtime::telemetry_analysis_run_id().as_deref(),
) else {
return ExitCode::SUCCESS;
};
let _ = crate::report::emit_json(&output, "audit-brief");
ExitCode::SUCCESS
}
Err(_) => ExitCode::SUCCESS,
}
}
fn print_brief_human(result: &AuditResult, quiet: bool, explain: bool, show_deprioritized: bool) {
let brief = build_brief_output(result);
if !quiet {
eprintln!();
print_decision_surface_human(&brief.decisions);
eprintln!(
"Review brief (drill-down): {} changed file{} vs {} \u{00b7} risk {} \u{00b7} effort {}",
result.changed_files_count,
crate::report::plural(result.changed_files_count),
result.base_ref,
risk_label(brief.triage.risk_class),
effort_label(brief.triage.review_effort),
);
if !brief.graph_facts.boundaries_touched.is_empty() {
eprintln!(
" boundaries touched: {}",
brief.graph_facts.boundaries_touched.join(", ")
);
}
print_partition_human(&brief.partition);
print_impact_closure_human(&brief.impact_closure);
print_focus_human(&brief.focus, show_deprioritized);
print_deltas_human(&brief.deltas);
print_weakening_human(&brief.weakening);
print_routing_human(&brief.routing);
}
crate::audit::print_audit_findings(result, quiet, explain, false);
}
fn print_partition_human(partition: &PartitionFacts) {
if partition.units.is_empty() {
return;
}
eprintln!(
" partition: {} unit{} (by module)",
partition.units.len(),
crate::report::plural(partition.units.len()),
);
if !partition.order.is_empty() {
let labeled: Vec<String> = partition.order.iter().map(|dir| unit_label(dir)).collect();
eprintln!(" review order: {}", labeled.join(" \u{2192} "));
}
}
fn unit_label(module_dir: &str) -> String {
if module_dir.is_empty() {
"<root>".to_string()
} else {
module_dir.to_string()
}
}
fn print_impact_closure_human(closure: &ImpactClosureFacts) {
if !closure.affected_not_shown.is_empty() {
eprintln!(
" impact closure: {} file{} affected beyond the diff",
closure.affected_not_shown.len(),
crate::report::plural(closure.affected_not_shown.len()),
);
}
for gap in &closure.coordination_gap {
eprintln!(
" coordination gap: {} consumes {} from {} (not in this diff)",
gap.consumer_file,
gap.consumed_symbols.join(", "),
gap.changed_file,
);
}
}
fn print_focus_human(focus: &crate::audit_focus::FocusMap, show_deprioritized: bool) {
if focus.total_units() == 0 {
return;
}
if !focus.review_here.is_empty() {
eprintln!(
" focus: {} unit{} to review here (of {} changed)",
focus.review_here.len(),
crate::report::plural(focus.review_here.len()),
focus.total_units(),
);
for unit in &focus.review_here {
eprintln!(
" [{}] {}: {}",
unit.label.token(),
unit.file,
unit.reason
);
for flag in &unit.confidence {
eprintln!(" confidence {}", flag.message());
}
}
}
if focus.deprioritized.is_empty() {
return;
}
if show_deprioritized {
eprintln!(" de-prioritized ({}):", focus.deprioritized.len());
for unit in &focus.deprioritized {
eprintln!(
" [{}] {}: {}",
unit.label.token(),
unit.file,
unit.reason
);
for flag in &unit.confidence {
eprintln!(" confidence {}", flag.message());
}
}
} else {
eprintln!(
" de-prioritized: {} unit{} (run with --show-deprioritized to list)",
focus.deprioritized.len(),
crate::report::plural(focus.deprioritized.len()),
);
}
}
fn print_deltas_human(deltas: &ReviewDeltas) {
for edge in &deltas.boundary_introduced {
eprintln!(" new boundary edge: {edge} (not present at base)");
}
for cycle in &deltas.cycle_introduced {
eprintln!(" new circular dependency: {cycle} (not present at base)");
}
if !deltas.public_api_added.is_empty() {
eprintln!(
" public API surface widened by {} export{} (exports-aware)",
deltas.public_api_added.len(),
crate::report::plural(deltas.public_api_added.len()),
);
}
}
fn print_weakening_human(signals: &[crate::audit::weakening::WeakeningSignal]) {
if signals.is_empty() {
return;
}
eprintln!(
" weakening signals ({}, reviewer-private, advisory):",
signals.len()
);
for signal in signals {
eprintln!(
" {}: {} in {}",
weakening_label(signal.kind),
signal.evidence,
signal.file,
);
}
}
fn print_routing_human(routing: &crate::audit::routing::RoutingFacts) {
for unit in &routing.units {
if unit.expert.is_empty() {
continue;
}
let bus = if unit.bus_factor_one {
" (bus-factor 1)"
} else {
""
};
eprintln!(
" review {}: ask {}{bus}",
unit.file,
unit.expert.join(", "),
);
}
}
fn print_decision_surface_human(surface: &crate::audit_decision_surface::DecisionSurface) {
if surface.decisions.is_empty() {
eprintln!("Decisions: none (no consequential structural decision in this change)");
eprintln!();
return;
}
eprintln!("Decisions to make ({}):", surface.decisions.len());
for (i, decision) in surface.decisions.iter().enumerate() {
eprintln!(
" {}. [{}] {}",
i + 1,
decision.category.tag(),
decision.question
);
if !decision.tradeoff.is_empty() {
eprintln!(" trade-off: {}", decision.tradeoff);
}
if !decision.expert.is_empty() {
let bus = if decision.bus_factor_one {
" (bus-factor 1)"
} else {
""
};
eprintln!(" ask: {}{bus}", decision.expert.join(", "));
}
}
if let Some(note) = &surface.truncated {
eprintln!(" ... {}", note.reason);
}
eprintln!();
}
fn weakening_label(kind: crate::audit::weakening::WeakeningKind) -> &'static str {
use crate::audit::weakening::WeakeningKind;
match kind {
WeakeningKind::TestWeakened => "test weakened",
WeakeningKind::ThresholdLowered => "threshold lowered",
WeakeningKind::SuppressionAdded => "suppression added",
WeakeningKind::SecurityCheckRemoved => "security check removed",
}
}
fn risk_label(risk: RiskClass) -> &'static str {
match risk {
RiskClass::Low => "low",
RiskClass::Medium => "medium",
RiskClass::High => "high",
}
}
fn effort_label(effort: ReviewEffort) -> &'static str {
match effort {
ReviewEffort::Glance => "glance",
ReviewEffort::Review => "review",
ReviewEffort::DeepDive => "deep-dive",
}
}
#[must_use]
pub fn print_brief_result(
result: &AuditResult,
quiet: bool,
explain: bool,
show_deprioritized: bool,
) -> ExitCode {
use fallow_config::OutputFormat;
match result.output {
OutputFormat::Json => print_brief_json(result),
OutputFormat::Human | OutputFormat::Compact | OutputFormat::Markdown => {
print_brief_human(result, quiet, explain, show_deprioritized);
ExitCode::SUCCESS
}
_ => {
let _ = crate::audit::print_audit_result(result, quiet, explain);
ExitCode::SUCCESS
}
}
}
#[must_use]
pub fn print_decision_surface_result(result: &AuditResult, quiet: bool) -> ExitCode {
use fallow_config::OutputFormat;
let surface = result.decision_surface.clone().unwrap_or_default();
match result.output {
OutputFormat::Json => {
let output = crate::audit_decision_surface::build_decision_surface_output(&surface);
match fallow_output::serialize_decision_surface_json_output(
output,
crate::output_runtime::current_root_envelope_mode(),
crate::output_runtime::telemetry_analysis_run_id().as_deref(),
) {
Ok(value) => {
let _ = crate::report::emit_json(&value, "decision-surface");
ExitCode::SUCCESS
}
Err(_) => ExitCode::SUCCESS,
}
}
_ => {
if !quiet {
print_decision_surface_human(&surface);
}
ExitCode::SUCCESS
}
}
}
#[must_use]
pub fn print_walkthrough_guide_result(result: &AuditResult) -> ExitCode {
let guide = crate::audit_walkthrough::build_guide_from_result(result);
if let Ok(value) = fallow_output::serialize_walkthrough_guide_json_output(
guide,
crate::output_runtime::current_root_envelope_mode(),
crate::output_runtime::telemetry_analysis_run_id().as_deref(),
) {
let _ = crate::report::emit_json(&value, "review-walkthrough-guide");
}
ExitCode::SUCCESS
}
#[must_use]
pub fn print_walkthrough_file_result(result: &AuditResult, path: &std::path::Path) -> ExitCode {
let contents = std::fs::read_to_string(path).unwrap_or_default();
let agent = crate::audit_walkthrough::parse_agent_walkthrough(&contents);
let surface = result.decision_surface.clone().unwrap_or_default();
let current_hash = result.graph_snapshot_hash.clone().unwrap_or_default();
let change_anchor_ids =
crate::audit_walkthrough::change_anchor_allowlist(&result.change_anchors);
let validation = crate::audit_walkthrough::validate_walkthrough(
&agent,
&surface,
&change_anchor_ids,
¤t_hash,
);
if let Ok(value) = fallow_output::serialize_walkthrough_validation_json_output(
validation,
crate::output_runtime::current_root_envelope_mode(),
crate::output_runtime::telemetry_analysis_run_id().as_deref(),
) {
let _ = crate::report::emit_json(&value, "review-walkthrough-validation");
}
ExitCode::SUCCESS
}
#[must_use]
pub fn print_walkthrough_human_result(
result: &AuditResult,
root: &std::path::Path,
cache_dir: &std::path::Path,
mark_viewed: &[std::path::PathBuf],
show_cleared: bool,
quiet: bool,
) -> ExitCode {
use fallow_config::OutputFormat;
if matches!(result.output, OutputFormat::Json) {
return print_walkthrough_guide_result(result);
}
let guide = crate::audit_walkthrough::build_guide_from_result(result);
record_walkthrough_marks(&guide, root, cache_dir, mark_viewed);
let viewed = crate::walkthrough_state::load_viewed_state(cache_dir);
if matches!(result.output, OutputFormat::Markdown) {
let viewed_files = crate::report::walkthrough_viewed_files(&guide, &viewed);
let markdown = fallow_api::build_walkthrough_markdown(&guide, root, &viewed_files);
outln!("{markdown}");
return ExitCode::SUCCESS;
}
let render = crate::report::build_walkthrough_human(&guide, &viewed, show_cleared);
if !quiet {
for line in &render.header {
eprintln!("{line}");
}
}
for line in &render.body {
outln!("{line}");
}
if !quiet {
eprintln!("{}", render.status);
}
ExitCode::SUCCESS
}
fn record_walkthrough_marks(
guide: &crate::audit_walkthrough::WalkthroughGuide,
root: &std::path::Path,
cache_dir: &std::path::Path,
mark_viewed: &[std::path::PathBuf],
) {
if mark_viewed.is_empty() {
return;
}
let keys: Vec<String> = mark_viewed
.iter()
.map(|path| walkthrough_view_key(path, root))
.collect();
let _ = crate::walkthrough_state::mark_viewed(cache_dir, &keys, &guide.graph_snapshot_hash);
}
fn walkthrough_view_key(path: &std::path::Path, root: &std::path::Path) -> String {
let rel = path.strip_prefix(root).unwrap_or(path);
rel.to_string_lossy().replace('\\', "/")
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use fallow_config::{AuditGate, OutputFormat};
use fallow_output::REVIEW_BRIEF_SCHEMA_VERSION;
use rustc_hash::FxHashSet;
use crate::audit::{AuditAttribution, AuditResult, AuditSummary, AuditVerdict};
fn str_set(files: &[&str]) -> FxHashSet<String> {
files.iter().map(|file| (*file).to_string()).collect()
}
#[test]
fn reconcile_runtime_focus_classifies_hot_cold_and_excludes_mixed() {
let hot_pairs = vec![
("src/hot.ts".to_string(), 120),
("src/hot.ts".to_string(), 900), ("src/both.ts".to_string(), 300), ];
let safe = str_set(&["src/cold.ts", "src/mixed.ts", "src/both.ts"]);
let other = str_set(&["src/mixed.ts"]); let focus =
super::reconcile_runtime_focus(hot_pairs, &safe, &other).expect("non-empty focus");
let hot: Vec<(&str, u64)> = focus
.hot_files
.iter()
.map(|hot| (hot.file.as_str(), hot.invocations))
.collect();
assert_eq!(hot, vec![("src/both.ts", 300), ("src/hot.ts", 900)]);
assert_eq!(focus.cold_files, vec!["src/cold.ts".to_string()]);
}
#[test]
fn reconcile_runtime_focus_is_none_when_empty() {
assert!(super::reconcile_runtime_focus(Vec::new(), &str_set(&[]), &str_set(&[])).is_none());
}
use super::*;
fn audit_result(verdict: AuditVerdict, output: OutputFormat) -> AuditResult {
AuditResult {
verdict,
summary: AuditSummary {
dead_code_issues: 0,
dead_code_has_errors: false,
complexity_findings: 0,
max_cyclomatic: None,
duplication_clone_groups: 0,
},
attribution: AuditAttribution {
gate: AuditGate::NewOnly,
..AuditAttribution::default()
},
base_snapshot: None,
base_snapshot_skipped: false,
changed_files_count: 0,
changed_files: Vec::new(),
base_ref: "origin/main".to_string(),
base_description: None,
head_sha: None,
output,
performance: false,
check: None,
dupes: None,
health: None,
elapsed: Duration::ZERO,
review_deltas: None,
weakening_signals: Vec::new(),
routing: None,
decision_surface: None,
graph_snapshot_hash: None,
change_anchors: Vec::new(),
}
}
#[test]
fn brief_mode_always_returns_success_even_when_verdict_is_fail() {
let human = audit_result(AuditVerdict::Fail, OutputFormat::Human);
assert_eq!(
print_brief_result(&human, true, false, false),
ExitCode::SUCCESS
);
let json = audit_result(AuditVerdict::Fail, OutputFormat::Json);
assert_eq!(
print_brief_result(&json, true, false, false),
ExitCode::SUCCESS
);
}
#[test]
fn brief_json_validates_against_audit_brief_schema_variant() {
let result = audit_result(AuditVerdict::Fail, OutputFormat::Json);
let value = fallow_output::serialize_review_brief_json_output(
build_brief_json(&result).expect("brief json must build"),
crate::output_runtime::current_root_envelope_mode(),
crate::output_runtime::telemetry_analysis_run_id().as_deref(),
)
.expect("brief json must serialize");
assert_eq!(value["kind"], "audit-brief");
assert_eq!(value["command"], "audit-brief");
assert_eq!(value["schema_version"], REVIEW_BRIEF_SCHEMA_VERSION);
}
#[test]
fn brief_json_is_byte_identical_on_repeated_serialization() {
let result = audit_result(AuditVerdict::Warn, OutputFormat::Json);
let first = build_brief_json(&result).expect("first build");
let second = build_brief_json(&result).expect("second build");
let first_str = serde_json::to_string_pretty(&first).expect("serialize first");
let second_str = serde_json::to_string_pretty(&second).expect("serialize second");
assert_eq!(first_str, second_str);
}
#[test]
fn risk_class_thresholds_are_pure_functions_of_size() {
assert_eq!(classify_risk(0, None), RiskClass::Low);
assert_eq!(classify_risk(RISK_MEDIUM_FILES, None), RiskClass::Medium);
assert_eq!(classify_risk(RISK_HIGH_FILES, None), RiskClass::High);
assert_eq!(classify_risk(1, Some(RISK_HIGH_LINES)), RiskClass::High);
assert_eq!(review_effort_for(RiskClass::High), ReviewEffort::DeepDive);
}
#[test]
fn brief_json_includes_empty_impact_closure_when_no_graph_retained() {
let result = audit_result(AuditVerdict::Warn, OutputFormat::Json);
let value = build_brief_json(&result).expect("brief json must build");
assert!(value.get("impact_closure").is_some(), "{value}");
assert_eq!(
value["impact_closure"]["affected_not_shown"],
serde_json::json!([])
);
assert_eq!(
value["impact_closure"]["coordination_gap"],
serde_json::json!([])
);
}
#[test]
fn derive_graph_facts_populates_reachable_from_from_closure() {
use fallow_engine::module_graph::{CoordinationGapPaths, ImpactClosurePaths};
let results = AnalysisResults::default();
let closure = ImpactClosurePaths {
in_diff: vec!["src/core.ts".to_string()],
affected_not_shown: vec!["src/app.ts".to_string(), "src/mid.ts".to_string()],
coordination_gap: vec![CoordinationGapPaths {
changed_file: "src/core.ts".to_string(),
consumer_file: "src/mid.ts".to_string(),
consumed_symbols: vec!["compute".to_string()],
}],
};
let facts = derive_graph_facts(&results, Some(&closure));
assert_eq!(
facts.reachable_from,
vec!["src/app.ts".to_string(), "src/mid.ts".to_string()]
);
}
#[test]
fn coordination_gap_fact_carries_honest_scope_note() {
let gap = CoordinationGapFact {
changed_file: "src/core.ts".to_string(),
consumer_file: "src/mid.ts".to_string(),
consumed_symbols: vec!["compute".to_string()],
note: COORDINATION_GAP_NOTE.to_string(),
};
assert!(gap.note.contains("attention pointer"));
assert!(gap.note.contains("not a correctness proof"));
}
}