token-goat 2.9.18 → 2.9.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +5 -1
  2. package/dist/token-goat-chunk-2CLWWRFR.mjs +26 -0
  3. package/dist/{token-goat-chunk-LTYWLKAG.mjs → token-goat-chunk-2Q3N5KUQ.mjs} +1283 -1012
  4. package/dist/{token-goat-chunk-6LGVCCMY.mjs → token-goat-chunk-3OGZOFLS.mjs} +26 -16
  5. package/dist/{token-goat-chunk-QML7ITHJ.mjs → token-goat-chunk-3VTJC7KU.mjs} +693 -438
  6. package/dist/{token-goat-chunk-QIDLJGXZ.mjs → token-goat-chunk-6LJNM4ZZ.mjs} +29 -5
  7. package/dist/{token-goat-chunk-CA6V36KW.mjs → token-goat-chunk-74ADIZZX.mjs} +68 -50
  8. package/dist/{token-goat-chunk-52IMXJ2L.mjs → token-goat-chunk-ASTBLE2R.mjs} +4 -4
  9. package/dist/token-goat-chunk-AU2K2QHR.mjs +20 -0
  10. package/dist/token-goat-chunk-BQIC3AVL.mjs +292 -0
  11. package/dist/token-goat-chunk-CBXLE54Q.mjs +30 -0
  12. package/dist/{token-goat-chunk-LMFO66YD.mjs → token-goat-chunk-DA43OE7L.mjs} +1 -1
  13. package/dist/{token-goat-chunk-ARNRFR6M.mjs → token-goat-chunk-DNAF26L4.mjs} +1 -1
  14. package/dist/{token-goat-chunk-BFEWOMA3.mjs → token-goat-chunk-DPHJU3P7.mjs} +1186 -4573
  15. package/dist/{token-goat-chunk-OWSJJY5R.mjs → token-goat-chunk-DW4VKU7D.mjs} +5 -5
  16. package/dist/token-goat-chunk-DYUBZZNP.mjs +198 -0
  17. package/dist/{token-goat-chunk-W3E65QOW.mjs → token-goat-chunk-ERGHDBC6.mjs} +310 -47
  18. package/dist/token-goat-chunk-EUZKG7ZO.mjs +304 -0
  19. package/dist/{token-goat-chunk-5MMJAJF3.mjs → token-goat-chunk-FWYZVS3E.mjs} +324 -31
  20. package/dist/token-goat-chunk-G6SYQQO7.mjs +41 -0
  21. package/dist/{token-goat-chunk-YQZE5NS6.mjs → token-goat-chunk-GIPMOGOI.mjs} +6 -6
  22. package/dist/{token-goat-chunk-BWCRC23L.mjs → token-goat-chunk-H4VC5TR6.mjs} +29 -20
  23. package/dist/{token-goat-chunk-V3ZWBEHM.mjs → token-goat-chunk-I5PQRHFE.mjs} +103 -24
  24. package/dist/token-goat-chunk-ITWYDOKP.mjs +341 -0
  25. package/dist/{token-goat-chunk-I67CPTDD.mjs → token-goat-chunk-K3ZNBN4U.mjs} +19 -14
  26. package/dist/{token-goat-chunk-HPOFR6QX.mjs → token-goat-chunk-K5XPIUVB.mjs} +222 -61
  27. package/dist/token-goat-chunk-KT75DZK4.mjs +1989 -0
  28. package/dist/{token-goat-chunk-H3FONFBO.mjs → token-goat-chunk-MNJWG2ZO.mjs} +3 -3
  29. package/dist/{token-goat-chunk-GRI6HWX3.mjs → token-goat-chunk-MUJV7BR3.mjs} +1 -1
  30. package/dist/{token-goat-chunk-ERTXEKB6.mjs → token-goat-chunk-NMTKNYGF.mjs} +2 -0
  31. package/dist/{token-goat-chunk-LA3O4JGD.mjs → token-goat-chunk-ODF7H7WS.mjs} +3 -3
  32. package/dist/{token-goat-chunk-K4DG6EEF.mjs → token-goat-chunk-ONMU7BON.mjs} +7 -8
  33. package/dist/{token-goat-chunk-4EXFN2AW.mjs → token-goat-chunk-OZRSREKR.mjs} +2 -2
  34. package/dist/{token-goat-chunk-EPXNOKIV.mjs → token-goat-chunk-P4XBHZXU.mjs} +2 -2
  35. package/dist/{token-goat-chunk-CTOYJ232.mjs → token-goat-chunk-PAVWB4L4.mjs} +173 -1840
  36. package/dist/{token-goat-chunk-UENEL6KX.mjs → token-goat-chunk-PTW22MRC.mjs} +2 -2
  37. package/dist/token-goat-chunk-SL454EN5.mjs +1816 -0
  38. package/dist/token-goat-chunk-SP5QR2HN.mjs +1379 -0
  39. package/dist/{token-goat-chunk-7VLWNQDQ.mjs → token-goat-chunk-T46UKRFM.mjs} +1 -1
  40. package/dist/{token-goat-chunk-L7VCX33I.mjs → token-goat-chunk-T63VHOL3.mjs} +40 -10
  41. package/dist/token-goat-chunk-VSJGPEBB.mjs +248 -0
  42. package/dist/{token-goat-chunk-HIZXQVNU.mjs → token-goat-chunk-VYXGHGUY.mjs} +4 -4
  43. package/dist/token-goat-chunk-W6QYCWAT.mjs +128 -0
  44. package/dist/{token-goat-chunk-WFX2V3OF.mjs → token-goat-chunk-Z4ALQADF.mjs} +579 -422
  45. package/dist/token-goat-chunk-ZHFM4DHJ.mjs +324 -0
  46. package/dist/token-goat-hook.mjs +18 -12
  47. package/dist/token-goat.core.mjs +29 -20
  48. package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
  49. package/docs/cli.md +15 -13
  50. package/docs/install.md +1 -1
  51. package/docs/security.md +2 -2
  52. package/package.json +3 -2
  53. package/dist/token-goat-chunk-2KTZ6J7T.mjs +0 -173
  54. package/dist/token-goat-chunk-34OUJ2IE.mjs +0 -35
  55. package/dist/token-goat-chunk-RHHFEXKF.mjs +0 -93
