tool_map: the drift guard graded a revision, so the pre-commit tree was ungraded; --rev WORKTREE derives the map from the files git would commit
Measured before: 4602bd5's message says "TOOLS.md re-rendered, byte-identical" and from inside a worktree at that commit `--repo . --check docs/TOOLS.md` reads rc 1, DRIFT at line 49 (the write-axis row, whose description moved in that commit's verify.py), and `--repo . --selftest` reads 15/16. Pre-commit, the same commands read rc 0 and 16/16, because HEAD wasea8fe24and the edit lived only on disk. The guard was working; it was guarding the last commit. Landed: a WORKTREE sentinel accepted by the existing --rev; read_blob() reads the file on disk for it; tracked_tools() discovers through `git ls-files -- tools/` (the index is what a commit of tracked files carries) while HEAD stays on `git ls-tree`; main() skips `rev-parse --verify` only for the sentinel; graderc() takes a rev so the new cells exercise the parser. Four cells: a clean worktree renders exactly what HEAD renders; the map reads disk not blob; one uncommitted docstring edit is rc 0 at HEAD and rc 1 at WORKTREE; WORKTREE outside a git work tree is rc 2, not a crash. Selftest 16 -> 20, 20/20 rc 0. Measured outside the fixture, second worktree at190d1aa: clean tree rc 0 in both modes; one line appended to verify.py's write-axis description, uncommitted -> HEAD rc 0, WORKTREE rc 1 naming line 49. Transcript: wizard-outputs/volt/2026-10-21-worktree-mode/measure.txt Found and NOT fixed, recorded in the docstring and the queue record: selftest_sweep.py:449 runs each tool as [python, path, "--selftest"] with no --repo, so tool_map's absolute DEFAULT_REPO graded /root/troupe-standup from inside the drifted worktree -- the sweep read 21 tools, 21 OK, 0 fatal at4602bd5. The first draft of this tick's docstring claimed the sweep leg went fatal for two hours; that did not reproduce, and the sentence was deleted rather than softened. Gate: verify --fast 5/5 (pre-commit); verify --repo . 6/6 at this commit, doc_command 65/65, doc_census 0 STALE.
This commit is contained in:
1 parent
190d1aae05
commit
214c200bb2
3 files changed
+132
-28
No files matched your search
+105
-17
@@ -2,12 +2,12 @@
|
||||
"""tool_map.py -- the instruments get a map, and the map is generated from the tree.
|
||||
|
||||
WHY THIS EXISTS
|
||||
`tools/` holds sixteen scripts and there is no document that says what they
|
||||
are. A reader meets them one at a time -- in a `verify.py` leg, in a queue
|
||||
block, in a reply -- and nothing states the whole set: which tool grades what,
|
||||
which flags each one actually accepts, which of them the two probe axes cover,
|
||||
and which leg of the operator's gate a failure shows up in. The closest thing
|
||||
is `verify.py`'s `LEGS` table, which names five of the sixteen.
|
||||
`tools/` holds one script per instrument and there is no document that says
|
||||
what they are. A reader meets them one at a time -- in a `verify.py` leg, in a
|
||||
queue block, in a reply -- and nothing states the whole set: which tool grades
|
||||
what, which flags each one actually accepts, which of them the two probe axes
|
||||
cover, and which leg of the operator's gate a failure shows up in. The closest
|
||||
thing is `verify.py`'s `LEGS` table, which names six of them.
|
||||
|
||||
A hand-written map would be a fourth notation of the defect this suite keeps
|
||||
grading: prose about the tree, one step from the thing it describes, with no
|
||||
@@ -39,6 +39,42 @@ WHAT IT MEASURES -- one verdict per invocation
|
||||
docstring, flags or probe rows without re-rendering the map turns the sweep
|
||||
RED instead of leaving a stale document that reads as current.
|
||||
|
||||
THE PRE-COMMIT BLIND SPOT, AND THE MODE THAT CLOSES IT
|
||||
Every cell above is derived from a REVISION, not from the working tree:
|
||||
`collect()` reads blobs with `git show <rev>:<path>` and runs `--help` from a
|
||||
copy of that blob. That is right for a document that makes a claim about a
|
||||
commit, and wrong for the ten minutes before a commit exists.
|
||||
|
||||
Measured on this repository, 2026-10-21. `4602bd5` is drifted: its commit
|
||||
message states "TOOLS.md re-rendered, byte-identical" and the map it ships is
|
||||
not the render of its own tree. Two runs, both from inside a worktree checked
|
||||
out at that commit: `--repo . --check docs/TOOLS.md` reads **rc 1, DRIFT at
|
||||
line 49** (the `write-axis` cell, whose description moved in `verify.py`),
|
||||
and `--repo . --selftest` reads **15/16, rc 1**, the one failure being the
|
||||
currency cell below. Before the commit existed both commands read rc 0 and
|
||||
16/16, because the document on disk and the tools at `HEAD` (`ea8fe24`) were
|
||||
still the pre-edit pair -- the edit lived only in the worktree.
|
||||
|
||||
The gate did not catch it either. `selftest_sweep.py --repo .` at `4602bd5`
|
||||
reads **21 tools, 21 OK, 0 fatal**, because the sweep runs each tool as
|
||||
`[python, path, "--selftest"]` with no `--repo` (`selftest_sweep.py:449`) and
|
||||
this tool's `DEFAULT_REPO` is an absolute path, so its selftest graded
|
||||
/root/troupe-standup -- a different, clean tree. That is a second defect, and
|
||||
it is NOT fixed here: the sweep's selftest axis can grade the wrong tree. It
|
||||
is recorded because it is why the pre-commit runs stayed green.
|
||||
|
||||
`--rev WORKTREE` derives the map from the files on disk, discovered through
|
||||
`git ls-files -- tools/` -- the index, which is what a commit of tracked
|
||||
files will carry. Measured on a clean worktree at `190d1aa`: rc 0, and
|
||||
byte-identical to what HEAD renders; with one uncommitted line appended to
|
||||
`verify.py`'s `write-axis` description, `--rev HEAD` still reads rc 0 and
|
||||
`--rev WORKTREE` reads **rc 1, DRIFT at line 49**. The currency cell stays
|
||||
HEAD-scoped by design: a worktree mid-edit is not a fault, and a sweep that
|
||||
fails while a docstring is being typed teaches nothing. The pre-commit guard
|
||||
is that `--check` is run in WORKTREE mode before the commit; four cells below
|
||||
measure that it separates the two trees and that a clean worktree renders
|
||||
byte-for-byte what HEAD renders, so the mode cannot invent a drift of its own.
|
||||
|
||||
READ-ONLY except for the one path it is told to write. The only process it
|
||||
starts is `<tool> --help`, which does no work by construction; it never runs a
|
||||
documented command, so it cannot write, post, or deploy anything.
|
||||
@@ -52,8 +88,9 @@ EXIT CODES (the tools/ contract)
|
||||
USAGE
|
||||
python3 tool_map.py --repo . --write docs/TOOLS.md # render
|
||||
python3 tool_map.py --repo . --check docs/TOOLS.md # grade drift, rc 1 on drift
|
||||
python3 tool_map.py --repo . --rev WORKTREE --check docs/TOOLS.md # ...as it will be committed
|
||||
python3 tool_map.py --repo . --json out.json
|
||||
python3 tool_map.py --selftest # 16 cells
|
||||
python3 tool_map.py --selftest # 20 cells
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -70,6 +107,10 @@ DEFAULT_REPO = "/root/troupe-standup"
|
||||
DEFAULT_DOC = "docs/TOOLS.md"
|
||||
HELP_TIMEOUT = 30
|
||||
|
||||
# The one value of `--rev` that does not name a revision: the files git would
|
||||
# commit from this worktree (the index lists them, the disk supplies them).
|
||||
WORKTREE = "WORKTREE"
|
||||
|
||||
# A long option, or a short one, as argparse prints them in the usage block.
|
||||
# `-h`/`--help` are dropped: every tool has them, and a column of sixteen
|
||||
# identical pairs is noise, not information.
|
||||
@@ -84,13 +125,29 @@ def git(repo, *args, inp=None):
|
||||
|
||||
|
||||
def read_blob(repo, rev, path):
|
||||
"""The file's text at `rev`; from disk when `rev` is WORKTREE."""
|
||||
if rev == WORKTREE:
|
||||
try:
|
||||
with open(os.path.join(repo, path), encoding="utf-8") as fh:
|
||||
return fh.read()
|
||||
except OSError:
|
||||
return None
|
||||
rc, out, err = git(repo, "show", f"{rev}:{path}")
|
||||
return out if rc == 0 else None
|
||||
|
||||
|
||||
def tracked_tools(repo, rev):
|
||||
"""Sorted `tools/*.py` basenames tracked at `rev`."""
|
||||
rc, out, _ = git(repo, "ls-tree", "-r", "--name-only", rev, "--", "tools/")
|
||||
"""Sorted `tools/*.py` basenames tracked at `rev`.
|
||||
|
||||
WORKTREE is not a revision and has no tree object: the set is the INDEX
|
||||
(`git ls-files`), which is what a commit of tracked files will carry, and
|
||||
the contents come from disk. Reading the index rather than a reflog of the
|
||||
worktree keeps one rule -- "the file git would commit" -- for both halves.
|
||||
"""
|
||||
if rev == WORKTREE:
|
||||
rc, out, _ = git(repo, "ls-files", "--", "tools/")
|
||||
else:
|
||||
rc, out, _ = git(repo, "ls-tree", "-r", "--name-only", rev, "--", "tools/")
|
||||
if rc != 0:
|
||||
return None
|
||||
names = [p.split("/")[-1] for p in out.split("\n")
|
||||
@@ -417,6 +474,34 @@ def selftest(repo):
|
||||
cell("--repo a directory that is not a git work tree: rc 2",
|
||||
rc_norepo == 2, f"rc {rc_norepo}")
|
||||
|
||||
# -- WORKTREE: the tree you are about to commit, not the last commit --
|
||||
# The map is derived from a revision by construction, so a docstring
|
||||
# edited after the last commit is invisible to both `--check` and the
|
||||
# currency cell below. That is measured here, not described: the same
|
||||
# document is current against HEAD and drifted against the worktree.
|
||||
cell("WORKTREE: a clean worktree renders exactly what HEAD renders",
|
||||
render(collect(fixture, WORKTREE)) == text)
|
||||
fake = os.path.join(fixture, "tools", "fake_tool.py")
|
||||
with open(fake, encoding="utf-8") as fh:
|
||||
src = fh.read()
|
||||
with open(fake, "w", encoding="utf-8") as fh:
|
||||
fh.write(src.replace("which nothing measured",
|
||||
"which nothing has measured yet"))
|
||||
wt = collect(fixture, WORKTREE)
|
||||
hd = collect(fixture, "HEAD")
|
||||
purpose_wt = {r["tool"]: r["purpose"] for r in wt["rows"]}["fake_tool.py"]
|
||||
purpose_hd = {r["tool"]: r["purpose"] for r in hd["rows"]}["fake_tool.py"]
|
||||
cell("WORKTREE: reads the file on disk, not the blob at HEAD",
|
||||
purpose_wt != purpose_hd and "has measured yet" in purpose_wt,
|
||||
f"worktree={purpose_wt!r} head={purpose_hd!r}")
|
||||
rc_head = compare(doc, render(hd), quiet=True)
|
||||
rc_wt = compare(doc, render(wt), quiet=True)
|
||||
cell("blind spot: an uncommitted docstring edit is rc 0 at HEAD and rc 1 at WORKTREE",
|
||||
rc_head == 0 and rc_wt == 1, f"HEAD rc {rc_head}, WORKTREE rc {rc_wt}")
|
||||
rc_wt_norepo = graderc(td, rev=WORKTREE)
|
||||
cell("WORKTREE outside a git work tree: rc 2, not a crash",
|
||||
rc_wt_norepo == 2, f"rc {rc_wt_norepo}")
|
||||
|
||||
# -- the currency cell: the reason this document stays true ----------
|
||||
here = os.path.join(repo, "docs", "TOOLS.md")
|
||||
try:
|
||||
@@ -436,11 +521,13 @@ def selftest(repo):
|
||||
return 0 if passed == len(cells) else 1
|
||||
|
||||
|
||||
def graderc(td):
|
||||
def graderc(td, rev=None):
|
||||
"""rc of a full `--check` run against a directory that is not a repo."""
|
||||
proc = subprocess.run([sys.executable, os.path.abspath(__file__),
|
||||
"--repo", td, "--check", os.path.join(td, "x.md")],
|
||||
capture_output=True, text=True)
|
||||
cmd = [sys.executable, os.path.abspath(__file__), "--repo", td]
|
||||
if rev:
|
||||
cmd += ["--rev", rev]
|
||||
cmd += ["--check", os.path.join(td, "x.md")]
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return proc.returncode
|
||||
|
||||
|
||||
@@ -488,10 +575,11 @@ def main():
|
||||
if rc != 0:
|
||||
print(f"tool_map: cannot read repo: {args.repo} is not a git work tree")
|
||||
return 2
|
||||
rc, _, err = git(args.repo, "rev-parse", "--verify", f"{args.rev}^{{commit}}")
|
||||
if rc != 0:
|
||||
print(f"tool_map: cannot read rev {args.rev}: {err.strip().splitlines()[0] if err.strip() else 'no such object'}")
|
||||
return 2
|
||||
if args.rev != WORKTREE:
|
||||
rc, _, err = git(args.repo, "rev-parse", "--verify", f"{args.rev}^{{commit}}")
|
||||
if rc != 0:
|
||||
print(f"tool_map: cannot read rev {args.rev}: {err.strip().splitlines()[0] if err.strip() else 'no such object'}")
|
||||
return 2
|
||||
|
||||
data = collect(args.repo, args.rev)
|
||||
text = render(data)
|
||||
|
||||
Reference in new issue
Block a user