@wrongstack/tools 0.7.5 → 0.7.7

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.
Files changed (48) hide show
  1. package/dist/audit.js +3 -3
  2. package/dist/audit.js.map +1 -1
  3. package/dist/bash.js +7 -6
  4. package/dist/bash.js.map +1 -1
  5. package/dist/builtin.js +2070 -133
  6. package/dist/builtin.js.map +1 -1
  7. package/dist/codebase-index/index.d.ts +120 -0
  8. package/dist/codebase-index/index.js +1974 -0
  9. package/dist/codebase-index/index.js.map +1 -0
  10. package/dist/codebase-stats-tool-BLhQmPNc.d.ts +158 -0
  11. package/dist/diff.js +4 -4
  12. package/dist/document.js +2 -2
  13. package/dist/edit.js +2 -2
  14. package/dist/exec.js +5 -5
  15. package/dist/exec.js.map +1 -1
  16. package/dist/fetch.js +4 -3
  17. package/dist/fetch.js.map +1 -1
  18. package/dist/format.js +3 -3
  19. package/dist/format.js.map +1 -1
  20. package/dist/git.js +3 -3
  21. package/dist/glob.js +2 -2
  22. package/dist/grep.js +3 -3
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +2039 -102
  25. package/dist/index.js.map +1 -1
  26. package/dist/install.js +3 -3
  27. package/dist/install.js.map +1 -1
  28. package/dist/json.js +1 -1
  29. package/dist/lint.js +3 -3
  30. package/dist/lint.js.map +1 -1
  31. package/dist/logs.js +5 -5
  32. package/dist/logs.js.map +1 -1
  33. package/dist/outdated.js +3 -3
  34. package/dist/pack.js +2070 -133
  35. package/dist/pack.js.map +1 -1
  36. package/dist/patch.js +4 -4
  37. package/dist/process-registry.js +1 -1
  38. package/dist/read.js +2 -2
  39. package/dist/replace.js +4 -4
  40. package/dist/replace.js.map +1 -1
  41. package/dist/scaffold.js +2 -2
  42. package/dist/test.js +3 -3
  43. package/dist/test.js.map +1 -1
  44. package/dist/tree.js +2 -2
  45. package/dist/typecheck.js +3 -3
  46. package/dist/typecheck.js.map +1 -1
  47. package/dist/write.js +2 -2
  48. package/package.json +8 -4
package/dist/index.js CHANGED
@@ -1,15 +1,23 @@
1
- import * as fs4 from 'fs/promises';
2
- import { stat } from 'fs/promises';
3
- import * as path from 'path';
4
- import { dirname } from 'path';
1
+ import * as fs4 from 'node:fs/promises';
2
+ import { stat } from 'node:fs/promises';
3
+ import * as path from 'node:path';
4
+ import { dirname } from 'node:path';
5
5
  import { atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, buildChildEnv, stripAnsi, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan } from '@wrongstack/core';
6
- import { spawn } from 'child_process';
7
- import * as os from 'os';
8
- import * as dns from 'dns/promises';
9
- import * as net from 'net';
10
- import { statSync } from 'fs';
6
+ import { spawn, execSync, spawnSync } from 'node:child_process';
7
+ import * as os from 'node:os';
8
+ import * as dns from 'node:dns/promises';
9
+ import * as net from 'node:net';
10
+ import * as fs13 from 'node:fs';
11
+ import { statSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
12
+ import { DatabaseSync } from 'node:sqlite';
13
+ import * as ts from 'typescript';
11
14
 
12
- // src/read.ts
15
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
16
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
17
+ }) : x)(function(x) {
18
+ if (typeof require !== "undefined") return require.apply(this, arguments);
19
+ throw Error('Dynamic require of "' + x + '" is not supported');
20
+ });
13
21
  function resolvePath(input, ctx) {
14
22
  return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
15
23
  }
