token-goat 2.9.12 → 2.9.13

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 (25) hide show
  1. package/README.md +7 -1
  2. package/dist/{token-goat-chunk-6NIGPPN6.mjs → token-goat-chunk-2WC4ZUXN.mjs} +1 -1
  3. package/dist/{token-goat-chunk-GCXX67HM.mjs → token-goat-chunk-3ESRORNM.mjs} +188 -97
  4. package/dist/token-goat-chunk-4NXUKV7D.mjs +56 -0
  5. package/dist/{token-goat-chunk-OTW7LC4Y.mjs → token-goat-chunk-6B44WLIF.mjs} +5 -5
  6. package/dist/{token-goat-chunk-6F5TLJC7.mjs → token-goat-chunk-B3CTCQTH.mjs} +1 -1
  7. package/dist/{token-goat-chunk-A6QTLAWO.mjs → token-goat-chunk-FQCNJV4V.mjs} +20 -6
  8. package/dist/{token-goat-chunk-SBYRP3X4.mjs → token-goat-chunk-FZU7GMUS.mjs} +49 -24
  9. package/dist/{token-goat-chunk-TOGYS5A7.mjs → token-goat-chunk-JOXLE672.mjs} +344 -5699
  10. package/dist/{token-goat-chunk-HKFOH6JH.mjs → token-goat-chunk-JVNPCQB7.mjs} +10 -7
  11. package/dist/{token-goat-chunk-LHLQFGWQ.mjs → token-goat-chunk-OMNQUUIT.mjs} +9 -27
  12. package/dist/{token-goat-chunk-VRWX6QYW.mjs → token-goat-chunk-P2PU4CR5.mjs} +8 -6
  13. package/dist/{token-goat-chunk-LOCOX2ML.mjs → token-goat-chunk-QKXBGBQR.mjs} +110 -21
  14. package/dist/{token-goat-chunk-THLHC6QJ.mjs → token-goat-chunk-QWSUZWFP.mjs} +44 -33
  15. package/dist/{token-goat-chunk-SF2CKCFQ.mjs → token-goat-chunk-RDITECDL.mjs} +7 -5
  16. package/dist/{token-goat-chunk-4CK445AW.mjs → token-goat-chunk-U7X6LQD2.mjs} +271 -69
  17. package/dist/token-goat-chunk-UM47DRD3.mjs +242 -0
  18. package/dist/token-goat-chunk-UMXJN7DI.mjs +5521 -0
  19. package/dist/token-goat-chunk-YOA4N6WA.mjs +308 -0
  20. package/dist/{token-goat-chunk-GM6QZWCF.mjs → token-goat-chunk-ZZI3IDQZ.mjs} +963 -966
  21. package/dist/token-goat-hook.mjs +8 -6
  22. package/dist/token-goat.core.mjs +10 -7
  23. package/docs/cli.md +11 -8
  24. package/docs/security.md +1 -1
  25. package/package.json +1 -1
