token-goat 2.9.30 → 2.9.31

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 (172) hide show
  1. package/README.md +24 -7
  2. package/SECURITY.md +10 -2
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/dist/native/linux-arm64/tg-hook +0 -0
  5. package/dist/native/linux-x64/tg-hook +0 -0
  6. package/dist/native/win32-arm64/tg-hook.exe +0 -0
  7. package/dist/native/win32-x64/tg-hook.exe +0 -0
  8. package/dist/{token-goat-chunk-QSBRTGM2.mjs → token-goat-chunk-25TRAWET.mjs} +17 -7
  9. package/dist/token-goat-chunk-2KXO6V2B.mjs +203 -0
  10. package/dist/{token-goat-chunk-IVKIZWAB.mjs → token-goat-chunk-2M7E4PF7.mjs} +41 -12
  11. package/dist/{token-goat-chunk-SMVRN2HU.mjs → token-goat-chunk-2UPZGKKY.mjs} +2 -2
  12. package/dist/token-goat-chunk-2WLPWKHV.mjs +135 -0
  13. package/dist/token-goat-chunk-2XBMMDBD.mjs +126 -0
  14. package/dist/token-goat-chunk-32ZOY4N5.mjs +23 -0
  15. package/dist/token-goat-chunk-3EZRNKTK.mjs +188 -0
  16. package/dist/token-goat-chunk-4ADZHI7J.mjs +63 -0
  17. package/dist/token-goat-chunk-4GBVPH3D.mjs +242 -0
  18. package/dist/{token-goat-chunk-KWPNJBDH.mjs → token-goat-chunk-4GILLLWY.mjs} +7 -4
  19. package/dist/{token-goat-chunk-67TQJ76B.mjs → token-goat-chunk-4NAPX7BA.mjs} +36 -3
  20. package/dist/{token-goat-chunk-PU7V3FD6.mjs → token-goat-chunk-4SC7UJYV.mjs} +3354 -2553
  21. package/dist/token-goat-chunk-4SMRO4TO.mjs +366 -0
  22. package/dist/{token-goat-chunk-AFLYLHDH.mjs → token-goat-chunk-4ZBFWLXF.mjs} +283 -427
  23. package/dist/{token-goat-chunk-JDWCRD6M.mjs → token-goat-chunk-57BIAXJL.mjs} +73 -40
  24. package/dist/token-goat-chunk-5AC66HNP.mjs +115 -0
  25. package/dist/token-goat-chunk-5IMLKSOP.mjs +145 -0
  26. package/dist/token-goat-chunk-646E6U44.mjs +398 -0
  27. package/dist/token-goat-chunk-65N4RHP4.mjs +33 -0
  28. package/dist/{token-goat-chunk-JN4XMLSD.mjs → token-goat-chunk-6DG6HWP3.mjs} +8 -2
  29. package/dist/token-goat-chunk-6ENQSNRZ.mjs +573 -0
  30. package/dist/token-goat-chunk-6ZY4GNF2.mjs +91 -0
  31. package/dist/{token-goat-chunk-VPBBTSJR.mjs → token-goat-chunk-76FGDLRO.mjs} +262 -255
  32. package/dist/{token-goat-chunk-7IN2MKDS.mjs → token-goat-chunk-7EZTZOMC.mjs} +121 -289
  33. package/dist/{token-goat-chunk-OJBNYTJP.mjs → token-goat-chunk-7MAOA6X4.mjs} +640 -80
  34. package/dist/token-goat-chunk-7R2ZVVAX.mjs +218 -0
  35. package/dist/{token-goat-chunk-PD5VKH46.mjs → token-goat-chunk-AUNGYYQX.mjs} +2 -2
  36. package/dist/{token-goat-chunk-HG45AF2B.mjs → token-goat-chunk-AUQYRMMH.mjs} +794 -37
  37. package/dist/token-goat-chunk-B3PEKBYG.mjs +128 -0
  38. package/dist/{token-goat-chunk-A3CK6N5Z.mjs → token-goat-chunk-BA4HI6BW.mjs} +1309 -110
  39. package/dist/{token-goat-chunk-JB6QN6VF.mjs → token-goat-chunk-BFCWY73P.mjs} +59 -23
  40. package/dist/{token-goat-chunk-B45VMIIH.mjs → token-goat-chunk-BWGFUZZE.mjs} +7 -5
  41. package/dist/{token-goat-chunk-P4QMM4WK.mjs → token-goat-chunk-C75NA7PL.mjs} +50 -23
  42. package/dist/token-goat-chunk-CBIQ7WTD.mjs +87 -0
  43. package/dist/token-goat-chunk-CETM74KC.mjs +307 -0
  44. package/dist/{token-goat-chunk-H2PHHGM5.mjs → token-goat-chunk-CIMQ2BB3.mjs} +57 -12
  45. package/dist/{token-goat-chunk-N2NMER56.mjs → token-goat-chunk-CJLS3CDL.mjs} +46 -4
  46. package/dist/{token-goat-chunk-VES7EL7O.mjs → token-goat-chunk-CO3UYX6G.mjs} +1 -1
  47. package/dist/token-goat-chunk-COPNXRJM.mjs +154 -0
  48. package/dist/token-goat-chunk-CSJ5L3NL.mjs +479 -0
  49. package/dist/{token-goat-chunk-4LCCNRTG.mjs → token-goat-chunk-CVCEVUGK.mjs} +8 -6
  50. package/dist/{token-goat-chunk-B2RZZCSS.mjs → token-goat-chunk-CXHOFN7R.mjs} +37 -20
  51. package/dist/token-goat-chunk-D735C4QM.mjs +603 -0
  52. package/dist/{token-goat-chunk-APGZXSPS.mjs → token-goat-chunk-DGS2LVNA.mjs} +47 -30
  53. package/dist/{token-goat-chunk-54K3EUV3.mjs → token-goat-chunk-DOWRSMWO.mjs} +1 -1
  54. package/dist/token-goat-chunk-E45INMY3.mjs +71 -0
  55. package/dist/token-goat-chunk-EC5F37F4.mjs +244 -0
  56. package/dist/{token-goat-chunk-XTQAOTSO.mjs → token-goat-chunk-EDBDRVH5.mjs} +4 -1
  57. package/dist/{token-goat-chunk-5BCIJXMO.mjs → token-goat-chunk-EELTBKDM.mjs} +14 -10
  58. package/dist/{token-goat-chunk-W3QCJS6G.mjs → token-goat-chunk-F4HYL64R.mjs} +8 -6
  59. package/dist/token-goat-chunk-F6PAUIEK.mjs +624 -0
  60. package/dist/token-goat-chunk-FDSREWLX.mjs +692 -0
  61. package/dist/token-goat-chunk-GGGG7RWB.mjs +236 -0
  62. package/dist/{token-goat-chunk-C63JQV5K.mjs → token-goat-chunk-GL3ETKSC.mjs} +233 -15
  63. package/dist/token-goat-chunk-GVB2MUST.mjs +1298 -0
  64. package/dist/token-goat-chunk-GWFLAUMQ.mjs +307 -0
  65. package/dist/{token-goat-chunk-ZEJVMUQH.mjs → token-goat-chunk-HLPW63A7.mjs} +2 -2
  66. package/dist/{token-goat-chunk-GOLEOXZP.mjs → token-goat-chunk-HU4TORC5.mjs} +44 -26
  67. package/dist/{token-goat-chunk-Z57YODA2.mjs → token-goat-chunk-I7VKZZAW.mjs} +2 -2
  68. package/dist/{token-goat-chunk-NYKZLXMK.mjs → token-goat-chunk-IA6XSPE3.mjs} +6 -6
  69. package/dist/{token-goat-chunk-E3OQX2XT.mjs → token-goat-chunk-IAGXMHIK.mjs} +81 -42
  70. package/dist/{token-goat-chunk-HYBFA5X3.mjs → token-goat-chunk-IK2DRMFX.mjs} +24 -68
  71. package/dist/{token-goat-chunk-COBQGL5S.mjs → token-goat-chunk-INNPTRUX.mjs} +249 -299
  72. package/dist/{token-goat-chunk-7NADEDKJ.mjs → token-goat-chunk-JJ5BX4F4.mjs} +6 -1
  73. package/dist/{token-goat-chunk-PEPTHRFF.mjs → token-goat-chunk-JVFSHVFJ.mjs} +6 -6
  74. package/dist/token-goat-chunk-KGEFFSHD.mjs +50 -0
  75. package/dist/token-goat-chunk-KQBLRPBN.mjs +433 -0
  76. package/dist/{token-goat-chunk-Q5VTGRH7.mjs → token-goat-chunk-LCNQ2CNN.mjs} +55 -319
  77. package/dist/token-goat-chunk-M3LFJDPU.mjs +58 -0
  78. package/dist/token-goat-chunk-M52KPT3C.mjs +87 -0
  79. package/dist/token-goat-chunk-MBEG42H7.mjs +150 -0
  80. package/dist/{token-goat-chunk-EVC4TOLE.mjs → token-goat-chunk-METECDOH.mjs} +3 -12
  81. package/dist/{token-goat-chunk-T4FK7VOV.mjs → token-goat-chunk-MHX7XCC3.mjs} +2 -1
  82. package/dist/token-goat-chunk-N5LSOVSU.mjs +137 -0
  83. package/dist/{token-goat-chunk-LOBGHXPB.mjs → token-goat-chunk-N5MMR2IW.mjs} +1 -1
  84. package/dist/token-goat-chunk-N7B3JQ2C.mjs +160 -0
  85. package/dist/{token-goat-chunk-RHXKCZX3.mjs → token-goat-chunk-NISNMD7T.mjs} +2182 -2959
  86. package/dist/token-goat-chunk-NMD42SFQ.mjs +434 -0
  87. package/dist/token-goat-chunk-NOSS5VWY.mjs +174 -0
  88. package/dist/{token-goat-chunk-KQNZDCBD.mjs → token-goat-chunk-NSNSPPBG.mjs} +25 -3
  89. package/dist/token-goat-chunk-OBOFIHKN.mjs +38 -0
  90. package/dist/token-goat-chunk-OBRFJ5O5.mjs +76 -0
  91. package/dist/{token-goat-chunk-JTQQGXZH.mjs → token-goat-chunk-ODBT2USM.mjs} +124 -57
  92. package/dist/token-goat-chunk-OS5EM5PP.mjs +107 -0
  93. package/dist/token-goat-chunk-OUKDTVNU.mjs +1178 -0
  94. package/dist/token-goat-chunk-OXFMP75N.mjs +566 -0
  95. package/dist/{token-goat-chunk-DZX2YDPT.mjs → token-goat-chunk-OYIYKDDT.mjs} +2 -2
  96. package/dist/{token-goat-chunk-QCLDAQTS.mjs → token-goat-chunk-P6HQSWQ3.mjs} +50 -53
  97. package/dist/token-goat-chunk-P6VGNTQT.mjs +71 -0
  98. package/dist/{token-goat-chunk-7BXSNBY5.mjs → token-goat-chunk-PFWIMH2E.mjs} +23 -15
  99. package/dist/token-goat-chunk-PJZXIS3G.mjs +39 -0
  100. package/dist/token-goat-chunk-PUO5INXP.mjs +606 -0
  101. package/dist/{token-goat-chunk-GFBSN7BD.mjs → token-goat-chunk-PX5PQWTY.mjs} +22 -250
  102. package/dist/token-goat-chunk-Q5QDBC6O.mjs +111 -0
  103. package/dist/token-goat-chunk-QFNQXSDT.mjs +40 -0
  104. package/dist/{token-goat-chunk-FBNQNSJK.mjs → token-goat-chunk-QH7YVYOY.mjs} +2198 -2782
  105. package/dist/{token-goat-chunk-64FBPDTK.mjs → token-goat-chunk-QLMUHEPB.mjs} +12 -9
  106. package/dist/{token-goat-chunk-RZRTDST7.mjs → token-goat-chunk-QQOJIHL3.mjs} +37 -11
  107. package/dist/{token-goat-chunk-XKI4SEMX.mjs → token-goat-chunk-QQTWJY2O.mjs} +19 -0
  108. package/dist/{token-goat-chunk-U4N3BN2W.mjs → token-goat-chunk-QYHO5CPH.mjs} +3 -2
  109. package/dist/token-goat-chunk-RELAVWAD.mjs +27 -0
  110. package/dist/{token-goat-chunk-ETBHVDB6.mjs → token-goat-chunk-RSLPOBKQ.mjs} +22 -11
  111. package/dist/token-goat-chunk-SAMFDIS2.mjs +712 -0
  112. package/dist/token-goat-chunk-SDELXLDM.mjs +48 -0
  113. package/dist/{token-goat-chunk-4JJS54TR.mjs → token-goat-chunk-SOAFZNNZ.mjs} +161 -38
  114. package/dist/{token-goat-chunk-664RTTUV.mjs → token-goat-chunk-SRTHT7KV.mjs} +21 -16
  115. package/dist/{token-goat-chunk-4FVIAHSI.mjs → token-goat-chunk-T5P5ACNQ.mjs} +7 -6
  116. package/dist/token-goat-chunk-T5YGG4V3.mjs +45 -0
  117. package/dist/token-goat-chunk-TAUZ4OFS.mjs +110 -0
  118. package/dist/{token-goat-chunk-COTXZHTS.mjs → token-goat-chunk-TM6MC754.mjs} +4 -3
  119. package/dist/token-goat-chunk-TWUKBOMP.mjs +72 -0
  120. package/dist/{token-goat-chunk-3CHUNJ2O.mjs → token-goat-chunk-U2KSB4SG.mjs} +8 -5
  121. package/dist/{token-goat-chunk-3Z5S4KGM.mjs → token-goat-chunk-U4WHMG6Y.mjs} +29 -49
  122. package/dist/{token-goat-chunk-PY533T7F.mjs → token-goat-chunk-UGJHGR5A.mjs} +21 -15
  123. package/dist/{token-goat-chunk-CYCEYAEO.mjs → token-goat-chunk-UOUDVA5U.mjs} +1 -1
  124. package/dist/{token-goat-chunk-JDHE26RP.mjs → token-goat-chunk-URRJ33GR.mjs} +16 -5
  125. package/dist/{token-goat-chunk-56EIMS3E.mjs → token-goat-chunk-UZOROKIS.mjs} +23 -17
  126. package/dist/{token-goat-chunk-CZ3LUXYZ.mjs → token-goat-chunk-VIHGGGEI.mjs} +427 -308
  127. package/dist/token-goat-chunk-VTJLAA7P.mjs +21 -0
  128. package/dist/token-goat-chunk-VWDXLT4F.mjs +678 -0
  129. package/dist/{token-goat-chunk-YARZFCZS.mjs → token-goat-chunk-VWZA2H4P.mjs} +62 -53
  130. package/dist/{token-goat-chunk-SQSSNQPE.mjs → token-goat-chunk-VXEXMJDL.mjs} +3979 -4745
  131. package/dist/token-goat-chunk-WBA5SKT4.mjs +547 -0
  132. package/dist/{token-goat-chunk-SJHWVNDQ.mjs → token-goat-chunk-WDH44VAU.mjs} +7 -4
  133. package/dist/token-goat-chunk-X2SUP3EI.mjs +107 -0
  134. package/dist/token-goat-chunk-X3BELZSJ.mjs +125 -0
  135. package/dist/{token-goat-chunk-FZ3NEW5O.mjs → token-goat-chunk-XLRBNZCC.mjs} +13 -8
  136. package/dist/{token-goat-chunk-Z44YO3QZ.mjs → token-goat-chunk-XRKUX6CM.mjs} +1 -1
  137. package/dist/token-goat-chunk-XX6O25RN.mjs +82 -0
  138. package/dist/token-goat-chunk-XZSUHBVS.mjs +65 -0
  139. package/dist/{token-goat-chunk-WY3L4DVT.mjs → token-goat-chunk-Y2ZTQOCL.mjs} +13 -8
  140. package/dist/{token-goat-chunk-UQ5UK2T7.mjs → token-goat-chunk-YB4VMGK2.mjs} +1 -1
  141. package/dist/token-goat-chunk-YBIIY3HR.mjs +51 -0
  142. package/dist/{token-goat-chunk-ZQSJMO2R.mjs → token-goat-chunk-YHVDKQBR.mjs} +105 -105
  143. package/dist/{token-goat-chunk-H2K3QP6H.mjs → token-goat-chunk-YJB5XLWI.mjs} +8 -6
  144. package/dist/{token-goat-chunk-ZCDTCVIS.mjs → token-goat-chunk-YKB6AQG3.mjs} +5 -2
  145. package/dist/token-goat-chunk-YMTG2YA5.mjs +35 -0
  146. package/dist/token-goat-chunk-YPBZYQRV.mjs +40 -0
  147. package/dist/{token-goat-chunk-HOJLCGDP.mjs → token-goat-chunk-YY6AVUFL.mjs} +2033 -2251
  148. package/dist/token-goat-chunk-Z7N5OMMP.mjs +98 -0
  149. package/dist/token-goat-chunk-ZNMNUPAZ.mjs +938 -0
  150. package/dist/token-goat-hook-client.cjs +1 -1
  151. package/dist/token-goat-hook-client.mjs +3 -3
  152. package/dist/token-goat-hook.mjs +67 -38
  153. package/dist/token-goat.core.mjs +4 -4
  154. package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
  155. package/docs/cli.md +40 -21
  156. package/docs/install.md +19 -9
  157. package/docs/security.md +5 -3
  158. package/package.json +2 -1
  159. package/dist/token-goat-chunk-5KDMCMF2.mjs +0 -362
  160. package/dist/token-goat-chunk-6QMAS7AB.mjs +0 -42
  161. package/dist/token-goat-chunk-7F2UBPD3.mjs +0 -397
  162. package/dist/token-goat-chunk-ARDNEC2U.mjs +0 -248
  163. package/dist/token-goat-chunk-ATCPY7EQ.mjs +0 -134
  164. package/dist/token-goat-chunk-EFHMMIGP.mjs +0 -68
  165. package/dist/token-goat-chunk-IVALLT3S.mjs +0 -48
  166. package/dist/token-goat-chunk-JDU3EXSU.mjs +0 -65
  167. package/dist/token-goat-chunk-MKPF4CL5.mjs +0 -37
  168. package/dist/token-goat-chunk-RC35RBUL.mjs +0 -312
  169. package/dist/token-goat-chunk-USILZ3T3.mjs +0 -53
  170. package/dist/token-goat-chunk-VL3YIFHZ.mjs +0 -2975
  171. package/dist/token-goat-chunk-XU4P6Z24.mjs +0 -133
  172. package/dist/token-goat-chunk-Y776AENC.mjs +0 -1597
