token-goat 2.6.21 → 2.6.22

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.
@@ -3050,7 +3050,7 @@ var require_commander = __commonJS({
3050
3050
  import { createRequire } from "node:module";
3051
3051
  function resolveVersion() {
3052
3052
  if (true) {
3053
- return "2.6.21";
3053
+ return "2.6.22";
3054
3054
  }
3055
3055
  const require2 = createRequire(import.meta.url);
3056
3056
  const pkg = require2("../package.json");
@@ -5449,6 +5449,7 @@ function _buildConfig(raw, projectRaw = {}) {
5449
5449
  ix.large_file_skip_kb = validatedInt(ix_raw["large_file_skip_kb"], ix.large_file_skip_kb, ...boundsOf("indexing.large_file_skip_kb"));
5450
5450
  ix.large_file_symbol_only_kb = Math.min(ix.large_file_symbol_only_kb, ix.large_file_skip_kb);
5451
5451
  ix.skip_dirs = validatedStrList(ix_raw["skip_dirs"], ix.skip_dirs);
5452
+ ix.skip_files = validatedStrList(ix_raw["skip_files"], ix.skip_files);
5452
5453
  ix.embeddings_enabled = validatedBool(ix_raw["embeddings_enabled"], ix.embeddings_enabled);
5453
5454
  ix.embeddings_enabled = envBool("TOKEN_GOAT_EMBEDDINGS_ENABLED", ix.embeddings_enabled);
5454
5455
  const cpr_raw = section(raw, "compression");
@@ -5624,6 +5625,7 @@ function saveConfig(config2) {
5624
5625
  large_file_symbol_only_kb: config2.indexing.large_file_symbol_only_kb,
5625
5626
  large_file_skip_kb: config2.indexing.large_file_skip_kb,
5626
5627
  skip_dirs: config2.indexing.skip_dirs,
5628
+ skip_files: config2.indexing.skip_files,
5627
5629
  embeddings_enabled: config2.indexing.embeddings_enabled
5628
5630
  },
5629
5631
  compression: {
@@ -5803,6 +5805,7 @@ var init_config = __esm({
5803
5805
  large_file_symbol_only_kb: 500,
5804
5806
  large_file_skip_kb: 2048,
5805
5807
  skip_dirs: [],
5808
+ skip_files: ["coverage.json", "coverage-final.json"],
5806
5809
  embeddings_enabled: true
5807
5810
  },
5808
5811
  compression: {
@@ -6114,7 +6117,8 @@ CREATE TABLE IF NOT EXISTS symbols (
6114
6117
  line_start INTEGER,
6115
6118
  line_end INTEGER,
6116
6119
  body TEXT,
6117
- docstring TEXT
6120
+ docstring TEXT,
6121
+ parent TEXT NOT NULL DEFAULT ''
6118
6122
  );
6119
6123
  CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
6120
6124
  CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path);
@@ -6317,12 +6321,14 @@ CREATE TRIGGER IF NOT EXISTS cache_recall_au AFTER UPDATE ON cache_recall BEGIN
6317
6321
  VALUES (new.row_id, new.label, new.content);
6318
6322
  END;
6319
6323
  `;
6320
- SCHEMA_VERSION = 8;
6324
+ SCHEMA_VERSION = 9;
6321
6325
  MIGRATIONS = {
6322
6326
  // v1 -> v2: adds files.embed_sha, tracked separately from files.sha so embedding freshness can be gated independently of parse freshness (see makeIndexer in worker.ts). A pre-existing v1 database's `files` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has the column from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, so a genuine ALTER TABLE failure is never silently lost.
6323
6327
  1: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN embed_sha TEXT"),
6324
6328
  // v2 -> v3: adds files.retry_count, a durable per-path counter for consecutive transient-read-failure requeues (see MAX_TRANSIENT_RETRIES / requeueDirtyPath / resetTransientRetryCount in worker.ts). Previously this counter lived only in an in-memory Map inside worker.ts, which meant resetTransientRetryCount -- called from appendDirtyPath (hooks_index.ts) in the short-lived hook CLI process -- could never actually reach the long-lived detached daemon process's own copy of that Map: they are different Node processes with no shared memory, so the reset was a silent no-op in the real deployed topology. Persisting the counter in `files` makes it visible to both processes via the one thing they do share: the index DB. Same swallow-duplicate-column pattern as v1 -> v2 above.
6325
- 2: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0")
6329
+ 2: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0"),
6330
+ // v8 -> v9: adds symbols.parent (see SCHEMA_VERSION comment above for why). A pre-existing v8 database's `symbols` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has the column from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, same pattern as v1 -> v2 / v2 -> v3 above.
6331
+ 8: (conn) => alterTableIdempotent(conn, "ALTER TABLE symbols ADD COLUMN parent TEXT NOT NULL DEFAULT ''")
6326
6332
  };
6327
6333
  registerReset(closeAllDbs);
6328
6334
  }
@@ -10522,7 +10528,27 @@ function registerHook(eventName, handler, opts) {
10522
10528
  list = [];
10523
10529
  _handlers.set(eventName, list);
10524
10530
  }
10525
- list.push({ handler, toolName: opts?.toolName, advisory: opts?.advisory === true });
10531
+ list.push({
10532
+ handler,
10533
+ toolName: opts?.toolName,
10534
+ toolPattern: opts?.toolPattern,
10535
+ advisory: opts?.advisory === true,
10536
+ followsMatcher: opts?.followsMatcher === true
10537
+ });
10538
+ }
10539
+ function toolMatcherFor(eventName) {
10540
+ const list = _handlers.get(eventName);
10541
+ if (list === void 0 || list.length === 0) return null;
10542
+ const parts = [];
10543
+ for (const { toolName, toolPattern, followsMatcher } of list) {
10544
+ if (followsMatcher) continue;
10545
+ if (toolName === void 0 && toolPattern === void 0) return null;
10546
+ for (const part of [toolName === void 0 ? void 0 : `^${toolName}$`, toolPattern]) {
10547
+ if (part !== void 0 && part !== "" && !parts.includes(part)) parts.push(part);
10548
+ }
10549
+ }
10550
+ if (parts.length === 0) return null;
10551
+ return parts.join("|");
10526
10552
  }
10527
10553
  async function runHook(event) {
10528
10554
  const list = _handlers.get(event.eventName);
@@ -10844,7 +10870,7 @@ var init_hint_stats = __esm({
10844
10870
  resolvePendingHintsForEvent(event);
10845
10871
  return passOutput();
10846
10872
  },
10847
- { advisory: true }
10873
+ { advisory: true, followsMatcher: true }
10848
10874
  );
10849
10875
  }
10850
10876
  });
@@ -11358,10 +11384,12 @@ function buildGuidanceBody(fallbackToolClause) {
11358
11384
  "",
11359
11385
  `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.`,
11360
11386
  "",
11387
+ "Fallback clauses may name native tools (`Read`, `Grep`, `Glob`, `shell`, `apply_patch`, `view_image`, `edit`, `create`) or PowerShell helpers (`Get-Content`, `Select-String`); shell/editor commands like `rg`, `grep`, `fd`, `sed`, `cat`, `find`, and `ls` are commands, not tool identifiers.",
11388
+ "",
11361
11389
  "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).",
11362
11390
  "",
11363
11391
  "Failure shapes to catch yourself in, and the command that replaces each:",
11364
- '- grep/search with context flags to find a function body \u2192 `read "file::symbol"`',
11392
+ '- shell `rg`/`grep` search with context flags to find a function body \u2192 `read "file::symbol"`',
11365
11393
  '- paging one function with view/view_range \u2192 `read "file::symbol"`',
11366
11394
  '- reading one heading of a large doc \u2192 `section "file::Heading"`',
11367
11395
  "- searching for a symbol's callers \u2192 `refs file::symbol --callers`",
@@ -11467,13 +11495,28 @@ function installHooks(scope = "user") {
11467
11495
  }
11468
11496
  }
11469
11497
  if (groupHasTokenGoat(groups, isCurrentTokenGoatHookCommand)) {
11470
- if (strippedLegacy) {
11498
+ const narrowed = toolMatcherFor(eventArg);
11499
+ let renarrowed = false;
11500
+ if (narrowed !== null) {
11501
+ for (let i = 0; i < groups.length; i++) {
11502
+ const group = groups[i];
11503
+ if (group === void 0) continue;
11504
+ const ownHooks = group.hooks ?? [];
11505
+ const isOwnGroup = ownHooks.length > 0 && ownHooks.every((h) => isCurrentTokenGoatHookCommand(h.command));
11506
+ if (isOwnGroup && group.matcher !== narrowed) {
11507
+ groups[i] = { ...group, matcher: narrowed };
11508
+ renarrowed = true;
11509
+ }
11510
+ }
11511
+ }
11512
+ if (strippedLegacy || renarrowed) {
11471
11513
  hooks[eventKey] = groups;
11472
11514
  changed = true;
11473
11515
  }
11474
11516
  continue;
11475
11517
  }
11476
- groups.push({ matcher: "", hooks: [{ type: "command", command: hookCommand(eventArg) }] });
11518
+ const matcher = toolMatcherFor(eventArg) ?? "";
11519
+ groups.push({ matcher, hooks: [{ type: "command", command: hookCommand(eventArg) }] });
11477
11520
  hooks[eventKey] = groups;
11478
11521
  changed = true;
11479
11522
  }
@@ -11606,6 +11649,7 @@ var init_install = __esm({
11606
11649
  "use strict";
11607
11650
  init_define_import_meta_env();
11608
11651
  init_guidance_block();
11652
+ init_hook_registry();
11609
11653
  init_paths();
11610
11654
  init_util2();
11611
11655
  HOOK_EVENT_MAP = [
@@ -11627,6 +11671,20 @@ var init_install = __esm({
11627
11671
  SKILL_MD_FRONTMATTER = `---
11628
11672
  name: token-goat
11629
11673
  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.
11674
+ allowed-tools:
11675
+ - symbol
11676
+ - read
11677
+ - section
11678
+ - semantic
11679
+ - outline
11680
+ - skeleton
11681
+ - map
11682
+ - refs
11683
+ - changed
11684
+ - config-get
11685
+ - bash-output
11686
+ - web-output
11687
+ - gdrive-sections
11630
11688
  ---`;
11631
11689
  SKILL_MD_CONTENT = `${SKILL_MD_FRONTMATTER}
11632
11690
 
@@ -11781,7 +11839,7 @@ function buildAgentsBlock() {
11781
11839
  return buildGuidanceBlock({
11782
11840
  beginMarker: AGENTS_BEGIN,
11783
11841
  endMarker: AGENTS_END,
11784
- fallbackToolClause: "Codex's own `shell` (cat/type), `apply_patch` preview, and `view_image` preferences"
11842
+ fallbackToolClause: "Codex's native `shell`, `apply_patch`, and `view_image` tools (shell commands like `cat`/`type` run inside `shell`)"
11785
11843
  });
11786
11844
  }
11787
11845
  function writeAgentsBlock(p) {
@@ -12171,11 +12229,16 @@ function copilotCliInstructionsPath(opts = {}) {
12171
12229
  return path11.join(path11.dirname(copilotCliHooksDir(opts)), "copilot-instructions.md");
12172
12230
  }
12173
12231
  function buildCopilotInstructionsBlock() {
12174
- return buildGuidanceBlock({
12175
- beginMarker: COPILOT_INSTRUCTIONS_BEGIN,
12176
- endMarker: COPILOT_INSTRUCTIONS_END,
12177
- fallbackToolClause: "Copilot CLI's own `view`, `grep`, and `glob` tool-preference rules (and `Get-Content`/`Select-String`)"
12178
- });
12232
+ return stripInlineCodeSpans(
12233
+ buildGuidanceBlock({
12234
+ beginMarker: COPILOT_INSTRUCTIONS_BEGIN,
12235
+ endMarker: COPILOT_INSTRUCTIONS_END,
12236
+ fallbackToolClause: "Copilot CLI's native `view`, `grep`, and `glob` tools (with PowerShell commands `Get-Content`/`Select-String` as search fallbacks)"
12237
+ })
12238
+ );
12239
+ }
12240
+ function stripInlineCodeSpans(text) {
12241
+ return text.replace(/`([^`]+)`/g, "$1");
12179
12242
  }
12180
12243
  function writeCopilotInstructionsBlock(p) {
12181
12244
  return upsertDelimitedBlock(p, COPILOT_INSTRUCTIONS_BEGIN, COPILOT_INSTRUCTIONS_END, buildCopilotInstructionsBlock());
@@ -13286,23 +13349,23 @@ function isNoisePath(inputPath) {
13286
13349
  }
13287
13350
  }
13288
13351
  const slashIdx = p.lastIndexOf("/");
13289
- const basename20 = slashIdx >= 0 ? p.slice(slashIdx + 1) : p;
13290
- if (NOISE_BASENAMES.has(basename20)) {
13352
+ const basename21 = slashIdx >= 0 ? p.slice(slashIdx + 1) : p;
13353
+ if (NOISE_BASENAMES.has(basename21)) {
13291
13354
  return true;
13292
13355
  }
13293
- if (basename20.startsWith(".improve-state-") || basename20.startsWith("improve_commit_msg_")) {
13356
+ if (basename21.startsWith(".improve-state-") || basename21.startsWith("improve_commit_msg_")) {
13294
13357
  return true;
13295
13358
  }
13296
- const dotIdx = basename20.lastIndexOf(".");
13359
+ const dotIdx = basename21.lastIndexOf(".");
13297
13360
  if (dotIdx >= 0) {
13298
- const ext2 = basename20.slice(dotIdx);
13361
+ const ext2 = basename21.slice(dotIdx);
13299
13362
  if (NOISE_EXTS.has(ext2)) {
13300
13363
  return true;
13301
13364
  }
13302
13365
  }
13303
13366
  for (const ext2 of NOISE_EXTS) {
13304
13367
  if (ext2.includes(".") && ext2.split(".").length > 2) {
13305
- if (basename20.endsWith(ext2)) {
13368
+ if (basename21.endsWith(ext2)) {
13306
13369
  return true;
13307
13370
  }
13308
13371
  }
@@ -13935,15 +13998,15 @@ var init_hints = __esm({
13935
13998
  });
13936
13999
 
13937
14000
  // src/hints/lang_patterns.ts
13938
- function isLockFile(basename20) {
13939
- return LOCK_FILE_NAMES.has(basename20.toLowerCase());
14001
+ function isLockFile(basename21) {
14002
+ return LOCK_FILE_NAMES.has(basename21.toLowerCase());
13940
14003
  }
13941
- function isManifestFile(basename20) {
13942
- const lower = basename20.toLowerCase();
14004
+ function isManifestFile(basename21) {
14005
+ const lower = basename21.toLowerCase();
13943
14006
  if (MANIFEST_FILE_NAMES.has(lower)) return true;
13944
14007
  const dot = lower.lastIndexOf(".");
13945
14008
  if (dot !== -1 && MANIFEST_EXTENSIONS.has(lower.slice(dot))) return true;
13946
- if (MANIFEST_BASENAME_PATTERNS.some((re) => re.test(basename20))) return true;
14009
+ if (MANIFEST_BASENAME_PATTERNS.some((re) => re.test(basename21))) return true;
13947
14010
  return false;
13948
14011
  }
13949
14012
  function pathSegments(filePath) {
@@ -14282,8 +14345,8 @@ function formatHeadingTree(headings, filePath) {
14282
14345
  }
14283
14346
  return lines2.join("\n");
14284
14347
  }
14285
- function getWellKnownSections(basename20) {
14286
- return WELL_KNOWN_SECTIONS[basename20] ?? [];
14348
+ function getWellKnownSections(basename21) {
14349
+ return WELL_KNOWN_SECTIONS[basename21] ?? [];
14287
14350
  }
14288
14351
  function extractChangelogVersionHint(content, filePath) {
14289
14352
  const lines2 = content.split("\n");
@@ -14326,6 +14389,64 @@ var init_markdown_hints = __esm({
14326
14389
  }
14327
14390
  });
14328
14391
 
14392
+ // src/doc_comment.ts
14393
+ function precedingDocComment(lines2, lineStart, style) {
14394
+ const aboveIdx = lineStart - 2;
14395
+ if (aboveIdx < 0 || aboveIdx >= lines2.length) return "";
14396
+ const aboveLine = lines2[aboveIdx];
14397
+ if (aboveLine === void 0) return "";
14398
+ const aboveTrimmed = aboveLine.trim();
14399
+ if (style === "hash") {
14400
+ if (!aboveTrimmed.startsWith("#")) return "";
14401
+ const collected = [];
14402
+ let i = aboveIdx;
14403
+ while (i >= 0) {
14404
+ const line = lines2[i];
14405
+ if (line === void 0) break;
14406
+ const trimmed = line.trim();
14407
+ if (!trimmed.startsWith("#")) break;
14408
+ collected.unshift(trimmed.replace(/^#+\s?/, ""));
14409
+ i--;
14410
+ }
14411
+ return collected.join("\n").trim();
14412
+ }
14413
+ if (aboveTrimmed.endsWith("*/")) {
14414
+ let blockStart = aboveIdx;
14415
+ while (blockStart >= 0) {
14416
+ const l = lines2[blockStart];
14417
+ if (l === void 0) break;
14418
+ if (l.trim().startsWith("/*")) break;
14419
+ blockStart--;
14420
+ }
14421
+ if (blockStart < 0) return "";
14422
+ const opener = lines2[blockStart];
14423
+ if (opener === void 0 || !opener.trim().startsWith("/*")) return "";
14424
+ return lines2.slice(blockStart, aboveIdx + 1).map(
14425
+ (l) => l.trim().replace(/^\/\*+/, "").replace(/\*+\/$/, "").replace(/^\*\s?/, "").trim()
14426
+ ).filter((l) => l !== "").join("\n");
14427
+ }
14428
+ if (aboveTrimmed.startsWith("//")) {
14429
+ const collected = [];
14430
+ let i = aboveIdx;
14431
+ while (i >= 0) {
14432
+ const line = lines2[i];
14433
+ if (line === void 0) break;
14434
+ const trimmed = line.trim();
14435
+ if (!trimmed.startsWith("//")) break;
14436
+ collected.unshift(trimmed.replace(/^\/\/[/!]?\s?/, ""));
14437
+ i--;
14438
+ }
14439
+ return collected.join("\n").trim();
14440
+ }
14441
+ return "";
14442
+ }
14443
+ var init_doc_comment = __esm({
14444
+ "src/doc_comment.ts"() {
14445
+ "use strict";
14446
+ init_define_import_meta_env();
14447
+ }
14448
+ });
14449
+
14329
14450
  // src/languages/common.ts
14330
14451
  function buildLineIndex(text) {
14331
14452
  const idx = [0];
@@ -14865,7 +14986,7 @@ function stripMultilineStringSpan(line, state, lang) {
14865
14986
  }
14866
14987
  return { code, state: cur };
14867
14988
  }
14868
- function makeSpanSymbol(filePath, name2, kind, span, docstring = "") {
14989
+ function makeSpanSymbol(filePath, name2, kind, span, parent = "", lines2, style) {
14869
14990
  return {
14870
14991
  filePath,
14871
14992
  name: name2,
@@ -14873,10 +14994,11 @@ function makeSpanSymbol(filePath, name2, kind, span, docstring = "") {
14873
14994
  lineStart: span.startLine,
14874
14995
  lineEnd: span.endLine,
14875
14996
  body: span.body,
14876
- docstring
14997
+ docstring: lines2 !== void 0 && style !== void 0 ? precedingDocComment(lines2, span.startLine, style) : "",
14998
+ parent
14877
14999
  };
14878
15000
  }
14879
- function makeLineSymbol(filePath, name2, kind, line, sig, parent) {
15001
+ function makeLineSymbol(filePath, name2, kind, line, sig, parent, lines2, style) {
14880
15002
  return {
14881
15003
  filePath,
14882
15004
  name: name2,
@@ -14884,7 +15006,8 @@ function makeLineSymbol(filePath, name2, kind, line, sig, parent) {
14884
15006
  lineStart: line,
14885
15007
  lineEnd: line,
14886
15008
  body: sig ?? "",
14887
- docstring: parent ?? ""
15009
+ docstring: lines2 !== void 0 && style !== void 0 ? precedingDocComment(lines2, line, style) : "",
15010
+ parent: parent ?? ""
14888
15011
  };
14889
15012
  }
14890
15013
  function makeSymbolEmitter(symbols, sections, seen, filePath, maxSymbols = 500, maxHeadingLen = 120) {
@@ -14901,7 +15024,8 @@ function makeSymbolEmitter(symbols, sections, seen, filePath, maxSymbols = 500,
14901
15024
  lineStart: line,
14902
15025
  lineEnd: line,
14903
15026
  body: "",
14904
- docstring: ""
15027
+ docstring: "",
15028
+ parent: ""
14905
15029
  });
14906
15030
  sections.push({ heading: name2, level: 1, line, endLine: line });
14907
15031
  };
@@ -14965,6 +15089,7 @@ var init_common = __esm({
14965
15089
  "use strict";
14966
15090
  init_define_import_meta_env();
14967
15091
  init_util2();
15092
+ init_doc_comment();
14968
15093
  HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
14969
15094
  HTML_SCRIPT_BODY_RE = /(<script\b[^>]*>)([\s\S]*?)(<\/script\s*>)/gi;
14970
15095
  HTML_CDATA_RE = /<!\[CDATA\[[\s\S]*?\]\]>/g;
@@ -16004,19 +16129,19 @@ async function preReadImageHandler(event) {
16004
16129
  }
16005
16130
  const result = await shrinkImage(input, { sizeThresholdBytes: 0 });
16006
16131
  if (result === null) return passOutput();
16007
- const basename20 = path18.basename(filePath);
16132
+ const basename21 = path18.basename(filePath);
16008
16133
  if (loadConfig().image_shrink.ocr_enabled) {
16009
16134
  const ocr = await ocrImage(result.data);
16010
16135
  if (ocr !== null && isTextHeavy(ocr, loadConfig().image_shrink.ocr_min_confidence)) {
16011
16136
  const textBytes = Buffer.byteLength(ocr.text, "utf8");
16012
16137
  const saved2 = Math.max(0, result.shrunkBytes - textBytes);
16013
- recordStat("image_ocr", saved2, Math.round(saved2 / 4), void 0, basename20);
16014
- return contextOutput(formatOcrSummary(ocr, basename20, result.originalBytes));
16138
+ recordStat("image_ocr", saved2, Math.round(saved2 / 4), void 0, basename21);
16139
+ return contextOutput(formatOcrSummary(ocr, basename21, result.originalBytes));
16015
16140
  }
16016
16141
  }
16017
16142
  const saved = result.originalBytes - result.shrunkBytes;
16018
- const { summary, dataUrl } = formatShrinkSummary(result, basename20);
16019
- recordStat("image_shrink", saved, Math.round(saved / 4), void 0, basename20);
16143
+ const { summary, dataUrl } = formatShrinkSummary(result, basename21);
16144
+ recordStat("image_shrink", saved, Math.round(saved / 4), void 0, basename21);
16020
16145
  return contextOutput(`${summary}
16021
16146
  ${dataUrl}`);
16022
16147
  }
@@ -16381,8 +16506,8 @@ var init_notebook_compact = __esm({
16381
16506
  // src/hooks_read.ts
16382
16507
  import * as fs21 from "node:fs";
16383
16508
  import * as path21 from "node:path";
16384
- function isTsConfigFile(basename20) {
16385
- const lower = basename20.toLowerCase();
16509
+ function isTsConfigFile(basename21) {
16510
+ const lower = basename21.toLowerCase();
16386
16511
  return /^tsconfig(\..+)?\.json$/i.test(lower) || lower === "jsconfig.json";
16387
16512
  }
16388
16513
  function largeFileDenyBytes() {
@@ -16482,18 +16607,18 @@ function describeSliceAdvice(slice, absPath) {
16482
16607
  }
16483
16608
  return "Use Read with offset/limit to sample specific sections.";
16484
16609
  }
16485
- function isSourceExtension(basename20) {
16486
- if (SOURCE_EXT_RE.test(basename20)) return true;
16487
- const language = detectLanguage(basename20);
16610
+ function isSourceExtension(basename21) {
16611
+ if (SOURCE_EXT_RE.test(basename21)) return true;
16612
+ const language = detectLanguage(basename21);
16488
16613
  return language === "apex" || language === "salesforce_metadata" || language === "salesforce_markup";
16489
16614
  }
16490
- function isDispatchedFileType(basename20) {
16491
- return DISPATCHED_FILE_TYPE_EXTS.has(path21.extname(basename20).slice(1).toLowerCase());
16615
+ function isDispatchedFileType(basename21) {
16616
+ return DISPATCHED_FILE_TYPE_EXTS.has(path21.extname(basename21).slice(1).toLowerCase());
16492
16617
  }
16493
- function surgicalHint(filePath, basename20, lineCount) {
16618
+ function surgicalHint(filePath, basename21, lineCount) {
16494
16619
  if (lineCount < loadConfig().hints.min_file_lines_for_hint) return "";
16495
- const isDocFile = /\.(md|mdx|rst|txt)$/i.test(basename20);
16496
- const isSectionFile = /\.(json|jsonc|css|scss|sass|less|yaml|yml|toml)$/i.test(basename20);
16620
+ const isDocFile = /\.(md|mdx|rst|txt)$/i.test(basename21);
16621
+ const isSectionFile = /\.(json|jsonc|css|scss|sass|less|yaml|yml|toml)$/i.test(basename21);
16497
16622
  if (isDocFile) {
16498
16623
  return 'Use `token-goat section "' + filePath + '::HeadingName"` to extract a part.';
16499
16624
  } else if (isSectionFile) {
@@ -16541,7 +16666,7 @@ function buildLineDiff(oldContent, newContent, label) {
16541
16666
  }
16542
16667
  return out2.join("\n");
16543
16668
  }
16544
- function loadSnapshotDiff(sessionId, normalized, basename20) {
16669
+ function loadSnapshotDiff(sessionId, normalized, basename21) {
16545
16670
  const oldSnap = load(sessionId, normalized);
16546
16671
  if (oldSnap === null) return { kind: "none" };
16547
16672
  try {
@@ -16553,7 +16678,7 @@ function loadSnapshotDiff(sessionId, normalized, basename20) {
16553
16678
  const truncIdx = oldRaw.indexOf(TRUNC_MARKER);
16554
16679
  const oldContent = truncIdx >= 0 ? oldRaw.slice(0, truncIdx) : oldRaw;
16555
16680
  if (oldContent === currentContent) return { kind: "unchanged", currentContent };
16556
- const diff = buildLineDiff(oldContent, currentContent, basename20);
16681
+ const diff = buildLineDiff(oldContent, currentContent, basename21);
16557
16682
  if (diff === "") return { kind: "none" };
16558
16683
  const savedBytes = Math.max(0, currentContent.length - diff.length);
16559
16684
  return { kind: "diff", diff, savedBytes, currentContent };
@@ -16626,8 +16751,8 @@ function preReadHandlerInner(event) {
16626
16751
  "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"
16627
16752
  );
16628
16753
  }
16629
- const basename20 = path21.basename(normalized);
16630
- if (isLockFile(basename20)) {
16754
+ const basename21 = path21.basename(normalized);
16755
+ if (isLockFile(basename21)) {
16631
16756
  return denyOutput(
16632
16757
  '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.'
16633
16758
  );
@@ -16650,20 +16775,20 @@ function preReadHandlerInner(event) {
16650
16775
  return quietContextOutput(manifestHint.text);
16651
16776
  }
16652
16777
  }
16653
- if (isTsConfigFile(basename20) && wasFileReadThisSession(normalized)) {
16778
+ if (isTsConfigFile(basename21) && wasFileReadThisSession(normalized)) {
16654
16779
  recordActualRead(event, normalized);
16655
16780
  return quietContextOutput(
16656
- "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."
16781
+ "Already read " + basename21 + '. Use `token-goat section "' + normalized + '::compilerOptions"` to extract compiler options, or `token-goat config-get ' + normalized + " compilerOptions.target` for a single value."
16657
16782
  );
16658
16783
  }
16659
- if (isManifestFile(basename20) && wasFileReadThisSession(normalized)) {
16784
+ if (isManifestFile(basename21) && wasFileReadThisSession(normalized)) {
16660
16785
  recordActualRead(event, normalized);
16661
16786
  return quietContextOutput(
16662
- "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."
16787
+ "You've already read " + basename21 + '. Use `token-goat section "' + normalized + '::<field>"` or `token-goat config-get ' + normalized + " <key>` to extract just the value you need."
16663
16788
  );
16664
16789
  }
16665
16790
  const skillName = detectSkillFile(normalized);
16666
- if (skillName && basename20 === "SKILL.md") {
16791
+ if (skillName && basename21 === "SKILL.md") {
16667
16792
  try {
16668
16793
  const body = fs21.readFileSync(normalized, "utf-8");
16669
16794
  const bodySha = contentHash(body);
@@ -16693,7 +16818,7 @@ function preReadHandlerInner(event) {
16693
16818
  }
16694
16819
  }
16695
16820
  }
16696
- const isNotebook = /\.ipynb$/i.test(basename20);
16821
+ const isNotebook = /\.ipynb$/i.test(basename21);
16697
16822
  if (event.toolName !== "Grep" && isNotebook) {
16698
16823
  try {
16699
16824
  const rawBytes = fs21.readFileSync(normalized);
@@ -16710,7 +16835,7 @@ function preReadHandlerInner(event) {
16710
16835
  } catch {
16711
16836
  }
16712
16837
  }
16713
- const isMarkdown = /\.(md|mdx|markdown|rst)$/i.test(basename20);
16838
+ const isMarkdown = /\.(md|mdx|markdown|rst)$/i.test(basename21);
16714
16839
  if (event.toolName !== "Grep" && isMarkdown) {
16715
16840
  let fileContent = null;
16716
16841
  let markdownSize = null;
@@ -16728,9 +16853,9 @@ function preReadHandlerInner(event) {
16728
16853
  const alreadyRead = wasFileReadThisSession(normalized);
16729
16854
  const hintText = formatHeadingTree(headings, normalized);
16730
16855
  const headingTextsLower = new Set(headings.map((h) => h.text.trim().toLowerCase()));
16731
- const wellKnown = getWellKnownSections(basename20).filter((s) => headingTextsLower.has(s.trim().toLowerCase()));
16856
+ const wellKnown = getWellKnownSections(basename21).filter((s) => headingTextsLower.has(s.trim().toLowerCase()));
16732
16857
  const wellKnownText = wellKnown.length > 0 ? "\nQuick access: " + wellKnown.map((s) => 'token-goat section "' + normalized + "::" + s + '"').join(" | ") : "";
16733
- const changelogExtra = basename20.toLowerCase() === "changelog.md" ? extractChangelogVersionHint(fileContent, normalized) : "";
16858
+ const changelogExtra = basename21.toLowerCase() === "changelog.md" ? extractChangelogVersionHint(fileContent, normalized) : "";
16734
16859
  let message = hintText + wellKnownText + changelogExtra;
16735
16860
  const slice = estimateRequestedSlice(event, normalized);
16736
16861
  const gateSize = slice.kind === "bytes" && markdownSize !== null ? Math.min(slice.bytes, markdownSize) : markdownSize;
@@ -16751,19 +16876,19 @@ function preReadHandlerInner(event) {
16751
16876
  if (isMemoryMd && wasFileReadThisSession(normalized)) {
16752
16877
  recordActualRead(event, normalized);
16753
16878
  recordStat("session_hint", 0, 0);
16754
- const isMainMemory = basename20.toLowerCase() === "memory.md";
16879
+ const isMainMemory = basename21.toLowerCase() === "memory.md";
16755
16880
  return denyOutput(
16756
16881
  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.'
16757
16882
  );
16758
16883
  }
16759
- if (/^\.improve-state-.*\.json$/.test(basename20) && wasFileReadThisSession(normalized)) {
16884
+ if (/^\.improve-state-.*\.json$/.test(basename21) && wasFileReadThisSession(normalized)) {
16760
16885
  recordActualRead(event, normalized);
16761
16886
  recordStat("session_hint", 0, 0);
16762
16887
  return denyOutput(
16763
16888
  "Orchestrator state already read this session. " + sessionArtifactRecall(normalized)
16764
16889
  );
16765
16890
  }
16766
- if (/^\.env(\.\w+)?$/.test(basename20) && wasFileReadThisSession(normalized)) {
16891
+ if (/^\.env(\.\w+)?$/.test(basename21) && wasFileReadThisSession(normalized)) {
16767
16892
  recordActualRead(event, normalized);
16768
16893
  recordStat("session_hint", 0, 0);
16769
16894
  return denyOutput(
@@ -16780,19 +16905,19 @@ function preReadHandlerInner(event) {
16780
16905
  );
16781
16906
  }
16782
16907
  const artifactSessionId = getSessionId();
16783
- const snapDiff = loadSnapshotDiff(artifactSessionId, normalized, basename20);
16908
+ const snapDiff = loadSnapshotDiff(artifactSessionId, normalized, basename21);
16784
16909
  if (snapDiff.kind === "unchanged") {
16785
16910
  recordActualRead(event, normalized);
16786
16911
  recordStat("session_hint", 0, 0);
16787
16912
  return denyOutput(
16788
- basename20 + " is unchanged since last read. " + sessionArtifactRecall(normalized)
16913
+ basename21 + " is unchanged since last read. " + sessionArtifactRecall(normalized)
16789
16914
  );
16790
16915
  }
16791
16916
  if (snapDiff.kind === "diff") {
16792
16917
  recordActualRead(event, normalized);
16793
16918
  recordStat("session_hint", snapDiff.savedBytes, Math.round(snapDiff.savedBytes / 4));
16794
16919
  return denyOutput(
16795
- "Content changed since last read of " + basename20 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + sessionArtifactRecall(normalized)
16920
+ "Content changed since last read of " + basename21 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + sessionArtifactRecall(normalized)
16796
16921
  );
16797
16922
  }
16798
16923
  recordActualRead(event, normalized);
@@ -16815,8 +16940,8 @@ function preReadHandlerInner(event) {
16815
16940
  return quietContextOutput(label + ": " + sessionArtifactRecall(normalized));
16816
16941
  }
16817
16942
  }
16818
- const isDocDiffable = /\.(md|mdx|markdown|rst|txt)$/i.test(basename20);
16819
- const isSourceDiffable = loadConfig().hints.serve_diff_on_reread && DIFFABLE_SOURCE_RE.test(basename20);
16943
+ const isDocDiffable = /\.(md|mdx|markdown|rst|txt)$/i.test(basename21);
16944
+ const isSourceDiffable = loadConfig().hints.serve_diff_on_reread && DIFFABLE_SOURCE_RE.test(basename21);
16820
16945
  if ((isDocDiffable || isSourceDiffable) && wasFileReadThisSession(normalized) && !isProtectedRecentRead(normalized, loadConfig().hints.protect_recent_reads)) {
16821
16946
  if (wasFileTruncatedThisSession(normalized)) {
16822
16947
  if (estimateTruncatedLineCount(normalized) >= loadConfig().hints.truncated_read_min_lines) {
@@ -16826,12 +16951,12 @@ function preReadHandlerInner(event) {
16826
16951
  }
16827
16952
  }
16828
16953
  const sessionId = getSessionId();
16829
- const snapDiff = loadSnapshotDiff(sessionId, normalized, basename20);
16954
+ const snapDiff = loadSnapshotDiff(sessionId, normalized, basename21);
16830
16955
  if (snapDiff.kind === "unchanged") {
16831
16956
  recordActualRead(event, normalized);
16832
16957
  recordStat("session_hint", 0, 0);
16833
16958
  return denyOutput(
16834
- (basename20 + " is unchanged since last read. " + surgicalHint(normalized, basename20, countTextLines(snapDiff.currentContent))).trimEnd()
16959
+ (basename21 + " is unchanged since last read. " + surgicalHint(normalized, basename21, countTextLines(snapDiff.currentContent))).trimEnd()
16835
16960
  );
16836
16961
  }
16837
16962
  if (snapDiff.kind === "diff") {
@@ -16839,7 +16964,7 @@ function preReadHandlerInner(event) {
16839
16964
  recordActualRead(event, normalized);
16840
16965
  recordStat("diff_hint", snapDiff.savedBytes, Math.round(snapDiff.savedBytes / 4));
16841
16966
  return denyOutput(
16842
- ("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()
16967
+ ("Content changed since last read of " + basename21 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + surgicalHint(normalized, basename21, countTextLines(snapDiff.currentContent))).trimEnd()
16843
16968
  );
16844
16969
  }
16845
16970
  }
@@ -16885,13 +17010,13 @@ function preReadHandlerInner(event) {
16885
17010
  return denyOutput(truncatedReadDenyMessage(normalized));
16886
17011
  }
16887
17012
  }
16888
- if (/\.(md|mdx|markdown|rst)$/i.test(basename20)) {
17013
+ if (/\.(md|mdx|markdown|rst)$/i.test(basename21)) {
16889
17014
  recordStat("session_hint", rereadBytes, Math.round(rereadBytes / 4));
16890
17015
  return denyOutput(
16891
17016
  'Markdown file already read this session. Use `token-goat section "' + normalized + '::HeadingName"` to read one section. ' + editAnywayHint(normalized)
16892
17017
  );
16893
17018
  }
16894
- const isSourceExt = isSourceExtension(basename20);
17019
+ const isSourceExt = isSourceExtension(basename21);
16895
17020
  if (isSourceExt && reads >= 2) {
16896
17021
  recordStat("read_count_deny", rereadBytes, Math.round(rereadBytes / 4));
16897
17022
  recordStat("session_hint", rereadBytes, Math.round(rereadBytes / 4));
@@ -18226,7 +18351,7 @@ function fetchTopSymbols(limit, dbPath, rootDir) {
18226
18351
  const db = getDb(dbPath);
18227
18352
  const { clause, param } = projectScopeClause("file_path");
18228
18353
  const rows = db.prepare(
18229
- `SELECT file_path, name, kind, line_start, line_end, body, docstring
18354
+ `SELECT file_path, name, kind, line_start, line_end, body, docstring, parent
18230
18355
  FROM symbols
18231
18356
  WHERE kind IN ('class', 'function', 'interface') AND ${clause}
18232
18357
  ORDER BY CASE kind WHEN 'class' THEN 0 WHEN 'interface' THEN 1 ELSE 2 END,
@@ -18240,7 +18365,8 @@ function fetchTopSymbols(limit, dbPath, rootDir) {
18240
18365
  lineStart: r.line_start,
18241
18366
  lineEnd: r.line_end,
18242
18367
  body: r.body ?? "",
18243
- docstring: r.docstring ?? ""
18368
+ docstring: r.docstring ?? "",
18369
+ parent: r.parent ?? ""
18244
18370
  }));
18245
18371
  } catch {
18246
18372
  return [];
@@ -18355,9 +18481,9 @@ function formatMemSuggestions(projectRoot) {
18355
18481
  if (suggestions.length === 0) return "";
18356
18482
  const lines2 = ["", "## mem suggestions"];
18357
18483
  for (const s of suggestions) {
18358
- const basename20 = path24.basename(s.path);
18484
+ const basename21 = path24.basename(s.path);
18359
18485
  lines2.push(
18360
- "Consider: mem import --from-md " + s.path + " # migrates " + s.count + " preference-shaped lines from " + basename20 + " as pending facts for review"
18486
+ "Consider: mem import --from-md " + s.path + " # migrates " + s.count + " preference-shaped lines from " + basename21 + " as pending facts for review"
18361
18487
  );
18362
18488
  }
18363
18489
  return lines2.join(String.fromCharCode(10));
@@ -18436,7 +18562,8 @@ function toSymbolEntry(row) {
18436
18562
  lineStart: row.line_start,
18437
18563
  lineEnd: row.line_end,
18438
18564
  body: row.body ?? "",
18439
- docstring: row.docstring ?? ""
18565
+ docstring: row.docstring ?? "",
18566
+ parent: row.parent ?? ""
18440
18567
  };
18441
18568
  }
18442
18569
  function toRefEntry(row) {
@@ -18475,7 +18602,7 @@ function buildSymbolWhere(opts) {
18475
18602
  function querySymbols(opts = {}, dbPath = globalDbPath()) {
18476
18603
  const { clause, params } = buildSymbolWhere(opts);
18477
18604
  const limit = opts.limit ?? 100;
18478
- const sql = `SELECT file_path, name, kind, line_start, line_end, body, docstring FROM symbols ${clause} ORDER BY file_path, line_start LIMIT ?`;
18605
+ const sql = `SELECT file_path, name, kind, line_start, line_end, body, docstring, parent FROM symbols ${clause} ORDER BY file_path, line_start LIMIT ?`;
18479
18606
  const db = getDb(dbPath);
18480
18607
  const rows = db.prepare(sql).all(...params, limit);
18481
18608
  return rows.map(toSymbolEntry);
@@ -18554,7 +18681,7 @@ function sanitizeFtsQuery(query, join46 = "AND") {
18554
18681
  return terms.join(join46 === "OR" ? " OR " : " ");
18555
18682
  }
18556
18683
  function runFtsQuery(db, match2, limit, scope, rootDir) {
18557
- const sql = `SELECT s.file_path, s.name, s.kind, s.line_start, s.line_end, s.body, s.docstring FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE symbols_fts MATCH ?${scope !== void 0 ? ` AND ${scope.clause}` : ""} ORDER BY bm25(symbols_fts) LIMIT ?`;
18684
+ const sql = `SELECT s.file_path, s.name, s.kind, s.line_start, s.line_end, s.body, s.docstring, s.parent FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE symbols_fts MATCH ?${scope !== void 0 ? ` AND ${scope.clause}` : ""} ORDER BY bm25(symbols_fts) LIMIT ?`;
18558
18685
  const params = [match2];
18559
18686
  if (scope !== void 0 && rootDir !== void 0) {
18560
18687
  params.push(scope.param(rootDir));
@@ -18623,12 +18750,12 @@ function extractCsharp(content, filePath) {
18623
18750
  }
18624
18751
  const nsM = NAMESPACE_RE.exec(stripped);
18625
18752
  if (nsM) {
18626
- symbols.push(makeLineSymbol(filePath, nsM[1] ?? "", "namespace", lineNum, stripped.slice(0, 200)));
18753
+ symbols.push(makeLineSymbol(filePath, nsM[1] ?? "", "namespace", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
18627
18754
  }
18628
18755
  const delM = DELEGATE_RE.exec(stripLeadingAttributes(stripped));
18629
18756
  if (delM) {
18630
18757
  const delegateParent = classStack.length > 0 ? classStack[classStack.length - 1].name : void 0;
18631
- symbols.push(makeLineSymbol(filePath, delM[1] ?? "", "interface", lineNum, stripped.slice(0, 200), delegateParent));
18758
+ symbols.push(makeLineSymbol(filePath, delM[1] ?? "", "interface", lineNum, stripped.slice(0, 200), delegateParent, lines2, "c"));
18632
18759
  }
18633
18760
  const cm = CLASS_HEADER_RE.exec(stripLeadingAttributes(stripped));
18634
18761
  if (cm) {
@@ -18636,7 +18763,7 @@ function extractCsharp(content, filePath) {
18636
18763
  const cname = cm[2] ?? "";
18637
18764
  const kind = keyword === "struct" ? "struct" : keyword === "interface" ? "interface" : keyword === "enum" ? "enum" : "class";
18638
18765
  const parent = classStack.length > 0 ? classStack[classStack.length - 1].name : void 0;
18639
- symbols.push(makeLineSymbol(filePath, cname, kind, lineNum, stripped.slice(0, 200), parent));
18766
+ symbols.push(makeLineSymbol(filePath, cname, kind, lineNum, stripped.slice(0, 200), parent, lines2, "c"));
18640
18767
  classStack.push({ name: cname, startDepth: braceDepth, bodyEntered: false });
18641
18768
  }
18642
18769
  const frame = classStack.length > 0 ? classStack[classStack.length - 1] : null;
@@ -18648,13 +18775,13 @@ function extractCsharp(content, filePath) {
18648
18775
  if (ctorM && ctorM[1] === frame.name) {
18649
18776
  const sigEnd = line.indexOf("{");
18650
18777
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trimEnd() : line.trimEnd();
18651
- symbols.push(makeLineSymbol(filePath, frame.name, "method", lineNum, sig.slice(0, 200), frame.name));
18778
+ symbols.push(makeLineSymbol(filePath, frame.name, "method", lineNum, sig.slice(0, 200), frame.name, lines2, "c"));
18652
18779
  }
18653
18780
  let isPropertyLine = false;
18654
18781
  const propM = PROPERTY_RE.exec(lineNoAttr);
18655
18782
  if (propM) {
18656
18783
  isPropertyLine = true;
18657
- symbols.push(makeLineSymbol(filePath, propM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
18784
+ symbols.push(makeLineSymbol(filePath, propM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
18658
18785
  } else {
18659
18786
  const headerM = PROPERTY_HEADER_RE.exec(lineNoAttr);
18660
18787
  if (headerM) {
@@ -18662,13 +18789,13 @@ function extractCsharp(content, filePath) {
18662
18789
  const accessorLine = (lines2[i + 2] ?? "").trim();
18663
18790
  if (braceLineNext === "{" && (ALLMAN_ACCESSOR_RE.test(accessorLine) || ALLMAN_ACCESSOR_BODY_RE.test(accessorLine))) {
18664
18791
  isPropertyLine = true;
18665
- symbols.push(makeLineSymbol(filePath, headerM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
18792
+ symbols.push(makeLineSymbol(filePath, headerM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
18666
18793
  }
18667
18794
  } else {
18668
18795
  const arrowM = PROPERTY_ARROW_RE.exec(lineNoAttr);
18669
18796
  if (arrowM) {
18670
18797
  isPropertyLine = true;
18671
- symbols.push(makeLineSymbol(filePath, arrowM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
18798
+ symbols.push(makeLineSymbol(filePath, arrowM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
18672
18799
  }
18673
18800
  }
18674
18801
  }
@@ -18678,7 +18805,7 @@ function extractCsharp(content, filePath) {
18678
18805
  if (mname && mname !== frame.name) {
18679
18806
  const sigEnd = line.indexOf("{");
18680
18807
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trimEnd() : line.trimEnd();
18681
- symbols.push(makeLineSymbol(filePath, mname, "method", lineNum, sig.slice(0, 200), frame.name));
18808
+ symbols.push(makeLineSymbol(filePath, mname, "method", lineNum, sig.slice(0, 200), frame.name, lines2, "c"));
18682
18809
  }
18683
18810
  }
18684
18811
  }
@@ -18783,7 +18910,7 @@ function extractPhp(content, filePath) {
18783
18910
  }
18784
18911
  const nsM = NAMESPACE_RE2.exec(stripped);
18785
18912
  if (nsM) {
18786
- symbols.push(makeLineSymbol(filePath, nsM[1] ?? "", "namespace", lineNum, stripped.slice(0, 200)));
18913
+ symbols.push(makeLineSymbol(filePath, nsM[1] ?? "", "namespace", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
18787
18914
  continue;
18788
18915
  }
18789
18916
  if (contextStack.length === 0) {
@@ -18817,7 +18944,7 @@ function extractPhp(content, filePath) {
18817
18944
  const preLineDepth = braceDepth - openB + closeB;
18818
18945
  const topFrame2 = contextStack.length > 0 ? contextStack[contextStack.length - 1] : void 0;
18819
18946
  const parent = topFrame2 !== void 0 && preLineDepth === topFrame2[1] + 1 ? topFrame2[0] : null;
18820
- symbols.push(makeLineSymbol(filePath, name2, kind, lineNum, stripped.slice(0, 200), parent ?? void 0));
18947
+ symbols.push(makeLineSymbol(filePath, name2, kind, lineNum, stripped.slice(0, 200), parent ?? void 0, lines2, "c"));
18821
18948
  contextStack.push([name2, braceDepth - openB + closeB, false]);
18822
18949
  if (openB > 0 && openB === closeB) {
18823
18950
  contextStack.pop();
@@ -18834,7 +18961,7 @@ function extractPhp(content, filePath) {
18834
18961
  const kind = parent ? "method" : "function";
18835
18962
  const sigEnd = stripped.indexOf(")");
18836
18963
  const sig = sigEnd >= 0 ? stripped.slice(0, sigEnd + 1) : stripped;
18837
- symbols.push(makeLineSymbol(filePath, name2, kind, lineNum, sig.slice(0, 200), parent ?? void 0));
18964
+ symbols.push(makeLineSymbol(filePath, name2, kind, lineNum, sig.slice(0, 200), parent ?? void 0, lines2, "c"));
18838
18965
  continue;
18839
18966
  }
18840
18967
  const propM = PROP_RE.exec(stripped);
@@ -18843,7 +18970,7 @@ function extractPhp(content, filePath) {
18843
18970
  const preLineDepth = braceDepth - openB + closeB;
18844
18971
  const topFrame2 = contextStack.length > 0 ? contextStack[contextStack.length - 1] : void 0;
18845
18972
  if (topFrame2 !== void 0 && preLineDepth === topFrame2[1] + 1) {
18846
- symbols.push(makeLineSymbol(filePath, name2, "var", lineNum, stripped.slice(0, 200), topFrame2[0]));
18973
+ symbols.push(makeLineSymbol(filePath, name2, "var", lineNum, stripped.slice(0, 200), topFrame2[0], lines2, "c"));
18847
18974
  }
18848
18975
  continue;
18849
18976
  }
@@ -18853,12 +18980,12 @@ function extractPhp(content, filePath) {
18853
18980
  const preLineDepth = braceDepth - openB + closeB;
18854
18981
  const topFrame2 = contextStack.length > 0 ? contextStack[contextStack.length - 1] : void 0;
18855
18982
  const parent = topFrame2 !== void 0 && preLineDepth === topFrame2[1] + 1 ? topFrame2[0] : void 0;
18856
- symbols.push(makeLineSymbol(filePath, name2, "const", lineNum, stripped.slice(0, 200), parent));
18983
+ symbols.push(makeLineSymbol(filePath, name2, "const", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
18857
18984
  continue;
18858
18985
  }
18859
18986
  const defineM = DEFINE_RE.exec(stripped);
18860
18987
  if (defineM) {
18861
- symbols.push(makeLineSymbol(filePath, defineM[1] ?? "", "const", lineNum, stripped.slice(0, 200)));
18988
+ symbols.push(makeLineSymbol(filePath, defineM[1] ?? "", "const", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
18862
18989
  }
18863
18990
  }
18864
18991
  return { symbols, imports };
@@ -18923,7 +19050,7 @@ function extractHtml(content, filePath) {
18923
19050
  const key = `${idVal}\0${line}`;
18924
19051
  if (!seenId.has(key)) {
18925
19052
  seenId.add(key);
18926
- symbols.push({ filePath, name: idVal, kind: "html_id", lineStart: line, lineEnd: line, body: "", docstring: "" });
19053
+ symbols.push({ filePath, name: idVal, kind: "html_id", lineStart: line, lineEnd: line, body: "", docstring: "", parent: "" });
18927
19054
  }
18928
19055
  }
18929
19056
  }
@@ -18938,7 +19065,7 @@ function extractHtml(content, filePath) {
18938
19065
  const key = `${cls}\0${line}`;
18939
19066
  if (!seenClass.has(key)) {
18940
19067
  seenClass.add(key);
18941
- symbols.push({ filePath, name: cls, kind: "html_class", lineStart: line, lineEnd: line, body: "", docstring: "" });
19068
+ symbols.push({ filePath, name: cls, kind: "html_class", lineStart: line, lineEnd: line, body: "", docstring: "", parent: "" });
18942
19069
  }
18943
19070
  }
18944
19071
  }
@@ -19041,7 +19168,7 @@ function extractLiquid(content, filePath, relPath) {
19041
19168
  if (name2) {
19042
19169
  const line = offsetToLine(lineIndex, m.index ?? 0);
19043
19170
  const endLine = offsetToLine(lineIndex, (m.index ?? 0) + (m[0]?.length ?? 0));
19044
- symbols.push({ filePath, name: name2, kind: "liquid_schema", lineStart: line, lineEnd: endLine, body: "", docstring: "" });
19171
+ symbols.push({ filePath, name: name2, kind: "liquid_schema", lineStart: line, lineEnd: endLine, body: "", docstring: "", parent: "" });
19045
19172
  }
19046
19173
  }
19047
19174
  } catch {
@@ -19051,7 +19178,7 @@ function extractLiquid(content, filePath, relPath) {
19051
19178
  const relPosix = resolvedRel.replace(/\\/g, "/");
19052
19179
  if (relPosix.startsWith("sections/") || relPosix.includes("/sections/")) {
19053
19180
  const stem = path27.basename(resolvedRel, path27.extname(resolvedRel));
19054
- symbols.push({ filePath, name: stem, kind: "liquid_section_file", lineStart: 1, lineEnd: 1, body: "", docstring: "" });
19181
+ symbols.push({ filePath, name: stem, kind: "liquid_section_file", lineStart: 1, lineEnd: 1, body: "", docstring: "", parent: "" });
19055
19182
  }
19056
19183
  const totalLines = content.split("\n").length;
19057
19184
  for (const hm of findHtmlHeadingMatches(content)) {
@@ -19142,14 +19269,14 @@ function extractKotlin(content, filePath) {
19142
19269
  if (companionM) {
19143
19270
  const cname = companionM[1] ?? "Companion";
19144
19271
  const parent = classStack.length > 0 ? classStack[classStack.length - 1].name : void 0;
19145
- symbols.push(makeLineSymbol(filePath, cname, "object", lineNum, line.trimEnd().slice(0, 200), parent));
19272
+ symbols.push(makeLineSymbol(filePath, cname, "object", lineNum, line.trimEnd().slice(0, 200), parent, lines2, "c"));
19146
19273
  classStack.push({ name: cname, braceDepth, bodyEntered: false, parenBalance: 0, pendingPop: false });
19147
19274
  } else if (cm) {
19148
19275
  const ckeyword = cm[1] ?? "class";
19149
19276
  const cname = cm[2] ?? "";
19150
19277
  const ckind = ckeyword === "interface" ? "interface" : ckeyword === "object" ? "object" : "class";
19151
19278
  const parent = classStack.length > 0 ? classStack[classStack.length - 1].name : void 0;
19152
- symbols.push(makeLineSymbol(filePath, cname, ckind, lineNum, line.trimEnd().slice(0, 200), parent));
19279
+ symbols.push(makeLineSymbol(filePath, cname, ckind, lineNum, line.trimEnd().slice(0, 200), parent, lines2, "c"));
19153
19280
  classStack.push({ name: cname, braceDepth, bodyEntered: false, parenBalance: 0, pendingPop: false });
19154
19281
  }
19155
19282
  const frame = classStack.length > 0 ? classStack[classStack.length - 1] : null;
@@ -19162,11 +19289,11 @@ function extractKotlin(content, filePath) {
19162
19289
  const fname = fm[1] ?? "";
19163
19290
  const sigEnd = line.indexOf("{");
19164
19291
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trim() : line.trimEnd();
19165
- symbols.push(makeLineSymbol(filePath, fname, "method", lineNum, sig.slice(0, 200), frame.name));
19292
+ symbols.push(makeLineSymbol(filePath, fname, "method", lineNum, sig.slice(0, 200), frame.name, lines2, "c"));
19166
19293
  }
19167
19294
  const constM = CONST_RE2.exec(lineNoAnn);
19168
19295
  if (constM) {
19169
- symbols.push(makeLineSymbol(filePath, constM[1] ?? "", "const", lineNum, stripped.slice(0, 200), frame.name));
19296
+ symbols.push(makeLineSymbol(filePath, constM[1] ?? "", "const", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19170
19297
  }
19171
19298
  }
19172
19299
  } else if (!isIndented) {
@@ -19176,11 +19303,11 @@ function extractKotlin(content, filePath) {
19176
19303
  const fname = tfm[1] ?? "";
19177
19304
  const sigEnd = line.indexOf("{");
19178
19305
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trim() : line.trimEnd();
19179
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, sig.slice(0, 200)));
19306
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, sig.slice(0, 200), void 0, lines2, "c"));
19180
19307
  }
19181
19308
  const topConstM = CONST_RE2.exec(lineNoAnn);
19182
19309
  if (topConstM) {
19183
- symbols.push(makeLineSymbol(filePath, topConstM[1] ?? "", "const", lineNum, stripped.slice(0, 200)));
19310
+ symbols.push(makeLineSymbol(filePath, topConstM[1] ?? "", "const", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
19184
19311
  }
19185
19312
  }
19186
19313
  const braceLine = stripStringLiterals(line);
@@ -19284,7 +19411,7 @@ function extractSwift(content, filePath) {
19284
19411
  const tname = tm[2] ?? "";
19285
19412
  const kind = keyword === "struct" ? "struct" : keyword === "enum" ? "enum" : keyword === "protocol" ? "protocol" : keyword === "extension" ? "extension" : keyword === "actor" ? "actor" : "class";
19286
19413
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19287
- symbols.push(makeLineSymbol(filePath, tname, kind, lineNum, stripped.slice(0, 200), parent));
19414
+ symbols.push(makeLineSymbol(filePath, tname, kind, lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19288
19415
  typeStack.push({ name: tname, startDepth: braceDepth, bodyEntered: false });
19289
19416
  }
19290
19417
  const frame = typeStack.length > 0 ? typeStack[typeStack.length - 1] : null;
@@ -19297,17 +19424,17 @@ function extractSwift(content, filePath) {
19297
19424
  const subscriptM = SUBSCRIPT_RE.exec(lineNoAttr);
19298
19425
  const fm = FUNC_RE.exec(lineNoAttr);
19299
19426
  if (initM) {
19300
- symbols.push(makeLineSymbol(filePath, initM[1] ?? "init", "method", lineNum, stripped.slice(0, 200), frame.name));
19427
+ symbols.push(makeLineSymbol(filePath, initM[1] ?? "init", "method", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19301
19428
  } else if (deinitM) {
19302
- symbols.push(makeLineSymbol(filePath, "deinit", "method", lineNum, stripped.slice(0, 200), frame.name));
19429
+ symbols.push(makeLineSymbol(filePath, "deinit", "method", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19303
19430
  } else if (subscriptM) {
19304
- symbols.push(makeLineSymbol(filePath, subscriptM[1] ?? "subscript", "method", lineNum, stripped.slice(0, 200), frame.name));
19431
+ symbols.push(makeLineSymbol(filePath, subscriptM[1] ?? "subscript", "method", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19305
19432
  } else if (fm) {
19306
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "method", lineNum, stripped.slice(0, 200), frame.name));
19433
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "method", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19307
19434
  } else {
19308
19435
  const propM = PROPERTY_RE2.exec(lineNoAttr);
19309
19436
  if (propM) {
19310
- symbols.push(makeLineSymbol(filePath, propM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
19437
+ symbols.push(makeLineSymbol(filePath, propM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19311
19438
  }
19312
19439
  }
19313
19440
  }
@@ -19315,7 +19442,7 @@ function extractSwift(content, filePath) {
19315
19442
  const lineNoAttr = stripLeadingAttributes2(line);
19316
19443
  const fm = FUNC_RE.exec(lineNoAttr);
19317
19444
  if (fm) {
19318
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200)));
19445
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
19319
19446
  }
19320
19447
  }
19321
19448
  const braceLine = stripStringLiterals(line);
@@ -19412,7 +19539,7 @@ function extractScala(content, filePath) {
19412
19539
  if (cm) {
19413
19540
  const cname = cm[1] ?? "";
19414
19541
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19415
- symbols.push(makeLineSymbol(filePath, cname, "class", lineNum, stripped.slice(0, 200), parent));
19542
+ symbols.push(makeLineSymbol(filePath, cname, "class", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19416
19543
  typeStack.push({ name: cname, startDepth: braceDepth, bodyEntered: false });
19417
19544
  if (/\bcase\s+class\b/.test(stripped) && !stripStringLiterals(line).includes("{")) {
19418
19545
  typeStack.pop();
@@ -19423,7 +19550,7 @@ function extractScala(content, filePath) {
19423
19550
  if (om) {
19424
19551
  const oname = om[1] ?? "";
19425
19552
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19426
- symbols.push(makeLineSymbol(filePath, oname, "object", lineNum, stripped.slice(0, 200), parent));
19553
+ symbols.push(makeLineSymbol(filePath, oname, "object", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19427
19554
  typeStack.push({ name: oname, startDepth: braceDepth, bodyEntered: false });
19428
19555
  if (/\bcase\s+object\b/.test(stripped) && !stripStringLiterals(line).includes("{")) {
19429
19556
  typeStack.pop();
@@ -19434,7 +19561,7 @@ function extractScala(content, filePath) {
19434
19561
  if (tm) {
19435
19562
  const tname = tm[1] ?? "";
19436
19563
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19437
- symbols.push(makeLineSymbol(filePath, tname, "trait", lineNum, stripped.slice(0, 200), parent));
19564
+ symbols.push(makeLineSymbol(filePath, tname, "trait", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19438
19565
  typeStack.push({ name: tname, startDepth: braceDepth, bodyEntered: false });
19439
19566
  matched = true;
19440
19567
  }
@@ -19442,7 +19569,7 @@ function extractScala(content, filePath) {
19442
19569
  if (enm) {
19443
19570
  const enname = enm[1] ?? "";
19444
19571
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19445
- symbols.push(makeLineSymbol(filePath, enname, "enum", lineNum, stripped.slice(0, 200), parent));
19572
+ symbols.push(makeLineSymbol(filePath, enname, "enum", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19446
19573
  typeStack.push({ name: enname, startDepth: braceDepth, bodyEntered: false });
19447
19574
  matched = true;
19448
19575
  }
@@ -19452,36 +19579,36 @@ function extractScala(content, filePath) {
19452
19579
  if (depthInType === 1) {
19453
19580
  const fm = FUNC_RE2.exec(stripped);
19454
19581
  if (fm) {
19455
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), frame.name));
19582
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19456
19583
  matched = true;
19457
19584
  }
19458
19585
  const vm = !matched ? VAL_RE.exec(stripped) : null;
19459
19586
  if (vm) {
19460
- symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "val", lineNum, stripped.slice(0, 200), frame.name));
19587
+ symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "val", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19461
19588
  matched = true;
19462
19589
  }
19463
19590
  if (!matched) {
19464
19591
  const varm = VAR_RE.exec(stripped);
19465
19592
  if (varm) {
19466
- symbols.push(makeLineSymbol(filePath, varm[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
19593
+ symbols.push(makeLineSymbol(filePath, varm[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19467
19594
  }
19468
19595
  }
19469
19596
  }
19470
19597
  } else if (!matched && frame === null && !isIndented) {
19471
19598
  const fm = FUNC_RE2.exec(stripped);
19472
19599
  if (fm) {
19473
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200)));
19600
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
19474
19601
  matched = true;
19475
19602
  }
19476
19603
  const vm = !matched ? VAL_RE.exec(stripped) : null;
19477
19604
  if (vm) {
19478
- symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "val", lineNum, stripped.slice(0, 200)));
19605
+ symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "val", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
19479
19606
  matched = true;
19480
19607
  }
19481
19608
  if (!matched) {
19482
19609
  const varm = VAR_RE.exec(stripped);
19483
19610
  if (varm) {
19484
- symbols.push(makeLineSymbol(filePath, varm[1] ?? "", "var", lineNum, stripped.slice(0, 200)));
19611
+ symbols.push(makeLineSymbol(filePath, varm[1] ?? "", "var", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
19485
19612
  }
19486
19613
  }
19487
19614
  }
@@ -19660,14 +19787,14 @@ function extractElixir(content, filePath) {
19660
19787
  const modM = MODULE_RE.exec(stripped);
19661
19788
  if (modM) {
19662
19789
  const modName = modM[1] ?? "";
19663
- symbols.push(makeLineSymbol(filePath, modName, "class", lineNum, stripped.slice(0, 200)));
19790
+ symbols.push(makeLineSymbol(filePath, modName, "class", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
19664
19791
  moduleStack.push({ name: modName, endKeywordNeeded: true, isBlock: false });
19665
19792
  continue;
19666
19793
  }
19667
19794
  const protoM = PROTOCOL_RE.exec(stripped);
19668
19795
  if (protoM) {
19669
19796
  const protoName = protoM[1] ?? "";
19670
- symbols.push(makeLineSymbol(filePath, protoName, "protocol", lineNum, stripped.slice(0, 200)));
19797
+ symbols.push(makeLineSymbol(filePath, protoName, "protocol", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
19671
19798
  moduleStack.push({ name: protoName, endKeywordNeeded: true, isBlock: false });
19672
19799
  continue;
19673
19800
  }
@@ -19676,9 +19803,9 @@ function extractElixir(content, filePath) {
19676
19803
  const fname = fm[1] ?? "";
19677
19804
  const parent = nearestDefName(moduleStack);
19678
19805
  if (parent !== void 0) {
19679
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), parent));
19806
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), parent, lines2, "hash"));
19680
19807
  } else {
19681
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
19808
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
19682
19809
  }
19683
19810
  if (opensDoBlock) {
19684
19811
  moduleStack.push({ name: fname, endKeywordNeeded: true, isBlock: false });
@@ -19690,9 +19817,9 @@ function extractElixir(content, filePath) {
19690
19817
  const fname = pfm[1] ?? "";
19691
19818
  const parent = nearestDefName(moduleStack);
19692
19819
  if (parent !== void 0) {
19693
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), parent));
19820
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), parent, lines2, "hash"));
19694
19821
  } else {
19695
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
19822
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
19696
19823
  }
19697
19824
  if (opensDoBlock) {
19698
19825
  moduleStack.push({ name: fname, endKeywordNeeded: true, isBlock: false });
@@ -19702,7 +19829,7 @@ function extractElixir(content, filePath) {
19702
19829
  if (STRUCT_RE.test(stripped)) {
19703
19830
  const parent = nearestDefName(moduleStack);
19704
19831
  if (parent !== void 0) {
19705
- symbols.push(makeLineSymbol(filePath, "__struct__", "var", lineNum, stripped.slice(0, 200), parent));
19832
+ symbols.push(makeLineSymbol(filePath, "__struct__", "var", lineNum, stripped.slice(0, 200), parent, lines2, "hash"));
19706
19833
  }
19707
19834
  continue;
19708
19835
  }
@@ -19762,7 +19889,7 @@ function extractDart(content, filePath) {
19762
19889
  if (cm) {
19763
19890
  const cname = cm[1] ?? "";
19764
19891
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19765
- symbols.push(makeLineSymbol(filePath, cname, "class", lineNum, stripped.slice(0, 200), parent));
19892
+ symbols.push(makeLineSymbol(filePath, cname, "class", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19766
19893
  typeStack.push({ name: cname, startDepth: braceDepth, bodyEntered: false });
19767
19894
  matched = true;
19768
19895
  }
@@ -19770,7 +19897,7 @@ function extractDart(content, filePath) {
19770
19897
  if (em) {
19771
19898
  const ename = em[1] ?? "";
19772
19899
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19773
- symbols.push(makeLineSymbol(filePath, ename, "enum", lineNum, stripped.slice(0, 200), parent));
19900
+ symbols.push(makeLineSymbol(filePath, ename, "enum", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19774
19901
  typeStack.push({ name: ename, startDepth: braceDepth, bodyEntered: false });
19775
19902
  matched = true;
19776
19903
  }
@@ -19778,7 +19905,7 @@ function extractDart(content, filePath) {
19778
19905
  if (mm) {
19779
19906
  const mname = mm[1] ?? "";
19780
19907
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19781
- symbols.push(makeLineSymbol(filePath, mname, "mixin", lineNum, stripped.slice(0, 200), parent));
19908
+ symbols.push(makeLineSymbol(filePath, mname, "mixin", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19782
19909
  typeStack.push({ name: mname, startDepth: braceDepth, bodyEntered: false });
19783
19910
  matched = true;
19784
19911
  }
@@ -19786,7 +19913,7 @@ function extractDart(content, filePath) {
19786
19913
  if (etm) {
19787
19914
  const etname = etm[1] ?? "";
19788
19915
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19789
- symbols.push(makeLineSymbol(filePath, etname, "extension_type", lineNum, stripped.slice(0, 200), parent));
19916
+ symbols.push(makeLineSymbol(filePath, etname, "extension_type", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19790
19917
  typeStack.push({ name: etname, startDepth: braceDepth, bodyEntered: false });
19791
19918
  matched = true;
19792
19919
  }
@@ -19794,7 +19921,7 @@ function extractDart(content, filePath) {
19794
19921
  if (extm) {
19795
19922
  const extname14 = extm[1] ?? "extension";
19796
19923
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
19797
- symbols.push(makeLineSymbol(filePath, extname14, "extension", lineNum, stripped.slice(0, 200), parent));
19924
+ symbols.push(makeLineSymbol(filePath, extname14, "extension", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19798
19925
  typeStack.push({ name: extname14, startDepth: braceDepth, bodyEntered: false });
19799
19926
  matched = true;
19800
19927
  }
@@ -19807,7 +19934,7 @@ function extractDart(content, filePath) {
19807
19934
  if (fm) {
19808
19935
  let fname = fm[1] ?? "";
19809
19936
  fname = fname.replace(/^operator\s+/, "");
19810
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), frame.name));
19937
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19811
19938
  }
19812
19939
  }
19813
19940
  } else if (!matched && frame === null && !isIndented) {
@@ -19815,7 +19942,7 @@ function extractDart(content, filePath) {
19815
19942
  if (fm) {
19816
19943
  let fname = fm[1] ?? "";
19817
19944
  fname = fname.replace(/^operator\s+/, "");
19818
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
19945
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
19819
19946
  }
19820
19947
  }
19821
19948
  const braceLine = stripStringLiterals(line);
@@ -19887,7 +20014,7 @@ function extractZig(content, filePath) {
19887
20014
  const skind = sm[2] ?? "struct";
19888
20015
  if (sname) {
19889
20016
  const parent = outerFrame !== null ? outerFrame.name : void 0;
19890
- symbols.push(makeLineSymbol(filePath, sname, skind, lineNum, stripped.slice(0, 200), parent));
20017
+ symbols.push(makeLineSymbol(filePath, sname, skind, lineNum, stripped.slice(0, 200), parent, lines2, "c"));
19891
20018
  scopeStack.push({ name: sname, startDepth: braceDepth, bodyEntered: false });
19892
20019
  matched = true;
19893
20020
  }
@@ -19898,7 +20025,7 @@ function extractZig(content, filePath) {
19898
20025
  const fm = FUNC_RE6.exec(stripped);
19899
20026
  if (fm) {
19900
20027
  const fname = fm[2] ?? "";
19901
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
20028
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
19902
20029
  matched = true;
19903
20030
  }
19904
20031
  } else if (!matched && frame !== null) {
@@ -19907,7 +20034,7 @@ function extractZig(content, filePath) {
19907
20034
  const fm = FUNC_RE6.exec(stripped);
19908
20035
  if (fm) {
19909
20036
  const fname = fm[2] ?? "";
19910
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), frame.name));
20037
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
19911
20038
  matched = true;
19912
20039
  }
19913
20040
  }
@@ -19915,13 +20042,13 @@ function extractZig(content, filePath) {
19915
20042
  if (!matched && !isIndented) {
19916
20043
  const cm = CONST_RE3.exec(stripped);
19917
20044
  if (cm) {
19918
- symbols.push(makeLineSymbol(filePath, cm[1] ?? "", "const", lineNum, stripped.slice(0, 200)));
20045
+ symbols.push(makeLineSymbol(filePath, cm[1] ?? "", "const", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
19919
20046
  matched = true;
19920
20047
  }
19921
20048
  if (!matched) {
19922
20049
  const vm = VAR_RE2.exec(stripped);
19923
20050
  if (vm) {
19924
- symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "var", lineNum, stripped.slice(0, 200)));
20051
+ symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "var", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
19925
20052
  }
19926
20053
  }
19927
20054
  }
@@ -19980,17 +20107,17 @@ function extractR(content, filePath) {
19980
20107
  if (!isIndented) {
19981
20108
  const fm = FUNC_ASSIGN_RE.exec(stripped);
19982
20109
  if (fm) {
19983
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200)));
20110
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
19984
20111
  continue;
19985
20112
  }
19986
20113
  const cm = SETCLASS_RE.exec(stripped);
19987
20114
  if (cm) {
19988
- symbols.push(makeLineSymbol(filePath, cm[1] ?? "", "class", lineNum, stripped.slice(0, 200)));
20115
+ symbols.push(makeLineSymbol(filePath, cm[1] ?? "", "class", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
19989
20116
  continue;
19990
20117
  }
19991
20118
  const mm = SETMETHOD_RE.exec(stripped);
19992
20119
  if (mm) {
19993
- symbols.push(makeLineSymbol(filePath, mm[1] ?? "", "function", lineNum, stripped.slice(0, 200)));
20120
+ symbols.push(makeLineSymbol(filePath, mm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
19994
20121
  }
19995
20122
  }
19996
20123
  }
@@ -20524,7 +20651,7 @@ function extractBash(content, filePath) {
20524
20651
  if (funcMatch) {
20525
20652
  const fname = funcMatch[1] ?? "";
20526
20653
  if (fname && symbols.length < MAX_SYMBOLS4) {
20527
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
20654
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
20528
20655
  }
20529
20656
  if (fname) {
20530
20657
  if (stripped.includes("{")) {
@@ -20545,7 +20672,7 @@ function extractBash(content, filePath) {
20545
20672
  if (varMatch) {
20546
20673
  const vname = varMatch[1] ?? "";
20547
20674
  if (vname && symbols.length < MAX_SYMBOLS4) {
20548
- symbols.push(makeLineSymbol(filePath, vname, "variable", lineNum, stripped.slice(0, 200)));
20675
+ symbols.push(makeLineSymbol(filePath, vname, "variable", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
20549
20676
  }
20550
20677
  }
20551
20678
  }
@@ -21054,7 +21181,7 @@ function extractPowershell(content, filePath) {
21054
21181
  if (funcMatch) {
21055
21182
  const fname = funcMatch[1] ?? "";
21056
21183
  if (symbols.length < MAX_SYMBOLS8) {
21057
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, line.trimEnd().slice(0, 200)));
21184
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, line.trimEnd().slice(0, 200), void 0, lines2, "hash"));
21058
21185
  }
21059
21186
  }
21060
21187
  }
@@ -21064,7 +21191,7 @@ function extractPowershell(content, filePath) {
21064
21191
  const cname = classMatch[2] ?? "";
21065
21192
  const kind = (classMatch[1] ?? "").toLowerCase() === "enum" ? "enum" : "class";
21066
21193
  if (symbols.length < MAX_SYMBOLS8) {
21067
- symbols.push(makeLineSymbol(filePath, cname, kind, lineNum, line.trimEnd().slice(0, 200)));
21194
+ symbols.push(makeLineSymbol(filePath, cname, kind, lineNum, line.trimEnd().slice(0, 200), void 0, lines2, "hash"));
21068
21195
  }
21069
21196
  if (kind === "class") {
21070
21197
  const strippedLine = stripPowershellStringLiterals(line);
@@ -21089,7 +21216,7 @@ function extractPowershell(content, filePath) {
21089
21216
  if (mname && symbols.length < MAX_SYMBOLS8) {
21090
21217
  const sigEnd = line.indexOf("{");
21091
21218
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trimEnd() : line.trimEnd();
21092
- symbols.push(makeLineSymbol(filePath, mname, "method", lineNum, sig.slice(0, 200), currentClass));
21219
+ symbols.push(makeLineSymbol(filePath, mname, "method", lineNum, sig.slice(0, 200), currentClass, lines2, "hash"));
21093
21220
  }
21094
21221
  }
21095
21222
  }
@@ -21187,12 +21314,13 @@ function extractApex(content, filePath) {
21187
21314
  const stringFree = stripStringLiterals(blockCommentFree);
21188
21315
  const code = stripCstyleComments(stringFree, /\/\/.*$/gm);
21189
21316
  const codeLines = code.split(/\r?\n/);
21190
- const emit5 = (name2, kind, span, docstring = "") => {
21317
+ const rawLines = content.split(/\r?\n/);
21318
+ const emit5 = (name2, kind, span, parent = "") => {
21191
21319
  if (!name2 || symbols.length >= MAX_SYMBOLS9) return;
21192
21320
  const key = `${name2}\0${kind}\0${span.startLine}`;
21193
21321
  if (seen.has(key)) return;
21194
21322
  seen.add(key);
21195
- symbols.push(makeSpanSymbol(filePath, name2, kind, span, docstring));
21323
+ symbols.push(makeSpanSymbol(filePath, name2, kind, span, parent, rawLines, "c"));
21196
21324
  };
21197
21325
  for (const match2 of code.matchAll(TRIGGER_RE2)) {
21198
21326
  const name2 = match2[1] ?? "";
@@ -21638,7 +21766,7 @@ function lwcTagAlias(name2) {
21638
21766
  return `c-${kebab}`;
21639
21767
  }
21640
21768
  function symbol(filePath, name2, kind, lineStart, lineEnd = lineStart) {
21641
- return { filePath, name: name2, kind, lineStart, lineEnd, body: "", docstring: "" };
21769
+ return { filePath, name: name2, kind, lineStart, lineEnd, body: "", docstring: "", parent: "" };
21642
21770
  }
21643
21771
  function ref(filePath, name2, line, col, context) {
21644
21772
  return { filePath, name: name2, line, col, context };
@@ -21910,7 +22038,7 @@ function maskSpans(content, spans) {
21910
22038
  return chars.join("");
21911
22039
  }
21912
22040
  function componentSymbol(filePath, name2, kind, totalLines) {
21913
- return { filePath, name: name2, kind, lineStart: 1, lineEnd: totalLines, body: "", docstring: "" };
22041
+ return { filePath, name: name2, kind, lineStart: 1, lineEnd: totalLines, body: "", docstring: "", parent: "" };
21914
22042
  }
21915
22043
  function extractVue(content, filePath) {
21916
22044
  const totalLines = content.split("\n").length;
@@ -22188,15 +22316,17 @@ function nodeName(node) {
22188
22316
  if (named !== null) return named.text;
22189
22317
  return null;
22190
22318
  }
22191
- function makeSymbol(filePath, name2, kind, node) {
22319
+ function makeSymbol(filePath, name2, kind, node, lines2, style) {
22320
+ const lineStart = node.startPosition.row + 1;
22192
22321
  return {
22193
22322
  filePath,
22194
22323
  name: name2,
22195
22324
  kind,
22196
- lineStart: node.startPosition.row + 1,
22325
+ lineStart,
22197
22326
  lineEnd: node.endPosition.row + 1,
22198
22327
  body: node.text,
22199
- docstring: ""
22328
+ docstring: lines2 !== void 0 && style !== void 0 ? precedingDocComment(lines2, lineStart, style) : "",
22329
+ parent: ""
22200
22330
  };
22201
22331
  }
22202
22332
  function collectPatternBindings(node) {
@@ -22211,7 +22341,7 @@ function collectPatternBindings(node) {
22211
22341
  walk(node);
22212
22342
  return names;
22213
22343
  }
22214
- function extractTsJsSymbols(root, filePath) {
22344
+ function extractTsJsSymbols(root, filePath, lines2) {
22215
22345
  const out2 = [];
22216
22346
  const visit = (node, insideFunction) => {
22217
22347
  const kind = TSJS_KIND_BY_TYPE.get(node.type);
@@ -22220,16 +22350,18 @@ function extractTsJsSymbols(root, filePath) {
22220
22350
  if (name2 !== null && name2 !== "") {
22221
22351
  const decorators = leadingTsDecorators(node);
22222
22352
  if (decorators.length === 0) {
22223
- out2.push(makeSymbol(filePath, name2, kind, node));
22353
+ out2.push(makeSymbol(filePath, name2, kind, node, lines2, "c"));
22224
22354
  } else {
22355
+ const lineStart = decorators[0].startPosition.row + 1;
22225
22356
  out2.push({
22226
22357
  filePath,
22227
22358
  name: name2,
22228
22359
  kind,
22229
- lineStart: decorators[0].startPosition.row + 1,
22360
+ lineStart,
22230
22361
  lineEnd: node.endPosition.row + 1,
22231
22362
  body: [...decorators, node].map((n) => n.text).join("\n"),
22232
- docstring: ""
22363
+ docstring: precedingDocComment(lines2, lineStart, "c"),
22364
+ parent: ""
22233
22365
  });
22234
22366
  }
22235
22367
  }
@@ -22242,10 +22374,10 @@ function extractTsJsSymbols(root, filePath) {
22242
22374
  if (name2 === null) continue;
22243
22375
  if (name2.type === "identifier") {
22244
22376
  const isFn = value !== null && (value.type === "arrow_function" || value.type === "function_expression" || value.type === "function");
22245
- out2.push(makeSymbol(filePath, name2.text, isFn ? "function" : "variable", child));
22377
+ out2.push(makeSymbol(filePath, name2.text, isFn ? "function" : "variable", child, lines2, "c"));
22246
22378
  } else {
22247
22379
  for (const bound of collectPatternBindings(name2)) {
22248
- out2.push(makeSymbol(filePath, bound, "variable", child));
22380
+ out2.push(makeSymbol(filePath, bound, "variable", child, lines2, "c"));
22249
22381
  }
22250
22382
  }
22251
22383
  }
@@ -22254,7 +22386,7 @@ function extractTsJsSymbols(root, filePath) {
22254
22386
  const fieldName = node.childForFieldName("name") ?? node.childForFieldName("property");
22255
22387
  const value = node.childForFieldName("value");
22256
22388
  if (fieldName !== null && value !== null && (value.type === "arrow_function" || value.type === "function_expression" || value.type === "function")) {
22257
- out2.push(makeSymbol(filePath, fieldName.text, "method", node));
22389
+ out2.push(makeSymbol(filePath, fieldName.text, "method", node, lines2, "c"));
22258
22390
  }
22259
22391
  }
22260
22392
  const childInside = insideFunction || TSJS_FN_SCOPE_TYPES.has(node.type);
@@ -22318,14 +22450,14 @@ function stripPythonStringQuotes(raw) {
22318
22450
  }
22319
22451
  return s.trim();
22320
22452
  }
22321
- function extractGoSymbols(root, filePath) {
22453
+ function extractGoSymbols(root, filePath, lines2) {
22322
22454
  const out2 = [];
22323
22455
  const visit = (node, insideFunction) => {
22324
22456
  const kind = GO_KIND_BY_TYPE.get(node.type);
22325
22457
  if (kind !== void 0 && !(insideFunction && GO_LOCAL_KINDS.has(node.type))) {
22326
22458
  const name2 = nodeName(node);
22327
22459
  if (name2 !== null && name2 !== "") {
22328
- out2.push(makeSymbol(filePath, name2, kind, node));
22460
+ out2.push(makeSymbol(filePath, name2, kind, node, lines2, "c"));
22329
22461
  }
22330
22462
  }
22331
22463
  const childInside = insideFunction || GO_FN_SCOPE_TYPES.has(node.type);
@@ -22345,7 +22477,7 @@ function leadingRustAttributes(node) {
22345
22477
  }
22346
22478
  return attrs;
22347
22479
  }
22348
- function extractRustSymbols(root, filePath) {
22480
+ function extractRustSymbols(root, filePath, lines2) {
22349
22481
  const out2 = [];
22350
22482
  const visit = (node, insideFunction) => {
22351
22483
  const kind = RUST_KIND_BY_TYPE.get(node.type);
@@ -22354,16 +22486,18 @@ function extractRustSymbols(root, filePath) {
22354
22486
  if (name2 !== null && name2 !== "") {
22355
22487
  const attrs = leadingRustAttributes(node);
22356
22488
  if (attrs.length === 0) {
22357
- out2.push(makeSymbol(filePath, name2, kind, node));
22489
+ out2.push(makeSymbol(filePath, name2, kind, node, lines2, "c"));
22358
22490
  } else {
22491
+ const lineStart = attrs[0].startPosition.row + 1;
22359
22492
  out2.push({
22360
22493
  filePath,
22361
22494
  name: name2,
22362
22495
  kind,
22363
- lineStart: attrs[0].startPosition.row + 1,
22496
+ lineStart,
22364
22497
  lineEnd: node.endPosition.row + 1,
22365
22498
  body: [...attrs, node].map((n) => n.text).join("\n"),
22366
- docstring: ""
22499
+ docstring: precedingDocComment(lines2, lineStart, "c"),
22500
+ parent: ""
22367
22501
  });
22368
22502
  }
22369
22503
  }
@@ -22376,14 +22510,14 @@ function extractRustSymbols(root, filePath) {
22376
22510
  visit(root, false);
22377
22511
  return out2;
22378
22512
  }
22379
- function extractSimpleSymbols(root, filePath, kindByType, nameFor = nodeName) {
22513
+ function extractSimpleSymbols(root, filePath, kindByType, lines2, style, nameFor = nodeName) {
22380
22514
  const out2 = [];
22381
22515
  const visit = (node) => {
22382
22516
  const kind = kindByType.get(node.type);
22383
22517
  if (kind !== void 0) {
22384
22518
  const name2 = nameFor(node);
22385
22519
  if (name2 !== null && name2 !== "") {
22386
- out2.push(makeSymbol(filePath, name2, kind, node));
22520
+ out2.push(makeSymbol(filePath, name2, kind, node, lines2, style));
22387
22521
  }
22388
22522
  }
22389
22523
  for (const child of node.namedChildren) {
@@ -22393,17 +22527,19 @@ function extractSimpleSymbols(root, filePath, kindByType, nameFor = nodeName) {
22393
22527
  visit(root);
22394
22528
  return out2;
22395
22529
  }
22396
- function extractRubySymbols(root, filePath) {
22397
- return extractSimpleSymbols(root, filePath, RUBY_KIND_BY_TYPE);
22530
+ function extractRubySymbols(root, filePath, lines2) {
22531
+ return extractSimpleSymbols(root, filePath, RUBY_KIND_BY_TYPE, lines2, "hash");
22398
22532
  }
22399
- function extractJavaSymbols(root, filePath) {
22400
- return extractSimpleSymbols(root, filePath, JAVA_KIND_BY_TYPE);
22533
+ function extractJavaSymbols(root, filePath, lines2) {
22534
+ return extractSimpleSymbols(root, filePath, JAVA_KIND_BY_TYPE, lines2, "c");
22401
22535
  }
22402
- function extractCppSymbols(root, filePath) {
22536
+ function extractCppSymbols(root, filePath, lines2) {
22403
22537
  return extractSimpleSymbols(
22404
22538
  root,
22405
22539
  filePath,
22406
22540
  CPP_KIND_BY_TYPE,
22541
+ lines2,
22542
+ "c",
22407
22543
  (node) => node.type === "function_definition" ? cFunctionName(node) : node.type === "type_definition" ? cTypedefAliasName(node) : node.type === "declaration" ? cFunctionPrototypeName(node) : nodeName(node)
22408
22544
  );
22409
22545
  }
@@ -22665,7 +22801,8 @@ function extractMarkdownSymbols(content, filePath) {
22665
22801
  lineStart: i + 1,
22666
22802
  lineEnd: i + 1,
22667
22803
  body: line.trim(),
22668
- docstring: ""
22804
+ docstring: "",
22805
+ parent: ""
22669
22806
  });
22670
22807
  }
22671
22808
  }
@@ -22724,7 +22861,8 @@ function extractJsonSymbols(content, filePath) {
22724
22861
  lineStart: strStartLine,
22725
22862
  lineEnd,
22726
22863
  body,
22727
- docstring: ""
22864
+ docstring: "",
22865
+ parent: ""
22728
22866
  });
22729
22867
  }
22730
22868
  }
@@ -22809,7 +22947,8 @@ function extractYamlSymbols(content, filePath) {
22809
22947
  lineStart: i + 1,
22810
22948
  lineEnd: i + 1,
22811
22949
  body: line.trim(),
22812
- docstring: ""
22950
+ docstring: "",
22951
+ parent: ""
22813
22952
  });
22814
22953
  openQuote = yamlOpenQuoteAfter(line, match2[0].length);
22815
22954
  }
@@ -22861,7 +23000,8 @@ function extractTomlSymbols(content, filePath) {
22861
23000
  lineStart: lineNum + 1,
22862
23001
  lineEnd: lineNum + 1,
22863
23002
  body: line.trim(),
22864
- docstring: ""
23003
+ docstring: "",
23004
+ parent: ""
22865
23005
  });
22866
23006
  }
22867
23007
  const keyMatch = /^\s*([a-zA-Z_][\w-]*)\s*=/.exec(line);
@@ -22873,7 +23013,8 @@ function extractTomlSymbols(content, filePath) {
22873
23013
  lineStart: lineNum + 1,
22874
23014
  lineEnd: lineNum + 1,
22875
23015
  body: line.trim(),
22876
- docstring: ""
23016
+ docstring: "",
23017
+ parent: ""
22877
23018
  });
22878
23019
  }
22879
23020
  }
@@ -22947,7 +23088,8 @@ function extractCssSymbols(content, filePath) {
22947
23088
  lineStart: p.line,
22948
23089
  lineEnd: p.line,
22949
23090
  body: p.body,
22950
- docstring: ""
23091
+ docstring: "",
23092
+ parent: ""
22951
23093
  });
22952
23094
  }
22953
23095
  pending = [];
@@ -22964,7 +23106,8 @@ function extractCssSymbols(content, filePath) {
22964
23106
  lineStart: i + 1,
22965
23107
  lineEnd: i + 1,
22966
23108
  body: line.trim(),
22967
- docstring: ""
23109
+ docstring: "",
23110
+ parent: ""
22968
23111
  });
22969
23112
  }
22970
23113
  }
@@ -22979,7 +23122,8 @@ function extractCssSymbols(content, filePath) {
22979
23122
  lineStart: p.line,
22980
23123
  lineEnd: p.line,
22981
23124
  body: p.body,
22982
- docstring: ""
23125
+ docstring: "",
23126
+ parent: ""
22983
23127
  });
22984
23128
  }
22985
23129
  pending = [];
@@ -23023,7 +23167,8 @@ function extractDockerfileSymbols(content, filePath) {
23023
23167
  lineStart: i + 1,
23024
23168
  lineEnd: i + 1,
23025
23169
  body: line.trim(),
23026
- docstring: ""
23170
+ docstring: "",
23171
+ parent: ""
23027
23172
  });
23028
23173
  }
23029
23174
  continuing = !isComment2 && line.trimEnd().endsWith("\\");
@@ -23036,7 +23181,7 @@ function extractWithRegex(content, filePath) {
23036
23181
  for (let i = 0; i < lines2.length; i++) {
23037
23182
  const line = lines2[i];
23038
23183
  if (line === void 0) continue;
23039
- for (const { re, kind } of FALLBACK_PATTERNS) {
23184
+ for (const { re, kind, style } of FALLBACK_PATTERNS) {
23040
23185
  const m = re.exec(line);
23041
23186
  if (m !== null && m[1] !== void 0) {
23042
23187
  out2.push({
@@ -23046,7 +23191,8 @@ function extractWithRegex(content, filePath) {
23046
23191
  lineStart: i + 1,
23047
23192
  lineEnd: i + 1,
23048
23193
  body: line.trim(),
23049
- docstring: ""
23194
+ docstring: precedingDocComment(lines2, i + 1, style),
23195
+ parent: ""
23050
23196
  });
23051
23197
  break;
23052
23198
  }
@@ -23101,17 +23247,17 @@ function parseContent(content, filePath, language) {
23101
23247
  if (language === "python") {
23102
23248
  symbols = extractPythonSymbols(root, filePath);
23103
23249
  } else if (language === "go") {
23104
- symbols = extractGoSymbols(root, filePath);
23250
+ symbols = extractGoSymbols(root, filePath, content.split(/\r?\n/));
23105
23251
  } else if (language === "rust") {
23106
- symbols = extractRustSymbols(root, filePath);
23252
+ symbols = extractRustSymbols(root, filePath, content.split(/\r?\n/));
23107
23253
  } else if (language === "ruby") {
23108
- symbols = extractRubySymbols(root, filePath);
23254
+ symbols = extractRubySymbols(root, filePath, content.split(/\r?\n/));
23109
23255
  } else if (language === "java") {
23110
- symbols = extractJavaSymbols(root, filePath);
23256
+ symbols = extractJavaSymbols(root, filePath, content.split(/\r?\n/));
23111
23257
  } else if (language === "cpp" || language === "c") {
23112
- symbols = extractCppSymbols(root, filePath);
23258
+ symbols = extractCppSymbols(root, filePath, content.split(/\r?\n/));
23113
23259
  } else {
23114
- symbols = extractTsJsSymbols(root, filePath);
23260
+ symbols = extractTsJsSymbols(root, filePath, content.split(/\r?\n/));
23115
23261
  }
23116
23262
  const refs = REF_LANGUAGES.has(language) ? extractRefs(root, filePath, language) : [];
23117
23263
  const parsed = { symbols, refs };
@@ -23130,7 +23276,8 @@ function sectionsToHeadingSymbols(sections, filePath) {
23130
23276
  lineStart: s.line,
23131
23277
  lineEnd: s.endLine,
23132
23278
  body: "",
23133
- docstring: ""
23279
+ docstring: "",
23280
+ parent: ""
23134
23281
  }));
23135
23282
  }
23136
23283
  function extractNoTreeSitter(content, filePath, language) {
@@ -23166,6 +23313,7 @@ function isUnderSkipDir(filePath, skipDirs) {
23166
23313
  }
23167
23314
  function isParseSkipEligible(filePath, cfg) {
23168
23315
  if (isUnderSkipDir(filePath, cfg.skip_dirs)) return true;
23316
+ if (cfg.skip_files.includes(path31.basename(filePath))) return true;
23169
23317
  try {
23170
23318
  const stat2 = fs26.statSync(filePath);
23171
23319
  if (stat2.size > cfg.large_file_skip_kb * 1024) return true;
@@ -23184,11 +23332,20 @@ function writeParseResult(filePath, content, result, dbPath) {
23184
23332
  "INSERT INTO files (path, sha, mtime, language, indexed_at) VALUES (?, ?, ?, ?, ?)"
23185
23333
  ).run(filePath, sha, mtime, result.language, now);
23186
23334
  const insSym = db.prepare(
23187
- "INSERT INTO symbols (file_path, name, kind, line_start, line_end, body, docstring) VALUES (?, ?, ?, ?, ?, ?, ?)"
23335
+ "INSERT INTO symbols (file_path, name, kind, line_start, line_end, body, docstring, parent) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
23188
23336
  );
23189
23337
  for (const s of result.symbols) {
23190
23338
  if (s.name === "" || s.kind === "") continue;
23191
- insSym.run(s.filePath, s.name, s.kind, s.lineStart, s.lineEnd, boundSymbolBody(s.body), boundSymbolDocstring(s.docstring));
23339
+ insSym.run(
23340
+ s.filePath,
23341
+ s.name,
23342
+ s.kind,
23343
+ s.lineStart,
23344
+ s.lineEnd,
23345
+ boundSymbolBody(s.body),
23346
+ boundSymbolDocstring(s.docstring),
23347
+ s.parent
23348
+ );
23192
23349
  }
23193
23350
  const insRef = db.prepare(
23194
23351
  "INSERT INTO refs (file_path, name, line, col, context) VALUES (?, ?, ?, ?, ?)"
@@ -23347,6 +23504,7 @@ var init_parser = __esm({
23347
23504
  init_sql_path();
23348
23505
  init_markdown_lines();
23349
23506
  init_parser_types();
23507
+ init_doc_comment();
23350
23508
  init_index_reader();
23351
23509
  init_markdown_hints();
23352
23510
  init_csharp();
@@ -23376,6 +23534,7 @@ var init_parser = __esm({
23376
23534
  init_sfc_idx();
23377
23535
  init_ipynb_idx();
23378
23536
  init_util2();
23537
+ init_doc_comment();
23379
23538
  _require4 = createRequire5(import.meta.url);
23380
23539
  _grammarCache = /* @__PURE__ */ new Map();
23381
23540
  CPP_HEADER_SNIFF_RE = /\bclass\s+\w|\bnamespace\s+\w|\btemplate\s*<|::\s*\w|\b(?:public|private|protected)\s*:/;
@@ -23818,28 +23977,31 @@ var init_parser = __esm({
23818
23977
  EMPTY_STRING_SET = /* @__PURE__ */ new Set();
23819
23978
  FALLBACK_PATTERNS = [
23820
23979
  // Python
23821
- { re: /^[ \t]*(?:async\s+)?def\s+([A-Za-z_]\w*)/, kind: "function" },
23822
- { re: /^[ \t]*class\s+([A-Za-z_]\w*)/, kind: "class" },
23980
+ { re: /^[ \t]*(?:async\s+)?def\s+([A-Za-z_]\w*)/, kind: "function", style: "hash" },
23981
+ { re: /^[ \t]*class\s+([A-Za-z_]\w*)/, kind: "class", style: "hash" },
23823
23982
  // TS/JS function & class declarations (optionally exported/async)
23824
23983
  {
23825
23984
  re: /^[ \t]*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/,
23826
- kind: "function"
23985
+ kind: "function",
23986
+ style: "c"
23827
23987
  },
23828
23988
  {
23829
23989
  re: /^[ \t]*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/,
23830
- kind: "class"
23990
+ kind: "class",
23991
+ style: "c"
23831
23992
  },
23832
- { re: /^[ \t]*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/, kind: "interface" },
23833
- { re: /^[ \t]*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/, kind: "type" },
23993
+ { re: /^[ \t]*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/, kind: "interface", style: "c" },
23994
+ { re: /^[ \t]*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/, kind: "type", style: "c" },
23834
23995
  // const/let/var bound to an arrow or function expression
23835
23996
  {
23836
23997
  re: /^[ \t]*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/,
23837
- kind: "function"
23998
+ kind: "function",
23999
+ style: "c"
23838
24000
  },
23839
24001
  // Rust / Go function & struct/type patterns
23840
- { re: /^[ \t]*(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_]\w*)/, kind: "function" },
23841
- { re: /^[ \t]*(?:pub\s+)?struct\s+([A-Za-z_]\w*)/, kind: "struct" },
23842
- { re: /^[ \t]*func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/, kind: "function" }
24002
+ { re: /^[ \t]*(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_]\w*)/, kind: "function", style: "c" },
24003
+ { re: /^[ \t]*(?:pub\s+)?struct\s+([A-Za-z_]\w*)/, kind: "struct", style: "c" },
24004
+ { re: /^[ \t]*func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/, kind: "function", style: "c" }
23843
24005
  ];
23844
24006
  NO_TREE_SITTER_EXTRACTORS = {
23845
24007
  markdown: extractMarkdownSymbols,
@@ -30020,6 +30182,8 @@ function findParentName(entry, fileSymbols) {
30020
30182
  }
30021
30183
  }
30022
30184
  if (best !== null) return best.name;
30185
+ const parent = (entry.parent ?? "").trim();
30186
+ if (parent !== "") return parent;
30023
30187
  const doc = entry.docstring.trim();
30024
30188
  if (doc !== "" && PARENT_IDENTIFIER_RE.test(doc)) return doc;
30025
30189
  return null;
@@ -30096,7 +30260,9 @@ function resolveSymbolSpec(spec, forceRefresh, projectRoot) {
30096
30260
  });
30097
30261
  const symBaseLower = symBase.toLowerCase();
30098
30262
  const scoped = candidates.filter((c) => {
30099
- if (c.docstring.toLowerCase() === symBaseLower) return true;
30263
+ const cParent = c.parent ?? "";
30264
+ if (cParent.toLowerCase() === symBaseLower) return true;
30265
+ if (cParent === "" && c.docstring.toLowerCase() === symBaseLower) return true;
30100
30266
  return containers.some(
30101
30267
  (cls) => cls.filePath === c.filePath && c.lineStart >= cls.lineStart && c.lineEnd <= cls.lineEnd
30102
30268
  );
@@ -30109,6 +30275,10 @@ function runRead(opts) {
30109
30275
  const range2 = parseLineRange(opts.spec);
30110
30276
  if (range2 !== null) return runLineRange(range2, opts);
30111
30277
  const { file: file2, symbol: symbol3 } = parseReadSpec(opts.spec);
30278
+ if (symbol3 !== void 0 && symbol3 !== "" && symbol3.includes(",") && parseColonLineRange(symbol3) === null) {
30279
+ const multiSymbols = symbol3.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
30280
+ if (multiSymbols.length > 1) return runReadMulti(file2, multiSymbols, opts);
30281
+ }
30112
30282
  if (symbol3 === void 0 || symbol3 === "") {
30113
30283
  const text2 = readFileText(file2);
30114
30284
  if (text2 === null) {
@@ -30136,22 +30306,55 @@ function runRead(opts) {
30136
30306
  }
30137
30307
  const match2 = resolution.entry;
30138
30308
  const fullSourceBytes = sumFileSizes([match2.filePath]);
30309
+ const refCounts = opts.stats === true ? queryRefCounts([match2.name], globalDbPath(), resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() })) : void 0;
30139
30310
  if (opts.json === true) {
30140
- const text2 = JSON.stringify({ ...match2, body: resolveBody(match2) }, null, 2);
30141
- recordReadStat("read_replacement", fullSourceBytes, text2, opts.spec);
30311
+ const text2 = JSON.stringify(
30312
+ {
30313
+ ...match2,
30314
+ body: resolveBody(match2),
30315
+ ...refCounts !== void 0 ? { refCount: refCounts.get(match2.name) ?? 0 } : {}
30316
+ },
30317
+ null,
30318
+ 2
30319
+ );
30320
+ if (opts.suppressStat !== true) recordReadStat("read_replacement", fullSourceBytes, text2, opts.spec);
30142
30321
  return { text: text2, code: 0 };
30143
30322
  }
30144
30323
  const body = resolveBody(match2);
30145
30324
  const bodyLen = match2.lineEnd - match2.lineStart + 1;
30325
+ const statsStr = formatStatsSuffix(refCounts, match2);
30146
30326
  const lines2 = [
30147
- `# ${bodyLen} lines (~${Math.ceil(body.length / 4)} tok)`,
30327
+ `# ${bodyLen} lines (~${Math.ceil(body.length / 4)} tok)${statsStr}`,
30148
30328
  body
30149
30329
  ];
30150
30330
  const warning = staleWarning(match2.filePath);
30151
30331
  const text = guardText(warning + trimBlankLines(lines2).join("\n"), "symbol");
30152
- recordReadStat("read_replacement", fullSourceBytes, text, opts.spec);
30332
+ if (opts.suppressStat !== true) recordReadStat("read_replacement", fullSourceBytes, text, opts.spec);
30153
30333
  return { text, code: 0 };
30154
30334
  }
30335
+ function runReadMulti(file2, symbols, opts) {
30336
+ let anyFound = false;
30337
+ const jsonOut = {};
30338
+ const textBlocks = [];
30339
+ for (const sym of symbols) {
30340
+ const sub = runRead({ ...opts, spec: `${file2}::${sym}`, suppressStat: true });
30341
+ if (sub.code === 0) anyFound = true;
30342
+ if (opts.json === true) {
30343
+ jsonOut[sym] = sub.code === 0 ? JSON.parse(sub.text) : { error: sub.text };
30344
+ continue;
30345
+ }
30346
+ textBlocks.push(`${sym}:
30347
+ ${sub.text}`);
30348
+ }
30349
+ if (anyFound) {
30350
+ const fullSourceBytes = sumFileSizes([resolveIndexPath(file2, opts.projectRoot ?? process.cwd())]);
30351
+ const text2 = opts.json === true ? JSON.stringify(jsonOut, null, 2) : textBlocks.join("\n\n");
30352
+ recordReadStat("read_replacement", fullSourceBytes, text2, opts.spec);
30353
+ return { text: text2, code: 0 };
30354
+ }
30355
+ const text = opts.json === true ? JSON.stringify(jsonOut, null, 2) : textBlocks.join("\n\n");
30356
+ return { text, code: 1 };
30357
+ }
30155
30358
  function runSection(opts) {
30156
30359
  const colonIdx = findSpecSeparator(opts.spec);
30157
30360
  if (colonIdx === -1) {
@@ -30354,6 +30557,13 @@ function noSymbolsMessage(displayPath, resolvedPath) {
30354
30557
  }
30355
30558
  return `No indexed symbols found in '${displayPath}'`;
30356
30559
  }
30560
+ function hasRealDocstring(docstring) {
30561
+ const doc = docstring.trim();
30562
+ return doc !== "" && !PARENT_IDENTIFIER_RE.test(doc);
30563
+ }
30564
+ function formatStatsSuffix(refCounts, sym) {
30565
+ return refCounts !== void 0 ? ` [${refCounts.get(sym.name) ?? 0} refs, ${hasRealDocstring(sym.docstring) ? "documented" : "undocumented"}]` : "";
30566
+ }
30357
30567
  function prepareSymbolListing(file2, opts) {
30358
30568
  const resolved = resolveIndexPath(file2, opts.projectRoot ?? process.cwd());
30359
30569
  if (opts.forceRefresh === true) {
@@ -30386,7 +30596,7 @@ function runSkeleton(opts) {
30386
30596
  kind: s.kind,
30387
30597
  lineStart: s.lineStart,
30388
30598
  lineEnd: s.lineEnd,
30389
- ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: s.docstring.trim().length > 0 } : {}
30599
+ ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: hasRealDocstring(s.docstring) } : {}
30390
30600
  }));
30391
30601
  const capped = guardJsonRows(rows);
30392
30602
  const payload = {
@@ -30402,7 +30612,7 @@ function runSkeleton(opts) {
30402
30612
  const lines2 = [`# Skeleton: ${opts.file} (${filtered.length} symbols, ${totalLines} lines)`];
30403
30613
  for (const sym of filtered) {
30404
30614
  const lineStr = sym.lineStart.toString().padStart(6);
30405
- const statsStr = refCounts !== void 0 ? ` [${refCounts.get(sym.name) ?? 0} refs, ${sym.docstring.trim().length > 0 ? "documented" : "undocumented"}]` : "";
30615
+ const statsStr = formatStatsSuffix(refCounts, sym);
30406
30616
  lines2.push(` ${lineStr} ${sym.kind.padEnd(10)} ${sym.name} ${firstBodyLine(sym.body)}${statsStr}`);
30407
30617
  }
30408
30618
  const text = guardText(staleWarning(resolved) + lines2.join("\n"), "symbol");
@@ -30419,7 +30629,7 @@ function runOutline(opts) {
30419
30629
  const rows = refCounts !== void 0 ? filtered.map((s) => ({
30420
30630
  ...s,
30421
30631
  refCount: refCounts.get(s.name) ?? 0,
30422
- hasDoc: s.docstring.trim().length > 0
30632
+ hasDoc: hasRealDocstring(s.docstring)
30423
30633
  })) : filtered;
30424
30634
  const capped = guardJsonRows(rows);
30425
30635
  const payload = {
@@ -30436,8 +30646,8 @@ function runOutline(opts) {
30436
30646
  const rangeStr = `${sym.lineStart.toString().padStart(4)}-${sym.lineEnd.toString().padEnd(6)}`;
30437
30647
  const kindStr = sym.kind.padEnd(14);
30438
30648
  const bodyLen = sym.lineEnd - sym.lineStart + 1;
30439
- const docFirst = sym.docstring ? ` # ${sym.docstring.split("\n")[0] ?? ""}` : "";
30440
- const statsStr = refCounts !== void 0 ? ` [${refCounts.get(sym.name) ?? 0} refs, ${sym.docstring.trim().length > 0 ? "documented" : "undocumented"}]` : "";
30649
+ const docFirst = hasRealDocstring(sym.docstring) ? ` # ${sym.docstring.split("\n")[0] ?? ""}` : "";
30650
+ const statsStr = formatStatsSuffix(refCounts, sym);
30441
30651
  lines2.push(` ${rangeStr} ${kindStr} ${sym.name} (${bodyLen}\u2113)${docFirst}${statsStr}`);
30442
30652
  }
30443
30653
  const text = guardText(staleWarning(resolved) + lines2.join("\n"), "symbol");
@@ -47447,21 +47657,23 @@ function createMcpServer() {
47447
47657
  server.registerTool(
47448
47658
  "read",
47449
47659
  {
47450
- description: "Read one symbol's full body, given a spec of the form file::symbol, or a line range file@N-M / file@N, or a bare file path.",
47660
+ description: "Read one symbol's full body, given a spec of the form file::symbol, or a line range file@N-M / file@N, or a bare file path. Pass a comma-separated spec (file::a,b) to fetch several symbols' bodies from one file in a single call.",
47451
47661
  inputSchema: {
47452
- spec: external_exports.string().describe("file::symbol, file@N-M, file@N, or a bare file path"),
47662
+ spec: external_exports.string().describe("file::symbol, file@N-M, file@N, a bare file path, or comma-separated file::a,b for a merged multi-symbol view"),
47453
47663
  json: external_exports.boolean().optional().describe("output as JSON"),
47454
47664
  forceRefresh: external_exports.boolean().optional().describe("reparse file from disk before querying (ignore stale index)"),
47665
+ stats: external_exports.boolean().optional().describe("add per-symbol reference count and doc-coverage flag"),
47455
47666
  projectRoot: projectRootField
47456
47667
  }
47457
47668
  },
47458
47669
  (args) => {
47459
- const { spec, json: json2, forceRefresh, projectRoot } = args;
47670
+ const { spec, json: json2, forceRefresh, stats, projectRoot } = args;
47460
47671
  return toCallToolResult(
47461
47672
  runRead({
47462
47673
  spec,
47463
47674
  ...json2 === true ? { json: true } : {},
47464
47675
  ...forceRefresh === true ? { forceRefresh: true } : {},
47676
+ ...stats === true ? { stats: true } : {},
47465
47677
  ...projectRoot !== void 0 ? { projectRoot } : {}
47466
47678
  })
47467
47679
  );
@@ -49460,7 +49672,7 @@ async function runDoctorAndExit(opts) {
49460
49672
  }
49461
49673
 
49462
49674
  // src/hooks_session_start.ts
49463
- 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.';
49675
+ var GENERIC_REMINDER = 'token-goat: prefer surgical reads over the Read/Grep tools on this codebase; shell commands like `rg`, `grep`, `fd`, `sed`, `cat`, `find`, and `ls` are just commands, not tool names -- `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.';
49464
49676
  function buildReminder(cwd) {
49465
49677
  if (cwd === void 0) return GENERIC_REMINDER;
49466
49678
  let symbolCount;
@@ -49470,7 +49682,7 @@ function buildReminder(cwd) {
49470
49682
  return GENERIC_REMINDER;
49471
49683
  }
49472
49684
  if (symbolCount <= 0) return GENERIC_REMINDER;
49473
- 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.`;
49685
+ 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 tool call; shell commands like \`rg\`, \`grep\`, \`fd\`, \`sed\`, \`cat\`, \`find\`, and \`ls\` are still just commands.`;
49474
49686
  }
49475
49687
  function sessionStartHandler(event) {
49476
49688
  try {
@@ -69321,16 +69533,16 @@ function isTempPath(fp) {
69321
69533
  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);
69322
69534
  }
69323
69535
  function isOrchestratorStateFile(filePath) {
69324
- const basename20 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
69325
- return /^\.improve-state-/.test(basename20);
69536
+ const basename21 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
69537
+ return /^\.improve-state-/.test(basename21);
69326
69538
  }
69327
69539
  function extractCatSourceFile(cmd) {
69328
69540
  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);
69329
69541
  return m?.[1] ?? null;
69330
69542
  }
69331
69543
  function classifyFileExtensions(filePath) {
69332
- const basename20 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
69333
- const isEnvFile = /^\.env(\.\w+)?$/i.test(basename20);
69544
+ const basename21 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
69545
+ const isEnvFile = /^\.env(\.\w+)?$/i.test(basename21);
69334
69546
  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);
69335
69547
  if (!hasKnownExt && !isEnvFile) return null;
69336
69548
  const isSql = /\.sql$/i.test(filePath);
@@ -71011,8 +71223,8 @@ ${redactSecrets(compressed).text}`
71011
71223
  }
71012
71224
  return passOutput();
71013
71225
  }
71014
- registerHook("pre_tool_use", preMcpHandler);
71015
- registerHook("post_tool_use", postMcpHandler);
71226
+ registerHook("pre_tool_use", preMcpHandler, { toolPattern: "^mcp__" });
71227
+ registerHook("post_tool_use", postMcpHandler, { toolPattern: "^mcp__" });
71016
71228
 
71017
71229
  // src/hooks_websearch.ts
71018
71230
  init_define_import_meta_env();
@@ -71095,7 +71307,7 @@ function preScreenshotHandler(event) {
71095
71307
  `${toolName} was called with no destination file, so the screenshot would land raw in context (tens of thousands of tokens). Re-issue it with \`${paramName}\` set to an absolute path, then Read the saved file \u2014 the read is automatically compressed by token-goat's image-shrink pipeline.`
71096
71308
  );
71097
71309
  }
71098
- registerHook("pre_tool_use", preScreenshotHandler);
71310
+ registerHook("pre_tool_use", preScreenshotHandler, { toolPattern: "^mcp__" });
71099
71311
 
71100
71312
  // src/hooks_browser_image.ts
71101
71313
  init_define_import_meta_env();
@@ -71173,7 +71385,7 @@ async function postBrowserImageHandler(event) {
71173
71385
  return passOutput();
71174
71386
  }
71175
71387
  }
71176
- registerHook("post_tool_use", postBrowserImageHandler);
71388
+ registerHook("post_tool_use", postBrowserImageHandler, { toolPattern: "^mcp__" });
71177
71389
 
71178
71390
  // src/hooks_agent_spawn.ts
71179
71391
  init_define_import_meta_env();
@@ -73199,8 +73411,8 @@ function formatTopFiles(ranked) {
73199
73411
  if (ranked.length === 0) return "";
73200
73412
  const lines2 = ["Top files this session:"];
73201
73413
  for (const { path: filePath, count } of ranked) {
73202
- const basename20 = path49.basename(filePath);
73203
- lines2.push(` ${count.toString().padStart(3)}x ${basename20} (${filePath})`);
73414
+ const basename21 = path49.basename(filePath);
73415
+ lines2.push(` ${count.toString().padStart(3)}x ${basename21} (${filePath})`);
73204
73416
  }
73205
73417
  return lines2.join("\n");
73206
73418
  }
@@ -81733,9 +81945,16 @@ function buildProgram() {
81733
81945
  )
81734
81946
  );
81735
81947
  program2.command("read <spec>").description(
81736
- "read one symbol's full body (spec: file::symbol; disambiguate a name shared by several classes with file::Parent.symbol)"
81737
- ).option("-j, --json", "output as JSON").option("--force-refresh", "reparse file from disk before querying (ignore stale index)").action(
81738
- (spec, opts) => runExitText(() => runRead({ spec, ...opts.json === true ? { json: true } : {}, ...opts.forceRefresh === true ? { forceRefresh: true } : {} }))
81948
+ "read one symbol's full body (spec: file::symbol; disambiguate a name shared by several classes with file::Parent.symbol; comma-separated file::a,b for a merged multi-symbol view)"
81949
+ ).option("-j, --json", "output as JSON").option("--force-refresh", "reparse file from disk before querying (ignore stale index)").option("--stats", "add per-symbol reference count and doc-coverage flag").action(
81950
+ (spec, opts) => runExitText(
81951
+ () => runRead({
81952
+ spec,
81953
+ ...opts.json === true ? { json: true } : {},
81954
+ ...opts.forceRefresh === true ? { forceRefresh: true } : {},
81955
+ ...opts.stats === true ? { stats: true } : {}
81956
+ })
81957
+ )
81739
81958
  );
81740
81959
  program2.command("brief <spec>").description("symbol body + callers + containing doc section in one call (spec: file::symbol)").option("-j, --json", "output as JSON").option("--limit <n>", "max callers to show (default: 20)").action(
81741
81960
  (spec, opts) => runExit(