@@ -9,7 +9,7 @@ import {
9
9
  stripMultilineStringSpan,
10
10
  stripNestedBlockCommentSpan,
11
11
  stripStringLiterals
12
- } from "./token-goat-chunk-LOCOX2ML.mjs";
12
+ } from "./token-goat-chunk-QKXBGBQR.mjs";
13
13
  import {
14
14
  init_define_import_meta_env
15
15
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -5,19 +5,23 @@ import {
5
5
  enqueueDirtyPathSafe,
6
6
  getProjectFileEntries,
7
7
  getTrackedFiles,
8
- indexRecallEntry,
8
+ indexRecallEntry
9
+ } from "./token-goat-chunk-ZZI3IDQZ.mjs";
10
+ import {
11
+ estimateTokensFromLength
12
+ } from "./token-goat-chunk-JOXLE672.mjs";
13
+ import {
9
14
  isBlobStale,
10
15
  loadBlob,
11
16
  storeBlob
12
- } from "./token-goat-chunk-GM6QZWCF.mjs";
17
+ } from "./token-goat-chunk-UM47DRD3.mjs";
13
18
  import {
14
- estimateTokensFromLength,
15
19
  fingerprintFile,
16
20
  getDb,
17
21
  getDisplayRoot,
18
22
  redactSecrets,
19
23
  shortFingerprint
20
- } from "./token-goat-chunk-TOGYS5A7.mjs";
24
+ } from "./token-goat-chunk-UMXJN7DI.mjs";
21
25
  import {
22
26
  registerReset
23
27
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -33,7 +37,7 @@ import {
33
37
  normalizePath,
34
38
  resolveIndexPath,
35
39
  toDisplayPath
36
- } from "./token-goat-chunk-LOCOX2ML.mjs";
40
+ } from "./token-goat-chunk-QKXBGBQR.mjs";
37
41
  import {
38
42
  init_define_import_meta_env
39
43
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -319,7 +323,17 @@ function reconcileProject(opts = {}) {
319
323
  const removed = [];
320
324
  if (!budgetExhausted && !trackedUnavailable) {
321
325
  for (const [folded, entry] of indexed) {
322
- if (!seenOnDisk.has(folded)) removed.push(entry.filePath);
326
+ if (seenOnDisk.has(folded)) continue;
327
+ if (Date.now() - startedAt > budgetMs) {
328
+ budgetExhausted = true;
329
+ removed.length = 0;
330
+ break;
331
+ }
332
+ try {
333
+ fs.statSync(entry.filePath);
334
+ } catch {
335
+ removed.push(entry.filePath);
336
+ }
323
337
  }
324
338
  }
325
339
  let enqueued = 0;
@@ -25,8 +25,7 @@ import {
25
25
  runSkeleton,
26
26
  runSymbol,
27
27
  withPinnedReads
28
- } from "./token-goat-chunk-4CK445AW.mjs";
29
- import "./token-goat-chunk-6F5TLJC7.mjs";
28
+ } from "./token-goat-chunk-U7X6LQD2.mjs";
30
29
  import {
31
30
  buildProjectMap,
32
31
  embeddingsDepsAvailable,
@@ -35,7 +34,10 @@ import {
35
34
  getProjectIndexCounts,
36
35
  isWorkerRunning,
37
36
  mapLookupBytesSaved
38
- } from "./token-goat-chunk-GM6QZWCF.mjs";
37
+ } from "./token-goat-chunk-ZZI3IDQZ.mjs";
38
+ import "./token-goat-chunk-JOXLE672.mjs";
39
+ import "./token-goat-chunk-YOA4N6WA.mjs";
40
+ import "./token-goat-chunk-UM47DRD3.mjs";
39
41
  import {
40
42
  envStrList,
41
43
  getDb,
@@ -43,8 +45,9 @@ import {
43
45
  recordStat,
44
46
  resolveProjectRoot,
45
47
  savedTokensFromBytes
46
- } from "./token-goat-chunk-TOGYS5A7.mjs";
48
+ } from "./token-goat-chunk-UMXJN7DI.mjs";
47
49
  import "./token-goat-chunk-EEIDFMEM.mjs";
50
+ import "./token-goat-chunk-B3CTCQTH.mjs";
48
51
  import {
49
52
  ENV_KEYS,
50
53
  VERSION,
@@ -54,7 +57,7 @@ import {
54
57
  foldCaseForContainment,
55
58
  globalDbPath,
56
59
  normalizePath
57
- } from "./token-goat-chunk-LOCOX2ML.mjs";
60
+ } from "./token-goat-chunk-QKXBGBQR.mjs";
58
61
  import {
59
62
  init_define_import_meta_env
60
63
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -242,7 +245,7 @@ function mcpToolAllowlist() {
242
245
  return names.length === 0 ? null : new Set(names);
243
246
  }
244
247
  async function createMcpServer() {
245
- const [{ McpServer }, { z }] = await Promise.all([import("./token-goat-chunk-LHLQFGWQ.mjs"), import("./token-goat-chunk-T6M7DAW3.mjs")]);
248
+ const [{ McpServer }, { z }] = await Promise.all([import("./token-goat-chunk-OMNQUUIT.mjs"), import("./token-goat-chunk-T6M7DAW3.mjs")]);
246
249
  const server = new McpServer({ name: "token-goat", version: VERSION });
247
250
  const allowlist = mcpToolAllowlist();
248
251
  if (allowlist !== null) {
@@ -276,7 +279,8 @@ async function createMcpServer() {
276
279
  kind: z.string().optional().describe("restrict to one kind (function, class, ...)"),
277
280
  json: z.boolean().optional().describe("output as JSON"),
278
281
  projectRoot: projectRootField
279
- }
282
+ },
283
+ annotations: { readOnlyHint: true, openWorldHint: false }
280
284
  },
281
285
  (args) => {
282
286
  const { name, limit, file, kind, json, projectRoot } = args;
@@ -314,7 +318,9 @@ async function createMcpServer() {
314
318
  forceRefresh: z.boolean().optional().describe("reparse file from disk before querying (ignore stale index)"),
315
319
  stats: z.boolean().optional().describe("add per-symbol reference count and doc-coverage flag"),
316
320
  projectRoot: projectRootField
317
- }
321
+ },
322
+ // What readOnlyHint claims here, once, for all fifteen tools that carry it: the caller's own environment is untouched -- the project's files, and anything the caller would notice missing. Three things these tools do are deliberately outside that. They append a row to token-goat's `stats` table. They may reparse a file whose index entry is stale, and enqueue it for the background indexer. Opening a database for the first time creates its schema. All three are token-goat's own derived state, rebuilt from the project on demand and worth nothing if deleted; calling a symbol lookup a writing tool on their account would cost the caller a confirmation prompt for every read while protecting nothing. A tool that touches anything the caller owns says so instead, and the guard beside this file measures which tools those are rather than trusting the claim.
323
+ annotations: { readOnlyHint: true, openWorldHint: false }
318
324
  },
319
325
  (args) => {
320
326
  const { spec, json, forceRefresh, stats, projectRoot } = args;
@@ -343,7 +349,8 @@ async function createMcpServer() {
343
349
  spec: z.string().describe("file::Heading"),
344
350
  json: z.boolean().optional().describe("output as JSON"),
345
351
  projectRoot: projectRootField
346
- }
352
+ },
353
+ annotations: { readOnlyHint: true, openWorldHint: false }
347
354
  },