@@ -12,7 +12,7 @@ import {
12
12
  } from "./token-goat-chunk-AH6QILZM.mjs";
13
13
  import {
14
14
  pushAll
15
- } from "./token-goat-chunk-QIDLJGXZ.mjs";
15
+ } from "./token-goat-chunk-6LJNM4ZZ.mjs";
16
16
  import {
17
17
  DocumentRefusedError,
18
18
  MAX_DOCUMENT_WORK_MILLIS
@@ -15,10 +15,10 @@ import {
15
15
  foldCase,
16
16
  foldPath,
17
17
  sleepSync
18
- } from "./token-goat-chunk-QIDLJGXZ.mjs";
18
+ } from "./token-goat-chunk-6LJNM4ZZ.mjs";
19
19
  import {
20
20
  safeJoin
21
- } from "./token-goat-chunk-ERTXEKB6.mjs";
21
+ } from "./token-goat-chunk-NMTKNYGF.mjs";
22
22
  import {
23
23
  init_define_import_meta_env
24
24
  } from "./token-goat-chunk-A37V4PBF.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" },
@@ -2044,12 +2048,29 @@ CREATE INDEX IF NOT EXISTS idx_cache_recall_type ON cache_recall(cache_type);
2044
2048
  -- emission event (a hook returning a 'context' HookOutput classified as a discretionary
2045
2049
  -- efficiency nudge, as opposed to a mandatory informational injection -- see hint_stats.ts's
2046
2050
  -- doc comment for the exact category list and what is deliberately excluded). correlator is a
2047
- -- best-effort file-path/output-id substring extracted from the hint's own text, used to check
2051
+ -- file-path/output-id pointer the hint carries, used to check
2048
2052
  -- whether a later Bash tool call in the same session actually followed the hint's specific
