testing_conventions/
agents.rs1use std::fs;
5use std::io::ErrorKind;
6use std::path::Path;
7
8use anyhow::{anyhow, bail, Context};
9use sha2::{Digest, Sha256};
10
11const SCHEMA_VERSION: u32 = 1;
12const BEGIN_OPEN: &str = "<!-- testing-conventions:begin ";
13const END_MARKER: &str = "<!-- testing-conventions:end -->";
14
15const TEMPLATE: &str = "\
16## Testing conventions
17
18This repository enforces [testing-conventions](https://thekevinscott.github.io/testing-conventions/) in CI. The contract:
19
20- Start every change with the docs update and red integration/e2e tests; CI witnesses them fail before the implementation lands.
21- Colocate a unit test with every source file, and mock every collaborator in unit tests.
22- Clear the coverage floor and kill the mutants on every line you touch.
23- Ship each capability at parity across Python, TypeScript, and Rust.
24- An exemption carries a written reason showing the isolation techniques you tried; near-zero is the bar.
25
26Machine-readable contract: https://thekevinscott.github.io/testing-conventions/llms.txt
27";
28
29fn begin_marker() -> String {
31 let hex = Sha256::digest(TEMPLATE.as_bytes())
32 .iter()
33 .map(|b| format!("{b:02x}"))
34 .collect::<String>();
35 format!("{BEGIN_OPEN}v{SCHEMA_VERSION} hash={} -->", &hex[..12])
36}
37
38pub fn install(path: &Path) -> anyhow::Result<()> {
41 if path
42 .symlink_metadata()
43 .map(|meta| meta.file_type().is_symlink())
44 .unwrap_or(false)
45 {
46 bail!(
47 "{} is a symlink; refusing to write through it",
48 path.display()
49 );
50 }
51
52 let existing = match fs::read_to_string(path) {
53 Ok(text) => Some(text),
54 Err(err) if err.kind() == ErrorKind::NotFound => None,
55 Err(err) => return Err(err).with_context(|| format!("reading {}", path.display())),
56 };
57
58 let region = format!("{}\n{TEMPLATE}{END_MARKER}", begin_marker());
59 let new = match &existing {
60 None => format!("{region}\n"),
61 Some(text) => match text.find(BEGIN_OPEN) {
62 Some(start) => {
63 let rel_end = text[start..].find(END_MARKER).ok_or_else(|| {
64 anyhow!(
65 "{}: a `testing-conventions` begin marker has no matching end marker \
66 — refusing to write, as replacing a partial block would delete \
67 surrounding content. Restore the `{END_MARKER}` marker (or remove the \
68 stray begin marker) and re-run.",
69 path.display()
70 )
71 })?;
72 let end = start + rel_end + END_MARKER.len();
73 format!("{}{region}{}", &text[..start], &text[end..])
74 }
75 None => {
76 let mut out = text.clone();
77 if !out.is_empty() && !out.ends_with('\n') {
78 out.push('\n');
79 }
80 if !out.is_empty() {
81 out.push('\n');
82 }
83 format!("{out}{region}\n")
84 }
85 },
86 };
87
88 if existing.as_deref() == Some(new.as_str()) {
89 return Ok(());
90 }
91
92 let name = path
95 .file_name()
96 .with_context(|| format!("{} has no file name", path.display()))?;
97 let tmp = path
98 .parent()
99 .filter(|dir| !dir.as_os_str().is_empty())
100 .unwrap_or_else(|| Path::new("."))
101 .join(format!(
102 ".{}.tc-tmp-{}",
103 name.to_string_lossy(),
104 std::process::id()
105 ));
106 fs::write(&tmp, &new).with_context(|| format!("writing {}", tmp.display()))?;
107 fs::rename(&tmp, path)
108 .with_context(|| format!("renaming {} over {}", tmp.display(), path.display()))
109}