token-goat 2.6.36 → 2.8.0
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 +31 -5
- package/SECURITY.md +46 -17
- package/dist/{token-goat-chunk-UFOVM7ZN.mjs → token-goat-chunk-2F6TFBZE.mjs} +1274 -367
- package/dist/token-goat-chunk-324QOJYZ.mjs +91 -0
- package/dist/{token-goat-chunk-V465YKOR.mjs → token-goat-chunk-44Y77VHR.mjs} +365 -23
- package/dist/{token-goat-chunk-CNDOJ3ZP.mjs → token-goat-chunk-4KZILRZN.mjs} +2 -2
- package/dist/token-goat-chunk-5CVKO3DA.mjs +185 -0
- package/dist/token-goat-chunk-A62K4XW2.mjs +23 -0
- package/dist/{token-goat-chunk-VYMGEVZS.mjs → token-goat-chunk-AO6MFFTW.mjs} +317 -11
- package/dist/{token-goat-chunk-65BKISIS.mjs → token-goat-chunk-FBGBTICM.mjs} +4 -4
- package/dist/{token-goat-chunk-LN6OUHTV.mjs → token-goat-chunk-J35AKWEQ.mjs} +5 -5
- package/dist/{token-goat-hook-chunk-BDR6C6IE.mjs → token-goat-chunk-LVCBDJVE.mjs} +318 -51
- package/dist/token-goat-chunk-R4SR7MQY.mjs +486 -0
- package/dist/{token-goat-chunk-IYTVE6KN.mjs → token-goat-chunk-SRAR6DOK.mjs} +133 -141
- package/dist/{token-goat-chunk-MGOUYAA2.mjs → token-goat-chunk-TUPJRK7R.mjs} +1 -1
- package/dist/{token-goat-chunk-KYFJC37X.mjs → token-goat-chunk-VXSYZGBA.mjs} +582 -135
- package/dist/{token-goat-chunk-DG53MVNJ.mjs → token-goat-chunk-WN5T5EW5.mjs} +212 -212
- package/dist/token-goat-hook.mjs +7 -7
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +9 -7
- package/dist/token-goat-chunk-FRTBMRP7.mjs +0 -10048
- package/dist/token-goat-hook-chunk-3QYSN4QV.mjs +0 -14764
- package/dist/token-goat-hook-chunk-3ZDBWJDF.mjs +0 -13659
- package/dist/token-goat-hook-chunk-5UH54CW6.mjs +0 -912
- package/dist/token-goat-hook-chunk-6ODM3MP7.mjs +0 -706
- package/dist/token-goat-hook-chunk-A77A26A7.mjs +0 -18184
- package/dist/token-goat-hook-chunk-BUOCULAM.mjs +0 -29
- package/dist/token-goat-hook-chunk-C6GIABOX.mjs +0 -15971
- package/dist/token-goat-hook-chunk-E257IGSN.mjs +0 -153
- package/dist/token-goat-hook-chunk-MW5HPEGD.mjs +0 -10411
- package/dist/token-goat-hook-chunk-QSCYNJ2B.mjs +0 -23
- package/dist/token-goat-hook-chunk-RFRLWOQH.mjs +0 -11
- package/dist/token-goat-hook-chunk-XUMIVYEN.mjs +0 -109
- package/dist/token-goat-hook-chunk-Y2WHX2P3.mjs +0 -6463
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
detectLanguage,
|
|
20
20
|
displaySafePath,
|
|
21
21
|
displaySafeText,
|
|
22
|
+
ensureDataDirPrivate,
|
|
22
23
|
ensureDirSync,
|
|
23
24
|
escapeRegExp,
|
|
24
25
|
extractEnv,
|
|
@@ -89,7 +90,7 @@ import {
|
|
|
89
90
|
withFileLock,
|
|
90
91
|
writeIfDifferent,
|
|
91
92
|
writeJsonSettings
|
|
92
|
-
} from "./token-goat-chunk-
|
|
93
|
+
} from "./token-goat-chunk-44Y77VHR.mjs";
|
|
93
94
|
import {
|
|
94
95
|
registerReset
|
|
95
96
|
} from "./token-goat-chunk-AO2QD2AG.mjs";
|
|
@@ -113,15 +114,221 @@ function createLazyModuleLoader(load3, errorLabel) {
|
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
// src/xml_parser.ts
|
|
118
|
+
var MAX_XML_DEPTH = 512;
|
|
119
|
+
var NAMED_ENTITIES = {
|
|
120
|
+
lt: "<",
|
|
121
|
+
gt: ">",
|
|
122
|
+
amp: "&",
|
|
123
|
+
quot: '"',
|
|
124
|
+
apos: "'"
|
|
125
|
+
};
|
|
126
|
+
function decodeXmlEntities(text) {
|
|
127
|
+
if (!text.includes("&")) return text;
|
|
128
|
+
return text.replace(/&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z_][\w.:-]*);/g, (whole, body) => {
|
|
129
|
+
if (body.charCodeAt(0) === 35) {
|
|
130
|
+
const isHex = body.charCodeAt(1) === 120 || body.charCodeAt(1) === 88;
|
|
131
|
+
const digits = isHex ? body.slice(2) : body.slice(1);
|
|
132
|
+
const code = parseInt(digits, isHex ? 16 : 10);
|
|
133
|
+
if (!Number.isFinite(code) || code < 0 || code > 1114111) return whole;
|
|
134
|
+
if (code >= 55296 && code <= 57343) return whole;
|
|
135
|
+
return String.fromCodePoint(code);
|
|
136
|
+
}
|
|
137
|
+
const named = NAMED_ENTITIES[body];
|
|
138
|
+
return named ?? whole;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function newFrame(name) {
|
|
142
|
+
return { name, children: /* @__PURE__ */ new Map(), text: [], attrs: [] };
|
|
143
|
+
}
|
|
144
|
+
function finishFrame(frame) {
|
|
145
|
+
const text = frame.text.join("");
|
|
146
|
+
if (frame.children.size === 0 && frame.attrs.length === 0) return text;
|
|
147
|
+
const obj = {};
|
|
148
|
+
for (const [key, value] of frame.children) obj[key] = value;
|
|
149
|
+
if (text.length > 0) obj["#text"] = text;
|
|
150
|
+
for (const [key, value] of frame.attrs) obj[`@_${key}`] = value;
|
|
151
|
+
return obj;
|
|
152
|
+
}
|
|
153
|
+
function addChild(parent, name, value) {
|
|
154
|
+
const existing = parent.children.get(name);
|
|
155
|
+
if (existing === void 0 && !parent.children.has(name)) {
|
|
156
|
+
parent.children.set(name, value);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (Array.isArray(existing)) {
|
|
160
|
+
existing.push(value);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
parent.children.set(name, [existing, value]);
|
|
164
|
+
}
|
|
165
|
+
var WHITESPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r"]);
|
|
166
|
+
function findTagEnd(src, from) {
|
|
167
|
+
let quote = "";
|
|
168
|
+
for (let i = from; i < src.length; i++) {
|
|
169
|
+
const ch = src[i];
|
|
170
|
+
if (quote !== "") {
|
|
171
|
+
if (ch === quote) quote = "";
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (ch === '"' || ch === "'") {
|
|
175
|
+
quote = ch;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (ch === ">") return i;
|
|
179
|
+
}
|
|
180
|
+
return -1;
|
|
181
|
+
}
|
|
182
|
+
function skipDeclaration(src, from) {
|
|
183
|
+
let quote = "";
|
|
184
|
+
let inSubset = false;
|
|
185
|
+
for (let i = from + 2; i < src.length; i++) {
|
|
186
|
+
const ch = src[i];
|
|
187
|
+
if (quote !== "") {
|
|
188
|
+
if (ch === quote) quote = "";
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (ch === '"' || ch === "'") {
|
|
192
|
+
quote = ch;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (ch === "[") inSubset = true;
|
|
196
|
+
else if (ch === "]") inSubset = false;
|
|
197
|
+
else if (ch === ">" && !inSubset) return i + 1;
|
|
198
|
+
}
|
|
199
|
+
return src.length;
|
|
200
|
+
}
|
|
201
|
+
function parseTagBody(body) {
|
|
202
|
+
let i = 0;
|
|
203
|
+
while (i < body.length && !WHITESPACE.has(body[i])) i++;
|
|
204
|
+
const name = body.slice(0, i);
|
|
205
|
+
const attrs = [];
|
|
206
|
+
while (i < body.length) {
|
|
207
|
+
while (i < body.length && WHITESPACE.has(body[i])) i++;
|
|
208
|
+
if (i >= body.length) break;
|
|
209
|
+
const nameStart = i;
|
|
210
|
+
while (i < body.length && !WHITESPACE.has(body[i]) && body[i] !== "=") i++;
|
|
211
|
+
const attrName = body.slice(nameStart, i);
|
|
212
|
+
if (attrName.length === 0) {
|
|
213
|
+
i++;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
while (i < body.length && WHITESPACE.has(body[i])) i++;
|
|
217
|
+
if (body[i] !== "=") {
|
|
218
|
+
attrs.push([attrName, ""]);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
i++;
|
|
222
|
+
while (i < body.length && WHITESPACE.has(body[i])) i++;
|
|
223
|
+
const quote = body[i];
|
|
224
|
+
if (quote === '"' || quote === "'") {
|
|
225
|
+
const valueStart = i + 1;
|
|
226
|
+
const valueEnd = body.indexOf(quote, valueStart);
|
|
227
|
+
const end = valueEnd === -1 ? body.length : valueEnd;
|
|
228
|
+
attrs.push([attrName, decodeXmlEntities(body.slice(valueStart, end))]);
|
|
229
|
+
i = end + 1;
|
|
230
|
+
} else {
|
|
231
|
+
const valueStart = i;
|
|
232
|
+
while (i < body.length && !WHITESPACE.has(body[i])) i++;
|
|
233
|
+
attrs.push([attrName, decodeXmlEntities(body.slice(valueStart, i))]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { name, attrs };
|
|
237
|
+
}
|
|
238
|
+
function parseXml(xml) {
|
|
239
|
+
const src = xml.charCodeAt(0) === 65279 ? xml.slice(1) : xml;
|
|
240
|
+
const root = newFrame("");
|
|
241
|
+
const stack = [root];
|
|
242
|
+
const len = src.length;
|
|
243
|
+
let i = 0;
|
|
244
|
+
const top = () => stack[stack.length - 1];
|
|
245
|
+
const pushText = (from, to) => {
|
|
246
|
+
if (to > from) top().text.push(decodeXmlEntities(src.slice(from, to)));
|
|
247
|
+
};
|
|
248
|
+
const closeElement = (name) => {
|
|
249
|
+
let target = -1;
|
|
250
|
+
for (let k = stack.length - 1; k >= 1; k--) {
|
|
251
|
+
if (stack[k].name === name) {
|
|
252
|
+
target = k;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (target === -1) return;
|
|
257
|
+
while (stack.length > target) {
|
|
258
|
+
const frame = stack.pop();
|
|
259
|
+
addChild(top(), frame.name, finishFrame(frame));
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
while (i < len) {
|
|
263
|
+
const lt = src.indexOf("<", i);
|
|
264
|
+
if (lt === -1) {
|
|
265
|
+
pushText(i, len);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
pushText(i, lt);
|
|
269
|
+
if (src.startsWith("<!--", lt)) {
|
|
270
|
+
const end2 = src.indexOf("-->", lt + 4);
|
|
271
|
+
i = end2 === -1 ? len : end2 + 3;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (src.startsWith("<![CDATA[", lt)) {
|
|
275
|
+
const end2 = src.indexOf("]]>", lt + 9);
|
|
276
|
+
top().text.push(src.slice(lt + 9, end2 === -1 ? len : end2));
|
|
277
|
+
i = end2 === -1 ? len : end2 + 3;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (src.startsWith("<!", lt)) {
|
|
281
|
+
i = skipDeclaration(src, lt);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (src.startsWith("<?", lt)) {
|
|
285
|
+
const end2 = src.indexOf("?>", lt + 2);
|
|
286
|
+
const { name: name2, attrs: attrs2 } = parseTagBody(src.slice(lt + 2, end2 === -1 ? len : end2));
|
|
287
|
+
if (name2.length > 0) {
|
|
288
|
+
const frame2 = newFrame(`?${name2}`);
|
|
289
|
+
frame2.attrs = attrs2;
|
|
290
|
+
addChild(top(), frame2.name, finishFrame(frame2));
|
|
291
|
+
}
|
|
292
|
+
i = end2 === -1 ? len : end2 + 2;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (src.startsWith("</", lt)) {
|
|
296
|
+
const end2 = src.indexOf(">", lt + 2);
|
|
297
|
+
closeElement(src.slice(lt + 2, end2 === -1 ? len : end2).trim());
|
|
298
|
+
i = end2 === -1 ? len : end2 + 1;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const end = findTagEnd(src, lt + 1);
|
|
302
|
+
const tagEnd = end === -1 ? len : end;
|
|
303
|
+
let body = src.slice(lt + 1, tagEnd);
|
|
304
|
+
const selfClosing = body.endsWith("/");
|
|
305
|
+
if (selfClosing) body = body.slice(0, -1);
|
|
306
|
+
const { name, attrs } = parseTagBody(body);
|
|
307
|
+
i = end === -1 ? len : end + 1;
|
|
308
|
+
if (name.length === 0) continue;
|
|
309
|
+
const frame = newFrame(name);
|
|
310
|
+
frame.attrs = attrs;
|
|
311
|
+
if (selfClosing) {
|
|
312
|
+
addChild(top(), name, finishFrame(frame));
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (stack.length > MAX_XML_DEPTH) {
|
|
316
|
+
throw new Error(`XML nesting deeper than ${MAX_XML_DEPTH} elements; refusing to parse (this file is not something any office application produces)`);
|
|
317
|
+
}
|
|
318
|
+
stack.push(frame);
|
|
319
|
+
}
|
|
320
|
+
while (stack.length > 1) {
|
|
321
|
+
const frame = stack.pop();
|
|
322
|
+
addChild(top(), frame.name, finishFrame(frame));
|
|
323
|
+
}
|
|
324
|
+
return Object.fromEntries(root.children);
|
|
325
|
+
}
|
|
326
|
+
|
|
116
327
|
// src/ooxml_extract.ts
|
|
117
328
|
var loadFflate = createLazyModuleLoader(
|
|
118
329
|
async () => await import("fflate"),
|
|
119
330
|
"office-file reading disabled (fflate unavailable)"
|
|
120
331
|
);
|
|
121
|
-
var loadXmlParser = createLazyModuleLoader(
|
|
122
|
-
async () => await import("fast-xml-parser"),
|
|
123
|
-
"office-file reading disabled (fast-xml-parser unavailable)"
|
|
124
|
-
);
|
|
125
332
|
var MAX_OOXML_INPUT_BYTES = 50 * 1024 * 1024;
|
|
126
333
|
function accessFailureMessage(err, filePath) {
|
|
127
334
|
const code = err?.code;
|
|
@@ -159,15 +366,7 @@ function decodeZipEntry(entries, entryPath) {
|
|
|
159
366
|
return new TextDecoder("utf-8").decode(bytes);
|
|
160
367
|
}
|
|
161
368
|
async function parseOoxmlPart(xmlText2) {
|
|
162
|
-
|
|
163
|
-
if (!fxp) throw new Error("fast-xml-parser is not installed; run `npm install fast-xml-parser` to enable this command");
|
|
164
|
-
const parser = new fxp.XMLParser({
|
|
165
|
-
ignoreAttributes: false,
|
|
166
|
-
preserveOrder: false,
|
|
167
|
-
trimValues: false,
|
|
168
|
-
parseTagValue: false
|
|
169
|
-
});
|
|
170
|
-
return parser.parse(xmlText2);
|
|
369
|
+
return parseXml(xmlText2);
|
|
171
370
|
}
|
|
172
371
|
function pushTextValue(runs, val) {
|
|
173
372
|
if (Array.isArray(val)) {
|
|
@@ -345,9 +544,9 @@ async function notesPathFor(entries, slidePath) {
|
|
|
345
544
|
}
|
|
346
545
|
return null;
|
|
347
546
|
}
|
|
348
|
-
async function parseSlide(entries,
|
|
349
|
-
const xml = decodeZipEntry(entries,
|
|
350
|
-
if (xml === null) throw new Error(`missing part: ${
|
|
547
|
+
async function parseSlide(entries, path31) {
|
|
548
|
+
const xml = decodeZipEntry(entries, path31);
|
|
549
|
+
if (xml === null) throw new Error(`missing part: ${path31}`);
|
|
351
550
|
return parseOoxmlPart(xml);
|
|
352
551
|
}
|
|
353
552
|
async function notesTextFor(entries, notesPath) {
|
|
@@ -363,8 +562,8 @@ async function pptxOutline(filePath) {
|
|
|
363
562
|
const { entries, slidePaths } = await listSlideParts(filePath);
|
|
364
563
|
const out = [];
|
|
365
564
|
for (let i = 0; i < slidePaths.length; i++) {
|
|
366
|
-
const
|
|
367
|
-
const parsed = await parseSlide(entries,
|
|
565
|
+
const path31 = slidePaths[i];
|
|
566
|
+
const parsed = await parseSlide(entries, path31);
|
|
368
567
|
const shapes = slideShapes(parsed);
|
|
369
568
|
const titleShape = shapes.find((s) => {
|
|
370
569
|
const t = shapePlaceholderType(s);
|
|
@@ -373,7 +572,7 @@ async function pptxOutline(filePath) {
|
|
|
373
572
|
const title = titleShape !== void 0 ? shapeText(titleShape) : "";
|
|
374
573
|
const allText = collectTextRuns(parsed, "a:t").join(" ");
|
|
375
574
|
const bodyChars = Math.max(0, allText.length - title.length);
|
|
376
|
-
const hasNotes = (await notesTextFor(entries, await notesPathFor(entries,
|
|
575
|
+
const hasNotes = (await notesTextFor(entries, await notesPathFor(entries, path31))).length > 0;
|
|
377
576
|
out.push({ slide: i + 1, title, bodyChars, hasNotes });
|
|
378
577
|
}
|
|
379
578
|
return out;
|
|
@@ -383,13 +582,13 @@ async function pptxSlideText(filePath, slideNumber, includeNotes) {
|
|
|
383
582
|
if (slideNumber < 1 || slideNumber > slidePaths.length) {
|
|
384
583
|
throw new Error(`slide ${slideNumber} out of range (this deck has ${slidePaths.length} slides)`);
|
|
385
584
|
}
|
|
386
|
-
const
|
|
387
|
-
const parsed = await parseSlide(entries,
|
|
585
|
+
const path31 = slidePaths[slideNumber - 1];
|
|
586
|
+
const parsed = await parseSlide(entries, path31);
|
|
388
587
|
const shapes = slideShapes(parsed);
|
|
389
588
|
const blocks = [...shapes.map(shapeText).filter((t) => t.length > 0), ...tableRowBlocks(parsed)];
|
|
390
589
|
const lines2 = [`# Slide ${slideNumber}`, ...blocks];
|
|
391
590
|
if (includeNotes) {
|
|
392
|
-
const notes = await notesTextFor(entries, await notesPathFor(entries,
|
|
591
|
+
const notes = await notesTextFor(entries, await notesPathFor(entries, path31));
|
|
393
592
|
if (notes.length > 0) lines2.push("", "## Speaker notes", notes);
|
|
394
593
|
}
|
|
395
594
|
return lines2.join("\n\n");
|
|
@@ -3023,19 +3222,30 @@ var CLAUDE_CODE_EVENT_NAMES = {
|
|
|
3023
3222
|
notification: "Notification",
|
|
3024
3223
|
stop: "Stop",
|
|
3025
3224
|
pre_compact: "PreCompact",
|
|
3225
|
+
post_compact: "PostCompact",
|
|
3026
3226
|
user_prompt_submit: "UserPromptSubmit",
|
|
3027
3227
|
subagent_stop: "SubagentStop",
|
|
3028
|
-
session_start: "SessionStart"
|
|
3228
|
+
session_start: "SessionStart",
|
|
3229
|
+
// Claude Code has no separate failure event -- a failed tool arrives on PostToolUse there,
|
|
3230
|
+
// and only Copilot splits it out. This entry exists because the map is exhaustive over
|
|
3231
|
+
// HookEventName, and it is a spelling for the response envelope rather than a claim that
|
|
3232
|
+
// Claude Code will ever send this event. Nothing iterates this map to build install config,
|
|
3233
|
+
// so naming an event Claude Code does not have cannot register one.
|
|
3234
|
+
post_tool_use_failure: "PostToolUseFailure"
|
|
3029
3235
|
};
|
|
3030
3236
|
var EVENTS_WITHOUT_ADDITIONAL_CONTEXT = /* @__PURE__ */ new Set([
|
|
3031
3237
|
"notification",
|
|
3032
3238
|
"pre_compact"
|
|
3033
3239
|
]);
|
|
3034
|
-
|
|
3240
|
+
var EVENTS_WITH_RAW_STDOUT_CONTEXT = /* @__PURE__ */ new Set(["pre_compact"]);
|
|
3241
|
+
function serializeOutput(output, eventName, harness) {
|
|
3035
3242
|
switch (output.hookType) {
|
|
3036
3243
|
case "deny":
|
|
3037
3244
|
return JSON.stringify({ decision: "block", reason: output.message });
|
|
3038
3245
|
case "context":
|
|
3246
|
+
if (EVENTS_WITH_RAW_STDOUT_CONTEXT.has(eventName) && harness === "claudecode") {
|
|
3247
|
+
return output.context;
|
|
3248
|
+
}
|
|
3039
3249
|
if (EVENTS_WITHOUT_ADDITIONAL_CONTEXT.has(eventName)) {
|
|
3040
3250
|
return JSON.stringify({ systemMessage: output.context });
|
|
3041
3251
|
}
|
|
@@ -3499,6 +3709,7 @@ var _bashOutputs = /* @__PURE__ */ new Map();
|
|
|
3499
3709
|
var _grepQueries = /* @__PURE__ */ new Map();
|
|
3500
3710
|
var _globQueries = /* @__PURE__ */ new Map();
|
|
3501
3711
|
var _lastTabContext = null;
|
|
3712
|
+
var _seenImageHashes = [];
|
|
3502
3713
|
var _outstandingAgentSpawns = [];
|
|
3503
3714
|
var _outstandingAgentSpawnsAtLoad = [];
|
|
3504
3715
|
var _bashReruns = /* @__PURE__ */ new Set();
|
|
@@ -3698,6 +3909,18 @@ function setLastTabContext(text) {
|
|
|
3698
3909
|
function getLastTabContext() {
|
|
3699
3910
|
return _lastTabContext;
|
|
3700
3911
|
}
|
|
3912
|
+
var MAX_SEEN_IMAGE_HASHES = 16;
|
|
3913
|
+
function hasSeenImage(hash2) {
|
|
3914
|
+
return _seenImageHashes.includes(hash2);
|
|
3915
|
+
}
|
|
3916
|
+
function recordSeenImage(hash2) {
|
|
3917
|
+
const at = _seenImageHashes.indexOf(hash2);
|
|
3918
|
+
if (at !== -1) _seenImageHashes.splice(at, 1);
|
|
3919
|
+
_seenImageHashes.push(hash2);
|
|
3920
|
+
if (_seenImageHashes.length > MAX_SEEN_IMAGE_HASHES) {
|
|
3921
|
+
_seenImageHashes.splice(0, _seenImageHashes.length - MAX_SEEN_IMAGE_HASHES);
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3701
3924
|
var MAX_OUTSTANDING_AGENT_SPAWNS = 30;
|
|
3702
3925
|
function recordOutstandingAgentSpawn(prompt) {
|
|
3703
3926
|
_outstandingAgentSpawns.push({ prompt, ts: Date.now() });
|
|
@@ -3811,6 +4034,7 @@ function exportSessionState() {
|
|
|
3811
4034
|
globQueries: Array.from(_globQueries.entries()),
|
|
3812
4035
|
outstandingAgentSpawns: _outstandingAgentSpawns.map((e) => [e.prompt, e.ts]),
|
|
3813
4036
|
..._lastTabContext !== null ? { lastTabContext: _lastTabContext } : {},
|
|
4037
|
+
..._seenImageHashes.length > 0 ? { seenImageHashes: [..._seenImageHashes] } : {},
|
|
3814
4038
|
..._compactedAt > 0 ? { compactedAt: _compactedAt } : {}
|
|
3815
4039
|
};
|
|
3816
4040
|
}
|
|
@@ -3836,6 +4060,7 @@ function importSessionState(s) {
|
|
|
3836
4060
|
_outstandingAgentSpawns = (s.outstandingAgentSpawns ?? []).map(([prompt, ts]) => ({ prompt, ts }));
|
|
3837
4061
|
_outstandingAgentSpawnsAtLoad = [..._outstandingAgentSpawns];
|
|
3838
4062
|
_lastTabContext = s.lastTabContext ?? null;
|
|
4063
|
+
_seenImageHashes = [...s.seenImageHashes ?? []];
|
|
3839
4064
|
_compactedAt = s.compactedAt ?? 0;
|
|
3840
4065
|
}
|
|
3841
4066
|
registerReset(() => {
|
|
@@ -3857,6 +4082,7 @@ registerReset(() => {
|
|
|
3857
4082
|
_outstandingAgentSpawns = [];
|
|
3858
4083
|
_outstandingAgentSpawnsAtLoad = [];
|
|
3859
4084
|
_lastTabContext = null;
|
|
4085
|
+
_seenImageHashes = [];
|
|
3860
4086
|
_compactedAt = 0;
|
|
3861
4087
|
_sessionId = null;
|
|
3862
4088
|
});
|
|
@@ -3879,9 +4105,11 @@ const VALID_HOOK_EVENTS = new Set([
|
|
|
3879
4105
|
'notification',
|
|
3880
4106
|
'stop',
|
|
3881
4107
|
'pre_compact',
|
|
4108
|
+
'post_compact',
|
|
3882
4109
|
'user_prompt_submit',
|
|
3883
4110
|
'subagent_stop',
|
|
3884
4111
|
'session_start',
|
|
4112
|
+
'post_tool_use_failure',
|
|
3885
4113
|
])`;
|
|
3886
4114
|
var SHIM_TRY_IN_PROCESS = `// Attempts the in-process hook call: import()s dist/token-goat-hook.mjs (a sibling of
|
|
3887
4115
|
// the baked token-goat entry path, built with zero load-time side effects -- unlike
|
|
@@ -4030,6 +4258,7 @@ var HOOK_EVENT_MAP = [
|
|
|
4030
4258
|
["PreToolUse", "pre_tool_use"],
|
|
4031
4259
|
["PostToolUse", "post_tool_use"],
|
|
4032
4260
|
["PreCompact", "pre_compact"],
|
|
4261
|
+
["PostCompact", "post_compact"],
|
|
4033
4262
|
["UserPromptSubmit", "user_prompt_submit"],
|
|
4034
4263
|
["SubagentStop", "subagent_stop"],
|
|
4035
4264
|
["SessionStart", "session_start"]
|
|
@@ -4346,9 +4575,11 @@ const HOOK_EVENT_NAME_MAP = {
|
|
|
4346
4575
|
notification: 'Notification',
|
|
4347
4576
|
stop: 'Stop',
|
|
4348
4577
|
pre_compact: 'PreCompact',
|
|
4578
|
+
post_compact: 'PostCompact',
|
|
4349
4579
|
user_prompt_submit: 'UserPromptSubmit',
|
|
4350
4580
|
subagent_stop: 'SubagentStop',
|
|
4351
4581
|
session_start: 'SessionStart',
|
|
4582
|
+
post_tool_use_failure: 'PostToolUseFailure',
|
|
4352
4583
|
}
|
|
4353
4584
|
|
|
4354
4585
|
function stripTg(value) {
|
|
@@ -4729,10 +4960,19 @@ const path = require('node:path')
|
|
|
4729
4960
|
const { pathToFileURL } = require('node:url')
|
|
4730
4961
|
|
|
4731
4962
|
// Copilot event name -> token-goat internal HookEventName (src/types.ts's
|
|
4732
|
-
// HOOK_EVENTS). Only these
|
|
4733
|
-
// Copilot event (sessionEnd,
|
|
4734
|
-
//
|
|
4963
|
+
// HOOK_EVENTS). Only these eight have a token-goat handler; every other real
|
|
4964
|
+
// Copilot event (sessionEnd, subagentStart, errorOccurred, notification,
|
|
4965
|
+
// permissionRequest) is left unimplemented
|
|
4735
4966
|
// rather than guessed at, and falls through to the default no-op below.
|
|
4967
|
+
// postToolUseFailure is the newest of the eight and the only one whose channel
|
|
4968
|
+
// costs tokens instead of saving them: it fires instead of postToolUse when a
|
|
4969
|
+
// tool result is a failure, and accepts only additionalContext back --
|
|
4970
|
+
// modifiedResult and suppressOutput are not honored there, so a failed result
|
|
4971
|
+
// still cannot be fenced, compressed or shrunk. It is wired anyway because
|
|
4972
|
+
// additionalContext demonstrably reaches the model (see the postToolUseFailure
|
|
4973
|
+
// branch in translate() for the bundle offset), and hooks_tool_failure.ts
|
|
4974
|
+
// spends that channel only on an exact repeat failure, where staying silent
|
|
4975
|
+
// costs a whole wasted retry.
|
|
4736
4976
|
// 'sessionStart' was previously a permanent no-op on the stated grounds that
|
|
4737
4977
|
// token-goat has no internal session_start handler. That was simply wrong --
|
|
4738
4978
|
// hooks_session_start.ts has long emitted the command-routing reminder that
|
|
@@ -4752,6 +4992,7 @@ const COPILOT_TO_TG_EVENT = {
|
|
|
4752
4992
|
agentStop: 'stop',
|
|
4753
4993
|
subagentStop: 'subagent_stop',
|
|
4754
4994
|
userPromptSubmitted: 'user_prompt_submit',
|
|
4995
|
+
postToolUseFailure: 'post_tool_use_failure',
|
|
4755
4996
|
}
|
|
4756
4997
|
|
|
4757
4998
|
// Copilot built-in tool name -> token-goat internal tool name. Confirmed via
|
|
@@ -4766,9 +5007,47 @@ const COPILOT_TO_TG_EVENT = {
|
|
|
4766
5007
|
// MCP-server tool invocations (<server-name>-<tool-name>) have no
|
|
4767
5008
|
// token-goat equivalent and are passed through unmapped (safe no-op for
|
|
4768
5009
|
// handlers that don't recognize the name).
|
|
5010
|
+
// 'read_bash'/'read_powershell' are the background-shell output pollers, and
|
|
5011
|
+
// they are the exact shape of Claude Code's BashOutput: Copilot's shell tool
|
|
5012
|
+
// is async, so a long-running command is started once and then re-read over
|
|
5013
|
+
// and over within one turn, each read returning the accumulated output again.
|
|
5014
|
+
// hooks_bashoutput.ts already collapses that into a delta (or a short
|
|
5015
|
+
// "unchanged" marker) for Claude Code, and now does the same here. Both the
|
|
5016
|
+
// names and the argument key were read out of the shipping 1.0.80 bundle
|
|
5017
|
+
// rather than guessed: the builtin tool-name table in
|
|
5018
|
+
// prebuilds/win32-x64/runtime.node lists read_bash/stop_bash/list_bash and
|
|
5019
|
+
// read_powershell/stop_powershell/list_powershell, and the poller's own input
|
|
5020
|
+
// schema in app.js is {shellId, delay} -- so the shell id needs the
|
|
5021
|
+
// POLL_ID_ARG_KEY remap below to reach postBashOutputHandler, which reads
|
|
5022
|
+
// 'bash_id'. read_shell/stop_shell/list_shells, which an earlier static sweep
|
|
5023
|
+
// of the same binary suggested were the real names, are internal Rust
|
|
5024
|
+
// identifiers (tool_read_shell_prepare_input, tool_list_shells_descriptor,
|
|
5025
|
+
// PreparedStopShellInput, and the serde field names of the shell config
|
|
5026
|
+
// struct), never wire tool names, so they are deliberately absent here.
|
|
5027
|
+
// stop_bash/list_bash and their powershell twins stay unmapped because
|
|
5028
|
+
// token-goat has no KillShell-equivalent handler for them to reach; a mapping
|
|
5029
|
+
// would be pure decoration.
|
|
5030
|
+
// 'bash'/'powershell' stay exactly as they were. The same sweep suggested the
|
|
5031
|
+
// executor had been renamed to write_bash/write_powershell and that this
|
|
5032
|
+
// mapping was dead, and that is not what the bundle says: app.js resolves the
|
|
5033
|
+
// executor as shellConfig?.shellToolName ?? "bash" (with "powershell" as the
|
|
5034
|
+
// Windows default of the same config), and its command lives under 'command'
|
|
5035
|
+
// exactly as hooks_bash.ts expects. write_bash/write_powershell are real tool
|
|
5036
|
+
// names, but they are a different tool -- their schema is {shellId, input,
|
|
5037
|
+
// delay} and the bundle files them under the subtype "write_shell", i.e. send
|
|
5038
|
+
// stdin to an already-running shell, not run a command. Mapping them to Bash
|
|
5039
|
+
// would label a stdin write as a shell execution, so they are left unmapped.
|
|
5040
|
+
// 'task', 'read_agent' and 'memory'-family tools are likewise left alone:
|
|
5041
|
+
// task's result is assembled in the native addon and its shape is unknown,
|
|
5042
|
+
// read_agent is an incremental poll that postAgentHandler is not written for,
|
|
5043
|
+
// and the real tool names behind memory were never confirmed. Each would put
|
|
5044
|
+
// a handler that rewrites model-visible output in front of a payload shape
|
|
5045
|
+
// nobody has seen, which is worse than leaving the compression on the table.
|
|
4769
5046
|
const TOOL_TO_TG = {
|
|
4770
5047
|
bash: 'Bash',
|
|
4771
5048
|
powershell: 'Bash',
|
|
5049
|
+
read_bash: 'BashOutput',
|
|
5050
|
+
read_powershell: 'BashOutput',
|
|
4772
5051
|
view: 'Read',
|
|
4773
5052
|
create: 'Write',
|
|
4774
5053
|
edit: 'Edit',
|
|
@@ -4809,6 +5088,17 @@ const FILE_PATH_ARG_KEY = {
|
|
|
4809
5088
|
create: 'path',
|
|
4810
5089
|
}
|
|
4811
5090
|
|
|
5091
|
+
// read_bash/read_powershell send the background shell's id under 'shellId' (confirmed
|
|
5092
|
+
// against the poller's input schema in the shipping 1.0.80 app.js: {shellId, delay});
|
|
5093
|
+
// postBashOutputHandler reads 'bash_id'. Without this the tool-name mapping above would
|
|
5094
|
+
// be inert -- the handler bails on a missing bash_id and every poll would keep costing
|
|
5095
|
+
// the full accumulated output. Same keying convention as FILE_PATH_ARG_KEY: the ORIGINAL
|
|
5096
|
+
// Copilot tool name, since that is what the argument shape belongs to.
|
|
5097
|
+
const POLL_ID_ARG_KEY = {
|
|
5098
|
+
read_bash: 'shellId',
|
|
5099
|
+
read_powershell: 'shellId',
|
|
5100
|
+
}
|
|
5101
|
+
|
|
4812
5102
|
// Copilot spawns a brand-new process for every single hook invocation (no long-lived plugin
|
|
4813
5103
|
// process the way OpenClaw's is -- OPENCLAW_HOOK_SCRIPT's own \`copilot-\${process.pid}-\${Date.now()}\`
|
|
4814
5104
|
// fallback is safe there specifically because that process lives for the whole session, so the
|
|
@@ -4825,13 +5115,19 @@ function stableFallbackSessionId(cwd) {
|
|
|
4825
5115
|
}
|
|
4826
5116
|
|
|
4827
5117
|
function remapToolInput(copilotToolName, input) {
|
|
5118
|
+
if (!input || typeof input !== 'object') return input
|
|
5119
|
+
let out = input
|
|
5120
|
+
// Add the canonical key alongside the original rather than renaming it, so nothing that
|
|
5121
|
+
// might read the original 'path'/'shellId' key elsewhere (e.g. a future handler) loses it.
|
|
4828
5122
|
const pathKey = FILE_PATH_ARG_KEY[copilotToolName]
|
|
4829
|
-
if (pathKey
|
|
4830
|
-
|
|
5123
|
+
if (pathKey !== undefined && pathKey in out) {
|
|
5124
|
+
out = Object.assign({}, out, { file_path: out[pathKey] })
|
|
5125
|
+
}
|
|
5126
|
+
const idKey = POLL_ID_ARG_KEY[copilotToolName]
|
|
5127
|
+
if (idKey !== undefined && idKey in out) {
|
|
5128
|
+
out = Object.assign({}, out, { bash_id: out[idKey] })
|
|
4831
5129
|
}
|
|
4832
|
-
|
|
4833
|
-
// might read the original 'path' key elsewhere (e.g. a future handler) loses it.
|
|
4834
|
-
return Object.assign({}, input, { file_path: input[pathKey] })
|
|
5130
|
+
return out
|
|
4835
5131
|
}
|
|
4836
5132
|
|
|
4837
5133
|
// Attempts the in-process hook call: import()s dist/token-goat-hook.mjs (a sibling of
|
|
@@ -4983,7 +5279,46 @@ function translate(copilotEvent, resp) {
|
|
|
4983
5279
|
}
|
|
4984
5280
|
|
|
4985
5281
|
if (copilotEvent === 'postToolUse') {
|
|
4986
|
-
//
|
|
5282
|
+
// Verified against the shipping @github/copilot 1.0.80 bundle, not against the docs page.
|
|
5283
|
+
// NativeHookPipelineProcessor.postToolExecution (app.js offset 2043150) gates on
|
|
5284
|
+
// toolResult.resultType === "success", then does n.toolResultJson && CSr(e.toolResult,
|
|
5285
|
+
// n.toolResultJson), where CSr (offset 2032350) is an in-place Object.assign; the mutated
|
|
5286
|
+
// object is re-serialized back to native in the postTool callback at offset 1793926. So
|
|
5287
|
+
// modifiedResult IS honored on this event, and token-goat's rewriteOutput producers
|
|
5288
|
+
// (compression, injection fencing, image shrink) really do reach the model. resultType is
|
|
5289
|
+
// hardcoded 'success' because success is the only branch this event ever runs on.
|
|
5290
|
+
//
|
|
5291
|
+
// additionalContext on THIS event is dropped on the JS path. grep -abo "onAdditionalContext:"
|
|
5292
|
+
// app.js returns nothing, so the callback is never supplied, and the two "onAdditionalContext?"
|
|
5293
|
+
// call sites (offsets 2041896 and 2043300) are both no-ops. preToolsExecution (offset 2041832)
|
|
5294
|
+
// additionally pushes each context into an array that IS drained into the native return
|
|
5295
|
+
// payload (additional_contexts, offset 1791950); postToolExecution has no such push and its
|
|
5296
|
+
// native return payload (offset 1793926) has no additional_contexts key. Residual, stated
|
|
5297
|
+
// honestly: the native hookProcessorPostToolUse might fold additionalContext into the
|
|
5298
|
+
// toolResultJson it returns, and that was NOT verified. The evidence leans against it, since
|
|
5299
|
+
// the failure sibling path appends its context explicitly in JS via
|
|
5300
|
+
// hookAppendPostToolUseFailureContext and there is no success-path counterpart. The
|
|
5301
|
+
// out.additionalContext below is kept as cheap best-effort, not as a channel anything should
|
|
5302
|
+
// depend on -- see src/pending_context.ts, whose whole design depended on it.
|
|
5303
|
+
//
|
|
5304
|
+
// Failed tool calls never reach this event. postToolUse and postToolUseFailure are two
|
|
5305
|
+
// distinct hook events (both listed in the runtime.node hook-event enum at offset 101618150),
|
|
5306
|
+
// and the shipped copilot-sdk/types.d.ts says onPostToolUse "does not fire for non-success
|
|
5307
|
+
// results". The failure event cannot carry a fence either: PostToolUseFailureHookInput carries
|
|
5308
|
+
// only a stringified error message, not the tool result, and PostToolUseFailureHookOutput
|
|
5309
|
+
// consumes only additionalContext -- "modifiedResult or suppressOutput are not honored for
|
|
5310
|
+
// failure hooks". rejected/denied/timeout results trigger no post hook at all. So on Copilot,
|
|
5311
|
+
// the output of a failed tool call reaches the model unfenced, uncompressed and unshrunk, and
|
|
5312
|
+
// no response shape this shim could emit changes that. That gap is real and still open. What
|
|
5313
|
+
// is now wired is the narrower thing that IS possible there: the postToolUseFailure branch
|
|
5314
|
+
// below carries advisory text alongside the failure, and never rewrites it.
|
|
5315
|
+
//
|
|
5316
|
+
// Emitted camelCase-only. The inbound side around line 247 also tolerates a snake_case "VS
|
|
5317
|
+
// Code compatible" shape, and this comment used to justify the camelCase choice by claiming
|
|
5318
|
+
// PascalCase event registration selects the snake_case response format. That reasoning is
|
|
5319
|
+
// UNVERIFIED in 1.0.80: every hook-response field in the native string tables is camelCase,
|
|
5320
|
+
// and the snake_case hits in the bundle are unrelated internal Rust identifiers. camelCase
|
|
5321
|
+
// stays because it is what the bundle reads; the old justification is no longer asserted.
|
|
4987
5322
|
const hso = resp && resp.hookSpecificOutput
|
|
4988
5323
|
const updatedToolOutput = hso && hso.updatedToolOutput
|
|
4989
5324
|
const context = extractContext(resp)
|
|
@@ -4995,6 +5330,25 @@ function translate(copilotEvent, resp) {
|
|
|
4995
5330
|
return out
|
|
4996
5331
|
}
|
|
4997
5332
|
|
|
5333
|
+
if (copilotEvent === 'postToolUseFailure') {
|
|
5334
|
+
// The failed-tool twin of postToolUse, and the only response field it accepts is
|
|
5335
|
+
// additionalContext: the shipped copilot-sdk/types.d.ts says "modifiedResult or suppressOutput
|
|
5336
|
+
// are not honored for failure hooks", so nothing here can fence, compress or shrink the failed
|
|
5337
|
+
// output -- that gap is real and stays open. What is NOT open, and was the reason this event
|
|
5338
|
+
// went unwired for so long, is whether additionalContext reaches the model at all. It does:
|
|
5339
|
+
// app.js 1.0.80 at offset 2043380 either folds it into textResultForLlm (when
|
|
5340
|
+
// appendFailureContextToToolResult is set) or has the native
|
|
5341
|
+
// hookAppendPostToolUseFailureContext push {content, source:'system'} onto
|
|
5342
|
+
// toolResult.newMessages. That is the exact opposite of postToolUse, whose additionalContext
|
|
5343
|
+
// the JS side drops on the floor, so neither event's behaviour generalises to the other.
|
|
5344
|
+
//
|
|
5345
|
+
// Because this channel spends tokens rather than saving them, the handler behind it
|
|
5346
|
+
// (hooks_tool_failure.ts) is silent on a first failure and speaks only on an exact repeat.
|
|
5347
|
+
const context = extractContext(resp)
|
|
5348
|
+
if (context) return { additionalContext: context }
|
|
5349
|
+
return {}
|
|
5350
|
+
}
|
|
5351
|
+
|
|
4998
5352
|
if (copilotEvent === 'sessionStart') {
|
|
4999
5353
|
// sessionStart has no tool result to modify -- only additionalContext applies, and it's the
|
|
5000
5354
|
// one channel that reaches the model before it picks its first read tool, so this is where
|
|
@@ -5018,12 +5372,47 @@ function translate(copilotEvent, resp) {
|
|
|
5018
5372
|
return { decision: 'allow' }
|
|
5019
5373
|
}
|
|
5020
5374
|
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5375
|
+
if (copilotEvent === 'userPromptSubmitted') {
|
|
5376
|
+
// Copilot's own hooks reference says command-hook output here "is dropped, including
|
|
5377
|
+
// modifiedPrompt", and this branch used to believe it and return nothing. That is wrong for
|
|
5378
|
+
// additionalContext on 1.0.80, established by experiment rather than by reading: a config-file
|
|
5379
|
+
// command hook (at <cwd>/.github/hooks/, the project scope -- the user scope ~/.copilot/hooks/
|
|
5380
|
+
// was never exercised) returned {"additionalContext":"<marker>"} and the marker turned up
|
|
5381
|
+
// verbatim inside the session's user.message.transformedContent, wrapped in a
|
|
5382
|
+
// <system_reminder> block. What settles that it reached the model rather than only the on-disk
|
|
5383
|
+
// record is the billing: the provider's returned usage charged ~140 input tokens for a turn
|
|
5384
|
+
// whose raw content field is 35 bytes, so the marker's bytes were paid for whichever field
|
|
5385
|
+
// carried them. (The weaker argument first offered for this -- that transformedContent carries
|
|
5386
|
+
// an envelope the content field lacks, 29 B vs 195 B -- was measured on the control turn that
|
|
5387
|
+
// had no marker in it, and proves nothing about the marker.) That the model sees it is the
|
|
5388
|
+
// whole point: hook.start/hook.end records also persist and reach nothing.
|
|
5389
|
+
//
|
|
5390
|
+
// Scope of the finding, stated honestly: demonstrated ONCE on 1.0.80, not shown to be
|
|
5391
|
+
// reliable. Of two turns in that experiment, one delivered the marker and one fired a
|
|
5392
|
+
// userPromptSubmitted hook that produced no output and never ran the script; no explanation
|
|
5393
|
+
// was established and the rate is unknown. A hint that silently fails to arrive costs nothing
|
|
5394
|
+
// and breaks nothing here, which is why the direct return is still the right default.
|
|
5395
|
+
//
|
|
5396
|
+
// modifiedPrompt is NOT claimed to work and is not wanted: rewriting a user's prompt is far
|
|
5397
|
+
// more invasive than anything token-goat does, so only additionalContext is forwarded.
|
|
5398
|
+
//
|
|
5399
|
+
// This is also the write end of any post-compaction channel. Copilot has no postCompact hook.
|
|
5400
|
+
// Whether the summary is recoverable from events.jsonl is NOT settled -- the emit()/
|
|
5401
|
+
// emitEphemeral() distinction does not gate the writer, and the real decision is in native
|
|
5402
|
+
// code; see COPILOT_NO_POST_COMPACT_REASON in ../bridges_status.ts for what was and was not
|
|
5403
|
+
// established. The read end that IS confirmed is preCompact, which fires as a notification, so
|
|
5404
|
+
// a manifest can be built there and drained here without reading the event log at all.
|
|
5405
|
+
const context = extractContext(resp)
|
|
5406
|
+
if (context) return { additionalContext: context }
|
|
5407
|
+
return {}
|
|
5408
|
+
}
|
|
5409
|
+
|
|
5410
|
+
// preCompact is the genuine notification-only case, and the contrast with userPromptSubmitted
|
|
5411
|
+
// above is why this fallthrough is worth a comment at all. The hooks reference marks it
|
|
5412
|
+
// "No -- notification only" for output processing, and unlike the additionalContext claim that
|
|
5413
|
+
// turned out to be false, this one is confirmed in the shipping bundle: both preCompact call
|
|
5414
|
+
// sites in app.js (1.0.79 and re-checked in 1.0.80) await the hook and never assign its result.
|
|
5415
|
+
// There is no field to aim at here, so nothing to reconsider on the next version bump.
|
|
5027
5416
|
return {}
|
|
5028
5417
|
}
|
|
5029
5418
|
|
|
@@ -5065,7 +5454,8 @@ var COPILOT_CLI_HOOK_EVENTS = [
|
|
|
5065
5454
|
"preCompact",
|
|
5066
5455
|
"agentStop",
|
|
5067
5456
|
"subagentStop",
|
|
5068
|
-
"userPromptSubmitted"
|
|
5457
|
+
"userPromptSubmitted",
|
|
5458
|
+
"postToolUseFailure"
|
|
5069
5459
|
];
|
|
5070
5460
|
function copilotCliUserRoot() {
|
|
5071
5461
|
const override = process.env["COPILOT_HOME"];
|
|
@@ -5078,6 +5468,23 @@ function copilotCliUserHooksDir() {
|
|
|
5078
5468
|
function copilotCliProjectHooksDir() {
|
|
5079
5469
|
return path5.join(process.cwd(), ".github", "hooks");
|
|
5080
5470
|
}
|
|
5471
|
+
function copilotCliCacheRoot() {
|
|
5472
|
+
const override = process.env["COPILOT_CACHE_HOME"];
|
|
5473
|
+
if (override !== void 0 && override.trim() !== "") return path5.resolve(override);
|
|
5474
|
+
const home = os3.homedir();
|
|
5475
|
+
if (process.platform === "darwin") return path5.join(home, "Library", "Caches", "copilot");
|
|
5476
|
+
if (process.platform === "win32") {
|
|
5477
|
+
const local = process.env["LOCALAPPDATA"];
|
|
5478
|
+
const base2 = local !== void 0 && local.trim() !== "" ? local : path5.join(home, ".cache");
|
|
5479
|
+
return path5.join(base2, "copilot");
|
|
5480
|
+
}
|
|
5481
|
+
const xdg = process.env["XDG_CACHE_HOME"];
|
|
5482
|
+
const base = xdg !== void 0 && xdg.trim() !== "" ? xdg : path5.join(home, ".cache");
|
|
5483
|
+
return path5.join(base, "copilot");
|
|
5484
|
+
}
|
|
5485
|
+
function copilotCliMcpToolsDir() {
|
|
5486
|
+
return path5.join(copilotCliCacheRoot(), "mcp-tools");
|
|
5487
|
+
}
|
|
5081
5488
|
function copilotCliHooksDir(opts = {}) {
|
|
5082
5489
|
return opts.local === true ? copilotCliProjectHooksDir() : copilotCliUserHooksDir();
|
|
5083
5490
|
}
|
|
@@ -6006,11 +6413,15 @@ function sessionFileStem(sessionId) {
|
|
|
6006
6413
|
return `${saltedStemPrefix(sessionId.slice(0, sep))}${digest}`;
|
|
6007
6414
|
}
|
|
6008
6415
|
function sessionPath(sessionId) {
|
|
6416
|
+
return sessionSidecarPath(sessionId, ".json");
|
|
6417
|
+
}
|
|
6418
|
+
function sessionSidecarPath(sessionId, suffix) {
|
|
6009
6419
|
if (!sessionId) return null;
|
|
6010
6420
|
const safe = sessionFileStem(sessionId);
|
|
6011
6421
|
if (!safe) return null;
|
|
6422
|
+
if (suffix.includes("/") || suffix.includes("\\") || suffix.includes("..")) return null;
|
|
6012
6423
|
const dir = path10.join(tokenGoatHome(), SESSIONS_SUBDIR);
|
|
6013
|
-
const candidate = path10.join(dir, `${safe}
|
|
6424
|
+
const candidate = path10.join(dir, `${safe}${suffix}`);
|
|
6014
6425
|
try {
|
|
6015
6426
|
const rel = path10.relative(dir, candidate);
|
|
6016
6427
|
if (rel.startsWith("..")) return null;
|
|
@@ -6099,6 +6510,7 @@ function coerce(raw) {
|
|
|
6099
6510
|
) : [];
|
|
6100
6511
|
const cliReads = Array.isArray(o["cliReads"]) ? o["cliReads"].filter((h) => typeof h === "string") : [];
|
|
6101
6512
|
const bashReruns = Array.isArray(o["bashReruns"]) ? o["bashReruns"].filter((h) => typeof h === "string") : [];
|
|
6513
|
+
const seenImageHashes = Array.isArray(o["seenImageHashes"]) ? o["seenImageHashes"].filter((h) => typeof h === "string") : [];
|
|
6102
6514
|
const pendingLargeFileHints = Array.isArray(o["pendingLargeFileHints"]) ? o["pendingLargeFileHints"].filter(
|
|
6103
6515
|
(p) => Array.isArray(p) && p.length === 2 && typeof p[0] === "string" && typeof p[1] === "number"
|
|
6104
6516
|
) : [];
|
|
@@ -6126,6 +6538,7 @@ function coerce(raw) {
|
|
|
6126
6538
|
globQueries,
|
|
6127
6539
|
outstandingAgentSpawns,
|
|
6128
6540
|
...typeof o["lastTabContext"] === "string" ? { lastTabContext: o["lastTabContext"] } : {},
|
|
6541
|
+
...seenImageHashes.length > 0 ? { seenImageHashes } : {},
|
|
6129
6542
|
...typeof o["compactedAt"] === "number" ? { compactedAt: o["compactedAt"] } : {},
|
|
6130
6543
|
...typeof o["created_ts"] === "number" ? { created_ts: o["created_ts"] } : {}
|
|
6131
6544
|
};
|
|
@@ -6233,12 +6646,21 @@ function mergeSessionState(disk, mem) {
|
|
|
6233
6646
|
grepQueries: mergePairs(disk.grepQueries ?? [], mem.grepQueries ?? []),
|
|
6234
6647
|
globQueries: mergePairs(disk.globQueries ?? [], mem.globQueries ?? []),
|
|
6235
6648
|
outstandingAgentSpawns: mergeOutstandingAgentSpawns(disk.outstandingAgentSpawns ?? [], mem.outstandingAgentSpawns ?? []),
|
|
6649
|
+
// An accumulating collection, so union rather than pick a winner: two hook processes can
|
|
6650
|
+
// each see a screenshot the other never did. Disk first, then mem, so the order stays
|
|
6651
|
+
// oldest-to-newest and the cap evicts the oldest -- the same policy recordSeenImage applies
|
|
6652
|
+
// in memory, applied again here because a union of two capped lists can exceed the cap.
|
|
6653
|
+
seenImageHashes: mergeSeenImageHashes(disk.seenImageHashes ?? [], mem.seenImageHashes ?? []),
|
|
6236
6654
|
// Last-seen scalar, not an accumulating collection: prefer mem's value (this process's freshest observation) over disk's, since a newer write always supersedes an older one.
|
|
6237
6655
|
...mem.lastTabContext !== void 0 ? { lastTabContext: mem.lastTabContext } : disk.lastTabContext !== void 0 ? { lastTabContext: disk.lastTabContext } : {},
|
|
6238
6656
|
// Prefer the value already on disk: it marks the original creation time, and must never be bumped forward to the merge's "now". `mem` never carries one (it is not tracked in memory), so this is really "keep whatever disk has".
|
|
6239
6657
|
...disk.created_ts !== void 0 ? { created_ts: disk.created_ts } : mem.created_ts !== void 0 ? { created_ts: mem.created_ts } : {}
|
|
6240
6658
|
};
|
|
6241
6659
|
}
|
|
6660
|
+
function mergeSeenImageHashes(disk, mem) {
|
|
6661
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...disk, ...mem]));
|
|
6662
|
+
return merged.length > MAX_SEEN_IMAGE_HASHES ? merged.slice(merged.length - MAX_SEEN_IMAGE_HASHES) : merged;
|
|
6663
|
+
}
|
|
6242
6664
|
function capFiles(s, max) {
|
|
6243
6665
|
if (s.files.length <= max) return s;
|
|
6244
6666
|
const kept = [...s.files].sort((a, b) => b.lastReadAt - a.lastReadAt).slice(0, max);
|
|
@@ -7075,6 +7497,19 @@ async function probeImageMeta(input) {
|
|
|
7075
7497
|
return null;
|
|
7076
7498
|
}
|
|
7077
7499
|
}
|
|
7500
|
+
async function imageQualifiesForShrink(input) {
|
|
7501
|
+
if (input.length >= DEFAULT_SIZE_THRESHOLD_BYTES) return true;
|
|
7502
|
+
const sharp = await loadSharp();
|
|
7503
|
+
if (sharp === null) return false;
|
|
7504
|
+
try {
|
|
7505
|
+
const cfg = loadConfig().image_shrink;
|
|
7506
|
+
const limitInputPixels = cfg.max_image_pixels > 0 ? cfg.max_image_pixels : false;
|
|
7507
|
+
const meta = await sharp(input, { limitInputPixels }).metadata();
|
|
7508
|
+
return Math.max(meta.width ?? 0, meta.height ?? 0) > DEFAULT_MAX_DIMENSION;
|
|
7509
|
+
} catch {
|
|
7510
|
+
return false;
|
|
7511
|
+
}
|
|
7512
|
+
}
|
|
7078
7513
|
async function shrinkImage(input, opts) {
|
|
7079
7514
|
const cfg = loadConfig().image_shrink;
|
|
7080
7515
|
const maxDimension = opts?.maxDimension ?? DEFAULT_MAX_DIMENSION;
|
|
@@ -7196,7 +7631,6 @@ async function preReadImageHandler(event) {
|
|
|
7196
7631
|
pruneShrinkCache();
|
|
7197
7632
|
const stat2 = statInfo(filePath);
|
|
7198
7633
|
if (stat2 === null) return passOutput();
|
|
7199
|
-
const size = stat2.size;
|
|
7200
7634
|
const quality = loadConfig().image_shrink.jpeg_quality;
|
|
7201
7635
|
const cached = findCachedShrink(filePath, stat2.size, stat2.mtimeMs, quality);
|
|
7202
7636
|
if (cached !== null) {
|
|
@@ -7223,34 +7657,13 @@ async function preReadImageHandler(event) {
|
|
|
7223
7657
|
} catch {
|
|
7224
7658
|
}
|
|
7225
7659
|
}
|
|
7226
|
-
let input
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
} catch {
|
|
7232
|
-
return passOutput();
|
|
7233
|
-
}
|
|
7234
|
-
const sharp = await loadSharp();
|
|
7235
|
-
if (sharp === null) return passOutput();
|
|
7236
|
-
try {
|
|
7237
|
-
const cfg = loadConfig().image_shrink;
|
|
7238
|
-
const limitInputPixels = cfg.max_image_pixels > 0 ? cfg.max_image_pixels : false;
|
|
7239
|
-
const meta = await sharp(input, { limitInputPixels }).metadata();
|
|
7240
|
-
const longestEdge = Math.max(meta.width ?? 0, meta.height ?? 0);
|
|
7241
|
-
qualifies = longestEdge > DEFAULT_MAX_DIMENSION;
|
|
7242
|
-
} catch {
|
|
7243
|
-
return passOutput();
|
|
7244
|
-
}
|
|
7245
|
-
}
|
|
7246
|
-
if (!qualifies) return passOutput();
|
|
7247
|
-
if (input === null) {
|
|
7248
|
-
try {
|
|
7249
|
-
input = fs16.readFileSync(filePath);
|
|
7250
|
-
} catch {
|
|
7251
|
-
return passOutput();
|
|
7252
|
-
}
|
|
7660
|
+
let input;
|
|
7661
|
+
try {
|
|
7662
|
+
input = fs16.readFileSync(filePath);
|
|
7663
|
+
} catch {
|
|
7664
|
+
return passOutput();
|
|
7253
7665
|
}
|
|
7666
|
+
if (!await imageQualifiesForShrink(input)) return passOutput();
|
|
7254
7667
|
const result = await shrinkImage(input, { quality, sizeThresholdBytes: 0 });
|
|
7255
7668
|
if (result === null) {
|
|
7256
7669
|
recordStat("image_shrink_skipped");
|
|
@@ -7261,30 +7674,432 @@ async function preReadImageHandler(event) {
|
|
|
7261
7674
|
}
|
|
7262
7675
|
registerHook("pre_tool_use", preReadImageHandler, { toolName: "Read" });
|
|
7263
7676
|
|
|
7264
|
-
// src/
|
|
7677
|
+
// src/embed_model.ts
|
|
7678
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
7679
|
+
import * as fs17 from "node:fs";
|
|
7265
7680
|
import { createRequire as createRequire3 } from "node:module";
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7681
|
+
import * as path13 from "node:path";
|
|
7682
|
+
import { pipeline } from "node:stream/promises";
|
|
7683
|
+
|
|
7684
|
+
// src/embed_tokenizer.ts
|
|
7685
|
+
var MAX_SEQUENCE_TOKENS = 512;
|
|
7686
|
+
var UnsupportedTokenizerError = class extends Error {
|
|
7687
|
+
constructor(message) {
|
|
7688
|
+
super(message);
|
|
7689
|
+
this.name = "UnsupportedTokenizerError";
|
|
7690
|
+
}
|
|
7691
|
+
};
|
|
7692
|
+
function fail(message) {
|
|
7693
|
+
throw new UnsupportedTokenizerError(message);
|
|
7694
|
+
}
|
|
7695
|
+
function asRecord(value, path31) {
|
|
7696
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail(`${path31} is not an object`);
|
|
7697
|
+
return value;
|
|
7698
|
+
}
|
|
7699
|
+
function asString(value, path31) {
|
|
7700
|
+
if (typeof value !== "string") fail(`${path31} is not a string`);
|
|
7701
|
+
return value;
|
|
7702
|
+
}
|
|
7703
|
+
function asArray2(value, path31) {
|
|
7704
|
+
if (!Array.isArray(value)) fail(`${path31} is not an array`);
|
|
7705
|
+
return value;
|
|
7706
|
+
}
|
|
7707
|
+
function expectValue(actual, wanted, path31) {
|
|
7708
|
+
if (actual !== wanted) fail(`${path31} is ${JSON.stringify(actual)}, expected ${JSON.stringify(wanted)}`);
|
|
7709
|
+
}
|
|
7710
|
+
function isChinese(cp) {
|
|
7711
|
+
return cp >= 19968 && cp <= 40959 || cp >= 13312 && cp <= 19903 || cp >= 131072 && cp <= 173791 || cp >= 173824 && cp <= 177983 || cp >= 177984 && cp <= 178207 || cp >= 178208 && cp <= 183983 || cp >= 63744 && cp <= 64255 || cp >= 194560 && cp <= 195103;
|
|
7712
|
+
}
|
|
7713
|
+
var PUNCTUATION = new RegExp("\\p{P}", "u");
|
|
7714
|
+
function isPunctuation(ch) {
|
|
7715
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
7716
|
+
if (cp >= 33 && cp <= 47 || cp >= 58 && cp <= 64 || cp >= 91 && cp <= 96 || cp >= 123 && cp <= 126) {
|
|
7717
|
+
return true;
|
|
7718
|
+
}
|
|
7719
|
+
return PUNCTUATION.test(ch);
|
|
7720
|
+
}
|
|
7721
|
+
var CONTROL = new RegExp("\\p{Cc}|\\p{Cf}|\\p{Co}|\\p{Cs}", "u");
|
|
7722
|
+
function isControl(ch) {
|
|
7723
|
+
if (ch === " " || ch === "\n" || ch === "\r") return false;
|
|
7724
|
+
return CONTROL.test(ch);
|
|
7725
|
+
}
|
|
7726
|
+
var SPACE_SEPARATOR = new RegExp("\\p{Zs}", "u");
|
|
7727
|
+
function isWhitespace(ch) {
|
|
7728
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || SPACE_SEPARATOR.test(ch);
|
|
7729
|
+
}
|
|
7730
|
+
var COMBINING_MARK = /[\u0300-\u036f]/gu;
|
|
7731
|
+
function normalize(text) {
|
|
7732
|
+
let out = "";
|
|
7733
|
+
for (const ch of text) {
|
|
7734
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
7735
|
+
if (cp === 0 || cp === 65533 || isControl(ch)) continue;
|
|
7736
|
+
if (isWhitespace(ch)) {
|
|
7737
|
+
out += " ";
|
|
7738
|
+
continue;
|
|
7278
7739
|
}
|
|
7279
|
-
|
|
7280
|
-
|
|
7740
|
+
if (isChinese(cp)) {
|
|
7741
|
+
out += ` ${ch} `;
|
|
7742
|
+
continue;
|
|
7743
|
+
}
|
|
7744
|
+
out += ch;
|
|
7281
7745
|
}
|
|
7746
|
+
return out.normalize("NFD").replace(COMBINING_MARK, "").toLowerCase();
|
|
7747
|
+
}
|
|
7748
|
+
function preTokenize(text) {
|
|
7749
|
+
const words = [];
|
|
7750
|
+
for (const piece of text.split(/\s+/)) {
|
|
7751
|
+
if (!piece) continue;
|
|
7752
|
+
let buf = "";
|
|
7753
|
+
for (const ch of piece) {
|
|
7754
|
+
if (isPunctuation(ch)) {
|
|
7755
|
+
if (buf) {
|
|
7756
|
+
words.push(buf);
|
|
7757
|
+
buf = "";
|
|
7758
|
+
}
|
|
7759
|
+
words.push(ch);
|
|
7760
|
+
} else {
|
|
7761
|
+
buf += ch;
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7764
|
+
if (buf) words.push(buf);
|
|
7765
|
+
}
|
|
7766
|
+
return words;
|
|
7767
|
+
}
|
|
7768
|
+
function readSpec(raw) {
|
|
7769
|
+
const spec = asRecord(raw, "tokenizer.json");
|
|
7770
|
+
const normalizer = asRecord(spec["normalizer"], "normalizer");
|
|
7771
|
+
expectValue(normalizer["type"], "BertNormalizer", "normalizer.type");
|
|
7772
|
+
expectValue(normalizer["clean_text"], true, "normalizer.clean_text");
|
|
7773
|
+
expectValue(normalizer["handle_chinese_chars"], true, "normalizer.handle_chinese_chars");
|
|
7774
|
+
expectValue(normalizer["lowercase"], true, "normalizer.lowercase");
|
|
7775
|
+
const stripAccents = normalizer["strip_accents"];
|
|
7776
|
+
if (stripAccents !== null && stripAccents !== true) {
|
|
7777
|
+
fail(`normalizer.strip_accents is ${JSON.stringify(stripAccents)}, expected null or true`);
|
|
7778
|
+
}
|
|
7779
|
+
expectValue(asRecord(spec["pre_tokenizer"], "pre_tokenizer")["type"], "BertPreTokenizer", "pre_tokenizer.type");
|
|
7780
|
+
const model = asRecord(spec["model"], "model");
|
|
7781
|
+
expectValue(model["type"], "WordPiece", "model.type");
|
|
7782
|
+
expectValue(model["continuing_subword_prefix"], "##", "model.continuing_subword_prefix");
|
|
7783
|
+
const post = asRecord(spec["post_processor"], "post_processor");
|
|
7784
|
+
expectValue(post["type"], "TemplateProcessing", "post_processor.type");
|
|
7785
|
+
const template = asArray2(post["single"], "post_processor.single").map((part) => {
|
|
7786
|
+
const special = asRecord(part, "post_processor.single[]")["SpecialToken"];
|
|
7787
|
+
return special === void 0 ? "A" : asString(asRecord(special, "SpecialToken")["id"], "SpecialToken.id");
|
|
7788
|
+
}).join(" ");
|
|
7789
|
+
if (template !== "[CLS] A [SEP]") fail(`post_processor.single is "${template}", expected "[CLS] A [SEP]"`);
|
|
7790
|
+
const vocab = /* @__PURE__ */ new Map();
|
|
7791
|
+
for (const [token, id] of Object.entries(asRecord(model["vocab"], "model.vocab"))) {
|
|
7792
|
+
if (typeof id !== "number" || !Number.isInteger(id)) fail(`model.vocab["${token}"] is not an integer id`);
|
|
7793
|
+
vocab.set(token, id);
|
|
7794
|
+
}
|
|
7795
|
+
const maxChars = model["max_input_chars_per_word"];
|
|
7796
|
+
if (maxChars !== void 0 && (typeof maxChars !== "number" || !Number.isInteger(maxChars) || maxChars < 1)) {
|
|
7797
|
+
fail("model.max_input_chars_per_word is not a positive integer");
|
|
7798
|
+
}
|
|
7799
|
+
return {
|
|
7800
|
+
vocab,
|
|
7801
|
+
unkToken: asString(model["unk_token"], "model.unk_token"),
|
|
7802
|
+
maxInputCharsPerWord: typeof maxChars === "number" ? maxChars : 100
|
|
7803
|
+
};
|
|
7282
7804
|
}
|
|
7805
|
+
var BertWordPiece = class _BertWordPiece {
|
|
7806
|
+
vocab;
|
|
7807
|
+
maxInputCharsPerWord;
|
|
7808
|
+
clsId;
|
|
7809
|
+
sepId;
|
|
7810
|
+
padId;
|
|
7811
|
+
unkId;
|
|
7812
|
+
/** @param raw the parsed contents of a tokenizer.json. */
|
|
7813
|
+
constructor(raw) {
|
|
7814
|
+
const spec = readSpec(raw);
|
|
7815
|
+
this.vocab = spec.vocab;
|
|
7816
|
+
this.maxInputCharsPerWord = spec.maxInputCharsPerWord;
|
|
7817
|
+
this.clsId = this.requireToken("[CLS]");
|
|
7818
|
+
this.sepId = this.requireToken("[SEP]");
|
|
7819
|
+
this.padId = this.requireToken("[PAD]");
|
|
7820
|
+
this.unkId = this.requireToken(spec.unkToken);
|
|
7821
|
+
}
|
|
7822
|
+
/** Parse and validate in one step, for the common case of reading the file off disk. */
|
|
7823
|
+
static fromJson(json) {
|
|
7824
|
+
let parsed;
|
|
7825
|
+
try {
|
|
7826
|
+
parsed = JSON.parse(json);
|
|
7827
|
+
} catch (err) {
|
|
7828
|
+
throw new UnsupportedTokenizerError(`tokenizer.json is not valid JSON: ${err.message}`);
|
|
7829
|
+
}
|
|
7830
|
+
return new _BertWordPiece(parsed);
|
|
7831
|
+
}
|
|
7832
|
+
requireToken(token) {
|
|
7833
|
+
const id = this.vocab.get(token);
|
|
7834
|
+
if (id === void 0) fail(`model.vocab is missing the ${token} token`);
|
|
7835
|
+
return id;
|
|
7836
|
+
}
|
|
7837
|
+
/** Greedy longest-match-first over one whitespace- and punctuation-free word. */
|
|
7838
|
+
wordToIds(word, into) {
|
|
7839
|
+
if (word.length > this.maxInputCharsPerWord) {
|
|
7840
|
+
into.push(this.unkId);
|
|
7841
|
+
return;
|
|
7842
|
+
}
|
|
7843
|
+
const pieces = [];
|
|
7844
|
+
let start = 0;
|
|
7845
|
+
while (start < word.length) {
|
|
7846
|
+
let end = word.length;
|
|
7847
|
+
let found = -1;
|
|
7848
|
+
while (start < end) {
|
|
7849
|
+
const sub = start === 0 ? word.slice(start, end) : `##${word.slice(start, end)}`;
|
|
7850
|
+
const id = this.vocab.get(sub);
|
|
7851
|
+
if (id !== void 0) {
|
|
7852
|
+
found = id;
|
|
7853
|
+
break;
|
|
7854
|
+
}
|
|
7855
|
+
end--;
|
|
7856
|
+
}
|
|
7857
|
+
if (found === -1) {
|
|
7858
|
+
into.push(this.unkId);
|
|
7859
|
+
return;
|
|
7860
|
+
}
|
|
7861
|
+
pieces.push(found);
|
|
7862
|
+
start = end;
|
|
7863
|
+
}
|
|
7864
|
+
for (const id of pieces) into.push(id);
|
|
7865
|
+
}
|
|
7866
|
+
/**
|
|
7867
|
+
* `[CLS] ... [SEP]`, cut to `maxLength` tokens.
|
|
7868
|
+
*
|
|
7869
|
+
* The cut is taken after the markers are added, not before, which means a sequence long enough to
|
|
7870
|
+
* be truncated ends on an ordinary token and has no [SEP] at all. That is what the reference does
|
|
7871
|
+
* and therefore what the model has been fed all along, so it is deliberate rather than an
|
|
7872
|
+
* oversight: keeping the [SEP] would be the more defensible sequence and a different one, and a
|
|
7873
|
+
* tokenizer whose whole justification is producing identical ids does not get to improve on them.
|
|
7874
|
+
* The oracle carries two cases that reach the limit; both end mid-text.
|
|
7875
|
+
*/
|
|
7876
|
+
encode(text, maxLength = MAX_SEQUENCE_TOKENS) {
|
|
7877
|
+
if (!Number.isInteger(maxLength) || maxLength < 2) {
|
|
7878
|
+
throw new RangeError(`maxLength must be an integer >= 2 (both markers), got ${maxLength}`);
|
|
7879
|
+
}
|
|
7880
|
+
const ids = [this.clsId];
|
|
7881
|
+
for (const word of preTokenize(normalize(text))) {
|
|
7882
|
+
this.wordToIds(word, ids);
|
|
7883
|
+
if (ids.length >= maxLength) return ids.slice(0, maxLength);
|
|
7884
|
+
}
|
|
7885
|
+
ids.push(this.sepId);
|
|
7886
|
+
return ids.length > maxLength ? ids.slice(0, maxLength) : ids;
|
|
7887
|
+
}
|
|
7888
|
+
};
|
|
7889
|
+
|
|
7890
|
+
// src/embed_model.ts
|
|
7891
|
+
var _require2 = createRequire3(import.meta.url);
|
|
7283
7892
|
var DEFAULT_MODEL = "Xenova/bge-small-en-v1.5";
|
|
7284
7893
|
var DEFAULT_DIM = 384;
|
|
7285
7894
|
var PINNED_MODEL_REVISION = "ea104dacec62c0de699686887e3f920caeb4f3e3";
|
|
7895
|
+
var MODEL_FILES = [
|
|
7896
|
+
{
|
|
7897
|
+
name: "tokenizer.json",
|
|
7898
|
+
sha256: "d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66",
|
|
7899
|
+
bytes: 711396
|
|
7900
|
+
},
|
|
7901
|
+
{
|
|
7902
|
+
name: "onnx/model_quantized.onnx",
|
|
7903
|
+
sha256: "6c9c6101a956d62dfb5e7190c538226c0c5bb9cb27b651234b6df063ee7dbfe4",
|
|
7904
|
+
bytes: 34014426
|
|
7905
|
+
}
|
|
7906
|
+
];
|
|
7907
|
+
function modelDir() {
|
|
7908
|
+
return path13.join(dataDir(), "models", ...DEFAULT_MODEL.split("/"), PINNED_MODEL_REVISION);
|
|
7909
|
+
}
|
|
7910
|
+
function downloadUrl(file) {
|
|
7911
|
+
return `https://huggingface.co/${DEFAULT_MODEL}/resolve/${PINNED_MODEL_REVISION}/${file.name}`;
|
|
7912
|
+
}
|
|
7913
|
+
var _ort = null;
|
|
7914
|
+
var _ortError = null;
|
|
7915
|
+
var _ortLoadAttempted = false;
|
|
7916
|
+
function ensureRuntimeLoaded() {
|
|
7917
|
+
if (_ortLoadAttempted) return;
|
|
7918
|
+
_ortLoadAttempted = true;
|
|
7919
|
+
try {
|
|
7920
|
+
_ort = _require2("onnxruntime-node");
|
|
7921
|
+
} catch (e) {
|
|
7922
|
+
_ortError = e instanceof Error ? e : new Error(String(e));
|
|
7923
|
+
}
|
|
7924
|
+
}
|
|
7925
|
+
function isRuntimeAvailable() {
|
|
7926
|
+
ensureRuntimeLoaded();
|
|
7927
|
+
return _ort !== null && _ortError === null;
|
|
7928
|
+
}
|
|
7929
|
+
function runtimeLoadError() {
|
|
7930
|
+
ensureRuntimeLoaded();
|
|
7931
|
+
return _ortError;
|
|
7932
|
+
}
|
|
7933
|
+
function runtimeVersion() {
|
|
7934
|
+
ensureRuntimeLoaded();
|
|
7935
|
+
if (_ort === null) return "unknown";
|
|
7936
|
+
try {
|
|
7937
|
+
let dir = path13.dirname(_require2.resolve("onnxruntime-node"));
|
|
7938
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
7939
|
+
const manifest = path13.join(dir, "package.json");
|
|
7940
|
+
if (fs17.existsSync(manifest)) {
|
|
7941
|
+
const parsed = JSON.parse(fs17.readFileSync(manifest, "utf8"));
|
|
7942
|
+
if (parsed.name === "onnxruntime-node" && typeof parsed.version === "string") return parsed.version;
|
|
7943
|
+
}
|
|
7944
|
+
const up = path13.dirname(dir);
|
|
7945
|
+
if (up === dir) break;
|
|
7946
|
+
dir = up;
|
|
7947
|
+
}
|
|
7948
|
+
} catch {
|
|
7949
|
+
}
|
|
7950
|
+
return "unknown";
|
|
7951
|
+
}
|
|
7952
|
+
function sha256Of(filePath) {
|
|
7953
|
+
return new Promise((resolve10, reject) => {
|
|
7954
|
+
const hash2 = createHash4("sha256");
|
|
7955
|
+
const stream = fs17.createReadStream(filePath);
|
|
7956
|
+
stream.on("error", reject);
|
|
7957
|
+
stream.on("data", (chunk) => hash2.update(chunk));
|
|
7958
|
+
stream.on("end", () => resolve10(hash2.digest("hex")));
|
|
7959
|
+
});
|
|
7960
|
+
}
|
|
7961
|
+
async function download(file, target) {
|
|
7962
|
+
const url = downloadUrl(file);
|
|
7963
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
7964
|
+
if (!response.ok) throw new Error(`GET ${url} returned ${response.status} ${response.statusText}`);
|
|
7965
|
+
if (!response.body) throw new Error(`GET ${url} returned no body`);
|
|
7966
|
+
const temp = `${target}.${process.pid}.partial`;
|
|
7967
|
+
const hash2 = createHash4("sha256");
|
|
7968
|
+
let written = 0;
|
|
7969
|
+
const out = fs17.createWriteStream(temp);
|
|
7970
|
+
try {
|
|
7971
|
+
await pipeline(async function* () {
|
|
7972
|
+
for await (const chunk of response.body) {
|
|
7973
|
+
written += chunk.byteLength;
|
|
7974
|
+
if (written > file.bytes) throw new Error(`${file.name} is longer than the pinned ${file.bytes} bytes`);
|
|
7975
|
+
hash2.update(chunk);
|
|
7976
|
+
yield chunk;
|
|
7977
|
+
}
|
|
7978
|
+
}, out);
|
|
7979
|
+
if (written !== file.bytes) {
|
|
7980
|
+
throw new Error(`${file.name} is ${written} bytes, expected the pinned ${file.bytes}`);
|
|
7981
|
+
}
|
|
7982
|
+
const digest = hash2.digest("hex");
|
|
7983
|
+
if (digest !== file.sha256) {
|
|
7984
|
+
throw new Error(`${file.name} has sha256 ${digest}, expected the pinned ${file.sha256}`);
|
|
7985
|
+
}
|
|
7986
|
+
fs17.renameSync(temp, target);
|
|
7987
|
+
} catch (e) {
|
|
7988
|
+
await new Promise((resolve10) => {
|
|
7989
|
+
if (out.closed) resolve10();
|
|
7990
|
+
else out.once("close", () => resolve10());
|
|
7991
|
+
});
|
|
7992
|
+
try {
|
|
7993
|
+
fs17.rmSync(temp, { force: true, maxRetries: 20, retryDelay: 25 });
|
|
7994
|
+
} catch {
|
|
7995
|
+
}
|
|
7996
|
+
throw e;
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
async function ensureModelFiles(modelName = DEFAULT_MODEL) {
|
|
8000
|
+
if (modelName !== DEFAULT_MODEL) {
|
|
8001
|
+
throw new Error(
|
|
8002
|
+
`Only ${DEFAULT_MODEL} is supported: its files are pinned to a revision and to a sha256 each, and "${modelName}" has neither, so there would be nothing to check the download against.`
|
|
8003
|
+
);
|
|
8004
|
+
}
|
|
8005
|
+
ensureDataDirPrivate();
|
|
8006
|
+
const dir = modelDir();
|
|
8007
|
+
const offline = loadConfig().network.offline;
|
|
8008
|
+
for (const file of MODEL_FILES) {
|
|
8009
|
+
const target = path13.join(dir, file.name);
|
|
8010
|
+
if (fs17.existsSync(target)) {
|
|
8011
|
+
const digest = await sha256Of(target);
|
|
8012
|
+
if (digest === file.sha256) continue;
|
|
8013
|
+
fs17.rmSync(target, { force: true });
|
|
8014
|
+
}
|
|
8015
|
+
if (offline) {
|
|
8016
|
+
throw new Error(
|
|
8017
|
+
`Offline mode is on (network.offline): refusing to download ${file.name} for the embedding model. Copy the pinned files into ${dir} on a connected machine to use semantic search here.`
|
|
8018
|
+
);
|
|
8019
|
+
}
|
|
8020
|
+
fs17.mkdirSync(path13.dirname(target), { recursive: true });
|
|
8021
|
+
console.warn(
|
|
8022
|
+
`Downloading the embedding model, once (${file.name}, ${Math.round(file.bytes / 1024 / 1024)} MB) into ${dir}`
|
|
8023
|
+
);
|
|
8024
|
+
await download(file, target);
|
|
8025
|
+
}
|
|
8026
|
+
return dir;
|
|
8027
|
+
}
|
|
8028
|
+
function poolAndNormalize(hidden, seq, dim) {
|
|
8029
|
+
const pooled = new Float64Array(dim);
|
|
8030
|
+
for (let t = 0; t < seq; t++) {
|
|
8031
|
+
const base = t * dim;
|
|
8032
|
+
for (let d = 0; d < dim; d++) pooled[d] = (pooled[d] ?? 0) + (hidden[base + d] ?? 0);
|
|
8033
|
+
}
|
|
8034
|
+
let sumOfSquares = 0;
|
|
8035
|
+
for (let d = 0; d < dim; d++) {
|
|
8036
|
+
const mean = (pooled[d] ?? 0) / seq;
|
|
8037
|
+
pooled[d] = mean;
|
|
8038
|
+
sumOfSquares += mean * mean;
|
|
8039
|
+
}
|
|
8040
|
+
const norm = Math.sqrt(sumOfSquares);
|
|
8041
|
+
const out = new Float32Array(dim);
|
|
8042
|
+
if (norm === 0 || !Number.isFinite(norm)) return out;
|
|
8043
|
+
for (let d = 0; d < dim; d++) out[d] = (pooled[d] ?? 0) / norm;
|
|
8044
|
+
return out;
|
|
8045
|
+
}
|
|
8046
|
+
var EmbeddingModel = class _EmbeddingModel {
|
|
8047
|
+
constructor(tokenizer, session, tensorFactory) {
|
|
8048
|
+
this.tokenizer = tokenizer;
|
|
8049
|
+
this.session = session;
|
|
8050
|
+
this.tensorFactory = tensorFactory;
|
|
8051
|
+
}
|
|
8052
|
+
tokenizer;
|
|
8053
|
+
session;
|
|
8054
|
+
tensorFactory;
|
|
8055
|
+
static async load(modelName = DEFAULT_MODEL) {
|
|
8056
|
+
if (!isRuntimeAvailable()) {
|
|
8057
|
+
throw new Error(`onnxruntime-node is not available: ${_ortError?.message ?? "unknown error"}`);
|
|
8058
|
+
}
|
|
8059
|
+
const dir = await ensureModelFiles(modelName);
|
|
8060
|
+
const tokenizer = BertWordPiece.fromJson(fs17.readFileSync(path13.join(dir, "tokenizer.json"), "utf8"));
|
|
8061
|
+
const ort = _ort;
|
|
8062
|
+
const session = await ort.InferenceSession.create(path13.join(dir, "onnx", "model_quantized.onnx"));
|
|
8063
|
+
return new _EmbeddingModel(tokenizer, session, ort.Tensor);
|
|
8064
|
+
}
|
|
8065
|
+
/** Embed one text. Sequences are run singly, so there is no padding and no mask to get wrong. */
|
|
8066
|
+
async embed(text) {
|
|
8067
|
+
const ids = this.tokenizer.encode(text);
|
|
8068
|
+
const length = ids.length;
|
|
8069
|
+
const feeds = {
|
|
8070
|
+
input_ids: new this.tensorFactory("int64", BigInt64Array.from(ids, BigInt), [1, length]),
|
|
8071
|
+
attention_mask: new this.tensorFactory("int64", new BigInt64Array(length).fill(1n), [1, length])
|
|
8072
|
+
};
|
|
8073
|
+
if (this.session.inputNames.includes("token_type_ids")) {
|
|
8074
|
+
feeds["token_type_ids"] = new this.tensorFactory("int64", new BigInt64Array(length), [1, length]);
|
|
8075
|
+
}
|
|
8076
|
+
const outputName = this.session.outputNames[0];
|
|
8077
|
+
if (outputName === void 0) throw new Error("the model declares no outputs");
|
|
8078
|
+
const output = (await this.session.run(feeds))[outputName];
|
|
8079
|
+
if (!output) throw new Error(`the model produced no ${outputName}`);
|
|
8080
|
+
const [, seq, dim] = output.dims;
|
|
8081
|
+
if (seq === void 0 || dim === void 0) {
|
|
8082
|
+
throw new Error(`expected a [batch, sequence, dimension] output, got [${output.dims.join(", ")}]`);
|
|
8083
|
+
}
|
|
8084
|
+
if (dim !== DEFAULT_DIM) {
|
|
8085
|
+
throw new Error(`the model produced ${dim}-dimension vectors, expected ${DEFAULT_DIM}`);
|
|
8086
|
+
}
|
|
8087
|
+
return poolAndNormalize(output.data, seq, dim);
|
|
8088
|
+
}
|
|
8089
|
+
};
|
|
8090
|
+
registerReset(() => {
|
|
8091
|
+
_ort = null;
|
|
8092
|
+
_ortError = null;
|
|
8093
|
+
_ortLoadAttempted = false;
|
|
8094
|
+
});
|
|
8095
|
+
|
|
8096
|
+
// src/embeddings.ts
|
|
7286
8097
|
var QUERY_INSTRUCTION_PREFIX = "Represent this sentence for searching relevant passages: ";
|
|
7287
8098
|
var _extractorCache = /* @__PURE__ */ new Map();
|
|
8099
|
+
var inHousePipelineFn = async (_task, modelName) => {
|
|
8100
|
+
const model = await EmbeddingModel.load(modelName);
|
|
8101
|
+
return async (text) => ({ data: await model.embed(text) });
|
|
8102
|
+
};
|
|
7288
8103
|
var _pipelineFnOverride = null;
|
|
7289
8104
|
registerReset(() => {
|
|
7290
8105
|
_extractorCache.clear();
|
|
@@ -7303,8 +8118,7 @@ async function buildExtractorWithRetry(pipelineFn, modelName) {
|
|
|
7303
8118
|
let lastError;
|
|
7304
8119
|
for (let attempt = 1; attempt <= PIPELINE_RETRY_ATTEMPTS; attempt++) {
|
|
7305
8120
|
try {
|
|
7306
|
-
|
|
7307
|
-
return await pipelineFn("feature-extraction", modelName, pipelineOptions);
|
|
8121
|
+
return await pipelineFn("feature-extraction", modelName);
|
|
7308
8122
|
} catch (e) {
|
|
7309
8123
|
lastError = e;
|
|
7310
8124
|
if (attempt < PIPELINE_RETRY_ATTEMPTS) await sleep(PIPELINE_RETRY_DELAY_MS * attempt);
|
|
@@ -7356,25 +8170,23 @@ var _MIN_TOKEN_LEN = 3;
|
|
|
7356
8170
|
var OVER_FETCH_FACTOR = 4;
|
|
7357
8171
|
var MAX_OVER_FETCH = 100;
|
|
7358
8172
|
function isAvailable() {
|
|
7359
|
-
|
|
7360
|
-
|
|
8173
|
+
return isRuntimeAvailable();
|
|
8174
|
+
}
|
|
8175
|
+
function embeddingBackendLoadError() {
|
|
8176
|
+
return runtimeLoadError();
|
|
7361
8177
|
}
|
|
7362
8178
|
async function embedTexts(texts, modelName = DEFAULT_MODEL) {
|
|
7363
8179
|
if (!isAvailable()) {
|
|
7364
8180
|
throw new Error(
|
|
7365
|
-
`
|
|
8181
|
+
`Embedding backend not available: ${runtimeLoadError()?.message ?? "unknown error"}`
|
|
7366
8182
|
);
|
|
7367
8183
|
}
|
|
7368
8184
|
if (texts.length === 0) {
|
|
7369
8185
|
return [];
|
|
7370
8186
|
}
|
|
7371
|
-
if (!_transformer || typeof _transformer !== "object") {
|
|
7372
|
-
throw new Error("Transformer module is unavailable");
|
|
7373
|
-
}
|
|
7374
8187
|
let extractorPromise = _extractorCache.get(modelName);
|
|
7375
8188
|
if (!extractorPromise) {
|
|
7376
|
-
const
|
|
7377
|
-
const pipelineFn = _pipelineFnOverride ?? transformerObj["pipeline"];
|
|
8189
|
+
const pipelineFn = _pipelineFnOverride ?? inHousePipelineFn;
|
|
7378
8190
|
extractorPromise = buildExtractorWithRetry(pipelineFn, modelName);
|
|
7379
8191
|
_extractorCache.set(modelName, extractorPromise);
|
|
7380
8192
|
extractorPromise.catch(() => {
|
|
@@ -7586,6 +8398,7 @@ async function upsertChunks(db, chunks) {
|
|
|
7586
8398
|
deleteFileEmbeddings(db, filePath);
|
|
7587
8399
|
return "unavailable";
|
|
7588
8400
|
}
|
|
8401
|
+
ensureEmbeddingProvenance(db);
|
|
7589
8402
|
const texts = chunks.map((c) => c.text);
|
|
7590
8403
|
const embeddings = await embedTexts(texts);
|
|
7591
8404
|
const chunkInsertStmt = db.prepare(`
|
|
@@ -7656,7 +8469,6 @@ function fetchScopedHits(db, queryVec, k, maxDistance, rootDir) {
|
|
|
7656
8469
|
}
|
|
7657
8470
|
async function searchSemantic(db, query, topK = 8, modelName = DEFAULT_MODEL, maxDistance = DEFAULT_DISTANCE_THRESHOLD, rootDir) {
|
|
7658
8471
|
if (!isAvailable()) {
|
|
7659
|
-
console.warn("Embeddings not available; semantic search disabled");
|
|
7660
8472
|
return [];
|
|
7661
8473
|
}
|
|
7662
8474
|
if (query.trim().length === 0) {
|
|
@@ -7665,6 +8477,7 @@ async function searchSemantic(db, query, topK = 8, modelName = DEFAULT_MODEL, ma
|
|
|
7665
8477
|
if (!chunkVectorsTableExists(db)) {
|
|
7666
8478
|
return [];
|
|
7667
8479
|
}
|
|
8480
|
+
ensureEmbeddingProvenance(db, modelName);
|
|
7668
8481
|
const queryEmbeddings = await embedTexts([`${QUERY_INSTRUCTION_PREFIX}${query}`], modelName);
|
|
7669
8482
|
if (queryEmbeddings.length === 0) {
|
|
7670
8483
|
return [];
|
|
@@ -7813,6 +8626,44 @@ function deleteFileEmbeddings(db, filePath) {
|
|
|
7813
8626
|
}
|
|
7814
8627
|
db.prepare(`DELETE FROM chunks WHERE ${pathEqClause("file_path")}`).run(folded);
|
|
7815
8628
|
}
|
|
8629
|
+
function resetAllEmbeddings(db) {
|
|
8630
|
+
const paths = db.prepare("SELECT DISTINCT file_path FROM chunks").pluck().all();
|
|
8631
|
+
const clearEmbedSha = db.prepare(`UPDATE files SET embed_sha = NULL WHERE ${pathEqClause("path")}`);
|
|
8632
|
+
const tx = db.transaction(() => {
|
|
8633
|
+
for (const p of paths) deleteFileEmbeddings(db, p);
|
|
8634
|
+
for (const p of paths) clearEmbedSha.run(foldPath(p));
|
|
8635
|
+
});
|
|
8636
|
+
tx.immediate();
|
|
8637
|
+
return paths.length;
|
|
8638
|
+
}
|
|
8639
|
+
function embeddingProvenance(modelName = DEFAULT_MODEL) {
|
|
8640
|
+
const revision = modelName === DEFAULT_MODEL ? PINNED_MODEL_REVISION.slice(0, 12) : "unpinned";
|
|
8641
|
+
return `${modelName}@${revision}/${backendId()}`;
|
|
8642
|
+
}
|
|
8643
|
+
function backendId() {
|
|
8644
|
+
return `onnxruntime-node@${majorMinor(runtimeVersion())}`;
|
|
8645
|
+
}
|
|
8646
|
+
function majorMinor(version) {
|
|
8647
|
+
const parts = version.split(".");
|
|
8648
|
+
return parts.length >= 2 ? `${parts[0]}.${parts[1]}` : version;
|
|
8649
|
+
}
|
|
8650
|
+
var _provenanceChecked = /* @__PURE__ */ new WeakSet();
|
|
8651
|
+
function ensureEmbeddingProvenance(db, modelName = DEFAULT_MODEL) {
|
|
8652
|
+
if (_provenanceChecked.has(db)) return;
|
|
8653
|
+
_provenanceChecked.add(db);
|
|
8654
|
+
const current = embeddingProvenance(modelName);
|
|
8655
|
+
const stored = db.prepare("SELECT provenance FROM embedding_provenance WHERE id = 1").pluck().get();
|
|
8656
|
+
if (stored === current) return;
|
|
8657
|
+
const cleared = resetAllEmbeddings(db);
|
|
8658
|
+
db.prepare(
|
|
8659
|
+
"INSERT INTO embedding_provenance (id, provenance) VALUES (1, ?) ON CONFLICT(id) DO UPDATE SET provenance = excluded.provenance"
|
|
8660
|
+
).run(current);
|
|
8661
|
+
if (cleared > 0) {
|
|
8662
|
+
console.warn(
|
|
8663
|
+
`Embedding stack changed (${stored ?? "unrecorded"} -> ${current}); discarded ${cleared} file${cleared === 1 ? "" : "s"} worth of vectors because old and new ones are not comparable. Run \`token-goat index\` to rebuild them.`
|
|
8664
|
+
);
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
7816
8667
|
function _extractQueryTokens(query) {
|
|
7817
8668
|
const tokens = /* @__PURE__ */ new Set();
|
|
7818
8669
|
const matches = query.matchAll(_TOKEN_RE);
|
|
@@ -7849,17 +8700,19 @@ function _pathPriorityPenalty(filePath) {
|
|
|
7849
8700
|
}
|
|
7850
8701
|
|
|
7851
8702
|
// src/hooks_read.ts
|
|
7852
|
-
import * as
|
|
7853
|
-
import * as
|
|
8703
|
+
import * as fs23 from "node:fs";
|
|
8704
|
+
import * as path19 from "node:path";
|
|
7854
8705
|
|
|
7855
8706
|
// src/compact.ts
|
|
7856
|
-
import * as
|
|
7857
|
-
import * as
|
|
8707
|
+
import * as fs18 from "node:fs";
|
|
8708
|
+
import * as path14 from "node:path";
|
|
7858
8709
|
|
|
7859
8710
|
// src/overflow_guard.ts
|
|
8711
|
+
function estimateTokensFromLength(length) {
|
|
8712
|
+
return Math.max(1, Math.floor(Math.max(0, length) / 3) + 1);
|
|
8713
|
+
}
|
|
7860
8714
|
function estimateTokens(text) {
|
|
7861
|
-
|
|
7862
|
-
return Math.max(1, Math.floor(stripped.length / 3) + 1);
|
|
8715
|
+
return estimateTokensFromLength(stripAnsiCodes(text).length);
|
|
7863
8716
|
}
|
|
7864
8717
|
function trimToBudget(text, budgetTokens, command) {
|
|
7865
8718
|
const markerMarginTokens = 64;
|
|
@@ -8116,7 +8969,7 @@ function inferSessionGoal(cache, maxTokens = 80) {
|
|
|
8116
8969
|
const dirCounts = new Counter();
|
|
8117
8970
|
for (const fpath of editedPaths) {
|
|
8118
8971
|
try {
|
|
8119
|
-
let parent =
|
|
8972
|
+
let parent = path14.dirname(fpath);
|
|
8120
8973
|
if (parent === ".") {
|
|
8121
8974
|
parent = "root";
|
|
8122
8975
|
} else if (parent.startsWith("./")) {
|
|
@@ -8192,11 +9045,11 @@ function isNoisePath(inputPath) {
|
|
|
8192
9045
|
}
|
|
8193
9046
|
function findLatestSessionId() {
|
|
8194
9047
|
try {
|
|
8195
|
-
const sessionsDir =
|
|
8196
|
-
if (!
|
|
9048
|
+
const sessionsDir = path14.join(tokenGoatHome(), "sessions");
|
|
9049
|
+
if (!fs18.existsSync(sessionsDir)) {
|
|
8197
9050
|
return null;
|
|
8198
9051
|
}
|
|
8199
|
-
const files =
|
|
9052
|
+
const files = fs18.readdirSync(sessionsDir);
|
|
8200
9053
|
const jsonFiles = files.filter((f) => f.endsWith(".json") && !f.includes(AGENT_SALT_MARKER));
|
|
8201
9054
|
if (jsonFiles.length === 0) {
|
|
8202
9055
|
return null;
|
|
@@ -8206,9 +9059,9 @@ function findLatestSessionId() {
|
|
|
8206
9059
|
return null;
|
|
8207
9060
|
}
|
|
8208
9061
|
let latestFile = firstFile;
|
|
8209
|
-
let latestMtime =
|
|
9062
|
+
let latestMtime = fs18.statSync(path14.join(sessionsDir, firstFile)).mtimeMs;
|
|
8210
9063
|
for (const file of jsonFiles) {
|
|
8211
|
-
const mtime =
|
|
9064
|
+
const mtime = fs18.statSync(path14.join(sessionsDir, file)).mtimeMs;
|
|
8212
9065
|
if (mtime > latestMtime) {
|
|
8213
9066
|
latestFile = file;
|
|
8214
9067
|
latestMtime = mtime;
|
|
@@ -8230,37 +9083,37 @@ function eventCount(cache) {
|
|
|
8230
9083
|
function writeSessionManifest(projectHash, sessionId, manifestJson) {
|
|
8231
9084
|
const safeSessionId = sanitizeIdForFilename(sessionId, 64);
|
|
8232
9085
|
if (!safeSessionId) return;
|
|
8233
|
-
const sessionsDir =
|
|
8234
|
-
if (!
|
|
9086
|
+
const sessionsDir = path14.join(dataDir(), "projects", projectHash, "sessions");
|
|
9087
|
+
if (!fs18.existsSync(sessionsDir)) {
|
|
8235
9088
|
ensureDirSync(sessionsDir);
|
|
8236
9089
|
}
|
|
8237
|
-
const dest =
|
|
9090
|
+
const dest = path14.join(sessionsDir, `${safeSessionId}.json`);
|
|
8238
9091
|
atomicWriteText(dest, JSON.stringify(manifestJson));
|
|
8239
9092
|
}
|
|
8240
9093
|
function readAllSessionManifests(projectHash, maxAgeSecs = 3600) {
|
|
8241
|
-
const sessionsDir =
|
|
8242
|
-
if (!
|
|
9094
|
+
const sessionsDir = path14.join(dataDir(), "projects", projectHash, "sessions");
|
|
9095
|
+
if (!fs18.existsSync(sessionsDir)) {
|
|
8243
9096
|
return [];
|
|
8244
9097
|
}
|
|
8245
9098
|
const now = Date.now() / 1e3;
|
|
8246
9099
|
const results = [];
|
|
8247
9100
|
try {
|
|
8248
|
-
const files =
|
|
9101
|
+
const files = fs18.readdirSync(sessionsDir);
|
|
8249
9102
|
for (const file of files) {
|
|
8250
9103
|
if (!file.endsWith(".json")) {
|
|
8251
9104
|
continue;
|
|
8252
9105
|
}
|
|
8253
9106
|
try {
|
|
8254
|
-
const fullPath =
|
|
8255
|
-
const stat2 =
|
|
9107
|
+
const fullPath = path14.join(sessionsDir, file);
|
|
9108
|
+
const stat2 = fs18.statSync(fullPath);
|
|
8256
9109
|
if (now - stat2.mtimeMs / 1e3 > maxAgeSecs) {
|
|
8257
9110
|
try {
|
|
8258
|
-
|
|
9111
|
+
fs18.unlinkSync(fullPath);
|
|
8259
9112
|
} catch {
|
|
8260
9113
|
}
|
|
8261
9114
|
continue;
|
|
8262
9115
|
}
|
|
8263
|
-
const text =
|
|
9116
|
+
const text = fs18.readFileSync(fullPath, "utf8");
|
|
8264
9117
|
const data = JSON.parse(text);
|
|
8265
9118
|
if (typeof data === "object" && data !== null && "files" in data) {
|
|
8266
9119
|
results.push(data);
|
|
@@ -8431,8 +9284,8 @@ function buildManifestWithCount(sessionId, opts) {
|
|
|
8431
9284
|
}
|
|
8432
9285
|
|
|
8433
9286
|
// src/snapshots.ts
|
|
8434
|
-
import * as
|
|
8435
|
-
import * as
|
|
9287
|
+
import * as fs19 from "node:fs";
|
|
9288
|
+
import * as path15 from "node:path";
|
|
8436
9289
|
var MAX_SNAPSHOTS_PER_SESSION = 150;
|
|
8437
9290
|
var MAX_SNAPSHOT_BYTES = 256 * 1024;
|
|
8438
9291
|
var SNAPSHOT_TRUNCATE_BYTES = 50 * 1024;
|
|
@@ -8442,10 +9295,10 @@ var VALID_KINDS = /* @__PURE__ */ new Set([KIND_READ, KIND_PREDICTIVE]);
|
|
|
8442
9295
|
function sessionDir(sessionId) {
|
|
8443
9296
|
if (!sessionId) return null;
|
|
8444
9297
|
const safe = sanitizeIdForFilename(sessionId, 64, "anon");
|
|
8445
|
-
const base =
|
|
8446
|
-
const candidate =
|
|
9298
|
+
const base = path15.join(tokenGoatHome(), "session_snapshots");
|
|
9299
|
+
const candidate = path15.join(base, safe);
|
|
8447
9300
|
try {
|
|
8448
|
-
const rel =
|
|
9301
|
+
const rel = path15.relative(base, candidate);
|
|
8449
9302
|
if (rel.startsWith("..")) return null;
|
|
8450
9303
|
} catch {
|
|
8451
9304
|
return null;
|
|
@@ -8458,7 +9311,7 @@ function pathKey(filePath) {
|
|
|
8458
9311
|
function snapshot_path(sessionId, filePath) {
|
|
8459
9312
|
const d = sessionDir(sessionId);
|
|
8460
9313
|
if (!d) return null;
|
|
8461
|
-
return
|
|
9314
|
+
return path15.join(d, `${pathKey(filePath)}.bin`);
|
|
8462
9315
|
}
|
|
8463
9316
|
function kindSidecarPath(snapshotPath) {
|
|
8464
9317
|
return snapshotPath + ".kind";
|
|
@@ -8466,11 +9319,11 @@ function kindSidecarPath(snapshotPath) {
|
|
|
8466
9319
|
function writeSnapshotKind(sidecarPath, kind) {
|
|
8467
9320
|
try {
|
|
8468
9321
|
const safeKind = VALID_KINDS.has(kind) ? kind : KIND_READ;
|
|
8469
|
-
const dir =
|
|
8470
|
-
if (!
|
|
9322
|
+
const dir = path15.dirname(sidecarPath);
|
|
9323
|
+
if (!fs19.existsSync(dir)) {
|
|
8471
9324
|
ensureDirSync(dir);
|
|
8472
9325
|
}
|
|
8473
|
-
|
|
9326
|
+
fs19.writeFileSync(sidecarPath, safeKind, "utf8");
|
|
8474
9327
|
return true;
|
|
8475
9328
|
} catch {
|
|
8476
9329
|
return false;
|
|
@@ -8479,12 +9332,12 @@ function writeSnapshotKind(sidecarPath, kind) {
|
|
|
8479
9332
|
function evictOldest(d, maxCount) {
|
|
8480
9333
|
try {
|
|
8481
9334
|
const entries = [];
|
|
8482
|
-
const files =
|
|
9335
|
+
const files = fs19.readdirSync(d);
|
|
8483
9336
|
for (const file of files) {
|
|
8484
|
-
const fullPath =
|
|
9337
|
+
const fullPath = path15.join(d, file);
|
|
8485
9338
|
if (!file.endsWith(".bin")) continue;
|
|
8486
9339
|
try {
|
|
8487
|
-
const stat2 =
|
|
9340
|
+
const stat2 = fs19.statSync(fullPath);
|
|
8488
9341
|
entries.push([fullPath, stat2.mtimeMs]);
|
|
8489
9342
|
} catch {
|
|
8490
9343
|
continue;
|
|
@@ -8496,10 +9349,10 @@ function evictOldest(d, maxCount) {
|
|
|
8496
9349
|
const over = entries.length - maxCount;
|
|
8497
9350
|
for (const [p] of entries.slice(0, over)) {
|
|
8498
9351
|
try {
|
|
8499
|
-
|
|
9352
|
+
fs19.unlinkSync(p);
|
|
8500
9353
|
removed++;
|
|
8501
9354
|
try {
|
|
8502
|
-
|
|
9355
|
+
fs19.unlinkSync(kindSidecarPath(p));
|
|
8503
9356
|
} catch {
|
|
8504
9357
|
}
|
|
8505
9358
|
} catch {
|
|
@@ -8528,10 +9381,10 @@ function store(sessionId, filePath, content, opts = {}) {
|
|
|
8528
9381
|
if (!p) return null;
|
|
8529
9382
|
const sha = fingerprintContent(stored);
|
|
8530
9383
|
try {
|
|
8531
|
-
const isNewEntry = !
|
|
9384
|
+
const isNewEntry = !fs19.existsSync(p);
|
|
8532
9385
|
if (!isNewEntry) {
|
|
8533
9386
|
try {
|
|
8534
|
-
const existing =
|
|
9387
|
+
const existing = fs19.readFileSync(p);
|
|
8535
9388
|
if (Buffer.from(existing).equals(stored)) {
|
|
8536
9389
|
return {
|
|
8537
9390
|
path: p,
|
|
@@ -8542,8 +9395,8 @@ function store(sessionId, filePath, content, opts = {}) {
|
|
|
8542
9395
|
} catch {
|
|
8543
9396
|
}
|
|
8544
9397
|
}
|
|
8545
|
-
const dir =
|
|
8546
|
-
if (!
|
|
9398
|
+
const dir = path15.dirname(p);
|
|
9399
|
+
if (!fs19.existsSync(dir)) {
|
|
8547
9400
|
ensureDirSync(dir);
|
|
8548
9401
|
}
|
|
8549
9402
|
if (isNewEntry) {
|
|
@@ -8563,9 +9416,9 @@ function store(sessionId, filePath, content, opts = {}) {
|
|
|
8563
9416
|
}
|
|
8564
9417
|
function load(sessionId, filePath, opts = {}) {
|
|
8565
9418
|
const p = snapshot_path(sessionId, filePath);
|
|
8566
|
-
if (!p || !
|
|
9419
|
+
if (!p || !fs19.existsSync(p)) return null;
|
|
8567
9420
|
try {
|
|
8568
|
-
const stat2 =
|
|
9421
|
+
const stat2 = fs19.statSync(p);
|
|
8569
9422
|
if (stat2.size > MAX_SNAPSHOT_BYTES) {
|
|
8570
9423
|
return null;
|
|
8571
9424
|
}
|
|
@@ -8573,7 +9426,7 @@ function load(sessionId, filePath, opts = {}) {
|
|
|
8573
9426
|
return null;
|
|
8574
9427
|
}
|
|
8575
9428
|
try {
|
|
8576
|
-
const data =
|
|
9429
|
+
const data = fs19.readFileSync(p);
|
|
8577
9430
|
if (opts.expected_sha) {
|
|
8578
9431
|
const actualSha = fingerprintContent(data);
|
|
8579
9432
|
if (actualSha.toLowerCase() !== opts.expected_sha.toLowerCase()) {
|
|
@@ -8586,31 +9439,31 @@ function load(sessionId, filePath, opts = {}) {
|
|
|
8586
9439
|
}
|
|
8587
9440
|
}
|
|
8588
9441
|
function removeEligibleSnapshotFile(fullPath, file, cutoff) {
|
|
8589
|
-
const stat2 =
|
|
9442
|
+
const stat2 = fs19.lstatSync(fullPath);
|
|
8590
9443
|
if ((stat2.mode & 61440) === 40960) return false;
|
|
8591
9444
|
if (cutoff !== void 0 && stat2.mtimeMs >= cutoff) return false;
|
|
8592
|
-
|
|
9445
|
+
fs19.unlinkSync(fullPath);
|
|
8593
9446
|
return file.endsWith(".bin");
|
|
8594
9447
|
}
|
|
8595
9448
|
function cleanup_stale(maxAgeHours = 24) {
|
|
8596
|
-
const base =
|
|
8597
|
-
if (!
|
|
9449
|
+
const base = path15.join(tokenGoatHome(), "session_snapshots");
|
|
9450
|
+
if (!fs19.existsSync(base)) return 0;
|
|
8598
9451
|
const cutoff = Date.now() - maxAgeHours * 3600 * 1e3;
|
|
8599
9452
|
let removed = 0;
|
|
8600
9453
|
try {
|
|
8601
|
-
const sessionDirs =
|
|
9454
|
+
const sessionDirs = fs19.readdirSync(base);
|
|
8602
9455
|
for (const sessionDir2 of sessionDirs) {
|
|
8603
|
-
const sessionPath2 =
|
|
9456
|
+
const sessionPath2 = path15.join(base, sessionDir2);
|
|
8604
9457
|
try {
|
|
8605
|
-
const stat2 =
|
|
9458
|
+
const stat2 = fs19.statSync(sessionPath2);
|
|
8606
9459
|
if (!stat2.isDirectory()) continue;
|
|
8607
9460
|
} catch {
|
|
8608
9461
|
continue;
|
|
8609
9462
|
}
|
|
8610
9463
|
try {
|
|
8611
|
-
const files =
|
|
9464
|
+
const files = fs19.readdirSync(sessionPath2);
|
|
8612
9465
|
for (const file of files) {
|
|
8613
|
-
const fullPath =
|
|
9466
|
+
const fullPath = path15.join(sessionPath2, file);
|
|
8614
9467
|
try {
|
|
8615
9468
|
if (removeEligibleSnapshotFile(fullPath, file, cutoff)) removed++;
|
|
8616
9469
|
} catch {
|
|
@@ -8618,7 +9471,7 @@ function cleanup_stale(maxAgeHours = 24) {
|
|
|
8618
9471
|
}
|
|
8619
9472
|
}
|
|
8620
9473
|
try {
|
|
8621
|
-
|
|
9474
|
+
fs19.rmdirSync(sessionPath2);
|
|
8622
9475
|
} catch {
|
|
8623
9476
|
}
|
|
8624
9477
|
} catch {
|
|
@@ -8654,11 +9507,11 @@ function buildPackageManifestHint(options) {
|
|
|
8654
9507
|
return null;
|
|
8655
9508
|
}
|
|
8656
9509
|
}
|
|
8657
|
-
function _sanitizeHintPath(
|
|
8658
|
-
if (typeof
|
|
9510
|
+
function _sanitizeHintPath(path31) {
|
|
9511
|
+
if (typeof path31 !== "string") {
|
|
8659
9512
|
return "???";
|
|
8660
9513
|
}
|
|
8661
|
-
return
|
|
9514
|
+
return path31.replace(/[\x00]/g, "").slice(0, 200);
|
|
8662
9515
|
}
|
|
8663
9516
|
|
|
8664
9517
|
// src/hints/lang_patterns.ts
|
|
@@ -9286,33 +10139,33 @@ function dispatchFileTypeHandler(filePath, content, contentLengthHint) {
|
|
|
9286
10139
|
}
|
|
9287
10140
|
|
|
9288
10141
|
// src/doc_compact.ts
|
|
9289
|
-
import * as
|
|
9290
|
-
import * as
|
|
10142
|
+
import * as fs20 from "fs";
|
|
10143
|
+
import * as path16 from "path";
|
|
9291
10144
|
var defaultSentencesPerSection = 2;
|
|
9292
10145
|
var headerPrefix = "<!-- token-goat doc-compact source-hash:";
|
|
9293
10146
|
var headerRegex = /^<!-- token-goat doc-compact source-hash:(\S+) source:(.+?) -->\r?$/;
|
|
9294
10147
|
var compactSubdir = "doc_compacts";
|
|
9295
10148
|
function sourceHash(filePath) {
|
|
9296
10149
|
try {
|
|
9297
|
-
return fingerprintContent(
|
|
10150
|
+
return fingerprintContent(fs20.readFileSync(filePath));
|
|
9298
10151
|
} catch {
|
|
9299
10152
|
return "";
|
|
9300
10153
|
}
|
|
9301
10154
|
}
|
|
9302
10155
|
function _compactSlug(absPathStr) {
|
|
9303
10156
|
const h = fingerprintContent(foldPath(absPathStr)).slice(0, 12);
|
|
9304
|
-
const ext =
|
|
9305
|
-
const stem =
|
|
10157
|
+
const ext = path16.extname(absPathStr);
|
|
10158
|
+
const stem = path16.basename(absPathStr, ext);
|
|
9306
10159
|
const safeStem = sanitizeIdForFilename(stem, 32);
|
|
9307
10160
|
return `${h}_${safeStem}`;
|
|
9308
10161
|
}
|
|
9309
10162
|
function compactPathFor(sourcePath) {
|
|
9310
10163
|
const abs = resolveIndexPath(sourcePath);
|
|
9311
|
-
return
|
|
10164
|
+
return path16.join(dataDir(), compactSubdir, `${_compactSlug(abs)}.md`);
|
|
9312
10165
|
}
|
|
9313
10166
|
function readCompactHeader(compactPath) {
|
|
9314
10167
|
try {
|
|
9315
|
-
const text =
|
|
10168
|
+
const text = fs20.readFileSync(compactPath, "utf-8");
|
|
9316
10169
|
const firstLine = text.split("\n")[0] || "";
|
|
9317
10170
|
const m = firstLine.match(headerRegex);
|
|
9318
10171
|
if (!m || !m[1] || !m[2]) return null;
|
|
@@ -9331,8 +10184,8 @@ function isCompactFresh(compactPath, sourcePath) {
|
|
|
9331
10184
|
}
|
|
9332
10185
|
function markCompactStale(compactPath) {
|
|
9333
10186
|
try {
|
|
9334
|
-
if (!
|
|
9335
|
-
const text =
|
|
10187
|
+
if (!fs20.existsSync(compactPath)) return false;
|
|
10188
|
+
const text = fs20.readFileSync(compactPath, "utf-8");
|
|
9336
10189
|
const lines2 = text.split("\n");
|
|
9337
10190
|
if (!lines2[0]) return false;
|
|
9338
10191
|
const m = lines2[0].match(headerRegex);
|
|
@@ -9347,7 +10200,7 @@ function markCompactStale(compactPath) {
|
|
|
9347
10200
|
}
|
|
9348
10201
|
function readCompactBody(compactPath) {
|
|
9349
10202
|
try {
|
|
9350
|
-
const text =
|
|
10203
|
+
const text = fs20.readFileSync(compactPath, "utf-8");
|
|
9351
10204
|
const lines2 = text.split("\n");
|
|
9352
10205
|
if (lines2.length < 2) return null;
|
|
9353
10206
|
const body = lines2.slice(1).join("\n").trimStart();
|
|
@@ -9357,14 +10210,14 @@ function readCompactBody(compactPath) {
|
|
|
9357
10210
|
}
|
|
9358
10211
|
}
|
|
9359
10212
|
function writeCompact(compactPath, sourcePath, compactBody, sourceRel) {
|
|
9360
|
-
const srcPath =
|
|
10213
|
+
const srcPath = path16.resolve(sourcePath);
|
|
9361
10214
|
const sha = sourceHash(srcPath);
|
|
9362
|
-
const displayRel = sourceRel ||
|
|
10215
|
+
const displayRel = sourceRel || path16.basename(srcPath);
|
|
9363
10216
|
const header = `${headerPrefix}${sha} source:${displayRel} -->
|
|
9364
10217
|
`;
|
|
9365
10218
|
const fullText = header + compactBody.trimStart();
|
|
9366
|
-
const dir =
|
|
9367
|
-
if (!
|
|
10219
|
+
const dir = path16.dirname(compactPath);
|
|
10220
|
+
if (!fs20.existsSync(dir)) {
|
|
9368
10221
|
ensureDirSync(dir);
|
|
9369
10222
|
}
|
|
9370
10223
|
atomicWriteText(compactPath, fullText);
|
|
@@ -9488,7 +10341,7 @@ function extractDocCompact(body, heading) {
|
|
|
9488
10341
|
}
|
|
9489
10342
|
function compactDoc(filePath, heading) {
|
|
9490
10343
|
try {
|
|
9491
|
-
const body =
|
|
10344
|
+
const body = fs20.readFileSync(filePath, "utf-8");
|
|
9492
10345
|
const compact = extractDocCompact(body, heading);
|
|
9493
10346
|
return compact || null;
|
|
9494
10347
|
} catch {
|
|
@@ -9498,21 +10351,21 @@ function compactDoc(filePath, heading) {
|
|
|
9498
10351
|
|
|
9499
10352
|
// src/evidence_cache.ts
|
|
9500
10353
|
import crypto2 from "node:crypto";
|
|
9501
|
-
import
|
|
9502
|
-
import
|
|
10354
|
+
import fs21 from "node:fs";
|
|
10355
|
+
import path17 from "node:path";
|
|
9503
10356
|
var MAX_ENTRIES = 500;
|
|
9504
10357
|
var MAX_TEXT_BYTES = 128 * 1024;
|
|
9505
10358
|
var MAX_SEMANTIC_CANDIDATES = 100;
|
|
9506
10359
|
var CACHE_FILE = "workspace-evidence.json";
|
|
9507
10360
|
function cachePath() {
|
|
9508
|
-
return
|
|
10361
|
+
return path17.join(dataDir(), CACHE_FILE);
|
|
9509
10362
|
}
|
|
9510
10363
|
function hash(text) {
|
|
9511
10364
|
return crypto2.createHash("sha256").update(text).digest("hex");
|
|
9512
10365
|
}
|
|
9513
10366
|
function load2() {
|
|
9514
10367
|
try {
|
|
9515
|
-
const parsed = JSON.parse(
|
|
10368
|
+
const parsed = JSON.parse(fs21.readFileSync(cachePath(), "utf8"));
|
|
9516
10369
|
if (!Array.isArray(parsed)) return [];
|
|
9517
10370
|
return parsed.filter(
|
|
9518
10371
|
(entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string" && typeof entry.projectRoot === "string" && typeof entry.source === "string" && (entry.representation === "file" || entry.representation === "tool-output") && typeof entry.contentHash === "string" && typeof entry.text === "string" && typeof entry.createdAt === "number" && (entry.embedding === void 0 || typeof entry.embedding === "string")
|
|
@@ -9524,7 +10377,7 @@ function load2() {
|
|
|
9524
10377
|
function save(entries) {
|
|
9525
10378
|
try {
|
|
9526
10379
|
ensureDirSync(dataDir());
|
|
9527
|
-
|
|
10380
|
+
fs21.writeFileSync(cachePath(), JSON.stringify(entries.slice(0, MAX_ENTRIES)), "utf8");
|
|
9528
10381
|
} catch {
|
|
9529
10382
|
}
|
|
9530
10383
|
}
|
|
@@ -9615,7 +10468,7 @@ function buildDeltaCapsule(projectRoot, limit = 8) {
|
|
|
9615
10468
|
const root = normalizePath(projectRoot);
|
|
9616
10469
|
const changed = load2().filter((entry) => entry.projectRoot === root && entry.representation === "file").filter((entry) => {
|
|
9617
10470
|
try {
|
|
9618
|
-
return hash(
|
|
10471
|
+
return hash(fs21.readFileSync(entry.source, "utf8")) !== entry.contentHash;
|
|
9619
10472
|
} catch {
|
|
9620
10473
|
return true;
|
|
9621
10474
|
}
|
|
@@ -9626,8 +10479,8 @@ ${changed.map((entry) => `- ${entry.source} (use a fresh surgical read)`).join("
|
|
|
9626
10479
|
}
|
|
9627
10480
|
|
|
9628
10481
|
// src/notebook_compact.ts
|
|
9629
|
-
import * as
|
|
9630
|
-
import * as
|
|
10482
|
+
import * as fs22 from "node:fs";
|
|
10483
|
+
import * as path18 from "node:path";
|
|
9631
10484
|
var NB_STRIP_MIN_SAVINGS = 4096;
|
|
9632
10485
|
function stripNotebook(nbDict) {
|
|
9633
10486
|
const cells = [];
|
|
@@ -9647,23 +10500,23 @@ function stripNotebook(nbDict) {
|
|
|
9647
10500
|
var SIDECAR_DEFAULT_MAX_COUNT = 200;
|
|
9648
10501
|
var SIDECAR_DEFAULT_MAX_AGE_MS = 24 * 3600 * 1e3;
|
|
9649
10502
|
function pruneSidecars(cacheRoot, maxCount = SIDECAR_DEFAULT_MAX_COUNT, maxAgeMs = SIDECAR_DEFAULT_MAX_AGE_MS) {
|
|
9650
|
-
const nbStripDir =
|
|
10503
|
+
const nbStripDir = path18.join(cacheRoot, "nb_strip");
|
|
9651
10504
|
let removed = 0;
|
|
9652
10505
|
try {
|
|
9653
|
-
if (!
|
|
10506
|
+
if (!fs22.existsSync(nbStripDir)) return 0;
|
|
9654
10507
|
const cutoff = Date.now() - maxAgeMs;
|
|
9655
10508
|
const kept = [];
|
|
9656
|
-
for (const entry of
|
|
9657
|
-
const dir =
|
|
10509
|
+
for (const entry of fs22.readdirSync(nbStripDir)) {
|
|
10510
|
+
const dir = path18.join(nbStripDir, entry);
|
|
9658
10511
|
let mtime;
|
|
9659
10512
|
try {
|
|
9660
|
-
mtime =
|
|
10513
|
+
mtime = fs22.statSync(dir).mtimeMs;
|
|
9661
10514
|
} catch {
|
|
9662
10515
|
continue;
|
|
9663
10516
|
}
|
|
9664
10517
|
if (mtime < cutoff) {
|
|
9665
10518
|
try {
|
|
9666
|
-
|
|
10519
|
+
fs22.rmSync(dir, { recursive: true, force: true });
|
|
9667
10520
|
removed++;
|
|
9668
10521
|
} catch {
|
|
9669
10522
|
continue;
|
|
@@ -9676,7 +10529,7 @@ function pruneSidecars(cacheRoot, maxCount = SIDECAR_DEFAULT_MAX_COUNT, maxAgeMs
|
|
|
9676
10529
|
kept.sort((a, b) => a[1] - b[1]);
|
|
9677
10530
|
for (const [dir] of kept.slice(0, kept.length - maxCount)) {
|
|
9678
10531
|
try {
|
|
9679
|
-
|
|
10532
|
+
fs22.rmSync(dir, { recursive: true, force: true });
|
|
9680
10533
|
removed++;
|
|
9681
10534
|
} catch {
|
|
9682
10535
|
continue;
|
|
@@ -9690,9 +10543,9 @@ function pruneSidecars(cacheRoot, maxCount = SIDECAR_DEFAULT_MAX_COUNT, maxAgeMs
|
|
|
9690
10543
|
}
|
|
9691
10544
|
function getOrCreateSidecar(rawBytes, cacheRoot, opts = {}) {
|
|
9692
10545
|
const sha = fingerprintContent(rawBytes);
|
|
9693
|
-
const sidecarDir =
|
|
9694
|
-
const sidecarPath =
|
|
9695
|
-
if (
|
|
10546
|
+
const sidecarDir = path18.join(cacheRoot, "nb_strip", sha);
|
|
10547
|
+
const sidecarPath = path18.join(sidecarDir, "stripped.ipynb");
|
|
10548
|
+
if (fs22.existsSync(sidecarPath)) {
|
|
9696
10549
|
return [sidecarPath, false];
|
|
9697
10550
|
}
|
|
9698
10551
|
let nb;
|
|
@@ -9709,7 +10562,7 @@ function getOrCreateSidecar(rawBytes, cacheRoot, opts = {}) {
|
|
|
9709
10562
|
try {
|
|
9710
10563
|
ensureDirSync(sidecarDir);
|
|
9711
10564
|
} catch (err) {
|
|
9712
|
-
if (!
|
|
10565
|
+
if (!fs22.existsSync(sidecarDir)) {
|
|
9713
10566
|
throw err;
|
|
9714
10567
|
}
|
|
9715
10568
|
}
|
|
@@ -9742,8 +10595,8 @@ function isNodeModulesPath(p) {
|
|
|
9742
10595
|
return check.includes("/node_modules/") || check.includes("\\node_modules\\");
|
|
9743
10596
|
}
|
|
9744
10597
|
function relPathWithinRoot(root, target) {
|
|
9745
|
-
const rel =
|
|
9746
|
-
if (rel.startsWith("..") ||
|
|
10598
|
+
const rel = path19.relative(root, target).replace(/\\/g, "/");
|
|
10599
|
+
if (rel.startsWith("..") || path19.isAbsolute(rel)) return null;
|
|
9747
10600
|
return rel;
|
|
9748
10601
|
}
|
|
9749
10602
|
function _isDocFile(filePath) {
|
|
@@ -9774,7 +10627,7 @@ function scanRequestedSlice(absPath, offset, limit) {
|
|
|
9774
10627
|
const windowEnd = offset + limit;
|
|
9775
10628
|
let fd;
|
|
9776
10629
|
try {
|
|
9777
|
-
fd =
|
|
10630
|
+
fd = fs23.openSync(absPath, "r");
|
|
9778
10631
|
} catch {
|
|
9779
10632
|
return null;
|
|
9780
10633
|
}
|
|
@@ -9788,7 +10641,7 @@ function scanRequestedSlice(absPath, offset, limit) {
|
|
|
9788
10641
|
const nearSingleLine = lineNumber < NEAR_SINGLE_LINE_SCAN_THRESHOLD;
|
|
9789
10642
|
return { bytes: sliceBytes, trustworthy: nearSingleLine, nearSingleLine };
|
|
9790
10643
|
}
|
|
9791
|
-
const bytesRead =
|
|
10644
|
+
const bytesRead = fs23.readSync(fd, buf, 0, buf.length, null);
|
|
9792
10645
|
if (bytesRead === 0) {
|
|
9793
10646
|
return {
|
|
9794
10647
|
bytes: sliceBytes,
|
|
@@ -9807,7 +10660,7 @@ function scanRequestedSlice(absPath, offset, limit) {
|
|
|
9807
10660
|
}
|
|
9808
10661
|
} finally {
|
|
9809
10662
|
try {
|
|
9810
|
-
|
|
10663
|
+
fs23.closeSync(fd);
|
|
9811
10664
|
} catch {
|
|
9812
10665
|
}
|
|
9813
10666
|
}
|
|
@@ -9844,7 +10697,7 @@ var BINARY_FILE_TYPE_EXTS = /* @__PURE__ */ new Set(["pdf", "docx", "xlsx", "ppt
|
|
|
9844
10697
|
var TEXT_FILE_TYPE_EXTS = /* @__PURE__ */ new Set(["html", "htm", "xhtml", "txt", "log", "out", "err", "trace", "csv", "tsv", "vtt", "srt"]);
|
|
9845
10698
|
var DISPATCHED_FILE_TYPE_EXTS = /* @__PURE__ */ new Set([...BINARY_FILE_TYPE_EXTS, ...TEXT_FILE_TYPE_EXTS]);
|
|
9846
10699
|
function isDispatchedFileType(basename12) {
|
|
9847
|
-
return DISPATCHED_FILE_TYPE_EXTS.has(
|
|
10700
|
+
return DISPATCHED_FILE_TYPE_EXTS.has(path19.extname(basename12).slice(1).toLowerCase());
|
|
9848
10701
|
}
|
|
9849
10702
|
function surgicalHint(filePath, basename12, lineCount) {
|
|
9850
10703
|
if (lineCount < loadConfig().hints.min_file_lines_for_hint) return "";
|
|
@@ -9907,7 +10760,7 @@ function loadSnapshotDiff(sessionId, normalized, basename12) {
|
|
|
9907
10760
|
try {
|
|
9908
10761
|
const sz = statSize(normalized);
|
|
9909
10762
|
if (sz === null || sz > 256 * 1024) return { kind: "none" };
|
|
9910
|
-
const currentContent =
|
|
10763
|
+
const currentContent = fs23.readFileSync(normalized, "utf8");
|
|
9911
10764
|
const TRUNC_MARKER = "\n<snapshot truncated at ";
|
|
9912
10765
|
const oldRaw = oldSnap.toString("utf8");
|
|
9913
10766
|
const truncIdx = oldRaw.indexOf(TRUNC_MARKER);
|
|
@@ -9993,7 +10846,7 @@ function preReadHandlerInner(event) {
|
|
|
9993
10846
|
try {
|
|
9994
10847
|
const cwd = getCwd(event) ?? process.cwd();
|
|
9995
10848
|
const project = findProject(cwd) ?? makeProjectAt(cwd);
|
|
9996
|
-
const current =
|
|
10849
|
+
const current = fs23.readFileSync(normalized, "utf8");
|
|
9997
10850
|
const evidence = findVerifiedFileEvidence(project.root, normalized, current);
|
|
9998
10851
|
if (evidence !== null) {
|
|
9999
10852
|
recordStat("evidence_cache_hit", 0);
|
|
@@ -10004,7 +10857,7 @@ function preReadHandlerInner(event) {
|
|
|
10004
10857
|
} catch {
|
|
10005
10858
|
}
|
|
10006
10859
|
}
|
|
10007
|
-
const basename12 =
|
|
10860
|
+
const basename12 = path19.basename(normalized);
|
|
10008
10861
|
if (isLockFile(basename12)) {
|
|
10009
10862
|
return denyOutput(
|
|
10010
10863
|
'Lock files are rarely useful to read in full. Use `token-goat section "' + shown + '::<section>"` to extract a specific dependency, or read the relevant manifest instead.'
|
|
@@ -10043,7 +10896,7 @@ function preReadHandlerInner(event) {
|
|
|
10043
10896
|
const skillName = detectSkillFile(normalized);
|
|
10044
10897
|
if (skillName && basename12 === "SKILL.md") {
|
|
10045
10898
|
try {
|
|
10046
|
-
const body =
|
|
10899
|
+
const body = fs23.readFileSync(normalized, "utf-8");
|
|
10047
10900
|
const bodySha = contentHash(body);
|
|
10048
10901
|
const compact = getCompactAnySessionSync(skillName);
|
|
10049
10902
|
const stale = isCompactStale(compact, skillName, bodySha);
|
|
@@ -10074,9 +10927,9 @@ function preReadHandlerInner(event) {
|
|
|
10074
10927
|
const isNotebook = /\.ipynb$/i.test(basename12);
|
|
10075
10928
|
if (event.toolName !== "Grep" && isNotebook) {
|
|
10076
10929
|
try {
|
|
10077
|
-
const rawBytes =
|
|
10930
|
+
const rawBytes = fs23.readFileSync(normalized);
|
|
10078
10931
|
const [sidecarPath] = getOrCreateSidecar(rawBytes, dataDir());
|
|
10079
|
-
const sidecarContent =
|
|
10932
|
+
const sidecarContent = fs23.readFileSync(sidecarPath, "utf-8");
|
|
10080
10933
|
const savedBytes = rawBytes.length - sidecarContent.length;
|
|
10081
10934
|
if (savedBytes >= NB_STRIP_MIN_SAVINGS) {
|
|
10082
10935
|
recordActualRead(event, normalized);
|
|
@@ -10096,7 +10949,7 @@ function preReadHandlerInner(event) {
|
|
|
10096
10949
|
const sz = statSize(normalized);
|
|
10097
10950
|
if (sz !== null && sz >= MARKDOWN_SIZE_THRESHOLD) {
|
|
10098
10951
|
markdownSize = sz;
|
|
10099
|
-
fileContent =
|
|
10952
|
+
fileContent = fs23.readFileSync(normalized, "utf8");
|
|
10100
10953
|
}
|
|
10101
10954
|
} catch {
|
|
10102
10955
|
}
|
|
@@ -10325,7 +11178,7 @@ function preReadHandlerInner(event) {
|
|
|
10325
11178
|
"Note: " + shown + " is large (" + kb + "KB). " + hint + contextPressureAdvisorySuffix()
|
|
10326
11179
|
);
|
|
10327
11180
|
}
|
|
10328
|
-
const fileTypeExt =
|
|
11181
|
+
const fileTypeExt = path19.extname(normalized).slice(1).toLowerCase();
|
|
10329
11182
|
const fileStatSize = size ?? statSize(normalized) ?? 0;
|
|
10330
11183
|
const isKnownFileType = DISPATCHED_FILE_TYPE_EXTS.has(fileTypeExt);
|
|
10331
11184
|
if (event.toolName !== "Grep" && !isImagePath(normalized) && (isKnownFileType || fileStatSize >= FILE_TYPE_THRESHOLDS.generic)) {
|
|
@@ -10334,7 +11187,7 @@ function preReadHandlerInner(event) {
|
|
|
10334
11187
|
let ftContent = "";
|
|
10335
11188
|
if (!BINARY_FILE_TYPE_EXTS.has(fileTypeExt) && fileStatSize <= SLICE_ESTIMATE_SCAN_CAP_BYTES) {
|
|
10336
11189
|
try {
|
|
10337
|
-
ftContent =
|
|
11190
|
+
ftContent = fs23.readFileSync(normalized, "utf8");
|
|
10338
11191
|
} catch {
|
|
10339
11192
|
}
|
|
10340
11193
|
}
|
|
@@ -10364,7 +11217,7 @@ function estimateTruncatedLineCount(normalized) {
|
|
|
10364
11217
|
try {
|
|
10365
11218
|
const sz = statSize(normalized);
|
|
10366
11219
|
if (sz !== null && sz <= SLICE_ESTIMATE_SCAN_CAP_BYTES) {
|
|
10367
|
-
return countTextLines(
|
|
11220
|
+
return countTextLines(fs23.readFileSync(normalized, "utf8"));
|
|
10368
11221
|
}
|
|
10369
11222
|
} catch {
|
|
10370
11223
|
}
|
|
@@ -10387,13 +11240,13 @@ function postReadHandlerInner(event) {
|
|
|
10387
11240
|
if (respText.includes("[Truncated:") || respText.includes("Truncated: PARTIAL view")) {
|
|
10388
11241
|
markFileTruncated(normalized);
|
|
10389
11242
|
}
|
|
10390
|
-
const postBasename =
|
|
11243
|
+
const postBasename = path19.basename(normalized);
|
|
10391
11244
|
const diffSourcesEnabled = loadConfig().hints.serve_diff_on_reread;
|
|
10392
11245
|
if (/\.(md|mdx|markdown|rst|txt)$/i.test(postBasename) || isSessionArtifactFile(normalized) || diffSourcesEnabled && DIFFABLE_SOURCE_RE.test(postBasename)) {
|
|
10393
11246
|
try {
|
|
10394
11247
|
const sz = statSize(normalized);
|
|
10395
11248
|
if (sz !== null && sz <= 256 * 1024) {
|
|
10396
|
-
const content =
|
|
11249
|
+
const content = fs23.readFileSync(normalized);
|
|
10397
11250
|
store(getSessionId(), normalized, content);
|
|
10398
11251
|
}
|
|
10399
11252
|
} catch {
|
|
@@ -10403,7 +11256,7 @@ function postReadHandlerInner(event) {
|
|
|
10403
11256
|
try {
|
|
10404
11257
|
const cwd = getCwd(event) ?? process.cwd();
|
|
10405
11258
|
const project = findProject(cwd) ?? makeProjectAt(cwd);
|
|
10406
|
-
const source = decodeSource(
|
|
11259
|
+
const source = decodeSource(fs23.readFileSync(normalized));
|
|
10407
11260
|
recordEvidence({ projectRoot: project.root, source: normalized, representation: "file", text: source });
|
|
10408
11261
|
} catch {
|
|
10409
11262
|
}
|
|
@@ -10434,7 +11287,7 @@ function postReadHandlerInner(event) {
|
|
|
10434
11287
|
try {
|
|
10435
11288
|
const sz = statSize(normalized);
|
|
10436
11289
|
if (sz !== null && sz <= SLICE_ESTIMATE_SCAN_CAP_BYTES) {
|
|
10437
|
-
const lineCount = countTextLines(
|
|
11290
|
+
const lineCount = countTextLines(fs23.readFileSync(normalized, "utf8"));
|
|
10438
11291
|
const minLines = loadConfig().post_read_code_compress.min_lines;
|
|
10439
11292
|
if (lineCount >= minLines && meetsSavingsFloor(sz)) {
|
|
10440
11293
|
recordStat("session_hint", 0, 0);
|
|
@@ -10454,9 +11307,9 @@ function postReadHandler(event) {
|
|
|
10454
11307
|
registerHook("post_tool_use", postReadHandler, { toolName: "Read" });
|
|
10455
11308
|
|
|
10456
11309
|
// src/cli_context_stats.ts
|
|
10457
|
-
import * as
|
|
11310
|
+
import * as fs25 from "node:fs";
|
|
10458
11311
|
import * as os8 from "node:os";
|
|
10459
|
-
import * as
|
|
11312
|
+
import * as path21 from "node:path";
|
|
10460
11313
|
|
|
10461
11314
|
// src/confirm_apply.ts
|
|
10462
11315
|
import * as readline from "node:readline";
|
|
@@ -10510,8 +11363,8 @@ ${diff}
|
|
|
10510
11363
|
}
|
|
10511
11364
|
|
|
10512
11365
|
// src/memory_prune.ts
|
|
10513
|
-
import * as
|
|
10514
|
-
import * as
|
|
11366
|
+
import * as fs24 from "node:fs";
|
|
11367
|
+
import * as path20 from "node:path";
|
|
10515
11368
|
var ENTRY_RE = /^\s*-\s*\[(?<title>[^\]]+)\]\((?<target>[^)]+?\.md)\)/;
|
|
10516
11369
|
var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
10517
11370
|
function parseIndex(text) {
|
|
@@ -10556,10 +11409,10 @@ function pruneIndex(memoryDir, opts) {
|
|
|
10556
11409
|
changed: false,
|
|
10557
11410
|
tokensSaved: 0
|
|
10558
11411
|
};
|
|
10559
|
-
const memoryMd =
|
|
11412
|
+
const memoryMd = path20.join(memoryDir, "MEMORY.md");
|
|
10560
11413
|
let text;
|
|
10561
11414
|
try {
|
|
10562
|
-
text =
|
|
11415
|
+
text = fs24.readFileSync(memoryMd, "utf-8");
|
|
10563
11416
|
} catch {
|
|
10564
11417
|
return result;
|
|
10565
11418
|
}
|
|
@@ -10570,7 +11423,7 @@ function pruneIndex(memoryDir, opts) {
|
|
|
10570
11423
|
const dups = [];
|
|
10571
11424
|
for (const entry of entries) {
|
|
10572
11425
|
const isUrl = URL_SCHEME_RE.test(entry.target);
|
|
10573
|
-
const targetExists = isUrl ? true :
|
|
11426
|
+
const targetExists = isUrl ? true : path20.isAbsolute(entry.target) ? fs24.existsSync(entry.target) : fs24.existsSync(path20.join(memoryDir, entry.target));
|
|
10574
11427
|
const foldedTarget = foldPath(entry.target);
|
|
10575
11428
|
if (!targetExists) {
|
|
10576
11429
|
dead.push(entry);
|
|
@@ -10624,7 +11477,7 @@ function jaccard(a, b) {
|
|
|
10624
11477
|
function siblingSnippet(filePath) {
|
|
10625
11478
|
let text;
|
|
10626
11479
|
try {
|
|
10627
|
-
text =
|
|
11480
|
+
text = fs24.readFileSync(filePath, { encoding: "utf-8" });
|
|
10628
11481
|
} catch {
|
|
10629
11482
|
return "";
|
|
10630
11483
|
}
|
|
@@ -10714,7 +11567,7 @@ async function tryEmbeddingClusters(siblings, snippets, threshold) {
|
|
|
10714
11567
|
}
|
|
10715
11568
|
async function findContentDuplicates(memoryDir, _opts) {
|
|
10716
11569
|
const threshold = _opts?.threshold ?? 0.92;
|
|
10717
|
-
const siblings =
|
|
11570
|
+
const siblings = fs24.readdirSync(memoryDir).filter((name) => name.toLowerCase().endsWith(".md") && name.toLowerCase() !== "memory.md").map((name) => path20.join(memoryDir, name)).sort();
|
|
10718
11571
|
if (siblings.length < 2) {
|
|
10719
11572
|
return [];
|
|
10720
11573
|
}
|
|
@@ -10738,7 +11591,7 @@ function auditClaudeMd(files) {
|
|
|
10738
11591
|
for (const filePath of files) {
|
|
10739
11592
|
let text;
|
|
10740
11593
|
try {
|
|
10741
|
-
text =
|
|
11594
|
+
text = fs24.readFileSync(filePath, { encoding: "utf-8" });
|
|
10742
11595
|
} catch {
|
|
10743
11596
|
continue;
|
|
10744
11597
|
}
|
|
@@ -10798,7 +11651,7 @@ function auditClaudeMd(files) {
|
|
|
10798
11651
|
const overlaps = [];
|
|
10799
11652
|
for (const [stripped, filesSet] of lineToFiles) {
|
|
10800
11653
|
if (filesSet.has(report.path) && filesSet.size > 1) {
|
|
10801
|
-
const others = Array.from(filesSet).filter((p) => p !== report.path).map((p) =>
|
|
11654
|
+
const others = Array.from(filesSet).filter((p) => p !== report.path).map((p) => path20.basename(p));
|
|
10802
11655
|
if (others.length > 0) {
|
|
10803
11656
|
if (stripped.length > 60) {
|
|
10804
11657
|
overlaps.push(
|
|
@@ -10818,7 +11671,7 @@ function auditClaudeMd(files) {
|
|
|
10818
11671
|
// src/cli_context_stats.ts
|
|
10819
11672
|
function tok(filePath) {
|
|
10820
11673
|
try {
|
|
10821
|
-
const size =
|
|
11674
|
+
const size = fs25.statSync(filePath).size;
|
|
10822
11675
|
return Math.floor(size / 4);
|
|
10823
11676
|
} catch {
|
|
10824
11677
|
return 0;
|
|
@@ -10827,42 +11680,42 @@ function tok(filePath) {
|
|
|
10827
11680
|
function findClaudeMdFiles(projectRoot, homeDir = os8.homedir()) {
|
|
10828
11681
|
const found = [];
|
|
10829
11682
|
const seen = /* @__PURE__ */ new Set();
|
|
10830
|
-
let current =
|
|
11683
|
+
let current = path21.resolve(projectRoot);
|
|
10831
11684
|
while (true) {
|
|
10832
|
-
const candidate =
|
|
10833
|
-
if (!seen.has(candidate) &&
|
|
11685
|
+
const candidate = path21.join(current, "CLAUDE.md");
|
|
11686
|
+
if (!seen.has(candidate) && fs25.existsSync(candidate)) {
|
|
10834
11687
|
found.push(candidate);
|
|
10835
11688
|
seen.add(candidate);
|
|
10836
11689
|
}
|
|
10837
|
-
const parent =
|
|
11690
|
+
const parent = path21.dirname(current);
|
|
10838
11691
|
if (parent === current) break;
|
|
10839
11692
|
current = parent;
|
|
10840
11693
|
}
|
|
10841
|
-
const globalMd =
|
|
10842
|
-
if (!seen.has(globalMd) &&
|
|
11694
|
+
const globalMd = path21.join(homeDir, ".claude", "CLAUDE.md");
|
|
11695
|
+
if (!seen.has(globalMd) && fs25.existsSync(globalMd)) {
|
|
10843
11696
|
found.push(globalMd);
|
|
10844
11697
|
}
|
|
10845
11698
|
return found;
|
|
10846
11699
|
}
|
|
10847
11700
|
function findMemoryMd(projectRoot, homeDir = os8.homedir(), alternateRoots = []) {
|
|
10848
11701
|
try {
|
|
10849
|
-
const projectsDir =
|
|
10850
|
-
if (!
|
|
10851
|
-
const rootStr =
|
|
11702
|
+
const projectsDir = path21.join(homeDir, ".claude", "projects");
|
|
11703
|
+
if (!fs25.existsSync(projectsDir)) return null;
|
|
11704
|
+
const rootStr = path21.resolve(projectRoot);
|
|
10852
11705
|
const candidateRoots = [rootStr];
|
|
10853
11706
|
try {
|
|
10854
|
-
const realRoot =
|
|
11707
|
+
const realRoot = fs25.realpathSync.native(rootStr);
|
|
10855
11708
|
if (realRoot !== rootStr) candidateRoots.push(realRoot);
|
|
10856
11709
|
} catch {
|
|
10857
11710
|
}
|
|
10858
11711
|
for (const alternate of alternateRoots) {
|
|
10859
|
-
const resolved =
|
|
11712
|
+
const resolved = path21.resolve(alternate);
|
|
10860
11713
|
if (!candidateRoots.includes(resolved)) candidateRoots.push(resolved);
|
|
10861
11714
|
}
|
|
10862
11715
|
for (const root of candidateRoots) {
|
|
10863
11716
|
const expectedSlug = root.replace(/[^A-Za-z0-9]/g, "-");
|
|
10864
|
-
const candidate =
|
|
10865
|
-
if (
|
|
11717
|
+
const candidate = path21.join(projectsDir, expectedSlug, "memory", "MEMORY.md");
|
|
11718
|
+
if (fs25.existsSync(candidate)) return candidate;
|
|
10866
11719
|
}
|
|
10867
11720
|
return null;
|
|
10868
11721
|
} catch {
|
|
@@ -10876,10 +11729,10 @@ function buildStats(projectRoot, homeDir = os8.homedir(), alternateRoots = []) {
|
|
|
10876
11729
|
for (const p of claudeMds) {
|
|
10877
11730
|
const t = tok(p);
|
|
10878
11731
|
claudeMdTotal += t;
|
|
10879
|
-
const parentDir =
|
|
11732
|
+
const parentDir = path21.basename(path21.dirname(p));
|
|
10880
11733
|
const label = parentDir === ".claude" ? "~/.claude/CLAUDE.md" : (() => {
|
|
10881
11734
|
try {
|
|
10882
|
-
return
|
|
11735
|
+
return path21.relative(projectRoot, p);
|
|
10883
11736
|
} catch {
|
|
10884
11737
|
return p;
|
|
10885
11738
|
}
|
|
@@ -10943,11 +11796,11 @@ async function runContextStats(opts = {}) {
|
|
|
10943
11796
|
process.stdout.write("[--fix] No MEMORY.md found; nothing to prune.\n");
|
|
10944
11797
|
} else {
|
|
10945
11798
|
const memPath = result.memory_md_path;
|
|
10946
|
-
const pruneResult = pruneIndex(
|
|
11799
|
+
const pruneResult = pruneIndex(path21.dirname(memPath), { dryRun: true });
|
|
10947
11800
|
if (!pruneResult.changed || pruneResult.after === void 0) {
|
|
10948
11801
|
process.stdout.write("[--fix] MEMORY.md already clean; nothing to prune.\n");
|
|
10949
11802
|
} else {
|
|
10950
|
-
const before =
|
|
11803
|
+
const before = fs25.readFileSync(memPath, "utf-8");
|
|
10951
11804
|
const applyResult = await confirmAndApply(
|
|
10952
11805
|
[{ path: memPath, before, after: pruneResult.after, label: "MEMORY.md" }],
|
|
10953
11806
|
opts.yes === true ? { yes: true } : {}
|
|
@@ -10974,8 +11827,8 @@ async function runContextStats(opts = {}) {
|
|
|
10974
11827
|
}
|
|
10975
11828
|
|
|
10976
11829
|
// src/baseline.ts
|
|
10977
|
-
import * as
|
|
10978
|
-
import * as
|
|
11830
|
+
import * as fs26 from "node:fs";
|
|
11831
|
+
import * as path22 from "node:path";
|
|
10979
11832
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10980
11833
|
"node_modules",
|
|
10981
11834
|
".git",
|
|
@@ -11013,12 +11866,12 @@ function walkProject(rootDir, opts = {}) {
|
|
|
11013
11866
|
if (dir === void 0) break;
|
|
11014
11867
|
let entries;
|
|
11015
11868
|
try {
|
|
11016
|
-
entries =
|
|
11869
|
+
entries = fs26.readdirSync(dir, { withFileTypes: true });
|
|
11017
11870
|
} catch {
|
|
11018
11871
|
continue;
|
|
11019
11872
|
}
|
|
11020
11873
|
for (const entry of entries) {
|
|
11021
|
-
const full =
|
|
11874
|
+
const full = path22.join(dir, entry.name);
|
|
11022
11875
|
if (entry.isDirectory()) {
|
|
11023
11876
|
if (SKIP_DIRS.has(entry.name) || extraSkipDirs.includes(entry.name)) continue;
|
|
11024
11877
|
if (entry.name.startsWith(".") && entry.name !== ".") {
|
|
@@ -11093,7 +11946,7 @@ function fetchTopSymbols(limit, dbPath, rootDir) {
|
|
|
11093
11946
|
}
|
|
11094
11947
|
}
|
|
11095
11948
|
function buildProjectMap(rootDir = process.cwd(), opts = {}) {
|
|
11096
|
-
const root =
|
|
11949
|
+
const root = path22.resolve(rootDir);
|
|
11097
11950
|
const config = loadConfig();
|
|
11098
11951
|
const { files, languages } = walkProject(root, { excludeTests: config.repomap.exclude_tests });
|
|
11099
11952
|
const compact = opts.compact === true || files.length > config.repomap.compact_file_threshold;
|
|
@@ -11102,12 +11955,12 @@ function buildProjectMap(rootDir = process.cwd(), opts = {}) {
|
|
|
11102
11955
|
const recentFiles = files.map((f) => {
|
|
11103
11956
|
let mtime;
|
|
11104
11957
|
try {
|
|
11105
|
-
mtime =
|
|
11958
|
+
mtime = fs26.statSync(f).mtimeMs;
|
|
11106
11959
|
} catch {
|
|
11107
11960
|
mtime = 0;
|
|
11108
11961
|
}
|
|
11109
11962
|
return { f, mtime };
|
|
11110
|
-
}).sort((a, b) => b.mtime - a.mtime).slice(0, compact ? 5 : 15).map((x) =>
|
|
11963
|
+
}).sort((a, b) => b.mtime - a.mtime).slice(0, compact ? 5 : 15).map((x) => path22.relative(root, x.f));
|
|
11111
11964
|
return {
|
|
11112
11965
|
rootDir: root,
|
|
11113
11966
|
fileCount: files.length,
|
|
@@ -11119,7 +11972,7 @@ function buildProjectMap(rootDir = process.cwd(), opts = {}) {
|
|
|
11119
11972
|
}
|
|
11120
11973
|
function formatProjectMap(map, compact = false) {
|
|
11121
11974
|
const lines2 = [];
|
|
11122
|
-
const rel =
|
|
11975
|
+
const rel = path22.basename(map.rootDir);
|
|
11123
11976
|
lines2.push(`# Project map: ${rel}`);
|
|
11124
11977
|
lines2.push(`Files: ${map.fileCount}`);
|
|
11125
11978
|
const langPairs = Object.entries(map.languages).sort((a, b) => b[1] - a[1]);
|
|
@@ -11147,13 +12000,13 @@ function formatProjectMap(map, compact = false) {
|
|
|
11147
12000
|
}
|
|
11148
12001
|
function mapLookupBytesSaved(map, emittedText) {
|
|
11149
12002
|
const referencedFiles = /* @__PURE__ */ new Set([
|
|
11150
|
-
...map.recentFiles.map((f) => normalizePath(
|
|
12003
|
+
...map.recentFiles.map((f) => normalizePath(path22.resolve(map.rootDir, f))),
|
|
11151
12004
|
...map.topSymbols.map((s) => normalizePath(s.filePath))
|
|
11152
12005
|
]);
|
|
11153
12006
|
let fullSourceBytes = 0;
|
|
11154
12007
|
for (const fp of referencedFiles) {
|
|
11155
12008
|
try {
|
|
11156
|
-
fullSourceBytes +=
|
|
12009
|
+
fullSourceBytes += fs26.statSync(fp).size;
|
|
11157
12010
|
} catch {
|
|
11158
12011
|
}
|
|
11159
12012
|
}
|
|
@@ -11167,14 +12020,14 @@ function findMemSuggestionCandidates(projectRoot) {
|
|
|
11167
12020
|
const claudeMdFiles = findClaudeMdFiles(projectRoot);
|
|
11168
12021
|
const candidateFiles = new Set(claudeMdFiles);
|
|
11169
12022
|
for (const claudeMd of claudeMdFiles) {
|
|
11170
|
-
const agentsMd =
|
|
11171
|
-
if (
|
|
12023
|
+
const agentsMd = path22.join(path22.dirname(claudeMd), "AGENTS.md");
|
|
12024
|
+
if (fs26.existsSync(agentsMd)) candidateFiles.add(agentsMd);
|
|
11172
12025
|
}
|
|
11173
12026
|
const suggestions = [];
|
|
11174
12027
|
for (const filePath of candidateFiles) {
|
|
11175
12028
|
let text;
|
|
11176
12029
|
try {
|
|
11177
|
-
text =
|
|
12030
|
+
text = fs26.readFileSync(filePath, { encoding: "utf-8" });
|
|
11178
12031
|
} catch {
|
|
11179
12032
|
continue;
|
|
11180
12033
|
}
|
|
@@ -11203,7 +12056,7 @@ function formatMemSuggestions(projectRoot) {
|
|
|
11203
12056
|
if (suggestions.length === 0) return "";
|
|
11204
12057
|
const lines2 = ["", "## mem suggestions"];
|
|
11205
12058
|
for (const s of suggestions) {
|
|
11206
|
-
const basename12 =
|
|
12059
|
+
const basename12 = path22.basename(s.path);
|
|
11207
12060
|
lines2.push(
|
|
11208
12061
|
"Consider: mem import --from-md " + s.path + " # migrates " + s.count + " preference-shaped lines from " + basename12 + " as pending facts for review"
|
|
11209
12062
|
);
|
|
@@ -11343,9 +12196,9 @@ function getFileEntry(filePath, dbPath = globalDbPath()) {
|
|
|
11343
12196
|
embedSha: row.embed_sha ?? ""
|
|
11344
12197
|
};
|
|
11345
12198
|
}
|
|
11346
|
-
function sanitizeFtsQuery(query,
|
|
12199
|
+
function sanitizeFtsQuery(query, join23 = "AND") {
|
|
11347
12200
|
const terms = query.split(/\s+/).filter((t) => t.length > 0).map((t) => '"' + t.replace(/"/g, '""') + '"');
|
|
11348
|
-
return terms.join(
|
|
12201
|
+
return terms.join(join23 === "OR" ? " OR " : " ");
|
|
11349
12202
|
}
|
|
11350
12203
|
function runFtsQuery(db, match, limit, scope, rootDir) {
|
|
11351
12204
|
const sql = `SELECT s.file_path, s.name, s.kind, s.line_start, s.line_end, s.body, s.docstring, s.parent FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE symbols_fts MATCH ?${scope !== void 0 ? ` AND ${scope.clause}` : ""} ORDER BY bm25(symbols_fts) LIMIT ?`;
|
|
@@ -11374,9 +12227,9 @@ function searchSymbolsFts(query, limit = 50, dbPath = globalDbPath(), rootDir) {
|
|
|
11374
12227
|
}
|
|
11375
12228
|
|
|
11376
12229
|
// src/parser.ts
|
|
11377
|
-
import * as
|
|
12230
|
+
import * as fs27 from "node:fs";
|
|
11378
12231
|
import { createRequire as createRequire4 } from "node:module";
|
|
11379
|
-
import * as
|
|
12232
|
+
import * as path27 from "node:path";
|
|
11380
12233
|
|
|
11381
12234
|
// src/languages/csharp.ts
|
|
11382
12235
|
var USING_RE = /^(?:global\s+)?using\s+(?:static\s+)?([A-Za-z_][A-Za-z0-9_.]*)\s*(?:=\s*([A-Za-z_][A-Za-z0-9_.<>,\s]*))?\s*;/;
|
|
@@ -11809,7 +12662,7 @@ function extractHtml(content, filePath) {
|
|
|
11809
12662
|
}
|
|
11810
12663
|
|
|
11811
12664
|
// src/languages/liquid.ts
|
|
11812
|
-
import * as
|
|
12665
|
+
import * as path23 from "node:path";
|
|
11813
12666
|
var INCLUDE_RE = /{%-?\s*include\s+(['"])((?:(?!\1)[\s\S])+?)\1/gi;
|
|
11814
12667
|
var SECTION_RE = /{%-?\s*section\s+(['"])((?:(?!\1)[\s\S])+?)\1/gi;
|
|
11815
12668
|
var RENDER_RE = /{%-?\s*render\s+(['"])((?:(?!\1)[\s\S])+?)\1/gi;
|
|
@@ -11852,7 +12705,7 @@ function extractLiquid(content, filePath, relPath) {
|
|
|
11852
12705
|
const resolvedRel = relPath ?? filePath;
|
|
11853
12706
|
const relPosix = resolvedRel.replace(/\\/g, "/");
|
|
11854
12707
|
if (relPosix.startsWith("sections/") || relPosix.includes("/sections/")) {
|
|
11855
|
-
const stem =
|
|
12708
|
+
const stem = path23.basename(resolvedRel, path23.extname(resolvedRel));
|
|
11856
12709
|
symbols.push({ filePath, name: stem, kind: "liquid_section_file", lineStart: 1, lineEnd: 1, body: "", docstring: "", parent: "" });
|
|
11857
12710
|
}
|
|
11858
12711
|
const totalLines = countContentLines(content);
|
|
@@ -14155,7 +15008,7 @@ function extractApex(content, filePath) {
|
|
|
14155
15008
|
}
|
|
14156
15009
|
|
|
14157
15010
|
// src/languages/salesforce_metadata.ts
|
|
14158
|
-
import * as
|
|
15011
|
+
import * as path24 from "node:path";
|
|
14159
15012
|
var MAX_SYMBOLS10 = 1e3;
|
|
14160
15013
|
var MAX_REFS = 1e3;
|
|
14161
15014
|
var FLOW_TAG_KIND = {
|
|
@@ -14213,7 +15066,7 @@ function normalizedPath(filePath) {
|
|
|
14213
15066
|
return filePath.replace(/\\/g, "/");
|
|
14214
15067
|
}
|
|
14215
15068
|
function basenameWithout(filePath, suffix) {
|
|
14216
|
-
const base =
|
|
15069
|
+
const base = path24.basename(filePath);
|
|
14217
15070
|
return base.toLowerCase().endsWith(suffix.toLowerCase()) ? base.slice(0, base.length - suffix.length) : base;
|
|
14218
15071
|
}
|
|
14219
15072
|
function objectNameFromPath(filePath) {
|
|
@@ -14252,12 +15105,12 @@ function snakeCase(value) {
|
|
|
14252
15105
|
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
14253
15106
|
}
|
|
14254
15107
|
function companionName(filePath) {
|
|
14255
|
-
const base =
|
|
15108
|
+
const base = path24.basename(filePath);
|
|
14256
15109
|
const match = /^(.+)\.(?:cls|trigger|page|component|cmp|app|evt|intf|design|auradoc|tokens|js)-meta\.xml$/i.exec(base);
|
|
14257
15110
|
return match?.[1] === void 0 ? null : `${match[1]}.metadata`;
|
|
14258
15111
|
}
|
|
14259
15112
|
function metadataArtifactName(filePath) {
|
|
14260
|
-
const base =
|
|
15113
|
+
const base = path24.basename(filePath);
|
|
14261
15114
|
const match = /^(.+)\.[^.]+-meta\.xml$/i.exec(base);
|
|
14262
15115
|
return match?.[1] ?? basenameWithout(filePath, "-meta.xml");
|
|
14263
15116
|
}
|
|
@@ -14348,7 +15201,7 @@ function extractSalesforceMetadata(rawContent, filePath) {
|
|
|
14348
15201
|
const seen = /* @__PURE__ */ new Set();
|
|
14349
15202
|
const refs = [];
|
|
14350
15203
|
const seenRefs = /* @__PURE__ */ new Set();
|
|
14351
|
-
const base =
|
|
15204
|
+
const base = path24.basename(filePath).toLowerCase();
|
|
14352
15205
|
const whole = wholeFileSpan(content);
|
|
14353
15206
|
const root = rootElement(content);
|
|
14354
15207
|
if (root === null) return { symbols, refs };
|
|
@@ -14492,14 +15345,14 @@ function extractSalesforceMetadata(rawContent, filePath) {
|
|
|
14492
15345
|
}
|
|
14493
15346
|
|
|
14494
15347
|
// src/languages/salesforce_frontend.ts
|
|
14495
|
-
import * as
|
|
15348
|
+
import * as path25 from "node:path";
|
|
14496
15349
|
function lines(content) {
|
|
14497
15350
|
return content.split("\n");
|
|
14498
15351
|
}
|
|
14499
15352
|
function bundleName(filePath) {
|
|
14500
15353
|
const normalized = filePath.replaceAll("\\", "/");
|
|
14501
|
-
const parent =
|
|
14502
|
-
const base =
|
|
15354
|
+
const parent = path25.posix.basename(path25.posix.dirname(normalized));
|
|
15355
|
+
const base = path25.posix.basename(normalized).replace(/\.[^.]+$/, "");
|
|
14503
15356
|
return parent === "lwc" || parent === "aura" ? base : parent;
|
|
14504
15357
|
}
|
|
14505
15358
|
function lwcTagAlias(name) {
|
|
@@ -14603,7 +15456,7 @@ var MARKUP_KIND = {
|
|
|
14603
15456
|
};
|
|
14604
15457
|
function markupArtifactName(filePath, extension) {
|
|
14605
15458
|
if (extension === ".page" || extension === ".component" || extension === ".email") {
|
|
14606
|
-
return
|
|
15459
|
+
return path25.posix.basename(filePath.replaceAll("\\", "/")).replace(new RegExp(`${extension.replace(".", "\\.")}$`, "i"), "");
|
|
14607
15460
|
}
|
|
14608
15461
|
return bundleName(filePath);
|
|
14609
15462
|
}
|
|
@@ -14623,7 +15476,7 @@ function attributeRefs(refs, content, filePath, attribute, split = false) {
|
|
|
14623
15476
|
}
|
|
14624
15477
|
function extractSalesforceMarkup(content, filePath) {
|
|
14625
15478
|
const normalized = filePath.replaceAll("\\", "/");
|
|
14626
|
-
const extension =
|
|
15479
|
+
const extension = path25.posix.extname(normalized).toLowerCase();
|
|
14627
15480
|
const kind = MARKUP_KIND[extension] ?? "salesforce_markup";
|
|
14628
15481
|
const symbols = [
|
|
14629
15482
|
symbol(filePath, markupArtifactName(normalized, extension), kind, 1, countContentLines(content))
|
|
@@ -14655,7 +15508,7 @@ function extractSalesforceMarkup(content, filePath) {
|
|
|
14655
15508
|
}
|
|
14656
15509
|
|
|
14657
15510
|
// src/languages/sfc_idx.ts
|
|
14658
|
-
import * as
|
|
15511
|
+
import * as path26 from "node:path";
|
|
14659
15512
|
var MAX_SYMBOLS11 = 500;
|
|
14660
15513
|
function dedupe2(values, key) {
|
|
14661
15514
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -14674,7 +15527,7 @@ function finalize(symbols, refs) {
|
|
|
14674
15527
|
}
|
|
14675
15528
|
function componentName(filePath) {
|
|
14676
15529
|
const normalized = filePath.replaceAll("\\", "/");
|
|
14677
|
-
return
|
|
15530
|
+
return path26.posix.basename(normalized).replace(/\.[^.]+$/, "");
|
|
14678
15531
|
}
|
|
14679
15532
|
function matchLine2(content, offset) {
|
|
14680
15533
|
return content.slice(0, offset).split("\n").length;
|
|
@@ -14986,8 +15839,8 @@ function countNewlines(s) {
|
|
|
14986
15839
|
return n;
|
|
14987
15840
|
}
|
|
14988
15841
|
function loadGrammar(lang, filePath, content) {
|
|
14989
|
-
const useTsx = lang === "typescript" && filePath !== void 0 &&
|
|
14990
|
-
const useCppHeader = lang === "c" && filePath !== void 0 &&
|
|
15842
|
+
const useTsx = lang === "typescript" && filePath !== void 0 && path27.extname(filePath).toLowerCase() === ".tsx";
|
|
15843
|
+
const useCppHeader = lang === "c" && filePath !== void 0 && path27.extname(filePath).toLowerCase() === ".h" && content !== void 0 && CPP_HEADER_SNIFF_RE.test(content);
|
|
14991
15844
|
const cacheKey = useTsx ? "typescript:tsx" : useCppHeader ? "c:cpp-header" : lang;
|
|
14992
15845
|
const cached = _grammarCache.get(cacheKey);
|
|
14993
15846
|
if (cached !== void 0) return cached;
|
|
@@ -15135,6 +15988,9 @@ function makeSymbol(filePath, name, kind, node, lines2, style) {
|
|
|
15135
15988
|
parent: ""
|
|
15136
15989
|
};
|
|
15137
15990
|
}
|
|
15991
|
+
function fanOutElidesBodies(nameCount, declarationChars) {
|
|
15992
|
+
return nameCount > 1 && nameCount * declarationChars > MAX_SYMBOL_BODY_CHARS;
|
|
15993
|
+
}
|
|
15138
15994
|
function collectPatternBindings(node) {
|
|
15139
15995
|
const names = [];
|
|
15140
15996
|
const walk = (n) => {
|
|
@@ -15142,6 +15998,16 @@ function collectPatternBindings(node) {
|
|
|
15142
15998
|
if (n.text !== "") names.push(n.text);
|
|
15143
15999
|
return;
|
|
15144
16000
|
}
|
|
16001
|
+
if (n.type === "assignment_pattern" || n.type === "object_assignment_pattern") {
|
|
16002
|
+
const left = n.childForFieldName("left");
|
|
16003
|
+
if (left !== null) walk(left);
|
|
16004
|
+
return;
|
|
16005
|
+
}
|
|
16006
|
+
if (n.type === "pair_pattern") {
|
|
16007
|
+
const value = n.childForFieldName("value");
|
|
16008
|
+
if (value !== null) walk(value);
|
|
16009
|
+
return;
|
|
16010
|
+
}
|
|
15145
16011
|
for (const child of n.namedChildren) walk(child);
|
|
15146
16012
|
};
|
|
15147
16013
|
walk(node);
|
|
@@ -15203,7 +16069,7 @@ function extractTsJsSymbols(root, filePath, lines2) {
|
|
|
15203
16069
|
out.push(makeSymbol(filePath, name.text, isFn ? "function" : "variable", child, lines2, "c"));
|
|
15204
16070
|
} else {
|
|
15205
16071
|
const bindings = collectPatternBindings(name);
|
|
15206
|
-
const elideBodies = bindings.length
|
|
16072
|
+
const elideBodies = fanOutElidesBodies(bindings.length, child.text.length);
|
|
15207
16073
|
for (const bound of bindings) {
|
|
15208
16074
|
const sym = makeSymbol(filePath, bound, "variable", child, lines2, "c");
|
|
15209
16075
|
out.push(elideBodies ? { ...sym, body: "" } : sym);
|
|
@@ -15321,14 +16187,26 @@ var GO_LOCAL_KINDS = /* @__PURE__ */ new Set([
|
|
|
15321
16187
|
// the interface type declaring them does not.
|
|
15322
16188
|
"method_elem"
|
|
15323
16189
|
]);
|
|
16190
|
+
var GO_MULTI_NAME_SPECS = /* @__PURE__ */ new Set(["var_spec", "const_spec"]);
|
|
15324
16191
|
function extractGoSymbols(root, filePath, lines2) {
|
|
15325
16192
|
const out = [];
|
|
15326
16193
|
const visit = (node, insideFunction) => {
|
|
15327
16194
|
const kind = GO_KIND_BY_TYPE.get(node.type);
|
|
15328
16195
|
if (kind !== void 0 && !(insideFunction && GO_LOCAL_KINDS.has(node.type))) {
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
|
|
16196
|
+
if (GO_MULTI_NAME_SPECS.has(node.type)) {
|
|
16197
|
+
const declared = node.namedChildren.filter(
|
|
16198
|
+
(c) => c.type === "identifier" && c.text !== "" && c.text !== "_"
|
|
16199
|
+
);
|
|
16200
|
+
const elideBodies = fanOutElidesBodies(declared.length, node.text.length);
|
|
16201
|
+
for (const child of declared) {
|
|
16202
|
+
const sym = makeSymbol(filePath, child.text, kind, node, lines2, "c");
|
|
16203
|
+
out.push(elideBodies ? { ...sym, body: "" } : sym);
|
|
16204
|
+
}
|
|
16205
|
+
} else {
|
|
16206
|
+
const name = nodeName(node);
|
|
16207
|
+
if (name !== null && name !== "") {
|
|
16208
|
+
out.push(makeSymbol(filePath, name, kind, node, lines2, "c"));
|
|
16209
|
+
}
|
|
15332
16210
|
}
|
|
15333
16211
|
}
|
|
15334
16212
|
const childInside = insideFunction || GO_FN_SCOPE_TYPES.has(node.type);
|
|
@@ -16654,9 +17532,9 @@ function isUnderSkipDir(filePath, skipDirs) {
|
|
|
16654
17532
|
}
|
|
16655
17533
|
function isParseSkipEligible(filePath, cfg) {
|
|
16656
17534
|
if (isUnderSkipDir(filePath, cfg.skip_dirs)) return true;
|
|
16657
|
-
if (cfg.skip_files.includes(
|
|
17535
|
+
if (cfg.skip_files.includes(path27.basename(filePath))) return true;
|
|
16658
17536
|
try {
|
|
16659
|
-
const stat2 =
|
|
17537
|
+
const stat2 = fs27.statSync(filePath);
|
|
16660
17538
|
if (stat2.size > cfg.large_file_skip_kb * 1024) return true;
|
|
16661
17539
|
} catch {
|
|
16662
17540
|
}
|
|
@@ -16698,7 +17576,8 @@ function writeParseResult(filePath, content, result, dbPath) {
|
|
|
16698
17576
|
});
|
|
16699
17577
|
writeAll.immediate();
|
|
16700
17578
|
}
|
|
16701
|
-
function indexFileSync(
|
|
17579
|
+
function indexFileSync(rawPath, dbPath = globalDbPath(), preReadBytes) {
|
|
17580
|
+
const filePath = canonicalizeIndexPath(rawPath);
|
|
16702
17581
|
const ixCfg = loadConfig().indexing;
|
|
16703
17582
|
if (ixCfg !== void 0 && isParseSkipEligible(filePath, ixCfg)) {
|
|
16704
17583
|
const db = getDb(dbPath);
|
|
@@ -16712,7 +17591,7 @@ function indexFileSync(filePath, dbPath = globalDbPath(), preReadBytes) {
|
|
|
16712
17591
|
raw = preReadBytes;
|
|
16713
17592
|
} else {
|
|
16714
17593
|
try {
|
|
16715
|
-
raw =
|
|
17594
|
+
raw = fs27.readFileSync(filePath);
|
|
16716
17595
|
} catch (err) {
|
|
16717
17596
|
if (err.code === "ENOENT") return;
|
|
16718
17597
|
throw err;
|
|
@@ -16743,14 +17622,32 @@ var UNAVAILABLE_EMBED_SHA_PREFIX = "unavailable:";
|
|
|
16743
17622
|
function unavailableEmbedSha(sha) {
|
|
16744
17623
|
return UNAVAILABLE_EMBED_SHA_PREFIX + sha;
|
|
16745
17624
|
}
|
|
17625
|
+
function canonicalizeIndexPath(absPath) {
|
|
17626
|
+
if (!isCaseInsensitiveFs()) return absPath;
|
|
17627
|
+
let real;
|
|
17628
|
+
try {
|
|
17629
|
+
real = fs27.realpathSync.native(absPath);
|
|
17630
|
+
} catch {
|
|
17631
|
+
return absPath;
|
|
17632
|
+
}
|
|
17633
|
+
const cut = Math.max(absPath.lastIndexOf("/"), absPath.lastIndexOf("\\"));
|
|
17634
|
+
const base = absPath.slice(cut + 1);
|
|
17635
|
+
const realNorm = normalizePath(real);
|
|
17636
|
+
const realBase = path27.basename(realNorm);
|
|
17637
|
+
if (base === realBase) return absPath;
|
|
17638
|
+
if (foldPath(base) !== foldPath(realBase)) return absPath;
|
|
17639
|
+
const callerDir = cut < 0 ? normalizePath(path27.resolve(".")) : normalizePath(absPath.slice(0, cut));
|
|
17640
|
+
if (foldPath(callerDir) !== foldPath(path27.dirname(realNorm))) return absPath;
|
|
17641
|
+
return absPath.slice(0, cut + 1) + realBase;
|
|
17642
|
+
}
|
|
16746
17643
|
function indexedPathSpellingIsStale(storedPath, absPath) {
|
|
16747
17644
|
if (!isCaseInsensitiveFs()) return false;
|
|
16748
17645
|
const stored = normalizePath(storedPath);
|
|
16749
|
-
const candidate = normalizePath(
|
|
17646
|
+
const candidate = normalizePath(path27.resolve(absPath));
|
|
16750
17647
|
if (foldPath(stored) !== foldPath(candidate)) return false;
|
|
16751
17648
|
let real;
|
|
16752
17649
|
try {
|
|
16753
|
-
real = normalizePath(
|
|
17650
|
+
real = normalizePath(fs27.realpathSync.native(absPath));
|
|
16754
17651
|
} catch {
|
|
16755
17652
|
return false;
|
|
16756
17653
|
}
|
|
@@ -16779,7 +17676,8 @@ function isEmbedFresh(storedEmbedSha, sha, embeddingsEnabled, depsAvailable) {
|
|
|
16779
17676
|
if (!depsAvailable && storedEmbedSha === unavailableEmbedSha(sha)) return true;
|
|
16780
17677
|
return false;
|
|
16781
17678
|
}
|
|
16782
|
-
async function indexFileEmbeddings(
|
|
17679
|
+
async function indexFileEmbeddings(rawPath, dbPath = globalDbPath(), sha, onError) {
|
|
17680
|
+
const filePath = canonicalizeIndexPath(rawPath);
|
|
16783
17681
|
const ixCfg = loadConfig().indexing;
|
|
16784
17682
|
if (!ixCfg.embeddings_enabled) {
|
|
16785
17683
|
stampEmbedSha(getDb(dbPath), filePath, sha, disabledEmbedSha);
|
|
@@ -16816,7 +17714,7 @@ async function indexFileEmbeddings(filePath, dbPath = globalDbPath(), sha, onErr
|
|
|
16816
17714
|
}
|
|
16817
17715
|
let content;
|
|
16818
17716
|
try {
|
|
16819
|
-
content = decodeSource(await
|
|
17717
|
+
content = decodeSource(await fs27.promises.readFile(filePath));
|
|
16820
17718
|
} catch {
|
|
16821
17719
|
return;
|
|
16822
17720
|
}
|
|
@@ -16862,15 +17760,15 @@ function stampEmbedSha(db, filePath, sha, makeValue) {
|
|
|
16862
17760
|
}
|
|
16863
17761
|
function safeMtime(filePath) {
|
|
16864
17762
|
try {
|
|
16865
|
-
return
|
|
17763
|
+
return fs27.statSync(filePath).mtimeMs / 1e3;
|
|
16866
17764
|
} catch {
|
|
16867
17765
|
return 0;
|
|
16868
17766
|
}
|
|
16869
17767
|
}
|
|
16870
17768
|
|
|
16871
17769
|
// src/index_prune.ts
|
|
16872
|
-
import * as
|
|
16873
|
-
import * as
|
|
17770
|
+
import * as fs28 from "node:fs";
|
|
17771
|
+
import * as path28 from "node:path";
|
|
16874
17772
|
function removeFileFromIndex(db, filePath) {
|
|
16875
17773
|
const tx = db.transaction(() => {
|
|
16876
17774
|
deleteFileRows(db, filePath);
|
|
@@ -16907,7 +17805,7 @@ function findDeletablePaths(rootPrefix, dbPath) {
|
|
|
16907
17805
|
for (const p of foldedPathsUnderRoot(rootPrefix, dbPath)) {
|
|
16908
17806
|
let stillExists;
|
|
16909
17807
|
try {
|
|
16910
|
-
const st =
|
|
17808
|
+
const st = fs28.statSync(p, { throwIfNoEntry: false });
|
|
16911
17809
|
stillExists = st !== void 0 && st.isFile();
|
|
16912
17810
|
} catch {
|
|
16913
17811
|
continue;
|
|
@@ -16935,7 +17833,7 @@ function removeDeletedFilesBestEffort(db, paths) {
|
|
|
16935
17833
|
for (const p of paths) {
|
|
16936
17834
|
let gone;
|
|
16937
17835
|
try {
|
|
16938
|
-
const st =
|
|
17836
|
+
const st = fs28.statSync(p, { throwIfNoEntry: false });
|
|
16939
17837
|
gone = st === void 0 || !st.isFile();
|
|
16940
17838
|
} catch {
|
|
16941
17839
|
continue;
|
|
@@ -17002,7 +17900,7 @@ function pruneOrphanedChunks(dbPath = globalDbPath()) {
|
|
|
17002
17900
|
return removed;
|
|
17003
17901
|
}
|
|
17004
17902
|
function recordKnownRoot(filePath, dbPath = globalDbPath()) {
|
|
17005
|
-
const project = findProject(
|
|
17903
|
+
const project = findProject(path28.dirname(filePath));
|
|
17006
17904
|
if (project === null || isTooShallowToPrune(project.root)) return;
|
|
17007
17905
|
const db = getDb(dbPath);
|
|
17008
17906
|
db.prepare(
|
|
@@ -17025,7 +17923,7 @@ function sweepKnownRoots(dbPath = globalDbPath(), opts) {
|
|
|
17025
17923
|
if (isTooShallowToPrune(root)) continue;
|
|
17026
17924
|
let reachable;
|
|
17027
17925
|
try {
|
|
17028
|
-
reachable =
|
|
17926
|
+
reachable = fs28.statSync(root, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
17029
17927
|
} catch {
|
|
17030
17928
|
reachable = false;
|
|
17031
17929
|
}
|
|
@@ -17060,19 +17958,19 @@ function sweepKnownRoots(dbPath = globalDbPath(), opts) {
|
|
|
17060
17958
|
}
|
|
17061
17959
|
var KNOWN_ROOT_RECORD_MIN_INTERVAL_MS = 60 * 60 * 1e3;
|
|
17062
17960
|
function knownRootRecordMarkerPath(dir, filePath) {
|
|
17063
|
-
return
|
|
17961
|
+
return path28.join(dir, `known-root-record-${shortFingerprint(path28.dirname(filePath))}.marker`);
|
|
17064
17962
|
}
|
|
17065
17963
|
var KNOWN_ROOT_MARKER_PREFIX = "known-root-record-";
|
|
17066
17964
|
function sweepExpiredKnownRootMarkers(dir = dataDir()) {
|
|
17067
17965
|
let removed = 0;
|
|
17068
17966
|
try {
|
|
17069
17967
|
const cutoff = Date.now() - KNOWN_ROOT_RECORD_MIN_INTERVAL_MS;
|
|
17070
|
-
for (const file of
|
|
17968
|
+
for (const file of fs28.readdirSync(dir)) {
|
|
17071
17969
|
if (!file.startsWith(KNOWN_ROOT_MARKER_PREFIX) || !file.endsWith(".marker")) continue;
|
|
17072
|
-
const full =
|
|
17970
|
+
const full = path28.join(dir, file);
|
|
17073
17971
|
try {
|
|
17074
|
-
if (
|
|
17075
|
-
|
|
17972
|
+
if (fs28.statSync(full).mtimeMs < cutoff) {
|
|
17973
|
+
fs28.unlinkSync(full);
|
|
17076
17974
|
removed += 1;
|
|
17077
17975
|
}
|
|
17078
17976
|
} catch {
|
|
@@ -17085,13 +17983,13 @@ function sweepExpiredKnownRootMarkers(dir = dataDir()) {
|
|
|
17085
17983
|
function recordKnownRootThrottled(filePath, dir = dataDir(), dbPath = globalDbPath()) {
|
|
17086
17984
|
const markerPath = knownRootRecordMarkerPath(dir, filePath);
|
|
17087
17985
|
try {
|
|
17088
|
-
const stat2 =
|
|
17986
|
+
const stat2 = fs28.statSync(markerPath);
|
|
17089
17987
|
if (Date.now() - stat2.mtimeMs < KNOWN_ROOT_RECORD_MIN_INTERVAL_MS) return;
|
|
17090
17988
|
} catch {
|
|
17091
17989
|
}
|
|
17092
17990
|
try {
|
|
17093
17991
|
ensureDirSync(dir);
|
|
17094
|
-
|
|
17992
|
+
fs28.writeFileSync(markerPath, "");
|
|
17095
17993
|
} catch {
|
|
17096
17994
|
}
|
|
17097
17995
|
recordKnownRoot(filePath, dbPath);
|
|
@@ -17099,8 +17997,8 @@ function recordKnownRootThrottled(filePath, dir = dataDir(), dbPath = globalDbPa
|
|
|
17099
17997
|
|
|
17100
17998
|
// src/worker.ts
|
|
17101
17999
|
import { spawn as spawn2 } from "node:child_process";
|
|
17102
|
-
import * as
|
|
17103
|
-
import * as
|
|
18000
|
+
import * as fs29 from "node:fs";
|
|
18001
|
+
import * as path29 from "node:path";
|
|
17104
18002
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
17105
18003
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
17106
18004
|
function resolvePollIntervalMs(explicit) {
|
|
@@ -17120,7 +18018,7 @@ var unclearedDrainingSnapshots = /* @__PURE__ */ new Map();
|
|
|
17120
18018
|
function drainingSnapshotStamp(file, content) {
|
|
17121
18019
|
let identity = "unknown";
|
|
17122
18020
|
try {
|
|
17123
|
-
const stat2 =
|
|
18021
|
+
const stat2 = fs29.statSync(file);
|
|
17124
18022
|
identity = `${stat2.mtimeMs}:${stat2.size}:${stat2.ino}`;
|
|
17125
18023
|
} catch {
|
|
17126
18024
|
}
|
|
@@ -17129,15 +18027,15 @@ function drainingSnapshotStamp(file, content) {
|
|
|
17129
18027
|
var DRAINING_READ_ATTEMPTS = 5;
|
|
17130
18028
|
var DRAINING_READ_RETRY_DELAY_MS = 50;
|
|
17131
18029
|
function listDrainingFiles(queuePath) {
|
|
17132
|
-
const dir =
|
|
17133
|
-
const base = `${
|
|
18030
|
+
const dir = path29.dirname(queuePath);
|
|
18031
|
+
const base = `${path29.basename(queuePath)}.draining`;
|
|
17134
18032
|
let entries;
|
|
17135
18033
|
try {
|
|
17136
|
-
entries =
|
|
18034
|
+
entries = fs29.readdirSync(dir);
|
|
17137
18035
|
} catch {
|
|
17138
18036
|
return [];
|
|
17139
18037
|
}
|
|
17140
|
-
return entries.filter((name) => name === base || name.startsWith(`${base}.alt-`) && !name.includes(".corrupt-")).sort().map((name) =>
|
|
18038
|
+
return entries.filter((name) => name === base || name.startsWith(`${base}.alt-`) && !name.includes(".corrupt-")).sort().map((name) => path29.join(dir, name));
|
|
17141
18039
|
}
|
|
17142
18040
|
var MAX_TRANSIENT_RETRIES = 5;
|
|
17143
18041
|
function bumpRetryCount(dbPath, absPath) {
|
|
@@ -17165,17 +18063,17 @@ function clearRetryCount(dbPath, absPath) {
|
|
|
17165
18063
|
}
|
|
17166
18064
|
}
|
|
17167
18065
|
function dirtyQueuePathFor(dir) {
|
|
17168
|
-
return
|
|
18066
|
+
return path29.join(dir, "queue", "dirty.txt");
|
|
17169
18067
|
}
|
|
17170
18068
|
function drainHeartbeatPathFor(dir) {
|
|
17171
|
-
return
|
|
18069
|
+
return path29.join(dir, "queue", "drain-heartbeat");
|
|
17172
18070
|
}
|
|
17173
18071
|
function writeDrainHeartbeat(dir, force = false) {
|
|
17174
18072
|
const now = Date.now();
|
|
17175
18073
|
if (!force && now - (heartbeatWriteTimes.get(dir) ?? 0) < WORKER_HEARTBEAT_REFRESH_MS) return;
|
|
17176
18074
|
try {
|
|
17177
|
-
ensureDirSync(
|
|
17178
|
-
|
|
18075
|
+
ensureDirSync(path29.dirname(drainHeartbeatPathFor(dir)));
|
|
18076
|
+
fs29.writeFileSync(drainHeartbeatPathFor(dir), `${process.pid}
|
|
17179
18077
|
`);
|
|
17180
18078
|
heartbeatWriteTimes.set(dir, now);
|
|
17181
18079
|
} catch {
|
|
@@ -17184,15 +18082,15 @@ function writeDrainHeartbeat(dir, force = false) {
|
|
|
17184
18082
|
function hasFreshWorkerHeartbeat(dir, pid) {
|
|
17185
18083
|
try {
|
|
17186
18084
|
const heartbeatPath = drainHeartbeatPathFor(dir);
|
|
17187
|
-
if (Date.now() -
|
|
17188
|
-
return
|
|
18085
|
+
if (Date.now() - fs29.statSync(heartbeatPath).mtimeMs > WORKER_HEARTBEAT_STALE_MS) return false;
|
|
18086
|
+
return fs29.readFileSync(heartbeatPath, "utf8").trim() === String(pid);
|
|
17189
18087
|
} catch {
|
|
17190
18088
|
return false;
|
|
17191
18089
|
}
|
|
17192
18090
|
}
|
|
17193
18091
|
function pidFileIsWithinStartupGrace(dir) {
|
|
17194
18092
|
try {
|
|
17195
|
-
return Date.now() -
|
|
18093
|
+
return Date.now() - fs29.statSync(workerPidPath(dir)).mtimeMs < WORKER_STARTUP_GRACE_MS;
|
|
17196
18094
|
} catch {
|
|
17197
18095
|
return false;
|
|
17198
18096
|
}
|
|
@@ -17226,28 +18124,28 @@ function parseDirtyQueueLines(raw) {
|
|
|
17226
18124
|
return out;
|
|
17227
18125
|
}
|
|
17228
18126
|
function workerPidPath(dir = dataDir()) {
|
|
17229
|
-
return
|
|
18127
|
+
return path29.join(dir, "worker.pid");
|
|
17230
18128
|
}
|
|
17231
18129
|
function getDirtyPathsFor(dir) {
|
|
17232
18130
|
let raw;
|
|
17233
18131
|
try {
|
|
17234
|
-
raw =
|
|
18132
|
+
raw = fs29.readFileSync(dirtyQueuePathFor(dir), "utf8");
|
|
17235
18133
|
} catch {
|
|
17236
18134
|
return [];
|
|
17237
18135
|
}
|
|
17238
18136
|
return parseDirtyQueueLines(raw);
|
|
17239
18137
|
}
|
|
17240
18138
|
function workerErrorLogPath(dir) {
|
|
17241
|
-
return
|
|
18139
|
+
return path29.join(dir, "worker-errors.log");
|
|
17242
18140
|
}
|
|
17243
18141
|
var WORKER_ERROR_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
17244
18142
|
var CORRUPT_QUARANTINE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
17245
18143
|
function cleanupWorkerStateFiles(dir) {
|
|
17246
18144
|
try {
|
|
17247
18145
|
const logPath = workerErrorLogPath(dir);
|
|
17248
|
-
const stat2 =
|
|
18146
|
+
const stat2 = fs29.statSync(logPath);
|
|
17249
18147
|
if (stat2.size > WORKER_ERROR_LOG_MAX_BYTES) {
|
|
17250
|
-
|
|
18148
|
+
fs29.writeFileSync(
|
|
17251
18149
|
logPath,
|
|
17252
18150
|
`${(/* @__PURE__ */ new Date()).toISOString()} worker-errors.log rotated (exceeded ${WORKER_ERROR_LOG_MAX_BYTES} bytes)
|
|
17253
18151
|
`
|
|
@@ -17256,13 +18154,13 @@ function cleanupWorkerStateFiles(dir) {
|
|
|
17256
18154
|
} catch {
|
|
17257
18155
|
}
|
|
17258
18156
|
try {
|
|
17259
|
-
const queueDir =
|
|
18157
|
+
const queueDir = path29.dirname(dirtyQueuePathFor(dir));
|
|
17260
18158
|
const cutoff = Date.now() - CORRUPT_QUARANTINE_MAX_AGE_MS;
|
|
17261
|
-
for (const file of
|
|
18159
|
+
for (const file of fs29.readdirSync(queueDir)) {
|
|
17262
18160
|
if (!file.includes(".corrupt-")) continue;
|
|
17263
|
-
const full =
|
|
18161
|
+
const full = path29.join(queueDir, file);
|
|
17264
18162
|
try {
|
|
17265
|
-
if (
|
|
18163
|
+
if (fs29.statSync(full).mtimeMs < cutoff) fs29.unlinkSync(full);
|
|
17266
18164
|
} catch {
|
|
17267
18165
|
}
|
|
17268
18166
|
}
|
|
@@ -17293,7 +18191,7 @@ registerReset(() => {
|
|
|
17293
18191
|
function embedFileSerialized(absPath, dbPath, sha) {
|
|
17294
18192
|
const key = foldPath(absPath);
|
|
17295
18193
|
const prior = inFlightEmbeddings.get(key);
|
|
17296
|
-
const dir =
|
|
18194
|
+
const dir = path29.dirname(dbPath);
|
|
17297
18195
|
const onEmbedError = (err) => {
|
|
17298
18196
|
const message = extractErrorMessage(err);
|
|
17299
18197
|
appendWorkerErrorLog(dir, `${(/* @__PURE__ */ new Date()).toISOString()} indexFileEmbeddings failed for ${absPath}: ${message}
|
|
@@ -17327,7 +18225,7 @@ function embedFileSerialized(absPath, dbPath, sha) {
|
|
|
17327
18225
|
}
|
|
17328
18226
|
function appendWorkerErrorLog(dir, line) {
|
|
17329
18227
|
try {
|
|
17330
|
-
|
|
18228
|
+
fs29.appendFileSync(workerErrorLogPath(dir), line);
|
|
17331
18229
|
} catch {
|
|
17332
18230
|
}
|
|
17333
18231
|
}
|
|
@@ -17344,7 +18242,7 @@ function logTransientReadFailure(dir, absPath) {
|
|
|
17344
18242
|
);
|
|
17345
18243
|
}
|
|
17346
18244
|
function bumpAndCheckRetry(dir, absPath) {
|
|
17347
|
-
const dbPath =
|
|
18245
|
+
const dbPath = path29.join(dir, "global.db");
|
|
17348
18246
|
const attempts = bumpRetryCount(dbPath, absPath);
|
|
17349
18247
|
if (attempts > MAX_TRANSIENT_RETRIES) {
|
|
17350
18248
|
if (attempts === MAX_TRANSIENT_RETRIES + 1) {
|
|
@@ -17361,14 +18259,14 @@ function bumpAndCheckRetry(dir, absPath) {
|
|
|
17361
18259
|
function appendToDirtyQueue(dir, absPath) {
|
|
17362
18260
|
const queuePath = dirtyQueuePathFor(dir);
|
|
17363
18261
|
try {
|
|
17364
|
-
ensureDirSync(
|
|
18262
|
+
ensureDirSync(path29.dirname(queuePath));
|
|
17365
18263
|
let leadingNewline = "";
|
|
17366
18264
|
try {
|
|
17367
|
-
const existing =
|
|
18265
|
+
const existing = fs29.readFileSync(queuePath, "utf8");
|
|
17368
18266
|
if (existing.length > 0 && !existing.endsWith("\n")) leadingNewline = "\n";
|
|
17369
18267
|
} catch {
|
|
17370
18268
|
}
|
|
17371
|
-
|
|
18269
|
+
fs29.appendFileSync(queuePath, `${leadingNewline}${encodeDirtyQueueLine(absPath)}
|
|
17372
18270
|
`);
|
|
17373
18271
|
} catch {
|
|
17374
18272
|
}
|
|
@@ -17377,7 +18275,7 @@ function requeueDirtyPath(dir, absPath) {
|
|
|
17377
18275
|
if (bumpAndCheckRetry(dir, absPath)) appendToDirtyQueue(dir, absPath);
|
|
17378
18276
|
}
|
|
17379
18277
|
function makeIndexer(dbPath) {
|
|
17380
|
-
const dir =
|
|
18278
|
+
const dir = path29.dirname(dbPath);
|
|
17381
18279
|
return (absPath, sha) => {
|
|
17382
18280
|
try {
|
|
17383
18281
|
const ixCfgForSkip = loadConfig().indexing;
|
|
@@ -17429,16 +18327,16 @@ function processDirtyBatch(paths, index = makeIndexer(globalDbPath()), remove =
|
|
|
17429
18327
|
requeue(dir, p);
|
|
17430
18328
|
continue;
|
|
17431
18329
|
}
|
|
17432
|
-
clearRetryCount(
|
|
18330
|
+
clearRetryCount(path29.join(dir, "global.db"), p);
|
|
17433
18331
|
try {
|
|
17434
|
-
const
|
|
18332
|
+
const dirname19 = path29.dirname(p);
|
|
17435
18333
|
let root;
|
|
17436
|
-
if (projectRootCache.has(
|
|
17437
|
-
root = projectRootCache.get(
|
|
18334
|
+
if (projectRootCache.has(dirname19)) {
|
|
18335
|
+
root = projectRootCache.get(dirname19) ?? null;
|
|
17438
18336
|
} else {
|
|
17439
|
-
const project = findProject(
|
|
18337
|
+
const project = findProject(dirname19);
|
|
17440
18338
|
root = project?.root ?? null;
|
|
17441
|
-
projectRootCache.set(
|
|
18339
|
+
projectRootCache.set(dirname19, root);
|
|
17442
18340
|
}
|
|
17443
18341
|
if (root) lastKnownProjectRoots.set(dir, root);
|
|
17444
18342
|
} catch {
|
|
@@ -17457,7 +18355,7 @@ function sleepSyncMs(ms) {
|
|
|
17457
18355
|
function drainOnce(dir, index, remove) {
|
|
17458
18356
|
const queuePath = dirtyQueuePathFor(dir);
|
|
17459
18357
|
const draining = `${queuePath}.draining`;
|
|
17460
|
-
const dbPath =
|
|
18358
|
+
const dbPath = path29.join(dir, "global.db");
|
|
17461
18359
|
const indexFn = index ?? makeIndexer(dbPath);
|
|
17462
18360
|
const removeFn = remove ?? makeRemover(dbPath);
|
|
17463
18361
|
let processed = 0;
|
|
@@ -17474,7 +18372,7 @@ function drainOnce(dir, index, remove) {
|
|
|
17474
18372
|
let drainingContent = null;
|
|
17475
18373
|
for (let attempt = 0; attempt < DRAINING_READ_ATTEMPTS; attempt++) {
|
|
17476
18374
|
try {
|
|
17477
|
-
drainingContent =
|
|
18375
|
+
drainingContent = fs29.readFileSync(drainingFile, "utf8");
|
|
17478
18376
|
break;
|
|
17479
18377
|
} catch {
|
|
17480
18378
|
if (attempt < DRAINING_READ_ATTEMPTS - 1) sleepSyncMs(DRAINING_READ_RETRY_DELAY_MS);
|
|
@@ -17482,7 +18380,7 @@ function drainOnce(dir, index, remove) {
|
|
|
17482
18380
|
}
|
|
17483
18381
|
if (drainingContent === null) {
|
|
17484
18382
|
try {
|
|
17485
|
-
|
|
18383
|
+
fs29.renameSync(drainingFile, `${drainingFile}.corrupt-${Date.now()}`);
|
|
17486
18384
|
unclearedDrainingSnapshots.delete(drainingFile);
|
|
17487
18385
|
} catch {
|
|
17488
18386
|
}
|
|
@@ -17492,23 +18390,23 @@ function drainOnce(dir, index, remove) {
|
|
|
17492
18390
|
processed += processDirtyBatch(parseDirtyQueueLines(drainingContent), indexFn, removeFn, dir, requeueFn);
|
|
17493
18391
|
}
|
|
17494
18392
|
try {
|
|
17495
|
-
|
|
18393
|
+
fs29.rmSync(drainingFile, { force: true });
|
|
17496
18394
|
unclearedDrainingSnapshots.delete(drainingFile);
|
|
17497
18395
|
} catch {
|
|
17498
18396
|
try {
|
|
17499
|
-
|
|
18397
|
+
fs29.renameSync(drainingFile, `${drainingFile}.corrupt-${Date.now()}`);
|
|
17500
18398
|
unclearedDrainingSnapshots.delete(drainingFile);
|
|
17501
18399
|
} catch {
|
|
17502
18400
|
unclearedDrainingSnapshots.set(drainingFile, drainingSnapshotStamp(drainingFile, drainingContent));
|
|
17503
18401
|
}
|
|
17504
18402
|
}
|
|
17505
18403
|
}
|
|
17506
|
-
if (
|
|
17507
|
-
const claimTarget =
|
|
18404
|
+
if (fs29.existsSync(queuePath)) {
|
|
18405
|
+
const claimTarget = fs29.existsSync(draining) ? `${draining}.alt-${Date.now()}` : draining;
|
|
17508
18406
|
let claimed = false;
|
|
17509
18407
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
17510
18408
|
try {
|
|
17511
|
-
|
|
18409
|
+
fs29.renameSync(queuePath, claimTarget);
|
|
17512
18410
|
claimed = true;
|
|
17513
18411
|
break;
|
|
17514
18412
|
} catch {
|
|
@@ -17519,14 +18417,14 @@ function drainOnce(dir, index, remove) {
|
|
|
17519
18417
|
let claimedContent = "";
|
|
17520
18418
|
let readOk = false;
|
|
17521
18419
|
try {
|
|
17522
|
-
claimedContent =
|
|
18420
|
+
claimedContent = fs29.readFileSync(claimTarget, "utf8");
|
|
17523
18421
|
readOk = true;
|
|
17524
18422
|
} catch {
|
|
17525
18423
|
}
|
|
17526
18424
|
if (readOk) {
|
|
17527
18425
|
processed += processDirtyBatch(parseDirtyQueueLines(claimedContent), indexFn, removeFn, dir, requeueFn);
|
|
17528
18426
|
try {
|
|
17529
|
-
const recheck =
|
|
18427
|
+
const recheck = fs29.readFileSync(claimTarget, "utf8");
|
|
17530
18428
|
if (recheck !== claimedContent) {
|
|
17531
18429
|
const extra = recheck.startsWith(claimedContent) ? recheck.slice(claimedContent.length) : recheck;
|
|
17532
18430
|
for (const p of parseDirtyQueueLines(extra)) appendToDirtyQueue(dir, p);
|
|
@@ -17534,10 +18432,10 @@ function drainOnce(dir, index, remove) {
|
|
|
17534
18432
|
} catch {
|
|
17535
18433
|
}
|
|
17536
18434
|
try {
|
|
17537
|
-
|
|
18435
|
+
fs29.rmSync(claimTarget, { force: true });
|
|
17538
18436
|
} catch {
|
|
17539
18437
|
try {
|
|
17540
|
-
|
|
18438
|
+
fs29.renameSync(claimTarget, `${claimTarget}.corrupt-${Date.now()}`);
|
|
17541
18439
|
} catch {
|
|
17542
18440
|
unclearedDrainingSnapshots.set(claimTarget, drainingSnapshotStamp(claimTarget, claimedContent));
|
|
17543
18441
|
}
|
|
@@ -17570,7 +18468,7 @@ function pidAlive(pid) {
|
|
|
17570
18468
|
}
|
|
17571
18469
|
function readPidFile(dir) {
|
|
17572
18470
|
try {
|
|
17573
|
-
const raw =
|
|
18471
|
+
const raw = fs29.readFileSync(workerPidPath(dir), "utf8").trim();
|
|
17574
18472
|
if (!/^\d+$/.test(raw)) return null;
|
|
17575
18473
|
return parseInt(raw, 10);
|
|
17576
18474
|
} catch {
|
|
@@ -17583,20 +18481,20 @@ function isWorkerRunning(dir = dataDir()) {
|
|
|
17583
18481
|
return pidAlive(pid) && hasFreshWorkerHeartbeat(dir, pid);
|
|
17584
18482
|
}
|
|
17585
18483
|
function workerHealthCheckMarkerPath(dir) {
|
|
17586
|
-
return
|
|
18484
|
+
return path29.join(dir, "worker-healthcheck.marker");
|
|
17587
18485
|
}
|
|
17588
18486
|
var WORKER_HEALTHCHECK_MIN_INTERVAL_MS = 5 * 60 * 1e3;
|
|
17589
18487
|
function ensureWorkerAlive(dir = dataDir()) {
|
|
17590
18488
|
if (process.env["TOKEN_GOAT_NO_WORKER_SPAWN"] === "1") return;
|
|
17591
18489
|
const markerPath = workerHealthCheckMarkerPath(dir);
|
|
17592
18490
|
try {
|
|
17593
|
-
const stat2 =
|
|
18491
|
+
const stat2 = fs29.statSync(markerPath);
|
|
17594
18492
|
if (Date.now() - stat2.mtimeMs < WORKER_HEALTHCHECK_MIN_INTERVAL_MS) return;
|
|
17595
18493
|
} catch {
|
|
17596
18494
|
}
|
|
17597
18495
|
try {
|
|
17598
18496
|
ensureDirSync(dir);
|
|
17599
|
-
|
|
18497
|
+
fs29.writeFileSync(markerPath, "");
|
|
17600
18498
|
} catch {
|
|
17601
18499
|
}
|
|
17602
18500
|
if (isWorkerRunning(dir)) return;
|
|
@@ -17626,7 +18524,7 @@ function stopWorker(dir = dataDir()) {
|
|
|
17626
18524
|
}
|
|
17627
18525
|
if (readPidFile(dir) === pid) {
|
|
17628
18526
|
try {
|
|
17629
|
-
|
|
18527
|
+
fs29.rmSync(workerPidPath(dir), { force: true });
|
|
17630
18528
|
} catch {
|
|
17631
18529
|
}
|
|
17632
18530
|
}
|
|
@@ -17641,7 +18539,7 @@ var WorkerAlreadyRunningError = class extends Error {
|
|
|
17641
18539
|
function claimWorkerPidFile(dir, pid) {
|
|
17642
18540
|
const pidPath = workerPidPath(dir);
|
|
17643
18541
|
try {
|
|
17644
|
-
|
|
18542
|
+
fs29.writeFileSync(pidPath, `${pid}
|
|
17645
18543
|
`, { flag: "wx" });
|
|
17646
18544
|
return true;
|
|
17647
18545
|
} catch (e) {
|
|
@@ -17658,11 +18556,11 @@ function claimWorkerPidFile(dir, pid) {
|
|
|
17658
18556
|
}
|
|
17659
18557
|
}
|
|
17660
18558
|
try {
|
|
17661
|
-
|
|
18559
|
+
fs29.rmSync(pidPath, { force: true });
|
|
17662
18560
|
} catch {
|
|
17663
18561
|
}
|
|
17664
18562
|
try {
|
|
17665
|
-
|
|
18563
|
+
fs29.writeFileSync(pidPath, `${pid}
|
|
17666
18564
|
`, { flag: "wx" });
|
|
17667
18565
|
return true;
|
|
17668
18566
|
} catch (e2) {
|
|
@@ -17672,9 +18570,9 @@ function claimWorkerPidFile(dir, pid) {
|
|
|
17672
18570
|
}
|
|
17673
18571
|
function daemonEntryScript() {
|
|
17674
18572
|
const self = fileURLToPath2(import.meta.url);
|
|
17675
|
-
const launcher =
|
|
18573
|
+
const launcher = path29.join(path29.dirname(self), "token-goat.mjs");
|
|
17676
18574
|
try {
|
|
17677
|
-
if (
|
|
18575
|
+
if (fs29.existsSync(launcher)) return launcher;
|
|
17678
18576
|
} catch {
|
|
17679
18577
|
}
|
|
17680
18578
|
return self;
|
|
@@ -17685,7 +18583,7 @@ function startDetachedWorker(opts) {
|
|
|
17685
18583
|
try {
|
|
17686
18584
|
ensureDirSync(dir);
|
|
17687
18585
|
} catch (e) {
|
|
17688
|
-
if (e.code !== "EEXIST" || !
|
|
18586
|
+
if (e.code !== "EEXIST" || !fs29.existsSync(dir)) throw e;
|
|
17689
18587
|
}
|
|
17690
18588
|
const child = spawn2(
|
|
17691
18589
|
process.execPath,
|
|
@@ -17722,7 +18620,7 @@ async function runWorkerLoop(dir, pollIntervalMs, shouldStop = () => false) {
|
|
|
17722
18620
|
let lastKnownRootsSweepMs = 0;
|
|
17723
18621
|
let ownedPidFile = false;
|
|
17724
18622
|
while (!shouldStop()) {
|
|
17725
|
-
if (!
|
|
18623
|
+
if (!fs29.existsSync(dir)) break;
|
|
17726
18624
|
const pidOwner = readPidFile(dir);
|
|
17727
18625
|
if (pidOwner === process.pid) ownedPidFile = true;
|
|
17728
18626
|
else if (ownedPidFile && pidOwner !== null) break;
|
|
@@ -17747,7 +18645,7 @@ async function runWorkerLoop(dir, pollIntervalMs, shouldStop = () => false) {
|
|
|
17747
18645
|
}
|
|
17748
18646
|
if (Date.now() - lastKnownRootsSweepMs >= KNOWN_ROOTS_SWEEP_INTERVAL_MS) {
|
|
17749
18647
|
try {
|
|
17750
|
-
const result = sweepKnownRoots(
|
|
18648
|
+
const result = sweepKnownRoots(path29.join(dir, "global.db"));
|
|
17751
18649
|
if (result.flaggedRoots.length > 0) {
|
|
17752
18650
|
appendWorkerErrorLog(
|
|
17753
18651
|
dir,
|
|
@@ -17770,7 +18668,7 @@ function runDetachedWorkerDaemon() {
|
|
|
17770
18668
|
process.on("exit", () => {
|
|
17771
18669
|
if (readPidFile(dir) === process.pid) {
|
|
17772
18670
|
try {
|
|
17773
|
-
|
|
18671
|
+
fs29.rmSync(workerPidPath(dir), { force: true });
|
|
17774
18672
|
} catch {
|
|
17775
18673
|
}
|
|
17776
18674
|
}
|
|
@@ -17780,26 +18678,26 @@ function runDetachedWorkerDaemon() {
|
|
|
17780
18678
|
}
|
|
17781
18679
|
|
|
17782
18680
|
// src/hooks_index.ts
|
|
17783
|
-
import * as
|
|
17784
|
-
import * as
|
|
18681
|
+
import * as fs30 from "node:fs";
|
|
18682
|
+
import * as path30 from "node:path";
|
|
17785
18683
|
function dirtyQueuePath() {
|
|
17786
|
-
return
|
|
18684
|
+
return path30.join(dataDir(), "queue", "dirty.txt");
|
|
17787
18685
|
}
|
|
17788
18686
|
function appendDirtyPath(normalizedPath2) {
|
|
17789
18687
|
const queuePath = dirtyQueuePath();
|
|
17790
|
-
const dir =
|
|
18688
|
+
const dir = path30.dirname(queuePath);
|
|
17791
18689
|
try {
|
|
17792
18690
|
ensureDirSync(dir);
|
|
17793
18691
|
} catch (e) {
|
|
17794
|
-
if (e.code !== "EEXIST" || !
|
|
18692
|
+
if (e.code !== "EEXIST" || !fs30.existsSync(dir)) throw e;
|
|
17795
18693
|
}
|
|
17796
18694
|
let leadingNewline = "";
|
|
17797
18695
|
try {
|
|
17798
|
-
const existing =
|
|
18696
|
+
const existing = fs30.readFileSync(queuePath, "utf8");
|
|
17799
18697
|
if (existing.length > 0 && !existing.endsWith("\n")) leadingNewline = "\n";
|
|
17800
18698
|
} catch {
|
|
17801
18699
|
}
|
|
17802
|
-
|
|
18700
|
+
fs30.appendFileSync(queuePath, `${leadingNewline}${encodeDirtyQueueLine(normalizedPath2)}
|
|
17803
18701
|
`);
|
|
17804
18702
|
}
|
|
17805
18703
|
function enqueueDirtyPathSafe(filePath, opts) {
|
|
@@ -17812,7 +18710,7 @@ function getDirtyPaths() {
|
|
|
17812
18710
|
const queuePath = dirtyQueuePath();
|
|
17813
18711
|
let raw;
|
|
17814
18712
|
try {
|
|
17815
|
-
raw =
|
|
18713
|
+
raw = fs30.readFileSync(queuePath, "utf8");
|
|
17816
18714
|
} catch {
|
|
17817
18715
|
return [];
|
|
17818
18716
|
}
|
|
@@ -17821,9 +18719,9 @@ function getDirtyPaths() {
|
|
|
17821
18719
|
function preCompactIndexHandler(_event) {
|
|
17822
18720
|
const paths = getDirtyPaths();
|
|
17823
18721
|
if (paths.length > 0) {
|
|
17824
|
-
const sidecar =
|
|
18722
|
+
const sidecar = path30.join(dataDir(), "queue", "pending.txt");
|
|
17825
18723
|
try {
|
|
17826
|
-
ensureDirSync(
|
|
18724
|
+
ensureDirSync(path30.dirname(sidecar));
|
|
17827
18725
|
atomicWriteBytes(sidecar, Buffer.from(`${paths.join("\n")}
|
|
17828
18726
|
`, "utf8"));
|
|
17829
18727
|
} catch {
|
|
@@ -18017,6 +18915,8 @@ export {
|
|
|
18017
18915
|
getGlobMatchCount,
|
|
18018
18916
|
setLastTabContext,
|
|
18019
18917
|
getLastTabContext,
|
|
18918
|
+
hasSeenImage,
|
|
18919
|
+
recordSeenImage,
|
|
18020
18920
|
recordOutstandingAgentSpawn,
|
|
18021
18921
|
getOutstandingAgentSpawns,
|
|
18022
18922
|
removeOutstandingAgentSpawn,
|
|
@@ -18040,6 +18940,8 @@ export {
|
|
|
18040
18940
|
installCodex,
|
|
18041
18941
|
uninstallCodex,
|
|
18042
18942
|
isCodexInstalled,
|
|
18943
|
+
copilotCliUserRoot,
|
|
18944
|
+
copilotCliMcpToolsDir,
|
|
18043
18945
|
copilotCliConfigPath,
|
|
18044
18946
|
copilotCliScriptPath,
|
|
18045
18947
|
installCopilotCli,
|
|
@@ -18063,11 +18965,13 @@ export {
|
|
|
18063
18965
|
loadBlob,
|
|
18064
18966
|
listBlobs,
|
|
18065
18967
|
pruneBlobs,
|
|
18968
|
+
estimateTokensFromLength,
|
|
18066
18969
|
estimateTokens,
|
|
18067
18970
|
trimToBudget,
|
|
18068
18971
|
capJsonRows,
|
|
18069
18972
|
SESSIONS_SUBDIR,
|
|
18070
18973
|
AGENT_SALT_MARKER,
|
|
18974
|
+
sessionSidecarPath,
|
|
18071
18975
|
listSiblingSessionStates,
|
|
18072
18976
|
loadSessionState,
|
|
18073
18977
|
saveSessionState,
|
|
@@ -18106,6 +19010,7 @@ export {
|
|
|
18106
19010
|
formatShrinkSummary,
|
|
18107
19011
|
isImagePath,
|
|
18108
19012
|
probeImageMeta,
|
|
19013
|
+
imageQualifiesForShrink,
|
|
18109
19014
|
shrinkImage,
|
|
18110
19015
|
compactPathFor,
|
|
18111
19016
|
isCompactFresh,
|
|
@@ -18116,6 +19021,8 @@ export {
|
|
|
18116
19021
|
compactDoc,
|
|
18117
19022
|
OVER_FETCH_FACTOR,
|
|
18118
19023
|
MAX_OVER_FETCH,
|
|
19024
|
+
isAvailable,
|
|
19025
|
+
embeddingBackendLoadError,
|
|
18119
19026
|
searchSemantic,
|
|
18120
19027
|
mergeNearbyHits,
|
|
18121
19028
|
embeddingsDepsAvailable,
|