@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/builtin.js CHANGED
@@ -61,7 +61,7 @@ function createOutputSpool(opts) {
61
61
  let filePath = null;
62
62
  let failed = false;
63
63
  let finalized = false;
64
- const open = () => {
64
+ const open2 = () => {
65
65
  if (stream || failed) return;
66
66
  try {
67
67
  const dir = toolOutputDir();
@@ -94,7 +94,7 @@ function createOutputSpool(opts) {
94
94
  return;
95
95
  }
96
96
  head += text;
97
- open();
97
+ open2();
98
98
  head = "";
99
99
  return;
100
100
  }
@@ -2654,6 +2654,8 @@ var IndexStore = class {
2654
2654
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)");
2655
2655
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)");
2656
2656
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
2657
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)");
2658
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)");
2657
2659
  this.db.exec(`
2658
2660
  CREATE TABLE IF NOT EXISTS refs (
2659
2661
  id INTEGER PRIMARY KEY,
@@ -3566,9 +3568,9 @@ async function syncGoParse(filePath, content, lang) {
3566
3568
  }
3567
3569
  }
3568
3570
  async function parseSymbols3(opts) {
3569
- const { file, lang } = opts;
3571
+ const { file, content, lang } = opts;
3570
3572
  try {
3571
- return await syncPyParse(file, lang);
3573
+ return await syncPyParse(file, content, lang);
3572
3574
  } catch {
3573
3575
  return { file, lang, symbols: [], mtimeMs: Date.now() };
3574
3576
  }
@@ -3636,8 +3638,7 @@ syms = []
3636
3638
  errors = []
3637
3639
 
3638
3640
  try:
3639
- with open(sys.argv[1], "r", encoding="utf-8") as f:
3640
- source = f.read()
3641
+ source = sys.stdin.read()
3641
3642
  tree = ast.parse(source, filename=sys.argv[1])
3642
3643
  except Exception as e:
3643
3644
  errors.append(str(e))
@@ -3777,16 +3778,21 @@ visitor.visit(tree)
3777
3778
 
3778
3779
  print(json.dumps([s.to_dict() for s in syms]))
3779
3780
  `;
3780
- async function syncPyParse(filePath, lang) {
3781
+ var _cachedScriptPath = null;
3782
+ async function syncPyParse(filePath, content, lang) {
3781
3783
  try {
3782
- const tmpDir = path3.join(os2.tmpdir(), "ws-py-parse");
3783
- await fs2.mkdir(tmpDir, { recursive: true });
3784
- const scriptPath = path3.join(tmpDir, "parse.py");
3785
- await fs2.writeFile(scriptPath, PY_PARSE_SCRIPT, "utf8");
3786
- const proc = spawn("python", [scriptPath, filePath], {
3784
+ if (!_cachedScriptPath) {
3785
+ const tmpDir = path3.join(os2.tmpdir(), "ws-py-parse");
3786
+ await fs2.mkdir(tmpDir, { recursive: true });
3787
+ _cachedScriptPath = path3.join(tmpDir, "parse.py");
3788
+ await fs2.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
3789
+ }
3790
+ const proc = spawn("python", [_cachedScriptPath, filePath], {
3787
3791
  stdio: ["pipe", "pipe", "pipe"],
3788
3792
  windowsHide: true
3789
3793
  });
3794
+ proc.stdin?.write(content);
3795
+ proc.stdin?.end();
3790
3796
  let stdout = "";
3791
3797
  proc.stdout?.on("data", (chunk) => {
3792
3798
  stdout += chunk.toString();
@@ -4453,7 +4459,7 @@ async function parseFile(file, content, lang) {
4453
4459
  case "go":
4454
4460
  return parseSymbols2({ file, content, lang: "go" });
4455
4461
  case "py":
4456
- return parseSymbols3({ file, lang: "py" });
4462
+ return parseSymbols3({ file, content, lang: "py" });
4457
4463
  case "rs":
4458
4464
  return parseSymbols4({ file, content, lang: "rs" });
4459
4465
  case "json":
@@ -4484,10 +4490,12 @@ async function runIndexerWithStore(store, opts) {
4484
4490
  let symbolsIndexed = 0;
4485
4491
  const isGitIgnored = await loadGitignoreMatcher(projectRoot);
4486
4492
  let files;
4493
+ let discoveredFiles = null;
4487
4494
  if (opts.files && opts.files.length > 0) {
4488
4495
  files = opts.files.map((f) => path3.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path3.relative(projectRoot, f).replace(/\\/g, "/"), false));
4489
4496
  } else {
4490
4497
  files = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
4498
+ discoveredFiles = new Set(files);
4491
4499
  }
4492
4500
  if (langs && langs.length > 0) {
4493
4501
  const langSet = new Set(langs);
@@ -4605,11 +4613,11 @@ async function runIndexerWithStore(store, opts) {
4605
4613
  filesIndexed++;
4606
4614
  }
4607
4615
  }
4608
- for (const [file_] of existingMeta) {
4609
- try {
4610
- await fs2.stat(file_);
4611
- } catch {
4612
- store.deleteFile(file_);
4616
+ if (discoveredFiles) {
4617
+ for (const [file_] of existingMeta) {
4618
+ if (!discoveredFiles.has(file_)) {
4619
+ store.deleteFile(file_);
4620
+ }
4613
4621
  }
4614
4622
  }
4615
4623
  const durationMs = Date.now() - startMs;
@@ -6530,7 +6538,27 @@ function runGit2(args, cwd, signal) {
6530
6538
  });
6531
6539
  });
6532
6540
  }
6541
+
6542
+ // src/_concurrency.ts
6543
+ async function mapWithConcurrency(items, limit, fn) {
6544
+ if (items.length === 0) return [];
6545
+ const effectiveLimit = Math.max(1, Math.min(limit | 0, items.length));
6546
+ const results = new Array(items.length);
6547
+ let nextIndex = 0;
6548
+ const worker2 = async () => {
6549
+ while (true) {
6550
+ const i = nextIndex++;
6551
+ if (i >= items.length) return;
6552
+ results[i] = await fn(items[i]);
6553
+ }
6554
+ };
6555
+ await Promise.all(Array.from({ length: effectiveLimit }, worker2));
6556
+ return results;
6557
+ }
6558
+
6559
+ // src/glob.ts
6533
6560
  var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"];
6561
+ var WALK_CONCURRENCY = 16;
6534
6562
  var globTool = {
6535
6563
  name: "glob",
6536
6564
  category: "Filesystem",
@@ -6568,6 +6596,22 @@ var globTool = {
6568
6596
  const re = compileGlob(input.pattern);
6569
6597
  const results = [];
6570
6598
  let truncated = false;
6599
+ const pushResult = async (full) => {
6600
+ if (truncated || results.length >= limit) {
6601
+ truncated = true;
6602
+ return;
6603
+ }
6604
+ try {
6605
+ const st = await fs2.stat(full);
6606
+ if (truncated || results.length >= limit) {
6607
+ truncated = true;
6608
+ return;
6609
+ }
6610
+ results.push({ rel: full, mtime: st.mtimeMs });
6611
+ if (results.length >= limit) truncated = true;
6612
+ } catch {
6613
+ }
6614
+ };
6571
6615
  const walk = async (dir, relPrefix) => {
6572
6616
  if (results.length >= limit) {
6573
6617
  truncated = true;
@@ -6579,6 +6623,8 @@ var globTool = {
6579
6623
  } catch {
6580
6624
  return;
6581
6625
  }
6626
+ const subdirs = [];
6627
+ const matchedFiles = [];
6582
6628
  for (const e of entries) {
6583
6629
  const name = e.name;
6584
6630
  if (DEFAULT_IGNORE2.includes(name)) continue;
@@ -6586,22 +6632,36 @@ var globTool = {
6586
6632
  const rel = relPrefix ? `${relPrefix}/${name}` : name;
6587
6633
  const full = path3.join(dir, name);
6588
6634
  if (e.isDirectory()) {
6589
- await walk(full, rel);
6590
- if (truncated) return;
6635
+ subdirs.push({ full, rel });
6591
6636
  } else if (e.isFile()) {
6592
- if (re.test(rel) || re.test(name)) {
6593
- try {
6594
- const st = await fs2.stat(full);
6595
- results.push({ rel: full, mtime: st.mtimeMs });
6596
- if (results.length >= limit) {
6597
- truncated = true;
6598
- return;
6599
- }
6600
- } catch {
6637
+ re.lastIndex = 0;
6638
+ const relMatch = re.test(rel);
6639
+ re.lastIndex = 0;
6640
+ const nameMatch = re.test(name);
6641
+ if (relMatch || nameMatch) {
6642
+ matchedFiles.push(full);
6643
+ }
6644
+ } else if (e.isSymbolicLink()) {
6645
+ try {
6646
+ const st = await fs2.stat(full);
6647
+ if (st.isDirectory()) {
6648
+ subdirs.push({ full, rel });
6649
+ } else if (st.isFile()) {
6650
+ re.lastIndex = 0;
6651
+ const relMatch = re.test(rel);
6652
+ re.lastIndex = 0;
6653
+ const nameMatch = re.test(name);
6654
+ if (relMatch || nameMatch) matchedFiles.push(full);
6601
6655
  }
6656
+ } catch {
6602
6657
  }
6603
6658
  }
6659
+ if (truncated) return;
6604
6660
  }
6661
+ await mapWithConcurrency(matchedFiles, WALK_CONCURRENCY, pushResult);
6662
+ if (truncated) return;
6663
+ const remainingSubdirs = truncated ? [] : subdirs;
6664
+ await mapWithConcurrency(remainingSubdirs, WALK_CONCURRENCY, ({ full, rel }) => walk(full, rel));
6605
6665
  };
6606
6666
  await walk(base, "");
6607
6667
  results.sort((a, b) => b.mtime - a.mtime);
@@ -6665,6 +6725,8 @@ function capSubject(line) {
6665
6725
  // src/grep.ts
6666
6726
  var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
6667
6727
  var NATIVE_SCAN_CONCURRENCY = 32;
6728
+ var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
6729
+ var NATIVE_MAX_FILE_BYTES = 1e6;
6668
6730
  var grepTool = {
6669
6731
  name: "grep",
6670
6732
  category: "Search",
@@ -6889,7 +6951,8 @@ async function runNative(input, base, mode, limit, signal) {
6889
6951
  const re = compiled.regex;
6890
6952
  const globRe = input.glob ? compileGlob(input.glob) : null;
6891
6953
  const matches = [];
6892
- const fileMatches = /* @__PURE__ */ new Map();
6954
+ const countOnlyFirstHit = mode === "count" && limit === 1;
6955
+ const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
6893
6956
  let total = 0;
6894
6957
  let stopped = false;
6895
6958
  const scanFile = async (full, name) => {
@@ -6898,34 +6961,79 @@ async function runNative(input, base, mode, limit, signal) {
6898
6961
  if (globRe) globRe.lastIndex = 0;
6899
6962
  try {
6900
6963
  const stat11 = await fs2.stat(full);
6901
- if (stat11.size > 1e6 || stopped || signal.aborted) return;
6902
- const head = await fs2.readFile(full);
6903
- if (isBinaryBuffer(head) || stopped || signal.aborted) return;
6904
- const text = head.toString("utf8");
6905
- const lines = text.split(/\r?\n/);
6906
- let fileHits = 0;
6907
- for (let i = 0; i < lines.length; i++) {
6908
- if (stopped || signal.aborted) break;
6909
- const ln = capSubject(lines[i] ?? "");
6910
- re.lastIndex = 0;
6911
- if (re.test(ln)) {
6912
- fileHits++;
6913
- total++;
6914
- if (mode === "content" && matches.length < limit) {
6915
- matches.push(`${full}:${i + 1}:${ln}`);
6964
+ if (!stat11.isFile() || stat11.size > maxBytes || stopped || signal.aborted) return;
6965
+ const file = await fs2.open(full, "r");
6966
+ try {
6967
+ let bytesReadTotal = 0;
6968
+ let lineNumber = 0;
6969
+ let fileHits = 0;
6970
+ let leftover = "";
6971
+ let binaryChecked = false;
6972
+ const buffer = Buffer.allocUnsafe(Math.min(NATIVE_READ_CHUNK_BYTES, maxBytes));
6973
+ while (!stopped && !signal.aborted && bytesReadTotal < maxBytes) {
6974
+ const remaining = maxBytes - bytesReadTotal;
6975
+ const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, remaining), null);
6976
+ if (bytesRead === 0) break;
6977
+ const chunk = buffer.subarray(0, bytesRead);
6978
+ if (!binaryChecked) {
6979
+ binaryChecked = true;
6980
+ if (isBinaryBuffer(chunk)) return;
6981
+ }
6982
+ bytesReadTotal += bytesRead;
6983
+ const text = leftover + chunk.toString("utf8");
6984
+ const lines = text.split(/\r?\n/);
6985
+ leftover = lines.pop() ?? "";
6986
+ for (const rawLine of lines) {
6987
+ if (stopped || signal.aborted) break;
6988
+ lineNumber++;
6989
+ const ln = capSubject(rawLine);
6990
+ re.lastIndex = 0;
6991
+ if (!re.test(ln)) continue;
6992
+ fileHits++;
6993
+ total++;
6994
+ if (mode === "content") {
6995
+ if (matches.length < limit) matches.push(`${full}:${lineNumber}:${ln}`);
6996
+ } else if (mode === "files_with_matches") {
6997
+ if (fileHits === 1 && matches.length < limit) matches.push(full);
6998
+ break;
6999
+ } else if (fileHits === 1 && matches.length < limit) {
7000
+ matches.push(`${full}:${countOnlyFirstHit ? 1 : 0}`);
7001
+ }
7002
+ if (countOnlyFirstHit || mode !== "content" && matches.length >= limit) {
7003
+ stopped = true;
7004
+ break;
7005
+ }
6916
7006
  }
7007
+ if (mode === "files_with_matches" && fileHits > 0) break;
6917
7008
  }
6918
- }
6919
- if (fileHits > 0) {
6920
- fileMatches.set(full, fileHits);
6921
- if (mode === "files_with_matches" && matches.length < limit) {
6922
- matches.push(full);
7009
+ if (!stopped && !signal.aborted && leftover.length > 0) {
7010
+ lineNumber++;
7011
+ const ln = capSubject(leftover);
7012
+ re.lastIndex = 0;
7013
+ if (re.test(ln)) {
7014
+ fileHits++;
7015
+ total++;
7016
+ if (mode === "content") {
7017
+ if (matches.length < limit) matches.push(`${full}:${lineNumber}:${ln}`);
7018
+ } else if (mode === "files_with_matches") {
7019
+ if (matches.length < limit) matches.push(full);
7020
+ } else if (matches.length < limit) {
7021
+ matches.push(`${full}:${countOnlyFirstHit ? 1 : fileHits}`);
7022
+ }
7023
+ }
6923
7024
  }
6924
- if (mode === "count" && matches.length < limit) {
6925
- matches.push(`${full}:${fileHits}`);
7025
+ if (fileHits > 0) {
7026
+ if (mode === "count") {
7027
+ const idx = matches.findIndex((entry) => entry.startsWith(`${full}:`));
7028
+ if (idx !== -1) matches[idx] = `${full}:${fileHits}`;
7029
+ }
7030
+ if (mode === "files_with_matches" && matches.length >= limit) stopped = true;
6926
7031
  }
7032
+ if (mode === "content" && matches.length >= limit) stopped = true;
7033
+ if (mode === "count" && matches.length >= limit && (countOnlyFirstHit || mode !== "count")) stopped = true;
7034
+ } finally {
7035
+ await file.close();
6927
7036
  }
6928
- if (matches.length >= limit) stopped = true;
6929
7037
  } catch {
6930
7038
  }
6931
7039
  };
@@ -6938,18 +7046,20 @@ async function runNative(input, base, mode, limit, signal) {
6938
7046
  return;
6939
7047
  }
6940
7048
  const files = [];
7049
+ const subdirs = [];
6941
7050
  for (const e of entries) {
6942
7051
  if (stopped) return;
6943
7052
  if (DEFAULT_IGNORE3.includes(e.name)) continue;
6944
7053
  if (e.isSymbolicLink()) continue;
6945
7054
  const full = path3.join(dir, e.name);
6946
7055
  if (e.isDirectory()) {
6947
- await walk(full);
7056
+ subdirs.push(full);
6948
7057
  } else if (e.isFile()) {
6949
7058
  files.push({ full, name: e.name });
6950
7059
  }
6951
7060
  }
6952
7061
  await mapWithConcurrency(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
7062
+ await mapWithConcurrency(subdirs, Math.min(16, NATIVE_SCAN_CONCURRENCY), walk);
6953
7063
  };
6954
7064
  await walk(base);
6955
7065
  return {
@@ -6959,20 +7069,6 @@ async function runNative(input, base, mode, limit, signal) {
6959
7069
  used: "native"
6960
7070
  };
6961
7071
  }
6962
- async function mapWithConcurrency(items, concurrency, fn) {
6963
- if (items.length === 0) return;
6964
- let next = 0;
6965
- const workerCount = Math.min(Math.max(1, concurrency), items.length);
6966
- const workers = Array.from({ length: workerCount }, async () => {
6967
- for (; ; ) {
6968
- const idx = next++;
6969
- if (idx >= items.length) return;
6970
- const item = items[idx];
6971
- if (item !== void 0) await fn(item);
6972
- }
6973
- });
6974
- await Promise.all(workers);
6975
- }
6976
7072
  var installTool = {
6977
7073
  name: "install",
6978
7074
  category: "Package Management",
@@ -8017,13 +8113,13 @@ ${formatTaskList(taskFile.tasks)}`
8017
8113
  }
8018
8114
  };
8019
8115
  function mkResult(plan, ok, message, todos) {
8020
- const open = plan.items.filter((i) => i.status !== "done").length;
8116
+ const open2 = plan.items.filter((i) => i.status !== "done").length;
8021
8117
  const result = {
8022
8118
  ok,
8023
8119
  message,
8024
8120
  plan: formatPlan(plan),
8025
8121
  count: plan.items.length,
8026
- open
8122
+ open: open2
8027
8123
  };
8028
8124
  if (todos !== void 0) result.todos = todos;
8029
8125
  return result;