package/README.md CHANGED
@@ -113,15 +113,24 @@ The fastest way to reduce AI token costs is fixing these six, not writing shorte
113
113
  | Large markdown file read in full (README.md, CHANGELOG.md, CLAUDE.md ≥8 KB) | Heading tree intercepted instead — H1–H3 with `#2`/`#3` disambiguation; `token-goat section` shortcuts listed for well-known files; post-edit injects a re-read suggestion rather than the full file |
114
114
  | Subagent reads a ≥30 KB markdown file whole, on its first look at it | Off by default. Set `subagent_markdown_first_read_deny = true` under `[hints]` to block that read and answer with the heading tree instead. Only fires in a subagent, only on a first, un-ranged read of a `.md`/`.mdx`/`.markdown` file with at least three headings; a read that already asks for a line range, and every main-session read, pass through untouched |
115
115
  | Claude Code spawns an agent without `subagent_type` while tools-restricted agent definitions exist | Off by default. Set `agent_scoped_spawn_deny = true` under `[hints]` (or `TOKEN_GOAT_AGENT_SCOPED_SPAWN_DENY=1`) to refuse the first such spawn in a session and name the restricted agents, so the retry can pick one. An untyped spawn runs as general-purpose, whose context starts with every tool and MCP schema on the machine. Re-issuing the same call runs it: the refusal happens once per session. A spawn that names any `subagent_type`, `general-purpose` included, is never refused, and Copilot CLI and VS Code are left alone |
