use crate::standards_review_patch::{NewSpan, new_spans};
use crate::standards_review_text::{utf16_length, utf16_prefix};
use std::collections::BTreeMap;
pub const TRACKED_LOG_RELATIVE: &str = "doc/audit/standards-review-gate-shadow.md";
pub const SHARED_LOG_RELATIVE: &str = "cli-git/standards-review-shadow.md";
pub fn shared_log_path(common_dir: &std::path::Path) -> std::path::PathBuf {
return common_dir.join(SHARED_LOG_RELATIVE);
}
pub fn has_shadow_log(repository: &std::path::Path) -> bool {
return repository.join(TRACKED_LOG_RELATIVE).is_file();
}
pub const CAP_LINES: usize = 2000;
pub const MAX_EXCERPT_FILES: usize = 8;
pub const EXCERPT_CONTEXT: i64 = 30;
pub const EXCERPT_LINE_CAP: i64 = 4000;
pub const HEAD_WINDOW: i64 = 2 * EXCERPT_CONTEXT + 1;
pub const INPUT_UNIT_CAP: usize = 24000;
pub const CODE_SUFFIXES: [&str; 6] = [".ts", ".tsx", ".rs", ".js", ".mjs", ".cjs"];
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Plan {
Silent,
NoCode,
Review(Vec<String>),
}
pub fn plan_for(names: &[String]) -> Plan {
if names.iter().all(|name| return name == TRACKED_LOG_RELATIVE) {
return Plan::Silent;
}
let code_files: Vec<String> = names
.iter()
.filter(|name| return CODE_SUFFIXES.iter().any(|suffix| return name.ends_with(suffix)))
.cloned()
.collect();
if code_files.is_empty() {
return Plan::NoCode;
}
return Plan::Review(code_files);
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct LandedCommit {
pub sha: String,
pub subject: String,
pub patch: String,
pub contents: BTreeMap<String, String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReviewInput {
pub code_file_count: usize,
pub diff_line_count: usize,
pub truncated: bool,
pub model_input: String,
pub model_input_units: usize,
}
fn excerpt(name: &str, content: &str, spans: &[NewSpan]) -> (Vec<String>, i64) {
let file_lines: Vec<&str> = content.split('\n').collect();
let total: i64 = i64::try_from(file_lines.len()).unwrap_or(i64::MAX);
let first_changed: Option<i64> = spans.iter().map(|span| return i64::try_from(span.first).unwrap_or(i64::MAX)).min();
let last_changed: Option<i64> = spans.iter().map(|span| return i64::try_from(span.last).unwrap_or(i64::MAX)).max();
let (from, to) = match (first_changed, last_changed) {
(Some(first), Some(last)) => (1.max(first.saturating_sub(EXCERPT_CONTEXT)), total.min(last.saturating_add(EXCERPT_CONTEXT))),
_ => (1, total.min(HEAD_WINDOW)),
};
let header: String = format!("// candidate excerpt of {name}, lines {from} to {to} of {total}");
let begin: usize = usize::try_from(from - 1).unwrap_or(0);
let end: usize = usize::try_from(to).unwrap_or(0);
let body: String = if begin < end { file_lines[begin..end].join("\n") } else { String::new() };
return (vec![header, body], to - from + 1);
}
pub fn build_input(commit: &LandedCommit, code_files: &[String]) -> ReviewInput {
let lines: Vec<&str> = commit.patch.split('\n').collect();
let kept: &[&str] = &lines[..lines.len().min(CAP_LINES)];
let mut parts: Vec<String> = Vec::new();
let mut excerpt_lines: i64 = 0;
for name in code_files.iter().take(MAX_EXCERPT_FILES) {
if excerpt_lines >= EXCERPT_LINE_CAP {
break;
}
let Some(content) = commit.contents.get(name) else {
continue;
};
let spans: Vec<NewSpan> = new_spans(lines.as_slice(), name);
let (pieces, covered) = excerpt(name, content, spans.as_slice());
parts.extend(pieces);
excerpt_lines += covered;
}
let full: String = format!("{}\n\n{}", kept.join("\n"), parts.join("\n\n"));
let units: usize = utf16_length(full.as_str()).min(INPUT_UNIT_CAP);
return ReviewInput {
code_file_count: code_files.len(),
diff_line_count: lines.len(),
truncated: lines.len() > CAP_LINES,
model_input: utf16_prefix(full.as_str(), INPUT_UNIT_CAP),
model_input_units: units,
};
}
#[cfg(test)]
#[path = "standards_review_input_tests.rs"]
mod tests;