token-goat 2.8.6 → 2.9.2

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