The rule PLAN.md describes and the rationale it gives for that rule disagree on one shape that turns out to be common. 235 checkboxes in the corpus sit under a plain bullet used as a group label, and by the letter of the rule those have no checkbox ancestor, so a finished one would be dropped on its own. But section 4 already argues the opposite case for checkbox parents: a finished item under an unfinished group is context, not cruft, and a label like `- Trellis "Ergonomics" Fixes` is a group in every sense that matters. Resolved in favour of the rationale, which collapses both cases into one rule. A top-level bullet is any bullet with no bullet ancestor in its section. It is dropped, with its subtree, exactly when that subtree holds at least one checkbox and all of them are [x]. Prose bullets hold none and so are never eligible, which is the property section 4 wanted. Validated across all 228 notes with zero invariant failures: no heading, no unfinished item and no checkbox-free subtree is ever removed, and no group label is left with its children gone. 270 lines would be dropped across 118 files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PU5ZFfQFtDTFqqhvMTWdGH
90 lines
2.8 KiB
Python
90 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Run the prune across every historical note and assert it never misbehaves.
|
|
|
|
The prune is the piece of this system carrying real semantic risk, and 228 real
|
|
notes exist to test it against, so this checks invariants rather than relying on
|
|
someone eyeballing a diff.
|
|
"""
|
|
|
|
import glob
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from prune import DONE, dropped_indices, parse, subtree_end, top_level_bullets
|
|
|
|
NOTES = os.path.expanduser("~/docs/notes/todo")
|
|
|
|
|
|
def audit(path):
|
|
lines = parse(open(path).read().split("\n"))
|
|
drop = dropped_indices(lines)
|
|
errs = []
|
|
|
|
for i in sorted(drop):
|
|
ln = lines[i]
|
|
if ln.is_heading:
|
|
errs.append(f"removed a heading: {ln.text!r}")
|
|
elif ln.is_bullet and ln.state is not None and ln.state != DONE:
|
|
errs.append(f"removed an unfinished item [{ln.state}]: {ln.text!r}")
|
|
|
|
# A dropped subtree must contain at least one checkbox, or it was prose.
|
|
for i in top_level_bullets(lines):
|
|
if i not in drop:
|
|
continue
|
|
end = subtree_end(lines, i)
|
|
if not any(
|
|
lines[j].is_bullet and lines[j].state is not None
|
|
for j in range(i, end + 1)
|
|
):
|
|
errs.append(f"dropped a checkbox-free subtree: {lines[i].text!r}")
|
|
|
|
# A surviving group label must not have lost all of its children.
|
|
for i, ln in enumerate(lines):
|
|
if i in drop or not ln.is_bullet or ln.state is not None:
|
|
continue
|
|
kids = [j for j in range(i + 1, subtree_end(lines, i) + 1) if lines[j].is_bullet]
|
|
if kids and all(j in drop for j in kids):
|
|
errs.append(f"left an emptied group label: {ln.text!r}")
|
|
|
|
return lines, drop, errs
|
|
|
|
|
|
def main():
|
|
files = sorted(glob.glob(os.path.join(NOTES, "*.md")))
|
|
if not files:
|
|
sys.exit(f"no notes found under {NOTES}")
|
|
|
|
failures = changed = dropped = 0
|
|
kinds = {}
|
|
for f in files:
|
|
lines, drop, errs = audit(f)
|
|
if errs:
|
|
failures += len(errs)
|
|
print(f"FAIL {os.path.basename(f)}")
|
|
for e in errs[:4]:
|
|
print(" ", e)
|
|
if drop:
|
|
changed += 1
|
|
dropped += len(drop)
|
|
for i in drop:
|
|
ln = lines[i]
|
|
k = (
|
|
f"checkbox[{ln.state}]"
|
|
if ln.is_bullet and ln.state
|
|
else "bullet" if ln.is_bullet else "continuation"
|
|
)
|
|
kinds[k] = kinds.get(k, 0) + 1
|
|
|
|
print()
|
|
print(f"files : {len(files)} ({changed} would change)")
|
|
print(f"lines dropped : {dropped}")
|
|
for k, v in sorted(kinds.items(), key=lambda x: -x[1]):
|
|
print(f" {k:>14} : {v}")
|
|
print(f"invariant failures: {failures}")
|
|
return 1 if failures else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|