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 was ea8fe24 and 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 at 190d1aa: 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 at 4602bd5. 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:
volt committed 2026-10-21 20:11:58 +00:00
1 parent 190d1aae05
commit 214c200bb2
3 files changed
+132 -28

No files matched your search

+16
View File
@@ -10536,3 +10536,19 @@ Verification, read at `6cd2114` (the revision this record was written against; a
7. **What it does not claim.** The reply-count guard is sentinel's artifact outside this repo and still hardcodes `249` against an archive of 257 — I did not edit another lane's file, and it was red before this tick, independent of the restore; it stays red until its snapshot advances, which is the point of point 2. The restored reply is byte-identical to `bf1b792`'s blob, but beam's commits were not re-verified this tick — restoration is not re-verification. `TARGET` closes a red only where the row's own citation holds, and the census still runs over a corpus of 499 files, not the archive's prose. No Q-item state changed, and nothing here decides anything Alexander holds. 7. **What it does not claim.** The reply-count guard is sentinel's artifact outside this repo and still hardcodes `249` against an archive of 257 — I did not edit another lane's file, and it was red before this tick, independent of the restore; it stays red until its snapshot advances, which is the point of point 2. The restored reply is byte-identical to `bf1b792`'s blob, but beam's commits were not re-verified this tick — restoration is not re-verification. `TARGET` closes a red only where the row's own citation holds, and the census still runs over a corpus of 499 files, not the archive's prose. No Q-item state changed, and nothing here decides anything Alexander holds.
— Volt — Volt
**Pre-commit guard 2026-10-21 (volt, Workhorse) — the map's drift guard graded a revision, so the ten minutes before a commit were ungraded: `--rev WORKTREE` derives the map from the files git would commit, and it was measured separating the two trees on a real worktree, not only on a fixture.** No item opened or closed; the open set is as the previous record left it (`Q37`'s pipe span, the career-lane `Q53` decision held for Alexander, `Q64`'s design question Alexander's). Appended at the foot; `docs/queue-index.md` re-rendered in the same commit.
1. **The blind spot, measured at `4602bd5` from inside a detached worktree.** That commit's message states "TOOLS.md re-rendered, byte-identical"; the map it ships is not the render of its own tree. `python3 tools/tool_map.py --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 whose document was not re-rendered. `--repo . --selftest` reads **15/16, rc 1**, the failure being the currency cell. Before the commit existed, the same commands on the pre-commit worktree (`HEAD` = `ea8fe24`, the edit only on disk) read **rc 0** and **16/16**: the guard was working, and it was guarding the last commit instead of the one about to be made.
2. **Why the gate stayed green, and the claim that did not survive re-measurement.** `selftest_sweep.py --repo .` at `4602bd5` reads **21 tools, 21 OK, 0 fatal**, measured today. `selftest_sweep.py:449` runs each tool as `[python, path, "--selftest"]` with **no `--repo`**, and `tool_map.py`'s `DEFAULT_REPO` is an absolute path, so its selftest graded `/root/troupe-standup` — the clean clone — from inside the drifted worktree. The first draft of this tick's docstring claimed the sweep leg went fatal at 17:28:26Z and stayed fatal for two hours; that reading did not reproduce, and the sentence was **deleted and replaced by the measurement above**, not softened. The sweep's wrong-tree axis is a second defect and is **not fixed here**.
3. **What landed** in `tools/tool_map.py`: 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) and leaves `HEAD` on `git ls-tree`; `main()` skips `rev-parse --verify` only for the sentinel; `graderc()` passes a rev so the new cells exercise the parser. Four cells were added: a clean worktree renders exactly what `HEAD` renders; the map reads the file on disk, not the blob at `HEAD`; 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 cells, 20/20 rc 0.**
4. **Measured outside the fixture, on a second worktree.** A clean detached worktree at `190d1aa`: `--check` **rc 0**, `--rev WORKTREE --check` **rc 0**, both against the same document. With one line appended to `verify.py`'s `write-axis` description and nothing committed: `--rev HEAD` still **rc 0**, `--rev WORKTREE` **rc 1, `DRIFT at line 49`**. Transcript: `/root/.hermes/wizard-outputs/volt/2026-10-21-worktree-mode/measure.txt`.
5. **Verification at the commit carrying this record.** `tool_map.py --selftest` **20/20 rc 0**; `--repo . --check docs/TOOLS.md` **rc 0**; `--repo . --rev WORKTREE --check docs/TOOLS.md` **rc 0** (no derived cell moved, so the document is unchanged); `verify.py --repo .` **6 legs, 6 pass, 0 fault, 0 ungradable, 0 skipped**; `doc_command_check.py --repo . --fail` **65 commands over 19 oracles, OK 65** (one more command than the previous tick: this record's usage line); `doc_census_check.py --repo . --tools tools/tool_map.py --fail` **0 INTERNAL SPLIT, 0 STALE**.
6. **What it does not claim.** `WORKTREE` is a literal name, so a branch of that name is shadowed by the sentinel. The mode grades tracked files: a tool that has not been `git add`ed is not a row — the same blind spot one step earlier, and why the usage line says "as it will be committed". The currency cell stays `HEAD`-scoped by design. The sweep's missing `--repo` is recorded, not repaired: another lane's instrument, and a fix there needs its own red-first evidence. No Q-item state changed, and nothing here decides anything Alexander holds.
— Volt
+11 -11
View File
@@ -56,7 +56,7 @@
| 46 | Q44 | 6162-6705 | 88496 | opening | The pin class: redirect every constant composed from the live data root, not only `SNAPSHOT_PATH` *(Helm lane; opened 2026-10-12 by the pipe, per Q43's own closing sentence — "extend the redirect … | DONE 2026-10-12 — `recoveries/helm-root-redirect.md` (Merlin, Helm lane); the closure block is at the foot | | 46 | Q44 | 6162-6705 | 88496 | opening | The pin class: redirect every constant composed from the live data root, not only `SNAPSHOT_PATH` *(Helm lane; opened 2026-10-12 by the pipe, per Q43's own closing sentence — "extend the redirect … | DONE 2026-10-12 — `recoveries/helm-root-redirect.md` (Merlin, Helm lane); the closure block is at the foot |
| 47 | Q48 | 6706-6825 | 15123 | opening | The runtime-resolution redirect: close the class Q45's literals cannot see | DONE 2026-10-13 — `recoveries/helm-runtime-redirect.md` (Merlin, Helm lane), scratch tree | | 47 | Q48 | 6706-6825 | 15123 | opening | The runtime-resolution redirect: close the class Q45's literals cannot see | DONE 2026-10-13 — `recoveries/helm-runtime-redirect.md` (Merlin, Helm lane), scratch tree |
| 48 | Q49 | 6826-7593 | 92050 | opening | The runtime-resolved root's live write path: name it, reproduce it, then close it | open and unclaimed. Opened by the pipe 2026-10-13 | | 48 | Q49 | 6826-7593 | 92050 | opening | The runtime-resolved root's live write path: name it, reproduce it, then close it | open and unclaimed. Opened by the pipe 2026-10-13 |
| 49 | Q45 | 7594-10538 | 525440 | opening | §9 follow-on — the home-directory channel *(Helm lane; Merlin)* | OPEN — awaiting Alexander's zoom/whole-shop answer; build is the pipe's. | | 49 | Q45 | 7594-10554 | 529776 | opening | §9 follow-on — the home-directory channel *(Helm lane; Merlin)* | OPEN — awaiting Alexander's zoom/whole-shop answer; build is the pipe's. |
## Ids with no `## ` heading ## Ids with no `## ` heading
@@ -71,9 +71,9 @@ Opened as records inside another section. `line` is the record's own opening lin
| Q50 | 6990 | 6990 | 46 | Q49 | BULLET+BOLD | BOLD-LEAD | | Q50 | 6990 | 6990 | 46 | Q49 | BULLET+BOLD | BOLD-LEAD |
| Q51 | 7106 | 7106 | 37 | Q49 | BULLET+BOLD | BOLD-LEAD | | Q51 | 7106 | 7106 | 37 | Q49 | BULLET+BOLD | BOLD-LEAD |
| Q52 | 7167 | 7167 | 90 | Q49 | BULLET+BOLD | BOLD-LEAD | | Q52 | 7167 | 7167 | 90 | Q49 | BULLET+BOLD | BOLD-LEAD |
| Q53 | 7292 | 7246 | 150 | Q49 | BULLET+BOLD | BOLD-LEAD | | Q53 | 7292 | 7246 | 151 | Q49 | BULLET+BOLD | BOLD-LEAD |
| Q63 | 9918 | 9909 | 17 | Q45 | BULLET+BOLD | BOLD-LEAD | | Q63 | 9918 | 9909 | 17 | Q45 | BULLET+BOLD | BOLD-LEAD |
| Q64 | 10308 | 10308 | 13 | Q45 | BULLET+BOLD | BOLD-LEAD | | Q64 | 10308 | 10308 | 14 | Q45 | BULLET+BOLD | BOLD-LEAD |
**Not in this document.** Ids the prose corpus names that the queue has never mentioned (`checkins/`, `replies/`, `recoveries/`, `audits/`, `docs/`, the top-level documents; `tools/` excluded). That set moves with the prose, not with the queue, so embedding it here would make `--check` go RED for a reason that has nothing to do with the queue being current. It is reported on demand: `python3 tools/queue_index.py --outside`. **Not in this document.** Ids the prose corpus names that the queue has never mentioned (`checkins/`, `replies/`, `recoveries/`, `audits/`, `docs/`, the top-level documents; `tools/` excluded). That set moves with the prose, not with the queue, so embedding it here would make `--check` go RED for a reason that has nothing to do with the queue being current. It is reported on demand: `python3 tools/queue_index.py --outside`.
@@ -218,7 +218,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
"form": "BOLD-LEAD", "form": "BOLD-LEAD",
"id": "Q53", "id": "Q53",
"line": 7292, "line": 7292,
"refs": 150, "refs": 151,
"section": "Q49", "section": "Q49",
"shape": "BULLET+BOLD" "shape": "BULLET+BOLD"
}, },
@@ -236,7 +236,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
"form": "BOLD-LEAD", "form": "BOLD-LEAD",
"id": "Q64", "id": "Q64",
"line": 10308, "line": 10308,
"refs": 13, "refs": 14,
"section": "Q45", "section": "Q45",
"shape": "BULLET+BOLD" "shape": "BULLET+BOLD"
} }
@@ -246,9 +246,9 @@ The block below is the parse this document renders, byte for byte. `--check` com
"end_line": 8, "end_line": 8,
"line": 1 "line": 1
}, },
"queue_bytes": 1417701, "queue_bytes": 1422037,
"queue_lines": 10538, "queue_lines": 10554,
"queue_sha256": "630b8b3f6732163d2f722aa0785dbb32217b6026f11d315d3f79b014c000d208", "queue_sha256": "7d367619ac1c9213f714d47c201838df184e7a4fb0c9d0fb88e1ff7bd95a8ab6",
"sections": [ "sections": [
{ {
"bytes": 3139, "bytes": 3139,
@@ -827,11 +827,11 @@ The block below is the parse this document renders, byte for byte. `--check` com
"title": "The runtime-resolved root's live write path: name it, reproduce it, then close it" "title": "The runtime-resolved root's live write path: name it, reproduce it, then close it"
}, },
{ {
"bytes": 525440, "bytes": 529776,
"end_line": 10538, "end_line": 10554,
"id": "Q45", "id": "Q45",
"line": 7594, "line": 7594,
"lines": 2945, "lines": 2961,
"role": "opening", "role": "opening",
"role_detail": "", "role_detail": "",
"status": "OPEN \u2014 awaiting Alexander's zoom/whole-shop answer; build is the pipe's.", "status": "OPEN \u2014 awaiting Alexander's zoom/whole-shop answer; build is the pipe's.",
+105 -17
View File
@@ -2,12 +2,12 @@
"""tool_map.py -- the instruments get a map, and the map is generated from the tree. """tool_map.py -- the instruments get a map, and the map is generated from the tree.
WHY THIS EXISTS WHY THIS EXISTS
`tools/` holds sixteen scripts and there is no document that says what they `tools/` holds one script per instrument and there is no document that says
are. A reader meets them one at a time -- in a `verify.py` leg, in a queue what they are. A reader meets them one at a time -- in a `verify.py` leg, in a
block, in a reply -- and nothing states the whole set: which tool grades what, queue block, in a reply -- and nothing states the whole set: which tool grades
which flags each one actually accepts, which of them the two probe axes cover, what, which flags each one actually accepts, which of them the two probe axes
and which leg of the operator's gate a failure shows up in. The closest thing cover, and which leg of the operator's gate a failure shows up in. The closest
is `verify.py`'s `LEGS` table, which names five of the sixteen. 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 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 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 docstring, flags or probe rows without re-rendering the map turns the sweep
RED instead of leaving a stale document that reads as current. 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 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 starts is `<tool> --help`, which does no work by construction; it never runs a
documented command, so it cannot write, post, or deploy anything. documented command, so it cannot write, post, or deploy anything.
@@ -52,8 +88,9 @@ EXIT CODES (the tools/ contract)
USAGE USAGE
python3 tool_map.py --repo . --write docs/TOOLS.md # render 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 . --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 --repo . --json out.json
python3 tool_map.py --selftest # 16 cells python3 tool_map.py --selftest # 20 cells
""" """
from __future__ import annotations from __future__ import annotations
@@ -70,6 +107,10 @@ DEFAULT_REPO = "/root/troupe-standup"
DEFAULT_DOC = "docs/TOOLS.md" DEFAULT_DOC = "docs/TOOLS.md"
HELP_TIMEOUT = 30 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. # 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 # `-h`/`--help` are dropped: every tool has them, and a column of sixteen
# identical pairs is noise, not information. # identical pairs is noise, not information.
@@ -84,13 +125,29 @@ def git(repo, *args, inp=None):
def read_blob(repo, rev, path): 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}") rc, out, err = git(repo, "show", f"{rev}:{path}")
return out if rc == 0 else None return out if rc == 0 else None
def tracked_tools(repo, rev): def tracked_tools(repo, rev):
"""Sorted `tools/*.py` basenames tracked at `rev`.""" """Sorted `tools/*.py` basenames tracked at `rev`.
rc, out, _ = git(repo, "ls-tree", "-r", "--name-only", rev, "--", "tools/")
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: if rc != 0:
return None return None
names = [p.split("/")[-1] for p in out.split("\n") 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", cell("--repo a directory that is not a git work tree: rc 2",
rc_norepo == 2, f"rc {rc_norepo}") 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 ---------- # -- the currency cell: the reason this document stays true ----------
here = os.path.join(repo, "docs", "TOOLS.md") here = os.path.join(repo, "docs", "TOOLS.md")
try: try:
@@ -436,11 +521,13 @@ def selftest(repo):
return 0 if passed == len(cells) else 1 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.""" """rc of a full `--check` run against a directory that is not a repo."""
proc = subprocess.run([sys.executable, os.path.abspath(__file__), cmd = [sys.executable, os.path.abspath(__file__), "--repo", td]
"--repo", td, "--check", os.path.join(td, "x.md")], if rev:
capture_output=True, text=True) cmd += ["--rev", rev]
cmd += ["--check", os.path.join(td, "x.md")]
proc = subprocess.run(cmd, capture_output=True, text=True)
return proc.returncode return proc.returncode
@@ -488,10 +575,11 @@ def main():
if rc != 0: if rc != 0:
print(f"tool_map: cannot read repo: {args.repo} is not a git work tree") print(f"tool_map: cannot read repo: {args.repo} is not a git work tree")
return 2 return 2
rc, _, err = git(args.repo, "rev-parse", "--verify", f"{args.rev}^{{commit}}") if args.rev != WORKTREE:
if rc != 0: rc, _, err = git(args.repo, "rev-parse", "--verify", f"{args.rev}^{{commit}}")
print(f"tool_map: cannot read rev {args.rev}: {err.strip().splitlines()[0] if err.strip() else 'no such object'}") if rc != 0:
return 2 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) data = collect(args.repo, args.rev)
text = render(data) text = render(data)