348
355
  (args) => {
349
356
  const { spec, json, projectRoot } = args;
@@ -373,7 +380,8 @@ async function createMcpServer() {
373
380
  forceRefresh: z.boolean().optional().describe("reparse file from disk before querying (ignore stale index)"),
374
381
  stats: z.boolean().optional().describe("add per-symbol reference count and doc-coverage flag"),
375
382
  projectRoot: projectRootField
376
- }
383
+ },
384
+ annotations: { readOnlyHint: true, openWorldHint: false }
377
385
  },
378
386
  (args) => {
379
387
  const { file, json, minLines, forceRefresh, stats, projectRoot } = args;
@@ -406,7 +414,8 @@ async function createMcpServer() {
406
414
  forceRefresh: z.boolean().optional().describe("reparse file from disk before querying (ignore stale index)"),
407
415
  stats: z.boolean().optional().describe("add per-symbol reference count and doc-coverage flag"),
408
416
  projectRoot: projectRootField
409
- }
417
+ },
418
+ annotations: { readOnlyHint: true, openWorldHint: false }
410
419
  },
411
420
  (args) => {
412
421
  const { file, json, minLines, forceRefresh, stats, projectRoot } = args;
@@ -439,7 +448,9 @@ async function createMcpServer() {
439
448
  excludeTests: z.boolean().optional().describe("hide hits whose file is a test file (opt-in; default output is unchanged)"),
440
449
  json: z.boolean().optional().describe("output as JSON"),
441
450
  projectRoot: makeProjectRootField("search")
442
- }
451
+ },
452
+ // openWorldHint stays false although a first call on a machine with no cached model downloads one over the network: what this tool interacts with is the local index, and the download provisions the tool rather than being the tool reaching out. A client reading `true` here would take it as "this searches the internet", which is the wrong warning.
453
+ annotations: { readOnlyHint: true, openWorldHint: false }
443
454
  },
