token-goat 2.9.26 → 2.9.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +30 -88
  2. package/SECURITY.md +14 -0
  3. package/dist/{token-goat-chunk-OZRSREKR.mjs → token-goat-chunk-2AWQ2R5X.mjs} +2 -2
  4. package/dist/{token-goat-chunk-BH4ZVRB2.mjs → token-goat-chunk-2NNUA2LR.mjs} +4 -4
  5. package/dist/token-goat-chunk-2WOUYWLM.mjs +955 -0
  6. package/dist/{token-goat-chunk-IKZLSRII.mjs → token-goat-chunk-435UQTKD.mjs} +5 -3
  7. package/dist/{token-goat-chunk-62IHOU6L.mjs → token-goat-chunk-44BM7DDQ.mjs} +5 -3
  8. package/dist/token-goat-chunk-52NSDWYY.mjs +96 -0
  9. package/dist/{token-goat-chunk-CXBOFPF4.mjs → token-goat-chunk-67BNIKKO.mjs} +32 -18
  10. package/dist/{token-goat-chunk-XBESNWEX.mjs → token-goat-chunk-6AK46ZT7.mjs} +2 -3
  11. package/dist/{token-goat-chunk-7SXPJOSZ.mjs → token-goat-chunk-7CPMDFGB.mjs} +166 -40
  12. package/dist/token-goat-chunk-ASLWOQ3I.mjs +14 -0
  13. package/dist/{token-goat-chunk-MWID3YOY.mjs → token-goat-chunk-BGYOZ6DO.mjs} +6 -4
  14. package/dist/{token-goat-chunk-PAV6NNY7.mjs → token-goat-chunk-C5YPNZKQ.mjs} +22 -8
  15. package/dist/{token-goat-chunk-D2X7XN5M.mjs → token-goat-chunk-CIQ2FTY3.mjs} +23 -242
  16. package/dist/token-goat-chunk-D4ETHJD6.mjs +246 -0
  17. package/dist/{token-goat-chunk-7Y6GWTII.mjs → token-goat-chunk-DNRPXHYH.mjs} +25 -17
  18. package/dist/{token-goat-chunk-L43RKNHA.mjs → token-goat-chunk-DNSPXQXQ.mjs} +2 -2
  19. package/dist/token-goat-chunk-DPNLUJWB.mjs +220 -0
  20. package/dist/token-goat-chunk-E4ZOJIZK.mjs +1382 -0
  21. package/dist/token-goat-chunk-E5TK5DNO.mjs +718 -0
  22. package/dist/{token-goat-chunk-VVROQBQJ.mjs → token-goat-chunk-EUSDXRXU.mjs} +62 -12
  23. package/dist/{token-goat-chunk-POOI26O6.mjs → token-goat-chunk-FTP4U4QA.mjs} +12 -8
  24. package/dist/token-goat-chunk-G23DWHV4.mjs +142 -0
  25. package/dist/{token-goat-chunk-MDV5VWF4.mjs → token-goat-chunk-G7IWXCFF.mjs} +223 -15
  26. package/dist/{token-goat-chunk-C2PG4K5D.mjs → token-goat-chunk-GLXRTTLE.mjs} +7 -5
  27. package/dist/{token-goat-chunk-M6CVNHTW.mjs → token-goat-chunk-H5J3C56Q.mjs} +95 -573
  28. package/dist/{token-goat-chunk-BAWKGODL.mjs → token-goat-chunk-HNFZZHFW.mjs} +2 -2
  29. package/dist/{token-goat-chunk-TG5ZPD6B.mjs → token-goat-chunk-HUYUHODZ.mjs} +1036 -759
  30. package/dist/token-goat-chunk-IVE6U7KV.mjs +53 -0
  31. package/dist/{token-goat-chunk-ZZT6PVB3.mjs → token-goat-chunk-IVWI2OCK.mjs} +72 -45
  32. package/dist/{token-goat-chunk-GMOUBOX4.mjs → token-goat-chunk-JTKCQMQA.mjs} +37 -5
  33. package/dist/{token-goat-chunk-H2CPLEMP.mjs → token-goat-chunk-KOPIR3KZ.mjs} +46 -23
  34. package/dist/token-goat-chunk-L7H3U27L.mjs +35 -0
  35. package/dist/{token-goat-chunk-FLEYOCAS.mjs → token-goat-chunk-LZNBQTDK.mjs} +6 -4
  36. package/dist/token-goat-chunk-MELOKFDJ.mjs +199 -0
  37. package/dist/{token-goat-chunk-RH27ZSPF.mjs → token-goat-chunk-NMBWLRHW.mjs} +164 -50
  38. package/dist/{token-goat-chunk-43JFN26X.mjs → token-goat-chunk-O6I2XU4P.mjs} +27 -967
  39. package/dist/{token-goat-chunk-G6EYO5CD.mjs → token-goat-chunk-ONXH6NZ5.mjs} +2 -87
  40. package/dist/token-goat-chunk-ORNFMIER.mjs +295 -0
  41. package/dist/{token-goat-chunk-LHUP4DXZ.mjs → token-goat-chunk-PGTH2U4C.mjs} +1 -1
  42. package/dist/{token-goat-chunk-5BQ7W7D7.mjs → token-goat-chunk-PKRWMHVP.mjs} +76 -59
  43. package/dist/{token-goat-chunk-DA43OE7L.mjs → token-goat-chunk-PQ53BSCF.mjs} +1 -1
  44. package/dist/{token-goat-chunk-STYRLQIW.mjs → token-goat-chunk-PYSUADX4.mjs} +2 -2
  45. package/dist/{token-goat-chunk-JALB3KWJ.mjs → token-goat-chunk-QO3LXVVR.mjs} +4247 -5005
  46. package/dist/{token-goat-chunk-L45IWWMC.mjs → token-goat-chunk-RLP6J7WR.mjs} +6 -6
  47. package/dist/token-goat-chunk-RQGGMPCR.mjs +205 -0
  48. package/dist/{token-goat-chunk-MBDJR6HU.mjs → token-goat-chunk-RTMVLHWX.mjs} +3708 -4003
  49. package/dist/{token-goat-chunk-E3K3BTEQ.mjs → token-goat-chunk-S4VSXURL.mjs} +8 -6
  50. package/dist/{token-goat-chunk-LKSXAMJB.mjs → token-goat-chunk-SE6E4BKJ.mjs} +1 -1
  51. package/dist/token-goat-chunk-TCQANXYC.mjs +351 -0
  52. package/dist/token-goat-chunk-TDUT2CL6.mjs +564 -0
  53. package/dist/{token-goat-chunk-LRIZ7K3F.mjs → token-goat-chunk-TGOXNAHW.mjs} +54 -111
  54. package/dist/{token-goat-chunk-WNBSCSAI.mjs → token-goat-chunk-UA5BKWWQ.mjs} +1 -1
  55. package/dist/{token-goat-chunk-RIB6XY2V.mjs → token-goat-chunk-UXSXQQIY.mjs} +1 -1
  56. package/dist/{token-goat-chunk-7EPYB34H.mjs → token-goat-chunk-VMFWNHGR.mjs} +84 -73
  57. package/dist/token-goat-chunk-VQ6F6T7X.mjs +36 -0
  58. package/dist/{token-goat-chunk-5M3NRJD2.mjs → token-goat-chunk-VZQEWRVH.mjs} +6 -4
  59. package/dist/{token-goat-chunk-OSUFN2FV.mjs → token-goat-chunk-XB5KKOCG.mjs} +5 -25
  60. package/dist/{token-goat-chunk-6E2IDLHF.mjs → token-goat-chunk-XYWPOSCN.mjs} +57 -615
  61. package/dist/{token-goat-chunk-CQHY4SL7.mjs → token-goat-chunk-YAGLESS3.mjs} +8 -5
  62. package/dist/token-goat-chunk-YKSRAWUU.mjs +33 -0
  63. package/dist/{token-goat-chunk-F2ARFHLJ.mjs → token-goat-chunk-YOOVFPTP.mjs} +2 -2
  64. package/dist/{token-goat-chunk-EEIDFMEM.mjs → token-goat-chunk-YZAAZU2S.mjs} +12 -3
  65. package/dist/token-goat-chunk-Z6KBMOBI.mjs +53 -0
  66. package/dist/{token-goat-chunk-4GEDH2WU.mjs → token-goat-chunk-ZER6EAXZ.mjs} +81 -70
  67. package/dist/token-goat-hook-client.cjs +475 -0
  68. package/dist/token-goat-hook-client.mjs +26 -0
  69. package/dist/token-goat-hook.mjs +31 -21
  70. package/dist/token-goat.core.mjs +9 -39
  71. package/docs/C4_RUNTIME_ARCHITECTURE.md +2 -2
  72. package/docs/cli.md +35 -23
  73. package/docs/install.md +18 -14
  74. package/package.json +1 -1
  75. package/dist/token-goat-chunk-ADMR3QVF.mjs +0 -41
  76. package/dist/token-goat-chunk-E6B2UFDF.mjs +0 -30
  77. package/dist/token-goat-chunk-GKZ2US5B.mjs +0 -187
  78. package/dist/token-goat-chunk-MZTO4FXH.mjs +0 -26
package/docs/cli.md CHANGED
@@ -41,15 +41,15 @@ token-goat pdf-extract manual.pdf --pages 12-15 --layout --head 120
41
41
  | `token-goat skill-section "<name>::<heading>"` | Extract a named section from an installed skill without reading the full skill file. |
42
42
  | `token-goat skeleton "file"` | Show all signatures in a file without bodies — typically 70–90% fewer tokens than a full read. `--force-refresh` reparses from disk first, bypassing a stale index. `--stats` adds a per-symbol reference count and doc-coverage flag, computed live from the index. `--grep <pattern>` narrows to symbols whose name matches a regex (a literal substring when the pattern is not valid regex), which is how you skim one area of a large file without dumping its whole symbol list; `--min-lines <n>` drops symbols shorter than N lines. Both compose, and if a filter removes everything the output says so and names the filter, rather than looking like a file with no symbols. Accepts a comma-separated file list (`"a,b,c"`) to cover several files in one call, one clearly-headed block per file; extra space-separated file arguments are reported in a note naming that comma form instead of being silently dropped. With `--json`, a comma-separated list returns one merged document (rows carry their own `filePath`), not one document per file. |
43
43
  | `token-goat outline "file"` | List top-level symbols with line ranges and docstring hints — one-glance file map. Doc hints are clipped to about a sentence with a visible ellipsis; the full doc comment is one `read "file::symbol"` away (`--json` carries it whole). `--force-refresh` reparses from disk first, bypassing a stale index. `--stats` adds a per-symbol reference count and doc-coverage flag, computed live from the index. `--grep <pattern>` narrows to symbols whose name matches a regex (a literal substring when the pattern is not valid regex), which is how you skim one area of a large file without dumping its whole symbol list; `--min-lines <n>` drops symbols shorter than N lines. Both compose, and if a filter removes everything the output says so and names the filter, rather than looking like a file with no symbols. Accepts a comma-separated file list (`"a,b,c"`) to cover several files in one call, one clearly-headed block per file; extra space-separated file arguments are reported in a note naming that comma form instead of being silently dropped. With `--json`, a comma-separated list returns one merged document (rows carry their own `filePath`), not one document per file. |
