use std::path::PathBuf;
fn workspace_root() -> PathBuf {
let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
loop {
let manifest = dir.join("Cargo.toml");
if manifest.is_file()
&& std::fs::read_to_string(&manifest).is_ok_and(|s| s.contains("[workspace]"))
{
return dir;
}
assert!(
dir.pop(),
"no Cargo.toml with [workspace] above CARGO_MANIFEST_DIR"
);
}
}
fn is_slug_char(c: char) -> bool {
c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'
}
fn sections(method_rules: &str) -> Vec<String> {
method_rules
.lines()
.filter_map(|l| l.strip_prefix("## "))
.map(str::trim)
.filter(|s| !s.is_empty() && s.chars().all(is_slug_char))
.map(str::to_owned)
.collect()
}
fn cited_anchors(claude: &str) -> Vec<String> {
const NEEDLE: &str = "method-rules.md#";
let mut out = Vec::new();
let mut from = 0usize;
while let Some(rel) = claude[from..].find(NEEDLE) {
let at = from + rel + NEEDLE.len();
let anchor: String = claude[at..]
.chars()
.take_while(|c| is_slug_char(*c))
.collect();
if !anchor.is_empty() {
out.push(anchor);
}
from = at;
}
out
}
fn unindexed(method_rules: &str, claude: &str) -> Vec<String> {
sections(method_rules)
.into_iter()
.filter(|s| !claude.contains(s.as_str()))
.collect()
}
fn unanchored(method_rules: &str, claude: &str) -> Vec<String> {
let cited = cited_anchors(claude);
sections(method_rules)
.into_iter()
.filter(|s| !cited.contains(s))
.collect()
}
fn anchorless_links(claude: &str) -> usize {
const NEEDLE: &str = "](docs/method-rules.md";
let mut n = 0usize;
let mut from = 0usize;
while let Some(rel) = claude[from..].find(NEEDLE) {
let at = from + rel + NEEDLE.len();
if claude[at..].starts_with(')') {
n += 1;
}
from = at;
}
n
}
fn dangling(method_rules: &str, claude: &str) -> Vec<String> {
let have = sections(method_rules);
let mut out: Vec<String> = cited_anchors(claude)
.into_iter()
.filter(|a| !have.contains(a))
.collect();
out.sort();
out.dedup();
out
}
fn anchors_in_line(line: &str) -> Vec<String> {
let chars: Vec<char> = line.chars().collect();
let mut out = Vec::new();
let mut in_code = false;
for i in 0..chars.len() {
if chars[i] == '`' {
in_code = !in_code;
continue;
}
if in_code || chars[i] != ']' {
continue;
}
if chars.get(i + 1) != Some(&'(') || chars.get(i + 2) != Some(&'#') {
continue;
}
let anchor: String = chars[i + 3..]
.iter()
.copied()
.take_while(|c| is_slug_char(*c))
.collect();
if !anchor.is_empty() {
out.push(anchor);
}
}
out
}
fn intra_dangling(method_rules: &str) -> Vec<String> {
let have = sections(method_rules);
let mut out: Vec<String> = Vec::new();
let mut fenced = false;
for line in method_rules.lines() {
if line.trim_start().starts_with("```") {
fenced = !fenced;
continue;
}
if fenced {
continue;
}
out.extend(
anchors_in_line(line)
.into_iter()
.filter(|a| !have.contains(a)),
);
}
out.sort();
out.dedup();
out
}
fn read_pair() -> (String, String) {
let root = workspace_root();
let mr = std::fs::read_to_string(root.join("docs/method-rules.md")).expect("method-rules.md");
let cm = std::fs::read_to_string(root.join("CLAUDE.md")).expect("CLAUDE.md");
(mr, cm)
}
#[test]
fn arm_a_every_method_rule_is_reachable_from_claude_md() {
let (mr, cm) = read_pair();
let missing = unindexed(&mr, &cm);
assert!(
missing.is_empty(),
"{} method-rules section(s) have NO mention in CLAUDE.md, so they are unreachable from the file that loads into every session — landed and invisible, which is close to not having landed. If the section carries its intended index line in a blockquote, move that across; otherwise compose one in the house style -- NOT every section has one. Missing: {missing:?}",
missing.len(),
);
}
#[test]
fn arm_b_every_claude_md_anchor_resolves_to_a_real_section() {
let (mr, cm) = read_pair();
let broken = dangling(&mr, &cm);
assert!(
broken.is_empty(),
"CLAUDE.md links to {} method-rules anchor(s) that do not exist. A link to nothing is \
worse per instance than a missing link: the rule appears to EXIST, and a reader who \
follows it concludes the corpus is unmaintained — which damages trust in every other \
link. Check whether the target is a MEMORY file rather than a method-rules section. \
Dangling: {broken:?}",
broken.len(),
);
}
#[test]
fn arm_c_every_section_has_a_link_whose_target_carries_its_anchor() {
let (mr, cm) = read_pair();
let unanchored = unanchored(&mr, &cm);
assert!(
unanchored.is_empty(),
"{} method-rules section(s) have NO link in CLAUDE.md whose TARGET carries their \
anchor. Arm A is satisfied by a bare NAME and arm B only checks anchors that already \
exist, so a citation like `[`…md` § `slug`](docs/method-rules.md)` -- name in the TEXT, \
no `#anchor` in the TARGET -- passes both and strands the reader at the top of a \
1,600-line file. It READS as a working citation and is not one. Fix the TARGET, not \
the text. Unanchored: {unanchored:?}",
unanchored.len(),
);
}
#[test]
fn arm_d_every_method_rules_link_carries_an_anchor() {
let (_mr, cm) = read_pair();
let n = anchorless_links(&cm);
assert_eq!(
n, 0,
"{n} CLAUDE.md link(s) target `docs/method-rules.md` with NO `#anchor`. Distinct from \
arm C: a rule cited several times with anchors passes THAT while one bad citation \
still lands the reader at the top of a 1,600-line file. Add the fragment to the \
TARGET; the link text is not what a reader follows.",
);
}
#[test]
fn arm_d_scanner_discriminates_anchored_from_anchorless() {
assert_eq!(
anchorless_links("see [`x`](docs/method-rules.md) here"),
1,
"arm D's scanner missed a link with no fragment",
);
assert_eq!(
anchorless_links("see [`x`](docs/method-rules.md#x) here"),
0,
"arm D's scanner flagged a correctly-anchored link",
);
}
#[test]
fn arm_c_scanner_can_see_a_section_cited_without_an_anchor() {
let mr = "## only-section\n\nbody\n";
let cm = "see [`docs/method-rules.md` § `only-section`](docs/method-rules.md) for detail\n";
assert_eq!(
unanchored(mr, cm),
vec!["only-section".to_string()],
"arm C's scanner did not flag a section whose only citation omits the #anchor from the \
link target -- the very shape it exists to catch",
);
let fixed = "see [`only-section`](docs/method-rules.md#only-section) for detail\n";
assert!(
unanchored(mr, fixed).is_empty(),
"arm C's scanner flagged a correctly-anchored citation",
);
}
#[test]
fn arm_a_scanner_can_see_an_unindexed_section() {
let mr = "## alpha-rule\ntext\n\n## beta-rule\ntext\n";
let cm = "we reference alpha-rule here and nothing else";
assert_eq!(unindexed(mr, cm), vec!["beta-rule".to_string()]);
}
#[test]
fn arm_b_scanner_can_see_a_dangling_anchor() {
let mr = "## alpha-rule\ntext\n";
let cm =
"see [`justification-scope-mismatch`](docs/method-rules.md#justification-scope-mismatch)";
assert_eq!(
dangling(mr, cm),
vec!["justification-scope-mismatch".to_string()]
);
}
#[test]
fn arm_b_keys_on_the_link_target_not_the_link_text() {
let mr = "## real-section\ntext\n";
let cm = "see [`totally-made-up-name`](docs/method-rules.md#real-section)";
assert!(
dangling(mr, cm).is_empty(),
"resolving target -> real-section, so not dangling"
);
}
#[test]
fn arm_e_every_intra_document_anchor_resolves_to_a_real_section() {
let (mr, _cm) = read_pair();
let bad = intra_dangling(&mr);
assert!(
bad.is_empty(),
"{} intra-document anchor(s) in docs/method-rules.md point at no section OF THAT FILE. Arm B checks CLAUDE.md -> method-rules; this checks method-rules -> method-rules, which was unchecked. A reader follows one of these and lands nowhere, which damages trust in every other link in the corpus. Usually the target is a MEMORY-file name with no section here: keep the name, drop the link. Dangling: {bad:?}",
bad.len(),
);
}
#[test]
fn arm_e_scanner_can_see_a_dangling_intra_anchor() {
let mr = "## alpha-rule
text
see [`magnitude-is-not-impossibility`](#magnitude-is-not-impossibility)
";
assert_eq!(
intra_dangling(mr),
vec!["magnitude-is-not-impossibility".to_string()]
);
}
#[test]
fn arm_e_scanner_ignores_anchors_inside_code() {
let inline = "## alpha-rule
text
Every `See [`x`](#no-such-section)` between sections is unchecked.
";
assert!(
intra_dangling(inline).is_empty(),
"an anchor inside an INLINE CODE SPAN is an example, not a link"
);
let fenced = "## alpha-rule
text
```text
see [x](#no-such-section)
```
";
assert!(
intra_dangling(fenced).is_empty(),
"an anchor inside a FENCED BLOCK is an example, not a link"
);
let mixed = "## alpha-rule
text
`code` and [real](#no-such-section) here
";
assert_eq!(
intra_dangling(mixed),
vec!["no-such-section".to_string()],
"excluding code spans must not blind the scanner to links beside them"
);
}