Skip to content

Commit 10244ce

Browse files
docs: remove obsolete markers, add CI drift test (#764)
- Remove {{ mcp_client_config }} (obsolete) section from docs/template-markers.md - Remove {{ copilot_version }} (removed) section from docs/template-markers.md - Add test_template_marker_docs_coverage in tests/compiler_tests.rs to catch future marker drift between src/data/*.yml and docs - Extend rust-tests.yml trigger paths to include docs/template-markers.md Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
1 parent 7b23404 commit 10244ce

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

.github/workflows/rust-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
paths:
66
- "src/**"
77
- "tests/**"
8+
- "docs/template-markers.md"
89
- "Cargo.toml"
910
- "Cargo.lock"
1011

tests/compiler_tests.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4505,3 +4505,86 @@ fn test_example_dogfood_failure_reporter_structure() {
45054505
"Example should target githubnext/ado-aw"
45064506
);
45074507
}
4508+
4509+
/// Test that every `{{ marker }}` used in `src/data/*.yml` has a corresponding
4510+
/// `## {{ marker }}` heading in `docs/template-markers.md`.
4511+
///
4512+
/// This is the CI/docs marker-drift guard: if a marker is added to a template
4513+
/// without updating the docs, this test fails.
4514+
#[test]
4515+
fn test_template_marker_docs_coverage() {
4516+
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
4517+
let data_dir = manifest_dir.join("src").join("data");
4518+
let docs_file = manifest_dir.join("docs").join("template-markers.md");
4519+
4520+
// --- collect markers from src/data/*.yml ---
4521+
let yml_entries = fs::read_dir(&data_dir)
4522+
.unwrap_or_else(|e| panic!("Cannot read {}: {e}", data_dir.display()));
4523+
4524+
let mut yml_markers: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
4525+
for entry in yml_entries.flatten() {
4526+
let path = entry.path();
4527+
if path.extension().and_then(|e| e.to_str()) != Some("yml") {
4528+
continue;
4529+
}
4530+
let content = fs::read_to_string(&path)
4531+
.unwrap_or_else(|e| panic!("Cannot read {}: {e}", path.display()));
4532+
for cap in regex_captures_markers(&content) {
4533+
yml_markers.insert(cap);
4534+
}
4535+
}
4536+
4537+
// --- collect documented marker headings from docs/template-markers.md ---
4538+
let docs = fs::read_to_string(&docs_file)
4539+
.unwrap_or_else(|e| panic!("Cannot read {}: {e}", docs_file.display()));
4540+
4541+
let mut documented: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
4542+
for line in docs.lines() {
4543+
// Match lines like: ## {{ marker_name }}
4544+
if let Some(rest) = line.strip_prefix("## {{ ")
4545+
&& let Some(name) = rest.split("}}").next()
4546+
{
4547+
documented.insert(name.trim().to_string());
4548+
}
4549+
}
4550+
4551+
// Every marker that appears in the yml files must have a docs heading.
4552+
let mut missing: Vec<String> = Vec::new();
4553+
for marker in &yml_markers {
4554+
if !documented.contains(marker.as_str()) {
4555+
missing.push(format!("{{{{ {marker} }}}}"));
4556+
}
4557+
}
4558+
4559+
assert!(
4560+
missing.is_empty(),
4561+
"The following template markers appear in src/data/*.yml but have no \
4562+
'## {{{{ marker }}}}' heading in docs/template-markers.md — add docs or \
4563+
update the marker name:\n {}",
4564+
missing.join("\n ")
4565+
);
4566+
}
4567+
4568+
/// Extract all `{{ name }}` marker names from `content` (excluding `${{ }}` ADO expressions).
4569+
fn regex_captures_markers(content: &str) -> Vec<String> {
4570+
let mut results = Vec::new();
4571+
let mut s: &str = content;
4572+
while let Some(start) = s.find("{{ ") {
4573+
// Skip ADO ${{ }} expressions
4574+
if start > 0 && s.as_bytes().get(start - 1) == Some(&b'$') {
4575+
s = &s[start + 3..];
4576+
continue;
4577+
}
4578+
let after = &s[start + 3..];
4579+
if let Some(end) = after.find("}}") {
4580+
let name = after[..end].trim().to_string();
4581+
if !name.is_empty() {
4582+
results.push(name);
4583+
}
4584+
s = &after[end + 2..];
4585+
} else {
4586+
break;
4587+
}
4588+
}
4589+
results
4590+
}

0 commit comments

Comments
 (0)