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
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
enqueueDirtyPathSafe,
|
|
5
|
+
getTrackedFiles
|
|
6
|
+
} from "./token-goat-chunk-BL5LNGBG.mjs";
|
|
7
|
+
import {
|
|
8
|
+
getToolName,
|
|
9
|
+
indexRecallEntry
|
|
10
|
+
} from "./token-goat-chunk-YQ7WI2CO.mjs";
|
|
11
|
+
import {
|
|
12
|
+
PARSER_FINGERPRINT
|
|
13
|
+
} from "./token-goat-chunk-NDRP4KJQ.mjs";
|
|
14
|
+
import {
|
|
15
|
+
getProjectFileEntries
|
|
16
|
+
} from "./token-goat-chunk-2X2EBBC6.mjs";
|
|
17
|
+
import {
|
|
18
|
+
isBlobStale,
|
|
19
|
+
loadBlob,
|
|
20
|
+
redactSecrets,
|
|
21
|
+
storeBlob
|
|
22
|
+
} from "./token-goat-chunk-IVUQLQWN.mjs";
|
|
23
|
+
import {
|
|
24
|
+
getDb
|
|
25
|
+
} from "./token-goat-chunk-K7F2BFIK.mjs";
|
|
26
|
+
import {
|
|
27
|
+
fingerprintFile,
|
|
28
|
+
getDisplayRoot,
|
|
29
|
+
shortFingerprint
|
|
30
|
+
} from "./token-goat-chunk-S4XRY446.mjs";
|
|
31
|
+
import {
|
|
32
|
+
registerReset
|
|
33
|
+
} from "./token-goat-chunk-EEIDFMEM.mjs";
|
|
34
|
+
import {
|
|
35
|
+
SYMBOL_BODY_CHAR_CAP,
|
|
36
|
+
countNoun,
|
|
37
|
+
extractErrorMessage,
|
|
38
|
+
foldPath,
|
|
39
|
+
globalDbPath
|
|
40
|
+
} from "./token-goat-chunk-PM76YS22.mjs";
|
|
41
|
+
import {
|
|
42
|
+
displaySafeJson,
|
|
43
|
+
normalizePath,
|
|
44
|
+
resolveIndexPath,
|
|
45
|
+
toDisplayPath
|
|
46
|
+
} from "./token-goat-chunk-ERTXEKB6.mjs";
|
|
47
|
+
import {
|
|
48
|
+
compileGuardedRegex
|
|
49
|
+
} from "./token-goat-chunk-GMOUBOX4.mjs";
|
|
50
|
+
import {
|
|
51
|
+
init_define_import_meta_env
|
|
52
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
53
|
+
|
|
54
|
+
// src/web_cache.ts
|
|
55
|
+
init_define_import_meta_env();
|
|
56
|
+
var WEB_OUTPUT_SUBDIR = "web_outputs";
|
|
57
|
+
var _byId = /* @__PURE__ */ new Map();
|
|
58
|
+
var _rawById = /* @__PURE__ */ new Map();
|
|
59
|
+
var _urlIndex = /* @__PURE__ */ new Map();
|
|
60
|
+
function cacheIdForUrl(url) {
|
|
61
|
+
return shortFingerprint(url);
|
|
62
|
+
}
|
|
63
|
+
function storeWebOutput(url, content, dedupKey = url, rawContent) {
|
|
64
|
+
const cacheId = cacheIdForUrl(dedupKey);
|
|
65
|
+
const redactedContent = redactSecrets(content).text;
|
|
66
|
+
_byId.set(cacheId, redactedContent);
|
|
67
|
+
_urlIndex.set(url, cacheId);
|
|
68
|
+
const redactedRaw = rawContent !== void 0 && rawContent !== content ? redactSecrets(rawContent).text : void 0;
|
|
69
|
+
if (redactedRaw !== void 0) _rawById.set(cacheId, redactedRaw);
|
|
70
|
+
else _rawById.delete(cacheId);
|
|
71
|
+
storeBlob(WEB_OUTPUT_SUBDIR, cacheId, redactedRaw !== void 0 ? { url, content: redactedContent, raw: redactedRaw } : { url, content: redactedContent });
|
|
72
|
+
const redactedUrl = redactSecrets(url).text;
|
|
73
|
+
indexRecallEntry("web", cacheId, redactedUrl, `${redactedUrl}
|
|
74
|
+
${redactedContent}`, Date.now());
|
|
75
|
+
return cacheId;
|
|
76
|
+
}
|
|
77
|
+
function coerceWebBlob(raw) {
|
|
78
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
79
|
+
const o = raw;
|
|
80
|
+
if (typeof o["content"] !== "string") return null;
|
|
81
|
+
return { url: typeof o["url"] === "string" ? o["url"] : null, content: o["content"], raw: typeof o["raw"] === "string" ? o["raw"] : null };
|
|
82
|
+
}
|
|
83
|
+
function getWebOutput(cacheId) {
|
|
84
|
+
const hit = _byId.get(cacheId);
|
|
85
|
+
if (hit !== void 0) return hit;
|
|
86
|
+
if (isBlobStale(WEB_OUTPUT_SUBDIR, cacheId)) return null;
|
|
87
|
+
const blob = coerceWebBlob(loadBlob(WEB_OUTPUT_SUBDIR, cacheId));
|
|
88
|
+
if (blob === null) return null;
|
|
89
|
+
_byId.set(cacheId, blob.content);
|
|
90
|
+
if (blob.raw !== null) _rawById.set(cacheId, blob.raw);
|
|
91
|
+
if (blob.url !== null) _urlIndex.set(blob.url, cacheId);
|
|
92
|
+
return blob.content;
|
|
93
|
+
}
|
|
94
|
+
function getWebOutputRaw(cacheId) {
|
|
95
|
+
const cleaned = getWebOutput(cacheId);
|
|
96
|
+
if (cleaned === null) return null;
|
|
97
|
+
return _rawById.get(cacheId) ?? cleaned;
|
|
98
|
+
}
|
|
99
|
+
function getWebOutputByUrlFromDisk(url, dedupKey = url) {
|
|
100
|
+
const cacheId = cacheIdForUrl(dedupKey);
|
|
101
|
+
const content = getWebOutput(cacheId);
|
|
102
|
+
if (content === null) return null;
|
|
103
|
+
return { cacheId, content };
|
|
104
|
+
}
|
|
105
|
+
registerReset(() => {
|
|
106
|
+
_byId = /* @__PURE__ */ new Map();
|
|
107
|
+
_rawById = /* @__PURE__ */ new Map();
|
|
108
|
+
_urlIndex = /* @__PURE__ */ new Map();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// src/stdin_json.ts
|
|
112
|
+
init_define_import_meta_env();
|
|
113
|
+
var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
|
|
114
|
+
var MAX_STDIN_WALL_MS = 6e4;
|
|
115
|
+
var MAX_STDIN_BYTES = 64 * 1024 * 1024;
|
|
116
|
+
function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
|
|
117
|
+
return new Promise((resolve2, reject) => {
|
|
118
|
+
const chunks = [];
|
|
119
|
+
let totalBytes = 0;
|
|
120
|
+
let settled = false;
|
|
121
|
+
const finish = (fn) => {
|
|
122
|
+
if (settled) return;
|
|
123
|
+
settled = true;
|
|
124
|
+
clearTimeout(idleTimer);
|
|
125
|
+
clearTimeout(wallTimer);
|
|
126
|
+
process.stdin.removeListener("data", onData);
|
|
127
|
+
process.stdin.removeListener("end", onEnd);
|
|
128
|
+
process.stdin.removeListener("error", onError);
|
|
129
|
+
fn();
|
|
130
|
+
};
|
|
131
|
+
let idleTimer = setTimeout(() => {
|
|
132
|
+
finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
|
|
133
|
+
}, timeoutMs);
|
|
134
|
+
const wallTimer = setTimeout(() => {
|
|
135
|
+
finish(() => {
|
|
136
|
+
process.stdin.destroy();
|
|
137
|
+
reject(new Error(`readStdinJson: stdin took longer than ${MAX_STDIN_WALL_MS} ms`));
|
|
138
|
+
});
|
|
139
|
+
}, MAX_STDIN_WALL_MS);
|
|
140
|
+
const onData = (chunk) => {
|
|
141
|
+
clearTimeout(idleTimer);
|
|
142
|
+
idleTimer = setTimeout(() => {
|
|
143
|
+
finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
|
|
144
|
+
}, timeoutMs);
|
|
145
|
+
totalBytes += chunk.length;
|
|
146
|
+
if (totalBytes > maxBytes) {
|
|
147
|
+
finish(() => {
|
|
148
|
+
process.stdin.destroy();
|
|
149
|
+
reject(new Error(`readStdinJson: stdin exceeded ${maxBytes} bytes`));
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
chunks.push(chunk);
|
|
154
|
+
};
|
|
155
|
+
const onEnd = () => {
|
|
156
|
+
finish(() => {
|
|
157
|
+
const text = Buffer.concat(chunks).toString("utf8").trim();
|
|
158
|
+
if (text === "") {
|
|
159
|
+
reject(new Error("readStdinJson: empty stdin"));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
resolve2(JSON.parse(text));
|
|
164
|
+
} catch (err) {
|
|
165
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
const onError = (err) => {
|
|
170
|
+
finish(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
171
|
+
};
|
|
172
|
+
process.stdin.on("data", onData);
|
|
173
|
+
process.stdin.on("end", onEnd);
|
|
174
|
+
process.stdin.on("error", onError);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/session_store_schema.ts
|
|
179
|
+
init_define_import_meta_env();
|
|
180
|
+
import { existsSync } from "node:fs";
|
|
181
|
+
import { resolve } from "node:path";
|
|
182
|
+
var SESSION_STORE_TABLES = [
|
|
183
|
+
{
|
|
184
|
+
name: "sessions",
|
|
185
|
+
kind: "view",
|
|
186
|
+
source: "both",
|
|
187
|
+
description: "Session metadata and discovery across projects and agents",
|
|
188
|
+
keyFields: ["id", "task_id", "cwd", "repository", "branch", "summary", "agent_name", "agent_description", "created_at", "updated_at"],
|
|
189
|
+
columns: [
|
|
190
|
+
{ name: "id", type: "TEXT", description: "Session UUID (matches .../sessions/<id>)", primaryKey: true, notNull: true },
|
|
191
|
+
{ name: "task_id", type: "TEXT", description: "Task UUID from .../tasks/<id> URL (cloud only)" },
|
|
192
|
+
{ name: "cwd", type: "TEXT", description: "Working directory path where session started" },
|
|
193
|
+
{ name: "repository", type: "TEXT", description: "Repository name or owner/repo slug" },
|
|
194
|
+
{ name: "branch", type: "TEXT", description: "Active git branch name" },
|
|
195
|
+
{ name: "summary", type: "TEXT", description: "Concise summary of session conversation" },
|
|
196
|
+
{ name: "agent_name", type: "TEXT", description: "Exact agent name, e.g. 'Copilot CLI', 'Copilot Coding Agent' (cloud only)" },
|
|
197
|
+
{ name: "agent_description", type: "TEXT", description: "Agent description or specialty (cloud only)" },
|
|
198
|
+
{ name: "created_at", type: "TIMESTAMP", description: "Session start timestamp (UTC)" },
|
|
199
|
+
{ name: "updated_at", type: "TIMESTAMP", description: "Session last active timestamp (UTC)" },
|
|
200
|
+
{ name: "host_type", type: "TEXT", description: "Host environment type (local source only)" }
|
|
201
|
+
],
|
|
202
|
+
notes: [
|
|
203
|
+
'In local mode (source: "local"), sessions only contains: id, cwd, repository, host_type, branch, summary, created_at, updated_at (no task_id or agent_*).',
|
|
204
|
+
'Filter exact agent_name values such as "Copilot Code Review", "Copilot Coding Agent", or "Copilot CLI", not summary text.',
|
|
205
|
+
"Always use LIMIT and filter by repository or time (created_at >= now() - INTERVAL '7 days')."
|
|
206
|
+
],
|
|
207
|
+
commonMisnomers: {
|
|
208
|
+
title: 'summary (sessions has no "title" column; checkpoints has "title")',
|
|
209
|
+
role: 'agent_name or agent_description (sessions has no "role" column)',
|
|
210
|
+
name: "agent_name (for agent identity) or repository (for project name)",
|
|
211
|
+
user: "user_message in turns table",
|
|
212
|
+
prompt: "user_message in turns table"
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "turns",
|
|
217
|
+
kind: "view",
|
|
218
|
+
source: "both",
|
|
219
|
+
description: "Conversation messages grouped into user and assistant turns",
|
|
220
|
+
keyFields: ["session_id", "turn_index", "user_message", "assistant_response", "timestamp"],
|
|
221
|
+
columns: [
|
|
222
|
+
{ name: "session_id", type: "TEXT", description: "Foreign key to sessions.id", notNull: true },
|
|
223
|
+
{ name: "turn_index", type: "INTEGER", description: "0-based turn index in conversation", notNull: true },
|
|
224
|
+
{ name: "user_message", type: "TEXT", description: "User input / prompt message" },
|
|
225
|
+
{ name: "assistant_response", type: "TEXT", description: "Assistant generated response" },
|
|
226
|
+
{ name: "timestamp", type: "TIMESTAMP", description: "Turn execution timestamp" }
|
|
227
|
+
],
|
|
228
|
+
notes: [
|
|
229
|
+
"Never ILIKE-scan turns without narrow time or session_id filters; turns table can exceed 50,000+ rows."
|
|
230
|
+
],
|
|
231
|
+
commonMisnomers: {
|
|
232
|
+
prompt: "user_message",
|
|
233
|
+
response: "assistant_response",
|
|
234
|
+
content: "user_message or assistant_response",
|
|
235
|
+
text: "user_message or assistant_response"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "checkpoints",
|
|
240
|
+
kind: "view",
|
|
241
|
+
source: "both",
|
|
242
|
+
description: "Handoff and checkpoint summaries created during sessions",
|
|
243
|
+
keyFields: ["session_id", "checkpoint_number", "title", "overview", "created_at"],
|
|
244
|
+
columns: [
|
|
245
|
+
{ name: "session_id", type: "TEXT", description: "Foreign key to sessions.id", notNull: true },
|
|
246
|
+
{ name: "checkpoint_number", type: "INTEGER", description: "Sequential checkpoint number (1-based)" },
|
|
247
|
+
{ name: "title", type: "TEXT", description: "Checkpoint short title" },
|
|
248
|
+
{ name: "overview", type: "TEXT", description: "Checkpoint narrative markdown summary" },
|
|
249
|
+
{ name: "created_at", type: "TIMESTAMP", description: "Checkpoint creation timestamp" }
|
|
250
|
+
],
|
|
251
|
+
commonMisnomers: {
|
|
252
|
+
summary: "overview or title",
|
|
253
|
+
description: "overview",
|
|
254
|
+
notes: "overview"
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "session_files",
|
|
259
|
+
kind: "view",
|
|
260
|
+
source: "both",
|
|
261
|
+
description: "Files touched, created, or modified during sessions",
|
|
262
|
+
keyFields: ["session_id", "file_path", "tool_name", "turn_index", "first_seen_at"],
|
|
263
|
+
columns: [
|
|
264
|
+
{ name: "session_id", type: "TEXT", description: "Foreign key to sessions.id", notNull: true },
|
|
265
|
+
{ name: "file_path", type: "TEXT", description: "Workspace or absolute file path", notNull: true },
|
|
266
|
+
{ name: "tool_name", type: "TEXT", description: "Tool used to touch file (edit, create, apply_patch)" },
|
|
267
|
+
{ name: "turn_index", type: "INTEGER", description: "Turn index where file was modified" },
|
|
268
|
+
{ name: "first_seen_at", type: "TIMESTAMP", description: "Timestamp file was first modified in session" }
|
|
269
|
+
],
|
|
270
|
+
commonMisnomers: {
|
|
271
|
+
path: "file_path",
|
|
272
|
+
filename: "file_path",
|
|
273
|
+
name: "file_path"
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "session_refs",
|
|
278
|
+
kind: "view",
|
|
279
|
+
source: "both",
|
|
280
|
+
description: "Commit, pull request, and issue references linked to sessions",
|
|
281
|
+
keyFields: ["session_id", "ref_type", "ref_value", "turn_index", "created_at"],
|
|
282
|
+
columns: [
|
|
283
|
+
{ name: "session_id", type: "TEXT", description: "Foreign key to sessions.id", notNull: true },
|
|
284
|
+
{ name: "ref_type", type: "TEXT", description: "Reference type ('commit', 'pr', or 'issue')" },
|
|
285
|
+
{ name: "ref_value", type: "TEXT", description: "Reference value (e.g. git SHA, PR number, or issue number)" },
|
|
286
|
+
{ name: "turn_index", type: "INTEGER", description: "Turn index where reference appeared" },
|
|
287
|
+
{ name: "created_at", type: "TIMESTAMP", description: "Reference creation timestamp" }
|
|
288
|
+
],
|
|
289
|
+
notes: [
|
|
290
|
+
"Prefer session_refs exact matches for PR/issue lookups over text-scanning turns."
|
|
291
|
+
],
|
|
292
|
+
commonMisnomers: {
|
|
293
|
+
type: "ref_type",
|
|
294
|
+
value: "ref_value",
|
|
295
|
+
ref: "ref_value"
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "session_usage",
|
|
300
|
+
kind: "view",
|
|
301
|
+
source: "cloud",
|
|
302
|
+
description: "Model and token usage aggregated per session and model (cloud only)",
|
|
303
|
+
keyFields: ["session_id", "usage_model", "api_call_count", "input_tokens", "output_tokens", "cost", "duration", "first_used_at", "last_used_at"],
|
|
304
|
+
columns: [
|
|
305
|
+
{ name: "session_id", type: "TEXT", description: "Foreign key to sessions.id", notNull: true },
|
|
306
|
+
{ name: "usage_model", type: "TEXT", description: "LLM model identifier used" },
|
|
307
|
+
{ name: "api_call_count", type: "INTEGER", description: "Total API calls issued to model" },
|
|
308
|
+
{ name: "input_tokens", type: "INTEGER", description: "Total prompt/input tokens" },
|
|
309
|
+
{ name: "output_tokens", type: "INTEGER", description: "Total generated/output tokens" },
|
|
310
|
+
{ name: "cache_read_tokens", type: "INTEGER", description: "Prompt tokens read from cache" },
|
|
311
|
+
{ name: "cache_write_tokens", type: "INTEGER", description: "Prompt tokens written to cache" },
|
|
312
|
+
{ name: "cost", type: "DOUBLE", description: "Sum of model billing multipliers" },
|
|
313
|
+
{ name: "duration", type: "BIGINT", description: "Total duration in milliseconds" },
|
|
314
|
+
{ name: "first_used_at", type: "TIMESTAMP", description: "First usage timestamp" },
|
|
315
|
+
{ name: "last_used_at", type: "TIMESTAMP", description: "Last usage timestamp" }
|
|
316
|
+
],
|
|
317
|
+
notes: [
|
|
318
|
+
"Filtering last_used_at selects session/model rows whose latest usage falls in the period; rows remain whole-session aggregates."
|
|
319
|
+
],
|
|
320
|
+
commonMisnomers: {
|
|
321
|
+
model: "usage_model",
|
|
322
|
+
tokens: "input_tokens or output_tokens"
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: "tool_executions",
|
|
327
|
+
kind: "view",
|
|
328
|
+
source: "cloud",
|
|
329
|
+
description: "Completed tool calls and outcomes (cloud only)",
|
|
330
|
+
keyFields: ["session_id", "tool_call_id", "tool_name", "started_at", "completed_at", "duration_ms", "success", "error_code"],
|
|
331
|
+
columns: [
|
|
332
|
+
{ name: "session_id", type: "TEXT", description: "Foreign key to sessions.id", notNull: true },
|
|
333
|
+
{ name: "tool_call_id", type: "TEXT", description: "Unique tool invocation identifier" },
|
|
334
|
+
{ name: "tool_name", type: "TEXT", description: "Tool name executed" },
|
|
335
|
+
{ name: "started_at", type: "TIMESTAMP", description: "Tool start timestamp" },
|
|
336
|
+
{ name: "completed_at", type: "TIMESTAMP", description: "Tool completion timestamp" },
|
|
337
|
+
{ name: "duration_ms", type: "BIGINT", description: "Execution duration in milliseconds" },
|
|
338
|
+
{ name: "success", type: "BOOLEAN", description: "True if tool succeeded, false on error" },
|
|
339
|
+
{ name: "error_code", type: "TEXT", description: "Error code or category on failure" }
|
|
340
|
+
],
|
|
341
|
+
notes: [
|
|
342
|
+
"To exclude invalid negative durations, filter completed_at >= started_at."
|
|
343
|
+
],
|
|
344
|
+
commonMisnomers: {
|
|
345
|
+
name: "tool_name",
|
|
346
|
+
duration: "duration_ms",
|
|
347
|
+
status: "success (boolean)"
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "tool_requests",
|
|
352
|
+
kind: "table",
|
|
353
|
+
source: "cloud",
|
|
354
|
+
description: "Raw tool request arguments in JSON format (cloud fallback table)",
|
|
355
|
+
keyFields: ["session_id", "tool_call_id", "name", "arguments_json"],
|
|
356
|
+
columns: [
|
|
357
|
+
{ name: "session_id", type: "TEXT", description: "Foreign key to sessions.id", notNull: true },
|
|
358
|
+
{ name: "tool_call_id", type: "TEXT", description: "Tool call identifier" },
|
|
359
|
+
{ name: "name", type: "TEXT", description: "Tool name requested" },
|
|
360
|
+
{ name: "arguments_json", type: "TEXT", description: "Raw JSON arguments payload" }
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "attachments",
|
|
365
|
+
kind: "table",
|
|
366
|
+
source: "cloud",
|
|
367
|
+
description: "Attachments submitted with user messages (cloud fallback table)",
|
|
368
|
+
keyFields: ["session_id", "display_name", "path", "type"],
|
|
369
|
+
columns: [
|
|
370
|
+
{ name: "session_id", type: "TEXT", description: "Foreign key to sessions.id", notNull: true },
|
|
371
|
+
{ name: "display_name", type: "TEXT", description: "Display name or label" },
|
|
372
|
+
{ name: "path", type: "TEXT", description: "Path to attachment" },
|
|
373
|
+
{ name: "type", type: "TEXT", description: "MIME type or category" }
|
|
374
|
+
]
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "events",
|
|
378
|
+
kind: "table",
|
|
379
|
+
source: "cloud",
|
|
380
|
+
description: "Underlying raw event stream (~90 columns, large table - filter by session_id and timestamp)",
|
|
381
|
+
keyFields: ["session_id", "timestamp", "type", "agent_name", "agent_description", "user_content", "assistant_content"],
|
|
382
|
+
columns: [
|
|
383
|
+
{ name: "session_id", type: "TEXT", description: "Foreign key to sessions.id", notNull: true },
|
|
384
|
+
{ name: "timestamp", type: "TIMESTAMP", description: "Event timestamp" },
|
|
385
|
+
{ name: "type", type: "TEXT", description: "Event type ('user.message', 'assistant.message', 'tool.execution_complete')" },
|
|
386
|
+
{ name: "agent_name", type: "TEXT", description: "Agent identifier" },
|
|
387
|
+
{ name: "agent_description", type: "TEXT", description: "Agent description" },
|
|
388
|
+
{ name: "user_content", type: "TEXT", description: "User message text" },
|
|
389
|
+
{ name: "assistant_content", type: "TEXT", description: "Assistant message text" },
|
|
390
|
+
{ name: "tool_start_name", type: "TEXT", description: "Tool name at invocation" },
|
|
391
|
+
{ name: "tool_complete_call_id", type: "TEXT", description: "Tool call ID on completion" },
|
|
392
|
+
{ name: "tool_complete_success", type: "BOOLEAN", description: "Success status flag" },
|
|
393
|
+
{ name: "tool_complete_result_content", type: "TEXT", description: "Tool execution result text" },
|
|
394
|
+
{ name: "usage_model", type: "TEXT", description: "Model name" },
|
|
395
|
+
{ name: "usage_input_tokens", type: "INTEGER", description: "Input token count" },
|
|
396
|
+
{ name: "usage_output_tokens", type: "INTEGER", description: "Output token count" }
|
|
397
|
+
]
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: "todos",
|
|
401
|
+
kind: "session_db",
|
|
402
|
+
source: "session",
|
|
403
|
+
description: "Active session SQLite workflow todos (managed via sql tool in session database)",
|
|
404
|
+
keyFields: ["id", "title", "description", "status", "created_at", "updated_at"],
|
|
405
|
+
columns: [
|
|
406
|
+
{ name: "id", type: "TEXT", description: "Unique kebab-case todo identifier", primaryKey: true, notNull: true },
|
|
407
|
+
{ name: "title", type: "TEXT", description: 'Gerund action title (e.g. "Creating auth module")', notNull: true },
|
|
408
|
+
{ name: "description", type: "TEXT", description: "Self-contained task description" },
|
|
409
|
+
{ name: "status", type: "TEXT", description: "'pending', 'in_progress', 'done', or 'blocked'", notNull: true },
|
|
410
|
+
{ name: "created_at", type: "TIMESTAMP", description: "Creation timestamp" },
|
|
411
|
+
{ name: "updated_at", type: "TIMESTAMP", description: "Last update timestamp" }
|
|
412
|
+
],
|
|
413
|
+
commonMisnomers: {
|
|
414
|
+
name: "title or id",
|
|
415
|
+
task: "title or description",
|
|
416
|
+
state: "status"
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
name: "todo_deps",
|
|
421
|
+
kind: "session_db",
|
|
422
|
+
source: "session",
|
|
423
|
+
description: "Active session SQLite todo dependencies (managed via sql tool in session database)",
|
|
424
|
+
keyFields: ["todo_id", "depends_on"],
|
|
425
|
+
columns: [
|
|
426
|
+
{ name: "todo_id", type: "TEXT", description: "Todo identifier that has a dependency", notNull: true },
|
|
427
|
+
{ name: "depends_on", type: "TEXT", description: "Todo identifier that must be done first", notNull: true }
|
|
428
|
+
],
|
|
429
|
+
commonMisnomers: {
|
|
430
|
+
parent: "depends_on",
|
|
431
|
+
child: "todo_id"
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "assistant_usage_events",
|
|
436
|
+
kind: "table",
|
|
437
|
+
source: "local",
|
|
438
|
+
description: 'Local SQLite assistant usage events (source: "local" only)',
|
|
439
|
+
keyFields: ["session_id", "timestamp", "model", "input_tokens", "output_tokens"],
|
|
440
|
+
columns: [
|
|
441
|
+
{ name: "session_id", type: "TEXT", description: "Session UUID" },
|
|
442
|
+
{ name: "timestamp", type: "TEXT", description: "Timestamp string" },
|
|
443
|
+
{ name: "model", type: "TEXT", description: "Model identifier" },
|
|
444
|
+
{ name: "input_tokens", type: "INTEGER", description: "Input token count" },
|
|
445
|
+
{ name: "output_tokens", type: "INTEGER", description: "Output token count" }
|
|
446
|
+
]
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: "search_index",
|
|
450
|
+
kind: "table",
|
|
451
|
+
source: "local",
|
|
452
|
+
description: 'Local SQLite FTS5 search index table for MATCH queries (source: "local" only)',
|
|
453
|
+
keyFields: ["search_index"],
|
|
454
|
+
columns: [
|
|
455
|
+
{ name: "search_index", type: "FTS5", description: "Full-text indexed search virtual table" }
|
|
456
|
+
]
|
|
457
|
+
}
|
|
458
|
+
];
|
|
459
|
+
function getSessionStoreTable(name) {
|
|
460
|
+
const norm = name.trim().toLowerCase();
|
|
461
|
+
return SESSION_STORE_TABLES.find((t) => t.name.toLowerCase() === norm);
|
|
462
|
+
}
|
|
463
|
+
function formatSessionStoreCatalog(opts) {
|
|
464
|
+
if (opts?.json === true) {
|
|
465
|
+
return displaySafeJson(SESSION_STORE_TABLES);
|
|
466
|
+
}
|
|
467
|
+
const lines = [
|
|
468
|
+
"# Session Store & Workflow Database Schema Catalog",
|
|
469
|
+
"",
|
|
470
|
+
"Available tables and materialized views for `session_store_sql` (DuckDB/SQLite) and `sql` (session SQLite):",
|
|
471
|
+
""
|
|
472
|
+
];
|
|
473
|
+
for (const table of SESSION_STORE_TABLES) {
|
|
474
|
+
const kindTag = table.kind === "session_db" ? "[session sql]" : `[${table.source}]`;
|
|
475
|
+
const cols = table.columns.map((c) => c.name).join(", ");
|
|
476
|
+
lines.push(`\u2022 **${table.name}** ${kindTag} \u2014 ${table.description}`);
|
|
477
|
+
lines.push(` Columns: ${cols}`);
|
|
478
|
+
lines.push("");
|
|
479
|
+
}
|
|
480
|
+
lines.push("Run `token-goat session-schema <table>` or `token-goat describe <table>` for detailed column specifications.");
|
|
481
|
+
return lines.join("\n");
|
|
482
|
+
}
|
|
483
|
+
function formatSessionStoreTable(table, opts) {
|
|
484
|
+
if (opts?.json === true) {
|
|
485
|
+
return displaySafeJson(table);
|
|
486
|
+
}
|
|
487
|
+
const lines = [
|
|
488
|
+
`# Schema: ${table.name} (${table.kind}, source: ${table.source})`,
|
|
489
|
+
"",
|
|
490
|
+
table.description,
|
|
491
|
+
"",
|
|
492
|
+
"## Columns",
|
|
493
|
+
"",
|
|
494
|
+
"| Column | Type | Description |",
|
|
495
|
+
"| :--- | :--- | :--- |"
|
|
496
|
+
];
|
|
497
|
+
for (const col of table.columns) {
|
|
498
|
+
const pk = col.primaryKey === true ? " *(PK)*" : "";
|
|
499
|
+
const req = col.notNull === true ? " *(not null)*" : "";
|
|
500
|
+
lines.push(`| \`${col.name}\`${pk}${req} | \`${col.type}\` | ${col.description} |`);
|
|
501
|
+
}
|
|
502
|
+
if (table.notes && table.notes.length > 0) {
|
|
503
|
+
lines.push("", "## Important Notes", "");
|
|
504
|
+
for (const note of table.notes) {
|
|
505
|
+
lines.push(`- ${note}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (table.commonMisnomers && Object.keys(table.commonMisnomers).length > 0) {
|
|
509
|
+
lines.push("", "## Common Column Misnomers & Pitfalls", "");
|
|
510
|
+
for (const [bad, good] of Object.entries(table.commonMisnomers)) {
|
|
511
|
+
lines.push(`- Don't use \`${bad}\` \u2192 use \`${good}\``);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return lines.join("\n");
|
|
515
|
+
}
|
|
516
|
+
async function describeTarget(target, table, opts) {
|
|
517
|
+
if (!target || target === "all" || target === "session_store_sql") {
|
|
518
|
+
return { exitCode: 0, text: formatSessionStoreCatalog(opts) };
|
|
519
|
+
}
|
|
520
|
+
const sessionTable = getSessionStoreTable(target);
|
|
521
|
+
if (sessionTable) {
|
|
522
|
+
return { exitCode: 0, text: formatSessionStoreTable(sessionTable, opts) };
|
|
523
|
+
}
|
|
524
|
+
const absPath = resolve(process.cwd(), target);
|
|
525
|
+
if (existsSync(absPath)) {
|
|
526
|
+
try {
|
|
527
|
+
const { isSqliteFile, getSqliteSchema, formatSqliteSchema } = await import("./token-goat-chunk-4EXFN2AW.mjs");
|
|
528
|
+
if (isSqliteFile(absPath)) {
|
|
529
|
+
const schema = getSqliteSchema(absPath);
|
|
530
|
+
if (table) {
|
|
531
|
+
const found = schema.tables.find((t) => t.name.toLowerCase() === table.toLowerCase());
|
|
532
|
+
if (!found) {
|
|
533
|
+
const avail2 = schema.tables.map((t) => t.name).join(", ");
|
|
534
|
+
return { exitCode: 1, text: `Table '${table}' not found in SQLite database ${target}. Available tables: ${avail2}` };
|
|
535
|
+
}
|
|
536
|
+
if (opts?.json === true) {
|
|
537
|
+
return { exitCode: 0, text: displaySafeJson(found) };
|
|
538
|
+
}
|
|
539
|
+
const lines = [
|
|
540
|
+
`# SQLite Table: ${found.name} (${found.kind}) in ${target}`,
|
|
541
|
+
`Row Count: ${found.rowCount ?? "unknown"}`,
|
|
542
|
+
"",
|
|
543
|
+
"| Column | Type | Nullable | Default | PK |",
|
|
544
|
+
"| :--- | :--- | :--- | :--- | :--- |"
|
|
545
|
+
];
|
|
546
|
+
for (const col of found.columns) {
|
|
547
|
+
lines.push(`| \`${col.name}\` | \`${col.type}\` | ${col.notNull ? "NO" : "YES"} | ${col.defaultValue ?? "NULL"} | ${col.primaryKey ? "YES" : "NO"} |`);
|
|
548
|
+
}
|
|
549
|
+
return { exitCode: 0, text: lines.join("\n") };
|
|
550
|
+
}
|
|
551
|
+
if (opts?.json === true) {
|
|
552
|
+
return { exitCode: 0, text: displaySafeJson(schema) };
|
|
553
|
+
}
|
|
554
|
+
return { exitCode: 0, text: formatSqliteSchema(schema) };
|
|
555
|
+
}
|
|
556
|
+
} catch (err) {
|
|
557
|
+
return { exitCode: 1, text: `Error reading SQLite database ${target}: ${err instanceof Error ? err.message : String(err)}` };
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const avail = SESSION_STORE_TABLES.map((t) => t.name).join(", ");
|
|
561
|
+
return {
|
|
562
|
+
exitCode: 1,
|
|
563
|
+
text: `Unknown table or SQLite file '${target}'. Available session store tables: ${avail}
|
|
564
|
+
Run 'token-goat session-schema' to see all tables.`
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function diagnoseSqlFailure(event, errorText) {
|
|
568
|
+
const toolName = (getToolName(event) || "").toLowerCase();
|
|
569
|
+
const isSqlTool = toolName === "session_store_sql" || toolName === "sql" || toolName === "sqlite_query" || toolName === "sqlite-query" || /sql/i.test(toolName);
|
|
570
|
+
const isColumnError = /referenced column\s*["']?([^"'\s]+)["']?\s*not found/i.test(errorText) || /no such column:\s*([^\s,;]+)/i.test(errorText) || /column\s*["']?([^"'\s]+)["']?\s*(?:does not exist|not found)/i.test(errorText) || /has no column named\s*([^\s,;]+)/i.test(errorText);
|
|
571
|
+
const isTableError = /table\s*["']?([^"'\s]+)["']?\s*(?:does not exist|not found)/i.test(errorText) || /no such table:\s*([^\s,;]+)/i.test(errorText);
|
|
572
|
+
if (!isColumnError && !isTableError && !isSqlTool) {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
const query = typeof event.toolInput["query"] === "string" ? event.toolInput["query"] : typeof event.toolInput["sql"] === "string" ? event.toolInput["sql"] : "";
|
|
576
|
+
let matchedTable;
|
|
577
|
+
if (query) {
|
|
578
|
+
const fromMatch = query.match(/(?:from|join)\s+([a-zA-Z0-9_]+)/i);
|
|
579
|
+
if (fromMatch && fromMatch[1]) {
|
|
580
|
+
matchedTable = getSessionStoreTable(fromMatch[1]);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
let badCol;
|
|
584
|
+
const colMatch = errorText.match(/referenced column\s*["']?([^"'\s]+)["']?\s*not found/i) ?? errorText.match(/no such column:\s*([^\s,;]+)/i) ?? errorText.match(/column\s*["']?([^"'\s]+)["']?\s*(?:does not exist|not found)/i) ?? errorText.match(/has no column named\s*([^\s,;]+)/i);
|
|
585
|
+
const matchedColName = colMatch?.[1];
|
|
586
|
+
if (matchedColName) {
|
|
587
|
+
const raw = matchedColName.trim();
|
|
588
|
+
if (!raw.startsWith("$") && !raw.includes("{")) {
|
|
589
|
+
badCol = raw.replace(/^[a-zA-Z0-9_]+\./, "").toLowerCase();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (matchedTable) {
|
|
593
|
+
const validCols = matchedTable.columns.map((c) => c.name).join(", ");
|
|
594
|
+
let advice = `[token-goat] Table '${matchedTable.name}' has columns: ${validCols}.`;
|
|
595
|
+
if (badCol && matchedTable.commonMisnomers && matchedTable.commonMisnomers[badCol]) {
|
|
596
|
+
advice += ` (For '${badCol}', use ${matchedTable.commonMisnomers[badCol]}).`;
|
|
597
|
+
} else if (badCol) {
|
|
598
|
+
advice += ` (Column '${badCol}' does not exist on '${matchedTable.name}').`;
|
|
599
|
+
}
|
|
600
|
+
advice += ` Run 'token-goat session-schema ${matchedTable.name}' to inspect full table structure.`;
|
|
601
|
+
return advice;
|
|
602
|
+
}
|
|
603
|
+
if (isTableError) {
|
|
604
|
+
const tableMatch = errorText.match(/table\s*["']?([^"'\s]+)["']?\s*(?:does not exist|not found)/i) ?? errorText.match(/no such table:\s*([^\s,;]+)/i);
|
|
605
|
+
const badTable = tableMatch ? tableMatch[1] : void 0;
|
|
606
|
+
if (badTable && (badTable.startsWith("$") || badTable.includes("{"))) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
const knownTables = SESSION_STORE_TABLES.map((t) => t.name).join(", ");
|
|
610
|
+
return `[token-goat] ${badTable ? `Table '${badTable}' does not exist.` : "Table not found."} Available session store tables: ${knownTables}. Run 'token-goat session-schema' for schema details.`;
|
|
611
|
+
}
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
function runSessionSchema(opts) {
|
|
615
|
+
if (!opts.table) {
|
|
616
|
+
process.stdout.write(formatSessionStoreCatalog({ json: opts.json }) + "\n");
|
|
617
|
+
return 0;
|
|
618
|
+
}
|
|
619
|
+
const found = getSessionStoreTable(opts.table);
|
|
620
|
+
if (!found) {
|
|
621
|
+
const avail = SESSION_STORE_TABLES.map((t) => t.name).join(", ");
|
|
622
|
+
process.stderr.write(`Unknown session store table '${opts.table}'. Available tables: ${avail}
|
|
623
|
+
Run 'token-goat session-schema' to see all tables.
|
|
624
|
+
`);
|
|
625
|
+
return 1;
|
|
626
|
+
}
|
|
627
|
+
process.stdout.write(formatSessionStoreTable(found, { json: opts.json }) + "\n");
|
|
628
|
+
return 0;
|
|
629
|
+
}
|
|
630
|
+
async function runDescribe(opts) {
|
|
631
|
+
const result = await describeTarget(opts.target, opts.table, { json: opts.json });
|
|
632
|
+
if (result.exitCode !== 0) {
|
|
633
|
+
process.stderr.write(result.text + "\n");
|
|
634
|
+
} else {
|
|
635
|
+
process.stdout.write(result.text + "\n");
|
|
636
|
+
}
|
|
637
|
+
return result.exitCode;
|
|
638
|
+
}
|
|
639
|
+
function cmdSessionSchema(table, opts) {
|
|
640
|
+
process.exitCode = runSessionSchema({ table, json: opts?.json });
|
|
641
|
+
}
|
|
642
|
+
async function cmdDescribe(target, table, opts) {
|
|
643
|
+
process.exitCode = await runDescribe({ target, table, json: opts?.json });
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/types.ts
|
|
647
|
+
init_define_import_meta_env();
|
|
648
|
+
var HOOK_EVENTS = [
|
|
649
|
+
"pre_tool_use",
|
|
650
|
+
"post_tool_use",
|
|
651
|
+
"notification",
|
|
652
|
+
"stop",
|
|
653
|
+
"pre_compact",
|
|
654
|
+
"post_compact",
|
|
655
|
+
"user_prompt_submit",
|
|
656
|
+
"subagent_stop",
|
|
657
|
+
"session_start",
|
|
658
|
+
"post_tool_use_failure"
|
|
659
|
+
];
|
|
660
|
+
|
|
661
|
+
// src/cli_commands.ts
|
|
662
|
+
init_define_import_meta_env();
|
|
663
|
+
function toEntry(cmd) {
|
|
664
|
+
return {
|
|
665
|
+
name: cmd.name(),
|
|
666
|
+
description: cmd.description(),
|
|
667
|
+
aliases: cmd.aliases(),
|
|
668
|
+
options: cmd.options.map((o) => ({ flags: o.flags, description: o.description })),
|
|
669
|
+
arguments: cmd.registeredArguments.map((a) => ({ name: a.name(), description: a.description, required: a.required })),
|
|
670
|
+
subcommands: cmd.commands.filter((sub) => sub.name() !== "help").map(toEntry)
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function buildCommandManifest(program) {
|
|
674
|
+
return program.commands.filter((cmd) => cmd.name() !== "help").map(toEntry);
|
|
675
|
+
}
|
|
676
|
+
function flattenCommandNames(manifest) {
|
|
677
|
+
const names = [];
|
|
678
|
+
for (const entry of manifest) {
|
|
679
|
+
names.push(entry.name);
|
|
680
|
+
for (const sub of entry.subcommands) {
|
|
681
|
+
names.push(`${entry.name} ${sub.name}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return names;
|
|
685
|
+
}
|
|
686
|
+
function entryMatches(entry, re, pattern) {
|
|
687
|
+
const haystacks = [entry.name, entry.description, ...entry.aliases];
|
|
688
|
+
return haystacks.some((h) => re !== null ? re.test(h) : h.includes(pattern));
|
|
689
|
+
}
|
|
690
|
+
function filterCommandManifest(manifest, pattern) {
|
|
691
|
+
const guarded = compileGuardedRegex(pattern);
|
|
692
|
+
const re = guarded.ok ? guarded.re : null;
|
|
693
|
+
const result = [];
|
|
694
|
+
for (const entry of manifest) {
|
|
695
|
+
if (entryMatches(entry, re, pattern)) {
|
|
696
|
+
result.push(entry);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
const matchingSubs = entry.subcommands.filter((sub) => entryMatches(sub, re, pattern));
|
|
700
|
+
if (matchingSubs.length > 0) {
|
|
701
|
+
result.push({ ...entry, subcommands: matchingSubs });
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return result;
|
|
705
|
+
}
|
|
706
|
+
function formatCommandManifest(manifest) {
|
|
707
|
+
const lines = [];
|
|
708
|
+
lines.push("# token-goat commands");
|
|
709
|
+
lines.push("");
|
|
710
|
+
for (const entry of manifest) {
|
|
711
|
+
lines.push(`## ${entry.name}${entry.aliases.length ? " (alias: " + entry.aliases.join(", ") + ")" : ""}${entry.description ? " -- " + entry.description : ""}`);
|
|
712
|
+
for (const arg of entry.arguments) {
|
|
713
|
+
lines.push(` arg: ${arg.name}${arg.required ? "" : " (optional)"}${arg.description ? " -- " + arg.description : ""}`);
|
|
714
|
+
}
|
|
715
|
+
for (const opt of entry.options) {
|
|
716
|
+
lines.push(` ${opt.flags}${opt.description ? " -- " + opt.description : ""}`);
|
|
717
|
+
}
|
|
718
|
+
for (const sub of entry.subcommands) {
|
|
719
|
+
lines.push(` ${entry.name} ${sub.name}${sub.description ? " -- " + sub.description : ""}`);
|
|
720
|
+
}
|
|
721
|
+
lines.push("");
|
|
722
|
+
}
|
|
723
|
+
return lines.join("\n").trimEnd();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/symbol_body_probe.ts
|
|
727
|
+
init_define_import_meta_env();
|
|
728
|
+
import * as fs from "fs";
|
|
729
|
+
var OVERSIZED_BODY_PROBE_SQL = `SELECT 1 FROM symbols WHERE LENGTH(body) > ${SYMBOL_BODY_CHAR_CAP} LIMIT 1`;
|
|
730
|
+
function checkSymbolBodySize(dbPath) {
|
|
731
|
+
if (!fs.existsSync(dbPath)) {
|
|
732
|
+
return { name: "Symbol body size", status: "ok", message: "no database yet" };
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
const db = getDb(dbPath);
|
|
736
|
+
const row = db.prepare(OVERSIZED_BODY_PROBE_SQL).get();
|
|
737
|
+
if (row !== void 0) {
|
|
738
|
+
return {
|
|
739
|
+
name: "Symbol body size",
|
|
740
|
+
status: "warn",
|
|
741
|
+
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`
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
return { name: "Symbol body size", status: "ok", message: "no stored symbol body exceeds the cap" };
|
|
745
|
+
} catch (err) {
|
|
746
|
+
return {
|
|
747
|
+
name: "Symbol body size",
|
|
748
|
+
status: "warn",
|
|
749
|
+
message: `could not query symbol body size: ${extractErrorMessage(err)}`
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/reconcile.ts
|
|
755
|
+
init_define_import_meta_env();
|
|
756
|
+
import * as fs2 from "node:fs";
|
|
757
|
+
var DEFAULT_RECONCILE_BUDGET_MS = 1500;
|
|
758
|
+
function readReconcileCursor(dbPath, root) {
|
|
759
|
+
try {
|
|
760
|
+
const row = getDb(dbPath).prepare("SELECT last_scanned_path FROM reconcile_cursor WHERE root = ?").get(root);
|
|
761
|
+
return row?.last_scanned_path ?? null;
|
|
762
|
+
} catch {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function writeReconcileCursor(dbPath, root, lastScannedPath) {
|
|
767
|
+
try {
|
|
768
|
+
getDb(dbPath).prepare(
|
|
769
|
+
"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"
|
|
770
|
+
).run(root, lastScannedPath, Date.now());
|
|
771
|
+
} catch {
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function clearReconcileCursor(dbPath, root) {
|
|
775
|
+
try {
|
|
776
|
+
getDb(dbPath).prepare("DELETE FROM reconcile_cursor WHERE root = ?").run(root);
|
|
777
|
+
} catch {
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function runReconcile(opts = {}) {
|
|
781
|
+
const raw = reconcileProject(opts);
|
|
782
|
+
const root = getDisplayRoot(opts.cwd ?? process.cwd());
|
|
783
|
+
const display = (paths) => paths.map((p) => toDisplayPath(root, p)).sort();
|
|
784
|
+
const result = {
|
|
785
|
+
...raw,
|
|
786
|
+
changed: display(raw.changed),
|
|
787
|
+
added: display(raw.added),
|
|
788
|
+
removed: display(raw.removed)
|
|
789
|
+
};
|
|
790
|
+
if (opts.json === true) {
|
|
791
|
+
process.stdout.write(`${displaySafeJson(result)}
|
|
792
|
+
`);
|
|
793
|
+
return 0;
|
|
794
|
+
}
|
|
795
|
+
const lines = [];
|
|
796
|
+
if (isReconcileClean(result)) {
|
|
797
|
+
if (!result.trackedUnavailable) {
|
|
798
|
+
lines.push(`Index matches disk: ${countNoun(result.scanned, "file")} checked in ${result.elapsedMs}ms.`);
|
|
799
|
+
}
|
|
800
|
+
} else {
|
|
801
|
+
const verb = opts.dryRun === true ? "would reindex" : "queued for reindexing";
|
|
802
|
+
const dropVerb = opts.dryRun === true ? "would drop from the index" : "queued for removal";
|
|
803
|
+
if (result.changed.length > 0) lines.push(`${countNoun(result.changed.length, "file")} changed since indexing (${verb}):`);
|
|
804
|
+
for (const f of result.changed) lines.push(` ~ ${f}`);
|
|
805
|
+
if (result.added.length > 0) lines.push(`${countNoun(result.added.length, "file")} not in the index (${verb}):`);
|
|
806
|
+
for (const f of result.added) lines.push(` + ${f}`);
|
|
807
|
+
if (result.removed.length > 0) lines.push(`${countNoun(result.removed.length, "file")} indexed but gone from disk (${dropVerb}):`);
|
|
808
|
+
for (const f of result.removed) lines.push(` - ${f}`);
|
|
809
|
+
lines.push(`Checked ${countNoun(result.scanned, "file")} in ${result.elapsedMs}ms.`);
|
|
810
|
+
}
|
|
811
|
+
if (result.mtimeOnly > 0) {
|
|
812
|
+
lines.push(`${countNoun(result.mtimeOnly, "file")} had a newer timestamp but identical content, so ${result.mtimeOnly === 1 ? "it was" : "they were"} left alone.`);
|
|
813
|
+
}
|
|
814
|
+
if (result.parserStale > 0) {
|
|
815
|
+
const reparseVerb = opts.dryRun === true ? "would reparse" : "queued for reparse";
|
|
816
|
+
lines.push(`${countNoun(result.parserStale, "file")} of the above ${result.parserStale === 1 ? "was" : "were"} unchanged on disk but indexed by an older parser (${reparseVerb}).`);
|
|
817
|
+
}
|
|
818
|
+
if (result.trackedUnavailable) {
|
|
819
|
+
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.");
|
|
820
|
+
}
|
|
821
|
+
if (result.budgetExhausted) {
|
|
822
|
+
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.`);
|
|
823
|
+
}
|
|
824
|
+
process.stdout.write(`${lines.join("\n")}
|
|
825
|
+
`);
|
|
826
|
+
return 0;
|
|
827
|
+
}
|
|
828
|
+
function isReconcileClean(r) {
|
|
829
|
+
return r.changed.length === 0 && r.added.length === 0 && r.removed.length === 0;
|
|
830
|
+
}
|
|
831
|
+
function reconcileProject(opts = {}) {
|
|
832
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
833
|
+
const budgetMs = opts.budgetMs ?? DEFAULT_RECONCILE_BUDGET_MS;
|
|
834
|
+
const startedAt = Date.now();
|
|
835
|
+
const dbPath = opts.dbPath ?? globalDbPath();
|
|
836
|
+
const tracked = getTrackedFiles(cwd);
|
|
837
|
+
const projectRoot = resolveIndexPath(".", cwd);
|
|
838
|
+
const indexed = getProjectFileEntries(projectRoot, dbPath);
|
|
839
|
+
let scanOrder = tracked;
|
|
840
|
+
const cursorPath = tracked.length > 0 ? readReconcileCursor(dbPath, projectRoot) : null;
|
|
841
|
+
if (cursorPath !== null) {
|
|
842
|
+
const cursorFolded = foldPath(normalizePath(cursorPath));
|
|
843
|
+
const cursorIdx = tracked.findIndex((f) => foldPath(normalizePath(f)) === cursorFolded);
|
|
844
|
+
if (cursorIdx !== -1) {
|
|
845
|
+
scanOrder = [...tracked.slice(cursorIdx + 1), ...tracked.slice(0, cursorIdx + 1)];
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
const changed = [];
|
|
849
|
+
const added = [];
|
|
850
|
+
const seenOnDisk = /* @__PURE__ */ new Set();
|
|
851
|
+
let mtimeOnly = 0;
|
|
852
|
+
let parserStale = 0;
|
|
853
|
+
let scanned = 0;
|
|
854
|
+
let budgetExhausted = false;
|
|
855
|
+
let lastScanned = null;
|
|
856
|
+
for (const file of scanOrder) {
|
|
857
|
+
if (Date.now() - startedAt > budgetMs) {
|
|
858
|
+
budgetExhausted = true;
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
scanned++;
|
|
862
|
+
lastScanned = file;
|
|
863
|
+
const folded = foldPath(normalizePath(file));
|
|
864
|
+
seenOnDisk.add(folded);
|
|
865
|
+
const entry = indexed.get(folded);
|
|
866
|
+
if (entry === void 0) {
|
|
867
|
+
added.push(file);
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
if (entry.parserSha !== PARSER_FINGERPRINT) {
|
|
871
|
+
changed.push(file);
|
|
872
|
+
parserStale++;
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
let mtimeMs;
|
|
876
|
+
try {
|
|
877
|
+
mtimeMs = fs2.statSync(file).mtimeMs;
|
|
878
|
+
} catch {
|
|
879
|
+
changed.push(file);
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
if (entry.mtime !== 0 && mtimeMs === entry.mtime) continue;
|
|
883
|
+
const diskSha = fingerprintFile(file);
|
|
884
|
+
if (diskSha === null) {
|
|
885
|
+
changed.push(file);
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (diskSha === entry.sha) {
|
|
889
|
+
mtimeOnly++;
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
changed.push(file);
|
|
893
|
+
}
|
|
894
|
+
const trackedUnavailable = tracked.length === 0 && indexed.size > 0;
|
|
895
|
+
const removed = [];
|
|
896
|
+
if (!budgetExhausted && !trackedUnavailable) {
|
|
897
|
+
for (const [folded, entry] of indexed) {
|
|
898
|
+
if (seenOnDisk.has(folded)) continue;
|
|
899
|
+
if (Date.now() - startedAt > budgetMs) {
|
|
900
|
+
budgetExhausted = true;
|
|
901
|
+
removed.length = 0;
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
fs2.statSync(entry.filePath);
|
|
906
|
+
} catch {
|
|
907
|
+
removed.push(entry.filePath);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
let enqueued = 0;
|
|
912
|
+
if (opts.dryRun !== true) {
|
|
913
|
+
for (const p of [...changed, ...added, ...removed]) {
|
|
914
|
+
enqueueDirtyPathSafe(resolveIndexPath(p, cwd), { alreadyResolved: true });
|
|
915
|
+
enqueued++;
|
|
916
|
+
}
|
|
917
|
+
if (budgetExhausted) {
|
|
918
|
+
if (lastScanned !== null) writeReconcileCursor(dbPath, projectRoot, lastScanned);
|
|
919
|
+
} else if (tracked.length > 0) {
|
|
920
|
+
clearReconcileCursor(dbPath, projectRoot);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return {
|
|
924
|
+
scanned,
|
|
925
|
+
changed,
|
|
926
|
+
added,
|
|
927
|
+
removed,
|
|
928
|
+
mtimeOnly,
|
|
929
|
+
parserStale,
|
|
930
|
+
budgetExhausted,
|
|
931
|
+
trackedUnavailable,
|
|
932
|
+
unscanned: Math.max(0, tracked.length - scanned),
|
|
933
|
+
enqueued,
|
|
934
|
+
elapsedMs: Date.now() - startedAt
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
export {
|
|
939
|
+
checkSymbolBodySize,
|
|
940
|
+
WEB_OUTPUT_SUBDIR,
|
|
941
|
+
storeWebOutput,
|
|
942
|
+
getWebOutput,
|
|
943
|
+
getWebOutputRaw,
|
|
944
|
+
getWebOutputByUrlFromDisk,
|
|
945
|
+
DEFAULT_RECONCILE_BUDGET_MS,
|
|
946
|
+
runReconcile,
|
|
947
|
+
isReconcileClean,
|
|
948
|
+
reconcileProject,
|
|
949
|
+
MAX_STDIN_BYTES,
|
|
950
|
+
readStdinJson,
|
|
951
|
+
diagnoseSqlFailure,
|
|
952
|
+
cmdSessionSchema,
|
|
953
|
+
cmdDescribe,
|
|
954
|
+
HOOK_EVENTS,
|
|
955
|
+
buildCommandManifest,
|
|
956
|
+
flattenCommandNames,
|
|
957
|
+
filterCommandManifest,
|
|
958
|
+
formatCommandManifest
|
|
959
|
+
};
|