token-goat 2.9.12 → 2.9.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +20 -1
  2. package/dist/token-goat-chunk-2ESBO4IN.mjs +209 -0
  3. package/dist/token-goat-chunk-3BTK54F3.mjs +1733 -0
  4. package/dist/{token-goat-chunk-6NIGPPN6.mjs → token-goat-chunk-3XQPEJMV.mjs} +1 -1
  5. package/dist/token-goat-chunk-4SDX3QP3.mjs +122 -0
  6. package/dist/token-goat-chunk-7OGKZ7AP.mjs +142 -0
  7. package/dist/token-goat-chunk-AH6QILZM.mjs +26 -0
  8. package/dist/token-goat-chunk-AMYCQJX4.mjs +1548 -0
  9. package/dist/{token-goat-chunk-OTW7LC4Y.mjs → token-goat-chunk-ASVVF4JV.mjs} +13 -10
  10. package/dist/{token-goat-chunk-GCXX67HM.mjs → token-goat-chunk-DAMXYVIW.mjs} +12477 -12156
  11. package/dist/token-goat-chunk-DBNY4RLN.mjs +308 -0
  12. package/dist/token-goat-chunk-EG3663UT.mjs +226 -0
  13. package/dist/token-goat-chunk-EZNVAIR3.mjs +21 -0
  14. package/dist/token-goat-chunk-GMOUBOX4.mjs +386 -0
  15. package/dist/{token-goat-chunk-6F5TLJC7.mjs → token-goat-chunk-HF6H7RNK.mjs} +1 -1
  16. package/dist/{token-goat-chunk-GM6QZWCF.mjs → token-goat-chunk-IT6O3PNN.mjs} +2660 -6877
  17. package/dist/token-goat-chunk-NEI4NC54.mjs +424 -0
  18. package/dist/token-goat-chunk-O5WAMISC.mjs +167 -0
  19. package/dist/{token-goat-chunk-LHLQFGWQ.mjs → token-goat-chunk-OMNQUUIT.mjs} +9 -27
  20. package/dist/{token-goat-chunk-SF2CKCFQ.mjs → token-goat-chunk-PGGDW7DZ.mjs} +30 -13
  21. package/dist/token-goat-chunk-POBYR64E.mjs +1632 -0
  22. package/dist/token-goat-chunk-PRJVGIC5.mjs +58 -0
  23. package/dist/{token-goat-chunk-4CK445AW.mjs → token-goat-chunk-RUDOKYPJ.mjs} +10074 -9765
  24. package/dist/token-goat-chunk-SAQ5PG4L.mjs +2634 -0
  25. package/dist/token-goat-chunk-T2IWWTHB.mjs +3155 -0
  26. package/dist/{token-goat-chunk-TOGYS5A7.mjs → token-goat-chunk-VZYD4OZB.mjs} +1469 -5752
  27. package/dist/token-goat-chunk-XEH6KBWW.mjs +23 -0
  28. package/dist/token-goat-chunk-XQF5J25J.mjs +33 -0
  29. package/dist/token-goat-chunk-XTQAOTSO.mjs +89 -0
  30. package/dist/{token-goat-chunk-THLHC6QJ.mjs → token-goat-chunk-XVZ4MNQC.mjs} +614 -603
  31. package/dist/token-goat-chunk-Y4AFKTHK.mjs +22 -0
  32. package/dist/token-goat-chunk-YHGTGG6K.mjs +396 -0
  33. package/dist/{token-goat-chunk-SBYRP3X4.mjs → token-goat-chunk-YTQHZJXW.mjs} +61 -27
  34. package/dist/token-goat-chunk-Z6UXPYJA.mjs +62 -0
  35. package/dist/token-goat-chunk-ZD4EM4LR.mjs +30 -0
  36. package/dist/token-goat-chunk-ZFM4PWXL.mjs +585 -0
  37. package/dist/{token-goat-chunk-A6QTLAWO.mjs → token-goat-chunk-ZYNNQ36L.mjs} +128 -328
  38. package/dist/token-goat-hook.mjs +12 -6
  39. package/dist/token-goat.core.mjs +22 -7
  40. package/docs/cli.md +12 -8
  41. package/docs/security.md +1 -1
  42. package/package.json +4 -2
  43. package/dist/token-goat-chunk-HKFOH6JH.mjs +0 -28
  44. package/dist/token-goat-chunk-LOCOX2ML.mjs +0 -3564
  45. package/dist/token-goat-chunk-VRWX6QYW.mjs +0 -24
@@ -3,37 +3,41 @@ const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  PARSER_FINGERPRINT,
5
5
  enqueueDirtyPathSafe,
6
+ getTrackedFiles
7
+ } from "./token-goat-chunk-IT6O3PNN.mjs";
8
+ import {
6
9
  getProjectFileEntries,
7
- getTrackedFiles,
8
- indexRecallEntry,
10
+ indexRecallEntry
11
+ } from "./token-goat-chunk-VZYD4OZB.mjs";
12
+ import {
13
+ getDb,
9
14
  isBlobStale,
10
15
  loadBlob,
16
+ redactSecrets,
11
17
  storeBlob
12
- } from "./token-goat-chunk-GM6QZWCF.mjs";
18
+ } from "./token-goat-chunk-T2IWWTHB.mjs";
13
19
  import {
14
- estimateTokensFromLength,
15
20
  fingerprintFile,
16
- getDb,
17
21
  getDisplayRoot,
18
- redactSecrets,
19
22
  shortFingerprint
20
- } from "./token-goat-chunk-TOGYS5A7.mjs";
23
+ } from "./token-goat-chunk-SAQ5PG4L.mjs";
21
24
  import {
22
25
  registerReset
23
26
  } from "./token-goat-chunk-EEIDFMEM.mjs";