116
- | Claude Code Bash command on Windows that contains two backslashes in a row | Refused before it runs, with the reason and what to do instead. Git Bash halves every run of two or more backslashes in the command it is handed, so an escaped path, a regular expression or a `printf` format string would run as different text with no error. Write the text to a file with the Write tool and run it by path, use the PowerShell tool, or use forward slashes in paths. Claude Code on Windows only; set `deny_bash_double_backslash = false` under `[hints]` (or `TOKEN_GOAT_DENY_BASH_DOUBLE_BACKSLASH=0`) to turn it off |
116
+ | An agent ends its turn after editing source files that no test, lint, typecheck or build run has covered since | A one-time note at the Stop event names the unverified files and the checks that would cover them. It is an advisory, never a block, because a block at Stop can trap a session in a loop. Claude Code and Codex CLI show it; Copilot CLI's and VS Code's stop hooks accept only a block, so it stays silent there. Turn it off with `stop_verification_advisory = false` under `[hints]` (or `TOKEN_GOAT_STOP_VERIFICATION_ADVISORY=0`) |
117
+ | A subagent finishes with a failed last check, source edited after its last passing check, or a report that claims a change it never made | Up to three lines are appended to the report the parent receives, saying which of those happened. A subagent with nothing to flag gets nothing added. On Copilot CLI the lines ride on the subagent report rewrite; the report part of that rewrite is the secret-redacted original, so a secret token-goat redacted stays redacted. Copilot's tool events carry no agent id, so there only the claimed-a-change-never-made check applies to a subagent (it reads the git tree), not the failed-check or unverified-edit flags. On Codex the lines ride on the result of the multi-agent v1 `wait_agent` tool, keyed on the subagent's thread id; Codex's v2 multi-agent tools return no report, so nothing is delivered there. VS Code Chat gets no receipt, because its SubagentStop payload carries no report text, and Visual Studio and JetBrains have no agent hooks. Turn it off with `agent_receipt = false` under `[hints]` (or `TOKEN_GOAT_AGENT_RECEIPT=0`) |
118
+ | Claude Code Bash command on Windows that contains two backslashes in a row | Run from a temp file when your permission rules already allow the command, otherwise refused before it runs, with the reason and what to do instead. Git Bash halves every run of two or more backslashes in the command it is handed, so an escaped path, a regular expression or a `printf` format string would run as different text with no error. A command that needs no prompt is written, as typed, to a file that deletes itself on its first line, and run from there; one that would prompt, or that uses `$`, a backtick, `eval`, rm or a protected path, is refused, because the prompt would show a file name and not the command. Then write the text to a file with the Write tool and run it by path, use the PowerShell tool, or use forward slashes in paths. A command over 7,000 characters with no such backslashes is left alone. Claude Code on Windows only; set `deny_bash_double_backslash = false` under `[hints]` (or `TOKEN_GOAT_DENY_BASH_DOUBLE_BACKSLASH=0`) to turn it off |
117
119
  | A source file is read whole for the first time, with no earlier copy to compare it against | Keeps each function's declaration and its first eight lines, replacing the rest with one line naming the symbol, the exact range removed, and the command that returns it. Set `fold_code_bodies = false` under `[hints]` to turn it off. Comment blocks of twelve lines or more fold to their first two on the same terms. Imports, signatures, type declarations, classes and interfaces are never folded, and a file whose index no longer matches what is on disk keeps its bodies. Measured on this repository's own source: 229 of 256 files fold, and 43% of what a read of them delivers is removed. A read that already names an offset or a limit, and any file holding something that looks like a secret, pass through untouched |
