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