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.
@@ -49,7 +49,7 @@ var init_define_import_meta_env = __esm({
49
49
  import { createRequire } from "node:module";
50
50
  function resolveVersion() {
51
51
  if (true) {
52
- return "2.6.21";
52
+ return "2.6.22";
53
53
  }
54
54
  const require2 = createRequire(import.meta.url);
55
55
  const pkg = require2("../package.json");
@@ -1907,10 +1907,12 @@ function buildGuidanceBody(fallbackToolClause) {
1907
1907
  "",
1908
1908
  `This gate decides *whether* to reach for a read tool at all. ${fallbackToolClause} only pick the *fallback* once token-goat has been ruled out for this read \u2014 they never authorize skipping the gate.`,
1909
1909
  "",
1910
+ "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.",
1911
+ "",
1910
1912
  "Exemptions (gate passes, read directly): the file is under ~200 lines and you need all of it; it was never indexed (new, untracked, or generated this turn); it is binary or an image; the target has no symbol handle (e.g. a literal mid-function).",
1911
1913
  "",
1912
1914
  "Failure shapes to catch yourself in, and the command that replaces each:",
1913
- '- grep/search with context flags to find a function body \u2192 `read "file::symbol"`',
1915
+ '- shell `rg`/`grep` search with context flags to find a function body \u2192 `read "file::symbol"`',
1914
1916
  '- paging one function with view/view_range \u2192 `read "file::symbol"`',
1915
1917
  '- reading one heading of a large doc \u2192 `section "file::Heading"`',
1916
1918
  "- searching for a symbol's callers \u2192 `refs file::symbol --callers`",
@@ -1935,6 +1937,113 @@ var init_guidance_block = __esm({
1935
1937
  }
1936
1938
  });
1937
1939
 
1940
+ // src/hook_registry.ts
1941
+ function registerHook(eventName, handler, opts) {
1942
+ let list = _handlers.get(eventName);
1943
+ if (list === void 0) {
1944
+ list = [];
1945
+ _handlers.set(eventName, list);
1946
+ }
1947
+ list.push({
1948
+ handler,
1949
+ toolName: opts?.toolName,
1950
+ toolPattern: opts?.toolPattern,
1951
+ advisory: opts?.advisory === true,
1952
+ followsMatcher: opts?.followsMatcher === true
1953
+ });
1954
+ }
1955
+ function toolMatcherFor(eventName) {
1956
+ const list = _handlers.get(eventName);
1957
+ if (list === void 0 || list.length === 0) return null;
1958
+ const parts = [];
1959
+ for (const { toolName, toolPattern, followsMatcher } of list) {
1960
+ if (followsMatcher) continue;
1961
+ if (toolName === void 0 && toolPattern === void 0) return null;
1962
+ for (const part of [toolName === void 0 ? void 0 : `^${toolName}$`, toolPattern]) {
1963
+ if (part !== void 0 && part !== "" && !parts.includes(part)) parts.push(part);
1964
+ }
1965
+ }
1966
+ if (parts.length === 0) return null;
1967
+ return parts.join("|");
1968
+ }
1969
+ async function runHook(event) {
1970
+ const list = _handlers.get(event.eventName);
1971
+ if (list === void 0) return { hookType: "pass" };
1972
+ let advisoryResult;
1973
+ for (const { handler, toolName, advisory } of list) {
1974
+ if (toolName !== void 0 && toolName !== event.toolName) continue;
1975
+ const result = await handler(event);
1976
+ if (result.hookType !== "pass") {
1977
+ if (advisory) {
1978
+ advisoryResult = result;
1979
+ continue;
1980
+ }
1981
+ return result;
1982
+ }
1983
+ }
1984
+ return advisoryResult ?? { hookType: "pass" };
1985
+ }
1986
+ function clearHooks() {
1987
+ _handlers.clear();
1988
+ }
1989
+ function serializeOutput(output, eventName) {
1990
+ switch (output.hookType) {
1991
+ case "deny":
1992
+ return JSON.stringify({ decision: "block", reason: output.message });
1993
+ case "context":
1994
+ if (EVENTS_WITHOUT_ADDITIONAL_CONTEXT.has(eventName)) {
1995
+ return JSON.stringify({ systemMessage: output.context });
1996
+ }
1997
+ return JSON.stringify({
1998
+ hookSpecificOutput: {
1999
+ hookEventName: CLAUDE_CODE_EVENT_NAMES[eventName],
2000
+ additionalContext: output.context
2001
+ }
2002
+ });
2003
+ case "rewriteInput":
2004
+ return JSON.stringify({
2005
+ hookSpecificOutput: {
2006
+ hookEventName: "PreToolUse",
2007
+ permissionDecision: "allow",
2008
+ updatedInput: output.updatedInput
2009
+ }
2010
+ });
2011
+ case "rewriteOutput":
2012
+ return JSON.stringify({
2013
+ hookSpecificOutput: {
2014
+ hookEventName: "PostToolUse",
2015
+ updatedToolOutput: output.updatedOutput
2016
+ }
2017
+ });
2018
+ case "pass":
2019
+ return JSON.stringify({});
2020
+ }
2021
+ }
2022
+ var _handlers, CLAUDE_CODE_EVENT_NAMES, EVENTS_WITHOUT_ADDITIONAL_CONTEXT;
2023
+ var init_hook_registry = __esm({
2024
+ "src/hook_registry.ts"() {
2025
+ "use strict";
2026
+ init_define_import_meta_env();
2027
+ init_reset();
2028
+ _handlers = /* @__PURE__ */ new Map();
2029
+ registerReset(clearHooks);
2030
+ CLAUDE_CODE_EVENT_NAMES = {
2031
+ pre_tool_use: "PreToolUse",
2032
+ post_tool_use: "PostToolUse",
2033
+ notification: "Notification",
2034
+ stop: "Stop",
2035
+ pre_compact: "PreCompact",
2036
+ user_prompt_submit: "UserPromptSubmit",
2037
+ subagent_stop: "SubagentStop",
2038
+ session_start: "SessionStart"
2039
+ };
2040
+ EVENTS_WITHOUT_ADDITIONAL_CONTEXT = /* @__PURE__ */ new Set([
2041
+ "notification",
2042
+ "pre_compact"
2043
+ ]);
2044
+ }
2045
+ });
2046
+
1938
2047
  // src/install.ts
1939
2048
  import * as fs2 from "node:fs";
1940
2049
  import * as os2 from "node:os";
@@ -2016,13 +2125,28 @@ function installHooks(scope = "user") {
2016
2125
  }
2017
2126
  }
2018
2127
  if (groupHasTokenGoat(groups, isCurrentTokenGoatHookCommand)) {
2019
- if (strippedLegacy) {
2128
+ const narrowed = toolMatcherFor(eventArg);
2129
+ let renarrowed = false;
2130
+ if (narrowed !== null) {
2131
+ for (let i = 0; i < groups.length; i++) {
2132
+ const group = groups[i];
2133
+ if (group === void 0) continue;
2134
+ const ownHooks = group.hooks ?? [];
2135
+ const isOwnGroup = ownHooks.length > 0 && ownHooks.every((h) => isCurrentTokenGoatHookCommand(h.command));
2136
+ if (isOwnGroup && group.matcher !== narrowed) {
2137
+ groups[i] = { ...group, matcher: narrowed };
2138
+ renarrowed = true;
2139
+ }
2140
+ }
2141
+ }
2142
+ if (strippedLegacy || renarrowed) {
2020
2143
  hooks[eventKey] = groups;
2021
2144
  changed = true;
2022
2145
  }
2023
2146
  continue;
2024
2147
  }
2025
- groups.push({ matcher: "", hooks: [{ type: "command", command: hookCommand(eventArg) }] });
2148
+ const matcher = toolMatcherFor(eventArg) ?? "";
2149
+ groups.push({ matcher, hooks: [{ type: "command", command: hookCommand(eventArg) }] });
2026
2150
  hooks[eventKey] = groups;
2027
2151
  changed = true;
2028
2152
  }
@@ -2155,6 +2279,7 @@ var init_install = __esm({
2155
2279
  "use strict";
2156
2280
  init_define_import_meta_env();
2157
2281
  init_guidance_block();
2282
+ init_hook_registry();
2158
2283
  init_paths();
2159
2284
  init_util2();
2160
2285
  HOOK_EVENT_MAP = [
@@ -2176,6 +2301,20 @@ var init_install = __esm({
2176
2301
  SKILL_MD_FRONTMATTER = `---
2177
2302
  name: token-goat
2178
2303
  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.
2304
+ allowed-tools:
2305
+ - symbol
2306
+ - read
2307
+ - section
2308
+ - semantic
2309
+ - outline
2310
+ - skeleton
2311
+ - map
2312
+ - refs
2313
+ - changed
2314
+ - config-get
2315
+ - bash-output
2316
+ - web-output
2317
+ - gdrive-sections
2179
2318
  ---`;
2180
2319
  SKILL_MD_CONTENT = `${SKILL_MD_FRONTMATTER}
2181
2320
 
@@ -2330,7 +2469,7 @@ function buildAgentsBlock() {
2330
2469
  return buildGuidanceBlock({
2331
2470
  beginMarker: AGENTS_BEGIN,
2332
2471
  endMarker: AGENTS_END,
2333
- fallbackToolClause: "Codex's own `shell` (cat/type), `apply_patch` preview, and `view_image` preferences"
2472
+ fallbackToolClause: "Codex's native `shell`, `apply_patch`, and `view_image` tools (shell commands like `cat`/`type` run inside `shell`)"
2334
2473
  });
2335
2474
  }
2336
2475
  function writeAgentsBlock(p) {
@@ -2720,11 +2859,16 @@ function copilotCliInstructionsPath(opts = {}) {
2720
2859
  return path6.join(path6.dirname(copilotCliHooksDir(opts)), "copilot-instructions.md");
2721
2860
  }
2722
2861
  function buildCopilotInstructionsBlock() {
2723
- return buildGuidanceBlock({
2724
- beginMarker: COPILOT_INSTRUCTIONS_BEGIN,
2725
- endMarker: COPILOT_INSTRUCTIONS_END,
2726
- fallbackToolClause: "Copilot CLI's own `view`, `grep`, and `glob` tool-preference rules (and `Get-Content`/`Select-String`)"
2727
- });
2862
+ return stripInlineCodeSpans(
2863
+ buildGuidanceBlock({
2864
+ beginMarker: COPILOT_INSTRUCTIONS_BEGIN,
2865
+ endMarker: COPILOT_INSTRUCTIONS_END,
2866
+ fallbackToolClause: "Copilot CLI's native `view`, `grep`, and `glob` tools (with PowerShell commands `Get-Content`/`Select-String` as search fallbacks)"
2867
+ })
2868
+ );
2869
+ }
2870
+ function stripInlineCodeSpans(text) {
2871
+ return text.replace(/`([^`]+)`/g, "$1");
2728
2872
  }
2729
2873
  function writeCopilotInstructionsBlock(p) {
2730
2874
  return upsertDelimitedBlock(p, COPILOT_INSTRUCTIONS_BEGIN, COPILOT_INSTRUCTIONS_END, buildCopilotInstructionsBlock());
@@ -3024,93 +3168,6 @@ var init_bridges = __esm({
3024
3168
  }
3025
3169
  });
3026
3170
 
3027
- // src/hook_registry.ts
3028
- function registerHook(eventName, handler, opts) {
3029
- let list = _handlers.get(eventName);
3030
- if (list === void 0) {
3031
- list = [];
3032
- _handlers.set(eventName, list);
3033
- }
3034
- list.push({ handler, toolName: opts?.toolName, advisory: opts?.advisory === true });
3035
- }
3036
- async function runHook(event) {
3037
- const list = _handlers.get(event.eventName);
3038
- if (list === void 0) return { hookType: "pass" };
3039
- let advisoryResult;
3040
- for (const { handler, toolName, advisory } of list) {
3041
- if (toolName !== void 0 && toolName !== event.toolName) continue;
3042
- const result = await handler(event);
3043
- if (result.hookType !== "pass") {
3044
- if (advisory) {
3045
- advisoryResult = result;
3046
- continue;
3047
- }
3048
- return result;
3049
- }
3050
- }
3051
- return advisoryResult ?? { hookType: "pass" };
3052
- }
3053
- function clearHooks() {
3054
- _handlers.clear();
3055
- }
3056
- function serializeOutput(output, eventName) {
3057
- switch (output.hookType) {
3058
- case "deny":
3059
- return JSON.stringify({ decision: "block", reason: output.message });
3060
- case "context":
3061
- if (EVENTS_WITHOUT_ADDITIONAL_CONTEXT.has(eventName)) {
3062
- return JSON.stringify({ systemMessage: output.context });
3063
- }
3064
- return JSON.stringify({
3065
- hookSpecificOutput: {
3066
- hookEventName: CLAUDE_CODE_EVENT_NAMES[eventName],
3067
- additionalContext: output.context
3068
- }
3069
- });
3070
- case "rewriteInput":
3071
- return JSON.stringify({
3072
- hookSpecificOutput: {
3073
- hookEventName: "PreToolUse",
3074
- permissionDecision: "allow",
3075
- updatedInput: output.updatedInput
3076
- }
3077
- });
3078
- case "rewriteOutput":
3079
- return JSON.stringify({
3080
- hookSpecificOutput: {
3081
- hookEventName: "PostToolUse",
3082
- updatedToolOutput: output.updatedOutput
3083
- }
3084
- });
3085
- case "pass":
3086
- return JSON.stringify({});
3087
- }
3088
- }
3089
- var _handlers, CLAUDE_CODE_EVENT_NAMES, EVENTS_WITHOUT_ADDITIONAL_CONTEXT;
3090
- var init_hook_registry = __esm({
3091
- "src/hook_registry.ts"() {
3092
- "use strict";
3093
- init_define_import_meta_env();
3094
- init_reset();
3095
- _handlers = /* @__PURE__ */ new Map();
3096
- registerReset(clearHooks);
3097
- CLAUDE_CODE_EVENT_NAMES = {
3098
- pre_tool_use: "PreToolUse",
3099
- post_tool_use: "PostToolUse",
3100
- notification: "Notification",
3101
- stop: "Stop",
3102
- pre_compact: "PreCompact",
3103
- user_prompt_submit: "UserPromptSubmit",
3104
- subagent_stop: "SubagentStop",
3105
- session_start: "SessionStart"
3106
- };
3107
- EVENTS_WITHOUT_ADDITIONAL_CONTEXT = /* @__PURE__ */ new Set([
3108
- "notification",
3109
- "pre_compact"
3110
- ]);
3111
- }
3112
- });
3113
-
3114
3171
  // src/env.ts
3115
3172
  function envStr(key, defaultVal) {
3116
3173
  const raw = process.env[key];
@@ -3815,6 +3872,7 @@ function _buildConfig(raw, projectRaw = {}) {
3815
3872
  ix.large_file_skip_kb = validatedInt(ix_raw["large_file_skip_kb"], ix.large_file_skip_kb, ...boundsOf("indexing.large_file_skip_kb"));
3816
3873
  ix.large_file_symbol_only_kb = Math.min(ix.large_file_symbol_only_kb, ix.large_file_skip_kb);
3817
3874
  ix.skip_dirs = validatedStrList(ix_raw["skip_dirs"], ix.skip_dirs);
3875
+ ix.skip_files = validatedStrList(ix_raw["skip_files"], ix.skip_files);
3818
3876
  ix.embeddings_enabled = validatedBool(ix_raw["embeddings_enabled"], ix.embeddings_enabled);
3819
3877
  ix.embeddings_enabled = envBool("TOKEN_GOAT_EMBEDDINGS_ENABLED", ix.embeddings_enabled);
3820
3878
  const cpr_raw = section(raw, "compression");
@@ -3990,6 +4048,7 @@ function saveConfig(config2) {
3990
4048
  large_file_symbol_only_kb: config2.indexing.large_file_symbol_only_kb,
3991
4049
  large_file_skip_kb: config2.indexing.large_file_skip_kb,
3992
4050
  skip_dirs: config2.indexing.skip_dirs,
4051
+ skip_files: config2.indexing.skip_files,
3993
4052
  embeddings_enabled: config2.indexing.embeddings_enabled
3994
4053
  },
3995
4054
  compression: {
@@ -4169,6 +4228,7 @@ var init_config = __esm({
4169
4228
  large_file_symbol_only_kb: 500,
4170
4229
  large_file_skip_kb: 2048,
4171
4230
  skip_dirs: [],
4231
+ skip_files: ["coverage.json", "coverage-final.json"],
4172
4232
  embeddings_enabled: true
4173
4233
  },
4174
4234
  compression: {
@@ -4525,7 +4585,8 @@ CREATE TABLE IF NOT EXISTS symbols (
4525
4585
  line_start INTEGER,
4526
4586
  line_end INTEGER,
4527
4587
  body TEXT,
4528
- docstring TEXT
4588
+ docstring TEXT,
4589
+ parent TEXT NOT NULL DEFAULT ''
4529
4590
  );
4530
4591
  CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
4531
4592
  CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path);
@@ -4728,12 +4789,14 @@ CREATE TRIGGER IF NOT EXISTS cache_recall_au AFTER UPDATE ON cache_recall BEGIN
4728
4789
  VALUES (new.row_id, new.label, new.content);
4729
4790
  END;
4730
4791
  `;
4731
- SCHEMA_VERSION = 8;
4792
+ SCHEMA_VERSION = 9;
4732
4793
  MIGRATIONS = {
4733
4794
  // 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.
4734
4795
  1: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN embed_sha TEXT"),
4735
4796
  // 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.
4736
- 2: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0")
4797
+ 2: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0"),
4798
+ // 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.
4799
+ 8: (conn) => alterTableIdempotent(conn, "ALTER TABLE symbols ADD COLUMN parent TEXT NOT NULL DEFAULT ''")
4737
4800
  };
4738
4801
  registerReset(closeAllDbs);
4739
4802
  }
@@ -7137,7 +7200,7 @@ var init_hint_stats = __esm({
7137
7200
  resolvePendingHintsForEvent(event);
7138
7201
  return passOutput();
7139
7202
  },
7140
- { advisory: true }
7203
+ { advisory: true, followsMatcher: true }
7141
7204
  );
7142
7205
  }
7143
7206
  });
@@ -7420,23 +7483,23 @@ function isNoisePath(inputPath) {
7420
7483
  }
7421
7484
  }
7422
7485
  const slashIdx = p.lastIndexOf("/");
7423
- const basename20 = slashIdx >= 0 ? p.slice(slashIdx + 1) : p;
7424
- if (NOISE_BASENAMES.has(basename20)) {
7486
+ const basename21 = slashIdx >= 0 ? p.slice(slashIdx + 1) : p;
7487
+ if (NOISE_BASENAMES.has(basename21)) {
7425
7488
  return true;
7426
7489
  }
7427
- if (basename20.startsWith(".improve-state-") || basename20.startsWith("improve_commit_msg_")) {
7490
+ if (basename21.startsWith(".improve-state-") || basename21.startsWith("improve_commit_msg_")) {
7428
7491
  return true;
7429
7492
  }
7430
- const dotIdx = basename20.lastIndexOf(".");
7493
+ const dotIdx = basename21.lastIndexOf(".");
7431
7494
  if (dotIdx >= 0) {
7432
- const ext2 = basename20.slice(dotIdx);
7495
+ const ext2 = basename21.slice(dotIdx);
7433
7496
  if (NOISE_EXTS.has(ext2)) {
7434
7497
  return true;
7435
7498
  }
7436
7499
  }
7437
7500
  for (const ext2 of NOISE_EXTS) {
7438
7501
  if (ext2.includes(".") && ext2.split(".").length > 2) {
7439
- if (basename20.endsWith(ext2)) {
7502
+ if (basename21.endsWith(ext2)) {
7440
7503
  return true;
7441
7504
  }
7442
7505
  }
@@ -8024,15 +8087,15 @@ var init_hints = __esm({
8024
8087
  });
8025
8088
 
8026
8089
  // src/hints/lang_patterns.ts
8027
- function isLockFile(basename20) {
8028
- return LOCK_FILE_NAMES.has(basename20.toLowerCase());
8090
+ function isLockFile(basename21) {
8091
+ return LOCK_FILE_NAMES.has(basename21.toLowerCase());
8029
8092
  }
8030
- function isManifestFile(basename20) {
8031
- const lower = basename20.toLowerCase();
8093
+ function isManifestFile(basename21) {
8094
+ const lower = basename21.toLowerCase();
8032
8095
  if (MANIFEST_FILE_NAMES.has(lower)) return true;
8033
8096
  const dot = lower.lastIndexOf(".");
8034
8097
  if (dot !== -1 && MANIFEST_EXTENSIONS.has(lower.slice(dot))) return true;
8035
- if (MANIFEST_BASENAME_PATTERNS.some((re) => re.test(basename20))) return true;
8098
+ if (MANIFEST_BASENAME_PATTERNS.some((re) => re.test(basename21))) return true;
8036
8099
  return false;
8037
8100
  }
8038
8101
  function pathSegments(filePath) {
@@ -8371,8 +8434,8 @@ function formatHeadingTree(headings, filePath) {
8371
8434
  }
8372
8435
  return lines2.join("\n");
8373
8436
  }
8374
- function getWellKnownSections(basename20) {
8375
- return WELL_KNOWN_SECTIONS[basename20] ?? [];
8437
+ function getWellKnownSections(basename21) {
8438
+ return WELL_KNOWN_SECTIONS[basename21] ?? [];
8376
8439
  }
8377
8440
  function extractChangelogVersionHint(content, filePath) {
8378
8441
  const lines2 = content.split("\n");
@@ -10190,6 +10253,64 @@ var init_sync = __esm({
10190
10253
  }
10191
10254
  });
10192
10255
 
10256
+ // src/doc_comment.ts
10257
+ function precedingDocComment(lines2, lineStart, style) {
10258
+ const aboveIdx = lineStart - 2;
10259
+ if (aboveIdx < 0 || aboveIdx >= lines2.length) return "";
10260
+ const aboveLine = lines2[aboveIdx];
10261
+ if (aboveLine === void 0) return "";
10262
+ const aboveTrimmed = aboveLine.trim();
10263
+ if (style === "hash") {
10264
+ if (!aboveTrimmed.startsWith("#")) return "";
10265
+ const collected = [];
10266
+ let i = aboveIdx;
10267
+ while (i >= 0) {
10268
+ const line = lines2[i];
10269
+ if (line === void 0) break;
10270
+ const trimmed = line.trim();
10271
+ if (!trimmed.startsWith("#")) break;
10272
+ collected.unshift(trimmed.replace(/^#+\s?/, ""));
10273
+ i--;
10274
+ }
10275
+ return collected.join("\n").trim();
10276
+ }
10277
+ if (aboveTrimmed.endsWith("*/")) {
10278
+ let blockStart = aboveIdx;
10279
+ while (blockStart >= 0) {
10280
+ const l = lines2[blockStart];
10281
+ if (l === void 0) break;
10282
+ if (l.trim().startsWith("/*")) break;
10283
+ blockStart--;
10284
+ }
10285
+ if (blockStart < 0) return "";
10286
+ const opener = lines2[blockStart];
10287
+ if (opener === void 0 || !opener.trim().startsWith("/*")) return "";
10288
+ return lines2.slice(blockStart, aboveIdx + 1).map(
10289
+ (l) => l.trim().replace(/^\/\*+/, "").replace(/\*+\/$/, "").replace(/^\*\s?/, "").trim()
10290
+ ).filter((l) => l !== "").join("\n");
10291
+ }
10292
+ if (aboveTrimmed.startsWith("//")) {
10293
+ const collected = [];
10294
+ let i = aboveIdx;
10295
+ while (i >= 0) {
10296
+ const line = lines2[i];
10297
+ if (line === void 0) break;
10298
+ const trimmed = line.trim();
10299
+ if (!trimmed.startsWith("//")) break;
10300
+ collected.unshift(trimmed.replace(/^\/\/[/!]?\s?/, ""));
10301
+ i--;
10302
+ }
10303
+ return collected.join("\n").trim();
10304
+ }
10305
+ return "";
10306
+ }
10307
+ var init_doc_comment = __esm({
10308
+ "src/doc_comment.ts"() {
10309
+ "use strict";
10310
+ init_define_import_meta_env();
10311
+ }
10312
+ });
10313
+
10193
10314
  // src/languages/common.ts
10194
10315
  function buildLineIndex(text) {
10195
10316
  const idx = [0];
@@ -10729,7 +10850,7 @@ function stripMultilineStringSpan(line, state, lang) {
10729
10850
  }
10730
10851
  return { code, state: cur };
10731
10852
  }
10732
- function makeSpanSymbol(filePath, name2, kind, span, docstring = "") {
10853
+ function makeSpanSymbol(filePath, name2, kind, span, parent = "", lines2, style) {
10733
10854
  return {
10734
10855
  filePath,
10735
10856
  name: name2,
@@ -10737,10 +10858,11 @@ function makeSpanSymbol(filePath, name2, kind, span, docstring = "") {
10737
10858
  lineStart: span.startLine,
10738
10859
  lineEnd: span.endLine,
10739
10860
  body: span.body,
10740
- docstring
10861
+ docstring: lines2 !== void 0 && style !== void 0 ? precedingDocComment(lines2, span.startLine, style) : "",
10862
+ parent
10741
10863
  };
10742
10864
  }
10743
- function makeLineSymbol(filePath, name2, kind, line, sig, parent) {
10865
+ function makeLineSymbol(filePath, name2, kind, line, sig, parent, lines2, style) {
10744
10866
  return {
10745
10867
  filePath,
10746
10868
  name: name2,
@@ -10748,7 +10870,8 @@ function makeLineSymbol(filePath, name2, kind, line, sig, parent) {
10748
10870
  lineStart: line,
10749
10871
  lineEnd: line,
10750
10872
  body: sig ?? "",
10751
- docstring: parent ?? ""
10873
+ docstring: lines2 !== void 0 && style !== void 0 ? precedingDocComment(lines2, line, style) : "",
10874
+ parent: parent ?? ""
10752
10875
  };
10753
10876
  }
10754
10877
  function makeSymbolEmitter(symbols, sections, seen, filePath, maxSymbols = 500, maxHeadingLen = 120) {
@@ -10765,7 +10888,8 @@ function makeSymbolEmitter(symbols, sections, seen, filePath, maxSymbols = 500,
10765
10888
  lineStart: line,
10766
10889
  lineEnd: line,
10767
10890
  body: "",
10768
- docstring: ""
10891
+ docstring: "",
10892
+ parent: ""
10769
10893
  });
10770
10894
  sections.push({ heading: name2, level: 1, line, endLine: line });
10771
10895
  };
@@ -10829,6 +10953,7 @@ var init_common = __esm({
10829
10953
  "use strict";
10830
10954
  init_define_import_meta_env();
10831
10955
  init_util2();
10956
+ init_doc_comment();
10832
10957
  HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
10833
10958
  HTML_SCRIPT_BODY_RE = /(<script\b[^>]*>)([\s\S]*?)(<\/script\s*>)/gi;
10834
10959
  HTML_CDATA_RE = /<!\[CDATA\[[\s\S]*?\]\]>/g;
@@ -11890,19 +12015,19 @@ async function preReadImageHandler(event) {
11890
12015
  }
11891
12016
  const result = await shrinkImage(input, { sizeThresholdBytes: 0 });
11892
12017
  if (result === null) return passOutput();
11893
- const basename20 = path16.basename(filePath);
12018
+ const basename21 = path16.basename(filePath);
11894
12019
  if (loadConfig().image_shrink.ocr_enabled) {
11895
12020
  const ocr = await ocrImage(result.data);
11896
12021
  if (ocr !== null && isTextHeavy(ocr, loadConfig().image_shrink.ocr_min_confidence)) {
11897
12022
  const textBytes = Buffer.byteLength(ocr.text, "utf8");
11898
12023
  const saved2 = Math.max(0, result.shrunkBytes - textBytes);
11899
- recordStat("image_ocr", saved2, Math.round(saved2 / 4), void 0, basename20);
11900
- return contextOutput(formatOcrSummary(ocr, basename20, result.originalBytes));
12024
+ recordStat("image_ocr", saved2, Math.round(saved2 / 4), void 0, basename21);
12025
+ return contextOutput(formatOcrSummary(ocr, basename21, result.originalBytes));
11901
12026
  }
11902
12027
  }
11903
12028
  const saved = result.originalBytes - result.shrunkBytes;
11904
- const { summary, dataUrl } = formatShrinkSummary(result, basename20);
11905
- recordStat("image_shrink", saved, Math.round(saved / 4), void 0, basename20);
12029
+ const { summary, dataUrl } = formatShrinkSummary(result, basename21);
12030
+ recordStat("image_shrink", saved, Math.round(saved / 4), void 0, basename21);
11906
12031
  return contextOutput(`${summary}
11907
12032
  ${dataUrl}`);
11908
12033
  }
@@ -12431,8 +12556,8 @@ var init_parser_types = __esm({
12431
12556
  // src/hooks_read.ts
12432
12557
  import * as fs19 from "node:fs";
12433
12558
  import * as path20 from "node:path";
12434
- function isTsConfigFile(basename20) {
12435
- const lower = basename20.toLowerCase();
12559
+ function isTsConfigFile(basename21) {
12560
+ const lower = basename21.toLowerCase();
12436
12561
  return /^tsconfig(\..+)?\.json$/i.test(lower) || lower === "jsconfig.json";
12437
12562
  }
12438
12563
  function largeFileDenyBytes() {
@@ -12532,18 +12657,18 @@ function describeSliceAdvice(slice, absPath) {
12532
12657
  }
12533
12658
  return "Use Read with offset/limit to sample specific sections.";
12534
12659
  }
12535
- function isSourceExtension(basename20) {
12536
- if (SOURCE_EXT_RE.test(basename20)) return true;
12537
- const language = detectLanguage(basename20);
12660
+ function isSourceExtension(basename21) {
12661
+ if (SOURCE_EXT_RE.test(basename21)) return true;
12662
+ const language = detectLanguage(basename21);
12538
12663
  return language === "apex" || language === "salesforce_metadata" || language === "salesforce_markup";
12539
12664
  }
12540
- function isDispatchedFileType(basename20) {
12541
- return DISPATCHED_FILE_TYPE_EXTS.has(path20.extname(basename20).slice(1).toLowerCase());
12665
+ function isDispatchedFileType(basename21) {
12666
+ return DISPATCHED_FILE_TYPE_EXTS.has(path20.extname(basename21).slice(1).toLowerCase());
12542
12667
  }
12543
- function surgicalHint(filePath, basename20, lineCount) {
12668
+ function surgicalHint(filePath, basename21, lineCount) {
12544
12669
  if (lineCount < loadConfig().hints.min_file_lines_for_hint) return "";
12545
- const isDocFile = /\.(md|mdx|rst|txt)$/i.test(basename20);
12546
- const isSectionFile = /\.(json|jsonc|css|scss|sass|less|yaml|yml|toml)$/i.test(basename20);
12670
+ const isDocFile = /\.(md|mdx|rst|txt)$/i.test(basename21);
12671
+ const isSectionFile = /\.(json|jsonc|css|scss|sass|less|yaml|yml|toml)$/i.test(basename21);
12547
12672
  if (isDocFile) {
12548
12673
  return 'Use `token-goat section "' + filePath + '::HeadingName"` to extract a part.';
12549
12674
  } else if (isSectionFile) {
@@ -12591,7 +12716,7 @@ function buildLineDiff(oldContent, newContent, label) {
12591
12716
  }
12592
12717
  return out2.join("\n");
12593
12718
  }
12594
- function loadSnapshotDiff(sessionId, normalized, basename20) {
12719
+ function loadSnapshotDiff(sessionId, normalized, basename21) {
12595
12720
  const oldSnap = load(sessionId, normalized);
12596
12721
  if (oldSnap === null) return { kind: "none" };
12597
12722
  try {
@@ -12603,7 +12728,7 @@ function loadSnapshotDiff(sessionId, normalized, basename20) {
12603
12728
  const truncIdx = oldRaw.indexOf(TRUNC_MARKER);
12604
12729
  const oldContent = truncIdx >= 0 ? oldRaw.slice(0, truncIdx) : oldRaw;
12605
12730
  if (oldContent === currentContent) return { kind: "unchanged", currentContent };
12606
- const diff = buildLineDiff(oldContent, currentContent, basename20);
12731
+ const diff = buildLineDiff(oldContent, currentContent, basename21);
12607
12732
  if (diff === "") return { kind: "none" };
12608
12733
  const savedBytes = Math.max(0, currentContent.length - diff.length);
12609
12734
  return { kind: "diff", diff, savedBytes, currentContent };
@@ -12676,8 +12801,8 @@ function preReadHandlerInner(event) {
12676
12801
  "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"
12677
12802
  );
12678
12803
  }
12679
- const basename20 = path20.basename(normalized);
12680
- if (isLockFile(basename20)) {
12804
+ const basename21 = path20.basename(normalized);
12805
+ if (isLockFile(basename21)) {
12681
12806
  return denyOutput(
12682
12807
  '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.'
12683
12808
  );
@@ -12700,20 +12825,20 @@ function preReadHandlerInner(event) {
12700
12825
  return quietContextOutput(manifestHint.text);
12701
12826
  }
12702
12827
  }
12703
- if (isTsConfigFile(basename20) && wasFileReadThisSession(normalized)) {
12828
+ if (isTsConfigFile(basename21) && wasFileReadThisSession(normalized)) {
12704
12829
  recordActualRead(event, normalized);
12705
12830
  return quietContextOutput(
12706
- "Already read " + basename20 + '. Use `token-goat section "' + normalized + '::compilerOptions"` to extract compiler options, or `token-goat config-get ' + normalized + " compilerOptions.target` for a single value."
12831
+ "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."
12707
12832
  );
12708
12833
  }
12709
- if (isManifestFile(basename20) && wasFileReadThisSession(normalized)) {
12834
+ if (isManifestFile(basename21) && wasFileReadThisSession(normalized)) {
12710
12835
  recordActualRead(event, normalized);
12711
12836
  return quietContextOutput(
12712
- "You've already read " + basename20 + '. Use `token-goat section "' + normalized + '::<field>"` or `token-goat config-get ' + normalized + " <key>` to extract just the value you need."
12837
+ "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."
12713
12838
  );
12714
12839
  }
12715
12840
  const skillName = detectSkillFile(normalized);
12716
- if (skillName && basename20 === "SKILL.md") {
12841
+ if (skillName && basename21 === "SKILL.md") {
12717
12842
  try {
12718
12843
  const body = fs19.readFileSync(normalized, "utf-8");
12719
12844
  const bodySha = contentHash(body);
@@ -12743,7 +12868,7 @@ function preReadHandlerInner(event) {
12743
12868
  }
12744
12869
  }
12745
12870
  }
12746
- const isNotebook = /\.ipynb$/i.test(basename20);
12871
+ const isNotebook = /\.ipynb$/i.test(basename21);
12747
12872
  if (event.toolName !== "Grep" && isNotebook) {
12748
12873
  try {
12749
12874
  const rawBytes = fs19.readFileSync(normalized);
@@ -12760,7 +12885,7 @@ function preReadHandlerInner(event) {
12760
12885
  } catch {
12761
12886
  }
12762
12887
  }
12763
- const isMarkdown = /\.(md|mdx|markdown|rst)$/i.test(basename20);
12888
+ const isMarkdown = /\.(md|mdx|markdown|rst)$/i.test(basename21);
12764
12889
  if (event.toolName !== "Grep" && isMarkdown) {
12765
12890
  let fileContent = null;
12766
12891
  let markdownSize = null;
@@ -12778,9 +12903,9 @@ function preReadHandlerInner(event) {
12778
12903
  const alreadyRead = wasFileReadThisSession(normalized);
12779
12904
  const hintText = formatHeadingTree(headings, normalized);
12780
12905
  const headingTextsLower = new Set(headings.map((h) => h.text.trim().toLowerCase()));
12781
- const wellKnown = getWellKnownSections(basename20).filter((s) => headingTextsLower.has(s.trim().toLowerCase()));
12906
+ const wellKnown = getWellKnownSections(basename21).filter((s) => headingTextsLower.has(s.trim().toLowerCase()));
12782
12907
  const wellKnownText = wellKnown.length > 0 ? "\nQuick access: " + wellKnown.map((s) => 'token-goat section "' + normalized + "::" + s + '"').join(" | ") : "";
12783
- const changelogExtra = basename20.toLowerCase() === "changelog.md" ? extractChangelogVersionHint(fileContent, normalized) : "";
12908
+ const changelogExtra = basename21.toLowerCase() === "changelog.md" ? extractChangelogVersionHint(fileContent, normalized) : "";
12784
12909
  let message = hintText + wellKnownText + changelogExtra;
12785
12910
  const slice = estimateRequestedSlice(event, normalized);
12786
12911
  const gateSize = slice.kind === "bytes" && markdownSize !== null ? Math.min(slice.bytes, markdownSize) : markdownSize;
@@ -12801,19 +12926,19 @@ function preReadHandlerInner(event) {
12801
12926
  if (isMemoryMd && wasFileReadThisSession(normalized)) {
12802
12927
  recordActualRead(event, normalized);
12803
12928
  recordStat("session_hint", 0, 0);
12804
- const isMainMemory = basename20.toLowerCase() === "memory.md";
12929
+ const isMainMemory = basename21.toLowerCase() === "memory.md";
12805
12930
  return denyOutput(
12806
12931
  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.'
12807
12932
  );
12808
12933
  }
12809
- if (/^\.improve-state-.*\.json$/.test(basename20) && wasFileReadThisSession(normalized)) {
12934
+ if (/^\.improve-state-.*\.json$/.test(basename21) && wasFileReadThisSession(normalized)) {
12810
12935
  recordActualRead(event, normalized);
12811
12936
  recordStat("session_hint", 0, 0);
12812
12937
  return denyOutput(
12813
12938
  "Orchestrator state already read this session. " + sessionArtifactRecall(normalized)
12814
12939
  );
12815
12940
  }
12816
- if (/^\.env(\.\w+)?$/.test(basename20) && wasFileReadThisSession(normalized)) {
12941
+ if (/^\.env(\.\w+)?$/.test(basename21) && wasFileReadThisSession(normalized)) {
12817
12942
  recordActualRead(event, normalized);
12818
12943
  recordStat("session_hint", 0, 0);
12819
12944
  return denyOutput(
@@ -12830,19 +12955,19 @@ function preReadHandlerInner(event) {
12830
12955
  );
12831
12956
  }
12832
12957
  const artifactSessionId = getSessionId();
12833
- const snapDiff = loadSnapshotDiff(artifactSessionId, normalized, basename20);
12958
+ const snapDiff = loadSnapshotDiff(artifactSessionId, normalized, basename21);
12834
12959
  if (snapDiff.kind === "unchanged") {
12835
12960
  recordActualRead(event, normalized);
12836
12961
  recordStat("session_hint", 0, 0);
12837
12962
  return denyOutput(
12838
- basename20 + " is unchanged since last read. " + sessionArtifactRecall(normalized)
12963
+ basename21 + " is unchanged since last read. " + sessionArtifactRecall(normalized)
12839
12964
  );
12840
12965
  }
12841
12966
  if (snapDiff.kind === "diff") {
12842
12967
  recordActualRead(event, normalized);
12843
12968
  recordStat("session_hint", snapDiff.savedBytes, Math.round(snapDiff.savedBytes / 4));
12844
12969
  return denyOutput(
12845
- "Content changed since last read of " + basename20 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + sessionArtifactRecall(normalized)
12970
+ "Content changed since last read of " + basename21 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + sessionArtifactRecall(normalized)
12846
12971
  );
12847
12972
  }
12848
12973
  recordActualRead(event, normalized);
@@ -12865,8 +12990,8 @@ function preReadHandlerInner(event) {
12865
12990
  return quietContextOutput(label + ": " + sessionArtifactRecall(normalized));
12866
12991
  }
12867
12992
  }
12868
- const isDocDiffable = /\.(md|mdx|markdown|rst|txt)$/i.test(basename20);
12869
- const isSourceDiffable = loadConfig().hints.serve_diff_on_reread && DIFFABLE_SOURCE_RE.test(basename20);
12993
+ const isDocDiffable = /\.(md|mdx|markdown|rst|txt)$/i.test(basename21);
12994
+ const isSourceDiffable = loadConfig().hints.serve_diff_on_reread && DIFFABLE_SOURCE_RE.test(basename21);
12870
12995
  if ((isDocDiffable || isSourceDiffable) && wasFileReadThisSession(normalized) && !isProtectedRecentRead(normalized, loadConfig().hints.protect_recent_reads)) {
12871
12996
  if (wasFileTruncatedThisSession(normalized)) {
12872
12997
  if (estimateTruncatedLineCount(normalized) >= loadConfig().hints.truncated_read_min_lines) {
@@ -12876,12 +13001,12 @@ function preReadHandlerInner(event) {
12876
13001
  }
12877
13002
  }
12878
13003
  const sessionId = getSessionId();
12879
- const snapDiff = loadSnapshotDiff(sessionId, normalized, basename20);
13004
+ const snapDiff = loadSnapshotDiff(sessionId, normalized, basename21);
12880
13005
  if (snapDiff.kind === "unchanged") {
12881
13006
  recordActualRead(event, normalized);
12882
13007
  recordStat("session_hint", 0, 0);
12883
13008
  return denyOutput(
12884
- (basename20 + " is unchanged since last read. " + surgicalHint(normalized, basename20, countTextLines(snapDiff.currentContent))).trimEnd()
13009
+ (basename21 + " is unchanged since last read. " + surgicalHint(normalized, basename21, countTextLines(snapDiff.currentContent))).trimEnd()
12885
13010
  );
12886
13011
  }
12887
13012
  if (snapDiff.kind === "diff") {
@@ -12889,7 +13014,7 @@ function preReadHandlerInner(event) {
12889
13014
  recordActualRead(event, normalized);
12890
13015
  recordStat("diff_hint", snapDiff.savedBytes, Math.round(snapDiff.savedBytes / 4));
12891
13016
  return denyOutput(
12892
- ("Content changed since last read of " + basename20 + ". Here is what changed:\n\n```diff\n" + snapDiff.diff + "\n```\n\n" + surgicalHint(normalized, basename20, countTextLines(snapDiff.currentContent))).trimEnd()
13017
+ ("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()
12893
13018
  );
12894
13019
  }
12895
13020
  }
@@ -12935,13 +13060,13 @@ function preReadHandlerInner(event) {
12935
13060
  return denyOutput(truncatedReadDenyMessage(normalized));
12936
13061
  }
12937
13062
  }
12938
- if (/\.(md|mdx|markdown|rst)$/i.test(basename20)) {
13063
+ if (/\.(md|mdx|markdown|rst)$/i.test(basename21)) {
12939
13064
  recordStat("session_hint", rereadBytes, Math.round(rereadBytes / 4));
12940
13065
  return denyOutput(
12941
13066
  'Markdown file already read this session. Use `token-goat section "' + normalized + '::HeadingName"` to read one section. ' + editAnywayHint(normalized)
12942
13067
  );
12943
13068
  }
12944
- const isSourceExt = isSourceExtension(basename20);
13069
+ const isSourceExt = isSourceExtension(basename21);
12945
13070
  if (isSourceExt && reads >= 2) {
12946
13071
  recordStat("read_count_deny", rereadBytes, Math.round(rereadBytes / 4));
12947
13072
  recordStat("session_hint", rereadBytes, Math.round(rereadBytes / 4));
@@ -14610,7 +14735,8 @@ function toSymbolEntry(row) {
14610
14735
  lineStart: row.line_start,
14611
14736
  lineEnd: row.line_end,
14612
14737
  body: row.body ?? "",
14613
- docstring: row.docstring ?? ""
14738
+ docstring: row.docstring ?? "",
14739
+ parent: row.parent ?? ""
14614
14740
  };
14615
14741
  }
14616
14742
  function toRefEntry(row) {
@@ -14649,7 +14775,7 @@ function buildSymbolWhere(opts) {
14649
14775
  function querySymbols(opts = {}, dbPath = globalDbPath()) {
14650
14776
  const { clause, params } = buildSymbolWhere(opts);
14651
14777
  const limit = opts.limit ?? 100;
14652
- const sql = `SELECT file_path, name, kind, line_start, line_end, body, docstring FROM symbols ${clause} ORDER BY file_path, line_start LIMIT ?`;
14778
+ const sql = `SELECT file_path, name, kind, line_start, line_end, body, docstring, parent FROM symbols ${clause} ORDER BY file_path, line_start LIMIT ?`;
14653
14779
  const db = getDb(dbPath);
14654
14780
  const rows = db.prepare(sql).all(...params, limit);
14655
14781
  return rows.map(toSymbolEntry);
@@ -14728,7 +14854,7 @@ function sanitizeFtsQuery(query, join46 = "AND") {
14728
14854
  return terms.join(join46 === "OR" ? " OR " : " ");
14729
14855
  }
14730
14856
  function runFtsQuery(db, match2, limit, scope, rootDir) {
14731
- 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 ?`;
14857
+ 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 ?`;
14732
14858
  const params = [match2];
14733
14859
  if (scope !== void 0 && rootDir !== void 0) {
14734
14860
  params.push(scope.param(rootDir));
@@ -14797,12 +14923,12 @@ function extractCsharp(content, filePath) {
14797
14923
  }
14798
14924
  const nsM = NAMESPACE_RE.exec(stripped);
14799
14925
  if (nsM) {
14800
- symbols.push(makeLineSymbol(filePath, nsM[1] ?? "", "namespace", lineNum, stripped.slice(0, 200)));
14926
+ symbols.push(makeLineSymbol(filePath, nsM[1] ?? "", "namespace", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
14801
14927
  }
14802
14928
  const delM = DELEGATE_RE.exec(stripLeadingAttributes(stripped));
14803
14929
  if (delM) {
14804
14930
  const delegateParent = classStack.length > 0 ? classStack[classStack.length - 1].name : void 0;
14805
- symbols.push(makeLineSymbol(filePath, delM[1] ?? "", "interface", lineNum, stripped.slice(0, 200), delegateParent));
14931
+ symbols.push(makeLineSymbol(filePath, delM[1] ?? "", "interface", lineNum, stripped.slice(0, 200), delegateParent, lines2, "c"));
14806
14932
  }
14807
14933
  const cm = CLASS_HEADER_RE.exec(stripLeadingAttributes(stripped));
14808
14934
  if (cm) {
@@ -14810,7 +14936,7 @@ function extractCsharp(content, filePath) {
14810
14936
  const cname = cm[2] ?? "";
14811
14937
  const kind = keyword === "struct" ? "struct" : keyword === "interface" ? "interface" : keyword === "enum" ? "enum" : "class";
14812
14938
  const parent = classStack.length > 0 ? classStack[classStack.length - 1].name : void 0;
14813
- symbols.push(makeLineSymbol(filePath, cname, kind, lineNum, stripped.slice(0, 200), parent));
14939
+ symbols.push(makeLineSymbol(filePath, cname, kind, lineNum, stripped.slice(0, 200), parent, lines2, "c"));
14814
14940
  classStack.push({ name: cname, startDepth: braceDepth, bodyEntered: false });
14815
14941
  }
14816
14942
  const frame = classStack.length > 0 ? classStack[classStack.length - 1] : null;
@@ -14822,13 +14948,13 @@ function extractCsharp(content, filePath) {
14822
14948
  if (ctorM && ctorM[1] === frame.name) {
14823
14949
  const sigEnd = line.indexOf("{");
14824
14950
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trimEnd() : line.trimEnd();
14825
- symbols.push(makeLineSymbol(filePath, frame.name, "method", lineNum, sig.slice(0, 200), frame.name));
14951
+ symbols.push(makeLineSymbol(filePath, frame.name, "method", lineNum, sig.slice(0, 200), frame.name, lines2, "c"));
14826
14952
  }
14827
14953
  let isPropertyLine = false;
14828
14954
  const propM = PROPERTY_RE.exec(lineNoAttr);
14829
14955
  if (propM) {
14830
14956
  isPropertyLine = true;
14831
- symbols.push(makeLineSymbol(filePath, propM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
14957
+ symbols.push(makeLineSymbol(filePath, propM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
14832
14958
  } else {
14833
14959
  const headerM = PROPERTY_HEADER_RE.exec(lineNoAttr);
14834
14960
  if (headerM) {
@@ -14836,13 +14962,13 @@ function extractCsharp(content, filePath) {
14836
14962
  const accessorLine = (lines2[i + 2] ?? "").trim();
14837
14963
  if (braceLineNext === "{" && (ALLMAN_ACCESSOR_RE.test(accessorLine) || ALLMAN_ACCESSOR_BODY_RE.test(accessorLine))) {
14838
14964
  isPropertyLine = true;
14839
- symbols.push(makeLineSymbol(filePath, headerM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
14965
+ symbols.push(makeLineSymbol(filePath, headerM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
14840
14966
  }
14841
14967
  } else {
14842
14968
  const arrowM = PROPERTY_ARROW_RE.exec(lineNoAttr);
14843
14969
  if (arrowM) {
14844
14970
  isPropertyLine = true;
14845
- symbols.push(makeLineSymbol(filePath, arrowM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
14971
+ symbols.push(makeLineSymbol(filePath, arrowM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
14846
14972
  }
14847
14973
  }
14848
14974
  }
@@ -14852,7 +14978,7 @@ function extractCsharp(content, filePath) {
14852
14978
  if (mname && mname !== frame.name) {
14853
14979
  const sigEnd = line.indexOf("{");
14854
14980
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trimEnd() : line.trimEnd();
14855
- symbols.push(makeLineSymbol(filePath, mname, "method", lineNum, sig.slice(0, 200), frame.name));
14981
+ symbols.push(makeLineSymbol(filePath, mname, "method", lineNum, sig.slice(0, 200), frame.name, lines2, "c"));
14856
14982
  }
14857
14983
  }
14858
14984
  }
@@ -14957,7 +15083,7 @@ function extractPhp(content, filePath) {
14957
15083
  }
14958
15084
  const nsM = NAMESPACE_RE2.exec(stripped);
14959
15085
  if (nsM) {
14960
- symbols.push(makeLineSymbol(filePath, nsM[1] ?? "", "namespace", lineNum, stripped.slice(0, 200)));
15086
+ symbols.push(makeLineSymbol(filePath, nsM[1] ?? "", "namespace", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
14961
15087
  continue;
14962
15088
  }
14963
15089
  if (contextStack.length === 0) {
@@ -14991,7 +15117,7 @@ function extractPhp(content, filePath) {
14991
15117
  const preLineDepth = braceDepth - openB + closeB;
14992
15118
  const topFrame2 = contextStack.length > 0 ? contextStack[contextStack.length - 1] : void 0;
14993
15119
  const parent = topFrame2 !== void 0 && preLineDepth === topFrame2[1] + 1 ? topFrame2[0] : null;
14994
- symbols.push(makeLineSymbol(filePath, name2, kind, lineNum, stripped.slice(0, 200), parent ?? void 0));
15120
+ symbols.push(makeLineSymbol(filePath, name2, kind, lineNum, stripped.slice(0, 200), parent ?? void 0, lines2, "c"));
14995
15121
  contextStack.push([name2, braceDepth - openB + closeB, false]);
14996
15122
  if (openB > 0 && openB === closeB) {
14997
15123
  contextStack.pop();
@@ -15008,7 +15134,7 @@ function extractPhp(content, filePath) {
15008
15134
  const kind = parent ? "method" : "function";
15009
15135
  const sigEnd = stripped.indexOf(")");
15010
15136
  const sig = sigEnd >= 0 ? stripped.slice(0, sigEnd + 1) : stripped;
15011
- symbols.push(makeLineSymbol(filePath, name2, kind, lineNum, sig.slice(0, 200), parent ?? void 0));
15137
+ symbols.push(makeLineSymbol(filePath, name2, kind, lineNum, sig.slice(0, 200), parent ?? void 0, lines2, "c"));
15012
15138
  continue;
15013
15139
  }
15014
15140
  const propM = PROP_RE.exec(stripped);
@@ -15017,7 +15143,7 @@ function extractPhp(content, filePath) {
15017
15143
  const preLineDepth = braceDepth - openB + closeB;
15018
15144
  const topFrame2 = contextStack.length > 0 ? contextStack[contextStack.length - 1] : void 0;
15019
15145
  if (topFrame2 !== void 0 && preLineDepth === topFrame2[1] + 1) {
15020
- symbols.push(makeLineSymbol(filePath, name2, "var", lineNum, stripped.slice(0, 200), topFrame2[0]));
15146
+ symbols.push(makeLineSymbol(filePath, name2, "var", lineNum, stripped.slice(0, 200), topFrame2[0], lines2, "c"));
15021
15147
  }
15022
15148
  continue;
15023
15149
  }
@@ -15027,12 +15153,12 @@ function extractPhp(content, filePath) {
15027
15153
  const preLineDepth = braceDepth - openB + closeB;
15028
15154
  const topFrame2 = contextStack.length > 0 ? contextStack[contextStack.length - 1] : void 0;
15029
15155
  const parent = topFrame2 !== void 0 && preLineDepth === topFrame2[1] + 1 ? topFrame2[0] : void 0;
15030
- symbols.push(makeLineSymbol(filePath, name2, "const", lineNum, stripped.slice(0, 200), parent));
15156
+ symbols.push(makeLineSymbol(filePath, name2, "const", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15031
15157
  continue;
15032
15158
  }
15033
15159
  const defineM = DEFINE_RE.exec(stripped);
15034
15160
  if (defineM) {
15035
- symbols.push(makeLineSymbol(filePath, defineM[1] ?? "", "const", lineNum, stripped.slice(0, 200)));
15161
+ symbols.push(makeLineSymbol(filePath, defineM[1] ?? "", "const", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
15036
15162
  }
15037
15163
  }
15038
15164
  return { symbols, imports };
@@ -15097,7 +15223,7 @@ function extractHtml(content, filePath) {
15097
15223
  const key = `${idVal}\0${line}`;
15098
15224
  if (!seenId.has(key)) {
15099
15225
  seenId.add(key);
15100
- symbols.push({ filePath, name: idVal, kind: "html_id", lineStart: line, lineEnd: line, body: "", docstring: "" });
15226
+ symbols.push({ filePath, name: idVal, kind: "html_id", lineStart: line, lineEnd: line, body: "", docstring: "", parent: "" });
15101
15227
  }
15102
15228
  }
15103
15229
  }
@@ -15112,7 +15238,7 @@ function extractHtml(content, filePath) {
15112
15238
  const key = `${cls}\0${line}`;
15113
15239
  if (!seenClass.has(key)) {
15114
15240
  seenClass.add(key);
15115
- symbols.push({ filePath, name: cls, kind: "html_class", lineStart: line, lineEnd: line, body: "", docstring: "" });
15241
+ symbols.push({ filePath, name: cls, kind: "html_class", lineStart: line, lineEnd: line, body: "", docstring: "", parent: "" });
15116
15242
  }
15117
15243
  }
15118
15244
  }
@@ -15215,7 +15341,7 @@ function extractLiquid(content, filePath, relPath) {
15215
15341
  if (name2) {
15216
15342
  const line = offsetToLine(lineIndex, m.index ?? 0);
15217
15343
  const endLine = offsetToLine(lineIndex, (m.index ?? 0) + (m[0]?.length ?? 0));
15218
- symbols.push({ filePath, name: name2, kind: "liquid_schema", lineStart: line, lineEnd: endLine, body: "", docstring: "" });
15344
+ symbols.push({ filePath, name: name2, kind: "liquid_schema", lineStart: line, lineEnd: endLine, body: "", docstring: "", parent: "" });
15219
15345
  }
15220
15346
  }
15221
15347
  } catch {
@@ -15225,7 +15351,7 @@ function extractLiquid(content, filePath, relPath) {
15225
15351
  const relPosix = resolvedRel.replace(/\\/g, "/");
15226
15352
  if (relPosix.startsWith("sections/") || relPosix.includes("/sections/")) {
15227
15353
  const stem = path22.basename(resolvedRel, path22.extname(resolvedRel));
15228
- symbols.push({ filePath, name: stem, kind: "liquid_section_file", lineStart: 1, lineEnd: 1, body: "", docstring: "" });
15354
+ symbols.push({ filePath, name: stem, kind: "liquid_section_file", lineStart: 1, lineEnd: 1, body: "", docstring: "", parent: "" });
15229
15355
  }
15230
15356
  const totalLines = content.split("\n").length;
15231
15357
  for (const hm of findHtmlHeadingMatches(content)) {
@@ -15316,14 +15442,14 @@ function extractKotlin(content, filePath) {
15316
15442
  if (companionM) {
15317
15443
  const cname = companionM[1] ?? "Companion";
15318
15444
  const parent = classStack.length > 0 ? classStack[classStack.length - 1].name : void 0;
15319
- symbols.push(makeLineSymbol(filePath, cname, "object", lineNum, line.trimEnd().slice(0, 200), parent));
15445
+ symbols.push(makeLineSymbol(filePath, cname, "object", lineNum, line.trimEnd().slice(0, 200), parent, lines2, "c"));
15320
15446
  classStack.push({ name: cname, braceDepth, bodyEntered: false, parenBalance: 0, pendingPop: false });
15321
15447
  } else if (cm) {
15322
15448
  const ckeyword = cm[1] ?? "class";
15323
15449
  const cname = cm[2] ?? "";
15324
15450
  const ckind = ckeyword === "interface" ? "interface" : ckeyword === "object" ? "object" : "class";
15325
15451
  const parent = classStack.length > 0 ? classStack[classStack.length - 1].name : void 0;
15326
- symbols.push(makeLineSymbol(filePath, cname, ckind, lineNum, line.trimEnd().slice(0, 200), parent));
15452
+ symbols.push(makeLineSymbol(filePath, cname, ckind, lineNum, line.trimEnd().slice(0, 200), parent, lines2, "c"));
15327
15453
  classStack.push({ name: cname, braceDepth, bodyEntered: false, parenBalance: 0, pendingPop: false });
15328
15454
  }
15329
15455
  const frame = classStack.length > 0 ? classStack[classStack.length - 1] : null;
@@ -15336,11 +15462,11 @@ function extractKotlin(content, filePath) {
15336
15462
  const fname = fm[1] ?? "";
15337
15463
  const sigEnd = line.indexOf("{");
15338
15464
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trim() : line.trimEnd();
15339
- symbols.push(makeLineSymbol(filePath, fname, "method", lineNum, sig.slice(0, 200), frame.name));
15465
+ symbols.push(makeLineSymbol(filePath, fname, "method", lineNum, sig.slice(0, 200), frame.name, lines2, "c"));
15340
15466
  }
15341
15467
  const constM = CONST_RE2.exec(lineNoAnn);
15342
15468
  if (constM) {
15343
- symbols.push(makeLineSymbol(filePath, constM[1] ?? "", "const", lineNum, stripped.slice(0, 200), frame.name));
15469
+ symbols.push(makeLineSymbol(filePath, constM[1] ?? "", "const", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15344
15470
  }
15345
15471
  }
15346
15472
  } else if (!isIndented) {
@@ -15350,11 +15476,11 @@ function extractKotlin(content, filePath) {
15350
15476
  const fname = tfm[1] ?? "";
15351
15477
  const sigEnd = line.indexOf("{");
15352
15478
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trim() : line.trimEnd();
15353
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, sig.slice(0, 200)));
15479
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, sig.slice(0, 200), void 0, lines2, "c"));
15354
15480
  }
15355
15481
  const topConstM = CONST_RE2.exec(lineNoAnn);
15356
15482
  if (topConstM) {
15357
- symbols.push(makeLineSymbol(filePath, topConstM[1] ?? "", "const", lineNum, stripped.slice(0, 200)));
15483
+ symbols.push(makeLineSymbol(filePath, topConstM[1] ?? "", "const", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
15358
15484
  }
15359
15485
  }
15360
15486
  const braceLine = stripStringLiterals(line);
@@ -15458,7 +15584,7 @@ function extractSwift(content, filePath) {
15458
15584
  const tname = tm[2] ?? "";
15459
15585
  const kind = keyword === "struct" ? "struct" : keyword === "enum" ? "enum" : keyword === "protocol" ? "protocol" : keyword === "extension" ? "extension" : keyword === "actor" ? "actor" : "class";
15460
15586
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15461
- symbols.push(makeLineSymbol(filePath, tname, kind, lineNum, stripped.slice(0, 200), parent));
15587
+ symbols.push(makeLineSymbol(filePath, tname, kind, lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15462
15588
  typeStack.push({ name: tname, startDepth: braceDepth, bodyEntered: false });
15463
15589
  }
15464
15590
  const frame = typeStack.length > 0 ? typeStack[typeStack.length - 1] : null;
@@ -15471,17 +15597,17 @@ function extractSwift(content, filePath) {
15471
15597
  const subscriptM = SUBSCRIPT_RE.exec(lineNoAttr);
15472
15598
  const fm = FUNC_RE.exec(lineNoAttr);
15473
15599
  if (initM) {
15474
- symbols.push(makeLineSymbol(filePath, initM[1] ?? "init", "method", lineNum, stripped.slice(0, 200), frame.name));
15600
+ symbols.push(makeLineSymbol(filePath, initM[1] ?? "init", "method", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15475
15601
  } else if (deinitM) {
15476
- symbols.push(makeLineSymbol(filePath, "deinit", "method", lineNum, stripped.slice(0, 200), frame.name));
15602
+ symbols.push(makeLineSymbol(filePath, "deinit", "method", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15477
15603
  } else if (subscriptM) {
15478
- symbols.push(makeLineSymbol(filePath, subscriptM[1] ?? "subscript", "method", lineNum, stripped.slice(0, 200), frame.name));
15604
+ symbols.push(makeLineSymbol(filePath, subscriptM[1] ?? "subscript", "method", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15479
15605
  } else if (fm) {
15480
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "method", lineNum, stripped.slice(0, 200), frame.name));
15606
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "method", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15481
15607
  } else {
15482
15608
  const propM = PROPERTY_RE2.exec(lineNoAttr);
15483
15609
  if (propM) {
15484
- symbols.push(makeLineSymbol(filePath, propM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
15610
+ symbols.push(makeLineSymbol(filePath, propM[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15485
15611
  }
15486
15612
  }
15487
15613
  }
@@ -15489,7 +15615,7 @@ function extractSwift(content, filePath) {
15489
15615
  const lineNoAttr = stripLeadingAttributes2(line);
15490
15616
  const fm = FUNC_RE.exec(lineNoAttr);
15491
15617
  if (fm) {
15492
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200)));
15618
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
15493
15619
  }
15494
15620
  }
15495
15621
  const braceLine = stripStringLiterals(line);
@@ -15586,7 +15712,7 @@ function extractScala(content, filePath) {
15586
15712
  if (cm) {
15587
15713
  const cname = cm[1] ?? "";
15588
15714
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15589
- symbols.push(makeLineSymbol(filePath, cname, "class", lineNum, stripped.slice(0, 200), parent));
15715
+ symbols.push(makeLineSymbol(filePath, cname, "class", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15590
15716
  typeStack.push({ name: cname, startDepth: braceDepth, bodyEntered: false });
15591
15717
  if (/\bcase\s+class\b/.test(stripped) && !stripStringLiterals(line).includes("{")) {
15592
15718
  typeStack.pop();
@@ -15597,7 +15723,7 @@ function extractScala(content, filePath) {
15597
15723
  if (om) {
15598
15724
  const oname = om[1] ?? "";
15599
15725
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15600
- symbols.push(makeLineSymbol(filePath, oname, "object", lineNum, stripped.slice(0, 200), parent));
15726
+ symbols.push(makeLineSymbol(filePath, oname, "object", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15601
15727
  typeStack.push({ name: oname, startDepth: braceDepth, bodyEntered: false });
15602
15728
  if (/\bcase\s+object\b/.test(stripped) && !stripStringLiterals(line).includes("{")) {
15603
15729
  typeStack.pop();
@@ -15608,7 +15734,7 @@ function extractScala(content, filePath) {
15608
15734
  if (tm) {
15609
15735
  const tname = tm[1] ?? "";
15610
15736
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15611
- symbols.push(makeLineSymbol(filePath, tname, "trait", lineNum, stripped.slice(0, 200), parent));
15737
+ symbols.push(makeLineSymbol(filePath, tname, "trait", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15612
15738
  typeStack.push({ name: tname, startDepth: braceDepth, bodyEntered: false });
15613
15739
  matched = true;
15614
15740
  }
@@ -15616,7 +15742,7 @@ function extractScala(content, filePath) {
15616
15742
  if (enm) {
15617
15743
  const enname = enm[1] ?? "";
15618
15744
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15619
- symbols.push(makeLineSymbol(filePath, enname, "enum", lineNum, stripped.slice(0, 200), parent));
15745
+ symbols.push(makeLineSymbol(filePath, enname, "enum", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15620
15746
  typeStack.push({ name: enname, startDepth: braceDepth, bodyEntered: false });
15621
15747
  matched = true;
15622
15748
  }
@@ -15626,36 +15752,36 @@ function extractScala(content, filePath) {
15626
15752
  if (depthInType === 1) {
15627
15753
  const fm = FUNC_RE2.exec(stripped);
15628
15754
  if (fm) {
15629
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), frame.name));
15755
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15630
15756
  matched = true;
15631
15757
  }
15632
15758
  const vm = !matched ? VAL_RE.exec(stripped) : null;
15633
15759
  if (vm) {
15634
- symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "val", lineNum, stripped.slice(0, 200), frame.name));
15760
+ symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "val", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15635
15761
  matched = true;
15636
15762
  }
15637
15763
  if (!matched) {
15638
15764
  const varm = VAR_RE.exec(stripped);
15639
15765
  if (varm) {
15640
- symbols.push(makeLineSymbol(filePath, varm[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name));
15766
+ symbols.push(makeLineSymbol(filePath, varm[1] ?? "", "var", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15641
15767
  }
15642
15768
  }
15643
15769
  }
15644
15770
  } else if (!matched && frame === null && !isIndented) {
15645
15771
  const fm = FUNC_RE2.exec(stripped);
15646
15772
  if (fm) {
15647
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200)));
15773
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
15648
15774
  matched = true;
15649
15775
  }
15650
15776
  const vm = !matched ? VAL_RE.exec(stripped) : null;
15651
15777
  if (vm) {
15652
- symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "val", lineNum, stripped.slice(0, 200)));
15778
+ symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "val", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
15653
15779
  matched = true;
15654
15780
  }
15655
15781
  if (!matched) {
15656
15782
  const varm = VAR_RE.exec(stripped);
15657
15783
  if (varm) {
15658
- symbols.push(makeLineSymbol(filePath, varm[1] ?? "", "var", lineNum, stripped.slice(0, 200)));
15784
+ symbols.push(makeLineSymbol(filePath, varm[1] ?? "", "var", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
15659
15785
  }
15660
15786
  }
15661
15787
  }
@@ -15834,14 +15960,14 @@ function extractElixir(content, filePath) {
15834
15960
  const modM = MODULE_RE.exec(stripped);
15835
15961
  if (modM) {
15836
15962
  const modName = modM[1] ?? "";
15837
- symbols.push(makeLineSymbol(filePath, modName, "class", lineNum, stripped.slice(0, 200)));
15963
+ symbols.push(makeLineSymbol(filePath, modName, "class", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
15838
15964
  moduleStack.push({ name: modName, endKeywordNeeded: true, isBlock: false });
15839
15965
  continue;
15840
15966
  }
15841
15967
  const protoM = PROTOCOL_RE.exec(stripped);
15842
15968
  if (protoM) {
15843
15969
  const protoName = protoM[1] ?? "";
15844
- symbols.push(makeLineSymbol(filePath, protoName, "protocol", lineNum, stripped.slice(0, 200)));
15970
+ symbols.push(makeLineSymbol(filePath, protoName, "protocol", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
15845
15971
  moduleStack.push({ name: protoName, endKeywordNeeded: true, isBlock: false });
15846
15972
  continue;
15847
15973
  }
@@ -15850,9 +15976,9 @@ function extractElixir(content, filePath) {
15850
15976
  const fname = fm[1] ?? "";
15851
15977
  const parent = nearestDefName(moduleStack);
15852
15978
  if (parent !== void 0) {
15853
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), parent));
15979
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), parent, lines2, "hash"));
15854
15980
  } else {
15855
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
15981
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
15856
15982
  }
15857
15983
  if (opensDoBlock) {
15858
15984
  moduleStack.push({ name: fname, endKeywordNeeded: true, isBlock: false });
@@ -15864,9 +15990,9 @@ function extractElixir(content, filePath) {
15864
15990
  const fname = pfm[1] ?? "";
15865
15991
  const parent = nearestDefName(moduleStack);
15866
15992
  if (parent !== void 0) {
15867
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), parent));
15993
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), parent, lines2, "hash"));
15868
15994
  } else {
15869
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
15995
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
15870
15996
  }
15871
15997
  if (opensDoBlock) {
15872
15998
  moduleStack.push({ name: fname, endKeywordNeeded: true, isBlock: false });
@@ -15876,7 +16002,7 @@ function extractElixir(content, filePath) {
15876
16002
  if (STRUCT_RE.test(stripped)) {
15877
16003
  const parent = nearestDefName(moduleStack);
15878
16004
  if (parent !== void 0) {
15879
- symbols.push(makeLineSymbol(filePath, "__struct__", "var", lineNum, stripped.slice(0, 200), parent));
16005
+ symbols.push(makeLineSymbol(filePath, "__struct__", "var", lineNum, stripped.slice(0, 200), parent, lines2, "hash"));
15880
16006
  }
15881
16007
  continue;
15882
16008
  }
@@ -15936,7 +16062,7 @@ function extractDart(content, filePath) {
15936
16062
  if (cm) {
15937
16063
  const cname = cm[1] ?? "";
15938
16064
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15939
- symbols.push(makeLineSymbol(filePath, cname, "class", lineNum, stripped.slice(0, 200), parent));
16065
+ symbols.push(makeLineSymbol(filePath, cname, "class", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15940
16066
  typeStack.push({ name: cname, startDepth: braceDepth, bodyEntered: false });
15941
16067
  matched = true;
15942
16068
  }
@@ -15944,7 +16070,7 @@ function extractDart(content, filePath) {
15944
16070
  if (em) {
15945
16071
  const ename = em[1] ?? "";
15946
16072
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15947
- symbols.push(makeLineSymbol(filePath, ename, "enum", lineNum, stripped.slice(0, 200), parent));
16073
+ symbols.push(makeLineSymbol(filePath, ename, "enum", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15948
16074
  typeStack.push({ name: ename, startDepth: braceDepth, bodyEntered: false });
15949
16075
  matched = true;
15950
16076
  }
@@ -15952,7 +16078,7 @@ function extractDart(content, filePath) {
15952
16078
  if (mm) {
15953
16079
  const mname = mm[1] ?? "";
15954
16080
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15955
- symbols.push(makeLineSymbol(filePath, mname, "mixin", lineNum, stripped.slice(0, 200), parent));
16081
+ symbols.push(makeLineSymbol(filePath, mname, "mixin", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15956
16082
  typeStack.push({ name: mname, startDepth: braceDepth, bodyEntered: false });
15957
16083
  matched = true;
15958
16084
  }
@@ -15960,7 +16086,7 @@ function extractDart(content, filePath) {
15960
16086
  if (etm) {
15961
16087
  const etname = etm[1] ?? "";
15962
16088
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15963
- symbols.push(makeLineSymbol(filePath, etname, "extension_type", lineNum, stripped.slice(0, 200), parent));
16089
+ symbols.push(makeLineSymbol(filePath, etname, "extension_type", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15964
16090
  typeStack.push({ name: etname, startDepth: braceDepth, bodyEntered: false });
15965
16091
  matched = true;
15966
16092
  }
@@ -15968,7 +16094,7 @@ function extractDart(content, filePath) {
15968
16094
  if (extm) {
15969
16095
  const extname14 = extm[1] ?? "extension";
15970
16096
  const parent = typeStack.length > 0 ? typeStack[typeStack.length - 1].name : void 0;
15971
- symbols.push(makeLineSymbol(filePath, extname14, "extension", lineNum, stripped.slice(0, 200), parent));
16097
+ symbols.push(makeLineSymbol(filePath, extname14, "extension", lineNum, stripped.slice(0, 200), parent, lines2, "c"));
15972
16098
  typeStack.push({ name: extname14, startDepth: braceDepth, bodyEntered: false });
15973
16099
  matched = true;
15974
16100
  }
@@ -15981,7 +16107,7 @@ function extractDart(content, filePath) {
15981
16107
  if (fm) {
15982
16108
  let fname = fm[1] ?? "";
15983
16109
  fname = fname.replace(/^operator\s+/, "");
15984
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), frame.name));
16110
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
15985
16111
  }
15986
16112
  }
15987
16113
  } else if (!matched && frame === null && !isIndented) {
@@ -15989,7 +16115,7 @@ function extractDart(content, filePath) {
15989
16115
  if (fm) {
15990
16116
  let fname = fm[1] ?? "";
15991
16117
  fname = fname.replace(/^operator\s+/, "");
15992
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
16118
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
15993
16119
  }
15994
16120
  }
15995
16121
  const braceLine = stripStringLiterals(line);
@@ -16061,7 +16187,7 @@ function extractZig(content, filePath) {
16061
16187
  const skind = sm[2] ?? "struct";
16062
16188
  if (sname) {
16063
16189
  const parent = outerFrame !== null ? outerFrame.name : void 0;
16064
- symbols.push(makeLineSymbol(filePath, sname, skind, lineNum, stripped.slice(0, 200), parent));
16190
+ symbols.push(makeLineSymbol(filePath, sname, skind, lineNum, stripped.slice(0, 200), parent, lines2, "c"));
16065
16191
  scopeStack.push({ name: sname, startDepth: braceDepth, bodyEntered: false });
16066
16192
  matched = true;
16067
16193
  }
@@ -16072,7 +16198,7 @@ function extractZig(content, filePath) {
16072
16198
  const fm = FUNC_RE6.exec(stripped);
16073
16199
  if (fm) {
16074
16200
  const fname = fm[2] ?? "";
16075
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
16201
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
16076
16202
  matched = true;
16077
16203
  }
16078
16204
  } else if (!matched && frame !== null) {
@@ -16081,7 +16207,7 @@ function extractZig(content, filePath) {
16081
16207
  const fm = FUNC_RE6.exec(stripped);
16082
16208
  if (fm) {
16083
16209
  const fname = fm[2] ?? "";
16084
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), frame.name));
16210
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), frame.name, lines2, "c"));
16085
16211
  matched = true;
16086
16212
  }
16087
16213
  }
@@ -16089,13 +16215,13 @@ function extractZig(content, filePath) {
16089
16215
  if (!matched && !isIndented) {
16090
16216
  const cm = CONST_RE3.exec(stripped);
16091
16217
  if (cm) {
16092
- symbols.push(makeLineSymbol(filePath, cm[1] ?? "", "const", lineNum, stripped.slice(0, 200)));
16218
+ symbols.push(makeLineSymbol(filePath, cm[1] ?? "", "const", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
16093
16219
  matched = true;
16094
16220
  }
16095
16221
  if (!matched) {
16096
16222
  const vm = VAR_RE2.exec(stripped);
16097
16223
  if (vm) {
16098
- symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "var", lineNum, stripped.slice(0, 200)));
16224
+ symbols.push(makeLineSymbol(filePath, vm[1] ?? "", "var", lineNum, stripped.slice(0, 200), void 0, lines2, "c"));
16099
16225
  }
16100
16226
  }
16101
16227
  }
@@ -16154,17 +16280,17 @@ function extractR(content, filePath) {
16154
16280
  if (!isIndented) {
16155
16281
  const fm = FUNC_ASSIGN_RE.exec(stripped);
16156
16282
  if (fm) {
16157
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200)));
16283
+ symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
16158
16284
  continue;
16159
16285
  }
16160
16286
  const cm = SETCLASS_RE.exec(stripped);
16161
16287
  if (cm) {
16162
- symbols.push(makeLineSymbol(filePath, cm[1] ?? "", "class", lineNum, stripped.slice(0, 200)));
16288
+ symbols.push(makeLineSymbol(filePath, cm[1] ?? "", "class", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
16163
16289
  continue;
16164
16290
  }
16165
16291
  const mm = SETMETHOD_RE.exec(stripped);
16166
16292
  if (mm) {
16167
- symbols.push(makeLineSymbol(filePath, mm[1] ?? "", "function", lineNum, stripped.slice(0, 200)));
16293
+ symbols.push(makeLineSymbol(filePath, mm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
16168
16294
  }
16169
16295
  }
16170
16296
  }
@@ -16698,7 +16824,7 @@ function extractBash(content, filePath) {
16698
16824
  if (funcMatch) {
16699
16825
  const fname = funcMatch[1] ?? "";
16700
16826
  if (fname && symbols.length < MAX_SYMBOLS4) {
16701
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
16827
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
16702
16828
  }
16703
16829
  if (fname) {
16704
16830
  if (stripped.includes("{")) {
@@ -16719,7 +16845,7 @@ function extractBash(content, filePath) {
16719
16845
  if (varMatch) {
16720
16846
  const vname = varMatch[1] ?? "";
16721
16847
  if (vname && symbols.length < MAX_SYMBOLS4) {
16722
- symbols.push(makeLineSymbol(filePath, vname, "variable", lineNum, stripped.slice(0, 200)));
16848
+ symbols.push(makeLineSymbol(filePath, vname, "variable", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
16723
16849
  }
16724
16850
  }
16725
16851
  }
@@ -17228,7 +17354,7 @@ function extractPowershell(content, filePath) {
17228
17354
  if (funcMatch) {
17229
17355
  const fname = funcMatch[1] ?? "";
17230
17356
  if (symbols.length < MAX_SYMBOLS8) {
17231
- symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, line.trimEnd().slice(0, 200)));
17357
+ symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, line.trimEnd().slice(0, 200), void 0, lines2, "hash"));
17232
17358
  }
17233
17359
  }
17234
17360
  }
@@ -17238,7 +17364,7 @@ function extractPowershell(content, filePath) {
17238
17364
  const cname = classMatch[2] ?? "";
17239
17365
  const kind = (classMatch[1] ?? "").toLowerCase() === "enum" ? "enum" : "class";
17240
17366
  if (symbols.length < MAX_SYMBOLS8) {
17241
- symbols.push(makeLineSymbol(filePath, cname, kind, lineNum, line.trimEnd().slice(0, 200)));
17367
+ symbols.push(makeLineSymbol(filePath, cname, kind, lineNum, line.trimEnd().slice(0, 200), void 0, lines2, "hash"));
17242
17368
  }
17243
17369
  if (kind === "class") {
17244
17370
  const strippedLine = stripPowershellStringLiterals(line);
@@ -17263,7 +17389,7 @@ function extractPowershell(content, filePath) {
17263
17389
  if (mname && symbols.length < MAX_SYMBOLS8) {
17264
17390
  const sigEnd = line.indexOf("{");
17265
17391
  const sig = sigEnd >= 0 ? line.slice(0, sigEnd).trimEnd() : line.trimEnd();
17266
- symbols.push(makeLineSymbol(filePath, mname, "method", lineNum, sig.slice(0, 200), currentClass));
17392
+ symbols.push(makeLineSymbol(filePath, mname, "method", lineNum, sig.slice(0, 200), currentClass, lines2, "hash"));
17267
17393
  }
17268
17394
  }
17269
17395
  }
@@ -17361,12 +17487,13 @@ function extractApex(content, filePath) {
17361
17487
  const stringFree = stripStringLiterals(blockCommentFree);
17362
17488
  const code = stripCstyleComments(stringFree, /\/\/.*$/gm);
17363
17489
  const codeLines = code.split(/\r?\n/);
17364
- const emit5 = (name2, kind, span, docstring = "") => {
17490
+ const rawLines = content.split(/\r?\n/);
17491
+ const emit5 = (name2, kind, span, parent = "") => {
17365
17492
  if (!name2 || symbols.length >= MAX_SYMBOLS9) return;
17366
17493
  const key = `${name2}\0${kind}\0${span.startLine}`;
17367
17494
  if (seen.has(key)) return;
17368
17495
  seen.add(key);
17369
- symbols.push(makeSpanSymbol(filePath, name2, kind, span, docstring));
17496
+ symbols.push(makeSpanSymbol(filePath, name2, kind, span, parent, rawLines, "c"));
17370
17497
  };
17371
17498
  for (const match2 of code.matchAll(TRIGGER_RE2)) {
17372
17499
  const name2 = match2[1] ?? "";
@@ -17812,7 +17939,7 @@ function lwcTagAlias(name2) {
17812
17939
  return `c-${kebab}`;
17813
17940
  }
17814
17941
  function symbol(filePath, name2, kind, lineStart, lineEnd = lineStart) {
17815
- return { filePath, name: name2, kind, lineStart, lineEnd, body: "", docstring: "" };
17942
+ return { filePath, name: name2, kind, lineStart, lineEnd, body: "", docstring: "", parent: "" };
17816
17943
  }
17817
17944
  function ref(filePath, name2, line, col, context) {
17818
17945
  return { filePath, name: name2, line, col, context };
@@ -18084,7 +18211,7 @@ function maskSpans(content, spans) {
18084
18211
  return chars.join("");
18085
18212
  }
18086
18213
  function componentSymbol(filePath, name2, kind, totalLines) {
18087
- return { filePath, name: name2, kind, lineStart: 1, lineEnd: totalLines, body: "", docstring: "" };
18214
+ return { filePath, name: name2, kind, lineStart: 1, lineEnd: totalLines, body: "", docstring: "", parent: "" };
18088
18215
  }
18089
18216
  function extractVue(content, filePath) {
18090
18217
  const totalLines = content.split("\n").length;
@@ -18362,15 +18489,17 @@ function nodeName(node) {
18362
18489
  if (named !== null) return named.text;
18363
18490
  return null;
18364
18491
  }
18365
- function makeSymbol(filePath, name2, kind, node) {
18492
+ function makeSymbol(filePath, name2, kind, node, lines2, style) {
18493
+ const lineStart = node.startPosition.row + 1;
18366
18494
  return {
18367
18495
  filePath,
18368
18496
  name: name2,
18369
18497
  kind,
18370
- lineStart: node.startPosition.row + 1,
18498
+ lineStart,
18371
18499
  lineEnd: node.endPosition.row + 1,
18372
18500
  body: node.text,
18373
- docstring: ""
18501
+ docstring: lines2 !== void 0 && style !== void 0 ? precedingDocComment(lines2, lineStart, style) : "",
18502
+ parent: ""
18374
18503
  };
18375
18504
  }
18376
18505
  function collectPatternBindings(node) {
@@ -18385,7 +18514,7 @@ function collectPatternBindings(node) {
18385
18514
  walk(node);
18386
18515
  return names;
18387
18516
  }
18388
- function extractTsJsSymbols(root, filePath) {
18517
+ function extractTsJsSymbols(root, filePath, lines2) {
18389
18518
  const out2 = [];
18390
18519
  const visit = (node, insideFunction) => {
18391
18520
  const kind = TSJS_KIND_BY_TYPE.get(node.type);
@@ -18394,16 +18523,18 @@ function extractTsJsSymbols(root, filePath) {
18394
18523
  if (name2 !== null && name2 !== "") {
18395
18524
  const decorators = leadingTsDecorators(node);
18396
18525
  if (decorators.length === 0) {
18397
- out2.push(makeSymbol(filePath, name2, kind, node));
18526
+ out2.push(makeSymbol(filePath, name2, kind, node, lines2, "c"));
18398
18527
  } else {
18528
+ const lineStart = decorators[0].startPosition.row + 1;
18399
18529
  out2.push({
18400
18530
  filePath,
18401
18531
  name: name2,
18402
18532
  kind,
18403
- lineStart: decorators[0].startPosition.row + 1,
18533
+ lineStart,
18404
18534
  lineEnd: node.endPosition.row + 1,
18405
18535
  body: [...decorators, node].map((n) => n.text).join("\n"),
18406
- docstring: ""
18536
+ docstring: precedingDocComment(lines2, lineStart, "c"),
18537
+ parent: ""
18407
18538
  });
18408
18539
  }
18409
18540
  }
@@ -18416,10 +18547,10 @@ function extractTsJsSymbols(root, filePath) {
18416
18547
  if (name2 === null) continue;
18417
18548
  if (name2.type === "identifier") {
18418
18549
  const isFn = value !== null && (value.type === "arrow_function" || value.type === "function_expression" || value.type === "function");
18419
- out2.push(makeSymbol(filePath, name2.text, isFn ? "function" : "variable", child));
18550
+ out2.push(makeSymbol(filePath, name2.text, isFn ? "function" : "variable", child, lines2, "c"));
18420
18551
  } else {
18421
18552
  for (const bound of collectPatternBindings(name2)) {
18422
- out2.push(makeSymbol(filePath, bound, "variable", child));
18553
+ out2.push(makeSymbol(filePath, bound, "variable", child, lines2, "c"));
18423
18554
  }
18424
18555
  }
18425
18556
  }
@@ -18428,7 +18559,7 @@ function extractTsJsSymbols(root, filePath) {
18428
18559
  const fieldName = node.childForFieldName("name") ?? node.childForFieldName("property");
18429
18560
  const value = node.childForFieldName("value");
18430
18561
  if (fieldName !== null && value !== null && (value.type === "arrow_function" || value.type === "function_expression" || value.type === "function")) {
18431
- out2.push(makeSymbol(filePath, fieldName.text, "method", node));
18562
+ out2.push(makeSymbol(filePath, fieldName.text, "method", node, lines2, "c"));
18432
18563
  }
18433
18564
  }
18434
18565
  const childInside = insideFunction || TSJS_FN_SCOPE_TYPES.has(node.type);
@@ -18492,14 +18623,14 @@ function stripPythonStringQuotes(raw) {
18492
18623
  }
18493
18624
  return s.trim();
18494
18625
  }
18495
- function extractGoSymbols(root, filePath) {
18626
+ function extractGoSymbols(root, filePath, lines2) {
18496
18627
  const out2 = [];
18497
18628
  const visit = (node, insideFunction) => {
18498
18629
  const kind = GO_KIND_BY_TYPE.get(node.type);
18499
18630
  if (kind !== void 0 && !(insideFunction && GO_LOCAL_KINDS.has(node.type))) {
18500
18631
  const name2 = nodeName(node);
18501
18632
  if (name2 !== null && name2 !== "") {
18502
- out2.push(makeSymbol(filePath, name2, kind, node));
18633
+ out2.push(makeSymbol(filePath, name2, kind, node, lines2, "c"));
18503
18634
  }
18504
18635
  }
18505
18636
  const childInside = insideFunction || GO_FN_SCOPE_TYPES.has(node.type);
@@ -18519,7 +18650,7 @@ function leadingRustAttributes(node) {
18519
18650
  }
18520
18651
  return attrs;
18521
18652
  }
18522
- function extractRustSymbols(root, filePath) {
18653
+ function extractRustSymbols(root, filePath, lines2) {
18523
18654
  const out2 = [];
18524
18655
  const visit = (node, insideFunction) => {
18525
18656
  const kind = RUST_KIND_BY_TYPE.get(node.type);
@@ -18528,16 +18659,18 @@ function extractRustSymbols(root, filePath) {
18528
18659
  if (name2 !== null && name2 !== "") {
18529
18660
  const attrs = leadingRustAttributes(node);
18530
18661
  if (attrs.length === 0) {
18531
- out2.push(makeSymbol(filePath, name2, kind, node));
18662
+ out2.push(makeSymbol(filePath, name2, kind, node, lines2, "c"));
18532
18663
  } else {
18664
+ const lineStart = attrs[0].startPosition.row + 1;
18533
18665
  out2.push({
18534
18666
  filePath,
18535
18667
  name: name2,
18536
18668
  kind,
18537
- lineStart: attrs[0].startPosition.row + 1,
18669
+ lineStart,
18538
18670
  lineEnd: node.endPosition.row + 1,
18539
18671
  body: [...attrs, node].map((n) => n.text).join("\n"),
18540
- docstring: ""
18672
+ docstring: precedingDocComment(lines2, lineStart, "c"),
18673
+ parent: ""
18541
18674
  });
18542
18675
  }
18543
18676
  }
@@ -18550,14 +18683,14 @@ function extractRustSymbols(root, filePath) {
18550
18683
  visit(root, false);
18551
18684
  return out2;
18552
18685
  }
18553
- function extractSimpleSymbols(root, filePath, kindByType, nameFor = nodeName) {
18686
+ function extractSimpleSymbols(root, filePath, kindByType, lines2, style, nameFor = nodeName) {
18554
18687
  const out2 = [];
18555
18688
  const visit = (node) => {
18556
18689
  const kind = kindByType.get(node.type);
18557
18690
  if (kind !== void 0) {
18558
18691
  const name2 = nameFor(node);
18559
18692
  if (name2 !== null && name2 !== "") {
18560
- out2.push(makeSymbol(filePath, name2, kind, node));
18693
+ out2.push(makeSymbol(filePath, name2, kind, node, lines2, style));
18561
18694
  }
18562
18695
  }
18563
18696
  for (const child of node.namedChildren) {
@@ -18567,17 +18700,19 @@ function extractSimpleSymbols(root, filePath, kindByType, nameFor = nodeName) {
18567
18700
  visit(root);
18568
18701
  return out2;
18569
18702
  }
18570
- function extractRubySymbols(root, filePath) {
18571
- return extractSimpleSymbols(root, filePath, RUBY_KIND_BY_TYPE);
18703
+ function extractRubySymbols(root, filePath, lines2) {
18704
+ return extractSimpleSymbols(root, filePath, RUBY_KIND_BY_TYPE, lines2, "hash");
18572
18705
  }
18573
- function extractJavaSymbols(root, filePath) {
18574
- return extractSimpleSymbols(root, filePath, JAVA_KIND_BY_TYPE);
18706
+ function extractJavaSymbols(root, filePath, lines2) {
18707
+ return extractSimpleSymbols(root, filePath, JAVA_KIND_BY_TYPE, lines2, "c");
18575
18708
  }
18576
- function extractCppSymbols(root, filePath) {
18709
+ function extractCppSymbols(root, filePath, lines2) {
18577
18710
  return extractSimpleSymbols(
18578
18711
  root,
18579
18712
  filePath,
18580
18713
  CPP_KIND_BY_TYPE,
18714
+ lines2,
18715
+ "c",
18581
18716
  (node) => node.type === "function_definition" ? cFunctionName(node) : node.type === "type_definition" ? cTypedefAliasName(node) : node.type === "declaration" ? cFunctionPrototypeName(node) : nodeName(node)
18582
18717
  );
18583
18718
  }
@@ -18839,7 +18974,8 @@ function extractMarkdownSymbols(content, filePath) {
18839
18974
  lineStart: i + 1,
18840
18975
  lineEnd: i + 1,
18841
18976
  body: line.trim(),
18842
- docstring: ""
18977
+ docstring: "",
18978
+ parent: ""
18843
18979
  });
18844
18980
  }
18845
18981
  }
@@ -18898,7 +19034,8 @@ function extractJsonSymbols(content, filePath) {
18898
19034
  lineStart: strStartLine,
18899
19035
  lineEnd,
18900
19036
  body,
18901
- docstring: ""
19037
+ docstring: "",
19038
+ parent: ""
18902
19039
  });
18903
19040
  }
18904
19041
  }
@@ -18983,7 +19120,8 @@ function extractYamlSymbols(content, filePath) {
18983
19120
  lineStart: i + 1,
18984
19121
  lineEnd: i + 1,
18985
19122
  body: line.trim(),
18986
- docstring: ""
19123
+ docstring: "",
19124
+ parent: ""
18987
19125
  });
18988
19126
  openQuote = yamlOpenQuoteAfter(line, match2[0].length);
18989
19127
  }
@@ -19035,7 +19173,8 @@ function extractTomlSymbols(content, filePath) {
19035
19173
  lineStart: lineNum + 1,
19036
19174
  lineEnd: lineNum + 1,
19037
19175
  body: line.trim(),
19038
- docstring: ""
19176
+ docstring: "",
19177
+ parent: ""
19039
19178
  });
19040
19179
  }
19041
19180
  const keyMatch = /^\s*([a-zA-Z_][\w-]*)\s*=/.exec(line);
@@ -19047,7 +19186,8 @@ function extractTomlSymbols(content, filePath) {
19047
19186
  lineStart: lineNum + 1,
19048
19187
  lineEnd: lineNum + 1,
19049
19188
  body: line.trim(),
19050
- docstring: ""
19189
+ docstring: "",
19190
+ parent: ""
19051
19191
  });
19052
19192
  }
19053
19193
  }
@@ -19121,7 +19261,8 @@ function extractCssSymbols(content, filePath) {
19121
19261
  lineStart: p.line,
19122
19262
  lineEnd: p.line,
19123
19263
  body: p.body,
19124
- docstring: ""
19264
+ docstring: "",
19265
+ parent: ""
19125
19266
  });
19126
19267
  }
19127
19268
  pending = [];
@@ -19138,7 +19279,8 @@ function extractCssSymbols(content, filePath) {
19138
19279
  lineStart: i + 1,
19139
19280
  lineEnd: i + 1,
19140
19281
  body: line.trim(),
19141
- docstring: ""
19282
+ docstring: "",
19283
+ parent: ""
19142
19284
  });
19143
19285
  }
19144
19286
  }
@@ -19153,7 +19295,8 @@ function extractCssSymbols(content, filePath) {
19153
19295
  lineStart: p.line,
19154
19296
  lineEnd: p.line,
19155
19297
  body: p.body,
19156
- docstring: ""
19298
+ docstring: "",
19299
+ parent: ""
19157
19300
  });
19158
19301
  }
19159
19302
  pending = [];
@@ -19197,7 +19340,8 @@ function extractDockerfileSymbols(content, filePath) {
19197
19340
  lineStart: i + 1,
19198
19341
  lineEnd: i + 1,
19199
19342
  body: line.trim(),
19200
- docstring: ""
19343
+ docstring: "",
19344
+ parent: ""
19201
19345
  });
19202
19346
  }
19203
19347
  continuing = !isComment2 && line.trimEnd().endsWith("\\");
@@ -19210,7 +19354,7 @@ function extractWithRegex(content, filePath) {
19210
19354
  for (let i = 0; i < lines2.length; i++) {
19211
19355
  const line = lines2[i];
19212
19356
  if (line === void 0) continue;
19213
- for (const { re, kind } of FALLBACK_PATTERNS) {
19357
+ for (const { re, kind, style } of FALLBACK_PATTERNS) {
19214
19358
  const m = re.exec(line);
19215
19359
  if (m !== null && m[1] !== void 0) {
19216
19360
  out2.push({
@@ -19220,7 +19364,8 @@ function extractWithRegex(content, filePath) {
19220
19364
  lineStart: i + 1,
19221
19365
  lineEnd: i + 1,
19222
19366
  body: line.trim(),
19223
- docstring: ""
19367
+ docstring: precedingDocComment(lines2, i + 1, style),
19368
+ parent: ""
19224
19369
  });
19225
19370
  break;
19226
19371
  }
@@ -19275,17 +19420,17 @@ function parseContent(content, filePath, language) {
19275
19420
  if (language === "python") {
19276
19421
  symbols = extractPythonSymbols(root, filePath);
19277
19422
  } else if (language === "go") {
19278
- symbols = extractGoSymbols(root, filePath);
19423
+ symbols = extractGoSymbols(root, filePath, content.split(/\r?\n/));
19279
19424
  } else if (language === "rust") {
19280
- symbols = extractRustSymbols(root, filePath);
19425
+ symbols = extractRustSymbols(root, filePath, content.split(/\r?\n/));
19281
19426
  } else if (language === "ruby") {
19282
- symbols = extractRubySymbols(root, filePath);
19427
+ symbols = extractRubySymbols(root, filePath, content.split(/\r?\n/));
19283
19428
  } else if (language === "java") {
19284
- symbols = extractJavaSymbols(root, filePath);
19429
+ symbols = extractJavaSymbols(root, filePath, content.split(/\r?\n/));
19285
19430
  } else if (language === "cpp" || language === "c") {
19286
- symbols = extractCppSymbols(root, filePath);
19431
+ symbols = extractCppSymbols(root, filePath, content.split(/\r?\n/));
19287
19432
  } else {
19288
- symbols = extractTsJsSymbols(root, filePath);
19433
+ symbols = extractTsJsSymbols(root, filePath, content.split(/\r?\n/));
19289
19434
  }
19290
19435
  const refs = REF_LANGUAGES.has(language) ? extractRefs(root, filePath, language) : [];
19291
19436
  const parsed = { symbols, refs };
@@ -19304,7 +19449,8 @@ function sectionsToHeadingSymbols(sections, filePath) {
19304
19449
  lineStart: s.line,
19305
19450
  lineEnd: s.endLine,
19306
19451
  body: "",
19307
- docstring: ""
19452
+ docstring: "",
19453
+ parent: ""
19308
19454
  }));
19309
19455
  }
19310
19456
  function extractNoTreeSitter(content, filePath, language) {
@@ -19340,6 +19486,7 @@ function isUnderSkipDir(filePath, skipDirs) {
19340
19486
  }
19341
19487
  function isParseSkipEligible(filePath, cfg) {
19342
19488
  if (isUnderSkipDir(filePath, cfg.skip_dirs)) return true;
19489
+ if (cfg.skip_files.includes(path26.basename(filePath))) return true;
19343
19490
  try {
19344
19491
  const stat2 = fs22.statSync(filePath);
19345
19492
  if (stat2.size > cfg.large_file_skip_kb * 1024) return true;
@@ -19358,11 +19505,20 @@ function writeParseResult(filePath, content, result, dbPath) {
19358
19505
  "INSERT INTO files (path, sha, mtime, language, indexed_at) VALUES (?, ?, ?, ?, ?)"
19359
19506
  ).run(filePath, sha, mtime, result.language, now);
19360
19507
  const insSym = db.prepare(
19361
- "INSERT INTO symbols (file_path, name, kind, line_start, line_end, body, docstring) VALUES (?, ?, ?, ?, ?, ?, ?)"
19508
+ "INSERT INTO symbols (file_path, name, kind, line_start, line_end, body, docstring, parent) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
19362
19509
  );
19363
19510
  for (const s of result.symbols) {
19364
19511
  if (s.name === "" || s.kind === "") continue;
19365
- insSym.run(s.filePath, s.name, s.kind, s.lineStart, s.lineEnd, boundSymbolBody(s.body), boundSymbolDocstring(s.docstring));
19512
+ insSym.run(
19513
+ s.filePath,
19514
+ s.name,
19515
+ s.kind,
19516
+ s.lineStart,
19517
+ s.lineEnd,
19518
+ boundSymbolBody(s.body),
19519
+ boundSymbolDocstring(s.docstring),
19520
+ s.parent
19521
+ );
19366
19522
  }
19367
19523
  const insRef = db.prepare(
19368
19524
  "INSERT INTO refs (file_path, name, line, col, context) VALUES (?, ?, ?, ?, ?)"
@@ -19521,6 +19677,7 @@ var init_parser = __esm({
19521
19677
  init_sql_path();
19522
19678
  init_markdown_lines();
19523
19679
  init_parser_types();
19680
+ init_doc_comment();
19524
19681
  init_index_reader();
19525
19682
  init_markdown_hints();
19526
19683
  init_csharp();
@@ -19550,6 +19707,7 @@ var init_parser = __esm({
19550
19707
  init_sfc_idx();
19551
19708
  init_ipynb_idx();
19552
19709
  init_util2();
19710
+ init_doc_comment();
19553
19711
  _require4 = createRequire5(import.meta.url);
19554
19712
  _grammarCache = /* @__PURE__ */ new Map();
19555
19713
  CPP_HEADER_SNIFF_RE = /\bclass\s+\w|\bnamespace\s+\w|\btemplate\s*<|::\s*\w|\b(?:public|private|protected)\s*:/;
@@ -19992,28 +20150,31 @@ var init_parser = __esm({
19992
20150
  EMPTY_STRING_SET = /* @__PURE__ */ new Set();
19993
20151
  FALLBACK_PATTERNS = [
19994
20152
  // Python
19995
- { re: /^[ \t]*(?:async\s+)?def\s+([A-Za-z_]\w*)/, kind: "function" },
19996
- { re: /^[ \t]*class\s+([A-Za-z_]\w*)/, kind: "class" },
20153
+ { re: /^[ \t]*(?:async\s+)?def\s+([A-Za-z_]\w*)/, kind: "function", style: "hash" },
20154
+ { re: /^[ \t]*class\s+([A-Za-z_]\w*)/, kind: "class", style: "hash" },
19997
20155
  // TS/JS function & class declarations (optionally exported/async)
19998
20156
  {
19999
20157
  re: /^[ \t]*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/,
20000
- kind: "function"
20158
+ kind: "function",
20159
+ style: "c"
20001
20160
  },
20002
20161
  {
20003
20162
  re: /^[ \t]*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/,
20004
- kind: "class"
20163
+ kind: "class",
20164
+ style: "c"
20005
20165
  },
20006
- { re: /^[ \t]*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/, kind: "interface" },
20007
- { re: /^[ \t]*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/, kind: "type" },
20166
+ { re: /^[ \t]*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/, kind: "interface", style: "c" },
20167
+ { re: /^[ \t]*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/, kind: "type", style: "c" },
20008
20168
  // const/let/var bound to an arrow or function expression
20009
20169
  {
20010
20170
  re: /^[ \t]*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/,
20011
- kind: "function"
20171
+ kind: "function",
20172
+ style: "c"
20012
20173
  },
20013
20174
  // Rust / Go function & struct/type patterns
20014
- { re: /^[ \t]*(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_]\w*)/, kind: "function" },
20015
- { re: /^[ \t]*(?:pub\s+)?struct\s+([A-Za-z_]\w*)/, kind: "struct" },
20016
- { re: /^[ \t]*func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/, kind: "function" }
20175
+ { re: /^[ \t]*(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_]\w*)/, kind: "function", style: "c" },
20176
+ { re: /^[ \t]*(?:pub\s+)?struct\s+([A-Za-z_]\w*)/, kind: "struct", style: "c" },
20177
+ { re: /^[ \t]*func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/, kind: "function", style: "c" }
20017
20178
  ];
20018
20179
  NO_TREE_SITTER_EXTRACTORS = {
20019
20180
  markdown: extractMarkdownSymbols,
@@ -24043,7 +24204,7 @@ function fetchTopSymbols(limit, dbPath, rootDir) {
24043
24204
  const db = getDb(dbPath);
24044
24205
  const { clause, param } = projectScopeClause("file_path");
24045
24206
  const rows = db.prepare(
24046
- `SELECT file_path, name, kind, line_start, line_end, body, docstring
24207
+ `SELECT file_path, name, kind, line_start, line_end, body, docstring, parent
24047
24208
  FROM symbols
24048
24209
  WHERE kind IN ('class', 'function', 'interface') AND ${clause}
24049
24210
  ORDER BY CASE kind WHEN 'class' THEN 0 WHEN 'interface' THEN 1 ELSE 2 END,
@@ -24057,7 +24218,8 @@ function fetchTopSymbols(limit, dbPath, rootDir) {
24057
24218
  lineStart: r.line_start,
24058
24219
  lineEnd: r.line_end,
24059
24220
  body: r.body ?? "",
24060
- docstring: r.docstring ?? ""
24221
+ docstring: r.docstring ?? "",
24222
+ parent: r.parent ?? ""
24061
24223
  }));
24062
24224
  } catch {
24063
24225
  return [];
@@ -24172,9 +24334,9 @@ function formatMemSuggestions(projectRoot) {
24172
24334
  if (suggestions.length === 0) return "";
24173
24335
  const lines2 = ["", "## mem suggestions"];
24174
24336
  for (const s of suggestions) {
24175
- const basename20 = path33.basename(s.path);
24337
+ const basename21 = path33.basename(s.path);
24176
24338
  lines2.push(
24177
- "Consider: mem import --from-md " + s.path + " # migrates " + s.count + " preference-shaped lines from " + basename20 + " as pending facts for review"
24339
+ "Consider: mem import --from-md " + s.path + " # migrates " + s.count + " preference-shaped lines from " + basename21 + " as pending facts for review"
24178
24340
  );
24179
24341
  }
24180
24342
  return lines2.join(String.fromCharCode(10));
@@ -29545,6 +29707,8 @@ function findParentName(entry, fileSymbols) {
29545
29707
  }
29546
29708
  }
29547
29709
  if (best !== null) return best.name;
29710
+ const parent = (entry.parent ?? "").trim();
29711
+ if (parent !== "") return parent;
29548
29712
  const doc = entry.docstring.trim();
29549
29713
  if (doc !== "" && PARENT_IDENTIFIER_RE.test(doc)) return doc;
29550
29714
  return null;
@@ -29621,7 +29785,9 @@ function resolveSymbolSpec(spec, forceRefresh, projectRoot) {
29621
29785
  });
29622
29786
  const symBaseLower = symBase.toLowerCase();
29623
29787
  const scoped = candidates.filter((c) => {
29624
- if (c.docstring.toLowerCase() === symBaseLower) return true;
29788
+ const cParent = c.parent ?? "";
29789
+ if (cParent.toLowerCase() === symBaseLower) return true;
29790
+ if (cParent === "" && c.docstring.toLowerCase() === symBaseLower) return true;
29625
29791
  return containers.some(
29626
29792
  (cls) => cls.filePath === c.filePath && c.lineStart >= cls.lineStart && c.lineEnd <= cls.lineEnd
29627
29793
  );
@@ -29634,6 +29800,10 @@ function runRead(opts) {
29634
29800
  const range2 = parseLineRange(opts.spec);
29635
29801
  if (range2 !== null) return runLineRange(range2, opts);
29636
29802
  const { file: file2, symbol: symbol3 } = parseReadSpec(opts.spec);
29803
+ if (symbol3 !== void 0 && symbol3 !== "" && symbol3.includes(",") && parseColonLineRange(symbol3) === null) {
29804
+ const multiSymbols = symbol3.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
29805
+ if (multiSymbols.length > 1) return runReadMulti(file2, multiSymbols, opts);
29806
+ }
29637
29807
  if (symbol3 === void 0 || symbol3 === "") {
29638
29808
  const text2 = readFileText(file2);
29639
29809
  if (text2 === null) {
@@ -29661,22 +29831,55 @@ function runRead(opts) {
29661
29831
  }
29662
29832
  const match2 = resolution.entry;
29663
29833
  const fullSourceBytes = sumFileSizes([match2.filePath]);
29834
+ const refCounts = opts.stats === true ? queryRefCounts([match2.name], globalDbPath(), resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() })) : void 0;
29664
29835
  if (opts.json === true) {
29665
- const text2 = JSON.stringify({ ...match2, body: resolveBody(match2) }, null, 2);
29666
- recordReadStat("read_replacement", fullSourceBytes, text2, opts.spec);
29836
+ const text2 = JSON.stringify(
29837
+ {
29838
+ ...match2,
29839
+ body: resolveBody(match2),
29840
+ ...refCounts !== void 0 ? { refCount: refCounts.get(match2.name) ?? 0 } : {}
29841
+ },
29842
+ null,
29843
+ 2
29844
+ );
29845
+ if (opts.suppressStat !== true) recordReadStat("read_replacement", fullSourceBytes, text2, opts.spec);
29667
29846
  return { text: text2, code: 0 };
29668
29847
  }
29669
29848
  const body = resolveBody(match2);
29670
29849
  const bodyLen = match2.lineEnd - match2.lineStart + 1;
29850
+ const statsStr = formatStatsSuffix(refCounts, match2);
29671
29851
  const lines2 = [
29672
- `# ${bodyLen} lines (~${Math.ceil(body.length / 4)} tok)`,
29852
+ `# ${bodyLen} lines (~${Math.ceil(body.length / 4)} tok)${statsStr}`,
29673
29853
  body
29674
29854
  ];
29675
29855
  const warning = staleWarning(match2.filePath);
29676
29856
  const text = guardText(warning + trimBlankLines(lines2).join("\n"), "symbol");
29677
- recordReadStat("read_replacement", fullSourceBytes, text, opts.spec);
29857
+ if (opts.suppressStat !== true) recordReadStat("read_replacement", fullSourceBytes, text, opts.spec);
29678
29858
  return { text, code: 0 };
29679
29859
  }
29860
+ function runReadMulti(file2, symbols, opts) {
29861
+ let anyFound = false;
29862
+ const jsonOut = {};
29863
+ const textBlocks = [];
29864
+ for (const sym of symbols) {
29865
+ const sub = runRead({ ...opts, spec: `${file2}::${sym}`, suppressStat: true });
29866
+ if (sub.code === 0) anyFound = true;
29867
+ if (opts.json === true) {
29868
+ jsonOut[sym] = sub.code === 0 ? JSON.parse(sub.text) : { error: sub.text };
29869
+ continue;
29870
+ }
29871
+ textBlocks.push(`${sym}:
29872
+ ${sub.text}`);
29873
+ }
29874
+ if (anyFound) {
29875
+ const fullSourceBytes = sumFileSizes([resolveIndexPath(file2, opts.projectRoot ?? process.cwd())]);
29876
+ const text2 = opts.json === true ? JSON.stringify(jsonOut, null, 2) : textBlocks.join("\n\n");
29877
+ recordReadStat("read_replacement", fullSourceBytes, text2, opts.spec);
29878
+ return { text: text2, code: 0 };
29879
+ }
29880
+ const text = opts.json === true ? JSON.stringify(jsonOut, null, 2) : textBlocks.join("\n\n");
29881
+ return { text, code: 1 };
29882
+ }
29680
29883
  function runSection(opts) {
29681
29884
  const colonIdx = findSpecSeparator(opts.spec);
29682
29885
  if (colonIdx === -1) {
@@ -29879,6 +30082,13 @@ function noSymbolsMessage(displayPath, resolvedPath) {
29879
30082
  }
29880
30083
  return `No indexed symbols found in '${displayPath}'`;
29881
30084
  }
30085
+ function hasRealDocstring(docstring) {
30086
+ const doc = docstring.trim();
30087
+ return doc !== "" && !PARENT_IDENTIFIER_RE.test(doc);
30088
+ }
30089
+ function formatStatsSuffix(refCounts, sym) {
30090
+ return refCounts !== void 0 ? ` [${refCounts.get(sym.name) ?? 0} refs, ${hasRealDocstring(sym.docstring) ? "documented" : "undocumented"}]` : "";
30091
+ }
29882
30092
  function prepareSymbolListing(file2, opts) {
29883
30093
  const resolved = resolveIndexPath(file2, opts.projectRoot ?? process.cwd());
29884
30094
  if (opts.forceRefresh === true) {
@@ -29911,7 +30121,7 @@ function runSkeleton(opts) {
29911
30121
  kind: s.kind,
29912
30122
  lineStart: s.lineStart,
29913
30123
  lineEnd: s.lineEnd,
29914
- ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: s.docstring.trim().length > 0 } : {}
30124
+ ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: hasRealDocstring(s.docstring) } : {}
29915
30125
  }));
29916
30126
  const capped = guardJsonRows(rows);
29917
30127
  const payload = {
@@ -29927,7 +30137,7 @@ function runSkeleton(opts) {
29927
30137
  const lines2 = [`# Skeleton: ${opts.file} (${filtered.length} symbols, ${totalLines} lines)`];
29928
30138
  for (const sym of filtered) {
29929
30139
  const lineStr = sym.lineStart.toString().padStart(6);
29930
- const statsStr = refCounts !== void 0 ? ` [${refCounts.get(sym.name) ?? 0} refs, ${sym.docstring.trim().length > 0 ? "documented" : "undocumented"}]` : "";
30140
+ const statsStr = formatStatsSuffix(refCounts, sym);
29931
30141
  lines2.push(` ${lineStr} ${sym.kind.padEnd(10)} ${sym.name} ${firstBodyLine(sym.body)}${statsStr}`);
29932
30142
  }
29933
30143
  const text = guardText(staleWarning(resolved) + lines2.join("\n"), "symbol");
@@ -29944,7 +30154,7 @@ function runOutline(opts) {
29944
30154
  const rows = refCounts !== void 0 ? filtered.map((s) => ({
29945
30155
  ...s,
29946
30156
  refCount: refCounts.get(s.name) ?? 0,
29947
- hasDoc: s.docstring.trim().length > 0
30157
+ hasDoc: hasRealDocstring(s.docstring)
29948
30158
  })) : filtered;
29949
30159
  const capped = guardJsonRows(rows);
29950
30160
  const payload = {
@@ -29961,8 +30171,8 @@ function runOutline(opts) {
29961
30171
  const rangeStr = `${sym.lineStart.toString().padStart(4)}-${sym.lineEnd.toString().padEnd(6)}`;
29962
30172
  const kindStr = sym.kind.padEnd(14);
29963
30173
  const bodyLen = sym.lineEnd - sym.lineStart + 1;
29964
- const docFirst = sym.docstring ? ` # ${sym.docstring.split("\n")[0] ?? ""}` : "";
29965
- const statsStr = refCounts !== void 0 ? ` [${refCounts.get(sym.name) ?? 0} refs, ${sym.docstring.trim().length > 0 ? "documented" : "undocumented"}]` : "";
30174
+ const docFirst = hasRealDocstring(sym.docstring) ? ` # ${sym.docstring.split("\n")[0] ?? ""}` : "";
30175
+ const statsStr = formatStatsSuffix(refCounts, sym);
29966
30176
  lines2.push(` ${rangeStr} ${kindStr} ${sym.name} (${bodyLen}\u2113)${docFirst}${statsStr}`);
29967
30177
  }
29968
30178
  const text = guardText(staleWarning(resolved) + lines2.join("\n"), "symbol");
@@ -46972,21 +47182,23 @@ function createMcpServer() {
46972
47182
  server.registerTool(
46973
47183
  "read",
46974
47184
  {
46975
- 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.",
47185
+ 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.",
46976
47186
  inputSchema: {
46977
- spec: external_exports.string().describe("file::symbol, file@N-M, file@N, or a bare file path"),
47187
+ 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"),
46978
47188
  json: external_exports.boolean().optional().describe("output as JSON"),
46979
47189
  forceRefresh: external_exports.boolean().optional().describe("reparse file from disk before querying (ignore stale index)"),
47190
+ stats: external_exports.boolean().optional().describe("add per-symbol reference count and doc-coverage flag"),
46980
47191
  projectRoot: projectRootField
46981
47192
  }
46982
47193
  },
46983
47194
  (args) => {
46984
- const { spec, json: json2, forceRefresh, projectRoot } = args;
47195
+ const { spec, json: json2, forceRefresh, stats, projectRoot } = args;
46985
47196
  return toCallToolResult(
46986
47197
  runRead({
46987
47198
  spec,
46988
47199
  ...json2 === true ? { json: true } : {},
46989
47200
  ...forceRefresh === true ? { forceRefresh: true } : {},
47201
+ ...stats === true ? { stats: true } : {},
46990
47202
  ...projectRoot !== void 0 ? { projectRoot } : {}
46991
47203
  })
46992
47204
  );
@@ -63766,8 +63978,8 @@ function formatTopFiles(ranked) {
63766
63978
  if (ranked.length === 0) return "";
63767
63979
  const lines2 = ["Top files this session:"];
63768
63980
  for (const { path: filePath, count } of ranked) {
63769
- const basename20 = path48.basename(filePath);
63770
- lines2.push(` ${count.toString().padStart(3)}x ${basename20} (${filePath})`);
63981
+ const basename21 = path48.basename(filePath);
63982
+ lines2.push(` ${count.toString().padStart(3)}x ${basename21} (${filePath})`);
63771
63983
  }
63772
63984
  return lines2.join("\n");
63773
63985
  }
@@ -72708,9 +72920,16 @@ function buildProgram() {
72708
72920
  )
72709
72921
  );
72710
72922
  program2.command("read <spec>").description(
72711
- "read one symbol's full body (spec: file::symbol; disambiguate a name shared by several classes with file::Parent.symbol)"
72712
- ).option("-j, --json", "output as JSON").option("--force-refresh", "reparse file from disk before querying (ignore stale index)").action(
72713
- (spec, opts) => runExitText(() => runRead({ spec, ...opts.json === true ? { json: true } : {}, ...opts.forceRefresh === true ? { forceRefresh: true } : {} }))
72923
+ "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)"
72924
+ ).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(
72925
+ (spec, opts) => runExitText(
72926
+ () => runRead({
72927
+ spec,
72928
+ ...opts.json === true ? { json: true } : {},
72929
+ ...opts.forceRefresh === true ? { forceRefresh: true } : {},
72930
+ ...opts.stats === true ? { stats: true } : {}
72931
+ })
72932
+ )
72714
72933
  );
72715
72934
  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(
72716
72935
  (spec, opts) => runExit(
@@ -73262,7 +73481,7 @@ init_hooks_common();
73262
73481
  init_config();
73263
73482
  init_index_reader();
73264
73483
  init_constants();
73265
- var GENERIC_REMINDER = 'token-goat: prefer surgical reads over Read/Grep on this codebase -- `token-goat symbol <name>`, `token-goat read "file::symbol"`, `token-goat section "file::Heading"`, `token-goat semantic "description"`, `token-goat outline <file>`. Run `token-goat index .` if this project is not indexed yet.';
73484
+ 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.';
73266
73485
  function buildReminder(cwd) {
73267
73486
  if (cwd === void 0) return GENERIC_REMINDER;
73268
73487
  let symbolCount;
@@ -73272,7 +73491,7 @@ function buildReminder(cwd) {
73272
73491
  return GENERIC_REMINDER;
73273
73492
  }
73274
73493
  if (symbolCount <= 0) return GENERIC_REMINDER;
73275
- return `token-goat: this project is indexed (${symbolCount} symbols). Prefer \`symbol <name>\`, \`read "file::symbol"\`, \`section "file::Heading"\`, \`semantic "description"\`, or \`outline <file>\` over a full Read/Grep.`;
73494
+ 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.`;
73276
73495
  }
73277
73496
  function sessionStartHandler(event) {
73278
73497
  try {
@@ -79575,16 +79794,16 @@ function isTempPath(fp) {
79575
79794
  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);
79576
79795
  }
79577
79796
  function isOrchestratorStateFile(filePath) {
79578
- const basename20 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
79579
- return /^\.improve-state-/.test(basename20);
79797
+ const basename21 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
79798
+ return /^\.improve-state-/.test(basename21);
79580
79799
  }
79581
79800
  function extractCatSourceFile(cmd) {
79582
79801
  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);
79583
79802
  return m?.[1] ?? null;
79584
79803
  }
79585
79804
  function classifyFileExtensions(filePath) {
79586
- const basename20 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
79587
- const isEnvFile = /^\.env(\.\w+)?$/i.test(basename20);
79805
+ const basename21 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
79806
+ const isEnvFile = /^\.env(\.\w+)?$/i.test(basename21);
79588
79807
  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);
79589
79808
  if (!hasKnownExt && !isEnvFile) return null;
79590
79809
  const isSql = /\.sql$/i.test(filePath);
@@ -81265,8 +81484,8 @@ ${redactSecrets(compressed).text}`
81265
81484
  }
81266
81485
  return passOutput();
81267
81486
  }
81268
- registerHook("pre_tool_use", preMcpHandler);
81269
- registerHook("post_tool_use", postMcpHandler);
81487
+ registerHook("pre_tool_use", preMcpHandler, { toolPattern: "^mcp__" });
81488
+ registerHook("post_tool_use", postMcpHandler, { toolPattern: "^mcp__" });
81270
81489
 
81271
81490
  // src/hooks_websearch.ts
81272
81491
  init_define_import_meta_env();
@@ -81349,7 +81568,7 @@ function preScreenshotHandler(event) {
81349
81568
  `${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.`
81350
81569
  );
81351
81570
  }
81352
- registerHook("pre_tool_use", preScreenshotHandler);
81571
+ registerHook("pre_tool_use", preScreenshotHandler, { toolPattern: "^mcp__" });
81353
81572
 
81354
81573
  // src/hooks_browser_image.ts
81355
81574
  init_define_import_meta_env();
@@ -81427,7 +81646,7 @@ async function postBrowserImageHandler(event) {
81427
81646
  return passOutput();
81428
81647
  }
81429
81648
  }
81430
- registerHook("post_tool_use", postBrowserImageHandler);
81649
+ registerHook("post_tool_use", postBrowserImageHandler, { toolPattern: "^mcp__" });
81431
81650
 
81432
81651
  // src/hooks_agent_spawn.ts
81433
81652
  init_define_import_meta_env();