@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.
- package/dist/audit.js +3 -3
- package/dist/audit.js.map +1 -1
- package/dist/bash.js +7 -6
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +2070 -133
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.d.ts +120 -0
- package/dist/codebase-index/index.js +1974 -0
- package/dist/codebase-index/index.js.map +1 -0
- package/dist/codebase-stats-tool-BLhQmPNc.d.ts +158 -0
- package/dist/diff.js +4 -4
- package/dist/document.js +2 -2
- package/dist/edit.js +2 -2
- package/dist/exec.js +5 -5
- package/dist/exec.js.map +1 -1
- package/dist/fetch.js +4 -3
- package/dist/fetch.js.map +1 -1
- package/dist/format.js +3 -3
- package/dist/format.js.map +1 -1
- package/dist/git.js +3 -3
- package/dist/glob.js +2 -2
- package/dist/grep.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2039 -102
- package/dist/index.js.map +1 -1
- package/dist/install.js +3 -3
- package/dist/install.js.map +1 -1
- package/dist/json.js +1 -1
- package/dist/lint.js +3 -3
- package/dist/lint.js.map +1 -1
- package/dist/logs.js +5 -5
- package/dist/logs.js.map +1 -1
- package/dist/outdated.js +3 -3
- package/dist/pack.js +2070 -133
- package/dist/pack.js.map +1 -1
- package/dist/patch.js +4 -4
- package/dist/process-registry.js +1 -1
- package/dist/read.js +2 -2
- package/dist/replace.js +4 -4
- package/dist/replace.js.map +1 -1
- package/dist/scaffold.js +2 -2
- package/dist/test.js +3 -3
- package/dist/test.js.map +1 -1
- package/dist/tree.js +2 -2
- package/dist/typecheck.js +3 -3
- package/dist/typecheck.js.map +1 -1
- package/dist/write.js +2 -2
- 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
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
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
|
-
|
|
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((
|
|
61
|
-
waiter =
|
|
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:
|
|
180
|
+
const { stat: stat11 } = await import('node:fs/promises');
|
|
173
181
|
try {
|
|
174
|
-
await
|
|
182
|
+
await stat11(`${cwd}/pnpm-lock.yaml`);
|
|
175
183
|
return "pnpm";
|
|
176
184
|
} catch {
|
|
177
185
|
}
|
|
178
186
|
try {
|
|
179
|
-
await
|
|
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((
|
|
744
|
+
const next = () => new Promise((resolve6) => {
|
|
737
745
|
const c = queue.shift();
|
|
738
|
-
if (c)
|
|
739
|
-
else resolveNext =
|
|
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
|
|
967
|
-
if (
|
|
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((
|
|
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) =>
|
|
988
|
-
child.on("error", (e) =>
|
|
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
|
|
1007
|
-
if (!
|
|
1008
|
-
const content = await
|
|
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
|
|
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
|
|
1106
|
-
if (
|
|
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
|
|
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 (!
|
|
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
|
|
1208
|
-
const updated = await
|
|
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
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
3829
|
+
const { stat: stat11 } = await import('node:fs/promises');
|
|
1896
3830
|
try {
|
|
1897
|
-
await
|
|
3831
|
+
await stat11(`${cwd}/biome.json`);
|
|
1898
3832
|
return "biome";
|
|
1899
3833
|
} catch {
|
|
1900
3834
|
try {
|
|
1901
|
-
await
|
|
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
|
|
2006
|
-
if (
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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((
|
|
4236
|
+
return new Promise((resolve6) => {
|
|
2303
4237
|
try {
|
|
2304
4238
|
const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore", signal });
|
|
2305
|
-
p.on("error", () =>
|
|
2306
|
-
p.on("close", (code) =>
|
|
4239
|
+
p.on("error", () => resolve6(false));
|
|
4240
|
+
p.on("close", (code) => resolve6(code === 0));
|
|
2307
4241
|
} catch {
|
|
2308
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
2470
|
-
if (
|
|
2471
|
-
const head = await
|
|
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:
|
|
4543
|
+
const { stat: stat11 } = await import('node:fs/promises');
|
|
2610
4544
|
try {
|
|
2611
|
-
await
|
|
4545
|
+
await stat11(`${cwd}/pnpm-lock.yaml`);
|
|
2612
4546
|
return "pnpm";
|
|
2613
4547
|
} catch {
|
|
2614
4548
|
try {
|
|
2615
|
-
await
|
|
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
|
|
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,
|
|
2695
|
-
const parts =
|
|
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:
|
|
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
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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 = /(
|
|
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((
|
|
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
|
-
|
|
5018
|
+
resolve6(result);
|
|
3085
5019
|
});
|
|
3086
5020
|
child.on("error", (e) => {
|
|
3087
|
-
|
|
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
|
|
5104
|
+
const tmpDir = await fs11.mkdtemp(path.join(os.tmpdir(), ".wstack_patch_"));
|
|
3171
5105
|
try {
|
|
3172
|
-
await
|
|
5106
|
+
await fs11.chmod(tmpDir, 448).catch(() => {
|
|
3173
5107
|
});
|
|
3174
5108
|
const patchFile = path.join(tmpDir, "in.diff");
|
|
3175
|
-
await
|
|
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
|
|
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((
|
|
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) =>
|
|
3229
|
-
child.on("error", (e) =>
|
|
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
|
|
5333
|
+
let stat11;
|
|
3400
5334
|
try {
|
|
3401
|
-
|
|
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 (!
|
|
3408
|
-
if (
|
|
3409
|
-
throw new Error(`read: file too large (${
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3496
|
-
if (!
|
|
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
|
|
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:
|
|
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
|
|
3552
|
-
if (
|
|
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((
|
|
5505
|
+
return new Promise((resolve6) => {
|
|
3572
5506
|
try {
|
|
3573
5507
|
const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore" });
|
|
3574
|
-
p.on("error", () =>
|
|
3575
|
-
p.on("close", (code) =>
|
|
5508
|
+
p.on("error", () => resolve6(false));
|
|
5509
|
+
p.on("close", (code) => resolve6(code === 0));
|
|
3576
5510
|
} catch {
|
|
3577
|
-
|
|
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((
|
|
5523
|
+
promise: new Promise((resolve6, reject) => {
|
|
3590
5524
|
child.on("error", reject);
|
|
3591
5525
|
child.on("close", () => {
|
|
3592
|
-
|
|
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
|
|
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 (
|
|
5542
|
+
if (DEFAULT_IGNORE4.includes(e.name)) continue;
|
|
3609
5543
|
const full = path.join(dir, e.name);
|
|
3610
5544
|
try {
|
|
3611
|
-
const
|
|
3612
|
-
if (
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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([...
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
4762
|
-
existed =
|
|
6695
|
+
const stat12 = await fs11.stat(absPath);
|
|
6696
|
+
existed = stat12.isFile();
|
|
4763
6697
|
if (existed) {
|
|
4764
6698
|
if (!ctx.hasRead(absPath)) {
|
|
4765
|
-
prev = await
|
|
4766
|
-
ctx.recordRead(absPath,
|
|
6699
|
+
prev = await fs11.readFile(absPath, "utf8");
|
|
6700
|
+
ctx.recordRead(absPath, stat12.mtimeMs);
|
|
4767
6701
|
} else {
|
|
4768
|
-
prev = await
|
|
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
|
|
4780
|
-
ctx.recordRead(absPath,
|
|
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
|