24
27
  import {
25
28
  SYMBOL_BODY_CHAR_CAP,
26
- compileGuardedRegex,
27
29
  countNoun,
28
30
  displaySafeJson,
29
- displaySafeText,
30
31
  extractErrorMessage,
31
32
  foldPath,
32
33
  globalDbPath,
33
34
  normalizePath,
34
35
  resolveIndexPath,
35
36
  toDisplayPath
36
- } from "./token-goat-chunk-LOCOX2ML.mjs";
37
+ } from "./token-goat-chunk-AMYCQJX4.mjs";
38
+ import {
39
+ compileGuardedRegex
40
+ } from "./token-goat-chunk-GMOUBOX4.mjs";
37
41
  import {
38
42
  init_define_import_meta_env
39
43
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -95,6 +99,73 @@ registerReset(() => {
95
99
  _urlIndex = /* @__PURE__ */ new Map();
96
100
  });
97
101
 
102
+ // src/stdin_json.ts
103
+ init_define_import_meta_env();
104
+ var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
105
+ var MAX_STDIN_WALL_MS = 6e4;
106
+ var MAX_STDIN_BYTES = 64 * 1024 * 1024;
107
+ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
108
+ return new Promise((resolve, reject) => {
109
+ const chunks = [];
110
+ let totalBytes = 0;
111
+ let settled = false;
112
+ const finish = (fn) => {
113
+ if (settled) return;
114
+ settled = true;
115
+ clearTimeout(idleTimer);
116
+ clearTimeout(wallTimer);
117
+ process.stdin.removeListener("data", onData);
118
+ process.stdin.removeListener("end", onEnd);
119
+ process.stdin.removeListener("error", onError);
120
+ fn();
121
+ };
122
+ let idleTimer = setTimeout(() => {
123
+ finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
124
+ }, timeoutMs);
125
+ const wallTimer = setTimeout(() => {
126
+ finish(() => {
127
+ process.stdin.destroy();
128
+ reject(new Error(`readStdinJson: stdin took longer than ${MAX_STDIN_WALL_MS} ms`));
129
+ });
130
+ }, MAX_STDIN_WALL_MS);
131
+ const onData = (chunk) => {
132
+ clearTimeout(idleTimer);
133
+ idleTimer = setTimeout(() => {
134
+ finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
135
+ }, timeoutMs);
136
+ totalBytes += chunk.length;
137
+ if (totalBytes > maxBytes) {
138
+ finish(() => {
139
+ process.stdin.destroy();
140
+ reject(new Error(`readStdinJson: stdin exceeded ${maxBytes} bytes`));
141
+ });
142
+ return;
143
+ }
144
+ chunks.push(chunk);
145
+ };
146
+ const onEnd = () => {
147
+ finish(() => {
148
+ const text = Buffer.concat(chunks).toString("utf8").trim();
149
+ if (text === "") {
150
+ reject(new Error("readStdinJson: empty stdin"));
151
+ return;
152
+ }
153
+ try {
154
+ resolve(JSON.parse(text));
155
+ } catch (err) {
156
+ reject(err instanceof Error ? err : new Error(String(err)));
157
+ }
158
+ });
159
+ };
160
+ const onError = (err) => {
161
+ finish(() => reject(err instanceof Error ? err : new Error(String(err))));
162
+ };
163
+ process.stdin.on("data", onData);
164
+ process.stdin.on("end", onEnd);
165
+ process.stdin.on("error", onError);
166
+ });
167
+ }
168
+
98
169
  // src/types.ts
99
170
  init_define_import_meta_env();
