@wrongstack/tools 0.273.1 → 0.274.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -778,7 +778,27 @@ async function globNative(pattern, base, extraGlob) {
778
778
  await walk(base);
779
779
  return results;
780
780
  }
781
+
782
+ // src/_concurrency.ts
783
+ async function mapWithConcurrency(items, limit, fn) {
784
+ if (items.length === 0) return [];
785
+ const effectiveLimit = Math.max(1, Math.min(limit | 0, items.length));
786
+ const results = new Array(items.length);
787
+ let nextIndex = 0;
788
+ const worker2 = async () => {
789
+ while (true) {
790
+ const i = nextIndex++;
791
+ if (i >= items.length) return;
792
+ results[i] = await fn(items[i]);
793
+ }
794
+ };
795
+ await Promise.all(Array.from({ length: effectiveLimit }, worker2));
796
+ return results;
797
+ }
798
+
799
+ // src/glob.ts
781
800
  var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"];
801
+ var WALK_CONCURRENCY = 16;
782
802
  var globTool = {
783
803
  name: "glob",
784
804
  category: "Filesystem",
@@ -816,6 +836,22 @@ var globTool = {
816
836
  const re = compileGlob(input.pattern);
817
837
  const results = [];
818
838
  let truncated = false;
839
+ const pushResult = async (full) => {
840
+ if (truncated || results.length >= limit) {
841
+ truncated = true;
842
+ return;
843
+ }
844
+ try {
845
+ const st = await fs7.stat(full);
846
+ if (truncated || results.length >= limit) {
847
+ truncated = true;
848
+ return;
849
+ }
850
+ results.push({ rel: full, mtime: st.mtimeMs });
851
+ if (results.length >= limit) truncated = true;
852
+ } catch {
853
+ }
854
+ };
819
855
  const walk = async (dir, relPrefix) => {
820
856
  if (results.length >= limit) {
821
857
  truncated = true;
@@ -827,6 +863,8 @@ var globTool = {
827
863
  } catch {
828
864
  return;
829
865
  }
866
+ const subdirs = [];
867
+ const matchedFiles = [];
830
868
  for (const e of entries) {
831
869
  const name = e.name;
832
870
  if (DEFAULT_IGNORE2.includes(name)) continue;
@@ -834,22 +872,36 @@ var globTool = {
834
872
  const rel = relPrefix ? `${relPrefix}/${name}` : name;
835
873
  const full = path.join(dir, name);
836
874
  if (e.isDirectory()) {
837
- await walk(full, rel);
838
- if (truncated) return;
875
+ subdirs.push({ full, rel });
839
876
  } else if (e.isFile()) {
840
- if (re.test(rel) || re.test(name)) {
841
- try {
842
- const st = await fs7.stat(full);
843
- results.push({ rel: full, mtime: st.mtimeMs });
844
- if (results.length >= limit) {
845
- truncated = true;
846
- return;
847
- }
848
- } catch {
877
+ re.lastIndex = 0;
878
+ const relMatch = re.test(rel);
879
+ re.lastIndex = 0;
880
+ const nameMatch = re.test(name);
881
+ if (relMatch || nameMatch) {
882
+ matchedFiles.push(full);
883
+ }
884
+ } else if (e.isSymbolicLink()) {
885
+ try {
886
+ const st = await fs7.stat(full);
887
+ if (st.isDirectory()) {
888
+ subdirs.push({ full, rel });
889
+ } else if (st.isFile()) {
890
+ re.lastIndex = 0;
891
+ const relMatch = re.test(rel);
892
+ re.lastIndex = 0;
893
+ const nameMatch = re.test(name);
894
+ if (relMatch || nameMatch) matchedFiles.push(full);
849
895
  }
896
+ } catch {
850
897
  }
851
898
  }
899
+ if (truncated) return;
852
900
  }
901
+ await mapWithConcurrency(matchedFiles, WALK_CONCURRENCY, pushResult);
902
+ if (truncated) return;
903
+ const remainingSubdirs = truncated ? [] : subdirs;
904
+ await mapWithConcurrency(remainingSubdirs, WALK_CONCURRENCY, ({ full, rel }) => walk(full, rel));
853
905
  };
854
906
  await walk(base, "");
855
907
  results.sort((a, b) => b.mtime - a.mtime);
@@ -866,6 +918,8 @@ async function readGitignore(dir) {
866
918
  }
867
919
  var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
868
920
  var NATIVE_SCAN_CONCURRENCY = 32;
921
+ var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
922
+ var NATIVE_MAX_FILE_BYTES = 1e6;
869
923
  var grepTool = {
870
924
  name: "grep",
871
925
  category: "Search",
@@ -1090,7 +1144,8 @@ async function runNative(input, base, mode, limit, signal) {
1090
1144
  const re = compiled.regex;
1091
1145
  const globRe = input.glob ? compileGlob(input.glob) : null;
1092
1146
  const matches = [];
1093
- const fileMatches = /* @__PURE__ */ new Map();
1147
+ const countOnlyFirstHit = mode === "count" && limit === 1;
1148
+ const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
1094
1149
  let total = 0;
1095
1150
  let stopped = false;
1096
1151
  const scanFile = async (full, name) => {
@@ -1099,34 +1154,79 @@ async function runNative(input, base, mode, limit, signal) {
1099
1154
  if (globRe) globRe.lastIndex = 0;
1100
1155
  try {
1101
1156
  const stat11 = await fs7.stat(full);
1102
- if (stat11.size > 1e6 || stopped || signal.aborted) return;
1103
- const head = await fs7.readFile(full);
1104
- if (isBinaryBuffer(head) || stopped || signal.aborted) return;
1105
- const text = head.toString("utf8");
1106
- const lines = text.split(/\r?\n/);
1107
- let fileHits = 0;
1108
- for (let i = 0; i < lines.length; i++) {
1109
- if (stopped || signal.aborted) break;
1110
- const ln = capSubject(lines[i] ?? "");
1111
- re.lastIndex = 0;
1112
- if (re.test(ln)) {
1113
- fileHits++;
1114
- total++;
1115
- if (mode === "content" && matches.length < limit) {
1116
- matches.push(`${full}:${i + 1}:${ln}`);
1157
+ if (!stat11.isFile() || stat11.size > maxBytes || stopped || signal.aborted) return;
1158
+ const file = await fs7.open(full, "r");
1159
+ try {
1160
+ let bytesReadTotal = 0;
1161
+ let lineNumber = 0;
1162
+ let fileHits = 0;
1163
+ let leftover = "";
1164
+ let binaryChecked = false;
1165
+ const buffer = Buffer.allocUnsafe(Math.min(NATIVE_READ_CHUNK_BYTES, maxBytes));
1166
+ while (!stopped && !signal.aborted && bytesReadTotal < maxBytes) {
1167
+ const remaining = maxBytes - bytesReadTotal;
1168
+ const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, remaining), null);
1169
+ if (bytesRead === 0) break;
1170
+ const chunk = buffer.subarray(0, bytesRead);
1171
+ if (!binaryChecked) {
1172
+ binaryChecked = true;
1173
+ if (isBinaryBuffer(chunk)) return;
1174
+ }
1175
+ bytesReadTotal += bytesRead;
1176
+ const text = leftover + chunk.toString("utf8");
1177
+ const lines = text.split(/\r?\n/);
1178
+ leftover = lines.pop() ?? "";
1179
+ for (const rawLine of lines) {
1180
+ if (stopped || signal.aborted) break;
1181
+ lineNumber++;
1182
+ const ln = capSubject(rawLine);
1183
+ re.lastIndex = 0;
1184
+ if (!re.test(ln)) continue;
1185
+ fileHits++;
1186
+ total++;
1187
+ if (mode === "content") {
1188
+ if (matches.length < limit) matches.push(`${full}:${lineNumber}:${ln}`);
1189
+ } else if (mode === "files_with_matches") {
1190
+ if (fileHits === 1 && matches.length < limit) matches.push(full);
1191
+ break;
1192
+ } else if (fileHits === 1 && matches.length < limit) {
1193
+ matches.push(`${full}:${countOnlyFirstHit ? 1 : 0}`);
1194
+ }
1195
+ if (countOnlyFirstHit || mode !== "content" && matches.length >= limit) {
1196
+ stopped = true;
1197
+ break;
1198
+ }
1117
1199
  }
1200
+ if (mode === "files_with_matches" && fileHits > 0) break;
1118
1201
  }
1119
- }
1120
- if (fileHits > 0) {
1121
- fileMatches.set(full, fileHits);
1122
- if (mode === "files_with_matches" && matches.length < limit) {
1123
- matches.push(full);
1202
+ if (!stopped && !signal.aborted && leftover.length > 0) {
1203
+ lineNumber++;
1204
+ const ln = capSubject(leftover);
1205
+ re.lastIndex = 0;
1206
+ if (re.test(ln)) {
1207
+ fileHits++;
1208
+ total++;
1209
+ if (mode === "content") {
1210
+ if (matches.length < limit) matches.push(`${full}:${lineNumber}:${ln}`);
1211
+ } else if (mode === "files_with_matches") {
1212
+ if (matches.length < limit) matches.push(full);
1213
+ } else if (matches.length < limit) {
1214
+ matches.push(`${full}:${countOnlyFirstHit ? 1 : fileHits}`);
1215
+ }
1216
+ }
1124
1217
  }
1125
- if (mode === "count" && matches.length < limit) {
1126
- matches.push(`${full}:${fileHits}`);
1218
+ if (fileHits > 0) {
1219
+ if (mode === "count") {
1220
+ const idx = matches.findIndex((entry) => entry.startsWith(`${full}:`));
1221
+ if (idx !== -1) matches[idx] = `${full}:${fileHits}`;
1222
+ }
1223
+ if (mode === "files_with_matches" && matches.length >= limit) stopped = true;
1127
1224
  }
1225
+ if (mode === "content" && matches.length >= limit) stopped = true;
1226
+ if (mode === "count" && matches.length >= limit && (countOnlyFirstHit || mode !== "count")) stopped = true;
1227
+ } finally {
1228
+ await file.close();
1128
1229
  }
1129
- if (matches.length >= limit) stopped = true;
1130
1230
  } catch {
1131
1231
  }
1132
1232
  };
@@ -1139,18 +1239,20 @@ async function runNative(input, base, mode, limit, signal) {
1139
1239
  return;
1140
1240
  }
1141
1241
  const files = [];
1242
+ const subdirs = [];
1142
1243
  for (const e of entries) {
1143
1244
  if (stopped) return;
1144
1245
  if (DEFAULT_IGNORE3.includes(e.name)) continue;
1145
1246
  if (e.isSymbolicLink()) continue;
1146
1247
  const full = path.join(dir, e.name);
1147
1248
  if (e.isDirectory()) {
1148
- await walk(full);
1249
+ subdirs.push(full);
1149
1250
  } else if (e.isFile()) {
1150
1251
  files.push({ full, name: e.name });
1151
1252
  }
1152
1253
  }
1153
1254
  await mapWithConcurrency(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
1255
+ await mapWithConcurrency(subdirs, Math.min(16, NATIVE_SCAN_CONCURRENCY), walk);
1154
1256
  };
1155
1257
  await walk(base);
1156
1258
  return {
@@ -1160,20 +1262,6 @@ async function runNative(input, base, mode, limit, signal) {
1160
1262
  used: "native"
1161
1263
  };
1162
1264
  }
1163
- async function mapWithConcurrency(items, concurrency, fn) {
1164
- if (items.length === 0) return;
1165
- let next = 0;
1166
- const workerCount = Math.min(Math.max(1, concurrency), items.length);
1167
- const workers = Array.from({ length: workerCount }, async () => {
1168
- for (; ; ) {
1169
- const idx = next++;
1170
- if (idx >= items.length) return;
1171
- const item = items[idx];
1172
- if (item !== void 0) await fn(item);
1173
- }
1174
- });
1175
- await Promise.all(workers);
1176
- }
1177
1265
  var SPOOL_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
1178
1266
  var SPOOL_WRITE_HWM_BYTES = 4 * 1024 * 1024;
1179
1267
  var sweepStarted = false;
@@ -1215,7 +1303,7 @@ function createOutputSpool(opts) {
1215
1303
  let filePath = null;
1216
1304
  let failed = false;
1217
1305
  let finalized = false;
1218
- const open = () => {
1306
+ const open2 = () => {
1219
1307
  if (stream || failed) return;
1220
1308
  try {
1221
1309
  const dir = toolOutputDir();
@@ -1248,7 +1336,7 @@ function createOutputSpool(opts) {
1248
1336
  return;
1249
1337
  }
1250
1338
  head += text;
1251
- open();
1339
+ open2();
1252
1340
  head = "";
1253
1341
  return;
1254
1342
  }
@@ -4161,13 +4249,13 @@ ${formatTaskList(taskFile.tasks)}`
4161
4249
  }
4162
4250
  };
4163
4251
  function mkResult(plan, ok, message, todos) {
4164
- const open = plan.items.filter((i) => i.status !== "done").length;
4252
+ const open2 = plan.items.filter((i) => i.status !== "done").length;
4165
4253
  const result = {
4166
4254
  ok,
4167
4255
  message,
4168
4256
  plan: formatPlan(plan),
4169
4257
  count: plan.items.length,
4170
- open
4258
+ open: open2
4171
4259
  };
4172
4260
  if (todos !== void 0) result.todos = todos;
4173
4261
  return result;
@@ -8039,6 +8127,8 @@ var IndexStore = class {
8039
8127
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)");
8040
8128
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)");
8041
8129
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
8130
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)");
8131
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)");
8042
8132
  this.db.exec(`
8043
8133
  CREATE TABLE IF NOT EXISTS refs (
8044
8134
  id INTEGER PRIMARY KEY,
@@ -8951,9 +9041,9 @@ async function syncGoParse(filePath, content, lang) {
8951
9041
  }
8952
9042
  }
8953
9043
  async function parseSymbols3(opts) {
8954
- const { file, lang } = opts;
9044
+ const { file, content, lang } = opts;
8955
9045
  try {
8956
- return await syncPyParse(file, lang);
9046
+ return await syncPyParse(file, content, lang);
8957
9047
  } catch {
8958
9048
  return { file, lang, symbols: [], mtimeMs: Date.now() };
8959
9049
  }
@@ -9021,8 +9111,7 @@ syms = []
9021
9111
  errors = []
9022
9112
 
9023
9113
  try:
9024
- with open(sys.argv[1], "r", encoding="utf-8") as f:
9025
- source = f.read()
9114
+ source = sys.stdin.read()
9026
9115
  tree = ast.parse(source, filename=sys.argv[1])
9027
9116
  except Exception as e:
9028
9117
  errors.append(str(e))
@@ -9162,16 +9251,21 @@ visitor.visit(tree)
9162
9251
 
9163
9252
  print(json.dumps([s.to_dict() for s in syms]))
9164
9253
  `;
9165
- async function syncPyParse(filePath, lang) {
9254
+ var _cachedScriptPath = null;
9255
+ async function syncPyParse(filePath, content, lang) {
9166
9256
  try {
9167
- const tmpDir = path.join(os2.tmpdir(), "ws-py-parse");
9168
- await fs7.mkdir(tmpDir, { recursive: true });
9169
- const scriptPath = path.join(tmpDir, "parse.py");
9170
- await fs7.writeFile(scriptPath, PY_PARSE_SCRIPT, "utf8");
9171
- const proc = spawn("python", [scriptPath, filePath], {
9257
+ if (!_cachedScriptPath) {
9258
+ const tmpDir = path.join(os2.tmpdir(), "ws-py-parse");
9259
+ await fs7.mkdir(tmpDir, { recursive: true });
9260
+ _cachedScriptPath = path.join(tmpDir, "parse.py");
9261
+ await fs7.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
9262
+ }
9263
+ const proc = spawn("python", [_cachedScriptPath, filePath], {
9172
9264
  stdio: ["pipe", "pipe", "pipe"],
9173
9265
  windowsHide: true
9174
9266
  });
9267
+ proc.stdin?.write(content);
9268
+ proc.stdin?.end();
9175
9269
  let stdout = "";
9176
9270
  proc.stdout?.on("data", (chunk) => {
9177
9271
  stdout += chunk.toString();
@@ -9838,7 +9932,7 @@ async function parseFile(file, content, lang) {
9838
9932
  case "go":
9839
9933
  return parseSymbols2({ file, content, lang: "go" });
9840
9934
  case "py":
9841
- return parseSymbols3({ file, lang: "py" });
9935
+ return parseSymbols3({ file, content, lang: "py" });
9842
9936
  case "rs":
9843
9937
  return parseSymbols4({ file, content, lang: "rs" });
9844
9938
  case "json":
@@ -9869,10 +9963,12 @@ async function runIndexerWithStore(store, opts) {
9869
9963
  let symbolsIndexed = 0;
9870
9964
  const isGitIgnored = await loadGitignoreMatcher(projectRoot);
9871
9965
  let files;
9966
+ let discoveredFiles = null;
9872
9967
  if (opts.files && opts.files.length > 0) {
9873
9968
  files = opts.files.map((f) => path.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path.relative(projectRoot, f).replace(/\\/g, "/"), false));
9874
9969
  } else {
9875
9970
  files = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
9971
+ discoveredFiles = new Set(files);
9876
9972
  }
9877
9973
  if (langs && langs.length > 0) {
9878
9974
  const langSet = new Set(langs);
@@ -9990,11 +10086,11 @@ async function runIndexerWithStore(store, opts) {
9990
10086
  filesIndexed++;
9991
10087
  }
9992
10088
  }
9993
- for (const [file_] of existingMeta) {
9994
- try {
9995
- await fs7.stat(file_);
9996
- } catch {
9997
- store.deleteFile(file_);
10089
+ if (discoveredFiles) {
10090
+ for (const [file_] of existingMeta) {
10091
+ if (!discoveredFiles.has(file_)) {
10092
+ store.deleteFile(file_);
10093
+ }
9998
10094
  }
9999
10095
  }
10000
10096
  const durationMs = Date.now() - startMs;