fix: repair case-sensitive README links

#11
Files changed (2) hide show
  1. README.md +3 -3
  2. tests/test_readme_link_contract.py +30 -0
README.md CHANGED
@@ -32,7 +32,7 @@ ecosystem-stage: "operational"
32
  [![SLSA L1 honest · L2 build-attested · L3 roadmap](https://img.shields.io/badge/SLSA-L1%20honest%20%C2%B7%20L2%20build--attested%20%C2%B7%20L3%20roadmap-c9b787?style=flat-square)](https://github.com/szl-holdings/killinchu)
33
  [![doctrine-v11](https://img.shields.io/badge/doctrine-v11%20LOCKED-0B1F3A?style=flat-square)](https://github.com/szl-holdings/.github/tree/main/doctrine)
34
  [![CI](https://github.com/szl-holdings/killinchu/actions/workflows/ci.yml/badge.svg)](https://github.com/szl-holdings/killinchu/actions)
35
- [![License](https://img.shields.io/badge/license-Apache--2.0-5fb3a3?style=flat-square)](LICENSE)
36
  [![Λ Conjecture 1](https://img.shields.io/badge/%CE%9B-Conjecture%201%20(conditional%20Theorem%20U)-B79BD6?style=flat-square)](https://github.com/szl-holdings/lutar-lean/blob/main/BOUNTY.md)
37
  [![Khipu Conjecture 2](https://img.shields.io/badge/Khipu%20BFT-Conjecture%202%20(Wave23%20conditional)-B79BD6?style=flat-square)](https://github.com/szl-holdings/khipu-consensus)
38
 
@@ -203,7 +203,7 @@ graph TD
203
  ```
204
 
205
  Full layout, the `register()` pattern, and the doctrine CI hard-gates →
206
- [`docs/architecture.md`](docs/architecture.md).
207
 
208
  ---
209
 
@@ -214,7 +214,7 @@ route assembly). The table below is a **logical** "where things live" guide; the
214
  listed already exist and run live. A user-visible surface is a self-contained module that
215
  exposes `register(app, ns="killinchu")` and is registered **before** the SPA catch-all
216
  (FastAPI matches routes in declaration order). Full table in
217
- [`docs/architecture.md`](docs/architecture.md).
218
 
219
  | Layer | Role | Representative modules |
220
  |---|---|---|
 
32
  [![SLSA L1 honest · L2 build-attested · L3 roadmap](https://img.shields.io/badge/SLSA-L1%20honest%20%C2%B7%20L2%20build--attested%20%C2%B7%20L3%20roadmap-c9b787?style=flat-square)](https://github.com/szl-holdings/killinchu)
33
  [![doctrine-v11](https://img.shields.io/badge/doctrine-v11%20LOCKED-0B1F3A?style=flat-square)](https://github.com/szl-holdings/.github/tree/main/doctrine)
34
  [![CI](https://github.com/szl-holdings/killinchu/actions/workflows/ci.yml/badge.svg)](https://github.com/szl-holdings/killinchu/actions)
35
+ [![License](https://img.shields.io/badge/license-Apache--2.0-5fb3a3?style=flat-square)](https://www.apache.org/licenses/LICENSE-2.0)
36
  [![Λ Conjecture 1](https://img.shields.io/badge/%CE%9B-Conjecture%201%20(conditional%20Theorem%20U)-B79BD6?style=flat-square)](https://github.com/szl-holdings/lutar-lean/blob/main/BOUNTY.md)
37
  [![Khipu Conjecture 2](https://img.shields.io/badge/Khipu%20BFT-Conjecture%202%20(Wave23%20conditional)-B79BD6?style=flat-square)](https://github.com/szl-holdings/khipu-consensus)
38
 
 
203
  ```
204
 
205
  Full layout, the `register()` pattern, and the doctrine CI hard-gates →
206
+ [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
207
 
208
  ---
209
 
 
214
  listed already exist and run live. A user-visible surface is a self-contained module that
215
  exposes `register(app, ns="killinchu")` and is registered **before** the SPA catch-all
216
  (FastAPI matches routes in declaration order). Full table in
217
+ [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
218
 
219
  | Layer | Role | Representative modules |
220
  |---|---|---|
tests/test_readme_link_contract.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import re
3
+ import subprocess
4
+ import unittest
5
+ from urllib.parse import unquote
6
+
7
+
8
+ ROOT = Path(__file__).resolve().parents[1]
9
+
10
+
11
+ class ReadmeLinkContractTests(unittest.TestCase):
12
+ def test_relative_readme_links_match_tracked_paths_exactly(self):
13
+ readme = (ROOT / "README.md").read_text(encoding="utf-8")
14
+ tracked = set(
15
+ subprocess.check_output(["git", "ls-files"], cwd=ROOT, text=True).splitlines()
16
+ )
17
+ missing = []
18
+ for raw in re.findall(r"\]\(([^)]+)\)", readme):
19
+ target = raw.strip().split()[0].strip("<>")
20
+ if not target or target.startswith(("#", "http:", "https:", "mailto:")):
21
+ continue
22
+ path = unquote(target.split("#", 1)[0]).replace("\\", "/")
23
+ if path and path not in tracked:
24
+ missing.append(path)
25
+
26
+ self.assertEqual(missing, [])
27
+
28
+
29
+ if __name__ == "__main__":
30
+ unittest.main()