444
455
  async (args) => {
445
456
  const { query, limit, grep, excludeTests, json, projectRoot } = args;
@@ -461,7 +472,8 @@ async function createMcpServer() {
461
472
  description: 'Report whether the index for a project can be trusted right now: whether it has ever been indexed at all, current file/symbol counts, dirty-reindex-queue depth, whether the background worker is alive, and whether embeddings are available (semantic silently degrades to full-text search without them). Call this after an unexpectedly empty result from another token-goat tool (symbol/read/semantic/refs/brief/...) to tell apart "no match" from "the index is not ready yet" -- an MCP-only client has no hook layer to warn about this on its own, so an empty tool result and a stale/unindexed project look identical without this check.',
462
473
  inputSchema: {
463
474
  projectRoot: makeProjectRootField("check")
464
- }
475
+ },
476
+ annotations: { readOnlyHint: true, openWorldHint: false }
465
477
  },
466
478
  (args) => {
467
479
  const { projectRoot } = args;
@@ -520,7 +532,8 @@ async function createMcpServer() {
520
532
  ),
521
533
  json: z.boolean().optional().describe("output as JSON"),
522
534
  projectRoot: projectRootField
523
- }
535
+ },
536
+ annotations: { readOnlyHint: true, openWorldHint: false }
524
537
  },
525
538
  (args) => {
526
539
  const { spec, callers, limit, top, json, projectRoot } = args;
@@ -554,7 +567,8 @@ async function createMcpServer() {
554
567
  excludeTests: z.boolean().optional().describe("hide callers whose call site lives in a test file (opt-in; default output is unchanged)"),
555
568
  grep: z.string().optional().describe("only show callers whose enclosing symbol name matches this regex (literal substring if it is not valid regex)"),
556
569
  projectRoot: makeProjectRootField("orient")
557
- }
570
+ },
571
+ annotations: { readOnlyHint: true, openWorldHint: false }
558
572
  },
559
573
  (args) => {
560
574
  const { spec, limit, json, context, excludeTests, grep, projectRoot } = args;
@@ -584,7 +598,8 @@ async function createMcpServer() {
584
598
  inputSchema: {
585
599
  compact: z.boolean().optional().describe("compact, low-token summary"),
586
600
  projectRoot: makeProjectRootField("overview")
587
- }
601
+ },
602
+ annotations: { readOnlyHint: true, openWorldHint: false }
588
603
  },
589
604
  (args) => {
590
605
  const { compact, projectRoot } = args;
@@ -604,7 +619,8 @@ async function createMcpServer() {
604
619
  symbolMode: z.boolean().optional().describe("list symbols instead of files"),
605
620
  json: z.boolean().optional().describe("output as JSON"),
606
621
  projectRoot: projectRootField
607
- }
622
+ },
623
+ annotations: { readOnlyHint: true, openWorldHint: false }
608
624
  },
609
625
  (args) => {
610
626
  const { ref, symbolMode, json, projectRoot } = args;
@@ -631,7 +647,8 @@ async function createMcpServer() {
631
647
  context: z.number().int().nonnegative().max(MCP_MAX_CONTEXT_LINES).optional().describe("lines of context to show before and after each match"),
632
648
  // runGrep takes no projectRoot of its own (its `path` array is its scope), so this field only names the root the confinement check is made against -- without it, a search rooted anywhere but the server process's cwd is refused.
633
649
  projectRoot: makeProjectRootField("search")
634
- }
650
+ },
651
+ annotations: { readOnlyHint: true, openWorldHint: false }
635
652
  },
