token-goat 2.8.4 → 2.9.1

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.
@@ -1,24 +1,30 @@
1
1
  import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
- copilotCliMcpToolsDir,
4
+ enqueueDirtyPathSafe,
5
5
  estimateTokensFromLength,
6
+ getProjectFileEntries,
7
+ getTrackedFiles,
8
+ indexRecallEntry,
6
9
  isBlobStale,
7
- isBuildCommand,
8
10
  loadBlob,
9
- sanitizeFtsQuery,
10
11
  storeBlob
11
- } from "./token-goat-chunk-4OTIB7SB.mjs";
12
+ } from "./token-goat-chunk-FDXINYK4.mjs";
12
13
  import {
13
14
  SYMBOL_BODY_CHAR_CAP,
15
+ copilotCliMcpToolsDir,
16
+ countNoun,
14
17
  extractErrorMessage,
18
+ fingerprintFile,
19
+ foldPath,
15
20
  getDb,
16
- globalDbPath,
21
+ getDisplayRoot,
17
22
  normalizePath,
18
23
  redactSecrets,
19
- runGit,
20
- shortFingerprint
21
- } from "./token-goat-chunk-E76UNTVK.mjs";
24
+ resolveIndexPath,
25
+ shortFingerprint,
26
+ toDisplayPath
27
+ } from "./token-goat-chunk-4ZDRTNGV.mjs";
22
28
  import {
23
29
  registerReset
24
30
  } from "./token-goat-chunk-AO2QD2AG.mjs";
@@ -307,492 +313,9 @@ function normalizePayload(payload, harness = "claude") {
307
313
  return result;
308
314
  }
309
315
 
