token-goat 2.9.13 → 2.9.15
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 +28 -1
- package/dist/token-goat-chunk-2X2EBBC6.mjs +277 -0
- package/dist/token-goat-chunk-3BTK54F3.mjs +1733 -0
- package/dist/token-goat-chunk-3NSDDTGL.mjs +34 -0
- package/dist/token-goat-chunk-4EXFN2AW.mjs +29 -0
- package/dist/token-goat-chunk-5V7DAC7V.mjs +123 -0
- package/dist/token-goat-chunk-6DLVZDB6.mjs +34 -0
- package/dist/token-goat-chunk-7ZYK25AO.mjs +24 -0
- package/dist/token-goat-chunk-A4JYKD5H.mjs +144 -0
- package/dist/token-goat-chunk-AH6QILZM.mjs +26 -0
- package/dist/token-goat-chunk-ASYEPR3S.mjs +212 -0
- package/dist/{token-goat-chunk-RDITECDL.mjs → token-goat-chunk-ATIFTMRC.mjs} +31 -13
- package/dist/{token-goat-chunk-ZZI3IDQZ.mjs → token-goat-chunk-BL5LNGBG.mjs} +3935 -11949
- package/dist/{token-goat-chunk-4NXUKV7D.mjs → token-goat-chunk-C5JIO6HK.mjs} +8 -4
- package/dist/{token-goat-chunk-FZU7GMUS.mjs → token-goat-chunk-DQ4J5AFF.mjs} +50 -18
- package/dist/token-goat-chunk-ERTXEKB6.mjs +228 -0
- package/dist/{token-goat-chunk-6B44WLIF.mjs → token-goat-chunk-GIIHUSZX.mjs} +142 -21
- package/dist/token-goat-chunk-GMOUBOX4.mjs +386 -0
- package/dist/{token-goat-chunk-3ESRORNM.mjs → token-goat-chunk-GMQQA7E4.mjs} +12576 -12326
- package/dist/token-goat-chunk-IVUQLQWN.mjs +2046 -0
- package/dist/token-goat-chunk-K7F2BFIK.mjs +2430 -0
- package/dist/{token-goat-chunk-U7X6LQD2.mjs → token-goat-chunk-LCZBPOIN.mjs} +10197 -9717
- package/dist/token-goat-chunk-LMFO66YD.mjs +331 -0
- package/dist/token-goat-chunk-LT7JRU6K.mjs +22 -0
- package/dist/token-goat-chunk-MZDIJJ3R.mjs +420 -0
- package/dist/token-goat-chunk-NDPO7GAH.mjs +177 -0
- package/dist/token-goat-chunk-NDRP4KJQ.mjs +4371 -0
- package/dist/token-goat-chunk-NEI4NC54.mjs +424 -0
- package/dist/token-goat-chunk-NU7TLMQK.mjs +585 -0
- package/dist/token-goat-chunk-OSUFN2FV.mjs +326 -0
- package/dist/token-goat-chunk-OUGNPMDA.mjs +959 -0
- package/dist/token-goat-chunk-PM76YS22.mjs +1341 -0
- package/dist/token-goat-chunk-S4XRY446.mjs +2637 -0
- package/dist/{token-goat-chunk-YOA4N6WA.mjs → token-goat-chunk-SFAS46RE.mjs} +5 -3
- package/dist/{token-goat-chunk-QWSUZWFP.mjs → token-goat-chunk-SZWYESBS.mjs} +793 -650
- package/dist/{token-goat-chunk-B3CTCQTH.mjs → token-goat-chunk-XEPXYDPI.mjs} +3 -2
- package/dist/token-goat-chunk-XTQAOTSO.mjs +89 -0
- package/dist/token-goat-chunk-Y4AFKTHK.mjs +22 -0
- package/dist/token-goat-chunk-YKG35VHC.mjs +228 -0
- package/dist/{token-goat-chunk-JOXLE672.mjs → token-goat-chunk-YQ7WI2CO.mjs} +990 -106
- package/dist/{token-goat-chunk-2WC4ZUXN.mjs → token-goat-chunk-YZX7EFG4.mjs} +1 -1
- package/dist/token-goat-chunk-Z6UXPYJA.mjs +62 -0
- package/dist/token-goat-hook.mjs +16 -8
- package/dist/token-goat.core.mjs +27 -10
- package/docs/cli.md +9 -6
- package/package.json +4 -2
- package/dist/token-goat-chunk-FQCNJV4V.mjs +0 -693
- package/dist/token-goat-chunk-JVNPCQB7.mjs +0 -31
- package/dist/token-goat-chunk-P2PU4CR5.mjs +0 -26
- package/dist/token-goat-chunk-QKXBGBQR.mjs +0 -3653
- package/dist/token-goat-chunk-UM47DRD3.mjs +0 -242
- package/dist/token-goat-chunk-UMXJN7DI.mjs +0 -5521
|
@@ -1,693 +0,0 @@
|
|
|
1
|
-
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
-
const require = __cjsRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
PARSER_FINGERPRINT,
|
|
5
|
-
enqueueDirtyPathSafe,
|
|
6
|
-
getProjectFileEntries,
|
|
7
|
-
getTrackedFiles,
|
|
8
|
-
indexRecallEntry
|
|
9
|
-
} from "./token-goat-chunk-ZZI3IDQZ.mjs";
|
|
10
|
-
import {
|
|
11
|
-
estimateTokensFromLength
|
|
12
|
-
} from "./token-goat-chunk-JOXLE672.mjs";
|
|
13
|
-
import {
|
|
14
|
-
isBlobStale,
|
|
15
|
-
loadBlob,
|
|
16
|
-
storeBlob
|
|
17
|
-
} from "./token-goat-chunk-UM47DRD3.mjs";
|
|
18
|
-
import {
|
|
19
|
-
fingerprintFile,
|
|
20
|
-
getDb,
|
|
21
|
-
getDisplayRoot,
|
|
22
|
-
redactSecrets,
|
|
23
|
-
shortFingerprint
|
|
24
|
-
} from "./token-goat-chunk-UMXJN7DI.mjs";
|
|
25
|
-
import {
|
|
26
|
-
registerReset
|
|
27
|
-
} from "./token-goat-chunk-EEIDFMEM.mjs";
|
|
28
|
-
import {
|
|
29
|
-
SYMBOL_BODY_CHAR_CAP,
|
|
30
|
-
compileGuardedRegex,
|
|
31
|
-
countNoun,
|
|
32
|
-
displaySafeJson,
|
|
33
|
-
displaySafeText,
|
|
34
|
-
extractErrorMessage,
|
|
35
|
-
foldPath,
|
|
36
|
-
globalDbPath,
|
|
37
|
-
normalizePath,
|
|
38
|
-
resolveIndexPath,
|
|
39
|
-
toDisplayPath
|
|
40
|
-
} from "./token-goat-chunk-QKXBGBQR.mjs";
|
|
41
|
-
import {
|
|
42
|
-
init_define_import_meta_env
|
|
43
|
-
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
44
|
-
|
|
45
|
-
// src/web_cache.ts
|
|
46
|
-
init_define_import_meta_env();
|
|
47
|
-
var WEB_OUTPUT_SUBDIR = "web_outputs";
|
|
48
|
-
var _byId = /* @__PURE__ */ new Map();
|
|
49
|
-
var _rawById = /* @__PURE__ */ new Map();
|
|
50
|
-
var _urlIndex = /* @__PURE__ */ new Map();
|
|
51
|
-
function cacheIdForUrl(url) {
|
|
52
|
-
return shortFingerprint(url);
|
|
53
|
-
}
|
|
54
|
-
function storeWebOutput(url, content, dedupKey = url, rawContent) {
|
|
55
|
-
const cacheId = cacheIdForUrl(dedupKey);
|
|
56
|
-
const redactedContent = redactSecrets(content).text;
|
|
57
|
-
_byId.set(cacheId, redactedContent);
|
|
58
|
-
_urlIndex.set(url, cacheId);
|
|
59
|
-
const redactedRaw = rawContent !== void 0 && rawContent !== content ? redactSecrets(rawContent).text : void 0;
|
|
60
|
-
if (redactedRaw !== void 0) _rawById.set(cacheId, redactedRaw);
|
|
61
|
-
else _rawById.delete(cacheId);
|
|
62
|
-
storeBlob(WEB_OUTPUT_SUBDIR, cacheId, redactedRaw !== void 0 ? { url, content: redactedContent, raw: redactedRaw } : { url, content: redactedContent });
|
|
63
|
-
const redactedUrl = redactSecrets(url).text;
|
|
64
|
-
indexRecallEntry("web", cacheId, redactedUrl, `${redactedUrl}
|
|
65
|
-
${redactedContent}`, Date.now());
|
|
66
|
-
return cacheId;
|
|
67
|
-
}
|
|
68
|
-
function coerceWebBlob(raw) {
|
|
69
|
-
if (raw === null || typeof raw !== "object") return null;
|
|
70
|
-
const o = raw;
|
|
71
|
-
if (typeof o["content"] !== "string") return null;
|
|
72
|
-
return { url: typeof o["url"] === "string" ? o["url"] : null, content: o["content"], raw: typeof o["raw"] === "string" ? o["raw"] : null };
|
|
73
|
-
}
|
|
74
|
-
function getWebOutput(cacheId) {
|
|
75
|
-
const hit = _byId.get(cacheId);
|
|
76
|
-
if (hit !== void 0) return hit;
|
|
77
|
-
if (isBlobStale(WEB_OUTPUT_SUBDIR, cacheId)) return null;
|
|
78
|
-
const blob = coerceWebBlob(loadBlob(WEB_OUTPUT_SUBDIR, cacheId));
|
|
79
|
-
if (blob === null) return null;
|
|
80
|
-
_byId.set(cacheId, blob.content);
|
|
81
|
-
if (blob.raw !== null) _rawById.set(cacheId, blob.raw);
|
|
82
|
-
if (blob.url !== null) _urlIndex.set(blob.url, cacheId);
|
|
83
|
-
return blob.content;
|
|
84
|
-
}
|
|
85
|
-
function getWebOutputRaw(cacheId) {
|
|
86
|
-
const cleaned = getWebOutput(cacheId);
|
|
87
|
-
if (cleaned === null) return null;
|
|
88
|
-
return _rawById.get(cacheId) ?? cleaned;
|
|
89
|
-
}
|
|
90
|
-
function getWebOutputByUrlFromDisk(url, dedupKey = url) {
|
|
91
|
-
const cacheId = cacheIdForUrl(dedupKey);
|
|
92
|
-
const content = getWebOutput(cacheId);
|
|
93
|
-
if (content === null) return null;
|
|
94
|
-
return { cacheId, content };
|
|
95
|
-
}
|
|
96
|
-
registerReset(() => {
|
|
97
|
-
_byId = /* @__PURE__ */ new Map();
|
|
98
|
-
_rawById = /* @__PURE__ */ new Map();
|
|
99
|
-
_urlIndex = /* @__PURE__ */ new Map();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// src/types.ts
|
|
103
|
-
init_define_import_meta_env();
|
|
104
|
-
var HOOK_EVENTS = [
|
|
105
|
-
"pre_tool_use",
|
|
106
|
-
"post_tool_use",
|
|
107
|
-
"notification",
|
|
108
|
-
"stop",
|
|
109
|
-
"pre_compact",
|
|
110
|
-
"post_compact",
|
|
111
|
-
"user_prompt_submit",
|
|
112
|
-
"subagent_stop",
|
|
113
|
-
"session_start",
|
|
114
|
-
"post_tool_use_failure"
|
|
115
|
-
];
|
|
116
|
-
|
|
117
|
-
// src/cli_commands.ts
|
|
118
|
-
init_define_import_meta_env();
|
|
119
|
-
function toEntry(cmd) {
|
|
120
|
-
return {
|
|
121
|
-
name: cmd.name(),
|
|
122
|
-
description: cmd.description(),
|
|
123
|
-
aliases: cmd.aliases(),
|
|
124
|
-
options: cmd.options.map((o) => ({ flags: o.flags, description: o.description })),
|
|
125
|
-
arguments: cmd.registeredArguments.map((a) => ({ name: a.name(), description: a.description, required: a.required })),
|
|
126
|
-
subcommands: cmd.commands.filter((sub) => sub.name() !== "help").map(toEntry)
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
function buildCommandManifest(program) {
|
|
130
|
-
return program.commands.filter((cmd) => cmd.name() !== "help").map(toEntry);
|
|
131
|
-
}
|
|
132
|
-
function flattenCommandNames(manifest) {
|
|
133
|
-
const names = [];
|
|
134
|
-
for (const entry of manifest) {
|
|
135
|
-
names.push(entry.name);
|
|
136
|
-
for (const sub of entry.subcommands) {
|
|
137
|
-
names.push(`${entry.name} ${sub.name}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return names;
|
|
141
|
-
}
|
|
142
|
-
function entryMatches(entry, re, pattern) {
|
|
143
|
-
const haystacks = [entry.name, entry.description, ...entry.aliases];
|
|
144
|
-
return haystacks.some((h) => re !== null ? re.test(h) : h.includes(pattern));
|
|
145
|
-
}
|
|
146
|
-
function filterCommandManifest(manifest, pattern) {
|
|
147
|
-
const guarded = compileGuardedRegex(pattern);
|
|
148
|
-
const re = guarded.ok ? guarded.re : null;
|
|
149
|
-
const result = [];
|
|
150
|
-
for (const entry of manifest) {
|
|
151
|
-
if (entryMatches(entry, re, pattern)) {
|
|
152
|
-
result.push(entry);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
const matchingSubs = entry.subcommands.filter((sub) => entryMatches(sub, re, pattern));
|
|
156
|
-
if (matchingSubs.length > 0) {
|
|
157
|
-
result.push({ ...entry, subcommands: matchingSubs });
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return result;
|
|
161
|
-
}
|
|
162
|
-
function formatCommandManifest(manifest) {
|
|
163
|
-
const lines = [];
|
|
164
|
-
lines.push("# token-goat commands");
|
|
165
|
-
lines.push("");
|
|
166
|
-
for (const entry of manifest) {
|
|
167
|
-
lines.push(`## ${entry.name}${entry.aliases.length ? " (alias: " + entry.aliases.join(", ") + ")" : ""}${entry.description ? " -- " + entry.description : ""}`);
|
|
168
|
-
for (const arg of entry.arguments) {
|
|
169
|
-
lines.push(` arg: ${arg.name}${arg.required ? "" : " (optional)"}${arg.description ? " -- " + arg.description : ""}`);
|
|
170
|
-
}
|
|
171
|
-
for (const opt of entry.options) {
|
|
172
|
-
lines.push(` ${opt.flags}${opt.description ? " -- " + opt.description : ""}`);
|
|
173
|
-
}
|
|
174
|
-
for (const sub of entry.subcommands) {
|
|
175
|
-
lines.push(` ${entry.name} ${sub.name}${sub.description ? " -- " + sub.description : ""}`);
|
|
176
|
-
}
|
|
177
|
-
lines.push("");
|
|
178
|
-
}
|
|
179
|
-
return lines.join("\n").trimEnd();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// src/reconcile.ts
|
|
183
|
-
init_define_import_meta_env();
|
|
184
|
-
import * as fs from "node:fs";
|
|
185
|
-
var DEFAULT_RECONCILE_BUDGET_MS = 1500;
|
|
186
|
-
function readReconcileCursor(dbPath, root) {
|
|
187
|
-
try {
|
|
188
|
-
const row = getDb(dbPath).prepare("SELECT last_scanned_path FROM reconcile_cursor WHERE root = ?").get(root);
|
|
189
|
-
return row?.last_scanned_path ?? null;
|
|
190
|
-
} catch {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
function writeReconcileCursor(dbPath, root, lastScannedPath) {
|
|
195
|
-
try {
|
|
196
|
-
getDb(dbPath).prepare(
|
|
197
|
-
"INSERT INTO reconcile_cursor (root, last_scanned_path, updated_at) VALUES (?, ?, ?) ON CONFLICT(root) DO UPDATE SET last_scanned_path = excluded.last_scanned_path, updated_at = excluded.updated_at"
|
|
198
|
-
).run(root, lastScannedPath, Date.now());
|
|
199
|
-
} catch {
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
function clearReconcileCursor(dbPath, root) {
|
|
203
|
-
try {
|
|
204
|
-
getDb(dbPath).prepare("DELETE FROM reconcile_cursor WHERE root = ?").run(root);
|
|
205
|
-
} catch {
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
function runReconcile(opts = {}) {
|
|
209
|
-
const raw = reconcileProject(opts);
|
|
210
|
-
const root = getDisplayRoot(opts.cwd ?? process.cwd());
|
|
211
|
-
const display = (paths) => paths.map((p) => toDisplayPath(root, p)).sort();
|
|
212
|
-
const result = {
|
|
213
|
-
...raw,
|
|
214
|
-
changed: display(raw.changed),
|
|
215
|
-
added: display(raw.added),
|
|
216
|
-
removed: display(raw.removed)
|
|
217
|
-
};
|
|
218
|
-
if (opts.json === true) {
|
|
219
|
-
process.stdout.write(`${displaySafeJson(result)}
|
|
220
|
-
`);
|
|
221
|
-
return 0;
|
|
222
|
-
}
|
|
223
|
-
const lines = [];
|
|
224
|
-
if (isReconcileClean(result)) {
|
|
225
|
-
if (!result.trackedUnavailable) {
|
|
226
|
-
lines.push(`Index matches disk: ${countNoun(result.scanned, "file")} checked in ${result.elapsedMs}ms.`);
|
|
227
|
-
}
|
|
228
|
-
} else {
|
|
229
|
-
const verb = opts.dryRun === true ? "would reindex" : "queued for reindexing";
|
|
230
|
-
const dropVerb = opts.dryRun === true ? "would drop from the index" : "queued for removal";
|
|
231
|
-
if (result.changed.length > 0) lines.push(`${countNoun(result.changed.length, "file")} changed since indexing (${verb}):`);
|
|
232
|
-
for (const f of result.changed) lines.push(` ~ ${f}`);
|
|
233
|
-
if (result.added.length > 0) lines.push(`${countNoun(result.added.length, "file")} not in the index (${verb}):`);
|
|
234
|
-
for (const f of result.added) lines.push(` + ${f}`);
|
|
235
|
-
if (result.removed.length > 0) lines.push(`${countNoun(result.removed.length, "file")} indexed but gone from disk (${dropVerb}):`);
|
|
236
|
-
for (const f of result.removed) lines.push(` - ${f}`);
|
|
237
|
-
lines.push(`Checked ${countNoun(result.scanned, "file")} in ${result.elapsedMs}ms.`);
|
|
238
|
-
}
|
|
239
|
-
if (result.mtimeOnly > 0) {
|
|
240
|
-
lines.push(`${countNoun(result.mtimeOnly, "file")} had a newer timestamp but identical content, so ${result.mtimeOnly === 1 ? "it was" : "they were"} left alone.`);
|
|
241
|
-
}
|
|
242
|
-
if (result.parserStale > 0) {
|
|
243
|
-
const reparseVerb = opts.dryRun === true ? "would reparse" : "queued for reparse";
|
|
244
|
-
lines.push(`${countNoun(result.parserStale, "file")} of the above ${result.parserStale === 1 ? "was" : "were"} unchanged on disk but indexed by an older parser (${reparseVerb}).`);
|
|
245
|
-
}
|
|
246
|
-
if (result.trackedUnavailable) {
|
|
247
|
-
lines.push("This project has an index but git listed no files in it, so nothing could be compared and no deletions were computed. Run token-goat inside the repository, or reindex with --walk if this directory is deliberately not under git.");
|
|
248
|
-
}
|
|
249
|
-
if (result.budgetExhausted) {
|
|
250
|
-
lines.push(`Stopped at the ${result.elapsedMs}ms budget with ${countNoun(result.unscanned, "file")} unchecked, so there may be more drift; deletions were not computed at all, because an unchecked file is indistinguishable from a deleted one. Raise --budget-ms for a complete sweep.`);
|
|
251
|
-
}
|
|
252
|
-
process.stdout.write(`${lines.join("\n")}
|
|
253
|
-
`);
|
|
254
|
-
return 0;
|
|
255
|
-
}
|
|
256
|
-
function isReconcileClean(r) {
|
|
257
|
-
return r.changed.length === 0 && r.added.length === 0 && r.removed.length === 0;
|
|
258
|
-
}
|
|
259
|
-
function reconcileProject(opts = {}) {
|
|
260
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
261
|
-
const budgetMs = opts.budgetMs ?? DEFAULT_RECONCILE_BUDGET_MS;
|
|
262
|
-
const startedAt = Date.now();
|
|
263
|
-
const dbPath = opts.dbPath ?? globalDbPath();
|
|
264
|
-
const tracked = getTrackedFiles(cwd);
|
|
265
|
-
const projectRoot = resolveIndexPath(".", cwd);
|
|
266
|
-
const indexed = getProjectFileEntries(projectRoot, dbPath);
|
|
267
|
-
let scanOrder = tracked;
|
|
268
|
-
const cursorPath = tracked.length > 0 ? readReconcileCursor(dbPath, projectRoot) : null;
|
|
269
|
-
if (cursorPath !== null) {
|
|
270
|
-
const cursorFolded = foldPath(normalizePath(cursorPath));
|
|
271
|
-
const cursorIdx = tracked.findIndex((f) => foldPath(normalizePath(f)) === cursorFolded);
|
|
272
|
-
if (cursorIdx !== -1) {
|
|
273
|
-
scanOrder = [...tracked.slice(cursorIdx + 1), ...tracked.slice(0, cursorIdx + 1)];
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
const changed = [];
|
|
277
|
-
const added = [];
|
|
278
|
-
const seenOnDisk = /* @__PURE__ */ new Set();
|
|
279
|
-
let mtimeOnly = 0;
|
|
280
|
-
let parserStale = 0;
|
|
281
|
-
let scanned = 0;
|
|
282
|
-
let budgetExhausted = false;
|
|
283
|
-
let lastScanned = null;
|
|
284
|
-
for (const file of scanOrder) {
|
|
285
|
-
if (Date.now() - startedAt > budgetMs) {
|
|
286
|
-
budgetExhausted = true;
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
scanned++;
|
|
290
|
-
lastScanned = file;
|
|
291
|
-
const folded = foldPath(normalizePath(file));
|
|
292
|
-
seenOnDisk.add(folded);
|
|
293
|
-
const entry = indexed.get(folded);
|
|
294
|
-
if (entry === void 0) {
|
|
295
|
-
added.push(file);
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
if (entry.parserSha !== PARSER_FINGERPRINT) {
|
|
299
|
-
changed.push(file);
|
|
300
|
-
parserStale++;
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
let mtimeMs;
|
|
304
|
-
try {
|
|
305
|
-
mtimeMs = fs.statSync(file).mtimeMs;
|
|
306
|
-
} catch {
|
|
307
|
-
changed.push(file);
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
if (entry.mtime !== 0 && mtimeMs === entry.mtime) continue;
|
|
311
|
-
const diskSha = fingerprintFile(file);
|
|
312
|
-
if (diskSha === null) {
|
|
313
|
-
changed.push(file);
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
if (diskSha === entry.sha) {
|
|
317
|
-
mtimeOnly++;
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
changed.push(file);
|
|
321
|
-
}
|
|
322
|
-
const trackedUnavailable = tracked.length === 0 && indexed.size > 0;
|
|
323
|
-
const removed = [];
|
|
324
|
-
if (!budgetExhausted && !trackedUnavailable) {
|
|
325
|
-
for (const [folded, entry] of indexed) {
|
|
326
|
-
if (seenOnDisk.has(folded)) continue;
|
|
327
|
-
if (Date.now() - startedAt > budgetMs) {
|
|
328
|
-
budgetExhausted = true;
|
|
329
|
-
removed.length = 0;
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
try {
|
|
333
|
-
fs.statSync(entry.filePath);
|
|
334
|
-
} catch {
|
|
335
|
-
removed.push(entry.filePath);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
let enqueued = 0;
|
|
340
|
-
if (opts.dryRun !== true) {
|
|
341
|
-
for (const p of [...changed, ...added, ...removed]) {
|
|
342
|
-
enqueueDirtyPathSafe(resolveIndexPath(p, cwd), { alreadyResolved: true });
|
|
343
|
-
enqueued++;
|
|
344
|
-
}
|
|
345
|
-
if (budgetExhausted) {
|
|
346
|
-
if (lastScanned !== null) writeReconcileCursor(dbPath, projectRoot, lastScanned);
|
|
347
|
-
} else if (tracked.length > 0) {
|
|
348
|
-
clearReconcileCursor(dbPath, projectRoot);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return {
|
|
352
|
-
scanned,
|
|
353
|
-
changed,
|
|
354
|
-
added,
|
|
355
|
-
removed,
|
|
356
|
-
mtimeOnly,
|
|
357
|
-
parserStale,
|
|
358
|
-
budgetExhausted,
|
|
359
|
-
trackedUnavailable,
|
|
360
|
-
unscanned: Math.max(0, tracked.length - scanned),
|
|
361
|
-
enqueued,
|
|
362
|
-
elapsedMs: Date.now() - startedAt
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// src/stdin_json.ts
|
|
367
|
-
init_define_import_meta_env();
|
|
368
|
-
var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
|
|
369
|
-
var MAX_STDIN_WALL_MS = 6e4;
|
|
370
|
-
var MAX_STDIN_BYTES = 64 * 1024 * 1024;
|
|
371
|
-
function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
|
|
372
|
-
return new Promise((resolve, reject) => {
|
|
373
|
-
const chunks = [];
|
|
374
|
-
let totalBytes = 0;
|
|
375
|
-
let settled = false;
|
|
376
|
-
const finish = (fn) => {
|
|
377
|
-
if (settled) return;
|
|
378
|
-
settled = true;
|
|
379
|
-
clearTimeout(idleTimer);
|
|
380
|
-
clearTimeout(wallTimer);
|
|
381
|
-
process.stdin.removeListener("data", onData);
|
|
382
|
-
process.stdin.removeListener("end", onEnd);
|
|
383
|
-
process.stdin.removeListener("error", onError);
|
|
384
|
-
fn();
|
|
385
|
-
};
|
|
386
|
-
let idleTimer = setTimeout(() => {
|
|
387
|
-
finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
|
|
388
|
-
}, timeoutMs);
|
|
389
|
-
const wallTimer = setTimeout(() => {
|
|
390
|
-
finish(() => {
|
|
391
|
-
process.stdin.destroy();
|
|
392
|
-
reject(new Error(`readStdinJson: stdin took longer than ${MAX_STDIN_WALL_MS} ms`));
|
|
393
|
-
});
|
|
394
|
-
}, MAX_STDIN_WALL_MS);
|
|
395
|
-
const onData = (chunk) => {
|
|
396
|
-
clearTimeout(idleTimer);
|
|
397
|
-
idleTimer = setTimeout(() => {
|
|
398
|
-
finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
|
|
399
|
-
}, timeoutMs);
|
|
400
|
-
totalBytes += chunk.length;
|
|
401
|
-
if (totalBytes > maxBytes) {
|
|
402
|
-
finish(() => {
|
|
403
|
-
process.stdin.destroy();
|
|
404
|
-
reject(new Error(`readStdinJson: stdin exceeded ${maxBytes} bytes`));
|
|
405
|
-
});
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
chunks.push(chunk);
|
|
409
|
-
};
|
|
410
|
-
const onEnd = () => {
|
|
411
|
-
finish(() => {
|
|
412
|
-
const text = Buffer.concat(chunks).toString("utf8").trim();
|
|
413
|
-
if (text === "") {
|
|
414
|
-
reject(new Error("readStdinJson: empty stdin"));
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
try {
|
|
418
|
-
resolve(JSON.parse(text));
|
|
419
|
-
} catch (err) {
|
|
420
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
};
|
|
424
|
-
const onError = (err) => {
|
|
425
|
-
finish(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
426
|
-
};
|
|
427
|
-
process.stdin.on("data", onData);
|
|
428
|
-
process.stdin.on("end", onEnd);
|
|
429
|
-
process.stdin.on("error", onError);
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// src/symbol_body_probe.ts
|
|
434
|
-
init_define_import_meta_env();
|
|
435
|
-
import * as fs2 from "fs";
|
|
436
|
-
var OVERSIZED_BODY_PROBE_SQL = `SELECT 1 FROM symbols WHERE LENGTH(body) > ${SYMBOL_BODY_CHAR_CAP} LIMIT 1`;
|
|
437
|
-
function checkSymbolBodySize(dbPath) {
|
|
438
|
-
if (!fs2.existsSync(dbPath)) {
|
|
439
|
-
return { name: "Symbol body size", status: "ok", message: "no database yet" };
|
|
440
|
-
}
|
|
441
|
-
try {
|
|
442
|
-
const db = getDb(dbPath);
|
|
443
|
-
const row = db.prepare(OVERSIZED_BODY_PROBE_SQL).get();
|
|
444
|
-
if (row !== void 0) {
|
|
445
|
-
return {
|
|
446
|
-
name: "Symbol body size",
|
|
447
|
-
status: "warn",
|
|
448
|
-
message: `one or more stored symbol bodies exceed the ${SYMBOL_BODY_CHAR_CAP}-char cap enforced by boundSymbolBody -- likely a pre-fix leftover from a minified/generated file. A plain 'token-goat reclaim-index' (VACUUM only) CANNOT remove these rows -- it only reclaims freed pages, it never deletes row content. Only 'token-goat reclaim-index --rebuild' drops and re-derives them under the cap (stop the worker first with 'token-goat worker stop', since reclaim-index refuses to run while it's live); --rebuild reparses and re-embeds every indexed file across every project and can take a long time on a large multi-project index`
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
return { name: "Symbol body size", status: "ok", message: "no stored symbol body exceeds the cap" };
|
|
452
|
-
} catch (err) {
|
|
453
|
-
return {
|
|
454
|
-
name: "Symbol body size",
|
|
455
|
-
status: "warn",
|
|
456
|
-
message: `could not query symbol body size: ${extractErrorMessage(err)}`
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// src/resident_context.ts
|
|
462
|
-
init_define_import_meta_env();
|
|
463
|
-
import * as fs3 from "node:fs";
|
|
464
|
-
var LARGE_TASK_LIST_BYTES = 2e4;
|
|
465
|
-
var LARGE_SKILL_BODY_BYTES = 2e4;
|
|
466
|
-
var SKILL_BODY_REPEAT_THRESHOLD = 2;
|
|
467
|
-
var RESIDENT_TAIL_MAX_BYTES = 1048576;
|
|
468
|
-
function createResidentContextStats() {
|
|
469
|
-
return {
|
|
470
|
-
attachmentsByType: /* @__PURE__ */ new Map(),
|
|
471
|
-
latestTaskList: null,
|
|
472
|
-
taskReminderCount: 0,
|
|
473
|
-
taskReminderBytes: 0,
|
|
474
|
-
skillBodies: /* @__PURE__ */ new Map(),
|
|
475
|
-
compactionCount: 0
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
function bump(map, key, bytes) {
|
|
479
|
-
const entry = map.get(key);
|
|
480
|
-
if (entry === void 0) map.set(key, { count: 1, bytes });
|
|
481
|
-
else {
|
|
482
|
-
entry.count += 1;
|
|
483
|
-
entry.bytes += bytes;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
function messageText(message) {
|
|
487
|
-
if (message === null || typeof message !== "object") return "";
|
|
488
|
-
const content = message["content"];
|
|
489
|
-
if (typeof content === "string") return content;
|
|
490
|
-
if (!Array.isArray(content)) return "";
|
|
491
|
-
let out = "";
|
|
492
|
-
for (const block of content) {
|
|
493
|
-
if (block === null || typeof block !== "object") continue;
|
|
494
|
-
const text = block["text"];
|
|
495
|
-
if (typeof text === "string") out += text;
|
|
496
|
-
}
|
|
497
|
-
return out;
|
|
498
|
-
}
|
|
499
|
-
function skillNameFromBody(text) {
|
|
500
|
-
const dir = /^Base directory for this skill:\s*(.+?)\s*$/m.exec(text);
|
|
501
|
-
if (dir?.[1] !== void 0) {
|
|
502
|
-
const segments = dir[1].split(/[\\/]/).filter((s) => s.length > 0);
|
|
503
|
-
const last = segments[segments.length - 1];
|
|
504
|
-
if (last !== void 0 && last.length > 0) return last;
|
|
505
|
-
}
|
|
506
|
-
const heading = /^#\s+(.+?)\s*$/m.exec(text);
|
|
507
|
-
if (heading?.[1] !== void 0 && heading[1].length > 0) return heading[1];
|
|
508
|
-
return null;
|
|
509
|
-
}
|
|
510
|
-
function readTaskList(attachment, bytes) {
|
|
511
|
-
const snapshot = {
|
|
512
|
-
bytes,
|
|
513
|
-
itemCount: typeof attachment["itemCount"] === "number" ? attachment["itemCount"] : 0,
|
|
514
|
-
completed: 0,
|
|
515
|
-
inProgress: 0,
|
|
516
|
-
pending: 0,
|
|
517
|
-
descriptionBytes: 0,
|
|
518
|
-
completedDescriptionBytes: 0
|
|
519
|
-
};
|
|
520
|
-
const content = attachment["content"];
|
|
521
|
-
if (!Array.isArray(content)) return snapshot;
|
|
522
|
-
if (content.length > snapshot.itemCount) snapshot.itemCount = content.length;
|
|
523
|
-
for (const item of content) {
|
|
524
|
-
if (item === null || typeof item !== "object") continue;
|
|
525
|
-
const record = item;
|
|
526
|
-
const status = typeof record["status"] === "string" ? record["status"] : "";
|
|
527
|
-
const description = typeof record["description"] === "string" ? record["description"] : "";
|
|
528
|
-
snapshot.descriptionBytes += description.length;
|
|
529
|
-
if (status === "completed") {
|
|
530
|
-
snapshot.completed += 1;
|
|
531
|
-
snapshot.completedDescriptionBytes += description.length;
|
|
532
|
-
} else if (status === "in_progress") snapshot.inProgress += 1;
|
|
533
|
-
else if (status === "pending") snapshot.pending += 1;
|
|
534
|
-
}
|
|
535
|
-
return snapshot;
|
|
536
|
-
}
|
|
537
|
-
function collectInvokedSkills(acc, record) {
|
|
538
|
-
const skills = record["skills"];
|
|
539
|
-
if (!Array.isArray(skills)) return;
|
|
540
|
-
for (const entry of skills) {
|
|
541
|
-
if (entry === null || typeof entry !== "object") continue;
|
|
542
|
-
const skill = entry;
|
|
543
|
-
const content = skill["content"];
|
|
544
|
-
if (typeof content !== "string" || content.length < LARGE_SKILL_BODY_BYTES) continue;
|
|
545
|
-
const name = skillName(skill);
|
|
546
|
-
if (name !== null) bump(acc.skillBodies, name, content.length);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
function skillName(skill) {
|
|
550
|
-
const name = skill["name"];
|
|
551
|
-
if (typeof name === "string" && name.trim() !== "") return name.trim();
|
|
552
|
-
const path = skill["path"];
|
|
553
|
-
if (typeof path !== "string") return null;
|
|
554
|
-
const segments = path.split(/[\\/]/).filter((part) => part !== "");
|
|
555
|
-
const last = segments[segments.length - 1];
|
|
556
|
-
return last === void 0 || last === "" ? null : last;
|
|
557
|
-
}
|
|
558
|
-
function accumulateResidentLine(acc, parsed, bytes) {
|
|
559
|
-
try {
|
|
560
|
-
if (parsed === null || typeof parsed !== "object") return;
|
|
561
|
-
const line = parsed;
|
|
562
|
-
if (line["type"] === "system" && line["subtype"] === "compact_boundary") acc.compactionCount += 1;
|
|
563
|
-
const attachment = line["attachment"];
|
|
564
|
-
if (attachment !== null && typeof attachment === "object") {
|
|
565
|
-
const record = attachment;
|
|
566
|
-
const type = typeof record["type"] === "string" ? record["type"] : "unknown";
|
|
567
|
-
bump(acc.attachmentsByType, type, bytes);
|
|
568
|
-
if (type === "task_reminder") {
|
|
569
|
-
acc.taskReminderCount += 1;
|
|
570
|
-
acc.taskReminderBytes += bytes;
|
|
571
|
-
acc.latestTaskList = readTaskList(record, bytes);
|
|
572
|
-
}
|
|
573
|
-
if (type === "invoked_skills") collectInvokedSkills(acc, record);
|
|
574
|
-
}
|
|
575
|
-
if (line["isMeta"] === true && line["type"] === "user") {
|
|
576
|
-
const text = messageText(line["message"]);
|
|
577
|
-
if (text.length >= LARGE_SKILL_BODY_BYTES) {
|
|
578
|
-
const skill = skillNameFromBody(text);
|
|
579
|
-
if (skill !== null) bump(acc.skillBodies, skill, text.length);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
} catch {
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
function summarizeResidentContext(acc) {
|
|
586
|
-
const attachmentClasses = [...acc.attachmentsByType.entries()].map(([type, v]) => ({ type, count: v.count, bytes: v.bytes })).sort((a, b) => b.bytes - a.bytes || a.type.localeCompare(b.type));
|
|
587
|
-
const repeatedSkillBodies = [...acc.skillBodies.entries()].filter(([, v]) => v.count >= SKILL_BODY_REPEAT_THRESHOLD).map(([skill, v]) => ({ skill, count: v.count, bytes: v.bytes })).sort((a, b) => b.bytes - a.bytes || a.skill.localeCompare(b.skill));
|
|
588
|
-
return {
|
|
589
|
-
attachmentClasses,
|
|
590
|
-
totalAttachmentBytes: attachmentClasses.reduce((n, c) => n + c.bytes, 0),
|
|
591
|
-
latestTaskList: acc.latestTaskList,
|
|
592
|
-
taskReminderCount: acc.taskReminderCount,
|
|
593
|
-
taskReminderBytes: acc.taskReminderBytes,
|
|
594
|
-
repeatedSkillBodies,
|
|
595
|
-
compactionCount: acc.compactionCount
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
function formatBytes(bytes) {
|
|
599
|
-
if (bytes >= 1e6) return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
600
|
-
if (bytes >= 1e3) return `${Math.round(bytes / 1024)} KB`;
|
|
601
|
-
return `${bytes} B`;
|
|
602
|
-
}
|
|
603
|
-
function formatTokenEstimate(tokens) {
|
|
604
|
-
return tokens >= 1e3 ? `${Math.round(tokens / 1e3)}K` : String(tokens);
|
|
605
|
-
}
|
|
606
|
-
function taskListPruneHint(snapshot) {
|
|
607
|
-
if (snapshot === null) return null;
|
|
608
|
-
if (snapshot.bytes < LARGE_TASK_LIST_BYTES) return null;
|
|
609
|
-
if (snapshot.completed === 0) return null;
|
|
610
|
-
const tokens = formatTokenEstimate(estimateTokensFromLength(snapshot.bytes));
|
|
611
|
-
const prunable = formatBytes(snapshot.completedDescriptionBytes);
|
|
612
|
-
return `Your task list is ${formatBytes(snapshot.bytes)} (~${tokens} tok est) and is re-injected in full whenever it changes; ${snapshot.completed} of ${snapshot.itemCount} items are already completed and their descriptions alone are ${prunable}. Prune completed items or shorten their descriptions with TaskUpdate. Check ownership first if other agents share this list.`;
|
|
613
|
-
}
|
|
614
|
-
function repeatedSkillBodyHint(injections) {
|
|
615
|
-
const worst = injections[0];
|
|
616
|
-
if (worst === void 0) return null;
|
|
617
|
-
if (worst.count < SKILL_BODY_REPEAT_THRESHOLD || worst.bytes < LARGE_SKILL_BODY_BYTES) return null;
|
|
618
|
-
const skill = displaySafeText(worst.skill);
|
|
619
|
-
const tokens = formatTokenEstimate(estimateTokensFromLength(worst.bytes));
|
|
620
|
-
return `The \`${skill}\` skill body has been injected ${worst.count} times this session (${formatBytes(worst.bytes)} total, ~${tokens} tok est). Slash expansion and the Skill tool both send the whole body every time, and no hook can intercept either. If it is already loaded, work from it instead of re-invoking; to reread one part, use \`token-goat skill-section ${skill} '<heading>'\`.`;
|
|
621
|
-
}
|
|
622
|
-
function readTranscriptTail(transcriptPath, maxBytes = RESIDENT_TAIL_MAX_BYTES) {
|
|
623
|
-
let fd = null;
|
|
624
|
-
try {
|
|
625
|
-
const stat = fs3.statSync(transcriptPath);
|
|
626
|
-
if (!stat.isFile() || stat.size === 0) return [];
|
|
627
|
-
const start = Math.max(0, stat.size - maxBytes);
|
|
628
|
-
const length = stat.size - start;
|
|
629
|
-
fd = fs3.openSync(transcriptPath, "r");
|
|
630
|
-
const buf = Buffer.allocUnsafe(length);
|
|
631
|
-
const read = fs3.readSync(fd, buf, 0, length, start);
|
|
632
|
-
const lines = buf.subarray(0, read).toString("utf8").split("\n");
|
|
633
|
-
if (start > 0) lines.shift();
|
|
634
|
-
return lines;
|
|
635
|
-
} catch {
|
|
636
|
-
return [];
|
|
637
|
-
} finally {
|
|
638
|
-
if (fd !== null) {
|
|
639
|
-
try {
|
|
640
|
-
fs3.closeSync(fd);
|
|
641
|
-
} catch {
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
function lineMayCarryResidentSignal(line) {
|
|
647
|
-
return line.includes('"task_reminder"') || line.includes('"compact_boundary"') || line.includes('"invoked_skills"') || line.includes('"isMeta"');
|
|
648
|
-
}
|
|
649
|
-
function accumulateResidentLines(lines) {
|
|
650
|
-
const acc = createResidentContextStats();
|
|
651
|
-
for (const line of lines) {
|
|
652
|
-
const trimmed = line.trim();
|
|
653
|
-
if (trimmed === "") continue;
|
|
654
|
-
let parsed;
|
|
655
|
-
try {
|
|
656
|
-
parsed = JSON.parse(trimmed);
|
|
657
|
-
} catch {
|
|
658
|
-
continue;
|
|
659
|
-
}
|
|
660
|
-
accumulateResidentLine(acc, parsed, trimmed.length);
|
|
661
|
-
}
|
|
662
|
-
return acc;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
export {
|
|
666
|
-
checkSymbolBodySize,
|
|
667
|
-
WEB_OUTPUT_SUBDIR,
|
|
668
|
-
storeWebOutput,
|
|
669
|
-
getWebOutput,
|
|
670
|
-
getWebOutputRaw,
|
|
671
|
-
getWebOutputByUrlFromDisk,
|
|
672
|
-
HOOK_EVENTS,
|
|
673
|
-
buildCommandManifest,
|
|
674
|
-
flattenCommandNames,
|
|
675
|
-
filterCommandManifest,
|
|
676
|
-
formatCommandManifest,
|
|
677
|
-
DEFAULT_RECONCILE_BUDGET_MS,
|
|
678
|
-
runReconcile,
|
|
679
|
-
isReconcileClean,
|
|
680
|
-
reconcileProject,
|
|
681
|
-
createResidentContextStats,
|
|
682
|
-
accumulateResidentLine,
|
|
683
|
-
summarizeResidentContext,
|
|
684
|
-
formatBytes,
|
|
685
|
-
formatTokenEstimate,
|
|
686
|
-
taskListPruneHint,
|
|
687
|
-
repeatedSkillBodyHint,
|
|
688
|
-
readTranscriptTail,
|
|
689
|
-
lineMayCarryResidentSignal,
|
|
690
|
-
accumulateResidentLines,
|
|
691
|
-
MAX_STDIN_BYTES,
|
|
692
|
-
readStdinJson
|
|
693
|
-
};
|