dsh-code-index 0.3.0 → 0.4.0

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.
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -7,7 +7,7 @@ English | [中文](README.zh.md)
7
7
 
8
8
  Semantic repo index — a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) plugin that gives the agent a **codebase map**: a tree-sitter symbol index, ranked symbol search, and a bounded auto-updating repo map in the system prompt.
9
9
 
10
- Fills a real ecosystem gap: search of the `dsh-plugin` topic (2026-08) shows git/voice/browser/memory plugins galore, but **no native, model-usable code index / repo-map capability** — the kind of thing aider's repo-map and Cursor's `@Codebase` do for their agents.
10
+ Fits a niche the ecosystem took a while to fill: alongside git/voice/browser/memory plugins, several code-intelligence plugins have appeared (graph-based, embedding-based), while this one stays deliberately **dependency-free** — pure in-process tree-sitter over WASM, the aider repo-map / Cursor `@Codebase` style for dsh agents.
11
11
 
12
12
  ## What the model gets
13
13
 
@@ -99,7 +99,7 @@ Options are passed as the plugin row's `config` in the profile patch (or default
99
99
 
100
100
  ## Supported languages
101
101
 
102
- TypeScript, JavaScript, Python, Go, Rust and Java (`.ts .tsx .mts .cts .js .jsx .mjs .cjs .py .pyi .go .rs .java`) via tree-sitter WASM — pure parsing, no native build. The symbol provider seam (`src/extract.ts` + grammars) is where other languages/embeddings plug in later.
102
+ TypeScript, JavaScript, Python, Go, Rust, Java, C++ and C (`.ts .tsx .mts .cts .js .jsx .mjs .cjs .py .pyi .go .rs .java .cpp .cc .cxx .c++ .hpp .hxx .hh .h .ipp .tpp .inl .c`) via tree-sitter WASM — pure parsing, no native build. The symbol provider seam (`src/extract.ts` + grammars) is where other languages/embeddings plug in later. C/C++ symbol extraction resolves names through the declarator chain (templates, qualified `ns::name` definitions, in-class methods), and `#include "…"` specifiers feed the repo-map reference graph.
103
103
 
104
104
  ## How it works
105
105
 
