use std::path::Path;
const PROTECTED_BRAIN_FILES: &[&str] = &[
"SOUL.md",
"USER.md",
"AGENTS.md",
"TOOLS.md",
"CODE.md",
"SECURITY.md",
"MEMORY.md",
"BOOT.md",
"IDENTITY.md",
];
pub fn is_protected_brain_file(name: &str) -> bool {
PROTECTED_BRAIN_FILES
.iter()
.any(|p| p.eq_ignore_ascii_case(name))
}
pub fn is_protected_path(path: &Path) -> bool {
path.file_name()
.and_then(|n| n.to_str())
.map(is_protected_brain_file)
.unwrap_or(false)
}
pub fn backup_before_write(path: &Path) -> std::io::Result<Option<std::path::PathBuf>> {
if !path.exists() {
return Ok(None);
}
let stamp = chrono::Utc::now().format("%Y-%m-%dT%H%M%S");
let mut backup = path.as_os_str().to_owned();
backup.push(format!(".{stamp}.bak"));
let backup = std::path::PathBuf::from(backup);
std::fs::copy(path, &backup)?;
Ok(Some(backup))
}
#[derive(Debug, PartialEq, Eq)]
pub enum ShrinkCheck {
Allowed,
Rejected { message: String },
}
pub fn check_no_shrink(
path: &Path,
existing: &str,
updated: &str,
dedup_intent: bool,
cleanup_intent: bool,
) -> ShrinkCheck {
if !is_protected_path(path) {
return ShrinkCheck::Allowed;
}
if updated.len() >= existing.len() {
return ShrinkCheck::Allowed;
}
if cleanup_intent {
return ShrinkCheck::Allowed;
}
let removed_bytes = existing.len().saturating_sub(updated.len());
let label = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("brain file");
if dedup_intent && shrink_only_drops_duplicates(existing, updated) {
return ShrinkCheck::Allowed;
}
let hint = if dedup_intent {
" (dedup_intent was set, but the bytes removed do not all reappear in the result \
— that's not deduplication, that's deletion)"
} else {
""
};
ShrinkCheck::Rejected {
message: format!(
"Refusing to shrink protected brain file {label} by {removed_bytes} bytes. \
Brain files are append-only — use action='apply' / operation='append' to \
add new content. Removals are only allowed for genuine deduplication, and \
must opt in via dedup_intent=true with a result that still contains every \
unique line of the original.{hint}"
),
}
}
#[derive(Debug)]
pub enum AppendDedup {
AllNew,
Filtered {
filtered_content: String,
skipped_paragraphs: usize,
},
AllDuplicate,
}
fn split_paragraphs(text: &str) -> Vec<String> {
let mut paragraphs = Vec::new();
let mut current = String::new();
for line in text.lines() {
if line.trim().is_empty() {
if !current.is_empty() {
paragraphs.push(current.trim_end().to_string());
current.clear();
}
} else {
if !current.is_empty() {
current.push('\n');
}
current.push_str(line);
}
}
if !current.is_empty() {
paragraphs.push(current.trim_end().to_string());
}
paragraphs
}
fn paragraph_exists(paragraph: &str, existing: &str) -> bool {
let trimmed = paragraph.trim();
if trimmed.is_empty() {
return true;
}
if existing.contains(trimmed) {
return true;
}
if let Some(first_line) = trimmed.lines().next() {
let header = first_line.trim();
if (header.starts_with("## ") || header.starts_with("### "))
&& existing.lines().any(|l| l.trim() == header)
{
return true;
}
}
let existing_lines: std::collections::HashSet<&str> = existing.lines().map(str::trim).collect();
let para_lines: Vec<&str> = trimmed.lines().filter(|l| !l.trim().is_empty()).collect();
if para_lines.len() >= 3 {
let overlap = para_lines
.iter()
.filter(|l| existing_lines.contains(l.trim()))
.count();
let ratio = overlap as f64 / para_lines.len() as f64;
if ratio > 0.7 {
return true;
}
}
false
}
pub fn filter_duplicate_append(existing: &str, new_content: &str) -> AppendDedup {
let new_trimmed = new_content.trim();
if new_trimmed.is_empty() {
return AppendDedup::AllDuplicate;
}
if existing.contains(new_trimmed) {
return AppendDedup::AllDuplicate;
}
let paragraphs = split_paragraphs(new_trimmed);
if paragraphs.is_empty() {
return AppendDedup::AllDuplicate;
}
let mut new_paragraphs = Vec::new();
let mut skipped = 0;
for para in ¶graphs {
if paragraph_exists(para, existing) {
skipped += 1;
} else {
new_paragraphs.push(para.clone());
}
}
if new_paragraphs.is_empty() {
return AppendDedup::AllDuplicate;
}
if skipped == 0 {
return AppendDedup::AllNew;
}
AppendDedup::Filtered {
filtered_content: new_paragraphs.join("\n\n"),
skipped_paragraphs: skipped,
}
}
pub fn is_duplicate_append(existing: &str, new_content: &str) -> bool {
matches!(
filter_duplicate_append(existing, new_content),
AppendDedup::AllDuplicate
)
}
fn shrink_only_drops_duplicates(existing: &str, updated: &str) -> bool {
let updated_lines: std::collections::HashSet<&str> =
updated.lines().map(str::trim_end).collect();
for line in existing.lines() {
let trimmed = line.trim_end();
if trimmed.is_empty() {
continue;
}
if !updated_lines.contains(trimmed) {
return false;
}
}
true
}