310
- // src/recall_index.ts
311
- var VALID_TYPES = ["bash", "web", "mcp"];
312
- function isRecallCacheType(value) {
313
- return VALID_TYPES.includes(value);
314
- }
315
- function indexRecallEntry(cacheType, id, label, content, storedAt) {
316
- try {
317
- const db = getDb(globalDbPath());
318
- db.prepare(
319
- `INSERT INTO cache_recall (cache_type, entry_id, label, content, stored_at)
320
- VALUES (@cacheType, @id, @label, @content, @storedAt)
321
- ON CONFLICT(cache_type, entry_id) DO UPDATE SET
322
- label = excluded.label,
323
- content = excluded.content,
324
- stored_at = excluded.stored_at`
325
- ).run({ cacheType, id, label, content, storedAt });
326
- } catch {
327
- }
328
- }
329
- var _ftsAvailable = null;
330
- function hasFtsTable() {
331
- if (_ftsAvailable !== null) return _ftsAvailable;
332
- try {
333
- const db = getDb(globalDbPath());
334
- const row = db.prepare(`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'cache_recall_fts'`).get();
335
- _ftsAvailable = row !== void 0;
336
- } catch {
337
- _ftsAvailable = false;
338
- }
339
- return _ftsAvailable;
340
- }
341
- function buildSnippet(content, query, maxLen = 160) {
342
- const flat = content.replace(/\s+/g, " ").trim();
343
- const tokens = query.split(/\s+/).filter(Boolean);
344
- let idx = -1;
345
- const lowerFlat = flat.toLowerCase();
346
- for (const token of tokens) {
347
- const at = lowerFlat.indexOf(token.toLowerCase());
348
- if (at !== -1) {
349
- idx = at;
350
- break;
351
- }
352
- }
353
- if (idx === -1) {
354
- return flat.length > maxLen ? flat.slice(0, maxLen) + "..." : flat;
355
- }
356
- const start = Math.max(0, idx - Math.floor(maxLen / 3));
357
- const end = Math.min(flat.length, start + maxLen);
358
- const prefix = start > 0 ? "..." : "";
359
- const suffix = end < flat.length ? "..." : "";
360
- return prefix + flat.slice(start, end) + suffix;
361
- }
362
- function toFtsMatchExpr(query) {
363
- if (query.trim().length === 0) return null;
364
- return sanitizeFtsQuery(query);
365
- }
366
- function mapRowsToHits(rows, snippetQuery) {
367
- return rows.filter((r) => isRecallCacheType(r.cacheType)).map((r) => ({
368
- id: r.id,
369
- cacheType: r.cacheType,
370
- label: r.label ?? "",
371
- snippet: buildSnippet(r.content ?? "", snippetQuery),
372
- storedAt: r.storedAt ?? 0
373
- }));
374
- }
375
- function ftsSearch(query, type, limit) {
376
- const matchExpr = toFtsMatchExpr(query);
377
- if (matchExpr === null) return [];
378
- const db = getDb(globalDbPath());
379
- const rows = type !== void 0 ? db.prepare(
380
- `SELECT c.entry_id AS id, c.cache_type AS cacheType, c.label AS label, c.content AS content, c.stored_at AS storedAt
381
- FROM cache_recall_fts
382
- JOIN cache_recall c ON c.row_id = cache_recall_fts.rowid
383
- WHERE cache_recall_fts MATCH ? AND c.cache_type = ?
384
- ORDER BY bm25(cache_recall_fts)
385
- LIMIT ?`
386
- ).all(matchExpr, type, limit) : db.prepare(
387
- `SELECT c.entry_id AS id, c.cache_type AS cacheType, c.label AS label, c.content AS content, c.stored_at AS storedAt
388
- FROM cache_recall_fts
389
- JOIN cache_recall c ON c.row_id = cache_recall_fts.rowid
390
- WHERE cache_recall_fts MATCH ?
391
- ORDER BY bm25(cache_recall_fts)
392
- LIMIT ?`
393
- ).all(matchExpr, limit);
394
- return mapRowsToHits(rows, query);
395
- }
396
- function likeSearch(query, type, limit) {
397
- const trimmed = query.trim();
398
- if (!trimmed) return [];
399
- const db = getDb(globalDbPath());
400
- const needle = `%${trimmed.replace(/\\/g, "\\\\").replace(/[%_]/g, (c) => `\\${c}`)}%`;
401
- const rows = type !== void 0 ? db.prepare(
402
- `SELECT entry_id AS id, cache_type AS cacheType, label, content, stored_at AS storedAt
403
- FROM cache_recall
404
- WHERE cache_type = ? AND (label LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\')
405
- ORDER BY stored_at DESC
406
- LIMIT ?`
407
- ).all(type, needle, needle, limit) : db.prepare(
408
- `SELECT entry_id AS id, cache_type AS cacheType, label, content, stored_at AS storedAt
409
- FROM cache_recall
410
- WHERE label LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\'
411
- ORDER BY stored_at DESC
412
- LIMIT ?`
413
- ).all(needle, needle, limit);
414
- return mapRowsToHits(rows, trimmed);
415
- }
416
- function listRecentRecall(opts = {}) {
417
- const limit = opts.limit ?? 10;
418
- const columns = `SELECT entry_id AS id, cache_type AS cacheType, label, content, stored_at AS storedAt FROM cache_recall`;
419
- try {
420
- const db = getDb(globalDbPath());
421
- const rows = opts.type !== void 0 ? db.prepare(`${columns} WHERE cache_type = ? ORDER BY stored_at DESC LIMIT ?`).all(opts.type, limit) : db.prepare(`${columns} ORDER BY stored_at DESC LIMIT ?`).all(limit);
422
- return mapRowsToHits(rows, "");
423
- } catch {
424
- return [];
425
- }
426
- }
427
- function searchRecall(query, opts = {}) {
428
- const limit = opts.limit ?? 10;
429
- if (query.trim() === "") return [];
430
- try {
431
- if (hasFtsTable()) {
432
- return ftsSearch(query, opts.type, limit);
433
- }
434
- return likeSearch(query, opts.type, limit);
435
- } catch {
436
- try {
437
- return likeSearch(query, opts.type, limit);
438
- } catch {
439
- return [];
440
- }
441
- }
442
- }
443
-
444
- // src/bash_output_cache.ts
445
- import { readdirSync, readFileSync, statSync } from "fs";
446
- import { resolve } from "path";
447
- var BASH_OUTPUT_SUBDIR = "bash_outputs";
448
- var _byId = /* @__PURE__ */ new Map();
449
- var COMMAND_PATTERNS = {
450
- gitMutable: /^\s*git\s+(diff|status)\b/i,
451
- gitImmutable: /^\s*git\s+show\s+[0-9a-f]{40}\b/i,
452
- gitDiffScoped: /\s--\s+\S/,
453
- dirListing: /^\s*(?:ls|eza|exa|dir|Get-ChildItem|gci)\b/i,
454
- depList: /^\s*(?:npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:ls|list)\b|pip\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list|freeze)\b|uv\s+pip\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list|freeze)\b|pnpm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list|ls)\b|yarn\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list)\b|cargo\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*tree\b|bundle\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list|show)\b|composer\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*show\b)/i,
455
- npmInstall: /^\s*npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:install|ci)\b/i,
456
- npmAudit: /^\s*npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*audit\b(?!.*(?:--fix|fix)\b)/i,
457
- npmOutdated: /^\s*npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*outdated\b/i,
458
- envProbe: /^\s*(?:node\s+(?:-v|--version)|npm\s+(?:-v|--version)|python3?\s+(?:(?:-V)\b|--?version)|git\s+--version|uv\s+--version|go\s+version|rustc\s+--version|cargo\s+--version|java\s+--version|ruby\s+--version|gem\s+--version|php\s+--version|which\b|where\b)/i,
459
- npx: /^\s*npx\s+(?:--?yes\s+)?(?!.*\b(?:install|add|remove|uninstall|i|rm|update|upgrade|set|get|publish|link|ci|audit|shrinkwrap|dedupe|prune|rebuild)\b)/i,
460
- gitPush: /^\s*git\s+push\b/i,
461
- testRunner: /^\s*(?:npx\s+)?(?:pytest|vitest|jest|go\s+test)\b/i,
462
- lintCommand: /^\s*(?:(?:npx\s+)?eslint|(?:uv\s+run\s+)?ruff)\b/i,
463
- npmRunScript: /^\s*npm\s+run(?:-script)?\b/i,
464
- catCommand: /^\s*cat\b/i
465
- };
466
- var DEP_LOCKFILES = {
467
- npm: ["package-lock.json", "yarn.lock"],
468
- pip: ["requirements.txt"],
469
- uv: ["uv.lock", "requirements.txt"],
470
- pnpm: ["pnpm-lock.yaml"],
471
- yarn: ["yarn.lock"],
472
- cargo: ["Cargo.lock"],
473
- bundle: ["Gemfile.lock"],
474
- composer: ["composer.lock"]
475
- };
476
- function isCommandOfType(cmd, type) {
477
- const pattern = COMMAND_PATTERNS[type];
478
- return pattern?.test(cmd) ?? false;
479
- }
480
- var isGitMutableCommand = (cmd) => isCommandOfType(cmd, "gitMutable");
481
- var isDirListingCommand = (cmd) => isCommandOfType(cmd, "dirListing");
482
- var isDepListCommand = (cmd) => isCommandOfType(cmd, "depList");
483
- var isNpmInstallCommand = (cmd) => isCommandOfType(cmd, "npmInstall");
484
- var isNpmAuditCommand = (cmd) => isCommandOfType(cmd, "npmAudit");
485
- var isNpmOutdatedCommand = (cmd) => isCommandOfType(cmd, "npmOutdated");
486
- var isGitPushCommand = (cmd) => isCommandOfType(cmd, "gitPush");
487
- var isTestRunnerCommand = (cmd) => isCommandOfType(cmd, "testRunner");
488
- var isLintCommand = (cmd) => isCommandOfType(cmd, "lintCommand");
489
- var isNpmRunScriptCommand = (cmd) => isCommandOfType(cmd, "npmRunScript");
490
- var isCatCommand = (cmd) => isCommandOfType(cmd, "catCommand");
491
- function isScopedGitStatusOrDiffStatCommand(cmd) {
492
- if (!isCommandOfType(cmd, "gitMutable")) return false;
493
- if (!isCommandOfType(cmd, "gitDiffScoped")) return false;
494
- if (/^\s*git\s+diff\b/i.test(cmd) && !/--stat\b/.test(cmd)) return false;
495
- return true;
496
- }
497
- function gitStateFingerprintSync(cwd) {
498
- try {
499
- const headResult = runGit(["rev-parse", "HEAD"], { cwd });
500
- if (headResult.exitCode !== 0) return null;
501
- const headSha = headResult.stdout.trim();
502
- let statusHash = "";
503
- const statusResult = runGit(["status", "--porcelain"], { cwd });
504
- if (statusResult.exitCode === 0) {
505
- statusHash = shortFingerprint(statusResult.stdout);
506
- }
507
- const key = `${headSha}\0${statusHash}`;
508
- return shortFingerprint(key);
509
- } catch {
510
- return null;
511
- }
512
- }
513
- var DIR_FINGERPRINT_LISTING_CAP_ENTRIES = 1e4;
514
- function dirStateFingerprintSync(path2) {
515
- try {
516
- const stat = statSync(path2);
517
- if (!stat.isDirectory()) return null;
518
- const entries = readdirSync(path2);
519
- if (entries.length <= DIR_FINGERPRINT_LISTING_CAP_ENTRIES) {
520
- return shortFingerprint(entries.slice().sort().join("\0"));
521
- }
522
- return shortFingerprint(stat.mtimeMs.toString());
523
- } catch {
524
- return null;
525
- }
526
- }
527
- var FILE_FINGERPRINT_CONTENT_CAP_BYTES = 2 * 1024 * 1024;
528
- function fileStateFingerprintSync(path2) {
529
- try {
530
- const stat = statSync(path2);
531
- if (!stat.isFile()) return null;
532
- if (stat.size <= FILE_FINGERPRINT_CONTENT_CAP_BYTES) {
533
- return shortFingerprint(readFileSync(path2));
534
- }
535
- return shortFingerprint(`${stat.mtimeMs}\0${stat.size}`);
536
- } catch {
537
- return null;
538
- }
539
- }
540
- function depLockfileFingerprintSync(cmd, cwd) {
541
- if (!cwd) return null;
542
- const stripped = cmd.trim();
543
- const firstToken = stripped.split(/\s+/)[0]?.toLowerCase() || "";
544
- if (!firstToken) return null;
545
- const candidates = firstToken === "uv" ? DEP_LOCKFILES["uv"] : DEP_LOCKFILES[firstToken];
546
- if (!candidates) return null;
547
- for (const lockfile of candidates) {
548
- try {
549
- const content = readFileSync(resolve(cwd, lockfile));
550
- return shortFingerprint(content);
551
- } catch {
552
- continue;
553
- }
554
- }
555
- return null;
556
- }
557
- function normalizeCommandForCacheKey(cmd) {
558
- const trimmed = cmd.trim();
559
- const tokens = [];
560
- let current = "";
561
- let quoteChar = null;
562
- const flush = () => {
563
- if (current.length > 0) {
564
- tokens.push(current);
565
- current = "";
566
- }
567
- };
568
- for (let i = 0; i < trimmed.length; i++) {
569
- const ch = trimmed[i];
570
- if (quoteChar !== null) {
571
- current += ch;
572
- if (ch === quoteChar) quoteChar = null;
573
- continue;
574
- }
575
- if (ch === '"' || ch === "'") {
576
- quoteChar = ch;
577
- current += ch;
578
- continue;
579
- }
580
- if (/\s/.test(ch)) {
581
- flush();
582
- continue;
583
- }
584
- current += ch === "\\" ? "/" : ch;
585
- }
586
- flush();
587
- const normalized_tokens = tokens.map((token) => {
588
- if (token.startsWith("-") || ["&&", "||", "|", ">", ">>", ";", "&"].includes(token)) {
589
- return token;
590
- }
591
- if (token.startsWith("./") && !token.startsWith("../")) {
592
- token = token.slice(2);
593
- }
594
- if (token.endsWith("/") && token !== "/") {
595
- token = token.slice(0, -1);
596
- }
597
- return token || ".";
598
- });
599
- return normalized_tokens.join(" ");
600
- }
601
- async function commandHash(command, cwd = null) {
602
- const normalized = normalizeCommandForCacheKey(command);
603
- let key = cwd ? `${normalizePath(cwd)}\0${normalized}` : normalized;
604
- if (cwd && isGitMutableCommand(command)) {
605
- const fp = gitStateFingerprintSync(cwd);
606
- if (fp) key = `${key}\0git:${fp}`;
607
- }
608
- if (cwd && isDirListingCommand(command)) {
609
- const target = extractFirstPathArg(command, cwd, cwd);
610
- if (target) {
611
- const fp = dirStateFingerprintSync(target);
612
- if (fp) key = `${key}\0dir:${fp}`;
613
- }
614
- }
615
- if (isDepListCommand(command)) {
616
- const fp = depLockfileFingerprintSync(command, cwd);
617
- if (fp) key = `${key}\0lockfile:${fp}`;
618
- }
619
- if (cwd && (isNpmInstallCommand(command) || isNpmAuditCommand(command) || isNpmOutdatedCommand(command))) {
620
- const fp = depLockfileFingerprintSync(command, cwd);
621
- if (fp) key = `${key}\0npm-install:${fp}`;
622
- }
623
- return shortFingerprint(key);
624
- }
625
- function computeBashFingerprints(command, cwd) {
626
- const fingerprints = {};
627
- if (cwd && (isGitMutableCommand(command) || isGitPushCommand(command) || isTestRunnerCommand(command) || isLintCommand(command) || isBuildCommand(command) || isNpmRunScriptCommand(command))) {
628
- const fp = gitStateFingerprintSync(cwd);
629
- if (fp) fingerprints.git = fp;
630
- }
631
- if (cwd && isDirListingCommand(command)) {
632
- const target = extractFirstPathArg(command, cwd, cwd);
633
- if (target) {
634
- const fp = dirStateFingerprintSync(target);
635
- if (fp) fingerprints.dir = fp;
636
- }
637
- }
638
- if (isDepListCommand(command) || cwd && (isNpmInstallCommand(command) || isNpmAuditCommand(command) || isNpmOutdatedCommand(command))) {
639
- const fp = depLockfileFingerprintSync(command, cwd);
640
- if (fp) fingerprints.lockfile = fp;
641
- }
642
- if (cwd && isCatCommand(command)) {
643
- const target = extractFirstPathArg(command, cwd, null);
644
- if (target) {
645
- const fp = fileStateFingerprintSync(target);
646
- if (fp) fingerprints.file = fp;
647
- }
648
- }
649
- return Object.keys(fingerprints).length > 0 ? fingerprints : void 0;
650
- }
651
- function isBashEntryStale(entry, command, cwd) {
652
- const stored = entry.fingerprints;
653
- if (!stored) return false;
654
- const current = computeBashFingerprints(command, cwd);
655
- if (stored.git !== void 0 && stored.git !== current?.git) return true;
656
- if (stored.dir !== void 0 && stored.dir !== current?.dir) return true;
657
- if (stored.lockfile !== void 0 && stored.lockfile !== current?.lockfile) return true;
658
- if (stored.file !== void 0 && stored.file !== current?.file) return true;
659
- return false;
660
- }
661
- function tokenizeShellArgs(cmd) {
662
- const trimmed = cmd.trim();
663
- const tokens = [];
664
- let current = "";
665
- let quoteChar = null;
666
- let hasToken = false;
667
- for (let i = 0; i < trimmed.length; i++) {
668
- const ch = trimmed[i];
669
- if (quoteChar !== null) {
670
- if (ch === quoteChar) {
671
- quoteChar = null;
672
- } else {
673
- current += ch;
674
- }
675
- continue;
676
- }
677
- if (ch === '"' || ch === "'") {
678
- quoteChar = ch;
679
- hasToken = true;
680
- continue;
681
- }
682
- if (/\s/.test(ch)) {
683
- if (hasToken) {
684
- tokens.push(current);
685
- current = "";
686
- hasToken = false;
687
- }
688
- continue;
689
- }
690
- current += ch;
691
- hasToken = true;
692
- }
693
- if (hasToken) tokens.push(current);
694
- return tokens;
695
- }
696
- function extractFirstPathArg(cmd, cwd, fallback) {
697
- const tokens = tokenizeShellArgs(cmd);
698
- for (let i = 1; i < tokens.length; i++) {
699
- const token = tokens[i];
700
- if (!token.startsWith("-")) {
701
- if (!token.startsWith("/")) {
702
- return resolve(cwd, token);
703
- }
704
- return token;
705
- }
706
- }
707
- return fallback;
708
- }
709
- var ISSUE_LINE_PATTERN = /\b(?:error|fail(?:ed|ure)?|warning)\b/i;
710
- function summarizeOutputDelta(oldOutput, newOutput) {
711
- if (oldOutput === newOutput) return null;
712
- const oldLines = oldOutput.split("\n");
713
- const newLines = newOutput.split("\n");
714
- const oldIssueLines = oldLines.filter((l) => ISSUE_LINE_PATTERN.test(l));
715
- if (oldIssueLines.length > 0) {
716
- const newIssueLines = newLines.filter((l) => ISSUE_LINE_PATTERN.test(l));
717
- const priorTotal = oldIssueLines.length;
718
- const availableCounts = /* @__PURE__ */ new Map();
719
- for (const l of newIssueLines) availableCounts.set(l, (availableCounts.get(l) ?? 0) + 1);
720
- let resolved = 0;
721
- for (const l of oldIssueLines) {
722
- const avail = availableCounts.get(l) ?? 0;
723
- if (avail > 0) {
724
- availableCounts.set(l, avail - 1);
725
- } else {
726
- resolved++;
727
- }
728
- }
729
- const remaining = newIssueLines.length;
730
- return `[token-goat: delta] ${resolved} of ${priorTotal} prior issues resolved; remaining: ${remaining}`;
731
- }
732
- return `[token-goat: delta] output changed: ${oldLines.length} -> ${newLines.length} lines`;
733
- }
734
- async function storeBashOutput(command, output, exitCode, cwd = null) {
735
- const id = await commandHash(command, cwd);
736
- const fingerprints = computeBashFingerprints(command, cwd);
737
- const redactedOutput = redactSecrets(output).text;
738
- const redactedCommand = redactSecrets(command).text;
739
- const entry = {
740
- id,
741
- command: redactedCommand,
742
- output: redactedOutput,
743
- exitCode,
744
- storedAt: Date.now(),
745
- sizeBytes: Buffer.byteLength(redactedOutput, "utf-8"),
746
- ...fingerprints ? { fingerprints } : {}
747
- };
748
- _byId.set(id, entry);
749
- storeBlob(BASH_OUTPUT_SUBDIR, id, entry);
750
- indexRecallEntry("bash", id, redactedCommand, `${redactedCommand}
751
- ${redactedOutput}`, entry.storedAt);
752
- return id;
753
- }
754
- function coerceBashEntry(raw) {
755
- if (raw === null || typeof raw !== "object") return null;
756
- const o = raw;
757
- if (typeof o["id"] !== "string" || typeof o["command"] !== "string" || typeof o["output"] !== "string" || typeof o["exitCode"] !== "number" || typeof o["storedAt"] !== "number" || typeof o["sizeBytes"] !== "number") {
758
- return null;
759
- }
760
- const entry = {
761
- id: o["id"],
762
- command: o["command"],
763
- output: o["output"],
764
- exitCode: o["exitCode"],
765
- storedAt: o["storedAt"],
766
- sizeBytes: o["sizeBytes"]
767
- };
768
- const rawFingerprints = o["fingerprints"];
769
- if (rawFingerprints !== null && typeof rawFingerprints === "object") {
770
- const f = rawFingerprints;
771
- const fingerprints = {};
772
- if (typeof f["git"] === "string") fingerprints.git = f["git"];
773
- if (typeof f["dir"] === "string") fingerprints.dir = f["dir"];
774
- if (typeof f["lockfile"] === "string") fingerprints.lockfile = f["lockfile"];
775
- if (typeof f["file"] === "string") fingerprints.file = f["file"];
776
- if (Object.keys(fingerprints).length > 0) return { ...entry, fingerprints };
777
- }
778
- return entry;
779
- }
780
- function getBashOutput(id) {
781
- const hit = _byId.get(id);
782
- if (hit !== void 0) return hit;
783
- if (isBlobStale(BASH_OUTPUT_SUBDIR, id)) return null;
784
- const entry = coerceBashEntry(loadBlob(BASH_OUTPUT_SUBDIR, id));
785
- if (entry === null) return null;
786
- _byId.set(id, entry);
787
- return entry;
788
- }
789
- registerReset(() => {
790
- _byId = /* @__PURE__ */ new Map();
791
- });
792
-
793
316
  // src/web_cache.ts
