token-goat 2.9.19 → 2.9.21
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 +3 -1
- package/dist/{token-goat-chunk-JNIFSVWM.mjs → token-goat-chunk-2ZHD3XCD.mjs} +5 -1
- package/dist/{token-goat-chunk-P46RTOAQ.mjs → token-goat-chunk-4IIU3GNS.mjs} +10 -7
- package/dist/{token-goat-chunk-2GTB5RBZ.mjs → token-goat-chunk-4V4OZHHV.mjs} +11 -271
- package/dist/token-goat-chunk-64OEF3UN.mjs +128 -0
- package/dist/{token-goat-chunk-AVRD7SBO.mjs → token-goat-chunk-6AQLZMCO.mjs} +270 -29
- package/dist/{token-goat-chunk-LACH3WKS.mjs → token-goat-chunk-BIEDTF6V.mjs} +3 -3
- package/dist/{token-goat-chunk-PIAESKVU.mjs → token-goat-chunk-BKHJRFDE.mjs} +568 -429
- package/dist/token-goat-chunk-CAHN4QU7.mjs +26 -0
- package/dist/{token-goat-chunk-MU6I4IDI.mjs → token-goat-chunk-CMAJGP3E.mjs} +11 -7
- package/dist/token-goat-chunk-CMHBTXMY.mjs +41 -0
- package/dist/{token-goat-chunk-HPMMLV37.mjs → token-goat-chunk-CZ5ZFJSN.mjs} +256 -341
- package/dist/{token-goat-chunk-7DGTVBWP.mjs → token-goat-chunk-DDP64XR7.mjs} +159 -1863
- package/dist/{token-goat-chunk-FX5DHKGF.mjs → token-goat-chunk-DISVHOW6.mjs} +4 -4
- package/dist/{token-goat-chunk-L2UOJBV4.mjs → token-goat-chunk-FZN77SZD.mjs} +27 -19
- package/dist/{token-goat-chunk-DA6TIDLI.mjs → token-goat-chunk-GO7OWONJ.mjs} +18 -13
- package/dist/{token-goat-chunk-EP7TCWHM.mjs → token-goat-chunk-IAMIQLCV.mjs} +83 -66
- package/dist/{token-goat-chunk-NJKNAC37.mjs → token-goat-chunk-IJUHQX2M.mjs} +1 -1
- package/dist/token-goat-chunk-KQI24O4B.mjs +292 -0
- package/dist/{token-goat-chunk-GYC34EGE.mjs → token-goat-chunk-KRPJIXCT.mjs} +2 -2
- package/dist/token-goat-chunk-KVAXTA2W.mjs +198 -0
- package/dist/{token-goat-chunk-FJZ5MZK7.mjs → token-goat-chunk-LK3YOKDF.mjs} +2 -2
- package/dist/token-goat-chunk-M43UJVYQ.mjs +1816 -0
- package/dist/{token-goat-chunk-SOKAPOOC.mjs → token-goat-chunk-M6CDVASA.mjs} +46 -47
- package/dist/token-goat-chunk-MECDAPXB.mjs +1989 -0
- package/dist/token-goat-chunk-MEKVJZUN.mjs +324 -0
- package/dist/token-goat-chunk-MQACN7VG.mjs +1379 -0
- package/dist/{token-goat-chunk-3W5P673V.mjs → token-goat-chunk-MZQORUX5.mjs} +1 -1
- package/dist/{token-goat-chunk-DHIXE6GV.mjs → token-goat-chunk-ORRDWKLA.mjs} +1 -1
- package/dist/{token-goat-chunk-F24NE3FO.mjs → token-goat-chunk-PCR43MCM.mjs} +2 -2
- package/dist/token-goat-chunk-PDYQA3AP.mjs +248 -0
- package/dist/{token-goat-chunk-S7RFIX4Y.mjs → token-goat-chunk-PHDNWISV.mjs} +45 -14
- package/dist/token-goat-chunk-PPYZS7TI.mjs +304 -0
- package/dist/{token-goat-chunk-BTKDZW7F.mjs → token-goat-chunk-QTIRVK3M.mjs} +1 -1
- package/dist/{token-goat-chunk-62VJ47WN.mjs → token-goat-chunk-QUM3UNWS.mjs} +5 -5
- package/dist/{token-goat-chunk-KWM6KBPA.mjs → token-goat-chunk-SHV6QQAS.mjs} +1 -1
- package/dist/{token-goat-chunk-FQYMZASI.mjs → token-goat-chunk-SMUYPBF5.mjs} +796 -4464
- package/dist/{token-goat-chunk-ZGQXRYKE.mjs → token-goat-chunk-TGBSMHPM.mjs} +1 -1
- package/dist/{token-goat-chunk-EXLLLIC3.mjs → token-goat-chunk-VXKT3W4C.mjs} +3 -3
- package/dist/{token-goat-chunk-WKO5TVNA.mjs → token-goat-chunk-XJUPKJF5.mjs} +544 -576
- package/dist/{token-goat-chunk-IBTFGZBX.mjs → token-goat-chunk-XQSSPX3X.mjs} +2 -2
- package/dist/{token-goat-chunk-VJQBYSXG.mjs → token-goat-chunk-ZJWPJL7O.mjs} +3 -3
- package/dist/token-goat-hook.mjs +17 -11
- package/dist/token-goat.core.mjs +27 -19
- package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
- package/docs/cli.md +13 -12
- package/docs/install.md +1 -1
- package/docs/security.md +2 -2
- package/package.json +3 -2
- package/dist/token-goat-chunk-NYFYIRG3.mjs +0 -35
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ permalink: /
|
|
|
21
21
|
|
|
22
22
|
Token-Goat sits silently between your AI and your tools. Re-read a file? It gets a one-line hint and a narrow-slice suggestion instead of the full file again. Grab a screenshot? A 100 KB copy reaches the model instead of 10 MB. Run `pytest`, `npm install`, `docker build`, or `cargo`? The thousands of progress bars and passing-test names are stripped to the failures before the output even reaches the context window. Open a PDF, a large Markdown doc, or a CSV? The hook intercepts it — heading tree, page count, or column preview — so the model never pays for the full file. Run `gh run watch` or `next dev` a second time? Prior output is recalled rather than re-run. Compact a long session? It gets a clean structured manifest of edited files and key symbols so nothing important is forgotten. Sessions drop 40–90%+ in cost. You change nothing about how you work.
|
|
23
23
|
|
|
24
|
+
The symbol index also understands native Nginx, Caddyfile, and Apache HTTP Server configuration blocks, so `token-goat read "file::symbol"` can jump directly to them.
|
|
25
|
+
|
|
24
26
|
Works with **Claude Code**, **Gemini CLI**, **Qwen Code**, **Codex CLI**, **Aider**, **Cursor**, **Cline**, **Windsurf**, **Copilot CLI**, **VS Code** (Copilot agent), **Visual Studio** (Copilot agent, MCP tools and instructions only), **Zed** (MCP tools only), **Kimi Code**, **Grok CLI** (xAI Grok Build), and OpenCode, plus **pi** ([pi-coding-agent](https://github.com/earendil-works/pi-mono)).
|
|
25
27
|
|
|
26
28
|
**Ask your AI to install it fully (give it this GitHub link), or install in one command:**
|
|
@@ -213,7 +215,7 @@ Numbers below come from synthetic-fixture benchmarks in the test suite, except t
|
|
|
213
215
|
| Symbol start_line | TypeScript decorators captured in symbol span | One `token-goat read` returns the decorator + signature + body; no re-read | `src/parser.ts` (TypeScript adapter) |
|
|
214
216
|
| Section extraction | Setext headings, h5/h6, anchor IDs, shell comment banners (`##`, `# ---`, `# ===`, `# [...]`, `# REGION:`), and `__frontmatter__` | `token-goat section` resolves more headings and procedural shell checks without falling back to a full file read | `src/parser.ts` (Markdown & Shell adapters) |
|
|
215
217
|
| Image cache | Repeat Read of an unchanged image serves the stored re-encode, keyed on path + size + mtime, instead of re-encoding it again | Skips the re-encode entirely on a hit; the same bytes reach the model, so the reported saving is identical either way | `src/image_shrink.ts` (`findCachedShrink`) |
|
|
216
|
-
| Monorepo defaults | Reindex batch 500 → 2000
|
|
218
|
+
| Monorepo defaults | Reindex batch 500 → 2000 | Fewer worker wakeups on a large tree | `src/config.ts` defaults |
|
|
217
219
|
| Miss suggestions | `read` / `section` print "Did you mean…?" on a miss; `section` also auto-redirects on an unambiguous heading-prefix match | Keeps agents on the surgical-read path instead of falling back to full-file `Read` | `src/read_commands.ts` |
|
|
218
220
|
| Atlassian MCP compression | Strips UI chrome, avatars, operations, and schema links from Jira and Confluence tool responses | 60–80% smaller before generic table compression | `src/mcp_atlassian_compress.ts` |
|
|
219
221
|
| MCP tool spill slicing | Narrow-slice queries on cached MCP tool outputs or dumped JSON files with `mcp-output --json-query` | 90–98% smaller than whole-file JSON re-reads | `src/mcp_json_query.ts` |
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
foldCase,
|
|
16
16
|
foldPath,
|
|
17
17
|
sleepSync
|
|
18
|
-
} from "./token-goat-chunk-
|
|
18
|
+
} from "./token-goat-chunk-TGBSMHPM.mjs";
|
|
19
19
|
import {
|
|
20
20
|
safeJoin
|
|
21
21
|
} from "./token-goat-chunk-NMTKNYGF.mjs";
|
|
@@ -1666,6 +1666,10 @@ var LANGUAGE_SPECS = [
|
|
|
1666
1666
|
// Oracle PL/SQL sources: package spec and body, standalone procedure/function, trigger, and object type spec and body.
|
|
1667
1667
|
{ id: "sql", extraction: "regex", extensions: [".sql", ".pks", ".pkb", ".pls", ".plsql", ".pck", ".prc", ".fnc", ".trg", ".tps", ".tpb"], label: "SQL", ...DATA, symbolBearing: true, diffable: true, fence: "sql" },
|
|
1668
1668
|
{ id: "ini", extraction: "regex", extensions: [".ini", ".cfg", ".conf"], label: "INI", ...DATA, fence: "ini" },
|
|
1669
|
+
{ id: "nginx", extraction: "regex", extensions: [".nginx"], basenames: ["nginx.conf"], label: "Nginx", ...DATA, symbolBearing: true, diffable: true, fence: "nginx" },
|
|
1670
|
+
// `basenames` is matched against a lower-cased basename, so `Caddyfile` is spelled in lower case here; the display spelling is the label's job.
|
|
1671
|
+
{ id: "caddy", extraction: "regex", extensions: [".caddy"], basenames: ["caddyfile", "caddy.conf"], label: "Caddyfile", ...DATA, symbolBearing: true, diffable: true, fence: "caddy" },
|
|
1672
|
+
{ id: "apache", extraction: "regex", extensions: [".apache", ".apache2"], basenames: ["httpd.conf", "apache2.conf", ".htaccess"], label: "Apache Config", ...DATA, symbolBearing: true, diffable: true, fence: "apache" },
|
|
1669
1673
|
// `.mk` fragments (config.mk, rules.mk) share a bare Makefile's syntax.
|
|
1670
1674
|
{ id: "makefile", extraction: "regex", extensions: [".mk"], basenames: ["makefile", "gnumakefile", "bsdmakefile"], ...DATA, fence: "makefile", basenameImportsExtension: ".mk" },
|
|
1671
1675
|
{ id: "proto", extraction: "regex", extensions: [".proto"], label: "Protocol Buffers", ...DATA, symbolBearing: true, fence: "protobuf" },
|
|
@@ -4,17 +4,19 @@ import {
|
|
|
4
4
|
DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
5
5
|
DEFAULT_TIMEOUT_SECONDS,
|
|
6
6
|
MAX_CAPTURE_BYTES,
|
|
7
|
+
resolveFilter,
|
|
7
8
|
run,
|
|
8
9
|
runRaw
|
|
9
|
-
} from "./token-goat-chunk-
|
|
10
|
-
import "./token-goat-chunk-
|
|
11
|
-
import "./token-goat-chunk-
|
|
12
|
-
import "./token-goat-chunk-
|
|
13
|
-
import "./token-goat-chunk-
|
|
14
|
-
import "./token-goat-chunk-
|
|
10
|
+
} from "./token-goat-chunk-CMAJGP3E.mjs";
|
|
11
|
+
import "./token-goat-chunk-DDP64XR7.mjs";
|
|
12
|
+
import "./token-goat-chunk-M43UJVYQ.mjs";
|
|
13
|
+
import "./token-goat-chunk-PHDNWISV.mjs";
|
|
14
|
+
import "./token-goat-chunk-XQSSPX3X.mjs";
|
|
15
|
+
import "./token-goat-chunk-2ZHD3XCD.mjs";
|
|
16
|
+
import "./token-goat-chunk-M6CDVASA.mjs";
|
|
15
17
|
import "./token-goat-chunk-EEIDFMEM.mjs";
|
|
16
18
|
import "./token-goat-chunk-OSUFN2FV.mjs";
|
|
17
|
-
import "./token-goat-chunk-
|
|
19
|
+
import "./token-goat-chunk-TGBSMHPM.mjs";
|
|
18
20
|
import "./token-goat-chunk-NMTKNYGF.mjs";
|
|
19
21
|
import "./token-goat-chunk-GMOUBOX4.mjs";
|
|
20
22
|
import "./token-goat-chunk-A37V4PBF.mjs";
|
|
@@ -22,6 +24,7 @@ export {
|
|
|
22
24
|
DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
23
25
|
DEFAULT_TIMEOUT_SECONDS,
|
|
24
26
|
MAX_CAPTURE_BYTES,
|
|
27
|
+
resolveFilter,
|
|
25
28
|
run,
|
|
26
29
|
runRaw
|
|
27
30
|
};
|
|
@@ -1,47 +1,39 @@
|
|
|
1
1
|
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
|
-
accumulateResidentLine,
|
|
5
|
-
claudeConfigDir,
|
|
6
|
-
createResidentContextStats,
|
|
7
4
|
enqueueDirtyPathsSafe,
|
|
8
|
-
estimateTokens,
|
|
9
5
|
fenceUntrusted,
|
|
10
6
|
getToolName,
|
|
11
7
|
getTrackedFiles,
|
|
12
|
-
scanAndRecord
|
|
13
|
-
|
|
14
|
-
} from "./token-goat-chunk-FQYMZASI.mjs";
|
|
8
|
+
scanAndRecord
|
|
9
|
+
} from "./token-goat-chunk-SMUYPBF5.mjs";
|
|
15
10
|
import {
|
|
16
11
|
RECALL_DEFAULT_LIMIT,
|
|
17
|
-
commandHash,
|
|
18
|
-
getBashOutput,
|
|
19
12
|
indexRecallEntry,
|
|
20
13
|
listRecentRecall,
|
|
21
|
-
normalizeCommandForCacheKey,
|
|
22
14
|
searchRecall
|
|
23
|
-
} from "./token-goat-chunk-
|
|
15
|
+
} from "./token-goat-chunk-M43UJVYQ.mjs";
|
|
24
16
|
import {
|
|
25
17
|
isBlobStale,
|
|
26
18
|
loadBlob,
|
|
27
19
|
redactSecrets,
|
|
28
20
|
storeBlob
|
|
29
|
-
} from "./token-goat-chunk-
|
|
21
|
+
} from "./token-goat-chunk-PHDNWISV.mjs";
|
|
30
22
|
import {
|
|
31
23
|
parserFingerprintForLanguage
|
|
32
|
-
} from "./token-goat-chunk-
|
|
24
|
+
} from "./token-goat-chunk-IAMIQLCV.mjs";
|
|
33
25
|
import {
|
|
34
26
|
getProjectFileEntries,
|
|
35
27
|
projectScopeClause
|
|
36
|
-
} from "./token-goat-chunk-
|
|
28
|
+
} from "./token-goat-chunk-XQSSPX3X.mjs";
|
|
37
29
|
import {
|
|
38
30
|
getDb
|
|
39
|
-
} from "./token-goat-chunk-
|
|
31
|
+
} from "./token-goat-chunk-2ZHD3XCD.mjs";
|
|
40
32
|
import {
|
|
41
33
|
fingerprintFile,
|
|
42
34
|
getDisplayRoot,
|
|
43
35
|
shortFingerprint
|
|
44
|
-
} from "./token-goat-chunk-
|
|
36
|
+
} from "./token-goat-chunk-M6CDVASA.mjs";
|
|
45
37
|
import {
|
|
46
38
|
registerReset
|
|
47
39
|
} from "./token-goat-chunk-EEIDFMEM.mjs";
|
|
@@ -52,7 +44,7 @@ import {
|
|
|
52
44
|
foldPath,
|
|
53
45
|
globalDbPath,
|
|
54
46
|
pad
|
|
55
|
-
} from "./token-goat-chunk-
|
|
47
|
+
} from "./token-goat-chunk-TGBSMHPM.mjs";
|
|
56
48
|
import {
|
|
57
49
|
UNTRUSTED_TOOL_TAG,
|
|
58
50
|
UNTRUSTED_WEB_TAG,
|
|
@@ -132,7 +124,7 @@ var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
|
|
|
132
124
|
var MAX_STDIN_WALL_MS = 6e4;
|
|
133
125
|
var MAX_STDIN_BYTES = 64 * 1024 * 1024;
|
|
134
126
|
function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
|
|
135
|
-
return new Promise((
|
|
127
|
+
return new Promise((resolve2, reject) => {
|
|
136
128
|
const chunks = [];
|
|
137
129
|
let totalBytes = 0;
|
|
138
130
|
let settled = false;
|
|
@@ -178,7 +170,7 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
|
|
|
178
170
|
return;
|
|
179
171
|
}
|
|
180
172
|
try {
|
|
181
|
-
|
|
173
|
+
resolve2(JSON.parse(text));
|
|
182
174
|
} catch (err) {
|
|
183
175
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
184
176
|
}
|
|
@@ -972,253 +964,6 @@ function reconcileProject(opts = {}) {
|
|
|
972
964
|
};
|
|
973
965
|
}
|
|
974
966
|
|
|
975
|
-
// src/waste.ts
|
|
976
|
-
init_define_import_meta_env();
|
|
977
|
-
import * as fs3 from "node:fs";
|
|
978
|
-
import * as path from "node:path";
|
|
979
|
-
function projectTranscriptsDir(projectRoot) {
|
|
980
|
-
const rootStr = path.resolve(projectRoot);
|
|
981
|
-
const slug = rootStr.replace(/[^A-Za-z0-9]/g, "-");
|
|
982
|
-
return path.join(claudeConfigDir(), "projects", slug);
|
|
983
|
-
}
|
|
984
|
-
function findLatestTranscript(projectRoot) {
|
|
985
|
-
const dir = projectTranscriptsDir(projectRoot);
|
|
986
|
-
let entries;
|
|
987
|
-
try {
|
|
988
|
-
entries = fs3.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
989
|
-
} catch {
|
|
990
|
-
return null;
|
|
991
|
-
}
|
|
992
|
-
let best = null;
|
|
993
|
-
for (const name of entries) {
|
|
994
|
-
const full = path.join(dir, name);
|
|
995
|
-
let stat;
|
|
996
|
-
try {
|
|
997
|
-
stat = fs3.statSync(full);
|
|
998
|
-
} catch {
|
|
999
|
-
continue;
|
|
1000
|
-
}
|
|
1001
|
-
if (best === null || stat.mtimeMs > best.mtimeMs) best = { file: full, mtimeMs: stat.mtimeMs };
|
|
1002
|
-
}
|
|
1003
|
-
return best?.file ?? null;
|
|
1004
|
-
}
|
|
1005
|
-
function safeStringify(value) {
|
|
1006
|
-
try {
|
|
1007
|
-
return JSON.stringify(value) ?? "";
|
|
1008
|
-
} catch {
|
|
1009
|
-
return "";
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
var FILE_PATH_TOOLS = /* @__PURE__ */ new Set(["Read", "Edit", "Write", "NotebookEdit", "MultiEdit"]);
|
|
1013
|
-
function extractFilePath(name, input) {
|
|
1014
|
-
if (!FILE_PATH_TOOLS.has(name)) return null;
|
|
1015
|
-
if (input === null || typeof input !== "object") return null;
|
|
1016
|
-
const o = input;
|
|
1017
|
-
const fp = o["file_path"];
|
|
1018
|
-
if (typeof fp === "string") return fp;
|
|
1019
|
-
const notebookPath = o["notebook_path"];
|
|
1020
|
-
return typeof notebookPath === "string" ? notebookPath : null;
|
|
1021
|
-
}
|
|
1022
|
-
function extractCommand(name, input) {
|
|
1023
|
-
if (name !== "Bash") return null;
|
|
1024
|
-
if (input === null || typeof input !== "object") return null;
|
|
1025
|
-
const cmd = input["command"];
|
|
1026
|
-
return typeof cmd === "string" ? cmd : null;
|
|
1027
|
-
}
|
|
1028
|
-
function summarizeInput(name, input, filePath, command) {
|
|
1029
|
-
if (filePath !== null) return filePath;
|
|
1030
|
-
if (command !== null) return command.length > 100 ? `${command.slice(0, 100)}\u2026` : command;
|
|
1031
|
-
if (input === null || typeof input !== "object") return "";
|
|
1032
|
-
const o = input;
|
|
1033
|
-
for (const key of ["pattern", "query", "path", "description", "prompt"]) {
|
|
1034
|
-
const v = o[key];
|
|
1035
|
-
if (typeof v === "string") return v.length > 100 ? `${v.slice(0, 100)}\u2026` : v;
|
|
1036
|
-
}
|
|
1037
|
-
const s = safeStringify(input);
|
|
1038
|
-
return s.length > 100 ? `${s.slice(0, 100)}\u2026` : s;
|
|
1039
|
-
}
|
|
1040
|
-
function extractResultText(content) {
|
|
1041
|
-
if (typeof content === "string") return content;
|
|
1042
|
-
if (Array.isArray(content)) {
|
|
1043
|
-
return content.map((b) => {
|
|
1044
|
-
if (b !== null && typeof b === "object" && b["type"] === "text") {
|
|
1045
|
-
const t = b["text"];
|
|
1046
|
-
return typeof t === "string" ? t : "";
|
|
1047
|
-
}
|
|
1048
|
-
return "";
|
|
1049
|
-
}).join("\n");
|
|
1050
|
-
}
|
|
1051
|
-
return "";
|
|
1052
|
-
}
|
|
1053
|
-
function parseTranscript(transcriptPath) {
|
|
1054
|
-
const raw = fs3.readFileSync(transcriptPath, "utf-8");
|
|
1055
|
-
const calls = [];
|
|
1056
|
-
const resultTextById = /* @__PURE__ */ new Map();
|
|
1057
|
-
const assistantTurns = [];
|
|
1058
|
-
const resident = createResidentContextStats();
|
|
1059
|
-
let seq = 0;
|
|
1060
|
-
for (const line of raw.split("\n")) {
|
|
1061
|
-
const trimmed = line.trim();
|
|
1062
|
-
if (trimmed === "") continue;
|
|
1063
|
-
let obj;
|
|
1064
|
-
try {
|
|
1065
|
-
obj = JSON.parse(trimmed);
|
|
1066
|
-
} catch {
|
|
1067
|
-
continue;
|
|
1068
|
-
}
|
|
1069
|
-
if (obj === null || typeof obj !== "object") continue;
|
|
1070
|
-
const o = obj;
|
|
1071
|
-
accumulateResidentLine(resident, o, trimmed.length);
|
|
1072
|
-
const message = o["message"];
|
|
1073
|
-
if (message === null || typeof message !== "object") continue;
|
|
1074
|
-
const messageObj = message;
|
|
1075
|
-
const content = messageObj["content"];
|
|
1076
|
-
if (!Array.isArray(content)) continue;
|
|
1077
|
-
const role = typeof messageObj["role"] === "string" ? messageObj["role"] : null;
|
|
1078
|
-
const cwd = typeof o["cwd"] === "string" ? o["cwd"] : null;
|
|
1079
|
-
let turnTextTokens = 0;
|
|
1080
|
-
let sawTextBlock = false;
|
|
1081
|
-
for (const block of content) {
|
|
1082
|
-
if (block === null || typeof block !== "object") continue;
|
|
1083
|
-
const b = block;
|
|
1084
|
-
if (b["type"] === "tool_use" && typeof b["id"] === "string" && typeof b["name"] === "string") {
|
|
1085
|
-
const name = b["name"];
|
|
1086
|
-
const input = b["input"];
|
|
1087
|
-
const filePath = extractFilePath(name, input);
|
|
1088
|
-
const command = extractCommand(name, input);
|
|
1089
|
-
calls.push({
|
|
1090
|
-
seq: seq++,
|
|
1091
|
-
id: b["id"],
|
|
1092
|
-
name,
|
|
1093
|
-
input,
|
|
1094
|
-
filePath,
|
|
1095
|
-
command,
|
|
1096
|
-
summary: summarizeInput(name, input, filePath, command),
|
|
1097
|
-
cwd
|
|
1098
|
-
});
|
|
1099
|
-
} else if (b["type"] === "tool_result" && typeof b["tool_use_id"] === "string") {
|
|
1100
|
-
const id = b["tool_use_id"];
|
|
1101
|
-
if (!resultTextById.has(id)) {
|
|
1102
|
-
resultTextById.set(id, extractResultText(b["content"]));
|
|
1103
|
-
}
|
|
1104
|
-
} else if (role === "assistant" && b["type"] === "text" && typeof b["text"] === "string") {
|
|
1105
|
-
sawTextBlock = true;
|
|
1106
|
-
turnTextTokens += estimateTokens(b["text"]);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
if (role === "assistant" && sawTextBlock) assistantTurns.push(turnTextTokens);
|
|
1110
|
-
}
|
|
1111
|
-
return { calls, resultTextById, assistantTurns, resident };
|
|
1112
|
-
}
|
|
1113
|
-
function costPerCall(parsed) {
|
|
1114
|
-
return parsed.calls.map((c) => ({
|
|
1115
|
-
...c,
|
|
1116
|
-
tokens: estimateTokens(parsed.resultTextById.get(c.id) ?? "")
|
|
1117
|
-
}));
|
|
1118
|
-
}
|
|
1119
|
-
function aggregateSortedDesc(pairs) {
|
|
1120
|
-
return [...pairs].map(([key, tokens]) => ({ key, tokens })).sort((a, b) => b.tokens - a.tokens);
|
|
1121
|
-
}
|
|
1122
|
-
function tokensByTool(costs) {
|
|
1123
|
-
const m = /* @__PURE__ */ new Map();
|
|
1124
|
-
for (const c of costs) m.set(c.name, (m.get(c.name) ?? 0) + c.tokens);
|
|
1125
|
-
return aggregateSortedDesc(m);
|
|
1126
|
-
}
|
|
1127
|
-
function tokensByFile(costs) {
|
|
1128
|
-
const m = /* @__PURE__ */ new Map();
|
|
1129
|
-
for (const c of costs) {
|
|
1130
|
-
if (c.filePath === null) continue;
|
|
1131
|
-
m.set(c.filePath, (m.get(c.filePath) ?? 0) + c.tokens);
|
|
1132
|
-
}
|
|
1133
|
-
return aggregateSortedDesc(m);
|
|
1134
|
-
}
|
|
1135
|
-
function topExpensiveCalls(costs, n) {
|
|
1136
|
-
return [...costs].sort((a, b) => b.tokens - a.tokens).slice(0, n);
|
|
1137
|
-
}
|
|
1138
|
-
function neverTouchedAgain(costs) {
|
|
1139
|
-
const readsByFile = /* @__PURE__ */ new Map();
|
|
1140
|
-
for (const c of costs) {
|
|
1141
|
-
if (c.name !== "Read" || c.filePath === null) continue;
|
|
1142
|
-
const arr = readsByFile.get(c.filePath) ?? [];
|
|
1143
|
-
arr.push(c);
|
|
1144
|
-
readsByFile.set(c.filePath, arr);
|
|
1145
|
-
}
|
|
1146
|
-
const out = [];
|
|
1147
|
-
for (const [filePath, reads] of readsByFile) {
|
|
1148
|
-
const firstSeq = Math.min(...reads.map((r) => r.seq));
|
|
1149
|
-
const touchedLater = costs.some((c) => {
|
|
1150
|
-
if (c.seq <= firstSeq) return false;
|
|
1151
|
-
if (c.filePath === filePath) return true;
|
|
1152
|
-
return safeStringify(c.input).includes(filePath);
|
|
1153
|
-
});
|
|
1154
|
-
if (!touchedLater) {
|
|
1155
|
-
out.push({ filePath, tokens: reads.reduce((n, r) => n + r.tokens, 0) });
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
return out.sort((a, b) => b.tokens - a.tokens);
|
|
1159
|
-
}
|
|
1160
|
-
async function repeatedUncompressedBashCommands(costs) {
|
|
1161
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1162
|
-
for (const c of costs) {
|
|
1163
|
-
if (c.name !== "Bash" || c.command === null) continue;
|
|
1164
|
-
const norm = normalizeCommandForCacheKey(c.command);
|
|
1165
|
-
const arr = groups.get(norm) ?? [];
|
|
1166
|
-
arr.push(c);
|
|
1167
|
-
groups.set(norm, arr);
|
|
1168
|
-
}
|
|
1169
|
-
const out = [];
|
|
1170
|
-
for (const [norm, calls] of groups) {
|
|
1171
|
-
if (calls.length < 2) continue;
|
|
1172
|
-
let cached = false;
|
|
1173
|
-
for (const c of calls) {
|
|
1174
|
-
try {
|
|
1175
|
-
const hash = await commandHash(c.command, c.cwd);
|
|
1176
|
-
if (getBashOutput(hash) !== null) {
|
|
1177
|
-
cached = true;
|
|
1178
|
-
break;
|
|
1179
|
-
}
|
|
1180
|
-
} catch {
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
if (cached) continue;
|
|
1184
|
-
const totalTokens = calls.reduce((n, c) => n + c.tokens, 0);
|
|
1185
|
-
out.push({
|
|
1186
|
-
normalized: norm,
|
|
1187
|
-
count: calls.length,
|
|
1188
|
-
totalTokens,
|
|
1189
|
-
avgTokens: Math.round(totalTokens / calls.length)
|
|
1190
|
-
});
|
|
1191
|
-
}
|
|
1192
|
-
return out.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
1193
|
-
}
|
|
1194
|
-
function assistantOutputCost(assistantTurns) {
|
|
1195
|
-
const turnCount = assistantTurns.length;
|
|
1196
|
-
let generatedTokens = 0;
|
|
1197
|
-
let resendCeilingTokens = 0;
|
|
1198
|
-
for (let i = 0; i < turnCount; i++) {
|
|
1199
|
-
const tokens = assistantTurns[i] ?? 0;
|
|
1200
|
-
generatedTokens += tokens;
|
|
1201
|
-
resendCeilingTokens += tokens * (turnCount - 1 - i);
|
|
1202
|
-
}
|
|
1203
|
-
return { turnCount, generatedTokens, resendCeilingTokens };
|
|
1204
|
-
}
|
|
1205
|
-
async function buildWasteReport(transcriptPath, opts = {}) {
|
|
1206
|
-
const parsed = parseTranscript(transcriptPath);
|
|
1207
|
-
const costs = costPerCall(parsed);
|
|
1208
|
-
const topN = opts.topN ?? 10;
|
|
1209
|
-
return {
|
|
1210
|
-
transcriptPath,
|
|
1211
|
-
totalTokens: costs.reduce((n, c) => n + c.tokens, 0),
|
|
1212
|
-
tokensByTool: tokensByTool(costs),
|
|
1213
|
-
tokensByFile: tokensByFile(costs),
|
|
1214
|
-
topCalls: topExpensiveCalls(costs, topN).map((c) => ({ seq: c.seq, name: c.name, summary: c.summary, tokens: c.tokens })),
|
|
1215
|
-
neverTouchedAgain: neverTouchedAgain(costs),
|
|
1216
|
-
repeatedUncompressedBash: await repeatedUncompressedBashCommands(costs),
|
|
1217
|
-
assistantOutput: assistantOutputCost(parsed.assistantTurns),
|
|
1218
|
-
residentContext: summarizeResidentContext(parsed.resident)
|
|
1219
|
-
};
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
967
|
// src/cli_recall.ts
|
|
1223
968
|
init_define_import_meta_env();
|
|
1224
969
|
var RECALL_COMMAND = {
|
|
@@ -1294,11 +1039,6 @@ export {
|
|
|
1294
1039
|
runReconcile,
|
|
1295
1040
|
isReconcileClean,
|
|
1296
1041
|
reconcileProject,
|
|
1297
|
-
projectTranscriptsDir,
|
|
1298
|
-
findLatestTranscript,
|
|
1299
|
-
safeStringify,
|
|
1300
|
-
extractResultText,
|
|
1301
|
-
buildWasteReport,
|
|
1302
1042
|
RECALL_COMMAND,
|
|
1303
1043
|
runRecallCommand,
|
|
1304
1044
|
MAX_STDIN_BYTES,
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
renderWebFetchRow
|
|
5
|
+
} from "./token-goat-chunk-PDYQA3AP.mjs";
|
|
6
|
+
import {
|
|
7
|
+
SESSIONS_SUBDIR,
|
|
8
|
+
extractChecklistSection,
|
|
9
|
+
getSkillFilePath,
|
|
10
|
+
listSkills
|
|
11
|
+
} from "./token-goat-chunk-MECDAPXB.mjs";
|
|
12
|
+
import {
|
|
13
|
+
getBashOutput
|
|
14
|
+
} from "./token-goat-chunk-M43UJVYQ.mjs";
|
|
15
|
+
import {
|
|
16
|
+
loadBlob
|
|
17
|
+
} from "./token-goat-chunk-PHDNWISV.mjs";
|
|
18
|
+
import {
|
|
19
|
+
resolveProjectRoot
|
|
20
|
+
} from "./token-goat-chunk-M6CDVASA.mjs";
|
|
21
|
+
import {
|
|
22
|
+
runGit,
|
|
23
|
+
safeSlice
|
|
24
|
+
} from "./token-goat-chunk-TGBSMHPM.mjs";
|
|
25
|
+
import {
|
|
26
|
+
displaySafePath,
|
|
27
|
+
displaySafeText,
|
|
28
|
+
neutralizeSpokenMarkers
|
|
29
|
+
} from "./token-goat-chunk-NMTKNYGF.mjs";
|
|
30
|
+
import {
|
|
31
|
+
init_define_import_meta_env
|
|
32
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
33
|
+
|
|
34
|
+
// src/resume.ts
|
|
35
|
+
init_define_import_meta_env();
|
|
36
|
+
import { readFile } from "node:fs/promises";
|
|
37
|
+
var MAX_RESUME_TOKENS = 2e3;
|
|
38
|
+
var MAX_RESUME_CHARS = MAX_RESUME_TOKENS * 4;
|
|
39
|
+
var MAX_RESUME_WEB_FETCHES = 8;
|
|
40
|
+
var SKILL_MAX_COUNT = 3;
|
|
41
|
+
var SKILL_MAX_CHARS_EACH = 400;
|
|
42
|
+
async function buildSkillsSection(sessionId) {
|
|
43
|
+
const skills = await listSkills(sessionId);
|
|
44
|
+
if (skills.length === 0) return [];
|
|
45
|
+
const skillLines = ["## Skills"];
|
|
46
|
+
for (const skill of skills.slice(0, SKILL_MAX_COUNT)) {
|
|
47
|
+
let checklist = null;
|
|
48
|
+
try {
|
|
49
|
+
const filePath = await getSkillFilePath(skill.name);
|
|
50
|
+
if (filePath !== null) {
|
|
51
|
+
const body = await readFile(filePath, "utf-8");
|
|
52
|
+
checklist = extractChecklistSection(body);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
checklist = null;
|
|
56
|
+
}
|
|
57
|
+
if (checklist !== null) {
|
|
58
|
+
const trimmed = checklist.length > SKILL_MAX_CHARS_EACH ? checklist.slice(0, SKILL_MAX_CHARS_EACH).trimEnd() + "\u2026" : checklist;
|
|
59
|
+
skillLines.push(`**${displaySafeText(skill.name)}**:`);
|
|
60
|
+
skillLines.push(neutralizeSpokenMarkers(trimmed));
|
|
61
|
+
} else {
|
|
62
|
+
skillLines.push(`**${displaySafeText(skill.name)}** \u2014 \`token-goat skill-body ${displaySafeText(skill.name)} --section DoD\``);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
skillLines.push("");
|
|
66
|
+
return skillLines;
|
|
67
|
+
}
|
|
68
|
+
async function buildResumePacket(sessionId) {
|
|
69
|
+
const blob = loadBlob(SESSIONS_SUBDIR, sessionId);
|
|
70
|
+
if (blob === null || typeof blob !== "object") return null;
|
|
71
|
+
const raw = blob;
|
|
72
|
+
const filesArr = Array.isArray(raw["files"]) ? raw["files"] : [];
|
|
73
|
+
const lines = [`# Resume packet \u2014 session ${displaySafeText(sessionId)}`, ""];
|
|
74
|
+
try {
|
|
75
|
+
lines.push(...await buildSkillsSection(sessionId));
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
const editedPaths = filesArr.filter((f) => f["wasEdited"] === true).map((f) => typeof f["path"] === "string" ? f["path"] : "").filter(Boolean).slice(0, 10);
|
|
79
|
+
if (editedPaths.length > 0) {
|
|
80
|
+
lines.push("## Edited files");
|
|
81
|
+
for (const p of editedPaths) lines.push(`- ${displaySafePath(p)}`);
|
|
82
|
+
lines.push("");
|
|
83
|
+
}
|
|
84
|
+
const topRead = [...filesArr].filter((f) => f["wasEdited"] !== true).sort((a, b) => (typeof b["readCount"] === "number" ? b["readCount"] : 0) - (typeof a["readCount"] === "number" ? a["readCount"] : 0)).slice(0, 8).map((f) => typeof f["path"] === "string" ? f["path"] : "").filter(Boolean);
|
|
85
|
+
if (topRead.length > 0) {
|
|
86
|
+
lines.push("## Top files read");
|
|
87
|
+
for (const p of topRead) lines.push(`- ${displaySafePath(p)}`);
|
|
88
|
+
lines.push("");
|
|
89
|
+
}
|
|
90
|
+
const bashOutputs = Array.isArray(raw["bashOutputs"]) ? raw["bashOutputs"] : [];
|
|
91
|
+
const recentBash = bashOutputs.slice(-2);
|
|
92
|
+
if (recentBash.length > 0) {
|
|
93
|
+
lines.push("## Recent bash commands");
|
|
94
|
+
for (const entry of recentBash) {
|
|
95
|
+
if (Array.isArray(entry) && typeof entry[1] === "string") {
|
|
96
|
+
const bashEntry = getBashOutput(entry[1]);
|
|
97
|
+
if (bashEntry !== null) lines.push(`- ${displaySafeText(bashEntry.command)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
lines.push("");
|
|
101
|
+
}
|
|
102
|
+
const webFetches = Array.isArray(raw["webFetches"]) ? raw["webFetches"] : [];
|
|
103
|
+
const webRows = webFetches.slice(-MAX_RESUME_WEB_FETCHES).filter((e) => Array.isArray(e) && typeof e[0] === "string" && typeof e[1] === "string").map(([key, cacheId]) => renderWebFetchRow(key, cacheId));
|
|
104
|
+
if (webRows.length > 0) {
|
|
105
|
+
lines.push("## Web pages fetched");
|
|
106
|
+
lines.push(...webRows);
|
|
107
|
+
lines.push("");
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const projectRoot = resolveProjectRoot();
|
|
111
|
+
const result = runGit(["diff", "--stat"], { cwd: projectRoot });
|
|
112
|
+
if (result.exitCode === 0 && result.stdout.trim().length > 0) {
|
|
113
|
+
lines.push("## Uncommitted changes (git diff --stat)");
|
|
114
|
+
lines.push(result.stdout.trim().split("\n").map(displaySafeText).join("\n"));
|
|
115
|
+
lines.push("");
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
const text = lines.join("\n");
|
|
120
|
+
if (text.length <= MAX_RESUME_CHARS) return text;
|
|
121
|
+
return safeSlice(text, MAX_RESUME_CHARS) + "\n... (truncated to cap)\n";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
MAX_RESUME_TOKENS,
|
|
126
|
+
MAX_RESUME_CHARS,
|
|
127
|
+
buildResumePacket
|
|
128
|
+
};
|