@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/pack.js CHANGED
@@ -1,15 +1,23 @@
1
- import { spawn } from 'child_process';
1
+ import { spawn, execSync, spawnSync } from 'node:child_process';
2
2
  import { buildChildEnv, stripAnsi, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan } from '@wrongstack/core';
3
- import * as path from 'path';
4
- import { dirname } from 'path';
5
- import * as os from 'os';
6
- import { statSync } from 'fs';
7
- import * as fs9 from 'fs/promises';
8
- import { stat } from 'fs/promises';
9
- import * as dns from 'dns/promises';
10
- import * as net from 'net';
3
+ import * as path from 'node:path';
4
+ import { dirname } from 'node:path';
5
+ import * as os from 'node:os';
6
+ import * as fs11 from 'node:fs/promises';
7
+ import { stat } from 'node:fs/promises';
8
+ import * as fs from 'node:fs';
9
+ import { statSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
10
+ import { DatabaseSync } from 'node:sqlite';
11
+ import * as ts from 'typescript';
12
+ import * as dns from 'node:dns/promises';
13
+ import * as net from 'node:net';
11
14
 
12
- // src/_spawn-stream.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
  async function* spawnStream(opts) {
14
22
  const max = opts.maxBytes ?? 2e5;
15
23
  const flushAt = opts.flushBytes ?? 4 * 1024;
@@ -57,8 +65,8 @@ async function* spawnStream(opts) {
57
65
  let spawnFailed = false;
58
66
  for (; ; ) {
59
67
  while (queue.length === 0) {
60
- await new Promise((resolve5) => {
61
- waiter = resolve5;
68
+ await new Promise((resolve6) => {
69
+ waiter = resolve6;
62
70
  });
63
71
  }
64
72
  const chunk = queue.shift();
@@ -169,14 +177,14 @@ var auditTool = {
169
177
  }
170
178
  };
171
179
  async function detectManager(cwd) {
172
- const { stat: stat10 } = await import('fs/promises');
180
+ const { stat: stat11 } = await import('node:fs/promises');
173
181
  try {
174
- await stat10(`${cwd}/pnpm-lock.yaml`);
182
+ await stat11(`${cwd}/pnpm-lock.yaml`);
175
183
  return "pnpm";
176
184
  } catch {
177
185
  }
178
186
  try {
179
- await stat10(`${cwd}/yarn.lock`);
187
+ await stat11(`${cwd}/yarn.lock`);
180
188
  return "yarn";
181
189
  } catch {
182
190
  }
@@ -733,10 +741,10 @@ var bashTool = {
733
741
  queue.push(c);
734
742
  }
735
743
  };
736
- const next = () => new Promise((resolve5) => {
744
+ const next = () => new Promise((resolve6) => {
737
745
  const c = queue.shift();
738
- if (c) resolve5(c);
739
- else resolveNext = resolve5;
746
+ if (c) resolve6(c);
747
+ else resolveNext = resolve6;
740
748
  });
741
749
  let lastFlush = Date.now();
742
750
  const flush = () => {
@@ -904,6 +912,1932 @@ async function executeSingle(call, ctx, opts) {
904
912
  };
905
913
  }
906
914
  }
915
+
916
+ // src/codebase-index/schema.ts
917
+ var SCHEMA_VERSION = 1;
918
+
919
+ // src/codebase-index/lsp-kind.ts
920
+ function lspKindToInternalKind(k) {
921
+ switch (k) {
922
+ case 5 /* Class */:
923
+ return "class";
924
+ case 6 /* Method */:
925
+ return "method";
926
+ case 7 /* Property */:
927
+ case 8 /* Field */:
928
+ return "property";
929
+ case 9 /* Constructor */:
930
+ return "class";
931
+ case 10 /* Enum */:
932
+ return "enum";
933
+ case 11 /* Interface */:
934
+ return "interface";
935
+ case 12 /* Function */:
936
+ return "function";
937
+ case 13 /* Variable */:
938
+ return "var";
939
+ case 14 /* Constant */:
940
+ return "const";
941
+ case 22 /* EnumMember */:
942
+ return "enum";
943
+ case 26 /* TypeParameter */:
944
+ return "type";
945
+ case 3 /* Namespace */:
946
+ return "namespace";
947
+ default:
948
+ return null;
949
+ }
950
+ }
951
+
952
+ // src/codebase-index/writer.ts
953
+ var INDEX_DIR = ".codebase-index";
954
+ var DB_FILE = "index.db";
955
+ var IndexStore = class {
956
+ constructor(projectRoot) {
957
+ this.projectRoot = projectRoot;
958
+ const dir = path.join(projectRoot, INDEX_DIR);
959
+ fs.mkdirSync(dir, { recursive: true });
960
+ this.db = new DatabaseSync(path.join(dir, DB_FILE));
961
+ this.initSchema();
962
+ }
963
+ projectRoot;
964
+ db;
965
+ initSchema() {
966
+ this.db.exec(`
967
+ CREATE TABLE IF NOT EXISTS metadata (
968
+ key TEXT PRIMARY KEY,
969
+ value TEXT NOT NULL
970
+ );
971
+ CREATE TABLE IF NOT EXISTS files (
972
+ file TEXT PRIMARY KEY,
973
+ lang TEXT NOT NULL,
974
+ mtime_ms INTEGER NOT NULL,
975
+ symbol_count INTEGER NOT NULL DEFAULT 0,
976
+ last_indexed INTEGER NOT NULL
977
+ );
978
+ CREATE TABLE IF NOT EXISTS symbols (
979
+ id INTEGER PRIMARY KEY,
980
+ lang TEXT NOT NULL,
981
+ kind TEXT NOT NULL,
982
+ name TEXT NOT NULL,
983
+ file TEXT NOT NULL,
984
+ line INTEGER NOT NULL,
985
+ col INTEGER NOT NULL,
986
+ signature TEXT NOT NULL DEFAULT '',
987
+ doc_comment TEXT NOT NULL DEFAULT '',
988
+ scope TEXT NOT NULL DEFAULT '',
989
+ text TEXT NOT NULL DEFAULT '',
990
+ file_fk TEXT NOT NULL
991
+ );
992
+ `);
993
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)");
994
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)");
995
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)");
996
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
997
+ this.db.exec(`
998
+ CREATE TABLE IF NOT EXISTS refs (
999
+ id INTEGER PRIMARY KEY,
1000
+ from_id INTEGER NOT NULL,
1001
+ to_name TEXT NOT NULL,
1002
+ to_id INTEGER,
1003
+ call_type TEXT NOT NULL,
1004
+ line INTEGER NOT NULL
1005
+ );
1006
+ `);
1007
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)");
1008
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)");
1009
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
1010
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
1011
+ const versionRows = this.db.prepare("SELECT value FROM metadata WHERE key = ?").all("version");
1012
+ if (!versionRows.length) {
1013
+ this.db.prepare("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
1014
+ }
1015
+ }
1016
+ // ─── Symbol CRUD ─────────────────────────────────────────────────────────────
1017
+ insertSymbols(symbols, nextId) {
1018
+ const stmt = this.db.prepare(
1019
+ `INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
1020
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
1021
+ );
1022
+ let id = nextId;
1023
+ for (const s of symbols) {
1024
+ stmt.run(
1025
+ id++,
1026
+ s.lang,
1027
+ s.kind,
1028
+ s.name,
1029
+ s.file,
1030
+ s.line,
1031
+ s.col,
1032
+ s.signature,
1033
+ s.docComment,
1034
+ s.scope,
1035
+ s.text,
1036
+ s.file
1037
+ );
1038
+ }
1039
+ return id;
1040
+ }
1041
+ deleteSymbolsForFile(file) {
1042
+ this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
1043
+ }
1044
+ deleteFile(file) {
1045
+ this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
1046
+ }
1047
+ // ─── File metadata ──────────────────────────────────────────────────────────
1048
+ upsertFile(meta) {
1049
+ this.db.prepare(
1050
+ `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
1051
+ VALUES (?, ?, ?, ?, ?)
1052
+ ON CONFLICT(file) DO UPDATE SET
1053
+ lang = excluded.lang,
1054
+ mtime_ms = excluded.mtime_ms,
1055
+ symbol_count = excluded.symbol_count,
1056
+ last_indexed = excluded.last_indexed`
1057
+ ).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
1058
+ }
1059
+ getFileMeta(file) {
1060
+ const rows = this.db.prepare(
1061
+ "SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files WHERE file = ?"
1062
+ ).all(file);
1063
+ if (!rows.length) return null;
1064
+ const r = rows[0];
1065
+ return { file: r.file, lang: r.lang, mtimeMs: r.mtime_ms, symbolCount: r.symbol_count, lastIndexed: r.last_indexed };
1066
+ }
1067
+ getAllFileMetas() {
1068
+ return this.db.prepare(
1069
+ "SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files"
1070
+ ).all().map(
1071
+ (r) => ({ file: r.file, lang: r.lang, mtimeMs: r.mtime_ms, symbolCount: r.symbol_count, lastIndexed: r.last_indexed })
1072
+ );
1073
+ }
1074
+ // ─── Search ──────────────────────────────────────────────────────────────────
1075
+ search(query2, filter) {
1076
+ const conditions = [];
1077
+ const values = [];
1078
+ let effectiveKind = filter?.kind;
1079
+ if (filter?.lspKind !== void 0) {
1080
+ const mapped = lspKindToInternalKind(filter.lspKind);
1081
+ if (mapped !== null) {
1082
+ effectiveKind = mapped;
1083
+ } else {
1084
+ return [];
1085
+ }
1086
+ }
1087
+ if (effectiveKind) {
1088
+ conditions.push("kind = ?");
1089
+ values.push(effectiveKind);
1090
+ }
1091
+ if (filter?.lang) {
1092
+ conditions.push("lang = ?");
1093
+ values.push(filter.lang);
1094
+ }
1095
+ if (filter?.file) {
1096
+ conditions.push("file LIKE ?");
1097
+ values.push(`%${filter.file}%`);
1098
+ }
1099
+ if (query2.trim()) {
1100
+ const tokens = query2.toLowerCase().split(/\s+/).filter(Boolean);
1101
+ const tokenConds = tokens.map(() => "text LIKE ?");
1102
+ conditions.push(`(${tokenConds.join(" OR ")})`);
1103
+ for (const t of tokens) values.push(`%${t}%`);
1104
+ }
1105
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
1106
+ const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}`;
1107
+ const stmt = this.db.prepare(sql);
1108
+ const rows = stmt.all(...values);
1109
+ return rows.map((r) => ({
1110
+ id: r.id,
1111
+ lang: r.lang,
1112
+ kind: r.kind,
1113
+ name: r.name,
1114
+ file: r.file,
1115
+ line: r.line,
1116
+ col: r.col,
1117
+ signature: r.signature,
1118
+ docComment: r.doc_comment,
1119
+ score: 0,
1120
+ snippet: "",
1121
+ lspKind: filter?.lspKind
1122
+ }));
1123
+ }
1124
+ getAllIndexable() {
1125
+ return this.db.prepare("SELECT id, text FROM symbols").all().map(
1126
+ ({ id, text }) => ({ id, text })
1127
+ );
1128
+ }
1129
+ // ─── Stats ───────────────────────────────────────────────────────────────────
1130
+ getStats() {
1131
+ const sizeBytes = this.sizeBytes();
1132
+ const lastRows = this.db.prepare(
1133
+ "SELECT value FROM metadata WHERE key = 'last_indexed'"
1134
+ ).all();
1135
+ const lastIndexed = lastRows.length ? Number(lastRows[0].value) : null;
1136
+ const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
1137
+ const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
1138
+ const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
1139
+ const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
1140
+ const langRows = this.db.prepare(
1141
+ "SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
1142
+ ).all();
1143
+ const byLang = {};
1144
+ for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
1145
+ const kindRows = this.db.prepare(
1146
+ "SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
1147
+ ).all();
1148
+ const byKind = {};
1149
+ for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
1150
+ return {
1151
+ totalSymbols,
1152
+ totalFiles,
1153
+ byLang,
1154
+ byKind,
1155
+ indexPath: path.join(this.projectRoot, INDEX_DIR),
1156
+ lastIndexed,
1157
+ sizeBytes,
1158
+ version: SCHEMA_VERSION
1159
+ };
1160
+ }
1161
+ setLastIndexed(ts2) {
1162
+ this.db.prepare(
1163
+ "INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)"
1164
+ ).run(String(ts2));
1165
+ }
1166
+ clearAll() {
1167
+ this.db.exec("DELETE FROM symbols");
1168
+ this.db.exec("DELETE FROM files");
1169
+ this.db.exec("DELETE FROM refs");
1170
+ }
1171
+ // ─── Ref CRUD ────────────────────────────────────────────────────────────────
1172
+ /**
1173
+ * Insert cross-references for a given source symbol id.
1174
+ * Replaces any existing refs from the same source (idempotent on re-index).
1175
+ */
1176
+ insertRefs(fromId, refs) {
1177
+ this.db.prepare("DELETE FROM refs WHERE from_id = ?").run(fromId);
1178
+ if (refs.length === 0) return;
1179
+ const stmt = this.db.prepare(
1180
+ `INSERT INTO refs(from_id, to_name, to_id, call_type, line)
1181
+ VALUES (?, ?, ?, ?, ?)`
1182
+ );
1183
+ for (const ref of refs) {
1184
+ stmt.run(fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Delete all refs whose source symbols are in a given file.
1189
+ * Used when re-indexing a file to clear stale refs.
1190
+ */
1191
+ deleteRefsForFile(file) {
1192
+ const ids = this.db.prepare(
1193
+ "SELECT id FROM symbols WHERE file = ?"
1194
+ ).all(file);
1195
+ if (!ids.length) return;
1196
+ const placeholders = ids.map(() => "?").join(",");
1197
+ this.db.prepare(`DELETE FROM refs WHERE from_id IN (${placeholders})`).run(...ids.map((r) => r.id));
1198
+ }
1199
+ /**
1200
+ * Resolve `to_name` → `to_id` for all refs that have a name but no id.
1201
+ * Call this after all symbols have been inserted to fill in cross-references.
1202
+ */
1203
+ resolveRefs() {
1204
+ const unresolved = this.db.prepare(
1205
+ "SELECT id, to_name FROM refs WHERE to_id IS NULL AND to_name IS NOT NULL"
1206
+ ).all();
1207
+ let resolved = 0;
1208
+ for (const row of unresolved) {
1209
+ const target = this.db.prepare("SELECT id FROM symbols WHERE name = ? LIMIT 1").all(row.to_name);
1210
+ if (target.length) {
1211
+ this.db.prepare("UPDATE refs SET to_id = ? WHERE id = ?").run(target[0].id, row.id);
1212
+ resolved++;
1213
+ }
1214
+ }
1215
+ return resolved;
1216
+ }
1217
+ /**
1218
+ * Find all references TO a given symbol (who calls / uses this symbol?).
1219
+ */
1220
+ findRefsTo(symbolId) {
1221
+ return this.db.prepare(
1222
+ "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 = ?)"
1223
+ ).all(symbolId, symbolId).map((r) => ({
1224
+ id: r.id,
1225
+ fromId: r.from_id,
1226
+ toName: r.to_name,
1227
+ toId: r.to_id ?? void 0,
1228
+ callType: r.call_type,
1229
+ line: r.line
1230
+ }));
1231
+ }
1232
+ /**
1233
+ * Find all references FROM a given symbol (what does this symbol call/use?).
1234
+ */
1235
+ findRefsFrom(symbolId) {
1236
+ return this.db.prepare(
1237
+ "SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?"
1238
+ ).all(symbolId).map((r) => ({
1239
+ id: r.id,
1240
+ fromId: r.from_id,
1241
+ toName: r.to_name,
1242
+ toId: r.to_id ?? void 0,
1243
+ callType: r.call_type,
1244
+ line: r.line
1245
+ }));
1246
+ }
1247
+ sizeBytes() {
1248
+ const dbPath = path.join(this.projectRoot, INDEX_DIR, DB_FILE);
1249
+ try {
1250
+ return fs.statSync(dbPath).size;
1251
+ } catch {
1252
+ return 0;
1253
+ }
1254
+ }
1255
+ close() {
1256
+ try {
1257
+ this.db.close();
1258
+ } catch {
1259
+ }
1260
+ }
1261
+ };
1262
+ var KIND_MAP = {
1263
+ [ts.SyntaxKind.ClassDeclaration]: "class",
1264
+ [ts.SyntaxKind.InterfaceDeclaration]: "interface",
1265
+ [ts.SyntaxKind.EnumDeclaration]: "enum",
1266
+ [ts.SyntaxKind.TypeAliasDeclaration]: "type",
1267
+ [ts.SyntaxKind.FunctionDeclaration]: "function",
1268
+ [ts.SyntaxKind.MethodDeclaration]: "method",
1269
+ [ts.SyntaxKind.GetAccessor]: "property",
1270
+ [ts.SyntaxKind.SetAccessor]: "property",
1271
+ [ts.SyntaxKind.PropertyDeclaration]: "property",
1272
+ [ts.SyntaxKind.Parameter]: "parameter",
1273
+ [ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
1274
+ };
1275
+ function kindOf(node) {
1276
+ if (ts.isVariableDeclaration(node)) {
1277
+ const parent = node.parent;
1278
+ if (ts.isVariableDeclarationList(parent)) {
1279
+ const flags = parent.flags;
1280
+ if (flags & ts.NodeFlags.Let) return "let";
1281
+ if (flags & ts.NodeFlags.Const) return "const";
1282
+ return "var";
1283
+ }
1284
+ }
1285
+ if (ts.isModuleDeclaration(node)) return "namespace";
1286
+ return KIND_MAP[node.kind] ?? null;
1287
+ }
1288
+ function extToLang(ext) {
1289
+ switch (ext) {
1290
+ case ".ts":
1291
+ return "ts";
1292
+ case ".tsx":
1293
+ return "tsx";
1294
+ case ".js":
1295
+ return "js";
1296
+ case ".jsx":
1297
+ return "jsx";
1298
+ case ".go":
1299
+ return "go";
1300
+ case ".py":
1301
+ return "py";
1302
+ case ".rs":
1303
+ return "rs";
1304
+ case ".json":
1305
+ return "json";
1306
+ case ".yaml":
1307
+ return "yaml";
1308
+ case ".yml":
1309
+ return "yaml";
1310
+ default:
1311
+ return null;
1312
+ }
1313
+ }
1314
+ function getSignature(node, sourceFile) {
1315
+ const printer = ts.createPrinter({});
1316
+ const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
1317
+ return raw.replace(/\s+/g, " ").slice(0, 500);
1318
+ }
1319
+ function getJsDoc(node, sourceFile) {
1320
+ const fullText = sourceFile.getFullText();
1321
+ const nodePos = node.getFullWidth();
1322
+ const comments = ts.getLeadingCommentRanges(fullText, nodePos);
1323
+ if (!comments) return "";
1324
+ for (const range of comments) {
1325
+ const commentText = fullText.slice(range.pos, range.end);
1326
+ const trimmed = commentText.trim();
1327
+ if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
1328
+ const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
1329
+ return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
1330
+ }
1331
+ }
1332
+ return "";
1333
+ }
1334
+ function buildScope(node) {
1335
+ const parts = [];
1336
+ let current = node.parent;
1337
+ while (current) {
1338
+ if (ts.isClassDeclaration(current) || ts.isInterfaceDeclaration(current) || ts.isEnumDeclaration(current) || ts.isTypeAliasDeclaration(current)) {
1339
+ parts.unshift(current.name?.text ?? "Anon");
1340
+ } else if (ts.isMethodDeclaration(current) || ts.isGetAccessor(current) || ts.isSetAccessor(current) || ts.isPropertyDeclaration(current) || ts.isFunctionDeclaration(current)) {
1341
+ if (current.name && ts.isIdentifier(current.name)) {
1342
+ parts.unshift(current.name.text);
1343
+ }
1344
+ }
1345
+ current = current.parent;
1346
+ }
1347
+ return parts.join(".");
1348
+ }
1349
+ function parseSymbols(opts) {
1350
+ const { file, content, lang } = opts;
1351
+ let sourceFile;
1352
+ try {
1353
+ sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
1354
+ } catch {
1355
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
1356
+ }
1357
+ const symbols = [];
1358
+ function visit(node) {
1359
+ const kind = kindOf(node);
1360
+ if (kind) {
1361
+ const nameNode = node.name;
1362
+ if (!nameNode || !ts.isIdentifier(nameNode)) return;
1363
+ const name = nameNode.text;
1364
+ const pos = nameNode.getStart(sourceFile);
1365
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(pos);
1366
+ const scope = buildScope(node);
1367
+ const signature = getSignature(node, sourceFile);
1368
+ const docComment = getJsDoc(node, sourceFile);
1369
+ const text = [name, signature, docComment].filter(Boolean).join(" | ");
1370
+ symbols.push({
1371
+ id: 0,
1372
+ lang,
1373
+ kind,
1374
+ name,
1375
+ file,
1376
+ line: line + 1,
1377
+ col: character,
1378
+ signature,
1379
+ docComment,
1380
+ scope,
1381
+ text
1382
+ });
1383
+ }
1384
+ ts.forEachChild(node, visit);
1385
+ }
1386
+ visit(sourceFile);
1387
+ const refs = extractRefs(sourceFile);
1388
+ return { file, lang, symbols, refs, mtimeMs: Date.now() };
1389
+ }
1390
+ function extractRefs(sourceFile) {
1391
+ const refs = [];
1392
+ function visit(node) {
1393
+ const pos = node.getStart(sourceFile);
1394
+ const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
1395
+ const lineNum = line + 1;
1396
+ if (ts.isCallExpression(node)) {
1397
+ const expr = node.expression;
1398
+ if (ts.isIdentifier(expr)) {
1399
+ refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
1400
+ }
1401
+ } else if (ts.isPropertyAccessExpression(node)) {
1402
+ if (ts.isIdentifier(node.expression)) {
1403
+ refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
1404
+ }
1405
+ } else if (ts.isTypeReferenceNode(node)) {
1406
+ const name = getTypeName(node.typeName);
1407
+ if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
1408
+ } else if (ts.isHeritageClause(node)) {
1409
+ for (const t of node.types) {
1410
+ const name = getTypeName(t.expression);
1411
+ if (name) refs.push({ fromId: 0, toName: name, callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement", line: lineNum });
1412
+ }
1413
+ } else if (ts.isImportDeclaration(node)) {
1414
+ const moduleName = getModuleName(node);
1415
+ if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
1416
+ }
1417
+ ts.forEachChild(node, visit);
1418
+ }
1419
+ visit(sourceFile);
1420
+ return deduplicateRefs(refs);
1421
+ }
1422
+ function getTypeName(name) {
1423
+ if (ts.isIdentifier(name)) return name.text;
1424
+ if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
1425
+ return "";
1426
+ }
1427
+ function getModuleName(node) {
1428
+ const moduleSpecifier = node.moduleSpecifier;
1429
+ if (ts.isStringLiteral(moduleSpecifier)) return moduleSpecifier.text;
1430
+ return "";
1431
+ }
1432
+ function deduplicateRefs(refs) {
1433
+ const seen = /* @__PURE__ */ new Set();
1434
+ return refs.filter((r) => {
1435
+ const key = `${r.toName}:${r.callType}:${r.line}`;
1436
+ if (seen.has(key)) return false;
1437
+ seen.add(key);
1438
+ return true;
1439
+ });
1440
+ }
1441
+ function detectLang(file) {
1442
+ const idx = file.lastIndexOf(".");
1443
+ if (idx < 0) return null;
1444
+ return extToLang(file.slice(idx));
1445
+ }
1446
+ function parseSymbols2(opts) {
1447
+ const { file, content, lang } = opts;
1448
+ try {
1449
+ return syncGoParse(file, content, lang);
1450
+ } catch {
1451
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
1452
+ }
1453
+ }
1454
+ var GO_PARSE_SCRIPT = `
1455
+ package main
1456
+
1457
+ import (
1458
+ "encoding/json"
1459
+ "fmt"
1460
+ "go/ast"
1461
+ "go/parser"
1462
+ "go/token"
1463
+ "os"
1464
+ "strings"
1465
+ )
1466
+
1467
+ type Sym struct {
1468
+ Name string \`json:"name"\`
1469
+ Kind string \`json:"kind"\`
1470
+ Line int \`json:"line"\`
1471
+ Col int \`json:"col"\`
1472
+ Signature string \`json:"signature"\`
1473
+ Scope string \`json:"scope"\`
1474
+ }
1475
+
1476
+ func main() {
1477
+ if len(os.Args) < 2 {
1478
+ fmt.Print("[]")
1479
+ return
1480
+ }
1481
+ src, err := os.ReadFile(os.Args[1])
1482
+ if err != nil {
1483
+ fmt.Print("[]")
1484
+ return
1485
+ }
1486
+ fset := token.NewFileSet()
1487
+ node, err := parser.ParseFile(fset, os.Args[1], src, 0)
1488
+ if err != nil {
1489
+ fmt.Print("[]")
1490
+ return
1491
+ }
1492
+
1493
+ var syms []Sym
1494
+
1495
+ // Package-level scope
1496
+ pkgScope := node.Name.Name
1497
+
1498
+ // Collect all top-level declarations
1499
+ for _, decl := range node.Decls {
1500
+ switch d := decl.(type) {
1501
+ case *ast.FuncDecl:
1502
+ name := d.Name.Name
1503
+ kind := "function"
1504
+ scope := pkgScope
1505
+ if d.Recv != nil && len(d.Recv.List) > 0 {
1506
+ scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + name
1507
+ kind = "method"
1508
+ } else {
1509
+ scope = pkgScope + "." + name
1510
+ }
1511
+ pos := fset.Position(d.Pos())
1512
+ sig := formatFuncSig(d)
1513
+ syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
1514
+
1515
+ case *ast.GenDecl:
1516
+ for _, spec := range d.Specs {
1517
+ switch s := spec.(type) {
1518
+ case *ast.TypeSpec:
1519
+ name := s.Name.Name
1520
+ pos := fset.Position(s.Pos())
1521
+ sig := "type " + name
1522
+ if s.TypeParams != nil {
1523
+ sig += formatTypeParams(s.TypeParams)
1524
+ }
1525
+ if st, ok := s.Type.(*ast.StructType); ok {
1526
+ sig += " = struct { " + formatFields(st.Fields.List) + " }"
1527
+ } else if it, ok := s.Type.(*ast.InterfaceType); ok {
1528
+ sig += " = interface { " + formatMethods(it.Methods.List) + " }"
1529
+ } else {
1530
+ sig += " = " + formatType(s.Type)
1531
+ }
1532
+ syms = append(syms, Sym{Name: name, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
1533
+
1534
+ case *ast.ValueSpec:
1535
+ for _, n := range s.Names {
1536
+ name := n.Name
1537
+ pos := fset.Position(n.Pos())
1538
+ kind := "var"
1539
+ if d.Tok == token.CONST {
1540
+ kind = "const"
1541
+ }
1542
+ sig := kind + " " + name
1543
+ if s.Type != nil {
1544
+ sig += " " + formatType(s.Type)
1545
+ }
1546
+ syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
1547
+ }
1548
+ }
1549
+ }
1550
+ }
1551
+ }
1552
+
1553
+ data, err := json.Marshal(syms)
1554
+ if err != nil {
1555
+ fmt.Print("[]")
1556
+ return
1557
+ }
1558
+ fmt.Print(string(data))
1559
+ }
1560
+
1561
+ func recvTypeName(t ast.Expr) string {
1562
+ switch v := t.(type) {
1563
+ case *ast.Ident:
1564
+ return v.Name
1565
+ case *ast.StarExpr:
1566
+ return recvTypeName(v.X)
1567
+ default:
1568
+ return "?"
1569
+ }
1570
+ }
1571
+
1572
+ func formatFuncSig(d *ast.FuncDecl) string {
1573
+ scope := ""
1574
+ if d.Recv != nil && len(d.Recv.List) > 0 {
1575
+ scope = "(" + formatFieldList(d.Recv.List) + ") "
1576
+ }
1577
+ scope += formatFuncType(d.Type)
1578
+ return "func " + scope
1579
+ }
1580
+
1581
+ func formatFuncType(f *ast.FuncType) string {
1582
+ params := formatFieldList(f.Params.List)
1583
+ results := ""
1584
+ if f.Results != nil {
1585
+ results = " -> " + formatFieldList(f.Results.List)
1586
+ }
1587
+ return params + results
1588
+ }
1589
+
1590
+ func formatFieldList(fields []*ast.Field) string {
1591
+ if len(fields) == 0 {
1592
+ return "()"
1593
+ }
1594
+ names := make([]string, 0, len(fields))
1595
+ for _, f := range fields {
1596
+ name := ""
1597
+ if len(f.Names) > 0 {
1598
+ name = f.Names[0].Name
1599
+ }
1600
+ t := formatType(f.Type)
1601
+ if name != "" {
1602
+ names = append(names, name+" "+t)
1603
+ } else {
1604
+ names = append(names, t)
1605
+ }
1606
+ }
1607
+ return "(" + strings.Join(names, ", ") + ")"
1608
+ }
1609
+
1610
+ func formatFields(fields []*ast.Field) string {
1611
+ lines := make([]string, 0)
1612
+ for _, f := range fields {
1613
+ name := ""
1614
+ if len(f.Names) > 0 {
1615
+ name = f.Names[0].Name
1616
+ }
1617
+ t := formatType(f.Type)
1618
+ if name != "" {
1619
+ lines = append(lines, name+" "+t)
1620
+ } else {
1621
+ lines = append(lines, t)
1622
+ }
1623
+ }
1624
+ return strings.Join(lines, "; ")
1625
+ }
1626
+
1627
+ func formatMethods(fields []*ast.Field) string {
1628
+ return formatFields(fields)
1629
+ }
1630
+
1631
+ func formatTypeParams(tp *ast.TypeParams) string {
1632
+ if tp == nil || len(tp.List) == 0 {
1633
+ return ""
1634
+ }
1635
+ params := make([]string, len(tp.List))
1636
+ for i, p := range tp.List {
1637
+ if len(p.Names) > 0 {
1638
+ params[i] = p.Names[0].Name
1639
+ } else {
1640
+ params[i] = "T"
1641
+ }
1642
+ }
1643
+ return "[" + strings.Join(params, ", ") + "]"
1644
+ }
1645
+
1646
+ func formatType(t ast.Expr) string {
1647
+ if t == nil {
1648
+ return "?"
1649
+ }
1650
+ switch v := t.(type) {
1651
+ case *ast.Ident:
1652
+ return v.Name
1653
+ case *ast.SelectorExpr:
1654
+ return formatType(v.X) + "." + v.Sel.Name
1655
+ case *ast.StarExpr:
1656
+ return "*" + formatType(v.X)
1657
+ case *ast.ArrayType:
1658
+ if v.Len == nil {
1659
+ return "[]" + formatType(v.Elt)
1660
+ }
1661
+ return "[...]" + formatType(v.Elt)
1662
+ case *ast.MapType:
1663
+ return "map[" + formatType(v.Key) + "]" + formatType(v.Value)
1664
+ case *ast.InterfaceType:
1665
+ return "interface{}"
1666
+ case *ast.StructType:
1667
+ return "struct{}"
1668
+ case *ast.FuncType:
1669
+ return formatFuncType(v)
1670
+ case *ast.ChanType:
1671
+ return "chan " + formatType(v.Value)
1672
+ case *ast.BasicLit:
1673
+ return v.Value
1674
+ default:
1675
+ return "?"
1676
+ }
1677
+ }
1678
+ `;
1679
+ function syncGoParse(filePath, _content, lang) {
1680
+ const tmpDir = path.join(process.env.TEMP ?? "/tmp", "ws-go-parse");
1681
+ try {
1682
+ mkdirSync(tmpDir, { recursive: true });
1683
+ const scriptPath = path.join(tmpDir, "parse.go");
1684
+ writeFileSync(scriptPath, GO_PARSE_SCRIPT, "utf8");
1685
+ let stdout;
1686
+ try {
1687
+ stdout = execSync(`go run "${scriptPath}" "${filePath}"`, {
1688
+ timeout: 15e3,
1689
+ encoding: "utf8",
1690
+ windowsHide: true
1691
+ });
1692
+ } finally {
1693
+ try {
1694
+ unlinkSync(scriptPath);
1695
+ } catch {
1696
+ }
1697
+ }
1698
+ if (!stdout.trim()) {
1699
+ return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
1700
+ }
1701
+ const raw = JSON.parse(stdout.trim());
1702
+ const symbols = raw.map((s) => ({
1703
+ id: 0,
1704
+ lang,
1705
+ kind: s.kind,
1706
+ name: s.name,
1707
+ file: filePath,
1708
+ line: s.line,
1709
+ col: s.col,
1710
+ signature: s.signature ?? "",
1711
+ docComment: "",
1712
+ scope: s.scope ?? "",
1713
+ text: `${s.name} ${s.signature ?? ""}`.trim()
1714
+ }));
1715
+ return { file: filePath, lang, symbols, mtimeMs: Date.now() };
1716
+ } catch {
1717
+ return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
1718
+ }
1719
+ }
1720
+ function parseSymbols3(opts) {
1721
+ const { file, lang } = opts;
1722
+ try {
1723
+ return syncPyParse(file, lang);
1724
+ } catch {
1725
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
1726
+ }
1727
+ }
1728
+ var PY_PARSE_SCRIPT = `import ast, json, sys, os
1729
+
1730
+ def get_name(node):
1731
+ if isinstance(node, ast.Name):
1732
+ return node.id
1733
+ elif isinstance(node, ast.Attribute):
1734
+ return get_name(node.value) + "." + node.attr
1735
+ elif isinstance(node, ast.Subscript):
1736
+ return get_name(node.value)
1737
+ elif isinstance(node, ast.Call):
1738
+ return get_name(node.func)
1739
+ elif isinstance(node, ast.Constant):
1740
+ return str(node.value)
1741
+ return ""
1742
+
1743
+ def get_decorators(node):
1744
+ decs = []
1745
+ for dec in node.decorator_list:
1746
+ decs.append(get_name(dec))
1747
+ return decs
1748
+
1749
+ def get_bases(node):
1750
+ bases = []
1751
+ for base in node.bases:
1752
+ bases.append(get_name(base))
1753
+ return bases
1754
+
1755
+ def get_args(args):
1756
+ parts = []
1757
+ for arg in args.args:
1758
+ parts.append(arg.arg)
1759
+ return ", ".join(parts)
1760
+
1761
+ def get_returns(node):
1762
+ if node.returns is None:
1763
+ return ""
1764
+ return get_name(node.returns)
1765
+
1766
+ class Sym:
1767
+ def __init__(self, name, kind, line, col, signature, scope):
1768
+ self.name = name
1769
+ self.kind = kind
1770
+ self.line = line
1771
+ self.col = col
1772
+ self.signature = signature
1773
+ self.scope = scope
1774
+ def to_dict(self):
1775
+ return {
1776
+ "name": self.name,
1777
+ "kind": self.kind,
1778
+ "line": self.line,
1779
+ "col": self.col,
1780
+ "signature": self.signature,
1781
+ "scope": self.scope,
1782
+ }
1783
+
1784
+ def is_private(name):
1785
+ return name.startswith("__") and not name.endswith("__")
1786
+
1787
+ syms = []
1788
+ errors = []
1789
+
1790
+ try:
1791
+ with open(sys.argv[1], "r", encoding="utf-8") as f:
1792
+ source = f.read()
1793
+ tree = ast.parse(source, filename=sys.argv[1])
1794
+ except Exception as e:
1795
+ errors.append(str(e))
1796
+ print("[]")
1797
+ sys.exit(0)
1798
+
1799
+ # Module-level scope
1800
+ module_scope = os.path.basename(sys.argv[1])[:-3] # strip .py
1801
+
1802
+ class ModuleVisitor(ast.NodeVisitor):
1803
+ def __init__(self):
1804
+ self.scope_stack = [module_scope]
1805
+
1806
+ def visit_ClassDef(self, node):
1807
+ bases = get_bases(node)
1808
+ decs = get_decorators(node)
1809
+ sig = "class " + node.name
1810
+ if bases:
1811
+ sig += "(" + ", ".join(bases) + ")"
1812
+ sig += ": ..."
1813
+ syms.append(Sym(
1814
+ name=node.name,
1815
+ kind="class",
1816
+ line=node.lineno,
1817
+ col=node.col_offset,
1818
+ signature=sig,
1819
+ scope=".".join(self.scope_stack) + "." + node.name,
1820
+ ))
1821
+ self.scope_stack.append(node.name)
1822
+ self.generic_visit(node)
1823
+ self.scope_stack.pop()
1824
+
1825
+ def visit_FunctionDef(self, node):
1826
+ decs = get_decorators(node)
1827
+ args = get_args(node.args)
1828
+ returns = get_returns(node)
1829
+ is_async = isinstance(node, ast.AsyncFunctionDef)
1830
+
1831
+ kind = "function"
1832
+ prefix = "def "
1833
+ if decs:
1834
+ for d in decs:
1835
+ if d.endswith(".staticmethod"):
1836
+ kind = "staticmethod"
1837
+ elif d.endswith(".classmethod"):
1838
+ kind = "classmethod"
1839
+ elif d == "property":
1840
+ kind = "property"
1841
+
1842
+ if is_async:
1843
+ kind = "async_" + kind
1844
+
1845
+ sig = f"{prefix}{node.name}({args})"
1846
+ if returns:
1847
+ sig += f" -> {returns}"
1848
+ scope = ".".join(self.scope_stack) + "." + node.name
1849
+
1850
+ syms.append(Sym(
1851
+ name=node.name,
1852
+ kind=kind,
1853
+ line=node.lineno,
1854
+ col=node.col_offset,
1855
+ signature=sig,
1856
+ scope=scope,
1857
+ ))
1858
+ # Don't descend into function bodies to avoid local symbols
1859
+ # self.generic_visit(node)
1860
+
1861
+ def visit_AsyncFunctionDef(self, node):
1862
+ # Treat as function
1863
+ self.visit_FunctionDef(node)
1864
+
1865
+ def visit_Assign(self, node):
1866
+ for target in node.targets:
1867
+ if isinstance(target, ast.Name):
1868
+ name = target.id
1869
+ if is_private(name):
1870
+ continue
1871
+ # Infer constness from UPPER_CASE naming
1872
+ kind = "const" if name.isupper() else "var"
1873
+ col = target.col_offset if hasattr(target, 'col_offset') else 0
1874
+ syms.append(Sym(
1875
+ name=name,
1876
+ kind=kind,
1877
+ line=node.lineno,
1878
+ col=col,
1879
+ signature=f"{name} = ...",
1880
+ scope=".".join(self.scope_stack),
1881
+ ))
1882
+
1883
+ def visit_AnnAssign(self, node):
1884
+ if isinstance(node.target, ast.Name):
1885
+ name = node.target.id
1886
+ if is_private(name):
1887
+ return
1888
+ kind = "const" if name.isupper() else "var"
1889
+ col = node.target.col_offset if hasattr(node.target, 'col_offset') else 0
1890
+ sig = f"{name}: {get_name(node.annotation)}"
1891
+ if node.value:
1892
+ sig += " = ..."
1893
+ syms.append(Sym(
1894
+ name=name,
1895
+ kind=kind,
1896
+ line=node.lineno,
1897
+ col=col,
1898
+ signature=sig,
1899
+ scope=".".join(self.scope_stack),
1900
+ ))
1901
+
1902
+ def visit_Import(self, node):
1903
+ for alias in node.names:
1904
+ name = alias.asname or alias.name
1905
+ syms.append(Sym(
1906
+ name=name,
1907
+ kind="import",
1908
+ line=node.lineno,
1909
+ col=node.col_offset,
1910
+ signature=f"import {alias.name}",
1911
+ scope=".".join(self.scope_stack),
1912
+ ))
1913
+
1914
+ def visit_ImportFrom(self, node):
1915
+ module = node.module or ""
1916
+ for alias in node.names:
1917
+ name = alias.asname or alias.name
1918
+ syms.append(Sym(
1919
+ name=name,
1920
+ kind="import",
1921
+ line=node.lineno,
1922
+ col=node.col_offset,
1923
+ signature=f"from {module} import {alias.name}",
1924
+ scope=".".join(self.scope_stack),
1925
+ ))
1926
+
1927
+ visitor = ModuleVisitor()
1928
+ visitor.visit(tree)
1929
+
1930
+ print(json.dumps([s.to_dict() for s in syms]))
1931
+ `;
1932
+ function syncPyParse(filePath, lang) {
1933
+ try {
1934
+ const stdout = execSync(`python -c "${PY_PARSE_SCRIPT.replace(/"/g, '\\"')}" "${filePath}"`, {
1935
+ timeout: 15e3,
1936
+ encoding: "utf8",
1937
+ windowsHide: true
1938
+ });
1939
+ if (!stdout.trim()) {
1940
+ return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
1941
+ }
1942
+ const raw = JSON.parse(stdout.trim());
1943
+ const symbols = raw.map((s) => ({
1944
+ id: 0,
1945
+ lang,
1946
+ kind: s.kind,
1947
+ name: s.name,
1948
+ file: filePath,
1949
+ line: s.line,
1950
+ col: s.col,
1951
+ signature: s.signature ?? "",
1952
+ docComment: "",
1953
+ scope: s.scope ?? "",
1954
+ text: `${s.name} ${s.signature ?? ""}`.trim()
1955
+ }));
1956
+ return { file: filePath, lang, symbols, mtimeMs: Date.now() };
1957
+ } catch {
1958
+ return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
1959
+ }
1960
+ }
1961
+ function parseSymbols4(opts) {
1962
+ const { file, content, lang } = opts;
1963
+ const nativeAvailable = checkNativeParser();
1964
+ if (nativeAvailable) {
1965
+ const result = tryNativeParse(file, content);
1966
+ if (result) return result;
1967
+ }
1968
+ return regexParse({ file, content, lang });
1969
+ }
1970
+ function checkNativeParser() {
1971
+ try {
1972
+ execSync("rustc --version", { stdio: "pipe" });
1973
+ const toolsDir = path.join(process.cwd(), "tools");
1974
+ try {
1975
+ execSync("cargo metadata --no-deps --format-version 1 --manifest-path " + path.join(toolsDir, "Cargo.toml"), { stdio: "pipe" });
1976
+ return true;
1977
+ } catch {
1978
+ return false;
1979
+ }
1980
+ } catch {
1981
+ return false;
1982
+ }
1983
+ }
1984
+ function tryNativeParse(file, content) {
1985
+ try {
1986
+ const toolsDir = path.join(process.cwd(), "tools");
1987
+ const crateDir = path.join(toolsDir, "syn-parser");
1988
+ const tmpFile = path.join(crateDir, "src", "input.rs");
1989
+ const { writeFileSync: writeFileSync2 } = __require("node:fs");
1990
+ writeFileSync2(tmpFile, content, "utf8");
1991
+ const result = spawnSync("cargo", ["run", "--manifest-path", path.join(toolsDir, "Cargo.toml")], {
1992
+ cwd: process.cwd(),
1993
+ encoding: "utf8",
1994
+ timeout: 15e3,
1995
+ stdio: ["pipe", "pipe", "pipe"]
1996
+ });
1997
+ if (result.status === 0 && result.stdout) {
1998
+ const symbols = JSON.parse(result.stdout);
1999
+ return {
2000
+ file,
2001
+ lang: "rs",
2002
+ symbols: symbols.map((s) => ({ ...s, id: 0, lang: "rs" })),
2003
+ mtimeMs: Date.now()
2004
+ };
2005
+ }
2006
+ } catch {
2007
+ }
2008
+ return null;
2009
+ }
2010
+ var RS_PATTERNS = [
2011
+ { regex: /fn\s+(\w+)\s*\([^)]*\)/g, kind: "function" },
2012
+ { regex: /struct\s+(\w+)/g, kind: "struct" },
2013
+ { regex: /enum\s+(\w+)/g, kind: "enum" },
2014
+ { regex: /trait\s+(\w+)/g, kind: "trait" },
2015
+ { regex: /impl\s+(?:<[^>]+>)?(\w+)/g, kind: "impl" },
2016
+ { regex: /type\s+(\w+)\s*=/g, kind: "type" },
2017
+ { regex: /const\s+(\w+)/g, kind: "const" },
2018
+ { regex: /static\s+(\w+)/g, kind: "static" },
2019
+ { regex: /mod\s+(\w+)/g, kind: "mod" }
2020
+ ];
2021
+ function regexParse(opts) {
2022
+ const { file, content, lang } = opts;
2023
+ const symbols = [];
2024
+ const lines = content.split("\n");
2025
+ const lineOffsets = [0];
2026
+ for (let i = 0; i < lines.length; i++) {
2027
+ lineOffsets.push(lineOffsets[i] + lines[i].length + 1);
2028
+ }
2029
+ function lineFromOffset(offset) {
2030
+ let lo = 0, hi = lineOffsets.length - 1;
2031
+ while (lo < hi) {
2032
+ const mid = lo + hi + 1 >>> 1;
2033
+ if (lineOffsets[mid] <= offset) lo = mid;
2034
+ else hi = mid - 1;
2035
+ }
2036
+ return lo + 1;
2037
+ }
2038
+ function extractDeclaration(lineIdx, match) {
2039
+ const line = lines[lineIdx] ?? "";
2040
+ return line.trim().slice(0, 500);
2041
+ }
2042
+ for (const pattern of RS_PATTERNS) {
2043
+ pattern.regex.lastIndex = 0;
2044
+ let match;
2045
+ while ((match = pattern.regex.exec(content)) !== null) {
2046
+ const name = match[1];
2047
+ const offset = match.index;
2048
+ const line = lineFromOffset(offset);
2049
+ const col = offset - (lineOffsets[line - 1] ?? 0);
2050
+ const lineIdx = line - 1;
2051
+ const signature = extractDeclaration(lineIdx);
2052
+ symbols.push({
2053
+ id: 0,
2054
+ lang,
2055
+ kind: pattern.kind,
2056
+ name,
2057
+ file,
2058
+ line,
2059
+ col,
2060
+ signature,
2061
+ docComment: "",
2062
+ scope: "",
2063
+ text: `${name} ${signature}`.trim()
2064
+ });
2065
+ }
2066
+ }
2067
+ const seen = /* @__PURE__ */ new Set();
2068
+ const deduped = symbols.filter((s) => {
2069
+ const key = `${s.name}:${s.line}`;
2070
+ if (seen.has(key)) return false;
2071
+ seen.add(key);
2072
+ return true;
2073
+ });
2074
+ return { file, lang, symbols: deduped, mtimeMs: Date.now() };
2075
+ }
2076
+ function parseSymbols5(opts) {
2077
+ const { file, content, lang } = opts;
2078
+ try {
2079
+ return regexParse2({ file, content, lang });
2080
+ } catch {
2081
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
2082
+ }
2083
+ }
2084
+ function regexParse2(opts) {
2085
+ const { file, content, lang } = opts;
2086
+ const symbols = [];
2087
+ const basename2 = path.basename(file).toLowerCase();
2088
+ const isPackageJson = basename2 === "package.json";
2089
+ const isTsconfig = basename2 === "tsconfig.json" || basename2 === "tsconfig.build.json";
2090
+ const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
2091
+ const isOpenApi = content.includes("openapi") || content.includes("swagger");
2092
+ const lines = content.split("\n");
2093
+ const lineOffsets = [0];
2094
+ for (let i = 0; i < lines.length; i++) {
2095
+ lineOffsets.push(lineOffsets[i] + lines[i].length + 1);
2096
+ }
2097
+ function lineFromOffset(offset) {
2098
+ let lo = 0, hi = lineOffsets.length - 1;
2099
+ while (lo < hi) {
2100
+ const mid = lo + hi + 1 >>> 1;
2101
+ if (lineOffsets[mid] <= offset) lo = mid;
2102
+ else hi = mid - 1;
2103
+ }
2104
+ return lo + 1;
2105
+ }
2106
+ const rootMatch = content.match(/^\s*\{/m);
2107
+ if (rootMatch) {
2108
+ const offset = rootMatch.index;
2109
+ const line = lineFromOffset(offset);
2110
+ symbols.push(makeSymbol({
2111
+ name: path.basename(file),
2112
+ kind: "object",
2113
+ line,
2114
+ col: 0,
2115
+ signature: `"${path.basename(file)}" = { ... }`,
2116
+ file,
2117
+ lang
2118
+ }));
2119
+ }
2120
+ const topLevelKeyRegex = /^\s*"([^"]+)"\s*:/gm;
2121
+ let match;
2122
+ while ((match = topLevelKeyRegex.exec(content)) !== null) {
2123
+ const key = match[1];
2124
+ const offset = match.index;
2125
+ const line = lineFromOffset(offset);
2126
+ const col = offset - (lineOffsets[line - 1] ?? 0);
2127
+ let kind = "property";
2128
+ let signature = `"${key}": ..."`;
2129
+ if (isPackageJson) {
2130
+ if (key === "scripts" || key === "dependencies" || key === "devDependencies" || key === "peerDependencies" || key === "optionalDependencies") {
2131
+ kind = "const";
2132
+ signature = `"${key}": { ... }`;
2133
+ }
2134
+ } else if (isTsconfig) {
2135
+ if (key === "compilerOptions") {
2136
+ kind = "property";
2137
+ signature = `"compilerOptions": { ... }`;
2138
+ }
2139
+ }
2140
+ if (isJsonSchema || isOpenApi) {
2141
+ if (key === "$schema" || key === "$id") {
2142
+ kind = "schema";
2143
+ signature = `"${key}": "..."`;
2144
+ } else if (key === "$ref") {
2145
+ kind = "schema";
2146
+ signature = `"$ref": "..."`;
2147
+ }
2148
+ }
2149
+ symbols.push(makeSymbol({
2150
+ name: key,
2151
+ kind,
2152
+ line,
2153
+ col,
2154
+ signature,
2155
+ file,
2156
+ lang
2157
+ }));
2158
+ if (isPackageJson && key === "scripts") {
2159
+ extractPackageScripts(content, symbols, file, lang, lineOffsets, lineFromOffset);
2160
+ }
2161
+ if (isTsconfig && key === "compilerOptions") {
2162
+ extractCompilerOptions(content, symbols, file, lang, lineOffsets, line, lineFromOffset);
2163
+ }
2164
+ }
2165
+ const defsRegex = /"\$defs"\s*:|"\$defs"\s*:/g;
2166
+ let defsMatch;
2167
+ while ((defsMatch = defsRegex.exec(content)) !== null) {
2168
+ const offset = defsMatch.index;
2169
+ const line = lineFromOffset(offset);
2170
+ symbols.push(makeSymbol({
2171
+ name: "$defs",
2172
+ kind: "property",
2173
+ line,
2174
+ col: offset - (lineOffsets[line - 1] ?? 0),
2175
+ signature: '"$defs": { ... }',
2176
+ file,
2177
+ lang
2178
+ }));
2179
+ break;
2180
+ }
2181
+ const defsPatterns = [
2182
+ /"\$defs"\s*:/g,
2183
+ /"definitions"\s*:/g,
2184
+ /"components"\s*:/g,
2185
+ /"schemas"\s*:/g
2186
+ ];
2187
+ for (const pat of defsPatterns) {
2188
+ pat.lastIndex = 0;
2189
+ while ((match = pat.exec(content)) !== null) {
2190
+ const offset = match.index;
2191
+ const line = lineFromOffset(offset);
2192
+ const key = match[0].match(/"([^"]+)"/)?.[1] ?? match[0];
2193
+ symbols.push(makeSymbol({
2194
+ name: key,
2195
+ kind: "property",
2196
+ line,
2197
+ col: offset - (lineOffsets[line - 1] ?? 0),
2198
+ signature: `"${key}": { ... }`,
2199
+ file,
2200
+ lang
2201
+ }));
2202
+ }
2203
+ }
2204
+ return { file, lang, symbols, mtimeMs: Date.now() };
2205
+ }
2206
+ function extractPackageScripts(content, symbols, file, lang, lineOffsets, lineFromOffset) {
2207
+ const scriptsBlockRegex = /"scripts"\s*:\s*\{([^}]+)\}/g;
2208
+ let match;
2209
+ while ((match = scriptsBlockRegex.exec(content)) !== null) {
2210
+ const blockContent = match[0];
2211
+ const blockOffset = match.index;
2212
+ const scriptKeyRegex = /"(\w[\w-]*)"\s*:/g;
2213
+ let scriptMatch;
2214
+ while ((scriptMatch = scriptKeyRegex.exec(blockContent)) !== null) {
2215
+ const key = scriptMatch[1];
2216
+ const keyOffset = blockOffset + scriptMatch.index;
2217
+ const line = lineFromOffset(keyOffset);
2218
+ symbols.push(makeSymbol({
2219
+ name: key,
2220
+ kind: "function",
2221
+ line,
2222
+ col: keyOffset - (lineOffsets[line - 1] ?? 0),
2223
+ signature: `"${key}": "..."`,
2224
+ file,
2225
+ lang
2226
+ }));
2227
+ }
2228
+ }
2229
+ }
2230
+ function extractCompilerOptions(content, symbols, file, lang, lineOffsets, parentLine, lineFromOffset) {
2231
+ const optsBlockRegex = /"compilerOptions"\s*:\s*\{([^}]+)\}/g;
2232
+ let match;
2233
+ while ((match = optsBlockRegex.exec(content)) !== null) {
2234
+ const blockContent = match[0];
2235
+ const blockOffset = match.index;
2236
+ const optKeyRegex = /"(\w[\w]*)"\s*:/g;
2237
+ let optMatch;
2238
+ while ((optMatch = optKeyRegex.exec(blockContent)) !== null) {
2239
+ const key = optMatch[1];
2240
+ const keyOffset = blockOffset + optMatch.index;
2241
+ const line = lineFromOffset(keyOffset);
2242
+ if (line <= parentLine) continue;
2243
+ symbols.push(makeSymbol({
2244
+ name: key,
2245
+ kind: "property",
2246
+ line,
2247
+ col: keyOffset - (lineOffsets[line - 1] ?? 0),
2248
+ signature: `"${key}": ...`,
2249
+ file,
2250
+ lang
2251
+ }));
2252
+ }
2253
+ }
2254
+ }
2255
+ function makeSymbol(opts) {
2256
+ return {
2257
+ id: 0,
2258
+ lang: opts.lang,
2259
+ kind: opts.kind,
2260
+ name: opts.name,
2261
+ file: opts.file,
2262
+ line: opts.line,
2263
+ col: opts.col,
2264
+ signature: opts.signature,
2265
+ docComment: "",
2266
+ scope: "",
2267
+ text: `${opts.name} ${opts.signature}`.trim()
2268
+ };
2269
+ }
2270
+
2271
+ // src/codebase-index/yaml-parser.ts
2272
+ function parseSymbols6(opts) {
2273
+ const { file, content, lang } = opts;
2274
+ try {
2275
+ return regexParse3({ file, content, lang });
2276
+ } catch {
2277
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
2278
+ }
2279
+ }
2280
+ function regexParse3(opts) {
2281
+ const { file, content, lang } = opts;
2282
+ const symbols = [];
2283
+ const lines = content.split("\n");
2284
+ const lineOffsets = [0];
2285
+ for (let i = 0; i < lines.length; i++) {
2286
+ lineOffsets.push(lineOffsets[i] + lines[i].length + 1);
2287
+ }
2288
+ function lineFromOffset(offset) {
2289
+ let lo = 0, hi = lineOffsets.length - 1;
2290
+ while (lo < hi) {
2291
+ const mid = lo + hi + 1 >>> 1;
2292
+ if (lineOffsets[mid] <= offset) lo = mid;
2293
+ else hi = mid - 1;
2294
+ }
2295
+ return lo + 1;
2296
+ }
2297
+ const anchorRegex = /&(\w[\w-]*)/g;
2298
+ let match;
2299
+ while ((match = anchorRegex.exec(content)) !== null) {
2300
+ const name = match[1];
2301
+ const offset = match.index;
2302
+ const line = lineFromOffset(offset);
2303
+ const col = offset - (lineOffsets[line - 1] ?? 0);
2304
+ symbols.push(makeSymbol2({
2305
+ name,
2306
+ kind: "const",
2307
+ line,
2308
+ col,
2309
+ signature: `&${name}`,
2310
+ file,
2311
+ lang
2312
+ }));
2313
+ }
2314
+ const aliasRegex = /\*(\w[\w-]*)/g;
2315
+ while ((match = aliasRegex.exec(content)) !== null) {
2316
+ const name = match[1];
2317
+ const offset = match.index;
2318
+ const line = lineFromOffset(offset);
2319
+ const col = offset - (lineOffsets[line - 1] ?? 0);
2320
+ symbols.push(makeSymbol2({
2321
+ name,
2322
+ kind: "const",
2323
+ line,
2324
+ col,
2325
+ signature: `*${name}`,
2326
+ file,
2327
+ lang
2328
+ }));
2329
+ }
2330
+ const kvRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:/gm;
2331
+ while ((match = kvRegex.exec(content)) !== null) {
2332
+ const indent = match[1].length;
2333
+ const key = match[2];
2334
+ const offset = match.index;
2335
+ const line = lineFromOffset(offset);
2336
+ const col = offset - (lineOffsets[line - 1] ?? 0);
2337
+ const lineContent = lines[line - 1] ?? "";
2338
+ if (/^[|&>]/.test(lineContent.trim())) continue;
2339
+ if (key === "---" || key === "...") continue;
2340
+ if (indent > 12) continue;
2341
+ const value = extractValue(content, match.index);
2342
+ const kind = isScalar(value) ? "literal" : "property";
2343
+ const signature = `${key}: ${truncate(value, 60)}`;
2344
+ symbols.push(makeSymbol2({ name: key, kind, line, col, signature, file, lang }));
2345
+ }
2346
+ const listItemRegex = /^-(\s+)([^:#\s][^:#\s]*)\s*:/gm;
2347
+ while ((match = listItemRegex.exec(content)) !== null) {
2348
+ const key = match[2];
2349
+ const offset = match.index;
2350
+ const line = lineFromOffset(offset);
2351
+ const col = offset - (lineOffsets[line - 1] ?? 0);
2352
+ const value = extractValue(content, offset + match[0].length);
2353
+ const kind = isScalar(value) ? "literal" : "property";
2354
+ symbols.push(makeSymbol2({
2355
+ name: key,
2356
+ kind,
2357
+ line,
2358
+ col,
2359
+ signature: `- ${key}: ${truncate(value, 60)}`,
2360
+ file,
2361
+ lang
2362
+ }));
2363
+ }
2364
+ const blockScalarRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:\s*[|>](\s|$)/gm;
2365
+ while ((match = blockScalarRegex.exec(content)) !== null) {
2366
+ const key = match[2];
2367
+ const offset = match.index;
2368
+ const line = lineFromOffset(offset);
2369
+ const col = offset - (lineOffsets[line - 1] ?? 0);
2370
+ symbols.push(makeSymbol2({
2371
+ name: key,
2372
+ kind: "property",
2373
+ line,
2374
+ col,
2375
+ signature: `${key}: | ...`,
2376
+ file,
2377
+ lang
2378
+ }));
2379
+ }
2380
+ return { file, lang, symbols, mtimeMs: Date.now() };
2381
+ }
2382
+ function extractValue(content, afterColonOffset) {
2383
+ const lineEnd = content.indexOf("\n", afterColonOffset);
2384
+ const rest = content.slice(afterColonOffset, lineEnd < 0 ? void 0 : lineEnd);
2385
+ return rest.trim();
2386
+ }
2387
+ function isScalar(value) {
2388
+ if (!value) return false;
2389
+ if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(value)) return true;
2390
+ if (/^(true|false|null|undefined)$/i.test(value)) return true;
2391
+ if (/^'[^']*'$/.test(value) || /^"[^"]*"$/.test(value)) return true;
2392
+ return false;
2393
+ }
2394
+ function truncate(s, max) {
2395
+ if (s.length <= max) return s;
2396
+ return s.slice(0, max) + "...";
2397
+ }
2398
+ function makeSymbol2(opts) {
2399
+ return {
2400
+ id: 0,
2401
+ lang: opts.lang,
2402
+ kind: opts.kind,
2403
+ name: opts.name,
2404
+ file: opts.file,
2405
+ line: opts.line,
2406
+ col: opts.col,
2407
+ signature: opts.signature,
2408
+ docComment: "",
2409
+ scope: "",
2410
+ text: `${opts.name} ${opts.signature}`.trim()
2411
+ };
2412
+ }
2413
+
2414
+ // src/codebase-index/indexer.ts
2415
+ var DEFAULT_IGNORE = [
2416
+ "node_modules",
2417
+ ".git",
2418
+ "dist",
2419
+ "build",
2420
+ ".next",
2421
+ "coverage",
2422
+ ".turbo",
2423
+ "__snapshots__",
2424
+ ".nyc_output"
2425
+ ];
2426
+ async function findSourceFiles(projectRoot, ignore) {
2427
+ const results = [];
2428
+ const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
2429
+ const globs = [
2430
+ { ext: ".ts", pat: compileGlob("**/*.ts") },
2431
+ { ext: ".tsx", pat: compileGlob("**/*.tsx") },
2432
+ { ext: ".js", pat: compileGlob("**/*.js") },
2433
+ { ext: ".jsx", pat: compileGlob("**/*.jsx") },
2434
+ { ext: ".go", pat: compileGlob("**/*.go") },
2435
+ { ext: ".py", pat: compileGlob("**/*.py") },
2436
+ { ext: ".rs", pat: compileGlob("**/*.rs") },
2437
+ { ext: ".json", pat: compileGlob("**/*.json") },
2438
+ { ext: ".yaml", pat: compileGlob("**/*.yaml") },
2439
+ { ext: ".yml", pat: compileGlob("**/*.yml") }
2440
+ ];
2441
+ const walk = async (dir) => {
2442
+ let entries;
2443
+ try {
2444
+ entries = await fs11.readdir(dir, { withFileTypes: true });
2445
+ } catch {
2446
+ return;
2447
+ }
2448
+ for (const e of entries) {
2449
+ if (ignoreSet.has(e.name)) continue;
2450
+ const full = path.join(dir, e.name);
2451
+ if (e.isDirectory()) {
2452
+ await walk(full);
2453
+ } else if (e.isFile()) {
2454
+ const rel = path.relative(projectRoot, full).replace(/\\/g, "/");
2455
+ const ext = path.extname(e.name);
2456
+ for (const { ext: extName, pat } of globs) {
2457
+ if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
2458
+ results.push(full);
2459
+ break;
2460
+ }
2461
+ }
2462
+ }
2463
+ }
2464
+ };
2465
+ await walk(projectRoot);
2466
+ return results;
2467
+ }
2468
+ async function parseFile(file, content, lang) {
2469
+ switch (lang) {
2470
+ case "ts":
2471
+ case "tsx":
2472
+ case "js":
2473
+ case "jsx":
2474
+ return parseSymbols({ file, content, lang });
2475
+ case "go":
2476
+ return parseSymbols2({ file, content, lang: "go" });
2477
+ case "py":
2478
+ return parseSymbols3({ file, lang: "py" });
2479
+ case "rs":
2480
+ return parseSymbols4({ file, content, lang: "rs" });
2481
+ case "json":
2482
+ return parseSymbols5({ file, content, lang: "json" });
2483
+ case "yaml":
2484
+ return parseSymbols6({ file, content, lang: "yaml" });
2485
+ default:
2486
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
2487
+ }
2488
+ }
2489
+ async function runIndexer(ctx, opts) {
2490
+ const { projectRoot, force = false, langs, ignore = [] } = opts;
2491
+ const store = new IndexStore(projectRoot);
2492
+ const startMs = Date.now();
2493
+ const errors = [];
2494
+ const langStats = {};
2495
+ let filesIndexed = 0;
2496
+ let symbolsIndexed = 0;
2497
+ let files;
2498
+ if (opts.files && opts.files.length > 0) {
2499
+ files = opts.files.map((f) => path.resolve(projectRoot, f));
2500
+ } else {
2501
+ files = await findSourceFiles(projectRoot, ignore);
2502
+ }
2503
+ if (langs && langs.length > 0) {
2504
+ const langSet = new Set(langs);
2505
+ files = files.filter((f) => {
2506
+ const lang = detectLang(f);
2507
+ return lang ? langSet.has(lang) : false;
2508
+ });
2509
+ }
2510
+ if (force) store.clearAll();
2511
+ const existingMeta = /* @__PURE__ */ new Map();
2512
+ if (!force) {
2513
+ for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
2514
+ }
2515
+ for (const file of files) {
2516
+ let stat11;
2517
+ try {
2518
+ stat11 = await fs11.stat(file);
2519
+ } catch {
2520
+ store.deleteFile(file);
2521
+ continue;
2522
+ }
2523
+ if (!stat11.isFile()) continue;
2524
+ const lang = detectLang(file);
2525
+ if (!lang) continue;
2526
+ const meta = existingMeta.get(file);
2527
+ if (!force && meta && meta.mtimeMs === Math.floor(stat11.mtimeMs)) {
2528
+ langStats[lang] = (langStats[lang] ?? 0) + meta.symbolCount;
2529
+ symbolsIndexed += meta.symbolCount;
2530
+ filesIndexed++;
2531
+ continue;
2532
+ }
2533
+ store.deleteSymbolsForFile(file);
2534
+ store.deleteRefsForFile(file);
2535
+ let content;
2536
+ try {
2537
+ content = await fs11.readFile(file, "utf8");
2538
+ } catch (e) {
2539
+ errors.push(`read error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
2540
+ continue;
2541
+ }
2542
+ let parsed;
2543
+ try {
2544
+ parsed = await parseFile(file, content, lang);
2545
+ } catch (e) {
2546
+ errors.push(`parse error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
2547
+ continue;
2548
+ }
2549
+ if (parsed.symbols.length === 0) {
2550
+ store.upsertFile({
2551
+ file,
2552
+ lang,
2553
+ mtimeMs: Math.floor(stat11.mtimeMs),
2554
+ symbolCount: 0,
2555
+ lastIndexed: Date.now()
2556
+ });
2557
+ filesIndexed++;
2558
+ continue;
2559
+ }
2560
+ const nextId = store.getStats().totalSymbols + 1;
2561
+ const symbolsWithIds = parsed.symbols.map((s, i) => ({ ...s, id: nextId + i }));
2562
+ store.insertSymbols(symbolsWithIds, nextId);
2563
+ const count = symbolsWithIds.length;
2564
+ symbolsIndexed += count;
2565
+ langStats[lang] = (langStats[lang] ?? 0) + count;
2566
+ if (parsed.refs && parsed.refs.length > 0) {
2567
+ for (let i = 0; i < symbolsWithIds.length; i++) {
2568
+ const sym = symbolsWithIds[i];
2569
+ const symRefs = parsed.refs.filter((r) => r.line === sym.line);
2570
+ if (symRefs.length > 0) {
2571
+ const refsWithFromId = symRefs.map((r) => ({ ...r, fromId: sym.id }));
2572
+ store.insertRefs(sym.id, refsWithFromId);
2573
+ }
2574
+ }
2575
+ }
2576
+ store.upsertFile({
2577
+ file,
2578
+ lang,
2579
+ mtimeMs: Math.floor(stat11.mtimeMs),
2580
+ symbolCount: count,
2581
+ lastIndexed: Date.now()
2582
+ });
2583
+ filesIndexed++;
2584
+ }
2585
+ for (const [file_] of existingMeta) {
2586
+ try {
2587
+ await fs11.stat(file_);
2588
+ } catch {
2589
+ store.deleteFile(file_);
2590
+ }
2591
+ }
2592
+ const durationMs = Date.now() - startMs;
2593
+ store.setLastIndexed(Date.now());
2594
+ store.close();
2595
+ return {
2596
+ filesIndexed,
2597
+ symbolsIndexed,
2598
+ langStats,
2599
+ durationMs,
2600
+ errors
2601
+ };
2602
+ }
2603
+
2604
+ // src/codebase-index/codebase-index-tool.ts
2605
+ var codebaseIndexTool = {
2606
+ name: "codebase-index",
2607
+ category: "Project",
2608
+ 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.",
2609
+ 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.",
2610
+ permission: "auto",
2611
+ mutating: true,
2612
+ timeoutMs: 12e4,
2613
+ inputSchema: {
2614
+ type: "object",
2615
+ properties: {
2616
+ force: {
2617
+ type: "boolean",
2618
+ description: "Force a full reindex \u2014 clears the index first and reindexes all files."
2619
+ },
2620
+ langs: {
2621
+ type: "array",
2622
+ items: { type: "string" },
2623
+ description: "Limit reindex to specific languages: ts, tsx, js, jsx, go, py, rs"
2624
+ }
2625
+ }
2626
+ },
2627
+ async execute(input, ctx) {
2628
+ const result = await runIndexer(ctx, {
2629
+ projectRoot: ctx.projectRoot,
2630
+ force: input.force ?? false,
2631
+ langs: input.langs
2632
+ });
2633
+ return result;
2634
+ }
2635
+ };
2636
+
2637
+ // src/codebase-index/bm25.ts
2638
+ var K1 = 1.5;
2639
+ var B = 0.75;
2640
+ function tokenise(text) {
2641
+ const sanitised = text.replace(/[^\p{L}\p{N}$'_]/gu, " ").replace(/_/g, " ");
2642
+ return sanitised.toLowerCase().split(" ").filter(Boolean);
2643
+ }
2644
+ function splitName(name) {
2645
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_\-]+/g, " ").trim();
2646
+ }
2647
+ function buildIndexableText(name, signature, docComment) {
2648
+ return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");
2649
+ }
2650
+ function buildBm25Index(docs) {
2651
+ const documents = docs.map((d) => {
2652
+ const tokens = tokenise(d.text);
2653
+ return { id: d.id, tokens, raw: d.text, len: tokens.length };
2654
+ });
2655
+ const df = {};
2656
+ for (const doc of documents) {
2657
+ const seen = /* @__PURE__ */ new Set();
2658
+ for (const t of doc.tokens) {
2659
+ if (!seen.has(t)) {
2660
+ df[t] = (df[t] ?? 0) + 1;
2661
+ seen.add(t);
2662
+ }
2663
+ }
2664
+ }
2665
+ const N = documents.length;
2666
+ const totalLen = documents.reduce((sum, d) => sum + d.len, 0);
2667
+ const avgLen = N === 0 ? 0 : totalLen / N;
2668
+ return new Bm25Index(documents, df, N, avgLen);
2669
+ }
2670
+ var Bm25Index = class {
2671
+ constructor(documents, df, N, avgLen) {
2672
+ this.documents = documents;
2673
+ this.df = df;
2674
+ this.N = N;
2675
+ this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
2676
+ }
2677
+ documents;
2678
+ df;
2679
+ N;
2680
+ safeAvgLen;
2681
+ score(query2, filter) {
2682
+ const qTokens = tokenise(query2);
2683
+ if (qTokens.length === 0) return [];
2684
+ const results = [];
2685
+ for (const doc of this.documents) {
2686
+ if (filter && !filter(doc.id)) continue;
2687
+ let docScore = 0;
2688
+ for (const qTerm of qTokens) {
2689
+ let tf = 0;
2690
+ for (const t of doc.tokens) {
2691
+ if (t === qTerm) tf++;
2692
+ }
2693
+ if (tf === 0) continue;
2694
+ const dfVal = this.df[qTerm] ?? 0;
2695
+ if (dfVal === 0) continue;
2696
+ const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
2697
+ const lenRatio = B * (doc.len / this.safeAvgLen);
2698
+ const tfComponent = tf * (K1 + 1) / (tf + K1 * (1 - B + lenRatio));
2699
+ docScore += idf * tfComponent;
2700
+ }
2701
+ if (docScore > 0) results.push({ id: doc.id, score: docScore });
2702
+ }
2703
+ return results;
2704
+ }
2705
+ getDoc(id) {
2706
+ return this.documents.find((d) => d.id === id);
2707
+ }
2708
+ extractSnippet(docId, queryTokens, radius = 40) {
2709
+ const doc = this.getDoc(docId);
2710
+ if (!doc) return "";
2711
+ for (const tok of queryTokens) {
2712
+ const idx = doc.raw.toLowerCase().indexOf(tok);
2713
+ if (idx !== -1) {
2714
+ const start = Math.max(0, idx - radius);
2715
+ const end = Math.min(doc.raw.length, idx + tok.length + radius);
2716
+ const excerpt = doc.raw.slice(start, end);
2717
+ const ellipsis = "\u2026";
2718
+ return (start > 0 ? ellipsis : "") + excerpt + (end < doc.raw.length ? ellipsis : "");
2719
+ }
2720
+ }
2721
+ return doc.raw.slice(0, radius * 2) + (doc.raw.length > radius * 2 ? "\u2026" : "");
2722
+ }
2723
+ };
2724
+
2725
+ // src/codebase-index/codebase-search-tool.ts
2726
+ var codebaseSearchTool = {
2727
+ name: "codebase-search",
2728
+ category: "Project",
2729
+ description: "Search indexed code symbols by name, signature, or doc comment. Uses BM25 ranking for relevance.",
2730
+ usageHint: "Pass `query` for keyword search. Filter with `kind` (class/function/interface/etc), `lang` (ts/js/etc), `file` (substring). `limit` caps results (default 20).",
2731
+ permission: "auto",
2732
+ mutating: false,
2733
+ timeoutMs: 1e4,
2734
+ inputSchema: {
2735
+ type: "object",
2736
+ properties: {
2737
+ query: {
2738
+ type: "string",
2739
+ description: "Search query \u2014 searches symbol names, signatures, and doc comments"
2740
+ },
2741
+ kind: {
2742
+ type: "string",
2743
+ description: "Filter by symbol kind: class, function, interface, method, const, let, var, property, type, enum"
2744
+ },
2745
+ lang: {
2746
+ type: "string",
2747
+ description: "Filter by language: ts, tsx, js, jsx"
2748
+ },
2749
+ lspKind: {
2750
+ type: "integer",
2751
+ description: "Filter by LSP SymbolKind number (e.g. 5=Class, 12=Function, 11=Interface, 10=Enum)"
2752
+ },
2753
+ file: {
2754
+ type: "string",
2755
+ description: "Filter to files matching this path substring"
2756
+ },
2757
+ limit: {
2758
+ type: "integer",
2759
+ description: "Maximum results to return (default 20, max 100)",
2760
+ minimum: 1,
2761
+ maximum: 100
2762
+ }
2763
+ },
2764
+ required: ["query"]
2765
+ },
2766
+ async execute(input, ctx) {
2767
+ const store = new IndexStore(ctx.projectRoot);
2768
+ try {
2769
+ const limit = Math.min(input.limit ?? 20, 100);
2770
+ const candidates = store.search(input.query, {
2771
+ kind: input.kind,
2772
+ lang: input.lang,
2773
+ file: input.file,
2774
+ lspKind: input.lspKind
2775
+ });
2776
+ if (candidates.length === 0) {
2777
+ return { results: [], total: 0, query: input.query };
2778
+ }
2779
+ const indexable = candidates.map((c) => ({
2780
+ id: c.id,
2781
+ text: buildIndexableText(c.name, c.signature, c.docComment)
2782
+ }));
2783
+ const bm25 = buildBm25Index(indexable);
2784
+ const scored = bm25.score(input.query, (id) => candidates.some((c) => c.id === id));
2785
+ scored.sort((a, b) => b.score - a.score);
2786
+ const top = scored.slice(0, limit);
2787
+ const qTokens = tokenise(input.query);
2788
+ const results = top.map(({ id, score }) => {
2789
+ const c = candidates.find((c2) => c2.id === id);
2790
+ const snippet = bm25.extractSnippet(id, qTokens);
2791
+ return {
2792
+ ...c,
2793
+ score,
2794
+ snippet
2795
+ };
2796
+ });
2797
+ return {
2798
+ results,
2799
+ total: candidates.length,
2800
+ query: input.query
2801
+ };
2802
+ } finally {
2803
+ store.close();
2804
+ }
2805
+ }
2806
+ };
2807
+
2808
+ // src/codebase-index/codebase-stats-tool.ts
2809
+ var codebaseStatsTool = {
2810
+ name: "codebase-stats",
2811
+ category: "Project",
2812
+ description: "Return statistics about the symbol index: total symbols, files, breakdown by language and kind, index size, and last update time.",
2813
+ usageHint: "No arguments needed. Use to check if the index is stale or healthy before running a search.",
2814
+ permission: "auto",
2815
+ mutating: false,
2816
+ timeoutMs: 5e3,
2817
+ inputSchema: {
2818
+ type: "object",
2819
+ properties: {},
2820
+ additionalProperties: false
2821
+ },
2822
+ async execute(_input, ctx) {
2823
+ const store = new IndexStore(ctx.projectRoot);
2824
+ try {
2825
+ const stats = store.getStats();
2826
+ return {
2827
+ totalSymbols: stats.totalSymbols,
2828
+ totalFiles: stats.totalFiles,
2829
+ byLang: stats.byLang,
2830
+ byKind: stats.byKind,
2831
+ lastIndexed: stats.lastIndexed,
2832
+ sizeBytes: stats.sizeBytes,
2833
+ indexPath: stats.indexPath,
2834
+ version: stats.version
2835
+ };
2836
+ } finally {
2837
+ store.close();
2838
+ }
2839
+ }
2840
+ };
907
2841
  var diffTool = {
908
2842
  name: "diff",
909
2843
  category: "Filesystem",
@@ -963,8 +2897,8 @@ function findGitDir(cwd) {
963
2897
  let dir = cwd;
964
2898
  for (let i = 0; i < 20; i++) {
965
2899
  try {
966
- const stat10 = statSync(path.join(dir, ".git"));
967
- if (stat10.isDirectory()) return dir;
2900
+ const stat11 = statSync(path.join(dir, ".git"));
2901
+ if (stat11.isDirectory()) return dir;
968
2902
  } catch {
969
2903
  }
970
2904
  const parent = path.dirname(dir);
@@ -974,7 +2908,7 @@ function findGitDir(cwd) {
974
2908
  return null;
975
2909
  }
976
2910
  function runGit(args, cwd, signal) {
977
- return new Promise((resolve5) => {
2911
+ return new Promise((resolve6) => {
978
2912
  let stdout = "";
979
2913
  let stderr = "";
980
2914
  const child = spawn("git", args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
@@ -984,8 +2918,8 @@ function runGit(args, cwd, signal) {
984
2918
  child.stderr?.on("data", (c) => {
985
2919
  stderr += c.toString();
986
2920
  });
987
- child.on("close", (code) => resolve5({ stdout, stderr, exitCode: code ?? 0 }));
988
- child.on("error", (e) => resolve5({ stdout: "", stderr: e.message, exitCode: 1 }));
2921
+ child.on("close", (code) => resolve6({ stdout, stderr, exitCode: code ?? 0 }));
2922
+ child.on("error", (e) => resolve6({ stdout: "", stderr: e.message, exitCode: 1 }));
989
2923
  });
990
2924
  }
991
2925
  async function fileDiff(input, ctx, signal) {
@@ -1003,9 +2937,9 @@ async function fileDiff(input, ctx, signal) {
1003
2937
  const results = [];
1004
2938
  for (const file of files) {
1005
2939
  const absPath = safeResolve(file, ctx);
1006
- const stat10 = await fs9.stat(absPath).catch(() => null);
1007
- if (!stat10?.isFile()) continue;
1008
- const content = await fs9.readFile(absPath, "utf8");
2940
+ const stat11 = await fs11.stat(absPath).catch(() => null);
2941
+ if (!stat11?.isFile()) continue;
2942
+ const content = await fs11.readFile(absPath, "utf8");
1009
2943
  const lines = content.split(/\r?\n/);
1010
2944
  results.push(`--- ${file}
1011
2945
  +++ ${file}
@@ -1066,7 +3000,7 @@ var documentTool = {
1066
3000
  const fileList = input.files ? await resolveFiles(Array.isArray(input.files) ? input.files.join(",") : input.files, cwd) : input.path ? [safeResolve(input.path, ctx)] : [];
1067
3001
  for (const absPath of fileList) {
1068
3002
  try {
1069
- const content = await fs9.readFile(absPath, "utf8");
3003
+ const content = await fs11.readFile(absPath, "utf8");
1070
3004
  filesProcessed++;
1071
3005
  const processed = processFile(
1072
3006
  content,
@@ -1102,8 +3036,8 @@ async function resolveFiles(filesInput, cwd) {
1102
3036
  for (const f of files) {
1103
3037
  const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
1104
3038
  try {
1105
- const stat10 = await fs9.stat(absPath);
1106
- if (stat10.isFile()) resolved.push(absPath);
3039
+ const stat11 = await fs11.stat(absPath);
3040
+ if (stat11.isFile()) resolved.push(absPath);
1107
3041
  } catch {
1108
3042
  }
1109
3043
  }
@@ -1194,18 +3128,18 @@ var editTool = {
1194
3128
  if (input.new_string === void 0) throw new Error("edit: new_string is required");
1195
3129
  if (input.old_string === "") throw new Error("edit: old_string cannot be empty");
1196
3130
  const absPath = safeResolve(input.path, ctx);
1197
- const stat10 = await fs9.stat(absPath).catch((err) => {
3131
+ const stat11 = await fs11.stat(absPath).catch((err) => {
1198
3132
  if (err.code === "ENOENT") {
1199
3133
  throw new Error(`edit: file "${input.path}" does not exist. Use \`write\` instead.`);
1200
3134
  }
1201
3135
  throw err;
1202
3136
  });
1203
- if (!stat10.isFile()) throw new Error(`edit: "${input.path}" is not a regular file`);
3137
+ if (!stat11.isFile()) throw new Error(`edit: "${input.path}" is not a regular file`);
1204
3138
  if (!ctx.hasRead(absPath)) {
1205
3139
  throw new Error(`edit: file "${input.path}" was not read in this session. Read it first.`);
1206
3140
  }
1207
- const original = await fs9.readFile(absPath, "utf8");
1208
- const updated = await fs9.stat(absPath);
3141
+ const original = await fs11.readFile(absPath, "utf8");
3142
+ const updated = await fs11.stat(absPath);
1209
3143
  const mtimeTolerance = process.platform === "win32" ? 2e3 : 1;
1210
3144
  const lastReadMtime = ctx.lastReadMtime(absPath);
1211
3145
  if (lastReadMtime !== void 0 && updated.mtimeMs > lastReadMtime + mtimeTolerance) {
@@ -1350,8 +3284,8 @@ var BLOCKED_ARG_PATTERNS = {
1350
3284
  docker: [/^build$/, /^run$/, /^exec$/, /^push$/, /^pull$/],
1351
3285
  // find -exec/-ok/-execdir execute arbitrary commands
1352
3286
  find: [/^-exec$/, /^-exec;$/, /^-ok$/, /^-ok;$/, /^-execdir$/, /^-execdir;$/, /^-exec=/, /^-ok=/, /^-execdir=/],
1353
- // rm -rf / is catastrophic — block root and home targets
1354
- rm: [/^\/$/, /^\/\*$/, /^~$/],
3287
+ // rm -rf / is catastrophic — block absolute paths, home, and dot-dir targets
3288
+ rm: [/^\//, /^~/, /^\.{1,2}$/],
1355
3289
  // npm run/exec/create/pack/publish can execute arbitrary scripts or publish malware
1356
3290
  npm: [/^run$/, /^exec$/, /^create$/, /^init$/, /^pack$/, /^publish$/, /^deploy$/],
1357
3291
  // pnpm run/dlx/exec/create can execute arbitrary scripts
@@ -1458,7 +3392,7 @@ var execTool = {
1458
3392
  }
1459
3393
  };
1460
3394
  function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1461
- return new Promise((resolve5) => {
3395
+ return new Promise((resolve6) => {
1462
3396
  let stdout = "";
1463
3397
  let stderr = "";
1464
3398
  let killed = false;
@@ -1492,7 +3426,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1492
3426
  const durationMs = Date.now() - startedAt;
1493
3427
  const exitCode = killed ? 124 : code ?? 1;
1494
3428
  registry.afterCall(durationMs, exitCode !== 0);
1495
- resolve5({
3429
+ resolve6({
1496
3430
  command: cmd,
1497
3431
  args,
1498
3432
  stdout: stdout.slice(0, MAX_OUTPUT2),
@@ -1506,7 +3440,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1506
3440
  clearTimeout(timer);
1507
3441
  if (typeof pid === "number") registry.unregister(pid);
1508
3442
  registry.afterCall(Date.now() - startedAt, true);
1509
- resolve5({
3443
+ resolve6({
1510
3444
  command: cmd,
1511
3445
  args,
1512
3446
  stdout: stdout.slice(0, MAX_OUTPUT2),
@@ -1892,13 +3826,13 @@ var formatTool = {
1892
3826
  }
1893
3827
  };
1894
3828
  async function detectFixer(cwd) {
1895
- const { stat: stat10 } = await import('fs/promises');
3829
+ const { stat: stat11 } = await import('node:fs/promises');
1896
3830
  try {
1897
- await stat10(`${cwd}/biome.json`);
3831
+ await stat11(`${cwd}/biome.json`);
1898
3832
  return "biome";
1899
3833
  } catch {
1900
3834
  try {
1901
- await stat10(`${cwd}/.prettierrc`);
3835
+ await stat11(`${cwd}/.prettierrc`);
1902
3836
  return "prettier";
1903
3837
  } catch {
1904
3838
  return "biome";
@@ -2002,8 +3936,8 @@ function findGitDir2(cwd, projectRoot) {
2002
3936
  let dir = cwd;
2003
3937
  for (let i = 0; i < 20; i++) {
2004
3938
  try {
2005
- const stat10 = statSync(`${dir}/.git`);
2006
- if (stat10.isDirectory()) return dir;
3939
+ const stat11 = statSync(`${dir}/.git`);
3940
+ if (stat11.isDirectory()) return dir;
2007
3941
  } catch {
2008
3942
  }
2009
3943
  if (dir === root) break;
@@ -2084,7 +4018,7 @@ function buildArgs(input) {
2084
4018
  }
2085
4019
  }
2086
4020
  function runGit2(args, cwd, signal) {
2087
- return new Promise((resolve5) => {
4021
+ return new Promise((resolve6) => {
2088
4022
  let stdout = "";
2089
4023
  let stderr = "";
2090
4024
  const child = spawn("git", args, {
@@ -2104,7 +4038,7 @@ function runGit2(args, cwd, signal) {
2104
4038
  }
2105
4039
  });
2106
4040
  child.on("error", (err) => {
2107
- resolve5({
4041
+ resolve6({
2108
4042
  command: args[0],
2109
4043
  stdout,
2110
4044
  stderr: err.message,
@@ -2113,7 +4047,7 @@ function runGit2(args, cwd, signal) {
2113
4047
  });
2114
4048
  });
2115
4049
  child.on("close", (code) => {
2116
- resolve5({
4050
+ resolve6({
2117
4051
  command: args[0],
2118
4052
  stdout: stdout.slice(0, MAX_OUTPUT3),
2119
4053
  stderr: stderr.slice(0, MAX_OUTPUT3),
@@ -2123,7 +4057,7 @@ function runGit2(args, cwd, signal) {
2123
4057
  });
2124
4058
  });
2125
4059
  }
2126
- var DEFAULT_IGNORE = ["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"];
4060
+ var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"];
2127
4061
  var globTool = {
2128
4062
  name: "glob",
2129
4063
  category: "Filesystem",
@@ -2157,13 +4091,13 @@ var globTool = {
2157
4091
  }
2158
4092
  let entries;
2159
4093
  try {
2160
- entries = await fs9.readdir(dir, { withFileTypes: true });
4094
+ entries = await fs11.readdir(dir, { withFileTypes: true });
2161
4095
  } catch {
2162
4096
  return;
2163
4097
  }
2164
4098
  for (const e of entries) {
2165
4099
  const name = e.name;
2166
- if (DEFAULT_IGNORE.includes(name)) continue;
4100
+ if (DEFAULT_IGNORE2.includes(name)) continue;
2167
4101
  if (ignored.includes(name)) continue;
2168
4102
  const rel = relPrefix ? `${relPrefix}/${name}` : name;
2169
4103
  const full = path.join(dir, name);
@@ -2173,7 +4107,7 @@ var globTool = {
2173
4107
  } else if (e.isFile()) {
2174
4108
  if (re.test(rel) || re.test(name)) {
2175
4109
  try {
2176
- const st = await fs9.stat(full);
4110
+ const st = await fs11.stat(full);
2177
4111
  results.push({ rel: full, mtime: st.mtimeMs });
2178
4112
  if (results.length >= limit) {
2179
4113
  truncated = true;
@@ -2192,7 +4126,7 @@ var globTool = {
2192
4126
  };
2193
4127
  async function readGitignore(dir) {
2194
4128
  try {
2195
- const raw = await fs9.readFile(path.join(dir, ".gitignore"), "utf8");
4129
+ const raw = await fs11.readFile(path.join(dir, ".gitignore"), "utf8");
2196
4130
  return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
2197
4131
  } catch {
2198
4132
  return [];
@@ -2245,7 +4179,7 @@ function capSubject(line) {
2245
4179
  }
2246
4180
 
2247
4181
  // src/grep.ts
2248
- var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
4182
+ var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
2249
4183
  var grepTool = {
2250
4184
  name: "grep",
2251
4185
  category: "Search",
@@ -2299,13 +4233,13 @@ var grepTool = {
2299
4233
  }
2300
4234
  };
2301
4235
  async function detectRg(signal) {
2302
- return new Promise((resolve5) => {
4236
+ return new Promise((resolve6) => {
2303
4237
  try {
2304
4238
  const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore", signal });
2305
- p.on("error", () => resolve5(false));
2306
- p.on("close", (code) => resolve5(code === 0));
4239
+ p.on("error", () => resolve6(false));
4240
+ p.on("close", (code) => resolve6(code === 0));
2307
4241
  } catch {
2308
- resolve5(false);
4242
+ resolve6(false);
2309
4243
  }
2310
4244
  });
2311
4245
  }
@@ -2318,7 +4252,7 @@ async function* runRgStream(input, base, mode, limit, signal) {
2318
4252
  args.push("-n");
2319
4253
  if (input.context_lines) args.push("-C", String(input.context_lines));
2320
4254
  }
2321
- for (const ignored of DEFAULT_IGNORE2) {
4255
+ for (const ignored of DEFAULT_IGNORE3) {
2322
4256
  args.push("--glob", `!${ignored}/**`, "--glob", `!**/${ignored}/**`);
2323
4257
  }
2324
4258
  if (input.glob) args.push("--glob", input.glob);
@@ -2451,13 +4385,13 @@ async function runNative(input, base, mode, limit, signal) {
2451
4385
  if (stopped || signal.aborted) return;
2452
4386
  let entries;
2453
4387
  try {
2454
- entries = await fs9.readdir(dir, { withFileTypes: true });
4388
+ entries = await fs11.readdir(dir, { withFileTypes: true });
2455
4389
  } catch {
2456
4390
  return;
2457
4391
  }
2458
4392
  for (const e of entries) {
2459
4393
  if (stopped) return;
2460
- if (DEFAULT_IGNORE2.includes(e.name)) continue;
4394
+ if (DEFAULT_IGNORE3.includes(e.name)) continue;
2461
4395
  if (e.isSymbolicLink()) continue;
2462
4396
  const full = path.join(dir, e.name);
2463
4397
  if (e.isDirectory()) {
@@ -2466,9 +4400,9 @@ async function runNative(input, base, mode, limit, signal) {
2466
4400
  if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;
2467
4401
  if (globRe) globRe.lastIndex = 0;
2468
4402
  try {
2469
- const stat10 = await fs9.stat(full);
2470
- if (stat10.size > 1e6) continue;
2471
- const head = await fs9.readFile(full);
4403
+ const stat11 = await fs11.stat(full);
4404
+ if (stat11.size > 1e6) continue;
4405
+ const head = await fs11.readFile(full);
2472
4406
  if (isBinaryBuffer(head)) continue;
2473
4407
  const text = head.toString("utf8");
2474
4408
  const lines = text.split(/\r?\n/);
@@ -2606,13 +4540,13 @@ var installTool = {
2606
4540
  }
2607
4541
  };
2608
4542
  async function detectPackageManager(cwd) {
2609
- const { stat: stat10 } = await import('fs/promises');
4543
+ const { stat: stat11 } = await import('node:fs/promises');
2610
4544
  try {
2611
- await stat10(`${cwd}/pnpm-lock.yaml`);
4545
+ await stat11(`${cwd}/pnpm-lock.yaml`);
2612
4546
  return "pnpm";
2613
4547
  } catch {
2614
4548
  try {
2615
- await stat10(`${cwd}/yarn.lock`);
4549
+ await stat11(`${cwd}/yarn.lock`);
2616
4550
  return "yarn";
2617
4551
  } catch {
2618
4552
  return "npm";
@@ -2653,7 +4587,7 @@ var jsonTool = {
2653
4587
  let raw;
2654
4588
  if (input.file) {
2655
4589
  try {
2656
- raw = await fs9.readFile(input.file, "utf8");
4590
+ raw = await fs11.readFile(input.file, "utf8");
2657
4591
  } catch {
2658
4592
  return { data: null, formatted: "", type: "unknown", error: `Could not read file` };
2659
4593
  }
@@ -2691,8 +4625,8 @@ var jsonTool = {
2691
4625
  };
2692
4626
  }
2693
4627
  };
2694
- function query(data, path12) {
2695
- const parts = path12.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
4628
+ function query(data, path17) {
4629
+ const parts = path17.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
2696
4630
  let current = data;
2697
4631
  for (const part of parts) {
2698
4632
  if (current === null || current === void 0) return void 0;
@@ -2819,11 +4753,11 @@ var lintTool = {
2819
4753
  }
2820
4754
  };
2821
4755
  async function detectLinter(cwd) {
2822
- const { stat: stat10 } = await import('fs/promises');
4756
+ const { stat: stat11 } = await import('node:fs/promises');
2823
4757
  const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
2824
4758
  for (const f of checks) {
2825
4759
  try {
2826
- await stat10(`${cwd}/${f}`);
4760
+ await stat11(`${cwd}/${f}`);
2827
4761
  if (f.includes("biome")) return "biome";
2828
4762
  if (f.includes("eslint")) return "eslint";
2829
4763
  if (f.includes("tslint")) return "tslint";
@@ -2912,7 +4846,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
2912
4846
  };
2913
4847
  }
2914
4848
  args.push("--timestamps", service);
2915
- return new Promise((resolve5) => {
4849
+ return new Promise((resolve6) => {
2916
4850
  let stdout = "";
2917
4851
  let stderr = "";
2918
4852
  const MAX = 2e5;
@@ -2926,7 +4860,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
2926
4860
  child.on("close", (code) => {
2927
4861
  const output = stdout + stderr;
2928
4862
  const entries = parseLogLines(output, filterRe);
2929
- resolve5({
4863
+ resolve6({
2930
4864
  source: `docker:${service}`,
2931
4865
  entries,
2932
4866
  total: entries.length,
@@ -2935,7 +4869,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
2935
4869
  });
2936
4870
  });
2937
4871
  child.on("error", (e) => {
2938
- resolve5({
4872
+ resolve6({
2939
4873
  source: `docker:${service}`,
2940
4874
  entries: [],
2941
4875
  total: 0,
@@ -2946,16 +4880,16 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
2946
4880
  });
2947
4881
  }
2948
4882
  var MAX_TAIL_LINES = 1e5;
2949
- async function fileLogs(path12, lines, filterRe, stream) {
2950
- const { createInterface } = await import('readline');
2951
- const { createReadStream } = await import('fs');
4883
+ async function fileLogs(path17, lines, filterRe, stream) {
4884
+ const { createInterface } = await import('node:readline');
4885
+ const { createReadStream } = await import('node:fs');
2952
4886
  const entries = [];
2953
4887
  const effLines = lines > 0 ? Math.min(lines, MAX_TAIL_LINES) : MAX_TAIL_LINES;
2954
4888
  const window = new Array(effLines);
2955
4889
  let writeIdx = 0;
2956
4890
  let totalLines = 0;
2957
4891
  const rl = createInterface({
2958
- input: createReadStream(path12),
4892
+ input: createReadStream(path17),
2959
4893
  crlfDelay: Number.POSITIVE_INFINITY
2960
4894
  });
2961
4895
  for await (const line of rl) {
@@ -2976,7 +4910,7 @@ async function fileLogs(path12, lines, filterRe, stream) {
2976
4910
  if (parsed) entries.push(parsed);
2977
4911
  }
2978
4912
  return {
2979
- source: path12,
4913
+ source: path17,
2980
4914
  entries,
2981
4915
  total: entries.length,
2982
4916
  truncated: totalLines > effLines,
@@ -3003,7 +4937,7 @@ function parseLine(line) {
3003
4937
  message: match[3] ?? ""
3004
4938
  };
3005
4939
  }
3006
- const levelRe = /(?:ERROR|WARN|INFO|DEBUG|TRACE)\s+(.*)/i;
4940
+ const levelRe = /(ERROR|WARN|INFO|DEBUG|TRACE)\s+(.*)/i;
3007
4941
  const levelMatch = levelRe.exec(line);
3008
4942
  if (levelMatch) {
3009
4943
  return {
@@ -3068,7 +5002,7 @@ async function detectManager2(cwd) {
3068
5002
  return "npm";
3069
5003
  }
3070
5004
  function runOutdated(manager, args, cwd, signal) {
3071
- return new Promise((resolve5) => {
5005
+ return new Promise((resolve6) => {
3072
5006
  let stdout = "";
3073
5007
  let stderr = "";
3074
5008
  const MAX = 1e5;
@@ -3081,10 +5015,10 @@ function runOutdated(manager, args, cwd, signal) {
3081
5015
  });
3082
5016
  child.on("close", (code) => {
3083
5017
  const result = parseOutdatedOutput(stdout, code ?? 0);
3084
- resolve5(result);
5018
+ resolve6(result);
3085
5019
  });
3086
5020
  child.on("error", (e) => {
3087
- resolve5({
5021
+ resolve6({
3088
5022
  exit_code: 1,
3089
5023
  packages: [],
3090
5024
  total: 0,
@@ -3167,12 +5101,12 @@ var patchTool = {
3167
5101
  };
3168
5102
  }
3169
5103
  }
3170
- const tmpDir = await fs9.mkdtemp(path.join(os.tmpdir(), ".wstack_patch_"));
5104
+ const tmpDir = await fs11.mkdtemp(path.join(os.tmpdir(), ".wstack_patch_"));
3171
5105
  try {
3172
- await fs9.chmod(tmpDir, 448).catch(() => {
5106
+ await fs11.chmod(tmpDir, 448).catch(() => {
3173
5107
  });
3174
5108
  const patchFile = path.join(tmpDir, "in.diff");
3175
- await fs9.writeFile(patchFile, input.patch, { mode: 384 });
5109
+ await fs11.writeFile(patchFile, input.patch, { mode: 384 });
3176
5110
  const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
3177
5111
  const result = await runPatch(args, dir, opts.signal);
3178
5112
  if (result.exitCode !== 0 && !dryRun) {
@@ -3193,7 +5127,7 @@ var patchTool = {
3193
5127
  message: result.stdout || "patch applied"
3194
5128
  };
3195
5129
  } finally {
3196
- await fs9.rm(tmpDir, { recursive: true, force: true }).catch(() => {
5130
+ await fs11.rm(tmpDir, { recursive: true, force: true }).catch(() => {
3197
5131
  });
3198
5132
  }
3199
5133
  }
@@ -3214,7 +5148,7 @@ function stripPathComponents(p, strip) {
3214
5148
  return parts.slice(strip).join("/");
3215
5149
  }
3216
5150
  function runPatch(args, cwd, signal) {
3217
- return new Promise((resolve5) => {
5151
+ return new Promise((resolve6) => {
3218
5152
  let stdout = "";
3219
5153
  let stderr = "";
3220
5154
  const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
@@ -3225,8 +5159,8 @@ function runPatch(args, cwd, signal) {
3225
5159
  child.stderr?.on("data", (c) => {
3226
5160
  stderr += c.toString();
3227
5161
  });
3228
- child.on("close", (code) => resolve5({ exitCode: code ?? 1, stdout, stderr }));
3229
- child.on("error", (e) => resolve5({ exitCode: 1, stdout: "", stderr: e.message }));
5162
+ child.on("close", (code) => resolve6({ exitCode: code ?? 1, stdout, stderr }));
5163
+ child.on("error", (e) => resolve6({ exitCode: 1, stdout: "", stderr: e.message }));
3230
5164
  });
3231
5165
  }
3232
5166
  function extractPatchedFiles(output) {
@@ -3396,19 +5330,19 @@ var readTool = {
3396
5330
  async execute(input, ctx) {
3397
5331
  if (!input?.path) throw new Error("read: path is required");
3398
5332
  const absPath = safeResolve(input.path, ctx);
3399
- let stat10;
5333
+ let stat11;
3400
5334
  try {
3401
- stat10 = await fs9.stat(absPath);
5335
+ stat11 = await fs11.stat(absPath);
3402
5336
  } catch (err) {
3403
5337
  const code = err.code;
3404
5338
  if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
3405
5339
  throw new Error(`read: failed to stat "${input.path}": ${err instanceof Error ? err.message : String(err)}`);
3406
5340
  }
3407
- if (!stat10.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
3408
- if (stat10.size > MAX_BYTES2) {
3409
- throw new Error(`read: file too large (${stat10.size} bytes, limit ${MAX_BYTES2})`);
5341
+ if (!stat11.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
5342
+ if (stat11.size > MAX_BYTES2) {
5343
+ throw new Error(`read: file too large (${stat11.size} bytes, limit ${MAX_BYTES2})`);
3410
5344
  }
3411
- const buf = await fs9.readFile(absPath);
5345
+ const buf = await fs11.readFile(absPath);
3412
5346
  if (isBinaryBuffer(buf)) {
3413
5347
  throw new Error(`read: "${input.path}" appears to be binary`);
3414
5348
  }
@@ -3418,14 +5352,14 @@ var readTool = {
3418
5352
  const offset = Math.max(1, input.offset ?? 1);
3419
5353
  const limit = Math.max(0, Math.min(input.limit ?? 2e3, 5e3));
3420
5354
  if (limit === 0) {
3421
- ctx.recordRead(absPath, stat10.mtimeMs);
5355
+ ctx.recordRead(absPath, stat11.mtimeMs);
3422
5356
  return { text: "", total_lines: total, encoding: "utf8", truncated: total > 0 };
3423
5357
  }
3424
5358
  const slice = allLines.slice(offset - 1, offset - 1 + limit);
3425
5359
  const truncated = offset - 1 + slice.length < total;
3426
5360
  const width = String(offset + slice.length - 1).length;
3427
5361
  const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
3428
- ctx.recordRead(absPath, stat10.mtimeMs);
5362
+ ctx.recordRead(absPath, stat11.mtimeMs);
3429
5363
  return {
3430
5364
  text: numbered,
3431
5365
  total_lines: total,
@@ -3434,7 +5368,7 @@ var readTool = {
3434
5368
  };
3435
5369
  }
3436
5370
  };
3437
- var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
5371
+ var DEFAULT_IGNORE4 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
3438
5372
  var replaceTool = {
3439
5373
  name: "replace",
3440
5374
  category: "Transform",
@@ -3478,7 +5412,7 @@ var replaceTool = {
3478
5412
  const results = [];
3479
5413
  let totalReplacements = 0;
3480
5414
  for (const absPath of fileList) {
3481
- const lstat2 = await fs9.lstat(absPath).catch((err) => {
5415
+ const lstat2 = await fs11.lstat(absPath).catch((err) => {
3482
5416
  if (err.code === "ENOENT") return null;
3483
5417
  throw err;
3484
5418
  });
@@ -3486,17 +5420,17 @@ var replaceTool = {
3486
5420
  if (lstat2.isSymbolicLink()) continue;
3487
5421
  let realPath;
3488
5422
  try {
3489
- realPath = await fs9.realpath(absPath);
5423
+ realPath = await fs11.realpath(absPath);
3490
5424
  } catch {
3491
5425
  continue;
3492
5426
  }
3493
5427
  const rel = path.relative(ctx.projectRoot, realPath);
3494
5428
  if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
3495
- const stat10 = await fs9.stat(realPath).catch(() => null);
3496
- if (!stat10 || !stat10.isFile()) continue;
5429
+ const stat11 = await fs11.stat(realPath).catch(() => null);
5430
+ if (!stat11 || !stat11.isFile()) continue;
3497
5431
  let content;
3498
5432
  try {
3499
- const buf = await fs9.readFile(realPath);
5433
+ const buf = await fs11.readFile(realPath);
3500
5434
  if (isBinaryBuffer(buf)) continue;
3501
5435
  content = buf.toString("utf8");
3502
5436
  } catch {
@@ -3518,7 +5452,7 @@ var replaceTool = {
3518
5452
  totalReplacements += count;
3519
5453
  if (!dryRun) {
3520
5454
  const newContent = toStyle(newContentLf, style);
3521
- await atomicWrite(realPath, newContent, { mode: stat10.mode & 511 });
5455
+ await atomicWrite(realPath, newContent, { mode: stat11.mode & 511 });
3522
5456
  }
3523
5457
  const diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
3524
5458
  fromFile: absPath,
@@ -3548,15 +5482,15 @@ async function resolveFiles2(filesInput, ctx, extraGlob) {
3548
5482
  const resolved = [];
3549
5483
  for (const p of parts) {
3550
5484
  const absPath = safeResolve(p, ctx);
3551
- const stat10 = await fs9.stat(absPath).catch(() => null);
3552
- if (stat10?.isFile()) {
5485
+ const stat11 = await fs11.stat(absPath).catch(() => null);
5486
+ if (stat11?.isFile()) {
3553
5487
  resolved.push(absPath);
3554
5488
  }
3555
5489
  }
3556
5490
  return resolved;
3557
5491
  }
3558
5492
  async function globFiles(pattern, base, extraGlob) {
3559
- const { spawn: spawn11 } = await import('child_process');
5493
+ const { spawn: spawn11 } = await import('node:child_process');
3560
5494
  const rgAvailable = await checkRg();
3561
5495
  if (rgAvailable) {
3562
5496
  try {
@@ -3568,13 +5502,13 @@ async function globFiles(pattern, base, extraGlob) {
3568
5502
  return await globNative(pattern, base, extraGlob);
3569
5503
  }
3570
5504
  function checkRg() {
3571
- return new Promise((resolve5) => {
5505
+ return new Promise((resolve6) => {
3572
5506
  try {
3573
5507
  const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore" });
3574
- p.on("error", () => resolve5(false));
3575
- p.on("close", (code) => resolve5(code === 0));
5508
+ p.on("error", () => resolve6(false));
5509
+ p.on("close", (code) => resolve6(code === 0));
3576
5510
  } catch {
3577
- resolve5(false);
5511
+ resolve6(false);
3578
5512
  }
3579
5513
  });
3580
5514
  }
@@ -3586,10 +5520,10 @@ function spawnRgFind(pattern, base) {
3586
5520
  buf += chunk.toString();
3587
5521
  });
3588
5522
  return {
3589
- promise: new Promise((resolve5, reject) => {
5523
+ promise: new Promise((resolve6, reject) => {
3590
5524
  child.on("error", reject);
3591
5525
  child.on("close", () => {
3592
- resolve5(buf.split("\n").filter(Boolean));
5526
+ resolve6(buf.split("\n").filter(Boolean));
3593
5527
  });
3594
5528
  })
3595
5529
  };
@@ -3600,16 +5534,16 @@ async function globNative(pattern, base, extraGlob) {
3600
5534
  const walk = async (dir) => {
3601
5535
  let entries;
3602
5536
  try {
3603
- entries = await fs9.readdir(dir, { withFileTypes: true });
5537
+ entries = await fs11.readdir(dir, { withFileTypes: true });
3604
5538
  } catch {
3605
5539
  return;
3606
5540
  }
3607
5541
  for (const e of entries) {
3608
- if (DEFAULT_IGNORE3.includes(e.name)) continue;
5542
+ if (DEFAULT_IGNORE4.includes(e.name)) continue;
3609
5543
  const full = path.join(dir, e.name);
3610
5544
  try {
3611
- const stat10 = await fs9.lstat(full);
3612
- if (stat10.isSymbolicLink()) continue;
5545
+ const stat11 = await fs11.lstat(full);
5546
+ if (stat11.isSymbolicLink()) continue;
3613
5547
  } catch {
3614
5548
  continue;
3615
5549
  }
@@ -3784,7 +5718,7 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
3784
5718
  }
3785
5719
  const fullPath = target;
3786
5720
  if (!dryRun) {
3787
- await fs9.mkdir(path.dirname(fullPath), { recursive: true });
5721
+ await fs11.mkdir(path.dirname(fullPath), { recursive: true });
3788
5722
  await atomicWrite(fullPath, substituteVars(content, name, vars));
3789
5723
  }
3790
5724
  files.push(resolvedPath);
@@ -4095,11 +6029,11 @@ var testTool = {
4095
6029
  }
4096
6030
  };
4097
6031
  async function detectRunner(cwd) {
4098
- const { stat: stat10 } = await import('fs/promises');
6032
+ const { stat: stat11 } = await import('node:fs/promises');
4099
6033
  const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
4100
6034
  for (const f of candidates) {
4101
6035
  try {
4102
- await stat10(path.join(cwd, f));
6036
+ await stat11(path.join(cwd, f));
4103
6037
  if (f.includes("vitest")) return "vitest";
4104
6038
  if (f.includes("jest")) return "jest";
4105
6039
  if (f.includes("mocha")) return "mocha";
@@ -4487,7 +6421,7 @@ var toolUseTool = {
4487
6421
  }
4488
6422
  }
4489
6423
  };
4490
- var DEFAULT_IGNORE4 = [
6424
+ var DEFAULT_IGNORE5 = [
4491
6425
  "node_modules",
4492
6426
  ".git",
4493
6427
  "dist",
@@ -4552,7 +6486,7 @@ var treeTool = {
4552
6486
  const showFiles = input.show_files ?? true;
4553
6487
  const showDirs = input.show_dirs ?? true;
4554
6488
  const showHidden = input.show_hidden ?? false;
4555
- const exclude = /* @__PURE__ */ new Set([...DEFAULT_IGNORE4, ...input.exclude ?? []]);
6489
+ const exclude = /* @__PURE__ */ new Set([...DEFAULT_IGNORE5, ...input.exclude ?? []]);
4556
6490
  const filterGlob = input.glob;
4557
6491
  const lines = [basePath];
4558
6492
  const totals = { totalFiles: { value: 0 }, totalDirs: { value: 0 } };
@@ -4617,7 +6551,7 @@ var treeTool = {
4617
6551
  }
4618
6552
  };
4619
6553
  async function walkDir(dir, depth, opts) {
4620
- const entries = await fs9.readdir(dir, { withFileTypes: true }).catch(() => []);
6554
+ const entries = await fs11.readdir(dir, { withFileTypes: true }).catch(() => []);
4621
6555
  const filtered = entries.filter((e) => {
4622
6556
  if (!opts.showHidden && e.name.startsWith(".")) return false;
4623
6557
  if (opts.exclude.has(e.name)) return false;
@@ -4724,11 +6658,11 @@ var typecheckTool = {
4724
6658
  }
4725
6659
  };
4726
6660
  async function findTsConfig(cwd) {
4727
- const { stat: stat10 } = await import('fs/promises');
6661
+ const { stat: stat11 } = await import('node:fs/promises');
4728
6662
  const candidates = ["tsconfig.json", "tsconfig.base.json"];
4729
6663
  for (const f of candidates) {
4730
6664
  try {
4731
- const s = await stat10(path.join(cwd, f));
6665
+ const s = await stat11(path.join(cwd, f));
4732
6666
  if (s.isFile()) return path.join(cwd, f);
4733
6667
  } catch {
4734
6668
  }
@@ -4758,14 +6692,14 @@ var writeTool = {
4758
6692
  let existed = false;
4759
6693
  let prev = "";
4760
6694
  try {
4761
- const stat11 = await fs9.stat(absPath);
4762
- existed = stat11.isFile();
6695
+ const stat12 = await fs11.stat(absPath);
6696
+ existed = stat12.isFile();
4763
6697
  if (existed) {
4764
6698
  if (!ctx.hasRead(absPath)) {
4765
- prev = await fs9.readFile(absPath, "utf8");
4766
- ctx.recordRead(absPath, stat11.mtimeMs);
6699
+ prev = await fs11.readFile(absPath, "utf8");
6700
+ ctx.recordRead(absPath, stat12.mtimeMs);
4767
6701
  } else {
4768
- prev = await fs9.readFile(absPath, "utf8");
6702
+ prev = await fs11.readFile(absPath, "utf8");
4769
6703
  }
4770
6704
  }
4771
6705
  } catch (err) {
@@ -4776,8 +6710,8 @@ var writeTool = {
4776
6710
  await atomicWrite(absPath, input.content);
4777
6711
  const diff = existed ? unifiedDiff(prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
4778
6712
  + (new file, ${input.content.split("\n").length} lines)`;
4779
- const stat10 = await fs9.stat(absPath);
4780
- ctx.recordRead(absPath, stat10.mtimeMs);
6713
+ const stat11 = await fs11.stat(absPath);
6714
+ ctx.recordRead(absPath, stat11.mtimeMs);
4781
6715
  ctx.session.recordFileChange({
4782
6716
  path: absPath,
4783
6717
  action: existed ? "modified" : "created",
@@ -4825,7 +6759,10 @@ var builtinTools = [
4825
6759
  toolSearchTool,
4826
6760
  toolUseTool,
4827
6761
  batchToolUseTool,
4828
- toolHelpTool
6762
+ toolHelpTool,
6763
+ codebaseIndexTool,
6764
+ codebaseSearchTool,
6765
+ codebaseStatsTool
4829
6766
  ];
4830
6767
 
4831
6768
  // src/pack.ts