Skip to main content

vtcode_core/prompts/
sections.rs

1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2pub(crate) enum SectionBoundaryMode {
3    BracketOnly,
4    BracketOrMarkdown,
5}
6
7pub(crate) fn find_prompt_section_bounds(
8    prompt: &str,
9    section_header: &str,
10    boundary_mode: SectionBoundaryMode,
11) -> Option<(usize, usize)> {
12    fn is_section_header_line(line: &str, boundary_mode: SectionBoundaryMode) -> bool {
13        let trimmed = line.trim();
14        (trimmed.starts_with('[') && trimmed.ends_with(']'))
15            || matches!(boundary_mode, SectionBoundaryMode::BracketOrMarkdown)
16                && (trimmed.starts_with("# ") || trimmed.starts_with("## "))
17    }
18
19    let mut offset = 0usize;
20    let mut section_start = None;
21
22    for line in prompt.split_inclusive('\n') {
23        let trimmed = line.trim();
24        if section_start.is_none() && trimmed == section_header {
25            section_start = Some(offset);
26            offset += line.len();
27            continue;
28        }
29
30        if let Some(start) = section_start
31            && is_section_header_line(line, boundary_mode)
32        {
33            return Some((start, offset));
34        }
35
36        offset += line.len();
37    }
38
39    section_start.map(|start| (start, prompt.len()))
40}
41
42#[cfg(test)]
43mod tests {
44    use super::{SectionBoundaryMode, find_prompt_section_bounds};
45
46    #[test]
47    fn markdown_boundary_mode_stops_before_next_markdown_heading() {
48        let prompt = "Base\n## Active Tools\n- a\n## Environment\n- b\n";
49        let bounds = find_prompt_section_bounds(prompt, "## Active Tools", SectionBoundaryMode::BracketOrMarkdown)
50            .expect("section bounds");
51
52        assert_eq!(&prompt[bounds.0..bounds.1], "## Active Tools\n- a\n");
53    }
54
55    #[test]
56    fn bracket_only_mode_ignores_markdown_headings() {
57        let prompt = "Base\n[Harness Limits]\n- a\n# Planning\n- b\n## Environment\n- c\n[Next Section]\n- d\n";
58        let bounds = find_prompt_section_bounds(prompt, "[Harness Limits]", SectionBoundaryMode::BracketOnly)
59            .expect("section bounds");
60
61        assert_eq!(&prompt[bounds.0..bounds.1], "[Harness Limits]\n- a\n# Planning\n- b\n## Environment\n- c\n");
62    }
63
64    #[test]
65    fn active_tools_boundary_preserves_following_planning_contract() {
66        let prompt = "Base\n## Active Tools\n- old\n# PLANNING WORKFLOW (READ-ONLY)\nKeep the plan.\n";
67        let (start, end) =
68            find_prompt_section_bounds(prompt, "## Active Tools", SectionBoundaryMode::BracketOrMarkdown)
69                .expect("section bounds");
70        let mut replaced = prompt.to_string();
71        replaced.replace_range(start..end, "## Active Tools\n- new\n");
72        assert_eq!(replaced, "Base\n## Active Tools\n- new\n# PLANNING WORKFLOW (READ-ONLY)\nKeep the plan.\n");
73    }
74}