44
- | `token-goat yaml-outline <file>` | Structural summary of a YAML document (array shape / object key types) instead of a raw Read. Multi-document streams (`---`-separated) outline as an array of documents. |
44
+ | `token-goat yaml-outline <file>` | Structural summary of a YAML document (array shape / object key types) instead of a raw Read. Multi-document streams (`---`-separated) outline as an array of documents. `--filter TEXT` keeps only the top-level keys containing the text and reports how many matched. |
45
45
  | `token-goat yaml-query <file> <path>` | Extract one value or a projected/filtered subset from a YAML document by dot-path instead of a raw Read (same grammar as `json-query`: `[n]` index, `[*]` wildcard, `[field=value]` filter — e.g. `items[status=active].name`). `--head <n>` caps a projected/filtered result. |
46
46
  | `token-goat xml-outline <file>` | Structural summary of an XML document (element tag hierarchy, attribute keys, child counts) instead of a raw Read. Supports `--depth <n>` (alias for `--max-depth`) and `--json`. |
47
47
  | `token-goat xml-query <file> [path]` | Extract one value, element text/XML, or a projected/filtered subset from an XML document by dot-path or `--xpath <expr>` instead of a raw Read (dot-path grammar matches `json-query`: element tags, `@attr`, `[n]`, `[*]`, `[attr=value]`). Supports `--xpath <expression>` with namespace and attribute predicate support, `--with-lines` to output exact source line ranges (`(lines start-end)`), `--decode-embedded-xml` to pretty-print and bound entity-encoded nested AML/XML payloads, `--head <n>`, and `--json`. |
48
48
  | `token-goat html-outline <file>` | Structural outline of an HTML document (DOM hierarchy, tags, IDs, classes, element counts, depth, landmarks, tables, forms) instead of reading thousands of lines of markup. Supports `--json`. |
49
49
  | `token-goat html-query <file> <selector>` | Extract matching HTML elements or text using standard CSS selectors (tags, `#id`, `.class`, attribute operators `[attr]`, `[attr=val]`, `[attr*=val]`, `[attr^=val]`, `[attr$=val]`, child `>` and descendant combinators) without a headless browser or heavy DOM dependencies. Supports `--text` to strip tags, `--head <n>`, and `--json`. |
50
50
  | `token-goat html-lint <file>` | Fast, zero-dependency HTML5 structural linter checking for unclosed tags, void element violations, duplicate IDs, missing viewport/charset, missing alt attributes, and inline script bloat. Supports `--json`. |
51
- | `token-goat json-outline <file>` | Structural summary of a JSON document (array shape / object key types) instead of a raw Read. |
52
- | `token-goat json-query <file> <path>` | Extract one value or a projected/filtered subset from a JSON document by dot-path instead of a raw Read: dot-separated keys with optional bracket segments — `[n]` index, `[*]` wildcard (projects every element/value), `[field=value]` filter. Examples: `data.items[3].name`, `items[*].id`, `items[status=active]`. |
51
+ | `token-goat json-outline <file> [--filter TEXT]` | Structural summary of a JSON document (array shape / object key types) instead of a raw Read. `--filter TEXT` keeps only the top-level keys containing the text, for a registry with hundreds of them: `json-outline registry.json --filter 11862` prints ten keys and `(10 of 93 keys contain "11862")`. |
52
+ | `token-goat json-query <file> <path>` | Extract one value or a projected/filtered subset from a JSON document by dot-path instead of a raw Read: dot-separated keys with optional bracket segments — `[n]` index, `[*]` wildcard (projects every element/value), `[field=value]` filter. A quoted bracket segment is one literal key, the only way to name a key holding a dot or a space: `"['a.b'].c"` reads `c` under the key `a.b`, where `a.b.c` walks three levels. Examples: `data.items[3].name`, `items[*].id`, `items[status=active]`. |
53
53
  | `token-goat brief "file::symbol"` | Bundle a symbol's body, resolved callers (grouped by enclosing function), and its containing doc section into one round-trip instead of three separate `read`/`callers`/`section` calls. `--limit <n>` caps the callers shown per symbol (default 20; the true caller count is reported even when truncated). Comma-separated `"file::a,b"` fetches several symbols' bundles from one file in a single call, mirroring `read`'s `file::a,b` multi-symbol grammar. Cross-file `"a.ts::x,b.ts::y"` bundles symbols from several files in one call, mirroring `read`'s cross-file grammar — a bare segment inherits the file to its left, and once more than one file is involved each bundle is keyed by the full `file::symbol` so two files contributing the same symbol name stay distinct. Also accepts `read`'s `symbol@LINE` anchor to pick out an otherwise-ambiguous candidate. `-C, --context <n>` adds N lines of real call-site source around each entry of the caller block. `--json`'s `symbol.filePath` and `callers[].file` render root-relative when a project root resolves, absolute when none does — matching the plain-text block above. `--exclude-tests` hides callers whose call site is in a test file, matching `refs`/`callers`; the caller count and the elided tail both count the filtered set, so they never disagree with the rows shown, and when the filter empties the block it says so instead of reporting a bare zero that would read as "nothing calls this". `--json` adds `hiddenByExcludeTests` only when the filter actually hid something. `--grep <pattern>` narrows the caller block to callers whose enclosing symbol name matches this regex (literal substring if it is not valid regex), the same filter `refs --grep`/`call-chain --grep` apply to their own results — useful for a high-fanout symbol whose default 20-caller window is otherwise mostly noise; composes with `--exclude-tests`, and reports `hiddenByGrep` under `--json` only when it hid something. |
54
54
  | `token-goat scope "file:line"` | Show symbols in scope at a given line — avoids reading the whole file to understand locals. |
55
55
  | `token-goat exports "file"` | List public (exported) symbols with types, docstring hints, and line ranges (`(lineStart-lineEnd)` in text mode, `lineStart`/`lineEnd` fields under `--json`). Names caught only by the source-text scan (no corresponding index row — e.g. certain re-export forms) report no location: omitted from text mode, `null` under `--json`. Accepts a comma-separated file list (`"a,b,c"`) to cover several files in one call, one clearly-headed block per file; extra space-separated file arguments are reported in a note naming that comma form instead of being silently dropped. `--grep <pattern>` only shows exported symbols whose NAME matches this regex (literal substring if it is not valid regex), applied before output is built; when it matches nothing among real exports, the output names the active filter instead of reading like the file has no exports at all. |
@@ -82,7 +82,7 @@ token-goat pdf-extract manual.pdf --pages 12-15 --layout --head 120
82
82
  | `token-goat coverage-gaps` | Find callables in non-test source files that never appear in a test file's reference records. Useful for spotting untested surface area before a refactor or release. `--top N` caps output; `--json` for structured output. |
83
83
  | `token-goat recent [N]` | Show the N most recently edited/accessed files with their symbols. |
84
84
  | `token-goat grep "<pattern>" [paths...]` | Built-in fallback regex search over files (no `rg` shell-out, no caching) — session-aware dedup for raw `rg`/`grep` Bash calls is a separate hook, not this command. Accepts zero or more paths: omit to walk cwd, or pass several to search them together with hits merged in argument order under one `--max-lines` cap. `-C, --context <n>` shows `n` lines before and after each match. `--symbol` annotates each hit with its enclosing indexed symbol — ` [name (kind)]` appended in text mode, a `symbol: {name, kind, lineStart, lineEnd} | null` field per item under `--json` — `null`/no tag when the hit falls outside any indexed symbol (e.g. module-level code). |
