vtcode_core/prompts/
sections.rs1#[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}