@@ -110,7 +110,7 @@ TypeScript, JavaScript, Python, Go, Rust and Java (`.ts .tsx .mts .cts .js .jsx
110
110
 
111
111
  ## Known limitations
112
112
 
113
- - **web-tree-sitter pinned to `^0.20.8`** — newer releases expect dylinked grammar wasm while `tree-sitter-wasms` ships static builds; this pair is verified working under Node ≥ 22/24.
113
+ - **web-tree-sitter pinned to `^0.25` (ESM)** — the 0.25 line uses ESM named exports (`Language`/`Query`); this pairing with `tree-sitter-wasms` static builds is verified working under Node ≥ 22/24.
114
114
  - Auto-injected section targets the **default workspace** (launch directory, matching headless/CLI mode). Multi-workspace Web UI sessions should use `code_map`/`code_symbols` (they resolve per-session cwd).
115
115
  - Local variables are indexed too — recall over precision; `code_search` ranking keeps them low.
116
116
  - Developer-preview harness: expect breaking harness/plugin API changes upstream.
package/README.zh.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  语义仓库索引 —— 一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)插件,为 agent 提供**代码库地图**:基于 tree-sitter 的符号索引、带排名的符号搜索,以及注入到系统提示词里的限量自动更新仓库地图。
6
6
 
7
- 填补了一个真实的生态缺口:检索 `dsh-plugin` 话题(2026-08)会发现 git/语音/浏览器/记忆类插件琳琅满目,但**缺少原生、模型可用的代码索引 / 仓库地图能力**——也就是 aider 的 repo-map 和 Cursor 的 `@Codebase` 为各自 agent 提供的同类能力。
7
+ 在一个生态姗姗来迟的细分领域占位:git/语音/浏览器/记忆类插件之外,代码智能方向的插件已陆续出现(图谱路线、向量嵌入路线),而本插件刻意保持**零外部依赖**——纯进程内 tree-sitter WASM,把 aider repo-map / Cursor `@Codebase` 的同类能力带给 dsh agent。
8
8
 
9
9
  ## 模型能得到什么
10
10
 
@@ -96,7 +96,7 @@ export function extractSymbols(code, id) — src/extract.ts:121
96
96
 
97
97
  ## 支持的语言
98
98
 
99
- TypeScript、JavaScript、Python、Go、Rust、Java(`.ts .tsx .mts .cts .js .jsx .mjs .cjs .py .pyi .go .rs .java`),通过 tree-sitter WASM 解析——纯解析,无需原生编译。符号提供方的接缝(`src/extract.ts` + 语法文件)预留了后续接入其他语言/嵌入检索的位置。
99
+ TypeScript、JavaScript、Python、Go、Rust、Java、C++、C(`.ts .tsx .mts .cts .js .jsx .mjs .cjs .py .pyi .go .rs .java .cpp .cc .cxx .c++ .hpp .hxx .hh .h .ipp .tpp .inl .c`),通过 tree-sitter WASM 解析——纯解析,无需原生编译。符号提供方的接缝(`src/extract.ts` + 语法文件)预留了后续接入其他语言/嵌入检索的位置。C/C++ 符号提取通过 declarator 链解析名字(模板、`ns::name` 限定定义、类内方法),`#include "…"` 会接入 repo map 的引用图。
100
100
 
101
101
  ## 工作原理
102
102
 
@@ -107,7 +107,7 @@ TypeScript、JavaScript、Python、Go、Rust、Java(`.ts .tsx .mts .cts .js .jsx
107
107
 
108
108
  ## 已知限制
109
109
 
110
- - **web-tree-sitter 固定为 `^0.20.8`** —— 新版本期望 dylink 语法的 wasm,而 `tree-sitter-wasms` 提供静态构建;此组合在 Node ≥ 22/24 下验证可用。
110
+ - **web-tree-sitter 固定为 `^0.25`(ESM)** —— 0.25 采用 ESM 具名导出(`Language`/`Query`);与 `tree-sitter-wasms` 静态构建的组合在 Node ≥ 22/24 下验证可用。
111
111
  - 自动注入段针对**默认工作区**(启动目录,与 headless/CLI 模式一致)。多工作区 Web UI 会话应使用 `code_map`/`code_symbols`(它们按会话 cwd 解析)。
112
112
  - 局部变量也会被索引——召回优先于精确;`code_search` 的排名会压低它们。
113
113
  - 开发者预览版 harness:上游 harness/插件 API 大概率有破坏性变更。
package/cordis.patch.yml CHANGED
File without changes
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import path4 from "path";
6
6
  import { readFile } from "fs/promises";
7
7
  import { createRequire } from "module";
8
8
  import path from "path";
9
- import Parser from "web-tree-sitter";
9
+ import { Language, Parser, Query } from "web-tree-sitter";
10
10
  var require2 = createRequire(import.meta.url);
11
11
  var WASM_DIR = path.dirname(require2.resolve("tree-sitter-wasms/out/tree-sitter-typescript.wasm"));
12
12
  var GRAMMAR_NAMES = {
@@ -15,7 +15,9 @@ var GRAMMAR_NAMES = {
15
15
  python: "tree-sitter-python",
16
16
  go: "tree-sitter-go",
17
17
  rust: "tree-sitter-rust",
18
- java: "tree-sitter-java"
18
+ java: "tree-sitter-java",
19
+ cpp: "tree-sitter-cpp",
20
+ c: "tree-sitter-c"
19
21
  };
20
22
  var EXT_TO_LANG = {
21
23
  ".ts": "typescript",
@@ -30,7 +32,19 @@ var EXT_TO_LANG = {
30
32
  ".pyi": "python",
31
33
  ".go": "go",
32
34
  ".rs": "rust",
33
- ".java": "java"
35
+ ".java": "java",
36
+ ".cpp": "cpp",
37
+ ".cc": "cpp",
38
+ ".cxx": "cpp",
39
+ ".c++": "cpp",
40
+ ".hpp": "cpp",
41
+ ".hxx": "cpp",
42
+ ".hh": "cpp",
43
+ ".h": "cpp",
44
+ ".ipp": "cpp",
45
+ ".tpp": "cpp",
46
+ ".inl": "cpp",
47
+ ".c": "c"
34
48
  };
35
49
  function languageForFile(filePath) {
36
50
  const ext = path.extname(filePath).toLowerCase();
@@ -79,6 +93,31 @@ var QUERIES = {
79
93
  (enum_declaration) @enum
80
94
  (method_declaration) @method
81
95
  (constructor_declaration) @method
96
+ `,
97
+ // C++ grammar (superset of C — also serves .h headers). function names
98
+ // live inside the declarator chain, so patterns stay broad and names,
99
+ // kinds and signatures are resolved in code (cppNameOf / kindFor /
100
+ // signatureFor). Top-level `declaration` covers prototypes, constructors
101
+ // and globals; field_declaration covers class-body members.
102
+ cpp: `
103
+ (function_definition) @function
104
+ (class_specifier body: (_)) @class
105
+ (struct_specifier body: (_)) @class
106
+ (enum_specifier body: (_)) @enum
107
+ (type_definition) @type
108
+ (alias_declaration) @type
109
+ (namespace_definition) @module
110
+ (field_declaration) @field
111
+ (declaration) @variable
112
+ `,
113
+ // Plain C: same core nodes minus C++-only namespace/alias forms.
114
+ c: `
115
+ (function_definition) @function
116
+ (struct_specifier body: (_)) @class
117
+ (enum_specifier body: (_)) @enum
118
+ (type_definition) @type
119
+ (field_declaration) @field
120
+ (declaration) @variable
82
121
  `
83
122
  };
84
123
  var CAPTURE_KINDS = {
@@ -89,7 +128,8 @@ var CAPTURE_KINDS = {
89
128
  type: { kind: "type" },
90
129
  enum: { kind: "enum" },
91
130
  variable: { kind: "variable" },
92
- field: { kind: "field" }
131
+ field: { kind: "field" },
132
+ module: { kind: "module" }
93
133
  };
94
134
  var IMPORT_QUERIES = {
95
135
  typescript: `
@@ -112,6 +152,14 @@ var IMPORT_QUERIES = {
112
152
  `,
113
153
  java: `
114
154
  (import_declaration) @import
155
+ `,
156
+ // C/C++: #include "local.hpp" — quoted form resolves in-repo; <system>
157
+ // captures too but yields null and is dropped by the extractor.
158
+ cpp: `
159
+ (preproc_include) @import
160
+ `,
161
+ c: `
162
+ (preproc_include) @import
115
163
  `
116
164
  };
117
165
  var parserPromise = null;
@@ -133,11 +181,9 @@ function getLanguage(id) {
133
181
  entry = getParser().then(async () => {
134
182
  const grammarPath = path.join(WASM_DIR, `${GRAMMAR_NAMES[id]}.wasm`);
135
183
  const bytes = await readFile(grammarPath);
136
- const lang = await Parser.Language.load(bytes);
137
- const q = lang.query(QUERIES[id]);
138
- q.delete();
139
- const iq = lang.query(IMPORT_QUERIES[id]);
140
- iq.delete();
184
+ const lang = await Language.load(bytes);
185
+ new Query(lang, QUERIES[id]).delete();
186
+ new Query(lang, IMPORT_QUERIES[id]).delete();
141
187
  return lang;
142
188
  });
143
189
  languageCache.set(id, entry);
@@ -149,17 +195,18 @@ async function extractAll(code, id) {
149
195
  const parser = await getParser();
150
196
  parser.setLanguage(lang);
151
197
  const tree = parser.parse(code);
198
+ if (!tree) throw new Error(`tree-sitter parse returned null for a ${id} source`);
152
199
  try {
153
200
  const symbols = [];
154
- const symbolQuery = lang.query(QUERIES[id]);
201
+ const symbolQuery = new Query(lang, QUERIES[id]);
155
202
  try {
156
203
  const captures = symbolQuery.captures(tree.rootNode);
157
204
  for (const cap of captures) {
158
205
  const def = CAPTURE_KINDS[cap.name];
159
206
  if (!def) continue;
160
207
  const node = cap.node;
161
- if (def.kind === "variable" && !isModuleLevelVariable(node)) continue;
162
- const name2 = nameOf(node);
208
+ if (def.kind === "variable" && !isModuleLevelVariable(id, node)) continue;
209
+ const name2 = nameOf(id, node);
163
210
  if (!name2) continue;
164
211
  symbols.push({
165
212
  name: name2,
@@ -169,14 +216,14 @@ async function extractAll(code, id) {
169
216
  line: node.startPosition.row + 1,
170
217
  endLine: node.endPosition.row + 1,
171
218
  exported: isExported(id, node),
172
- signature: signatureFor(node)
219
+ signature: signatureFor(id, node)
173
220
  });
174
221
  }
175
222
  } finally {
176
223
  symbolQuery.delete();
177
224
  }
178
225
  const imports = [];
179
- const importQuery = lang.query(IMPORT_QUERIES[id]);
226
+ const importQuery = new Query(lang, IMPORT_QUERIES[id]);
180
227
  try {
181
228
  for (const cap of importQuery.captures(tree.rootNode)) {
182
229
  const spec = specifierOf(id, cap.node);
@@ -186,7 +233,14 @@ async function extractAll(code, id) {
186
233
  importQuery.delete();
187
234
  }
188
235
  symbols.sort((a, b) => a.line - b.line);
189
- return { symbols, imports };
236
+ const seen = /* @__PURE__ */ new Set();
237
+ const deduped = symbols.filter((s) => {
238
+ const key = `${s.kind}|${s.name}|${s.line}`;
239
+ if (seen.has(key)) return false;
240
+ seen.add(key);
241
+ return true;
242
+ });
243
+ return { symbols: deduped, imports };
190
244
  } finally {
191
245
  tree.delete();
192
246
  }
@@ -194,11 +248,40 @@ async function extractAll(code, id) {
194
248
  async function extractSymbols(code, id) {
195
249
  return (await extractAll(code, id)).symbols;
196
250
  }
197
- function nameOf(node) {
251
+ function nameOf(id, node) {
252
+ if (id === "cpp" || id === "c") return cppNameOf(node);
198
253
  const field = node.childForFieldName?.("name");
199
254
  if (field) return field.text.trim();
200
255
  return "";
201
256
  }
257
+ function cppNameOf(node) {
258
+ const nameField = node.childForFieldName?.("name");
259
+ if (nameField) return nameField.text.trim();
260
+ if (node.type === "type_definition") {
261
+ return node.childForFieldName?.("declarator")?.text.trim() ?? "";
262
+ }
263
+ let declarator = node.childForFieldName?.("declarator");
264
+ if (declarator?.type === "init_declarator") {
265
+ declarator = declarator.childForFieldName?.("declarator");
266
+ }
267
+ if (declarator) {
268
+ let current = declarator;
269
+ while (current) {
270
+ if (current.type === "function_declarator" || current.type === "pointer_declarator" || current.type === "array_declarator" || current.type === "reference_declarator" || current.type === "parenthesized_declarator" || current.type === "init_declarator") {
271
+ current = current.childForFieldName?.("declarator") ?? current.namedChildren[0] ?? null;
272
+ continue;
273
+ }
274
+ if (current.type === "qualified_identifier") {
275
+ return current.childForFieldName?.("name")?.text.trim() ?? current.text.trim();
276
+ }
277
+ if (current.type === "identifier" || current.type === "field_identifier" || current.type === "type_identifier") {
278
+ return current.text.trim();
279
+ }
280
+ break;
281
+ }
282
+ }
283
+ return "";
284
+ }
202
285
  function specifierOf(id, node) {
203
286
  switch (node.type) {
204
287
  case "import_statement":
@@ -217,6 +300,8 @@ function specifierOf(id, node) {
217
300
  return rustUsePath(node.childForFieldName("argument")?.text);
218
301
  case "import_declaration":
219
302
  return dottedToPath(node.namedChildren[0]?.text);
303
+ case "preproc_include":
304
+ return stripQuotes(node.childForFieldName("path")?.text);
220
305
  default:
221
306
  return null;
222
307
  }
@@ -248,21 +333,43 @@ function pythonRelative(raw) {
248
333
  const prefix = dots === 1 ? "./" : "../".repeat(dots - 1);
249
334
  return prefix + rest;
250
335
  }
251
- function signatureFor(node) {
252
- const name2 = nameOf(node);
336
+ function signatureFor(id, node) {
337
+ const sig = rawSignature(id, node);
338
+ return sig.length > 80 ? `${sig.slice(0, 77)}...` : sig;
339
+ }
340
+ function rawSignature(id, node) {
341
+ const name2 = nameOf(id, node);
253
342
  const params = node.childForFieldName?.("parameters") ?? node.namedChildren.find(
254
- (c) => c.type === "formal_parameters" || c.type === "method_parameters" || c.type === "parameters" || c.type === "parameter_list"
343
+ (c) => c?.type === "formal_parameters" || c?.type === "method_parameters" || c?.type === "parameters" || c?.type === "parameter_list"
255
344
  );
256
345
  if (params) {
257
346
  return `${name2}${collapseSpace(params.text)}`;
258
347
  }
348
+ if (id === "cpp" || id === "c") {
349
+ let current = node.childForFieldName?.("declarator");
350
+ while (current) {
351
+ if (current.type === "function_declarator") {
352
+ const list = current.childForFieldName?.("parameters");
353
+ return list ? `${name2}${collapseSpace(list.text)}` : name2;
354
+ }
355
+ current = current.childForFieldName?.("declarator") ?? current.namedChildren[0] ?? null;
356
+ if (current && !current.type.includes("declarator") && current.type !== "identifier" && current.type !== "qualified_identifier" && current.type !== "field_identifier") break;
357
+ }
358
+ }
259
359
  const first = node.namedChildren[0];
260
360
  return first ? collapseSpace(first.text) : name2;
261
361
  }
262
362
  function collapseSpace(text) {
263
363
  return text.replace(/\s+/g, " ").replace(/\( /g, "(").replace(/ \)/g, ")").replace(/ ,/g, ",").replace(/,\)/g, ")").trim();
264
364
  }
265
- function isModuleLevelVariable(node) {
365
+ function isModuleLevelVariable(id, node) {
366
+ if (id === "cpp" || id === "c") {
367
+ for (let parent = node.parent; parent; parent = parent.parent) {
368
+ if (parent.type === "function_definition" || parent.type === "compound_statement") return false;
369
+ if (parent.type === "translation_unit" || parent.type === "declaration_list" || parent.type === "field_declaration_list") return true;
370
+ }
371
+ return false;
372
+ }
266
373
  const declaration = node.parent;
267
374
  const container = declaration?.parent;
268
375
  return container?.type === "program" || container?.type === "export_statement";
@@ -272,17 +379,20 @@ function isExported(id, node) {
272
379
  return node.parent?.type === "module";
273
380
  }
274
381
  if (id === "go") {
275
- const name2 = nameOf(node);
382
+ const name2 = nameOf(id, node);
276
383
  return !!name2 && /^[A-Z]/.test(name2);
277
384
  }
278
385
  if (id === "rust") {
279
386
  return node.namedChildren.some(
280
- (c) => c.type === "visibility_modifier" && c.text === "pub"
387
+ (c) => c?.type === "visibility_modifier" && c.text === "pub"
281
388
  );
282
389
  }
283
390
  if (id === "java") {
284
391
  if (node.parent?.type === "interface_body") return true;
285
- return node.namedChildren.some((c) => c.type === "modifiers" && /\bpublic\b/.test(c.text));
392
+ return node.namedChildren.some((c) => c?.type === "modifiers" && /\bpublic\b/.test(c?.text ?? ""));
393
+ }
394
+ if (id === "cpp" || id === "c") {
395
+ return cppIsExported(node);
286
396
  }
287
397
  for (let parent = node.parent; parent; parent = parent.parent) {
288
398
  if (parent.type === "export_statement") return true;
@@ -291,6 +401,31 @@ function isExported(id, node) {
291
401
  }
292
402
  return false;
293
403
  }
404
+ function cppIsExported(node) {
405
+ const isStatic = node.namedChildren.some(
406
+ (c) => c?.type === "storage_class_specifier" && c.text === "static"
407
+ );
408
+ for (let parent = node.parent; parent; parent = parent.parent) {
409
+ if (parent.type === "field_declaration_list") {
410
+ const body = parent;
411
+ const structKind = body.parent?.type === "struct_specifier" ? "struct" : "class";
412
+ let current = structKind === "struct" ? "public" : "private";
413
+ for (let up = node; up && up.id !== body.id; up = up.parent) {
414
+ const next = up.parent;
415
+ if (next?.type === "field_declaration_list") {
416
+ const siblingIndex = next.namedChildren.findIndex((c) => c?.id === up.id);
417
+ const accessBefore = next.namedChildren.slice(0, siblingIndex).filter((c) => c?.type === "access_specifier");
418
+ if (accessBefore.length > 0) {
419
+ current = accessBefore.at(-1).text.replace(":", "").trim();
420
+ }
421
+ }
422
+ }
423
+ return current === "public" && !isStatic;
424
+ }
425
+ if (parent.type === "translation_unit") return !isStatic;
426
+ }
427
+ return !isStatic;
428
+ }
294
429
  function kindFor(id, node, kind) {
295
430
  if (id === "python" && kind === "function") {
296
431
  const inClassBody = node.parent?.type === "block" && node.parent?.parent?.type === "class_definition";
@@ -306,6 +441,19 @@ function kindFor(id, node, kind) {
306
441
  const inImpl = node.parent?.type === "declaration_list" && node.parent?.parent?.type === "impl_item";
307
442
  if (inImpl) return "method";
308
443
  }
444
+ if ((id === "cpp" || id === "c") && (kind === "variable" || kind === "field")) {
445
+ let current = node.childForFieldName?.("declarator");
446
+ while (current) {
447
+ if (current.type === "function_declarator") {
448
+ return node.parent?.type === "field_declaration_list" ? "method" : "function";
449
+ }
450
+ current = current.childForFieldName?.("declarator") ?? current.namedChildren[0] ?? null;
451
+ }
452
+ return node.type === "field_declaration" ? "field" : "variable";
453
+ }
454
+ if ((id === "cpp" || id === "c") && node.type === "function_definition") {
455
+ if (node.parent?.type === "field_declaration_list") return "method";
456
+ }
309
457
  return kind;
310
458
  }
311
459
  async function parseFileToSymbols(filePath, repoRoot, code) {
@@ -349,7 +497,19 @@ var SUPPORTED_EXTS = /* @__PURE__ */ new Set([
349
497
  ".pyi",
350
498
  ".go",
351
499
  ".rs",
352
- ".java"
500
+ ".java",
501
+ ".cpp",
502
+ ".cc",
503
+ ".cxx",
504
+ ".c++",
505
+ ".hpp",
506
+ ".hxx",
507
+ ".hh",
508
+ ".h",
509
+ ".ipp",
510
+ ".tpp",
511
+ ".inl",
512
+ ".c"
353
513
  ]);
354
514
  async function scanRepo(root, options = {}) {
355
515
  const excluded = /* @__PURE__ */ new Set([...DEFAULT_EXCLUDED_DIRS, ...options.excludeDirs ?? []]);
@@ -698,6 +858,7 @@ function resolveImport(spec, fromPath, fileSet) {
698
858
  return null;
699
859
  }
700
860
  function candidateFor(base, fileSet) {
861
+ if (fileSet.has(base)) return base;
701
862
  for (const ext of SUPPORTED_EXTS) {
702
863
  if (fileSet.has(`${base}${ext}`)) return `${base}${ext}`;
703
864
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/buildIndex.ts","../src/extract.ts","../src/scan.ts","../src/store.ts","../src/types.ts","../src/search.ts","../src/repomap.ts","../src/tools.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["/** Build a RepoIndex for a workspace: scan -> extract -> persist. */\n\nimport { readFile, stat } from 'node:fs/promises'\nimport path from 'node:path'\nimport { extractAll, languageForFile } from './extract.js'\nimport { scanRepo, DEFAULT_EXCLUDED_DIRS } from './scan.js'\nimport {\n cacheKeyForRoot,\n defaultCachePath,\n legacyCachePath,\n loadIndex,\n saveIndex,\n} from './store.js'\nimport type { IndexOptions, IndexedFile, RepoIndex } from './types.js'\n\n/**\n * Full build: scan + extract every supported file. Existing per-file\n * extraction is reused when `previous` holds an identical mtime for the\n * same rel path (incremental refresh), so touched files alone re-parse.\n */\nexport async function buildIndex(\n root: string,\n options: IndexOptions = {},\n previous: RepoIndex | null = null,\n): Promise<RepoIndex> {\n const scanned = await scanRepo(root, options)\n\n // Index stale/current state from the previous run by rel path.\n const prevByPath = new Map((previous?.files ?? []).map((f) => [f.path, f]))\n const prevMtime = new Map((previous?.files ?? []).map((f) => [f.path, f.mtimeMs]))\n\n const files: IndexedFile[] = []\n const BATCH = 8\n for (let i = 0; i < scanned.length; i += BATCH) {\n const batch = scanned.slice(i, i + BATCH)\n const rows = await Promise.all(\n batch.map(async (f) => {\n const lang = languageForFile(f.abs)\n if (!lang) return null\n if (prevMtime.get(f.rel) === f.mtimeMs) {\n const cached = prevByPath.get(f.rel)!\n return {\n ...cached,\n mtimeMs: f.mtimeMs,\n symbols: cached.symbols.map((symbol) => ({ ...symbol, file: f.rel })),\n }\n }\n let code: string\n try {\n code = await readFile(f.abs, 'utf8')\n } catch {\n return null\n }\n const { symbols, imports } = await extractAll(code, lang)\n // Backfill the repo-relative path: the extractor is file-agnostic and\n // leaves SymbolInfo.file empty, but search/render depend on it.\n return {\n path: f.rel,\n lang,\n mtimeMs: f.mtimeMs,\n symbols: symbols.map((s) => ({ ...s, file: f.rel })),\n imports,\n } satisfies IndexedFile\n }),\n )\n for (const f of rows) {\n if (f) files.push(f)\n }\n }\n\n return {\n root,\n generatedAt: Date.now(),\n files,\n excludedDirs: [...DEFAULT_EXCLUDED_DIRS, ...(options.excludeDirs ?? [])],\n }\n}\n\n/** Convenience wrapper: evolve an on-disk cache if `root` exists. */\nexport async function buildIndexWithCache(\n root: string,\n options: IndexOptions = {},\n cacheDir?: string,\n): Promise<RepoIndex> {\n const cachePath = defaultCachePath(root, cacheDir)\n let prev = await loadIndex(cachePath)\n let loadedLegacy = false\n if (!prev) {\n const oldPath = legacyCachePath(root, cacheDir)\n if (oldPath !== cachePath) {\n prev = await loadIndex(oldPath)\n loadedLegacy = prev !== null\n }\n }\n const reusable = prev && cacheKeyForRoot(prev.root) === cacheKeyForRoot(root) ? prev : null\n const fresh = await buildIndex(root, options, reusable)\n if (prev && indexesEqual(prev, fresh)) {\n if (loadedLegacy) await saveIndex(cachePath, fresh)\n return loadedLegacy ? fresh : prev\n }\n await saveIndex(cachePath, fresh)\n return fresh\n}\n\nfunction indexesEqual(left: RepoIndex, right: RepoIndex): boolean {\n if (cacheKeyForRoot(left.root) !== cacheKeyForRoot(right.root)) return false\n if (left.files.length !== right.files.length) return false\n if (left.excludedDirs.length !== right.excludedDirs.length) return false\n if (left.excludedDirs.some((value, index) => value !== right.excludedDirs[index])) return false\n\n return left.files.every((file, fileIndex) => {\n const other = right.files[fileIndex]\n if (!other || file.path !== other.path || file.lang !== other.lang || file.mtimeMs !== other.mtimeMs) {\n return false\n }\n if (file.symbols.length !== other.symbols.length) return false\n return file.symbols.every((symbol, symbolIndex) => {\n const candidate = other.symbols[symbolIndex]\n return candidate !== undefined\n && symbol.name === candidate.name\n && symbol.kind === candidate.kind\n && symbol.file === candidate.file\n && symbol.line === candidate.line\n && symbol.endLine === candidate.endLine\n && symbol.exported === candidate.exported\n && symbol.signature === candidate.signature\n })\n })\n}\n\n/**\n * Locate the git repo root for a path by walking up to the nearest `.git`\n * directory. Bounded walk (max `maxLevels` levels); returns `null` when no\n * repo marker is found — callers must NOT index an untagged directory\n * (the fs root is the classic footgun: indexing `C:\\` by accident).\n */\nexport async function findRepoRoot(startDir: string, maxLevels = 12): Promise<string | null> {\n let dir = path.resolve(startDir)\n for (let level = 0; level < maxLevels; level++) {\n try {\n // stat() accepts BOTH a `.git` directory (normal repos) and a `.git`\n // file (worktrees / submodules) — readFile only handled the file form.\n await stat(path.join(dir, '.git'))\n return dir\n } catch {\n // not a repo here — keep walking\n }\n const parent = path.dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n return null\n}\n","/**\n * tree-sitter symbol extraction.\n *\n * Parses a source text with a per-language grammar (web-tree-sitter WASM,\n * static grammars from tree-sitter-wasms) and returns flat SymbolInfo rows.\n *\n * NOTE on the dependency pin: web-tree-sitter must stay at ^0.20.x — newer\n * releases expect dylinked grammar wasm, while tree-sitter-wasms ships\n * static builds. Verified working pair: web-tree-sitter@0.20.8 + tree-sitter-wasms@0.1.13.\n */\n\nimport { readFile } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport Parser from 'web-tree-sitter'\nimport type { SymbolInfo, SymbolKind } from './types.js'\n\nconst require = createRequire(import.meta.url)\n// web-tree-sitter@0.20.8 is CJS `export = Parser`; default-interop gives the class directly.\n\nexport type LanguageId = 'typescript' | 'javascript' | 'python' | 'go' | 'rust' | 'java'\n\nconst WASM_DIR = path.dirname(require.resolve('tree-sitter-wasms/out/tree-sitter-typescript.wasm'))\n\nconst GRAMMAR_NAMES: Record<LanguageId, string> = {\n typescript: 'tree-sitter-typescript',\n javascript: 'tree-sitter-javascript',\n python: 'tree-sitter-python',\n go: 'tree-sitter-go',\n rust: 'tree-sitter-rust',\n java: 'tree-sitter-java',\n}\n\nconst EXT_TO_LANG: Record<string, LanguageId> = {\n '.ts': 'typescript',\n '.tsx': 'typescript',\n '.mts': 'typescript',\n '.cts': 'typescript',\n '.js': 'javascript',\n '.jsx': 'javascript',\n '.mjs': 'javascript',\n '.cjs': 'javascript',\n '.py': 'python',\n '.pyi': 'python',\n '.go': 'go',\n '.rs': 'rust',\n '.java': 'java',\n}\n\nexport function languageForFile(filePath: string): LanguageId | null {\n const ext = path.extname(filePath).toLowerCase()\n return EXT_TO_LANG[ext] ?? null\n}\n\n/** Kind of a capture per query. */\ntype CaptureDef = { kind: SymbolKind; exported?: boolean }\n\n// Query definitions per language. Captures bind to the DECLARATION node\n// (capture OUTSIDE the pattern: `(function_declaration) @function` — the\n// 0.20 query parser rejects a capture sitting directly after the node type\n// inside the parens). Names/signatures/export come from node fields.\nconst QUERIES: Record<LanguageId, string> = {\n typescript: `\n (function_declaration) @function\n (generator_function_declaration) @function\n (method_definition) @method\n (class_declaration) @class\n (interface_declaration) @interface\n (type_alias_declaration) @type\n (enum_declaration) @enum\n (variable_declarator) @variable\n (public_field_definition) @field\n (abstract_class_declaration) @class\n `,\n // JS shares the same query surface; type-related patterns simply never match.\n javascript: `\n (function_declaration) @function\n (generator_function_declaration) @function\n (method_definition) @method\n (class_declaration) @class\n (variable_declarator) @variable\n `,\n python: `\n (function_definition) @function\n (class_definition) @class\n `,\n go: `\n (function_declaration) @function\n (method_declaration) @method\n (type_spec) @type\n `,\n rust: `\n (function_item) @function\n (function_signature_item) @method\n (struct_item) @class\n (enum_item) @enum\n (trait_item) @interface\n `,\n java: `\n (class_declaration) @class\n (interface_declaration) @interface\n (enum_declaration) @enum\n (method_declaration) @method\n (constructor_declaration) @method\n `,\n}\n\nconst CAPTURE_KINDS: Record<string, CaptureDef> = {\n function: { kind: 'function' },\n method: { kind: 'method' },\n class: { kind: 'class' },\n interface: { kind: 'interface' },\n type: { kind: 'type' },\n enum: { kind: 'enum' },\n variable: { kind: 'variable' },\n field: { kind: 'field' },\n}\n\n// Import statements per language. Whole statements are captured; the module\n// specifier is read from node fields in code (string quoting, dotted vs\n// relative vs URL-style syntax differ per grammar). CommonJS require() is out\n// of scope — the ES import surface covers the modern plugin ecosystem.\nconst IMPORT_QUERIES: Record<LanguageId, string> = {\n typescript: `\n (import_statement) @import\n (export_statement) @reexport\n `,\n javascript: `\n (import_statement) @import\n (export_statement) @reexport\n `,\n python: `\n (import_from_statement) @import\n (import_statement) @import\n `,\n go: `\n (import_spec) @import\n `,\n rust: `\n (use_declaration) @use\n `,\n java: `\n (import_declaration) @import\n `,\n}\n\nlet parserPromise: Promise<Parser> | null = null\n\nasync function getParser(): Promise<Parser> {\n if (!parserPromise) {\n parserPromise = (async () => {\n const wasm = path.join(path.dirname(require.resolve('web-tree-sitter')), 'tree-sitter.wasm')\n await Parser.init({ locateFile: () => wasm })\n const p = new Parser()\n return p\n })()\n }\n return parserPromise\n}\n\nconst languageCache = new Map<LanguageId, Promise<Parser.Language>>()\n\nfunction getLanguage(id: LanguageId): Promise<Parser.Language> {\n let entry = languageCache.get(id)\n if (!entry) {\n entry = getParser().then(async () => {\n const grammarPath = path.join(WASM_DIR, `${GRAMMAR_NAMES[id]}.wasm`)\n const bytes = await readFile(grammarPath)\n const lang = await Parser.Language.load(bytes)\n // Precompile queries per language to catch authoring errors early.\n const q = lang.query(QUERIES[id])\n q.delete()\n const iq = lang.query(IMPORT_QUERIES[id])\n iq.delete()\n return lang\n })\n languageCache.set(id, entry)\n }\n return entry\n}\n\n/**\n * Extract symbols and raw import specifiers from source text of the given\n * language, from a single parse. Returns rows ordered by file line. Never\n * throws for parse errors — a failed parse yields empty lists (the caller\n * logs and continues).\n */\nexport interface ExtractedFile {\n symbols: SymbolInfo[]\n /** Raw module specifiers, e.g. './util', 'node:fs', './utils' (py). */\n imports: string[]\n}\n\nexport async function extractAll(code: string, id: LanguageId): Promise<ExtractedFile> {\n const lang = await getLanguage(id)\n const parser = await getParser()\n parser.setLanguage(lang)\n const tree = parser.parse(code)\n try {\n const symbols: SymbolInfo[] = []\n const symbolQuery = lang.query(QUERIES[id])\n try {\n const captures = symbolQuery.captures(tree.rootNode)\n for (const cap of captures) {\n const def = CAPTURE_KINDS[cap.name]\n if (!def) continue\n const node = cap.node\n // tree-sitter queries match at any depth; without this check the\n // variable_declarator pattern would also capture function-body\n // locals — pure index noise.\n if (def.kind === 'variable' && !isModuleLevelVariable(node)) continue\n const name = nameOf(node)\n if (!name) continue\n symbols.push({\n name,\n kind: kindFor(id, node, def.kind),\n file: '', // set by the caller (extractor is file-agnostic)\n line: node.startPosition.row + 1,\n endLine: node.endPosition.row + 1,\n exported: isExported(id, node),\n signature: signatureFor(node),\n })\n }\n } finally {\n symbolQuery.delete()\n }\n\n const imports: string[] = []\n const importQuery = lang.query(IMPORT_QUERIES[id])\n try {\n for (const cap of importQuery.captures(tree.rootNode)) {\n const spec = specifierOf(id, cap.node)\n if (spec) imports.push(spec)\n }\n } finally {\n importQuery.delete()\n }\n\n symbols.sort((a, b) => a.line - b.line)\n return { symbols, imports }\n } finally {\n tree.delete()\n }\n}\n\n/** Symbols only — see extractAll for the combined parse. */\nexport async function extractSymbols(code: string, id: LanguageId): Promise<SymbolInfo[]> {\n return (await extractAll(code, id)).symbols\n}\n\n/** The declared name of a declaration node, via its `name` field. */\nfunction nameOf(node: Parser.SyntaxNode): string {\n const field = node.childForFieldName?.('name')\n if (field) return field.text.trim()\n // e.g. an anonymous default export — skip those.\n return ''\n}\n\n/** Read the raw module specifier out of a captured import statement. */\nfunction specifierOf(id: LanguageId, node: Parser.SyntaxNode): string | null {\n switch (node.type) {\n case 'import_statement':\n // ts/js: `import … from './x'` (source); python: `import a.b` (name)\n if (id === 'python') return dottedToPath(node.childForFieldName('name')?.text)\n return stripQuotes(node.childForFieldName('source')?.text)\n case 'export_statement':\n // ts/js re-export: `export … from './x'`; plain exports have no source\n return stripQuotes(node.childForFieldName('source')?.text)\n case 'import_from_statement': {\n // python: `from .utils import x` / `from mypkg.core import Thing`\n const raw = node.childForFieldName('module_name')?.text\n if (raw == null) return null\n return raw.startsWith('.') ? pythonRelative(raw) : dottedToPath(raw)\n }\n case 'import_spec': // go: `import \"example.com/foo/util\"`\n return stripQuotes(node.childForFieldName('path')?.text)\n case 'use_declaration': // rust: `use crate::a::b::{c, d}`\n return rustUsePath(node.childForFieldName('argument')?.text)\n case 'import_declaration': // java: `import com.example.Thing;`\n return dottedToPath(node.namedChildren[0]?.text)\n default:\n return null\n }\n}\n\n/**\n * Rust use paths: 'crate::a::b' is root-relative, 'super::x' is parent-module\n * ('../x'), 'self::x' is current ('./x'); everything else (std, external\n * crates) stays root-relative and simply won't resolve in-repo. Use-lists\n * ('a::b::{c,d}') contribute their base path.\n */\nfunction rustUsePath(text: string | undefined | null): string | null {\n if (!text) return null\n const base = text.split('{')[0].trim()\n if (!base) return null\n const parts = base.split('::').filter(Boolean)\n if (parts[0] === 'crate') parts.shift()\n return parts.map((p) => (p === 'super' ? '..' : p === 'self' ? '.' : p)).join('/')\n}\n\nfunction stripQuotes(text: string | undefined | null): string | null {\n if (text == null || text.length < 2) return null\n const first = text[0]\n const last = text[text.length - 1]\n if ((first === \"'\" && last === \"'\") || (first === '\"' && last === '\"')) {\n return text.slice(1, -1)\n }\n return null\n}\n\n/** 'os.path' → 'os/path' — dotted module path to repo-relative-ish path. */\nfunction dottedToPath(text: string | undefined | null): string | null {\n if (!text) return null\n return text.trim().replace(/\\./g, '/')\n}\n\n/** '.utils' → './utils', '..pkg.mod' → '../pkg/mod' — python relative import. */\nfunction pythonRelative(raw: string): string {\n const dots = raw.match(/^\\.+/)?.[0].length ?? 0\n const rest = raw.slice(dots).replace(/\\./g, '/')\n const prefix = dots === 1 ? './' : '../'.repeat(dots - 1)\n return prefix + rest\n}\n\n/** Best-effort declaration signature: `name` + parameter list, if any. */\nfunction signatureFor(node: Parser.SyntaxNode): string {\n const name = nameOf(node)\n // Field first (go method_declaration has both a receiver and a parameters\n // parameter_list — the field picks the right one), type fallback otherwise.\n const params =\n node.childForFieldName?.('parameters') ??\n node.namedChildren.find(\n (c) =>\n c.type === 'formal_parameters' ||\n c.type === 'method_parameters' ||\n c.type === 'parameters' ||\n c.type === 'parameter_list',\n )\n if (params) {\n return `${name}${collapseSpace(params.text)}`\n }\n const first = node.namedChildren[0]\n return first ? collapseSpace(first.text) : name\n}\n\n/**\n * Signatures must stay one line: repo-map rows are budgeted per line, and a\n * multi-line parameter list would both wrap the format and burn chars.\n */\nfunction collapseSpace(text: string): string {\n return text\n .replace(/\\s+/g, ' ')\n .replace(/\\( /g, '(')\n .replace(/ \\)/g, ')')\n .replace(/ ,/g, ',')\n .replace(/,\\)/g, ')')\n .trim()\n}\n\n/**\n * A variable is indexable only at module level: its declaration must sit\n * directly inside the program (or the export_statement wrapping it). Anything\n * deeper — function bodies, blocks, for-of heads — is a local with no\n * navigation value.\n */\nfunction isModuleLevelVariable(node: Parser.SyntaxNode): boolean {\n const declaration = node.parent // variable_declaration | lexical_declaration\n const container = declaration?.parent // program | export_statement | …\n return container?.type === 'program' || container?.type === 'export_statement'\n}\n\n/**\n * A symbol is exported when walking up from it reaches an export_statement\n * before any function or class body — only module-level declarations count.\n * A method inside `export class` must NOT inherit the class's export, and a\n * function nested inside an exported function is not exported either.\n */\nfunction isExported(id: LanguageId, node: Parser.SyntaxNode): boolean {\n if (id === 'python') {\n // Python has no export syntax: a def/class directly under the module is\n // importable, anything nested (methods, inner functions) is not a\n // module-level symbol.\n return node.parent?.type === 'module'\n }\n if (id === 'go') {\n // Go exports by capitalisation, not by keyword.\n const name = nameOf(node)\n return !!name && /^[A-Z]/.test(name)\n }\n if (id === 'rust') {\n // `pub` = exported; `pub(crate)` & friends are crate-local, not public.\n return node.namedChildren.some(\n (c) => c.type === 'visibility_modifier' && c.text === 'pub',\n )\n }\n if (id === 'java') {\n // Interface members are implicitly public.\n if (node.parent?.type === 'interface_body') return true\n return node.namedChildren.some((c) => c.type === 'modifiers' && /\\bpublic\\b/.test(c.text))\n }\n for (let parent = node.parent; parent; parent = parent.parent) {\n if (parent.type === 'export_statement') return true\n if (parent.type === 'statement_block' || parent.type === 'class_body') return false\n if (parent.type === 'program') return false\n }\n return false\n}\n\n/**\n * Language quirks the query syntax can't express:\n * - python: no distinct method node — a def directly in a class body is one\n * - go: a type_spec is a class (struct) or interface by its type child\n * - rust: function_item directly in an impl body is a method\n */\nfunction kindFor(id: LanguageId, node: Parser.SyntaxNode, kind: SymbolKind): SymbolKind {\n if (id === 'python' && kind === 'function') {\n const inClassBody = node.parent?.type === 'block' && node.parent?.parent?.type === 'class_definition'\n if (inClassBody) return 'method'\n }\n if (id === 'go' && node.type === 'type_spec') {\n const type = node.childForFieldName('type')?.type\n if (type === 'struct_type') return 'class'\n if (type === 'interface_type') return 'interface'\n return 'type'\n }\n if (id === 'rust' && node.type === 'function_item') {\n const inImpl = node.parent?.type === 'declaration_list' && node.parent?.parent?.type === 'impl_item'\n if (inImpl) return 'method'\n }\n return kind\n}\n\n/** Compile a symbol query for a code sample (used by tests). */\nexport async function parseFileToSymbols(\n filePath: string,\n repoRoot: string,\n code?: string,\n): Promise<SymbolInfo[]> {\n const lang = languageForFile(filePath)\n if (!lang) return []\n const text = code ?? (await readFile(filePath, 'utf8'))\n const rows = await extractSymbols(text, lang)\n const rel = path.relative(repoRoot, filePath).split(path.sep).join('/')\n return rows.map((r) => ({ ...r, file: rel }))\n}","/** Workspace file discovery with dir exclusions and mtime tracking. */\n\nimport { readdir, stat } from 'node:fs/promises'\nimport path from 'node:path'\nimport type { IndexOptions } from './types.js'\n\nexport const DEFAULT_EXCLUDED_DIRS = [\n 'node_modules',\n '.git',\n '.idea',\n '.vscode',\n 'dist',\n 'build',\n 'out',\n 'coverage',\n '.next',\n '.nuxt',\n '.cache',\n 'target',\n 'vendor',\n '.dsh-code-index',\n]\n\n/** Language-agnostic source extensions we index. */\nexport const SUPPORTED_EXTS = new Set([\n '.ts',\n '.tsx',\n '.mts',\n '.cts',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.py',\n '.pyi',\n '.go',\n '.rs',\n '.java',\n])\n\nexport interface ScannedFile {\n /** Absolute path on disk. */\n abs: string\n /** Repo-relative path (forward-slash). */\n rel: string\n mtimeMs: number\n}\n\n/**\n * Recursively walk `root` and return indexed files, skipping excluded dir\n * components at any depth. Uses readdir with `withFileTypes` so we never\n * stat every entry; mtime comes from a targeted stat per candidate file.\n */\nexport async function scanRepo(\n root: string,\n options: IndexOptions = {},\n): Promise<ScannedFile[]> {\n const excluded = new Set([...DEFAULT_EXCLUDED_DIRS, ...(options.excludeDirs ?? [])])\n const results: ScannedFile[] = []\n const queue: Array<[string, string]> = [[root, '']] // [absDir, relDir]\n\n while (queue.length) {\n const [absDir, relDir] = queue.pop()!\n let entries\n try {\n entries = await readdir(absDir, { withFileTypes: true })\n } catch {\n continue // unreadable dir: skip into the void\n }\n for (const entry of entries) {\n const abs = path.join(absDir, entry.name)\n const rel = relDir ? `${relDir}/${entry.name}` : entry.name\n if (entry.isDirectory()) {\n if (excluded.has(entry.name)) continue\n queue.push([abs, rel])\n } else if (entry.isFile() && SUPPORTED_EXTS.has(path.extname(entry.name).toLowerCase())) {\n try {\n const st = await stat(abs)\n results.push({ abs, rel, mtimeMs: st.mtimeMs })\n } catch {\n // race: file deleted mid-scan — ignore\n }\n }\n }\n }\n\n results.sort((a, b) => a.rel.localeCompare(b.rel))\n return results\n}","/** On-disk JSON cache for RepoIndex, keyed by repo root. */\n\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { createHash, randomUUID } from 'node:crypto'\nimport type { RepoIndex } from './types.js'\n\nexport const CACHE_DIR_NAME = '.dsh-code-index'\n\n/** Stable root identity for cache hashing, including Windows case aliases. */\nexport function cacheKeyForRoot(root: string): string {\n const resolved = path.resolve(root)\n return process.platform === 'win32' ? resolved.toLowerCase() : resolved\n}\n\n/** Default cache location: `<repoRoot>/.dsh-code-index/index.json`. */\nexport function defaultCachePath(root: string, cacheDir?: string): string {\n const dir = cacheDir ?? path.join(root, CACHE_DIR_NAME)\n const hash = createHash('sha1').update(cacheKeyForRoot(root)).digest('hex').slice(0, 12)\n return path.join(dir, `${hash}.json`)\n}\n\n/** Cache filename used before Windows root casing was canonicalized. */\nexport function legacyCachePath(root: string, cacheDir?: string): string {\n const dir = cacheDir ?? path.join(root, CACHE_DIR_NAME)\n const hash = createHash('sha1').update(root).digest('hex').slice(0, 12)\n return path.join(dir, `${hash}.json`)\n}\n\nexport async function loadIndex(cachePath: string): Promise<RepoIndex | null> {\n try {\n const raw = await readFile(cachePath, 'utf8')\n const parsed = JSON.parse(raw) as RepoIndex\n if (!parsed || typeof parsed.root !== 'string' || !Array.isArray(parsed.files)) return null\n return healSymbolFiles(parsed)\n } catch {\n return null // missing or corrupt cache == no cache\n }\n}\n\n/**\n * Self-heal caches written before the per-symbol file backfill (the\n * \"SymbolInfo.file is always set\" invariant). Empty `file` fields are derived\n * from the containing IndexedFile path on load, so any consumer of loadIndex\n * sees consistent rows — and the next save persists the healed form.\n */\nexport function healSymbolFiles(index: RepoIndex): RepoIndex {\n for (const file of index.files) {\n for (const symbol of file.symbols) {\n if (!symbol.file) symbol.file = file.path\n }\n }\n return index\n}\n\nexport async function saveIndex(cachePath: string, index: RepoIndex): Promise<void> {\n await mkdir(path.dirname(cachePath), { recursive: true })\n const temporaryPath = `${cachePath}.${process.pid}.${randomUUID()}.tmp`\n try {\n await writeFile(temporaryPath, JSON.stringify(index, null, 2), { encoding: 'utf8', flag: 'wx' })\n await rename(temporaryPath, cachePath)\n } finally {\n await rm(temporaryPath, { force: true })\n }\n}\n","/** Shared data model for dsh-code-index. */\n\nexport type SymbolKind =\n | 'function'\n | 'method'\n | 'class'\n | 'interface'\n | 'type'\n | 'enum'\n | 'variable'\n | 'field'\n | 'import'\n | 'module'\n\nexport interface SymbolInfo {\n /** Symbol name (identifier / type id / member name). */\n name: string\n kind: SymbolKind\n /** Repo-relative path of the containing file, always forward-slash. */\n file: string\n /** 1-based start line. */\n line: number\n /** 1-based end line (inclusive). */\n endLine: number\n /** True when the symbol is exported at module level (export .. / export default). */\n exported: boolean\n /** Short human-readable signature, e.g. `greet(name: string)` — empty when n/a. */\n signature: string\n}\n\nexport interface IndexedFile {\n /** Repo-relative path (forward-slash). */\n path: string\n lang: string\n /** fs mtime of the source file at index time (milliseconds). */\n mtimeMs: number\n symbols: SymbolInfo[]\n /** Raw import specifiers found in this file ('./util', 'mypkg/core', …).\n * Optional because caches written before reference ranking lack it. */\n imports?: string[]\n}\n\nexport interface RepoIndex {\n /** Absolute source root this index describes. */\n root: string\n /** Unix ms when the index was generated. */\n generatedAt: number\n files: IndexedFile[]\n excludedDirs: string[]\n}\n\nexport function symbolCount(index: RepoIndex): number {\n return index.files.reduce((n, f) => n + f.symbols.length, 0)\n}\n\n/** Optional config consumed by the index build pipeline. */\nexport interface IndexOptions {\n /** Extra directories to exclude, appended to the defaults. */\n excludeDirs?: string[]\n /** Minimum mtime delta (ms) that forces a re-extract in refresh mode. */\n staleToleranceMs?: number\n}","/** Pure search / filter logic over a RepoIndex — unit-testable, no IO. */\n\nimport type { RepoIndex, SymbolInfo, SymbolKind } from './types.js'\n\nexport interface SymbolFilter {\n query?: string\n file?: string\n kind?: SymbolKind\n exportedOnly?: boolean\n}\n\nexport interface RankedHit extends SymbolInfo {\n /** 0..1 relevance. 1 = exact name match; lower = fuzzier. */\n score: number\n}\n\nfunction matchScore(name: string, query: string): number {\n const q = query.toLowerCase()\n const n = name.toLowerCase()\n if (n === q) return 1\n if (n.startsWith(q)) return 0.8\n if (n.includes(q)) return 0.5\n if (q.length >= 3 && isSubsequence(q, n)) return 0.3\n return 0\n}\n\n/**\n * Every query char appears in the name, in order — 'cfgldr' finds\n * 'configLoader'. Gated to 3+ chars so short queries don't match everything.\n */\nfunction isSubsequence(query: string, name: string): boolean {\n let i = 0\n for (const ch of name) {\n if (ch === query[i]) i++\n if (i === query.length) return true\n }\n return false\n}\n\n/** Filter symbols by file/kind/export, then score by name against query (if any). */\nexport function searchSymbols(\n index: RepoIndex,\n filter: SymbolFilter,\n limit = 50,\n): RankedHit[] {\n const q = (filter.query ?? '').trim()\n const filePat = filter.file?.trim().toLowerCase()\n const hits: RankedHit[] = []\n\n for (const file of index.files) {\n if (filePat && !file.path.toLowerCase().includes(filePat)) continue\n for (const sym of file.symbols) {\n if (filter.kind && sym.kind !== filter.kind) continue\n if (filter.exportedOnly && !sym.exported) continue\n const score = q ? matchScore(sym.name, q) : 0.4\n if (q && score === 0) continue\n hits.push({ ...sym, score })\n }\n }\n\n // Export boost, then relevance, then stable name order.\n hits.sort((a, b) => {\n const ab =\n Number(b.exported) - Number(a.exported) ||\n b.score - a.score ||\n a.name.localeCompare(b.name) ||\n a.file.localeCompare(b.file)\n return ab\n })\n return hits.slice(0, limit)\n}\n\n/** Neat one-line rendering of a hit for terminal/chat output. */\nexport function renderHit(hit: RankedHit): string {\n const exportMark = hit.exported ? 'export ' : ''\n const sig = hit.signature || hit.name\n const score = hit.score < 1 ? ` [${hit.score.toFixed(2)}]` : ''\n return `${exportMark}${hit.kind} ${sig}${score} — ${hit.file}:${hit.line}`\n}","/**\n * Bounded, ranked repo map generation — the \"aider-style\" overview the model\n * can turn to before browsing files. Pure logic, no IO.\n */\n\nimport path from 'node:path'\nimport { SUPPORTED_EXTS } from './scan.js'\nimport type { IndexedFile, RepoIndex, SymbolInfo, SymbolKind } from './types.js'\n\n/** Higher-weight symbols pull their file up the map. */\nexport const KIND_WEIGHT: Record<SymbolKind, number> = {\n class: 1.0,\n interface: 1.0,\n type: 0.8,\n function: 0.8,\n method: 0.7,\n enum: 0.9,\n variable: 0.25,\n field: 0.2,\n import: 0.05,\n module: 0.3,\n}\n\nexport interface RepoMapOptions {\n /** Max files to include (default 24). */\n topFiles?: number\n /** Max symbols per file (default 18). */\n symbolsPerFile?: number\n /** Hard cap on rendered characters (default 3200). */\n maxChars?: number\n}\n\nexport interface RepoMapEntry {\n path: string\n score: number\n symbols: Array<{ name: string; kind: SymbolKind; line: number; signature: string }>\n}\n\n/**\n * Paths that look like tests. A map led by test files reads as noise: tests\n * reference an API, they rarely explain it — and symbol density actively\n * favours them (many small functions), so they need an explicit damper.\n */\nconst TEST_PATH_RE =\n /(^|\\/)(tests?|__tests__)(\\/|$)|\\.(?:spec|test)\\.[cm]?[jt]sx?$|(^|\\/)(?:test_[^/]+\\.py|[^/]+_test\\.(?:py|go))$/\n\n/** Density-aware file score: weighted symbols minus bloat; test paths damped. */\nexport function scoreFile(file: IndexedFile): number {\n let score = 0\n for (const sym of file.symbols) {\n score += KIND_WEIGHT[sym.kind] ?? 0.3\n if (sym.exported) score += 0.3\n }\n score /= 1 + file.symbols.length * 0.04\n if (TEST_PATH_RE.test(file.path)) score *= 0.2\n return score\n}\n\n/** Rank files, take the top slice, cap per-file symbols. */\nexport function rankRepoMap(index: RepoIndex, options: RepoMapOptions = {}): RepoMapEntry[] {\n const topFiles = options.topFiles ?? 24\n const perFile = options.symbolsPerFile ?? 18\n const density = new Map<string, number>()\n for (const f of index.files) {\n if (f.symbols.length > 0) density.set(f.path, scoreFile(f))\n }\n const centrality = referencePageRank(index.files, density)\n const ranked = index.files\n .filter((f) => f.symbols.length > 0)\n .map((f) => ({\n file: f,\n score: (density.get(f.path) ?? 0) + REF_WEIGHT * (centrality.get(f.path) ?? 0),\n }))\n .sort((a, b) => b.score - a.score || a.file.path.localeCompare(b.file.path))\n .slice(0, topFiles)\n\n return ranked.map(({ file, score }) => ({\n path: file.path,\n score,\n symbols: file.symbols.slice(0, perFile).map((s) => ({\n name: s.name,\n kind: s.kind,\n line: s.line,\n signature: s.signature,\n })),\n }))\n}\n\n/** How much one unit of reference centrality is worth in map score. */\nconst REF_WEIGHT = 0.5\n\n/**\n * Count in-repo references: how many OTHER indexed files import each file.\n * Self-imports don't count; each (importer, target) pair counts once.\n */\nexport function countReferences(files: IndexedFile[]): Map<string, number> {\n const fileSet = new Set(files.map((f) => f.path))\n const counts = new Map<string, number>()\n for (const file of files) {\n const targets = new Set<string>()\n for (const spec of file.imports ?? []) {\n const target = resolveImport(spec, file.path, fileSet)\n if (target && target !== file.path) targets.add(target)\n }\n for (const target of targets) {\n counts.set(target, (counts.get(target) ?? 0) + 1)\n }\n }\n return counts\n}\n\n/** PageRank damping — the standard 0.85. */\nconst DAMPING = 0.85\n/** Stop the power iteration once total rank movement drops below this. */\nconst CONVERGENCE_TOL = 1e-6\n/** Hard cap; convergence on repo-sized graphs needs ~20-40. */\nconst MAX_ITERATIONS = 100\n\n/**\n * Personalized PageRank over the import graph: nodes are files, an edge\n * A → B means A imports B, so rank flows toward heavily-imported hubs — and\n * a hub that other hubs themselves import accumulates more than a merely\n * popular leaf, which flat in-degree counting cannot see.\n *\n * Teleport uses each file's density share instead of a uniform vector, so\n * sparsely-linked repos keep their 0.2.x ordering (with no edges the fixed\n * point IS the normalized density vector). Returns L1-normalized importance\n * mass per file.\n */\nfunction referencePageRank(files: IndexedFile[], density: Map<string, number>): Map<string, number> {\n const nodes = files.filter((f) => density.has(f.path)).map((f) => f.path)\n const n = nodes.length\n if (n === 0) return new Map()\n if (n === 1) return new Map([[nodes[0]!, 1]])\n\n const totalDensity = nodes.reduce((sum, p) => sum + (density.get(p) ?? 0), 0)\n const teleport = nodes.map((p) => (density.get(p) ?? 0) / totalDensity)\n\n const fileSet = new Set(nodes)\n const out = new Map<string, Set<string>>()\n for (const file of files) {\n if (!fileSet.has(file.path)) continue\n const targets = out.get(file.path) ?? new Set<string>()\n for (const spec of file.imports ?? []) {\n const target = resolveImport(spec, file.path, fileSet)\n if (target && target !== file.path) targets.add(target)\n }\n out.set(file.path, targets)\n }\n\n const incoming = new Map<string, Array<[fromIdx: number, weight: number]>>()\n for (const [from, targets] of out) {\n if (targets.size === 0) continue\n const weight = 1 / targets.size\n const fromIdx = nodes.indexOf(from)\n for (const target of targets) {\n const list = incoming.get(target) ?? []\n list.push([fromIdx, weight])\n incoming.set(target, list)\n }\n }\n\n let rank = teleport.slice()\n for (let iter = 0; iter < MAX_ITERATIONS; iter++) {\n let danglingMass = 0\n for (let i = 0; i < n; i++) {\n if (!out.get(nodes[i]!)?.size) danglingMass += rank[i]!\n }\n const next = new Array<number>(n)\n let delta = 0\n for (let i = 0; i < n; i++) {\n let flow = 0\n for (const [fromIdx, weight] of incoming.get(nodes[i]!) ?? []) {\n flow += rank[fromIdx]! * weight\n }\n next[i] = (1 - DAMPING) * teleport[i]! + DAMPING * (flow + danglingMass * teleport[i]!)\n delta += Math.abs(next[i]! - rank[i]!)\n }\n rank = next\n if (delta < CONVERGENCE_TOL) break\n }\n\n const result = new Map<string, number>()\n for (let i = 0; i < n; i++) result.set(nodes[i]!, rank[i]!)\n return result\n}\n\n/**\n * Resolve a raw import specifier against the indexed file set; returns the\n * repo-relative target path or null. Relative specifiers resolve against the\n * importing file; absolute ones against the repo root — with a suffix\n * fallback so Go module paths and Java package names match their in-repo\n * location without reading go.mod / package-info.\n */\nexport function resolveImport(spec: string, fromPath: string, fileSet: Set<string>): string | null {\n if (!spec) return null\n if (spec.startsWith('./') || spec.startsWith('../')) {\n const base = path.posix.normalize(path.posix.join(path.posix.dirname(fromPath), spec))\n return candidateFor(base, fileSet)\n }\n const segments = spec.split('/').filter(Boolean)\n for (let skip = 0; skip < segments.length; skip++) {\n const hit = candidateFor(segments.slice(skip).join('/'), fileSet)\n if (hit) return hit\n }\n return null\n}\n\n/** Extension and index-file candidates for a specifier base path. */\nfunction candidateFor(base: string, fileSet: Set<string>): string | null {\n for (const ext of SUPPORTED_EXTS) {\n if (fileSet.has(`${base}${ext}`)) return `${base}${ext}`\n }\n for (const ext of SUPPORTED_EXTS) {\n if (fileSet.has(`${base}/index${ext}`)) return `${base}/index${ext}`\n if (fileSet.has(`${base}/__init__${ext}`)) return `${base}/__init__${ext}`\n }\n return null\n}\n\n/** Render the ranked map as markdown, hard-truncated to maxChars. */\nexport function renderRepoMap(entries: RepoMapEntry[], options: RepoMapOptions = {}): string {\n const maxChars = options.maxChars ?? 3200\n const lines: string[] = ['# repo map']\n let totalSymbols = 0\n for (const e of entries) {\n totalSymbols += e.symbols.length\n lines.push(`## ${e.path} (${e.symbols.length})`)\n for (const s of e.symbols) {\n const label = s.signature || s.name\n lines.push(` ${s.kind} ${label} :${s.line}`)\n }\n }\n let text = lines.join('\\n')\n if (text.length > maxChars) {\n text = `${text.slice(0, maxChars)}\\n… truncated`\n }\n return totalSymbols > 0 ? text : ''\n}","/** Model-visible tools: code_index, code_symbols, code_search. */\n\nimport { defineTool, type JsonValue } from '@deepseek-ai/dsh-tools'\nimport path from 'node:path'\nimport { buildIndexWithCache, findRepoRoot } from './buildIndex.js'\nimport { renderHit, searchSymbols } from './search.js'\nimport { rankRepoMap, renderRepoMap } from './repomap.js'\nimport { getConfig, indexOptions } from './config.js'\nimport { cacheKeyForRoot } from './store.js'\nimport type { RepoIndex } from './types.js'\n\n/** Minimal structural types for the harness surfaces we touch. */\ninterface ToolCwdContext {\n agent?: {\n session?: { header?: { cwd?: string } }\n }\n}\ntype ToolRunExec = ToolCwdContext & { signal?: AbortSignal }\n\ninterface TextBlock {\n type: 'text'\n text: string\n}\n\nexport interface IndexCache {\n get(root: string, force?: boolean): Promise<RepoIndex>\n invalidate(): void\n}\n\n/** Retain completed indexes briefly while still coalescing concurrent refreshes. */\nexport function createIndexCache(\n load: (root: string) => Promise<RepoIndex>,\n ttlMs = 60_000,\n now: () => number = Date.now,\n): IndexCache {\n interface CompletedEntry {\n index: RepoIndex\n at: number\n epoch: number\n }\n interface LoadEntry {\n promise: Promise<RepoIndex>\n epoch: number\n }\n\n const completed = new Map<string, CompletedEntry>()\n const inFlight = new Map<string, LoadEntry>()\n let epoch = 0\n\n function startLoad(key: string, root: string, previous?: Promise<RepoIndex>): Promise<RepoIndex> {\n const loadEpoch = epoch\n const begin = previous\n ? previous.catch(() => undefined).then(() => load(root))\n : load(root)\n const promise = begin\n .then((index) => {\n if (loadEpoch === epoch) {\n completed.delete(key)\n completed.set(key, { index, at: now(), epoch: loadEpoch })\n while (completed.size > 16) completed.delete(completed.keys().next().value!)\n }\n return index\n })\n .finally(() => {\n if (inFlight.get(key)?.promise === promise) inFlight.delete(key)\n })\n inFlight.set(key, { promise, epoch: loadEpoch })\n return promise\n }\n\n return {\n get(root, force = false) {\n const resolvedRoot = path.resolve(root)\n const key = cacheKeyForRoot(resolvedRoot)\n const running = inFlight.get(key)\n if (running) {\n if (running.epoch === epoch && !force) return running.promise\n return startLoad(key, resolvedRoot, running.promise)\n }\n\n if (!force) {\n const cached = completed.get(key)\n if (cached && cached.epoch === epoch && now() - cached.at < ttlMs) {\n completed.delete(key)\n completed.set(key, cached)\n return Promise.resolve(cached.index)\n }\n if (cached) completed.delete(key)\n } else {\n completed.delete(key)\n }\n return startLoad(key, resolvedRoot)\n },\n invalidate() {\n epoch++\n completed.clear()\n },\n }\n}\n\nconst indexCache = createIndexCache((root) => buildIndexWithCache(root, indexOptions()))\n\nexport function getIndex(root: string, force = false): Promise<RepoIndex> {\n return indexCache.get(root, force)\n}\n\nexport function invalidateIndexCache(): void {\n indexCache.invalidate()\n}\n\nasync function resolveRoot(arg: string | undefined, exec: ToolRunExec): Promise<string> {\n const cwd = exec.agent?.session?.header?.cwd\n const base = arg ?? cwd ?? process.cwd()\n const root = await findRepoRoot(base)\n if (!root) throw new Error(`no git repository found from ${path.resolve(base)}`)\n return root\n}\n\nexport const tools = [\n defineTool({\n name: 'code_index',\n description:\n 'Manage the semantic repo index: status or (re)build it for the current workspace. Auto-builds the first time it is queried. Returns file/symbol counts and the index location.',\n parameters: {\n action: {\n type: 'string',\n enum: ['status', 'build'],\n description: '\"status\" (default) reports without forcing a rebuild; \"build\" forces a fresh scan.',\n },\n repoRoot: {\n type: 'string',\n description:\n 'Optional absolute repo path. Defaults to the workspace root of the current session.',\n },\n },\n output: {\n schema: { type: 'string' },\n render: (_args, value: string): TextBlock[] => [{ type: 'text', text: value }],\n },\n async execute(args: { action?: string; repoRoot?: string }, exec: ToolRunExec): Promise<string> {\n try {\n const root = await resolveRoot(args.repoRoot, exec)\n const index = await getIndex(root, args.action === 'build')\n const total = index.files.reduce((n, f) => n + f.symbols.length, 0)\n const langs = new Set(index.files.map((f) => f.lang))\n return [\n `repo: ${root}`,\n `files indexed: ${index.files.length}`,\n `symbols: ${total}`,\n `languages: ${[...langs].join(', ')}`,\n index.files.length === 0 ? 'no supported files found' : 'index up to date',\n ].join('\\n')\n } catch (error) {\n return `code_index: ${(error as Error).message ?? String(error)}`\n }\n },\n }),\n\n defineTool({\n name: 'code_symbols',\n description:\n 'List symbols (functions, classes, interfaces, types, methods, variables) in the current repo. Filter by name substring, file path substring, or symbol kind. Results are exported-first, alphabetically ordered.',\n parameters: {\n query: {\n type: 'string',\n description: 'Substring of the symbol name to match (case-insensitive). Omit to list all.',\n },\n file: {\n type: 'string',\n description: 'Substring of the repo-relative file path to match, e.g. \"src/core\".',\n },\n kind: {\n type: 'string',\n enum: ['function', 'method', 'class', 'interface', 'type', 'enum', 'variable', 'field'],\n description: 'Only return symbols of this kind.',\n },\n exportedOnly: {\n type: 'boolean',\n description: 'Only exported (module-level public) symbols.',\n },\n limit: {\n type: 'number',\n description: 'Max rows (default 50).',\n },\n repoRoot: {\n type: 'string',\n description: 'Optional absolute repo path; defaults to the session workspace root.',\n },\n },\n output: {\n schema: { type: 'array' },\n render: (_args, value: JsonValue[]): TextBlock[] =>\n value.map((v) => ({\n type: 'text' as const,\n text: renderHit(v as unknown as Parameters<typeof renderHit>[0]),\n })),\n },\n async execute(\n args: {\n query?: string\n file?: string\n kind?: 'function' | 'method' | 'class' | 'interface' | 'type' | 'enum' | 'variable' | 'field'\n exportedOnly?: boolean\n limit?: number\n repoRoot?: string\n },\n exec: ToolRunExec,\n ): Promise<JsonValue[]> {\n const root = await resolveRoot(args.repoRoot, exec)\n const index = await getIndex(root)\n const hits = searchSymbols(\n index,\n { query: args.query, file: args.file, kind: args.kind, exportedOnly: args.exportedOnly },\n args.limit ?? 50,\n )\n return hits as unknown as JsonValue[]\n },\n }),\n\n defineTool({\n name: 'code_search',\n description:\n 'Ranked symbol search over the repo index: exact > prefix > substring name matches, exported symbols first. Results carry a relevance score and file:line, so the model can locate definitions quickly.',\n parameters: {\n query: {\n type: 'string',\n required: true,\n description: 'Symbol name (or fragment) to find.',\n },\n limit: {\n type: 'number',\n description: 'Max hits (default 20).',\n },\n repoRoot: {\n type: 'string',\n description: 'Optional absolute repo path; defaults to the session workspace root.',\n },\n },\n output: {\n schema: { type: 'array' },\n render: (_args, value: JsonValue[]): TextBlock[] =>\n value.map((v) => ({\n type: 'text' as const,\n text: renderHit(v as unknown as Parameters<typeof renderHit>[0]),\n })),\n },\n async execute(\n args: { query: string; limit?: number; repoRoot?: string },\n exec: ToolRunExec,\n ): Promise<JsonValue[]> {\n const root = await resolveRoot(args.repoRoot, exec)\n const index = await getIndex(root)\n const hits = searchSymbols(index, { query: args.query }, args.limit ?? 20)\n return hits as unknown as JsonValue[]\n },\n }),\n\n defineTool({\n name: 'code_map',\n description:\n 'Return a bounded, ranked map of the current repo (top files by symbol density, with their key symbols and lines). The model can call this once per session to build an internal model of the codebase before browsing files.',\n parameters: {\n repoRoot: {\n type: 'string',\n description: 'Optional absolute repo path; defaults to the session workspace root.',\n },\n },\n output: {\n schema: { type: 'string' },\n render: (_args, value: string): TextBlock[] => [{ type: 'text', text: value }],\n },\n async execute(args: { repoRoot?: string }, exec: ToolRunExec): Promise<string> {\n try {\n const root = await resolveRoot(args.repoRoot, exec)\n const index = await getIndex(root)\n const cfg = getConfig()\n const map = renderRepoMap(\n rankRepoMap(index, { topFiles: cfg.mapTopFiles }),\n { maxChars: cfg.mapMaxChars },\n )\n if (!map) return 'no indexable symbols found in this repo'\n return map\n } catch (error) {\n return `code_index: ${(error as Error).message ?? String(error)}`\n }\n },\n }),\n]\n","/** Plugin configuration: merged once at apply time, read wherever needed. */\n\nimport type { IndexOptions } from './types.js'\n\nexport interface PluginConfig {\n /** Extra directories to exclude from indexing (appended to defaults). */\n excludeDirs?: string[]\n /** Max files in the ranked repo map (code_map / auto section). */\n mapTopFiles?: number\n /** Hard char cap for rendered maps. */\n mapMaxChars?: number\n /** Refresh interval for the auto-injected map (ms, min 1000). */\n mapTtlMs?: number\n /** Set false to disable the auto-injected system section. */\n autoInject?: boolean\n}\n\ninterface EffectiveConfig {\n excludeDirs: string[]\n mapTopFiles: number\n mapMaxChars: number\n mapTtlMs: number\n autoInject: boolean\n}\n\nconst DEFAULTS: EffectiveConfig = {\n excludeDirs: [],\n mapTopFiles: 24,\n mapMaxChars: 3200,\n mapTtlMs: 60_000,\n autoInject: true,\n}\n\nconst state: { current: EffectiveConfig } = { current: { ...DEFAULTS } }\n\n/** Merge a plugin-provided partial config over the defaults (idempotent). */\nexport function applyConfig(partial?: PluginConfig): void {\n state.current = {\n ...DEFAULTS,\n ...(partial ?? {}),\n excludeDirs: [...DEFAULTS.excludeDirs, ...(partial?.excludeDirs ?? [])],\n }\n // Coerce obviously wrong inputs.\n if (!Number.isFinite(state.current.mapTopFiles) || state.current.mapTopFiles < 1) {\n state.current.mapTopFiles = DEFAULTS.mapTopFiles\n }\n if (!Number.isFinite(state.current.mapMaxChars) || state.current.mapMaxChars < 200) {\n state.current.mapMaxChars = DEFAULTS.mapMaxChars\n }\n if (!Number.isFinite(state.current.mapTtlMs) || state.current.mapTtlMs < 1_000) {\n state.current.mapTtlMs = DEFAULTS.mapTtlMs\n }\n}\n\nexport function getConfig(): Readonly<EffectiveConfig> {\n return state.current\n}\n\n/** Map the effective config onto the index pipeline options. */\nexport function indexOptions(): IndexOptions {\n return { excludeDirs: state.current.excludeDirs }\n}","/**\n * dsh-code-index — DeepSeek Harness bundle entry.\n *\n * Registers four model-visible tools (code_index / code_symbols /\n * code_search / code_map) backed by a tree-sitter symbol index, and\n * injects a bounded auto-updating repo map for the default workspace\n * into the system prompt.\n */\n\ntype Disposer = void | (() => void)\n\n/** Minimal structural Context; the real @deepseek-ai/cordis type is a\n * runtime dependency we intentionally do not import in the bundle entry. */\ninterface MinimalContext {\n effect(fn: () => Disposer): void\n tools: { register(t: unknown): () => void }\n systemPrompt: {\n section(section: {\n name: string\n order: number\n text: string | ((context: unknown) => string)\n }): () => void\n }\n}\n\nexport const name = 'dsh-code-index'\n\n// Public API surface (consumable by other bundles / tests).\nexport { buildIndex, buildIndexWithCache, findRepoRoot } from './buildIndex.js'\nexport { extractSymbols, extractAll, languageForFile, parseFileToSymbols } from './extract.js'\nexport { scanRepo, DEFAULT_EXCLUDED_DIRS, SUPPORTED_EXTS } from './scan.js'\nexport { loadIndex, saveIndex, defaultCachePath, CACHE_DIR_NAME } from './store.js'\nexport { symbolCount } from './types.js'\nexport type { RepoIndex, IndexedFile, SymbolInfo, SymbolKind, IndexOptions } from './types.js'\nexport { searchSymbols, renderHit } from './search.js'\nexport { rankRepoMap, renderRepoMap, scoreFile } from './repomap.js'\nexport { tools } from './tools.js'\n\nimport { getIndex, invalidateIndexCache, tools } from './tools.js'\nimport { findRepoRoot } from './buildIndex.js'\nimport { rankRepoMap, renderRepoMap } from './repomap.js'\nimport { symbolCount } from './types.js'\nimport { applyConfig, getConfig, type PluginConfig } from './config.js'\n\nexport const inject = ['tools', 'systemPrompt'] as const\n\nexport function apply(ctx: MinimalContext, pluginConfig?: PluginConfig) {\n applyConfig(pluginConfig)\n invalidateIndexCache()\n ctx.effect(() => {\n const disposers: Array<() => void> = []\n console.log('[dsh-code-index] plugin loaded')\n for (const tool of tools) {\n disposers.push(ctx.tools.register(tool))\n console.log(`[dsh-code-index] registered tool: ${tool.name}`)\n }\n\n // Auto-inject a bounded repo map for the DEFAULT workspace (the dsh\n // launch directory, per the harness docs). Multi-workspace web sessions\n // should rely on the `code_map` tool, which resolves the per-session cwd.\n let cached: { root: string; at: number; text: string } | null = null\n\n async function warmMap(): Promise<void> {\n const now = Date.now()\n const cfg = getConfig()\n try {\n const root = await findRepoRoot(process.cwd())\n if (!root) {\n cached = { root: '', at: now, text: '' }\n return\n }\n const index = await getIndex(root)\n const text = renderRepoMap(\n rankRepoMap(index, { topFiles: cfg.mapTopFiles }),\n { maxChars: cfg.mapMaxChars },\n )\n const stats = symbolCount(index)\n cached = {\n root: index.root,\n at: Date.now(),\n text: text ? `${text}\\n\\n(summary: ${index.files.length} files, ${stats} symbols)` : '',\n }\n } catch {\n cached = { root: '', at: now, text: '' } // never let injection fail the boot\n }\n }\n\n // Warm eagerly at load so the first assembly already has the map.\n void warmMap()\n\n if (getConfig().autoInject) {\n disposers.push(ctx.systemPrompt.section({\n name: 'code-index:repo-map',\n order: 60, // before tool guidance (100–199), after persona (0)\n text: () => {\n const now = Date.now()\n if (cached && now - cached.at < getConfig().mapTtlMs) return cached.text\n void warmMap()\n return cached?.text ?? ''\n },\n }))\n }\n\n return () => {\n const errors: unknown[] = []\n for (const dispose of disposers.reverse()) {\n try {\n dispose()\n } catch (error) {\n errors.push(error)\n }\n }\n console.log('[dsh-code-index] plugin unloaded')\n if (errors.length > 0) throw new AggregateError(errors, 'failed to unload dsh-code-index')\n }\n })\n}\n"],"mappings":"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","names":["readFile","stat","path","require","name","path","readFile","path","readFile","path","stat","name","path","path","path","path"]}
1
+ {"version":3,"sources":["../src/buildIndex.ts","../src/extract.ts","../src/scan.ts","../src/store.ts","../src/types.ts","../src/search.ts","../src/repomap.ts","../src/tools.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["/** Build a RepoIndex for a workspace: scan -> extract -> persist. */\n\nimport { readFile, stat } from 'node:fs/promises'\nimport path from 'node:path'\nimport { extractAll, languageForFile } from './extract.js'\nimport { scanRepo, DEFAULT_EXCLUDED_DIRS } from './scan.js'\nimport {\n cacheKeyForRoot,\n defaultCachePath,\n legacyCachePath,\n loadIndex,\n saveIndex,\n} from './store.js'\nimport type { IndexOptions, IndexedFile, RepoIndex } from './types.js'\n\n/**\n * Full build: scan + extract every supported file. Existing per-file\n * extraction is reused when `previous` holds an identical mtime for the\n * same rel path (incremental refresh), so touched files alone re-parse.\n */\nexport async function buildIndex(\n root: string,\n options: IndexOptions = {},\n previous: RepoIndex | null = null,\n): Promise<RepoIndex> {\n const scanned = await scanRepo(root, options)\n\n // Index stale/current state from the previous run by rel path.\n const prevByPath = new Map((previous?.files ?? []).map((f) => [f.path, f]))\n const prevMtime = new Map((previous?.files ?? []).map((f) => [f.path, f.mtimeMs]))\n\n const files: IndexedFile[] = []\n const BATCH = 8\n for (let i = 0; i < scanned.length; i += BATCH) {\n const batch = scanned.slice(i, i + BATCH)\n const rows = await Promise.all(\n batch.map(async (f) => {\n const lang = languageForFile(f.abs)\n if (!lang) return null\n if (prevMtime.get(f.rel) === f.mtimeMs) {\n const cached = prevByPath.get(f.rel)!\n return {\n ...cached,\n mtimeMs: f.mtimeMs,\n symbols: cached.symbols.map((symbol) => ({ ...symbol, file: f.rel })),\n }\n }\n let code: string\n try {\n code = await readFile(f.abs, 'utf8')\n } catch {\n return null\n }\n const { symbols, imports } = await extractAll(code, lang)\n // Backfill the repo-relative path: the extractor is file-agnostic and\n // leaves SymbolInfo.file empty, but search/render depend on it.\n return {\n path: f.rel,\n lang,\n mtimeMs: f.mtimeMs,\n symbols: symbols.map((s) => ({ ...s, file: f.rel })),\n imports,\n } satisfies IndexedFile\n }),\n )\n for (const f of rows) {\n if (f) files.push(f)\n }\n }\n\n return {\n root,\n generatedAt: Date.now(),\n files,\n excludedDirs: [...DEFAULT_EXCLUDED_DIRS, ...(options.excludeDirs ?? [])],\n }\n}\n\n/** Convenience wrapper: evolve an on-disk cache if `root` exists. */\nexport async function buildIndexWithCache(\n root: string,\n options: IndexOptions = {},\n cacheDir?: string,\n): Promise<RepoIndex> {\n const cachePath = defaultCachePath(root, cacheDir)\n let prev = await loadIndex(cachePath)\n let loadedLegacy = false\n if (!prev) {\n const oldPath = legacyCachePath(root, cacheDir)\n if (oldPath !== cachePath) {\n prev = await loadIndex(oldPath)\n loadedLegacy = prev !== null\n }\n }\n const reusable = prev && cacheKeyForRoot(prev.root) === cacheKeyForRoot(root) ? prev : null\n const fresh = await buildIndex(root, options, reusable)\n if (prev && indexesEqual(prev, fresh)) {\n if (loadedLegacy) await saveIndex(cachePath, fresh)\n return loadedLegacy ? fresh : prev\n }\n await saveIndex(cachePath, fresh)\n return fresh\n}\n\nfunction indexesEqual(left: RepoIndex, right: RepoIndex): boolean {\n if (cacheKeyForRoot(left.root) !== cacheKeyForRoot(right.root)) return false\n if (left.files.length !== right.files.length) return false\n if (left.excludedDirs.length !== right.excludedDirs.length) return false\n if (left.excludedDirs.some((value, index) => value !== right.excludedDirs[index])) return false\n\n return left.files.every((file, fileIndex) => {\n const other = right.files[fileIndex]\n if (!other || file.path !== other.path || file.lang !== other.lang || file.mtimeMs !== other.mtimeMs) {\n return false\n }\n if (file.symbols.length !== other.symbols.length) return false\n return file.symbols.every((symbol, symbolIndex) => {\n const candidate = other.symbols[symbolIndex]\n return candidate !== undefined\n && symbol.name === candidate.name\n && symbol.kind === candidate.kind\n && symbol.file === candidate.file\n && symbol.line === candidate.line\n && symbol.endLine === candidate.endLine\n && symbol.exported === candidate.exported\n && symbol.signature === candidate.signature\n })\n })\n}\n\n/**\n * Locate the git repo root for a path by walking up to the nearest `.git`\n * directory. Bounded walk (max `maxLevels` levels); returns `null` when no\n * repo marker is found — callers must NOT index an untagged directory\n * (the fs root is the classic footgun: indexing `C:\\` by accident).\n */\nexport async function findRepoRoot(startDir: string, maxLevels = 12): Promise<string | null> {\n let dir = path.resolve(startDir)\n for (let level = 0; level < maxLevels; level++) {\n try {\n // stat() accepts BOTH a `.git` directory (normal repos) and a `.git`\n // file (worktrees / submodules) — readFile only handled the file form.\n await stat(path.join(dir, '.git'))\n return dir\n } catch {\n // not a repo here — keep walking\n }\n const parent = path.dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n return null\n}\n","/**\n * tree-sitter symbol extraction.\n *\n * Parses a source text with a per-language grammar (web-tree-sitter WASM,\n * static grammars from tree-sitter-wasms) and returns flat SymbolInfo rows.\n *\n * NOTE on the dependency pin: web-tree-sitter must stay at ^0.20.x — newer\n * releases expect dylinked grammar wasm, while tree-sitter-wasms ships\n * static builds. Verified working pair: web-tree-sitter@0.20.8 + tree-sitter-wasms@0.1.13.\n */\n\nimport { readFile } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport { type Node, Language, Parser, Query } from 'web-tree-sitter'\nimport type { SymbolInfo, SymbolKind } from './types.js'\n\nconst require = createRequire(import.meta.url)\n// web-tree-sitter ≥0.25 is ESM with named exports; 0.20.x was CJS\n// `export = Parser`. tsup externalizes the dep, so the import shape must\n// match the published ESM entry.\n\nexport type LanguageId =\n | 'typescript'\n | 'javascript'\n | 'python'\n | 'go'\n | 'rust'\n | 'java'\n | 'cpp'\n | 'c'\n\nconst WASM_DIR = path.dirname(require.resolve('tree-sitter-wasms/out/tree-sitter-typescript.wasm'))\n\nconst GRAMMAR_NAMES: Record<LanguageId, string> = {\n typescript: 'tree-sitter-typescript',\n javascript: 'tree-sitter-javascript',\n python: 'tree-sitter-python',\n go: 'tree-sitter-go',\n rust: 'tree-sitter-rust',\n java: 'tree-sitter-java',\n cpp: 'tree-sitter-cpp',\n c: 'tree-sitter-c',\n}\n\nconst EXT_TO_LANG: Record<string, LanguageId> = {\n '.ts': 'typescript',\n '.tsx': 'typescript',\n '.mts': 'typescript',\n '.cts': 'typescript',\n '.js': 'javascript',\n '.jsx': 'javascript',\n '.mjs': 'javascript',\n '.cjs': 'javascript',\n '.py': 'python',\n '.pyi': 'python',\n '.go': 'go',\n '.rs': 'rust',\n '.java': 'java',\n '.cpp': 'cpp',\n '.cc': 'cpp',\n '.cxx': 'cpp',\n '.c++': 'cpp',\n '.hpp': 'cpp',\n '.hxx': 'cpp',\n '.hh': 'cpp',\n '.h': 'cpp',\n '.ipp': 'cpp',\n '.tpp': 'cpp',\n '.inl': 'cpp',\n '.c': 'c',\n}\n\nexport function languageForFile(filePath: string): LanguageId | null {\n const ext = path.extname(filePath).toLowerCase()\n return EXT_TO_LANG[ext] ?? null\n}\n\n/** Kind of a capture per query. */\ntype CaptureDef = { kind: SymbolKind; exported?: boolean }\n\n// Query definitions per language. Captures bind to the DECLARATION node\n// (capture OUTSIDE the pattern: `(function_declaration) @function` — the\n// 0.20 query parser rejects a capture sitting directly after the node type\n// inside the parens). Names/signatures/export come from node fields.\nconst QUERIES: Record<LanguageId, string> = {\n typescript: `\n (function_declaration) @function\n (generator_function_declaration) @function\n (method_definition) @method\n (class_declaration) @class\n (interface_declaration) @interface\n (type_alias_declaration) @type\n (enum_declaration) @enum\n (variable_declarator) @variable\n (public_field_definition) @field\n (abstract_class_declaration) @class\n `,\n // JS shares the same query surface; type-related patterns simply never match.\n javascript: `\n (function_declaration) @function\n (generator_function_declaration) @function\n (method_definition) @method\n (class_declaration) @class\n (variable_declarator) @variable\n `,\n python: `\n (function_definition) @function\n (class_definition) @class\n `,\n go: `\n (function_declaration) @function\n (method_declaration) @method\n (type_spec) @type\n `,\n rust: `\n (function_item) @function\n (function_signature_item) @method\n (struct_item) @class\n (enum_item) @enum\n (trait_item) @interface\n `,\n java: `\n (class_declaration) @class\n (interface_declaration) @interface\n (enum_declaration) @enum\n (method_declaration) @method\n (constructor_declaration) @method\n `,\n // C++ grammar (superset of C — also serves .h headers). function names\n // live inside the declarator chain, so patterns stay broad and names,\n // kinds and signatures are resolved in code (cppNameOf / kindFor /\n // signatureFor). Top-level `declaration` covers prototypes, constructors\n // and globals; field_declaration covers class-body members.\n cpp: `\n (function_definition) @function\n (class_specifier body: (_)) @class\n (struct_specifier body: (_)) @class\n (enum_specifier body: (_)) @enum\n (type_definition) @type\n (alias_declaration) @type\n (namespace_definition) @module\n (field_declaration) @field\n (declaration) @variable\n `,\n // Plain C: same core nodes minus C++-only namespace/alias forms.\n c: `\n (function_definition) @function\n (struct_specifier body: (_)) @class\n (enum_specifier body: (_)) @enum\n (type_definition) @type\n (field_declaration) @field\n (declaration) @variable\n `,\n}\n\nconst CAPTURE_KINDS: Record<string, CaptureDef> = {\n function: { kind: 'function' },\n method: { kind: 'method' },\n class: { kind: 'class' },\n interface: { kind: 'interface' },\n type: { kind: 'type' },\n enum: { kind: 'enum' },\n variable: { kind: 'variable' },\n field: { kind: 'field' },\n module: { kind: 'module' },\n}\n\n// Import statements per language. Whole statements are captured; the module\n// specifier is read from node fields in code (string quoting, dotted vs\n// relative vs URL-style syntax differ per grammar). CommonJS require() is out\n// of scope — the ES import surface covers the modern plugin ecosystem.\nconst IMPORT_QUERIES: Record<LanguageId, string> = {\n typescript: `\n (import_statement) @import\n (export_statement) @reexport\n `,\n javascript: `\n (import_statement) @import\n (export_statement) @reexport\n `,\n python: `\n (import_from_statement) @import\n (import_statement) @import\n `,\n go: `\n (import_spec) @import\n `,\n rust: `\n (use_declaration) @use\n `,\n java: `\n (import_declaration) @import\n `,\n // C/C++: #include \"local.hpp\" — quoted form resolves in-repo; <system>\n // captures too but yields null and is dropped by the extractor.\n cpp: `\n (preproc_include) @import\n `,\n c: `\n (preproc_include) @import\n `,\n}\n\nlet parserPromise: Promise<Parser> | null = null\n\nasync function getParser(): Promise<Parser> {\n if (!parserPromise) {\n parserPromise = (async () => {\n const wasm = path.join(path.dirname(require.resolve('web-tree-sitter')), 'tree-sitter.wasm')\n await Parser.init({ locateFile: () => wasm })\n const p = new Parser()\n return p\n })()\n }\n return parserPromise\n}\n\nconst languageCache = new Map<LanguageId, Promise<Language>>()\n\nfunction getLanguage(id: LanguageId): Promise<Language> {\n let entry = languageCache.get(id)\n if (!entry) {\n entry = getParser().then(async () => {\n const grammarPath = path.join(WASM_DIR, `${GRAMMAR_NAMES[id]}.wasm`)\n const bytes = await readFile(grammarPath)\n const lang = await Language.load(bytes)\n // Precompile queries per language to catch authoring errors early.\n // (0.25 deprecates lang.query(); use the Query constructor.)\n new Query(lang, QUERIES[id]).delete()\n new Query(lang, IMPORT_QUERIES[id]).delete()\n return lang\n })\n languageCache.set(id, entry)\n }\n return entry\n}\n\n/**\n * Extract symbols and raw import specifiers from source text of the given\n * language, from a single parse. Returns rows ordered by file line. Never\n * throws for parse errors — a failed parse yields empty lists (the caller\n * logs and continues).\n */\nexport interface ExtractedFile {\n symbols: SymbolInfo[]\n /** Raw module specifiers, e.g. './util', 'node:fs', './utils' (py). */\n imports: string[]\n}\n\nexport async function extractAll(code: string, id: LanguageId): Promise<ExtractedFile> {\n const lang = await getLanguage(id)\n const parser = await getParser()\n parser.setLanguage(lang)\n const tree = parser.parse(code)\n if (!tree) throw new Error(`tree-sitter parse returned null for a ${id} source`)\n try {\n const symbols: SymbolInfo[] = []\n const symbolQuery = new Query(lang, QUERIES[id])\n try {\n const captures = symbolQuery.captures(tree.rootNode)\n for (const cap of captures) {\n const def = CAPTURE_KINDS[cap.name]\n if (!def) continue\n const node = cap.node\n // tree-sitter queries match at any depth; without this check the\n // variable_declarator pattern would also capture function-body\n // locals — pure index noise.\n if (def.kind === 'variable' && !isModuleLevelVariable(id, node)) continue\n const name = nameOf(id, node)\n if (!name) continue\n symbols.push({\n name,\n kind: kindFor(id, node, def.kind),\n file: '', // set by the caller (extractor is file-agnostic)\n line: node.startPosition.row + 1,\n endLine: node.endPosition.row + 1,\n exported: isExported(id, node),\n signature: signatureFor(id, node),\n })\n }\n } finally {\n symbolQuery.delete()\n }\n\n const imports: string[] = []\n const importQuery = new Query(lang, IMPORT_QUERIES[id])\n try {\n for (const cap of importQuery.captures(tree.rootNode)) {\n const spec = specifierOf(id, cap.node)\n if (spec) imports.push(spec)\n }\n } finally {\n importQuery.delete()\n }\n\n symbols.sort((a, b) => a.line - b.line)\n const seen = new Set<string>()\n const deduped = symbols.filter((s) => {\n const key = `${s.kind}|${s.name}|${s.line}`\n if (seen.has(key)) return false\n seen.add(key)\n return true\n })\n return { symbols: deduped, imports }\n } finally {\n tree.delete()\n }\n}\n\n/** Symbols only — see extractAll for the combined parse. */\nexport async function extractSymbols(code: string, id: LanguageId): Promise<SymbolInfo[]> {\n return (await extractAll(code, id)).symbols\n}\n\n/** The declared name of a declaration node — language-aware dispatch. */\nfunction nameOf(id: LanguageId, node: Node): string {\n if (id === 'cpp' || id === 'c') return cppNameOf(node)\n const field = node.childForFieldName?.('name')\n if (field) return field.text.trim()\n // e.g. an anonymous default export — skip those.\n return ''\n}\n\n/**\n * C/C++ declaration names hide in the declarator chain:\n * - function_definition → declarator:function_declarator → identifier |\n * qualified_identifier (A::b) | field_identifier (in-class method)\n * - class/struct/enum specifiers and alias_declaration carry `name`\n * - type_definition → declarator:type_identifier\n * Returns '' for anonymous declarations (skipped upstream).\n */\nfunction cppNameOf(node: Node): string {\n const nameField = node.childForFieldName?.('name')\n if (nameField) return nameField.text.trim()\n if (node.type === 'type_definition') {\n return node.childForFieldName?.('declarator')?.text.trim() ?? ''\n }\n let declarator = node.childForFieldName?.('declarator')\n if (declarator?.type === 'init_declarator') {\n declarator = declarator.childForFieldName?.('declarator')\n }\n if (declarator) {\n let current: Node | null = declarator\n while (current) {\n if (\n current.type === 'function_declarator' ||\n current.type === 'pointer_declarator' ||\n current.type === 'array_declarator' ||\n current.type === 'reference_declarator' ||\n current.type === 'parenthesized_declarator' ||\n current.type === 'init_declarator'\n ) {\n current = current.childForFieldName?.('declarator') ?? current.namedChildren[0] ?? null\n continue\n }\n if (current.type === 'qualified_identifier') {\n // app::main_entry — the bare name is the last segment.\n return current.childForFieldName?.('name')?.text.trim() ?? current.text.trim()\n }\n if (\n current.type === 'identifier' ||\n current.type === 'field_identifier' ||\n current.type === 'type_identifier'\n ) {\n return current.text.trim()\n }\n break\n }\n }\n return ''\n}\n\n/** Read the raw module specifier out of a captured import statement. */\nfunction specifierOf(id: LanguageId, node: Node): string | null {\n switch (node.type) {\n case 'import_statement':\n // ts/js: `import … from './x'` (source); python: `import a.b` (name)\n if (id === 'python') return dottedToPath(node.childForFieldName('name')?.text)\n return stripQuotes(node.childForFieldName('source')?.text)\n case 'export_statement':\n // ts/js re-export: `export … from './x'`; plain exports have no source\n return stripQuotes(node.childForFieldName('source')?.text)\n case 'import_from_statement': {\n // python: `from .utils import x` / `from mypkg.core import Thing`\n const raw = node.childForFieldName('module_name')?.text\n if (raw == null) return null\n return raw.startsWith('.') ? pythonRelative(raw) : dottedToPath(raw)\n }\n case 'import_spec': // go: `import \"example.com/foo/util\"`\n return stripQuotes(node.childForFieldName('path')?.text)\n case 'use_declaration': // rust: `use crate::a::b::{c, d}`\n return rustUsePath(node.childForFieldName('argument')?.text)\n case 'import_declaration': // java: `import com.example.Thing;`\n return dottedToPath(node.namedChildren[0]?.text)\n case 'preproc_include':\n // path field: string_literal for \"x.hpp\" (in-repo, resolvable) or\n // system_lib_string for <cstdio> (stripQuotes yields null → dropped).\n return stripQuotes(node.childForFieldName('path')?.text)\n default:\n return null\n }\n}\n\n/**\n * Rust use paths: 'crate::a::b' is root-relative, 'super::x' is parent-module\n * ('../x'), 'self::x' is current ('./x'); everything else (std, external\n * crates) stays root-relative and simply won't resolve in-repo. Use-lists\n * ('a::b::{c,d}') contribute their base path.\n */\nfunction rustUsePath(text: string | undefined | null): string | null {\n if (!text) return null\n const base = text.split('{')[0].trim()\n if (!base) return null\n const parts = base.split('::').filter(Boolean)\n if (parts[0] === 'crate') parts.shift()\n return parts.map((p) => (p === 'super' ? '..' : p === 'self' ? '.' : p)).join('/')\n}\n\nfunction stripQuotes(text: string | undefined | null): string | null {\n if (text == null || text.length < 2) return null\n const first = text[0]\n const last = text[text.length - 1]\n if ((first === \"'\" && last === \"'\") || (first === '\"' && last === '\"')) {\n return text.slice(1, -1)\n }\n return null\n}\n\n/** 'os.path' → 'os/path' — dotted module path to repo-relative-ish path. */\nfunction dottedToPath(text: string | undefined | null): string | null {\n if (!text) return null\n return text.trim().replace(/\\./g, '/')\n}\n\n/** '.utils' → './utils', '..pkg.mod' → '../pkg/mod' — python relative import. */\nfunction pythonRelative(raw: string): string {\n const dots = raw.match(/^\\.+/)?.[0].length ?? 0\n const rest = raw.slice(dots).replace(/\\./g, '/')\n const prefix = dots === 1 ? './' : '../'.repeat(dots - 1)\n return prefix + rest\n}\n\n/** Best-effort declaration signature: `name` + parameter list, if any. */\nfunction signatureFor(id: LanguageId, node: Node): string {\n const sig = rawSignature(id, node)\n return sig.length > 80 ? `${sig.slice(0, 77)}...` : sig\n}\n\nfunction rawSignature(id: LanguageId, node: Node): string {\n const name = nameOf(id, node)\n // Field first (go method_declaration has both a receiver and a parameters\n // parameter_list — the field picks the right one), type fallback otherwise.\n const params =\n node.childForFieldName?.('parameters') ??\n node.namedChildren.find(\n (c) =>\n c?.type === 'formal_parameters' ||\n c?.type === 'method_parameters' ||\n c?.type === 'parameters' ||\n c?.type === 'parameter_list',\n )\n if (params) {\n return `${name}${collapseSpace(params.text)}`\n }\n if (id === 'cpp' || id === 'c') {\n // C/C++: the parameter list hides inside the declarator chain.\n let current = node.childForFieldName?.('declarator')\n while (current) {\n if (current.type === 'function_declarator') {\n const list = current.childForFieldName?.('parameters')\n return list ? `${name}${collapseSpace(list.text)}` : name\n }\n current = current.childForFieldName?.('declarator') ?? current.namedChildren[0] ?? null\n if (current && !current.type.includes('declarator') && current.type !== 'identifier' && current.type !== 'qualified_identifier' && current.type !== 'field_identifier') break\n }\n }\n const first = node.namedChildren[0]\n return first ? collapseSpace(first.text) : name\n}\n\n/**\n * Signatures must stay one line: repo-map rows are budgeted per line, and a\n * multi-line parameter list would both wrap the format and burn chars.\n */\nfunction collapseSpace(text: string): string {\n return text\n .replace(/\\s+/g, ' ')\n .replace(/\\( /g, '(')\n .replace(/ \\)/g, ')')\n .replace(/ ,/g, ',')\n .replace(/,\\)/g, ')')\n .trim()\n}\n\n/**\n * A variable is indexable only at module level: its declaration must sit\n * directly inside the program (or the export_statement wrapping it). Anything\n * deeper — function bodies, blocks, for-of heads — is a local with no\n * navigation value.\n */\nfunction isModuleLevelVariable(id: LanguageId, node: Node): boolean {\n if (id === 'cpp' || id === 'c') {\n // A (field_)declaration is indexable at namespace/file scope or in a\n // class body — anything inside a function body is a local.\n for (let parent = node.parent; parent; parent = parent.parent) {\n if (parent.type === 'function_definition' || parent.type === 'compound_statement') return false\n if (parent.type === 'translation_unit' || parent.type === 'declaration_list' || parent.type === 'field_declaration_list') return true\n }\n return false\n }\n const declaration = node.parent // variable_declaration | lexical_declaration\n const container = declaration?.parent // program | export_statement | …\n return container?.type === 'program' || container?.type === 'export_statement'\n}\n\n/**\n * A symbol is exported when walking up from it reaches an export_statement\n * before any function or class body — only module-level declarations count.\n * A method inside `export class` must NOT inherit the class's export, and a\n * function nested inside an exported function is not exported either.\n */\nfunction isExported(id: LanguageId, node: Node): boolean {\n if (id === 'python') {\n // Python has no export syntax: a def/class directly under the module is\n // importable, anything nested (methods, inner functions) is not a\n // module-level symbol.\n return node.parent?.type === 'module'\n }\n if (id === 'go') {\n // Go exports by capitalisation, not by keyword.\n const name = nameOf(id, node)\n return !!name && /^[A-Z]/.test(name)\n }\n if (id === 'rust') {\n // `pub` = exported; `pub(crate)` & friends are crate-local, not public.\n return node.namedChildren.some(\n (c) => c?.type === 'visibility_modifier' && c.text === 'pub',\n )\n }\n if (id === 'java') {\n // Interface members are implicitly public.\n if (node.parent?.type === 'interface_body') return true\n return node.namedChildren.some((c) => c?.type === 'modifiers' && /\\bpublic\\b/.test(c?.text ?? ''))\n }\n if (id === 'cpp' || id === 'c') {\n return cppIsExported(node)\n }\n for (let parent = node.parent; parent; parent = parent.parent) {\n if (parent.type === 'export_statement') return true\n if (parent.type === 'statement_block' || parent.type === 'class_body') return false\n if (parent.type === 'program') return false\n }\n return false\n}\n\n/**\n * C/C++ export semantics:\n * - anything under `public:` in a class body is exported; `private:`/\n * `protected:` sections are not\n * - top-level declarations are exported unless marked `static`\n * (internal-linkage). C has no access sections; C++ namespaces stay\n * transparent — a symbol under namespace X is still module-level.\n */\nfunction cppIsExported(node: Node): boolean {\n const isStatic = node.namedChildren.some(\n (c) => c?.type === 'storage_class_specifier' && c.text === 'static',\n )\n for (let parent = node.parent; parent; parent = parent.parent) {\n if (parent.type === 'field_declaration_list') {\n const body = parent\n // Struct members default to public, class members to private (C++ rule).\n const structKind = body.parent?.type === 'struct_specifier' ? 'struct' : 'class'\n let current: string | null = structKind === 'struct' ? 'public' : 'private'\n for (let up: Node | null = node; up && up.id !== body.id; up = up.parent) {\n const next = up.parent\n if (next?.type === 'field_declaration_list') {\n const siblingIndex = next.namedChildren.findIndex((c) => c?.id === up.id)\n const accessBefore = next.namedChildren\n .slice(0, siblingIndex)\n .filter((c) => c?.type === 'access_specifier')\n if (accessBefore.length > 0) {\n current = accessBefore.at(-1)!.text.replace(':', '').trim()\n }\n }\n }\n return current === 'public' && !isStatic\n }\n if (parent.type === 'translation_unit') return !isStatic\n }\n return !isStatic\n}\n\n/**\n * Language quirks the query syntax can't express:\n * - python: no distinct method node — a def directly in a class body is one\n * - go: a type_spec is a class (struct) or interface by its type child\n * - rust: function_item directly in an impl body is a method\n */\nfunction kindFor(id: LanguageId, node: Node, kind: SymbolKind): SymbolKind {\n if (id === 'python' && kind === 'function') {\n const inClassBody = node.parent?.type === 'block' && node.parent?.parent?.type === 'class_definition'\n if (inClassBody) return 'method'\n }\n if (id === 'go' && node.type === 'type_spec') {\n const type = node.childForFieldName('type')?.type\n if (type === 'struct_type') return 'class'\n if (type === 'interface_type') return 'interface'\n return 'type'\n }\n if (id === 'rust' && node.type === 'function_item') {\n const inImpl = node.parent?.type === 'declaration_list' && node.parent?.parent?.type === 'impl_item'\n if (inImpl) return 'method'\n }\n if ((id === 'cpp' || id === 'c') && (kind === 'variable' || kind === 'field')) {\n // A declaration whose declarator chain holds a function_declarator is a\n // function prototype/definition — not a data member or global.\n let current = node.childForFieldName?.('declarator')\n while (current) {\n if (current.type === 'function_declarator') {\n return node.parent?.type === 'field_declaration_list' ? 'method' : 'function'\n }\n current = current.childForFieldName?.('declarator') ?? current.namedChildren[0] ?? null\n }\n return node.type === 'field_declaration' ? 'field' : 'variable'\n }\n if ((id === 'cpp' || id === 'c') && node.type === 'function_definition') {\n // A function defined directly in a class/struct body is a method.\n if (node.parent?.type === 'field_declaration_list') return 'method'\n }\n return kind\n}\n\n/** Compile a symbol query for a code sample (used by tests). */\nexport async function parseFileToSymbols(\n filePath: string,\n repoRoot: string,\n code?: string,\n): Promise<SymbolInfo[]> {\n const lang = languageForFile(filePath)\n if (!lang) return []\n const text = code ?? (await readFile(filePath, 'utf8'))\n const rows = await extractSymbols(text, lang)\n const rel = path.relative(repoRoot, filePath).split(path.sep).join('/')\n return rows.map((r) => ({ ...r, file: rel }))\n}","/** Workspace file discovery with dir exclusions and mtime tracking. */\n\nimport { readdir, stat } from 'node:fs/promises'\nimport path from 'node:path'\nimport type { IndexOptions } from './types.js'\n\nexport const DEFAULT_EXCLUDED_DIRS = [\n 'node_modules',\n '.git',\n '.idea',\n '.vscode',\n 'dist',\n 'build',\n 'out',\n 'coverage',\n '.next',\n '.nuxt',\n '.cache',\n 'target',\n 'vendor',\n '.dsh-code-index',\n]\n\n/** Language-agnostic source extensions we index. */\nexport const SUPPORTED_EXTS = new Set([\n '.ts',\n '.tsx',\n '.mts',\n '.cts',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.py',\n '.pyi',\n '.go',\n '.rs',\n '.java',\n '.cpp',\n '.cc',\n '.cxx',\n '.c++',\n '.hpp',\n '.hxx',\n '.hh',\n '.h',\n '.ipp',\n '.tpp',\n '.inl',\n '.c',\n])\n\nexport interface ScannedFile {\n /** Absolute path on disk. */\n abs: string\n /** Repo-relative path (forward-slash). */\n rel: string\n mtimeMs: number\n}\n\n/**\n * Recursively walk `root` and return indexed files, skipping excluded dir\n * components at any depth. Uses readdir with `withFileTypes` so we never\n * stat every entry; mtime comes from a targeted stat per candidate file.\n */\nexport async function scanRepo(\n root: string,\n options: IndexOptions = {},\n): Promise<ScannedFile[]> {\n const excluded = new Set([...DEFAULT_EXCLUDED_DIRS, ...(options.excludeDirs ?? [])])\n const results: ScannedFile[] = []\n const queue: Array<[string, string]> = [[root, '']] // [absDir, relDir]\n\n while (queue.length) {\n const [absDir, relDir] = queue.pop()!\n let entries\n try {\n entries = await readdir(absDir, { withFileTypes: true })\n } catch {\n continue // unreadable dir: skip into the void\n }\n for (const entry of entries) {\n const abs = path.join(absDir, entry.name)\n const rel = relDir ? `${relDir}/${entry.name}` : entry.name\n if (entry.isDirectory()) {\n if (excluded.has(entry.name)) continue\n queue.push([abs, rel])\n } else if (entry.isFile() && SUPPORTED_EXTS.has(path.extname(entry.name).toLowerCase())) {\n try {\n const st = await stat(abs)\n results.push({ abs, rel, mtimeMs: st.mtimeMs })\n } catch {\n // race: file deleted mid-scan — ignore\n }\n }\n }\n }\n\n results.sort((a, b) => a.rel.localeCompare(b.rel))\n return results\n}","/** On-disk JSON cache for RepoIndex, keyed by repo root. */\n\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { createHash, randomUUID } from 'node:crypto'\nimport type { RepoIndex } from './types.js'\n\nexport const CACHE_DIR_NAME = '.dsh-code-index'\n\n/** Stable root identity for cache hashing, including Windows case aliases. */\nexport function cacheKeyForRoot(root: string): string {\n const resolved = path.resolve(root)\n return process.platform === 'win32' ? resolved.toLowerCase() : resolved\n}\n\n/** Default cache location: `<repoRoot>/.dsh-code-index/index.json`. */\nexport function defaultCachePath(root: string, cacheDir?: string): string {\n const dir = cacheDir ?? path.join(root, CACHE_DIR_NAME)\n const hash = createHash('sha1').update(cacheKeyForRoot(root)).digest('hex').slice(0, 12)\n return path.join(dir, `${hash}.json`)\n}\n\n/** Cache filename used before Windows root casing was canonicalized. */\nexport function legacyCachePath(root: string, cacheDir?: string): string {\n const dir = cacheDir ?? path.join(root, CACHE_DIR_NAME)\n const hash = createHash('sha1').update(root).digest('hex').slice(0, 12)\n return path.join(dir, `${hash}.json`)\n}\n\nexport async function loadIndex(cachePath: string): Promise<RepoIndex | null> {\n try {\n const raw = await readFile(cachePath, 'utf8')\n const parsed = JSON.parse(raw) as RepoIndex\n if (!parsed || typeof parsed.root !== 'string' || !Array.isArray(parsed.files)) return null\n return healSymbolFiles(parsed)\n } catch {\n return null // missing or corrupt cache == no cache\n }\n}\n\n/**\n * Self-heal caches written before the per-symbol file backfill (the\n * \"SymbolInfo.file is always set\" invariant). Empty `file` fields are derived\n * from the containing IndexedFile path on load, so any consumer of loadIndex\n * sees consistent rows — and the next save persists the healed form.\n */\nexport function healSymbolFiles(index: RepoIndex): RepoIndex {\n for (const file of index.files) {\n for (const symbol of file.symbols) {\n if (!symbol.file) symbol.file = file.path\n }\n }\n return index\n}\n\nexport async function saveIndex(cachePath: string, index: RepoIndex): Promise<void> {\n await mkdir(path.dirname(cachePath), { recursive: true })\n const temporaryPath = `${cachePath}.${process.pid}.${randomUUID()}.tmp`\n try {\n await writeFile(temporaryPath, JSON.stringify(index, null, 2), { encoding: 'utf8', flag: 'wx' })\n await rename(temporaryPath, cachePath)\n } finally {\n await rm(temporaryPath, { force: true })\n }\n}\n","/** Shared data model for dsh-code-index. */\n\nexport type SymbolKind =\n | 'function'\n | 'method'\n | 'class'\n | 'interface'\n | 'type'\n | 'enum'\n | 'variable'\n | 'field'\n | 'import'\n | 'module'\n\nexport interface SymbolInfo {\n /** Symbol name (identifier / type id / member name). */\n name: string\n kind: SymbolKind\n /** Repo-relative path of the containing file, always forward-slash. */\n file: string\n /** 1-based start line. */\n line: number\n /** 1-based end line (inclusive). */\n endLine: number\n /** True when the symbol is exported at module level (export .. / export default). */\n exported: boolean\n /** Short human-readable signature, e.g. `greet(name: string)` — empty when n/a. */\n signature: string\n}\n\nexport interface IndexedFile {\n /** Repo-relative path (forward-slash). */\n path: string\n lang: string\n /** fs mtime of the source file at index time (milliseconds). */\n mtimeMs: number\n symbols: SymbolInfo[]\n /** Raw import specifiers found in this file ('./util', 'mypkg/core', …).\n * Optional because caches written before reference ranking lack it. */\n imports?: string[]\n}\n\nexport interface RepoIndex {\n /** Absolute source root this index describes. */\n root: string\n /** Unix ms when the index was generated. */\n generatedAt: number\n files: IndexedFile[]\n excludedDirs: string[]\n}\n\nexport function symbolCount(index: RepoIndex): number {\n return index.files.reduce((n, f) => n + f.symbols.length, 0)\n}\n\n/** Optional config consumed by the index build pipeline. */\nexport interface IndexOptions {\n /** Extra directories to exclude, appended to the defaults. */\n excludeDirs?: string[]\n /** Minimum mtime delta (ms) that forces a re-extract in refresh mode. */\n staleToleranceMs?: number\n}","/** Pure search / filter logic over a RepoIndex — unit-testable, no IO. */\n\nimport type { RepoIndex, SymbolInfo, SymbolKind } from './types.js'\n\nexport interface SymbolFilter {\n query?: string\n file?: string\n kind?: SymbolKind\n exportedOnly?: boolean\n}\n\nexport interface RankedHit extends SymbolInfo {\n /** 0..1 relevance. 1 = exact name match; lower = fuzzier. */\n score: number\n}\n\nfunction matchScore(name: string, query: string): number {\n const q = query.toLowerCase()\n const n = name.toLowerCase()\n if (n === q) return 1\n if (n.startsWith(q)) return 0.8\n if (n.includes(q)) return 0.5\n if (q.length >= 3 && isSubsequence(q, n)) return 0.3\n return 0\n}\n\n/**\n * Every query char appears in the name, in order — 'cfgldr' finds\n * 'configLoader'. Gated to 3+ chars so short queries don't match everything.\n */\nfunction isSubsequence(query: string, name: string): boolean {\n let i = 0\n for (const ch of name) {\n if (ch === query[i]) i++\n if (i === query.length) return true\n }\n return false\n}\n\n/** Filter symbols by file/kind/export, then score by name against query (if any). */\nexport function searchSymbols(\n index: RepoIndex,\n filter: SymbolFilter,\n limit = 50,\n): RankedHit[] {\n const q = (filter.query ?? '').trim()\n const filePat = filter.file?.trim().toLowerCase()\n const hits: RankedHit[] = []\n\n for (const file of index.files) {\n if (filePat && !file.path.toLowerCase().includes(filePat)) continue\n for (const sym of file.symbols) {\n if (filter.kind && sym.kind !== filter.kind) continue\n if (filter.exportedOnly && !sym.exported) continue\n const score = q ? matchScore(sym.name, q) : 0.4\n if (q && score === 0) continue\n hits.push({ ...sym, score })\n }\n }\n\n // Export boost, then relevance, then stable name order.\n hits.sort((a, b) => {\n const ab =\n Number(b.exported) - Number(a.exported) ||\n b.score - a.score ||\n a.name.localeCompare(b.name) ||\n a.file.localeCompare(b.file)\n return ab\n })\n return hits.slice(0, limit)\n}\n\n/** Neat one-line rendering of a hit for terminal/chat output. */\nexport function renderHit(hit: RankedHit): string {\n const exportMark = hit.exported ? 'export ' : ''\n const sig = hit.signature || hit.name\n const score = hit.score < 1 ? ` [${hit.score.toFixed(2)}]` : ''\n return `${exportMark}${hit.kind} ${sig}${score} — ${hit.file}:${hit.line}`\n}","/**\n * Bounded, ranked repo map generation — the \"aider-style\" overview the model\n * can turn to before browsing files. Pure logic, no IO.\n */\n\nimport path from 'node:path'\nimport { SUPPORTED_EXTS } from './scan.js'\nimport type { IndexedFile, RepoIndex, SymbolInfo, SymbolKind } from './types.js'\n\n/** Higher-weight symbols pull their file up the map. */\nexport const KIND_WEIGHT: Record<SymbolKind, number> = {\n class: 1.0,\n interface: 1.0,\n type: 0.8,\n function: 0.8,\n method: 0.7,\n enum: 0.9,\n variable: 0.25,\n field: 0.2,\n import: 0.05,\n module: 0.3,\n}\n\nexport interface RepoMapOptions {\n /** Max files to include (default 24). */\n topFiles?: number\n /** Max symbols per file (default 18). */\n symbolsPerFile?: number\n /** Hard cap on rendered characters (default 3200). */\n maxChars?: number\n}\n\nexport interface RepoMapEntry {\n path: string\n score: number\n symbols: Array<{ name: string; kind: SymbolKind; line: number; signature: string }>\n}\n\n/**\n * Paths that look like tests. A map led by test files reads as noise: tests\n * reference an API, they rarely explain it — and symbol density actively\n * favours them (many small functions), so they need an explicit damper.\n */\nconst TEST_PATH_RE =\n /(^|\\/)(tests?|__tests__)(\\/|$)|\\.(?:spec|test)\\.[cm]?[jt]sx?$|(^|\\/)(?:test_[^/]+\\.py|[^/]+_test\\.(?:py|go))$/\n\n/** Density-aware file score: weighted symbols minus bloat; test paths damped. */\nexport function scoreFile(file: IndexedFile): number {\n let score = 0\n for (const sym of file.symbols) {\n score += KIND_WEIGHT[sym.kind] ?? 0.3\n if (sym.exported) score += 0.3\n }\n score /= 1 + file.symbols.length * 0.04\n if (TEST_PATH_RE.test(file.path)) score *= 0.2\n return score\n}\n\n/** Rank files, take the top slice, cap per-file symbols. */\nexport function rankRepoMap(index: RepoIndex, options: RepoMapOptions = {}): RepoMapEntry[] {\n const topFiles = options.topFiles ?? 24\n const perFile = options.symbolsPerFile ?? 18\n const density = new Map<string, number>()\n for (const f of index.files) {\n if (f.symbols.length > 0) density.set(f.path, scoreFile(f))\n }\n const centrality = referencePageRank(index.files, density)\n const ranked = index.files\n .filter((f) => f.symbols.length > 0)\n .map((f) => ({\n file: f,\n score: (density.get(f.path) ?? 0) + REF_WEIGHT * (centrality.get(f.path) ?? 0),\n }))\n .sort((a, b) => b.score - a.score || a.file.path.localeCompare(b.file.path))\n .slice(0, topFiles)\n\n return ranked.map(({ file, score }) => ({\n path: file.path,\n score,\n symbols: file.symbols.slice(0, perFile).map((s) => ({\n name: s.name,\n kind: s.kind,\n line: s.line,\n signature: s.signature,\n })),\n }))\n}\n\n/** How much one unit of reference centrality is worth in map score. */\nconst REF_WEIGHT = 0.5\n\n/**\n * Count in-repo references: how many OTHER indexed files import each file.\n * Self-imports don't count; each (importer, target) pair counts once.\n */\nexport function countReferences(files: IndexedFile[]): Map<string, number> {\n const fileSet = new Set(files.map((f) => f.path))\n const counts = new Map<string, number>()\n for (const file of files) {\n const targets = new Set<string>()\n for (const spec of file.imports ?? []) {\n const target = resolveImport(spec, file.path, fileSet)\n if (target && target !== file.path) targets.add(target)\n }\n for (const target of targets) {\n counts.set(target, (counts.get(target) ?? 0) + 1)\n }\n }\n return counts\n}\n\n/** PageRank damping — the standard 0.85. */\nconst DAMPING = 0.85\n/** Stop the power iteration once total rank movement drops below this. */\nconst CONVERGENCE_TOL = 1e-6\n/** Hard cap; convergence on repo-sized graphs needs ~20-40. */\nconst MAX_ITERATIONS = 100\n\n/**\n * Personalized PageRank over the import graph: nodes are files, an edge\n * A → B means A imports B, so rank flows toward heavily-imported hubs — and\n * a hub that other hubs themselves import accumulates more than a merely\n * popular leaf, which flat in-degree counting cannot see.\n *\n * Teleport uses each file's density share instead of a uniform vector, so\n * sparsely-linked repos keep their 0.2.x ordering (with no edges the fixed\n * point IS the normalized density vector). Returns L1-normalized importance\n * mass per file.\n */\nfunction referencePageRank(files: IndexedFile[], density: Map<string, number>): Map<string, number> {\n const nodes = files.filter((f) => density.has(f.path)).map((f) => f.path)\n const n = nodes.length\n if (n === 0) return new Map()\n if (n === 1) return new Map([[nodes[0]!, 1]])\n\n const totalDensity = nodes.reduce((sum, p) => sum + (density.get(p) ?? 0), 0)\n const teleport = nodes.map((p) => (density.get(p) ?? 0) / totalDensity)\n\n const fileSet = new Set(nodes)\n const out = new Map<string, Set<string>>()\n for (const file of files) {\n if (!fileSet.has(file.path)) continue\n const targets = out.get(file.path) ?? new Set<string>()\n for (const spec of file.imports ?? []) {\n const target = resolveImport(spec, file.path, fileSet)\n if (target && target !== file.path) targets.add(target)\n }\n out.set(file.path, targets)\n }\n\n const incoming = new Map<string, Array<[fromIdx: number, weight: number]>>()\n for (const [from, targets] of out) {\n if (targets.size === 0) continue\n const weight = 1 / targets.size\n const fromIdx = nodes.indexOf(from)\n for (const target of targets) {\n const list = incoming.get(target) ?? []\n list.push([fromIdx, weight])\n incoming.set(target, list)\n }\n }\n\n let rank = teleport.slice()\n for (let iter = 0; iter < MAX_ITERATIONS; iter++) {\n let danglingMass = 0\n for (let i = 0; i < n; i++) {\n if (!out.get(nodes[i]!)?.size) danglingMass += rank[i]!\n }\n const next = new Array<number>(n)\n let delta = 0\n for (let i = 0; i < n; i++) {\n let flow = 0\n for (const [fromIdx, weight] of incoming.get(nodes[i]!) ?? []) {\n flow += rank[fromIdx]! * weight\n }\n next[i] = (1 - DAMPING) * teleport[i]! + DAMPING * (flow + danglingMass * teleport[i]!)\n delta += Math.abs(next[i]! - rank[i]!)\n }\n rank = next\n if (delta < CONVERGENCE_TOL) break\n }\n\n const result = new Map<string, number>()\n for (let i = 0; i < n; i++) result.set(nodes[i]!, rank[i]!)\n return result\n}\n\n/**\n * Resolve a raw import specifier against the indexed file set; returns the\n * repo-relative target path or null. Relative specifiers resolve against the\n * importing file; absolute ones against the repo root — with a suffix\n * fallback so Go module paths and Java package names match their in-repo\n * location without reading go.mod / package-info.\n */\nexport function resolveImport(spec: string, fromPath: string, fileSet: Set<string>): string | null {\n if (!spec) return null\n if (spec.startsWith('./') || spec.startsWith('../')) {\n const base = path.posix.normalize(path.posix.join(path.posix.dirname(fromPath), spec))\n return candidateFor(base, fileSet)\n }\n const segments = spec.split('/').filter(Boolean)\n for (let skip = 0; skip < segments.length; skip++) {\n const hit = candidateFor(segments.slice(skip).join('/'), fileSet)\n if (hit) return hit\n }\n return null\n}\n\n/** Extension and index-file candidates for a specifier base path. */\nfunction candidateFor(base: string, fileSet: Set<string>): string | null {\n // Bare path first: C/C++ includes already carry their extension\n // (\"net/socket.hpp\"), and matching the exact file beats guessing.\n if (fileSet.has(base)) return base\n for (const ext of SUPPORTED_EXTS) {\n if (fileSet.has(`${base}${ext}`)) return `${base}${ext}`\n }\n for (const ext of SUPPORTED_EXTS) {\n if (fileSet.has(`${base}/index${ext}`)) return `${base}/index${ext}`\n if (fileSet.has(`${base}/__init__${ext}`)) return `${base}/__init__${ext}`\n }\n return null\n}\n\n/** Render the ranked map as markdown, hard-truncated to maxChars. */\nexport function renderRepoMap(entries: RepoMapEntry[], options: RepoMapOptions = {}): string {\n const maxChars = options.maxChars ?? 3200\n const lines: string[] = ['# repo map']\n let totalSymbols = 0\n for (const e of entries) {\n totalSymbols += e.symbols.length\n lines.push(`## ${e.path} (${e.symbols.length})`)\n for (const s of e.symbols) {\n const label = s.signature || s.name\n lines.push(` ${s.kind} ${label} :${s.line}`)\n }\n }\n let text = lines.join('\\n')\n if (text.length > maxChars) {\n text = `${text.slice(0, maxChars)}\\n… truncated`\n }\n return totalSymbols > 0 ? text : ''\n}","/** Model-visible tools: code_index, code_symbols, code_search. */\n\nimport { defineTool, type JsonValue } from '@deepseek-ai/dsh-tools'\nimport path from 'node:path'\nimport { buildIndexWithCache, findRepoRoot } from './buildIndex.js'\nimport { renderHit, searchSymbols } from './search.js'\nimport { rankRepoMap, renderRepoMap } from './repomap.js'\nimport { getConfig, indexOptions } from './config.js'\nimport { cacheKeyForRoot } from './store.js'\nimport type { RepoIndex } from './types.js'\n\n/** Minimal structural types for the harness surfaces we touch. */\ninterface ToolCwdContext {\n agent?: {\n session?: { header?: { cwd?: string } }\n }\n}\ntype ToolRunExec = ToolCwdContext & { signal?: AbortSignal }\n\ninterface TextBlock {\n type: 'text'\n text: string\n}\n\nexport interface IndexCache {\n get(root: string, force?: boolean): Promise<RepoIndex>\n invalidate(): void\n}\n\n/** Retain completed indexes briefly while still coalescing concurrent refreshes. */\nexport function createIndexCache(\n load: (root: string) => Promise<RepoIndex>,\n ttlMs = 60_000,\n now: () => number = Date.now,\n): IndexCache {\n interface CompletedEntry {\n index: RepoIndex\n at: number\n epoch: number\n }\n interface LoadEntry {\n promise: Promise<RepoIndex>\n epoch: number\n }\n\n const completed = new Map<string, CompletedEntry>()\n const inFlight = new Map<string, LoadEntry>()\n let epoch = 0\n\n function startLoad(key: string, root: string, previous?: Promise<RepoIndex>): Promise<RepoIndex> {\n const loadEpoch = epoch\n const begin = previous\n ? previous.catch(() => undefined).then(() => load(root))\n : load(root)\n const promise = begin\n .then((index) => {\n if (loadEpoch === epoch) {\n completed.delete(key)\n completed.set(key, { index, at: now(), epoch: loadEpoch })\n while (completed.size > 16) completed.delete(completed.keys().next().value!)\n }\n return index\n })\n .finally(() => {\n if (inFlight.get(key)?.promise === promise) inFlight.delete(key)\n })\n inFlight.set(key, { promise, epoch: loadEpoch })\n return promise\n }\n\n return {\n get(root, force = false) {\n const resolvedRoot = path.resolve(root)\n const key = cacheKeyForRoot(resolvedRoot)\n const running = inFlight.get(key)\n if (running) {\n if (running.epoch === epoch && !force) return running.promise\n return startLoad(key, resolvedRoot, running.promise)\n }\n\n if (!force) {\n const cached = completed.get(key)\n if (cached && cached.epoch === epoch && now() - cached.at < ttlMs) {\n completed.delete(key)\n completed.set(key, cached)\n return Promise.resolve(cached.index)\n }\n if (cached) completed.delete(key)\n } else {\n completed.delete(key)\n }\n return startLoad(key, resolvedRoot)\n },\n invalidate() {\n epoch++\n completed.clear()\n },\n }\n}\n\nconst indexCache = createIndexCache((root) => buildIndexWithCache(root, indexOptions()))\n\nexport function getIndex(root: string, force = false): Promise<RepoIndex> {\n return indexCache.get(root, force)\n}\n\nexport function invalidateIndexCache(): void {\n indexCache.invalidate()\n}\n\nasync function resolveRoot(arg: string | undefined, exec: ToolRunExec): Promise<string> {\n const cwd = exec.agent?.session?.header?.cwd\n const base = arg ?? cwd ?? process.cwd()\n const root = await findRepoRoot(base)\n if (!root) throw new Error(`no git repository found from ${path.resolve(base)}`)\n return root\n}\n\nexport const tools = [\n defineTool({\n name: 'code_index',\n description:\n 'Manage the semantic repo index: status or (re)build it for the current workspace. Auto-builds the first time it is queried. Returns file/symbol counts and the index location.',\n parameters: {\n action: {\n type: 'string',\n enum: ['status', 'build'],\n description: '\"status\" (default) reports without forcing a rebuild; \"build\" forces a fresh scan.',\n },\n repoRoot: {\n type: 'string',\n description:\n 'Optional absolute repo path. Defaults to the workspace root of the current session.',\n },\n },\n output: {\n schema: { type: 'string' },\n render: (_args, value: string): TextBlock[] => [{ type: 'text', text: value }],\n },\n async execute(args: { action?: string; repoRoot?: string }, exec: ToolRunExec): Promise<string> {\n try {\n const root = await resolveRoot(args.repoRoot, exec)\n const index = await getIndex(root, args.action === 'build')\n const total = index.files.reduce((n, f) => n + f.symbols.length, 0)\n const langs = new Set(index.files.map((f) => f.lang))\n return [\n `repo: ${root}`,\n `files indexed: ${index.files.length}`,\n `symbols: ${total}`,\n `languages: ${[...langs].join(', ')}`,\n index.files.length === 0 ? 'no supported files found' : 'index up to date',\n ].join('\\n')\n } catch (error) {\n return `code_index: ${(error as Error).message ?? String(error)}`\n }\n },\n }),\n\n defineTool({\n name: 'code_symbols',\n description:\n 'List symbols (functions, classes, interfaces, types, methods, variables) in the current repo. Filter by name substring, file path substring, or symbol kind. Results are exported-first, alphabetically ordered.',\n parameters: {\n query: {\n type: 'string',\n description: 'Substring of the symbol name to match (case-insensitive). Omit to list all.',\n },\n file: {\n type: 'string',\n description: 'Substring of the repo-relative file path to match, e.g. \"src/core\".',\n },\n kind: {\n type: 'string',\n enum: ['function', 'method', 'class', 'interface', 'type', 'enum', 'variable', 'field'],\n description: 'Only return symbols of this kind.',\n },\n exportedOnly: {\n type: 'boolean',\n description: 'Only exported (module-level public) symbols.',\n },\n limit: {\n type: 'number',\n description: 'Max rows (default 50).',\n },\n repoRoot: {\n type: 'string',\n description: 'Optional absolute repo path; defaults to the session workspace root.',\n },\n },\n output: {\n schema: { type: 'array' },\n render: (_args, value: JsonValue[]): TextBlock[] =>\n value.map((v) => ({\n type: 'text' as const,\n text: renderHit(v as unknown as Parameters<typeof renderHit>[0]),\n })),\n },\n async execute(\n args: {\n query?: string\n file?: string\n kind?: 'function' | 'method' | 'class' | 'interface' | 'type' | 'enum' | 'variable' | 'field'\n exportedOnly?: boolean\n limit?: number\n repoRoot?: string\n },\n exec: ToolRunExec,\n ): Promise<JsonValue[]> {\n const root = await resolveRoot(args.repoRoot, exec)\n const index = await getIndex(root)\n const hits = searchSymbols(\n index,\n { query: args.query, file: args.file, kind: args.kind, exportedOnly: args.exportedOnly },\n args.limit ?? 50,\n )\n return hits as unknown as JsonValue[]\n },\n }),\n\n defineTool({\n name: 'code_search',\n description:\n 'Ranked symbol search over the repo index: exact > prefix > substring name matches, exported symbols first. Results carry a relevance score and file:line, so the model can locate definitions quickly.',\n parameters: {\n query: {\n type: 'string',\n required: true,\n description: 'Symbol name (or fragment) to find.',\n },\n limit: {\n type: 'number',\n description: 'Max hits (default 20).',\n },\n repoRoot: {\n type: 'string',\n description: 'Optional absolute repo path; defaults to the session workspace root.',\n },\n },\n output: {\n schema: { type: 'array' },\n render: (_args, value: JsonValue[]): TextBlock[] =>\n value.map((v) => ({\n type: 'text' as const,\n text: renderHit(v as unknown as Parameters<typeof renderHit>[0]),\n })),\n },\n async execute(\n args: { query: string; limit?: number; repoRoot?: string },\n exec: ToolRunExec,\n ): Promise<JsonValue[]> {\n const root = await resolveRoot(args.repoRoot, exec)\n const index = await getIndex(root)\n const hits = searchSymbols(index, { query: args.query }, args.limit ?? 20)\n return hits as unknown as JsonValue[]\n },\n }),\n\n defineTool({\n name: 'code_map',\n description:\n 'Return a bounded, ranked map of the current repo (top files by symbol density, with their key symbols and lines). The model can call this once per session to build an internal model of the codebase before browsing files.',\n parameters: {\n repoRoot: {\n type: 'string',\n description: 'Optional absolute repo path; defaults to the session workspace root.',\n },\n },\n output: {\n schema: { type: 'string' },\n render: (_args, value: string): TextBlock[] => [{ type: 'text', text: value }],\n },\n async execute(args: { repoRoot?: string }, exec: ToolRunExec): Promise<string> {\n try {\n const root = await resolveRoot(args.repoRoot, exec)\n const index = await getIndex(root)\n const cfg = getConfig()\n const map = renderRepoMap(\n rankRepoMap(index, { topFiles: cfg.mapTopFiles }),\n { maxChars: cfg.mapMaxChars },\n )\n if (!map) return 'no indexable symbols found in this repo'\n return map\n } catch (error) {\n return `code_index: ${(error as Error).message ?? String(error)}`\n }\n },\n }),\n]\n","/** Plugin configuration: merged once at apply time, read wherever needed. */\n\nimport type { IndexOptions } from './types.js'\n\nexport interface PluginConfig {\n /** Extra directories to exclude from indexing (appended to defaults). */\n excludeDirs?: string[]\n /** Max files in the ranked repo map (code_map / auto section). */\n mapTopFiles?: number\n /** Hard char cap for rendered maps. */\n mapMaxChars?: number\n /** Refresh interval for the auto-injected map (ms, min 1000). */\n mapTtlMs?: number\n /** Set false to disable the auto-injected system section. */\n autoInject?: boolean\n}\n\ninterface EffectiveConfig {\n excludeDirs: string[]\n mapTopFiles: number\n mapMaxChars: number\n mapTtlMs: number\n autoInject: boolean\n}\n\nconst DEFAULTS: EffectiveConfig = {\n excludeDirs: [],\n mapTopFiles: 24,\n mapMaxChars: 3200,\n mapTtlMs: 60_000,\n autoInject: true,\n}\n\nconst state: { current: EffectiveConfig } = { current: { ...DEFAULTS } }\n\n/** Merge a plugin-provided partial config over the defaults (idempotent). */\nexport function applyConfig(partial?: PluginConfig): void {\n state.current = {\n ...DEFAULTS,\n ...(partial ?? {}),\n excludeDirs: [...DEFAULTS.excludeDirs, ...(partial?.excludeDirs ?? [])],\n }\n // Coerce obviously wrong inputs.\n if (!Number.isFinite(state.current.mapTopFiles) || state.current.mapTopFiles < 1) {\n state.current.mapTopFiles = DEFAULTS.mapTopFiles\n }\n if (!Number.isFinite(state.current.mapMaxChars) || state.current.mapMaxChars < 200) {\n state.current.mapMaxChars = DEFAULTS.mapMaxChars\n }\n if (!Number.isFinite(state.current.mapTtlMs) || state.current.mapTtlMs < 1_000) {\n state.current.mapTtlMs = DEFAULTS.mapTtlMs\n }\n}\n\nexport function getConfig(): Readonly<EffectiveConfig> {\n return state.current\n}\n\n/** Map the effective config onto the index pipeline options. */\nexport function indexOptions(): IndexOptions {\n return { excludeDirs: state.current.excludeDirs }\n}","/**\n * dsh-code-index — DeepSeek Harness bundle entry.\n *\n * Registers four model-visible tools (code_index / code_symbols /\n * code_search / code_map) backed by a tree-sitter symbol index, and\n * injects a bounded auto-updating repo map for the default workspace\n * into the system prompt.\n */\n\ntype Disposer = void | (() => void)\n\n/** Minimal structural Context; the real @deepseek-ai/cordis type is a\n * runtime dependency we intentionally do not import in the bundle entry. */\ninterface MinimalContext {\n effect(fn: () => Disposer): void\n tools: { register(t: unknown): () => void }\n systemPrompt: {\n section(section: {\n name: string\n order: number\n text: string | ((context: unknown) => string)\n }): () => void\n }\n}\n\nexport const name = 'dsh-code-index'\n\n// Public API surface (consumable by other bundles / tests).\nexport { buildIndex, buildIndexWithCache, findRepoRoot } from './buildIndex.js'\nexport { extractSymbols, extractAll, languageForFile, parseFileToSymbols } from './extract.js'\nexport { scanRepo, DEFAULT_EXCLUDED_DIRS, SUPPORTED_EXTS } from './scan.js'\nexport { loadIndex, saveIndex, defaultCachePath, CACHE_DIR_NAME } from './store.js'\nexport { symbolCount } from './types.js'\nexport type { RepoIndex, IndexedFile, SymbolInfo, SymbolKind, IndexOptions } from './types.js'\nexport { searchSymbols, renderHit } from './search.js'\nexport { rankRepoMap, renderRepoMap, scoreFile } from './repomap.js'\nexport { tools } from './tools.js'\n\nimport { getIndex, invalidateIndexCache, tools } from './tools.js'\nimport { findRepoRoot } from './buildIndex.js'\nimport { rankRepoMap, renderRepoMap } from './repomap.js'\nimport { symbolCount } from './types.js'\nimport { applyConfig, getConfig, type PluginConfig } from './config.js'\n\nexport const inject = ['tools', 'systemPrompt'] as const\n\nexport function apply(ctx: MinimalContext, pluginConfig?: PluginConfig) {\n applyConfig(pluginConfig)\n invalidateIndexCache()\n ctx.effect(() => {\n const disposers: Array<() => void> = []\n console.log('[dsh-code-index] plugin loaded')\n for (const tool of tools) {\n disposers.push(ctx.tools.register(tool))\n console.log(`[dsh-code-index] registered tool: ${tool.name}`)\n }\n\n // Auto-inject a bounded repo map for the DEFAULT workspace (the dsh\n // launch directory, per the harness docs). Multi-workspace web sessions\n // should rely on the `code_map` tool, which resolves the per-session cwd.\n let cached: { root: string; at: number; text: string } | null = null\n\n async function warmMap(): Promise<void> {\n const now = Date.now()\n const cfg = getConfig()\n try {\n const root = await findRepoRoot(process.cwd())\n if (!root) {\n cached = { root: '', at: now, text: '' }\n return\n }\n const index = await getIndex(root)\n const text = renderRepoMap(\n rankRepoMap(index, { topFiles: cfg.mapTopFiles }),\n { maxChars: cfg.mapMaxChars },\n )\n const stats = symbolCount(index)\n cached = {\n root: index.root,\n at: Date.now(),\n text: text ? `${text}\\n\\n(summary: ${index.files.length} files, ${stats} symbols)` : '',\n }\n } catch {\n cached = { root: '', at: now, text: '' } // never let injection fail the boot\n }\n }\n\n // Warm eagerly at load so the first assembly already has the map.\n void warmMap()\n\n if (getConfig().autoInject) {\n disposers.push(ctx.systemPrompt.section({\n name: 'code-index:repo-map',\n order: 60, // before tool guidance (100–199), after persona (0)\n text: () => {\n const now = Date.now()\n if (cached && now - cached.at < getConfig().mapTtlMs) return cached.text\n void warmMap()\n return cached?.text ?? ''\n },\n }))\n }\n\n return () => {\n const errors: unknown[] = []\n for (const dispose of disposers.reverse()) {\n try {\n dispose()\n } catch (error) {\n errors.push(error)\n }\n }\n console.log('[dsh-code-index] plugin unloaded')\n if (errors.length > 0) throw new AggregateError(errors, 'failed to unload dsh-code-index')\n }\n })\n}\n"],"mappings":"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","names":["readFile","stat","path","require","name","path","readFile","path","readFile","path","stat","name","path","path","path","path"]}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "dsh-code-index",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Semantic repo index for DeepSeek Harness — tree-sitter symbol index, code search, and a bounded ranked repo map injected into agent context.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "files": [
9
9
  "dist",
10
+ "scripts",
10
11
  "cordis.patch.yml"
11
12
  ],
12
13
  "scripts": {
@@ -32,9 +33,16 @@
32
33
  }
33
34
  },
34
35
  "peerDependencies": {
35
- "@deepseek-ai/cordis": "^4.0.1"
36
+ "@deepseek-ai/cordis": "^4.0.1",
37
+ "@deepseek-ai/dsh-tools": ">=0.1.0-rc.1 <0.2.0-0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@deepseek-ai/cordis": {
41
+ "optional": true
42
+ }
36
43
  },
37
44
  "devDependencies": {
45
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.8",
38
46
  "@types/node": "^26.2.0",
39
47
  "tsup": "^8.5.0",
40
48
  "typescript": "^6.0.3",
@@ -53,8 +61,7 @@
53
61
  "url": "https://github.com/lemonxiny55/dsh-code-index/issues"
54
62
  },
55
63
  "dependencies": {
56
- "@deepseek-ai/dsh-tools": "0.1.0-rc.8",
57
64
  "tree-sitter-wasms": "^0.1.13",
58
- "web-tree-sitter": "^0.20.8"
65
+ "web-tree-sitter": "^0.25.10"
59
66
  }
60
67
  }
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * fix-wsl-links — repair pnpm symlinks broken by WSL→Windows installs.
4
+ *
5
+ * Running `pnpm install` from WSL against a checkout on /mnt/c lays down
6
+ * Linux-style symlinks that Windows cannot traverse (EACCES / "cannot find
7
+ * package"). This script walks node_modules, finds every such dead link,
8
+ * and re-points it at its real target inside .pnpm as a Windows junction.
9
+ *
10
+ * Run from Windows-side Node (the dsh-bundled node.exe works):
11
+ *
12
+ * node.exe scripts\fix-wsl-links.mjs [root]
13
+ *
14
+ * `root` defaults to the repo this script lives in. Pass a dsh profile
15
+ * directory (…\.dsh\profiles\web) to fix a plugin install instead.
16
+ */
17
+
18
+ import { existsSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, lstatSync } from 'node:fs'
19
+ import path from 'node:path'
20
+ import { fileURLToPath } from 'node:url'
21
+
22
+ const defaultRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
23
+ const root = path.resolve(process.argv[2] ?? defaultRoot)
24
+ const nm = path.join(root, 'node_modules')
25
+ const pnpm = path.join(nm, '.pnpm')
26
+
27
+ if (!existsSync(nm)) {
28
+ console.error(`no node_modules under ${root} — run pnpm install first`)
29
+ process.exit(1)
30
+ }
31
+ if (process.platform === 'win32' && !existsSync(pnpm)) {
32
+ console.error(`no .pnpm store under ${nm} — nothing to re-link against`)
33
+ process.exit(1)
34
+ }
35
+
36
+ let fixed = 0
37
+ let checked = 0
38
+
39
+ /** True for a link entry whose target Windows Node cannot resolve. */
40
+ function broken(link) {
41
+ try {
42
+ realpathSync(link)
43
+ return false
44
+ } catch {
45
+ return true
46
+ }
47
+ }
48
+
49
+ /** Replace `link` with a junction to `target`; report and count. */
50
+ function junction(link, target) {
51
+ rmSync(link, { force: true, recursive: true })
52
+ try {
53
+ symlinkSync(target, link, 'junction')
54
+ } catch {
55
+ try {
56
+ rmSync(link, { force: true, recursive: true })
57
+ } catch {}
58
+ console.error(` FAIL ${rel(link)} → ${target}`)
59
+ return
60
+ }
61
+ fixed++
62
+ console.log(` fixed ${rel(link)}`)
63
+ }
64
+
65
+ function rel(p) {
66
+ return path.relative(root, p) || p
67
+ }
68
+
69
+ /** Resolve the canonical store dir for a package: .pnpm/<+name>@<ver>/node_modules</name>. */
70
+ function storeDir(pkgName, versionHint) {
71
+ const storeName = pkgName.replace('/', '+')
72
+ if (versionHint) {
73
+ const direct = path.join(pnpm, `${storeName}@${versionHint}`)
74
+ if (existsSync(direct)) return direct
75
+ }
76
+ const prefix = `${storeName}@`
77
+ const hit = readdirSync(pnpm).find(
78
+ (d) => d.startsWith(prefix) && existsSync(path.join(pnpm, d, 'node_modules', pkgName)),
79
+ )
80
+ return hit ? path.join(pnpm, hit) : null
81
+ }
82
+
83
+ /** Fix every dead scoped/unscoped link directly under one node_modules. */
84
+ function fixLevel(levelNm, scope = null) {
85
+ for (const entry of readdirSync(levelNm)) {
86
+ const link = path.join(levelNm, entry)
87
+ let st
88
+ try {
89
+ st = lstatSync(link)
90
+ } catch {
91
+ continue
92
+ }
93
+ if (!st.isSymbolicLink()) {
94
+ if (st.isDirectory() && entry.startsWith('@')) fixLevel(link, entry)
95
+ continue
96
+ }
97
+ checked++
98
+ if (!broken(link)) continue
99
+
100
+ const target = readlinkSync(link)
101
+ const pkgName = scope ? `${scope}/${entry}` : entry
102
+ const storeName = pkgName.replace('/', '+')
103
+
104
+ // pnpm layout: <link> → .pnpm/<pkg>@<ver[_peer]>…/node_modules/<pkg>.
105
+ // Re-derive that dir; the old target string is Linux-shaped and useless.
106
+ const base = target.split('node_modules/')[0]?.split('/').pop() ?? ''
107
+ const versionHint = base.startsWith(`${storeName}@`) ? base : null
108
+ const store = storeDir(pkgName, versionHint)
109
+ let resolved = store ? path.join(store, 'node_modules', pkgName) : null
110
+
111
+ // Scoped aliases link a nested path (e.g. @standard-schema/spec lives in
112
+ // the @standard-schema+spec store); fall back to matching by the target's
113
+ // node_modules tail — or, when the target names no node_modules at all
114
+ // (dangling junk), by the package name itself.
115
+ if (!resolved) {
116
+ const tail = target.includes('node_modules/')
117
+ ? target.split('node_modules/').pop()
118
+ : pkgName
119
+ for (const dir of readdirSync(pnpm)) {
120
+ const candidate = path.join(pnpm, dir, 'node_modules', tail)
121
+ if (existsSync(candidate)) {
122
+ resolved = candidate
123
+ break
124
+ }
125
+ }
126
+ }
127
+
128
+ if (resolved && existsSync(resolved)) {
129
+ junction(link, resolved)
130
+ continue
131
+ }
132
+
133
+ // Non-store link (workspace `link:` deps, profile installs): re-point at
134
+ // the original Windows path when that still exists.
135
+ const asWin = target.replaceAll('/', '\\')
136
+ if (existsSync(asWin)) {
137
+ junction(link, asWin)
138
+ continue
139
+ }
140
+ console.error(` no target for ${rel(link)} (${target}) — remove or reinstall`)
141
+ }
142
+ }
143
+
144
+ if (existsSync(pnpm)) {
145
+ for (const dir of readdirSync(pnpm)) {
146
+ const levelNm = path.join(pnpm, dir, 'node_modules')
147
+ if (existsSync(levelNm)) fixLevel(levelNm)
148
+ }
149
+ }
150
+ fixLevel(nm)
151
+
152
+ console.log(`\n${root}`)
153
+ console.log(`links checked: ${checked}, fixed: ${fixed}`)
154
+ process.exit(fixed > 0 ? 0 : 0)