794
317
  var WEB_OUTPUT_SUBDIR = "web_outputs";
795
- var _byId2 = /* @__PURE__ */ new Map();
318
+ var _byId = /* @__PURE__ */ new Map();
796
319
  var _rawById = /* @__PURE__ */ new Map();
797
320
  var _urlIndex = /* @__PURE__ */ new Map();
798
321
  function cacheIdForUrl(url) {
@@ -801,7 +324,7 @@ function cacheIdForUrl(url) {
801
324
  function storeWebOutput(url, content, dedupKey = url, rawContent) {
802
325
  const cacheId = cacheIdForUrl(dedupKey);
803
326
  const redactedContent = redactSecrets(content).text;
804
- _byId2.set(cacheId, redactedContent);
327
+ _byId.set(cacheId, redactedContent);
805
328
  _urlIndex.set(url, cacheId);
806
329
  const redactedRaw = rawContent !== void 0 && rawContent !== content ? redactSecrets(rawContent).text : void 0;
807
330
  if (redactedRaw !== void 0) _rawById.set(cacheId, redactedRaw);
@@ -819,12 +342,12 @@ function coerceWebBlob(raw) {
819
342
  return { url: typeof o["url"] === "string" ? o["url"] : null, content: o["content"], raw: typeof o["raw"] === "string" ? o["raw"] : null };
820
343
  }
821
344
  function getWebOutput(cacheId) {
822
- const hit = _byId2.get(cacheId);
345
+ const hit = _byId.get(cacheId);
823
346
  if (hit !== void 0) return hit;
824
347
  if (isBlobStale(WEB_OUTPUT_SUBDIR, cacheId)) return null;
825
348
  const blob = coerceWebBlob(loadBlob(WEB_OUTPUT_SUBDIR, cacheId));
826
349
  if (blob === null) return null;
827
- _byId2.set(cacheId, blob.content);
350
+ _byId.set(cacheId, blob.content);
828
351
  if (blob.raw !== null) _rawById.set(cacheId, blob.raw);
829
352
  if (blob.url !== null) _urlIndex.set(blob.url, cacheId);
830
353
  return blob.content;
@@ -841,7 +364,7 @@ function getWebOutputByUrlFromDisk(url, dedupKey = url) {
841
364
  return { cacheId, content };
842
365
  }
843
366
  registerReset(() => {
844
- _byId2 = /* @__PURE__ */ new Map();
367
+ _byId = /* @__PURE__ */ new Map();
845
368
  _rawById = /* @__PURE__ */ new Map();
846
369
  _urlIndex = /* @__PURE__ */ new Map();
847
370
  });
@@ -928,12 +451,135 @@ function formatCommandManifest(manifest) {
928
451
  return lines.join("\n").trimEnd();
929
452
  }
930
453
 
454
+ // src/reconcile.ts
455
+ import * as fs2 from "node:fs";
456
+ var DEFAULT_RECONCILE_BUDGET_MS = 1500;
457
+ function runReconcile(opts = {}) {
458
+ const raw = reconcileProject(opts);
459
+ const root = getDisplayRoot(opts.cwd ?? process.cwd());
460
+ const display = (paths) => paths.map((p) => toDisplayPath(root, p)).sort();
461
+ const result = {
462
+ ...raw,
463
+ changed: display(raw.changed),
464
+ added: display(raw.added),
465
+ removed: display(raw.removed)
466
+ };
467
+ if (opts.json === true) {
468
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
469
+ `);
470
+ return 0;
471
+ }
472
+ const lines = [];
473
+ if (isReconcileClean(result)) {
474
+ if (!result.trackedUnavailable) {
475
+ lines.push(`Index matches disk: ${countNoun(result.scanned, "file")} checked in ${result.elapsedMs}ms.`);
476
+ }
477
+ } else {
478
+ const verb = opts.dryRun === true ? "would reindex" : "queued for reindexing";
479
+ const dropVerb = opts.dryRun === true ? "would drop from the index" : "queued for removal";
480
+ if (result.changed.length > 0) lines.push(`${countNoun(result.changed.length, "file")} changed since indexing (${verb}):`);
481
+ for (const f of result.changed) lines.push(` ~ ${f}`);
482
+ if (result.added.length > 0) lines.push(`${countNoun(result.added.length, "file")} not in the index (${verb}):`);
483
+ for (const f of result.added) lines.push(` + ${f}`);
484
+ if (result.removed.length > 0) lines.push(`${countNoun(result.removed.length, "file")} indexed but gone from disk (${dropVerb}):`);
485
+ for (const f of result.removed) lines.push(` - ${f}`);
486
+ lines.push(`Checked ${countNoun(result.scanned, "file")} in ${result.elapsedMs}ms.`);
487
+ }
488
+ if (result.mtimeOnly > 0) {
489
+ lines.push(`${countNoun(result.mtimeOnly, "file")} had a newer timestamp but identical content, so ${result.mtimeOnly === 1 ? "it was" : "they were"} left alone.`);
490
+ }
491
+ if (result.trackedUnavailable) {
492
+ lines.push("This project has an index but git listed no files in it, so nothing could be compared and no deletions were computed. Run token-goat inside the repository, or reindex with --walk if this directory is deliberately not under git.");
493
+ }
494
+ if (result.budgetExhausted) {
495
+ lines.push(`Stopped at the ${result.elapsedMs}ms budget with ${countNoun(result.unscanned, "file")} unchecked, so there may be more drift; deletions were not computed at all, because an unchecked file is indistinguishable from a deleted one. Raise --budget-ms for a complete sweep.`);
496
+ }
497
+ process.stdout.write(`${lines.join("\n")}
498
+ `);
499
+ return 0;
500
+ }
501
+ function isReconcileClean(r) {
502
+ return r.changed.length === 0 && r.added.length === 0 && r.removed.length === 0;
503
+ }
504
+ function reconcileProject(opts = {}) {
505
+ const cwd = opts.cwd ?? process.cwd();
506
+ const budgetMs = opts.budgetMs ?? DEFAULT_RECONCILE_BUDGET_MS;
507
+ const startedAt = Date.now();
508
+ const tracked = getTrackedFiles(cwd);
509
+ const projectRoot = resolveIndexPath(".", cwd);
510
+ const indexed = getProjectFileEntries(projectRoot);
511
+ const changed = [];
512
+ const added = [];
513
+ const seenOnDisk = /* @__PURE__ */ new Set();
514
+ let mtimeOnly = 0;
515
+ let scanned = 0;
516
+ let budgetExhausted = false;
517
+ for (const file of tracked) {
518
+ if (Date.now() - startedAt > budgetMs) {
519
+ budgetExhausted = true;
520
+ break;
521
+ }
522
+ scanned++;
523
+ const folded = foldPath(normalizePath(file));
524
+ seenOnDisk.add(folded);
525
+ const entry = indexed.get(folded);
526
+ if (entry === void 0) {
527
+ added.push(file);
528
+ continue;
529
+ }
530
+ let mtimeMs;
531
+ try {
532
+ mtimeMs = fs2.statSync(file).mtimeMs;
533
+ } catch {
534
+ changed.push(file);
535
+ continue;
536
+ }
537
+ if (entry.mtime !== 0 && mtimeMs === entry.mtime) continue;
538
+ const diskSha = fingerprintFile(file);
539
+ if (diskSha === null) {
540
+ changed.push(file);
541
+ continue;
542
+ }
543
+ if (diskSha === entry.sha) {
544
+ mtimeOnly++;
545
+ continue;
546
+ }
547
+ changed.push(file);
548
+ }
549
+ const trackedUnavailable = tracked.length === 0 && indexed.size > 0;
550
+ const removed = [];
551
+ if (!budgetExhausted && !trackedUnavailable) {
552
+ for (const [folded, entry] of indexed) {
553
+ if (!seenOnDisk.has(folded)) removed.push(entry.filePath);
554
+ }
555
+ }
556
+ let enqueued = 0;
557
+ if (opts.dryRun !== true) {
558
+ for (const p of [...changed, ...added, ...removed]) {
559
+ enqueueDirtyPathSafe(resolveIndexPath(p, cwd), { alreadyResolved: true });
560
+ enqueued++;
561
+ }
562
+ }
563
+ return {
564
+ scanned,
565
+ changed,
566
+ added,
567
+ removed,
568
+ mtimeOnly,
569
+ budgetExhausted,
570
+ trackedUnavailable,
571
+ unscanned: Math.max(0, tracked.length - scanned),
572
+ enqueued,
573
+ elapsedMs: Date.now() - startedAt
574
+ };
575
+ }
576
+
931
577
  // src/stdin_json.ts
932
578
  var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
933
579
  var MAX_STDIN_WALL_MS = 6e4;
934
580
  var MAX_STDIN_BYTES = 64 * 1024 * 1024;
935
581
  function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
936
- return new Promise((resolve2, reject) => {
582
+ return new Promise((resolve, reject) => {
937
583
  const chunks = [];
938
584
  let totalBytes = 0;
939
585
  let settled = false;
@@ -979,7 +625,7 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
979
625
  return;
980
626
  }
981
627
  try {
982
- resolve2(JSON.parse(text));
628
+ resolve(JSON.parse(text));
983
629
  } catch (err) {
984
630
  reject(err instanceof Error ? err : new Error(String(err)));
985
631
  }
@@ -995,10 +641,10 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
995
641
  }
996
642
 
997
643
  // src/symbol_body_probe.ts
998
- import * as fs2 from "fs";
644
+ import * as fs3 from "fs";
999
645
  var OVERSIZED_BODY_PROBE_SQL = `SELECT 1 FROM symbols WHERE LENGTH(body) > ${SYMBOL_BODY_CHAR_CAP} LIMIT 1`;
1000
646
  function checkSymbolBodySize(dbPath) {
1001
- if (!fs2.existsSync(dbPath)) {
647
+ if (!fs3.existsSync(dbPath)) {
1002
648
  return { name: "Symbol body size", status: "ok", message: "no database yet" };
1003
649
  }
1004
650
  try {
@@ -1022,7 +668,7 @@ function checkSymbolBodySize(dbPath) {
1022
668
  }
1023
669
 
1024
670
  // src/resident_context.ts
1025
- import * as fs3 from "node:fs";
671
+ import * as fs4 from "node:fs";
1026
672
  var LARGE_TASK_LIST_BYTES = 2e4;
1027
673
  var LARGE_SKILL_BODY_BYTES = 2e4;
1028
674
  var SKILL_BODY_REPEAT_THRESHOLD = 2;
@@ -1183,13 +829,13 @@ function repeatedSkillBodyHint(injections) {
1183
829
  function readTranscriptTail(transcriptPath, maxBytes = RESIDENT_TAIL_MAX_BYTES) {
1184
830
  let fd = null;
1185
831
  try {
1186
- const stat = fs3.statSync(transcriptPath);
832
+ const stat = fs4.statSync(transcriptPath);
1187
833
  if (!stat.isFile() || stat.size === 0) return [];
1188
834
  const start = Math.max(0, stat.size - maxBytes);
1189
835
  const length = stat.size - start;
1190
- fd = fs3.openSync(transcriptPath, "r");
836
+ fd = fs4.openSync(transcriptPath, "r");
1191
837
  const buf = Buffer.allocUnsafe(length);
1192
- const read = fs3.readSync(fd, buf, 0, length, start);
838
+ const read = fs4.readSync(fd, buf, 0, length, start);
1193
839
  const lines = buf.subarray(0, read).toString("utf8").split("\n");
1194
840
  if (start > 0) lines.shift();
1195
841
  return lines;
@@ -1198,7 +844,7 @@ function readTranscriptTail(transcriptPath, maxBytes = RESIDENT_TAIL_MAX_BYTES)
1198
844
  } finally {
1199
845
  if (fd !== null) {
1200
846
  try {
1201
- fs3.closeSync(fd);
847
+ fs4.closeSync(fd);
1202
848
  } catch {
1203
849
  }
1204
850
  }
@@ -1227,18 +873,6 @@ export {
1227
873
  readCopilotMcpTools,
1228
874
  GEMINI_TOOL_NAME_MAP,
1229
875
  normalizePayload,
1230
- isRecallCacheType,
1231
- indexRecallEntry,
1232
- listRecentRecall,
1233
- searchRecall,
1234
- BASH_OUTPUT_SUBDIR,
1235
- isScopedGitStatusOrDiffStatCommand,
1236
- normalizeCommandForCacheKey,
1237
- commandHash,
1238
- isBashEntryStale,
1239
- summarizeOutputDelta,
1240
- storeBashOutput,
1241
- getBashOutput,
1242
876
  WEB_OUTPUT_SUBDIR,
1243
877
  storeWebOutput,
1244
878
  getWebOutput,
@@ -1249,6 +883,10 @@ export {
1249
883
  flattenCommandNames,
1250
884
  filterCommandManifest,
1251
885
  formatCommandManifest,
886
+ DEFAULT_RECONCILE_BUDGET_MS,
887
+ runReconcile,
888
+ isReconcileClean,
889
+ reconcileProject,
1252
890
  checkSymbolBodySize,
1253
891
  createResidentContextStats,
1254
892
  accumulateResidentLine,