100
171
  var HOOK_EVENTS = [
@@ -175,9 +246,37 @@ function formatCommandManifest(manifest) {
175
246
  return lines.join("\n").trimEnd();
176
247
  }
177
248
 
249
+ // src/symbol_body_probe.ts
250
+ init_define_import_meta_env();
251
+ import * as fs from "fs";
252
+ var OVERSIZED_BODY_PROBE_SQL = `SELECT 1 FROM symbols WHERE LENGTH(body) > ${SYMBOL_BODY_CHAR_CAP} LIMIT 1`;
253
+ function checkSymbolBodySize(dbPath) {
254
+ if (!fs.existsSync(dbPath)) {
255
+ return { name: "Symbol body size", status: "ok", message: "no database yet" };
256
+ }
257
+ try {
258
+ const db = getDb(dbPath);
259
+ const row = db.prepare(OVERSIZED_BODY_PROBE_SQL).get();
260
+ if (row !== void 0) {
261
+ return {
262
+ name: "Symbol body size",
263
+ status: "warn",
264
+ message: `one or more stored symbol bodies exceed the ${SYMBOL_BODY_CHAR_CAP}-char cap enforced by boundSymbolBody -- likely a pre-fix leftover from a minified/generated file. A plain 'token-goat reclaim-index' (VACUUM only) CANNOT remove these rows -- it only reclaims freed pages, it never deletes row content. Only 'token-goat reclaim-index --rebuild' drops and re-derives them under the cap (stop the worker first with 'token-goat worker stop', since reclaim-index refuses to run while it's live); --rebuild reparses and re-embeds every indexed file across every project and can take a long time on a large multi-project index`
265
+ };
266
+ }
267
+ return { name: "Symbol body size", status: "ok", message: "no stored symbol body exceeds the cap" };
268
+ } catch (err) {
269
+ return {
270
+ name: "Symbol body size",
271
+ status: "warn",
272
+ message: `could not query symbol body size: ${extractErrorMessage(err)}`
273
+ };
274
+ }
275
+ }
276
+
178
277
  // src/reconcile.ts
179
278
  init_define_import_meta_env();
180
- import * as fs from "node:fs";
279
+ import * as fs2 from "node:fs";
181
280
  var DEFAULT_RECONCILE_BUDGET_MS = 1500;
182
281
  function readReconcileCursor(dbPath, root) {
183
282
  try {
@@ -298,7 +397,7 @@ function reconcileProject(opts = {}) {
298
397
  }
299
398
  let mtimeMs;
300
399
  try {
301
- mtimeMs = fs.statSync(file).mtimeMs;
400
+ mtimeMs = fs2.statSync(file).mtimeMs;
302
401
  } catch {
303
402
  changed.push(file);
304
403
  continue;
@@ -319,7 +418,17 @@ function reconcileProject(opts = {}) {
319
418
  const removed = [];
320
419
  if (!budgetExhausted && !trackedUnavailable) {
321
420
  for (const [folded, entry] of indexed) {
322
- if (!seenOnDisk.has(folded)) removed.push(entry.filePath);
421
+ if (seenOnDisk.has(folded)) continue;
422
+ if (Date.now() - startedAt > budgetMs) {
423
+ budgetExhausted = true;
424
+ removed.length = 0;
425
+ break;
426
+ }
427
+ try {
428
+ fs2.statSync(entry.filePath);
429
+ } catch {
430
+ removed.push(entry.filePath);
431
+ }
323
432
  }
324
433
  }
325
434
  let enqueued = 0;
@@ -349,305 +458,6 @@ function reconcileProject(opts = {}) {
349
458
  };
350
459
  }
351
460
 
352
- // src/stdin_json.ts
353
- init_define_import_meta_env();
354
- var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
355
- var MAX_STDIN_WALL_MS = 6e4;
356
- var MAX_STDIN_BYTES = 64 * 1024 * 1024;
357
- function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
358
- return new Promise((resolve, reject) => {
359
- const chunks = [];
360
- let totalBytes = 0;
361
- let settled = false;
362
- const finish = (fn) => {
363
- if (settled) return;
364
- settled = true;
365
- clearTimeout(idleTimer);
366
- clearTimeout(wallTimer);
367
- process.stdin.removeListener("data", onData);
368
- process.stdin.removeListener("end", onEnd);
369
- process.stdin.removeListener("error", onError);
370
- fn();
371
- };
372
- let idleTimer = setTimeout(() => {
373
- finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
374
- }, timeoutMs);
375
- const wallTimer = setTimeout(() => {
376
- finish(() => {
377
- process.stdin.destroy();
378
- reject(new Error(`readStdinJson: stdin took longer than ${MAX_STDIN_WALL_MS} ms`));
379
- });
380
- }, MAX_STDIN_WALL_MS);
381
- const onData = (chunk) => {
382
- clearTimeout(idleTimer);
383
- idleTimer = setTimeout(() => {
384
- finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
385
- }, timeoutMs);
386
- totalBytes += chunk.length;
387
- if (totalBytes > maxBytes) {
388
- finish(() => {
389
- process.stdin.destroy();
390
- reject(new Error(`readStdinJson: stdin exceeded ${maxBytes} bytes`));
391
- });
392
- return;
393
- }
394
- chunks.push(chunk);
395
- };
396
- const onEnd = () => {
397
- finish(() => {
398
- const text = Buffer.concat(chunks).toString("utf8").trim();
399
- if (text === "") {
400
- reject(new Error("readStdinJson: empty stdin"));
401
- return;
402
- }
403
- try {
404
- resolve(JSON.parse(text));
405
- } catch (err) {
406
- reject(err instanceof Error ? err : new Error(String(err)));
407
- }
408
- });
409
- };
410
- const onError = (err) => {
411
- finish(() => reject(err instanceof Error ? err : new Error(String(err))));
412
- };
413
- process.stdin.on("data", onData);
414
- process.stdin.on("end", onEnd);
415
- process.stdin.on("error", onError);
416
- });
417
- }
418
-
419
- // src/symbol_body_probe.ts
420
- init_define_import_meta_env();
421
- import * as fs2 from "fs";
422
- var OVERSIZED_BODY_PROBE_SQL = `SELECT 1 FROM symbols WHERE LENGTH(body) > ${SYMBOL_BODY_CHAR_CAP} LIMIT 1`;
423
- function checkSymbolBodySize(dbPath) {
424
- if (!fs2.existsSync(dbPath)) {
425
- return { name: "Symbol body size", status: "ok", message: "no database yet" };
426
- }
427
- try {
428
- const db = getDb(dbPath);
429
- const row = db.prepare(OVERSIZED_BODY_PROBE_SQL).get();
430
- if (row !== void 0) {
431
- return {
432
- name: "Symbol body size",
433
- status: "warn",
434
- message: `one or more stored symbol bodies exceed the ${SYMBOL_BODY_CHAR_CAP}-char cap enforced by boundSymbolBody -- likely a pre-fix leftover from a minified/generated file. A plain 'token-goat reclaim-index' (VACUUM only) CANNOT remove these rows -- it only reclaims freed pages, it never deletes row content. Only 'token-goat reclaim-index --rebuild' drops and re-derives them under the cap (stop the worker first with 'token-goat worker stop', since reclaim-index refuses to run while it's live); --rebuild reparses and re-embeds every indexed file across every project and can take a long time on a large multi-project index`
435
- };
436
- }
437
- return { name: "Symbol body size", status: "ok", message: "no stored symbol body exceeds the cap" };
438
- } catch (err) {
439
- return {
440
- name: "Symbol body size",
441
- status: "warn",
442
- message: `could not query symbol body size: ${extractErrorMessage(err)}`
443
- };
444
- }
445
- }
446
-
447
- // src/resident_context.ts
448
- init_define_import_meta_env();
449
- import * as fs3 from "node:fs";
450
- var LARGE_TASK_LIST_BYTES = 2e4;
451
- var LARGE_SKILL_BODY_BYTES = 2e4;
452
- var SKILL_BODY_REPEAT_THRESHOLD = 2;
453
- var RESIDENT_TAIL_MAX_BYTES = 1048576;
454
- function createResidentContextStats() {
455
- return {
456
- attachmentsByType: /* @__PURE__ */ new Map(),
457
- latestTaskList: null,
458
- taskReminderCount: 0,
459
- taskReminderBytes: 0,
460
- skillBodies: /* @__PURE__ */ new Map(),
461
- compactionCount: 0
462
- };
463
- }
464
- function bump(map, key, bytes) {
465
- const entry = map.get(key);
466
- if (entry === void 0) map.set(key, { count: 1, bytes });
467
- else {
468
- entry.count += 1;
469
- entry.bytes += bytes;
470
- }
471
- }
472
- function messageText(message) {
473
- if (message === null || typeof message !== "object") return "";
474
- const content = message["content"];
475
- if (typeof content === "string") return content;
476
- if (!Array.isArray(content)) return "";
477
- let out = "";
478
- for (const block of content) {
479
- if (block === null || typeof block !== "object") continue;
480
- const text = block["text"];
481
- if (typeof text === "string") out += text;
482
- }
483
- return out;
484
- }
485
- function skillNameFromBody(text) {
486
- const dir = /^Base directory for this skill:\s*(.+?)\s*$/m.exec(text);
487
- if (dir?.[1] !== void 0) {
488
- const segments = dir[1].split(/[\\/]/).filter((s) => s.length > 0);
489
- const last = segments[segments.length - 1];
490
- if (last !== void 0 && last.length > 0) return last;
491
- }
492
- const heading = /^#\s+(.+?)\s*$/m.exec(text);
493
- if (heading?.[1] !== void 0 && heading[1].length > 0) return heading[1];
494
- return null;
495
- }
496
- function readTaskList(attachment, bytes) {
497
- const snapshot = {
498
- bytes,
499
- itemCount: typeof attachment["itemCount"] === "number" ? attachment["itemCount"] : 0,
500
- completed: 0,
501
- inProgress: 0,
502
- pending: 0,
503
- descriptionBytes: 0,
504
- completedDescriptionBytes: 0
505
- };
506
- const content = attachment["content"];
507
- if (!Array.isArray(content)) return snapshot;
508
- if (content.length > snapshot.itemCount) snapshot.itemCount = content.length;
509
- for (const item of content) {
510
- if (item === null || typeof item !== "object") continue;
511
- const record = item;
512
- const status = typeof record["status"] === "string" ? record["status"] : "";
513
- const description = typeof record["description"] === "string" ? record["description"] : "";
514
- snapshot.descriptionBytes += description.length;
515
- if (status === "completed") {
516
- snapshot.completed += 1;
517
- snapshot.completedDescriptionBytes += description.length;
518
- } else if (status === "in_progress") snapshot.inProgress += 1;
519
- else if (status === "pending") snapshot.pending += 1;
520
- }
521
- return snapshot;
522
- }
523
- function collectInvokedSkills(acc, record) {
524
- const skills = record["skills"];
525
- if (!Array.isArray(skills)) return;
526
- for (const entry of skills) {
527
- if (entry === null || typeof entry !== "object") continue;
528
- const skill = entry;
529
- const content = skill["content"];
530
- if (typeof content !== "string" || content.length < LARGE_SKILL_BODY_BYTES) continue;
531
- const name = skillName(skill);
532
- if (name !== null) bump(acc.skillBodies, name, content.length);
533
- }
534
- }
535
- function skillName(skill) {
536
- const name = skill["name"];
537
- if (typeof name === "string" && name.trim() !== "") return name.trim();
538
- const path = skill["path"];
539
- if (typeof path !== "string") return null;
540
- const segments = path.split(/[\\/]/).filter((part) => part !== "");
541
- const last = segments[segments.length - 1];
542
- return last === void 0 || last === "" ? null : last;
543
- }
544
- function accumulateResidentLine(acc, parsed, bytes) {
545
- try {
546
- if (parsed === null || typeof parsed !== "object") return;
547
- const line = parsed;
548
- if (line["type"] === "system" && line["subtype"] === "compact_boundary") acc.compactionCount += 1;
549
- const attachment = line["attachment"];
550
- if (attachment !== null && typeof attachment === "object") {
551
- const record = attachment;
552
- const type = typeof record["type"] === "string" ? record["type"] : "unknown";
553
- bump(acc.attachmentsByType, type, bytes);
554
- if (type === "task_reminder") {
555
- acc.taskReminderCount += 1;
556
- acc.taskReminderBytes += bytes;
557
- acc.latestTaskList = readTaskList(record, bytes);
558
- }
559
- if (type === "invoked_skills") collectInvokedSkills(acc, record);
560
- }
561
- if (line["isMeta"] === true && line["type"] === "user") {
562
- const text = messageText(line["message"]);
563
- if (text.length >= LARGE_SKILL_BODY_BYTES) {
564
- const skill = skillNameFromBody(text);
565
- if (skill !== null) bump(acc.skillBodies, skill, text.length);
566
- }
567
- }
568
- } catch {
569
- }
570
- }
571
- function summarizeResidentContext(acc) {
572
- const attachmentClasses = [...acc.attachmentsByType.entries()].map(([type, v]) => ({ type, count: v.count, bytes: v.bytes })).sort((a, b) => b.bytes - a.bytes || a.type.localeCompare(b.type));
573
- const repeatedSkillBodies = [...acc.skillBodies.entries()].filter(([, v]) => v.count >= SKILL_BODY_REPEAT_THRESHOLD).map(([skill, v]) => ({ skill, count: v.count, bytes: v.bytes })).sort((a, b) => b.bytes - a.bytes || a.skill.localeCompare(b.skill));
574
- return {
575
- attachmentClasses,
576
- totalAttachmentBytes: attachmentClasses.reduce((n, c) => n + c.bytes, 0),
577
- latestTaskList: acc.latestTaskList,
578
- taskReminderCount: acc.taskReminderCount,
579
- taskReminderBytes: acc.taskReminderBytes,
580
- repeatedSkillBodies,
581
- compactionCount: acc.compactionCount
582
- };
583
- }
584
- function formatBytes(bytes) {
585
- if (bytes >= 1e6) return `${(bytes / 1048576).toFixed(1)} MB`;
586
- if (bytes >= 1e3) return `${Math.round(bytes / 1024)} KB`;
587
- return `${bytes} B`;
588
- }
589
- function formatTokenEstimate(tokens) {
590
- return tokens >= 1e3 ? `${Math.round(tokens / 1e3)}K` : String(tokens);
591
- }
592
- function taskListPruneHint(snapshot) {
593
- if (snapshot === null) return null;
594
- if (snapshot.bytes < LARGE_TASK_LIST_BYTES) return null;
595
- if (snapshot.completed === 0) return null;
596
- const tokens = formatTokenEstimate(estimateTokensFromLength(snapshot.bytes));
597
- const prunable = formatBytes(snapshot.completedDescriptionBytes);
598
- return `Your task list is ${formatBytes(snapshot.bytes)} (~${tokens} tok est) and is re-injected in full whenever it changes; ${snapshot.completed} of ${snapshot.itemCount} items are already completed and their descriptions alone are ${prunable}. Prune completed items or shorten their descriptions with TaskUpdate. Check ownership first if other agents share this list.`;
599
- }
600
- function repeatedSkillBodyHint(injections) {
601
- const worst = injections[0];
602
- if (worst === void 0) return null;
603
- if (worst.count < SKILL_BODY_REPEAT_THRESHOLD || worst.bytes < LARGE_SKILL_BODY_BYTES) return null;
604
- const skill = displaySafeText(worst.skill);
605
- const tokens = formatTokenEstimate(estimateTokensFromLength(worst.bytes));
606
- return `The \`${skill}\` skill body has been injected ${worst.count} times this session (${formatBytes(worst.bytes)} total, ~${tokens} tok est). Slash expansion and the Skill tool both send the whole body every time, and no hook can intercept either. If it is already loaded, work from it instead of re-invoking; to reread one part, use \`token-goat skill-section ${skill} '<heading>'\`.`;
607
- }
608
- function readTranscriptTail(transcriptPath, maxBytes = RESIDENT_TAIL_MAX_BYTES) {
609
- let fd = null;
610
- try {
611
- const stat = fs3.statSync(transcriptPath);
612
- if (!stat.isFile() || stat.size === 0) return [];
613
- const start = Math.max(0, stat.size - maxBytes);
614
- const length = stat.size - start;
615
- fd = fs3.openSync(transcriptPath, "r");
616
- const buf = Buffer.allocUnsafe(length);
617
- const read = fs3.readSync(fd, buf, 0, length, start);
618
- const lines = buf.subarray(0, read).toString("utf8").split("\n");
619
- if (start > 0) lines.shift();
620
- return lines;
621
- } catch {
622
- return [];
623
- } finally {
624
- if (fd !== null) {
625
- try {
626
- fs3.closeSync(fd);
627
- } catch {
628
- }
629
- }
630
- }
631
- }
632
- function lineMayCarryResidentSignal(line) {
633
- return line.includes('"task_reminder"') || line.includes('"compact_boundary"') || line.includes('"invoked_skills"') || line.includes('"isMeta"');
634
- }
635
- function accumulateResidentLines(lines) {
636
- const acc = createResidentContextStats();
637
- for (const line of lines) {
638
- const trimmed = line.trim();
639
- if (trimmed === "") continue;
640
- let parsed;
641
- try {
642
- parsed = JSON.parse(trimmed);
643
- } catch {
644
- continue;
645
- }
646
- accumulateResidentLine(acc, parsed, trimmed.length);
647
- }
648
- return acc;
649
- }
650
-
651
461
  export {
652
462
  checkSymbolBodySize,
653
463
  WEB_OUTPUT_SUBDIR,
@@ -655,25 +465,15 @@ export {
655
465
  getWebOutput,
656
466
  getWebOutputRaw,
657
467
  getWebOutputByUrlFromDisk,
658
- HOOK_EVENTS,
659
- buildCommandManifest,
660
- flattenCommandNames,
661
- filterCommandManifest,
662
- formatCommandManifest,
663
468
  DEFAULT_RECONCILE_BUDGET_MS,
664
469
  runReconcile,
665
470
  isReconcileClean,
666
471
  reconcileProject,
667
- createResidentContextStats,
668
- accumulateResidentLine,
669
- summarizeResidentContext,
670
- formatBytes,
671
- formatTokenEstimate,
672
- taskListPruneHint,
673
- repeatedSkillBodyHint,
674
- readTranscriptTail,
675
- lineMayCarryResidentSignal,
676
- accumulateResidentLines,
677
472
  MAX_STDIN_BYTES,
678
- readStdinJson
473
+ readStdinJson,
474
+ HOOK_EVENTS,
475
+ buildCommandManifest,
476
+ flattenCommandNames,
477
+ filterCommandManifest,
478
+ formatCommandManifest
679
479
  };
@@ -2,13 +2,19 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  relayInProcess
5
- } from "./token-goat-chunk-THLHC6QJ.mjs";
6
- import "./token-goat-chunk-6NIGPPN6.mjs";
7
- import "./token-goat-chunk-A6QTLAWO.mjs";
8
- import "./token-goat-chunk-GM6QZWCF.mjs";
9
- import "./token-goat-chunk-TOGYS5A7.mjs";
5
+ } from "./token-goat-chunk-XVZ4MNQC.mjs";
6
+ import "./token-goat-chunk-3XQPEJMV.mjs";
7
+ import "./token-goat-chunk-ZYNNQ36L.mjs";
8
+ import "./token-goat-chunk-IT6O3PNN.mjs";
9
+ import "./token-goat-chunk-VZYD4OZB.mjs";
10
+ import "./token-goat-chunk-3BTK54F3.mjs";
11
+ import "./token-goat-chunk-Y4AFKTHK.mjs";
12
+ import "./token-goat-chunk-T2IWWTHB.mjs";
13
+ import "./token-goat-chunk-SAQ5PG4L.mjs";
10
14
  import "./token-goat-chunk-EEIDFMEM.mjs";
11
- import "./token-goat-chunk-LOCOX2ML.mjs";
15
+ import "./token-goat-chunk-POBYR64E.mjs";
16
+ import "./token-goat-chunk-AMYCQJX4.mjs";
17
+ import "./token-goat-chunk-GMOUBOX4.mjs";
12
18
  import {
13
19
  init_define_import_meta_env
14
20
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -2,16 +2,31 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./token-goat-chunk-GCXX67HM.mjs";
6
- import "./token-goat-chunk-4CK445AW.mjs";
7
- import "./token-goat-chunk-6F5TLJC7.mjs";
8
- import "./token-goat-chunk-A6QTLAWO.mjs";
9
- import "./token-goat-chunk-GM6QZWCF.mjs";
10
- import "./token-goat-chunk-TOGYS5A7.mjs";
5
+ } from "./token-goat-chunk-DAMXYVIW.mjs";
6
+ import "./token-goat-chunk-RUDOKYPJ.mjs";
7
+ import "./token-goat-chunk-ZYNNQ36L.mjs";
8
+ import "./token-goat-chunk-IT6O3PNN.mjs";
9
+ import "./token-goat-chunk-VZYD4OZB.mjs";
10
+ import "./token-goat-chunk-NEI4NC54.mjs";
11
+ import "./token-goat-chunk-4SDX3QP3.mjs";
12
+ import "./token-goat-chunk-2ESBO4IN.mjs";
13
+ import "./token-goat-chunk-ZFM4PWXL.mjs";
14
+ import "./token-goat-chunk-EG3663UT.mjs";
15
+ import "./token-goat-chunk-3BTK54F3.mjs";
16
+ import "./token-goat-chunk-YHGTGG6K.mjs";
17
+ import "./token-goat-chunk-XTQAOTSO.mjs";
18
+ import "./token-goat-chunk-AH6QILZM.mjs";
19
+ import "./token-goat-chunk-Y4AFKTHK.mjs";
20
+ import "./token-goat-chunk-DBNY4RLN.mjs";
21
+ import "./token-goat-chunk-T2IWWTHB.mjs";
22
+ import "./token-goat-chunk-SAQ5PG4L.mjs";
11
23
  import "./token-goat-chunk-EEIDFMEM.mjs";
24
+ import "./token-goat-chunk-HF6H7RNK.mjs";
25
+ import "./token-goat-chunk-POBYR64E.mjs";
12
26
  import {
13
27
  installEpipeGuard
14
- } from "./token-goat-chunk-LOCOX2ML.mjs";
28
+ } from "./token-goat-chunk-AMYCQJX4.mjs";
29
+ import "./token-goat-chunk-GMOUBOX4.mjs";
15
30
  import {
16
31
  init_define_import_meta_env
17
32
  } from "./token-goat-chunk-A37V4PBF.mjs";
package/docs/cli.md CHANGED
@@ -66,6 +66,7 @@ token-goat pdf-extract manual.pdf --pages 12-15 --layout --head 120
66
66
  | `token-goat types ["file"]` | List type definitions (TypedDict, Protocol, dataclass, Pydantic models) in a file or across the project. `--grep <pattern>` only shows type declarations whose NAME matches this regex (literal substring if it is not valid regex), applied before output is built; when it matches nothing among declarations that do exist, the output names the active filter instead of reading like there are none. `--exclude-tests` hides type declarations DEFINED in a test file (opt-in — omitted, output is unchanged), the same definition-site sense `dead --exclude-tests` uses. Applied before the per-kind `--limit` slice, so the flag selects from the whole matching set rather than an already-capped page; when it hides every declaration there was, the output names how many were hidden and exits 0, instead of the exit-1 `No type declarations found` a genuinely empty scope returns. `--json`'s `filePath` renders root-relative when a project root resolves, absolute when none does, matching plain-text output. `--json` emits the shared `{items, truncated, totalCount}` envelope — the same shape `symbol`/`refs`/`skeleton`/`outline --json` return, present whether or not truncation occurred, so a script never has to branch on shape. |
67
67
  | `token-goat openapi-outline <spec>` | Per-operation listing (method, path, operationId, summary, tags) of an OpenAPI 3.x / Swagger 2.0 spec (JSON or YAML) instead of a raw Read. |
68
68
  | `token-goat openapi-op <spec> <operation>` | Full detail (parameters, request body schema, response schemas, description) for exactly one OpenAPI operation instead of a raw Read. `operation` may be an operationId (exact match) or a `"METHOD path"` spec, e.g. `"GET /users/{id}"`. |
69
+ | `token-goat sqlite-tables <db> [--json]` | Ultra-compact overview of tables, views, row counts, and column counts in a SQLite database instead of a raw Read. |
69
70
  | `token-goat sqlite-schema <db>` | Tables/views, columns, indexes, foreign keys, and row counts of a SQLite database instead of a raw Read. |
70
71
  | `token-goat sqlite-query <db> "<SELECT ...>"` | Run a read-only `SELECT` against a SQLite database instead of a raw Read or shelling out to `sqlite3` — rejects any non-`SELECT` statement. |
71
72
  | `token-goat imports "file"` | Show the import graph for a file one level deep. Accepts a comma-separated file list (`"a,b,c"`) to cover several files in one call, one clearly-headed block per file; extra space-separated file arguments are reported in a note naming that comma form instead of being silently dropped. `--grep <pattern>` only shows imports whose MODULE SPECIFIER matches this regex (literal substring if it is not valid regex), applied before `--json`'s truncation; when it matches nothing among real imports, the output names the active filter instead of reading like the file has no imports at all. |
@@ -123,20 +124,22 @@ token-goat pdf-extract manual.pdf --pages 12-15 --layout --head 120
123
124
  | `token-goat pdf-outline <file>` | List a PDF's bookmark/outline tree with page numbers instead of a raw Read. |
124
125
  | `token-goat pdf-meta <file> [--json]` | Page count, title/author, and whether a PDF has an extractable text layer (so you know before extracting whether it's scanned/image-only). `--json` emits `{ pageCount, title, author, hasTextLayer }` — `hasTextLayer` as a real boolean rather than a prose sentence, and an absent title/author as `null` rather than the literal `(none)`. |
125
126
  | `token-goat image-meta <file> [--json]` | Dimensions, format, byte size, and what a `shrinkImage` pass would cost — a cheap "should I even look at this" probe that reads image metadata only and never runs OCR. Needs no optional package: the image pipeline is pure TypeScript and ships in the bundle. Says so plainly when the bytes are not a format token-goat can read. |
126
- | `token-goat image-text <file> [--json]` | OCR text for an image instead of a raw Read. Reports confidence and character count either way; below the usefulness threshold it says so plainly instead of printing low-confidence noise as content. Requires `tesseract.js`; degrades with a clear message when it's missing. |
127
+ | `token-goat image-text <file> [--lang <lang>] [--json]` | OCR text for an image instead of a raw Read. Reports confidence and character count either way; below the usefulness threshold it says so plainly instead of printing low-confidence noise as content. English (`eng`) is always included; accepts `--lang <lang>` (e.g. `--lang fra` or `--lang "fra,spa"`) for opt-in multilingual extraction (`eng`, `fra`, `spa`, `deu`, `ita`, `por`, `nld`, `pol`, `rus`, `tur`, `swe`, `ara`, `chi_sim`, `chi_tra`, `jpn`, `kor`), defaulting to `image_shrink.ocr_lang` in `config.toml` (persistent across upgrades) or `.token-goat.toml`. Requires `tesseract.js`; degrades with a clear message when it's missing. |
127
128
  | `token-goat csv-query <file>` | Project columns and/or filter rows from a CSV instead of a raw Read. `--columns <cols>` selects a comma-separated subset; `--where <spec>` is repeatable and ANDed, supporting `col=value`, `col!=value`, `col>value`, `col<value`, and `col~=regex`; `--head <n>` caps rows; `--json` emits rows as a JSON array of objects instead of a formatted table; `--delimiter <char>` and `--no-header` handle non-comma or headerless files. |
128
129
  | `token-goat csv-profile <file>` | Per-column type inference (number/date/string), null/distinct counts, and min/max or top values for low-cardinality columns, instead of a raw Read. Same `--delimiter`/`--no-header` flags as `csv-query`. |
129
130
  | `token-goat sharepoint-resolve <shareUrl>` | Best-effort resolve a SharePoint/OneDrive sharing URL to a local synced file path, purely from the local filesystem and `OneDrive`/`OneDriveCommercial` env vars -- no network call, no Graph API, no credentials. Prints the resolved path (feed it to `xlsx-sheets`/`pptx-outline`/etc.) or an honest "could not resolve" with the paths it tried. |
130
131
  | `token-goat video-chapters <file>` | Lists a video's embedded chapter markers (timestamps + titles) and subtitle/caption streams via `ffprobe`, instead of downloading/transcoding the file to inspect it. Requires ffmpeg on PATH; degrades with a clear message when it's missing. |
131
132
  | `token-goat xlsx-sheets <file> [--json]` | List sheet names, used range, and dimensions in an Excel workbook instead of a raw Read. `--json` emits `{ name, ref, rows, cols }[]`, so a sheet name can be fed straight into the `--sheet` of `xlsx-head`/`xlsx-range`/`xlsx-query` instead of being parsed back out of the text line. |
132
- | `token-goat xlsx-head <file> --sheet <name>` | Preview the header + first N rows of one sheet (`--rows`, default 20) instead of a raw Read. |
133
- | `token-goat xlsx-range <file> --sheet <name> --range <a1>` | Extract one cell range (e.g. `A1:D50`) from a sheet; `--formulas` shows formulas instead of computed values. |
134
- | `token-goat xlsx-query <file> --sheet <name>` | Project columns / filter rows from one sheet instead of a raw Read (same `--columns`/`--where`/`--head` shape as `csv-query`, via the sheet's CSV projection). |
133
+ | `token-goat xlsx-columns <file> [--sheet <name>] [--head <n>] [--json]` | Column names, column letters, fill rates, and sample values across wide spreadsheets instead of a raw Read. `--sheet` defaults to the first sheet if omitted. |
134
+ | `token-goat xlsx-head <file> [--sheet <name>]` | Preview the header + first N rows of one sheet (`--rows`, default 20) instead of a raw Read. `--columns <a,b,c>` projects only the specified column names or letters. `--sheet` defaults to the first sheet if omitted. |
135
+ | `token-goat xlsx-range <file> [--sheet <name>] --range <a1>` | Extract one cell range (e.g. `A1:D50`) from a sheet; `--formulas` shows formulas instead of computed values. `--sheet` defaults to the first sheet if omitted. |
136
+ | `token-goat xlsx-query <file> [--sheet <name>]` | Project columns / filter rows from one sheet instead of a raw Read (same `--columns`/`--where`/`--head` shape as `csv-query`, via the sheet's CSV projection). `--sheet` defaults to the first sheet if omitted. |
135
137
  | `token-goat pptx-outline <file>` | Per-slide title, body size, and speaker-notes flag instead of a raw Read. |
136
138
  | `token-goat pptx-slide <file> --slide <n>` | Full text of one slide; `--notes` appends that slide's speaker notes. |
137
139
  | `token-goat pptx-notes <file>` | Speaker notes for one slide (`--slide <n>`) or all slides, instead of a raw Read. |
138
140
  | `token-goat pptx-text <file> --grep <pattern>` | Find slides whose text matches a pattern instead of a raw Read. |
139
141
  | `token-goat docx-outline <file>` | Heading tree of a Word document instead of a raw Read. |
142
+ | `token-goat docx-tables <file>` | Extract tables from a Word document as Markdown tables instead of a raw Read; `--table <n>` selects a specific table (1-based), `--json` outputs structured table data. |
140
143
  | `token-goat docx-text <file>` | Full body text of a Word document instead of a raw Read; `--head`/`--tail`/`--grep`/`--section`/`--max-matches` slice it the same way `pdf-extract` does. |
141
144
  | `token-goat transcript-outline <file>` | Speaker list, duration, and time-bucketed markers for a WebVTT/SRT transcript instead of a raw Read. |
142
145
  | `token-goat transcript <file>` | Slice a WebVTT/SRT transcript by `--speaker <name>`, `--from`/`--to <hh:mm:ss>`, and/or `--grep <pattern>` instead of a raw Read. |
@@ -170,6 +173,7 @@ token-goat pdf-extract manual.pdf --pages 12-15 --layout --head 120
170
173
  | `token-goat project exclude <path>` | Add a project root to the blocklist so the worker never indexes it. Writes the resolved absolute path to `[worker] blocked_roots` in `config.toml`; idempotent. It also removes anything already indexed under that path and says how many files went, so excluding a directory means its contents stop being readable through `symbol` rather than merely stopping future indexing. Remove the entry from the config to re-enable indexing, then run `token-goat index` to bring the contents back. |
171
174
  | `token-goat project prune [--dry-run]` | Remove blocked/excluded roots that no longer exist on disk. `--dry-run` previews removals without touching the config file. Useful after deleting or moving projects. |
172
175
  | `token-goat install` | Wire up hooks (and, with the harness flags below, other AI tool integrations). No `--dry-run` or `--verify` flag — run `token-goat doctor` after install to audit the result. |
176
+ | `token-goat upgrade` | Check for updates or upgrade token-goat to the latest version via npm. Pass `--check` to report status without installing. `--json` emits machine-readable version status. |
173
177
  | `token-goat doctor` | Confirm everything is wired correctly. Surfaces install state, cold-import timing, cache hit rates, compaction-budget telemetry, opt-in flag status, and canonical-root sanity. A **Parser freshness** check reports how much of the index for this project was written by a different build of the extractor. The stamp only refreshes when something touches a file, so after an upgrade a project goes on answering `symbol`, `read`, `outline` and `skeleton` from the previous extractor with nothing saying so. It warns past a quarter and names the fix: a plain `token-goat index` in that project, which is enough on its own, since a parser mismatch reparses without `--force`. A **Tool names** check reports any tool name a harness sent that reached no handler wanting it, and calls out the ones that differ from a handled name only by capitalisation or punctuation — the signature of a bridge that forgot to rename something, which is otherwise invisible. A **Security** section reports the posture in one place: whether offline mode is on, whether injection scanning is on, whether the Google Drive integration is enabled, whether fetching runs against an allow list or a deny list, whether MCP reads are confined to the project root, and whether the data directory is readable by other local users. It only warns when a protection that ships on has been switched off, so a default install stays quiet. It read-only audits `~/.copilot/mcp-config.json` for globally configured Chrome DevTools or Playwright `npx` launchers, recommending project scope or removal when inactive; it never prints server configuration or secrets. On Windows, it also reports duplicate Chrome DevTools/Playwright MCP launchers and orphaned Node processes without terminating anything. Pass `--context` to show the **Context footprint** section: a fill bar with severity (ok / warn / high / URGENT), per-component breakdown (skills catalog, loaded skill bodies, CLAUDE.md+MEMORY.md, conversation estimate), session-to-session growth trend with sessions-to-URGENT projection, and tiered compaction recommendations (Tier 0–4) naming the exact commands to run. Auto-shown when fill > 40 % or any loaded skill > 2 K tokens lacks a compact. `--json` emits the check results (one entry per check, with `ok`/`warn`/`fail` status) as JSON instead of text. |
174
178
  | `token-goat capabilities` | List every capability that can send data off this machine or leave data on it, with whether it is currently on, the config key that decides that, and the exact `file::symbol` where the decision is made — so a reviewer can open the code rather than take the list's word for it. `--json` emits the same thing for a pipeline to assert on, which is the point: the answer comes from the binary installed on your machine, not from documentation that may describe a different build. A test in the suite fails the build when a module that can open a network connection is missing from this list, and equally when the list names one that no longer connects anywhere. |
175
179
  | `token-goat baseline` | Emit a project map: file count, per-language file counts, the top indexed symbols (by name/kind/location), and the most recently modified files. `--subagent` emits a terser variant (fewer symbols, fewer recent files) for context handed to a freshly spawned subagent; `--json` for the machine-readable form. |
@@ -360,13 +364,13 @@ case filter in out saved fidelity
360
364
  ---------------------------------------------------------------
361
365
  git-log-stat git-log 57227 3027 94.7% 2/2
362
366
  npm-ls-all dep-list 34546 1060 96.9% 2/2
363
- vitest-run vitest 22684 22682 - 2/2
367
+ vitest-run vitest 22684 322 98.6% 2/2
364
368
  ---------------------------------------------------------------
365
- TOTAL 114457 26769 76.6% 6/6
369
+ TOTAL 114457 4409 96.1% 6/6
366
370
 
367
- ratio 76.6% saved (PRIMARY -- must improve; measured floor 0.0%, headroom 23.4%)
371
+ ratio 96.1% saved (PRIMARY -- must improve; measured floor 0.0%, headroom 3.9%)
368
372
  fidelity 6/6 kept (GUARD -- must not regress; any miss exits 1)
369
- coverage 3/157 filters exercised, 2/3 cases compressed
373
+ coverage 3/157 filters exercised, 3/3 cases compressed
370
374
  ```
371
375
 
372
376
  There are two numbers on purpose. **Ratio** is the thing to push up. **Fidelity** counts the lines