use anyhow::{bail, Result};
use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Component {
pub name: String,
pub attrs: HashMap<String, String>,
pub open_start: usize,
pub open_end: usize,
pub close_start: usize,
pub close_end: usize,
}
impl Component {
#[allow(dead_code)] pub fn content<'a>(&self, doc: &'a str) -> &'a str {
&doc[self.open_end..self.close_start]
}
pub fn patch_mode(&self) -> Option<&str> {
self.attrs.get("patch").map(|s| s.as_str())
.or_else(|| self.attrs.get("mode").map(|s| s.as_str()))
}
pub fn replace_content(&self, doc: &str, new_content: &str) -> String {
let mut result = String::with_capacity(doc.len() + new_content.len());
result.push_str(&doc[..self.open_end]);
result.push_str(new_content);
result.push_str(&doc[self.close_start..]);
result
}
pub fn append_with_caret(&self, doc: &str, content: &str, caret_offset: Option<usize>) -> String {
let existing = &doc[self.open_end..self.close_start];
if let Some(caret) = caret_offset {
if caret > self.open_end && caret <= self.close_start {
let insert_at = doc[..caret].rfind('\n')
.map(|i| i + 1)
.unwrap_or(self.open_end);
let insert_at = insert_at.max(self.open_end);
let mut result = String::with_capacity(doc.len() + content.len() + 1);
result.push_str(&doc[..insert_at]);
result.push_str(content.trim_end());
result.push('\n');
result.push_str(&doc[insert_at..]);
return result;
}
}
let mut result = String::with_capacity(doc.len() + content.len() + 1);
result.push_str(&doc[..self.open_end]);
result.push_str(existing.trim_end());
result.push('\n');
result.push_str(content.trim_end());
result.push('\n');
result.push_str(&doc[self.close_start..]);
result
}
pub fn append_with_boundary(&self, doc: &str, content: &str, boundary_id: &str) -> String {
let boundary_marker = format!("<!-- agent:boundary:{} -->", boundary_id);
let content_region = &doc[self.open_end..self.close_start];
let code_ranges = find_code_ranges(doc);
let mut search_from = 0;
let found_pos = loop {
match content_region[search_from..].find(&boundary_marker) {
Some(rel_pos) => {
let abs_pos = self.open_end + search_from + rel_pos;
if code_ranges.iter().any(|&(cs, ce)| abs_pos >= cs && abs_pos < ce) {
search_from += rel_pos + boundary_marker.len();
continue;
}
break Some(abs_pos);
}
None => break None,
}
};
if let Some(abs_pos) = found_pos {
let line_start = doc[..abs_pos]
.rfind('\n')
.map(|i| i + 1)
.unwrap_or(self.open_end)
.max(self.open_end);
let marker_end = abs_pos + boundary_marker.len();
let line_end = if marker_end < self.close_start
&& doc.as_bytes().get(marker_end) == Some(&b'\n')
{
marker_end + 1
} else {
marker_end
};
let line_end = line_end.min(self.close_start);
let new_id = crate::new_boundary_id();
let new_marker = crate::format_boundary_marker(&new_id);
let mut result = String::with_capacity(doc.len() + content.len() + new_marker.len());
result.push_str(&doc[..line_start]);
result.push_str(content.trim_end());
result.push('\n');
result.push_str(&new_marker);
result.push('\n');
result.push_str(&doc[line_end..]);
return result;
}
self.append_with_caret(doc, content, None)
}
}
fn is_valid_name(name: &str) -> bool {
if name.is_empty() {
return false;
}
let first = name.as_bytes()[0];
if !first.is_ascii_alphanumeric() {
return false;
}
name.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'-')
}
pub fn is_agent_marker(comment_text: &str) -> bool {
let trimmed = comment_text.trim();
if let Some(rest) = trimmed.strip_prefix("/agent:") {
is_valid_name(rest)
} else if let Some(rest) = trimmed.strip_prefix("agent:") {
let name_part = rest.split_whitespace().next().unwrap_or("");
is_valid_name(name_part)
} else {
false
}
}
pub fn parse_attrs(attr_text: &str) -> HashMap<String, String> {
let mut attrs = HashMap::new();
for token in attr_text.split_whitespace() {
if let Some((key, value)) = token.split_once('=')
&& !key.is_empty()
&& !value.is_empty()
{
attrs.insert(key.to_string(), value.to_string());
}
}
attrs
}
pub fn find_code_ranges(doc: &str) -> Vec<(usize, usize)> {
let t = std::time::Instant::now();
let mut ranges = Vec::new();
let parser = Parser::new_ext(doc, Options::empty());
let mut iter = parser.into_offset_iter();
while let Some((event, range)) = iter.next() {
match event {
Event::Code(_) => {
ranges.push((range.start, range.end));
}
Event::Start(Tag::CodeBlock(_)) => {
let block_start = range.start;
let mut block_end = range.end;
for (inner_event, inner_range) in iter.by_ref() {
block_end = inner_range.end;
if matches!(inner_event, Event::End(TagEnd::CodeBlock)) {
break;
}
}
ranges.push((block_start, block_end));
}
_ => {}
}
}
let elapsed = t.elapsed().as_millis();
if elapsed > 0 {
eprintln!("[perf] find_code_ranges: {}ms", elapsed);
}
ranges
}
pub fn parse(doc: &str) -> Result<Vec<Component>> {
let bytes = doc.as_bytes();
let len = bytes.len();
let code_ranges = find_code_ranges(doc);
let mut templates: Vec<Component> = Vec::new();
let mut stack: Vec<(String, HashMap<String, String>, usize, usize)> = Vec::new();
let mut pos = 0;
while pos + 4 <= len {
if &bytes[pos..pos + 4] != b"<!--" {
pos += 1;
continue;
}
if code_ranges.iter().any(|&(start, end)| pos >= start && pos < end) {
pos += 4;
continue;
}
let marker_start = pos;
let close = match find_comment_end(bytes, pos + 4) {
Some(c) => c,
None => {
pos += 4;
continue;
}
};
let inner = &doc[marker_start + 4..close - 3]; let trimmed = inner.trim();
let mut marker_end = close;
if marker_end < len && bytes[marker_end] == b'\n' {
marker_end += 1;
}
if let Some(name) = trimmed.strip_prefix("/agent:") {
if !is_valid_name(name) {
bail!("invalid component name: '{}'", name);
}
match stack.pop() {
Some((open_name, open_attrs, open_start, open_end)) => {
if open_name != name {
bail!(
"mismatched component: opened '{}' but closed '{}'",
open_name,
name
);
}
templates.push(Component {
name: name.to_string(),
attrs: open_attrs,
open_start,
open_end,
close_start: marker_start,
close_end: marker_end,
});
}
None => bail!("closing marker <!-- /agent:{} --> without matching open", name),
}
} else if let Some(rest) = trimmed.strip_prefix("agent:") {
if rest.starts_with("boundary:") {
pos = close;
continue;
}
let mut parts = rest.splitn(2, |c: char| c.is_whitespace());
let name = parts.next().unwrap_or("");
let attr_text = parts.next().unwrap_or("");
if !is_valid_name(name) {
bail!("invalid component name: '{}'", name);
}
let attrs = parse_attrs(attr_text);
stack.push((name.to_string(), attrs, marker_start, marker_end));
}
pos = close;
}
if let Some((name, _, _, _)) = stack.last() {
bail!(
"unclosed component: <!-- agent:{} --> without matching close",
name
);
}
templates.sort_by_key(|t| t.open_start);
Ok(templates)
}
pub(crate) fn find_comment_end(bytes: &[u8], start: usize) -> Option<usize> {
let len = bytes.len();
let mut i = start;
while i + 3 <= len {
if &bytes[i..i + 3] == b"-->" {
return Some(i + 3);
}
i += 1;
}
None
}
pub fn strip_comments(content: &str) -> String {
let code_ranges = find_code_ranges(content);
let in_code = |pos: usize| code_ranges.iter().any(|&(start, end)| pos >= start && pos < end);
let mut result = String::with_capacity(content.len());
let bytes = content.as_bytes();
let len = bytes.len();
let mut pos = 0;
while pos < len {
if bytes[pos] == b'['
&& !in_code(pos)
&& is_line_start_at(bytes, pos)
&& let Some(end) = match_link_ref_comment_at(bytes, pos)
{
pos = end;
continue;
}
if pos + 4 <= len
&& &bytes[pos..pos + 4] == b"<!--"
&& !in_code(pos)
&& let Some((end, inner)) = match_html_comment_at(content, pos)
{
if is_agent_marker(inner) {
result.push_str(&content[pos..end]);
pos = end;
} else {
let mut skip_to = end;
if is_line_start_at(bytes, pos) && skip_to < len && bytes[skip_to] == b'\n' {
skip_to += 1;
}
pos = skip_to;
}
continue;
}
result.push(content[pos..].chars().next().unwrap());
pos += content[pos..].chars().next().unwrap().len_utf8();
}
result
}
fn is_line_start_at(bytes: &[u8], pos: usize) -> bool {
pos == 0 || bytes[pos - 1] == b'\n'
}
fn match_link_ref_comment_at(bytes: &[u8], pos: usize) -> Option<usize> {
let prefix = b"[//]: # (";
let len = bytes.len();
if pos + prefix.len() > len || &bytes[pos..pos + prefix.len()] != prefix {
return None;
}
let mut i = pos + prefix.len();
while i < len && bytes[i] != b')' && bytes[i] != b'\n' {
i += 1;
}
if i < len && bytes[i] == b')' {
i += 1;
if i < len && bytes[i] == b'\n' {
i += 1;
}
Some(i)
} else {
None
}
}
fn match_html_comment_at(content: &str, pos: usize) -> Option<(usize, &str)> {
let bytes = content.as_bytes();
let len = bytes.len();
let mut i = pos + 4;
while i + 3 <= len {
if &bytes[i..i + 3] == b"-->" {
let inner = &content[pos + 4..i];
return Some((i + 3, inner));
}
i += 1;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_range() {
let doc = "before\n<!-- agent:status -->\nHello\n<!-- /agent:status -->\nafter\n";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].name, "status");
assert_eq!(ranges[0].content(doc), "Hello\n");
}
#[test]
fn nested_ranges() {
let doc = "\
<!-- agent:outer -->
<!-- agent:inner -->
content
<!-- /agent:inner -->
<!-- /agent:outer -->
";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 2);
assert_eq!(ranges[0].name, "outer");
assert_eq!(ranges[1].name, "inner");
assert_eq!(ranges[1].content(doc), "content\n");
}
#[test]
fn siblings() {
let doc = "\
<!-- agent:a -->
alpha
<!-- /agent:a -->
<!-- agent:b -->
beta
<!-- /agent:b -->
";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 2);
assert_eq!(ranges[0].name, "a");
assert_eq!(ranges[0].content(doc), "alpha\n");
assert_eq!(ranges[1].name, "b");
assert_eq!(ranges[1].content(doc), "beta\n");
}
#[test]
fn no_ranges() {
let doc = "# Just a document\n\nWith no range templates.\n";
let ranges = parse(doc).unwrap();
assert!(ranges.is_empty());
}
#[test]
fn unmatched_open_error() {
let doc = "<!-- agent:orphan -->\nContent\n";
let err = parse(doc).unwrap_err();
assert!(err.to_string().contains("unclosed component"));
}
#[test]
fn unmatched_close_error() {
let doc = "Content\n<!-- /agent:orphan -->\n";
let err = parse(doc).unwrap_err();
assert!(err.to_string().contains("without matching open"));
}
#[test]
fn mismatched_names_error() {
let doc = "<!-- agent:foo -->\n<!-- /agent:bar -->\n";
let err = parse(doc).unwrap_err();
assert!(err.to_string().contains("mismatched"));
}
#[test]
fn invalid_name() {
let doc = "<!-- agent:-bad -->\n<!-- /agent:-bad -->\n";
let err = parse(doc).unwrap_err();
assert!(err.to_string().contains("invalid component name"));
}
#[test]
fn name_validation() {
assert!(is_valid_name("status"));
assert!(is_valid_name("my-section"));
assert!(is_valid_name("a1"));
assert!(is_valid_name("A"));
assert!(!is_valid_name(""));
assert!(!is_valid_name("-bad"));
assert!(!is_valid_name("has space"));
assert!(!is_valid_name("has_underscore"));
}
#[test]
fn content_extraction() {
let doc = "<!-- agent:x -->\nfoo\nbar\n<!-- /agent:x -->\n";
let ranges = parse(doc).unwrap();
assert_eq!(ranges[0].content(doc), "foo\nbar\n");
}
#[test]
fn replace_roundtrip() {
let doc = "before\n<!-- agent:s -->\nold\n<!-- /agent:s -->\nafter\n";
let ranges = parse(doc).unwrap();
let new_doc = ranges[0].replace_content(doc, "new\n");
assert_eq!(
new_doc,
"before\n<!-- agent:s -->\nnew\n<!-- /agent:s -->\nafter\n"
);
let ranges2 = parse(&new_doc).unwrap();
assert_eq!(ranges2.len(), 1);
assert_eq!(ranges2[0].content(&new_doc), "new\n");
}
#[test]
fn is_agent_marker_yes() {
assert!(is_agent_marker(" agent:status "));
assert!(is_agent_marker("/agent:status"));
assert!(is_agent_marker("agent:my-thing"));
assert!(is_agent_marker(" /agent:A1 "));
}
#[test]
fn is_agent_marker_no() {
assert!(!is_agent_marker("just a comment"));
assert!(!is_agent_marker("agent:"));
assert!(!is_agent_marker("/agent:"));
assert!(!is_agent_marker("agent:-bad"));
assert!(!is_agent_marker("some agent:fake stuff"));
}
#[test]
fn regular_comments_ignored() {
let doc = "<!-- just a comment -->\n<!-- agent:x -->\ndata\n<!-- /agent:x -->\n";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].name, "x");
}
#[test]
fn multiline_comment_ignored() {
let doc = "\
<!--
multi
line
comment
-->
<!-- agent:s -->
content
<!-- /agent:s -->
";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].name, "s");
}
#[test]
fn empty_content() {
let doc = "<!-- agent:empty --><!-- /agent:empty -->\n";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].content(doc), "");
}
#[test]
fn markers_in_fenced_code_block_ignored() {
let doc = "\
<!-- agent:real -->
content
<!-- /agent:real -->
```markdown
<!-- agent:fake -->
this is just an example
<!-- /agent:fake -->
```
";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].name, "real");
}
#[test]
fn markers_in_inline_code_ignored() {
let doc = "\
Use `<!-- agent:example -->` markers for components.
<!-- agent:real -->
content
<!-- /agent:real -->
";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].name, "real");
}
#[test]
fn markers_in_tilde_fence_ignored() {
let doc = "\
<!-- agent:x -->
data
<!-- /agent:x -->
~~~
<!-- agent:y -->
example
<!-- /agent:y -->
~~~
";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].name, "x");
}
#[test]
fn markers_in_indented_fenced_code_block_ignored() {
let doc = "\
<!-- agent:exchange -->
Content here.
<!-- /agent:exchange -->
```markdown
<!-- agent:fake -->
demo without closing tag
```
";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].name, "exchange");
}
#[test]
fn indented_fence_inside_component_ignored() {
let doc = "\
<!-- agent:exchange -->
Here's how to set up:
```markdown
<!-- agent:status -->
Your status here
```
Done explaining.
<!-- /agent:exchange -->
";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].name, "exchange");
}
#[test]
fn deeply_indented_fence_ignored() {
let doc = "\
<!-- agent:x -->
ok
<!-- /agent:x -->
```
<!-- agent:y -->
inside fence
```
";
let ranges = parse(doc).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].name, "x");
}
#[test]
fn indented_fence_code_ranges_detected() {
let doc = "before\n ```\n code\n ```\nafter\n";
let ranges = find_code_ranges(doc);
assert_eq!(ranges.len(), 1);
assert!(doc[ranges[0].0..ranges[0].1].contains("code"));
}
#[test]
fn code_ranges_detected() {
let doc = "before\n```\ncode\n```\nafter `inline` end\n";
let ranges = find_code_ranges(doc);
assert_eq!(ranges.len(), 2);
assert!(doc[ranges[0].0..ranges[0].1].contains("code"));
assert!(doc[ranges[1].0..ranges[1].1].contains("inline"));
}
#[test]
fn code_ranges_double_backtick() {
let doc = "text `` `<!--` `` more\n";
let ranges = find_code_ranges(doc);
assert_eq!(ranges.len(), 1);
let span = &doc[ranges[0].0..ranges[0].1];
assert!(span.contains("<!--"), "double-backtick span should contain <!--: {:?}", span);
}
#[test]
fn code_ranges_double_backtick_does_not_match_single() {
let doc = "text `` foo ` bar `` end\n";
let ranges = find_code_ranges(doc);
assert_eq!(ranges.len(), 1);
let span = &doc[ranges[0].0..ranges[0].1];
assert_eq!(span, "`` foo ` bar ``");
}
#[test]
fn double_backtick_comment_before_agent_marker() {
let doc = "\
<!-- agent:exchange -->\n\
text `` `<!--` `` description\n\
new content here\n\
<!-- /agent:exchange -->\n";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "exchange");
assert!(components[0].content(doc).contains("new content here"));
}
#[test]
fn parse_component_with_mode_attr() {
let doc = "<!-- agent:exchange mode=append -->\nContent\n<!-- /agent:exchange -->\n";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "exchange");
assert_eq!(components[0].attrs.get("mode").map(|s| s.as_str()), Some("append"));
assert_eq!(components[0].content(doc), "Content\n");
}
#[test]
fn parse_component_with_multiple_attrs() {
let doc = "<!-- agent:log mode=prepend timestamp=true -->\nData\n<!-- /agent:log -->\n";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "log");
assert_eq!(components[0].attrs.get("mode").map(|s| s.as_str()), Some("prepend"));
assert_eq!(components[0].attrs.get("timestamp").map(|s| s.as_str()), Some("true"));
}
#[test]
fn parse_component_no_attrs_backward_compat() {
let doc = "<!-- agent:status -->\nOK\n<!-- /agent:status -->\n";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "status");
assert!(components[0].attrs.is_empty());
}
#[test]
fn is_agent_marker_with_attrs() {
assert!(is_agent_marker(" agent:exchange mode=append "));
assert!(is_agent_marker("agent:status mode=replace"));
assert!(is_agent_marker("agent:log mode=prepend timestamp=true"));
}
#[test]
fn closing_tag_unchanged_with_attrs() {
let doc = "<!-- agent:status mode=replace -->\n- [x] Done\n<!-- /agent:status -->\n";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
let new_doc = components[0].replace_content(doc, "- [ ] Todo\n");
assert!(new_doc.contains("<!-- agent:status mode=replace -->"));
assert!(new_doc.contains("<!-- /agent:status -->"));
assert!(new_doc.contains("- [ ] Todo"));
}
#[test]
fn parse_component_with_patch_attr() {
let doc = "<!-- agent:exchange patch=append -->\nContent\n<!-- /agent:exchange -->\n";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "exchange");
assert_eq!(components[0].patch_mode(), Some("append"));
assert_eq!(components[0].content(doc), "Content\n");
}
#[test]
fn patch_attr_takes_precedence_over_mode() {
let doc = "<!-- agent:exchange patch=replace mode=append -->\nContent\n<!-- /agent:exchange -->\n";
let components = parse(doc).unwrap();
assert_eq!(components[0].patch_mode(), Some("replace"));
}
#[test]
fn mode_attr_backward_compat() {
let doc = "<!-- agent:exchange mode=append -->\nContent\n<!-- /agent:exchange -->\n";
let components = parse(doc).unwrap();
assert_eq!(components[0].patch_mode(), Some("append"));
}
#[test]
fn no_patch_or_mode_attr() {
let doc = "<!-- agent:exchange -->\nContent\n<!-- /agent:exchange -->\n";
let components = parse(doc).unwrap();
assert_eq!(components[0].patch_mode(), None);
}
#[test]
fn single_backtick_component_tag_ignored() {
let doc = "\
Use `<!-- agent:pending patch=replace -->` to mark pending sections.
<!-- agent:real -->
content
<!-- /agent:real -->
";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "real");
}
#[test]
fn double_backtick_component_tag_ignored() {
let doc = "\
Use ``<!-- agent:pending patch=replace -->`` to mark pending sections.
<!-- agent:real -->
content
<!-- /agent:real -->
";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "real");
}
#[test]
fn component_tags_not_in_backticks_still_work() {
let doc = "\
<!-- agent:a -->
alpha
<!-- /agent:a -->
<!-- agent:b patch=append -->
beta
<!-- /agent:b -->
";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 2);
assert_eq!(components[0].name, "a");
assert_eq!(components[1].name, "b");
assert_eq!(components[1].patch_mode(), Some("append"));
}
#[test]
fn mixed_backtick_and_real_tags() {
let doc = "\
Here is an example: `<!-- agent:fake -->` and ``<!-- /agent:fake -->``.
<!-- agent:real -->
real content
<!-- /agent:real -->
Another example: `<!-- agent:also-fake patch=replace -->` is just documentation.
";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "real");
assert_eq!(components[0].content(doc), "real content\n");
}
#[test]
fn inline_code_mid_line_with_surrounding_text_ignored() {
let doc = "\
Wrap markers like `<!-- agent:status -->` in backticks to show them literally.
<!-- agent:real -->
actual content
<!-- /agent:real -->
";
let components = parse(doc).unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "real");
assert_eq!(components[0].content(doc), "actual content\n");
}
#[test]
fn parse_attrs_unit() {
let attrs = parse_attrs("mode=append");
assert_eq!(attrs.get("mode").map(|s| s.as_str()), Some("append"));
let attrs = parse_attrs("mode=replace timestamp=true");
assert_eq!(attrs.len(), 2);
let attrs = parse_attrs("");
assert!(attrs.is_empty());
let attrs = parse_attrs("mode=append broken novalue=");
assert_eq!(attrs.len(), 1);
assert_eq!(attrs.get("mode").map(|s| s.as_str()), Some("append"));
}
#[test]
fn append_with_boundary_skips_code_block() {
let boundary_id = "real-uuid";
let doc = format!(
"<!-- agent:exchange patch=append -->\n\
user prompt\n\
```\n\
<!-- agent:boundary:{boundary_id} -->\n\
```\n\
more user text\n\
<!-- agent:boundary:{boundary_id} -->\n\
<!-- /agent:exchange -->\n"
);
let components = parse(&doc).unwrap();
let comp = &components[0];
let result = comp.append_with_boundary(&doc, "### Re: Response\n\nContent here.", boundary_id);
assert!(result.contains("### Re: Response"));
assert!(result.contains("more user text"));
assert!(result.contains(&format!("<!-- agent:boundary:{boundary_id} -->\n```")));
assert!(!result.contains(&format!("more user text\n<!-- agent:boundary:{boundary_id} -->\n<!-- /agent:exchange -->")));
}
#[test]
fn append_with_boundary_no_code_block() {
let boundary_id = "simple-uuid";
let doc = format!(
"<!-- agent:exchange patch=append -->\n\
user prompt\n\
<!-- agent:boundary:{boundary_id} -->\n\
<!-- /agent:exchange -->\n"
);
let components = parse(&doc).unwrap();
let comp = &components[0];
let result = comp.append_with_boundary(&doc, "### Re: Answer\n\nDone.", boundary_id);
assert!(result.contains("### Re: Answer"));
assert!(result.contains("user prompt"));
assert!(!result.contains(&format!("agent:boundary:{boundary_id}")));
assert!(result.contains("agent:boundary:"));
}
#[test]
fn strip_comments_removes_html_comment() {
let result = strip_comments("before\n<!-- a comment -->\nafter\n");
assert_eq!(result, "before\nafter\n");
}
#[test]
fn strip_comments_preserves_agent_markers() {
let input = "text\n<!-- agent:status -->\ncontent\n<!-- /agent:status -->\n";
let result = strip_comments(input);
assert!(result.contains("<!-- agent:status -->"));
assert!(result.contains("<!-- /agent:status -->"));
}
#[test]
fn strip_comments_removes_link_ref() {
let result = strip_comments("[//]: # (hidden note)\nvisible\n");
assert_eq!(result, "visible\n");
}
}