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.
- package/README.md +24 -7
- package/SECURITY.md +10 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/native/linux-arm64/tg-hook +0 -0
- package/dist/native/linux-x64/tg-hook +0 -0
- package/dist/native/win32-arm64/tg-hook.exe +0 -0
- package/dist/native/win32-x64/tg-hook.exe +0 -0
- package/dist/{token-goat-chunk-QSBRTGM2.mjs → token-goat-chunk-25TRAWET.mjs} +17 -7
- package/dist/token-goat-chunk-2KXO6V2B.mjs +203 -0
- package/dist/{token-goat-chunk-IVKIZWAB.mjs → token-goat-chunk-2M7E4PF7.mjs} +41 -12
- package/dist/{token-goat-chunk-SMVRN2HU.mjs → token-goat-chunk-2UPZGKKY.mjs} +2 -2
- package/dist/token-goat-chunk-2WLPWKHV.mjs +135 -0
- package/dist/token-goat-chunk-2XBMMDBD.mjs +126 -0
- package/dist/token-goat-chunk-32ZOY4N5.mjs +23 -0
- package/dist/token-goat-chunk-3EZRNKTK.mjs +188 -0
- package/dist/token-goat-chunk-4ADZHI7J.mjs +63 -0
- package/dist/token-goat-chunk-4GBVPH3D.mjs +242 -0
- package/dist/{token-goat-chunk-KWPNJBDH.mjs → token-goat-chunk-4GILLLWY.mjs} +7 -4
- package/dist/{token-goat-chunk-67TQJ76B.mjs → token-goat-chunk-4NAPX7BA.mjs} +36 -3
- package/dist/{token-goat-chunk-PU7V3FD6.mjs → token-goat-chunk-4SC7UJYV.mjs} +3354 -2553
- package/dist/token-goat-chunk-4SMRO4TO.mjs +366 -0
- package/dist/{token-goat-chunk-AFLYLHDH.mjs → token-goat-chunk-4ZBFWLXF.mjs} +283 -427
- package/dist/{token-goat-chunk-JDWCRD6M.mjs → token-goat-chunk-57BIAXJL.mjs} +73 -40
- package/dist/token-goat-chunk-5AC66HNP.mjs +115 -0
- package/dist/token-goat-chunk-5IMLKSOP.mjs +145 -0
- package/dist/token-goat-chunk-646E6U44.mjs +398 -0
- package/dist/token-goat-chunk-65N4RHP4.mjs +33 -0
- package/dist/{token-goat-chunk-JN4XMLSD.mjs → token-goat-chunk-6DG6HWP3.mjs} +8 -2
- package/dist/token-goat-chunk-6ENQSNRZ.mjs +573 -0
- package/dist/token-goat-chunk-6ZY4GNF2.mjs +91 -0
- package/dist/{token-goat-chunk-VPBBTSJR.mjs → token-goat-chunk-76FGDLRO.mjs} +262 -255
- package/dist/{token-goat-chunk-7IN2MKDS.mjs → token-goat-chunk-7EZTZOMC.mjs} +121 -289
- package/dist/{token-goat-chunk-OJBNYTJP.mjs → token-goat-chunk-7MAOA6X4.mjs} +640 -80
- package/dist/token-goat-chunk-7R2ZVVAX.mjs +218 -0
- package/dist/{token-goat-chunk-PD5VKH46.mjs → token-goat-chunk-AUNGYYQX.mjs} +2 -2
- package/dist/{token-goat-chunk-HG45AF2B.mjs → token-goat-chunk-AUQYRMMH.mjs} +794 -37
- package/dist/token-goat-chunk-B3PEKBYG.mjs +128 -0
- package/dist/{token-goat-chunk-A3CK6N5Z.mjs → token-goat-chunk-BA4HI6BW.mjs} +1309 -110
- package/dist/{token-goat-chunk-JB6QN6VF.mjs → token-goat-chunk-BFCWY73P.mjs} +59 -23
- package/dist/{token-goat-chunk-B45VMIIH.mjs → token-goat-chunk-BWGFUZZE.mjs} +7 -5
- package/dist/{token-goat-chunk-P4QMM4WK.mjs → token-goat-chunk-C75NA7PL.mjs} +50 -23
- package/dist/token-goat-chunk-CBIQ7WTD.mjs +87 -0
- package/dist/token-goat-chunk-CETM74KC.mjs +307 -0
- package/dist/{token-goat-chunk-H2PHHGM5.mjs → token-goat-chunk-CIMQ2BB3.mjs} +57 -12
- package/dist/{token-goat-chunk-N2NMER56.mjs → token-goat-chunk-CJLS3CDL.mjs} +46 -4
- package/dist/{token-goat-chunk-VES7EL7O.mjs → token-goat-chunk-CO3UYX6G.mjs} +1 -1
- package/dist/token-goat-chunk-COPNXRJM.mjs +154 -0
- package/dist/token-goat-chunk-CSJ5L3NL.mjs +479 -0
- package/dist/{token-goat-chunk-4LCCNRTG.mjs → token-goat-chunk-CVCEVUGK.mjs} +8 -6
- package/dist/{token-goat-chunk-B2RZZCSS.mjs → token-goat-chunk-CXHOFN7R.mjs} +37 -20
- package/dist/token-goat-chunk-D735C4QM.mjs +603 -0
- package/dist/{token-goat-chunk-APGZXSPS.mjs → token-goat-chunk-DGS2LVNA.mjs} +47 -30
- package/dist/{token-goat-chunk-54K3EUV3.mjs → token-goat-chunk-DOWRSMWO.mjs} +1 -1
- package/dist/token-goat-chunk-E45INMY3.mjs +71 -0
- package/dist/token-goat-chunk-EC5F37F4.mjs +244 -0
- package/dist/{token-goat-chunk-XTQAOTSO.mjs → token-goat-chunk-EDBDRVH5.mjs} +4 -1
- package/dist/{token-goat-chunk-5BCIJXMO.mjs → token-goat-chunk-EELTBKDM.mjs} +14 -10
- package/dist/{token-goat-chunk-W3QCJS6G.mjs → token-goat-chunk-F4HYL64R.mjs} +8 -6
- package/dist/token-goat-chunk-F6PAUIEK.mjs +624 -0
- package/dist/token-goat-chunk-FDSREWLX.mjs +692 -0
- package/dist/token-goat-chunk-GGGG7RWB.mjs +236 -0
- package/dist/{token-goat-chunk-C63JQV5K.mjs → token-goat-chunk-GL3ETKSC.mjs} +233 -15
- package/dist/token-goat-chunk-GVB2MUST.mjs +1298 -0
- package/dist/token-goat-chunk-GWFLAUMQ.mjs +307 -0
- package/dist/{token-goat-chunk-ZEJVMUQH.mjs → token-goat-chunk-HLPW63A7.mjs} +2 -2
- package/dist/{token-goat-chunk-GOLEOXZP.mjs → token-goat-chunk-HU4TORC5.mjs} +44 -26
- package/dist/{token-goat-chunk-Z57YODA2.mjs → token-goat-chunk-I7VKZZAW.mjs} +2 -2
- package/dist/{token-goat-chunk-NYKZLXMK.mjs → token-goat-chunk-IA6XSPE3.mjs} +6 -6
- package/dist/{token-goat-chunk-E3OQX2XT.mjs → token-goat-chunk-IAGXMHIK.mjs} +81 -42
- package/dist/{token-goat-chunk-HYBFA5X3.mjs → token-goat-chunk-IK2DRMFX.mjs} +24 -68
- package/dist/{token-goat-chunk-COBQGL5S.mjs → token-goat-chunk-INNPTRUX.mjs} +249 -299
- package/dist/{token-goat-chunk-7NADEDKJ.mjs → token-goat-chunk-JJ5BX4F4.mjs} +6 -1
- package/dist/{token-goat-chunk-PEPTHRFF.mjs → token-goat-chunk-JVFSHVFJ.mjs} +6 -6
- package/dist/token-goat-chunk-KGEFFSHD.mjs +50 -0
- package/dist/token-goat-chunk-KQBLRPBN.mjs +433 -0
- package/dist/{token-goat-chunk-Q5VTGRH7.mjs → token-goat-chunk-LCNQ2CNN.mjs} +55 -319
- package/dist/token-goat-chunk-M3LFJDPU.mjs +58 -0
- package/dist/token-goat-chunk-M52KPT3C.mjs +87 -0
- package/dist/token-goat-chunk-MBEG42H7.mjs +150 -0
- package/dist/{token-goat-chunk-EVC4TOLE.mjs → token-goat-chunk-METECDOH.mjs} +3 -12
- package/dist/{token-goat-chunk-T4FK7VOV.mjs → token-goat-chunk-MHX7XCC3.mjs} +2 -1
- package/dist/token-goat-chunk-N5LSOVSU.mjs +137 -0
- package/dist/{token-goat-chunk-LOBGHXPB.mjs → token-goat-chunk-N5MMR2IW.mjs} +1 -1
- package/dist/token-goat-chunk-N7B3JQ2C.mjs +160 -0
- package/dist/{token-goat-chunk-RHXKCZX3.mjs → token-goat-chunk-NISNMD7T.mjs} +2182 -2959
- package/dist/token-goat-chunk-NMD42SFQ.mjs +434 -0
- package/dist/token-goat-chunk-NOSS5VWY.mjs +174 -0
- package/dist/{token-goat-chunk-KQNZDCBD.mjs → token-goat-chunk-NSNSPPBG.mjs} +25 -3
- package/dist/token-goat-chunk-OBOFIHKN.mjs +38 -0
- package/dist/token-goat-chunk-OBRFJ5O5.mjs +76 -0
- package/dist/{token-goat-chunk-JTQQGXZH.mjs → token-goat-chunk-ODBT2USM.mjs} +124 -57
- package/dist/token-goat-chunk-OS5EM5PP.mjs +107 -0
- package/dist/token-goat-chunk-OUKDTVNU.mjs +1178 -0
- package/dist/token-goat-chunk-OXFMP75N.mjs +566 -0
- package/dist/{token-goat-chunk-DZX2YDPT.mjs → token-goat-chunk-OYIYKDDT.mjs} +2 -2
- package/dist/{token-goat-chunk-QCLDAQTS.mjs → token-goat-chunk-P6HQSWQ3.mjs} +50 -53
- package/dist/token-goat-chunk-P6VGNTQT.mjs +71 -0
- package/dist/{token-goat-chunk-7BXSNBY5.mjs → token-goat-chunk-PFWIMH2E.mjs} +23 -15
- package/dist/token-goat-chunk-PJZXIS3G.mjs +39 -0
- package/dist/token-goat-chunk-PUO5INXP.mjs +606 -0
- package/dist/{token-goat-chunk-GFBSN7BD.mjs → token-goat-chunk-PX5PQWTY.mjs} +22 -250
- package/dist/token-goat-chunk-Q5QDBC6O.mjs +111 -0
- package/dist/token-goat-chunk-QFNQXSDT.mjs +40 -0
- package/dist/{token-goat-chunk-FBNQNSJK.mjs → token-goat-chunk-QH7YVYOY.mjs} +2198 -2782
- package/dist/{token-goat-chunk-64FBPDTK.mjs → token-goat-chunk-QLMUHEPB.mjs} +12 -9
- package/dist/{token-goat-chunk-RZRTDST7.mjs → token-goat-chunk-QQOJIHL3.mjs} +37 -11
- package/dist/{token-goat-chunk-XKI4SEMX.mjs → token-goat-chunk-QQTWJY2O.mjs} +19 -0
- package/dist/{token-goat-chunk-U4N3BN2W.mjs → token-goat-chunk-QYHO5CPH.mjs} +3 -2
- package/dist/token-goat-chunk-RELAVWAD.mjs +27 -0
- package/dist/{token-goat-chunk-ETBHVDB6.mjs → token-goat-chunk-RSLPOBKQ.mjs} +22 -11
- package/dist/token-goat-chunk-SAMFDIS2.mjs +712 -0
- package/dist/token-goat-chunk-SDELXLDM.mjs +48 -0
- package/dist/{token-goat-chunk-4JJS54TR.mjs → token-goat-chunk-SOAFZNNZ.mjs} +161 -38
- package/dist/{token-goat-chunk-664RTTUV.mjs → token-goat-chunk-SRTHT7KV.mjs} +21 -16
- package/dist/{token-goat-chunk-4FVIAHSI.mjs → token-goat-chunk-T5P5ACNQ.mjs} +7 -6
- package/dist/token-goat-chunk-T5YGG4V3.mjs +45 -0
- package/dist/token-goat-chunk-TAUZ4OFS.mjs +110 -0
- package/dist/{token-goat-chunk-COTXZHTS.mjs → token-goat-chunk-TM6MC754.mjs} +4 -3
- package/dist/token-goat-chunk-TWUKBOMP.mjs +72 -0
- package/dist/{token-goat-chunk-3CHUNJ2O.mjs → token-goat-chunk-U2KSB4SG.mjs} +8 -5
- package/dist/{token-goat-chunk-3Z5S4KGM.mjs → token-goat-chunk-U4WHMG6Y.mjs} +29 -49
- package/dist/{token-goat-chunk-PY533T7F.mjs → token-goat-chunk-UGJHGR5A.mjs} +21 -15
- package/dist/{token-goat-chunk-CYCEYAEO.mjs → token-goat-chunk-UOUDVA5U.mjs} +1 -1
- package/dist/{token-goat-chunk-JDHE26RP.mjs → token-goat-chunk-URRJ33GR.mjs} +16 -5
- package/dist/{token-goat-chunk-56EIMS3E.mjs → token-goat-chunk-UZOROKIS.mjs} +23 -17
- package/dist/{token-goat-chunk-CZ3LUXYZ.mjs → token-goat-chunk-VIHGGGEI.mjs} +427 -308
- package/dist/token-goat-chunk-VTJLAA7P.mjs +21 -0
- package/dist/token-goat-chunk-VWDXLT4F.mjs +678 -0
- package/dist/{token-goat-chunk-YARZFCZS.mjs → token-goat-chunk-VWZA2H4P.mjs} +62 -53
- package/dist/{token-goat-chunk-SQSSNQPE.mjs → token-goat-chunk-VXEXMJDL.mjs} +3979 -4745
- package/dist/token-goat-chunk-WBA5SKT4.mjs +547 -0
- package/dist/{token-goat-chunk-SJHWVNDQ.mjs → token-goat-chunk-WDH44VAU.mjs} +7 -4
- package/dist/token-goat-chunk-X2SUP3EI.mjs +107 -0
- package/dist/token-goat-chunk-X3BELZSJ.mjs +125 -0
- package/dist/{token-goat-chunk-FZ3NEW5O.mjs → token-goat-chunk-XLRBNZCC.mjs} +13 -8
- package/dist/{token-goat-chunk-Z44YO3QZ.mjs → token-goat-chunk-XRKUX6CM.mjs} +1 -1
- package/dist/token-goat-chunk-XX6O25RN.mjs +82 -0
- package/dist/token-goat-chunk-XZSUHBVS.mjs +65 -0
- package/dist/{token-goat-chunk-WY3L4DVT.mjs → token-goat-chunk-Y2ZTQOCL.mjs} +13 -8
- package/dist/{token-goat-chunk-UQ5UK2T7.mjs → token-goat-chunk-YB4VMGK2.mjs} +1 -1
- package/dist/token-goat-chunk-YBIIY3HR.mjs +51 -0
- package/dist/{token-goat-chunk-ZQSJMO2R.mjs → token-goat-chunk-YHVDKQBR.mjs} +105 -105
- package/dist/{token-goat-chunk-H2K3QP6H.mjs → token-goat-chunk-YJB5XLWI.mjs} +8 -6
- package/dist/{token-goat-chunk-ZCDTCVIS.mjs → token-goat-chunk-YKB6AQG3.mjs} +5 -2
- package/dist/token-goat-chunk-YMTG2YA5.mjs +35 -0
- package/dist/token-goat-chunk-YPBZYQRV.mjs +40 -0
- package/dist/{token-goat-chunk-HOJLCGDP.mjs → token-goat-chunk-YY6AVUFL.mjs} +2033 -2251
- package/dist/token-goat-chunk-Z7N5OMMP.mjs +98 -0
- package/dist/token-goat-chunk-ZNMNUPAZ.mjs +938 -0
- package/dist/token-goat-hook-client.cjs +1 -1
- package/dist/token-goat-hook-client.mjs +3 -3
- package/dist/token-goat-hook.mjs +67 -38
- package/dist/token-goat.core.mjs +4 -4
- package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
- package/docs/cli.md +40 -21
- package/docs/install.md +19 -9
- package/docs/security.md +5 -3
- package/package.json +2 -1
- package/dist/token-goat-chunk-5KDMCMF2.mjs +0 -362
- package/dist/token-goat-chunk-6QMAS7AB.mjs +0 -42
- package/dist/token-goat-chunk-7F2UBPD3.mjs +0 -397
- package/dist/token-goat-chunk-ARDNEC2U.mjs +0 -248
- package/dist/token-goat-chunk-ATCPY7EQ.mjs +0 -134
- package/dist/token-goat-chunk-EFHMMIGP.mjs +0 -68
- package/dist/token-goat-chunk-IVALLT3S.mjs +0 -48
- package/dist/token-goat-chunk-JDU3EXSU.mjs +0 -65
- package/dist/token-goat-chunk-MKPF4CL5.mjs +0 -37
- package/dist/token-goat-chunk-RC35RBUL.mjs +0 -312
- package/dist/token-goat-chunk-USILZ3T3.mjs +0 -53
- package/dist/token-goat-chunk-VL3YIFHZ.mjs +0 -2975
- package/dist/token-goat-chunk-XU4P6Z24.mjs +0 -133
- 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
|
-
|
|
|
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
|
|
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`, `
|
|
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 `
|
|
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
|
|
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
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
Binary file
|
|
Binary file
|
|
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-
|
|
5
|
+
} from "./token-goat-chunk-T5P5ACNQ.mjs";
|
|
6
6
|
import {
|
|
7
7
|
loadConfig
|
|
8
|
-
} from "./token-goat-chunk-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
|
336
|
-
var
|
|
337
|
-
var
|
|
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
|
-
|
|
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
|
|
374
|
+
const withParas = replaceElements(xml, MATH_PARA_OPEN, MATH_PARA_CLOSE, (inner) => {
|
|
346
375
|
const lines = [];
|
|
347
|
-
for (const
|
|
348
|
-
const text = mathRunText(
|
|
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
|
|
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-
|
|
8
|
+
} from "./token-goat-chunk-HU4TORC5.mjs";
|
|
9
9
|
import {
|
|
10
10
|
dataDir
|
|
11
|
-
} from "./token-goat-chunk-
|
|
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";
|