use sha2::{Digest, Sha256};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HeaderStyle {
Shell,
Toml,
YamlFrontmatter,
HtmlComment,
None,
}
fn generated_line(version: &str) -> String {
format!("generated by ctx v{version} — regenerate with 'ctx harness init'")
}
fn is_checksum_line(line: &str) -> bool {
let trimmed = line.trim();
let body = trimmed
.strip_prefix("<!--")
.or_else(|| trimmed.strip_prefix("//"))
.or_else(|| trimmed.strip_prefix('#'))
.unwrap_or(trimmed);
body.trim_start().starts_with("ctx:checksum")
}
pub fn content_checksum(bytes: &[u8]) -> String {
let mut hasher = Sha256::new();
match std::str::from_utf8(bytes) {
Ok(text) => {
for line in text.split_inclusive('\n') {
if !is_checksum_line(line) {
hasher.update(line.as_bytes());
}
}
}
Err(_) => hasher.update(bytes),
}
format!("{:x}", hasher.finalize())
}
pub fn recorded_checksum(content: &str) -> Option<String> {
for line in content.lines() {
if is_checksum_line(line) {
let after = line.split("ctx:checksum").nth(1)?;
let token = after.split_whitespace().next()?;
let hex = token.strip_prefix("sha256:").unwrap_or(token);
let hex = hex.trim_end_matches("-->").trim();
if !hex.is_empty() {
return Some(hex.to_string());
}
}
}
None
}
pub fn generated_version(content: &str) -> Option<String> {
for line in content.lines() {
if let Some(after) = line.split("generated by ctx v").nth(1) {
let version: String = after
.chars()
.take_while(|c| c.is_ascii_digit() || *c == '.' || *c == '-' || c.is_alphanumeric())
.collect();
if !version.is_empty() {
return Some(version);
}
}
}
None
}
pub fn finalize(content: &str, style: HeaderStyle, version: &str) -> String {
const PLACEHOLDER: &str = "{{CHECKSUM}}";
let headered = match style {
HeaderStyle::None => return content.to_string(),
HeaderStyle::Shell => {
let header = format!(
"# {}\n# ctx:checksum sha256:{}\n",
generated_line(version),
PLACEHOLDER
);
match content.split_once('\n') {
Some((first, rest)) if first.starts_with("#!") => {
format!("{first}\n{header}{rest}")
}
_ => format!("{header}{content}"),
}
}
HeaderStyle::Toml => format!(
"# {}\n# ctx:checksum sha256:{}\n{}",
generated_line(version),
PLACEHOLDER,
content
),
HeaderStyle::YamlFrontmatter => {
let header = format!(
"# {}\n# ctx:checksum sha256:{}\n",
generated_line(version),
PLACEHOLDER
);
match content.split_once('\n') {
Some((first, rest)) if first.trim_end() == "---" => {
format!("{first}\n{header}{rest}")
}
_ => format!("---\n{header}---\n{content}"),
}
}
HeaderStyle::HtmlComment => format!(
"<!-- {} -->\n<!-- ctx:checksum sha256:{} -->\n{}",
generated_line(version),
PLACEHOLDER,
content
),
};
let checksum = content_checksum(headered.as_bytes());
headered.replace(PLACEHOLDER, &checksum)
}
pub fn style_for_path(rel_path: &str) -> HeaderStyle {
let name = rel_path.rsplit('/').next().unwrap_or(rel_path);
if name.ends_with(".sh") {
HeaderStyle::Shell
} else if name.ends_with(".toml") || name.ends_with(".lock") {
HeaderStyle::Toml
} else if name == "SKILL.md" {
HeaderStyle::YamlFrontmatter
} else if name.ends_with(".md") {
HeaderStyle::HtmlComment
} else {
HeaderStyle::None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_checksum_roundtrip_shell() {
let raw = "#!/bin/sh\nset -u\necho hello\n";
let finalized = finalize(raw, HeaderStyle::Shell, "1.2.3");
assert!(finalized.starts_with("#!/bin/sh\n# generated by ctx v1.2.3"));
assert!(finalized.contains("regenerate with 'ctx harness init'"));
let recorded = recorded_checksum(&finalized).unwrap();
assert_eq!(recorded, content_checksum(finalized.as_bytes()));
assert_eq!(generated_version(&finalized).as_deref(), Some("1.2.3"));
}
#[test]
fn test_one_byte_tamper_changes_checksum() {
let finalized = finalize("#!/bin/sh\necho hi\n", HeaderStyle::Shell, "1.2.3");
let recorded = recorded_checksum(&finalized).unwrap();
let tampered = finalized.replace("echo hi", "echo ho");
assert_ne!(recorded, content_checksum(tampered.as_bytes()));
assert_eq!(recorded_checksum(&tampered).unwrap(), recorded);
}
#[test]
fn test_checksum_line_is_leader_agnostic() {
let body = "some content\nmore content\n";
let hash = |c: &str| content_checksum(c.as_bytes());
assert_eq!(
hash(&format!("# ctx:checksum sha256:abc\n{body}")),
hash(&format!("// ctx:checksum sha256:abc\n{body}")),
);
assert_eq!(
hash(&format!("<!-- ctx:checksum sha256:abc -->\n{body}")),
hash(body),
);
assert_eq!(
hash(&format!("# ctx:checksum sha256:abc\n{body}")),
hash(&format!("# ctx:checksum sha256:def\n{body}")),
);
}
#[test]
fn test_json_degenerates_to_full_content_hash() {
let json = "{\n \"name\": \"ctx\"\n}\n";
assert_eq!(finalize(json, HeaderStyle::None, "1.2.3"), json);
let mut hasher = Sha256::new();
hasher.update(json.as_bytes());
assert_eq!(
content_checksum(json.as_bytes()),
format!("{:x}", hasher.finalize())
);
}
#[test]
fn test_yaml_frontmatter_header_stays_inside_frontmatter() {
let raw = "---\nname: ctx\ndescription: test\n---\n\n# Body\n";
let finalized = finalize(raw, HeaderStyle::YamlFrontmatter, "1.2.3");
assert!(finalized.starts_with("---\n# generated by ctx v1.2.3"));
assert_eq!(finalized.matches("---\n").count(), 2);
let recorded = recorded_checksum(&finalized).unwrap();
assert_eq!(recorded, content_checksum(finalized.as_bytes()));
}
#[test]
fn test_html_comment_header() {
let raw = "# Title\n\nBody text.\n";
let finalized = finalize(raw, HeaderStyle::HtmlComment, "1.2.3");
assert!(finalized.starts_with("<!-- generated by ctx v1.2.3"));
let recorded = recorded_checksum(&finalized).unwrap();
assert_eq!(recorded, content_checksum(finalized.as_bytes()));
}
#[test]
fn test_style_for_path() {
assert_eq!(
style_for_path(".claude/hooks/ctx/stop.sh"),
HeaderStyle::Shell
);
assert_eq!(style_for_path(".ctx/rules.toml"), HeaderStyle::Toml);
assert_eq!(style_for_path(".ctx/harness.lock"), HeaderStyle::Toml);
assert_eq!(
style_for_path("skills/ctx/SKILL.md"),
HeaderStyle::YamlFrontmatter
);
assert_eq!(style_for_path("README.md"), HeaderStyle::HtmlComment);
assert_eq!(
style_for_path(".claude-plugin/plugin.json"),
HeaderStyle::None
);
}
}