forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck_agent_doc_references.py
More file actions
194 lines (158 loc) · 6.99 KB
/
Copy pathcheck_agent_doc_references.py
File metadata and controls
194 lines (158 loc) · 6.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/usr/bin/env python3
"""Every repo path an agent doc points at must exist.
Agent docs are read by agents that cannot tell a stale pointer from a live one.
A rename that orphans a reference is silent until an agent follows it, wastes a
turn, and then improvises -- which is exactly the failure mode guidance is
supposed to prevent.
Real instances this would have caught, all live on main when this landed:
- CLAUDE.md pointed at `desktop/CLAUDE.md` and `desktop/e2e/SKILL.md`; both
moved under `desktop/macos/` long before.
- desktop/macos/AGENTS.md pointed at `.claude/skills/firebase/SKILL.md` and
`.claude/skills/sentry-release/SKILL.md`, which are agent-local and have
never existed in this repo.
- desktop/macos/AGENTS.md told agents to launch via `./reset-and-run.sh`,
which does not exist.
What counts as a reference: a markdown link to a relative path, and a backticked
token that looks like a repo path (contains `/` and a known source extension, or
starts with a top-level repo directory). Bare filenames like `auth.py` are
shorthand, not pointers, and are ignored -- resolving those would need judgment,
and a check that needs judgment is a check that gets an allowlist.
"""
from __future__ import annotations
import re
import subprocess
import sys
import tempfile
from pathlib import Path
# Docs whose references must resolve. Component AGENTS.md files are discovered.
EXTRA_DOCS = ("CLAUDE.md", "PRODUCT.md", ".github/agent-docs", ".cursor/cloud-agent-environment.md")
SOURCE_SUFFIXES = {
".md", ".py", ".sh", ".yaml", ".yml", ".json", ".dart", ".swift",
".ts", ".tsx", ".rs", ".mdx", ".toml", ".kt", ".java", ".h", ".c",
}
# Top-level directories that make a backticked token unambiguously a repo path.
REPO_ROOTS = (
".github/", ".cursor/", "app/", "backend/", "desktop/", "docs/",
"infrastructure/", "omi/", "product/", "scripts/", "web/",
)
MD_LINK = re.compile(r"\[[^\]]*\]\(\s*(?!https?:|mailto:|#)([^)\s#]+)")
BACKTICK = re.compile(r"`([^`\n]+)`")
# Placeholder segments: a path that is illustrative, not a real file.
PLACEHOLDER = re.compile(r"[<>{}*]|\.\.\.|\bYYYY\b|\bXXXX\b|20260628-short-description")
def is_repo_path(token: str) -> bool:
"""True when a backticked token is a pointer rather than shorthand."""
if PLACEHOLDER.search(token) or " " in token:
return False
if token.startswith(REPO_ROOTS):
return True
# A relative path with a directory part and a known extension, e.g.
# `scripts/foo.sh` inside a component guide.
return "/" in token and Path(token).suffix in SOURCE_SUFFIXES
def ignored(repo: Path, candidates: list[Path]) -> bool:
"""True when git deliberately ignores the path.
Build outputs, virtualenvs, .env files, and generated sources are absent in a
clean checkout by design. Pointing at them is correct documentation, not a
stale reference, so gitignore is the repo's own answer to 'is this expected
to be missing?' -- no hand-maintained allowlist required.
"""
rels = []
for c in candidates:
try:
rels.append(str(c.relative_to(repo)))
except ValueError:
continue
if not rels:
return False
result = subprocess.run(
["git", "check-ignore", "--quiet", "--no-index", "--stdin"],
cwd=repo,
input="\n".join(rels),
capture_output=True,
text=True,
)
return result.returncode == 0
def resolve(repo: Path, doc: Path, ref: str) -> bool:
"""A reference resolves if it exists relative to the doc or the repo root."""
ref = ref.rstrip("/")
if not ref:
return True
candidates = [doc.parent / ref, repo / ref]
if any(c.exists() for c in candidates):
return True
return ignored(repo, candidates)
def collect_docs(repo: Path) -> list[Path]:
docs = sorted(
p for p in repo.rglob("AGENTS.md")
if "node_modules" not in p.parts and ".build" not in p.parts
)
for extra in EXTRA_DOCS:
target = repo / extra
if target.is_dir():
# Historical design notes under superpowers/ are not live agent
# pointers; scanning them would fail closed on stale eval paths.
docs.extend(
sorted(p for p in target.rglob("*.md") if "superpowers" not in p.parts)
)
elif target.exists():
docs.append(target)
return docs
def check_doc(repo: Path, doc: Path) -> list[str]:
text = doc.read_text(encoding="utf-8")
rel = doc.relative_to(repo)
errors = []
seen: set[str] = set()
for ref in MD_LINK.findall(text):
if ref in seen or PLACEHOLDER.search(ref):
continue
seen.add(ref)
if not resolve(repo, doc, ref):
errors.append(f"{rel}: markdown link -> '{ref}' does not exist")
for token in BACKTICK.findall(text):
token = token.strip()
if token in seen or not is_repo_path(token):
continue
seen.add(token)
if not resolve(repo, doc, token):
errors.append(f"{rel}: reference `{token}` does not exist")
return errors
def self_test() -> None:
with tempfile.TemporaryDirectory() as tmp:
repo = Path(tmp)
(repo / "backend").mkdir()
(repo / "backend" / "real.py").write_text("")
doc = repo / "AGENTS.md"
doc.write_text("see `backend/real.py` and [ok](backend/real.py)\n")
assert check_doc(repo, doc) == [], "existing references must pass"
doc.write_text("see `backend/gone.py`\n")
assert check_doc(repo, doc), "missing backticked repo path must fail"
doc.write_text("see [x](backend/gone.py)\n")
assert check_doc(repo, doc), "missing markdown link must fail"
doc.write_text("run `auth.py` and `make setup` and `pip install -e .`\n")
assert check_doc(repo, doc) == [], "bare shorthand must be ignored"
doc.write_text("see `docs/<component>/guide.md` and `changelog/YYYY-x.json`\n")
assert check_doc(repo, doc) == [], "placeholders must be ignored"
# A component guide resolving a path relative to its own directory.
(repo / "backend" / "AGENTS.md").write_text("run `scripts/x.sh`\n")
(repo / "backend" / "scripts").mkdir()
(repo / "backend" / "scripts" / "x.sh").write_text("")
assert check_doc(repo, repo / "backend" / "AGENTS.md") == [], (
"component-relative reference must resolve"
)
def main() -> int:
self_test()
repo = Path(__file__).resolve().parents[2]
errors = [e for doc in collect_docs(repo) for e in check_doc(repo, doc)]
if errors:
print("Agent docs reference paths that do not exist:\n", file=sys.stderr)
for e in errors:
print(f" {e}", file=sys.stderr)
print(
"\nFix the path, or delete the pointer if the target is gone.\n"
"Agents cannot tell a stale pointer from a live one.",
file=sys.stderr,
)
return 1
print("agent doc references OK")
return 0
if __name__ == "__main__":
raise SystemExit(main())