token-goat 2.6.19 → 2.6.21

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.
@@ -49,7 +49,7 @@ var init_define_import_meta_env = __esm({
49
49
  import { createRequire } from "node:module";
50
50
  function resolveVersion() {
51
51
  if (true) {
52
- return "2.6.19";
52
+ return "2.6.21";
53
53
  }
54
54
  const require2 = createRequire(import.meta.url);
55
55
  const pkg = require2("../package.json");
@@ -286,6 +286,7 @@ const VALID_HOOK_EVENTS = new Set([
286
286
  'pre_compact',
287
287
  'user_prompt_submit',
288
288
  'subagent_stop',
289
+ 'session_start',
289
290
  ])
290
291
 
291
292
  // Keep in sync with CLAUDE_CODE_EVENT_NAMES in src/hook_registry.ts -- the
@@ -300,6 +301,7 @@ const HOOK_EVENT_NAME_MAP = {
300
301
  pre_compact: 'PreCompact',
301
302
  user_prompt_submit: 'UserPromptSubmit',
302
303
  subagent_stop: 'SubagentStop',
304
+ session_start: 'SessionStart',
303
305
  }
304
306
 
305
307
  function stripTg(value) {
@@ -1368,8 +1370,8 @@ function normalizeDarwinSystemAlias(p) {
1368
1370
  }
1369
1371
  function resolveIndexPath(file2, base = process.cwd()) {
1370
1372
  const isWindowsAbsolute = (s) => /^[a-zA-Z]:[/\\]/.test(s);
1371
- const resolve20 = isWindowsAbsolute(file2) || isWindowsAbsolute(base) ? path2.win32.resolve : path2.resolve;
1372
- return normalizePath(resolve20(base, file2));
1373
+ const resolve21 = isWindowsAbsolute(file2) || isWindowsAbsolute(base) ? path2.win32.resolve : path2.resolve;
1374
+ return normalizePath(resolve21(base, file2));
1373
1375
  }
1374
1376
  function safeJoin(base, ...parts) {
1375
1377
  for (const part of parts) {
@@ -1896,6 +1898,43 @@ setInterval(tick, Number(ms))
1896
1898
  }
1897
1899
  });
1898
1900
 
1901
+ // src/bridges/guidance_block.ts
1902
+ function buildGuidanceBody(fallbackToolClause) {
1903
+ return [
1904
+ "## token-goat",
1905
+ "",
1906
+ "**Gate \u2014 before every file read, answer one question first: is there a token-goat command that returns just what I need?** If yes, run it. A read tool invoked without answering the gate is a violation, not an oversight. The gate is per file: batched or parallel reads do not exempt it.",
1907
+ "",
1908
+ `This gate decides *whether* to reach for a read tool at all. ${fallbackToolClause} only pick the *fallback* once token-goat has been ruled out for this read \u2014 they never authorize skipping the gate.`,
1909
+ "",
1910
+ "Exemptions (gate passes, read directly): the file is under ~200 lines and you need all of it; it was never indexed (new, untracked, or generated this turn); it is binary or an image; the target has no symbol handle (e.g. a literal mid-function).",
1911
+ "",
1912
+ "Failure shapes to catch yourself in, and the command that replaces each:",
1913
+ '- grep/search with context flags to find a function body \u2192 `read "file::symbol"`',
1914
+ '- paging one function with view/view_range \u2192 `read "file::symbol"`',
1915
+ '- reading one heading of a large doc \u2192 `section "file::Heading"`',
1916
+ "- searching for a symbol's callers \u2192 `refs file::symbol --callers`",
1917
+ '- searching for a *concept* rather than a literal string \u2192 `semantic "description"`',
1918
+ "- re-reading output you already captured \u2192 `bash-output`/`web-output` by ID",
1919
+ "- `glob`/`ls **/*` to orient in an unfamiliar repo \u2192 `map --compact`",
1920
+ "",
1921
+ 'Commands: `symbol NAME`, `read "file::symbol"`, `section "file::Heading"`, `semantic "description"`, `outline file`/`skeleton file`, `map --compact`, `refs file::symbol --callers`, `changed --symbol`, `config-get file KEY`, `bash-output`/`web-output`, `gdrive-sections <file-id>`.',
1922
+ "",
1923
+ "Sub-agent briefs must carry this gate verbatim: a sub-agent inherits none of this context and its reads spend the same token budget.",
1924
+ "",
1925
+ "`token-goat stats` \u2014 self-check. Flat counts during code work mean the gate is being skipped."
1926
+ ].join("\n");
1927
+ }
1928
+ function buildGuidanceBlock(h) {
1929
+ return [h.beginMarker, buildGuidanceBody(h.fallbackToolClause), h.endMarker].join("\n");
1930
+ }
1931
+ var init_guidance_block = __esm({
1932
+ "src/bridges/guidance_block.ts"() {
1933
+ "use strict";
1934
+ init_define_import_meta_env();
1935
+ }
1936
+ });
1937
+
1899
1938
  // src/install.ts
1900
1939
  import * as fs2 from "node:fs";
1901
1940
  import * as os2 from "node:os";
@@ -2022,28 +2061,11 @@ function claudeMdPath() {
2022
2061
  return path4.join(os2.homedir(), ".claude", "CLAUDE.md");
2023
2062
  }
2024
2063
  function buildClaudeMdBlock() {
2025
- return [
2026
- CLAUDE_MD_BEGIN,
2027
- "## token-goat",
2028
- "",
2029
- "Prefer token-goat commands over reading whole files:",
2030
- "- `token-goat symbol NAME` -- find a function/class/type",
2031
- '- `token-goat read "file::symbol"` -- one function/method body',
2032
- '- `token-goat section "file::Heading"` -- one doc or config section',
2033
- '- `token-goat semantic "description"` -- find code by meaning',
2034
- "- `token-goat outline file` / `token-goat skeleton file` -- signatures without bodies",
2035
- "- `token-goat map --compact` -- project overview (low-token summary)",
2036
- "- `token-goat refs file::symbol --callers` -- find callers of a symbol",
2037
- "- `token-goat changed --symbol` -- symbols changed since a git ref",
2038
- "- `token-goat config-get file KEY` -- read one config value",
2039
- "- `token-goat bash-output` / `token-goat web-output` -- re-inspect cached output by ID",
2040
- "- `token-goat gdrive-sections <file-id>` -- outline a Google Doc by ID",
2041
- "",
2042
- "Use this before a full-file `Read` or wide `Grep`, and before opening a large image",
2043
- "(token-goat hooks shrink oversized images automatically). token-goat commands return",
2044
- "narrow slices, typically 85-97% smaller than the full file.",
2045
- CLAUDE_MD_END
2046
- ].join("\n");
2064
+ return buildGuidanceBlock({
2065
+ beginMarker: CLAUDE_MD_BEGIN,
2066
+ endMarker: CLAUDE_MD_END,
2067
+ fallbackToolClause: "Claude Code's own Read, Grep, and Glob preference rules"
2068
+ });
2047
2069
  }
2048
2070
  function writeClaudeMdBlock(p) {
2049
2071
  return upsertDelimitedBlock(p, CLAUDE_MD_BEGIN, CLAUDE_MD_END, buildClaudeMdBlock());
@@ -2059,6 +2081,50 @@ function installClaudeMd() {
2059
2081
  function uninstallClaudeMd() {
2060
2082
  return stripClaudeMdBlock(claudeMdPath());
2061
2083
  }
2084
+ function findStrayClaudeMdBlocks(searchRoot) {
2085
+ const root = searchRoot ?? path4.join(os2.homedir(), ".claude");
2086
+ const canonical = path4.resolve(claudeMdPath());
2087
+ const found = [];
2088
+ if (!fs2.existsSync(root)) return found;
2089
+ const SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git"]);
2090
+ const MAX_DEPTH = 6;
2091
+ const hasRealBlock = (text) => {
2092
+ let sawBegin = false;
2093
+ for (const line of text.split(/\r?\n/)) {
2094
+ const trimmed = line.trim();
2095
+ if (trimmed === CLAUDE_MD_BEGIN) sawBegin = true;
2096
+ else if (sawBegin && trimmed === CLAUDE_MD_END) return true;
2097
+ }
2098
+ return false;
2099
+ };
2100
+ const walk = (dir, depth) => {
2101
+ if (depth > MAX_DEPTH) return;
2102
+ let entries;
2103
+ try {
2104
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
2105
+ } catch {
2106
+ return;
2107
+ }
2108
+ for (const entry of entries) {
2109
+ const full = path4.join(dir, entry.name);
2110
+ if (entry.isDirectory()) {
2111
+ if (!SKIP_DIRS2.has(entry.name)) walk(full, depth + 1);
2112
+ continue;
2113
+ }
2114
+ if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".md")) continue;
2115
+ if (path4.resolve(full) === canonical) continue;
2116
+ let text;
2117
+ try {
2118
+ text = fs2.readFileSync(full, "utf8");
2119
+ } catch {
2120
+ continue;
2121
+ }
2122
+ if (hasRealBlock(text)) found.push(full);
2123
+ }
2124
+ };
2125
+ walk(root, 0);
2126
+ return found.sort();
2127
+ }
2062
2128
  function skillDir() {
2063
2129
  return path4.join(os2.homedir(), ".claude", "skills", "token-goat");
2064
2130
  }
@@ -2083,11 +2149,12 @@ function uninstallSkill() {
2083
2149
  fs2.rmSync(dir, { recursive: true, force: true });
2084
2150
  return true;
2085
2151
  }
2086
- var HOOK_EVENT_MAP, COMMAND_MARKER, LEGACY_COMMAND_MARKERS, HOOK_MARKER_PATTERNS, CURRENT_MARKER_PATTERN, SettingsParseError, CLAUDE_MD_BEGIN, CLAUDE_MD_END, SKILL_MD_CONTENT;
2152
+ var HOOK_EVENT_MAP, COMMAND_MARKER, LEGACY_COMMAND_MARKERS, HOOK_MARKER_PATTERNS, CURRENT_MARKER_PATTERN, SettingsParseError, CLAUDE_MD_BEGIN, CLAUDE_MD_END, SKILL_MD_FRONTMATTER, SKILL_MD_CONTENT;
2087
2153
  var init_install = __esm({
2088
2154
  "src/install.ts"() {
2089
2155
  "use strict";
2090
2156
  init_define_import_meta_env();
2157
+ init_guidance_block();
2091
2158
  init_paths();
2092
2159
  init_util2();
2093
2160
  HOOK_EVENT_MAP = [
@@ -2095,7 +2162,8 @@ var init_install = __esm({
2095
2162
  ["PostToolUse", "post_tool_use"],
2096
2163
  ["PreCompact", "pre_compact"],
2097
2164
  ["UserPromptSubmit", "user_prompt_submit"],
2098
- ["SubagentStop", "subagent_stop"]
2165
+ ["SubagentStop", "subagent_stop"],
2166
+ ["SessionStart", "session_start"]
2099
2167
  ];
2100
2168
  COMMAND_MARKER = "token-goat hook";
2101
2169
  LEGACY_COMMAND_MARKERS = ["tokenwise", "token_goat", "tg-hook", "token-goat-hook"];
@@ -2105,31 +2173,13 @@ var init_install = __esm({
2105
2173
  };
2106
2174
  CLAUDE_MD_BEGIN = "<!-- token-goat-begin -->";
2107
2175
  CLAUDE_MD_END = "<!-- token-goat-end -->";
2108
- SKILL_MD_CONTENT = `---
2176
+ SKILL_MD_FRONTMATTER = `---
2109
2177
  name: token-goat
2110
2178
  description: Use before reading whole files or grepping wide. token-goat commands (symbol, read, section, semantic, outline, skeleton, map, refs, changed, config-get, bash-output, web-output, gdrive-sections) return narrow slices of code and docs at a fraction of the token cost.
2111
- ---
2179
+ ---`;
2180
+ SKILL_MD_CONTENT = `${SKILL_MD_FRONTMATTER}
2112
2181
 
2113
- # token-goat
2114
-
2115
- Prefer token-goat commands over reading whole files:
2116
- - \`token-goat symbol NAME\` -- find a function/class/type
2117
- - \`token-goat read "file::symbol"\` -- one function/method body
2118
- - \`token-goat section "file::Heading"\` -- one doc or config section
2119
- - \`token-goat semantic "description"\` -- find code by meaning
2120
- - \`token-goat outline file\` / \`token-goat skeleton file\` -- signatures without bodies
2121
- - \`token-goat map --compact\` -- project overview (low-token summary)
2122
- - \`token-goat refs file::symbol --callers\` -- find callers of a symbol
2123
- - \`token-goat changed --symbol\` -- symbols changed since a git ref
2124
- - \`token-goat config-get file KEY\` -- read one config value
2125
- - \`token-goat bash-output\` / \`token-goat web-output\` -- re-inspect cached output by ID
2126
- - \`token-goat gdrive-sections <file-id>\` -- outline a Google Doc by ID
2127
-
2128
- Read is the right call when the file is under about 200 lines, was never indexed (new or
2129
- untracked), or is an image (token-goat's hooks shrink oversized images automatically).
2130
-
2131
- Image shrinking and repeat-read hints run on their own via hooks -- you do not call those
2132
- directly.
2182
+ ${buildGuidanceBody("Claude Code's own Read, Grep, and Glob preference rules")}
2133
2183
  `;
2134
2184
  }
2135
2185
  });
@@ -2277,23 +2327,11 @@ function uninstallCodex() {
2277
2327
  return removedAny;
2278
2328
  }
2279
2329
  function buildAgentsBlock() {
2280
- return [
2281
- AGENTS_BEGIN,
2282
- "## token-goat",
2283
- "",
2284
- "Prefer token-goat commands over reading whole files:",
2285
- "- `token-goat symbol NAME` -- find a function/class/type",
2286
- '- `token-goat read "file::symbol"` -- one function/method body',
2287
- '- `token-goat section "file::Heading"` -- one doc or config section',
2288
- '- `token-goat semantic "description"` -- find code by meaning',
2289
- "- `token-goat outline file` / `token-goat skeleton file` -- signatures without bodies",
2290
- "",
2291
- "Use this before a full-file read via `shell` (cat/type), before previewing a diff with",
2292
- "`apply_patch`, and before a `view_image` on a large screenshot (token-goat hooks shrink",
2293
- "oversized images automatically). token-goat commands return narrow slices, typically",
2294
- "85-97% smaller than the full file.",
2295
- AGENTS_END
2296
- ].join("\n");
2330
+ return buildGuidanceBlock({
2331
+ beginMarker: AGENTS_BEGIN,
2332
+ endMarker: AGENTS_END,
2333
+ fallbackToolClause: "Codex's own `shell` (cat/type), `apply_patch` preview, and `view_image` preferences"
2334
+ });
2297
2335
  }
2298
2336
  function writeAgentsBlock(p) {
2299
2337
  return upsertDelimitedBlock(p, AGENTS_BEGIN, AGENTS_END, buildAgentsBlock());
@@ -2310,6 +2348,7 @@ var init_codex_install = __esm({
2310
2348
  init_util2();
2311
2349
  init_install();
2312
2350
  init_codex();
2351
+ init_guidance_block();
2313
2352
  init_matcher_group();
2314
2353
  CODEX_COMMAND_MARKER = "token-goat-shim";
2315
2354
  CODEX_MATCHERS = ["view_image|Bash", "apply_patch", "web_search"];
@@ -2677,6 +2716,22 @@ function copilotCliConfigPath(opts = {}) {
2677
2716
  function copilotCliScriptPath(opts = {}) {
2678
2717
  return path6.join(copilotCliHooksDir(opts), "token-goat-shim.js");
2679
2718
  }
2719
+ function copilotCliInstructionsPath(opts = {}) {
2720
+ return path6.join(path6.dirname(copilotCliHooksDir(opts)), "copilot-instructions.md");
2721
+ }
2722
+ function buildCopilotInstructionsBlock() {
2723
+ return buildGuidanceBlock({
2724
+ beginMarker: COPILOT_INSTRUCTIONS_BEGIN,
2725
+ endMarker: COPILOT_INSTRUCTIONS_END,
2726
+ fallbackToolClause: "Copilot CLI's own `view`, `grep`, and `glob` tool-preference rules (and `Get-Content`/`Select-String`)"
2727
+ });
2728
+ }
2729
+ function writeCopilotInstructionsBlock(p) {
2730
+ return upsertDelimitedBlock(p, COPILOT_INSTRUCTIONS_BEGIN, COPILOT_INSTRUCTIONS_END, buildCopilotInstructionsBlock());
2731
+ }
2732
+ function stripCopilotInstructionsBlock(p) {
2733
+ return stripDelimitedBlock(p, COPILOT_INSTRUCTIONS_BEGIN, COPILOT_INSTRUCTIONS_END);
2734
+ }
2680
2735
  function hookPowershellCommandFor(scriptPath, event) {
2681
2736
  return `& ${hookCommandFor(scriptPath, event)}`;
2682
2737
  }
@@ -2698,14 +2753,22 @@ function buildConfig(scriptPath) {
2698
2753
  function installCopilotCli(opts = {}) {
2699
2754
  const configPath2 = copilotCliConfigPath(opts);
2700
2755
  const scriptPath = copilotCliScriptPath(opts);
2756
+ const instructionsPath = copilotCliInstructionsPath(opts);
2701
2757
  const scriptChanged = writeIfDifferent(scriptPath, COPILOT_CLI_HOOK_SCRIPT);
2702
2758
  const desiredText = JSON.stringify(buildConfig(scriptPath), null, 2) + "\n";
2703
2759
  const configChanged = writeIfDifferent(configPath2, desiredText, true);
2704
- return { configPath: configPath2, scriptPath, alreadyInstalled: !scriptChanged && !configChanged };
2760
+ const instructionsChanged = writeCopilotInstructionsBlock(instructionsPath);
2761
+ return {
2762
+ configPath: configPath2,
2763
+ scriptPath,
2764
+ instructionsPath,
2765
+ alreadyInstalled: !scriptChanged && !configChanged && !instructionsChanged
2766
+ };
2705
2767
  }
2706
2768
  function uninstallCopilotCliScope(opts) {
2707
2769
  const configPath2 = copilotCliConfigPath(opts);
2708
2770
  const scriptPath = copilotCliScriptPath(opts);
2771
+ const instructionsPath = copilotCliInstructionsPath(opts);
2709
2772
  let removedAny = false;
2710
2773
  try {
2711
2774
  fs4.unlinkSync(configPath2);
@@ -2717,6 +2780,9 @@ function uninstallCopilotCliScope(opts) {
2717
2780
  removedAny = true;
2718
2781
  } catch {
2719
2782
  }
2783
+ if (stripCopilotInstructionsBlock(instructionsPath)) {
2784
+ removedAny = true;
2785
+ }
2720
2786
  return removedAny;
2721
2787
  }
2722
2788
  function uninstallCopilotCli(opts = {}) {
@@ -2727,13 +2793,14 @@ function uninstallCopilotCli(opts = {}) {
2727
2793
  const localRemoved = uninstallCopilotCliScope({ local: true });
2728
2794
  return userRemoved || localRemoved;
2729
2795
  }
2730
- var COPILOT_CLI_HOOK_EVENTS, HOOK_TIMEOUT_SEC;
2796
+ var COPILOT_CLI_HOOK_EVENTS, COPILOT_INSTRUCTIONS_BEGIN, COPILOT_INSTRUCTIONS_END, HOOK_TIMEOUT_SEC;
2731
2797
  var init_copilot_cli_install = __esm({
2732
2798
  "src/bridges/copilot_cli_install.ts"() {
2733
2799
  "use strict";
2734
2800
  init_define_import_meta_env();
2735
2801
  init_util2();
2736
2802
  init_copilot_cli();
2803
+ init_guidance_block();
2737
2804
  COPILOT_CLI_HOOK_EVENTS = [
2738
2805
  "preToolUse",
2739
2806
  "postToolUse",
@@ -2742,6 +2809,8 @@ var init_copilot_cli_install = __esm({
2742
2809
  "subagentStop",
2743
2810
  "userPromptSubmitted"
2744
2811
  ];
2812
+ COPILOT_INSTRUCTIONS_BEGIN = "<!-- token-goat-begin -->";
2813
+ COPILOT_INSTRUCTIONS_END = "<!-- token-goat-end -->";
2745
2814
  HOOK_TIMEOUT_SEC = 60;
2746
2815
  }
2747
2816
  });
@@ -3032,7 +3101,8 @@ var init_hook_registry = __esm({
3032
3101
  stop: "Stop",
3033
3102
  pre_compact: "PreCompact",
3034
3103
  user_prompt_submit: "UserPromptSubmit",
3035
- subagent_stop: "SubagentStop"
3104
+ subagent_stop: "SubagentStop",
3105
+ session_start: "SessionStart"
3036
3106
  };
3037
3107
  EVENTS_WITHOUT_ADDITIONAL_CONTEXT = /* @__PURE__ */ new Set([
3038
3108
  "notification",
@@ -3709,6 +3779,8 @@ function _buildConfig(raw, projectRaw = {}) {
3709
3779
  hi.cross_session_read_dedup_ttl_secs = envInt("TOKEN_GOAT_CROSS_SESSION_READ_DEDUP_TTL_SECS", hi.cross_session_read_dedup_ttl_secs, ...boundsOf("hints.cross_session_read_dedup_ttl_secs"));
3710
3780
  hi.mcp_dedup_ttl_secs = validatedInt(hi_raw["mcp_dedup_ttl_secs"], hi.mcp_dedup_ttl_secs, ...boundsOf("hints.mcp_dedup_ttl_secs"));
3711
3781
  hi.mcp_dedup_ttl_secs = envInt("TOKEN_GOAT_MCP_DEDUP_TTL_SECS", hi.mcp_dedup_ttl_secs, ...boundsOf("hints.mcp_dedup_ttl_secs"));
3782
+ hi.session_start_reminder = validatedBool(hi_raw["session_start_reminder"], hi.session_start_reminder);
3783
+ hi.session_start_reminder = envBool("TOKEN_GOAT_SESSION_START_REMINDER", hi.session_start_reminder);
3712
3784
  hi.min_session_hint_savings_bytes = envInt("TOKEN_GOAT_SESSION_HINT_MIN_BYTES", hi.min_session_hint_savings_bytes, ...boundsOf("hints.min_session_hint_savings_bytes"));
3713
3785
  const triggers_raw = hi_raw["prompt_triggers"];
3714
3786
  if (Array.isArray(triggers_raw)) {
@@ -3896,7 +3968,8 @@ function saveConfig(config2) {
3896
3968
  log_large_file_hint_outcomes: config2.hints.log_large_file_hint_outcomes,
3897
3969
  cross_session_read_dedup: config2.hints.cross_session_read_dedup,
3898
3970
  cross_session_read_dedup_ttl_secs: config2.hints.cross_session_read_dedup_ttl_secs,
3899
- mcp_dedup_ttl_secs: config2.hints.mcp_dedup_ttl_secs
3971
+ mcp_dedup_ttl_secs: config2.hints.mcp_dedup_ttl_secs,
3972
+ session_start_reminder: config2.hints.session_start_reminder
3900
3973
  },
3901
3974
  hooks: {
3902
3975
  watchdog_ms: config2.hooks.watchdog_ms
@@ -4074,7 +4147,8 @@ var init_config = __esm({
4074
4147
  log_large_file_hint_outcomes: false,
4075
4148
  cross_session_read_dedup: false,
4076
4149
  cross_session_read_dedup_ttl_secs: 2700,
4077
- mcp_dedup_ttl_secs: 45
4150
+ mcp_dedup_ttl_secs: 45,
4151
+ session_start_reminder: true
4078
4152
  },
4079
4153
  hooks: {
4080
4154
  watchdog_ms: 700
@@ -4254,6 +4328,7 @@ var init_config = __esm({
4254
4328
  "hints.cross_session_read_dedup": ["TOKEN_GOAT_CROSS_SESSION_READ_DEDUP"],
4255
4329
  "hints.cross_session_read_dedup_ttl_secs": ["TOKEN_GOAT_CROSS_SESSION_READ_DEDUP_TTL_SECS"],
4256
4330
  "hints.mcp_dedup_ttl_secs": ["TOKEN_GOAT_MCP_DEDUP_TTL_SECS"],
4331
+ "hints.session_start_reminder": ["TOKEN_GOAT_SESSION_START_REMINDER"],
4257
4332
  "hints.min_session_hint_savings_bytes": ["TOKEN_GOAT_SESSION_HINT_MIN_BYTES"],
4258
4333
  "hooks.watchdog_ms": ["TOKEN_GOAT_HOOK_WATCHDOG_MS"],
4259
4334
  "webfetch.max_file_count": ["TOKEN_GOAT_WEB_CACHE_MAX_FILES"],
@@ -5763,6 +5838,7 @@ var init_stats = __esm({
5763
5838
  session_slice: SOURCE_READ,
5764
5839
  gdrive_sections: SOURCE_READ,
5765
5840
  pr_slice: SOURCE_READ,
5841
+ compact_doc: SOURCE_READ,
5766
5842
  note_read: SOURCE_READ,
5767
5843
  note_list: SOURCE_READ,
5768
5844
  // note-add is a write (like insert-section/replace, which record no stat at all -- neither
@@ -5833,6 +5909,7 @@ var init_stats = __esm({
5833
5909
  "session-slice": /* @__PURE__ */ new Set(["session_slice"]),
5834
5910
  "gdrive-sections": /* @__PURE__ */ new Set(["gdrive_sections"]),
5835
5911
  "pr-slice": /* @__PURE__ */ new Set(["pr_slice"]),
5912
+ "compact-doc": /* @__PURE__ */ new Set(["compact_doc"]),
5836
5913
  "note-add": /* @__PURE__ */ new Set(["note_write"]),
5837
5914
  "note-get": /* @__PURE__ */ new Set(["note_read"]),
5838
5915
  "note-list": /* @__PURE__ */ new Set(["note_list"]),
@@ -7343,23 +7420,23 @@ function isNoisePath(inputPath) {
7343
7420
  }
7344
7421
  }
7345
7422
  const slashIdx = p.lastIndexOf("/");
7346
- const basename19 = slashIdx >= 0 ? p.slice(slashIdx + 1) : p;
7347
- if (NOISE_BASENAMES.has(basename19)) {
7423
+ const basename20 = slashIdx >= 0 ? p.slice(slashIdx + 1) : p;
7424
+ if (NOISE_BASENAMES.has(basename20)) {
7348
7425
  return true;
7349
7426
  }
7350
- if (basename19.startsWith(".improve-state-") || basename19.startsWith("improve_commit_msg_")) {
7427
+ if (basename20.startsWith(".improve-state-") || basename20.startsWith("improve_commit_msg_")) {
7351
7428
  return true;
7352
7429
  }
7353
- const dotIdx = basename19.lastIndexOf(".");
7430
+ const dotIdx = basename20.lastIndexOf(".");
7354
7431
  if (dotIdx >= 0) {
7355
- const ext2 = basename19.slice(dotIdx);
7432
+ const ext2 = basename20.slice(dotIdx);
7356
7433
  if (NOISE_EXTS.has(ext2)) {
7357
7434
  return true;
7358
7435
  }
7359
7436
  }
7360
7437
  for (const ext2 of NOISE_EXTS) {
7361
7438
  if (ext2.includes(".") && ext2.split(".").length > 2) {
7362
- if (basename19.endsWith(ext2)) {
7439
+ if (basename20.endsWith(ext2)) {
7363
7440
  return true;
7364
7441
  }
7365
7442
  }
@@ -7931,11 +8008,11 @@ function buildPackageManifestHint(options) {
7931
8008
  return null;
7932
8009
  }
7933
8010
  }
7934
- function _sanitizeHintPath(path63) {
7935
- if (typeof path63 !== "string") {
8011
+ function _sanitizeHintPath(path64) {
8012
+ if (typeof path64 !== "string") {
7936
8013
  return "???";
7937
8014
  }
7938
- return path63.replace(/[\x00]/g, "").slice(0, 200);
8015
+ return path64.replace(/[\x00]/g, "").slice(0, 200);
7939
8016
  }
7940
8017
  var HINT_PRIORITY_MEDIUM;
7941
8018
  var init_hints = __esm({
@@ -7947,15 +8024,15 @@ var init_hints = __esm({
7947
8024
  });
7948
8025
 
7949
8026
  // src/hints/lang_patterns.ts
7950
- function isLockFile(basename19) {
7951
- return LOCK_FILE_NAMES.has(basename19.toLowerCase());
8027
+ function isLockFile(basename20) {
8028
+ return LOCK_FILE_NAMES.has(basename20.toLowerCase());
7952
8029
  }
7953
- function isManifestFile(basename19) {
7954
- const lower = basename19.toLowerCase();
8030
+ function isManifestFile(basename20) {
8031
+ const lower = basename20.toLowerCase();
7955
8032
  if (MANIFEST_FILE_NAMES.has(lower)) return true;
7956
8033
  const dot = lower.lastIndexOf(".");
7957
8034
  if (dot !== -1 && MANIFEST_EXTENSIONS.has(lower.slice(dot))) return true;
7958
- if (MANIFEST_BASENAME_PATTERNS.some((re) => re.test(basename19))) return true;
8035
+ if (MANIFEST_BASENAME_PATTERNS.some((re) => re.test(basename20))) return true;
7959
8036
  return false;
7960
8037
  }
7961
8038
  function pathSegments(filePath) {
@@ -8294,8 +8371,8 @@ function formatHeadingTree(headings, filePath) {
8294
8371
  }
8295
8372
  return lines2.join("\n");
8296
8373
  }
8297
- function getWellKnownSections(basename19) {
8298
- return WELL_KNOWN_SECTIONS[basename19] ?? [];
8374
+ function getWellKnownSections(basename20) {
8375
+ return WELL_KNOWN_SECTIONS[basename20] ?? [];
8299
8376
  }
8300
8377
  function extractChangelogVersionHint(content, filePath) {
8301
8378
  const lines2 = content.split("\n");
@@ -11006,16 +11083,16 @@ var init_file_type_handler = __esm({
11006
11083
 
11007
11084
  // src/skill_cache.ts
11008
11085
  import * as fs15 from "fs/promises";
11009
- import { resolve as resolve5 } from "path";
11086
+ import { resolve as resolve6 } from "path";
11010
11087
  import { homedir as homedir7 } from "os";
11011
- import { readdirSync as readdirSync7, readFileSync as readFileSync10, existsSync as existsSync13, statSync as statSync7, unlinkSync as unlinkSync8 } from "node:fs";
11088
+ import { readdirSync as readdirSync8, readFileSync as readFileSync11, existsSync as existsSync13, statSync as statSync7, unlinkSync as unlinkSync8 } from "node:fs";
11012
11089
  function skillOutputsDir() {
11013
11090
  if (_skillOutputsDirOverride) return _skillOutputsDirOverride;
11014
- return resolve5(dataDir(), SKILLS_OUTPUT_SUBDIR);
11091
+ return resolve6(dataDir(), SKILLS_OUTPUT_SUBDIR);
11015
11092
  }
11016
11093
  function skillsSourceDir() {
11017
11094
  if (_skillsSourceDirOverride) return _skillsSourceDirOverride;
11018
- return resolve5(homedir7(), ".claude", "skills");
11095
+ return resolve6(homedir7(), ".claude", "skills");
11019
11096
  }
11020
11097
  async function ensureSkillsDir() {
11021
11098
  try {
@@ -11158,7 +11235,7 @@ async function listOutputs() {
11158
11235
  continue;
11159
11236
  }
11160
11237
  try {
11161
- const content = await fs15.readFile(resolve5(dir, entry.name), "utf-8");
11238
+ const content = await fs15.readFile(resolve6(dir, entry.name), "utf-8");
11162
11239
  const meta3 = JSON.parse(content);
11163
11240
  metas.push(meta3);
11164
11241
  } catch {
@@ -11192,7 +11269,7 @@ async function findCrossSessionEntry(skillName, contentSha) {
11192
11269
  continue;
11193
11270
  }
11194
11271
  const dir = skillOutputsDir();
11195
- const bodyPath = resolve5(dir, `${meta3.outputId}.txt`);
11272
+ const bodyPath = resolve6(dir, `${meta3.outputId}.txt`);
11196
11273
  try {
11197
11274
  const bodyExists = await fs15.access(bodyPath).then(() => true).catch(() => false);
11198
11275
  if (bodyExists) {
@@ -11247,7 +11324,7 @@ async function storeOutput(sessionId, skillName, body, opts) {
11247
11324
  const truncBuf = buf.slice(truncStart);
11248
11325
  storedBody = truncBuf.toString("utf-8");
11249
11326
  }
11250
- await atomicWriteText(resolve5(dir, `${outId}.txt`), storedBody);
11327
+ await atomicWriteText(resolve6(dir, `${outId}.txt`), storedBody);
11251
11328
  const meta3 = {
11252
11329
  outputId: outId,
11253
11330
  skillName: name2,
@@ -11257,7 +11334,7 @@ async function storeOutput(sessionId, skillName, body, opts) {
11257
11334
  truncated,
11258
11335
  sourcePath: opts?.sourcePath || ""
11259
11336
  };
11260
- await atomicWriteText(resolve5(dir, `${outId}.meta`), JSON.stringify(meta3, null, 2));
11337
+ await atomicWriteText(resolve6(dir, `${outId}.meta`), JSON.stringify(meta3, null, 2));
11261
11338
  pruneSkillOutputs();
11262
11339
  return meta3;
11263
11340
  } catch {
@@ -11277,7 +11354,7 @@ async function storeCompact(sessionId, skillName, compactText, sourceSha) {
11277
11354
  text = `<!-- source_sha: ${sourceSha.slice(0, 12)} -->
11278
11355
  ${text}`;
11279
11356
  }
11280
- await atomicWriteText(resolve5(dir, fileId), text);
11357
+ await atomicWriteText(resolve6(dir, fileId), text);
11281
11358
  } catch {
11282
11359
  }
11283
11360
  }
@@ -11293,11 +11370,11 @@ function getCompactAnySessionSync(skillName) {
11293
11370
  const suffix = compactSessionSuffix(skillName);
11294
11371
  if (!suffix) return null;
11295
11372
  const dir = skillOutputsDir();
11296
- const entries = readdirSync7(dir, { withFileTypes: true });
11373
+ const entries = readdirSync8(dir, { withFileTypes: true });
11297
11374
  for (const entry of entries) {
11298
11375
  if (!matchesCompactSuffix(entry.name, entry.isFile(), suffix)) continue;
11299
11376
  try {
11300
- const text = readFileSync10(resolve5(dir, entry.name), "utf-8");
11377
+ const text = readFileSync11(resolve6(dir, entry.name), "utf-8");
11301
11378
  if (text.trim()) return text;
11302
11379
  } catch {
11303
11380
  continue;
@@ -11325,7 +11402,7 @@ async function readSkillHits(skillName) {
11325
11402
  const name2 = safeSkillName(skillName);
11326
11403
  if (!name2) return { count: 0, lastTs: 0 };
11327
11404
  const dir = skillOutputsDir();
11328
- const hitsFile = resolve5(dir, `${sanitizeSkillId(name2)}.hits`);
11405
+ const hitsFile = resolve6(dir, `${sanitizeSkillId(name2)}.hits`);
11329
11406
  const content = await fs15.readFile(hitsFile, "utf-8").catch(() => null);
11330
11407
  if (content) {
11331
11408
  const parsed = JSON.parse(content);
@@ -11375,7 +11452,7 @@ async function incrementSkillHit(skillName) {
11375
11452
  if (!name2) return;
11376
11453
  await ensureSkillsDir();
11377
11454
  const dir = skillOutputsDir();
11378
- const hitsFile = resolve5(dir, `${sanitizeSkillId(name2)}.hits`);
11455
+ const hitsFile = resolve6(dir, `${sanitizeSkillId(name2)}.hits`);
11379
11456
  const lockPath = `${hitsFile}.lock`;
11380
11457
  await runExclusiveInProcess(lockPath, async () => {
11381
11458
  const locked = await acquireSkillHitLock(lockPath);
@@ -11422,14 +11499,14 @@ async function listSkills(sessionId) {
11422
11499
  let compactLen = 0;
11423
11500
  let compactText = "";
11424
11501
  try {
11425
- const stat2 = await fs15.stat(resolve5(dir, compactFileId));
11502
+ const stat2 = await fs15.stat(resolve6(dir, compactFileId));
11426
11503
  compactLen = stat2.size;
11427
- compactText = await fs15.readFile(resolve5(dir, compactFileId), "utf-8").catch(() => "");
11504
+ compactText = await fs15.readFile(resolve6(dir, compactFileId), "utf-8").catch(() => "");
11428
11505
  } catch {
11429
11506
  compactLen = 0;
11430
11507
  }
11431
11508
  const hasMarker = extractCompactFromMarker(
11432
- await fs15.readFile(resolve5(dir, `${meta3.outputId}.txt`), "utf-8").catch(() => "")
11509
+ await fs15.readFile(resolve6(dir, `${meta3.outputId}.txt`), "utf-8").catch(() => "")
11433
11510
  ) !== null;
11434
11511
  const compactStale = isCompactStale(compactText, meta3.skillName, meta3.contentSha);
11435
11512
  const { count: hitCount } = await readSkillHits(meta3.skillName);
@@ -11465,7 +11542,7 @@ async function getSkillFilePath(skillName) {
11465
11542
  }
11466
11543
  }
11467
11544
  async function resolvePluginSkillPath(pluginName, skillSlug) {
11468
- const manifestPath = _pluginsManifestPathOverride ?? resolve5(homedir7(), ".claude", "plugins", "installed_plugins.json");
11545
+ const manifestPath = _pluginsManifestPathOverride ?? resolve6(homedir7(), ".claude", "plugins", "installed_plugins.json");
11469
11546
  let raw;
11470
11547
  try {
11471
11548
  raw = await fs15.readFile(manifestPath, "utf8");
@@ -11488,7 +11565,7 @@ async function resolvePluginSkillPath(pluginName, skillSlug) {
11488
11565
  if (typeof entry !== "object" || entry === null) continue;
11489
11566
  const installPath = entry["installPath"];
11490
11567
  if (typeof installPath !== "string" || installPath === "") continue;
11491
- const diskPath = resolve5(installPath, "skills", skillSlug, "SKILL.md");
11568
+ const diskPath = resolve6(installPath, "skills", skillSlug, "SKILL.md");
11492
11569
  try {
11493
11570
  await fs15.access(diskPath);
11494
11571
  return diskPath;
@@ -11507,7 +11584,7 @@ async function installedSkillPath(skillName) {
11507
11584
  const pluginPath = await resolvePluginSkillPath(name2.slice(0, colonIdx), name2.slice(colonIdx + 1));
11508
11585
  if (pluginPath !== null) return pluginPath;
11509
11586
  }
11510
- const diskPath = resolve5(skillsSourceDir(), name2, "SKILL.md");
11587
+ const diskPath = resolve6(skillsSourceDir(), name2, "SKILL.md");
11511
11588
  try {
11512
11589
  await fs15.access(diskPath);
11513
11590
  return diskPath;
@@ -11522,16 +11599,16 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
11522
11599
  if (!existsSync13(dir)) return 0;
11523
11600
  const cutoff = Date.now() - maxAgeMs;
11524
11601
  const entries = [];
11525
- for (const file2 of readdirSync7(dir)) {
11602
+ for (const file2 of readdirSync8(dir)) {
11526
11603
  if (!file2.endsWith(".meta")) continue;
11527
11604
  const outputId = file2.slice(0, -".meta".length);
11528
11605
  let ts;
11529
11606
  try {
11530
- const parsed = JSON.parse(readFileSync10(resolve5(dir, file2), "utf-8"));
11607
+ const parsed = JSON.parse(readFileSync11(resolve6(dir, file2), "utf-8"));
11531
11608
  ts = parsed.ts;
11532
11609
  } catch {
11533
11610
  try {
11534
- ts = statSync7(resolve5(dir, file2)).mtimeMs;
11611
+ ts = statSync7(resolve6(dir, file2)).mtimeMs;
11535
11612
  } catch {
11536
11613
  continue;
11537
11614
  }
@@ -11541,7 +11618,7 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
11541
11618
  const removeEntry = (outputId) => {
11542
11619
  for (const ext2 of [".meta", ".txt"]) {
11543
11620
  try {
11544
- unlinkSync8(resolve5(dir, `${outputId}${ext2}`));
11621
+ unlinkSync8(resolve6(dir, `${outputId}${ext2}`));
11545
11622
  } catch {
11546
11623
  }
11547
11624
  }
@@ -11655,7 +11732,7 @@ async function ocrImage(input) {
11655
11732
  if (_ocrUnavailableThisProcess) return null;
11656
11733
  const entryPath = resolveTesseractEntry();
11657
11734
  if (entryPath === null) return null;
11658
- return new Promise((resolve20) => {
11735
+ return new Promise((resolve21) => {
11659
11736
  let settled = false;
11660
11737
  let child;
11661
11738
  try {
@@ -11664,7 +11741,7 @@ async function ocrImage(input) {
11664
11741
  });
11665
11742
  } catch {
11666
11743
  _ocrUnavailableThisProcess = true;
11667
- resolve20(null);
11744
+ resolve21(null);
11668
11745
  return;
11669
11746
  }
11670
11747
  const chunks = [];
@@ -11677,7 +11754,7 @@ async function ocrImage(input) {
11677
11754
  child.kill();
11678
11755
  } catch {
11679
11756
  }
11680
- resolve20(result);
11757
+ resolve21(result);
11681
11758
  };
11682
11759
  const timer = setTimeout(() => finish(null, true), _ocrTimeoutMs);
11683
11760
  child.stdout?.on("data", (c) => chunks.push(c));
@@ -11813,19 +11890,19 @@ async function preReadImageHandler(event) {
11813
11890
  }
11814
11891
  const result = await shrinkImage(input, { sizeThresholdBytes: 0 });
11815
11892
  if (result === null) return passOutput();
11816
- const basename19 = path16.basename(filePath);
11893
+ const basename20 = path16.basename(filePath);
11817
11894
  if (loadConfig().image_shrink.ocr_enabled) {
11818
11895
  const ocr = await ocrImage(result.data);
11819
11896
  if (ocr !== null && isTextHeavy(ocr, loadConfig().image_shrink.ocr_min_confidence)) {
11820
11897
  const textBytes = Buffer.byteLength(ocr.text, "utf8");
11821
11898
  const saved2 = Math.max(0, result.shrunkBytes - textBytes);
11822
- recordStat("image_ocr", saved2, Math.round(saved2 / 4), void 0, basename19);
11823
- return contextOutput(formatOcrSummary(ocr, basename19, result.originalBytes));
11899
+ recordStat("image_ocr", saved2, Math.round(saved2 / 4), void 0, basename20);
11900
+ return contextOutput(formatOcrSummary(ocr, basename20, result.originalBytes));
11824
11901
  }
11825
11902
  }
11826
11903
  const saved = result.originalBytes - result.shrunkBytes;
11827
- const { summary, dataUrl } = formatShrinkSummary(result, basename19);
11828
- recordStat("image_shrink", saved, Math.round(saved / 4), void 0, basename19);
11904
+ const { summary, dataUrl } = formatShrinkSummary(result, basename20);
11905
+ recordStat("image_shrink", saved, Math.round(saved / 4), void 0, basename20);
11829
11906
  return contextOutput(`${summary}
11830
11907
  ${dataUrl}`);
11831
11908
  }
@@ -12354,8 +12431,8 @@ var init_parser_types = __esm({
12354
12431
  // src/hooks_read.ts
12355
12432
  import * as fs19 from "node:fs";
12356
12433
  import * as path20 from "node:path";
12357
- function isTsConfigFile(basename19) {
12358
- const lower = basename19.toLowerCase();
12434
+ function isTsConfigFile(basename20) {
12435
+ const lower = basename20.toLowerCase();
12359
12436
  return /^tsconfig(\..+)?\.json$/i.test(lower) || lower === "jsconfig.json";
12360
12437
  }
12361
12438
  function largeFileDenyBytes() {
@@ -12455,18 +12532,18 @@ function describeSliceAdvice(slice, absPath) {
12455
12532
  }
12456
12533
  return "Use Read with offset/limit to sample specific sections.";
12457
12534
  }
12458
- function isSourceExtension(basename19) {
12459
- if (SOURCE_EXT_RE.test(basename19)) return true;
12460
- const language = detectLanguage(basename19);
12535
+ function isSourceExtension(basename20) {
12536
+ if (SOURCE_EXT_RE.test(basename20)) return true;
12537
+ const language = detectLanguage(basename20);
12461
12538
  return language === "apex" || language === "salesforce_metadata" || language === "salesforce_markup";
12462
12539
  }
12463
- function isDispatchedFileType(basename19) {
12464
- return DISPATCHED_FILE_TYPE_EXTS.has(path20.extname(basename19).slice(1).toLowerCase());
12540
+ function isDispatchedFileType(basename20) {
12541
+ return DISPATCHED_FILE_TYPE_EXTS.has(path20.extname(basename20).slice(1).toLowerCase());
12465
12542
  }
12466
- function surgicalHint(filePath, basename19, lineCount) {
12543
+ function surgicalHint(filePath, basename20, lineCount) {
12467
12544
  if (lineCount < loadConfig().hints.min_file_lines_for_hint) return "";
12468
- const isDocFile = /\.(md|mdx|rst|txt)$/i.test(basename19);
12469
- const isSectionFile = /\.(json|jsonc|css|scss|sass|less|yaml|yml|toml)$/i.test(basename19);
12545
+ const isDocFile = /\.(md|mdx|rst|txt)$/i.test(basename20);
12546
+ const isSectionFile = /\.(json|jsonc|css|scss|sass|less|yaml|yml|toml)$/i.test(basename20);
12470
12547
  if (isDocFile) {
12471
12548
  return 'Use `token-goat section "' + filePath + '::HeadingName"` to extract a part.';
12472
12549
  } else if (isSectionFile) {
@@ -12514,7 +12591,7 @@ function buildLineDiff(oldContent, newContent, label) {
12514
12591
  }
12515
12592
  return out2.join("\n");
12516
12593
  }
12517
- function loadSnapshotDiff(sessionId, normalized, basename19) {
12594
+ function loadSnapshotDiff(sessionId, normalized, basename20) {
12518
12595
  const oldSnap = load(sessionId, normalized);
12519
12596
  if (oldSnap === null) return { kind: "none" };
12520
12597
  try {
@@ -12526,7 +12603,7 @@ function loadSnapshotDiff(sessionId, normalized, basename19) {
12526
12603
  const truncIdx = oldRaw.indexOf(TRUNC_MARKER);
12527
12604
  const oldContent = truncIdx >= 0 ? oldRaw.slice(0, truncIdx) : oldRaw;
12528
12605
  if (oldContent === currentContent) return { kind: "unchanged", currentContent };
12529
- const diff = buildLineDiff(oldContent, currentContent, basename19);
12606
+ const diff = buildLineDiff(oldContent, currentContent, basename20);
12530
12607
  if (diff === "") return { kind: "none" };
12531
12608
  const savedBytes = Math.max(0, currentContent.length - diff.length);
12532
12609
  return { kind: "diff", diff, savedBytes, currentContent };
@@ -12599,8 +12676,8 @@ function preReadHandlerInner(event) {
12599
12676
  "node_modules is typically noise; use npm ls, npm outdated, or npm audit instead for dependency info. To force access, use: token-goat read node_modules/package/file.js::symbol-name or token-goat section node_modules/package/file.js::heading"
12600
12677
  );
12601
12678
  }
12602
- const basename19 = path20.basename(normalized);
12603
- if (isLockFile(basename19)) {
12679
+ const basename20 = path20.basename(normalized);
12680
+ if (isLockFile(basename20)) {
12604
12681
  return denyOutput(
12605
12682
  'Lock files are rarely useful to read in full. Use `token-goat section "' + normalized + '::<section>"` to extract a specific dependency, or read the relevant manifest instead.'
12606
12683
  );
@@ -12623,20 +12700,20 @@ function preReadHandlerInner(event) {
12623
12700
  return quietContextOutput(manifestHint.text);
12624
12701
  }
12625
12702
  }
12626
- if (isTsConfigFile(basename19) && wasFileReadThisSession(normalized)) {
12703
+ if (isTsConfigFile(basename20) && wasFileReadThisSession(normalized)) {
12627
12704
  recordActualRead(event, normalized);
12628
12705
  return quietContextOutput(
12629
- "Already read " + basename19 + '. Use `token-goat section "' + normalized + '::compilerOptions"` to extract compiler options, or `token-goat config-get ' + normalized + " compilerOptions.target` for a single value."
12706
+ "Already read " + basename20 + '. Use `token-goat section "' + normalized + '::compilerOptions"` to extract compiler options, or `token-goat config-get ' + normalized + " compilerOptions.target` for a single value."
12630
12707
  );
12631
12708
  }
12632
- if (isManifestFile(basename19) && wasFileReadThisSession(normalized)) {
12709
+ if (isManifestFile(basename20) && wasFileReadThisSession(normalized)) {
12633
12710
  recordActualRead(event, normalized);
12634
12711
  return quietContextOutput(
12635
- "You've already read " + basename19 + '. Use `token-goat section "' + normalized + '::<field>"` or `token-goat config-get ' + normalized + " <key>` to extract just the value you need."
12712
+ "You've already read " + basename20 + '. Use `token-goat section "' + normalized + '::<field>"` or `token-goat config-get ' + normalized + " <key>` to extract just the value you need."
12636
12713
  );
12637
12714
  }
12638
12715
  const skillName = detectSkillFile(normalized);
12639
- if (skillName && basename19 === "SKILL.md") {
12716
+ if (skillName && basename20 === "SKILL.md") {
12640
12717
  try {
12641
12718
  const body = fs19.readFileSync(normalized, "utf-8");
12642
12719
  const bodySha = contentHash(body);
@@ -12666,7 +12743,7 @@ function preReadHandlerInner(event) {
12666
12743
  }
12667
12744
  }
12668
12745
  }
12669
- const isNotebook = /\.ipynb$/i.test(basename19);
12746
+ const isNotebook = /\.ipynb$/i.test(basename20);
12670
12747
  if (event.toolName !== "Grep" && isNotebook) {
12671
12748
  try {
12672
12749
  const rawBytes = fs19.readFileSync(normalized);
@@ -12683,7 +12760,7 @@ function preReadHandlerInner(event) {
12683
12760
  } catch {
12684
12761
  }
12685
12762
  }
12686
- const isMarkdown = /\.(md|mdx|markdown|rst)$/i.test(basename19);
12763
+ const isMarkdown = /\.(md|mdx|markdown|rst)$/i.test(basename20);
12687
12764
  if (event.toolName !== "Grep" && isMarkdown) {
12688
12765
  let fileContent = null;
12689
12766
  let markdownSize = null;
@@ -12701,9 +12778,9 @@ function preReadHandlerInner(event) {
12701
12778
  const alreadyRead = wasFileReadThisSession(normalized);
12702
12779
  const hintText = formatHeadingTree(headings, normalized);
12703
12780
  const headingTextsLower = new Set(headings.map((h) => h.text.trim().toLowerCase()));
12704
- const wellKnown = getWellKnownSections(basename19).filter((s) => headingTextsLower.has(s.trim().toLowerCase()));
12781
+ const wellKnown = getWellKnownSections(basename20).filter((s) => headingTextsLower.has(s.trim().toLowerCase()));
12705
12782
  const wellKnownText = wellKnown.length > 0 ? "\nQuick access: " + wellKnown.map((s) => 'token-goat section "' + normalized + "::" + s + '"').join(" | ") : "";
12706
- const changelogExtra = basename19.toLowerCase() === "changelog.md" ? extractChangelogVersionHint(fileContent, normalized) : "";
12783
+ const changelogExtra = basename20.toLowerCase() === "changelog.md" ? extractChangelogVersionHint(fileContent, normalized) : "";
12707
12784
  let message = hintText + wellKnownText + changelogExtra;
12708
12785
  const slice = estimateRequestedSlice(event, normalized);
12709
12786
  const gateSize = slice.kind === "bytes" && markdownSize !== null ? Math.min(slice.bytes, markdownSize) : markdownSize;
@@ -12724,19 +12801,19 @@ function preReadHandlerInner(event) {
12724
12801
  if (isMemoryMd && wasFileReadThisSession(normalized)) {
12725
12802
  recordActualRead(event, normalized);
12726
12803
  recordStat("session_hint", 0, 0);
12727
- const isMainMemory = basename19.toLowerCase() === "memory.md";
12804
+ const isMainMemory = basename20.toLowerCase() === "memory.md";
12728
12805
  return denyOutput(
12729
12806
  isMainMemory ? "MEMORY.md was read this session. Its content is in the compact manifest as 'session memory'." : normalized + ' was already read this session. Memory files rarely change mid-session. Use `token-goat section "' + normalized + '::SectionHeading"` to extract one section.'
12730
12807
  );
12731
12808
  }
12732
- if (/^\.improve-state-.*\.json$/.test(basename19) && wasFileReadThisSession(normalized)) {
12809
+ if (/^\.improve-state-.*\.json$/.test(basename20) && wasFileReadThisSession(normalized)) {
12733
12810
  recordActualRead(event, normalized);
12734
12811
  recordStat("session_hint", 0, 0);
12735
12812
  return denyOutput(
12736
12813
  "Orchestrator state already read this session. " + sessionArtifactRecall(normalized)
12737
12814
  );
12738
12815
  }
12739
- if (/^\.env(\.\w+)?$/.test(basename19) && wasFileReadThisSession(normalized)) {
12816
+ if (/^\.env(\.\w+)?$/.test(basename20) && wasFileReadThisSession(normalized)) {
12740
12817
  recordActualRead(event, normalized);
12741
12818
  recordStat("session_hint", 0, 0);
12742
12819
  return denyOutput(
@@ -12753,19 +12830,19 @@ function preReadHandlerInner(event) {
12753
12830
  );
12754
12831
  }
12755
12832
  const artifactSessionId = getSessionId();
12756
- const snapDiff = loadSnapshotDiff(artifactSessionId, normalized, basename19);
12833
+ const snapDiff = loadSnapshotDiff(artifactSessionId, normalized, basename20);
12757
12834
  if (snapDiff.kind === "unchanged") {
12758
12835
  recordActualRead(event, normalized);
12759
12836
  recordStat("session_hint", 0, 0);
12760
12837
  return denyOutput(
12761
- basename19 + " is unchanged since last read. " + sessionArtifactRecall(normalized)
12838
+ basename20 + " is unchanged since last read. " + sessionArtifactRecall(normalized)
12762
12839
  );
12763
12840
  }
12764
12841
  if (snapDiff.kind === "diff") {
12765
12842
  recordActualRead(event, normalized);
12766
12843
  recordStat("session_hint", snapDiff.savedBytes, Math.round(snapDiff.savedBytes / 4));
12767
12844
  return denyOutput(
12768
- "Content changed since last read of " + basename19 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + sessionArtifactRecall(normalized)
12845
+ "Content changed since last read of " + basename20 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + sessionArtifactRecall(normalized)
12769
12846
  );
12770
12847
  }
12771
12848
  recordActualRead(event, normalized);
@@ -12788,8 +12865,8 @@ function preReadHandlerInner(event) {
12788
12865
  return quietContextOutput(label + ": " + sessionArtifactRecall(normalized));
12789
12866
  }
12790
12867
  }
12791
- const isDocDiffable = /\.(md|mdx|markdown|rst|txt)$/i.test(basename19);
12792
- const isSourceDiffable = loadConfig().hints.serve_diff_on_reread && DIFFABLE_SOURCE_RE.test(basename19);
12868
+ const isDocDiffable = /\.(md|mdx|markdown|rst|txt)$/i.test(basename20);
12869
+ const isSourceDiffable = loadConfig().hints.serve_diff_on_reread && DIFFABLE_SOURCE_RE.test(basename20);
12793
12870
  if ((isDocDiffable || isSourceDiffable) && wasFileReadThisSession(normalized) && !isProtectedRecentRead(normalized, loadConfig().hints.protect_recent_reads)) {
12794
12871
  if (wasFileTruncatedThisSession(normalized)) {
12795
12872
  if (estimateTruncatedLineCount(normalized) >= loadConfig().hints.truncated_read_min_lines) {
@@ -12799,12 +12876,12 @@ function preReadHandlerInner(event) {
12799
12876
  }
12800
12877
  }
12801
12878
  const sessionId = getSessionId();
12802
- const snapDiff = loadSnapshotDiff(sessionId, normalized, basename19);
12879
+ const snapDiff = loadSnapshotDiff(sessionId, normalized, basename20);
12803
12880
  if (snapDiff.kind === "unchanged") {
12804
12881
  recordActualRead(event, normalized);
12805
12882
  recordStat("session_hint", 0, 0);
12806
12883
  return denyOutput(
12807
- (basename19 + " is unchanged since last read. " + surgicalHint(normalized, basename19, countTextLines(snapDiff.currentContent))).trimEnd()
12884
+ (basename20 + " is unchanged since last read. " + surgicalHint(normalized, basename20, countTextLines(snapDiff.currentContent))).trimEnd()
12808
12885
  );
12809
12886
  }
12810
12887
  if (snapDiff.kind === "diff") {
@@ -12812,7 +12889,7 @@ function preReadHandlerInner(event) {
12812
12889
  recordActualRead(event, normalized);
12813
12890
  recordStat("diff_hint", snapDiff.savedBytes, Math.round(snapDiff.savedBytes / 4));
12814
12891
  return denyOutput(
12815
- ("Content changed since last read of " + basename19 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + surgicalHint(normalized, basename19, countTextLines(snapDiff.currentContent))).trimEnd()
12892
+ ("Content changed since last read of " + basename20 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + surgicalHint(normalized, basename20, countTextLines(snapDiff.currentContent))).trimEnd()
12816
12893
  );
12817
12894
  }
12818
12895
  }
@@ -12858,13 +12935,13 @@ function preReadHandlerInner(event) {
12858
12935
  return denyOutput(truncatedReadDenyMessage(normalized));
12859
12936
  }
12860
12937
  }
12861
- if (/\.(md|mdx|markdown|rst)$/i.test(basename19)) {
12938
+ if (/\.(md|mdx|markdown|rst)$/i.test(basename20)) {
12862
12939
  recordStat("session_hint", rereadBytes, Math.round(rereadBytes / 4));
12863
12940
  return denyOutput(
12864
12941
  'Markdown file already read this session. Use `token-goat section "' + normalized + '::HeadingName"` to read one section. ' + editAnywayHint(normalized)
12865
12942
  );
12866
12943
  }
12867
- const isSourceExt = isSourceExtension(basename19);
12944
+ const isSourceExt = isSourceExtension(basename20);
12868
12945
  if (isSourceExt && reads >= 2) {
12869
12946
  recordStat("read_count_deny", rereadBytes, Math.round(rereadBytes / 4));
12870
12947
  recordStat("session_hint", rereadBytes, Math.round(rereadBytes / 4));
@@ -13115,7 +13192,7 @@ function ensureTransformerLoaded() {
13115
13192
  }
13116
13193
  }
13117
13194
  function sleep(ms) {
13118
- return new Promise((resolve20) => setTimeout(resolve20, ms));
13195
+ return new Promise((resolve21) => setTimeout(resolve21, ms));
13119
13196
  }
13120
13197
  async function buildExtractorWithRetry(pipelineFn, modelName) {
13121
13198
  let lastError;
@@ -13799,8 +13876,8 @@ async function readOoxmlZip(filePath) {
13799
13876
  const data = fs20.readFileSync(filePath);
13800
13877
  return fflate.unzipSync(new Uint8Array(data));
13801
13878
  }
13802
- function decodeZipEntry(entries, path63) {
13803
- const bytes = entries[path63];
13879
+ function decodeZipEntry(entries, path64) {
13880
+ const bytes = entries[path64];
13804
13881
  if (bytes === void 0) return null;
13805
13882
  return new TextDecoder("utf-8").decode(bytes);
13806
13883
  }
@@ -14010,9 +14087,9 @@ async function notesPathFor(entries, slidePath) {
14010
14087
  }
14011
14088
  return null;
14012
14089
  }
14013
- async function parseSlide(entries, path63) {
14014
- const xml = decodeZipEntry(entries, path63);
14015
- if (xml === null) throw new Error(`missing part: ${path63}`);
14090
+ async function parseSlide(entries, path64) {
14091
+ const xml = decodeZipEntry(entries, path64);
14092
+ if (xml === null) throw new Error(`missing part: ${path64}`);
14016
14093
  return parseOoxmlPart(xml);
14017
14094
  }
14018
14095
  async function notesTextFor(entries, notesPath) {
@@ -14028,8 +14105,8 @@ async function pptxOutline(filePath) {
14028
14105
  const { entries, slidePaths } = await listSlideParts(filePath);
14029
14106
  const out2 = [];
14030
14107
  for (let i = 0; i < slidePaths.length; i++) {
14031
- const path63 = slidePaths[i];
14032
- const parsed = await parseSlide(entries, path63);
14108
+ const path64 = slidePaths[i];
14109
+ const parsed = await parseSlide(entries, path64);
14033
14110
  const shapes = slideShapes(parsed);
14034
14111
  const titleShape = shapes.find((s) => {
14035
14112
  const t = shapePlaceholderType(s);
@@ -14038,7 +14115,7 @@ async function pptxOutline(filePath) {
14038
14115
  const title = titleShape !== void 0 ? shapeText(titleShape) : "";
14039
14116
  const allText = collectTextRuns(parsed, "a:t").join(" ");
14040
14117
  const bodyChars = Math.max(0, allText.length - title.length);
14041
- const hasNotes = (await notesTextFor(entries, await notesPathFor(entries, path63))).length > 0;
14118
+ const hasNotes = (await notesTextFor(entries, await notesPathFor(entries, path64))).length > 0;
14042
14119
  out2.push({ slide: i + 1, title, bodyChars, hasNotes });
14043
14120
  }
14044
14121
  return out2;
@@ -14048,13 +14125,13 @@ async function pptxSlideText(filePath, slideNumber, includeNotes) {
14048
14125
  if (slideNumber < 1 || slideNumber > slidePaths.length) {
14049
14126
  throw new Error(`slide ${slideNumber} out of range (this deck has ${slidePaths.length} slides)`);
14050
14127
  }
14051
- const path63 = slidePaths[slideNumber - 1];
14052
- const parsed = await parseSlide(entries, path63);
14128
+ const path64 = slidePaths[slideNumber - 1];
14129
+ const parsed = await parseSlide(entries, path64);
14053
14130
  const shapes = slideShapes(parsed);
14054
14131
  const blocks = [...shapes.map(shapeText).filter((t) => t.length > 0), ...tableRowBlocks(parsed)];
14055
14132
  const lines2 = [`# Slide ${slideNumber}`, ...blocks];
14056
14133
  if (includeNotes) {
14057
- const notes = await notesTextFor(entries, await notesPathFor(entries, path63));
14134
+ const notes = await notesTextFor(entries, await notesPathFor(entries, path64));
14058
14135
  if (notes.length > 0) lines2.push("", "## Speaker notes", notes);
14059
14136
  }
14060
14137
  return lines2.join("\n\n");
@@ -18160,6 +18237,77 @@ function loadParserCtor() {
18160
18237
  }
18161
18238
  return _parserCtor;
18162
18239
  }
18240
+ function boundSymbolBody(body) {
18241
+ return body.length > MAX_SYMBOL_BODY_CHARS ? "" : body;
18242
+ }
18243
+ function boundSymbolDocstring(docstring) {
18244
+ if (docstring.length <= MAX_SYMBOL_DOCSTRING_CHARS) return docstring;
18245
+ let end = MAX_SYMBOL_DOCSTRING_CHARS - DOCSTRING_TRUNCATION_MARKER.length;
18246
+ const cutsSurrogatePair = end > 0 && docstring.charCodeAt(end - 1) >= 55296 && docstring.charCodeAt(end - 1) <= 56319;
18247
+ if (cutsSurrogatePair) end -= 1;
18248
+ return docstring.slice(0, Math.max(0, end)) + DOCSTRING_TRUNCATION_MARKER;
18249
+ }
18250
+ function scanJsonValueEnd(content, start) {
18251
+ const first2 = content[start];
18252
+ if (first2 === void 0) return content.length;
18253
+ if (first2 === '"') {
18254
+ let escaping = false;
18255
+ for (let j = start + 1; j < content.length; j++) {
18256
+ const c = content[j];
18257
+ if (escaping) {
18258
+ escaping = false;
18259
+ continue;
18260
+ }
18261
+ if (c === "\\") {
18262
+ escaping = true;
18263
+ continue;
18264
+ }
18265
+ if (c === '"') return j + 1;
18266
+ }
18267
+ return content.length;
18268
+ }
18269
+ if (first2 === "{" || first2 === "[") {
18270
+ const stack = [];
18271
+ let inStr = false;
18272
+ let escaping = false;
18273
+ for (let j = start; j < content.length; j++) {
18274
+ const c = content[j];
18275
+ if (inStr) {
18276
+ if (escaping) {
18277
+ escaping = false;
18278
+ continue;
18279
+ }
18280
+ if (c === "\\") {
18281
+ escaping = true;
18282
+ continue;
18283
+ }
18284
+ if (c === '"') inStr = false;
18285
+ continue;
18286
+ }
18287
+ if (c === '"') {
18288
+ inStr = true;
18289
+ continue;
18290
+ }
18291
+ if (c === "{" || c === "[") stack.push(c);
18292
+ else if (c === "}" || c === "]") {
18293
+ const open = stack.pop();
18294
+ if (open !== (c === "}" ? "{" : "[")) return j;
18295
+ if (stack.length === 0) return j + 1;
18296
+ }
18297
+ }
18298
+ return content.length;
18299
+ }
18300
+ for (let j = start; j < content.length; j++) {
18301
+ const c = content[j];
18302
+ if (c === "," || c === "}" || c === "]" || c === "\n" || c === "\r") return j;
18303
+ }
18304
+ return content.length;
18305
+ }
18306
+ function countNewlines(s) {
18307
+ let n = 0;
18308
+ for (let i = 0; i < s.length; i++) if (s[i] === "\n") n++;
18309
+ return n;
18310
+ }
18163
18311
  function loadGrammar(lang, filePath, content) {
18164
18312
  const useTsx = lang === "typescript" && filePath !== void 0 && path26.extname(filePath).toLowerCase() === ".tsx";
18165
18313
  const useCppHeader = lang === "c" && filePath !== void 0 && path26.extname(filePath).toLowerCase() === ".h" && content !== void 0 && CPP_HEADER_SNIFF_RE.test(content);
@@ -18701,12 +18849,12 @@ function extractMarkdownSymbols(content, filePath) {
18701
18849
  function extractJsonSymbols(content, filePath) {
18702
18850
  const out2 = [];
18703
18851
  try {
18704
- const lines2 = content.split(/\r?\n/);
18705
18852
  let depth = 0;
18706
18853
  let inString = false;
18707
18854
  let escaping = false;
18708
18855
  let strChars = [];
18709
18856
  let strStartLine = 1;
18857
+ let strStartOffset = 0;
18710
18858
  let depthWhenStringOpened = 0;
18711
18859
  let line = 1;
18712
18860
  for (let i = 0; i < content.length; i++) {
@@ -18727,45 +18875,22 @@ function extractJsonSymbols(content, filePath) {
18727
18875
  inString = true;
18728
18876
  strChars = [];
18729
18877
  strStartLine = line;
18878
+ strStartOffset = i;
18730
18879
  depthWhenStringOpened = depth;
18731
18880
  } else {
18732
18881
  inString = false;
18733
18882
  let k = i + 1;
18734
- let keyToColonNewlines = 0;
18735
18883
  while (k < content.length && /\s/.test(content[k] ?? "")) {
18736
- if (content[k] === "\n") keyToColonNewlines++;
18737
18884
  k++;
18738
18885
  }
18739
18886
  if (content[k] === ":" && depthWhenStringOpened === 1) {
18740
18887
  let v = k + 1;
18741
- let gapNewlines = 0;
18742
18888
  while (v < content.length && /\s/.test(content[v] ?? "")) {
18743
- if (content[v] === "\n") gapNewlines++;
18744
18889
  v++;
18745
18890
  }
18746
- let lineEnd = strStartLine;
18747
- let body = (lines2[strStartLine - 1] ?? "").trim();
18748
- if (content[v] === '"') {
18749
- let valueLine = line + keyToColonNewlines + gapNewlines;
18750
- let valueEscaping = false;
18751
- for (let j = v + 1; j < content.length; j++) {
18752
- const vch = content[j];
18753
- if (vch === "\n") valueLine++;
18754
- if (valueEscaping) {
18755
- valueEscaping = false;
18756
- continue;
18757
- }
18758
- if (vch === "\\") {
18759
- valueEscaping = true;
18760
- continue;
18761
- }
18762
- if (vch === '"') break;
18763
- }
18764
- lineEnd = valueLine;
18765
- if (lineEnd > strStartLine) {
18766
- body = lines2.slice(strStartLine - 1, lineEnd).join("\n").trim();
18767
- }
18768
- }
18891
+ const valueEnd = scanJsonValueEnd(content, v);
18892
+ const body = content.slice(strStartOffset, valueEnd);
18893
+ const lineEnd = strStartLine + countNewlines(body);
18769
18894
  out2.push({
18770
18895
  filePath,
18771
18896
  name: strChars.join(""),
@@ -19237,7 +19362,7 @@ function writeParseResult(filePath, content, result, dbPath) {
19237
19362
  );
19238
19363
  for (const s of result.symbols) {
19239
19364
  if (s.name === "" || s.kind === "") continue;
19240
- insSym.run(s.filePath, s.name, s.kind, s.lineStart, s.lineEnd, s.body, s.docstring);
19365
+ insSym.run(s.filePath, s.name, s.kind, s.lineStart, s.lineEnd, boundSymbolBody(s.body), boundSymbolDocstring(s.docstring));
19241
19366
  }
19242
19367
  const insRef = db.prepare(
19243
19368
  "INSERT INTO refs (file_path, name, line, col, context) VALUES (?, ?, ?, ?, ?)"
@@ -19382,7 +19507,7 @@ function safeMtime(filePath) {
19382
19507
  return 0;
19383
19508
  }
19384
19509
  }
19385
- var _require4, _parserCtor, _grammarCache, CPP_HEADER_SNIFF_RE, TSJS_KIND_BY_TYPE, TSJS_FN_SCOPE_TYPES, PY_KIND_BY_TYPE, GO_KIND_BY_TYPE, GO_FN_SCOPE_TYPES, GO_LOCAL_KINDS, RUST_KIND_BY_TYPE, RUST_FN_SCOPE_TYPES, RUST_LOCAL_KINDS, RUBY_KIND_BY_TYPE, JAVA_KIND_BY_TYPE, CPP_KIND_BY_TYPE, REF_LANGUAGES, SCOPE_TYPES_BY_LANG, CALL_TYPES_BY_LANG, REF_NOISE_BY_LANG, JS_NOISE, EMPTY_STRING_SET, FALLBACK_PATTERNS, NO_TREE_SITTER_EXTRACTORS, DISABLED_EMBED_SHA_PREFIX, UNAVAILABLE_EMBED_SHA_PREFIX;
19510
+ var _require4, _parserCtor, _grammarCache, CPP_HEADER_SNIFF_RE, MAX_SYMBOL_BODY_CHARS, MAX_SYMBOL_DOCSTRING_CHARS, DOCSTRING_TRUNCATION_MARKER, TSJS_KIND_BY_TYPE, TSJS_FN_SCOPE_TYPES, PY_KIND_BY_TYPE, GO_KIND_BY_TYPE, GO_FN_SCOPE_TYPES, GO_LOCAL_KINDS, RUST_KIND_BY_TYPE, RUST_FN_SCOPE_TYPES, RUST_LOCAL_KINDS, RUBY_KIND_BY_TYPE, JAVA_KIND_BY_TYPE, CPP_KIND_BY_TYPE, REF_LANGUAGES, SCOPE_TYPES_BY_LANG, CALL_TYPES_BY_LANG, REF_NOISE_BY_LANG, JS_NOISE, EMPTY_STRING_SET, FALLBACK_PATTERNS, NO_TREE_SITTER_EXTRACTORS, DISABLED_EMBED_SHA_PREFIX, UNAVAILABLE_EMBED_SHA_PREFIX;
19386
19511
  var init_parser = __esm({
19387
19512
  "src/parser.ts"() {
19388
19513
  "use strict";
@@ -19428,6 +19553,9 @@ var init_parser = __esm({
19428
19553
  _require4 = createRequire5(import.meta.url);
19429
19554
  _grammarCache = /* @__PURE__ */ new Map();
19430
19555
  CPP_HEADER_SNIFF_RE = /\bclass\s+\w|\bnamespace\s+\w|\btemplate\s*<|::\s*\w|\b(?:public|private|protected)\s*:/;
19556
+ MAX_SYMBOL_BODY_CHARS = 128 * 1024;
19557
+ MAX_SYMBOL_DOCSTRING_CHARS = 16 * 1024;
19558
+ DOCSTRING_TRUNCATION_MARKER = "\n[... docstring truncated by token-goat ...]";
19431
19559
  TSJS_KIND_BY_TYPE = /* @__PURE__ */ new Map([
19432
19560
  ["function_declaration", "function"],
19433
19561
  ["generator_function_declaration", "function"],
@@ -21275,8 +21403,8 @@ var require_command = __commonJS({
21275
21403
  init_define_import_meta_env();
21276
21404
  var EventEmitter = __require("node:events").EventEmitter;
21277
21405
  var childProcess = __require("node:child_process");
21278
- var path63 = __require("node:path");
21279
- var fs53 = __require("node:fs");
21406
+ var path64 = __require("node:path");
21407
+ var fs56 = __require("node:fs");
21280
21408
  var process4 = __require("node:process");
21281
21409
  var { Argument: Argument2, humanReadableArgName } = require_argument();
21282
21410
  var { CommanderError: CommanderError2 } = require_error();
@@ -22208,11 +22336,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
22208
22336
  let launchWithNode = false;
22209
22337
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
22210
22338
  function findFile(baseDir, baseName) {
22211
- const localBin = path63.resolve(baseDir, baseName);
22212
- if (fs53.existsSync(localBin)) return localBin;
22213
- if (sourceExt.includes(path63.extname(baseName))) return void 0;
22339
+ const localBin = path64.resolve(baseDir, baseName);
22340
+ if (fs56.existsSync(localBin)) return localBin;
22341
+ if (sourceExt.includes(path64.extname(baseName))) return void 0;
22214
22342
  const foundExt = sourceExt.find(
22215
- (ext2) => fs53.existsSync(`${localBin}${ext2}`)
22343
+ (ext2) => fs56.existsSync(`${localBin}${ext2}`)
22216
22344
  );
22217
22345
  if (foundExt) return `${localBin}${foundExt}`;
22218
22346
  return void 0;
@@ -22224,21 +22352,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
22224
22352
  if (this._scriptPath) {
22225
22353
  let resolvedScriptPath;
22226
22354
  try {
22227
- resolvedScriptPath = fs53.realpathSync(this._scriptPath);
22355
+ resolvedScriptPath = fs56.realpathSync(this._scriptPath);
22228
22356
  } catch (err2) {
22229
22357
  resolvedScriptPath = this._scriptPath;
22230
22358
  }
22231
- executableDir = path63.resolve(
22232
- path63.dirname(resolvedScriptPath),
22359
+ executableDir = path64.resolve(
22360
+ path64.dirname(resolvedScriptPath),
22233
22361
  executableDir
22234
22362
  );
22235
22363
  }
22236
22364
  if (executableDir) {
22237
22365
  let localFile = findFile(executableDir, executableFile);
22238
22366
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
22239
- const legacyName = path63.basename(
22367
+ const legacyName = path64.basename(
22240
22368
  this._scriptPath,
22241
- path63.extname(this._scriptPath)
22369
+ path64.extname(this._scriptPath)
22242
22370
  );
22243
22371
  if (legacyName !== this._name) {
22244
22372
  localFile = findFile(
@@ -22249,7 +22377,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
22249
22377
  }
22250
22378
  executableFile = localFile || executableFile;
22251
22379
  }
22252
- launchWithNode = sourceExt.includes(path63.extname(executableFile));
22380
+ launchWithNode = sourceExt.includes(path64.extname(executableFile));
22253
22381
  let proc;
22254
22382
  if (process4.platform !== "win32") {
22255
22383
  if (launchWithNode) {
@@ -23089,7 +23217,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
23089
23217
  * @return {Command}
23090
23218
  */
23091
23219
  nameFromFilename(filename) {
23092
- this._name = path63.basename(filename, path63.extname(filename));
23220
+ this._name = path64.basename(filename, path64.extname(filename));
23093
23221
  return this;
23094
23222
  }
23095
23223
  /**
@@ -23103,9 +23231,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
23103
23231
  * @param {string} [path]
23104
23232
  * @return {(string|null|Command)}
23105
23233
  */
23106
- executableDir(path64) {
23107
- if (path64 === void 0) return this._executableDir;
23108
- this._executableDir = path64;
23234
+ executableDir(path65) {
23235
+ if (path65 === void 0) return this._executableDir;
23236
+ this._executableDir = path65;
23109
23237
  return this;
23110
23238
  }
23111
23239
  /**
@@ -23341,7 +23469,7 @@ import * as readline from "node:readline";
23341
23469
  async function defaultConfirm(question) {
23342
23470
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
23343
23471
  try {
23344
- const answer = await new Promise((resolve20) => rl.question(question, resolve20));
23472
+ const answer = await new Promise((resolve21) => rl.question(question, resolve21));
23345
23473
  return /^y(es)?$/i.test(answer.trim());
23346
23474
  } finally {
23347
23475
  rl.close();
@@ -23880,7 +24008,8 @@ function walkProject(rootDir, opts = {}) {
23880
24008
  const excludeTests = opts.excludeTests === true;
23881
24009
  const includeEmbeddableDocuments = opts.includeEmbeddableDocuments === true;
23882
24010
  const extraSkipDirs = loadConfig().indexing.skip_dirs;
23883
- while (stack.length > 0 && files.length < MAX_FILES_SCANNED) {
24011
+ const maxFiles = opts.maxFiles ?? MAX_FILES_SCANNED;
24012
+ while (stack.length > 0 && files.length < maxFiles) {
23884
24013
  const dir = stack.pop();
23885
24014
  if (dir === void 0) break;
23886
24015
  let entries;
@@ -23903,7 +24032,7 @@ function walkProject(rootDir, opts = {}) {
23903
24032
  if (excludeTests && isTestFile(full)) continue;
23904
24033
  files.push(full);
23905
24034
  languages[lang] = (languages[lang] ?? 0) + 1;
23906
- if (files.length >= MAX_FILES_SCANNED) break;
24035
+ if (files.length >= maxFiles) break;
23907
24036
  }
23908
24037
  }
23909
24038
  }
@@ -24043,9 +24172,9 @@ function formatMemSuggestions(projectRoot) {
24043
24172
  if (suggestions.length === 0) return "";
24044
24173
  const lines2 = ["", "## mem suggestions"];
24045
24174
  for (const s of suggestions) {
24046
- const basename19 = path33.basename(s.path);
24175
+ const basename20 = path33.basename(s.path);
24047
24176
  lines2.push(
24048
- "Consider: mem import --from-md " + s.path + " # migrates " + s.count + " preference-shaped lines from " + basename19 + " as pending facts for review"
24177
+ "Consider: mem import --from-md " + s.path + " # migrates " + s.count + " preference-shaped lines from " + basename20 + " as pending facts for review"
24049
24178
  );
24050
24179
  }
24051
24180
  return lines2.join(String.fromCharCode(10));
@@ -24116,7 +24245,7 @@ var init_repomap = __esm({
24116
24245
  });
24117
24246
 
24118
24247
  // src/section_reader.ts
24119
- import { readFileSync as readFileSync26 } from "node:fs";
24248
+ import { readFileSync as readFileSync27 } from "node:fs";
24120
24249
  function parseHeadingSpec(spec, headers) {
24121
24250
  const m = /^(.*?)#(\d+)$/.exec(spec);
24122
24251
  if (m !== null && m[1] !== void 0 && m[2] !== void 0) {
@@ -24349,7 +24478,7 @@ function extractSection(text, headingSpec) {
24349
24478
  function readTextForSections(filePath) {
24350
24479
  let text;
24351
24480
  try {
24352
- text = readFileSync26(filePath, "utf-8");
24481
+ text = readFileSync27(filePath, "utf-8");
24353
24482
  } catch {
24354
24483
  return null;
24355
24484
  }
@@ -24413,7 +24542,7 @@ var init_section_reader = __esm({
24413
24542
  });
24414
24543
 
24415
24544
  // src/graph_commands.ts
24416
- import * as fs33 from "node:fs";
24545
+ import * as fs34 from "node:fs";
24417
24546
  import * as os16 from "node:os";
24418
24547
  import * as path42 from "node:path";
24419
24548
  import { execFileSync, spawnSync as spawnSync4 } from "node:child_process";
@@ -24680,7 +24809,7 @@ function runDead(opts) {
24680
24809
  function runDeps(opts) {
24681
24810
  let text;
24682
24811
  try {
24683
- text = fs33.readFileSync(opts.file, "utf-8");
24812
+ text = fs34.readFileSync(opts.file, "utf-8");
24684
24813
  } catch {
24685
24814
  emitErr(`Could not read: ${opts.file}`);
24686
24815
  return 1;
@@ -24694,22 +24823,22 @@ function runDeps(opts) {
24694
24823
  if (imp.startsWith("./") || imp.startsWith("../")) {
24695
24824
  const base = path42.resolve(dir, imp);
24696
24825
  let resolved = imp;
24697
- if (fs33.existsSync(base) && fs33.statSync(base).isFile()) {
24826
+ if (fs34.existsSync(base) && fs34.statSync(base).isFile()) {
24698
24827
  resolved = base;
24699
24828
  } else {
24700
24829
  const baseExt = path42.extname(base);
24701
24830
  const bareBase = SOURCE_EXTENSIONS.includes(baseExt) ? base.slice(0, -baseExt.length) : base;
24702
24831
  for (const srcExt of SOURCE_EXTENSIONS) {
24703
24832
  const candidate = bareBase + srcExt;
24704
- if (fs33.existsSync(candidate)) {
24833
+ if (fs34.existsSync(candidate)) {
24705
24834
  resolved = candidate;
24706
24835
  break;
24707
24836
  }
24708
24837
  }
24709
- if (resolved === imp && fs33.existsSync(base) && fs33.statSync(base).isDirectory()) {
24838
+ if (resolved === imp && fs34.existsSync(base) && fs34.statSync(base).isDirectory()) {
24710
24839
  for (const srcExt of SOURCE_EXTENSIONS) {
24711
24840
  const candidate = path42.join(base, "index" + srcExt);
24712
- if (fs33.existsSync(candidate)) {
24841
+ if (fs34.existsSync(candidate)) {
24713
24842
  resolved = candidate;
24714
24843
  break;
24715
24844
  }
@@ -25070,7 +25199,7 @@ function runArch(opts) {
25070
25199
  for (const file2 of files) {
25071
25200
  let text;
25072
25201
  try {
25073
- text = fs33.readFileSync(file2, "utf8");
25202
+ text = fs34.readFileSync(file2, "utf8");
25074
25203
  } catch {
25075
25204
  continue;
25076
25205
  }
@@ -25202,7 +25331,7 @@ ANSWER:`;
25202
25331
  if (result.status === 0) {
25203
25332
  if (codexOutPath) {
25204
25333
  try {
25205
- answer = fs33.readFileSync(codexOutPath, "utf8").trim();
25334
+ answer = fs34.readFileSync(codexOutPath, "utf8").trim();
25206
25335
  } catch {
25207
25336
  }
25208
25337
  } else {
@@ -25221,7 +25350,7 @@ ANSWER:`;
25221
25350
  } finally {
25222
25351
  if (codexOutPath) {
25223
25352
  try {
25224
- fs33.unlinkSync(codexOutPath);
25353
+ fs34.unlinkSync(codexOutPath);
25225
25354
  } catch {
25226
25355
  }
25227
25356
  }
@@ -28076,23 +28205,23 @@ var init_pr_slice = __esm({
28076
28205
  });
28077
28206
 
28078
28207
  // src/sqlite_query.ts
28079
- import * as fs34 from "node:fs";
28208
+ import * as fs35 from "node:fs";
28080
28209
  import Database2 from "better-sqlite3";
28081
28210
  function readMagicBytes(filePath) {
28082
28211
  let fd;
28083
28212
  try {
28084
- fd = fs34.openSync(filePath, "r");
28213
+ fd = fs35.openSync(filePath, "r");
28085
28214
  } catch {
28086
28215
  return null;
28087
28216
  }
28088
28217
  try {
28089
28218
  const buf = Buffer.alloc(SQLITE_MAGIC.length);
28090
- const bytesRead = fs34.readSync(fd, buf, 0, buf.length, 0);
28219
+ const bytesRead = fs35.readSync(fd, buf, 0, buf.length, 0);
28091
28220
  return bytesRead === buf.length ? buf : null;
28092
28221
  } catch {
28093
28222
  return null;
28094
28223
  } finally {
28095
- fs34.closeSync(fd);
28224
+ fs35.closeSync(fd);
28096
28225
  }
28097
28226
  }
28098
28227
  function isSqliteFile(filePath) {
@@ -28100,7 +28229,7 @@ function isSqliteFile(filePath) {
28100
28229
  return magic !== null && magic.equals(SQLITE_MAGIC);
28101
28230
  }
28102
28231
  function openReadonlySqlite(filePath) {
28103
- if (!fs34.existsSync(filePath)) {
28232
+ if (!fs35.existsSync(filePath)) {
28104
28233
  throw new Error(`file not found: ${filePath}`);
28105
28234
  }
28106
28235
  if (!isSqliteFile(filePath)) {
@@ -28821,12 +28950,12 @@ var init_conflict_query = __esm({
28821
28950
  });
28822
28951
 
28823
28952
  // src/screenshot.ts
28824
- import fs35 from "node:fs";
28953
+ import fs36 from "node:fs";
28825
28954
  import path44 from "node:path";
28826
28955
  function findPlaywrightChromium(msPlaywrightDir) {
28827
28956
  let entries;
28828
28957
  try {
28829
- entries = fs35.readdirSync(msPlaywrightDir);
28958
+ entries = fs36.readdirSync(msPlaywrightDir);
28830
28959
  } catch {
28831
28960
  return [];
28832
28961
  }
@@ -28865,13 +28994,13 @@ function platformCandidatePaths() {
28865
28994
  return candidates;
28866
28995
  }
28867
28996
  function resolveBrowserExecutablePath(explicit) {
28868
- if (explicit && fs35.existsSync(explicit)) return explicit;
28997
+ if (explicit && fs36.existsSync(explicit)) return explicit;
28869
28998
  const cfgPath = loadConfig().screenshot.chrome_path;
28870
- if (cfgPath && fs35.existsSync(cfgPath)) return cfgPath;
28999
+ if (cfgPath && fs36.existsSync(cfgPath)) return cfgPath;
28871
29000
  const envPath = process.env["TOKEN_GOAT_CHROME_PATH"];
28872
- if (envPath && fs35.existsSync(envPath)) return envPath;
29001
+ if (envPath && fs36.existsSync(envPath)) return envPath;
28873
29002
  for (const candidate of platformCandidatePaths()) {
28874
- if (fs35.existsSync(candidate)) return candidate;
29003
+ if (fs36.existsSync(candidate)) return candidate;
28875
29004
  }
28876
29005
  return null;
28877
29006
  }
@@ -28919,6 +29048,7 @@ var init_screenshot = __esm({
28919
29048
  });
28920
29049
 
28921
29050
  // src/notes.ts
29051
+ import * as fs37 from "node:fs";
28922
29052
  function toNoteRow(row) {
28923
29053
  return {
28924
29054
  id: row.id,
@@ -28935,7 +29065,17 @@ function pickEarliest(matches2) {
28935
29065
  }
28936
29066
  function resolveSymbolMatch(filePath, symbolName, dbPath = globalDbPath()) {
28937
29067
  const matches2 = querySymbols({ filePath, name: symbolName }, dbPath);
28938
- return matches2.length === 0 ? null : pickEarliest(matches2);
29068
+ if (matches2.length === 0) return null;
29069
+ const match2 = pickEarliest(matches2);
29070
+ if (match2.body !== "") return match2;
29071
+ return { ...match2, body: bodyFromSource(match2) };
29072
+ }
29073
+ function bodyFromSource(entry) {
29074
+ try {
29075
+ return fs37.readFileSync(entry.filePath, "utf8").split(/\r?\n/).slice(Math.max(0, entry.lineStart - 1), entry.lineEnd).join("\n");
29076
+ } catch {
29077
+ return "";
29078
+ }
28939
29079
  }
28940
29080
  function symbolNamesInFile(filePath, dbPath = globalDbPath()) {
28941
29081
  const symbols = querySymbols({ filePath, limit: 1e5 }, dbPath);
@@ -29181,11 +29321,11 @@ var init_ts_refs = __esm({
29181
29321
  });
29182
29322
 
29183
29323
  // src/read_commands.ts
29184
- import * as fs36 from "node:fs";
29324
+ import * as fs38 from "node:fs";
29185
29325
  import * as path46 from "node:path";
29186
29326
  function fileExists(p) {
29187
29327
  try {
29188
- fs36.statSync(p);
29328
+ fs38.statSync(p);
29189
29329
  return true;
29190
29330
  } catch {
29191
29331
  return false;
@@ -29193,14 +29333,14 @@ function fileExists(p) {
29193
29333
  }
29194
29334
  function readFileText(p) {
29195
29335
  try {
29196
- return fs36.readFileSync(p, "utf-8");
29336
+ return fs38.readFileSync(p, "utf-8");
29197
29337
  } catch {
29198
29338
  return null;
29199
29339
  }
29200
29340
  }
29201
29341
  function readFileBytes(p) {
29202
29342
  try {
29203
- return fs36.readFileSync(p);
29343
+ return fs38.readFileSync(p);
29204
29344
  } catch {
29205
29345
  return null;
29206
29346
  }
@@ -29265,7 +29405,7 @@ function sumFileSizes(filePaths) {
29265
29405
  let total = 0;
29266
29406
  for (const fp of new Set(filePaths)) {
29267
29407
  try {
29268
- total += fs36.statSync(fp).size;
29408
+ total += fs38.statSync(fp).size;
29269
29409
  } catch {
29270
29410
  }
29271
29411
  }
@@ -29522,7 +29662,7 @@ function runRead(opts) {
29522
29662
  const match2 = resolution.entry;
29523
29663
  const fullSourceBytes = sumFileSizes([match2.filePath]);
29524
29664
  if (opts.json === true) {
29525
- const text2 = JSON.stringify(match2, null, 2);
29665
+ const text2 = JSON.stringify({ ...match2, body: resolveBody(match2) }, null, 2);
29526
29666
  recordReadStat("read_replacement", fullSourceBytes, text2, opts.spec);
29527
29667
  return { text: text2, code: 0 };
29528
29668
  }
@@ -29547,7 +29687,7 @@ function runSection(opts) {
29547
29687
  const heading = opts.spec.slice(colonIdx + 2);
29548
29688
  const result = readSection(filePath, heading);
29549
29689
  if (result === null) {
29550
- if (!fs36.existsSync(filePath)) {
29690
+ if (!fs38.existsSync(filePath)) {
29551
29691
  return { text: `File not found: '${filePath}'`, code: 1 };
29552
29692
  }
29553
29693
  const messages = [`Section '${heading}' not found in '${filePath}'`];
@@ -30303,7 +30443,7 @@ function runConflicts(opts) {
30303
30443
  const abs = path46.resolve(opts.path);
30304
30444
  let stat2;
30305
30445
  try {
30306
- stat2 = fs36.statSync(abs);
30446
+ stat2 = fs38.statSync(abs);
30307
30447
  } catch {
30308
30448
  emitErr2(`Could not read: ${opts.path}`);
30309
30449
  return 1;
@@ -30338,7 +30478,7 @@ async function runPdfExtractText(file2, pagesSpec, layout = false) {
30338
30478
  if (!fileExists(file2)) {
30339
30479
  throw new Error(`Could not read: ${file2}`);
30340
30480
  }
30341
- const data = fs36.readFileSync(file2);
30481
+ const data = fs38.readFileSync(file2);
30342
30482
  const result = await extractPdfText(new Uint8Array(data), pagesSpec, layout);
30343
30483
  return result.text;
30344
30484
  }
@@ -30346,14 +30486,14 @@ async function runPdfOutline(file2) {
30346
30486
  if (!fileExists(file2)) {
30347
30487
  throw new Error(`Could not read: ${file2}`);
30348
30488
  }
30349
- const data = fs36.readFileSync(file2);
30489
+ const data = fs38.readFileSync(file2);
30350
30490
  return extractPdfOutline(new Uint8Array(data));
30351
30491
  }
30352
30492
  async function runPdfMeta(file2) {
30353
30493
  if (!fileExists(file2)) {
30354
30494
  throw new Error(`Could not read: ${file2}`);
30355
30495
  }
30356
- const data = fs36.readFileSync(file2);
30496
+ const data = fs38.readFileSync(file2);
30357
30497
  return extractPdfMeta(new Uint8Array(data));
30358
30498
  }
30359
30499
  async function runScreenshot(url2, destPath, opts) {
@@ -30771,7 +30911,7 @@ function runGrep(opts) {
30771
30911
  const hits = [];
30772
30912
  function searchFile(filePath) {
30773
30913
  try {
30774
- const text = fs36.readFileSync(filePath, "utf-8");
30914
+ const text = fs38.readFileSync(filePath, "utf-8");
30775
30915
  const lines2 = text.split(/\r?\n/);
30776
30916
  lines2.forEach((lineText, idx) => {
30777
30917
  if (regex.test(lineText)) {
@@ -30792,10 +30932,10 @@ function runGrep(opts) {
30792
30932
  }
30793
30933
  function searchDir(dir) {
30794
30934
  try {
30795
- for (const entry of fs36.readdirSync(dir)) {
30935
+ for (const entry of fs38.readdirSync(dir)) {
30796
30936
  if (entry.startsWith(".")) continue;
30797
30937
  const full = path46.join(dir, entry);
30798
- const stat2 = fs36.statSync(full);
30938
+ const stat2 = fs38.statSync(full);
30799
30939
  if (stat2.isDirectory()) {
30800
30940
  if (SKIP_DIRS.has(entry)) continue;
30801
30941
  if (opts.recursive !== false) searchDir(full);
@@ -30811,7 +30951,7 @@ function runGrep(opts) {
30811
30951
  emitErr2(`Path not found: ${searchPath}`);
30812
30952
  return 1;
30813
30953
  }
30814
- const stat2 = fs36.statSync(searchPath);
30954
+ const stat2 = fs38.statSync(searchPath);
30815
30955
  if (stat2.isDirectory()) {
30816
30956
  searchDir(searchPath);
30817
30957
  } else {
@@ -31408,15 +31548,20 @@ function symbolHeader(s) {
31408
31548
  }
31409
31549
  async function runSemantic(query, opts) {
31410
31550
  if (opts.limit !== void 0 && opts.limit <= 0) {
31411
- return { text: `--limit must be a positive number, got: ${opts.limit}`, code: 1 };
31551
+ const message = `--limit must be a positive number, got: ${opts.limit}`;
31552
+ if (opts.json === true) {
31553
+ return { text: JSON.stringify({ error: message }, null, 2), code: 1 };
31554
+ }
31555
+ return { text: message, code: 1 };
31412
31556
  }
31413
31557
  const n = opts.limit !== void 0 && Number.isFinite(opts.limit) ? opts.limit : 20;
31414
31558
  if (opts.projectRoot !== void 0) {
31415
- if (!path46.isAbsolute(opts.projectRoot) || !fs36.existsSync(opts.projectRoot) || !fs36.statSync(opts.projectRoot).isDirectory()) {
31416
- return {
31417
- text: `token-goat: projectRoot must be an absolute, existing directory, got '${opts.projectRoot}'`,
31418
- code: 1
31419
- };
31559
+ if (!path46.isAbsolute(opts.projectRoot) || !fs38.existsSync(opts.projectRoot) || !fs38.statSync(opts.projectRoot).isDirectory()) {
31560
+ const message = `token-goat: projectRoot must be an absolute, existing directory, got '${opts.projectRoot}'`;
31561
+ if (opts.json === true) {
31562
+ return { text: JSON.stringify({ error: message }, null, 2), code: 1 };
31563
+ }
31564
+ return { text: message, code: 1 };
31420
31565
  }
31421
31566
  }
31422
31567
  const rootDir = opts.projectRoot ?? resolveProjectRoot({ project: process.cwd() });
@@ -31431,6 +31576,21 @@ async function runSemantic(query, opts) {
31431
31576
  );
31432
31577
  const hits = mergeNearbyHits(rawHits).slice(0, n);
31433
31578
  if (hits.length > 0) {
31579
+ if (opts.json === true) {
31580
+ const items = hits.map((h) => ({
31581
+ filePath: h.filePath,
31582
+ name: null,
31583
+ kind: null,
31584
+ startLine: h.startLine,
31585
+ endLine: h.endLine,
31586
+ distance: h.distance,
31587
+ preview: previewLines(h.text, 3)
31588
+ }));
31589
+ const capped = guardJsonRows(items);
31590
+ const text3 = JSON.stringify({ source: "embeddings", ...capped }, null, 2);
31591
+ recordReadStat("semantic_search", sumFileSizes(hits.map((h) => h.filePath)), text3, query);
31592
+ return { text: text3, code: 0 };
31593
+ }
31434
31594
  const blocks2 = hits.map(
31435
31595
  (h) => `# ${h.filePath}:${h.startLine}-${h.endLine} (distance ${h.distance.toFixed(3)})
31436
31596
  ${previewLines(h.text, 3)}`
@@ -31441,8 +31601,27 @@ ${previewLines(h.text, 3)}`
31441
31601
  }
31442
31602
  const results = searchSymbolsFts(query, n, void 0, rootDir);
31443
31603
  if (results.length === 0) {
31604
+ if (opts.json === true) {
31605
+ const text2 = JSON.stringify({ source: "fts", items: [], truncated: false, totalCount: 0 }, null, 2);
31606
+ return { text: text2, code: 1 };
31607
+ }
31444
31608
  return { text: `token-goat: no matches for '${query}'`, code: 1 };
31445
31609
  }
31610
+ if (opts.json === true) {
31611
+ const items = results.map((s) => ({
31612
+ filePath: s.filePath,
31613
+ name: s.name,
31614
+ kind: s.kind,
31615
+ startLine: s.lineStart,
31616
+ endLine: s.lineEnd,
31617
+ distance: null,
31618
+ preview: previewLines(s.body, 3)
31619
+ }));
31620
+ const capped = guardJsonRows(items);
31621
+ const text2 = JSON.stringify({ source: "fts", ...capped }, null, 2);
31622
+ recordReadStat("semantic_search", sumFileSizes(results.map((s) => s.filePath)), text2, query);
31623
+ return { text: text2, code: 0 };
31624
+ }
31446
31625
  const blocks = results.map((s) => `${symbolHeader(s)}
31447
31626
  ${previewLines(s.body, 3)}`);
31448
31627
  const text = guardText(blocks.join("\n\n"), "semantic");
@@ -31798,10 +31977,10 @@ function mergeDefs(...defs) {
31798
31977
  function cloneDef(schema) {
31799
31978
  return mergeDefs(schema._zod.def);
31800
31979
  }
31801
- function getElementAtPath(obj, path63) {
31802
- if (!path63)
31980
+ function getElementAtPath(obj, path64) {
31981
+ if (!path64)
31803
31982
  return obj;
31804
- return path63.reduce((acc, key) => acc?.[key], obj);
31983
+ return path64.reduce((acc, key) => acc?.[key], obj);
31805
31984
  }
31806
31985
  function promiseAllObject(promisesObj) {
31807
31986
  const keys = Object.keys(promisesObj);
@@ -32129,11 +32308,11 @@ function explicitlyAborted(x, startIndex = 0) {
32129
32308
  }
32130
32309
  return false;
32131
32310
  }
32132
- function prefixIssues(path63, issues) {
32311
+ function prefixIssues(path64, issues) {
32133
32312
  return issues.map((iss) => {
32134
32313
  var _a6;
32135
32314
  (_a6 = iss).path ?? (_a6.path = []);
32136
- iss.path.unshift(path63);
32315
+ iss.path.unshift(path64);
32137
32316
  return iss;
32138
32317
  });
32139
32318
  }
@@ -32351,16 +32530,16 @@ function flattenError(error52, mapper = (issue2) => issue2.message) {
32351
32530
  }
32352
32531
  function formatError2(error52, mapper = (issue2) => issue2.message) {
32353
32532
  const fieldErrors = { _errors: [] };
32354
- const processError = (error53, path63 = []) => {
32533
+ const processError = (error53, path64 = []) => {
32355
32534
  for (const issue2 of error53.issues) {
32356
32535
  if (issue2.code === "invalid_union" && issue2.errors.length) {
32357
- issue2.errors.map((issues) => processError({ issues }, [...path63, ...issue2.path]));
32536
+ issue2.errors.map((issues) => processError({ issues }, [...path64, ...issue2.path]));
32358
32537
  } else if (issue2.code === "invalid_key") {
32359
- processError({ issues: issue2.issues }, [...path63, ...issue2.path]);
32538
+ processError({ issues: issue2.issues }, [...path64, ...issue2.path]);
32360
32539
  } else if (issue2.code === "invalid_element") {
32361
- processError({ issues: issue2.issues }, [...path63, ...issue2.path]);
32540
+ processError({ issues: issue2.issues }, [...path64, ...issue2.path]);
32362
32541
  } else {
32363
- const fullpath = [...path63, ...issue2.path];
32542
+ const fullpath = [...path64, ...issue2.path];
32364
32543
  if (fullpath.length === 0) {
32365
32544
  fieldErrors._errors.push(mapper(issue2));
32366
32545
  } else {
@@ -32387,17 +32566,17 @@ function formatError2(error52, mapper = (issue2) => issue2.message) {
32387
32566
  }
32388
32567
  function treeifyError(error52, mapper = (issue2) => issue2.message) {
32389
32568
  const result = { errors: [] };
32390
- const processError = (error53, path63 = []) => {
32569
+ const processError = (error53, path64 = []) => {
32391
32570
  var _a6, _b;
32392
32571
  for (const issue2 of error53.issues) {
32393
32572
  if (issue2.code === "invalid_union" && issue2.errors.length) {
32394
- issue2.errors.map((issues) => processError({ issues }, [...path63, ...issue2.path]));
32573
+ issue2.errors.map((issues) => processError({ issues }, [...path64, ...issue2.path]));
32395
32574
  } else if (issue2.code === "invalid_key") {
32396
- processError({ issues: issue2.issues }, [...path63, ...issue2.path]);
32575
+ processError({ issues: issue2.issues }, [...path64, ...issue2.path]);
32397
32576
  } else if (issue2.code === "invalid_element") {
32398
- processError({ issues: issue2.issues }, [...path63, ...issue2.path]);
32577
+ processError({ issues: issue2.issues }, [...path64, ...issue2.path]);
32399
32578
  } else {
32400
- const fullpath = [...path63, ...issue2.path];
32579
+ const fullpath = [...path64, ...issue2.path];
32401
32580
  if (fullpath.length === 0) {
32402
32581
  result.errors.push(mapper(issue2));
32403
32582
  continue;
@@ -32429,8 +32608,8 @@ function treeifyError(error52, mapper = (issue2) => issue2.message) {
32429
32608
  }
32430
32609
  function toDotPath(_path) {
32431
32610
  const segs = [];
32432
- const path63 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
32433
- for (const seg of path63) {
32611
+ const path64 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
32612
+ for (const seg of path64) {
32434
32613
  if (typeof seg === "number")
32435
32614
  segs.push(`[${seg}]`);
32436
32615
  else if (typeof seg === "symbol")
@@ -45933,13 +46112,13 @@ function resolveRef(ref2, ctx) {
45933
46112
  if (!ref2.startsWith("#")) {
45934
46113
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
45935
46114
  }
45936
- const path63 = ref2.slice(1).split("/").filter(Boolean);
45937
- if (path63.length === 0) {
46115
+ const path64 = ref2.slice(1).split("/").filter(Boolean);
46116
+ if (path64.length === 0) {
45938
46117
  return ctx.rootSchema;
45939
46118
  }
45940
46119
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
45941
- if (path63[0] === defsKey) {
45942
- const key = path63[1];
46120
+ if (path64[0] === defsKey) {
46121
+ const key = path64[1];
45943
46122
  if (!key || !ctx.defs[key]) {
45944
46123
  throw new Error(`Reference not found: ${ref2}`);
45945
46124
  }
@@ -47210,7 +47389,8 @@ var HOOK_EVENTS = [
47210
47389
  "stop",
47211
47390
  "pre_compact",
47212
47391
  "user_prompt_submit",
47213
- "subagent_stop"
47392
+ "subagent_stop",
47393
+ "session_start"
47214
47394
  ];
47215
47395
 
47216
47396
  // src/relay.ts
@@ -47234,7 +47414,7 @@ function grepIntInput(toolInput, key) {
47234
47414
  function grepSignature(toolInput) {
47235
47415
  const pattern = toolInput["pattern"];
47236
47416
  if (typeof pattern !== "string" || pattern === "") return null;
47237
- const path63 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
47417
+ const path64 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
47238
47418
  const outputMode = typeof toolInput["output_mode"] === "string" ? toolInput["output_mode"] : "files_with_matches";
47239
47419
  const glob = typeof toolInput["glob"] === "string" ? toolInput["glob"] : "";
47240
47420
  const type = typeof toolInput["type"] === "string" ? toolInput["type"] : "";
@@ -47249,7 +47429,7 @@ function grepSignature(toolInput) {
47249
47429
  const offset = grepIntInput(toolInput, "offset");
47250
47430
  return JSON.stringify([
47251
47431
  pattern,
47252
- path63,
47432
+ path64,
47253
47433
  outputMode,
47254
47434
  glob,
47255
47435
  type,
@@ -47283,8 +47463,8 @@ init_session();
47283
47463
  function globSignature(toolInput) {
47284
47464
  const pattern = toolInput["pattern"];
47285
47465
  if (typeof pattern !== "string" || pattern === "") return null;
47286
- const path63 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
47287
- return JSON.stringify([pattern, path63]);
47466
+ const path64 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
47467
+ return JSON.stringify([pattern, path64]);
47288
47468
  }
47289
47469
  var { post: postGlobHandler, pre: preGlobDedupHandler } = makeDedupHintHandlers({
47290
47470
  toolName: "Glob",
@@ -47371,7 +47551,7 @@ init_hook_registry();
47371
47551
  init_hooks_common();
47372
47552
  init_stats();
47373
47553
  init_config();
47374
- import { readFileSync as readFileSync18, statSync as statSync14 } from "node:fs";
47554
+ import { readFileSync as readFileSync19, statSync as statSync14 } from "node:fs";
47375
47555
  var MAX_LINES_FOR_DIFF = 4e3;
47376
47556
  var MAX_OLD_FILE_BYTES_FOR_DIFF = 4 * 1024 * 1024;
47377
47557
  function splitLines(text) {
@@ -47409,7 +47589,7 @@ function preWriteRewriteHandler(event) {
47409
47589
  if (stat2.size > MAX_OLD_FILE_BYTES_FOR_DIFF) return passOutput();
47410
47590
  let oldContent;
47411
47591
  try {
47412
- oldContent = readFileSync18(filePath, "utf-8");
47592
+ oldContent = readFileSync19(filePath, "utf-8");
47413
47593
  } catch {
47414
47594
  return passOutput();
47415
47595
  }
@@ -47453,8 +47633,8 @@ init_reset();
47453
47633
  init_util2();
47454
47634
  init_disk_cache();
47455
47635
  init_lang_patterns();
47456
- import { readdirSync as readdirSync10, readFileSync as readFileSync19, statSync as statSync15 } from "fs";
47457
- import { resolve as resolve7 } from "path";
47636
+ import { readdirSync as readdirSync11, readFileSync as readFileSync20, statSync as statSync15 } from "fs";
47637
+ import { resolve as resolve8 } from "path";
47458
47638
 
47459
47639
  // src/recall_index.ts
47460
47640
  init_define_import_meta_env();
@@ -47652,11 +47832,11 @@ function gitStateFingerprintSync(cwd) {
47652
47832
  }
47653
47833
  }
47654
47834
  var DIR_FINGERPRINT_LISTING_CAP_ENTRIES = 1e4;
47655
- function dirStateFingerprintSync(path63) {
47835
+ function dirStateFingerprintSync(path64) {
47656
47836
  try {
47657
- const stat2 = statSync15(path63);
47837
+ const stat2 = statSync15(path64);
47658
47838
  if (!stat2.isDirectory()) return null;
47659
- const entries = readdirSync10(path63);
47839
+ const entries = readdirSync11(path64);
47660
47840
  if (entries.length <= DIR_FINGERPRINT_LISTING_CAP_ENTRIES) {
47661
47841
  return shortFingerprint(entries.slice().sort().join("\0"));
47662
47842
  }
@@ -47666,12 +47846,12 @@ function dirStateFingerprintSync(path63) {
47666
47846
  }
47667
47847
  }
47668
47848
  var FILE_FINGERPRINT_CONTENT_CAP_BYTES = 2 * 1024 * 1024;
47669
- function fileStateFingerprintSync(path63) {
47849
+ function fileStateFingerprintSync(path64) {
47670
47850
  try {
47671
- const stat2 = statSync15(path63);
47851
+ const stat2 = statSync15(path64);
47672
47852
  if (!stat2.isFile()) return null;
47673
47853
  if (stat2.size <= FILE_FINGERPRINT_CONTENT_CAP_BYTES) {
47674
- return shortFingerprint(readFileSync19(path63));
47854
+ return shortFingerprint(readFileSync20(path64));
47675
47855
  }
47676
47856
  return shortFingerprint(`${stat2.mtimeMs}\0${stat2.size}`);
47677
47857
  } catch {
@@ -47687,7 +47867,7 @@ function depLockfileFingerprintSync(cmd, cwd) {
47687
47867
  if (!candidates) return null;
47688
47868
  for (const lockfile of candidates) {
47689
47869
  try {
47690
- const content = readFileSync19(resolve7(cwd, lockfile));
47870
+ const content = readFileSync20(resolve8(cwd, lockfile));
47691
47871
  return shortFingerprint(content);
47692
47872
  } catch {
47693
47873
  continue;
@@ -47840,7 +48020,7 @@ function extractFirstPathArg(cmd, cwd, fallback) {
47840
48020
  const token2 = tokens[i];
47841
48021
  if (!token2.startsWith("-")) {
47842
48022
  if (!token2.startsWith("/")) {
47843
- return resolve7(cwd, token2);
48023
+ return resolve8(cwd, token2);
47844
48024
  }
47845
48025
  return token2;
47846
48026
  }
@@ -47854,10 +48034,20 @@ function summarizeOutputDelta(oldOutput, newOutput) {
47854
48034
  const newLines = newOutput.split("\n");
47855
48035
  const oldIssueLines = oldLines.filter((l) => ISSUE_LINE_PATTERN.test(l));
47856
48036
  if (oldIssueLines.length > 0) {
47857
- const newIssueLineSet = new Set(newLines.filter((l) => ISSUE_LINE_PATTERN.test(l)));
48037
+ const newIssueLines = newLines.filter((l) => ISSUE_LINE_PATTERN.test(l));
47858
48038
  const priorTotal = oldIssueLines.length;
47859
- const resolved = oldIssueLines.filter((l) => !newIssueLineSet.has(l)).length;
47860
- const remaining = newIssueLineSet.size;
48039
+ const availableCounts = /* @__PURE__ */ new Map();
48040
+ for (const l of newIssueLines) availableCounts.set(l, (availableCounts.get(l) ?? 0) + 1);
48041
+ let resolved = 0;
48042
+ for (const l of oldIssueLines) {
48043
+ const avail = availableCounts.get(l) ?? 0;
48044
+ if (avail > 0) {
48045
+ availableCounts.set(l, avail - 1);
48046
+ } else {
48047
+ resolved++;
48048
+ }
48049
+ }
48050
+ const remaining = newIssueLines.length;
47861
48051
  return `[token-goat: delta] ${resolved} of ${priorTotal} prior issues resolved; remaining: ${remaining}`;
47862
48052
  }
47863
48053
  return `[token-goat: delta] output changed: ${oldLines.length} -> ${newLines.length} lines`;
@@ -48134,14 +48324,15 @@ var {
48134
48324
  init_baseline();
48135
48325
  init_stats();
48136
48326
  init_repomap();
48137
- import * as fs52 from "fs";
48138
- import * as path62 from "path";
48327
+ import * as fs55 from "fs";
48328
+ import * as path63 from "path";
48139
48329
  import { homedir as homedir17 } from "os";
48140
48330
 
48141
48331
  // src/walk_index.ts
48142
48332
  init_define_import_meta_env();
48143
48333
  init_baseline();
48144
48334
  init_util2();
48335
+ import * as fs29 from "node:fs";
48145
48336
  import * as os9 from "node:os";
48146
48337
  import * as path35 from "node:path";
48147
48338
  function isWalkExcluded(file2) {
@@ -48151,7 +48342,11 @@ function isWalkExcluded(file2) {
48151
48342
  return false;
48152
48343
  }
48153
48344
  function assertWalkableRoot(root) {
48154
- const resolved = path35.resolve(root);
48345
+ let resolved = path35.resolve(root);
48346
+ try {
48347
+ resolved = fs29.realpathSync.native(resolved);
48348
+ } catch {
48349
+ }
48155
48350
  const norm = foldPath(normalizePath(resolved));
48156
48351
  const fsRoot = foldPath(normalizePath(path35.parse(resolved).root));
48157
48352
  if (norm === fsRoot) {
@@ -48169,13 +48364,16 @@ function assertWalkableRoot(root) {
48169
48364
  }
48170
48365
  }
48171
48366
  }
48172
- function collectWalkIndexFiles(root) {
48367
+ var MAX_FILES_SCANNED_FORCED = 5e5;
48368
+ function collectWalkIndexFiles(root, opts = {}) {
48173
48369
  const resolved = path35.resolve(root);
48174
48370
  assertWalkableRoot(resolved);
48175
- const { files } = walkProject(resolved, { includeEmbeddableDocuments: true });
48176
- if (files.length >= MAX_FILES_SCANNED) {
48371
+ const force = opts.force === true;
48372
+ const ceiling = force ? MAX_FILES_SCANNED_FORCED : MAX_FILES_SCANNED;
48373
+ const { files } = walkProject(resolved, { includeEmbeddableDocuments: true, maxFiles: ceiling });
48374
+ if (files.length >= ceiling) {
48177
48375
  throw new Error(
48178
- `'${resolved}' has too many source files (>= ${MAX_FILES_SCANNED}); refusing to walk-index \u2014 index a git repo or point at a narrower path`
48376
+ `'${resolved}' has too many source files (walk stopped at ${ceiling}; the real total is at least that). ` + (force ? "Even --force-walk will not walk a tree this large \u2014 point at a narrower path." : `Index a git repo, point at a narrower path, or pass --force-walk to raise the cap to ${MAX_FILES_SCANNED_FORCED}.`)
48179
48377
  );
48180
48378
  }
48181
48379
  return files.filter((f) => !isWalkExcluded(f));
@@ -48199,7 +48397,7 @@ init_codex_install();
48199
48397
 
48200
48398
  // src/bridges/gemini_install.ts
48201
48399
  init_define_import_meta_env();
48202
- import * as fs29 from "node:fs";
48400
+ import * as fs30 from "node:fs";
48203
48401
  import * as os10 from "node:os";
48204
48402
  import * as path36 from "node:path";
48205
48403
  init_install();
@@ -48222,7 +48420,7 @@ function geminiSettingsPath() {
48222
48420
  function readGeminiSettings(p, opts = {}) {
48223
48421
  let raw;
48224
48422
  try {
48225
- raw = fs29.readFileSync(p, "utf8");
48423
+ raw = fs30.readFileSync(p, "utf8");
48226
48424
  } catch {
48227
48425
  return {};
48228
48426
  }
@@ -48336,7 +48534,7 @@ function uninstallGemini() {
48336
48534
  init_define_import_meta_env();
48337
48535
  init_install();
48338
48536
  init_util2();
48339
- import * as fs30 from "node:fs";
48537
+ import * as fs31 from "node:fs";
48340
48538
  import * as os11 from "node:os";
48341
48539
  import * as path37 from "node:path";
48342
48540
  var QWEN_HOOK_EVENTS = ["PreToolUse", "PostToolUse", "PreCompact", "UserPromptSubmit", "SubagentStop"];
@@ -48355,7 +48553,7 @@ function qwenSettingsPath() {
48355
48553
  function readQwenSettings(p, opts = {}) {
48356
48554
  let raw;
48357
48555
  try {
48358
- raw = fs30.readFileSync(p, "utf8");
48556
+ raw = fs31.readFileSync(p, "utf8");
48359
48557
  } catch {
48360
48558
  return {};
48361
48559
  }
@@ -49103,7 +49301,7 @@ function uninstallOpencode() {
49103
49301
  // src/bridges/openclaw_install.ts
49104
49302
  init_define_import_meta_env();
49105
49303
  init_util2();
49106
- import * as fs31 from "node:fs";
49304
+ import * as fs32 from "node:fs";
49107
49305
  import * as os14 from "node:os";
49108
49306
  import * as path40 from "node:path";
49109
49307
 
@@ -49255,7 +49453,7 @@ function openclawEntrySidecarPath() {
49255
49453
  function readOpenclawConfig(p, opts = {}) {
49256
49454
  let raw;
49257
49455
  try {
49258
- raw = fs31.readFileSync(p, "utf8");
49456
+ raw = fs32.readFileSync(p, "utf8");
49259
49457
  } catch {
49260
49458
  return {};
49261
49459
  }
@@ -49295,7 +49493,7 @@ function installOpenclaw() {
49295
49493
  const pluginPath = openclawPluginPath();
49296
49494
  let existingPlugin;
49297
49495
  try {
49298
- existingPlugin = fs31.readFileSync(pluginPath, "utf8");
49496
+ existingPlugin = fs32.readFileSync(pluginPath, "utf8");
49299
49497
  } catch {
49300
49498
  existingPlugin = void 0;
49301
49499
  }
@@ -49336,12 +49534,12 @@ function uninstallOpenclaw() {
49336
49534
  const pluginPath = openclawPluginPath();
49337
49535
  let removed = false;
49338
49536
  try {
49339
- fs31.unlinkSync(pluginPath);
49537
+ fs32.unlinkSync(pluginPath);
49340
49538
  removed = true;
49341
49539
  } catch {
49342
49540
  }
49343
49541
  try {
49344
- fs31.unlinkSync(openclawEntrySidecarPath());
49542
+ fs32.unlinkSync(openclawEntrySidecarPath());
49345
49543
  } catch {
49346
49544
  }
49347
49545
  const settings = readOpenclawConfig(configPath2);
@@ -49442,12 +49640,12 @@ import * as os15 from "node:os";
49442
49640
 
49443
49641
  // src/shell.ts
49444
49642
  init_define_import_meta_env();
49445
- import * as fs32 from "node:fs";
49643
+ import * as fs33 from "node:fs";
49446
49644
  import * as path41 from "node:path";
49447
49645
  var WSL_LAUNCHER_SEGMENTS = /* @__PURE__ */ new Set(["system32", "syswow64", "windowsapps"]);
49448
49646
  function isExecutable(p) {
49449
49647
  try {
49450
- return fs32.statSync(p).isFile();
49648
+ return fs33.statSync(p).isFile();
49451
49649
  } catch {
49452
49650
  return false;
49453
49651
  }
@@ -63073,7 +63271,7 @@ var BRIDGE_CAPABILITY_MATRIX = [
63073
63271
  harness: "claudecode",
63074
63272
  label: "Claude Code",
63075
63273
  sourceFile: "src/install.ts (HOOK_EVENT_MAP)",
63076
- implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
63274
+ implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
63077
63275
  reasons: [{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }]
63078
63276
  },
63079
63277
  {
@@ -63081,14 +63279,26 @@ var BRIDGE_CAPABILITY_MATRIX = [
63081
63279
  label: "Codex CLI",
63082
63280
  sourceFile: "src/bridges/codex_install.ts (CODEX_HOOK_EVENTS, CODEX_GLOBAL_HOOK_EVENTS)",
63083
63281
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
63084
- reasons: [{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }]
63282
+ reasons: [
63283
+ { events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON },
63284
+ {
63285
+ events: ["session_start"],
63286
+ reason: "Codex CLI's hook config schema (CODEX_EVENT_ARG) has no session-start-equivalent event to map onto token-goat's session_start -- unlike Claude Code's real SessionStart hook"
63287
+ }
63288
+ ]
63085
63289
  },
63086
63290
  {
63087
63291
  harness: "grok",
63088
63292
  label: "Grok CLI",
63089
63293
  sourceFile: "src/bridges/grok_install.ts (GROK_HOOK_EVENTS)",
63090
63294
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
63091
- reasons: [{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }]
63295
+ reasons: [
63296
+ { events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON },
63297
+ {
63298
+ events: ["session_start"],
63299
+ reason: "GROK_HOOK_EVENTS (this row's explicit `install --grok` writer) mirrors install.ts's HOOK_EVENT_MAP as of the doc verification date -- Grok's real hooks doc has not been re-checked for SessionStart support since, so it is left unwired rather than guessed at (Grok's *default*, non---grok path still rides Claude Code's own settings.json directly and does receive session_start there, same as claudecode)"
63300
+ }
63301
+ ]
63092
63302
  },
63093
63303
  {
63094
63304
  harness: "copilot_cli",
@@ -63099,6 +63309,10 @@ var BRIDGE_CAPABILITY_MATRIX = [
63099
63309
  {
63100
63310
  events: ["notification"],
63101
63311
  reason: "Copilot CLI has a real 'notification' hook event, but copilot_cli.ts's COPILOT_TO_TG_EVENT deliberately leaves it (and sessionEnd/postToolUseFailure/subagentStart/errorOccurred/permissionRequest) unimplemented rather than guessed at"
63312
+ },
63313
+ {
63314
+ events: ["session_start"],
63315
+ reason: "COPILOT_TO_TG_EVENT has no session-start mapping wired yet -- left unimplemented rather than guessed at"
63102
63316
  }
63103
63317
  ]
63104
63318
  },
@@ -63109,7 +63323,7 @@ var BRIDGE_CAPABILITY_MATRIX = [
63109
63323
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
63110
63324
  reasons: [
63111
63325
  {
63112
- events: ["notification", "stop", "user_prompt_submit", "subagent_stop"],
63326
+ events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
63113
63327
  reason: `Gemini CLI's hooks integration only wires BeforeTool/AfterTool/PreCompress (README "Gemini CLI users")`
63114
63328
  }
63115
63329
  ]
@@ -63124,7 +63338,11 @@ var BRIDGE_CAPABILITY_MATRIX = [
63124
63338
  events: ["notification"],
63125
63339
  reason: "Only five Qwen Code events have a token-goat handler; every other real event (Notification, SessionEnd, PostToolUseFailure, StopFailure, SubagentStart, PermissionRequest, TodoCreated, TodoCompleted) is left unimplemented rather than guessed at (qwen_install.ts)"
63126
63340
  },
63127
- { events: ["stop"], reason: NO_SERVER_HANDLER_REASON }
63341
+ { events: ["stop"], reason: NO_SERVER_HANDLER_REASON },
63342
+ {
63343
+ events: ["session_start"],
63344
+ reason: "QWEN_EVENT_ARG has no session-start mapping wired yet -- left unimplemented rather than guessed at"
63345
+ }
63128
63346
  ]
63129
63347
  },
63130
63348
  {
@@ -63134,7 +63352,7 @@ var BRIDGE_CAPABILITY_MATRIX = [
63134
63352
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
63135
63353
  reasons: [
63136
63354
  {
63137
- events: ["notification", "stop", "user_prompt_submit", "subagent_stop"],
63355
+ events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
63138
63356
  reason: "opencode's plugin API only exposes three relevant hooks -- tool.execute.before, tool.execute.after, experimental.session.compacting (opencode.ts module docstring, verified against opencode's real source)"
63139
63357
  }
63140
63358
  ]
@@ -63146,7 +63364,7 @@ var BRIDGE_CAPABILITY_MATRIX = [
63146
63364
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
63147
63365
  reasons: [
63148
63366
  {
63149
- events: ["notification", "stop", "user_prompt_submit", "subagent_stop"],
63367
+ events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
63150
63368
  reason: "OpenClaw's in-process plugin API only exposes before_tool_call/after_tool_call/before_compaction as api.on() handlers relevant here"
63151
63369
  }
63152
63370
  ]
@@ -63158,8 +63376,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
63158
63376
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
63159
63377
  reasons: [
63160
63378
  {
63161
- events: ["notification", "stop", "user_prompt_submit", "subagent_stop"],
63162
- reason: "pi's extension API subscribes to session_start/tool_call/tool_result/session_before_compact/session_compact, of which only tool_call/tool_result/session_before_compact map onto real HOOK_EVENTS names (pre_tool_use/post_tool_use/pre_compact)"
63379
+ events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
63380
+ reason: "pi's extension API subscribes to session_start/tool_call/tool_result/session_before_compact/session_compact, of which only tool_call/tool_result/session_before_compact map onto real HOOK_EVENTS names (pre_tool_use/post_tool_use/pre_compact) -- pi's own session_start event is never forwarded to token-goat's callHook()"
63163
63381
  }
63164
63382
  ]
63165
63383
  }
@@ -63262,7 +63480,7 @@ init_csv_query();
63262
63480
 
63263
63481
  // src/sharepoint_resolve.ts
63264
63482
  init_define_import_meta_env();
63265
- import * as fs37 from "node:fs";
63483
+ import * as fs39 from "node:fs";
63266
63484
  import * as os17 from "node:os";
63267
63485
  import * as path47 from "node:path";
63268
63486
  var TENANT_HOST_RE = /^([a-z0-9-]+?)(-my)?\.sharepoint\.com$/i;
@@ -63311,7 +63529,7 @@ function candidateRoots(env, home) {
63311
63529
  if (commercial !== void 0) roots.push(commercial);
63312
63530
  if (personal !== void 0 && personal !== commercial) roots.push(personal);
63313
63531
  try {
63314
- for (const entry of fs37.readdirSync(home, { withFileTypes: true })) {
63532
+ for (const entry of fs39.readdirSync(home, { withFileTypes: true })) {
63315
63533
  if (entry.isDirectory() && /onedrive/i.test(entry.name)) {
63316
63534
  const full = path47.join(home, entry.name);
63317
63535
  if (!roots.includes(full)) roots.push(full);
@@ -63324,13 +63542,13 @@ function candidateRoots(env, home) {
63324
63542
  function tryLibraryPaths(root, libSegments, triedPaths) {
63325
63543
  const rawJoined = path47.join(root, ...libSegments);
63326
63544
  triedPaths.push(rawJoined);
63327
- if (fs37.existsSync(rawJoined)) return rawJoined;
63545
+ if (fs39.existsSync(rawJoined)) return rawJoined;
63328
63546
  if (libSegments.length > 0) {
63329
63547
  const aliasedFirst = normalizeLibrarySegment(libSegments[0]);
63330
63548
  if (aliasedFirst !== libSegments[0]) {
63331
63549
  const aliasedJoined = path47.join(root, aliasedFirst, ...libSegments.slice(1));
63332
63550
  triedPaths.push(aliasedJoined);
63333
- if (fs37.existsSync(aliasedJoined)) return aliasedJoined;
63551
+ if (fs39.existsSync(aliasedJoined)) return aliasedJoined;
63334
63552
  }
63335
63553
  }
63336
63554
  return null;
@@ -63344,7 +63562,7 @@ function resolveLocalPath(parsed, env = process.env, home = os17.homedir()) {
63344
63562
  if (found !== null) return { resolvedPath: found, triedPaths };
63345
63563
  if (parsed.siteType === "site" && parsed.siteName.length > 0) {
63346
63564
  try {
63347
- for (const entry of fs37.readdirSync(root, { withFileTypes: true })) {
63565
+ for (const entry of fs39.readdirSync(root, { withFileTypes: true })) {
63348
63566
  if (!entry.isDirectory() || !entry.name.toLowerCase().includes(parsed.siteName.toLowerCase())) continue;
63349
63567
  const siteRoot = path47.join(root, entry.name);
63350
63568
  const siteFound = tryLibraryPaths(siteRoot, libSegments, triedPaths);
@@ -63407,7 +63625,7 @@ function extractVideoChapters(file2) {
63407
63625
 
63408
63626
  // src/transcript_extract.ts
63409
63627
  init_define_import_meta_env();
63410
- import * as fs38 from "node:fs";
63628
+ import * as fs40 from "node:fs";
63411
63629
  var TIMESTAMP_RE = /(\d{1,2}:)?(\d{2}):(\d{2})[.,](\d{1,3})/;
63412
63630
  var CUE_LINE_RE = new RegExp(`^\\s*${TIMESTAMP_RE.source}\\s*-->\\s*${TIMESTAMP_RE.source}`);
63413
63631
  var V_TAG_RE = /^<v(?:\.\w+)?\s+([^>]+)>\s*(.*)$/;
@@ -63459,7 +63677,7 @@ function parseTranscript(content) {
63459
63677
  return cues;
63460
63678
  }
63461
63679
  function readTranscript(filePath) {
63462
- const content = fs38.readFileSync(filePath, "utf8");
63680
+ const content = fs40.readFileSync(filePath, "utf8");
63463
63681
  return parseTranscript(content);
63464
63682
  }
63465
63683
  function formatTimestamp(seconds) {
@@ -63535,7 +63753,7 @@ init_constants();
63535
63753
  init_session();
63536
63754
  init_util2();
63537
63755
  init_ansi();
63538
- import * as fs39 from "node:fs";
63756
+ import * as fs41 from "node:fs";
63539
63757
  import * as path48 from "node:path";
63540
63758
  function writeRaw(text) {
63541
63759
  const payload = colorStdout() ? text : stripAnsi(text);
@@ -63548,8 +63766,8 @@ function formatTopFiles(ranked) {
63548
63766
  if (ranked.length === 0) return "";
63549
63767
  const lines2 = ["Top files this session:"];
63550
63768
  for (const { path: filePath, count } of ranked) {
63551
- const basename19 = path48.basename(filePath);
63552
- lines2.push(` ${count.toString().padStart(3)}x ${basename19} (${filePath})`);
63769
+ const basename20 = path48.basename(filePath);
63770
+ lines2.push(` ${count.toString().padStart(3)}x ${basename20} (${filePath})`);
63553
63771
  }
63554
63772
  return lines2.join("\n");
63555
63773
  }
@@ -63566,11 +63784,11 @@ function renderTopSessionFiles(topN = 5) {
63566
63784
  function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
63567
63785
  try {
63568
63786
  const sessionsDir = overrideSessionsDir ?? path48.join(dataDir(), "sessions");
63569
- if (!fs39.existsSync(sessionsDir)) return "";
63570
- const files = fs39.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs39.statSync(path48.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
63787
+ if (!fs41.existsSync(sessionsDir)) return "";
63788
+ const files = fs41.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs41.statSync(path48.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
63571
63789
  for (const { name: name2 } of files) {
63572
63790
  try {
63573
- const raw = fs39.readFileSync(path48.join(sessionsDir, name2), "utf-8");
63791
+ const raw = fs41.readFileSync(path48.join(sessionsDir, name2), "utf-8");
63574
63792
  const data = JSON.parse(raw);
63575
63793
  const filesList = data["files"];
63576
63794
  if (!Array.isArray(filesList)) continue;
@@ -63633,33 +63851,36 @@ init_constants();
63633
63851
  init_cli_context_stats();
63634
63852
  init_skill_cache();
63635
63853
  init_copilot_cli_install();
63854
+ init_install();
63636
63855
  init_ts_refs();
63637
- import * as fs40 from "fs";
63856
+ init_parser();
63857
+ import * as fs42 from "fs";
63638
63858
  import * as path49 from "path";
63639
63859
  import { execSync, spawnSync as spawnSync7 } from "child_process";
63640
63860
  function checkWorkerRunning(dataDir2) {
63641
63861
  return dataDir2 !== void 0 ? isWorkerRunning(dataDir2) : isWorkerRunning();
63642
63862
  }
63863
+ var DB_SIZE_WARN_BYTES = 1024 * 1024 * 1024;
63643
63864
  function checkDbExists(dataDir2) {
63644
63865
  const dbPath = path49.join(dataDir2, "global.db");
63645
- if (!fs40.existsSync(dbPath)) {
63866
+ if (!fs42.existsSync(dbPath)) {
63646
63867
  return {
63647
63868
  name: "Database",
63648
63869
  status: "warn",
63649
63870
  message: `global.db not found at ${dbPath}`
63650
63871
  };
63651
63872
  }
63652
- const sizeBytes = fs40.statSync(dbPath).size;
63873
+ const sizeBytes = fs42.statSync(dbPath).size;
63653
63874
  const SQLITE_HEADER = "SQLite format 3\0";
63654
63875
  let header = "";
63655
63876
  try {
63656
- const fd = fs40.openSync(dbPath, "r");
63877
+ const fd = fs42.openSync(dbPath, "r");
63657
63878
  try {
63658
63879
  const buf = Buffer.alloc(SQLITE_HEADER.length);
63659
- const bytesRead = fs40.readSync(fd, buf, 0, buf.length, 0);
63880
+ const bytesRead = fs42.readSync(fd, buf, 0, buf.length, 0);
63660
63881
  header = buf.toString("latin1", 0, bytesRead);
63661
63882
  } finally {
63662
- fs40.closeSync(fd);
63883
+ fs42.closeSync(fd);
63663
63884
  }
63664
63885
  } catch {
63665
63886
  }
@@ -63670,14 +63891,44 @@ function checkDbExists(dataDir2) {
63670
63891
  message: `global.db at ${dbPath} is not a valid SQLite file (${sizeBytes} bytes) \u2014 likely truncated or corrupt`
63671
63892
  };
63672
63893
  }
63894
+ if (sizeBytes > DB_SIZE_WARN_BYTES) {
63895
+ return {
63896
+ name: "Database",
63897
+ status: "warn",
63898
+ message: `global.db is ${Math.round(sizeBytes / (1024 * 1024))} MB at ${dbPath} \u2014 far larger than a healthy index. Large writes against it can exceed the 15s busy_timeout and appear as "database is locked". Try 'token-goat reclaim-index' first (a plain VACUUM, cheap, can recover a useful amount on its own); only reach for 'token-goat reclaim-index --rebuild' if that isn't enough, since --rebuild reparses and re-embeds every indexed file across every project and can take a long time on a large multi-project index`
63899
+ };
63900
+ }
63673
63901
  return {
63674
63902
  name: "Database",
63675
63903
  status: "ok",
63676
63904
  message: `global.db exists (${toKB(sizeBytes)} KB)`
63677
63905
  };
63678
63906
  }
63907
+ function checkSymbolBodySize(dbPath) {
63908
+ if (!fs42.existsSync(dbPath)) {
63909
+ return { name: "Symbol body size", status: "ok", message: "no database yet" };
63910
+ }
63911
+ try {
63912
+ const db = getDb(dbPath);
63913
+ const row = db.prepare("SELECT LENGTH(body) as len, file_path as filePath FROM symbols WHERE LENGTH(body) > ? LIMIT 1").get(MAX_SYMBOL_BODY_CHARS);
63914
+ if (row !== void 0) {
63915
+ return {
63916
+ name: "Symbol body size",
63917
+ status: "warn",
63918
+ message: `a stored symbol body in ${row.filePath} is ${row.len} chars, above the ${MAX_SYMBOL_BODY_CHARS}-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`
63919
+ };
63920
+ }
63921
+ return { name: "Symbol body size", status: "ok", message: "no stored symbol body exceeds the cap" };
63922
+ } catch (err2) {
63923
+ return {
63924
+ name: "Symbol body size",
63925
+ status: "warn",
63926
+ message: `could not query symbol body size: ${extractErrorMessage(err2)}`
63927
+ };
63928
+ }
63929
+ }
63679
63930
  function checkSymbolCount(dbPath, rootDir) {
63680
- if (!fs40.existsSync(dbPath)) {
63931
+ if (!fs42.existsSync(dbPath)) {
63681
63932
  return { name: "Symbols", status: "ok", message: "no database yet" };
63682
63933
  }
63683
63934
  try {
@@ -63716,7 +63967,7 @@ var DRAIN_HEARTBEAT_STALE_MS = 6e4;
63716
63967
  function checkDirtyQueueHealth(dataDir2) {
63717
63968
  let pendingCount = 0;
63718
63969
  try {
63719
- const raw = fs40.readFileSync(dirtyQueuePathFor(dataDir2), "utf8");
63970
+ const raw = fs42.readFileSync(dirtyQueuePathFor(dataDir2), "utf8");
63720
63971
  pendingCount = raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).length;
63721
63972
  } catch {
63722
63973
  }
@@ -63732,7 +63983,7 @@ function checkDirtyQueueHealth(dataDir2) {
63732
63983
  }
63733
63984
  let heartbeatAgeMs = null;
63734
63985
  try {
63735
- heartbeatAgeMs = Date.now() - fs40.statSync(drainHeartbeatPathFor(dataDir2)).mtimeMs;
63986
+ heartbeatAgeMs = Date.now() - fs42.statSync(drainHeartbeatPathFor(dataDir2)).mtimeMs;
63736
63987
  } catch {
63737
63988
  }
63738
63989
  if (heartbeatAgeMs !== null && heartbeatAgeMs > DRAIN_HEARTBEAT_STALE_MS) {
@@ -63772,7 +64023,7 @@ function checkTsCompiler() {
63772
64023
  };
63773
64024
  }
63774
64025
  function checkConfigValid(configPath2) {
63775
- if (!fs40.existsSync(configPath2)) {
64026
+ if (!fs42.existsSync(configPath2)) {
63776
64027
  return {
63777
64028
  name: "Config",
63778
64029
  status: "warn",
@@ -63780,7 +64031,7 @@ function checkConfigValid(configPath2) {
63780
64031
  };
63781
64032
  }
63782
64033
  try {
63783
- const content = fs40.readFileSync(configPath2, "utf-8");
64034
+ const content = fs42.readFileSync(configPath2, "utf-8");
63784
64035
  parse(content);
63785
64036
  return {
63786
64037
  name: "Config",
@@ -63807,9 +64058,9 @@ function formatDiskSpace(bytes) {
63807
64058
  }
63808
64059
  var LOW_DISK_WARN_BYTES = 1024 * 1024 * 1024;
63809
64060
  function checkDiskSpace(dataDir2) {
63810
- if (typeof fs40.statfsSync === "function") {
64061
+ if (typeof fs42.statfsSync === "function") {
63811
64062
  try {
63812
- const stats = fs40.statfsSync(dataDir2);
64063
+ const stats = fs42.statfsSync(dataDir2);
63813
64064
  const availableBytes = stats.bavail * stats.bsize;
63814
64065
  const status = availableBytes < LOW_DISK_WARN_BYTES ? "warn" : "ok";
63815
64066
  const suffix = status === "warn" ? " \u2014 running low, indexing/embeddings writes may start failing" : "";
@@ -63840,12 +64091,12 @@ function checkDiskSpace(dataDir2) {
63840
64091
  return { name: "Disk Space", status: "warn", message: "disk space check unavailable on this platform" };
63841
64092
  }
63842
64093
  function checkCopilotCli(configPath2, scriptPath) {
63843
- if (!fs40.existsSync(configPath2) || !fs40.existsSync(scriptPath)) {
64094
+ if (!fs42.existsSync(configPath2) || !fs42.existsSync(scriptPath)) {
63844
64095
  return null;
63845
64096
  }
63846
64097
  let config2;
63847
64098
  try {
63848
- config2 = JSON.parse(fs40.readFileSync(configPath2, "utf-8"));
64099
+ config2 = JSON.parse(fs42.readFileSync(configPath2, "utf-8"));
63849
64100
  } catch (err2) {
63850
64101
  return {
63851
64102
  name: "Copilot CLI",
@@ -63862,7 +64113,7 @@ function checkCopilotCli(configPath2, scriptPath) {
63862
64113
  };
63863
64114
  }
63864
64115
  const bakedExecPath = /^"([^"]+)"/.exec(preToolUseCommand)?.[1];
63865
- if (bakedExecPath !== void 0 && !fs40.existsSync(bakedExecPath)) {
64116
+ if (bakedExecPath !== void 0 && !fs42.existsSync(bakedExecPath)) {
63866
64117
  return {
63867
64118
  name: "Copilot CLI",
63868
64119
  status: "fail",
@@ -63907,13 +64158,26 @@ function checkCopilotCli(configPath2, scriptPath) {
63907
64158
  }
63908
64159
  return { name: "Copilot CLI", status: "ok", message: "preToolUse hook invokes cleanly and returns valid JSON" };
63909
64160
  }
64161
+ function checkStrayClaudeMdBlocks(searchRoot) {
64162
+ const strays = findStrayClaudeMdBlocks(searchRoot);
64163
+ if (strays.length === 0) {
64164
+ return { name: "CLAUDE.md block", status: "ok", message: "no stray copies outside CLAUDE.md" };
64165
+ }
64166
+ return {
64167
+ name: "CLAUDE.md block",
64168
+ status: "warn",
64169
+ message: `${strays.length} stray cop${strays.length === 1 ? "y" : "ies"} outside CLAUDE.md (never refreshed by install, never removed by uninstall, will go stale): ${strays.join(", ")}`
64170
+ };
64171
+ }
63910
64172
  function runDoctor(dataDir2, configPath2, rootDir) {
63911
64173
  const results = [];
63912
64174
  const actualDataDir = dataDir2 || dataDir();
63913
64175
  results.push(checkInstall());
63914
64176
  results.push(checkTsCompiler());
64177
+ results.push(checkStrayClaudeMdBlocks());
63915
64178
  results.push(checkWorkerRunning(actualDataDir) ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
63916
64179
  results.push(checkDbExists(actualDataDir));
64180
+ results.push(checkSymbolBodySize(path49.join(actualDataDir, "global.db")));
63917
64181
  results.push(checkSymbolCount(path49.join(actualDataDir, "global.db"), rootDir));
63918
64182
  results.push(checkDirtyQueueHealth(actualDataDir));
63919
64183
  const actualConfigPath = configPath2 || configPath();
@@ -63953,16 +64217,16 @@ async function runDoctorAndExit(opts) {
63953
64217
  try {
63954
64218
  const dir = skillOutputsDir();
63955
64219
  const pregenPath = path49.join(dir, "pregen.json");
63956
- if (fs40.existsSync(pregenPath)) {
63957
- const content = JSON.parse(fs40.readFileSync(pregenPath, "utf-8"));
64220
+ if (fs42.existsSync(pregenPath)) {
64221
+ const content = JSON.parse(fs42.readFileSync(pregenPath, "utf-8"));
63958
64222
  const pregenNames = new Set(content.names || []);
63959
64223
  const skillsSeen = /* @__PURE__ */ new Set();
63960
64224
  try {
63961
- const entries = fs40.readdirSync(dir, { withFileTypes: true });
64225
+ const entries = fs42.readdirSync(dir, { withFileTypes: true });
63962
64226
  for (const entry of entries) {
63963
64227
  if (!entry.isFile() || !entry.name.endsWith(".meta")) continue;
63964
64228
  try {
63965
- const meta3 = JSON.parse(fs40.readFileSync(path49.join(dir, entry.name), "utf-8"));
64229
+ const meta3 = JSON.parse(fs42.readFileSync(path49.join(dir, entry.name), "utf-8"));
63966
64230
  if (meta3.skillName && !pregenNames.has(meta3.skillName)) {
63967
64231
  skillsSeen.add(meta3.skillName);
63968
64232
  }
@@ -64109,7 +64373,7 @@ async function getSectionContent(fileId, heading, opts = {}) {
64109
64373
 
64110
64374
  // src/pack.ts
64111
64375
  init_define_import_meta_env();
64112
- import * as fs41 from "node:fs";
64376
+ import * as fs43 from "node:fs";
64113
64377
  import * as path51 from "node:path";
64114
64378
 
64115
64379
  // node_modules/minimatch/dist/esm/index.js
@@ -64137,14 +64401,14 @@ init_define_import_meta_env();
64137
64401
  init_define_import_meta_env();
64138
64402
  var balanced = (a, b, str) => {
64139
64403
  const ma = a instanceof RegExp ? maybeMatch(a, str) : a;
64140
- const mb = b instanceof RegExp ? maybeMatch(b, str) : b;
64141
- const r = ma !== null && mb != null && range(ma, mb, str);
64404
+ const mb2 = b instanceof RegExp ? maybeMatch(b, str) : b;
64405
+ const r = ma !== null && mb2 != null && range(ma, mb2, str);
64142
64406
  return r && {
64143
64407
  start: r[0],
64144
64408
  end: r[1],
64145
64409
  pre: str.slice(0, r[0]),
64146
64410
  body: str.slice(r[0] + ma.length, r[1]),
64147
- post: str.slice(r[1] + mb.length)
64411
+ post: str.slice(r[1] + mb2.length)
64148
64412
  };
64149
64413
  };
64150
64414
  var maybeMatch = (reg, str) => {
@@ -65999,24 +66263,24 @@ function isPathWithinRoot(rootReal, resolvedPath) {
65999
66263
  function openWithinRoot(rootReal, p) {
66000
66264
  let fd;
66001
66265
  try {
66002
- fd = fs41.openSync(p, "r");
66266
+ fd = fs43.openSync(p, "r");
66003
66267
  } catch {
66004
66268
  return null;
66005
66269
  }
66006
66270
  let ownershipTransferred = false;
66007
66271
  try {
66008
- const stat2 = fs41.fstatSync(fd);
66272
+ const stat2 = fs43.fstatSync(fd);
66009
66273
  if (!stat2.isFile()) return null;
66010
66274
  let realPath;
66011
66275
  try {
66012
- realPath = fs41.realpathSync(p);
66276
+ realPath = fs43.realpathSync(p);
66013
66277
  } catch {
66014
66278
  return "outside-root";
66015
66279
  }
66016
66280
  if (!isPathWithinRoot(rootReal, realPath)) return "outside-root";
66017
66281
  let realStat;
66018
66282
  try {
66019
- realStat = fs41.statSync(realPath);
66283
+ realStat = fs43.statSync(realPath);
66020
66284
  } catch {
66021
66285
  return "outside-root";
66022
66286
  }
@@ -66026,7 +66290,7 @@ function openWithinRoot(rootReal, p) {
66026
66290
  } finally {
66027
66291
  if (!ownershipTransferred) {
66028
66292
  try {
66029
- fs41.closeSync(fd);
66293
+ fs43.closeSync(fd);
66030
66294
  } catch {
66031
66295
  }
66032
66296
  }
@@ -66050,7 +66314,7 @@ function* resolveOpenCandidates(projectRoot, patterns, rootReal, ignorePatterns,
66050
66314
  continue;
66051
66315
  }
66052
66316
  if (ignorePatterns && matches(rel, ignorePatterns)) {
66053
- fs41.closeSync(opened.fd);
66317
+ fs43.closeSync(opened.fd);
66054
66318
  continue;
66055
66319
  }
66056
66320
  yield { p, rel, fd: opened.fd, stat: opened.stat };
@@ -66071,18 +66335,17 @@ function advanceQuoteState(text, from, to, state) {
66071
66335
  backslashes++;
66072
66336
  continue;
66073
66337
  }
66074
- if (backslashes % 2 === 0) {
66075
- if (ch === '"') state.dq = !state.dq;
66076
- else if (ch === "'") state.sq = !state.sq;
66077
- else if (ch === "`") state.bt = !state.bt;
66338
+ if (backslashes % 2 === 0 && (ch === '"' || ch === "'" || ch === "`")) {
66339
+ if (state.open === null) state.open = ch;
66340
+ else if (state.open === ch) state.open = null;
66078
66341
  }
66079
66342
  backslashes = 0;
66080
66343
  }
66081
66344
  return state;
66082
66345
  }
66083
66346
  function computeLineStartQuoteStates(content) {
66084
- const states = [{ dq: false, sq: false, bt: false }];
66085
- let state = { dq: false, sq: false, bt: false };
66347
+ const states = [{ open: null }];
66348
+ let state = { open: null };
66086
66349
  let lineStart = 0;
66087
66350
  for (let i = 0; i < content.length; i++) {
66088
66351
  if (content[i] === "\n") {
@@ -66096,9 +66359,9 @@ function computeLineStartQuoteStates(content) {
66096
66359
  function isInsideStringLiteral2(text, index, lineStates) {
66097
66360
  const lineStart = text.lastIndexOf("\n", index - 1) + 1;
66098
66361
  const lineNum = text.slice(0, lineStart).split("\n").length - 1;
66099
- const startState = lineStates[lineNum] ?? { dq: false, sq: false, bt: false };
66362
+ const startState = lineStates[lineNum] ?? { open: null };
66100
66363
  const state = advanceQuoteState(text, lineStart, index, { ...startState });
66101
- return state.dq || state.sq || state.bt;
66364
+ return state.open !== null;
66102
66365
  }
66103
66366
  function stripLineComments(content, pattern, lineStates) {
66104
66367
  return content.replace(
@@ -66177,7 +66440,7 @@ function collectFiles(projectRoot, patterns, opts = {}) {
66177
66440
  const rootResolved = path51.resolve(projectRoot);
66178
66441
  let rootReal;
66179
66442
  try {
66180
- rootReal = fs41.realpathSync(rootResolved);
66443
+ rootReal = fs43.realpathSync(rootResolved);
66181
66444
  } catch {
66182
66445
  rootReal = rootResolved;
66183
66446
  }
@@ -66191,7 +66454,7 @@ function collectFiles(projectRoot, patterns, opts = {}) {
66191
66454
  }
66192
66455
  let content;
66193
66456
  try {
66194
- content = fs41.readFileSync(fd, "utf8");
66457
+ content = fs43.readFileSync(fd, "utf8");
66195
66458
  } catch {
66196
66459
  result.skipped.push(`${rel} (unreadable)`);
66197
66460
  continue;
@@ -66213,13 +66476,13 @@ function collectFiles(projectRoot, patterns, opts = {}) {
66213
66476
  result.total_lines += lines2;
66214
66477
  result.total_tokens += tokens;
66215
66478
  } finally {
66216
- fs41.closeSync(fd);
66479
+ fs43.closeSync(fd);
66217
66480
  }
66218
66481
  }
66219
66482
  return result;
66220
66483
  }
66221
66484
  function collectFromStdin(projectRoot, opts = {}) {
66222
- const input = fs41.readFileSync(0, "utf8");
66485
+ const input = fs43.readFileSync(0, "utf8");
66223
66486
  const paths = input.split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0);
66224
66487
  return collectFiles(projectRoot, paths, opts);
66225
66488
  }
@@ -66352,7 +66615,7 @@ function estimateBudget(projectRoot, patterns, opts = {}) {
66352
66615
  const rootResolved = path51.resolve(projectRoot);
66353
66616
  let rootReal;
66354
66617
  try {
66355
- rootReal = fs41.realpathSync(rootResolved);
66618
+ rootReal = fs43.realpathSync(rootResolved);
66356
66619
  } catch {
66357
66620
  rootReal = rootResolved;
66358
66621
  }
@@ -66367,7 +66630,7 @@ function estimateBudget(projectRoot, patterns, opts = {}) {
66367
66630
  let lines2;
66368
66631
  let tokens;
66369
66632
  try {
66370
- const data = fs41.readFileSync(fd);
66633
+ const data = fs43.readFileSync(fd);
66371
66634
  const sampleSize = Math.min(1e3, data.length);
66372
66635
  const sampleLines = (data.toString("utf8", 0, sampleSize).match(/\n/g) || []).length;
66373
66636
  lines2 = data.length > sampleSize ? Math.ceil(sampleLines * (data.length / sampleSize)) : sampleLines + 1;
@@ -66385,7 +66648,7 @@ function estimateBudget(projectRoot, patterns, opts = {}) {
66385
66648
  result.total_lines += lines2;
66386
66649
  result.total_tokens += tokens;
66387
66650
  } finally {
66388
- fs41.closeSync(fd);
66651
+ fs43.closeSync(fd);
66389
66652
  }
66390
66653
  }
66391
66654
  result.entries.sort((a, b) => b.tokens - a.tokens);
@@ -66802,7 +67065,7 @@ function formatFailureDeltaJson(delta, runner) {
66802
67065
  init_define_import_meta_env();
66803
67066
  init_constants();
66804
67067
  init_util2();
66805
- import * as fs42 from "node:fs";
67068
+ import * as fs44 from "node:fs";
66806
67069
  import * as path52 from "node:path";
66807
67070
  var KEY_RE = /^[A-Za-z0-9_-]{1,80}$/;
66808
67071
  var DEFAULT_FAILURES_STATE_KEY = "default";
@@ -66820,8 +67083,8 @@ function failuresStatePath(projectHash2, key) {
66820
67083
  function loadFailureSnapshot(projectHash2, key) {
66821
67084
  const p = failuresStatePath(projectHash2, key);
66822
67085
  try {
66823
- if (!fs42.existsSync(p)) return null;
66824
- const parsed = JSON.parse(fs42.readFileSync(p, "utf-8"));
67086
+ if (!fs44.existsSync(p)) return null;
67087
+ const parsed = JSON.parse(fs44.readFileSync(p, "utf-8"));
66825
67088
  if (typeof parsed !== "object" || parsed === null) return null;
66826
67089
  const obj = parsed;
66827
67090
  if (!Array.isArray(obj.signatures)) return null;
@@ -66860,14 +67123,14 @@ init_graph_commands();
66860
67123
  init_index_reader();
66861
67124
  init_paths();
66862
67125
  init_project();
66863
- import * as fs44 from "fs";
67126
+ import * as fs46 from "fs";
66864
67127
  import * as path54 from "path";
66865
67128
 
66866
67129
  // src/project_memory.ts
66867
67130
  init_define_import_meta_env();
66868
67131
  init_constants();
66869
67132
  init_util2();
66870
- import * as fs43 from "node:fs";
67133
+ import * as fs45 from "node:fs";
66871
67134
  import * as path53 from "node:path";
66872
67135
  var MAX_ENTRIES = 30;
66873
67136
  var KEY_RE2 = /^[A-Za-z0-9_-]{1,80}$/;
@@ -66917,10 +67180,10 @@ function parseTOML(content) {
66917
67180
  }
66918
67181
  function loadRaw(filePath) {
66919
67182
  try {
66920
- if (!fs43.existsSync(filePath)) {
67183
+ if (!fs45.existsSync(filePath)) {
66921
67184
  return {};
66922
67185
  }
66923
- const content = fs43.readFileSync(filePath, "utf-8");
67186
+ const content = fs45.readFileSync(filePath, "utf-8");
66924
67187
  return parseTOML(content);
66925
67188
  } catch {
66926
67189
  return {};
@@ -66978,7 +67241,7 @@ function unsetEntry(projectHash2, key) {
66978
67241
  function clearAll(projectHash2) {
66979
67242
  const p = memoryPath(projectHash2);
66980
67243
  const doClear = () => {
66981
- if (fs43.existsSync(p)) {
67244
+ if (fs45.existsSync(p)) {
66982
67245
  save(p, {});
66983
67246
  }
66984
67247
  return true;
@@ -66991,8 +67254,8 @@ init_read_commands();
66991
67254
  init_session();
66992
67255
  init_util2();
66993
67256
  function readInput(src) {
66994
- if (src !== void 0) return fs44.readFileSync(src, "utf8");
66995
- return fs44.readFileSync(0, "utf8");
67257
+ if (src !== void 0) return fs46.readFileSync(src, "utf8");
67258
+ return fs46.readFileSync(0, "utf8");
66996
67259
  }
66997
67260
  function splitLines2(text) {
66998
67261
  return text.split(/\r?\n/);
@@ -67019,7 +67282,7 @@ function isInsideStringLiteral3(line, markerIndex) {
67019
67282
  function scanFileForTodos(filePath, kindSet) {
67020
67283
  let text;
67021
67284
  try {
67022
- text = fs44.readFileSync(filePath, "utf8");
67285
+ text = fs46.readFileSync(filePath, "utf8");
67023
67286
  } catch {
67024
67287
  return [];
67025
67288
  }
@@ -67043,7 +67306,7 @@ function collectTodoFiles(patterns) {
67043
67306
  for (const p of patterns) {
67044
67307
  const abs = path54.resolve(p);
67045
67308
  try {
67046
- const stat2 = fs44.statSync(abs);
67309
+ const stat2 = fs46.statSync(abs);
67047
67310
  if (stat2.isDirectory()) {
67048
67311
  results.push(...walkProject(abs).files);
67049
67312
  } else {
@@ -67475,7 +67738,7 @@ var LOCK_PRIORITY = [
67475
67738
  "Cargo.lock"
67476
67739
  ];
67477
67740
  function findLockfile(startPath) {
67478
- const stat2 = fs44.statSync(startPath, { throwIfNoEntry: false });
67741
+ const stat2 = fs46.statSync(startPath, { throwIfNoEntry: false });
67479
67742
  if (stat2 !== void 0 && stat2.isFile()) {
67480
67743
  return { file: startPath, others: [] };
67481
67744
  }
@@ -67483,10 +67746,10 @@ function findLockfile(startPath) {
67483
67746
  const found = [];
67484
67747
  for (const name2 of LOCK_PRIORITY) {
67485
67748
  const candidate = path54.join(dir, name2);
67486
- if (fs44.existsSync(candidate)) found.push(candidate);
67749
+ if (fs46.existsSync(candidate)) found.push(candidate);
67487
67750
  }
67488
67751
  try {
67489
- const entries = fs44.readdirSync(dir);
67752
+ const entries = fs46.readdirSync(dir);
67490
67753
  for (const e of entries) {
67491
67754
  if (/^requirements.*\.txt$/.test(e)) {
67492
67755
  const p = path54.join(dir, e);
@@ -67687,7 +67950,7 @@ function parsePnpmLock(content) {
67687
67950
  }
67688
67951
  function parseLockFile(filePath) {
67689
67952
  const base = path54.basename(filePath);
67690
- const content = fs44.readFileSync(filePath, "utf8");
67953
+ const content = fs46.readFileSync(filePath, "utf8");
67691
67954
  if (base === "package-lock.json") return { format: "npm", deps: parsePackageLockJson(content) };
67692
67955
  if (base === "yarn.lock") return { format: "yarn", deps: parseYarnLock(content) };
67693
67956
  if (base === "pnpm-lock.yaml") return { format: "pnpm", deps: parsePnpmLock(content) };
@@ -67760,7 +68023,7 @@ function cmdLockdepsPackage(lockfile, format, deps, query, json2) {
67760
68023
  }
67761
68024
  const primary = matches2.find((d) => d.kind === "direct") ?? matches2[0];
67762
68025
  const otherVersions = [...new Set(matches2.filter((d) => d.version !== primary.version).map((d) => d.version))];
67763
- const graph = format === "npm" ? buildNpmEdges(fs44.readFileSync(lockfile, "utf8")) : null;
68026
+ const graph = format === "npm" ? buildNpmEdges(fs46.readFileSync(lockfile, "utf8")) : null;
67764
68027
  const graphAvailable = graph !== null;
67765
68028
  const dependsOn = graph !== null ? [...graph.edges.get(query) ?? []].sort() : [];
67766
68029
  const dependedOnBy = graph !== null ? findReverseDirectDeps(query, graph.edges, graph.directNames) : [];
@@ -67894,7 +68157,7 @@ function loadAllSessionReadCounts() {
67894
68157
  const totals = /* @__PURE__ */ new Map();
67895
68158
  let entries;
67896
68159
  try {
67897
- entries = fs44.readdirSync(sessionsDir, { withFileTypes: true });
68160
+ entries = fs46.readdirSync(sessionsDir, { withFileTypes: true });
67898
68161
  } catch {
67899
68162
  return totals;
67900
68163
  }
@@ -67903,7 +68166,7 @@ function loadAllSessionReadCounts() {
67903
68166
  const filePath = path54.join(sessionsDir, entry.name);
67904
68167
  let raw;
67905
68168
  try {
67906
- raw = JSON.parse(fs44.readFileSync(filePath, "utf8"));
68169
+ raw = JSON.parse(fs46.readFileSync(filePath, "utf8"));
67907
68170
  } catch {
67908
68171
  continue;
67909
68172
  }
@@ -67979,7 +68242,7 @@ function detectWalkMode(cwd) {
67979
68242
  if (project?.marker === ".git") return "git";
67980
68243
  let cur = cwd;
67981
68244
  while (true) {
67982
- if (fs44.existsSync(path54.join(cur, ".git"))) return "git";
68245
+ if (fs46.existsSync(path54.join(cur, ".git"))) return "git";
67983
68246
  const parent = path54.dirname(cur);
67984
68247
  if (parent === cur) break;
67985
68248
  cur = parent;
@@ -68035,7 +68298,7 @@ init_overflow_guard();
68035
68298
  init_stats();
68036
68299
  init_util2();
68037
68300
  import { createRequire as createRequire7 } from "node:module";
68038
- import * as fs45 from "node:fs";
68301
+ import * as fs47 from "node:fs";
68039
68302
  import * as path55 from "node:path";
68040
68303
  var _require6 = createRequire7(import.meta.url);
68041
68304
  var _ts2 = null;
@@ -68055,21 +68318,21 @@ function loadTs2() {
68055
68318
  }
68056
68319
  function dirExists(p) {
68057
68320
  try {
68058
- return fs45.statSync(p).isDirectory();
68321
+ return fs47.statSync(p).isDirectory();
68059
68322
  } catch {
68060
68323
  return false;
68061
68324
  }
68062
68325
  }
68063
68326
  function fileExists2(p) {
68064
68327
  try {
68065
- return fs45.statSync(p).isFile();
68328
+ return fs47.statSync(p).isFile();
68066
68329
  } catch {
68067
68330
  return false;
68068
68331
  }
68069
68332
  }
68070
68333
  function readFileTextOrNull(p) {
68071
68334
  try {
68072
- return fs45.readFileSync(p, "utf8");
68335
+ return fs47.readFileSync(p, "utf8");
68073
68336
  } catch {
68074
68337
  return null;
68075
68338
  }
@@ -68077,7 +68340,7 @@ function readFileTextOrNull(p) {
68077
68340
  function listInstalledPackageNames(nodeModulesDir) {
68078
68341
  let entries;
68079
68342
  try {
68080
- entries = fs45.readdirSync(nodeModulesDir, { withFileTypes: true });
68343
+ entries = fs47.readdirSync(nodeModulesDir, { withFileTypes: true });
68081
68344
  } catch {
68082
68345
  return [];
68083
68346
  }
@@ -68087,7 +68350,7 @@ function listInstalledPackageNames(nodeModulesDir) {
68087
68350
  if (entry.name.startsWith("@")) {
68088
68351
  let scoped;
68089
68352
  try {
68090
- scoped = fs45.readdirSync(path55.join(nodeModulesDir, entry.name), { withFileTypes: true });
68353
+ scoped = fs47.readdirSync(path55.join(nodeModulesDir, entry.name), { withFileTypes: true });
68091
68354
  } catch {
68092
68355
  continue;
68093
68356
  }
@@ -68109,7 +68372,7 @@ function findReadmeFile(pkgDir) {
68109
68372
  function findReadmeIn(dir) {
68110
68373
  let entries;
68111
68374
  try {
68112
- entries = fs45.readdirSync(dir, { withFileTypes: true });
68375
+ entries = fs47.readdirSync(dir, { withFileTypes: true });
68113
68376
  } catch {
68114
68377
  return null;
68115
68378
  }
@@ -68247,7 +68510,7 @@ function runDepDocs(opts) {
68247
68510
  const hint = suggestions.length > 0 ? ` (did you mean: ${suggestions.join(", ")}?)` : "";
68248
68511
  return { text: `Package '${opts.packageName}' not found under ${nodeModulesDir}${hint}`, code: 1 };
68249
68512
  }
68250
- const pkgJsonRaw = fs45.readFileSync(pkgJsonPath, "utf8");
68513
+ const pkgJsonRaw = fs47.readFileSync(pkgJsonPath, "utf8");
68251
68514
  let pkgJson;
68252
68515
  try {
68253
68516
  pkgJson = JSON.parse(pkgJsonRaw);
@@ -68331,11 +68594,11 @@ init_install();
68331
68594
  // src/webfetch.ts
68332
68595
  init_define_import_meta_env();
68333
68596
  init_constants();
68334
- import { existsSync as existsSync32, readdirSync as readdirSync19, statSync as statSync26, unlinkSync as unlinkSync12 } from "fs";
68597
+ import { existsSync as existsSync32, readdirSync as readdirSync20, statSync as statSync26, unlinkSync as unlinkSync12 } from "fs";
68335
68598
  import * as http from "http";
68336
68599
  import * as https from "https";
68337
68600
  import { isIPv4, isIPv6 } from "net";
68338
- import { resolve as resolve15, join as join40 } from "path";
68601
+ import { resolve as resolve16, join as join40 } from "path";
68339
68602
  import { URL as URL2 } from "url";
68340
68603
  import { promisify } from "util";
68341
68604
  import { lookup as dnsLookup } from "dns";
@@ -68504,10 +68767,10 @@ function cleanupStaleDownloads() {
68504
68767
  if (!existsSync32(cacheDir)) return 0;
68505
68768
  let removed = 0;
68506
68769
  try {
68507
- const files = readdirSync19(cacheDir);
68770
+ const files = readdirSync20(cacheDir);
68508
68771
  for (const file2 of files) {
68509
68772
  if (file2.endsWith(".tmp")) {
68510
- const filePath = resolve15(cacheDir, file2);
68773
+ const filePath = resolve16(cacheDir, file2);
68511
68774
  try {
68512
68775
  const stat2 = statSync26(filePath);
68513
68776
  if (Date.now() - stat2.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
@@ -69067,6 +69330,121 @@ function cmdBaseline(opts) {
69067
69330
  process.stdout.write(out2 + String.fromCharCode(10));
69068
69331
  }
69069
69332
 
69333
+ // src/index_reclaim.ts
69334
+ init_define_import_meta_env();
69335
+ init_db();
69336
+ init_constants();
69337
+ init_worker();
69338
+ import * as fs48 from "node:fs";
69339
+ import * as path56 from "node:path";
69340
+ var DERIVED_TABLES = ["chunk_vectors", "chunks", "refs", "symbols", "files"];
69341
+ function indexSizeBytes(dbPath) {
69342
+ let total = 0;
69343
+ for (const p of [dbPath, `${dbPath}-wal`]) {
69344
+ try {
69345
+ total += fs48.statSync(p).size;
69346
+ } catch {
69347
+ }
69348
+ }
69349
+ return total;
69350
+ }
69351
+ function tableExists(db, table) {
69352
+ const row = db.prepare(`SELECT 1 AS present FROM sqlite_master WHERE type IN ('table','view') AND name = ?`).get(table);
69353
+ return row?.present === 1;
69354
+ }
69355
+ function reclaimIndex(dbPath, opts = {}) {
69356
+ const rebuild = opts.rebuild === true;
69357
+ const beforeBytes = indexSizeBytes(dbPath);
69358
+ const db = getDb(dbPath);
69359
+ const dropped = {};
69360
+ if (rebuild) {
69361
+ db.transaction(() => {
69362
+ for (const table of DERIVED_TABLES) {
69363
+ if (!tableExists(db, table)) continue;
69364
+ const before = db.prepare(`SELECT count(*) AS c FROM "${table}"`).get().c;
69365
+ db.prepare(`DELETE FROM "${table}"`).run();
69366
+ dropped[table] = before;
69367
+ }
69368
+ })();
69369
+ if (tableExists(db, "symbols_fts")) {
69370
+ try {
69371
+ db.prepare(`INSERT INTO symbols_fts(symbols_fts) VALUES('rebuild')`).run();
69372
+ } catch {
69373
+ }
69374
+ }
69375
+ }
69376
+ const checkpointBusy = walCheckpointBusy(db);
69377
+ const vacuumDeferred = !vacuumOrDefer(db);
69378
+ const finalCheckpointBusy = walCheckpointBusy(db);
69379
+ return {
69380
+ beforeBytes,
69381
+ afterBytes: indexSizeBytes(dbPath),
69382
+ dropped,
69383
+ rebuilt: rebuild,
69384
+ checkpointBusy: checkpointBusy || finalCheckpointBusy,
69385
+ vacuumDeferred
69386
+ };
69387
+ }
69388
+ function vacuumOrDefer(db) {
69389
+ try {
69390
+ db.exec("VACUUM");
69391
+ return true;
69392
+ } catch (err2) {
69393
+ const code = err2.code ?? "";
69394
+ if (!code.startsWith("SQLITE_BUSY") && !code.startsWith("SQLITE_LOCKED")) throw err2;
69395
+ return false;
69396
+ }
69397
+ }
69398
+ function walCheckpointBusy(db) {
69399
+ const rows = db.pragma("wal_checkpoint(TRUNCATE)");
69400
+ return rows[0]?.busy === 1;
69401
+ }
69402
+ function mb(bytes) {
69403
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
69404
+ }
69405
+ function cmdReclaimIndex(opts) {
69406
+ const dbPath = opts.dbPath ?? globalDbPath();
69407
+ if (opts.force !== true && isWorkerRunning(path56.dirname(dbPath))) {
69408
+ throw new Error(
69409
+ "reclaim-index: the worker daemon is running and writing to this index. Stop it first with 'token-goat worker stop', then re-run. Pass --force to proceed anyway (results may be inaccurate if the worker is really live)."
69410
+ );
69411
+ }
69412
+ const result = reclaimIndex(dbPath, { rebuild: opts.rebuild === true });
69413
+ if (opts.json === true) {
69414
+ process.stdout.write(JSON.stringify({ dbPath, ...result }, null, 2) + "\n");
69415
+ return;
69416
+ }
69417
+ const freed = result.beforeBytes - result.afterBytes;
69418
+ process.stdout.write(`reclaim-index: ${dbPath}
69419
+ `);
69420
+ process.stdout.write(
69421
+ ` ${mb(result.beforeBytes)} -> ${mb(result.afterBytes)} (freed ${mb(freed)})
69422
+ `
69423
+ );
69424
+ if (result.rebuilt) {
69425
+ for (const [table, n] of Object.entries(result.dropped)) {
69426
+ process.stdout.write(` dropped ${n} row(s) from ${table}
69427
+ `);
69428
+ }
69429
+ process.stdout.write(
69430
+ ` derived rows dropped -- run 'token-goat index' in each project to rebuild them
69431
+ `
69432
+ );
69433
+ }
69434
+ if (result.vacuumDeferred) {
69435
+ process.stdout.write(
69436
+ ` note: VACUUM could not get an exclusive lock and was skipped, so on-disk size may be unchanged. The index itself was reclaimed; re-run 'token-goat reclaim-index' once nothing else is using the database to release the freed pages
69437
+ `
69438
+ );
69439
+ }
69440
+ if (result.checkpointBusy) {
69441
+ process.stdout.write(
69442
+ ` note: a concurrent reader blocked WAL truncation, so some space may still be held in ${path56.basename(dbPath)}-wal
69443
+ `
69444
+ );
69445
+ }
69446
+ }
69447
+
69070
69448
  // src/config_commands.ts
69071
69449
  init_define_import_meta_env();
69072
69450
  init_dist();
@@ -69076,13 +69454,14 @@ init_image_shrink();
69076
69454
  init_project();
69077
69455
  init_index_prune();
69078
69456
  init_disk_cache();
69079
- import * as fs46 from "node:fs";
69457
+ import * as fs49 from "node:fs";
69080
69458
  import * as os18 from "node:os";
69081
- import * as path56 from "node:path";
69459
+ import * as path57 from "node:path";
69082
69460
  init_util2();
69083
69461
  init_paths();
69084
69462
  init_ansi();
69085
69463
  init_constants();
69464
+ init_stats();
69086
69465
  function emit4(text) {
69087
69466
  const payload = colorStdout() ? text : stripAnsi(text);
69088
69467
  process.stdout.write(ensureNewline(payload));
@@ -69091,7 +69470,7 @@ function emitErr4(text) {
69091
69470
  process.stderr.write(ensureNewline(text));
69092
69471
  }
69093
69472
  function saveConfigSafe(cfg) {
69094
- ensureDirSync(path56.dirname(configPath()));
69473
+ ensureDirSync(path57.dirname(configPath()));
69095
69474
  saveConfig(cfg);
69096
69475
  }
69097
69476
  function levenshtein(a, b, cap = 3) {
@@ -69251,7 +69630,7 @@ function cmdConfig(opts) {
69251
69630
  const parseErrAtLoad = getLastConfigParseError();
69252
69631
  if (parseErrAtLoad !== null) {
69253
69632
  try {
69254
- fs46.copyFileSync(configPath(), `${configPath()}.bak`);
69633
+ fs49.copyFileSync(configPath(), `${configPath()}.bak`);
69255
69634
  emitErr4(`config set: warning: config.toml failed to parse (${parseErrAtLoad}); backed up the original to config.toml.bak and rewriting it from defaults`);
69256
69635
  } catch {
69257
69636
  }
@@ -69287,8 +69666,8 @@ function cmdConfig(opts) {
69287
69666
  saveConfigSafe(cfg);
69288
69667
  return coercedValue;
69289
69668
  };
69290
- ensureDirSync(path56.dirname(configPath()));
69291
- const lockPath = path56.join(path56.dirname(configPath()), ".config.lock");
69669
+ ensureDirSync(path57.dirname(configPath()));
69670
+ const lockPath = path57.join(path57.dirname(configPath()), ".config.lock");
69292
69671
  const lockResult = withFileLock(lockPath, applySet, { waitMs: LOCK_WAIT_MS_HARDENED });
69293
69672
  const coerced = lockResult === void 0 ? applySet() : lockResult;
69294
69673
  invalidateConfigCache();
@@ -69316,7 +69695,7 @@ function cmdConfig(opts) {
69316
69695
  let raw = {};
69317
69696
  let parseErr = null;
69318
69697
  try {
69319
- const text = fs46.readFileSync(cfgFile, "utf8");
69698
+ const text = fs49.readFileSync(cfgFile, "utf8");
69320
69699
  raw = parse(text);
69321
69700
  } catch (e) {
69322
69701
  const code = e.code;
@@ -69399,7 +69778,7 @@ function cmdProject(opts) {
69399
69778
  emitErr4("project exclude requires a path argument");
69400
69779
  throw new Error("missing path");
69401
69780
  }
69402
- const target = path56.resolve(opts.pathArg);
69781
+ const target = path57.resolve(opts.pathArg);
69403
69782
  const cfg = loadPersistedConfig();
69404
69783
  const targetFolded = foldPath(normalizePath(target));
69405
69784
  if (cfg.worker.blocked_roots.some((r) => foldPath(normalizePath(r)) === targetFolded)) {
@@ -69421,7 +69800,7 @@ function cmdProject(opts) {
69421
69800
  const before = cfg.worker.blocked_roots;
69422
69801
  const after = before.filter((r) => {
69423
69802
  try {
69424
- return fs46.existsSync(r);
69803
+ return fs49.existsSync(r);
69425
69804
  } catch {
69426
69805
  return false;
69427
69806
  }
@@ -69463,19 +69842,27 @@ function cmdProject(opts) {
69463
69842
  emitErr4(`project: unknown action '${action}'. Use list, exclude, or prune.`);
69464
69843
  throw new Error(`unknown project action: ${action}`);
69465
69844
  }
69845
+ function recordCompactDocStat(fullSourceBytes, emittedText, detail) {
69846
+ const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emittedText, "utf8"));
69847
+ recordStat("compact_doc", bytesSaved, Math.round(bytesSaved / 4), void 0, detail);
69848
+ }
69466
69849
  function cmdCompactDoc(opts) {
69467
- const resolved = path56.resolve(opts.filePath);
69850
+ const resolved = path57.resolve(opts.filePath);
69468
69851
  if (opts.heading !== void 0) {
69469
69852
  const result = compactDoc(resolved, opts.heading);
69470
69853
  if (result === null) {
69471
69854
  emitErr4(`compact-doc: could not read or compact '${resolved}'`);
69472
69855
  throw new Error(`could not compact: ${resolved}`);
69473
69856
  }
69857
+ const legacyFullBytes = fs49.statSync(resolved).size;
69474
69858
  if (opts.json === true) {
69475
- emit4(JSON.stringify({ path: resolved, compact: result }, null, 2));
69859
+ const jsonText = JSON.stringify({ path: resolved, compact: result }, null, 2);
69860
+ emit4(jsonText);
69861
+ recordCompactDocStat(legacyFullBytes, jsonText, resolved);
69476
69862
  return;
69477
69863
  }
69478
69864
  emit4(result);
69865
+ recordCompactDocStat(legacyFullBytes, result, resolved);
69479
69866
  return;
69480
69867
  }
69481
69868
  let sentences;
@@ -69494,7 +69881,7 @@ function cmdCompactDoc(opts) {
69494
69881
  if (opts.force === true || opts.sentences !== void 0 || !fresh) {
69495
69882
  let sourceText;
69496
69883
  try {
69497
- sourceText = fs46.readFileSync(resolved, "utf-8");
69884
+ sourceText = fs49.readFileSync(resolved, "utf-8");
69498
69885
  } catch {
69499
69886
  emitErr4(`compact-doc: could not read or compact '${resolved}'`);
69500
69887
  throw new Error(`could not compact: ${resolved}`);
@@ -69510,12 +69897,16 @@ function cmdCompactDoc(opts) {
69510
69897
  }
69511
69898
  body = existing;
69512
69899
  }
69900
+ const extractiveFullBytes = fs49.statSync(resolved).size;
69513
69901
  if (opts.json === true) {
69514
- emit4(JSON.stringify({ path: resolved, compactPath, rebuilt, compact: body }, null, 2));
69902
+ const jsonText = JSON.stringify({ path: resolved, compactPath, rebuilt, compact: body }, null, 2);
69903
+ emit4(jsonText);
69904
+ recordCompactDocStat(extractiveFullBytes, jsonText, resolved);
69515
69905
  return;
69516
69906
  }
69517
69907
  if (opts.show === true) {
69518
69908
  emit4(body);
69909
+ recordCompactDocStat(extractiveFullBytes, body, resolved);
69519
69910
  return;
69520
69911
  }
69521
69912
  emit4(
@@ -69563,7 +69954,7 @@ async function cmdFetchImage(opts) {
69563
69954
  throw new Error(`fetch failed: ${opts.url}`, { cause: e });
69564
69955
  }
69565
69956
  const buf = fetched.body;
69566
- const outPath = opts.out ?? path56.join(os18.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
69957
+ const outPath = opts.out ?? path57.join(os18.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
69567
69958
  const originalBytes = buf.length;
69568
69959
  let shrunkBytes;
69569
69960
  let outData;
@@ -69652,8 +70043,8 @@ init_cli_context_stats();
69652
70043
  init_memory_prune();
69653
70044
  init_project();
69654
70045
  init_confirm_apply();
69655
- import * as fs47 from "node:fs";
69656
- import * as path57 from "node:path";
70046
+ import * as fs50 from "node:fs";
70047
+ import * as path58 from "node:path";
69657
70048
  function removeExactDupLines(text, report) {
69658
70049
  if (report.exactDupLines.length === 0) return text;
69659
70050
  const toRemove = new Set(report.exactDupLines.map(([, dupIdx]) => dupIdx));
@@ -69711,7 +70102,7 @@ function printReport(reports, clusters) {
69711
70102
  } else {
69712
70103
  for (const cluster of clusters) {
69713
70104
  w(
69714
- ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => path57.basename(m)).join(", ")}
70105
+ ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => path58.basename(m)).join(", ")}
69715
70106
  `
69716
70107
  );
69717
70108
  }
@@ -69726,7 +70117,7 @@ async function runMemoryCommand(opts = {}) {
69726
70117
  const claudeMds = findClaudeMdFiles(projectRoot);
69727
70118
  const reports = auditClaudeMd(claudeMds);
69728
70119
  const memoryMdPath = findMemoryMd(projectRoot);
69729
- const clusters = memoryMdPath !== null ? await findContentDuplicates(path57.dirname(memoryMdPath)) : [];
70120
+ const clusters = memoryMdPath !== null ? await findContentDuplicates(path58.dirname(memoryMdPath)) : [];
69730
70121
  printReport(reports, clusters);
69731
70122
  if (opts.fix !== true) return;
69732
70123
  const changes = [];
@@ -69734,7 +70125,7 @@ async function runMemoryCommand(opts = {}) {
69734
70125
  if (report.exactDupLines.length === 0) continue;
69735
70126
  let before;
69736
70127
  try {
69737
- before = fs47.readFileSync(report.path, "utf-8");
70128
+ before = fs50.readFileSync(report.path, "utf-8");
69738
70129
  } catch {
69739
70130
  continue;
69740
70131
  }
@@ -69773,34 +70164,34 @@ async function runMemoryCommand(opts = {}) {
69773
70164
  // src/cli_waste.ts
69774
70165
  init_define_import_meta_env();
69775
70166
  init_project();
69776
- import * as fs49 from "node:fs";
69777
- import * as path59 from "node:path";
70167
+ import * as fs52 from "node:fs";
70168
+ import * as path60 from "node:path";
69778
70169
 
69779
70170
  // src/waste.ts
69780
70171
  init_define_import_meta_env();
69781
70172
  init_overflow_guard();
69782
- import * as fs48 from "node:fs";
70173
+ import * as fs51 from "node:fs";
69783
70174
  import * as os19 from "node:os";
69784
- import * as path58 from "node:path";
70175
+ import * as path59 from "node:path";
69785
70176
  function projectTranscriptsDir(projectRoot) {
69786
- const rootStr = path58.resolve(projectRoot);
70177
+ const rootStr = path59.resolve(projectRoot);
69787
70178
  const slug = rootStr.replace(/[^A-Za-z0-9]/g, "-");
69788
- return path58.join(os19.homedir(), ".claude", "projects", slug);
70179
+ return path59.join(os19.homedir(), ".claude", "projects", slug);
69789
70180
  }
69790
70181
  function findLatestTranscript(projectRoot) {
69791
70182
  const dir = projectTranscriptsDir(projectRoot);
69792
70183
  let entries;
69793
70184
  try {
69794
- entries = fs48.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
70185
+ entries = fs51.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
69795
70186
  } catch {
69796
70187
  return null;
69797
70188
  }
69798
70189
  let best = null;
69799
70190
  for (const name2 of entries) {
69800
- const full = path58.join(dir, name2);
70191
+ const full = path59.join(dir, name2);
69801
70192
  let stat2;
69802
70193
  try {
69803
- stat2 = fs48.statSync(full);
70194
+ stat2 = fs51.statSync(full);
69804
70195
  } catch {
69805
70196
  continue;
69806
70197
  }
@@ -69857,7 +70248,7 @@ function extractResultText(content) {
69857
70248
  return "";
69858
70249
  }
69859
70250
  function parseTranscript2(transcriptPath) {
69860
- const raw = fs48.readFileSync(transcriptPath, "utf-8");
70251
+ const raw = fs51.readFileSync(transcriptPath, "utf-8");
69861
70252
  const calls = [];
69862
70253
  const resultTextById = /* @__PURE__ */ new Map();
69863
70254
  let seq = 0;
@@ -70050,7 +70441,7 @@ function printReport2(report) {
70050
70441
  }
70051
70442
  async function runWasteCommand(opts = {}) {
70052
70443
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
70053
- const transcriptPath = opts.transcript !== void 0 ? path59.resolve(opts.transcript) : findLatestTranscript(projectRoot);
70444
+ const transcriptPath = opts.transcript !== void 0 ? path60.resolve(opts.transcript) : findLatestTranscript(projectRoot);
70054
70445
  if (transcriptPath === null) {
70055
70446
  if (opts.json === true) {
70056
70447
  process.stdout.write(`${JSON.stringify({ error: "no session transcript found", project: projectRoot })}
@@ -70064,7 +70455,7 @@ async function runWasteCommand(opts = {}) {
70064
70455
  process.exitCode = 1;
70065
70456
  return;
70066
70457
  }
70067
- if (!fs49.existsSync(transcriptPath)) {
70458
+ if (!fs52.existsSync(transcriptPath)) {
70068
70459
  process.stderr.write(`token-goat: transcript not found: ${transcriptPath}
70069
70460
  `);
70070
70461
  process.exitCode = 1;
@@ -70082,18 +70473,18 @@ async function runWasteCommand(opts = {}) {
70082
70473
  // src/session_read.ts
70083
70474
  init_define_import_meta_env();
70084
70475
  init_compact();
70085
- import * as fs50 from "node:fs";
70086
- import * as path60 from "node:path";
70476
+ import * as fs53 from "node:fs";
70477
+ import * as path61 from "node:path";
70087
70478
  import * as readline2 from "node:readline";
70088
70479
  init_project();
70089
70480
  function resolveSessionTranscript(arg, opts = {}) {
70090
70481
  if (arg !== void 0 && arg !== "") {
70091
- if (fs50.existsSync(arg) && fs50.statSync(arg).isFile()) return arg;
70482
+ if (fs53.existsSync(arg) && fs53.statSync(arg).isFile()) return arg;
70092
70483
  const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
70093
70484
  const dir = projectTranscriptsDir(projectRoot2);
70094
70485
  const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
70095
- const candidate = path60.join(dir, byId);
70096
- if (fs50.existsSync(candidate) && fs50.statSync(candidate).isFile()) return candidate;
70486
+ const candidate = path61.join(dir, byId);
70487
+ if (fs53.existsSync(candidate) && fs53.statSync(candidate).isFile()) return candidate;
70097
70488
  return null;
70098
70489
  }
70099
70490
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
@@ -70151,7 +70542,7 @@ function toolCallsForBlocks(blocks) {
70151
70542
  return blocks.filter((b) => b.type === "tool_use" && b.name !== void 0).map((b) => b.name);
70152
70543
  }
70153
70544
  async function* streamTurns(transcriptPath) {
70154
- const input = fs50.createReadStream(transcriptPath, { encoding: "utf8" });
70545
+ const input = fs53.createReadStream(transcriptPath, { encoding: "utf8" });
70155
70546
  const rl = readline2.createInterface({ input, crlfDelay: Infinity });
70156
70547
  try {
70157
70548
  let lineNumber = 0;
@@ -70252,15 +70643,15 @@ function formatSessionSlice(turns) {
70252
70643
  init_define_import_meta_env();
70253
70644
  init_project();
70254
70645
  init_disk_cache();
70255
- import * as fs51 from "node:fs";
70256
- import * as path61 from "node:path";
70646
+ import * as fs54 from "node:fs";
70647
+ import * as path62 from "node:path";
70257
70648
  function readMcpConfig(projectRoot) {
70258
- const configPath2 = path61.join(projectRoot, ".mcp.json");
70649
+ const configPath2 = path62.join(projectRoot, ".mcp.json");
70259
70650
  try {
70260
- if (!fs51.existsSync(configPath2)) {
70651
+ if (!fs54.existsSync(configPath2)) {
70261
70652
  return null;
70262
70653
  }
70263
- const content = fs51.readFileSync(configPath2, "utf-8");
70654
+ const content = fs54.readFileSync(configPath2, "utf-8");
70264
70655
  const parsed = JSON.parse(content);
70265
70656
  return parsed.mcpServers || parsed;
70266
70657
  } catch {
@@ -70569,8 +70960,8 @@ function requirePositiveInt(flag, raw) {
70569
70960
  }
70570
70961
  async function cmdSemantic(query, opts) {
70571
70962
  const limit = opts.limit !== void 0 ? requireNonNegativeInt("--limit", opts.limit) : 20;
70572
- const { text, code } = await runSemantic(query, { limit });
70573
- (code === 0 ? out : err)(text);
70963
+ const { text, code } = await runSemantic(query, { limit, ...opts.json === true ? { json: true } : {} });
70964
+ (opts.json === true || code === 0 ? out : err)(text);
70574
70965
  process.exitCode = code;
70575
70966
  }
70576
70967
  async function cmdIndex(pathArg, opts = {}) {
@@ -70584,7 +70975,13 @@ async function cmdIndex(pathArg, opts = {}) {
70584
70975
  `no tracked files found under '${root}' (is it a git repo?). Pass --walk to index a non-git folder.`
70585
70976
  );
70586
70977
  }
70587
- files = collectWalkIndexFiles(root);
70978
+ files = collectWalkIndexFiles(root, { force: opts.forceWalk === true });
70979
+ if (opts.forceWalk === true) {
70980
+ process.stderr.write(
70981
+ `token-goat: --force-walk raised the walk cap to ${MAX_FILES_SCANNED_FORCED} files; indexing ${files.length} files may take a long time and produce a large index. Run 'token-goat doctor' afterwards to check index size.
70982
+ `
70983
+ );
70984
+ }
70588
70985
  }
70589
70986
  const blockedRoots = loadConfig().worker.blocked_roots;
70590
70987
  const ixCfg = loadConfig().indexing;
@@ -70671,8 +71068,8 @@ async function cmdMcpServe() {
70671
71068
  const server = createMcpServer2();
70672
71069
  const transport = new StdioServerTransport();
70673
71070
  await server.connect(transport);
70674
- await new Promise((resolve20) => {
70675
- server.server.onclose = resolve20;
71071
+ await new Promise((resolve21) => {
71072
+ server.server.onclose = resolve21;
70676
71073
  });
70677
71074
  }
70678
71075
  async function cmdHook(event, opts) {
@@ -70689,6 +71086,9 @@ async function cmdInstall(opts) {
70689
71086
  out(
70690
71087
  claudeMdResult.alreadyInstalled ? `CLAUDE.md block already up to date \u2192 ${claudeMdResult.path}` : `Updated CLAUDE.md \u2192 ${claudeMdResult.path}`
70691
71088
  );
71089
+ for (const stray of findStrayClaudeMdBlocks()) {
71090
+ out(`WARNING: stray token-goat block in ${stray} \u2014 not managed by install/uninstall; delete it to avoid duplicate, stale guidance.`);
71091
+ }
70692
71092
  const skillResult = installSkill();
70693
71093
  out(
70694
71094
  skillResult.alreadyInstalled ? `token-goat skill already up to date \u2192 ${skillResult.path}` : `Installed token-goat skill \u2192 ${skillResult.path}`
@@ -70738,7 +71138,7 @@ async function cmdInstall(opts) {
70738
71138
  if (copilotResult.alreadyInstalled) {
70739
71139
  out(`Copilot CLI integration already installed \u2192 ${copilotResult.configPath}`);
70740
71140
  } else {
70741
- out(`Installed token-goat Copilot CLI integration \u2192 ${copilotResult.configPath}, ${copilotResult.scriptPath}`);
71141
+ out(`Installed token-goat Copilot CLI integration \u2192 ${copilotResult.configPath}, ${copilotResult.scriptPath}, ${copilotResult.instructionsPath}`);
70742
71142
  }
70743
71143
  }
70744
71144
  if (opts.opencode === true) {
@@ -70763,16 +71163,16 @@ async function cmdInstall(opts) {
70763
71163
  );
70764
71164
  }
70765
71165
  try {
70766
- const skillDir2 = path62.join(homedir17(), ".claude", "skills");
70767
- if (fs52.existsSync(skillDir2)) {
70768
- const entries = fs52.readdirSync(skillDir2, { withFileTypes: true });
71166
+ const skillDir2 = path63.join(homedir17(), ".claude", "skills");
71167
+ if (fs55.existsSync(skillDir2)) {
71168
+ const entries = fs55.readdirSync(skillDir2, { withFileTypes: true });
70769
71169
  const skillNames = [];
70770
71170
  const sessionId = getSessionId();
70771
71171
  for (const entry of entries) {
70772
71172
  if (!entry.isDirectory()) continue;
70773
- const skillFile = path62.join(skillDir2, entry.name, "SKILL.md");
70774
- if (fs52.existsSync(skillFile)) {
70775
- const body = fs52.readFileSync(skillFile, "utf-8");
71173
+ const skillFile = path63.join(skillDir2, entry.name, "SKILL.md");
71174
+ if (fs55.existsSync(skillFile)) {
71175
+ const body = fs55.readFileSync(skillFile, "utf-8");
70776
71176
  const compact = extractCompactFromMarker(body);
70777
71177
  if (compact === null) continue;
70778
71178
  const sourceSha = contentHash(body);
@@ -70782,10 +71182,10 @@ async function cmdInstall(opts) {
70782
71182
  }
70783
71183
  if (skillNames.length > 0) {
70784
71184
  const dir = skillOutputsDir();
70785
- await fs52.promises.mkdir(dir, { recursive: true });
70786
- const pregenPath = path62.join(dir, "pregen.json");
71185
+ await fs55.promises.mkdir(dir, { recursive: true });
71186
+ const pregenPath = path63.join(dir, "pregen.json");
70787
71187
  const pregenData = { ts: Date.now(), names: skillNames };
70788
- await fs52.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
71188
+ await fs55.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
70789
71189
  out(`Pre-generated ${skillNames.length} skill compacts.`);
70790
71190
  }
70791
71191
  }
@@ -70798,6 +71198,9 @@ function cmdUninstall(opts) {
70798
71198
  out(removed ? `Removed token-goat hooks (${scope}).` : `No token-goat hooks to remove (${scope}).`);
70799
71199
  const claudeMdRemoved = uninstallClaudeMd();
70800
71200
  out(claudeMdRemoved ? "Removed token-goat block from CLAUDE.md." : "No token-goat block in CLAUDE.md to remove.");
71201
+ for (const stray of findStrayClaudeMdBlocks()) {
71202
+ out(`NOTE: a token-goat block remains in ${stray} \u2014 outside CLAUDE.md, so it was not removed. Delete it manually if unwanted.`);
71203
+ }
70801
71204
  const skillRemoved = uninstallSkill();
70802
71205
  out(skillRemoved ? "Removed token-goat skill." : "No token-goat skill to remove.");
70803
71206
  const removals = [
@@ -70900,7 +71303,7 @@ ${formatSessionOutline(turns)}`;
70900
71303
  }
70901
71304
  function sessionTranscriptSize(transcriptPath) {
70902
71305
  try {
70903
- return fs52.statSync(transcriptPath).size;
71306
+ return fs55.statSync(transcriptPath).size;
70904
71307
  } catch {
70905
71308
  return 0;
70906
71309
  }
@@ -71004,7 +71407,7 @@ function _applyFiltersAndPrint(content, opts) {
71004
71407
  }
71005
71408
  function fileSizeOrZero(filePath) {
71006
71409
  try {
71007
- return fs52.statSync(filePath).size;
71410
+ return fs55.statSync(filePath).size;
71008
71411
  } catch {
71009
71412
  return 0;
71010
71413
  }
@@ -71019,11 +71422,11 @@ function cmdBashOutput(id, opts) {
71019
71422
  }
71020
71423
  let content;
71021
71424
  try {
71022
- const st = fs52.statSync(opts.file);
71425
+ const st = fs55.statSync(opts.file);
71023
71426
  if (st.isFIFO() || st.isSocket()) {
71024
71427
  throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
71025
71428
  }
71026
- content = fs52.readFileSync(opts.file, "utf-8");
71429
+ content = fs55.readFileSync(opts.file, "utf-8");
71027
71430
  } catch (e) {
71028
71431
  if (e instanceof CliError) throw e;
71029
71432
  throw new CliError(`cannot read file: ${opts.file}`);
@@ -71359,7 +71762,7 @@ async function cmdSkillBody(name2, opts) {
71359
71762
  if (filePath === null) {
71360
71763
  throw new CliError(`skill '${name2}' not found`);
71361
71764
  }
71362
- const body = fs52.readFileSync(filePath, "utf-8");
71765
+ const body = fs55.readFileSync(filePath, "utf-8");
71363
71766
  if (opts.compact === true) {
71364
71767
  out(extractCompactFromMarker(body) ?? body);
71365
71768
  } else {
@@ -71376,7 +71779,7 @@ async function cmdSkillCompact(name2, opts) {
71376
71779
  for (const skill of skills) {
71377
71780
  const filePath = await getSkillFilePath(skill.name);
71378
71781
  if (!filePath) continue;
71379
- const body2 = fs52.readFileSync(filePath, "utf-8");
71782
+ const body2 = fs55.readFileSync(filePath, "utf-8");
71380
71783
  const compact2 = extractCompactFromMarker(body2);
71381
71784
  if (compact2 === null) continue;
71382
71785
  const sourceSha2 = contentHash(body2);
@@ -71397,19 +71800,19 @@ async function cmdSkillCompact(name2, opts) {
71397
71800
  if (!opts.path.trim()) {
71398
71801
  throw new CliError("--path cannot be empty");
71399
71802
  }
71400
- if (!fs52.existsSync(opts.path)) {
71803
+ if (!fs55.existsSync(opts.path)) {
71401
71804
  throw new CliError(`skill file not found: ${opts.path}`);
71402
71805
  }
71403
71806
  try {
71404
- body = fs52.readFileSync(opts.path, "utf-8");
71807
+ body = fs55.readFileSync(opts.path, "utf-8");
71405
71808
  } catch (e) {
71406
71809
  if (e.code === "ENOENT") {
71407
71810
  throw new CliError(`skill file not found: ${opts.path}`);
71408
71811
  }
71409
71812
  throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
71410
71813
  }
71411
- cacheName = name2 ?? path62.basename(path62.dirname(path62.resolve(opts.path)));
71412
- sourcePath = path62.resolve(opts.path);
71814
+ cacheName = name2 ?? path63.basename(path63.dirname(path63.resolve(opts.path)));
71815
+ sourcePath = path63.resolve(opts.path);
71413
71816
  } else {
71414
71817
  if (name2 === void 0 || !name2.trim()) {
71415
71818
  throw new CliError("skill-compact requires a <name> or --path <file>");
@@ -71418,7 +71821,7 @@ async function cmdSkillCompact(name2, opts) {
71418
71821
  if (filePath === null) {
71419
71822
  throw new CliError(`skill '${name2}' not found`);
71420
71823
  }
71421
- body = fs52.readFileSync(filePath, "utf-8");
71824
+ body = fs55.readFileSync(filePath, "utf-8");
71422
71825
  cacheName = name2;
71423
71826
  sourcePath = filePath;
71424
71827
  }
@@ -71527,8 +71930,8 @@ async function cmdSkillDiff(name2) {
71527
71930
  }
71528
71931
  const newer = versions[0];
71529
71932
  const older = versions[1];
71530
- const newerBody = await fs52.promises.readFile(path62.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
71531
- const olderBody = await fs52.promises.readFile(path62.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
71933
+ const newerBody = await fs55.promises.readFile(path63.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
71934
+ const olderBody = await fs55.promises.readFile(path63.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
71532
71935
  if (newerBody === null || olderBody === null) {
71533
71936
  out(`a cached version of '${name2}' was evicted while diffing -- try again`);
71534
71937
  return;
@@ -71557,7 +71960,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
71557
71960
  if (!filePath) {
71558
71961
  throw new CliError(`skill '${skillName}' not found`);
71559
71962
  }
71560
- const body = fs52.readFileSync(filePath, "utf-8");
71963
+ const body = fs55.readFileSync(filePath, "utf-8");
71561
71964
  const extracted = extractNamedSection(body, heading);
71562
71965
  if (!extracted) {
71563
71966
  process.exitCode = 1;
@@ -71567,7 +71970,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
71567
71970
  }
71568
71971
  function atomicWriteBuffer(dest, data) {
71569
71972
  try {
71570
- if (fs52.statSync(dest).isDirectory()) {
71973
+ if (fs55.statSync(dest).isDirectory()) {
71571
71974
  const e = Object.assign(new Error(`EISDIR: illegal operation on a directory, open '${dest}'`), { code: "EISDIR", path: dest });
71572
71975
  throw e;
71573
71976
  }
@@ -71575,23 +71978,23 @@ function atomicWriteBuffer(dest, data) {
71575
71978
  if (e.code !== "ENOENT") throw e;
71576
71979
  }
71577
71980
  const rnd = Math.random().toString(36).slice(2, 8);
71578
- const tmp = path62.join(path62.dirname(path62.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
71981
+ const tmp = path63.join(path63.dirname(path63.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
71579
71982
  try {
71580
- fs52.writeFileSync(tmp, data, { mode: 384 });
71983
+ fs55.writeFileSync(tmp, data, { mode: 384 });
71581
71984
  try {
71582
- const destMode = fs52.statSync(dest).mode;
71583
- fs52.chmodSync(tmp, destMode);
71985
+ const destMode = fs55.statSync(dest).mode;
71986
+ fs55.chmodSync(tmp, destMode);
71584
71987
  } catch (e) {
71585
71988
  if (e.code !== "ENOENT") throw e;
71586
71989
  }
71587
71990
  withRetryOnLock(() => {
71588
71991
  try {
71589
- fs52.renameSync(tmp, dest);
71992
+ fs55.renameSync(tmp, dest);
71590
71993
  } catch (e) {
71591
71994
  if (e.code === "EXDEV") {
71592
- fs52.copyFileSync(tmp, dest);
71995
+ fs55.copyFileSync(tmp, dest);
71593
71996
  try {
71594
- fs52.unlinkSync(tmp);
71997
+ fs55.unlinkSync(tmp);
71595
71998
  } catch (ue) {
71596
71999
  process.stderr.write(`token-goat write-file: warning: could not remove temp file ${tmp}: ${ue.message}
71597
72000
  `);
@@ -71603,7 +72006,7 @@ function atomicWriteBuffer(dest, data) {
71603
72006
  });
71604
72007
  } catch (e) {
71605
72008
  try {
71606
- fs52.unlinkSync(tmp);
72009
+ fs55.unlinkSync(tmp);
71607
72010
  } catch {
71608
72011
  }
71609
72012
  throw e;
@@ -71613,9 +72016,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
71613
72016
  const fe = e;
71614
72017
  if (fe.code === "ENOENT") {
71615
72018
  const errPath = fe.path ?? "";
71616
- const isSource = src !== void 0 && path62.resolve(errPath) === path62.resolve(src);
72019
+ const isSource = src !== void 0 && path63.resolve(errPath) === path63.resolve(src);
71617
72020
  if (isSource) throw new CliError(`${srcLabel} file not found: ${src}`);
71618
- const destDir = dest ? path62.dirname(path62.resolve(dest)) : path62.dirname(path62.resolve(errPath || "."));
72021
+ const destDir = dest ? path63.dirname(path63.resolve(dest)) : path63.dirname(path63.resolve(errPath || "."));
71619
72022
  throw new CliError(`destination directory does not exist: ${destDir}`);
71620
72023
  }
71621
72024
  if (fe.code === "ENOTDIR") {
@@ -71626,7 +72029,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
71626
72029
  }
71627
72030
  if (fe.code === "EISDIR") {
71628
72031
  const errPath = fe.path ?? "";
71629
- const isSource = src !== void 0 && (errPath === "" || path62.resolve(errPath) === path62.resolve(src));
72032
+ const isSource = src !== void 0 && (errPath === "" || path63.resolve(errPath) === path63.resolve(src));
71630
72033
  if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
71631
72034
  throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
71632
72035
  }
@@ -71695,7 +72098,7 @@ function validateWritablePath(dest, label) {
71695
72098
  throw new CliError(`${label} path contains a null byte`);
71696
72099
  }
71697
72100
  if (isWindows()) {
71698
- const base = path62.basename(dest);
72101
+ const base = path63.basename(dest);
71699
72102
  const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
71700
72103
  if (WIN_RESERVED.has(stem)) {
71701
72104
  throw new CliError(`${label} '${base}' is a reserved Windows device name`);
@@ -71725,7 +72128,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
71725
72128
  throw new CliError(`${label} ${filePath} requires piped input; use ${altLabel} for interactive use`);
71726
72129
  }
71727
72130
  try {
71728
- const st = fs52.statSync(filePath);
72131
+ const st = fs55.statSync(filePath);
71729
72132
  if (st.isFIFO() || st.isSocket()) {
71730
72133
  throw new CliError(`${label} '${filePath}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
71731
72134
  }
@@ -71733,7 +72136,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
71733
72136
  if (st.size > maxBytes) {
71734
72137
  throw new CliError(`${label} '${filePath}' exceeds size limit (${Math.round(st.size / 1024 / 1024)} MB); set TOKEN_GOAT_MAX_STDIN_MB to override`);
71735
72138
  }
71736
- return fs52.readFileSync(filePath);
72139
+ return fs55.readFileSync(filePath);
71737
72140
  } catch (e) {
71738
72141
  if (e instanceof CliError) throw e;
71739
72142
  mapFsError(e, filePath, void 0, label);
@@ -71777,7 +72180,7 @@ function cmdNoteAdd(file2, opts) {
71777
72180
  throw new CliError("note content must be valid UTF-8 text");
71778
72181
  }
71779
72182
  const resolvedPath = resolveIndexPath(file2);
71780
- if (!fs52.existsSync(resolvedPath)) {
72183
+ if (!fs55.existsSync(resolvedPath)) {
71781
72184
  throw new CliError(`File not found: '${resolvedPath}'`);
71782
72185
  }
71783
72186
  healStaleIndex(resolvedPath);
@@ -71834,7 +72237,7 @@ function cmdWriteFile(dest, opts) {
71834
72237
  throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
71835
72238
  }
71836
72239
  const maxBytes = maxMB * 1024 * 1024;
71837
- return new Promise((resolve20, reject) => {
72240
+ return new Promise((resolve21, reject) => {
71838
72241
  const chunks = [];
71839
72242
  let totalBytes = 0;
71840
72243
  let settled = false;
@@ -71858,7 +72261,7 @@ function cmdWriteFile(dest, opts) {
71858
72261
  try {
71859
72262
  atomicWriteBuffer(dest, Buffer.concat(chunks));
71860
72263
  enqueueDirtyPathSafe(dest);
71861
- resolve20();
72264
+ resolve21();
71862
72265
  } catch (e) {
71863
72266
  try {
71864
72267
  mapFsError(e, void 0, dest);
@@ -71961,7 +72364,7 @@ function cmdReplace(file2, opts) {
71961
72364
  const targetBuf = readFileBoundedRaw(file2, "target file", true);
71962
72365
  let preWriteStat;
71963
72366
  try {
71964
- preWriteStat = fs52.statSync(file2);
72367
+ preWriteStat = fs55.statSync(file2);
71965
72368
  } catch {
71966
72369
  }
71967
72370
  const usingFrom = opts.oldFrom !== void 0 || opts.newFrom !== void 0;
@@ -72030,7 +72433,7 @@ ${diff}`
72030
72433
  }
72031
72434
  let preRenameStat;
72032
72435
  try {
72033
- preRenameStat = fs52.statSync(file2);
72436
+ preRenameStat = fs55.statSync(file2);
72034
72437
  } catch {
72035
72438
  }
72036
72439
  if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
@@ -72061,7 +72464,7 @@ function cmdInsertSection(file2, opts) {
72061
72464
  }
72062
72465
  let preWriteStat;
72063
72466
  try {
72064
- preWriteStat = fs52.statSync(file2);
72467
+ preWriteStat = fs55.statSync(file2);
72065
72468
  } catch {
72066
72469
  }
72067
72470
  const result = readSection(file2, opts.after);
@@ -72073,7 +72476,7 @@ function cmdInsertSection(file2, opts) {
72073
72476
  }
72074
72477
  let rawText;
72075
72478
  try {
72076
- rawText = fs52.readFileSync(file2, "utf-8");
72479
+ rawText = fs55.readFileSync(file2, "utf-8");
72077
72480
  } catch (e) {
72078
72481
  mapFsError(e, void 0, file2);
72079
72482
  }
@@ -72088,7 +72491,7 @@ function cmdInsertSection(file2, opts) {
72088
72491
  if (preWriteStat !== void 0) {
72089
72492
  let preRenameStat;
72090
72493
  try {
72091
- preRenameStat = fs52.statSync(file2);
72494
+ preRenameStat = fs55.statSync(file2);
72092
72495
  } catch {
72093
72496
  }
72094
72497
  if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
@@ -72126,25 +72529,25 @@ ${content}`;
72126
72529
  }
72127
72530
  function expandGlobs(root, patterns, globFnOverride) {
72128
72531
  const out2 = [];
72129
- const globFn = globFnOverride ?? fs52["globSync"];
72532
+ const globFn = globFnOverride ?? fs55["globSync"];
72130
72533
  for (const p of patterns) {
72131
72534
  if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
72132
72535
  try {
72133
72536
  const hits = globFn(p, { cwd: root });
72134
- for (const h of hits) out2.push(path62.isAbsolute(h) ? h : path62.join(root, h));
72537
+ for (const h of hits) out2.push(path63.isAbsolute(h) ? h : path63.join(root, h));
72135
72538
  continue;
72136
72539
  } catch {
72137
72540
  }
72138
72541
  }
72139
- out2.push(path62.isAbsolute(p) ? p : path62.join(root, p));
72542
+ out2.push(path63.isAbsolute(p) ? p : path63.join(root, p));
72140
72543
  }
72141
72544
  return out2;
72142
72545
  }
72143
72546
  function readIgnoreFile(root) {
72144
- const ignorePath = path62.join(root, ".tokengoatignore");
72547
+ const ignorePath = path63.join(root, ".tokengoatignore");
72145
72548
  let raw;
72146
72549
  try {
72147
- raw = fs52.readFileSync(ignorePath, "utf8");
72550
+ raw = fs55.readFileSync(ignorePath, "utf8");
72148
72551
  } catch {
72149
72552
  return void 0;
72150
72553
  }
@@ -72185,14 +72588,14 @@ function cmdPack(patterns, opts) {
72185
72588
  }
72186
72589
  let instruction;
72187
72590
  if (opts.instructionFile !== void 0) {
72188
- instruction = fs52.readFileSync(opts.instructionFile, "utf8");
72591
+ instruction = fs55.readFileSync(opts.instructionFile, "utf8");
72189
72592
  }
72190
72593
  const formatted = formatPack(result, style, {
72191
72594
  ...opts.lineNumbers === true ? { line_numbers: true } : {},
72192
72595
  ...instruction !== void 0 ? { instruction } : {}
72193
72596
  });
72194
72597
  if (opts.output !== void 0) {
72195
- fs52.writeFileSync(opts.output, formatted, "utf8");
72598
+ fs55.writeFileSync(opts.output, formatted, "utf8");
72196
72599
  } else {
72197
72600
  out(formatted);
72198
72601
  }
@@ -72210,7 +72613,7 @@ function cmdTokens(patterns, opts) {
72210
72613
  if (opts.tree === true) {
72211
72614
  const dirs = /* @__PURE__ */ new Map();
72212
72615
  for (const e of entries) {
72213
- const dir = path62.dirname(e.rel_path);
72616
+ const dir = path63.dirname(e.rel_path);
72214
72617
  if (!dirs.has(dir)) dirs.set(dir, []);
72215
72618
  dirs.get(dir).push(e);
72216
72619
  }
@@ -72220,7 +72623,7 @@ function cmdTokens(patterns, opts) {
72220
72623
  const pct = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
72221
72624
  lines3.push(`${dir}/ (${dirTokens} tokens, ${pct}%)`);
72222
72625
  for (const e of dirEntries) {
72223
- lines3.push(` ${path62.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
72626
+ lines3.push(` ${path63.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
72224
72627
  }
72225
72628
  }
72226
72629
  out(lines3.join("\n"));
@@ -72251,7 +72654,7 @@ function cmdBudget(patterns, opts) {
72251
72654
  }
72252
72655
  }
72253
72656
  function cmdFailures(src, opts) {
72254
- const text = src !== void 0 ? fs52.readFileSync(src, "utf8") : fs52.readFileSync(0, "utf8");
72657
+ const text = src !== void 0 ? fs55.readFileSync(src, "utf8") : fs55.readFileSync(0, "utf8");
72255
72658
  const result = extractFailures(text, opts.runner !== void 0 ? { runner: opts.runner } : {});
72256
72659
  if (opts.delta !== true) {
72257
72660
  out(opts.json === true ? formatFailuresJson(result) : formatFailuresText(result));
@@ -72323,7 +72726,7 @@ function buildProgram() {
72323
72726
  ).option("-j, --json", "output as JSON").option("--list", "list all section headings in the file instead of reading one").action(
72324
72727
  (spec, opts) => opts.list === true ? runExit(() => runListSections({ file: spec, ...opts.json === true ? { json: true } : {} })) : runExitText(() => runSection({ spec, ...opts.json === true ? { json: true } : {} }))
72325
72728
  );
72326
- program2.command("semantic <query>").description("semantic search (falls back to full-text search)").option("-l, --limit <n>", "max results").action(guard(cmdSemantic));
72729
+ program2.command("semantic <query>").description("semantic search (falls back to full-text search)").option("-l, --limit <n>", "max results").option("-j, --json", "output as JSON").action(guard(cmdSemantic));
72327
72730
  program2.command("skeleton <file>").description("list all symbols in a file without bodies").option("-j, --json", "output as JSON").option("--min-lines <n>", "only show symbols at least N lines long").option("--force-refresh", "reparse file from disk before querying (ignore stale index)").option("--stats", "add per-symbol reference count and doc-coverage flag").action(
72328
72731
  (file2, opts) => runExitText(
72329
72732
  () => runSkeleton({
@@ -72360,14 +72763,14 @@ function buildProgram() {
72360
72763
  })
72361
72764
  )
72362
72765
  );
72363
- program2.command("index [path]").description("parse all git-tracked files and (re)build the symbol index").option("--walk", "if not a git repo, index a bounded directory walk instead (skips .env / generated / oversized trees)").option("--force", "bypass the SHA-freshness cache and reindex every tracked file, even byte-identical ones (e.g. after a parser upgrade changes what gets extracted)").action(guard(cmdIndex));
72766
+ program2.command("index [path]").description("parse all git-tracked files and (re)build the symbol index").option("--walk", "if not a git repo, index a bounded directory walk instead (skips .env / generated / oversized trees)").option("--force", "bypass the SHA-freshness cache and reindex every tracked file, even byte-identical ones (e.g. after a parser upgrade changes what gets extracted)").option("--force-walk", `with --walk, raise the ${MAX_FILES_SCANNED} source-file refusal to ${MAX_FILES_SCANNED_FORCED} for a folder you know is genuinely that large (slow; produces a large index)`).action(guard(cmdIndex));
72364
72767
  program2.command("map").description("project overview").option("-c, --compact", "compact, low-token summary").action(guard(cmdMap));
72365
72768
  program2.command("bridges-status").description("hook-event parity matrix across every AI-harness bridge (read-only static analysis, never invokes a real harness binary)").option("--json", "emit the matrix as JSON instead of text").action(guard(cmdBridgesStatus));
72366
72769
  program2.command("commands").description("machine-readable manifest of every registered command, its options, and its arguments").option("--json", "emit the manifest as JSON instead of text").action(guard(cmdCommands));
72367
72770
  program2.command("mcp-serve").description("run token-goat as an MCP stdio server exposing read/symbol/section/outline/skeleton/semantic tools").action(guard(cmdMcpServe));
72368
72771
  program2.command("hook <event>").description("hook relay entrypoint (reads JSON on stdin)").option("--harness <name>", "override harness detection for this invocation (sets TOKEN_GOAT_HARNESS_OVERRIDE)").action(guard(cmdHook));
72369
- program2.command("install").description("install hooks into Claude Code settings").option("-p, --project", "install into project scope instead of user scope").option("--codex", "also patch Codex CLI (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "also patch Gemini CLI (~/.gemini/settings.json)").option("--qwen", "also patch Qwen Code (~/.qwen/settings.json)").option("--pi", "also drop a pi (pi-coding-agent) extension (~/.pi/agent/extensions/token-goat.ts)").option("--opencode", "also drop an opencode plugin (~/.config/opencode/plugins/token-goat.ts, %APPDATA%\\opencode\\plugins\\token-goat.ts on Windows)").option("--hermes", "verify token-goat hooks are present for Hermes Agent (writes nothing new)").option("--openclaw", "also register an OpenClaw plugin (~/.openclaw/openclaw.json, ~/.openclaw/plugins/token-goat.ts)").option("--copilot", "also register a Copilot CLI hook config (~/.copilot/hooks/token-goat.json, ~/.copilot/hooks/token-goat-shim.js)").option("--grok", "also register a Grok CLI (xAI Grok Build) hook config (~/.grok/hooks/token-goat.json, ~/.grok/hooks/token-goat-shim.js)").option("--local", "with --pi, install the project-local extension (<project>/.pi/extensions/token-goat.ts) instead of the global one").action(guard(cmdInstall));
72370
- program2.command("uninstall").description("remove token-goat hooks from Claude Code settings").option("-p, --project", "uninstall from project scope instead of user scope").option("--codex", "also strip the Codex CLI integration (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "also strip the Gemini CLI integration (~/.gemini/settings.json)").option("--qwen", "also strip the Qwen Code integration (~/.qwen/settings.json)").option("--pi", "also remove the pi (pi-coding-agent) extension").option("--opencode", "also remove the opencode plugin").option("--hermes", "no-op verification flag for symmetry with install (removes no files)").option("--openclaw", "also remove the OpenClaw plugin and config entry").option("--copilot", "also remove the Copilot CLI hook config and shim script").option("--grok", "also remove the Grok CLI hook config and shim script").option("--local", "with --pi, remove the project-local extension instead of the global one").action(guard(cmdUninstall));
72772
+ program2.command("install").description("install hooks into Claude Code settings").option("-p, --project", "install into project scope instead of user scope").option("--codex", "also patch Codex CLI (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "also patch Gemini CLI (~/.gemini/settings.json)").option("--qwen", "also patch Qwen Code (~/.qwen/settings.json)").option("--pi", "also drop a pi (pi-coding-agent) extension (~/.pi/agent/extensions/token-goat.ts)").option("--opencode", "also drop an opencode plugin (~/.config/opencode/plugins/token-goat.ts, %APPDATA%\\opencode\\plugins\\token-goat.ts on Windows)").option("--hermes", "verify token-goat hooks are present for Hermes Agent (writes nothing new)").option("--openclaw", "also register an OpenClaw plugin (~/.openclaw/openclaw.json, ~/.openclaw/plugins/token-goat.ts)").option("--copilot", "also register a Copilot CLI hook config and routing block (~/.copilot/hooks/token-goat.json, ~/.copilot/hooks/token-goat-shim.js, ~/.copilot/copilot-instructions.md; with --local, <project>/.github/hooks/token-goat.json, <project>/.github/hooks/token-goat-shim.js, <project>/.github/copilot-instructions.md)").option("--grok", "also register a Grok CLI (xAI Grok Build) hook config (~/.grok/hooks/token-goat.json, ~/.grok/hooks/token-goat-shim.js)").option("--local", "with --pi, install the project-local extension (<project>/.pi/extensions/token-goat.ts) instead of the global one").action(guard(cmdInstall));
72773
+ program2.command("uninstall").description("remove token-goat hooks from Claude Code settings").option("-p, --project", "uninstall from project scope instead of user scope").option("--codex", "also strip the Codex CLI integration (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "also strip the Gemini CLI integration (~/.gemini/settings.json)").option("--qwen", "also strip the Qwen Code integration (~/.qwen/settings.json)").option("--pi", "also remove the pi (pi-coding-agent) extension").option("--opencode", "also remove the opencode plugin").option("--hermes", "no-op verification flag for symmetry with install (removes no files)").option("--openclaw", "also remove the OpenClaw plugin and config entry").option("--copilot", "also remove the Copilot CLI hook config and shim script, and strip the token-goat block from ~/.copilot/copilot-instructions.md (or <project>/.github/copilot-instructions.md with --local)").option("--grok", "also remove the Grok CLI hook config and shim script").option("--local", "with --pi, remove the project-local extension instead of the global one").action(guard(cmdUninstall));
72371
72774
  const worker = program2.command("worker").description("background indexer lifecycle");
72372
72775
  worker.command("start").description("start the background indexer").action(guard(cmdWorkerStart));
72373
72776
  worker.command("stop").description("stop the background indexer").action(guard(cmdWorkerStop));
@@ -72565,6 +72968,9 @@ function buildProgram() {
72565
72968
  program2.command("bash-history").description("list cached bash output entries, newest first").option("-l, --limit <n>", "max results (default: 30)").option("-j, --json", "output as JSON").action((opts) => guard(() => cmdBashHistory(opts))());
72566
72969
  program2.command("web-history").description("list cached web-fetch output entries, newest first").option("-l, --limit <n>", "max results (default: 30)").option("-j, --json", "output as JSON").action((opts) => guard(() => cmdWebHistory(opts))());
72567
72970
  program2.command("mcp-history").description("list cached MCP tool result entries, newest first").option("-l, --limit <n>", "max results (default: 30)").option("-j, --json", "output as JSON").action((opts) => guard(() => cmdMcpHistory(opts))());
72971
+ program2.command("reclaim-index").description("shrink an oversized symbol index: VACUUM, or --rebuild to drop derived rows so the next index run re-derives them").option("--rebuild", "also drop all derived rows (files/symbols/refs/chunks) so the next `token-goat index` reparses from scratch under current parser rules").option("--db-path <path>", "index database to reclaim (default: the global index)").option("--force", "proceed even if the worker daemon appears to be running").option("-j, --json", "output as JSON").action(
72972
+ (opts) => guard(() => cmdReclaimIndex(opts))()
72973
+ );
72568
72974
  program2.command("clean-cache").description("prune all cache subdirs to default retention limits (200 entries, 24 h)").option("-j, --json", "output as JSON").action((opts) => guard(() => cmdCleanCache(opts))());
72569
72975
  program2.command("prune-cache").description("evict cache entries older than --max-age-hours or beyond --max-count (caller-specified bounds)").option("--max-count <n>", "max entries to keep per subdir (default: 200)").option("--max-age-hours <h>", "max age in hours to keep (default: 24)").option("-j, --json", "output as JSON").action((opts) => guard(() => cmdPruneCache(opts))());
72570
72976
  program2.command("cache-audit").description("check settings.json hook installation and env-var gates that defeat token-goat caching").option("-j, --json", "output as JSON").action((opts) => guard(() => cmdCacheAudit(opts))());
@@ -72849,6 +73255,43 @@ function subagentStopHandler(event) {
72849
73255
  registerHook("user_prompt_submit", userPromptSubmitHandler);
72850
73256
  registerHook("subagent_stop", subagentStopHandler);
72851
73257
 
73258
+ // src/hooks_session_start.ts
73259
+ init_define_import_meta_env();
73260
+ init_hook_registry();
73261
+ init_hooks_common();
73262
+ init_config();
73263
+ init_index_reader();
73264
+ init_constants();
73265
+ var GENERIC_REMINDER = 'token-goat: prefer surgical reads over Read/Grep on this codebase -- `token-goat symbol <name>`, `token-goat read "file::symbol"`, `token-goat section "file::Heading"`, `token-goat semantic "description"`, `token-goat outline <file>`. Run `token-goat index .` if this project is not indexed yet.';
73266
+ function buildReminder(cwd) {
73267
+ if (cwd === void 0) return GENERIC_REMINDER;
73268
+ let symbolCount;
73269
+ try {
73270
+ symbolCount = countSymbols({ rootDir: cwd }, globalDbPath());
73271
+ } catch {
73272
+ return GENERIC_REMINDER;
73273
+ }
73274
+ if (symbolCount <= 0) return GENERIC_REMINDER;
73275
+ return `token-goat: this project is indexed (${symbolCount} symbols). Prefer \`symbol <name>\`, \`read "file::symbol"\`, \`section "file::Heading"\`, \`semantic "description"\`, or \`outline <file>\` over a full Read/Grep.`;
73276
+ }
73277
+ function sessionStartHandler(event) {
73278
+ try {
73279
+ if (!loadConfig().hints.session_start_reminder) return passOutput();
73280
+ let context = buildReminder(getCwd(event));
73281
+ try {
73282
+ const dbHealth = checkSymbolBodySize(globalDbPath());
73283
+ if (dbHealth.status === "warn") {
73284
+ context += ` token-goat: ${dbHealth.message}`;
73285
+ }
73286
+ } catch {
73287
+ }
73288
+ return contextOutput(context);
73289
+ } catch {
73290
+ return passOutput();
73291
+ }
73292
+ }
73293
+ registerHook("session_start", sessionStartHandler);
73294
+
72852
73295
  // src/hooks_fetch.ts
72853
73296
  init_define_import_meta_env();
72854
73297
  init_hook_registry();
@@ -73436,7 +73879,7 @@ function ab(pa, pb, join46) {
73436
73879
  return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) => join46(ma.value, vb, data, i, j)));
73437
73880
  }
73438
73881
  function abc(pa, pb, pc, join46) {
73439
- return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) => join46(ma.value, mb.value, vc, data, i, j))));
73882
+ return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb2) => mapInner(pc(data, mb2.position), (vc, j) => join46(ma.value, mb2.value, vc, data, i, j))));
73440
73883
  }
73441
73884
  function ahead(p) {
73442
73885
  return (data, i) => mapOuter(p(data, i), (m1) => ({
@@ -77227,8 +77670,8 @@ function trimCharacterEnd(str, char) {
77227
77670
  function unicodeEscape(str) {
77228
77671
  return str.replace(/[\s\S]/g, (c) => "\\u" + c.charCodeAt().toString(16).padStart(4, "0"));
77229
77672
  }
77230
- function get(obj, path63) {
77231
- for (const key of path63) {
77673
+ function get(obj, path64) {
77674
+ for (const key of path64) {
77232
77675
  if (!obj) {
77233
77676
  return void 0;
77234
77677
  }
@@ -78429,8 +78872,8 @@ function withBrackets(str, brackets) {
78429
78872
  const rbr = typeof brackets[1] === "string" ? brackets[1] : "]";
78430
78873
  return lbr + str + rbr;
78431
78874
  }
78432
- function pathRewrite(path63, rewriter, baseUrl, metadata, elem) {
78433
- const modifiedPath = typeof rewriter === "function" ? rewriter(path63, metadata, elem) : path63;
78875
+ function pathRewrite(path64, rewriter, baseUrl, metadata, elem) {
78876
+ const modifiedPath = typeof rewriter === "function" ? rewriter(path64, metadata, elem) : path64;
78434
78877
  return modifiedPath[0] === "/" && baseUrl ? trimCharacterEnd(baseUrl, "/") + modifiedPath : modifiedPath;
78435
78878
  }
78436
78879
  function formatImage(elem, walk, builder, formatOptions) {
@@ -78754,9 +79197,9 @@ function handleDeprecatedOptions(options) {
78754
79197
  options.selectors.push(...tagDefinitions);
78755
79198
  options.selectors = mergeDuplicatesPreferLast(options.selectors, (s) => s.selector);
78756
79199
  }
78757
- function set2(obj, path63, value) {
78758
- const valueKey = path63.pop();
78759
- for (const key of path63) {
79200
+ function set2(obj, path64, value) {
79201
+ const valueKey = path64.pop();
79202
+ for (const key of path64) {
78760
79203
  let nested = obj[key];
78761
79204
  if (!nested) {
78762
79205
  nested = {};
@@ -79036,9 +79479,10 @@ init_lang_patterns();
79036
79479
  init_stats();
79037
79480
  init_config();
79038
79481
  init_parser_types();
79482
+ init_project();
79039
79483
  init_util2();
79040
79484
  init_hooks_index();
79041
- import { statSync as statSync30, existsSync as existsSync38 } from "node:fs";
79485
+ import { statSync as statSync32, existsSync as existsSync38 } from "node:fs";
79042
79486
  function stripCdPrefix(cmd) {
79043
79487
  const stripped = cmd.replace(/^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)\s*&&\s*)+/, "");
79044
79488
  return stripped.trim() || cmd;
@@ -79128,19 +79572,19 @@ var ORIG_HEAD_ELIGIBLE_GIT_RE = /^\s*git\s+(?:pull|merge|rebase)\b/i;
79128
79572
  var ORIG_HEAD_REFLOG_MSG_RE = /^(merge\s|rebase\s\(|pull\s)/i;
79129
79573
  function isTempPath(fp) {
79130
79574
  const norm = fp.replace(/\\/g, "/");
79131
- return /^\/tmp\//i.test(norm) || /\/var\/folders\//i.test(norm) || /AppData\/Local\/Temp\//i.test(norm) || norm.startsWith("/c/Users/") && norm.includes("/AppData/Local/Temp/");
79575
+ return /^\/tmp\//i.test(norm) || /\/var\/folders\//i.test(norm) || /AppData\/Local\/Temp\//i.test(norm) || norm.startsWith("/c/Users/") && norm.includes("/AppData/Local/Temp/") || isUnderSystemTemp(fp);
79132
79576
  }
79133
79577
  function isOrchestratorStateFile(filePath) {
79134
- const basename19 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
79135
- return /^\.improve-state-/.test(basename19);
79578
+ const basename20 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
79579
+ return /^\.improve-state-/.test(basename20);
79136
79580
  }
79137
79581
  function extractCatSourceFile(cmd) {
79138
79582
  const m = /^cat\s+(\S+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less))\s*$/.exec(cmd);
79139
79583
  return m?.[1] ?? null;
79140
79584
  }
79141
79585
  function classifyFileExtensions(filePath) {
79142
- const basename19 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
79143
- const isEnvFile = /^\.env(\.\w+)?$/i.test(basename19);
79586
+ const basename20 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
79587
+ const isEnvFile = /^\.env(\.\w+)?$/i.test(basename20);
79144
79588
  const hasKnownExt = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql|ps1|psm1|env)$/i.test(filePath);
79145
79589
  if (!hasKnownExt && !isEnvFile) return null;
79146
79590
  const isSql = /\.sql$/i.test(filePath);
@@ -79185,7 +79629,7 @@ var PS_GETCONTENT_INNER_RE = /^(?:Get-Content|gc|cat|type)(?:\s+(?:-[a-zA-Z]+|--
79185
79629
  var PS_TEMP_READ_FLOOD_BYTES = 16 * 1024;
79186
79630
  function isLargeFileOnDisk(filePath, floor) {
79187
79631
  try {
79188
- return statSync30(filePath).size >= floor;
79632
+ return statSync32(filePath).size >= floor;
79189
79633
  } catch {
79190
79634
  return false;
79191
79635
  }
@@ -80057,7 +80501,7 @@ function preBashHandlerInner(event) {
80057
80501
  const prevResolvedPath = prevPath !== null ? resolveIndexPath(prevPath, preHookCwd ?? process.cwd()) : null;
80058
80502
  if (prevPath !== null && prevResolvedPath !== null && !existsSync38(prevResolvedPath)) {
80059
80503
  clearCurlDownload(curlDl.url);
80060
- } else if (prevPath !== null && prevResolvedPath !== null && statSync30(prevResolvedPath).size >= loadConfig().hints.bash_dedup_min_bytes) {
80504
+ } else if (prevPath !== null && prevResolvedPath !== null && statSync32(prevResolvedPath).size >= loadConfig().hints.bash_dedup_min_bytes) {
80061
80505
  recordStat("session_hint", 0, 0);
80062
80506
  return denyOutput(
80063
80507
  "Already downloaded to " + prevPath + " earlier this session. Use `rg '<pattern>' " + prevPath + '` to search it, or `token-goat read "' + prevPath + '::SectionName"` to read a part of it.'
@@ -80994,7 +81438,7 @@ init_session();
80994
81438
  init_compact();
80995
81439
  init_util2();
80996
81440
  init_stats();
80997
- var BRIEFING_TARGET_TOKENS = 300;
81441
+ var BRIEFING_TARGET_TOKENS = 450;
80998
81442
  function buildSubagentBriefing() {
80999
81443
  try {
81000
81444
  const head = [];
@@ -81022,7 +81466,7 @@ function buildSubagentBriefing() {
81022
81466
  }
81023
81467
  const tail = [];
81024
81468
  tail.push("");
81025
- tail.push('Prefer surgical reads over full-file dumps: `token-goat symbol <name>` / `token-goat read "file::symbol"` / `token-goat section "file::<heading>"` are cheaper alternatives that are already cached by the hook system.');
81469
+ tail.push('Before your first read of any file, check for a token-goat command that returns just what you need and run it instead of a full-file read or wide grep: `token-goat symbol <name>`, `token-goat read "file::symbol"`, `token-goat section "file::<heading>"`. Skipping that check is a violation, not an oversight; the only exemptions are a file under ~200 lines you need whole, a never-indexed file, or an image.');
81026
81470
  const withCacheIds = head.join("\n") + cacheIdsBlock + "\n" + tail.join("\n");
81027
81471
  if (estimateTokens(withCacheIds) <= BRIEFING_TARGET_TOKENS) {
81028
81472
  return withCacheIds;
@@ -81114,7 +81558,7 @@ init_image_shrink();
81114
81558
  var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
81115
81559
  var MAX_STDIN_BYTES = 64 * 1024 * 1024;
81116
81560
  function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
81117
- return new Promise((resolve20, reject) => {
81561
+ return new Promise((resolve21, reject) => {
81118
81562
  const chunks = [];
81119
81563
  let totalBytes = 0;
81120
81564
  let settled = false;
@@ -81149,7 +81593,7 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
81149
81593
  return;
81150
81594
  }
81151
81595
  try {
81152
- resolve20(JSON.parse(text));
81596
+ resolve21(JSON.parse(text));
81153
81597
  } catch (err2) {
81154
81598
  reject(err2 instanceof Error ? err2 : new Error(String(err2)));
81155
81599
  }