tracetree · diff
git:20261015.e4b28a5 to git:20261015.18c4986
2 added, 2 removed. Audit A to A.
---
name: tracetree
description: Internal kit of tracer and sub-tracer — tracetree.py opens a trace run, stores each node's hops once, waits for the whole tree, checks every quote against the file and maps the components. Not invoked directly — ask tracer; a finished run → /tracer:use.
visibility:
visible-to:
include: [tracer, sub-tracer]
---
# tracetree
Run `python3 $HOME/.claude/skills/tracetree/tracetree.py {command}`, the path written in full in every command, `$HOME` and never `~`. Python 3 standard library; it reads the repo and writes only the run directory under `/tmp/{project}/tracer/`. A failure prints `TRACETREE FAILED — {reason}` and exits 2.
| Command | Who runs it | Does |
| --- | --- | --- |
| `new ROOT [--seek REGEX] [--questions]` | tracer, when the brief carries no `RUN:` | Creates the run directory, holding `ROOT` and, in a git tree, `BASE` (the HEAD commit), and prints its path; `--questions` stores stdin's numbered questions as `RUN/questions`; `--seek` then prints a `PRIOR` line for each verified hop of an earlier run on the same root, from the last 6 hours, whose path or line matches, re-read from disk and cut at 160 characters — at most 30, then a `PRIOR:` count naming the runs it could not read |
| `seek RUN REGEX` | tracer, on a brief's `RUN:` | Prints the run's path, then the `PRIOR` lines of `new --seek` for that existing run |
- | `recall ROOT [--since-hours N] [--before RUN]` | `intake`; tracer's caller | Reads numbered questions on stdin; serves each from earlier runs sharing ROOT's git common dir (a removed worktree's included; `--before RUN`: only runs started strictly before RUN, never RUN itself): answer lines naming a question's backticked text, path or identifier, each cite placed against ROOT now — `FRESH`, `MOVED` (re-addressed, fresh) or `STALE`, also `STALE` when a hop its node walked no longer holds; every identifier, `$VAR`, backticked token and path the line names, cites included, checked as a whole word against the `git diff` from the run's BASE plus untracked files, a changed occurrence making it `DELTA` with at most 5 `+`/`- path:N text` rows, then `+{k} more`; a closed-world line (nothing, no, none, only, every, never, N callers) naming none of them is a `GAP` row; per question `RECALL Q{n} · {fresh}/{total} lines fresh · runs {ids}`, its `FRESH` and `DELTA` lines, `STALE Q{n} · {reason}` and `GAP Q{n} · {why}` rows; a run it cannot read `UNREADABLE {run}: {reason}`; past 12 KB the lowest-match lines go, `RECALL CUT {k} lines past the 12 KB cap`; last `RECALL COVER {served}/{questions} · {stale} stale · {delta} delta` (` · {k} unwitnessed` when GAP rows exist) |
+ | `recall ROOT [--since-hours N] [--before RUN]` | `intake`; tracer's caller | Reads numbered questions on stdin; serves each from earlier runs sharing ROOT's git common dir (a removed worktree's included; `--before RUN`: only runs started strictly before RUN, never RUN itself): answer lines naming a question's backticked text, path or identifier, each cite placed against ROOT now — `FRESH`, `MOVED` (re-addressed, fresh) or `STALE`, also `STALE` when a hop its node walked no longer holds; every identifier, `$VAR`, backticked token and path the line names, cites included, checked as a whole word against the `git diff` from the run's BASE plus untracked files, a changed occurrence making it `DELTA` with at most 5 `+`/`- path:N text` rows, one per changed line, then `+{k} more`; a closed-world line (nothing, no, none, only, every, never, N callers) naming none of them is a `GAP` row; per question `RECALL Q{n} · {fresh}/{total} lines fresh · runs {ids}`, then `ANSWERED Q{n}` when every checked line is FRESH and every anchor sits on a kept FRESH line, its `FRESH` and `DELTA` lines, `STALE Q{n} · {reason}` and `GAP Q{n} · {why}` rows; a run it cannot read `UNREADABLE {run}: {reason}`; past 12 KB STALE and GAP rows go first, then DELTA occurrence rows shrink to `+{k} rows`, then DELTA lines, FRESH lines last, fewest anchor hits first within each, `RECALL CUT {k} lines past the 12 KB cap · {s} stale/gap, {d} delta, {f} fresh`; last `RECALL COVER {served}/{questions} · {stale} stale · {delta} delta` (` · {k} unwitnessed` when GAP rows exist) |
| `start RUN ID PATH:LINE [QUESTION]` | sub-tracer, first call | Registers node `ID` as running (`{ID}.started`, holding `PATH:LINE` and QUESTION for the map page), prints the stored text of a `Q{n}` QUESTION, a line naming `PATH` when it is over 1 MB, then `PATH` from `LINE`−3 to `LINE`+40, numbered; `PATH` may be `{rev}:{path}` |
| `write RUN ID` | sub-tracer, once | Stores stdin as `{ID}.hops`, each `@` rewritten to its resolved address, beside `{ID}.write.json` (each hop's status and file hash at write); refuses an incomplete file unstored, refuses once unstored a file with a hop that does not hold — the file's own lines printed beside it — stores the second attempt as it is, and refuses any write after the store |
| `wait RUN ID… [--timeout S] [--grace S] [--start S]` | tracer | Blocks until the named nodes and every node that ran `start` have written, then prints the answers view of the named nodes and their children (COUNTS tallies the whole run), the `MAP` line and an `OPEN` block — each node's NOT READ and LEAD lines suffixed `run: node {ID}`, a `path:line` no hop holds marked `(grep only — read it)`, a line range (`path:A-B`, `path lines A-B`) verified hops hold marked `(held: node {ID} verified {path}:{lines})`; the full view goes to `RUN/answers.txt`, and past 28000 characters the printout keeps the flagged lines, OPEN and MAP whole and trims the answers; a named node not started after `--start` seconds (default 120) is reported `never started`, never waited for; a timeout names each node that started and never wrote |
| `answer RUN [Q…] [--timeout S] [--grace S] [--start S]` | tracer's caller | Blocks until every node whose card named one of the questions, and every child it split off, has written and none changed for `--grace` seconds (default 5), then prints a head line saying each cite was re-read from disk, each answered question's text with its nodes' answers view, and their `OPEN` block; no number answers every question in `RUN/questions`; a question no node took `--start` seconds (default 90) after the first node started prints `NO THREAD`, one still walking at `--timeout` (default 300) `STILL WALKING` with its nodes; past 28000 characters the full printout goes to `RUN/answer-Q{n…}.txt` and the answers are trimmed |
| `read RUN FILE:A-B…` | tracer | Prints each block numbered by the file's lines (`FILE` may be `{rev}:{path}`); refuses a block over 30 lines or one a verified hop holds (naming its `--full ID`), and a second call before the next `wait` |
| `show RUN FILE:A-B…` | tracer's caller | Prints each block as the file holds it now under `== FILE:A-B · held N/M`, each line a verified hop holds marked `[{IDs}]` (re-addressed after edits), then the holding nodes' NOT READ and LEAD lines; any size, any number of calls; a block not `FILE:A-B`, in no file or outside the file is refused on its own line |
| `check RUN` | tracer | The answers view: each node's ANSWER lines, cites checked against the verified hops |
| `check RUN --hops [ID]` | tracer | Every hop of the run, or of `ID`'s subtree, one line each, re-read from disk |
| `check RUN --full [ID]` | tracer | Every hop with every line of its quoted range, numbered |
| `map RUN [--from NAME \| --to NAME] [--all] [--json \| --html OUT]` | tracer's caller | The component map from the verified hops (`tracemap.py`): a text declaration, the tree below (`--from`) or above (`--to`) a component, JSON, or on request the Tracer Loom, one self-contained page (`loom.py`, `tracemap_html.py`) with four views: the Loom wall of the traced files with every reader's hops sewn in and a replay of the walks one hop per step, the chain of custody from each question to the line behind each claim, the hourglass of the `--to` / `--from` component (`--all` shows walk-order links) and the stitch key; `wait` writes the text map to `RUN/map.txt` and prints a `MAP` line |
| `book [--html OUT]` | tracer's caller | One row per run under `/tmp/{project}/tracer`, newest first: id, repo, questions, wait time, nodes and the hop tallies (held, re-addressed, worn, holes), claims and flagged answers; a run the kit cannot load is listed with its reason; `--html OUT` writes the swatch book, one swatch per run, sortable newest, most worn or most flagged |
| `segment ROOT [--rev SHA] --seed SEED…\|--seeds FILE [--within PATH…] [--variant contract\|package\|cochange\|tracer] [--run RUN] [--floor 150] [--read-max 0.5] [--max 4] [--json]` | flights-foreman, once its door list exists and before it reads any segment's code | Cuts the doors' blast area into a SPINE of shared contracts the parent freezes first and at most --max child segments: SEGMENT header, SEED or NOTFOUND per seed, SPINE with CONTRACT rows, PARENT, SEGMENT S{n} each with ORDER, ESCALATE and `DECIDE S{n} CHILD\|KEEP · {reason}` (CHILD: it builds a `new=` seed or its seeds carry 3+ rows), VERDICT SPLIT k \| SOLO ending `· CHILD n` \| EMPTY; exit 0 split/solo, 1 EMPTY (every seed NOTFOUND with its reason), 2 `TRACETREE FAILED`. Seeds: Name, pkg.Name, Type.Name, path, dir/, path:LINE, gone=PATH, use=Name, new=NAME (a symbol, field, type or file the change creates: never searched or NOTFOUND, a file placed, anything else beside its row's seeds, spine `NEW` when they span two segments), ROW=SEED, and in FILE a `{row}:` line head tying the line's seeds to its row, never itself a seed; a name under 4 characters or a common verb is NOTFOUND; past 60 files a seed is BROAD. Feed it the door list, never request text; a path that does not exist yet is placed as `create` |
| `cutgate` | `flights-foreman`'s PreToolUse hook on `Edit\|Write\|MultiEdit\|NotebookEdit` | Denies a code edit until the agent's own transcript (its `agent-{id}.jsonl` when the payload names an `agent_id`) holds a Bash `segment` call whose result has a `VERDICT` line other than `EMPTY`; a write under `$HOME/.local/state/pfm/` or `/tmp/`, and a child whose first message or the brief file it names there holds a `SEGMENT ` or `DEPTH 3` line, pass silently; every other tool passes untouched; a payload or transcript it cannot read allows the edit with an `additionalContext` naming the cause, and the cause on stderr |
- | `intake` | pfm's launch hook `tracer-intake`, a PreToolUse hook on `Agent\|Task` | On a `tracer` spawn with numbered questions and no `RUN:` line: opens the run (ROOT from the brief's `Repo root:`, else the hook's cwd), stores the questions, adds `RUN: {run}` to the brief, writes `recall` of the questions to `{RUN}/recall.txt` and adds `RECALL: {path}` under the RUN line, and tells the caller the final is its answer and `answer` gives raw facts early, never a ruling; every other call passes untouched; a run it cannot open passes the spawn on unchanged with the reason |
+ | `intake` | pfm's launch hook `tracer-intake`, a PreToolUse hook on `Agent\|Task` | On a `tracer` spawn with numbered questions and no `RUN:` line: opens the run (ROOT from the brief's `Repo root:`, else the hook's cwd), stores the questions, adds `RUN: {run}` to the brief, writes `recall` of the questions to `{RUN}/recall.txt` and adds `RECALL: {path}` under the RUN line, and tells the caller the final is its answer and `answer` gives raw facts early, never a ruling; denies a `sub-tracer` card whose `QUESTION:` names only questions recall.txt marks `ANSWERED`, unless the card carries `REASON: {why}`, logged to `intake.log`; every other call passes untouched; a run it cannot open passes the spawn on unchanged with the reason |
## The check
A file past 1 MB is searched only within 30 lines of a hop's address and of an answer cite's: a quote further off is `REJECTED` (`quote not within 30 lines of the address`) and a cite's quoted text is not moved there; a file past 128 MB is not read and its hops are `REJECTED`. Each such file is named once, as a `·` note after the WARNINGS block and by `start`.
A hop, a `start` FROM, a `read` block and an answer cite pinned to a git revision of ROOT read `{rev}:{path}:{line}`: the file is that commit's (`git show`, read-only, nothing checked out), stored and printed as `{commit, 12 hex}:{path}:{line}`. It is checked like any hop, never placed in the working tree, never `≈ DRIFTED`; a path the commit lacks or holds as a directory, a file there past 128 MB, or a revision naming no commit is `REJECTED` with that reason, and a git that cannot answer with `git could not say: {reason}`.
Each hop's quote is re-read from its file: found at its address it is verified; found within 30 lines or elsewhere in the file it is re-addressed. A path that names no file from ROOT — `server.go`, `internal/x.go`, `pkg/x.go` for `app/internal/x.go`, `a/../x.go` — resolves to the one file under ROOT ending in it, else to the one file of that basename holding the quote near the line; a quote absent from its named file resolves to the one sibling file holding it within 2 lines of the cited line, else, for a line of 24 or more non-space characters, to the one file of the same extension holding that line and the only file under ROOT that does; each prints as its full path with `(path resolved from …: {how})`. A hop no step places is `REJECTED` — unless it held when walked: its node's write recorded it verified (`{ID}.write.json`), or the commit the run started from (`RUN/BASE`, else the reflog) holds it, and the file changed since; then it is `≈ DRIFTED`, counted apart. A git lookup that fails prints `DRIFT UNKNOWN` and leaves the hop REJECTED. The printout always shows the file's own text, never the node's.
In the answers view, a cite whose backticked text sits on another line of the cited file is moved there (`↻`), and so is a cite at the line its reader gave a hop the kit re-addressed, by that hop's shift; a line citing no verified hop and no quoted text is `unbacked`; a backticked text found in no cited file is `?`; a `SPLIT` child that started and wrote nothing is `MISSING`. A cite whose only hops drifted is `≈`. The last line is `COUNTS:`, the run's tally of nodes, hops, verified, moved, rejected, drifted, placed, followed, refused and flagged lines.
Every `wait` and `check` view opens with the WARNINGS block: a `⚠` header naming how many nodes broke the hop contract, then a `⚠` line per node that named paths the kit had to place away from the cited line, stored hops after a refusal or has REJECTED hops, a run line for drift, and — beside those — one for hops that followed a file moved since the walk, which breaks no contract. A placed hop whose quote sits at its very cited line only miscopied its path: it breaks no contract and prints a `· node {ID}: the kit corrected a miscopied path …` note after the block, or after `WARNINGS: none`. Flagged answers stay in COUNTS alone, so a clean walk prints the single line `WARNINGS: none`. In the answers view every answer line of a node that broke the hop contract, and every line whose cite only such a node's hops back, ends `⚠ node {ID} broke the hop contract`. The MAP line, the page's red block and the JSON's `warnings` carry the same lines, the JSON's `notes` and a quiet block on the page the notes; `write` warns its own node on stderr.