120
+ | A source or markdown file of 50 KB or more is read whole for the first time, and token-goat has indexed its symbols or headings | The read goes through with a note naming the file's size, its symbol or heading count and the `read`, `outline` or `skeleton` command that returns just the part you need. Set `first_read_symbol_policy = "deny"` under `[hints]` (or `TOKEN_GOAT_FIRST_READ_SYMBOL_POLICY=deny`) to refuse the whole-file read instead, or `"off"` to say nothing. `first_read_symbol_bytes` sets the size (50000 by default, 0 turns the check off). Both can only be set in your global config or the environment: a repository's `.token-goat.toml` cannot set them |
121
+ | A Write, Edit, MultiEdit or Codex `apply_patch` puts a placeholder such as `// ... existing code ...` or `[rest of file unchanged]` into a file, or a whole-file Write to a file that was only partly read deletes five or more lines the session never saw | Refused before it runs, with the placeholder or the unseen line ranges named and a `token-goat read` command for each range. Repeating the identical call goes through. Codex's pre-tool hook can refuse an `apply_patch`, and VS Code's `apply_patch` and `multi_replace_string_in_file` are checked; VS Code's `insert_edit_into_file` edits are not covered. Set `write_blind_drop = "warn"` under `[hints]` (or `TOKEN_GOAT_WRITE_BLIND_DROP=warn`) to add a note instead of refusing, or `"off"` to say nothing. A note is silenced during quiet hours, a refusal is not |
122
+ | A Write creates a new file named like a copy of an existing one in the same directory (`solver_v2.py`, `solver_fixed.py`, `solver.py.bak`) and repeats most of that file | A note, once per original per session, naming how much of the original it repeats and suggesting `Edit`, `token-goat replace` or `token-goat outline` on the original. It needs the original to reach `write_rewrite_min_lines` (40) and `write_rewrite_unchanged_pct` (75) under `[hints]`. Files under `migrations`, `versions`, `fixtures`, `__snapshots__` and any `vN` directory below the project root are skipped, `write_sibling_copy_allow` adds path globs to skip, and `write_sibling_copy = false` (or `TOKEN_GOAT_WRITE_SIBLING_COPY=0`) turns it off |
123
+ | A Read, Grep, Edit or Write names a path that does not exist, or a shell command fails with `No such file or directory` | The failed call is answered with up to three existing paths it probably meant (a stray `@` or `:12` suffix, a curly apostrophe, a one or two letter typo, a case difference, the same name in another directory), once per path per session. A credential-style file name is never offered. The read commands print the same list under `Did you mean:` |
124
+ | An Edit fails because its `old_string` was one invisible character off (trailing space, curly quote, indentation) | The reply quotes the exact matching lines as they are on disk, so the retry copies them instead of re-reading the file. A credential-style file name gets the line range only |
125
+ | A shell command fails that an earlier session fixed twice (an install, build or test command from a short allowlist) | One line offers the fix again, marked `not verified`. Only known runners and paths are kept, and no other part of the command is stored on disk, only digests |
126
+ | A whole-file re-read is refused and the agent asks again, or tries the same file at other line ranges | The whole-file refusal is given at most once per file per compaction, then the read goes through with a short note. A refused line range locks later line-range reads on that file until it changes, and the refusal names the `read "path@START-END"` or `skeleton` command to use instead |
118
127
  | PDF opened via Read | Full read denied; PDF shows page count and outline (`token-goat pdf-extract` pulls the actual text, optionally paged/sliced, when the outline isn't enough) |
119
128
  | Excel/PowerPoint/Word file (.xlsx/.pptx/.docx) opened via Read | Full read denied; redirects to the matching narrow-slice command family (`xlsx-sheets`/`xlsx-columns`/`xlsx-head`/`xlsx-range`/`xlsx-query`, `pptx-outline`/`pptx-slide`/`pptx-notes`/`pptx-text`, `docx-outline`/`docx-tables`/`docx-text`) instead of extracting the whole document as text |
120
129
  | SQLite database (.db, .sqlite, .sqlite3, .db3) opened via Read | Full read denied; redirects to the SQLite narrow-slice family (`sqlite-tables`/`sqlite-schema`/`sqlite-query`) instead of reading binary bytes into context |
121
130
  | Parquet file (.parquet) opened via Read | Full read denied; redirects to DuckDB for SQL querying instead of reading binary columnar data |
122
131
  | Large JSON or YAML file (≥20 KB) read in full | Top-level keys or array summary previewed; redirects to `json-outline`/`json-query` or `yaml-outline`/`yaml-query` instead of blowing context on multi-megabyte payloads |
123
132
  | A file read through inline Python, Node or PowerShell (`python -c "...open('r.json')..."`) | Runs capped at 2,000 tokens with no other filtering, so a one-key projection comes back whole; a whole-file dump is cut, ends with a recall id, and names `json-outline`/`json-query` for JSON and YAML. Still refused where the wrapper cannot run: a pipeline or chain, a background `&`, compression turned off |
124
- | Findings you want to survive a compaction | `token-goat note set <key> "<finding>"` stores one per project; notes print at every session start and in the compaction manifest. Add `--anchor src/auth.ts::login`, or name a `file::symbol` in the finding, and the note is marked "(changed since note)" once that symbol changes |
133
+ | Findings you want to survive a compaction | `token-goat note set <key> "<finding>"` stores one per project; notes print at every session start and in the compaction manifest. Add `--anchor src/auth.ts::login`, or name a `file::symbol` in the finding, and the note is marked "(changed since note)" once that symbol changes. If a slow start misses its 3-second deadline on Copilot CLI or VS Code, the session-start text and notes arrive with the first tool call |
125
134
  | Large JSON Lines file (.jsonl ≥20 KB) read in full | Record count and first-record schema shown; suggests slicing records with offset/limit instead of loading millions of tokens |
126
135
  | Other Office binary (.odt, .ods, .ott, .odp) opened via Read | Full read denied; redirects to `pandoc` for text extraction (no dedicated reader for these formats yet) |
127
136
  | Large CSV or TSV file (≥10 KB) read in full | Column headers, row count, and 3 sample rows shown; `token-goat csv-query` projects columns and/or filters rows instead of a full read; `duckdb` query suggestion for very large tabular data |
@@ -183,7 +192,7 @@ The fastest way to reduce AI token costs is fixing these six, not writing shorte
183
192
  | Broad recursive Glob sweep (`*`, `**/*`) on root directory | Pre-Glob hook warns against tree-dumping and points at `token-goat map --compact` for fast, lightweight structure inspection |
184
193
  | Guessing database column names in `session_store_sql` / `sql` and falling back to `SELECT *` | `token-goat session-schema [table]` and `describe <target>` provide instant schema discovery; `post_tool_use_failure` hook intercepts unknown columns and guides the query — ~85–95% smaller than trial-and-error `SELECT *` dumps |
185
194
  | Compound test/build pipeline (`npm run build && npm run typecheck && npm test`) | Post-Bash hook routes chained build/test/lint commands to `generic-ci` compression, dropping verbose passing steps and compiler noise |
186
- | Verbose command output on Windows under Codex CLI, Copilot CLI, or a PowerShell session, where the compression hook used to skip every command | Pre-Bash hook hands the command to PowerShell itself, base64-encoded through an environment variable the launched script clears first, so backslashes, quotes, and redirection survive and the output is compressed like anywhere else |
195
+ | Verbose command output on Windows under Codex CLI or a PowerShell session, where the compression hook used to skip every command | Pre-Bash hook hands the command to PowerShell itself, base64-encoded through an environment variable the launched script clears first, so backslashes, quotes, and redirection survive and the output is compressed like anywhere else |
187
196
 
188
197
  On a per-token API plan, 100K wasted tokens per session runs about $0.30. Five sessions a week is ~$450/year. AI coding cost reduction at that scale comes from fixing the waste, not from using the product less. Token-goat is free. And on subscription plans, it can result in limits feeling 10x higher.
189
198
 
@@ -260,6 +269,8 @@ out: 38 lines # ~300 tokens (97% smaller)
260
269
 
261
270
  A line number works in place of a name, which is what you are holding when a grep hit, a stack frame, or a diff hunk points at code: `token-goat read "src/auth.py:142"` returns the symbol enclosing line 142, and `read "src/auth.py:142-190"` returns every symbol the range touches. A Windows drive letter stays part of the path, so `C:/src/auth.py:142` reads line 142 of that file, and a bare `C:142` stays a drive-relative path rather than a line number.
262
271
 
272
+ A line range given as a flag (`--lines 1-5`, `--line`, `--range`, `--start`/`--end`, `--from`/`--to`, `--offset`/`--limit`) is not an option of `read`, but when the file argument names no place yet and the range is complete it runs as `read "file@1-5"` and says so on stderr: ``Note: ran as `token-goat read "f.ts@1-5"` ...``. A guessed spelling of a real flag (`--top`, `-k`, `--max` or `--count` for `--limit` on `search`, `semantic` and `recall`; `--search`, `--filter` or `--pattern` for `--grep` on the cached-output commands) runs the same way with the same note when its value is usable. Anything less certain still fails, with a `Did you mean` line naming the command to run.
273
+
263
274
  Same applies to `token-goat section "README.md::Install"` — one heading instead of the whole document. Anchor IDs and setext headings resolve too, so `section "doc.md::Quick-start"` works when the file uses `Quick start` as an `<h2>` with an explicit `{#quick-start}` anchor.
264
275
 
265
276
  ### 3. Compact manifest — preserve what mattered
@@ -321,6 +332,8 @@ Agents and interactive users must pass ordinary plain shell commands directly: a
321
332
 
322
333
  `-q` / `--quiet-success` trades a clean run's output for one line. On exit code 0 the full output is stored for recall and only the summary prints; on any other exit code the compressed output is delivered as usual.
323
334
 
335
+ The wrapped command gets no standard input by default, on every path (plain, `--max-tokens`, `-q`, a filter, `--no-compress`), so a hook-wrapped command can never wait on input the harness will not send. `--stdin` hands it the caller's standard input instead, byte for byte: `printf 'héllo' | token-goat compress --stdin -c cat`.
336
+
324
337
  ```
325
338
  $ token-goat compress -q --cmd "npm run lint"
326
339
  [tg: ok] npm run lint (22.2s, recall: token-goat bash-output 326d9800e732eb9d)
@@ -370,7 +383,7 @@ Every command, with its flags, its output, and the comparison workflows that cha
370
383
  token-goat mcp-serve
371
384
  ```
372
385
 
373
- Runs token-goat as an MCP ([Model Context Protocol](https://modelcontextprotocol.io)) stdio server, exposing surgical-read tools (including `brief`, a one-shot symbol orientation call bundling body, callers, and containing doc section, and `index_status`, which reports whether a project's index has ever been populated, current file/symbol counts, dirty-queue depth, worker liveness, and embeddings availability — call it after an unexpectedly empty result from another tool, since an MCP-only client has no hook layer to warn it apart from a genuine "not found") plus `compress_text`, `retrieve_text`, `handoff_create`, and `handoff_resolve`. These local-only tools use bounded, redacted storage; MCP never intercepts a client's built-in file reads. Every tool takes an optional `projectRoot`. When a call leaves it out, the server infers the project: from an absolute path that exists, then from indexed file paths ending with the path you gave, then, for a bare name, from the project that defines that symbol. It only infers a project token-goat has indexed that sits inside the server's allowed roots; otherwise it keeps its own working directory, so with `mcp.confine_reads_to_project_root` on, a path outside it is still refused. Passing `projectRoot` always wins.
386
+ Runs token-goat as an MCP ([Model Context Protocol](https://modelcontextprotocol.io)) stdio server, exposing seven tools by default (`symbol`, `read`, `section`, `outline`, `semantic`, `refs`, and `retrieve_text`, which recovers a compressed payload) and eleven more you switch on by name, since every tool listed is sent to the model on every turn. The others are surgical-read tools (including `brief`, a one-shot symbol orientation call bundling body, callers, and containing doc section, and `index_status`, which reports whether a project's index has ever been populated, current file/symbol counts, dirty-queue depth, worker liveness, and embeddings availability — call it after an unexpectedly empty result from another tool, since an MCP-only client has no hook layer to warn it apart from a genuine "not found") plus `compress_text`, `handoff_create`, and `handoff_resolve`. To switch on the others, set `TOKEN_GOAT_MCP_TOOLS` to the names you want, separated by commas (for example `symbol,read,section,outline,semantic,refs,retrieve_text,grep`); a list you set replaces the default, so name the seven too, and name all eighteen to get every tool. These local-only tools use bounded, redacted storage; MCP never intercepts a client's built-in file reads. Every tool takes an optional `projectRoot`. When a call leaves it out, the server infers the project: from an absolute path that exists, then from indexed file paths ending with the path you gave, then, for a bare name, from the project that defines that symbol. It only infers a project token-goat has indexed that sits inside the server's allowed roots; otherwise it keeps its own working directory, so with `mcp.confine_reads_to_project_root` on, a path outside it is still refused. Passing `projectRoot` always wins.
374
387
 
375
388
  ### Generic compression and handoffs
376
389
 
@@ -400,7 +413,7 @@ token-goat handoff-resolve review-notes --full
400
413
  }
401
414
  ```
402
415
 
403
- `token-goat install --vscode` creates or idempotently updates VS Code's user-profile `mcp.json` by default (`%APPDATA%\Code\User\mcp.json` on Windows, `~/Library/Application Support/Code/User/mcp.json` on macOS, `~/.config/Code/User/mcp.json` on Linux) — add `-p`/`--project` for the project-local `.vscode/mcp.json` shown above instead. It also adds a delimited routing block, preserving unrelated JSON and user text: to `~/.copilot/instructions/token-goat.instructions.md` for the user install (a personal instructions file VS Code applies in every workspace, so the folder you run it from is left alone), or to `.github/copilot-instructions.md` with `-p`. It fails clearly on malformed JSON, and refuses to install into one scope if the other scope already has a token-goat-managed entry (registering it twice would duplicate its tool schemas in that workspace). It also installs agent hooks in `~/.copilot/hooks/` (or `.github/hooks/` with `-p`), the folder VS Code's Copilot agent reads hooks from, so token-goat sees the agent's built-in reads, edits, and terminal commands. `token-goat uninstall --vscode` (add `-p`/`--project` for the project scope) removes only token-goat's server entry, guidance block, and hooks, and keeps the hooks if `--copilot` still uses them.
416
+ `token-goat install --vscode` creates or idempotently updates VS Code's user-profile `mcp.json` by default (`%APPDATA%\Code\User\mcp.json` on Windows, `~/Library/Application Support/Code/User/mcp.json` on macOS, `~/.config/Code/User/mcp.json` on Linux, or under `$XDG_CONFIG_HOME` when that is set to an absolute path) — add `-p`/`--project` for the project-local `.vscode/mcp.json` shown above instead. It also adds a delimited routing block, preserving unrelated JSON and user text: to `~/.copilot/instructions/token-goat.instructions.md` for the user install (a personal instructions file VS Code applies in every workspace, so the folder you run it from is left alone), or to `.github/copilot-instructions.md` with `-p`. It fails clearly on malformed JSON, and refuses to install into one scope if the other scope already has a token-goat-managed entry (registering it twice would duplicate its tool schemas in that workspace). It also installs agent hooks in `~/.copilot/hooks/` (or `.github/hooks/` with `-p`), the folder VS Code's Copilot agent reads hooks from, so token-goat sees the agent's built-in reads, edits, and terminal commands. `token-goat uninstall --vscode` (add `-p`/`--project` for the project scope) removes only token-goat's server entry, guidance block, and hooks, and keeps the hooks if `--copilot` still uses them.
404
417
 
405
418
  **Visual Studio** (2022 17.14 or later, or 2026): `token-goat install --visualstudio` adds the same `servers` entry to `%USERPROFILE%\.mcp.json` and a routing block to `%USERPROFILE%\copilot-instructions.md` (Visual Studio 2026 reads that file). With `-p`/`--project` it writes `.mcp.json` and `.github/copilot-instructions.md` in the solution folder instead. Visual Studio has no agent hooks, so token-goat works there through its MCP tools and instructions only: no read dedup, hints, image shrink, or output folding. Two switches in Visual Studio turn it on: the Tools > Options checkbox for custom instructions, and the token-goat tools in the chat Tools picker, since new MCP tools start disabled. `token-goat uninstall --visualstudio` (add `-p` for the project) removes only token-goat's entry and block. See [Visual Studio users](docs/install.md#visual-studio-users).
406
419
 
@@ -432,7 +445,7 @@ If the extension is running somewhere that contribution did not take effect, it
432
445
 
433
446
  ## What gets installed?
434
447
 
435
- `token-goat install` writes hook entries and a delimited guidance block into your agent's config directory, plus an index, cache and models under a data directory. Nothing else, anywhere. Every entry is reversed by `token-goat uninstall`, integrations added with `--codex`, `--copilot` or a sibling flag are named rather than silently removed, and `token-goat doctor` lists what is present right now.
448
+ `token-goat install` writes hook entries and a delimited guidance block into your agent's config directory, plus an index, cache and models under a data directory. Nothing else, anywhere. Every entry is reversed by `token-goat uninstall`, integrations added with `--codex`, `--copilot` or a sibling flag are named rather than silently removed, and `token-goat doctor` lists what is present right now. When `CODEX_HOME` is set, `install --codex`, `uninstall` and `doctor --repair` use that directory for Codex's config, `AGENTS.md` and hooks instead of `~/.codex`.
436
449
 
437
450
  **What the index actually holds, in plain terms.** The point of a surgical read is returning a function body without the file around it, which means the database stores those bodies. `symbols.body` holds the source text of every indexed symbol, `symbols.docstring` its doc comment, `refs.context` the line around each reference, and `chunks.text` the passages that semantic search embeds. There is also a full-text index over the bodies and docstrings. So the database is not a list of names and line numbers: it is a substantial copy of your source, sitting in a plain unencrypted SQLite file outside the repository.
438
451
 
@@ -446,7 +459,7 @@ Hooks fire automatically on every tool call once installed — nothing to start
446
459
 
447
460
  Hooks also answer faster after the first call. That call starts a small resident process, the hook server, and later hook calls go to it instead of starting Node and loading token-goat each time. In measurements on Windows that took a hook call from 110 to 135 ms down to about 70 ms in Claude Code, Codex, Copilot CLI and VS Code chat, and a read command such as `token-goat section` from 122 ms to 60 ms when its output goes to the agent rather than a terminal. You never start it yourself. It stops after 30 minutes idle, after an upgrade, when you turn it off, and when `uninstall --purge` runs. `token-goat hook-server status` shows what is running and `token-goat hook-server stop` stops it. To turn it off, set `server = false` under `[hooks]` in your global config, or `TOKEN_GOAT_HOOK_SERVER=0`. A project's `.token-goat.toml` cannot change this setting, because one server answers every project.
448
461
 
449
- To move to a newer release, `token-goat upgrade` installs it from npm and then re-runs `token-goat install`, so your hooks and integration manifests point at the build that just landed. `token-goat upgrade --check` reports whether one is available without installing anything, and `--json` gives the same answer for a script. The check asks `npm view` first, so a registry set up in npm (Artifactory, Nexus), its auth token and its CA certificate are used; it falls back to a direct request to the registry in `npm_config_registry`, or to npmjs.org. A successful result is cached for 24 hours, though `upgrade --check` itself always asks again. When a newer version is out, `token-goat doctor` mentions it at the end of its report, `token-goat stats` lists it in its insights, and the context added at session start says so. None of this happens when `network.offline` is set, and `token-goat doctor --repair` installs the update as its last step.
462
+ To move to a newer release, `token-goat upgrade` installs it from npm and then re-runs `token-goat install`, so your hooks and integration manifests point at the build that just landed. `token-goat upgrade --check` reports whether one is available without installing anything, and `--json` gives the same answer for a script. When it cannot learn the latest version (`network.offline` is set, or the registry did not answer), it exits 1, with the reason on stderr or, under `--json`, in the `error` field. The check asks `npm view` first, so a registry set up in npm (Artifactory, Nexus), its auth token and its CA certificate are used; it falls back to a direct request to the registry in `npm_config_registry`, or to npmjs.org. A successful result is cached for 24 hours, though `upgrade --check` itself always asks again. When a newer version is out, `token-goat doctor` mentions it at the end of its report, `token-goat stats` lists it in its insights, and the context added at session start says so. None of this happens when `network.offline` is set, and `token-goat doctor --repair` installs the update as its last step.
450
463
 
451
464
  ## Verify
452
465
 
@@ -467,6 +480,8 @@ token-goat install
467
480
 
468
481
  `install` is idempotent: it adds missing entries, rewrites outdated token-goat entries in place, and leaves everything else in `settings.json` alone. Re-running it is always safe. Restart any running session afterwards so it picks up the new entries.
469
482
 
483
+ `token-goat doctor --probe HOST` runs a host headless and reports whether its hooks reached the model. It exits 1 when a hook is at fault, 2 when the harness failed before its model replied (not logged in, access denied), and 3 for `vscode`, `visualstudio` and `jetbrains`, which run inside an editor and have no headless mode.
484
+
470
485
  **AI agents:** after `token-goat install`, run `token-goat doctor` and check the `Claude Code hook events` line before marking the install complete.
471
486
 
472
487
  ## Image support
@@ -483,6 +498,8 @@ The pipeline is written in TypeScript and decodes PNG, JPEG, GIF, and BMP itself
483
498
 
484
499
  The one thing the decoders will do is decline. A file whose header asks for more than 256 MB of pixel buffer, or that is in a variant token-goat cannot read exactly, is refused rather than guessed at, and the untouched original goes to the model. An untouched original is the right picture, so the only cost of a refusal is that the read is not shrunk.
485
500
 
501
+ On Claude Code the shrunk copy reaches the model by pointing the Read at a temporary file holding it. The hook approves that rewritten read, and Claude Code asks before reading a file outside the session's working directory, so token-goat shrinks only images inside it; one outside is read unshrunk, after Claude Code's own prompt.
502
+
486
503
  When `tesseract.js` is installed, text-heavy images can also have their text extracted via OCR (`token-goat image-text <file>`) to replace raw pixel data with plain text. English (`eng`) is always enabled by default, and users can combine one or more additional languages (`token-goat image-text <file> --lang "fra,spa"`). Language preferences persist across version updates when saved globally (`token-goat config set image_shrink.ocr_lang "fra,spa"`), per-project in `.token-goat.toml` (`[image_shrink] ocr_lang = "fra+spa"`), or via `TOKEN_GOAT_OCR_LANG`. 16 languages are supported (`eng`, `fra`, `spa`, `deu`, `ita`, `por`, `nld`, `pol`, `rus`, `tur`, `swe`, `ara`, `chi_sim`, `chi_tra`, `jpn`, `kor`), each cryptographically verified against pinned package checksums to protect against supply-chain tampering.
487
504
 
488
505
  ## Stats display
package/SECURITY.md CHANGED
@@ -39,9 +39,17 @@ The following are not treated as security issues unless paired with a working pr
39
39
  - Social-engineering attacks that require tricking the user into running malicious commands
40
40
  - Issues that require an already-compromised local user account
41
41
 
42
+ ## Check commands in the session record
43
+
44
+ When a test, lint, typecheck or build command runs, the session record keeps the runner, its subcommand, a short list of known switches and any path-shaped word, and replaces every other word with an ellipsis. A secret passed as a bare positional argument that looks like a path, such as `hunter2.txt`, would be kept, because a file name is what identifies the check; a secret that follows a flag, uses a `user:pass@host` form or is shaped like a JWT is dropped.
45
+
46
+ ## Remembered failure fixes
47
+
48
+ The failed commands token-goat learns fixes for are kept only as keyed digests, never as text, and the key sits in the same folder as the saved fixes, so a leak of that whole folder exposes both.
49
+
42
50
  ## Dependency advisories
43
51
 
44
- `npm audit` reports Token-Goat clean for anyone who installs it, and that has been true of a default install since the embedding library that carried every finding was removed. This repository's own tree is clean as well, development dependencies included: `adm-zip` and `hono` were resolved to patched versions once they had cleared Dependabot's seven-day cooldown, not bumped the day a fix was published -- see [`.github/dependabot.yml`](.github/dependabot.yml) for why a plain `npm update` isn't the fix. All three numbers below are reproducible with the commands shown. The one place an advisory can still reach is the optional native runtime, which a default install does not carry, and it is described further down rather than folded into these rows.
52
+ `npm audit` reports Token-Goat clean for anyone who installs it, and that has been true of a default install since the embedding library that carried every finding was removed. This repository's own tree is clean as well, development dependencies included: `adm-zip`, `hono`, `proxy-addr` and `source-map-js` were resolved to patched versions through the lock-file refresh, not bumped by hand the day a fix was published -- see [`.github/dependabot.yml`](.github/dependabot.yml) for why a plain `npm update` isn't the fix. All three numbers below are reproducible with the commands shown. The one place an advisory can still reach is the optional native runtime, which a default install does not carry, and it is described further down rather than folded into these rows.
45
53
 
46
54
  | What you scan | Command | Result |
47
55
  | --- | --- | --- |
@@ -49,7 +57,7 @@ The following are not treated as security issues unless paired with a working pr
49
57
  | an install without optional packages | `npm install --omit=optional token-goat` then `npm audit --omit=dev --omit=optional` | clean, 2 packages |
50
58
  | a default install | `npm install token-goat` then `npm audit --omit=dev` | clean, 62 packages |
51
59
 
52
- The repository carries an `overrides` block in [`package.json`](package.json) that pins six packages to patched versions, and **npm applies `overrides` only in the root project**, so those pins do not travel to anyone who installs Token-Goat as a dependency. That distinction used to matter a great deal, because a clean repository scan was standing in for an install that was not clean. It decides one thing now, and the section on the embedding runtime below says which. Otherwise the packages those pins were protecting against are not in a consumer's tree at all, whether or not the pins travel; the block stays because the development tree still resolves them.
60
+ The repository carries an `overrides` block in [`package.json`](package.json) that pins nine packages to patched versions, and **npm applies `overrides` only in the root project**, so those pins do not travel to anyone who installs Token-Goat as a dependency. That distinction used to matter a great deal, because a clean repository scan was standing in for an install that was not clean. It decides one thing now, and the section on the embedding runtime below says which. Otherwise the packages those pins were protecting against are not in a consumer's tree at all, whether or not the pins travel; the block stays because the development tree still resolves them.
53
61
 
54
62
  There is no table of reaching advisories here any more, and the last entry in it is worth recording rather than deleting. `@xenova/transformers` was an optional dependency, which in npm means everyone got it unless they asked not to, and it carried [`protobufjs`](https://github.com/advisories/GHSA-xq3m-2v4x-88gg) through `onnx-proto` and `onnxruntime-web` -- one critical advisory and four more -- plus its own nested, older `sharp` carrying [four inherited libvips CVEs](https://github.com/advisories/GHSA-f88m-g3jw-g9cj). None of the six was fixable from here: every one sits in a transitive package with no patched version, and the pins that would have solved it do not travel to consumers.
55
63
 
@@ -130,7 +130,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
130
130
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
131
131
  ```
132
132
 
133
- ## csv-parse 7.0.2
133
+ ## csv-parse 7.0.3
134
134
 
135
135
  License: MIT
136
136
 
Binary file
Binary file
@@ -2,10 +2,10 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  redactSecrets
5
- } from "./token-goat-chunk-4FVIAHSI.mjs";
5
+ } from "./token-goat-chunk-T5P5ACNQ.mjs";
6
6
  import {
7
7
  loadConfig
8
- } from "./token-goat-chunk-7IN2MKDS.mjs";
8
+ } from "./token-goat-chunk-7EZTZOMC.mjs";
9
9
  import {
10
10
  createLazyModuleLoader
11
11
  } from "./token-goat-chunk-AH6QILZM.mjs";
@@ -14,8 +14,9 @@ import {
14
14
  MAX_DOCUMENT_WORK_MILLIS
15
15
  } from "./token-goat-chunk-Y4AFKTHK.mjs";
16
16
  import {
17
- compileGuardedRegex
18
- } from "./token-goat-chunk-GOLEOXZP.mjs";
17
+ compileGuardedRegex,
18
+ countNoun
19
+ } from "./token-goat-chunk-HU4TORC5.mjs";
19
20
  import {
20
21
  init_define_import_meta_env
21
22
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -145,7 +146,14 @@ function parsePageRange(spec, pageCount) {
145
146
  const end = m[2] ? parseInt(m[2], 10) : start;
146
147
  if (start < 1 || end < start) throw new Error(`invalid --pages spec: ${spec}`);
147
148
  if (start > pageCount) throw new Error(`invalid --pages spec: ${spec} (page ${start} is past end of document with ${pageCount} pages)`);
148
- return { start, end: Math.min(end, pageCount) };
149
+ return end > pageCount ? { start, end: pageCount, requestedEnd: end } : { start, end };
150
+ }
151
+ function pagesPastEndNote(range, pageCount, stoppedAfter) {
152
+ if (range?.requestedEnd === void 0) return void 0;
153
+ const pages = (last) => range.start === last ? `page ${range.start}` : `pages ${range.start}-${last}`;
154
+ const cut = `--pages ${range.start}-${range.requestedEnd} runs past the document's ${countNoun(pageCount, "page")}`;
155
+ if (stoppedAfter !== void 0) return `${cut}; searched ${pages(stoppedAfter)} before --max-matches stopped the scan.`;
156
+ return `${cut}; showing ${pages(range.end)}.`;
149
157
  }
150
158
  var Y_EPSILON = 2;
151
159
  function reconstructLayout(items, deadline) {
@@ -292,7 +300,8 @@ async function extractPdfText(data, pagesSpec, layout = false) {
292
300
  }
293
301
  const text = pages.join("\n\n");
294
302
  assertPdfTextWithinBounds(text);
295
- return { text, pageCount: doc.numPages, pagesExtracted: end - start + 1 };
303
+ const pagesNote = pagesPastEndNote(range, doc.numPages);
304
+ return { text, pageCount: doc.numPages, pagesExtracted: end - start + 1, ...pagesNote !== void 0 ? { pagesNote } : {} };
296
305
  });