636
653
  (args) => {
637
654
  const { pattern, path: searchPath, maxLines, json, recursive, context, projectRoot } = args;
@@ -663,7 +680,8 @@ async function createMcpServer() {
663
680
  file: z.string().describe("file path"),
664
681
  json: z.boolean().optional().describe("output as JSON"),
665
682
  projectRoot: projectRootField
666
- }
683
+ },
684
+ annotations: { readOnlyHint: true, openWorldHint: false }
667
685
  },
668
686
  (args) => {
669
687
  const { file, json, projectRoot } = args;
@@ -686,7 +704,8 @@ async function createMcpServer() {
686
704
  file: z.string().describe("file path"),
687
705
  json: z.boolean().optional().describe("output as JSON"),
688
706
  projectRoot: projectRootField
689
- }
707
+ },
708
+ annotations: { readOnlyHint: true, openWorldHint: false }
690
709
  },
691
710
  (args) => {
692
711
  const { file, json, projectRoot } = args;
@@ -707,7 +726,9 @@ async function createMcpServer() {
707
726
  description: "Compress arbitrary local text, persist it in the bounded local cache, and return an opaque recovery ID plus metadata.",
708
727
  inputSchema: {
709
728
  text: z.string().max(CONTENT_MAX_INPUT_CHARS).describe("text to compress")
710
- }
729
+ },
730
+ // destructiveHint is true for every tool that writes here, including the ones that only ever add: the store is bounded by item count and by total bytes, so a write can evict an earlier entry and leave a recovery id the caller is holding unredeemable. idempotentHint survives a repeat writing a fresh timestamp and another stats row, on the same reading of "its environment" set out on the read tool above.
731
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
711
732
  },
712
733
  (args) => toCallToolResult({ text: displaySafeJson(compressionPayload(compressText(args.text))), code: 0 })
713
734
  );
@@ -717,7 +738,8 @@ async function createMcpServer() {
717
738
  description: "Retrieve original text from a token-goat compression ID.",
718
739
  inputSchema: {
719
740
  id: z.string().regex(/^tg_[0-9a-f]{16}$/).describe("opaque token-goat content ID")
720
- }
741
+ },
742
+ annotations: { readOnlyHint: true, openWorldHint: false }
721
743
  },
722
744
  (args) => {
723
745
  const text = retrieveText(args.id);
@@ -732,7 +754,8 @@ async function createMcpServer() {
732
754
  name: z.string().regex(/^[A-Za-z0-9._-]{1,128}$/).describe("handoff name"),
733
755
  text: z.string().max(CONTENT_MAX_INPUT_CHARS).describe("handoff text"),
734
756
  projectRoot: makeProjectRootField("scope")
735
- }
757
+ },
758
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
736
759
  },
737
760
  (args) => toCallToolResult({
738
761
  text: displaySafeJson(createHandoff(args.name, args.text, resolveToolRoot(args.projectRoot))),
@@ -747,7 +770,9 @@ async function createMcpServer() {
747
770
  name: z.string().regex(/^[A-Za-z0-9._-]{1,128}$/).describe("handoff name"),
748
771
  full: z.boolean().optional().describe("return full text instead of a compact payload"),
749
772
  projectRoot: makeProjectRootField("scope")
750
- }
773
+ },
774
+ // Reads like a read, and is not one: resolving compactly runs the handoff text back through compressText, which stores the compact payload so the recovery id it hands back can be redeemed. A measurement, not a reading of this code -- the guard hashes the store's bytes around every tool call, and this is the tool it caught.
775
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
751
776
  },
752
777
  (args) => {
753
778
  const result = resolveHandoff(args.name, {