85
- | `token-goat semantic "<query>"` | Find code by meaning, not by filename: embedding-vector similarity search over indexed file chunks and full-text search (BM25) over symbol names/bodies both run on every query and are fused by Reciprocal Rank Fusion (`score = sum of 1/(60 + rank)` per list), so an exact keyword match can outrank a weak vector hit instead of being shadowed by the vector branch. Covers extracted text from PDF/DOCX/PPTX/XLSX files alongside source code, so a query can surface a spec PDF, design doc, deck, or spreadsheet, not just code. Results are re-ranked with a path-priority multiplier so live source wins ties/near-ties against stale or archival prose (`archive/`, `archived/`, `old/`, `deprecated/`, `plans/`, `drafts/`, `CHANGELOG*`, `*.bak`, `*.orig`) and, more mildly, general docs (`docs/**`, `*.md`) — a nudge, not a hard filter, so a genuinely much better archival match still surfaces. Configure with `token-goat config set semantic.archive_weight <0-1>` / `token-goat config set semantic.docs_weight <0-1>` (both default `<1`; set to `1` to disable that penalty entirely, e.g. for a project with a genuinely live `plans/` directory). `token-goat config set semantic.max_distance <0.05-2>` is the relevance floor: a vector hit whose raw distance exceeds it is dropped before fusion, so a corpus whose own distances have been measured can stop the vector half answering a question it has nothing for. It defaults to `1.2`, the retrieval bound's own value, so out of the box it filters nothing — measured genuine matches run from 0.635 on a large index to 0.934 on a two-file one, overlapping the off-corpus band, so no fixed number separates them across corpus sizes and the default is deliberately inert. When the floor empties the vector half, the command says so and names the closest distance it rejected, so the number to change is visible rather than inferred. The key is refused from a project's own `.token-goat.toml` (see `docs/security.md`), since a checked-in file setting it near the minimum would withhold that repository's code from the vector half while BM25 went on answering. `--preflight` runs an advance diagnostic verification of embedding runtime availability, model weight cache presence, and project coverage without executing a query; add `--warm` to load/cache the ONNX model into memory. `--limit <n>` caps result count; `--json` for structured output: `{source, items, truncated, totalCount}`, where `source` is `"hybrid"` when both the embedding and BM25 branches contributed at least one raw hit, `"embeddings"` when only the embedding branch did (e.g. no vector index exists yet: optional embedding deps unavailable, or `indexing.embeddings_enabled` is off), or `"fts"` when only BM25 did, and every item carries the same keys (`filePath`, `name`, `kind`, `startLine`, `endLine`, `distance`, `preview`, `rank`, `rrf`, `retrieval`), with `null` for whichever of `name`/`kind`/`distance` don't apply to that item's source. `rank` is the item's 1-based position, `rrf` the fused score it was sorted on, and `retrieval` is `"dense"`, `"lexical"` or `"both"` — together they are what makes the ordering reproducible, since `distance` is only the vector leg's own score and a lexical-only item has none at all, so an item at distance 0.850 legitimately sits above one at 0.776 when the keyword pass voted for the first as well, and `filePath` rendered root-relative when a project root resolves, absolute when none does, matching plain-text output. On an embeddings hit, `name`/`kind` are resolved to the innermost indexed symbol whose line range contains the hit's start line (`null`/`null` when the hit falls outside any symbol, e.g. a top-of-file imports chunk); text output appends the same as a `— inside <name> (<kind>)` suffix. `--grep <pattern>` only shows hits whose FILE PATH matches this regex (literal substring if it is not valid regex), tested against the path exactly as rendered (so an anchored `--grep "^src/"` matches what you see, not the stored absolute path) — the high-value case is dropping test/vendored noise from a project-wide semantic hit list. Applied before the `--limit` slice in both the embeddings and full-text-fallback branches, so it selects from the whole hit set, not an already-capped page; when it matches nothing among hits that do exist, the output names the active filter (and `--json` sets `grepFilteredToEmpty: true`) instead of reading like the search found nothing. `--exclude-tests` hides hits whose file is a test file (opt-in — omitted, output is unchanged), covering the case `--grep` structurally cannot: `--grep` can only ever *select* a path, and the negative-lookahead pattern that would express "not a test" silently degrades to a literal substring match whenever the regex-compile fallback fires. Applied before the `--limit` slice in both branches and composable with `--grep` (a hit must satisfy both); when it hides every hit there was, the output names how many were hidden (and `--json` sets `excludeTestsFilteredToEmpty: true`) and exits 0, instead of the exit-1 "no matches" a genuinely empty search returns. With both filters set and both emptying the view, the `--grep` notice takes priority. |
85
+ | `token-goat semantic "<query>" ["<query2>" ...]` | Find code by meaning, not by filename: embedding-vector similarity search over indexed file chunks and full-text search (BM25) over symbol names/bodies both run on every query and are fused by Reciprocal Rank Fusion (`score = sum of 1/(60 + rank)` per list), so an exact keyword match can outrank a weak vector hit instead of being shadowed by the vector branch. Several queries run in one call: each prints its own headed block in argument order (`--json`: an array of per-query payloads, each carrying its `query`); a single query's output is unchanged. `--preflight` combined with extra queries is rejected, since it never searches. When the closest hit that survives the relevance floor is farther than 0.85, a notice on stderr names the distance and points at a keyword route or a rephrase; `--json` carries it as `lowConfidence: {closestDistance, threshold}`. Covers extracted text from PDF/DOCX/PPTX/XLSX files alongside source code, so a query can surface a spec PDF, design doc, deck, or spreadsheet, not just code. Results are re-ranked with a path-priority multiplier so live source wins ties/near-ties against stale or archival prose (`archive/`, `archived/`, `old/`, `deprecated/`, `plans/`, `drafts/`, `CHANGELOG*`, `*.bak`, `*.orig`) and, more mildly, general docs (`docs/**`, `*.md`) — a nudge, not a hard filter, so a genuinely much better archival match still surfaces. Configure with `token-goat config set semantic.archive_weight <0-1>` / `token-goat config set semantic.docs_weight <0-1>` (both default `<1`; set to `1` to disable that penalty entirely, e.g. for a project with a genuinely live `plans/` directory). `token-goat config set semantic.max_distance <0.05-2>` is the relevance floor: a vector hit whose raw distance exceeds it is dropped before fusion, so a corpus whose own distances have been measured can stop the vector half answering a question it has nothing for. It defaults to `1.2`, the retrieval bound's own value, so out of the box it filters nothing — measured genuine matches run from 0.635 on a large index to 0.934 on a two-file one, overlapping the off-corpus band, so no fixed number separates them across corpus sizes and the default is deliberately inert. When the floor empties the vector half, the command says so and names the closest distance it rejected, so the number to change is visible rather than inferred. The key is refused from a project's own `.token-goat.toml` (see `docs/security.md`), since a checked-in file setting it near the minimum would withhold that repository's code from the vector half while BM25 went on answering. `--preflight` runs an advance diagnostic verification of embedding runtime availability, model weight cache presence, and project coverage without executing a query; add `--warm` to load/cache the ONNX model into memory. `--limit <n>` caps result count; `--json` for structured output: `{source, items, truncated, totalCount}`, where `source` is `"hybrid"` when both the embedding and BM25 branches contributed at least one raw hit, `"embeddings"` when only the embedding branch did (e.g. no vector index exists yet: optional embedding deps unavailable, or `indexing.embeddings_enabled` is off), or `"fts"` when only BM25 did, and every item carries the same keys (`filePath`, `name`, `kind`, `startLine`, `endLine`, `distance`, `preview`, `rank`, `rrf`, `retrieval`), with `null` for whichever of `name`/`kind`/`distance` don't apply to that item's source. `rank` is the item's 1-based position, `rrf` the fused score it was sorted on, and `retrieval` is `"dense"`, `"lexical"` or `"both"` — together they are what makes the ordering reproducible, since `distance` is only the vector leg's own score and a lexical-only item has none at all, so an item at distance 0.850 legitimately sits above one at 0.776 when the keyword pass voted for the first as well, and `filePath` rendered root-relative when a project root resolves, absolute when none does, matching plain-text output. On an embeddings hit, `name`/`kind` are resolved to the innermost indexed symbol whose line range contains the hit's start line (`null`/`null` when the hit falls outside any symbol, e.g. a top-of-file imports chunk); text output appends the same as a `— inside <name> (<kind>)` suffix. `--grep <pattern>` only shows hits whose FILE PATH matches this regex (literal substring if it is not valid regex), tested against the path exactly as rendered (so an anchored `--grep "^src/"` matches what you see, not the stored absolute path) — the high-value case is dropping test/vendored noise from a project-wide semantic hit list. Applied before the `--limit` slice in both the embeddings and full-text-fallback branches, so it selects from the whole hit set, not an already-capped page; when it matches nothing among hits that do exist, the output names the active filter (and `--json` sets `grepFilteredToEmpty: true`) instead of reading like the search found nothing. `--exclude-tests` hides hits whose file is a test file (opt-in — omitted, output is unchanged), covering the case `--grep` structurally cannot: `--grep` can only ever *select* a path, and the negative-lookahead pattern that would express "not a test" silently degrades to a literal substring match whenever the regex-compile fallback fires. Applied before the `--limit` slice in both branches and composable with `--grep` (a hit must satisfy both); when it hides every hit there was, the output names how many were hidden (and `--json` sets `excludeTestsFilteredToEmpty: true`) and exits 0, instead of the exit-1 "no matches" a genuinely empty search returns. With both filters set and both emptying the view, the `--grep` notice takes priority. When a single identifier returns zero matches, the command suggests checking `token-goat symbol "<name>"` and notes if that identifier is already cataloged in the symbol index. |
86
86
  | `token-goat map` | Get a compact orientation of the repo. Add `--compact` to fit a fixed 2000-token budget. `--json` emits the project map as JSON instead of text. |
87
87
  | `token-goat deps "file"` | One-level import listing for a single file: resolves relative imports to project files (`internal`, root-relative paths) and groups everything else as `external`. `--json` for structured output. `--grep <pattern>` only shows dependencies whose MODULE SPECIFIER (the resolved internal path or the external package name) matches this regex (literal substring if it is not valid regex), applied before output is built; when it matches nothing among real dependencies, the output names the active filter instead of reading like the file has no imports at all. Complemented by `token-goat arch` for the project-wide graph. |
88
88
  | `token-goat arch` | Project-wide import graph summary: hub modules (most imported), entry points (nothing imports them), and circular chains. `--modules` adds a grouping of the files that mostly import each other, naming each group by its most connected file, saying whether the group is one directory or spread across several, and listing which groups reach into which. That section also prints the grouping's modularity and calls it out when it is too weak to mean anything, since the algorithm returns groups for any graph, including one with no real structure. `--json` carries each group's full member list. Complements `token-goat deps <file>` for per-file depth. |
@@ -99,14 +99,14 @@ token-goat pdf-extract manual.pdf --pages 12-15 --layout --head 120
99
99
  | `token-goat waste [--project <path>] [--transcript <path>] [--top <n>] [--json] [--copilot]` | Session spend-ledger: parses the current project's Claude Code session transcript and reports token cost by tool, by file, the top N most expensive individual tool calls, files read once and never referenced again, Bash commands run repeatedly without hitting token-goat's own bash-output cache, and the assistant's own text-output cost (generated tokens plus a cache-unaware re-send upper bound). See [Session waste ledger](#session-waste-ledger) below. |
100
100
  | `token-goat mcp-audit [--project <path>] [--json]` | MCP server schema cost report: scans .mcp.json for installed MCP servers, estimates per-server token costs from cached tool calls, correlates schema complexity against real call frequency. Outputs as markdown table or JSON. |