297
306
  }
298
307
  async function locatePdfPages(data, pattern, opts) {
@@ -319,7 +328,8 @@ async function locatePdfPages(data, pattern, opts) {
319
328
  matches.push({ page: i, snippet: locateSnippet(pageText, m.index, m[0].length, context) });
320
329
  }
321
330
  const truncated = matches.length >= maxMatches && i <= end;
322
- return { matches, truncated };
331
+ const pagesNote = pagesPastEndNote(range, doc.numPages, truncated ? i - 1 : void 0);
332
+ return { matches, truncated, ...pagesNote !== void 0 ? { pagesNote } : {} };
323
333
  });
324
334
  }
325
335
  function joinTextItems(items, eol) {
@@ -0,0 +1,203 @@
1
+ import { createRequire as __cjsRequire } from 'node:module';
2
+ const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ BRIDGE_CAPABILITY_MATRIX
5
+ } from "./token-goat-chunk-RSLPOBKQ.mjs";
6
+ import {
7
+ PROBE_EVENT_TAGS,
8
+ PROBE_NONCE_ENV,
9
+ probeDir
10
+ } from "./token-goat-chunk-HLPW63A7.mjs";
11
+ import "./token-goat-chunk-KKUFXAPD.mjs";
12
+ import {
13
+ HOOK_PROBE_ENV
14
+ } from "./token-goat-chunk-U4WHMG6Y.mjs";
15
+ import "./token-goat-chunk-GVB2MUST.mjs";
16
+ import "./token-goat-chunk-XB5KKOCG.mjs";
17
+ import "./token-goat-chunk-OUKDTVNU.mjs";
18
+ import "./token-goat-chunk-XX6O25RN.mjs";
19
+ import "./token-goat-chunk-QYHO5CPH.mjs";
20
+ import "./token-goat-chunk-YZAAZU2S.mjs";
21
+ import "./token-goat-chunk-HU4TORC5.mjs";
22
+ import "./token-goat-chunk-GGGG7RWB.mjs";
23
+ import {
24
+ resolveOnPath,
25
+ spawnResolvedSync
26
+ } from "./token-goat-chunk-AM5V6GSX.mjs";
27
+ import {
28
+ displaySafeText
29
+ } from "./token-goat-chunk-FNFBKLRD.mjs";
30
+ import "./token-goat-chunk-CO3UYX6G.mjs";
31
+ import {
32
+ init_define_import_meta_env
33
+ } from "./token-goat-chunk-A37V4PBF.mjs";
34
+
35
+ // src/doctor_probe.ts
36
+ init_define_import_meta_env();
37
+ import { randomBytes } from "node:crypto";
38
+ import { readdirSync, rmSync } from "node:fs";
39
+ import path from "node:path";
40
+ var PROBE_PROMPT = "Reply with every line of your context that begins with token-goat probe marker, copied exactly, one per line. If there are none, reply NONE.";
41
+ var PROBE_COMMANDS = {
42
+ claudecode: { bin: "claude", args: ["-p", PROBE_PROMPT] },
43
+ codex: { bin: "codex", args: ["exec", PROBE_PROMPT] },
44
+ copilot_cli: { bin: "copilot", args: ["-p", PROBE_PROMPT] }
45
+ };
46
+ var IDE_HOSTS_WITHOUT_PROBE = /* @__PURE__ */ new Set(["vscode", "visualstudio", "jetbrains"]);
47
+ var NO_HEADLESS_PROBE_EXIT_CODE = 3;
48
+ function noHeadlessProbeMessage(host) {
49
+ return `${host} has no headless mode, so doctor --probe cannot send it a prompt; run token-goat doctor to check its install and hooks`;
50
+ }
51
+ function hideResolvedPath(message, resolved) {
52
+ if (resolved === "") return message;
53
+ if (process.platform !== "win32") return message.split(resolved).join("<harness>");
54
+ const fold = (text) => text.replaceAll("\\", "/").toLowerCase();
55
+ const haystack = fold(message);
56
+ const needle = fold(resolved);
57
+ if (haystack.length !== message.length || needle.length !== resolved.length) return message.split(resolved).join("<harness>");
58
+ let out = "";
59
+ let from = 0;
60
+ for (let at = haystack.indexOf(needle); at !== -1; at = haystack.indexOf(needle, Math.max(from, at + 1))) {
61
+ if (/[a-z0-9._-]/.test(haystack[at + needle.length] ?? "")) continue;
62
+ out += message.slice(from, at) + "<harness>";
63
+ from = at + needle.length;
64
+ }
65
+ return out + message.slice(from);
66
+ }
67
+ function isProbeHarness(name) {
68
+ return Object.prototype.hasOwnProperty.call(PROBE_COMMANDS, name);
69
+ }
70
+ var MISLEADING_ENV = ["TOKEN_GOAT_HARNESS_OVERRIDE", "CLAUDE_CODE_SESSION_ID", "CLAUDE_CODE_VERSION", "CLAUDE_CODE_ENTRYPOINT", "CLAUDECODE", "CODEX_SESSION_ID", "CODEX_SESSION"];
71
+ var PROBE_TIMEOUT_MS = 24e4;
72
+ function modelReplied(stdout, nonce) {
73
+ return stdout.includes(nonce) || /\bNONE\b/.test(stdout);
74
+ }
75
+ function classifyProbeEvent(opts) {
76
+ if (!opts.wired) return "not_wired";
77
+ if (!opts.modelReplied) return "inconclusive";
78
+ if (opts.stdout.includes(`${opts.nonce}-${opts.tag}`)) return "reached";
79
+ return opts.receipts.has(opts.tag) ? "fired_not_delivered" : "not_fired";
80
+ }
81
+ function probeEnv(nonce, env = process.env) {
82
+ const child = { ...env };
83
+ for (const key of MISLEADING_ENV) delete child[key];
84
+ if (child["TERM_PROGRAM"] === "claude-code") delete child["TERM_PROGRAM"];
85
+ child[PROBE_NONCE_ENV] = nonce;
86
+ child[HOOK_PROBE_ENV] = "1";
87
+ return child;
88
+ }
89
+ function receiptTags(nonce) {
90
+ const tags = /* @__PURE__ */ new Set();
91
+ try {
92
+ for (const name of readdirSync(probeDir())) {
93
+ if (name.startsWith(`${nonce}.`)) tags.add(name.slice(nonce.length + 1));
94
+ }
95
+ } catch {
96
+ }
97
+ return tags;
98
+ }
99
+ function removeReceipts(nonce) {
100
+ for (const tag of receiptTags(nonce)) rmSync(path.join(probeDir(), `${nonce}.${tag}`), { force: true });
101
+ }
102
+ function tail(text) {
103
+ return text.trim().split(/\r?\n/).slice(-5).join("\n");
104
+ }
105
+ var START_ERROR_CODES = /* @__PURE__ */ new Set(["ENOENT", "EACCES", "EPERM", "EINVAL", "EFTYPE", "ENOEXEC", "ENOTDIR"]);
106
+ function runProbe(harness, opts = {}) {
107
+ const spec = PROBE_COMMANDS[harness];
108
+ if (spec === void 0) throw new Error(`no headless probe for ${harness}; supported: ${Object.keys(PROBE_COMMANDS).join(", ")}`);
109
+ const command = `${spec.bin} ${spec.args[0] ?? ""} "<probe prompt>"`;
110
+ const implemented = BRIDGE_CAPABILITY_MATRIX.find((r) => r.harness === harness)?.implemented ?? /* @__PURE__ */ new Set();
111
+ const probed = Object.keys(PROBE_EVENT_TAGS);
112
+ const resolved = (opts.resolve ?? resolveOnPath)(spec.bin);
113
+ if (resolved === null) {
114
+ return { harness, command, status: "not_installed", exitCode: null, events: probed.map((event) => ({ event, result: implemented.has(event) ? "not_fired" : "not_wired" })) };
115
+ }
116
+ const nonce = randomBytes(10).toString("hex");
117
+ try {
118
+ const run = spawnResolvedSync(resolved, spec.args, { cwd: opts.cwd ?? process.cwd(), env: probeEnv(nonce), encoding: "utf8", timeout: opts.timeoutMs ?? PROBE_TIMEOUT_MS, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
119
+ const stdout = run.stdout ?? "";
120
+ const receipts = receiptTags(nonce);
121
+ const timedOut = run.error?.code === "ETIMEDOUT";
122
+ const status = timedOut ? "timeout" : run.status === 0 ? "ok" : "failed";
123
+ const output = `${run.stderr ?? ""}
124
+ ${stdout}`.trim();
125
+ const detail = tail(output || hideResolvedPath(run.error?.message ?? "", resolved));
126
+ const startError = run.error?.code;
127
+ const spawnError = startError !== void 0 && START_ERROR_CODES.has(startError) ? startError : void 0;
128
+ if (spawnError !== void 0) return { harness, command, status: "spawn_failed", exitCode: null, events: probed.map((event) => ({ event, result: implemented.has(event) ? "not_fired" : "not_wired" })), detail: spawnError };
129
+ const knownCause = probeCause(output);
130
+ const replied = status === "ok" || modelReplied(stdout, nonce) || status === "timeout" || knownCause === null;
131
+ const events = probed.map((event) => ({ event, result: classifyProbeEvent({ wired: implemented.has(event), nonce, tag: PROBE_EVENT_TAGS[event] ?? "", stdout, receipts, modelReplied: replied }) }));
132
+ const report = { harness, command, status, exitCode: run.status, events };
133
+ return status === "ok" ? report : { ...report, detail, knownCause };
134
+ } finally {
135
+ removeReceipts(nonce);
136
+ }
137
+ }
138
+ var RESULT_TEXT = {
139
+ reached: "reached the model",
140
+ fired_not_delivered: "hook ran, but the model never saw its output",
141
+ not_fired: "hook did not run",
142
+ not_wired: "not wired for this harness",
143
+ inconclusive: "not judged: the harness never produced a model reply"
144
+ };
145
+ function probeVerdict(report) {
146
+ if (report.events.some((e) => e.result === "inconclusive")) return "inconclusive";
147
+ if (report.status !== "ok") return "fail";
148
+ return report.events.every((e) => e.result === "reached" || e.result === "not_wired") ? "pass" : "fail";
149
+ }
150
+ function probePassed(report) {
151
+ return probeVerdict(report) === "pass";
152
+ }
153
+ var PROBE_CAUSES = [
154
+ // codex_untrusted_directory.stderr
155
+ { re: /^Not inside a trusted directory and --skip-git-repo-check was not specified\./m, cause: "the harness refused to run in an untrusted directory", remedy: "run the probe from a git repository or a directory the harness trusts" },
156
+ // claude_unknown_option.stderr and codex_unexpected_argument.stderr
157
+ { re: /^error: (?:unknown option|unexpected argument) '/m, cause: "the harness rejected a command-line flag", remedy: "its CLI changed; update token-goat, or report the harness version" },
158
+ // copilot_access_denied.stderr
159
+ { re: /^Error: Access denied by policy settings/m, cause: "the account or its organization policy denied the harness", remedy: "check the Copilot access and policy settings of the signed-in account" },
160
+ // claude_not_logged_in.stdout and copilot_no_authentication.stderr
161
+ { re: /^(?:Not logged in · Please run \/login|Error: No authentication information found\.)/m, cause: "the harness is not logged in", remedy: "sign in to the harness, then run the probe again" }
162
+ ];
163
+ function probeCause(detail) {
164
+ const hit = PROBE_CAUSES.find((c) => c.re.test(detail));
165
+ return hit === void 0 ? null : { cause: hit.cause, remedy: hit.remedy };
166
+ }
167
+ function formatProbeReport(report) {
168
+ const lines = [`Probe: ${report.command} (one real prompt on your account)`];
169
+ const inconclusive = probeVerdict(report) === "inconclusive";
170
+ const mark = inconclusive ? "[WARN]" : "[FAIL]";
171
+ if (report.status === "not_installed") lines.push(` [FAIL] ${PROBE_COMMANDS[report.harness]?.bin ?? report.harness} is not on PATH`);
172
+ else if (report.status === "timeout") lines.push(` ${mark} the harness did not finish in time`);
173
+ else if (report.status === "spawn_failed") lines.push(displaySafeText(` [FAIL] the harness could not be started: ${report.detail ?? "unknown error"}`));
174
+ else if (report.status === "failed") lines.push(` ${mark} the harness exited with code ${report.exitCode ?? "unknown"}`);
175
+ if (inconclusive) {
176
+ const known = report.knownCause !== void 0 ? report.knownCause : probeCause(report.detail ?? "");
177
+ lines.push(displaySafeText(known === null ? " Probe inconclusive: the harness failed before the model replied; see its output below" : ` Probe inconclusive: ${known.cause}. ${known.remedy}`));
178
+ }
179
+ if (report.status !== "not_installed" && report.status !== "spawn_failed") {
180
+ for (const e of report.events) {
181
+ const eventMark = e.result === "reached" ? "[OK] " : e.result === "not_wired" ? "[--] " : e.result === "inconclusive" ? "[??] " : "[FAIL]";
182
+ lines.push(` ${eventMark} ${e.event.padEnd(19)} ${RESULT_TEXT[e.result]}`);
183
+ }
184
+ }
185
+ if (report.status !== "spawn_failed" && report.detail !== void 0 && report.detail !== "") lines.push(...displaySafeText(report.detail).split("\n").map((l) => ` ${l}`));
186
+ return lines.join("\n");
187
+ }
188
+ export {
189
+ IDE_HOSTS_WITHOUT_PROBE,
190
+ NO_HEADLESS_PROBE_EXIT_CODE,
191
+ PROBE_COMMANDS,
192
+ PROBE_PROMPT,
193
+ classifyProbeEvent,
194
+ formatProbeReport,
195
+ hideResolvedPath,
196
+ isProbeHarness,
197
+ noHeadlessProbeMessage,
198
+ probeCause,
199
+ probeEnv,
200
+ probePassed,
201
+ probeVerdict,
202
+ runProbe
203
+ };
@@ -1,12 +1,16 @@
1
1
  import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ charFinder,
5
+ findElements
6
+ } from "./token-goat-chunk-E45INMY3.mjs";
3
7
  import {
4
8
  MAX_ZIP_INPUT_BYTES,
5
9
  MAX_ZIP_OUTPUT_BYTES,
6
10
  ZipInputTooLargeError,
7
11
  ZipOutputTooLargeError,
8
12
  unzipBounded
9
- } from "./token-goat-chunk-XTQAOTSO.mjs";
13
+ } from "./token-goat-chunk-EDBDRVH5.mjs";
10
14
  import {
11
15
  createLazyModuleLoader
12
16
  } from "./token-goat-chunk-AH6QILZM.mjs";
@@ -16,7 +20,10 @@ import {
16
20
  } from "./token-goat-chunk-Y4AFKTHK.mjs";
17
21
  import {
18
22
  pushAll
19
- } from "./token-goat-chunk-GOLEOXZP.mjs";
23
+ } from "./token-goat-chunk-HU4TORC5.mjs";
24
+ import {
25
+ echoedValue
26
+ } from "./token-goat-chunk-GGGG7RWB.mjs";
20
27
  import {
21
28
  init_define_import_meta_env
22
29
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -268,8 +275,8 @@ var loadFflate = createLazyModuleLoader(
268
275
  );
269
276
  function accessFailureMessage(err, filePath) {
270
277
  const code = err?.code;
271
- if (code === "ENOENT") return `File not found: ${filePath}`;
272
- return `could not read ${filePath} (${code ?? "unknown error"})`;
278
+ if (code === "ENOENT") return `File not found: ${echoedValue(filePath)}`;
279
+ return `could not read ${echoedValue(filePath)} (${code ?? "unknown error"})`;
273
280
  }
274
281
  async function readOoxmlZip(filePath, kind) {
275
282
  const fflate = await loadFflate();
@@ -332,25 +339,47 @@ function pushTextValue(runs, val) {
332
339
  runs.push(String(val["#text"]));
333
340
  }
334
341
  }
335
- var MATH_PARA_RE = /<m:oMathPara(?:\s[^>]*)?>([\s\S]*?)<\/m:oMathPara>/g;
336
- var MATH_RE = /<m:oMath(?:\s[^>]*)?>([\s\S]*?)<\/m:oMath>/g;
337
- var MATH_TEXT_RE = /<m:t(?:\s[^>]*)?>([^<]*)<\/m:t>/g;
342
+ var MATH_PARA_OPEN = /<m:oMathPara(?=[\s>])/g;
343
+ var MATH_PARA_CLOSE = /<\/m:oMathPara>/g;
344
+ var MATH_OPEN = /<m:oMath(?=[\s>])/g;
345
+ var MATH_CLOSE = /<\/m:oMath>/g;
338
346
  function mathRunText(fragment) {
339
347
  let out = "";
340
- for (const m of fragment.matchAll(MATH_TEXT_RE)) out += m[1];
348
+ const nextGt = charFinder(fragment, ">");
349
+ const nextLt = charFinder(fragment, "<");
350
+ const open = /<m:t(?=[\s>])/g;
351
+ let m;
352
+ while ((m = open.exec(fragment)) !== null) {
353
+ const gt = nextGt(m.index + m[0].length);
354
+ if (gt < 0) break;
355
+ const lt = nextLt(gt + 1);
356
+ if (lt < 0) break;
357
+ if (!fragment.startsWith("</m:t>", lt)) continue;
358
+ out += fragment.slice(gt + 1, lt);
359
+ open.lastIndex = lt + 6;
360
+ }
341
361
  return out;
342
362
  }
363
+ function replaceElements(xml, open, close, fn) {
364
+ let out = "";
365
+ let pos = 0;
366
+ for (const el of findElements(xml, open, close)) {
367
+ out += xml.slice(pos, el.start) + fn(el.body);
368
+ pos = el.end;
369
+ }
370
+ return out + xml.slice(pos);
371
+ }
343
372
  function inlineMathRuns(xml, openRun, closeRun) {
344
373
  const asRun = (text) => text.length > 0 ? `${openRun}${text}${closeRun}` : "";
345
- const withParas = xml.replace(MATH_PARA_RE, (_whole, inner) => {
374
+ const withParas = replaceElements(xml, MATH_PARA_OPEN, MATH_PARA_CLOSE, (inner) => {
346
375
  const lines = [];
347
- for (const m of inner.matchAll(MATH_RE)) {
348
- const text = mathRunText(m[1]);
376
+ for (const el of findElements(inner, MATH_OPEN, MATH_CLOSE)) {
377
+ const text = mathRunText(el.body);
349
378
  if (text.length > 0) lines.push(text);
350
379
  }
351
380
  return asRun(lines.length > 0 ? lines.join(" ") : mathRunText(inner));
352
381
  });
353
- return withParas.replace(MATH_RE, (_whole, inner) => asRun(mathRunText(inner)));
382
+ return replaceElements(withParas, MATH_OPEN, MATH_CLOSE, (inner) => asRun(mathRunText(inner)));
354
383
  }
355
384
  function collectTextRuns(node, tag, skipInside = []) {
356
385
  const runs = [];
@@ -5,10 +5,10 @@ import {
5
5
  atomicWriteText,
6
6
  ensureDirSync,
7
7
  withFileLock
8
- } from "./token-goat-chunk-GOLEOXZP.mjs";
8
+ } from "./token-goat-chunk-HU4TORC5.mjs";
9
9
  import {
10
10
  dataDir
11
- } from "./token-goat-chunk-VES7EL7O.mjs";
11
+ } from "./token-goat-chunk-CO3UYX6G.mjs";
12
12
  import {
13
13
  init_define_import_meta_env
14
14
  } from "./token-goat-chunk-A37V4PBF.mjs";