2049
- -- pointer (see resolvePendingHintsForEvent) -- NULL when no such pointer could be extracted,
2050
- -- in which case the row is inserted already resolved with acted_on=0 (counted as emitted, never
2051
- -- eligible for auto-detected credit). calls_remaining is the countdown of subsequent tool-use
2052
- -- events still eligible to resolve this row before it is considered timed out.
2053
+ -- pointer (see resolvePendingHintsForEvent). Supplied by the hint builder itself via
2054
+ -- HookOutput's correlators field wherever one exists, and only fallen back to a regex scrape
2055
+ -- of the rendered hint text (hint_stats.ts's extractPathCorrelator, whose doc comment records
2056
+ -- what that scrape actually measured) where it does not. A row can name SEVERAL files -- the
2057
+ -- sed/awk line-range hint joins one per file into one emission -- stored newline-separated and
2058
+ -- matched on any member; see joinCorrelators. NULL when no pointer could be supplied or extracted,
2059
+ -- in which case the row is inserted already resolved and observable=0: there is nothing a later
2060
+ -- command could do that this row would recognize, so no verdict about it was ever observed.
2061
+ -- observable=1 is the normal case. Efficacy counts only observable rows, numerator and denominator
2062
+ -- both, because an unobservable row's acted_on is a fabrication in whichever direction the code
2063
+ -- picked and averaging fabrications into a measured rate is how 174 of bash_redirect's 698 rows
2064
+ -- came to hold its efficacy down to 0.86% when the observed figure was 1.15%. calls_remaining is
2065
+ -- the countdown of subsequent tool-use events still eligible to resolve this row before it is
2066
+ -- considered timed out. displayed=0 marks a detection that never reached the agent -- auto-
2067
+ -- suppressed, or declined by a hint's own net-benefit gate -- recorded so the ledger can answer
2068
+ -- how often a hint fired at all, which it previously could not: a suppressed detection left no
2069
+ -- trace, so a fully-muted category was indistinguishable from one that had stopped triggering.
2070
+ -- Such a row is always observable=0 and bytes_emitted=0: nothing was shown, so nothing was spent
2071
+ -- and no verdict was available. It shares hint_emissions' 180-day retention (pruneHintEmissions
2072
+ -- in stats.ts deletes on emitted_at alone), deliberately unlike hint_suppression_probes, which is
2073
+ -- one upserted row per key rather than a growing log and is therefore never pruned.
2053
2074
  CREATE TABLE IF NOT EXISTS hint_emissions (
2054
2075
  id INTEGER PRIMARY KEY AUTOINCREMENT,
2055
2076
  category TEXT NOT NULL,
@@ -2060,7 +2081,9 @@ CREATE TABLE IF NOT EXISTS hint_emissions (
2060
2081
  resolved INTEGER NOT NULL DEFAULT 0,
2061
2082
  acted_on INTEGER NOT NULL DEFAULT 0,
2062
2083
  calls_remaining INTEGER NOT NULL DEFAULT 0,
2063
- bytes_emitted INTEGER
2084
+ bytes_emitted INTEGER,
2085
+ observable INTEGER NOT NULL DEFAULT 1,
2086
+ displayed INTEGER NOT NULL DEFAULT 1
2064
2087
  );
2065
2088
  CREATE INDEX IF NOT EXISTS idx_hint_emissions_category ON hint_emissions(category);
2066
2089
  CREATE INDEX IF NOT EXISTS idx_hint_emissions_session_pending ON hint_emissions(session_id, resolved);
@@ -2206,7 +2229,7 @@ CREATE TRIGGER IF NOT EXISTS cache_recall_au AFTER UPDATE ON cache_recall BEGIN
2206
2229
  VALUES (new.row_id, new.label, new.content);
2207
2230
  END;
2208
2231
  `;
2209
- var SCHEMA_VERSION = 14;
2232
+ var SCHEMA_VERSION = 16;
2210
2233
  function alterTableIdempotent(conn, sql) {
2211
2234
  try {
2212
2235
  conn.exec(sql);
@@ -2262,7 +2285,14 @@ var MIGRATIONS = {
2262
2285
  // v12 -> v13: adds files.parser_sha, the digest of the extraction logic that produced this file's rows, tracked separately from files.sha for the same reason embed_sha is -- content freshness and parse freshness are different questions, and the content sha alone could only ever answer the first. A pre-existing v12 database's `files` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has it from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, same pattern as v1 -> v2 / v2 -> v3 / v8 -> v9 / v9 -> v10 above. Deliberately left NULL for every existing row rather than backfilled with the current fingerprint: NULL is the truthful answer (nobody recorded which parser wrote those rows), and it is also the answer that makes the freshness gates reparse them once, which is exactly what a database indexed by an older parser needs. v13 -> v14: changes both FTS5 tables' tokenizer to `unicode61 remove_diacritics 2`, so a search for `Noi` or `Viet` finds `Hà Nội` and `Việt Nam` -- combining marks that `remove_diacritics 1`, FTS5's default, leaves in place. This is the first schema change that `CREATE VIRTUAL TABLE IF NOT EXISTS` cannot express at all rather than merely cannot express on a populated table: against an existing virtual table that statement is a silent no-op, so without MIGRATIONS[13] the new tokenizer would reach only databases created after this release. The step drops both tables, re-runs FTS_SQL to re-create them at the current declaration, and rebuilds each from its content table.
2263
2286
  12: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN parser_sha TEXT"),
2264
2287
  // v13 -> v14: re-creates both FTS5 tables at the tokenizer FTS_SQL currently declares (see the SCHEMA_VERSION comment above for why no `IF NOT EXISTS` form can do this).
2265
- 13: rebuildFtsAtCurrentTokenizer
2288
+ 13: rebuildFtsAtCurrentTokenizer,
2289
+ // v14 -> v15: adds hint_emissions.observable and backfills it from the column that always implied it. A pre-existing v14 database's `hint_emissions` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has it from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, same pattern as v8 -> v9 / v9 -> v10 / v12 -> v13 above. The backfill is exact rather than a guess: until this version a NULL correlator was the only way a row could be born with nothing to wait for, so `correlator IS NULL` identifies precisely the population the new column names. It runs unconditionally after the ALTER because alterTableIdempotent swallows the duplicate-column case, and re-running the UPDATE on an already-migrated database sets the same rows to the same values.
2290
+ 14: (conn) => {
2291
+ alterTableIdempotent(conn, "ALTER TABLE hint_emissions ADD COLUMN observable INTEGER NOT NULL DEFAULT 1");
2292
+ conn.prepare("UPDATE hint_emissions SET observable = 0 WHERE correlator IS NULL").run();
2293
+ },
2294
+ // v15 -> v16: adds hint_emissions.displayed (see the SCHEMA_VERSION comment above for why). A pre-existing v15 database's `hint_emissions` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has it from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, same pattern as v8 -> v9 / v9 -> v10 / v12 -> v13 / v14 -> v15 above. No backfill, unlike v14 -> v15: until this version a row was only ever written when a hint was actually shown, so the column's own DEFAULT 1 is already the truth for every existing row, and an UPDATE would be a no-op dressed up as a migration step.
2295
+ 15: (conn) => alterTableIdempotent(conn, "ALTER TABLE hint_emissions ADD COLUMN displayed INTEGER NOT NULL DEFAULT 1")
2266
2296
  };
2267
2297
  function runMigrations(conn, fromVersion, toVersion) {
2268
2298
  for (let v = fromVersion; v < toVersion; v++) {
@@ -0,0 +1,248 @@
1
+ import { createRequire as __cjsRequire } from 'node:module';
2
+ const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ WEB_FETCH_KEY_SEP,
5
+ computeAdaptiveBudget,
6
+ getContextPressure,
7
+ getSessionBashOutputs,
8
+ getSessionBashReruns,
9
+ getSessionFiles,
10
+ getSessionWebFetches,
11
+ isNoisePath,
12
+ listSiblingSessionStates,
13
+ loadSessionCache
14
+ } from "./token-goat-chunk-KT75DZK4.mjs";
15
+ import {
16
+ getBashOutput
17
+ } from "./token-goat-chunk-SL454EN5.mjs";
18
+ import {
19
+ loadConfig
20
+ } from "./token-goat-chunk-74ADIZZX.mjs";
21
+ import {
22
+ foldPath,
23
+ runGit,
24
+ toKB
25
+ } from "./token-goat-chunk-6LJNM4ZZ.mjs";
26
+ import {
27
+ displaySafePath,
28
+ displaySafeText,
29
+ neutralizeSpokenMarkers
30
+ } from "./token-goat-chunk-NMTKNYGF.mjs";
31
+ import {
32
+ init_define_import_meta_env
33
+ } from "./token-goat-chunk-A37V4PBF.mjs";
34
+
35
+ // src/manifest.ts
36
+ init_define_import_meta_env();
37
+ import { spawnSync } from "node:child_process";
38
+ var MEM_EPOCH_TIMEOUT_MS = 800;
39
+ var MAX_ROWS = 40;
40
+ function appendCappedSection(lines, header, rows, cap) {
41
+ if (rows.length === 0) return;
42
+ lines.push("");
43
+ lines.push(header);
44
+ for (const row of rows.slice(0, cap)) lines.push(row);
45
+ if (rows.length > cap) lines.push(`- ...and ${rows.length - cap} more`);
46
+ }
47
+ function renderReadRow(entry) {
48
+ const kb = Math.max(1, toKB(entry.sizeBytes));
49
+ const plural = entry.readCount === 1 ? "read" : "reads";
50
+ const edited = entry.wasEdited ? ", edited" : "";
51
+ return `- ${displaySafePath(entry.path)} (${kb}kb, ${entry.readCount} ${plural}${edited})`;
52
+ }
53
+ function renderSymbolReadRow(entry) {
54
+ return `- ${displaySafePath(entry.path)} (symbols: ${(entry.symbols_read ?? []).map(displaySafeText).join(", ")})`;
55
+ }
56
+ function mergeManifestFiles(parent, siblingFiles) {
57
+ const byPath = /* @__PURE__ */ new Map();
58
+ for (const f of parent) byPath.set(foldPath(f.path), f);
59
+ for (const f of siblingFiles) {
60
+ const key = foldPath(f.path);
61
+ const prev = byPath.get(key);
62
+ if (prev === void 0) {
63
+ byPath.set(key, f);
64
+ continue;
65
+ }
66
+ const mergedSymbols = [.../* @__PURE__ */ new Set([...prev.symbols_read ?? [], ...f.symbols_read ?? []])];
67
+ byPath.set(key, {
68
+ path: prev.path,
69
+ readCount: Math.max(prev.readCount, f.readCount),
70
+ lastReadAt: Math.max(prev.lastReadAt, f.lastReadAt),
71
+ wasEdited: prev.wasEdited || f.wasEdited,
72
+ sizeBytes: f.lastReadAt >= prev.lastReadAt ? f.sizeBytes : prev.sizeBytes,
73
+ ...prev.wasTruncated || f.wasTruncated ? { wasTruncated: true } : {},
74
+ ...mergedSymbols.length > 0 ? { symbols_read: mergedSymbols } : {}
75
+ });
76
+ }
77
+ return Array.from(byPath.values());
78
+ }
79
+ function renderWebFetchRow(key, cacheId) {
80
+ const [url = key, prompt = ""] = key.split(WEB_FETCH_KEY_SEP);
81
+ const promptSuffix = prompt ? `, prompt: ${neutralizeSpokenMarkers(JSON.stringify(prompt))}` : "";
82
+ return `- ${displaySafeText(url)} (cacheId: ${cacheId}${promptSuffix})`;
83
+ }
84
+ function selectManifestFiles(siblings) {
85
+ const ownFiles = [...getSessionFiles().values()];
86
+ const siblingFiles = siblings.flatMap((s) => s.files);
87
+ const files = siblingFiles.length > 0 ? mergeManifestFiles(ownFiles, siblingFiles) : ownFiles;
88
+ const editedFiles = files.filter((f) => f.wasEdited);
89
+ const readFiles = files.filter((f) => f.readCount > 0 && !f.wasEdited && !isNoisePath(f.path));
90
+ const symbolOnlyFiles = files.filter((f) => f.readCount === 0 && !f.wasEdited && (f.symbols_read?.length ?? 0) > 0);
91
+ return { files, readFiles, editedFiles, symbolOnlyFiles };
92
+ }
93
+ function selectManifestWebFetches(siblings) {
94
+ const merged = /* @__PURE__ */ new Map();
95
+ for (const sibling of siblings) for (const [key, cacheId] of sibling.webFetches) merged.set(key, cacheId);
96
+ for (const [key, cacheId] of getSessionWebFetches()) merged.set(key, cacheId);
97
+ return [...merged.entries()];
98
+ }
99
+ function manifestPrintedPaths(sessionId, limit, cwd, transcriptPath) {
100
+ const { printed } = buildManifestParts(sessionId, cwd, transcriptPath);
101
+ return printed.slice(0, limit);
102
+ }
103
+ function buildManifestParts(sessionId, cwd, transcriptPath) {
104
+ const siblings = sessionId !== void 0 ? listSiblingSessionStates(sessionId) : [];
105
+ const { files, readFiles, editedFiles, symbolOnlyFiles } = selectManifestFiles(siblings);
106
+ const webFetches = selectManifestWebFetches(siblings);
107
+ const lines = [];
108
+ lines.push("## Session context");
109
+ lines.push(`Files read: ${readFiles.length}`);
110
+ lines.push(`Files edited: ${editedFiles.length}`);
111
+ const fileRows = [];
112
+ const appendFileSection = (header, entries, render) => {
113
+ const rows = entries.map(render);
114
+ appendCappedSection(lines, header, rows, MAX_ROWS);
115
+ for (let i = 0; i < Math.min(rows.length, MAX_ROWS); i++) fileRows.push({ path: displaySafePath(entries[i].path), row: rows[i] });
116
+ };
117
+ appendFileSection("### Edited files", editedFiles, (entry) => `- ${displaySafePath(entry.path)}`);
118
+ appendFileSection("### Surgically read files (symbol/section reads, never read whole)", symbolOnlyFiles, renderSymbolReadRow);
119
+ appendFileSection("### Read files", readFiles, renderReadRow);
120
+ appendCappedSection(lines, "### Web URLs fetched", webFetches.map(([key, cacheId]) => renderWebFetchRow(key, cacheId)), MAX_ROWS);
121
+ lines.push(...buildSafeToDiscardSection(files));
122
+ lines.push(...buildMemEpochSection());
123
+ const text = capManifestChars(lines.join("\n"), sessionId, cwd, transcriptPath);
124
+ const seen = /* @__PURE__ */ new Set();
125
+ const printed = [];
126
+ for (const { path, row } of fileRows) {
127
+ if (seen.has(path) || !(text.includes(row + "\n") || text.endsWith(row))) continue;
128
+ seen.add(path);
129
+ printed.push(path);
130
+ }
131
+ return { text, printed };
132
+ }
133
+ function buildManifest(sessionId, cwd, transcriptPath) {
134
+ return buildManifestParts(sessionId, cwd, transcriptPath).text;
135
+ }
136
+ function gitDirtySignals(cwd) {
137
+ const timeoutMs = loadConfig().hints.git_hint_max_ms;
138
+ let hasPendingDiff = false;
139
+ try {
140
+ const diffResult = runGit(["diff", "--no-color", "--stat", "HEAD"], { cwd, timeoutMs });
141
+ hasPendingDiff = diffResult.exitCode === 0 && diffResult.stdout.trim() !== "";
142
+ } catch {
143
+ }
144
+ let hasUncommittedChanges = false;
145
+ try {
146
+ const statusResult = runGit(["status", "--porcelain"], { cwd, timeoutMs });
147
+ hasUncommittedChanges = statusResult.exitCode === 0 && statusResult.stdout.trim() !== "";
148
+ } catch {
149
+ }
150
+ return { hasPendingDiff, hasUncommittedChanges };
151
+ }
152
+ function adaptiveCharBonus(sessionId, cwd, transcriptPath) {
153
+ if (!cwd) return 0;
154
+ const cache = loadSessionCache(sessionId ?? "") ?? {};
155
+ const ageSecs = cache.created_ts !== void 0 ? Math.max(0, Date.now() / 1e3 - cache.created_ts) : 0;
156
+ const contextPressure = getContextPressure(cache, transcriptPath);
157
+ const { hasPendingDiff, hasUncommittedChanges } = gitDirtySignals(cwd);
158
+ if (!hasPendingDiff && !hasUncommittedChanges) return 0;
159
+ const baseline = computeAdaptiveBudget(cache, ageSecs, { contextPressure });
160
+ const withGitSignal = computeAdaptiveBudget(cache, ageSecs, { hasPendingDiff, hasUncommittedChanges, contextPressure });
161
+ const deltaTokens = Math.max(0, withGitSignal - baseline);
162
+ return deltaTokens * 3;
163
+ }
164
+ function capManifestChars(manifest, sessionId, cwd, transcriptPath) {
165
+ const cap = loadConfig().compact_assist.max_manifest_chars;
166
+ if (cap <= 0) return manifest;
167
+ if (manifest.length <= cap) return manifest;
168
+ const effectiveCap = cap + adaptiveCharBonus(sessionId, cwd, transcriptPath);
169
+ if (manifest.length <= effectiveCap) return manifest;
170
+ const omitted = manifest.length - effectiveCap;
171
+ return manifest.slice(0, effectiveCap) + `
172
+ ...(manifest truncated at ${effectiveCap} chars; ${omitted} chars omitted)`;
173
+ }
174
+ function buildSafeToDiscardSection(files) {
175
+ const rerunHashes = getSessionBashReruns();
176
+ const bashOutputs = getSessionBashOutputs();
177
+ const rerunHashSet = new Set(rerunHashes);
178
+ const rerunRows = [];
179
+ for (const hash of rerunHashes) {
180
+ const id = bashOutputs.find(([h]) => h === hash)?.[1];
181
+ if (id === void 0) continue;
182
+ const entry = getBashOutput(id);
183
+ if (entry === null) continue;
184
+ const flatCommand = neutralizeSpokenMarkers(entry.command.replace(/[\t\r\n]+/g, " "));
185
+ rerunRows.push("- `" + flatCommand + "` \u2014 an older run of this exact command was superseded; recall the surviving copy with `bash-output " + id + " --full`");
186
+ }
187
+ const supersededReadRows = [];
188
+ for (const f of files) {
189
+ if (f.readCount > 1 || f.wasEdited) {
190
+ const reason = f.wasEdited ? "edited after being read" : "re-read " + f.readCount + "x";
191
+ supersededReadRows.push("- " + displaySafePath(f.path) + " (" + reason + " \u2014 only the latest content already in context is current)");
192
+ }
193
+ }
194
+ const cachedOutputRows = [];
195
+ for (const [hash, id] of bashOutputs) {
196
+ if (rerunHashSet.has(hash)) continue;
197
+ const entry = getBashOutput(id);
198
+ if (entry === null) continue;
199
+ const flatCommand = neutralizeSpokenMarkers(entry.command.replace(/[\t\r\n]+/g, " "));
200
+ cachedOutputRows.push("- `" + flatCommand + "` \u2014 recallable via `bash-output " + id + " --full`");
201
+ }
202
+ const total = rerunRows.length + supersededReadRows.length + cachedOutputRows.length;
203
+ if (total === 0) return [];
204
+ const lines = [];
205
+ lines.push("");
206
+ lines.push("### SAFE_TO_DISCARD (" + total + " items \u2014 provably inert; each is recallable, not gone)");
207
+ appendCappedSection(lines, "Superseded reruns (" + rerunRows.length + "):", rerunRows, MAX_ROWS);
208
+ appendCappedSection(lines, "Superseded file reads (" + supersededReadRows.length + "):", supersededReadRows, MAX_ROWS);
209
+ appendCappedSection(lines, "Other cached bash outputs (" + cachedOutputRows.length + "):", cachedOutputRows, MAX_ROWS);
210
+ return lines;
211
+ }
212
+ function buildMemEpochSection() {
213
+ let result;
214
+ try {
215
+ result = spawnSync("mem", ["epoch"], {
216
+ encoding: "utf-8",
217
+ timeout: MEM_EPOCH_TIMEOUT_MS,
218
+ windowsHide: true
219
+ });
220
+ } catch {
221
+ return [];
222
+ }
223
+ if (result.error !== void 0 || result.status !== 0) return [];
224
+ const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
225
+ if (!/^\d+$/.test(stdout)) return [];
226
+ return [
227
+ "",
228
+ "### mem epoch",
229
+ `mem epoch: ${stdout} (no live TGMEM block is tracked in this session -- only the epoch counter is folded in; see buildMemEpochSection's doc comment)`
230
+ ];
231
+ }
232
+ var MANIFEST_PREAMBLE = "When summarizing this session, keep the file paths and symbol names below exactly as written -- they are the handles the next turn needs to resume work. Do not paraphrase them into prose.";
233
+ var MANIFEST_RECOVERY_PREAMBLE = "The conversation was just compacted. Below is what this session touched, recovered from token-goat's own ledger rather than from the summary -- use it to re-establish paths and symbol names the summary may have dropped.";
234
+ var BUDGET_ESCALATION_MARKER = "TG-BUDGET-ESCALATION:";
235
+ function summaryBudgetDirective(budgetChars) {
236
+ if (budgetChars <= 0) return "";
237
+ return ` Aim for at most ${budgetChars} characters. Prefer a shorter summary that keeps every path, symbol, command and decision over a longer one that reproduces tool output verbatim: cite the recall id (\`token-goat bash-output <id> --full\`) rather than quoting the output again. If this session genuinely cannot be summarized within that target without losing state the next turn needs, exceed it and open the summary with a single line reading \`${BUDGET_ESCALATION_MARKER} <one-sentence reason>\`.`;
238
+ }
239
+
240
+ export {
241
+ renderWebFetchRow,
242
+ manifestPrintedPaths,
243
+ buildManifest,
244
+ MANIFEST_PREAMBLE,
245
+ MANIFEST_RECOVERY_PREAMBLE,
246
+ BUDGET_ESCALATION_MARKER,
247
+ summaryBudgetDirective
248
+ };
@@ -5,12 +5,12 @@ import {
5
5
  docxTables,
6
6
  docxText,
7
7
  formatDocxTables
8
- } from "./token-goat-chunk-UENEL6KX.mjs";
9
- import "./token-goat-chunk-7VLWNQDQ.mjs";
8
+ } from "./token-goat-chunk-PTW22MRC.mjs";
9
+ import "./token-goat-chunk-T46UKRFM.mjs";
10
10
  import "./token-goat-chunk-XTQAOTSO.mjs";