101
101
  | `token-goat recall ["<query>"] [--type bash\|web\|mcp] [--limit <n>] [--json]` | Full-text search across every cached bash-output, web-output, and mcp-output entry at once — one command instead of remembering which cache type holds a prior result. Ranked by relevance (BM25 via SQLite FTS5). With **no query**, lists every cached entry newest-first instead of searching, so you can browse when the ids have scrolled out of context and you have no term to search for. `--type` narrows to one cache type; `--limit` caps results (default 10). Each hit shows its cache type, id, the exact recall command (`bash-output <id>` / `web-output <id>` / `mcp-output <id>`), and a content snippet. See [Cross-cache recall](#cross-cache-recall) below. |
102
- | `token-goat hint-stats [--json] [--reset] [--mark-effective <cat>] [--mark-ineffective <cat>]` | Per-category efficacy report for token-goat's discretionary hint hooks: how often each hint category was emitted, how often the agent actually followed its specific suggestion within the next few tool calls, whether the category is currently auto-suppressed, and the bytes each category spent (injected into context) plus an all-time saved-bytes/spent-bytes summary line (the two cover disjoint populations and are deliberately never netted against each other). `--reset` clears all tracked data; `--mark-effective`/`--mark-ineffective <category>` record a manual vote as a supplement to the automatic signal. See [Hint efficacy tracking](#hint-efficacy-tracking) below. |
102
+ | `token-goat hint-stats [--json] [--session-id <id>] [--reset] [--mark-effective <cat>] [--mark-ineffective <cat>]` | Per-category efficacy report for token-goat's discretionary hint hooks: how often each hint category was emitted, how often the agent actually followed its specific suggestion within the next few tool calls, whether the category is currently auto-suppressed, and the bytes each category spent (injected into context) plus an all-time saved-bytes/spent-bytes summary line (the two cover disjoint populations and are deliberately never netted against each other). `--session-id <id>` (or `latest`) narrows the emission counts, efficacy and spend to one session; suppression and manual marks stay all-time, saved-bytes is left out because the stats ledger records no session, and `--session-id` is rejected alongside the three mutating flags. `--reset` clears all tracked data; `--mark-effective`/`--mark-ineffective <category>` record a manual vote as a supplement to the automatic signal. See [Hint efficacy tracking](#hint-efficacy-tracking) below. |
103
103
  | `token-goat history` | Show current session access history: bash commands and URLs fetched. |
104
104
  | `token-goat session-outline` | Turn-by-turn structure (role, preview, tool calls, approx size) of a Claude Code session JSONL transcript, instead of a raw Read; defaults to the current project's most recent session. |
105
105
  | `token-goat session-slice <turns>` | Full content of one turn range from a Claude Code session JSONL transcript (see `session-outline` for turn numbers), instead of a raw Read. |
106
- | `token-goat session-audit [--dir <path>] [--json]` | Corpus-wide token attribution across every local Claude Code session transcript, including nested subagent transcripts (default corpus: `~/.claude/projects`): measured billed usage from each API response's own usage record, estimated content size by source and by tool, a per-attachment-kind census ranked by modeled billed cost (cache write plus compaction-capped cache re-reads over the model-visible fields only), a hook-output census split by origin, a subagent-lane rollup (spawn-prefix size and its modeled billed carriage, plus a per-agent-type breakdown from each lane's meta file), a Read-interception census (diverted reads versus full serves, with each large full serve split into first read versus repeat and repeats classified as deliberate paging or divert-miss candidates), a Bash filter fire-rate census (results carrying a token-goat marker versus the untouched remainder, bucketed by bare command head: the binary name only), and billed cost by session position. Output is aggregate counts only, never transcript content or command lines. |
106
+ | `token-goat session-audit [--dir <path>] [--json] [--tool-errors]` | Corpus-wide token attribution across every local Claude Code session transcript, including nested subagent transcripts (default corpus: `~/.claude/projects`): measured billed usage from each API response's own usage record, estimated content size by source and by tool, a per-attachment-kind census ranked by modeled billed cost (cache write plus compaction-capped cache re-reads over the model-visible fields only), a hook-output census split by origin, a subagent-lane rollup (spawn-prefix size and its modeled billed carriage, plus a per-agent-type breakdown from each lane's meta file), a Read-interception census (diverted reads versus full serves, with each large full serve split into first read versus repeat and repeats classified as deliberate paging or divert-miss candidates), a Bash filter fire-rate census (results carrying a token-goat marker versus the untouched remainder, bucketed by bare command head: the binary name only), and billed cost by session position. `--tool-errors` replaces this with a census of failed tool calls instead: calls, errors and unknown errors per tool and per model, with the most frequent unknown error prefixes, so a call that fails without a recognized reason still points at what to classify next. A deny that delivers the content it withholds, such as a skill's compact slice, is counted separately and named on its own line rather than folded into the error counts. Output is aggregate counts only, never transcript content or command lines. |
107
107
  | `token-goat bash-output <id>` | Retrieve a cached Bash output by ID instead of re-running the command. Large outputs return a head(30)+tail(80) view by default; pass `--full` for the entire stored entry with no elision, `--head N`/`--tail N` for a specific slice, or narrow with `--grep PATTERN` (cap `--grep` to the first N hits with `--max-matches N`). Read a file directly with `--file <path>` (e.g. a background task's `tasks/<id>.output`); add `--transcript` to parse that file as a subagent JSONL transcript, keeping only assistant text blocks in order before the slicers apply. Add `--verify-last-write [seconds]` (with optional `--strict`) when reading with `--file` to verify the output file was modified within the last N seconds (default 60s), alerting or rejecting stale/cached reads from silent no-op commands before repeating runs. |
108
108
  | `token-goat bash-history` | List cached Bash outputs (newest first) with their IDs, byte sizes, and exit codes. |
109
- | `token-goat compress --cmd '<command>'` | Preview what the Bash compression hook would do to any command — runs it, applies the matching filter, and prints the compressed view. |
109
+ | `token-goat compress [--cmd '<command>' | --cmd-b64 <b64>] [--shell <type>]` | Run a command through token-goat's output compression filters. Accepts an inline command via `--cmd` or base64-encoded string via `--cmd-b64` (preventing shell quoting and escaping issues). `--shell` specifies runner (`bash`, `pwsh`, `powershell`, or `native`, defaulting to bash on POSIX and resolving PowerShell on Windows). `-f passthrough` applies no filter, only the size limits. `--max-tokens <n>` caps what is delivered, and output cut by the cap or a filter ends with a `bash-output <id> --full` line to recall the rest. The Bash hook adds `--cap-hint-b64`, a base64 sentence printed after that line naming the narrower command to run next. |
110
110
  | `token-goat web-output <id>` | Retrieve a cached WebFetch response body by ID — same head+tail default and `--full`/`--head`/`--tail`/`--grep`/`--max-matches` slicers as `bash-output`. `--raw` returns the body as actually fetched, before `webfetch.compress_bodies`'s HTML-cleaning pass, for recovering a selector/script tag/embedded JSON that the default cleaned text drops; falls back to the (already-raw) cleaned body when no separate raw copy was stored. |
111
111
  | `token-goat web-history` | List cached WebFetch responses (newest first) with their IDs, byte sizes, status codes, and URL previews. |
112
112
  | `token-goat mcp-output [id]` | Retrieve a cached MCP tool result by ID (or an on-disk tool spill file via `--file <path>`). Supports `--json-query '<path>'` for narrow dot-path and bracket slicing on large Jira, Confluence, or JSON responses (e.g. `issues[*].key`, `total`), avoiding whole-file re-reads. Also supports `--head <n>`, `--tail <n>`, `--grep <pattern>`, `--section <heading>`, `--json`, and `--full`. |
@@ -170,17 +170,18 @@ token-goat pdf-extract manual.pdf --pages 12-15 --layout --head 120
170
170
  | `token-goat mcp-serve` | Run token-goat as an MCP stdio server exposing all 18 tools: read/symbol/section/outline/skeleton/semantic/index_status/refs/brief/map/changed/grep/imports/exports/compress_text/retrieve_text/handoff_create/handoff_resolve. |
171
171
  | `token-goat version` | Print the token-goat version. |
172
172
  | `token-goat statusline` | Claude Code statusline command surfacing session stats (bytes saved, hint efficacy, cache hit rate) inline in the terminal. |
173
+ | `token-goat hook-server status` / `stop` | Inspect or stop the resident hook servers: up to three per data directory, started in the background by the first hook call, and each answering hook calls and read-only commands from an already-loaded process instead of a fresh Node start. `status` prints each running server's slot, pid, version, uptime, calls served, errors and idle time (`--json` for the same as JSON). `stop` asks each one to exit over its authenticated channel; one in the middle of a call finishes it first. A server also exits on its own after 30 minutes idle (3 minutes for the two overflow slots), when the installed build changes, when `hooks.server` in the global config or `TOKEN_GOAT_HOOK_SERVER` turns it off, and within two seconds of its key file being deleted. `token-goat hook-server run --slot N` is what the background start runs; you do not need to call it. |
173
174
  | `token-goat lockdeps [path]` | Summarize lock file dependencies as a compact table. Reads poetry.lock, uv.lock, requirements.txt, Pipfile.lock, package-lock.json, Cargo.lock, and yarn.lock. Direct dependencies only — optional and transitive entries excluded. `--json` for structured output. |
174
175
  | `token-goat logfold [src]` | Collapse consecutive duplicate log lines. Runs of identical or structurally equivalent lines fold to `[Nx] line`. Normalizes timestamps, UUIDs, IPs, hex IDs, and bare integers (counters, PIDs, ports, byte counts) before comparing so the same event with different values folds correctly. `--tail N` keeps last N lines; `--no-normalize` disables normalization; `--fold-repeats` also folds non-consecutive duplicates anywhere in the input, attributing the total count to the first occurrence (capped at 20,000 distinct keys, past which it falls back to consecutive-only); `--json` for structured output. |
175
176
  | `token-goat hot [--limit N]` | Cross-session file frequency table: read and edit counts tallied from all stored sessions, ranked by total activity. Shows which files dominate your token spend across your entire history. `--project <dir>` filters to one project; `--json` for structured output. |
176
- | `token-goat note set/get/unset/list/clear` | Persistent per-project notes stored as key-value pairs. Token-goat injects them at session start and after compaction so they survive conversation rollover. Use to pin decisions, constraints, or reminders that would otherwise vanish after compaction. `note list --json` for machine-readable output; `note clear` removes everything at once. |
177
+ | `token-goat note set/get/unset/list/clear` | Persistent per-project notes stored as key-value pairs. Token-goat prints them at every session start and in the compaction manifest, ahead of the files read, so they survive conversation rollover; on Codex, whose session start hook is not wired, the manifest is how they come back. Use to pin decisions, constraints, or reminders that would otherwise vanish after compaction. `note list --json` for machine-readable output; `note clear` removes everything at once. |
177
178
  | `token-goat project list` | Show all project roots indexed by token-goat with their file counts. Roots on the blocklist appear tagged `[excluded]`. `--json` for structured output. |
178
179
  | `token-goat project exclude <path>` | Add a project root to the blocklist so the worker never indexes it. Writes the resolved absolute path to `[worker] blocked_roots` in `config.toml`; idempotent. It also removes anything already indexed under that path and says how many files went, so excluding a directory means its contents stop being readable through `symbol` rather than merely stopping future indexing. Remove the entry from the config to re-enable indexing, then run `token-goat index` to bring the contents back. |
179
180
  | `token-goat project prune [--dry-run]` | Remove blocked/excluded roots that no longer exist on disk, and index rows for files under the OS temp dir. `--dry-run` previews removals without touching the config file. Useful after deleting or moving projects. |
180
181
  | `token-goat install` | Wire up hooks (and, with the harness flags below, other AI tool integrations). No `--dry-run` or `--verify` flag — run `token-goat doctor` after install to audit the result. |
181
182
  | `token-goat upgrade` | Check for updates or upgrade token-goat to the latest version via npm. Pass `--check` to report status without installing. `--json` emits machine-readable version status. |
182
183
  | `token-goat doctor` | Confirm everything is wired correctly. Surfaces install state, cold-import timing, cache hit rates, compaction-budget telemetry, opt-in flag status, and canonical-root sanity. A **Parser freshness** check reports how much of the index for this project was written by a different build of the extractor. The stamp only refreshes when something touches a file, so after an upgrade a project goes on answering `symbol`, `read`, `outline` and `skeleton` from the previous extractor with nothing saying so. It warns past a quarter and names the fix: a plain `token-goat index` in that project, which is enough on its own, since a parser mismatch reparses without `--force`. A **Tool names** check reports any tool name a harness sent that reached no handler wanting it, and calls out the ones that differ from a handled name only by capitalisation or punctuation — the signature of a bridge that forgot to rename something, which is otherwise invisible. When `global.db` passes 1 GB, the warning now also breaks the total down by category (symbol bodies, refs, chunk text, embedding vectors, stats detail) with each one's measured byte share and the exact command that shrinks it -- `reclaim-index --rebuild` for the derived-row categories, disabling `indexing.embeddings_enabled` first for vectors so they don't regrow, and nothing manual for stats detail, which ages out on its own. A **Security** section reports the posture in one place: whether offline mode is on, whether injection scanning is on, whether the Google Drive integration is enabled, whether fetching runs against an allow list or a deny list, whether MCP reads are confined to the project root, and whether the data directory is readable by other local users. It only warns when a protection that ships on has been switched off, so a default install stays quiet. It read-only audits `~/.copilot/mcp-config.json` for globally configured Chrome DevTools or Playwright `npx` launchers, recommending project scope or removal when inactive; it never prints server configuration or secrets. On Windows, it also reports duplicate Chrome DevTools/Playwright MCP launchers and orphaned Node processes without terminating anything. Pass `--context` to show the **Context footprint** section: a fill bar with severity (ok / warn / high / URGENT), per-component breakdown (skills catalog, loaded skill bodies, CLAUDE.md+MEMORY.md, conversation estimate), session-to-session growth trend with sessions-to-URGENT projection, and tiered compaction recommendations (Tier 0–4) naming the exact commands to run. Auto-shown when fill > 40 % or any loaded skill > 2 K tokens lacks a compact. `--json` emits the check results (one entry per check, with `ok`/`warn`/`fail` status) as JSON instead of text. |
183
- | `token-goat capabilities` | List every capability that can send data off this machine or leave data on it, with whether it is currently on, the config key that decides that, and the exact `file::symbol` where the decision is made — so a reviewer can open the code rather than take the list's word for it. `--json` emits the same thing for a pipeline to assert on, which is the point: the answer comes from the binary installed on your machine, not from documentation that may describe a different build. A test in the suite fails the build when a module that can open a network connection is missing from this list, and equally when the list names one that no longer connects anywhere. |
184
+ | `token-goat capabilities` | List every capability that can send data off this machine, leave data on it, or listen for other processes on it, with whether it is currently on, the config key that decides that, and the exact `file::symbol` where the decision is made, so a reviewer can open the code rather than take the list's word for it. `--json` emits the same thing for a pipeline to assert on, which is the point: the answer comes from the binary installed on your machine, not from documentation that may describe a different build. A test in the suite fails the build when a module that can open a network connection is missing from this list, and equally when the list names one that no longer connects anywhere. |
184
185
  | `token-goat baseline` | Emit a project map: file count, per-language file counts, the top indexed symbols (by name/kind/location), and the most recently modified files. `--subagent` emits a terser variant (fewer symbols, fewer recent files) for context handed to a freshly spawned subagent; `--json` for the machine-readable form. |
185
186
  | `token-goat compact-doc <path>` | Build an extractive compact sidecar for a large reference doc (`.md`/`.markdown`). The compact is stored in the token-goat data dir as a SHA-keyed sidecar; `pre_read` serves it in place of the full file when it exists and is fresh, typically saving 60–95% of the tokens the full read would cost (measured across this repo's own 44 docs: median 67%, range 5–99%, depending on how much of the doc is prose under headings). Use `--force` to rebuild, `--sentences N` to control lines per section (default 2), `--show` to print the result. The sidecar is automatically marked stale when you edit the source file. Config: `[hints] stable_doc_compacts = true` (default on). |
186
187
 
@@ -268,16 +269,21 @@ Total tokens: 18420
268
269
  $ token-goat waste --copilot
269
270
 
270
271
  ## Per-request fixed overhead (Copilot's own token counts)
271
- System prompt: 8,981 tok
272
- Tool definitions: 11,548 tok
273
- Conversation: 722 tok
272
+ System prompt: 7,897 tok
273
+ Tool definitions: 10,993 tok
274
+ Conversation: 577 tok
274
275
 
275
276
  ## Tool definitions by MCP server (estimated)
276
- github-mcp-server: 6 tools, 6 KB, ~2,135 tok
277
+ github-mcp-server: 6 tools, 6 KB, ~2,135 tok, 0 calls this session
277
278
  ~2,135 tok estimated across 1 server, re-sent every request.
278
- Copilot counted 11,548 tok of tool definitions in total, so this is roughly 18.5% of it.
279
+ Copilot counted 10,993 tok of tool definitions in total, so this is roughly 19.4% of it.
280
+ Dropping a server saves its line above on every request for the rest of the session.
281
+ Never called this session: `copilot mcp disable github-mcp-server` drops it from future sessions.
282
+ 'copilot mcp enable <name>' restores one, and '--disable-mcp-server <name>' drops one for a single run instead.
279
283
  ```
280
284
 
285
+ Calls per server come from the server name Copilot records on every MCP tool call. A server with none gets the command that turns it off, which Copilot keeps in its settings until `copilot mcp enable` undoes it. A log that records no tool calls at all says so instead, since it cannot tell a server nobody needed from a session that never ran a tool. `token-goat audit` carries the same verdict into its recommended fix.
286
+
281
287
  The fixed overhead is the largest number in a Copilot session and no hook can reach it: Copilot assembles the system prompt and the tool definitions natively, with nothing between assembly and send. Only configuration moves it. The per-server breakdown exists to make that configuration decision possible, since one aggregate says the tool definitions are expensive without saying which tools. It is read from Copilot's own MCP tool cache, counts only the fields a model is actually sent, and is labeled an estimate throughout: it comes from byte length rather than Copilot's tokeniser, and it deliberately does not add up to Copilot's total, because Copilot's own built-in tools are not cached there.
282
288
 
283
289
  The "Assistant output" section is separate from the tool-call ledger above it: `generatedTokens` is what was actually paid, once, to produce the assistant's own text turns. `resendCeilingTokens` is a cache-unaware upper bound on how much re-sending those turns as conversation history on every later request could cost — not real spend, since Claude Code's prompt caching bills a repeated conversation prefix at cache-read rates, a fraction of full input price. Treat it as a ceiling on how bad unbounded verbosity could get, not as a dollar figure.
@@ -304,7 +310,7 @@ Run `token-goat recall` with **no query** to browse instead of search: every cac
304
310
 
305
311
  ### Hint efficacy tracking
306
312
 
307
- Every hint hook (the re-read/dedup/surgical-read nudges in the Bash, Read, and Edit hooks) is
313
+ Every hint hook (the re-read/dedup/surgical-read nudges in the Bash, Read, Edit, Grep and Glob hooks, and the call-streak hints) is
308
314
  worth its keep only if it's actually followed. `token-goat hint-stats` reports, per hint
309
315
  category: how many times it fired, how many times a later Bash command in the same session
310
316
  actually invoked the specific `token-goat` command (or referenced the specific cached-output id)
@@ -314,14 +320,18 @@ that category — the real cost of emitting it, not just how often it fired):
314
320
 
315
321
  ```
316
322
  $ token-goat hint-stats
317
- category emitted undisplayed acted-on efficacy suppressed manual+ manual- spent-bytes
318
- bash_redirect 42 - 9 21.4% no 0 0 3150
319
- bash_recall 18 - 15 83.3% no 0 0 1080
320
- read_reread_dedup 11 - 2 18.2% * no 0 0 660
321
- read_structural_nav 7 3 1 14.3% yes 0 1 420
322
- edit_reread_suggest 3 - 0 0% * no 0 0 180
323
-
324
- TOTAL saved-bytes=48200 (all-time, every hint kind) spent-bytes=5490 (hint_emissions ledger only)
323
+ category emitted undisplayed acted-on efficacy suppressed manual+ manual- spent-bytes
324
+ bash_redirect 42 - 9 21.4% no 0 0 3150
325
+ bash_recall 18 - 15 83.3% no 0 0 1080
326
+ read_reread_dedup 11 - 2 18.2% * no 0 0 660
327
+ read_structural_nav 7 3 1 14.3% * yes 0 1 420
328
+ edit_reread_suggest 3 - 0 0% * no 0 0 180
329
+ read_batch 6 - 4 66.7% no 0 0 900
330
+ search_brake 2 - 1 50.0% no 0 0 330
331
+ grep_dedup_hint 4 - 0 n/a no 0 0 240
332
+ glob_dedup_hint 1 - 0 n/a no 0 0 60
333
+
334
+ TOTAL saved-bytes=48200 (all-time, every hint kind) spent-bytes=7020 (hint_emissions ledger only)
325
335
  ```
326
336
 
327
337
  `spent-bytes` (and the `TOTAL` line's `spent-bytes`) render `n/a` instead of a fake `0` whenever a
@@ -357,6 +367,8 @@ Note that what this feature calls "harness" (Claude Code, Codex, Gemini, ...) is
357
367
  "which LLM model" — no bridge in this codebase exposes an LLM model identifier to hooks, so
358
368
  harness is the closest real signal available.
359
369
 
370
+ Two categories are scored on a pattern of calls rather than a named command. `read_batch` (three or more reads or searches in a row, each sent a full turn after the previous result) counts as acted on when the first later turn that makes read-only calls makes at least two of them together. `search_brake` (three searches in a row found nothing) counts as acted on when the next search from a later turn is `token-goat answer` or `token-goat semantic`. Until that later call arrives the emission stays pending and counts as not acted on. `grep_dedup_hint` and `glob_dedup_hint` (an identical Grep or Glob already ran this session) are never scored: the note rides on the re-run it describes, so no later call can show whether it was heeded. They appear in `spent-bytes` and as the `~N` beside `emitted`, never in `emitted` itself or the efficacy figure.
371
+
360
372
  ### Scoring the compressors — `token-goat bench`
361
373
 
362
374
  `token-goat bench` replays a fixed corpus of captured command output through the same function
package/docs/install.md CHANGED
@@ -20,6 +20,8 @@ token-goat doctor # confirms the hooks, index, and integrations are hea
20
20
 
21
21
  Three commands. Done. Hooks register and start working immediately; no terminal popups, no tray icon, no service to babysit.
22
22
 
23
+ > **WSL performance tip:** Keep active repositories on WSL's native ext4 filesystem (`~/projects/...`) rather than Windows mounts (`/mnt/c/...`) to avoid 9P cross-OS filesystem translation overhead during initial indexing.
24
+
23
25
  ### Agents choose the commands
24
26
 
25
27
  People install token-goat. Agents use it. You do not need to memorize its commands or tell the agent which file type it has.
@@ -126,7 +128,7 @@ This writes hook entries into `~/.qwen/settings.json`. Unlike Gemini CLI (its ow
126
128
  token-goat install --kimi
127
129
  ```
128
130
 
129
- This writes `[[hooks]]` entries into `~/.kimi-code/config.toml` (or `$KIMI_CODE_HOME/config.toml`), covering Kimi Code's `PreToolUse`, `PostToolUse`, `PreCompact`, `UserPromptSubmit`, `SubagentStop`, and `SessionStart` events. Kimi Code sends a Claude-Code-shaped snake_case payload on stdin, but it reads a different response: only a top-level `message` and `hookSpecificOutput.permissionDecision` / `permissionDecisionReason`. So the install also writes a small shim at `~/.kimi-code/hooks/token-goat-shim.js` that translates token-goat's answer into that contract, turns a hint into `message`, and writes nothing at all for a no-op. Image shrinking, session hints, post-edit indexing, compact assist, and bash output compression all work. `Notification` and `Stop` are not wired, because token-goat has no handler for them. Input and output rewriting are not wired either: Kimi Code offers no channel to replace a tool's input or its result. This bridge was built from MoonshotAI/kimi-code's own source and docs, not tested against a live Kimi Code install, so if hooks are not firing, `token-goat doctor` and the `config.toml` contents are the first things to check. To remove: `token-goat uninstall --kimi`.
131
+ This writes `[[hooks]]` entries into `~/.kimi-code/config.toml` (or `$KIMI_CODE_HOME/config.toml`), covering Kimi Code's `PreToolUse`, `PostToolUse`, `PreCompact`, `UserPromptSubmit`, `SubagentStop`, and `SessionStart` events. Kimi Code sends a Claude-Code-shaped snake_case payload on stdin, but it reads a different response: only a top-level `message` and `hookSpecificOutput.permissionDecision` / `permissionDecisionReason`. So the install also writes a small shim at `~/.kimi-code/hooks/token-goat-shim.cjs` that translates token-goat's answer into that contract, turns a hint into `message`, and writes nothing at all for a no-op. Image shrinking, session hints, post-edit indexing, compact assist, and bash output compression all work. `Notification` and `Stop` are not wired, because token-goat has no handler for them. Input and output rewriting are not wired either: Kimi Code offers no channel to replace a tool's input or its result. This bridge was built from MoonshotAI/kimi-code's own source and docs, not tested against a live Kimi Code install, so if hooks are not firing, `token-goat doctor` and the `config.toml` contents are the first things to check. To remove: `token-goat uninstall --kimi`.
130
132
 
131
133
  ### opencode users
132
134
 
@@ -134,7 +136,7 @@ This writes `[[hooks]]` entries into `~/.kimi-code/config.toml` (or `$KIMI_CODE_
134
136
  token-goat install --opencode
135
137
  ```
136
138
 
137
- The `--opencode` flag patches Claude Code and drops a TypeScript bridge plugin into opencode's plugins directory — one command, no separate base install. Image shrinking, post-edit indexing, compact assist, and rewritten tool results (prompt-injection fencing, secret redaction, and output compression replace the raw result, the same protection Claude Code sessions get) work. So do repeat-search denial for `websearch`, repeat-load denial for `skill`, and the subagent prompt briefing for `task` — all three tool ids and their argument keys were verified against opencode's own source at the installed release's tag. Session hints don't — opencode's plugin API has no way to inject context before a tool read.
139
+ The `--opencode` flag drops a TypeScript bridge plugin into opencode's plugins directory. Image shrinking, post-edit indexing, compact assist, and rewritten tool results (prompt-injection fencing, secret redaction, and output compression replace the raw result, the same protection Claude Code sessions get) work. So do repeat-search denial for `websearch`, repeat-load denial for `skill`, and the subagent prompt briefing for `task` — all three tool ids and their argument keys were verified against opencode's own source at the installed release's tag. Session hints don't — opencode's plugin API has no way to inject context before a tool read.
138
140
 
139
141
  ### openclaw users
140
142
 
@@ -172,13 +174,13 @@ This writes `.pi/extensions/token-goat.ts` in the current project only. Remove i
172
174
  token-goat install --copilot
173
175
  ```
174
176
 
175
- The `--copilot` flag patches Claude Code and registers a Copilot CLI hook config: `~/.copilot/hooks/token-goat.json` (a `{ version, hooks }` file registering `sessionStart`, `preToolUse`, `postToolUse`, `preCompact`, `agentStop`, `subagentStop`, and `userPromptSubmitted`, per Copilot's own [hooks reference](https://docs.github.com/en/copilot/reference/hooks-reference)) plus the shim script it points at, `~/.copilot/hooks/token-goat-shim.js`. Unlike Codex, Copilot's event names and response schema (`permissionDecision`/`modifiedArgs` for `preToolUse`, `modifiedResult`/`additionalContext` for `postToolUse`, `decision`/`reason` for `agentStop`/`subagentStop`) genuinely differ from Claude Code's, so the shim translates rather than passes through.
177
+ The `--copilot` flag patches Claude Code and registers a Copilot CLI hook config: `~/.copilot/hooks/token-goat.json` (a `{ version, hooks }` file registering `sessionStart`, `preToolUse`, `postToolUse`, `preCompact`, `agentStop`, `subagentStop`, and `userPromptSubmitted`, per Copilot's own [hooks reference](https://docs.github.com/en/copilot/reference/hooks-reference)) plus the shim script it points at, `~/.copilot/hooks/token-goat-shim.cjs`. Unlike Codex, Copilot's event names and response schema (`permissionDecision`/`modifiedArgs` for `preToolUse`, `modifiedResult`/`additionalContext` for `postToolUse`, `decision`/`reason` for `agentStop`/`subagentStop`) genuinely differ from Claude Code's, so the shim translates rather than passes through.
176
178
 
177
179
  What works: **the command-routing reminder** (`sessionStart` returns `additionalContext`, so Copilot is told token-goat exists before it picks its first read tool — this is the one channel that lands ahead of that decision), **bash output compression and re-read denial** (`preToolUse` returns `modifiedArgs` or `permissionDecision: "deny"`), **background-shell output compression** (`postToolUse` returns `modifiedResult`), **image shrinking** (`preToolUse` on a `view` call returns `modifiedArgs` carrying the full original arguments with `path` swapped to a materialized shrunk copy — Copilot replaces the tool call's arguments wholesale with `modifiedArgs`, so the rewrite must carry them all), **post-edit indexing** (a `postToolUse` side effect; it needs no response channel), and **stop-hallucination logging** (`agentStop`/`subagentStop` map a token-goat `deny` onto `decision: "block"`, everything else onto `decision: "allow"`). `preCompact` and `userPromptSubmitted` are notification-only on real Copilot CLI, per its docs: Copilot never reads a response body for either, so token-goat's compaction manifest and prompt-context hints have no surfacing channel there. The shim still calls through for both so token-goat's internal side effects keep running, but nothing gets injected back into the agent. Copilot's built-in tool names are remapped onto token-goat's internal names where a clear match exists (`view`→Read, `edit`→Edit, `create`→Write, `bash`/`powershell`→Bash, `read_bash`/`read_powershell`→BashOutput, `web_fetch`→WebFetch, `grep`→Grep, `glob`→Glob). MCP-server tool calls, which Copilot names `<server>-<tool>` rather than `mcp__<server>__<tool>`, are translated too, but only when the name matches Copilot's own cached tool list exactly — never guessed from the name's shape, because a server name can itself contain a hyphen and a wrong guess would make the read-only MCP dedup path deny an ordinary built-in call. With no cache to match against, nothing is translated. `memory`, `ask_user`, `write_bash`/`write_powershell` (which send keystrokes to a running shell, not commands), and `stop_bash`/`list_bash` pass through unmapped and simply no-op. `task`, Copilot's subagent tool, is not remapped either, but it is handled under its own name: a `task` spawn gets the same prompt briefing, duplicate-spawn advisory, and recall pointer on a long report that a Claude Code `Agent` spawn gets. The once-per-session unrestricted-spawn advisory is the one exception: it is suppressed under Copilot, because it rides the `postToolUse` `additionalContext` channel Copilot discards, and its `subagent_type` advice describes Claude Code's Task schema, which Copilot's `task` tool does not use.
178
180
 
179
181
  **Why the background-shell compression matters most on Copilot.** Copilot runs shell commands in the background: a build or a test suite is started once, and the model then checks on it repeatedly while it runs. Each check hands back everything the command has printed since it started, from the first line. So the second check re-sends the whole first check, the third re-sends the first two, and a check ten minutes into a slow build re-sends the same output for the tenth time. The model has already read all of it and pays again for every word, every time. Token-goat sends the first check through untouched, then returns only the new part on each later check, with one line saying that is what it is; a check that found nothing new comes back as a single short line instead of the whole output again. Measured through the installed hook: a second check of 5,200 characters came back as about 1,250, and a third check that added nothing came back as 60 — roughly a quarter of the cost for the second look and about one percent for the third, improving the longer the command runs. Nothing is lost, because what is cut is what was already sent. It only shortens a check when the new output genuinely continues the last one seen; anything else passes straight through, so the worst case is a saving that does not happen rather than a wrong answer.
180
182
 
181
- No ambient environment variable documents "this process is running under Copilot CLI" the way Codex/opencode set one, so the shim sets `TOKEN_GOAT_HARNESS_OVERRIDE=copilot_cli` itself before calling `token-goat hook` (same workaround `--pi` uses). Install also writes a token-goat routing block into `~/.copilot/copilot-instructions.md` (the same delimited-block gate written to `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`), merged idempotently so any hand-written content outside the markers is preserved byte-for-byte. If you set `COPILOT_HOME`, install follows it — hooks go to `$COPILOT_HOME/hooks/` and the routing block to `$COPILOT_HOME/copilot-instructions.md`, matching where Copilot CLI actually reads them. To install for one project instead of user scope: `token-goat install --copilot --local` (writes `.github/hooks/token-goat.json` and `.github/copilot-instructions.md` in the current project). `.github/hooks/token-goat.json` holds absolute paths to node and token-goat on your machine, so do not commit it or the `token-goat-shim.js` next to it: list them in `.git/info/exclude` (just for you) or `.gitignore`. Install prints this reminder. To remove: `token-goat uninstall --copilot`.
183
+ No ambient environment variable documents "this process is running under Copilot CLI" the way Codex/opencode set one, so the shim sets `TOKEN_GOAT_HARNESS_OVERRIDE=copilot_cli` itself before calling `token-goat hook` (same workaround `--pi` uses). Install also writes a token-goat routing block into `~/.copilot/copilot-instructions.md` (the same delimited-block gate written to `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`), merged idempotently so any hand-written content outside the markers is preserved byte-for-byte. If you set `COPILOT_HOME`, install follows it — hooks go to `$COPILOT_HOME/hooks/` and the routing block to `$COPILOT_HOME/copilot-instructions.md`, matching where Copilot CLI actually reads them. To install for one project instead of user scope: `token-goat install --copilot --local` (writes `.github/hooks/token-goat.json` and `.github/copilot-instructions.md` in the current project). `.github/hooks/token-goat.json` holds absolute paths to node and token-goat on your machine, so do not commit it or the `token-goat-shim.cjs` and `token-goat-shim.js` next to it: list them in `.git/info/exclude` (just for you) or `.gitignore`. Install prints this reminder. To remove: `token-goat uninstall --copilot`.
182
184
 
183
185
  **If Copilot CLI starts denying every tool call with `Denied by preToolUse hook ... (hook errored)`:** this is Copilot's own fail-closed behavior for a `preToolUse` hook that crashes, exits non-zero, or returns unparseable output -- it isn't limited to token-goat's own tool calls, since a fail-closed `preToolUse` hook blocks the whole session. Copilot caches hook configs at session start, so **renaming or reinstalling the hook mid-session has no effect** -- the only recovery is: run `token-goat install --copilot` (or `token-goat doctor`, which now checks the installed hook end-to-end and calls out a stale node-binary path from an nvm/fnm/volta upgrade specifically), then **fully restart Copilot CLI**.
184
186
 
@@ -188,7 +190,7 @@ No ambient environment variable documents "this process is running under Copilot
188
190
  token-goat install --vscode
189
191
  ```
190
192
 
191
- This installs into the **current project**: the MCP entry goes to `.vscode/mcp.json` (a timestamped `.bak` is written before any change to an existing file), the agent hooks to `.github/hooks/` (`token-goat.json` plus the `token-goat-shim.js` it runs), and the routing guidance to `.github/copilot-instructions.md`. Run it once in each project you want token-goat in. VS Code's Copilot agent reads hooks from that folder, so token-goat sees the agent's built-in tool calls: `read_file`, `view_image`, `list_dir`, `grep_search`, `file_search`, `create_file`, `replace_string_in_file`, `insert_edit_into_file`, `edit_notebook_file`, and `run_in_terminal`. Both `.vscode/mcp.json` and `.github/hooks/token-goat.json` hold absolute paths to node and token-goat on your machine, so do not commit them or the `token-goat-shim.js` next to them: list them in `.git/info/exclude` (just for you) or `.gitignore`. Install prints this reminder.
193
+ This installs into the **current project**: the MCP entry goes to `.vscode/mcp.json` (a timestamped `.bak` is written before any change to an existing file), the agent hooks to `.github/hooks/` (`token-goat.json` plus the `token-goat-shim.cjs` it runs), and the routing guidance to `.github/copilot-instructions.md`. Run it once in each project you want token-goat in. VS Code's Copilot agent reads hooks from that folder, so token-goat sees the agent's built-in tool calls: `read_file`, `view_image`, `list_dir`, `grep_search`, `file_search`, `create_file`, `replace_string_in_file`, `insert_edit_into_file`, `edit_notebook_file`, and `run_in_terminal`. Both `.vscode/mcp.json` and `.github/hooks/token-goat.json` hold absolute paths to node and token-goat on your machine, so do not commit them or the `token-goat-shim.js` next to them: list them in `.git/info/exclude` (just for you) or `.gitignore`. Install prints this reminder.
192
194
 
193
195
  `--vscode` is the one integration that installs into the project rather than user scope by default, and the reason is a limitation of VS Code itself. VS Code resolves an agent hook's working directory from the hook **file's** own location: the folder of the workspace that contains it, or the workspace's first folder when no workspace contains it. A user-scope hooks file lives in `~/.copilot/hooks/`, which is inside no workspace folder, so it always runs with the **first** folder of a multi-root workspace as its working directory — and since token-goat confines every pre-approval hook to that directory, read hints, image shrinking and edit interception silently do nothing for every other folder. A project-scope hooks file is inside its own folder, so each folder gets its own.
194
196
 
@@ -253,7 +255,7 @@ Grok Build already reads Claude Code's `~/.claude/settings.json` as a "Harness C
253
255
  token-goat install --grok
254
256
  ```
255
257
 
256
- The `--grok` flag patches Claude Code and additionally writes a standalone hook config at `~/.grok/hooks/token-goat.json` (global scope only — Grok's own project-scoped `<project>/.grok/hooks/*.json` requires a separate manual `/hooks-trust` grant this bridge can't perform for you) plus the shim it points at, `~/.grok/hooks/token-goat-shim.js`. The shim's only job is translating that one response shape: a token-goat `{"decision":"block",...}` deny becomes Grok's documented `{"decision":"deny",...}` (with exit code 2, matching Grok's own "explicit deny" convention), and every other event's response is forwarded through unmodified — Grok already sends the raw camelCase wire payload (`toolName`/`toolInput`/`sessionId`) token-goat's built-in `grok` harness detection (`GROK_SESSION_ID`, set on every hook subprocess Grok spawns) already normalizes correctly. That normalization maps every tool id registered in the grok 0.2.93 binary itself — both shell-tool spellings (`run_terminal_command` and `run_terminal_cmd`), `web_fetch`, `web_search`, `glob`, and the `hashline_*`/`*_concise` read/edit/grep variants — onto token-goat's internal tool names, so hooks fire regardless of which id a given Grok build sends.
258
+ The `--grok` flag patches Claude Code and additionally writes a standalone hook config at `~/.grok/hooks/token-goat.json` (global scope only — Grok's own project-scoped `<project>/.grok/hooks/*.json` requires a separate manual `/hooks-trust` grant this bridge can't perform for you) plus the shim it points at, `~/.grok/hooks/token-goat-shim.cjs`. The shim's only job is translating that one response shape: a token-goat `{"decision":"block",...}` deny becomes Grok's documented `{"decision":"deny",...}` (with exit code 2, matching Grok's own "explicit deny" convention), and every other event's response is forwarded through unmodified — Grok already sends the raw camelCase wire payload (`toolName`/`toolInput`/`sessionId`) token-goat's built-in `grok` harness detection (`GROK_SESSION_ID`, set on every hook subprocess Grok spawns) already normalizes correctly. That normalization maps every tool id registered in the grok 0.2.93 binary itself — both shell-tool spellings (`run_terminal_command` and `run_terminal_cmd`), `web_fetch`, `web_search`, `glob`, and the `hashline_*`/`*_concise` read/edit/grep variants — onto token-goat's internal tool names, so hooks fire regardless of which id a given Grok build sends.
257
259
 
258
260
  To remove: `token-goat uninstall --grok`.
259
261
 
@@ -282,7 +284,7 @@ There is no auto-update mechanism — token-goat never schedules or runs anythin
282
284
 
283
285
  ### Upgrading from the Python version
284
286
 
285
- The old Python package (`pip install token-goat`) wrote hook entries into `settings.json` with commands containing `token_goat` (underscore), invoking Python directly: something like `pythonw.exe -m token_goat.cli hook pre_tool_use`. The npm package invokes a generated shim instead (`"<node>" "~/.claude/hooks/token-goat-shim.js" pre_tool_use "<entry>"`).
287
+ The old Python package (`pip install token-goat`) wrote hook entries into `settings.json` with commands containing `token_goat` (underscore), invoking Python directly: something like `pythonw.exe -m token_goat.cli hook pre_tool_use`. The npm package invokes a generated shim instead (`"<node>" "~/.claude/hooks/token-goat-shim.cjs" pre_tool_use "<entry>"`).
286
288
 
287
289
  Both `install` and `uninstall` recognize the older command spellings — `token_goat`, `tokenwise`, `tg-hook`, `token-goat-hook`, and the pre-shim `token-goat hook` — so you do not need to hand-edit `settings.json`. Installing replaces a stale entry in place rather than leaving a dead one beside the new one, and uninstalling removes it.
288
290
 
@@ -384,19 +386,21 @@ MATLAB, OpenEdge ABL and Salesforce metadata share their file extensions with ot
384
386
 
385
387
  `token-goat install` writes the following on your machine — nothing else, anywhere. Every entry is reversed by `token-goat uninstall`. Integrations for other harnesses are additive on the way out as well as in, so a plain uninstall does not touch one you installed with `--codex`, `--copilot`, or a sibling flag: rather than undo something you did not ask about, it names each one still present and the flag that removes it. Run `token-goat doctor` at any time to see which of these are currently present.
386
388
 
387
- A bare `token-goat install` (no other flag) installs the Claude Code integration below. Passing a harness flag — `--vscode`, `--codex`, `--gemini`, and so on — installs only that harness's own files, listed in its own section further down: it never also touches `~/.claude/` on the side. If you want both, run `install` again with the other flag, or pass both flags in the same command. The one exception is `--hermes`, which delegates to `claude -p` and so genuinely needs the Claude Code hooks below; it installs them the same way a bare `install` does.
389
+ A bare `token-goat install` (no other flag) installs the Claude Code integration below. Passing a harness flag (`--vscode`, `--codex`, `--gemini`, and so on) installs only that harness's own files, listed in its own section further down: it never also touches `~/.claude/` on the side. To have the Claude Code integration as well, run a bare `token-goat install` too. Several harness flags in one command install each of those harnesses. The one exception is `--hermes`, which delegates to `claude -p` and so genuinely needs the Claude Code hooks below; it installs them the same way a bare `install` does.
388
390
 
389
391
  **Claude Code integration** (`~/.claude/`; written by a bare `install`, or by `--hermes`)
390
392
 
391
393
  | Path | What |
392
394
  |------|------|
393
395
  | `~/.claude/settings.json` | Hook entries for `SessionStart`, `PreToolUse` (Read/Grep/Bash, Drive/WebFetch), `PostToolUse` (Edit/Write/MultiEdit, Read/Grep/Glob, Bash, WebFetch, Skill), and `PreCompact`. Hook entries only: install writes nothing under `permissions`, so it never grants the agent unprompted execution of anything. Existing hooks are preserved; a timestamped `.bak` is written before any change.<br><br>The `PreToolUse` and `PostToolUse` matchers are narrowed to exactly the tools token-goat handles (plus `^mcp__`), generated from the live hook registry rather than a fixed list, so they can't fall out of date as handlers change. Claude Code starts a new process per matcher hit and most of that cost is process startup, so a catch-all matcher would make every unrelated tool call — `TodoWrite`, `TaskUpdate`, and friends — pay for a hook that has nothing to do. |
394
- | `~/.claude/hooks/token-goat-shim.js` | The hook script those `settings.json` commands invoke (`"<node>" "<shim>" <event> "<entry>"`). It imports the hook library in-process instead of spawning a second process, and naming the node binary directly skips the npm bin wrapper — on Windows a `cmd.exe` layer every hook would otherwise pay for. Measured 480 ms → 324 ms per hook call. Regenerated on every `install` run. Always written here even for a `--project` install, since the command bakes in machine-specific absolute paths; a project-scope `settings.json` just points at this one. |
396
+ | `~/.claude/hooks/token-goat-shim.cjs` | The hook script those `settings.json` commands invoke (`"<node>" "<shim>" <event> "<entry>"`). It imports the hook library in-process instead of spawning a second process, and naming the node binary directly skips the npm bin wrapper — on Windows a `cmd.exe` layer every hook would otherwise pay for. Measured 480 ms → 324 ms per hook call. Regenerated on every `install` run. Always written here even for a `--project` install, since the command bakes in machine-specific absolute paths; a project-scope `settings.json` just points at this one. The `.cjs` extension keeps Node from loading it as an ES module when a package.json above it says `"type": "module"`. A small `token-goat-shim.js` beside it hands off to the `.cjs` file, for sessions started before the rename that still run the old path. |
395
397
  | `~/.claude/CLAUDE.md` | A delimited block (`<!-- token-goat-begin -->` … `<!-- token-goat-end -->`) telling the agent to prefer `token-goat read` / `symbol` / `section` over `Read` / `Grep`. Any existing content is preserved; a timestamped `.bak` is written before any change. |
396
398
  | `~/.claude/skills/token-goat/SKILL.md` | The token-goat skill — the same routing guidance in skill form. A timestamped `.bak` is written before any change. |
397
399
 
398
400
  **Background worker.** token-goat does not register any persistent OS-level autostart entry — no Windows registry `Run` key, no systemd user unit, no XDG `.desktop` entry, and no macOS launchd `.plist`. The worker that drains the reindex queue is started manually as a detached child process: `token-goat worker start` launches `node <npm-prefix>/lib/node_modules/token-goat/dist/token-goat.mjs --worker-daemon` and returns immediately, and the child keeps running independent of the parent shell. `token-goat worker status` reports whether it's running; `token-goat worker stop` kills it. If it crashes or is killed while the machine stays up, the next edit hook detects it's gone and respawns it automatically (checked on every edit, rate-limited to roughly once every 5 minutes). It does not survive a reboot or logout, though — re-run `token-goat worker start` after either.
399
401
 
402
+ **Hook server.** The first hook call also starts up to three hook servers in the background: plain detached `node` processes, not OS services, that answer later hook calls and read-only commands so each call skips starting Node. Each listens on a named pipe (Windows) or a Unix socket with mode 0600 (elsewhere), never on a network port, and answers only a caller that proves it holds the random key in `hook-server.key` in the data directory, a file readable only by you. `hook-server.spawn-<slot>`, `hook-server.disabled` and `hook-server.failed` beside it are small marker files: a start rate limit, a record that the config turned the server off, and the reason the last start failed. A server exits after 30 minutes idle, after an upgrade replaces the build it loaded, when `server = false` under `[hooks]` in the global config or `TOKEN_GOAT_HOOK_SERVER=0` turns it off, and within two seconds of its key file being deleted. `uninstall` and `uninstall --purge` stop every running server before touching anything else. `token-goat capabilities` lists it under "Listens for other processes on this machine".
403
+
400
404
  There is no auto-update mechanism. Updating token-goat is always a manual `npm install -g token-goat@latest`.
401
405
 
402
406
  **Data directory** (created on first run)
@@ -419,7 +423,7 @@ Three things follow, and they are worth knowing before you decide. It never leav
419
423
  |------|------|
420
424
  | `~/.codex/config.toml` | Hooks block with Codex-specific matchers (`view_image|Bash`, `apply_patch`, `web_search`) plus `PreCompact`/`UserPromptSubmit`/`SubagentStop` global hooks. Existing hooks preserved. |
421
425
  | `~/.codex/AGENTS.md` | A delimited block (`<!-- token-goat-codex-begin -->` … `<!-- token-goat-codex-end -->`) with the same routing guidance, adapted for Codex tool names. |
422
- | `~/.codex/hooks/token-goat-shim.js` | The hook script `config.toml`'s hook commands invoke (`node "<path>" <event>`). Strips internal `_tg_*` keys and injects `hookSpecificOutput.hookEventName` to satisfy Codex's strict schemas. Regenerated on every `install --codex` run. |
426
+ | `~/.codex/hooks/token-goat-shim.cjs` | The hook script `config.toml`'s hook commands invoke (`node "<path>" <event>`). Strips internal `_tg_*` keys and injects `hookSpecificOutput.hookEventName` to satisfy Codex's strict schemas. Regenerated on every `install --codex` run. The `.cjs` extension keeps Node from loading it as an ES module when a package.json above it says `"type": "module"`. A small `token-goat-shim.js` beside it hands off to the `.cjs` file, for sessions started before the rename that still run the old path. |
423
427
 
424
428
  **With `--gemini`** (Gemini CLI integration)
425
429
 
@@ -438,7 +442,7 @@ Three things follow, and they are worth knowing before you decide. It never leav
438
442
  | Path | What |
439
443
  |------|------|
440
444
  | `~/.kimi-code/config.toml` | `[[hooks]]` entries for Kimi Code's `PreToolUse`, `PostToolUse`, `PreCompact`, `UserPromptSubmit`, `SubagentStop`, and `SessionStart` events. Each entry carries only `event` and `command`, the keys Kimi Code's strict schema accepts. Existing hooks and other config keys preserved; a timestamped `.bak` is written before any change. |
441
- | `~/.kimi-code/hooks/token-goat-shim.js` | The hook script those commands invoke. Rewrites a token-goat block into `hookSpecificOutput.permissionDecision` and a hint into a top-level `message`, and writes empty stdout for a no-op. Regenerated on every `install --kimi` run. |
445
+ | `~/.kimi-code/hooks/token-goat-shim.cjs` | The hook script those commands invoke. Rewrites a token-goat block into `hookSpecificOutput.permissionDecision` and a hint into a top-level `message`, and writes empty stdout for a no-op. Regenerated on every `install --kimi` run. The `.cjs` extension keeps Node from loading it as an ES module when a package.json above it says `"type": "module"`. A small `token-goat-shim.js` beside it hands off to the `.cjs` file, for sessions started before the rename that still run the old path. |
442
446
  | `~/.kimi-code/AGENTS.md` | A delimited block (`<!-- token-goat-kimi-begin -->` ... `<!-- token-goat-kimi-end -->`) with the routing guidance, adapted for Kimi Code tool names. |
443
447
  | `~/.kimi-code/skills/token-goat/SKILL.md` | The same guidance as a Kimi Code skill. |
444
448
 
@@ -459,7 +463,7 @@ Three things follow, and they are worth knowing before you decide. It never leav
459
463
  | Path | What |
460
464
  |------|------|
461
465
  | `~/.copilot/hooks/token-goat.json` | Hook config (`{ version, hooks }`) registering `preToolUse`, `postToolUse`, `preCompact`, `agentStop`, and `subagentStop`, each pointing at the shim script below. Existing files elsewhere in the hooks directory are untouched. |
462
- | `~/.copilot/hooks/token-goat-shim.js` | The shim `token-goat.json`'s hook commands invoke (`node "<path>"`). Translates Copilot's event names and response schema (`permissionDecision`/`modifiedArgs`, `additionalContext`) to/from token-goat's internal hook protocol. Regenerated on every `install --copilot` run. A project-local install (`--copilot --local`) writes `<project>/.github/hooks/token-goat.json` and `<project>/.github/hooks/token-goat-shim.js` instead. |
466
+ | `~/.copilot/hooks/token-goat-shim.cjs` | The shim `token-goat.json`'s hook commands invoke (`node "<path>"`). Translates Copilot's event names and response schema (`permissionDecision`/`modifiedArgs`, `additionalContext`) to/from token-goat's internal hook protocol. Regenerated on every `install --copilot` run. A project-local install (`--copilot --local`) writes `<project>/.github/hooks/token-goat.json` and `<project>/.github/hooks/token-goat-shim.cjs` instead. The `.cjs` extension keeps Node from loading it as an ES module in a repository whose package.json says `"type": "module"`. A two-line `token-goat-shim.js` beside it hands off to the `.cjs` file, for Copilot sessions started before the rename that still run the old path. |
463
467
  | `~/.copilot/copilot-instructions.md` | A delimited block (`<!-- token-goat-begin -->` … `<!-- token-goat-end -->`) with the same routing gate written to `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`, naming Copilot CLI's own `view`/`grep`/`glob` tools in the conflict-resolution clause. Merged idempotently — everything outside the markers is preserved byte-for-byte. A project-local install (`--copilot --local`) writes `<project>/.github/copilot-instructions.md` instead. |
464
468
 
465
469
  **With `--grok`** (Grok CLI / xAI Grok Build hook bridge)
@@ -467,7 +471,7 @@ Three things follow, and they are worth knowing before you decide. It never leav
467
471
  | Path | What |
468
472
  |------|------|
469
473
  | `~/.grok/hooks/token-goat.json` | Hook config (`{ hooks }`) registering `PreToolUse`, `PostToolUse`, `PreCompact`, `UserPromptSubmit`, and `SubagentStop` with an empty (match-everything) matcher, each pointing at the shim script below. Existing files elsewhere in the hooks directory are untouched; global scope only (Grok's project-scoped `.grok/hooks/` requires a separate manual `/hooks-trust` grant). |
470
- | `~/.grok/hooks/token-goat-shim.js` | The shim `token-goat.json`'s hook commands invoke. Translates `PreToolUse`'s deny shape only (`{"decision":"block",...}` → Grok's documented `{"decision":"deny",...}`, plus exit code 2); every other event's response is forwarded unmodified. Regenerated on every `install --grok` run. |
474
+ | `~/.grok/hooks/token-goat-shim.cjs` | The shim `token-goat.json`'s hook commands invoke. Translates `PreToolUse`'s deny shape only (`{"decision":"block",...}` → Grok's documented `{"decision":"deny",...}`, plus exit code 2); every other event's response is forwarded unmodified. Regenerated on every `install --grok` run. The `.cjs` extension keeps Node from loading it as an ES module when a package.json above it says `"type": "module"`. A small `token-goat-shim.js` beside it hands off to the `.cjs` file, for sessions started before the rename that still run the old path. |
471
475
 
472
476
  **With `--vscode`** (VS Code MCP configuration; **project scope by default** — the only integration that inverts the usual default, because VS Code pins a user-scope hook to the first folder of a multi-root workspace — with `--user` for the old every-project install)
473
477
 
@@ -475,7 +479,7 @@ Three things follow, and they are worth knowing before you decide. It never leav
475
479
  |------|------|
476
480
  | `<project>/.vscode/mcp.json` — or `%APPDATA%\Code\User\mcp.json` (Windows) / `~/Library/Application Support/Code/User/mcp.json` (macOS) / `~/.config/Code/User/mcp.json` (Linux) with `--user` | Merges the `token-goat` stdio entry under VS Code's `servers` root key, preserving unrelated servers and settings. `--user` refuses to write when the project scope already has a token-goat-managed entry, to avoid a duplicate registration; the reverse direction is a migration instead, and removes the user-scope install. |
477
481
  | `<project>/.github/copilot-instructions.md` (`~/.copilot/instructions/token-goat.instructions.md` with `--user`) | A delimited VS Code routing block that documents supported MCP selection and what the agent hooks can and cannot do with built-in file reads. The user-scope file is a personal instructions file with `applyTo: '**'`, so VS Code applies it in every workspace; install creates it with that frontmatter if it is missing and otherwise merges the block in, and uninstall deletes it again when nothing else is left in it. A user-scope install never writes into the folder you run it from. |
478
- | `<project>/.github/hooks/token-goat.json`, `token-goat-shim.js`, `token-goat.owners` (`~/.copilot/hooks/` with `--user`) | VS Code agent hooks, shared with `--copilot`; the owners file records which of the two installs still uses them. |
482
+ | `<project>/.github/hooks/token-goat.json`, `token-goat-shim.cjs`, `token-goat-shim.js`, `token-goat.owners` (`~/.copilot/hooks/` with `--user`) | VS Code agent hooks, shared with `--copilot`; the owners file records which of the two installs still uses them. |
479
483
 
480
484
  **With `--visualstudio`** (Visual Studio MCP configuration; user scope by default, `-p`/`--project` for the solution folder)
481
485
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-goat",
3
- "version": "2.9.26",
3
+ "version": "2.9.29",
4
4
  "description": "Surgical token-reduction companion for Claude Code and other AI coding agents",
5
5
  "type": "module",
6
6
  "main": "./dist/token-goat.mjs",
@@ -1,41 +0,0 @@
1
- import { createRequire as __cjsRequire } from 'node:module';
2
- const require = __cjsRequire(import.meta.url);
3
- import {
4
- buildEvent,
5
- relay,
6
- relayInProcess
7
- } from "./token-goat-chunk-JALB3KWJ.mjs";
8
- import "./token-goat-chunk-ZZT6PVB3.mjs";
9
- import {
10
- MAX_STDIN_BYTES,
11
- readStdinJson
12
- } from "./token-goat-chunk-7Y6GWTII.mjs";
13
- import "./token-goat-chunk-62IHOU6L.mjs";
14
- import "./token-goat-chunk-MDV5VWF4.mjs";
15
- import "./token-goat-chunk-MBDJR6HU.mjs";
16
- import "./token-goat-chunk-4GEDH2WU.mjs";
17
- import "./token-goat-chunk-VVROQBQJ.mjs";
18
- import "./token-goat-chunk-M6CVNHTW.mjs";
19
- import "./token-goat-chunk-G6EYO5CD.mjs";
20
- import "./token-goat-chunk-43JFN26X.mjs";
21
- import "./token-goat-chunk-D2X7XN5M.mjs";
22
- import "./token-goat-chunk-3BTK54F3.mjs";
23
- import "./token-goat-chunk-7EPYB34H.mjs";
24
- import "./token-goat-chunk-IKZLSRII.mjs";
25
- import "./token-goat-chunk-7SXPJOSZ.mjs";
26
- import "./token-goat-chunk-LRIZ7K3F.mjs";
27
- import "./token-goat-chunk-EEIDFMEM.mjs";
28
- import "./token-goat-chunk-OSUFN2FV.mjs";
29
- import "./token-goat-chunk-6E2IDLHF.mjs";
30
- import "./token-goat-chunk-NMTKNYGF.mjs";
31
- import "./token-goat-chunk-Y4AFKTHK.mjs";
32
- import "./token-goat-chunk-GMOUBOX4.mjs";
33
- import "./token-goat-chunk-RRNZMM3A.mjs";
34
- import "./token-goat-chunk-A37V4PBF.mjs";
35
- export {
36
- MAX_STDIN_BYTES,
37
- buildEvent,
38
- readStdinJson,
39
- relay,
40
- relayInProcess
41
- };
@@ -1,30 +0,0 @@
1
- import { createRequire as __cjsRequire } from 'node:module';
2
- const require = __cjsRequire(import.meta.url);
3
- import {
4
- DEFAULT_HEARTBEAT_INTERVAL_MS,
5
- DEFAULT_TIMEOUT_SECONDS,
6
- MAX_CAPTURE_BYTES,
7
- resolveFilter,
8
- run,
9
- runRaw
10
- } from "./token-goat-chunk-ZZT6PVB3.mjs";
11
- import "./token-goat-chunk-4GEDH2WU.mjs";
12
- import "./token-goat-chunk-43JFN26X.mjs";
13
- import "./token-goat-chunk-D2X7XN5M.mjs";
14
- import "./token-goat-chunk-IKZLSRII.mjs";
15
- import "./token-goat-chunk-7SXPJOSZ.mjs";
16
- import "./token-goat-chunk-LRIZ7K3F.mjs";
17
- import "./token-goat-chunk-EEIDFMEM.mjs";
18
- import "./token-goat-chunk-OSUFN2FV.mjs";
19
- import "./token-goat-chunk-6E2IDLHF.mjs";
20
- import "./token-goat-chunk-NMTKNYGF.mjs";
21
- import "./token-goat-chunk-GMOUBOX4.mjs";
22
- import "./token-goat-chunk-A37V4PBF.mjs";
23
- export {
24
- DEFAULT_HEARTBEAT_INTERVAL_MS,
25
- DEFAULT_TIMEOUT_SECONDS,
26
- MAX_CAPTURE_BYTES,
27
- resolveFilter,
28
- run,
29
- runRaw
30
- };