Point-of-use enforcement needs hands, not advice: - `destroy-vault.py` — moves the pages on a kill list to `.AGENTTRASH/` and then deletes every reference to them from the survivors, including the whole line when the dead link was the line's only content. Stems are seeded from the trash so the pass is re-runnable; `pathlib.Path.stem` is NOT used to compare link targets, because it strips the real suffix off names like `sd.cpp`. - `axe-measurements.py` — deletes bench/throughput ledger sections and rate rows, blanks any table cell that reports a rate, and folds Eras older than the current one into a single row that says the numbers were destroyed and that rollback is a commit. Never touches a shape number or a Law/Wire/Rollback section. - `repair-damage.py` — fixes the two artifacts those passes can leave (a list marker that lost its space, a table separator left without its header). - `restore-punctuation.py` — restores a line whose only difference from HEAD is stripped trailing punctuation. Written after `repair-damage.py`'s first version matched every wrapped prose line ending in a comma and deleted 660 of them: the rule is now `… see .` only, and the incident is recorded in its docstring. - `gen-vault-index.py` — never walks into `.AGENTTRASH/`, and sweeps rows whose page cell is empty (a description left nameless by a destruction). Full suite: 451 tests pass.
67 lines
2.1 KiB
Python
Executable File
67 lines
2.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Undo trailing-punctuation damage by restoring lines that match HEAD exactly
|
|
except for a stripped trailing `,` / `;` / removed trailing word.
|
|
|
|
The repair pass over-reached: its "dangling pointer" cleanup also matched every
|
|
wrapped prose line ending in a comma, and deleted the comma. A line whose text
|
|
is otherwise byte-identical to its HEAD version, differing only by trailing
|
|
punctuation, is not an edit — it is damage, so HEAD's line goes back.
|
|
|
|
Usage: restore-punctuation.py [--apply]
|
|
"""
|
|
import pathlib
|
|
import subprocess
|
|
import sys
|
|
|
|
REPO = pathlib.Path(".")
|
|
VAULT = REPO / "serverAppConfigs-docs"
|
|
|
|
|
|
def head_text(rel):
|
|
r = subprocess.run(["git", "show", f"HEAD:{rel}"], capture_output=True, text=True)
|
|
return r.stdout if r.returncode == 0 else None
|
|
|
|
|
|
def main(argv):
|
|
apply = "--apply" in argv
|
|
restored = 0
|
|
pages = 0
|
|
for p in sorted(VAULT.rglob("*.md")):
|
|
s = str(p)
|
|
if ".AGENTTRASH" in s or ".obsidian" in s:
|
|
continue
|
|
rel = s
|
|
head = head_text(rel)
|
|
if head is None:
|
|
continue
|
|
cur = p.read_text(errors="replace").split("\n")
|
|
# Key on (length, prefix) of the HEAD line minus trailing punctuation so a
|
|
# damaged line can find its original.
|
|
index = {}
|
|
for hl in head.split("\n"):
|
|
h = hl.rstrip()
|
|
if h.endswith((",", ";")) or True:
|
|
key = (len(h.rstrip(",;. ")), h.rstrip(",;. ")[:60])
|
|
index.setdefault(key, h)
|
|
touched = 0
|
|
for i, cl in enumerate(cur):
|
|
c = cl.rstrip()
|
|
if not c or c.endswith((".", ":", "|", "`", ")", "," , ";")):
|
|
continue
|
|
key = (len(c), c[:60])
|
|
hl = index.get(key)
|
|
if hl and hl != c and hl.startswith(c):
|
|
cur[i] = hl
|
|
touched += 1
|
|
if touched:
|
|
pages += 1
|
|
restored += touched
|
|
if apply:
|
|
p.write_text("\n".join(cur))
|
|
print(f"{'APPLIED' if apply else 'dry run'}: {restored} lines restored across {pages} pages")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv))
|