feat(scripts): OKF conformance linter, trash-aware vault seams
Add scripts/check-okf.py: enforces the OKF v0.1 hard rules the repo had been carrying by convention — parseable frontmatter with type/title/ description/timestamp, description shape (length, not-equals-title, no restated quantity), the reserved-name and index.md rules, log.md's date heading shape (bare ISO date, one entry per date, newest first), the 500-line hint, and the doubled-page signature (a second `type:` key in the body). Citations are reported as a soft tally, not a failure. The 500-line ceiling is escapable two ways, both explainable: `okf-exempt: length` in a page's own frontmatter, and pages derived as one-per- endpoint — live endpoints from scripts/, retired-endpoint era pages from their tag (AGENTS.md lets a campaign ledger live on the page that measured it). scripts/gen-vault-index.py: sibling-presence was tested as a bare stem substring, so a page whose name is a prefix of another page's name (`frank-medium` inside `frank-medium-rocmfpx`) silently never got an index row. Test the wikilink form instead. scripts/check-vault-links.py: `.AGENTTRASH` is deleted-but-held state, not vault content. It is now excluded from both the scan set and the target index, so a live link into the trash reads as unresolved instead of resolved. tests/docs_completeness.bats: the same exclusion in both page walks — trashed pages need no index row, and a trashed page must not satisfy the docs requirement for a live compose service. Adds the OKF conformance and index-freshness tests. scripts/port-census.py: the port-before-cut orphan census used to prove a merge lost no fact, kept so the next collapse can re-run it.
This commit is contained in:
1 parent
1ed7f0ddf2
commit
3880503057
5 files changed
+439
-138
No files matched your search
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Port census: prove a merge/collapse lost no fact.
|
||||
|
||||
For every line that existed in the OLD set and not in the NEW set, check that
|
||||
each of its significant tokens (numbers, long identifiers, paths) still occurs
|
||||
somewhere in the NEW corpus. A line whose every token survives is a reworded
|
||||
line; a line with an unheld token is a candidate fact loss and gets printed.
|
||||
|
||||
port-census.py <new-glob-dir> <old-file> [<old-file> ...]
|
||||
|
||||
Old files may be `git-rev:path` (read from git) or a path (e.g. a trash copy).
|
||||
"""
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def toks(line):
|
||||
out = []
|
||||
for n in re.findall(r"\d[\d,]*(?:\.\d+)?", line):
|
||||
if len(n.replace(",", "").split(".")[0]) >= 3 or (
|
||||
"." in n and len(n.split(".")[-1]) >= 2 and len(n.replace(",", "")) >= 4
|
||||
):
|
||||
out.append(n)
|
||||
# Identifiers, flags, paths and backticked spans are the facts worth
|
||||
# tracking; ordinary prose words are not — they reword on every rewrite.
|
||||
out += [w for w in re.findall(r"[A-Za-z][\w./:-]{7,}", line)
|
||||
if re.search(r"[\d_.:/-]", w) or w.isupper()]
|
||||
out += [s.strip() for s in re.findall(r"`([^`]+)`", line) if len(s) > 3]
|
||||
return out
|
||||
|
||||
|
||||
def held(tok, corpus):
|
||||
t = tok.replace(",", "")
|
||||
alt = rf"\b{re.escape(t)}\b"
|
||||
for c in corpus:
|
||||
if tok in c or t in c:
|
||||
return True
|
||||
if re.search(alt, c.replace(",", "")):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main(argv):
|
||||
new_dir = pathlib.Path(argv[1])
|
||||
corpus = [
|
||||
p.read_text(errors="replace")
|
||||
for p in new_dir.rglob("*.md")
|
||||
if ".AGENTTRASH" not in str(p) and ".obsidian" not in str(p)
|
||||
]
|
||||
blob = "\n".join(corpus)
|
||||
lost = 0
|
||||
checked = 0
|
||||
for spec in argv[2:]:
|
||||
if spec.startswith("git-"):
|
||||
ref, path = spec[4:].split(":", 1)
|
||||
old = subprocess.run(["git", "show", f"{ref}:{path}"],
|
||||
capture_output=True, text=True).stdout
|
||||
label = f"{ref}:{path}"
|
||||
else:
|
||||
old = pathlib.Path(spec).read_text(errors="replace")
|
||||
label = spec
|
||||
for line in old.splitlines():
|
||||
s = line.strip().strip("->*|# ").strip()
|
||||
if len(s) < 30 or s.startswith("```"):
|
||||
continue
|
||||
checked += 1
|
||||
missing = [t for t in toks(s) if not held(t, [blob])]
|
||||
if missing:
|
||||
lost += 1
|
||||
print(f" {label}\n L: {s[:150]}\n unheld: {', '.join(missing[:6])}")
|
||||
print(f"{checked} old lines checked, {lost} with unheld tokens")
|
||||
return 1 if lost else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
Reference in new issue
Block a user