@@ -63,17 +71,17 @@ var readTool = {
63
71
  async execute(input, ctx) {
64
72
  if (!input?.path) throw new Error("read: path is required");
65
73
  const absPath = safeResolve(input.path, ctx);
66
- let stat10;
74
+ let stat11;
67
75
  try {
68
- stat10 = await fs4.stat(absPath);
76
+ stat11 = await fs4.stat(absPath);
69
77
  } catch (err) {
70
78
  const code = err.code;
71
79
  if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
72
80
  throw new Error(`read: failed to stat "${input.path}": ${err instanceof Error ? err.message : String(err)}`);
73
81
  }
74
- if (!stat10.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
75
- if (stat10.size > MAX_BYTES) {
76
- throw new Error(`read: file too large (${stat10.size} bytes, limit ${MAX_BYTES})`);
82
+ if (!stat11.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
83
+ if (stat11.size > MAX_BYTES) {
84
+ throw new Error(`read: file too large (${stat11.size} bytes, limit ${MAX_BYTES})`);
77
85
  }
78
86
  const buf = await fs4.readFile(absPath);
79
87
  if (isBinaryBuffer(buf)) {
@@ -85,14 +93,14 @@ var readTool = {
85
93
  const offset = Math.max(1, input.offset ?? 1);
86
94
  const limit = Math.max(0, Math.min(input.limit ?? 2e3, 5e3));
87
95
  if (limit === 0) {
88
- ctx.recordRead(absPath, stat10.mtimeMs);
96
+ ctx.recordRead(absPath, stat11.mtimeMs);
89
97
  return { text: "", total_lines: total, encoding: "utf8", truncated: total > 0 };
90
98
  }
91
99
  const slice = allLines.slice(offset - 1, offset - 1 + limit);
92
100
  const truncated = offset - 1 + slice.length < total;
93
101
  const width = String(offset + slice.length - 1).length;
94
102
  const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
95
- ctx.recordRead(absPath, stat10.mtimeMs);
103
+ ctx.recordRead(absPath, stat11.mtimeMs);
96
104
  return {
97
105
  text: numbered,
98
106
  total_lines: total,
@@ -124,12 +132,12 @@ var writeTool = {
124
132
  let existed = false;
125
133
  let prev = "";
126
134
  try {
127
- const stat11 = await fs4.stat(absPath);
128
- existed = stat11.isFile();
135
+ const stat12 = await fs4.stat(absPath);
136
+ existed = stat12.isFile();
129
137
  if (existed) {
130
138
  if (!ctx.hasRead(absPath)) {
131
139
  prev = await fs4.readFile(absPath, "utf8");
132
- ctx.recordRead(absPath, stat11.mtimeMs);
140
+ ctx.recordRead(absPath, stat12.mtimeMs);
133
141
  } else {
134
142
  prev = await fs4.readFile(absPath, "utf8");
135
143
  }
@@ -142,8 +150,8 @@ var writeTool = {
142
150
  await atomicWrite(absPath, input.content);
143
151
  const diff = existed ? unifiedDiff(prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
144
152
  + (new file, ${input.content.split("\n").length} lines)`;
145
- const stat10 = await fs4.stat(absPath);
146
- ctx.recordRead(absPath, stat10.mtimeMs);
153
+ const stat11 = await fs4.stat(absPath);
154
+ ctx.recordRead(absPath, stat11.mtimeMs);
147
155
  ctx.session.recordFileChange({
148
156
  path: absPath,
149
157
  action: existed ? "modified" : "created",
@@ -182,13 +190,13 @@ var editTool = {
182
190
  if (input.new_string === void 0) throw new Error("edit: new_string is required");
183
191
  if (input.old_string === "") throw new Error("edit: old_string cannot be empty");
184
192
  const absPath = safeResolve(input.path, ctx);
185
- const stat10 = await fs4.stat(absPath).catch((err) => {
193
+ const stat11 = await fs4.stat(absPath).catch((err) => {
186
194
  if (err.code === "ENOENT") {
187
195
  throw new Error(`edit: file "${input.path}" does not exist. Use \`write\` instead.`);
188
196
  }
189
197
  throw err;
190
198
  });
191
- if (!stat10.isFile()) throw new Error(`edit: "${input.path}" is not a regular file`);
199
+ if (!stat11.isFile()) throw new Error(`edit: "${input.path}" is not a regular file`);
192
200
  if (!ctx.hasRead(absPath)) {
193
201
  throw new Error(`edit: file "${input.path}" was not read in this session. Read it first.`);
194
202
  }
@@ -380,8 +388,8 @@ var replaceTool = {
380
388
  }
381
389
  const rel = path.relative(ctx.projectRoot, realPath);
382
390
  if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
383
- const stat10 = await fs4.stat(realPath).catch(() => null);
384
- if (!stat10 || !stat10.isFile()) continue;
391
+ const stat11 = await fs4.stat(realPath).catch(() => null);
392
+ if (!stat11 || !stat11.isFile()) continue;
385
393
  let content;
386
394
  try {
387
395
  const buf = await fs4.readFile(realPath);
@@ -406,7 +414,7 @@ var replaceTool = {
406
414
  totalReplacements += count;
407
415
  if (!dryRun) {
408
416
  const newContent = toStyle(newContentLf, style);
409
- await atomicWrite(realPath, newContent, { mode: stat10.mode & 511 });
417
+ await atomicWrite(realPath, newContent, { mode: stat11.mode & 511 });
410
418
  }
411
419
  const diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
412
420
  fromFile: absPath,
@@ -436,15 +444,15 @@ async function resolveFiles(filesInput, ctx, extraGlob) {
436
444
  const resolved = [];
437
445
  for (const p of parts) {
438
446
  const absPath = safeResolve(p, ctx);
439
- const stat10 = await fs4.stat(absPath).catch(() => null);
440
- if (stat10?.isFile()) {
447
+ const stat11 = await fs4.stat(absPath).catch(() => null);
448
+ if (stat11?.isFile()) {
441
449
  resolved.push(absPath);
442
450
  }
443
451
  }
444
452
  return resolved;
445
453
  }
446
454
  async function globFiles(pattern, base, extraGlob) {
447
- const { spawn: spawn11 } = await import('child_process');
455
+ const { spawn: spawn11 } = await import('node:child_process');
448
456
  const rgAvailable = await checkRg();
449
457
  if (rgAvailable) {
450
458
  try {
@@ -456,13 +464,13 @@ async function globFiles(pattern, base, extraGlob) {
456
464
  return await globNative(pattern, base, extraGlob);
457
465
  }
458
466
  function checkRg() {
459
- return new Promise((resolve5) => {
467
+ return new Promise((resolve6) => {
460
468
  try {
461
469
  const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore" });
462
- p.on("error", () => resolve5(false));
463
- p.on("close", (code) => resolve5(code === 0));
470
+ p.on("error", () => resolve6(false));
471
+ p.on("close", (code) => resolve6(code === 0));
464
472
  } catch {
465
- resolve5(false);
473
+ resolve6(false);
466
474
  }
467
475
  });
468
476
  }
@@ -474,10 +482,10 @@ function spawnRgFind(pattern, base) {
474
482
  buf += chunk.toString();
475
483
  });
476
484
  return {
477
- promise: new Promise((resolve5, reject) => {
485
+ promise: new Promise((resolve6, reject) => {
478
486
  child.on("error", reject);
479
487
  child.on("close", () => {
480
- resolve5(buf.split("\n").filter(Boolean));
488
+ resolve6(buf.split("\n").filter(Boolean));
481
489
  });
482
490
  })
483
491
  };
@@ -496,8 +504,8 @@ async function globNative(pattern, base, extraGlob) {
496
504
  if (DEFAULT_IGNORE.includes(e.name)) continue;
497
505
  const full = path.join(dir, e.name);
498
506
  try {
499
- const stat10 = await fs4.lstat(full);
500
- if (stat10.isSymbolicLink()) continue;
507
+ const stat11 = await fs4.lstat(full);
508
+ if (stat11.isSymbolicLink()) continue;
501
509
  } catch {
502
510
  continue;
503
511
  }
@@ -646,13 +654,13 @@ var grepTool = {
646
654
  }
647
655
  };
648
656
  async function detectRg(signal) {
649
- return new Promise((resolve5) => {
657
+ return new Promise((resolve6) => {
650
658
  try {
651
659
  const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore", signal });
652
- p.on("error", () => resolve5(false));
653
- p.on("close", (code) => resolve5(code === 0));
660
+ p.on("error", () => resolve6(false));
661
+ p.on("close", (code) => resolve6(code === 0));
654
662
  } catch {
655
- resolve5(false);
663
+ resolve6(false);
656
664
  }
657
665
  });
658
666
  }
@@ -813,8 +821,8 @@ async function runNative(input, base, mode, limit, signal) {
813
821
  if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;
814
822
  if (globRe) globRe.lastIndex = 0;
815
823
  try {
816
- const stat10 = await fs4.stat(full);
817
- if (stat10.size > 1e6) continue;
824
+ const stat11 = await fs4.stat(full);
825
+ if (stat11.size > 1e6) continue;
818
826
  const head = await fs4.readFile(full);
819
827
  if (isBinaryBuffer(head)) continue;
820
828
  const text = head.toString("utf8");
@@ -1363,10 +1371,10 @@ var bashTool = {
1363
1371
  queue.push(c);
1364
1372
  }
1365
1373
  };
1366
- const next = () => new Promise((resolve5) => {
1374
+ const next = () => new Promise((resolve6) => {
1367
1375
  const c = queue.shift();
1368
- if (c) resolve5(c);
1369
- else resolveNext = resolve5;
1376
+ if (c) resolve6(c);
1377
+ else resolveNext = resolve6;
1370
1378
  });
1371
1379
  let lastFlush = Date.now();
1372
1380
  const flush = () => {
@@ -1493,8 +1501,8 @@ var BLOCKED_ARG_PATTERNS = {
1493
1501
  docker: [/^build$/, /^run$/, /^exec$/, /^push$/, /^pull$/],
1494
1502
  // find -exec/-ok/-execdir execute arbitrary commands
1495
1503
  find: [/^-exec$/, /^-exec;$/, /^-ok$/, /^-ok;$/, /^-execdir$/, /^-execdir;$/, /^-exec=/, /^-ok=/, /^-execdir=/],
1496
- // rm -rf / is catastrophic — block root and home targets
1497
- rm: [/^\/$/, /^\/\*$/, /^~$/],
1504
+ // rm -rf / is catastrophic — block absolute paths, home, and dot-dir targets
1505
+ rm: [/^\//, /^~/, /^\.{1,2}$/],
1498
1506
  // npm run/exec/create/pack/publish can execute arbitrary scripts or publish malware
1499
1507
  npm: [/^run$/, /^exec$/, /^create$/, /^init$/, /^pack$/, /^publish$/, /^deploy$/],
1500
1508
  // pnpm run/dlx/exec/create can execute arbitrary scripts
@@ -1601,7 +1609,7 @@ var execTool = {
1601
1609
  }
1602
1610
  };
1603
1611
  function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1604
- return new Promise((resolve5) => {
1612
+ return new Promise((resolve6) => {
1605
1613
  let stdout = "";
1606
1614
  let stderr = "";
1607
1615
  let killed = false;
@@ -1635,7 +1643,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1635
1643
  const durationMs = Date.now() - startedAt;
1636
1644
  const exitCode = killed ? 124 : code ?? 1;
1637
1645
  registry.afterCall(durationMs, exitCode !== 0);
1638
- resolve5({
1646
+ resolve6({
1639
1647
  command: cmd,
1640
1648
  args,
1641
1649
  stdout: stdout.slice(0, MAX_OUTPUT2),
@@ -1649,7 +1657,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1649
1657
  clearTimeout(timer);
1650
1658
  if (typeof pid === "number") registry.unregister(pid);
1651
1659
  registry.afterCall(Date.now() - startedAt, true);
1652
- resolve5({
1660
+ resolve6({
1653
1661
  command: cmd,
1654
1662
  args,
1655
1663
  stdout: stdout.slice(0, MAX_OUTPUT2),
@@ -2448,8 +2456,8 @@ function findGitDir(cwd, projectRoot) {
2448
2456
  let dir = cwd;
2449
2457
  for (let i = 0; i < 20; i++) {
2450
2458
  try {
2451
- const stat10 = statSync(`${dir}/.git`);
2452
- if (stat10.isDirectory()) return dir;
2459
+ const stat11 = statSync(`${dir}/.git`);
2460
+ if (stat11.isDirectory()) return dir;
2453
2461
  } catch {
2454
2462
  }
2455
2463
  if (dir === root) break;
@@ -2530,7 +2538,7 @@ function buildArgs(input) {
2530
2538
  }
2531
2539
  }
2532
2540
  function runGit(args, cwd, signal) {
2533
- return new Promise((resolve5) => {
2541
+ return new Promise((resolve6) => {
2534
2542
  let stdout = "";
2535
2543
  let stderr = "";
2536
2544
  const child = spawn("git", args, {
@@ -2550,7 +2558,7 @@ function runGit(args, cwd, signal) {
2550
2558
  }
2551
2559
  });
2552
2560
  child.on("error", (err) => {
2553
- resolve5({
2561
+ resolve6({
2554
2562
  command: args[0],
2555
2563
  stdout,
2556
2564
  stderr: err.message,
@@ -2559,7 +2567,7 @@ function runGit(args, cwd, signal) {
2559
2567
  });
2560
2568
  });
2561
2569
  child.on("close", (code) => {
2562
- resolve5({
2570
+ resolve6({
2563
2571
  command: args[0],
2564
2572
  stdout: stdout.slice(0, MAX_OUTPUT3),
2565
2573
  stderr: stderr.slice(0, MAX_OUTPUT3),
@@ -2655,7 +2663,7 @@ function stripPathComponents(p, strip) {
2655
2663
  return parts.slice(strip).join("/");
2656
2664
  }
2657
2665
  function runPatch(args, cwd, signal) {
2658
- return new Promise((resolve5) => {
2666
+ return new Promise((resolve6) => {
2659
2667
  let stdout = "";
2660
2668
  let stderr = "";
2661
2669
  const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
@@ -2666,8 +2674,8 @@ function runPatch(args, cwd, signal) {
2666
2674
  child.stderr?.on("data", (c) => {
2667
2675
  stderr += c.toString();
2668
2676
  });
2669
- child.on("close", (code) => resolve5({ exitCode: code ?? 1, stdout, stderr }));
2670
- child.on("error", (e) => resolve5({ exitCode: 1, stdout: "", stderr: e.message }));
2677
+ child.on("close", (code) => resolve6({ exitCode: code ?? 1, stdout, stderr }));
2678
+ child.on("error", (e) => resolve6({ exitCode: 1, stdout: "", stderr: e.message }));
2671
2679
  });
2672
2680
  }
2673
2681
  function extractPatchedFiles(output) {
@@ -2750,8 +2758,8 @@ var jsonTool = {
2750
2758
  };
2751
2759
  }
2752
2760
  };
2753
- function query(data, path12) {
2754
- const parts = path12.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
2761
+ function query(data, path17) {
2762
+ const parts = path17.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
2755
2763
  let current = data;
2756
2764
  for (const part of parts) {
2757
2765
  if (current === null || current === void 0) return void 0;
@@ -2858,8 +2866,8 @@ function findGitDir2(cwd) {
2858
2866
  let dir = cwd;
2859
2867
  for (let i = 0; i < 20; i++) {
2860
2868
  try {
2861
- const stat10 = statSync(path.join(dir, ".git"));
2862
- if (stat10.isDirectory()) return dir;
2869
+ const stat11 = statSync(path.join(dir, ".git"));
2870
+ if (stat11.isDirectory()) return dir;
2863
2871
  } catch {
2864
2872
  }
2865
2873
  const parent = path.dirname(dir);
@@ -2869,7 +2877,7 @@ function findGitDir2(cwd) {
2869
2877
  return null;
2870
2878
  }
2871
2879
  function runGit2(args, cwd, signal) {
2872
- return new Promise((resolve5) => {
2880
+ return new Promise((resolve6) => {
2873
2881
  let stdout = "";
2874
2882
  let stderr = "";
2875
2883
  const child = spawn("git", args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
@@ -2879,8 +2887,8 @@ function runGit2(args, cwd, signal) {
2879
2887
  child.stderr?.on("data", (c) => {
2880
2888
  stderr += c.toString();
2881
2889
  });
2882
- child.on("close", (code) => resolve5({ stdout, stderr, exitCode: code ?? 0 }));
2883
- child.on("error", (e) => resolve5({ stdout: "", stderr: e.message, exitCode: 1 }));
2890
+ child.on("close", (code) => resolve6({ stdout, stderr, exitCode: code ?? 0 }));
2891
+ child.on("error", (e) => resolve6({ stdout: "", stderr: e.message, exitCode: 1 }));
2884
2892
  });
2885
2893
  }
2886
2894
  async function fileDiff(input, ctx, signal) {
@@ -2898,8 +2906,8 @@ async function fileDiff(input, ctx, signal) {
2898
2906
  const results = [];
2899
2907
  for (const file of files) {
2900
2908
  const absPath = safeResolve(file, ctx);
2901
- const stat10 = await fs4.stat(absPath).catch(() => null);
2902
- if (!stat10?.isFile()) continue;
2909
+ const stat11 = await fs4.stat(absPath).catch(() => null);
2910
+ if (!stat11?.isFile()) continue;
2903
2911
  const content = await fs4.readFile(absPath, "utf8");
2904
2912
  const lines = content.split(/\r?\n/);
2905
2913
  results.push(`--- ${file}
@@ -3131,8 +3139,8 @@ async function* spawnStream(opts) {
3131
3139
  let spawnFailed = false;
3132
3140
  for (; ; ) {
3133
3141
  while (queue.length === 0) {
3134
- await new Promise((resolve5) => {
3135
- waiter = resolve5;
3142
+ await new Promise((resolve6) => {
3143
+ waiter = resolve6;
3136
3144
  });
3137
3145
  }
3138
3146
  const chunk = queue.shift();
@@ -3241,11 +3249,11 @@ var lintTool = {
3241
3249
  }
3242
3250
  };
3243
3251
  async function detectLinter(cwd) {
3244
- const { stat: stat10 } = await import('fs/promises');
3252
+ const { stat: stat11 } = await import('node:fs/promises');
3245
3253
  const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
3246
3254
  for (const f of checks) {
3247
3255
  try {
3248
- await stat10(`${cwd}/${f}`);
3256
+ await stat11(`${cwd}/${f}`);
3249
3257
  if (f.includes("biome")) return "biome";
3250
3258
  if (f.includes("eslint")) return "eslint";
3251
3259
  if (f.includes("tslint")) return "tslint";
@@ -3340,13 +3348,13 @@ var formatTool = {
3340
3348
  }
3341
3349
  };
3342
3350
  async function detectFixer(cwd) {
3343
- const { stat: stat10 } = await import('fs/promises');
3351
+ const { stat: stat11 } = await import('node:fs/promises');
3344
3352
  try {
3345
- await stat10(`${cwd}/biome.json`);
3353
+ await stat11(`${cwd}/biome.json`);
3346
3354
  return "biome";
3347
3355
  } catch {
3348
3356
  try {
3349
- await stat10(`${cwd}/.prettierrc`);
3357
+ await stat11(`${cwd}/.prettierrc`);
3350
3358
  return "prettier";
3351
3359
  } catch {
3352
3360
  return "biome";
@@ -3422,11 +3430,11 @@ var typecheckTool = {
3422
3430
  }
3423
3431
  };
3424
3432
  async function findTsConfig(cwd) {
3425
- const { stat: stat10 } = await import('fs/promises');
3433
+ const { stat: stat11 } = await import('node:fs/promises');
3426
3434
  const candidates = ["tsconfig.json", "tsconfig.base.json"];
3427
3435
  for (const f of candidates) {
3428
3436
  try {
3429
- const s = await stat10(path.join(cwd, f));
3437
+ const s = await stat11(path.join(cwd, f));
3430
3438
  if (s.isFile()) return path.join(cwd, f);
3431
3439
  } catch {
3432
3440
  }
@@ -3503,11 +3511,11 @@ var testTool = {
3503
3511
  }
3504
3512
  };
3505
3513
  async function detectRunner(cwd) {
3506
- const { stat: stat10 } = await import('fs/promises');
3514
+ const { stat: stat11 } = await import('node:fs/promises');
3507
3515
  const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
3508
3516
  for (const f of candidates) {
3509
3517
  try {
3510
- await stat10(path.join(cwd, f));
3518
+ await stat11(path.join(cwd, f));
3511
3519
  if (f.includes("vitest")) return "vitest";
3512
3520
  if (f.includes("jest")) return "jest";
3513
3521
  if (f.includes("mocha")) return "mocha";
@@ -3678,13 +3686,13 @@ var installTool = {
3678
3686
  }
3679
3687
  };
3680
3688
  async function detectPackageManager(cwd) {
3681
- const { stat: stat10 } = await import('fs/promises');
3689
+ const { stat: stat11 } = await import('node:fs/promises');
3682
3690
  try {
3683
- await stat10(`${cwd}/pnpm-lock.yaml`);
3691
+ await stat11(`${cwd}/pnpm-lock.yaml`);
3684
3692
  return "pnpm";
3685
3693
  } catch {
3686
3694
  try {
3687
- await stat10(`${cwd}/yarn.lock`);
3695
+ await stat11(`${cwd}/yarn.lock`);
3688
3696
  return "yarn";
3689
3697
  } catch {
3690
3698
  return "npm";
@@ -3743,14 +3751,14 @@ var auditTool = {
3743
3751
  }
3744
3752
  };
3745
3753
  async function detectManager(cwd) {
3746
- const { stat: stat10 } = await import('fs/promises');
3754
+ const { stat: stat11 } = await import('node:fs/promises');
3747
3755
  try {
3748
- await stat10(`${cwd}/pnpm-lock.yaml`);
3756
+ await stat11(`${cwd}/pnpm-lock.yaml`);
3749
3757
  return "pnpm";
3750
3758
  } catch {
3751
3759
  }
3752
3760
  try {
3753
- await stat10(`${cwd}/yarn.lock`);
3761
+ await stat11(`${cwd}/yarn.lock`);
3754
3762
  return "yarn";
3755
3763
  } catch {
3756
3764
  }
@@ -3851,7 +3859,7 @@ async function detectManager2(cwd) {
3851
3859
  return "npm";
3852
3860
  }
3853
3861
  function runOutdated(manager, args, cwd, signal) {
3854
- return new Promise((resolve5) => {
3862
+ return new Promise((resolve6) => {
3855
3863
  let stdout = "";
3856
3864
  let stderr = "";
3857
3865
  const MAX = 1e5;
@@ -3864,10 +3872,10 @@ function runOutdated(manager, args, cwd, signal) {
3864
3872
  });
3865
3873
  child.on("close", (code) => {
3866
3874
  const result = parseOutdatedOutput(stdout, code ?? 0);
3867
- resolve5(result);
3875
+ resolve6(result);
3868
3876
  });
3869
3877
  child.on("error", (e) => {
3870
- resolve5({
3878
+ resolve6({
3871
3879
  exit_code: 1,
3872
3880
  packages: [],
3873
3881
  total: 0,
@@ -3991,7 +3999,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
3991
3999
  };
3992
4000
  }
3993
4001
  args.push("--timestamps", service);
3994
- return new Promise((resolve5) => {
4002
+ return new Promise((resolve6) => {
3995
4003
  let stdout = "";
3996
4004
  let stderr = "";
3997
4005
  const MAX = 2e5;
@@ -4005,7 +4013,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
4005
4013
  child.on("close", (code) => {
4006
4014
  const output = stdout + stderr;
4007
4015
  const entries = parseLogLines(output, filterRe);
4008
- resolve5({
4016
+ resolve6({
4009
4017
  source: `docker:${service}`,
4010
4018
  entries,
4011
4019
  total: entries.length,
@@ -4014,7 +4022,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
4014
4022
  });
4015
4023
  });
4016
4024
  child.on("error", (e) => {
4017
- resolve5({
4025
+ resolve6({
4018
4026
  source: `docker:${service}`,
4019
4027
  entries: [],
4020
4028
  total: 0,
@@ -4025,16 +4033,16 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
4025
4033
  });
4026
4034
  }
4027
4035
  var MAX_TAIL_LINES = 1e5;
4028
- async function fileLogs(path12, lines, filterRe, stream) {
4029
- const { createInterface } = await import('readline');
4030
- const { createReadStream } = await import('fs');
4036
+ async function fileLogs(path17, lines, filterRe, stream) {
4037
+ const { createInterface } = await import('node:readline');
4038
+ const { createReadStream } = await import('node:fs');
4031
4039
  const entries = [];
4032
4040
  const effLines = lines > 0 ? Math.min(lines, MAX_TAIL_LINES) : MAX_TAIL_LINES;
4033
4041
  const window = new Array(effLines);
4034
4042
  let writeIdx = 0;
4035
4043
  let totalLines = 0;
4036
4044
  const rl = createInterface({
4037
- input: createReadStream(path12),
4045
+ input: createReadStream(path17),
4038
4046
  crlfDelay: Number.POSITIVE_INFINITY
4039
4047
  });
4040
4048
  for await (const line of rl) {
@@ -4055,7 +4063,7 @@ async function fileLogs(path12, lines, filterRe, stream) {
4055
4063
  if (parsed) entries.push(parsed);
4056
4064
  }
4057
4065
  return {
4058
- source: path12,
4066
+ source: path17,
4059
4067
  entries,
4060
4068
  total: entries.length,
4061
4069
  truncated: totalLines > effLines,
@@ -4082,7 +4090,7 @@ function parseLine(line) {
4082
4090
  message: match[3] ?? ""
4083
4091
  };
4084
4092
  }
4085
- const levelRe = /(?:ERROR|WARN|INFO|DEBUG|TRACE)\s+(.*)/i;
4093
+ const levelRe = /(ERROR|WARN|INFO|DEBUG|TRACE)\s+(.*)/i;
4086
4094
  const levelMatch = levelRe.exec(line);
4087
4095
  if (levelMatch) {
4088
4096
  return {
@@ -4178,8 +4186,8 @@ async function resolveFiles2(filesInput, cwd) {
4178
4186
  for (const f of files) {
4179
4187
  const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
4180
4188
  try {
4181
- const stat10 = await fs4.stat(absPath);
4182
- if (stat10.isFile()) resolved.push(absPath);
4189
+ const stat11 = await fs4.stat(absPath);
4190
+ if (stat11.isFile()) resolved.push(absPath);
4183
4191
  } catch {
4184
4192
  }
4185
4193
  }
@@ -4933,6 +4941,1932 @@ ${mode.description}`
4933
4941
  };
4934
4942
  }
4935
4943
 
4944
+ // src/codebase-index/schema.ts
4945
+ var SCHEMA_VERSION = 1;
4946
+
4947
+ // src/codebase-index/lsp-kind.ts
4948
+ function lspKindToInternalKind(k) {
4949
+ switch (k) {
4950
+ case 5 /* Class */:
4951
+ return "class";
4952
+ case 6 /* Method */:
4953
+ return "method";
4954
+ case 7 /* Property */:
4955
+ case 8 /* Field */:
4956
+ return "property";
4957
+ case 9 /* Constructor */:
4958
+ return "class";
4959
+ case 10 /* Enum */:
4960
+ return "enum";
4961
+ case 11 /* Interface */:
4962
+ return "interface";
4963
+ case 12 /* Function */:
4964
+ return "function";
4965
+ case 13 /* Variable */:
4966
+ return "var";
4967
+ case 14 /* Constant */:
4968
+ return "const";
4969
+ case 22 /* EnumMember */:
4970
+ return "enum";
4971
+ case 26 /* TypeParameter */:
4972
+ return "type";
4973
+ case 3 /* Namespace */:
4974
+ return "namespace";
4975
+ default:
4976
+ return null;
4977
+ }
4978
+ }
4979
+
4980
+ // src/codebase-index/writer.ts
4981
+ var INDEX_DIR = ".codebase-index";
4982
+ var DB_FILE = "index.db";
4983
+ var IndexStore = class {
4984
+ constructor(projectRoot) {
4985
+ this.projectRoot = projectRoot;
4986
+ const dir = path.join(projectRoot, INDEX_DIR);
4987
+ fs13.mkdirSync(dir, { recursive: true });
4988
+ this.db = new DatabaseSync(path.join(dir, DB_FILE));
4989
+ this.initSchema();
4990
+ }
4991
+ projectRoot;
4992
+ db;
4993
+ initSchema() {
4994
+ this.db.exec(`
4995
+ CREATE TABLE IF NOT EXISTS metadata (
4996
+ key TEXT PRIMARY KEY,
4997
+ value TEXT NOT NULL
4998
+ );
4999
+ CREATE TABLE IF NOT EXISTS files (
5000
+ file TEXT PRIMARY KEY,
5001
+ lang TEXT NOT NULL,
5002
+ mtime_ms INTEGER NOT NULL,
5003
+ symbol_count INTEGER NOT NULL DEFAULT 0,
5004
+ last_indexed INTEGER NOT NULL
5005
+ );
5006
+ CREATE TABLE IF NOT EXISTS symbols (
5007
+ id INTEGER PRIMARY KEY,
5008
+ lang TEXT NOT NULL,
5009
+ kind TEXT NOT NULL,
5010
+ name TEXT NOT NULL,
5011
+ file TEXT NOT NULL,
5012
+ line INTEGER NOT NULL,
5013
+ col INTEGER NOT NULL,
5014
+ signature TEXT NOT NULL DEFAULT '',
5015
+ doc_comment TEXT NOT NULL DEFAULT '',
5016
+ scope TEXT NOT NULL DEFAULT '',
5017
+ text TEXT NOT NULL DEFAULT '',
5018
+ file_fk TEXT NOT NULL
5019
+ );
5020
+ `);
5021
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)");
5022
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)");
5023
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)");
5024
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
5025
+ this.db.exec(`
5026
+ CREATE TABLE IF NOT EXISTS refs (
5027
+ id INTEGER PRIMARY KEY,
5028
+ from_id INTEGER NOT NULL,
5029
+ to_name TEXT NOT NULL,
5030
+ to_id INTEGER,
5031
+ call_type TEXT NOT NULL,
5032
+ line INTEGER NOT NULL
5033
+ );
5034
+ `);
5035
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)");
5036
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)");
5037
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
5038
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
5039
+ const versionRows = this.db.prepare("SELECT value FROM metadata WHERE key = ?").all("version");
5040
+ if (!versionRows.length) {
5041
+ this.db.prepare("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
5042
+ }
5043
+ }
5044
+ // ─── Symbol CRUD ─────────────────────────────────────────────────────────────
5045
+ insertSymbols(symbols, nextId) {
5046
+ const stmt = this.db.prepare(
5047
+ `INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
5048
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
5049
+ );
5050
+ let id = nextId;
5051
+ for (const s of symbols) {
5052
+ stmt.run(
5053
+ id++,
5054
+ s.lang,
5055
+ s.kind,
5056
+ s.name,
5057
+ s.file,
5058
+ s.line,
5059
+ s.col,
5060
+ s.signature,
5061
+ s.docComment,
5062
+ s.scope,
5063
+ s.text,
5064
+ s.file
5065
+ );
5066
+ }
5067
+ return id;
5068
+ }
5069
+ deleteSymbolsForFile(file) {
5070
+ this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
5071
+ }
5072
+ deleteFile(file) {
5073
+ this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
5074
+ }
5075
+ // ─── File metadata ──────────────────────────────────────────────────────────
5076
+ upsertFile(meta) {
5077
+ this.db.prepare(
5078
+ `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
5079
+ VALUES (?, ?, ?, ?, ?)
5080
+ ON CONFLICT(file) DO UPDATE SET
5081
+ lang = excluded.lang,
5082
+ mtime_ms = excluded.mtime_ms,
5083
+ symbol_count = excluded.symbol_count,
5084
+ last_indexed = excluded.last_indexed`
5085
+ ).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
5086
+ }
5087
+ getFileMeta(file) {
5088
+ const rows = this.db.prepare(
5089
+ "SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files WHERE file = ?"
5090
+ ).all(file);
5091
+ if (!rows.length) return null;
5092
+ const r = rows[0];
5093
+ return { file: r.file, lang: r.lang, mtimeMs: r.mtime_ms, symbolCount: r.symbol_count, lastIndexed: r.last_indexed };
5094
+ }
5095
+ getAllFileMetas() {
5096
+ return this.db.prepare(
5097
+ "SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files"
5098
+ ).all().map(
5099
+ (r) => ({ file: r.file, lang: r.lang, mtimeMs: r.mtime_ms, symbolCount: r.symbol_count, lastIndexed: r.last_indexed })
5100
+ );
5101
+ }
5102
+ // ─── Search ──────────────────────────────────────────────────────────────────
5103
+ search(query2, filter) {
5104
+ const conditions = [];
5105
+ const values = [];
5106
+ let effectiveKind = filter?.kind;
5107
+ if (filter?.lspKind !== void 0) {
5108
+ const mapped = lspKindToInternalKind(filter.lspKind);
5109
+ if (mapped !== null) {
5110
+ effectiveKind = mapped;
5111
+ } else {
5112
+ return [];
5113
+ }
5114
+ }
5115
+ if (effectiveKind) {
5116
+ conditions.push("kind = ?");
5117
+ values.push(effectiveKind);
5118
+ }
5119
+ if (filter?.lang) {
5120
+ conditions.push("lang = ?");
5121
+ values.push(filter.lang);
5122
+ }
5123
+ if (filter?.file) {
5124
+ conditions.push("file LIKE ?");
5125
+ values.push(`%${filter.file}%`);
5126
+ }
5127
+ if (query2.trim()) {
5128
+ const tokens = query2.toLowerCase().split(/\s+/).filter(Boolean);
5129
+ const tokenConds = tokens.map(() => "text LIKE ?");
5130
+ conditions.push(`(${tokenConds.join(" OR ")})`);
5131
+ for (const t of tokens) values.push(`%${t}%`);
5132
+ }
5133
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
5134
+ const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}`;
5135
+ const stmt = this.db.prepare(sql);
5136
+ const rows = stmt.all(...values);
5137
+ return rows.map((r) => ({
5138
+ id: r.id,
5139
+ lang: r.lang,
5140
+ kind: r.kind,
5141
+ name: r.name,
5142
+ file: r.file,
5143
+ line: r.line,
5144
+ col: r.col,
5145
+ signature: r.signature,
5146
+ docComment: r.doc_comment,
5147
+ score: 0,
5148
+ snippet: "",
5149
+ lspKind: filter?.lspKind
5150
+ }));
5151
+ }
5152
+ getAllIndexable() {
5153
+ return this.db.prepare("SELECT id, text FROM symbols").all().map(
5154
+ ({ id, text }) => ({ id, text })
5155
+ );
5156
+ }
5157
+ // ─── Stats ───────────────────────────────────────────────────────────────────
5158
+ getStats() {
5159
+ const sizeBytes = this.sizeBytes();
5160
+ const lastRows = this.db.prepare(
5161
+ "SELECT value FROM metadata WHERE key = 'last_indexed'"
5162
+ ).all();
5163
+ const lastIndexed = lastRows.length ? Number(lastRows[0].value) : null;
5164
+ const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
5165
+ const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
5166
+ const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
5167
+ const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
5168
+ const langRows = this.db.prepare(
5169
+ "SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
5170
+ ).all();
5171
+ const byLang = {};
5172
+ for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
5173
+ const kindRows = this.db.prepare(
5174
+ "SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
5175
+ ).all();
5176
+ const byKind = {};
5177
+ for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
5178
+ return {
5179
+ totalSymbols,
5180
+ totalFiles,
5181
+ byLang,
5182
+ byKind,
5183
+ indexPath: path.join(this.projectRoot, INDEX_DIR),
5184
+ lastIndexed,
5185
+ sizeBytes,
5186
+ version: SCHEMA_VERSION
5187
+ };
5188
+ }
5189
+ setLastIndexed(ts2) {
5190
+ this.db.prepare(
5191
+ "INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)"
5192
+ ).run(String(ts2));
5193
+ }
5194
+ clearAll() {
5195
+ this.db.exec("DELETE FROM symbols");
5196
+ this.db.exec("DELETE FROM files");
5197
+ this.db.exec("DELETE FROM refs");
5198
+ }
5199
+ // ─── Ref CRUD ────────────────────────────────────────────────────────────────
5200
+ /**
5201
+ * Insert cross-references for a given source symbol id.
5202
+ * Replaces any existing refs from the same source (idempotent on re-index).
5203
+ */
5204
+ insertRefs(fromId, refs) {
5205
+ this.db.prepare("DELETE FROM refs WHERE from_id = ?").run(fromId);
5206
+ if (refs.length === 0) return;
5207
+ const stmt = this.db.prepare(
5208
+ `INSERT INTO refs(from_id, to_name, to_id, call_type, line)
5209
+ VALUES (?, ?, ?, ?, ?)`
5210
+ );
5211
+ for (const ref of refs) {
5212
+ stmt.run(fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
5213
+ }
5214
+ }
5215
+ /**
5216
+ * Delete all refs whose source symbols are in a given file.
5217
+ * Used when re-indexing a file to clear stale refs.
5218
+ */
5219
+ deleteRefsForFile(file) {
5220
+ const ids = this.db.prepare(
5221
+ "SELECT id FROM symbols WHERE file = ?"
5222
+ ).all(file);
5223
+ if (!ids.length) return;
5224
+ const placeholders = ids.map(() => "?").join(",");
5225
+ this.db.prepare(`DELETE FROM refs WHERE from_id IN (${placeholders})`).run(...ids.map((r) => r.id));
5226
+ }
5227
+ /**
5228
+ * Resolve `to_name` → `to_id` for all refs that have a name but no id.
5229
+ * Call this after all symbols have been inserted to fill in cross-references.
5230
+ */
5231
+ resolveRefs() {
5232
+ const unresolved = this.db.prepare(
5233
+ "SELECT id, to_name FROM refs WHERE to_id IS NULL AND to_name IS NOT NULL"
5234
+ ).all();
5235
+ let resolved = 0;
5236
+ for (const row of unresolved) {
5237
+ const target = this.db.prepare("SELECT id FROM symbols WHERE name = ? LIMIT 1").all(row.to_name);
5238
+ if (target.length) {
5239
+ this.db.prepare("UPDATE refs SET to_id = ? WHERE id = ?").run(target[0].id, row.id);
5240
+ resolved++;
5241
+ }
5242
+ }
5243
+ return resolved;
5244
+ }
5245
+ /**
5246
+ * Find all references TO a given symbol (who calls / uses this symbol?).
5247
+ */
5248
+ findRefsTo(symbolId) {
5249
+ return this.db.prepare(
5250
+ "SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE to_id = ? OR to_name = (SELECT name FROM symbols WHERE id = ?)"
5251
+ ).all(symbolId, symbolId).map((r) => ({
5252
+ id: r.id,
5253
+ fromId: r.from_id,
5254
+ toName: r.to_name,
5255
+ toId: r.to_id ?? void 0,
5256
+ callType: r.call_type,
5257
+ line: r.line
5258
+ }));
5259
+ }
5260
+ /**
5261
+ * Find all references FROM a given symbol (what does this symbol call/use?).
5262
+ */
5263
+ findRefsFrom(symbolId) {
5264
+ return this.db.prepare(
5265
+ "SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?"
5266
+ ).all(symbolId).map((r) => ({
5267
+ id: r.id,
5268
+ fromId: r.from_id,
5269
+ toName: r.to_name,
5270
+ toId: r.to_id ?? void 0,
5271
+ callType: r.call_type,
5272
+ line: r.line
5273
+ }));
5274
+ }
5275
+ sizeBytes() {
5276
+ const dbPath = path.join(this.projectRoot, INDEX_DIR, DB_FILE);
5277
+ try {
5278
+ return fs13.statSync(dbPath).size;
5279
+ } catch {
5280
+ return 0;
5281
+ }
5282
+ }
5283
+ close() {
5284
+ try {
5285
+ this.db.close();
5286
+ } catch {
5287
+ }
5288
+ }
5289
+ };
5290
+ var KIND_MAP = {
5291
+ [ts.SyntaxKind.ClassDeclaration]: "class",
5292
+ [ts.SyntaxKind.InterfaceDeclaration]: "interface",
5293
+ [ts.SyntaxKind.EnumDeclaration]: "enum",
5294
+ [ts.SyntaxKind.TypeAliasDeclaration]: "type",
5295
+ [ts.SyntaxKind.FunctionDeclaration]: "function",
5296
+ [ts.SyntaxKind.MethodDeclaration]: "method",
5297
+ [ts.SyntaxKind.GetAccessor]: "property",
5298
+ [ts.SyntaxKind.SetAccessor]: "property",
5299
+ [ts.SyntaxKind.PropertyDeclaration]: "property",
5300
+ [ts.SyntaxKind.Parameter]: "parameter",
5301
+ [ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
5302
+ };
5303
+ function kindOf(node) {
5304
+ if (ts.isVariableDeclaration(node)) {
5305
+ const parent = node.parent;
5306
+ if (ts.isVariableDeclarationList(parent)) {
5307
+ const flags = parent.flags;
5308
+ if (flags & ts.NodeFlags.Let) return "let";
5309
+ if (flags & ts.NodeFlags.Const) return "const";
5310
+ return "var";
5311
+ }
5312
+ }
5313
+ if (ts.isModuleDeclaration(node)) return "namespace";
5314
+ return KIND_MAP[node.kind] ?? null;
5315
+ }
5316
+ function extToLang(ext) {
5317
+ switch (ext) {
5318
+ case ".ts":
5319
+ return "ts";
5320
+ case ".tsx":
5321
+ return "tsx";
5322
+ case ".js":
5323
+ return "js";
5324
+ case ".jsx":
5325
+ return "jsx";
5326
+ case ".go":
5327
+ return "go";
5328
+ case ".py":
5329
+ return "py";
5330
+ case ".rs":
5331
+ return "rs";
5332
+ case ".json":
5333
+ return "json";
5334
+ case ".yaml":
5335
+ return "yaml";
5336
+ case ".yml":
5337
+ return "yaml";
5338
+ default:
5339
+ return null;
5340
+ }
5341
+ }
5342
+ function getSignature(node, sourceFile) {
5343
+ const printer = ts.createPrinter({});
5344
+ const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
5345
+ return raw.replace(/\s+/g, " ").slice(0, 500);
5346
+ }
5347
+ function getJsDoc(node, sourceFile) {
5348
+ const fullText = sourceFile.getFullText();
5349
+ const nodePos = node.getFullWidth();
5350
+ const comments = ts.getLeadingCommentRanges(fullText, nodePos);
5351
+ if (!comments) return "";
5352
+ for (const range of comments) {
5353
+ const commentText = fullText.slice(range.pos, range.end);
5354
+ const trimmed = commentText.trim();
5355
+ if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
5356
+ const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
5357
+ return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
5358
+ }
5359
+ }
5360
+ return "";
5361
+ }
5362
+ function buildScope(node) {
5363
+ const parts = [];
5364
+ let current = node.parent;
5365
+ while (current) {
5366
+ if (ts.isClassDeclaration(current) || ts.isInterfaceDeclaration(current) || ts.isEnumDeclaration(current) || ts.isTypeAliasDeclaration(current)) {
5367
+ parts.unshift(current.name?.text ?? "Anon");
5368
+ } else if (ts.isMethodDeclaration(current) || ts.isGetAccessor(current) || ts.isSetAccessor(current) || ts.isPropertyDeclaration(current) || ts.isFunctionDeclaration(current)) {
5369
+ if (current.name && ts.isIdentifier(current.name)) {
5370
+ parts.unshift(current.name.text);
5371
+ }
5372
+ }
5373
+ current = current.parent;
5374
+ }
5375
+ return parts.join(".");
5376
+ }
5377
+ function parseSymbols(opts) {
5378
+ const { file, content, lang } = opts;
5379
+ let sourceFile;
5380
+ try {
5381
+ sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
5382
+ } catch {
5383
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
5384
+ }
5385
+ const symbols = [];
5386
+ function visit(node) {
5387
+ const kind = kindOf(node);
5388
+ if (kind) {
5389
+ const nameNode = node.name;
5390
+ if (!nameNode || !ts.isIdentifier(nameNode)) return;
5391
+ const name = nameNode.text;
5392
+ const pos = nameNode.getStart(sourceFile);
5393
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(pos);
5394
+ const scope = buildScope(node);
5395
+ const signature = getSignature(node, sourceFile);
5396
+ const docComment = getJsDoc(node, sourceFile);
5397
+ const text = [name, signature, docComment].filter(Boolean).join(" | ");
5398
+ symbols.push({
5399
+ id: 0,
5400
+ lang,
5401
+ kind,
5402
+ name,
5403
+ file,
5404
+ line: line + 1,
5405
+ col: character,
5406
+ signature,
5407
+ docComment,
5408
+ scope,
5409
+ text
5410
+ });
5411
+ }
5412
+ ts.forEachChild(node, visit);
5413
+ }
5414
+ visit(sourceFile);
5415
+ const refs = extractRefs(sourceFile);
5416
+ return { file, lang, symbols, refs, mtimeMs: Date.now() };
5417
+ }
5418
+ function extractRefs(sourceFile) {
5419
+ const refs = [];
5420
+ function visit(node) {
5421
+ const pos = node.getStart(sourceFile);
5422
+ const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
5423
+ const lineNum = line + 1;
5424
+ if (ts.isCallExpression(node)) {
5425
+ const expr = node.expression;
5426
+ if (ts.isIdentifier(expr)) {
5427
+ refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
5428
+ }
5429
+ } else if (ts.isPropertyAccessExpression(node)) {
5430
+ if (ts.isIdentifier(node.expression)) {
5431
+ refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
5432
+ }
5433
+ } else if (ts.isTypeReferenceNode(node)) {
5434
+ const name = getTypeName(node.typeName);
5435
+ if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
5436
+ } else if (ts.isHeritageClause(node)) {
5437
+ for (const t of node.types) {
5438
+ const name = getTypeName(t.expression);
5439
+ if (name) refs.push({ fromId: 0, toName: name, callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement", line: lineNum });
5440
+ }
5441
+ } else if (ts.isImportDeclaration(node)) {
5442
+ const moduleName = getModuleName(node);
5443
+ if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
5444
+ }
5445
+ ts.forEachChild(node, visit);
5446
+ }
5447
+ visit(sourceFile);
5448
+ return deduplicateRefs(refs);
5449
+ }
5450
+ function getTypeName(name) {
5451
+ if (ts.isIdentifier(name)) return name.text;
5452
+ if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
5453
+ return "";
5454
+ }
5455
+ function getModuleName(node) {
5456
+ const moduleSpecifier = node.moduleSpecifier;
5457
+ if (ts.isStringLiteral(moduleSpecifier)) return moduleSpecifier.text;
5458
+ return "";
5459
+ }
5460
+ function deduplicateRefs(refs) {
5461
+ const seen = /* @__PURE__ */ new Set();
5462
+ return refs.filter((r) => {
5463
+ const key = `${r.toName}:${r.callType}:${r.line}`;
5464
+ if (seen.has(key)) return false;
5465
+ seen.add(key);
5466
+ return true;
5467
+ });
5468
+ }
5469
+ function detectLang(file) {
5470
+ const idx = file.lastIndexOf(".");
5471
+ if (idx < 0) return null;
5472
+ return extToLang(file.slice(idx));
5473
+ }
5474
+ function parseSymbols2(opts) {
5475
+ const { file, content, lang } = opts;
5476
+ try {
5477
+ return syncGoParse(file, content, lang);
5478
+ } catch {
5479
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
5480
+ }
5481
+ }
5482
+ var GO_PARSE_SCRIPT = `
5483
+ package main
5484
+
5485
+ import (
5486
+ "encoding/json"
5487
+ "fmt"
5488
+ "go/ast"
5489
+ "go/parser"
5490
+ "go/token"
5491
+ "os"
5492
+ "strings"
5493
+ )
5494
+
5495
+ type Sym struct {
5496
+ Name string \`json:"name"\`
5497
+ Kind string \`json:"kind"\`
5498
+ Line int \`json:"line"\`
5499
+ Col int \`json:"col"\`
5500
+ Signature string \`json:"signature"\`
5501
+ Scope string \`json:"scope"\`
5502
+ }
5503
+
5504
+ func main() {
5505
+ if len(os.Args) < 2 {
5506
+ fmt.Print("[]")
5507
+ return
5508
+ }
5509
+ src, err := os.ReadFile(os.Args[1])
5510
+ if err != nil {
5511
+ fmt.Print("[]")
5512
+ return
5513
+ }
5514
+ fset := token.NewFileSet()
5515
+ node, err := parser.ParseFile(fset, os.Args[1], src, 0)
5516
+ if err != nil {
5517
+ fmt.Print("[]")
5518
+ return
5519
+ }
5520
+
5521
+ var syms []Sym
5522
+
5523
+ // Package-level scope
5524
+ pkgScope := node.Name.Name
5525
+
5526
+ // Collect all top-level declarations
5527
+ for _, decl := range node.Decls {
5528
+ switch d := decl.(type) {
5529
+ case *ast.FuncDecl:
5530
+ name := d.Name.Name
5531
+ kind := "function"
5532
+ scope := pkgScope
5533
+ if d.Recv != nil && len(d.Recv.List) > 0 {
5534
+ scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + name
5535
+ kind = "method"
5536
+ } else {
5537
+ scope = pkgScope + "." + name
5538
+ }
5539
+ pos := fset.Position(d.Pos())
5540
+ sig := formatFuncSig(d)
5541
+ syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
5542
+
5543
+ case *ast.GenDecl:
5544
+ for _, spec := range d.Specs {
5545
+ switch s := spec.(type) {
5546
+ case *ast.TypeSpec:
5547
+ name := s.Name.Name
5548
+ pos := fset.Position(s.Pos())
5549
+ sig := "type " + name
5550
+ if s.TypeParams != nil {
5551
+ sig += formatTypeParams(s.TypeParams)
5552
+ }
5553
+ if st, ok := s.Type.(*ast.StructType); ok {
5554
+ sig += " = struct { " + formatFields(st.Fields.List) + " }"
5555
+ } else if it, ok := s.Type.(*ast.InterfaceType); ok {
5556
+ sig += " = interface { " + formatMethods(it.Methods.List) + " }"
5557
+ } else {
5558
+ sig += " = " + formatType(s.Type)
5559
+ }
5560
+ syms = append(syms, Sym{Name: name, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
5561
+
5562
+ case *ast.ValueSpec:
5563
+ for _, n := range s.Names {
5564
+ name := n.Name
5565
+ pos := fset.Position(n.Pos())
5566
+ kind := "var"
5567
+ if d.Tok == token.CONST {
5568
+ kind = "const"
5569
+ }
5570
+ sig := kind + " " + name
5571
+ if s.Type != nil {
5572
+ sig += " " + formatType(s.Type)
5573
+ }
5574
+ syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
5575
+ }
5576
+ }
5577
+ }
5578
+ }
5579
+ }
5580
+
5581
+ data, err := json.Marshal(syms)
5582
+ if err != nil {
5583
+ fmt.Print("[]")
5584
+ return
5585
+ }
5586
+ fmt.Print(string(data))
5587
+ }
5588
+
5589
+ func recvTypeName(t ast.Expr) string {
5590
+ switch v := t.(type) {
5591
+ case *ast.Ident:
5592
+ return v.Name
5593
+ case *ast.StarExpr:
5594
+ return recvTypeName(v.X)
5595
+ default:
5596
+ return "?"
5597
+ }
5598
+ }
5599
+
5600
+ func formatFuncSig(d *ast.FuncDecl) string {
5601
+ scope := ""
5602
+ if d.Recv != nil && len(d.Recv.List) > 0 {
5603
+ scope = "(" + formatFieldList(d.Recv.List) + ") "
5604
+ }
5605
+ scope += formatFuncType(d.Type)
5606
+ return "func " + scope
5607
+ }
5608
+
5609
+ func formatFuncType(f *ast.FuncType) string {
5610
+ params := formatFieldList(f.Params.List)
5611
+ results := ""
5612
+ if f.Results != nil {
5613
+ results = " -> " + formatFieldList(f.Results.List)
5614
+ }
5615
+ return params + results
5616
+ }
5617
+
5618
+ func formatFieldList(fields []*ast.Field) string {
5619
+ if len(fields) == 0 {
5620
+ return "()"
5621
+ }
5622
+ names := make([]string, 0, len(fields))
5623
+ for _, f := range fields {
5624
+ name := ""
5625
+ if len(f.Names) > 0 {
5626
+ name = f.Names[0].Name
5627
+ }
5628
+ t := formatType(f.Type)
5629
+ if name != "" {
5630
+ names = append(names, name+" "+t)
5631
+ } else {
5632
+ names = append(names, t)
5633
+ }
5634
+ }
5635
+ return "(" + strings.Join(names, ", ") + ")"
5636
+ }
5637
+
5638
+ func formatFields(fields []*ast.Field) string {
5639
+ lines := make([]string, 0)
5640
+ for _, f := range fields {
5641
+ name := ""
5642
+ if len(f.Names) > 0 {
5643
+ name = f.Names[0].Name
5644
+ }
5645
+ t := formatType(f.Type)
5646
+ if name != "" {
5647
+ lines = append(lines, name+" "+t)
5648
+ } else {
5649
+ lines = append(lines, t)
5650
+ }
5651
+ }
5652
+ return strings.Join(lines, "; ")
5653
+ }
5654
+
5655
+ func formatMethods(fields []*ast.Field) string {
5656
+ return formatFields(fields)
5657
+ }
5658
+
5659
+ func formatTypeParams(tp *ast.TypeParams) string {
5660
+ if tp == nil || len(tp.List) == 0 {
5661
+ return ""
5662
+ }
5663
+ params := make([]string, len(tp.List))
5664
+ for i, p := range tp.List {
5665
+ if len(p.Names) > 0 {
5666
+ params[i] = p.Names[0].Name
5667
+ } else {
5668
+ params[i] = "T"
5669
+ }
5670
+ }
5671
+ return "[" + strings.Join(params, ", ") + "]"
5672
+ }
5673
+
5674
+ func formatType(t ast.Expr) string {
5675
+ if t == nil {
5676
+ return "?"
5677
+ }
5678
+ switch v := t.(type) {
5679
+ case *ast.Ident:
5680
+ return v.Name
5681
+ case *ast.SelectorExpr:
5682
+ return formatType(v.X) + "." + v.Sel.Name
5683
+ case *ast.StarExpr:
5684
+ return "*" + formatType(v.X)
5685
+ case *ast.ArrayType:
5686
+ if v.Len == nil {
5687
+ return "[]" + formatType(v.Elt)
5688
+ }
5689
+ return "[...]" + formatType(v.Elt)
5690
+ case *ast.MapType:
5691
+ return "map[" + formatType(v.Key) + "]" + formatType(v.Value)
5692
+ case *ast.InterfaceType:
5693
+ return "interface{}"
5694
+ case *ast.StructType:
5695
+ return "struct{}"
5696
+ case *ast.FuncType:
5697
+ return formatFuncType(v)
5698
+ case *ast.ChanType:
5699
+ return "chan " + formatType(v.Value)
5700
+ case *ast.BasicLit:
5701
+ return v.Value
5702
+ default:
5703
+ return "?"
5704
+ }
5705
+ }
5706
+ `;
5707
+ function syncGoParse(filePath, _content, lang) {
5708
+ const tmpDir = path.join(process.env.TEMP ?? "/tmp", "ws-go-parse");
5709
+ try {
5710
+ mkdirSync(tmpDir, { recursive: true });
5711
+ const scriptPath = path.join(tmpDir, "parse.go");
5712
+ writeFileSync(scriptPath, GO_PARSE_SCRIPT, "utf8");
5713
+ let stdout;
5714
+ try {
5715
+ stdout = execSync(`go run "${scriptPath}" "${filePath}"`, {
5716
+ timeout: 15e3,
5717
+ encoding: "utf8",
5718
+ windowsHide: true
5719
+ });
5720
+ } finally {
5721
+ try {
5722
+ unlinkSync(scriptPath);
5723
+ } catch {
5724
+ }
5725
+ }
5726
+ if (!stdout.trim()) {
5727
+ return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
5728
+ }
5729
+ const raw = JSON.parse(stdout.trim());
5730
+ const symbols = raw.map((s) => ({
5731
+ id: 0,
5732
+ lang,
5733
+ kind: s.kind,
5734
+ name: s.name,
5735
+ file: filePath,
5736
+ line: s.line,
5737
+ col: s.col,
5738
+ signature: s.signature ?? "",
5739
+ docComment: "",
5740
+ scope: s.scope ?? "",
5741
+ text: `${s.name} ${s.signature ?? ""}`.trim()
5742
+ }));
5743
+ return { file: filePath, lang, symbols, mtimeMs: Date.now() };
5744
+ } catch {
5745
+ return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
5746
+ }
5747
+ }
5748
+ function parseSymbols3(opts) {
5749
+ const { file, lang } = opts;
5750
+ try {
5751
+ return syncPyParse(file, lang);
5752
+ } catch {
5753
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
5754
+ }
5755
+ }
5756
+ var PY_PARSE_SCRIPT = `import ast, json, sys, os
5757
+
5758
+ def get_name(node):
5759
+ if isinstance(node, ast.Name):
5760
+ return node.id
5761
+ elif isinstance(node, ast.Attribute):
5762
+ return get_name(node.value) + "." + node.attr
5763
+ elif isinstance(node, ast.Subscript):
5764
+ return get_name(node.value)
5765
+ elif isinstance(node, ast.Call):
5766
+ return get_name(node.func)
5767
+ elif isinstance(node, ast.Constant):
5768
+ return str(node.value)
5769
+ return ""
5770
+
5771
+ def get_decorators(node):
5772
+ decs = []
5773
+ for dec in node.decorator_list:
5774
+ decs.append(get_name(dec))
5775
+ return decs
5776
+
5777
+ def get_bases(node):
5778
+ bases = []
5779
+ for base in node.bases:
5780
+ bases.append(get_name(base))
5781
+ return bases
5782
+
5783
+ def get_args(args):
5784
+ parts = []
5785
+ for arg in args.args:
5786
+ parts.append(arg.arg)
5787
+ return ", ".join(parts)
5788
+
5789
+ def get_returns(node):
5790
+ if node.returns is None:
5791
+ return ""
5792
+ return get_name(node.returns)
5793
+
5794
+ class Sym:
5795
+ def __init__(self, name, kind, line, col, signature, scope):
5796
+ self.name = name
5797
+ self.kind = kind
5798
+ self.line = line
5799
+ self.col = col
5800
+ self.signature = signature
5801
+ self.scope = scope
5802
+ def to_dict(self):
5803
+ return {
5804
+ "name": self.name,
5805
+ "kind": self.kind,
5806
+ "line": self.line,
5807
+ "col": self.col,
5808
+ "signature": self.signature,
5809
+ "scope": self.scope,
5810
+ }
5811
+
5812
+ def is_private(name):
5813
+ return name.startswith("__") and not name.endswith("__")
5814
+
5815
+ syms = []
5816
+ errors = []
5817
+
5818
+ try:
5819
+ with open(sys.argv[1], "r", encoding="utf-8") as f:
5820
+ source = f.read()
5821
+ tree = ast.parse(source, filename=sys.argv[1])
5822
+ except Exception as e:
5823
+ errors.append(str(e))
5824
+ print("[]")
5825
+ sys.exit(0)
5826
+
5827
+ # Module-level scope
5828
+ module_scope = os.path.basename(sys.argv[1])[:-3] # strip .py
5829
+
5830
+ class ModuleVisitor(ast.NodeVisitor):
5831
+ def __init__(self):
5832
+ self.scope_stack = [module_scope]
5833
+
5834
+ def visit_ClassDef(self, node):
5835
+ bases = get_bases(node)
5836
+ decs = get_decorators(node)
5837
+ sig = "class " + node.name
5838
+ if bases:
5839
+ sig += "(" + ", ".join(bases) + ")"
5840
+ sig += ": ..."
5841
+ syms.append(Sym(
5842
+ name=node.name,
5843
+ kind="class",
5844
+ line=node.lineno,
5845
+ col=node.col_offset,
5846
+ signature=sig,
5847
+ scope=".".join(self.scope_stack) + "." + node.name,
5848
+ ))
5849
+ self.scope_stack.append(node.name)
5850
+ self.generic_visit(node)
5851
+ self.scope_stack.pop()
5852
+
5853
+ def visit_FunctionDef(self, node):
5854
+ decs = get_decorators(node)
5855
+ args = get_args(node.args)
5856
+ returns = get_returns(node)
5857
+ is_async = isinstance(node, ast.AsyncFunctionDef)
5858
+
5859
+ kind = "function"
5860
+ prefix = "def "
5861
+ if decs:
5862
+ for d in decs:
5863
+ if d.endswith(".staticmethod"):
5864
+ kind = "staticmethod"
5865
+ elif d.endswith(".classmethod"):
5866
+ kind = "classmethod"
5867
+ elif d == "property":
5868
+ kind = "property"
5869
+
5870
+ if is_async:
5871
+ kind = "async_" + kind
5872
+
5873
+ sig = f"{prefix}{node.name}({args})"
5874
+ if returns:
5875
+ sig += f" -> {returns}"
5876
+ scope = ".".join(self.scope_stack) + "." + node.name
5877
+
5878
+ syms.append(Sym(
5879
+ name=node.name,
5880
+ kind=kind,
5881
+ line=node.lineno,
5882
+ col=node.col_offset,
5883
+ signature=sig,
5884
+ scope=scope,
5885
+ ))
5886
+ # Don't descend into function bodies to avoid local symbols
5887
+ # self.generic_visit(node)
5888
+
5889
+ def visit_AsyncFunctionDef(self, node):
5890
+ # Treat as function
5891
+ self.visit_FunctionDef(node)
5892
+
5893
+ def visit_Assign(self, node):
5894
+ for target in node.targets:
5895
+ if isinstance(target, ast.Name):
5896
+ name = target.id
5897
+ if is_private(name):
5898
+ continue
5899
+ # Infer constness from UPPER_CASE naming
5900
+ kind = "const" if name.isupper() else "var"
5901
+ col = target.col_offset if hasattr(target, 'col_offset') else 0
5902
+ syms.append(Sym(
5903
+ name=name,
5904
+ kind=kind,
5905
+ line=node.lineno,
5906
+ col=col,
5907
+ signature=f"{name} = ...",
5908
+ scope=".".join(self.scope_stack),
5909
+ ))
5910
+
5911
+ def visit_AnnAssign(self, node):
5912
+ if isinstance(node.target, ast.Name):
5913
+ name = node.target.id
5914
+ if is_private(name):
5915
+ return
5916
+ kind = "const" if name.isupper() else "var"
5917
+ col = node.target.col_offset if hasattr(node.target, 'col_offset') else 0
5918
+ sig = f"{name}: {get_name(node.annotation)}"
5919
+ if node.value:
5920
+ sig += " = ..."
5921
+ syms.append(Sym(
5922
+ name=name,
5923
+ kind=kind,
5924
+ line=node.lineno,
5925
+ col=col,
5926
+ signature=sig,
5927
+ scope=".".join(self.scope_stack),
5928
+ ))
5929
+
5930
+ def visit_Import(self, node):
5931
+ for alias in node.names:
5932
+ name = alias.asname or alias.name
5933
+ syms.append(Sym(
5934
+ name=name,
5935
+ kind="import",
5936
+ line=node.lineno,
5937
+ col=node.col_offset,
5938
+ signature=f"import {alias.name}",
5939
+ scope=".".join(self.scope_stack),
5940
+ ))
5941
+
5942
+ def visit_ImportFrom(self, node):
5943
+ module = node.module or ""
5944
+ for alias in node.names:
5945
+ name = alias.asname or alias.name
5946
+ syms.append(Sym(
5947
+ name=name,
5948
+ kind="import",
5949
+ line=node.lineno,
5950
+ col=node.col_offset,
5951
+ signature=f"from {module} import {alias.name}",
5952
+ scope=".".join(self.scope_stack),
5953
+ ))
5954
+
5955
+ visitor = ModuleVisitor()
5956
+ visitor.visit(tree)
5957
+
5958
+ print(json.dumps([s.to_dict() for s in syms]))
5959
+ `;
5960
+ function syncPyParse(filePath, lang) {
5961
+ try {
5962
+ const stdout = execSync(`python -c "${PY_PARSE_SCRIPT.replace(/"/g, '\\"')}" "${filePath}"`, {
5963
+ timeout: 15e3,
5964
+ encoding: "utf8",
5965
+ windowsHide: true
5966
+ });
5967
+ if (!stdout.trim()) {
5968
+ return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
5969
+ }
5970
+ const raw = JSON.parse(stdout.trim());
5971
+ const symbols = raw.map((s) => ({
5972
+ id: 0,
5973
+ lang,
5974
+ kind: s.kind,
5975
+ name: s.name,
5976
+ file: filePath,
5977
+ line: s.line,
5978
+ col: s.col,
5979
+ signature: s.signature ?? "",
5980
+ docComment: "",
5981
+ scope: s.scope ?? "",
5982
+ text: `${s.name} ${s.signature ?? ""}`.trim()
5983
+ }));
5984
+ return { file: filePath, lang, symbols, mtimeMs: Date.now() };
5985
+ } catch {
5986
+ return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
5987
+ }
5988
+ }
5989
+ function parseSymbols4(opts) {
5990
+ const { file, content, lang } = opts;
5991
+ const nativeAvailable = checkNativeParser();
5992
+ if (nativeAvailable) {
5993
+ const result = tryNativeParse(file, content);
5994
+ if (result) return result;
5995
+ }
5996
+ return regexParse({ file, content, lang });
5997
+ }
5998
+ function checkNativeParser() {
5999
+ try {
6000
+ execSync("rustc --version", { stdio: "pipe" });
6001
+ const toolsDir = path.join(process.cwd(), "tools");
6002
+ try {
6003
+ execSync("cargo metadata --no-deps --format-version 1 --manifest-path " + path.join(toolsDir, "Cargo.toml"), { stdio: "pipe" });
6004
+ return true;
6005
+ } catch {
6006
+ return false;
6007
+ }
6008
+ } catch {
6009
+ return false;
6010
+ }
6011
+ }
6012
+ function tryNativeParse(file, content) {
6013
+ try {
6014
+ const toolsDir = path.join(process.cwd(), "tools");
6015
+ const crateDir = path.join(toolsDir, "syn-parser");
6016
+ const tmpFile = path.join(crateDir, "src", "input.rs");
6017
+ const { writeFileSync: writeFileSync2 } = __require("node:fs");
6018
+ writeFileSync2(tmpFile, content, "utf8");
6019
+ const result = spawnSync("cargo", ["run", "--manifest-path", path.join(toolsDir, "Cargo.toml")], {
6020
+ cwd: process.cwd(),
6021
+ encoding: "utf8",
6022
+ timeout: 15e3,
6023
+ stdio: ["pipe", "pipe", "pipe"]
6024
+ });
6025
+ if (result.status === 0 && result.stdout) {
6026
+ const symbols = JSON.parse(result.stdout);
6027
+ return {
6028
+ file,
6029
+ lang: "rs",
6030
+ symbols: symbols.map((s) => ({ ...s, id: 0, lang: "rs" })),
6031
+ mtimeMs: Date.now()
6032
+ };
6033
+ }
6034
+ } catch {
6035
+ }
6036
+ return null;
6037
+ }
6038
+ var RS_PATTERNS = [
6039
+ { regex: /fn\s+(\w+)\s*\([^)]*\)/g, kind: "function" },
6040
+ { regex: /struct\s+(\w+)/g, kind: "struct" },
6041
+ { regex: /enum\s+(\w+)/g, kind: "enum" },
6042
+ { regex: /trait\s+(\w+)/g, kind: "trait" },
6043
+ { regex: /impl\s+(?:<[^>]+>)?(\w+)/g, kind: "impl" },
6044
+ { regex: /type\s+(\w+)\s*=/g, kind: "type" },
6045
+ { regex: /const\s+(\w+)/g, kind: "const" },
6046
+ { regex: /static\s+(\w+)/g, kind: "static" },
6047
+ { regex: /mod\s+(\w+)/g, kind: "mod" }
6048
+ ];
6049
+ function regexParse(opts) {
6050
+ const { file, content, lang } = opts;
6051
+ const symbols = [];
6052
+ const lines = content.split("\n");
6053
+ const lineOffsets = [0];
6054
+ for (let i = 0; i < lines.length; i++) {
6055
+ lineOffsets.push(lineOffsets[i] + lines[i].length + 1);
6056
+ }
6057
+ function lineFromOffset(offset) {
6058
+ let lo = 0, hi = lineOffsets.length - 1;
6059
+ while (lo < hi) {
6060
+ const mid = lo + hi + 1 >>> 1;
6061
+ if (lineOffsets[mid] <= offset) lo = mid;
6062
+ else hi = mid - 1;
6063
+ }
6064
+ return lo + 1;
6065
+ }
6066
+ function extractDeclaration(lineIdx, match) {
6067
+ const line = lines[lineIdx] ?? "";
6068
+ return line.trim().slice(0, 500);
6069
+ }
6070
+ for (const pattern of RS_PATTERNS) {
6071
+ pattern.regex.lastIndex = 0;
6072
+ let match;
6073
+ while ((match = pattern.regex.exec(content)) !== null) {
6074
+ const name = match[1];
6075
+ const offset = match.index;
6076
+ const line = lineFromOffset(offset);
6077
+ const col = offset - (lineOffsets[line - 1] ?? 0);
6078
+ const lineIdx = line - 1;
6079
+ const signature = extractDeclaration(lineIdx);
6080
+ symbols.push({
6081
+ id: 0,
6082
+ lang,
6083
+ kind: pattern.kind,
6084
+ name,
6085
+ file,
6086
+ line,
6087
+ col,
6088
+ signature,
6089
+ docComment: "",
6090
+ scope: "",
6091
+ text: `${name} ${signature}`.trim()
6092
+ });
6093
+ }
6094
+ }
6095
+ const seen = /* @__PURE__ */ new Set();
6096
+ const deduped = symbols.filter((s) => {
6097
+ const key = `${s.name}:${s.line}`;
6098
+ if (seen.has(key)) return false;
6099
+ seen.add(key);
6100
+ return true;
6101
+ });
6102
+ return { file, lang, symbols: deduped, mtimeMs: Date.now() };
6103
+ }
6104
+ function parseSymbols5(opts) {
6105
+ const { file, content, lang } = opts;
6106
+ try {
6107
+ return regexParse2({ file, content, lang });
6108
+ } catch {
6109
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
6110
+ }
6111
+ }
6112
+ function regexParse2(opts) {
6113
+ const { file, content, lang } = opts;
6114
+ const symbols = [];
6115
+ const basename2 = path.basename(file).toLowerCase();
6116
+ const isPackageJson = basename2 === "package.json";
6117
+ const isTsconfig = basename2 === "tsconfig.json" || basename2 === "tsconfig.build.json";
6118
+ const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
6119
+ const isOpenApi = content.includes("openapi") || content.includes("swagger");
6120
+ const lines = content.split("\n");
6121
+ const lineOffsets = [0];
6122
+ for (let i = 0; i < lines.length; i++) {
6123
+ lineOffsets.push(lineOffsets[i] + lines[i].length + 1);
6124
+ }
6125
+ function lineFromOffset(offset) {
6126
+ let lo = 0, hi = lineOffsets.length - 1;
6127
+ while (lo < hi) {
6128
+ const mid = lo + hi + 1 >>> 1;
6129
+ if (lineOffsets[mid] <= offset) lo = mid;
6130
+ else hi = mid - 1;
6131
+ }
6132
+ return lo + 1;
6133
+ }
6134
+ const rootMatch = content.match(/^\s*\{/m);
6135
+ if (rootMatch) {
6136
+ const offset = rootMatch.index;
6137
+ const line = lineFromOffset(offset);
6138
+ symbols.push(makeSymbol({
6139
+ name: path.basename(file),
6140
+ kind: "object",
6141
+ line,
6142
+ col: 0,
6143
+ signature: `"${path.basename(file)}" = { ... }`,
6144
+ file,
6145
+ lang
6146
+ }));
6147
+ }
6148
+ const topLevelKeyRegex = /^\s*"([^"]+)"\s*:/gm;
6149
+ let match;
6150
+ while ((match = topLevelKeyRegex.exec(content)) !== null) {
6151
+ const key = match[1];
6152
+ const offset = match.index;
6153
+ const line = lineFromOffset(offset);
6154
+ const col = offset - (lineOffsets[line - 1] ?? 0);
6155
+ let kind = "property";
6156
+ let signature = `"${key}": ..."`;
6157
+ if (isPackageJson) {
6158
+ if (key === "scripts" || key === "dependencies" || key === "devDependencies" || key === "peerDependencies" || key === "optionalDependencies") {
6159
+ kind = "const";
6160
+ signature = `"${key}": { ... }`;
6161
+ }
6162
+ } else if (isTsconfig) {
6163
+ if (key === "compilerOptions") {
6164
+ kind = "property";
6165
+ signature = `"compilerOptions": { ... }`;
6166
+ }
6167
+ }
6168
+ if (isJsonSchema || isOpenApi) {
6169
+ if (key === "$schema" || key === "$id") {
6170
+ kind = "schema";
6171
+ signature = `"${key}": "..."`;
6172
+ } else if (key === "$ref") {
6173
+ kind = "schema";
6174
+ signature = `"$ref": "..."`;
6175
+ }
6176
+ }
6177
+ symbols.push(makeSymbol({
6178
+ name: key,
6179
+ kind,
6180
+ line,
6181
+ col,
6182
+ signature,
6183
+ file,
6184
+ lang
6185
+ }));
6186
+ if (isPackageJson && key === "scripts") {
6187
+ extractPackageScripts(content, symbols, file, lang, lineOffsets, lineFromOffset);
6188
+ }
6189
+ if (isTsconfig && key === "compilerOptions") {
6190
+ extractCompilerOptions(content, symbols, file, lang, lineOffsets, line, lineFromOffset);
6191
+ }
6192
+ }
6193
+ const defsRegex = /"\$defs"\s*:|"\$defs"\s*:/g;
6194
+ let defsMatch;
6195
+ while ((defsMatch = defsRegex.exec(content)) !== null) {
6196
+ const offset = defsMatch.index;
6197
+ const line = lineFromOffset(offset);
6198
+ symbols.push(makeSymbol({
6199
+ name: "$defs",
6200
+ kind: "property",
6201
+ line,
6202
+ col: offset - (lineOffsets[line - 1] ?? 0),
6203
+ signature: '"$defs": { ... }',
6204
+ file,
6205
+ lang
6206
+ }));
6207
+ break;
6208
+ }
6209
+ const defsPatterns = [
6210
+ /"\$defs"\s*:/g,
6211
+ /"definitions"\s*:/g,
6212
+ /"components"\s*:/g,
6213
+ /"schemas"\s*:/g
6214
+ ];
6215
+ for (const pat of defsPatterns) {
6216
+ pat.lastIndex = 0;
6217
+ while ((match = pat.exec(content)) !== null) {
6218
+ const offset = match.index;
6219
+ const line = lineFromOffset(offset);
6220
+ const key = match[0].match(/"([^"]+)"/)?.[1] ?? match[0];
6221
+ symbols.push(makeSymbol({
6222
+ name: key,
6223
+ kind: "property",
6224
+ line,
6225
+ col: offset - (lineOffsets[line - 1] ?? 0),
6226
+ signature: `"${key}": { ... }`,
6227
+ file,
6228
+ lang
6229
+ }));
6230
+ }
6231
+ }
6232
+ return { file, lang, symbols, mtimeMs: Date.now() };
6233
+ }
6234
+ function extractPackageScripts(content, symbols, file, lang, lineOffsets, lineFromOffset) {
6235
+ const scriptsBlockRegex = /"scripts"\s*:\s*\{([^}]+)\}/g;
6236
+ let match;
6237
+ while ((match = scriptsBlockRegex.exec(content)) !== null) {
6238
+ const blockContent = match[0];
6239
+ const blockOffset = match.index;
6240
+ const scriptKeyRegex = /"(\w[\w-]*)"\s*:/g;
6241
+ let scriptMatch;
6242
+ while ((scriptMatch = scriptKeyRegex.exec(blockContent)) !== null) {
6243
+ const key = scriptMatch[1];
6244
+ const keyOffset = blockOffset + scriptMatch.index;
6245
+ const line = lineFromOffset(keyOffset);
6246
+ symbols.push(makeSymbol({
6247
+ name: key,
6248
+ kind: "function",
6249
+ line,
6250
+ col: keyOffset - (lineOffsets[line - 1] ?? 0),
6251
+ signature: `"${key}": "..."`,
6252
+ file,
6253
+ lang
6254
+ }));
6255
+ }
6256
+ }
6257
+ }
6258
+ function extractCompilerOptions(content, symbols, file, lang, lineOffsets, parentLine, lineFromOffset) {
6259
+ const optsBlockRegex = /"compilerOptions"\s*:\s*\{([^}]+)\}/g;
6260
+ let match;
6261
+ while ((match = optsBlockRegex.exec(content)) !== null) {
6262
+ const blockContent = match[0];
6263
+ const blockOffset = match.index;
6264
+ const optKeyRegex = /"(\w[\w]*)"\s*:/g;
6265
+ let optMatch;
6266
+ while ((optMatch = optKeyRegex.exec(blockContent)) !== null) {
6267
+ const key = optMatch[1];
6268
+ const keyOffset = blockOffset + optMatch.index;
6269
+ const line = lineFromOffset(keyOffset);
6270
+ if (line <= parentLine) continue;
6271
+ symbols.push(makeSymbol({
6272
+ name: key,
6273
+ kind: "property",
6274
+ line,
6275
+ col: keyOffset - (lineOffsets[line - 1] ?? 0),
6276
+ signature: `"${key}": ...`,
6277
+ file,
6278
+ lang
6279
+ }));
6280
+ }
6281
+ }
6282
+ }
6283
+ function makeSymbol(opts) {
6284
+ return {
6285
+ id: 0,
6286
+ lang: opts.lang,
6287
+ kind: opts.kind,
6288
+ name: opts.name,
6289
+ file: opts.file,
6290
+ line: opts.line,
6291
+ col: opts.col,
6292
+ signature: opts.signature,
6293
+ docComment: "",
6294
+ scope: "",
6295
+ text: `${opts.name} ${opts.signature}`.trim()
6296
+ };
6297
+ }
6298
+
6299
+ // src/codebase-index/yaml-parser.ts
6300
+ function parseSymbols6(opts) {
6301
+ const { file, content, lang } = opts;
6302
+ try {
6303
+ return regexParse3({ file, content, lang });
6304
+ } catch {
6305
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
6306
+ }
6307
+ }
6308
+ function regexParse3(opts) {
6309
+ const { file, content, lang } = opts;
6310
+ const symbols = [];
6311
+ const lines = content.split("\n");
6312
+ const lineOffsets = [0];
6313
+ for (let i = 0; i < lines.length; i++) {
6314
+ lineOffsets.push(lineOffsets[i] + lines[i].length + 1);
6315
+ }
6316
+ function lineFromOffset(offset) {
6317
+ let lo = 0, hi = lineOffsets.length - 1;
6318
+ while (lo < hi) {
6319
+ const mid = lo + hi + 1 >>> 1;
6320
+ if (lineOffsets[mid] <= offset) lo = mid;
6321
+ else hi = mid - 1;
6322
+ }
6323
+ return lo + 1;
6324
+ }
6325
+ const anchorRegex = /&(\w[\w-]*)/g;
6326
+ let match;
6327
+ while ((match = anchorRegex.exec(content)) !== null) {
6328
+ const name = match[1];
6329
+ const offset = match.index;
6330
+ const line = lineFromOffset(offset);
6331
+ const col = offset - (lineOffsets[line - 1] ?? 0);
6332
+ symbols.push(makeSymbol2({
6333
+ name,
6334
+ kind: "const",
6335
+ line,
6336
+ col,
6337
+ signature: `&${name}`,
6338
+ file,
6339
+ lang
6340
+ }));
6341
+ }
6342
+ const aliasRegex = /\*(\w[\w-]*)/g;
6343
+ while ((match = aliasRegex.exec(content)) !== null) {
6344
+ const name = match[1];
6345
+ const offset = match.index;
6346
+ const line = lineFromOffset(offset);
6347
+ const col = offset - (lineOffsets[line - 1] ?? 0);
6348
+ symbols.push(makeSymbol2({
6349
+ name,
6350
+ kind: "const",
6351
+ line,
6352
+ col,
6353
+ signature: `*${name}`,
6354
+ file,
6355
+ lang
6356
+ }));
6357
+ }
6358
+ const kvRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:/gm;
6359
+ while ((match = kvRegex.exec(content)) !== null) {
6360
+ const indent = match[1].length;
6361
+ const key = match[2];
6362
+ const offset = match.index;
6363
+ const line = lineFromOffset(offset);
6364
+ const col = offset - (lineOffsets[line - 1] ?? 0);
6365
+ const lineContent = lines[line - 1] ?? "";
6366
+ if (/^[|&>]/.test(lineContent.trim())) continue;
6367
+ if (key === "---" || key === "...") continue;
6368
+ if (indent > 12) continue;
6369
+ const value = extractValue(content, match.index);
6370
+ const kind = isScalar(value) ? "literal" : "property";
6371
+ const signature = `${key}: ${truncate(value, 60)}`;
6372
+ symbols.push(makeSymbol2({ name: key, kind, line, col, signature, file, lang }));
6373
+ }
6374
+ const listItemRegex = /^-(\s+)([^:#\s][^:#\s]*)\s*:/gm;
6375
+ while ((match = listItemRegex.exec(content)) !== null) {
6376
+ const key = match[2];
6377
+ const offset = match.index;
6378
+ const line = lineFromOffset(offset);
6379
+ const col = offset - (lineOffsets[line - 1] ?? 0);
6380
+ const value = extractValue(content, offset + match[0].length);
6381
+ const kind = isScalar(value) ? "literal" : "property";
6382
+ symbols.push(makeSymbol2({
6383
+ name: key,
6384
+ kind,
6385
+ line,
6386
+ col,
6387
+ signature: `- ${key}: ${truncate(value, 60)}`,
6388
+ file,
6389
+ lang
6390
+ }));
6391
+ }
6392
+ const blockScalarRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:\s*[|>](\s|$)/gm;
6393
+ while ((match = blockScalarRegex.exec(content)) !== null) {
6394
+ const key = match[2];
6395
+ const offset = match.index;
6396
+ const line = lineFromOffset(offset);
6397
+ const col = offset - (lineOffsets[line - 1] ?? 0);
6398
+ symbols.push(makeSymbol2({
6399
+ name: key,
6400
+ kind: "property",
6401
+ line,
6402
+ col,
6403
+ signature: `${key}: | ...`,
6404
+ file,
6405
+ lang
6406
+ }));
6407
+ }
6408
+ return { file, lang, symbols, mtimeMs: Date.now() };
6409
+ }
6410
+ function extractValue(content, afterColonOffset) {
6411
+ const lineEnd = content.indexOf("\n", afterColonOffset);
6412
+ const rest = content.slice(afterColonOffset, lineEnd < 0 ? void 0 : lineEnd);
6413
+ return rest.trim();
6414
+ }
6415
+ function isScalar(value) {
6416
+ if (!value) return false;
6417
+ if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(value)) return true;
6418
+ if (/^(true|false|null|undefined)$/i.test(value)) return true;
6419
+ if (/^'[^']*'$/.test(value) || /^"[^"]*"$/.test(value)) return true;
6420
+ return false;
6421
+ }
6422
+ function truncate(s, max) {
6423
+ if (s.length <= max) return s;
6424
+ return s.slice(0, max) + "...";
6425
+ }
6426
+ function makeSymbol2(opts) {
6427
+ return {
6428
+ id: 0,
6429
+ lang: opts.lang,
6430
+ kind: opts.kind,
6431
+ name: opts.name,
6432
+ file: opts.file,
6433
+ line: opts.line,
6434
+ col: opts.col,
6435
+ signature: opts.signature,
6436
+ docComment: "",
6437
+ scope: "",
6438
+ text: `${opts.name} ${opts.signature}`.trim()
6439
+ };
6440
+ }
6441
+
6442
+ // src/codebase-index/indexer.ts
6443
+ var DEFAULT_IGNORE5 = [
6444
+ "node_modules",
6445
+ ".git",
6446
+ "dist",
6447
+ "build",
6448
+ ".next",
6449
+ "coverage",
6450
+ ".turbo",
6451
+ "__snapshots__",
6452
+ ".nyc_output"
6453
+ ];
6454
+ async function findSourceFiles(projectRoot, ignore) {
6455
+ const results = [];
6456
+ const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE5, ...ignore]);
6457
+ const globs = [
6458
+ { ext: ".ts", pat: compileGlob("**/*.ts") },
6459
+ { ext: ".tsx", pat: compileGlob("**/*.tsx") },
6460
+ { ext: ".js", pat: compileGlob("**/*.js") },
6461
+ { ext: ".jsx", pat: compileGlob("**/*.jsx") },
6462
+ { ext: ".go", pat: compileGlob("**/*.go") },
6463
+ { ext: ".py", pat: compileGlob("**/*.py") },
6464
+ { ext: ".rs", pat: compileGlob("**/*.rs") },
6465
+ { ext: ".json", pat: compileGlob("**/*.json") },
6466
+ { ext: ".yaml", pat: compileGlob("**/*.yaml") },
6467
+ { ext: ".yml", pat: compileGlob("**/*.yml") }
6468
+ ];
6469
+ const walk = async (dir) => {
6470
+ let entries;
6471
+ try {
6472
+ entries = await fs4.readdir(dir, { withFileTypes: true });
6473
+ } catch {
6474
+ return;
6475
+ }
6476
+ for (const e of entries) {
6477
+ if (ignoreSet.has(e.name)) continue;
6478
+ const full = path.join(dir, e.name);
6479
+ if (e.isDirectory()) {
6480
+ await walk(full);
6481
+ } else if (e.isFile()) {
6482
+ const rel = path.relative(projectRoot, full).replace(/\\/g, "/");
6483
+ const ext = path.extname(e.name);
6484
+ for (const { ext: extName, pat } of globs) {
6485
+ if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
6486
+ results.push(full);
6487
+ break;
6488
+ }
6489
+ }
6490
+ }
6491
+ }
6492
+ };
6493
+ await walk(projectRoot);
6494
+ return results;
6495
+ }
6496
+ async function parseFile(file, content, lang) {
6497
+ switch (lang) {
6498
+ case "ts":
6499
+ case "tsx":
6500
+ case "js":
6501
+ case "jsx":
6502
+ return parseSymbols({ file, content, lang });
6503
+ case "go":
6504
+ return parseSymbols2({ file, content, lang: "go" });
6505
+ case "py":
6506
+ return parseSymbols3({ file, lang: "py" });
6507
+ case "rs":
6508
+ return parseSymbols4({ file, content, lang: "rs" });
6509
+ case "json":
6510
+ return parseSymbols5({ file, content, lang: "json" });
6511
+ case "yaml":
6512
+ return parseSymbols6({ file, content, lang: "yaml" });
6513
+ default:
6514
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
6515
+ }
6516
+ }
6517
+ async function runIndexer(ctx, opts) {
6518
+ const { projectRoot, force = false, langs, ignore = [] } = opts;
6519
+ const store = new IndexStore(projectRoot);
6520
+ const startMs = Date.now();
6521
+ const errors = [];
6522
+ const langStats = {};
6523
+ let filesIndexed = 0;
6524
+ let symbolsIndexed = 0;
6525
+ let files;
6526
+ if (opts.files && opts.files.length > 0) {
6527
+ files = opts.files.map((f) => path.resolve(projectRoot, f));
6528
+ } else {
6529
+ files = await findSourceFiles(projectRoot, ignore);
6530
+ }
6531
+ if (langs && langs.length > 0) {
6532
+ const langSet = new Set(langs);
6533
+ files = files.filter((f) => {
6534
+ const lang = detectLang(f);
6535
+ return lang ? langSet.has(lang) : false;
6536
+ });
6537
+ }
6538
+ if (force) store.clearAll();
6539
+ const existingMeta = /* @__PURE__ */ new Map();
6540
+ if (!force) {
6541
+ for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
6542
+ }
6543
+ for (const file of files) {
6544
+ let stat11;
6545
+ try {
6546
+ stat11 = await fs4.stat(file);
6547
+ } catch {
6548
+ store.deleteFile(file);
6549
+ continue;
6550
+ }
6551
+ if (!stat11.isFile()) continue;
6552
+ const lang = detectLang(file);
6553
+ if (!lang) continue;
6554
+ const meta = existingMeta.get(file);
6555
+ if (!force && meta && meta.mtimeMs === Math.floor(stat11.mtimeMs)) {
6556
+ langStats[lang] = (langStats[lang] ?? 0) + meta.symbolCount;
6557
+ symbolsIndexed += meta.symbolCount;
6558
+ filesIndexed++;
6559
+ continue;
6560
+ }
6561
+ store.deleteSymbolsForFile(file);
6562
+ store.deleteRefsForFile(file);
6563
+ let content;
6564
+ try {
6565
+ content = await fs4.readFile(file, "utf8");
6566
+ } catch (e) {
6567
+ errors.push(`read error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
6568
+ continue;
6569
+ }
6570
+ let parsed;
6571
+ try {
6572
+ parsed = await parseFile(file, content, lang);
6573
+ } catch (e) {
6574
+ errors.push(`parse error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
6575
+ continue;
6576
+ }
6577
+ if (parsed.symbols.length === 0) {
6578
+ store.upsertFile({
6579
+ file,
6580
+ lang,
6581
+ mtimeMs: Math.floor(stat11.mtimeMs),
6582
+ symbolCount: 0,
6583
+ lastIndexed: Date.now()
6584
+ });
6585
+ filesIndexed++;
6586
+ continue;
6587
+ }
6588
+ const nextId = store.getStats().totalSymbols + 1;
6589
+ const symbolsWithIds = parsed.symbols.map((s, i) => ({ ...s, id: nextId + i }));
6590
+ store.insertSymbols(symbolsWithIds, nextId);
6591
+ const count = symbolsWithIds.length;
6592
+ symbolsIndexed += count;
6593
+ langStats[lang] = (langStats[lang] ?? 0) + count;
6594
+ if (parsed.refs && parsed.refs.length > 0) {
6595
+ for (let i = 0; i < symbolsWithIds.length; i++) {
6596
+ const sym = symbolsWithIds[i];
6597
+ const symRefs = parsed.refs.filter((r) => r.line === sym.line);
6598
+ if (symRefs.length > 0) {
6599
+ const refsWithFromId = symRefs.map((r) => ({ ...r, fromId: sym.id }));
6600
+ store.insertRefs(sym.id, refsWithFromId);
6601
+ }
6602
+ }
6603
+ }
6604
+ store.upsertFile({
6605
+ file,
6606
+ lang,
6607
+ mtimeMs: Math.floor(stat11.mtimeMs),
6608
+ symbolCount: count,
6609
+ lastIndexed: Date.now()
6610
+ });
6611
+ filesIndexed++;
6612
+ }
6613
+ for (const [file_] of existingMeta) {
6614
+ try {
6615
+ await fs4.stat(file_);
6616
+ } catch {
6617
+ store.deleteFile(file_);
6618
+ }
6619
+ }
6620
+ const durationMs = Date.now() - startMs;
6621
+ store.setLastIndexed(Date.now());
6622
+ store.close();
6623
+ return {
6624
+ filesIndexed,
6625
+ symbolsIndexed,
6626
+ langStats,
6627
+ durationMs,
6628
+ errors
6629
+ };
6630
+ }
6631
+
6632
+ // src/codebase-index/codebase-index-tool.ts
6633
+ var codebaseIndexTool = {
6634
+ name: "codebase-index",
6635
+ category: "Project",
6636
+ description: "Build or update the symbol index for the project. Runs incrementally by default \u2014 only re-indexes files that changed since the last run.",
6637
+ usageHint: "Call with `force: true` to wipe and rebuild the index from scratch. Use `langs` to limit to specific languages. First call without arguments to do an incremental index.",
6638
+ permission: "auto",
6639
+ mutating: true,
6640
+ timeoutMs: 12e4,
6641
+ inputSchema: {
6642
+ type: "object",
6643
+ properties: {
6644
+ force: {
6645
+ type: "boolean",
6646
+ description: "Force a full reindex \u2014 clears the index first and reindexes all files."
6647
+ },
6648
+ langs: {
6649
+ type: "array",
6650
+ items: { type: "string" },
6651
+ description: "Limit reindex to specific languages: ts, tsx, js, jsx, go, py, rs"
6652
+ }
6653
+ }
6654
+ },
6655
+ async execute(input, ctx) {
6656
+ const result = await runIndexer(ctx, {
6657
+ projectRoot: ctx.projectRoot,
6658
+ force: input.force ?? false,
6659
+ langs: input.langs
6660
+ });
6661
+ return result;
6662
+ }
6663
+ };
6664
+
6665
+ // src/codebase-index/bm25.ts
6666
+ var K1 = 1.5;
6667
+ var B = 0.75;
6668
+ function tokenise(text) {
6669
+ const sanitised = text.replace(/[^\p{L}\p{N}$'_]/gu, " ").replace(/_/g, " ");
6670
+ return sanitised.toLowerCase().split(" ").filter(Boolean);
6671
+ }
6672
+ function splitName(name) {
6673
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_\-]+/g, " ").trim();
6674
+ }
6675
+ function buildIndexableText(name, signature, docComment) {
6676
+ return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");
6677
+ }
6678
+ function buildBm25Index(docs) {
6679
+ const documents = docs.map((d) => {
6680
+ const tokens = tokenise(d.text);
6681
+ return { id: d.id, tokens, raw: d.text, len: tokens.length };
6682
+ });
6683
+ const df = {};
6684
+ for (const doc of documents) {
6685
+ const seen = /* @__PURE__ */ new Set();
6686
+ for (const t of doc.tokens) {
6687
+ if (!seen.has(t)) {
6688
+ df[t] = (df[t] ?? 0) + 1;
6689
+ seen.add(t);
6690
+ }
6691
+ }
6692
+ }
6693
+ const N = documents.length;
6694
+ const totalLen = documents.reduce((sum, d) => sum + d.len, 0);
6695
+ const avgLen = N === 0 ? 0 : totalLen / N;
6696
+ return new Bm25Index(documents, df, N, avgLen);
6697
+ }
6698
+ var Bm25Index = class {
6699
+ constructor(documents, df, N, avgLen) {
6700
+ this.documents = documents;
6701
+ this.df = df;
6702
+ this.N = N;
6703
+ this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
6704
+ }
6705
+ documents;
6706
+ df;
6707
+ N;
6708
+ safeAvgLen;
6709
+ score(query2, filter) {
6710
+ const qTokens = tokenise(query2);
6711
+ if (qTokens.length === 0) return [];
6712
+ const results = [];
6713
+ for (const doc of this.documents) {
6714
+ if (filter && !filter(doc.id)) continue;
6715
+ let docScore = 0;
6716
+ for (const qTerm of qTokens) {
6717
+ let tf = 0;
6718
+ for (const t of doc.tokens) {
6719
+ if (t === qTerm) tf++;
6720
+ }
6721
+ if (tf === 0) continue;
6722
+ const dfVal = this.df[qTerm] ?? 0;
6723
+ if (dfVal === 0) continue;
6724
+ const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
6725
+ const lenRatio = B * (doc.len / this.safeAvgLen);
6726
+ const tfComponent = tf * (K1 + 1) / (tf + K1 * (1 - B + lenRatio));
6727
+ docScore += idf * tfComponent;
6728
+ }
6729
+ if (docScore > 0) results.push({ id: doc.id, score: docScore });
6730
+ }
6731
+ return results;
6732
+ }
6733
+ getDoc(id) {
6734
+ return this.documents.find((d) => d.id === id);
6735
+ }
6736
+ extractSnippet(docId, queryTokens, radius = 40) {
6737
+ const doc = this.getDoc(docId);
6738
+ if (!doc) return "";
6739
+ for (const tok of queryTokens) {
6740
+ const idx = doc.raw.toLowerCase().indexOf(tok);
6741
+ if (idx !== -1) {
6742
+ const start = Math.max(0, idx - radius);
6743
+ const end = Math.min(doc.raw.length, idx + tok.length + radius);
6744
+ const excerpt = doc.raw.slice(start, end);
6745
+ const ellipsis = "\u2026";
6746
+ return (start > 0 ? ellipsis : "") + excerpt + (end < doc.raw.length ? ellipsis : "");
6747
+ }
6748
+ }
6749
+ return doc.raw.slice(0, radius * 2) + (doc.raw.length > radius * 2 ? "\u2026" : "");
6750
+ }
6751
+ };
6752
+
6753
+ // src/codebase-index/codebase-search-tool.ts
6754
+ var codebaseSearchTool = {
6755
+ name: "codebase-search",
6756
+ category: "Project",
6757
+ description: "Search indexed code symbols by name, signature, or doc comment. Uses BM25 ranking for relevance.",
6758
+ usageHint: "Pass `query` for keyword search. Filter with `kind` (class/function/interface/etc), `lang` (ts/js/etc), `file` (substring). `limit` caps results (default 20).",
6759
+ permission: "auto",
6760
+ mutating: false,
6761
+ timeoutMs: 1e4,
6762
+ inputSchema: {
6763
+ type: "object",
6764
+ properties: {
6765
+ query: {
6766
+ type: "string",
6767
+ description: "Search query \u2014 searches symbol names, signatures, and doc comments"
6768
+ },
6769
+ kind: {
6770
+ type: "string",
6771
+ description: "Filter by symbol kind: class, function, interface, method, const, let, var, property, type, enum"
6772
+ },
6773
+ lang: {
6774
+ type: "string",
6775
+ description: "Filter by language: ts, tsx, js, jsx"
6776
+ },
6777
+ lspKind: {
6778
+ type: "integer",
6779
+ description: "Filter by LSP SymbolKind number (e.g. 5=Class, 12=Function, 11=Interface, 10=Enum)"
6780
+ },
6781
+ file: {
6782
+ type: "string",
6783
+ description: "Filter to files matching this path substring"
6784
+ },
6785
+ limit: {
6786
+ type: "integer",
6787
+ description: "Maximum results to return (default 20, max 100)",
6788
+ minimum: 1,
6789
+ maximum: 100
6790
+ }
6791
+ },
6792
+ required: ["query"]
6793
+ },
6794
+ async execute(input, ctx) {
6795
+ const store = new IndexStore(ctx.projectRoot);
6796
+ try {
6797
+ const limit = Math.min(input.limit ?? 20, 100);
6798
+ const candidates = store.search(input.query, {
6799
+ kind: input.kind,
6800
+ lang: input.lang,
6801
+ file: input.file,
6802
+ lspKind: input.lspKind
6803
+ });
6804
+ if (candidates.length === 0) {
6805
+ return { results: [], total: 0, query: input.query };
6806
+ }
6807
+ const indexable = candidates.map((c) => ({
6808
+ id: c.id,
6809
+ text: buildIndexableText(c.name, c.signature, c.docComment)
6810
+ }));
6811
+ const bm25 = buildBm25Index(indexable);
6812
+ const scored = bm25.score(input.query, (id) => candidates.some((c) => c.id === id));
6813
+ scored.sort((a, b) => b.score - a.score);
6814
+ const top = scored.slice(0, limit);
6815
+ const qTokens = tokenise(input.query);
6816
+ const results = top.map(({ id, score }) => {
6817
+ const c = candidates.find((c2) => c2.id === id);
6818
+ const snippet = bm25.extractSnippet(id, qTokens);
6819
+ return {
6820
+ ...c,
6821
+ score,
6822
+ snippet
6823
+ };
6824
+ });
6825
+ return {
6826
+ results,
6827
+ total: candidates.length,
6828
+ query: input.query
6829
+ };
6830
+ } finally {
6831
+ store.close();
6832
+ }
6833
+ }
6834
+ };
6835
+
6836
+ // src/codebase-index/codebase-stats-tool.ts
6837
+ var codebaseStatsTool = {
6838
+ name: "codebase-stats",
6839
+ category: "Project",
6840
+ description: "Return statistics about the symbol index: total symbols, files, breakdown by language and kind, index size, and last update time.",
6841
+ usageHint: "No arguments needed. Use to check if the index is stale or healthy before running a search.",
6842
+ permission: "auto",
6843
+ mutating: false,
6844
+ timeoutMs: 5e3,
6845
+ inputSchema: {
6846
+ type: "object",
6847
+ properties: {},
6848
+ additionalProperties: false
6849
+ },
6850
+ async execute(_input, ctx) {
6851
+ const store = new IndexStore(ctx.projectRoot);
6852
+ try {
6853
+ const stats = store.getStats();
6854
+ return {
6855
+ totalSymbols: stats.totalSymbols,
6856
+ totalFiles: stats.totalFiles,
6857
+ byLang: stats.byLang,
6858
+ byKind: stats.byKind,
6859
+ lastIndexed: stats.lastIndexed,
6860
+ sizeBytes: stats.sizeBytes,
6861
+ indexPath: stats.indexPath,
6862
+ version: stats.version
6863
+ };
6864
+ } finally {
6865
+ store.close();
6866
+ }
6867
+ }
6868
+ };
6869
+
4936
6870
  // src/builtin.ts
4937
6871
  var builtinTools = [
4938
6872
  readTool,
@@ -4965,7 +6899,10 @@ var builtinTools = [
4965
6899
  toolSearchTool,
4966
6900
  toolUseTool,
4967
6901
  batchToolUseTool,
4968
- toolHelpTool
6902
+ toolHelpTool,
6903
+ codebaseIndexTool,
6904
+ codebaseSearchTool,
6905
+ codebaseStatsTool
4969
6906
  ];
4970
6907
 
4971
6908
  // src/pack.ts
@@ -4975,6 +6912,6 @@ var builtinToolsPack = {
4975
6912
  tools: builtinTools
4976
6913
  };
4977
6914
 
4978
- export { CircuitBreaker, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, createModeTool, diffTool, documentTool, editTool, execTool, fetchTool, forgetTool, formatTool, getProcessRegistry, gitTool, globTool, grepTool, installTool, jsonTool, lintTool, logsTool, outdatedTool, patchTool, planTool, readTool, rememberTool, replaceTool, scaffoldTool, searchTool, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
6915
+ export { CircuitBreaker, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, createModeTool, diffTool, documentTool, editTool, execTool, fetchTool, forgetTool, formatTool, getProcessRegistry, gitTool, globTool, grepTool, installTool, jsonTool, lintTool, logsTool, outdatedTool, patchTool, planTool, readTool, rememberTool, replaceTool, scaffoldTool, searchTool, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
4979
6916
  //# sourceMappingURL=index.js.map
4980
6917
  //# sourceMappingURL=index.js.map