pub const SCC_SECTION_OPEN: &str = "<!-- SCC-SECTION";
pub const SCC_SECTION_CLOSE: &str = "<!-- /SCC-SECTION -->";
pub const SCC_OMP_SECTION_OPEN: &str = "<!-- SCC-OMP-SECTION -->";
pub const SCC_OMP_SECTION_CLOSE: &str = "<!-- /SCC-OMP-SECTION -->";
fn is_open_tag_boundary(rest: &str) -> bool {
matches!(
rest.chars().next(),
Some(' ') | Some('\t') | Some('\n') | Some('\r')
) || rest.starts_with("-->")
}
fn find_open_markers(existing: &str, open: &str) -> Vec<usize> {
let exact = open.ends_with("-->");
let mut from = 0;
let mut out = Vec::new();
while let Some(rel) = existing[from..].find(open) {
let start = from + rel;
if exact {
out.push(start);
from = start + open.len();
continue;
}
let rest = &existing[start + open.len()..];
if is_open_tag_boundary(rest) {
out.push(start);
}
from = start + open.len();
}
out
}
fn find_complete_pair(existing: &str, open: &str, close: &str) -> Option<(usize, usize)> {
let opens = find_open_markers(existing, open);
for (i, start) in opens.iter().copied().enumerate() {
let search_from = start + open.len();
let Some(rel) = existing[search_from..].find(close) else {
continue;
};
let end = search_from + rel;
let next_open = opens.get(i + 1).copied();
if next_open.map(|n| end < n).unwrap_or(true) {
return Some((start, end));
}
}
None
}
pub fn replace_scc_section(existing: &str, new_section: &str) -> String {
replace_scc_section_in(existing, new_section, SCC_SECTION_OPEN, SCC_SECTION_CLOSE)
}
pub fn replace_scc_section_in(
existing: &str,
new_section: &str,
open: &str,
close: &str,
) -> String {
let (before, after) = match find_complete_pair(existing, open, close) {
Some((start, end)) => {
let before = existing[..start].trim_end();
let after = existing[end + close.len()..].trim_start();
(before, after)
}
None => (existing.trim(), ""),
};
join_parts(before, new_section, after)
}
fn join_parts(before: &str, section: &str, after: &str) -> String {
let mut out = String::new();
if !before.is_empty() {
out.push_str(before);
out.push_str("\n\n");
}
out.push_str(section);
if !section.ends_with('\n') {
out.push('\n');
}
if !after.is_empty() {
if !out.ends_with('\n') {
out.push('\n');
}
out.push('\n');
out.push_str(after);
if !after.ends_with('\n') {
out.push('\n');
}
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AgentsTarget {
Omp,
Root,
}
#[derive(Debug, Clone)]
pub struct AgentsResolution {
pub target: AgentsTarget,
pub both_exist: bool,
}
pub fn resolve_omp_agents_path(root: &std::path::Path) -> AgentsResolution {
let omp = root.join(".omp").join("AGENTS.md");
let root_agents = root.join("AGENTS.md");
let omp_exists = omp.is_file();
let root_exists = root_agents.is_file();
if omp_exists {
AgentsResolution {
target: AgentsTarget::Omp,
both_exist: root_exists,
}
} else {
AgentsResolution {
target: AgentsTarget::Root,
both_exist: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn replace_preserves_text_after_closing_marker() {
let existing = "BEFORE\n<!-- SCC-SECTION -->\nold\n<!-- /SCC-SECTION -->\nAFTER USER\n";
let out = replace_scc_section(existing, "<!-- SCC-SECTION -->\nNEW\n<!-- /SCC-SECTION -->\n");
assert!(out.starts_with("BEFORE"), "{out}");
assert!(out.contains("NEW"), "{out}");
assert!(out.contains("AFTER USER"), "later user text must survive: {out}");
assert!(!out.contains("old"), "{out}");
}
#[test]
fn replace_appends_when_no_markers() {
let out = replace_scc_section("USER\n", "<!-- SCC-SECTION -->\nNEW\n<!-- /SCC-SECTION -->\n");
assert!(out.starts_with("USER"), "{out}");
assert!(out.contains("NEW"), "{out}");
}
#[test]
fn replace_ignores_lookalike_open_prefix_and_preserves_later_text() {
let existing = "USER\n<!-- SCC-SECTION-NOTES -->\nkeep later\n";
let out = replace_scc_section(existing, "<!-- SCC-SECTION -->\nNEW\n<!-- /SCC-SECTION -->\n");
assert!(out.contains("<!-- SCC-SECTION-NOTES -->"), "{out}");
assert!(out.contains("keep later"), "later user text must survive a lookalike tag: {out}");
assert!(out.contains("NEW"), "{out}");
}
#[test]
fn replace_unpaired_open_preserves_later_user_content() {
let existing = "BEFORE\n<!-- SCC-SECTION -->\nbroken managed without close\nKEEP AFTER\n";
let out = replace_scc_section(existing, "<!-- SCC-SECTION -->\nNEW\n<!-- /SCC-SECTION -->\n");
assert!(out.contains("KEEP AFTER"), "unpaired open must not drop the tail: {out}");
assert!(out.contains("NEW"), "{out}");
}
#[test]
fn replace_twice_on_unpaired_open_still_preserves_tail() {
let existing = "BEFORE\n<!-- SCC-SECTION -->\nbroken managed without close\nKEEP AFTER\n";
let section = "<!-- SCC-SECTION -->\nNEW\n<!-- /SCC-SECTION -->\n";
let once = replace_scc_section(existing, section);
let twice = replace_scc_section(&once, "<!-- SCC-SECTION -->\nNEWER\n<!-- /SCC-SECTION -->\n");
assert!(
twice.contains("KEEP AFTER"),
"second replace must not pair the orphan open with the later close: {twice}"
);
assert!(twice.contains("NEWER"), "{twice}");
assert!(!twice.contains("\nNEW\n"), "replaced body must not keep the first NEW: {twice}");
}
}