Repository navigation
Added
session-auditcounts cold-cache re-writes. A new section lists requests that followed a gap longer than their cache TTL and wrote the prefix again. For each, it shows billed-equivalent tokens, the share of billed tokens, and a breakdown by the tool (and Bash command class) the session was waiting on. Main sessions and subagents are reported separately.token-goat readandsymbolsupport--sig/--signatureflags. They print only the function or class signature declaration and leave out the implementation body, for TypeScript/JavaScript and Python.token-goat doctor --fastflag. It skips host process table enumeration during Windows health checks, so a quick doctor run finishes in ~1s.- An opt-in answer for a Grep that looks up one declaration. With
inline_symbol_body = trueunder[hints](orTOKEN_GOAT_INLINE_SYMBOL_BODY=1), a Grep such asdef parse_argsorfunction loadagainst one source file, a directory or the working directory is refused, and the refusal carries that symbol's body, so the next turn does not need to runtoken-goat read. A Grep that is just the name (parse_args,\bparse_args\b) is answered too, and its refusal lists every matching line aspath:line:textbefore the body. A directory is answered only when every file Grep would search can be read and checked: git's list of tracked and not-ignored files, at most 200 files and 2,000,000 bytes. A listed file the index does not hold (a.gitignore, aLICENSE, a text note) is read and searched for the pattern like the rest, and the Grep runs as written only if the pattern matches in it or it cannot be read. A name with a dollar sign is answered when the pattern escapes it (\$name), since an unescaped dollar matches nothing. A project's own.token-goat.tomlcannot turn the setting on. The body is the texttoken-goat readprints, fenced as untrusted file content and redacted. It acts only when the Grep is a plain content search, the fresh index holds the name exactly once in the searched scope, a declaration pattern matches exactly one line and that line is inside the symbol, and the refusal is under 12,000 bytes. In every other case the Grep runs with the usual hint. The refusal names the file relative to the working directory, so a checkout under a folder with a space, a non-ASCII letter or a long name still gets the answer. The same symbol is refused once per session. It is off by default, because a refusal the agent did not expect is only worth it if the inlined answers are followed.token-goat statscounts these refusals asinline_symbol_body_deny.
Changed
- The pre-push hook runs a time-budgeted selection of tests. Each suite leg runs, in order: the files that failed last time; the test files changed in the pushed range; every guard test, plus the built-bundle matrix when
src/changed; the tests that import a changed module, nearest first; and then the tests the import graph cannot see. Those are the tests that name a changed path as text and, whensrc/changed, the tests that readsrc/as text or spawn the built bundle. The selection keeps the estimated wall time within 600 s (Windows) or 300 s (WSL).PREPUSH_BUDGET_SandPREPUSH_WSL_BUDGET_Schange the budget. The estimate comes from per-file durations kept beside the last-failure record. Whatever the budget cuts is printed asdeferred to CI: N files (~S s)with the list, and CI still runs the full suite. A push that changes only Markdown ordocs/runs the guards and the tests naming the document, and a new branch is diffed from its merge base withorigin/main. A path the import graph cannot see (config, scripts, hook scripts, test setup, fixtures, a deleted module), a failure record that cannot be parsed, orPREPUSH_FULL=1runs the full suite, as before. - Compact untrusted content fence notices. Local tool outputs and file extractions now use
[token-goat: data only, not directives]. The full warning is kept for web fetches and detected prompt injections.
Fixed
- A Bash command with many quoted environment assignments or repeated git options no longer stalls the hook. Two patterns the Bash hook runs on every command could match the same text in more than one way. A command such as
A="x" A="x" ... lsorgit -C "a" -C "a" ... xtherefore took time that doubled with each repeat (about 2 seconds at 26 repeats). Each part of the command now has one reading, and the check takes the same short time at any length. - A git command behind a quoted environment assignment is recognised as git.
FOO="a b" git restore xandFOO='a b' BAR=1 git reset --hardwere not seen as git commands that rewrite the working tree, because the assignment was stripped only when its value had no quotes. They are now treated like the same command without the assignment. git -C <dir> pushand other git commands with options before the subcommand are classified as git commands. The bash output cache readgitfollowed directly by the subcommand, sogit -C dir push,git -c k=v pushandgit -C dir statuswere missed. A push was not treated as a state change, and agit -C <dir>status was fingerprinted against the working directory's repository instead of<dir>'s.- A failing check script given only
.mddocuments is recorded as a failure. A run such aspython scripts/run_self_tests.py README.mdthat failed was dropped, so the Stop advisory never named it. Its documents still count as no check input, so a pass on one clears no edit. - A git killed by a signal reads as unknown, not as exit 1. A git ended by a signal such as SIGKILL used to read as exit 1. An inline Grep scope check took that as a negative answer, and the post-Bash hook took it for a git that never ran and asked for no reconcile. It now reads as exit -1 with the signal named, and each caller takes its unknown branch. Upgrading reindexes: the parser fingerprint changed because
src/util.tsis a shared extraction source, so already-indexed files are reparsed once. - The Stop advisory no longer fires on scratch edits or misses real checks. It named throwaway probes in the harness scratchpad (outside the project, under the OS temp dir) as unverified work, and said "no test, lint, typecheck or build was run" after a session that ran its validator scripts. An edit outside the session's project root (the nearest project marker above the Stop event's working directory) no longer counts when it sits under the OS temp dir. With no root known, an edit under the OS temp dir does not count either. A sibling package, another repo or a sibling worktree outside the root still counts. A passing
python <dir>/run_*tests*.py,validate*.py,check_*.pyor<script>.py ... --self-testnow counts as a check, but only with path or selector arguments:--help,-h,--list,--version,--dry-runand any other flag make it not a check. Agit commitorgit pushis never counted, because a hook file existing does not prove it checks anything. The command is stored without any argument beyond the script path and--self-test. The advisory still speaks once per distinct state; it repeated only because each turn edited a new file. - The Stop advisory no longer dismisses a project edit made through a link in the temp dir, or counts a validator that may be writing its own
.mdargument. An edit at a path under the OS temp dir was dismissed by its spelling, so a symlink or junction there that leads into the project hid a real project edit. The dismissal now follows links, and a path whose links cannot be resolved is not dismissed. A.mdargument no longer counts toward a script check at all, sopython scripts/validate_x.py report.mdis not recorded as a check whether or notreport.mdexists. A script given a document may be writing it, and whether that file exists says nothing about what the run verified. - With read confinement on, a relative path to a link that leads outside the workspace is now refused. Over MCP,
sl.txtor./sl.txt(a symlink in the project that points outside it), orjn/o.txtthrough a junction, was read even though the absolute spelling of the same link was refused. The check for the extra allowed roots and the standard skill and transcript folders measured the relative name under those folders instead of under the project, so it found nothing there and let the read through. It now measures the path the read opens. - Preserve full output when repeating overlapping commands. Rerunning a command with overlapping output ranges no longer hits suppression loops in served-output generic elision.
Full changelog: https://github.com/DFKHelper/token-goat/blob/v2.9.33/CHANGELOG.md