11
11
  import "./token-goat-chunk-AH6QILZM.mjs";
12
- import "./token-goat-chunk-QIDLJGXZ.mjs";
13
- import "./token-goat-chunk-ERTXEKB6.mjs";
12
+ import "./token-goat-chunk-6LJNM4ZZ.mjs";
13
+ import "./token-goat-chunk-NMTKNYGF.mjs";
14
14
  import "./token-goat-chunk-Y4AFKTHK.mjs";
15
15
  import "./token-goat-chunk-GMOUBOX4.mjs";
16
16
  import "./token-goat-chunk-A37V4PBF.mjs";
@@ -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-VSJGPEBB.mjs";
6
+ import {
7
+ SESSIONS_SUBDIR,
8
+ extractChecklistSection,
9
+ getSkillFilePath,
10
+ listSkills
11
+ } from "./token-goat-chunk-KT75DZK4.mjs";
12
+ import {
13
+ getBashOutput
14
+ } from "./token-goat-chunk-SL454EN5.mjs";
15
+ import {
16
+ loadBlob
17
+ } from "./token-goat-chunk-K5XPIUVB.mjs";
18
+ import {
19
+ resolveProjectRoot
20
+ } from "./token-goat-chunk-74ADIZZX.mjs";
21
+ import {
22
+ runGit,
23
+ safeSlice
24
+ } from "./token-goat-chunk-6LJNM4ZZ.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
+ };