@wrongstack/tools 0.273.0 → 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
@@ -7,11 +7,12 @@ import { spawn, execFileSync } from 'node:child_process';
7
7
  import * as os2 from 'node:os';
8
8
  import * as fs8 from 'node:fs';
9
9
  import { statSync, mkdirSync, createWriteStream } from 'node:fs';
10
+ import { toErrorMessage as toErrorMessage$1 } from '@wrongstack/core/utils/error';
10
11
  import * as dns from 'node:dns/promises';
11
12
  import * as net from 'node:net';
12
13
  import { Agent } from 'undici';
13
14
  import TurndownService from 'turndown';
14
- import { toErrorMessage as toErrorMessage$1 } from '@wrongstack/core/utils';
15
+ import { toErrorMessage as toErrorMessage$2 } from '@wrongstack/core/utils';
15
16
  import { randomUUID } from 'node:crypto';
16
17
  import { createRequire } from 'node:module';
17
18
  import { fileURLToPath } from 'node:url';
@@ -777,7 +778,27 @@ async function globNative(pattern, base, extraGlob) {
777
778
  await walk(base);
778
779
  return results;
779
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
780
800
  var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"];
801
+ var WALK_CONCURRENCY = 16;
781
802
  var globTool = {
782
803
  name: "glob",
783
804
  category: "Filesystem",
@@ -815,6 +836,22 @@ var globTool = {
815
836
  const re = compileGlob(input.pattern);
816
837
  const results = [];
817
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
+ };
818
855
  const walk = async (dir, relPrefix) => {
819
856
  if (results.length >= limit) {
820
857
  truncated = true;
@@ -826,6 +863,8 @@ var globTool = {
826
863
  } catch {
827
864
  return;
828
865
  }
866
+ const subdirs = [];
867
+ const matchedFiles = [];
829
868
  for (const e of entries) {
830
869
  const name = e.name;
831
870
  if (DEFAULT_IGNORE2.includes(name)) continue;
@@ -833,22 +872,36 @@ var globTool = {
833
872
  const rel = relPrefix ? `${relPrefix}/${name}` : name;
834
873
  const full = path.join(dir, name);
835
874
  if (e.isDirectory()) {
836
- await walk(full, rel);
837
- if (truncated) return;
875
+ subdirs.push({ full, rel });
838
876
  } else if (e.isFile()) {
839
- if (re.test(rel) || re.test(name)) {
840
- try {
841
- const st = await fs7.stat(full);
842
- results.push({ rel: full, mtime: st.mtimeMs });
843
- if (results.length >= limit) {
844
- truncated = true;
845
- return;
846
- }
847
- } 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);
848
895
  }
896
+ } catch {
849
897
  }
850
898
  }
899
+ if (truncated) return;
851
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));
852
905
  };
853
906
  await walk(base, "");
854
907
  results.sort((a, b) => b.mtime - a.mtime);
@@ -865,6 +918,8 @@ async function readGitignore(dir) {
865
918
  }
866
919
  var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
867
920
  var NATIVE_SCAN_CONCURRENCY = 32;
921
+ var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
922
+ var NATIVE_MAX_FILE_BYTES = 1e6;
868
923
  var grepTool = {
869
924
  name: "grep",
870
925
  category: "Search",
@@ -1089,7 +1144,8 @@ async function runNative(input, base, mode, limit, signal) {
1089
1144
  const re = compiled.regex;
1090
1145
  const globRe = input.glob ? compileGlob(input.glob) : null;
1091
1146
  const matches = [];
1092
- 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);
1093
1149
  let total = 0;
1094
1150
  let stopped = false;
1095
1151
  const scanFile = async (full, name) => {
@@ -1098,34 +1154,79 @@ async function runNative(input, base, mode, limit, signal) {
1098
1154
  if (globRe) globRe.lastIndex = 0;
1099
1155
  try {
1100
1156
  const stat11 = await fs7.stat(full);
1101
- if (stat11.size > 1e6 || stopped || signal.aborted) return;
1102
- const head = await fs7.readFile(full);
1103
- if (isBinaryBuffer(head) || stopped || signal.aborted) return;
1104
- const text = head.toString("utf8");
1105
- const lines = text.split(/\r?\n/);
1106
- let fileHits = 0;
1107
- for (let i = 0; i < lines.length; i++) {
1108
- if (stopped || signal.aborted) break;
1109
- const ln = capSubject(lines[i] ?? "");
1110
- re.lastIndex = 0;
1111
- if (re.test(ln)) {
1112
- fileHits++;
1113
- total++;
1114
- if (mode === "content" && matches.length < limit) {
1115
- 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
+ }
1116
1199
  }
1200
+ if (mode === "files_with_matches" && fileHits > 0) break;
1117
1201
  }
1118
- }
1119
- if (fileHits > 0) {
1120
- fileMatches.set(full, fileHits);
1121
- if (mode === "files_with_matches" && matches.length < limit) {
1122
- 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
+ }
1123
1217
  }
1124
- if (mode === "count" && matches.length < limit) {
1125
- 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;
1126
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();
1127
1229
  }
1128
- if (matches.length >= limit) stopped = true;
1129
1230
  } catch {
1130
1231
  }
1131
1232
  };
@@ -1138,18 +1239,20 @@ async function runNative(input, base, mode, limit, signal) {
1138
1239
  return;
1139
1240
  }
1140
1241
  const files = [];
1242
+ const subdirs = [];
1141
1243
  for (const e of entries) {
1142
1244
  if (stopped) return;
1143
1245
  if (DEFAULT_IGNORE3.includes(e.name)) continue;
1144
1246
  if (e.isSymbolicLink()) continue;
1145
1247
  const full = path.join(dir, e.name);
1146
1248
  if (e.isDirectory()) {
1147
- await walk(full);
1249
+ subdirs.push(full);
1148
1250
  } else if (e.isFile()) {
1149
1251
  files.push({ full, name: e.name });
1150
1252
  }
1151
1253
  }
1152
1254
  await mapWithConcurrency(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
1255
+ await mapWithConcurrency(subdirs, Math.min(16, NATIVE_SCAN_CONCURRENCY), walk);
1153
1256
  };
1154
1257
  await walk(base);
1155
1258
  return {
@@ -1159,20 +1262,6 @@ async function runNative(input, base, mode, limit, signal) {
1159
1262
  used: "native"
1160
1263
  };
1161
1264
  }
1162
- async function mapWithConcurrency(items, concurrency, fn) {
1163
- if (items.length === 0) return;
1164
- let next = 0;
1165
- const workerCount = Math.min(Math.max(1, concurrency), items.length);
1166
- const workers = Array.from({ length: workerCount }, async () => {
1167
- for (; ; ) {
1168
- const idx = next++;
1169
- if (idx >= items.length) return;
1170
- const item = items[idx];
1171
- if (item !== void 0) await fn(item);
1172
- }
1173
- });
1174
- await Promise.all(workers);
1175
- }
1176
1265
  var SPOOL_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
1177
1266
  var SPOOL_WRITE_HWM_BYTES = 4 * 1024 * 1024;
1178
1267
  var sweepStarted = false;
@@ -1214,7 +1303,7 @@ function createOutputSpool(opts) {
1214
1303
  let filePath = null;
1215
1304
  let failed = false;
1216
1305
  let finalized = false;
1217
- const open = () => {
1306
+ const open2 = () => {
1218
1307
  if (stream || failed) return;
1219
1308
  try {
1220
1309
  const dir = toolOutputDir();
@@ -1247,7 +1336,7 @@ function createOutputSpool(opts) {
1247
1336
  return;
1248
1337
  }
1249
1338
  head += text;
1250
- open();
1339
+ open2();
1251
1340
  head = "";
1252
1341
  return;
1253
1342
  }
@@ -2379,6 +2468,155 @@ async function checkAndBlockKillCommand(command) {
2379
2468
  }
2380
2469
  return { blocked: false };
2381
2470
  }
2471
+ function pickShell(platform4, command, env) {
2472
+ const override = env.get("WRONGSTACK_SHELL")?.trim().toLowerCase();
2473
+ if (override === "cmd" || override === "cmd.exe") return "cmd";
2474
+ if (override === "powershell" || override === "powershell.exe") return "powershell";
2475
+ if (override === "pwsh" || override === "pwsh.exe") return "pwsh";
2476
+ if (looksLikePowerShell(command)) return "pwsh";
2477
+ return "cmd";
2478
+ }
2479
+ function looksLikePowerShell(command) {
2480
+ if (!command) return false;
2481
+ const trimmed = command.trimStart();
2482
+ if (/\.ps1\b/i.test(trimmed)) return true;
2483
+ if (/^\s*#requires\s/i.test(trimmed)) return true;
2484
+ if (/^\s*param\s*\(/i.test(trimmed)) return true;
2485
+ if (/\$[\w:{]/i.test(trimmed)) return true;
2486
+ if (/\$\(/.test(trimmed)) return true;
2487
+ if (/@\s*['"]/.test(trimmed)) return true;
2488
+ if (/&\s+\$/.test(trimmed)) return true;
2489
+ if (/(^|\s)@\s*\(/.test(trimmed)) return true;
2490
+ if (/(^|\s)@\{/.test(trimmed)) return true;
2491
+ if (/(?:^|[\s\[\(\{,;])(?:-eq|-ne|-lt|-gt|-le|-ge|-like|-notlike|-match|-notmatch|-contains|-notcontains|-in|-notin|-and|-or|-not|-band|-bor|-bxor|-replace|-isplit|-csplit|-osplit|-join|-is|-as|-f)(?:$|[\s\]\)\},;])/i.test(trimmed)) {
2492
+ return true;
2493
+ }
2494
+ if (PS_VERB_RE.test(trimmed)) return true;
2495
+ if (/(?:^|[\s;&|])(gci|gi|gp|gcm|gps|sl|rm|cat|cp|mv)\b/i.test(trimmed)) {
2496
+ return true;
2497
+ }
2498
+ if (looksLikePowerShellExtended(command)) return true;
2499
+ return false;
2500
+ }
2501
+ function looksLikePowerShellExtended(command) {
2502
+ if (!command) return false;
2503
+ const trimmed = command.trimStart();
2504
+ if (/(?:^|\s)[-/](?:WhatIf|Confirm|ErrorAction)(?::[^\s]+|\s|=|$)/i.test(trimmed)) {
2505
+ return true;
2506
+ }
2507
+ if (/(?:^|[\s;&|])(Where-Object|ForEach-Object|Select-Object|Sort-Object|Group-Object|Measure-Object|Compare-Object|Tee-Object)(?:\s|$)/i.test(trimmed)) {
2508
+ return true;
2509
+ }
2510
+ if (/\bWrite-(?:Host|Output|Error|Warning|Verbose|Debug|Information)(?:\s|$)/i.test(trimmed)) {
2511
+ return true;
2512
+ }
2513
+ if (/HK(?:LM|CU|CR|U|CC|DD|PD):\\/i.test(trimmed)) return true;
2514
+ if (/\[(?:string|int|bool|xml|double|float|decimal|char|byte|long|System\.)/i.test(trimmed)) {
2515
+ return true;
2516
+ }
2517
+ if (/^\s*<#|#>\s*$/m.test(trimmed)) return true;
2518
+ if (/(?:^|\s)[-/\/](?:AsPlainText|PipelineVariable|pv|FilterHashtable|OutVariable|ov)(?:\s|=|$)/i.test(trimmed)) {
2519
+ return true;
2520
+ }
2521
+ return false;
2522
+ }
2523
+ function wrapPowerShellScript(command) {
2524
+ const bootstrap = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;$ConfirmPreference='None';$WhatIfPreference=$false";
2525
+ return "\uFEFF" + bootstrap + "\ntry {\n" + command + "\n} finally { exit $LASTEXITCODE }";
2526
+ }
2527
+ var PS_VERB_RE = new RegExp(
2528
+ // Boundaries: start-of-string, whitespace, `;`, `&`, `|`, `(`, `{`, `,`.
2529
+ "(?:^|[\\s;&|\\(\\{,])(?:Get|Set|New|Remove|Add|Clear|Copy|Move|Rename|Test|Update|Write|Read|Push|Pop|Invoke|Start|Stop|Wait|Out|Format|Group|Measure|Compare|Resolve|ConvertTo|ConvertFrom|Convert|Import|Export|Select|Where|ForEach|Sort|Tee|Split|Join|Limit|Skip|Step|Trace|Debug|Register|Unregister|Enable|Disable|Restart|Suspend|Resume|Save|Open|Close|Lock|Unlock|Mount|Dismount|Enter|Exit|Use|Show|Hide|Find|Search|Watch|Initialize|Optimize|Compress|Expand|Merge|Checkpoint|Undo|Redo|Approve|Deny|Block|Grant|Revoke|Assert|Confirm|Receive|Send|Connect|Disconnect|Reset|Backup|Restore|Publish|Unpublish|Install|Uninstall|Build|Rebuild|Deploy|Submit|Process|Complete|Approve|Revoke|Pay|Refund|Decline|Receive|Send)-[A-Za-z][A-Za-z0-9]+(?:[\\-\\+][A-Za-z][A-Za-z0-9]+)*(?:$|[\\s\\-\\;\\&\\|\\(\\)\\{\\},])",
2530
+ "i"
2531
+ );
2532
+ function shellArgs(shell) {
2533
+ if (shell === "powershell" || shell === "pwsh") {
2534
+ return ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "-"];
2535
+ }
2536
+ return ["/c"];
2537
+ }
2538
+ function diagnoseBashism(command, shell) {
2539
+ if (!command) return void 0;
2540
+ const isCmd = shell === "cmd";
2541
+ const hints = [];
2542
+ const add = (h) => {
2543
+ if (!hints.includes(h)) hints.push(h);
2544
+ };
2545
+ if (/\/dev\/null/.test(command)) {
2546
+ add(
2547
+ isCmd ? "use `nul` instead of `/dev/null` (e.g. `2>nul`)" : "use `$null` instead of `/dev/null` (e.g. `2>$null`)"
2548
+ );
2549
+ }
2550
+ if (/(^|[;&|]\s*)export\s+[A-Za-z_]\w*=/.test(command)) {
2551
+ add(
2552
+ isCmd ? "set env vars with `set NAME=value`, not `export`" : "set env vars with `$env:NAME = 'value'`, not `export`"
2553
+ );
2554
+ }
2555
+ if (/<<-?\s*['"]?[A-Za-z_]\w*/.test(command)) {
2556
+ add(
2557
+ isCmd ? "cmd has no heredocs \u2014 write the content to a file or use multiple `echo` lines" : "PowerShell has no heredocs \u2014 use a single-quoted here-string `@'\u2026'@` (closing `'@` at column 0)"
2558
+ );
2559
+ }
2560
+ if (shell === "powershell" && /(&&|\|\|)/.test(command)) {
2561
+ add("Windows PowerShell 5.1 has no `&&`/`||` \u2014 separate commands with `;` (check `$LASTEXITCODE`)");
2562
+ }
2563
+ if (/\brm\s+-[A-Za-z]*[rf]/.test(command)) {
2564
+ add(
2565
+ isCmd ? "`rm` is not a cmd builtin \u2014 use `del` (files) or `rmdir /s /q` (dirs)" : "use `Remove-Item -Recurse -Force` \u2014 the `rm -rf` bash flags don't exist in PowerShell"
2566
+ );
2567
+ }
2568
+ if (/\bwhich\s+\S/.test(command)) {
2569
+ add(isCmd ? "use `where <cmd>` instead of `which`" : "use `Get-Command <cmd>` instead of `which`");
2570
+ }
2571
+ if (hints.length === 0) return void 0;
2572
+ const label = isCmd ? "cmd.exe" : shell === "pwsh" ? "PowerShell 7" : "Windows PowerShell";
2573
+ return `[wrongstack] This command failed and contains bash/POSIX syntax that ${label} does not accept \u2014 ${hints.join("; ")}. Rewrite it in ${isCmd ? "cmd" : "PowerShell"} syntax and retry.`;
2574
+ }
2575
+ function resolveWin32Command(cmd) {
2576
+ if (process.platform !== "win32") return cmd;
2577
+ if (cmd.includes("/") || cmd.includes("\\") || path.extname(cmd.replace(/\//g, "\\"))) {
2578
+ return cmd;
2579
+ }
2580
+ const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
2581
+ const pathDirs = (process.env["PATH"] ?? "").split(path.delimiter);
2582
+ for (const dir of pathDirs) {
2583
+ const base = path.join(dir, cmd);
2584
+ for (const ext of pathext) {
2585
+ const full = `${base}${ext}`;
2586
+ try {
2587
+ fs8.accessSync(full, fs8.constants.X_OK);
2588
+ return full;
2589
+ } catch {
2590
+ }
2591
+ }
2592
+ }
2593
+ return cmd;
2594
+ }
2595
+ function resolvePowerShell(cmd) {
2596
+ if (process.platform !== "win32") return cmd;
2597
+ const lower = cmd.toLowerCase();
2598
+ if (lower !== "pwsh" && lower !== "powershell" && lower !== "pwsh.exe" && lower !== "powershell.exe") {
2599
+ return resolveWin32Command(cmd);
2600
+ }
2601
+ const primary = lower.startsWith("pwsh") ? "pwsh.exe" : "powershell.exe";
2602
+ const fallback = lower.startsWith("pwsh") ? "powershell.exe" : "pwsh.exe";
2603
+ const resolved = resolveWin32Command(primary);
2604
+ if (resolved !== primary) {
2605
+ const fb = resolveWin32Command(fallback);
2606
+ return fb === fallback ? cmd : fb;
2607
+ }
2608
+ return resolved;
2609
+ }
2610
+ var WIN32_SHELL_META = /[&|<>\r\n\0]/;
2611
+ function assertSafeWin32ShellArgs(args) {
2612
+ for (const a of args) {
2613
+ if (typeof a === "string" && WIN32_SHELL_META.test(a)) {
2614
+ throw new Error(
2615
+ "win32 shell spawn: argument contains a shell metacharacter (one of & | < > or a newline) that could enable command injection through the .cmd/.bat wrapper \u2014 refusing to run. Offending argument: " + JSON.stringify(a)
2616
+ );
2617
+ }
2618
+ }
2619
+ }
2382
2620
 
2383
2621
  // src/bash.ts
2384
2622
  var MAX_OUTPUT = 32768;
@@ -2475,18 +2713,36 @@ var bashTool = {
2475
2713
  }
2476
2714
  const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
2477
2715
  const isWin3 = os2.platform() === "win32";
2478
- const shell = (() => {
2479
- const explicit = process.env[isWin3 ? "WRONGSTACK_COMSPEC" : "WRONGSTACK_SHELL"];
2480
- if (explicit) return explicit;
2481
- if (isWin3) return process.env["COMSPEC"] ?? "cmd.exe";
2482
- const fromEnv = process.env["SHELL"];
2483
- if (fromEnv) {
2484
- const name = fromEnv.split("/").pop() ?? "";
2485
- if (["bash", "zsh", "sh", "dash", "fish"].includes(name)) return fromEnv;
2486
- }
2487
- return "/bin/bash";
2488
- })();
2489
- const args = isWin3 ? ["/c", input.command] : ["-c", input.command];
2716
+ let plan;
2717
+ let winShellKind;
2718
+ if (isWin3) {
2719
+ const shell2 = pickShell("win32", input.command, {
2720
+ get: (k) => process.env[k]
2721
+ });
2722
+ winShellKind = shell2;
2723
+ const bin = shell2 === "powershell" ? resolvePowerShell("powershell.exe") : shell2 === "pwsh" ? resolvePowerShell("pwsh.exe") : process.env["COMSPEC"] ?? "cmd.exe";
2724
+ plan = {
2725
+ bin,
2726
+ argv: shellArgs(shell2),
2727
+ useStdin: shell2 === "powershell" || shell2 === "pwsh",
2728
+ stdinBody: shell2 === "powershell" || shell2 === "pwsh" ? wrapPowerShellScript(input.command) : void 0
2729
+ };
2730
+ } else {
2731
+ const explicit = process.env["WRONGSTACK_SHELL"];
2732
+ let bin;
2733
+ if (explicit) bin = explicit;
2734
+ else {
2735
+ const fromEnv = process.env["SHELL"];
2736
+ if (fromEnv) {
2737
+ const name = fromEnv.split("/").pop() ?? "";
2738
+ if (["bash", "zsh", "sh", "dash", "fish"].includes(name)) bin = fromEnv;
2739
+ else bin = "/bin/bash";
2740
+ } else bin = "/bin/bash";
2741
+ }
2742
+ plan = { bin, argv: ["-c"], useStdin: false, stdinBody: void 0 };
2743
+ }
2744
+ const shell = plan.bin;
2745
+ const args = plan.useStdin ? [...plan.argv] : [...plan.argv, input.command];
2490
2746
  const env = buildChildEnv(ctx.session?.id);
2491
2747
  const detached = !isWin3;
2492
2748
  const startedAt = Date.now();
@@ -2496,7 +2752,9 @@ var bashTool = {
2496
2752
  const child2 = spawn(shell, args, {
2497
2753
  cwd: ctx.projectRoot,
2498
2754
  env,
2499
- stdio: ["ignore", "pipe", "pipe"],
2755
+ // PowerShell takes the script on stdin (no argv quoting); cmd.exe
2756
+ // and POSIX shells ignore stdin when given the command inline.
2757
+ stdio: [plan.useStdin ? "pipe" : "ignore", "pipe", "pipe"],
2500
2758
  // win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when
2501
2759
  // DETACHED_PROCESS (detached: true) is set, so the console-less
2502
2760
  // cmd.exe's grandchildren (node, dev servers) each allocate a fresh
@@ -2507,6 +2765,13 @@ var bashTool = {
2507
2765
  detached: !isWin3,
2508
2766
  windowsHide: true
2509
2767
  });
2768
+ if (plan.useStdin) {
2769
+ try {
2770
+ child2.stdin?.write(plan.stdinBody ?? input.command);
2771
+ child2.stdin?.end();
2772
+ } catch {
2773
+ }
2774
+ }
2510
2775
  const pid2 = child2.pid;
2511
2776
  if (typeof pid2 === "number") {
2512
2777
  registry.register({
@@ -2561,11 +2826,20 @@ var bashTool = {
2561
2826
  const child = spawn(shell, args, {
2562
2827
  cwd: ctx.projectRoot,
2563
2828
  env,
2564
- stdio: ["ignore", "pipe", "pipe"],
2829
+ // PowerShell takes the script on stdin (no argv quoting); cmd.exe
2830
+ // and POSIX shells ignore stdin when given the command inline.
2831
+ stdio: [plan.useStdin ? "pipe" : "ignore", "pipe", "pipe"],
2565
2832
  detached,
2566
2833
  windowsHide: true,
2567
2834
  ...isWin3 ? {} : { signal: opts.signal }
2568
2835
  });
2836
+ if (plan.useStdin) {
2837
+ try {
2838
+ child.stdin?.write(plan.stdinBody ?? input.command);
2839
+ child.stdin?.end();
2840
+ } catch {
2841
+ }
2842
+ }
2569
2843
  const pid = child.pid;
2570
2844
  if (typeof pid === "number") {
2571
2845
  registry.register({
@@ -2716,10 +2990,13 @@ var bashTool = {
2716
2990
  yield { type: "partial_output", text: remainder };
2717
2991
  }
2718
2992
  const spooled = spool.finalize();
2993
+ const hint = !timedOut && typeof c.code === "number" && c.code !== 0 && winShellKind ? diagnoseBashism(input.command, winShellKind) : void 0;
2719
2994
  yield {
2720
2995
  type: "final",
2721
2996
  output: {
2722
- output: normalizeCommandOutput(buf) + (spooled ? spoolNote(spooled) : ""),
2997
+ output: normalizeCommandOutput(buf) + (spooled ? spoolNote(spooled) : "") + (hint ? `
2998
+
2999
+ ${hint}` : ""),
2723
3000
  exit_code: c.code,
2724
3001
  timed_out: timedOut
2725
3002
  }
@@ -2747,82 +3024,115 @@ var bashTool = {
2747
3024
  }
2748
3025
  }
2749
3026
  };
2750
- function resolveWin32Command(cmd) {
2751
- if (process.platform !== "win32") return cmd;
2752
- if (cmd.includes("/") || cmd.includes("\\") || path.extname(cmd.replace(/\//g, "\\"))) {
2753
- return cmd;
2754
- }
2755
- const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
2756
- const pathDirs = (process.env["PATH"] ?? "").split(path.delimiter);
2757
- for (const dir of pathDirs) {
2758
- const base = path.join(dir, cmd);
2759
- for (const ext of pathext) {
2760
- const full = `${base}${ext}`;
2761
- try {
2762
- fs8.accessSync(full, fs8.constants.X_OK);
2763
- return full;
2764
- } catch {
2765
- }
2766
- }
2767
- }
2768
- return cmd;
2769
- }
2770
- var WIN32_SHELL_META = /[&|<>\r\n\0]/;
2771
- function assertSafeWin32ShellArgs(args) {
2772
- for (const a of args) {
2773
- if (typeof a === "string" && WIN32_SHELL_META.test(a)) {
2774
- throw new Error(
2775
- "win32 shell spawn: argument contains a shell metacharacter (one of & | < > or a newline) that could enable command injection through the .cmd/.bat wrapper \u2014 refusing to run. Offending argument: " + JSON.stringify(a)
2776
- );
2777
- }
2778
- }
2779
- }
2780
3027
 
2781
- // src/exec.ts
3028
+ // src/_session-shell.ts
3029
+ function normalizeShell(value) {
3030
+ const v = value?.trim().toLowerCase();
3031
+ if (v === "cmd" || v === "cmd.exe") return "cmd";
3032
+ if (v === "powershell" || v === "powershell.exe") return "powershell";
3033
+ if (v === "pwsh" || v === "pwsh.exe") return "pwsh";
3034
+ return void 0;
3035
+ }
3036
+ function resolveSessionShell(platform4, env, deps = {}) {
3037
+ if (platform4 !== "win32") return void 0;
3038
+ const override = normalizeShell(env.get("WRONGSTACK_SHELL"));
3039
+ if (override) return override;
3040
+ const hasBinary = deps.hasBinary ?? ((bin) => resolveWin32Command(bin) !== bin);
3041
+ if (hasBinary("pwsh.exe")) return "pwsh";
3042
+ if (hasBinary("powershell.exe")) return "powershell";
3043
+ return "cmd";
3044
+ }
3045
+ function ensureSessionShell(opts = {}) {
3046
+ const env = opts.env ?? process.env;
3047
+ const platform4 = opts.platform ?? process.platform;
3048
+ if (platform4 !== "win32") return void 0;
3049
+ const existing = normalizeShell(env["WRONGSTACK_SHELL"]);
3050
+ if (existing) return existing;
3051
+ const chosen = resolveSessionShell(platform4, { get: (k) => env[k] }, { hasBinary: opts.hasBinary }) ?? "cmd";
3052
+ env["WRONGSTACK_SHELL"] = chosen;
3053
+ return chosen;
3054
+ }
2782
3055
  var isWin = process.platform === "win32";
2783
- var ALLOWED_COMMANDS = {
2784
- node: ["--version", "-r", "--input-type=module"],
2785
- npm: ["--version", "list", "pkg", "doctor", "view", "outdated", "audit"],
2786
- pnpm: ["--version", "remove", "list", "view", "outdated", "audit"],
2787
- npx: ["--version"],
2788
- git: [
2789
- "--version",
2790
- "status",
2791
- "log",
2792
- "diff",
2793
- "branch",
2794
- "checkout",
2795
- "stash",
2796
- "add",
2797
- "commit",
2798
- "push",
2799
- "pull"
2800
- ],
2801
- ls: ["-la", "-l", "-a"],
2802
- cat: [],
2803
- head: ["-n"],
2804
- tail: ["-n"],
2805
- wc: ["-l", "-w", "-c"],
2806
- grep: [],
2807
- find: [],
2808
- echo: [],
2809
- mkdir: ["-p"],
2810
- cp: ["-r"],
2811
- mv: [],
2812
- rm: ["-rf"],
2813
- touch: [],
2814
- bun: ["--version"],
2815
- tsc: ["--version", "--noEmit", "--project"],
2816
- vitest: ["--version", "run", "--coverage"],
2817
- biome: ["--version", "lint", "format", "check"],
2818
- cargo: ["--version", "build", "test", "check"],
2819
- rustc: ["--version"],
2820
- go: ["version", "run", "build", "test"],
2821
- python: ["--version"],
2822
- pip: ["--version", "list"],
2823
- docker: ["--version", "ps", "images"],
2824
- kubectl: ["version", "get", "describe", "logs"]
2825
- };
3056
+ var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
3057
+ // JS / TS toolchain
3058
+ "node",
3059
+ "npm",
3060
+ "pnpm",
3061
+ "yarn",
3062
+ "npx",
3063
+ "bun",
3064
+ "deno",
3065
+ "tsc",
3066
+ "vitest",
3067
+ "jest",
3068
+ "biome",
3069
+ "eslint",
3070
+ "prettier",
3071
+ // version control
3072
+ "git",
3073
+ // Rust
3074
+ "cargo",
3075
+ "rustc",
3076
+ // Go
3077
+ "go",
3078
+ // Python
3079
+ "python",
3080
+ "python3",
3081
+ "pip",
3082
+ "pip3",
3083
+ // Ruby
3084
+ "ruby",
3085
+ "gem",
3086
+ "bundle",
3087
+ // JVM
3088
+ "java",
3089
+ "javac",
3090
+ "mvn",
3091
+ "gradle",
3092
+ "gradlew",
3093
+ // .NET
3094
+ "dotnet",
3095
+ // C / C++ / native build
3096
+ "make",
3097
+ "cmake",
3098
+ // containers / orchestration (read-only subcommands; see BLOCKED_ARG_PATTERNS)
3099
+ "docker",
3100
+ "kubectl",
3101
+ // common POSIX file/text utilities
3102
+ "ls",
3103
+ "cat",
3104
+ "head",
3105
+ "tail",
3106
+ "wc",
3107
+ "grep",
3108
+ "find",
3109
+ "echo",
3110
+ "mkdir",
3111
+ "cp",
3112
+ "mv",
3113
+ "rm",
3114
+ "touch"
3115
+ ]);
3116
+ var allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
3117
+ var normalizeCmd = (c) => c.trim();
3118
+ function configureExecPolicy(opts = {}) {
3119
+ const next = new Set(DEFAULT_ALLOWED_COMMANDS);
3120
+ for (const c of opts.allow ?? []) {
3121
+ const n = normalizeCmd(c);
3122
+ if (n) next.add(n);
3123
+ }
3124
+ for (const c of opts.deny ?? []) next.delete(normalizeCmd(c));
3125
+ allowedCommands = next;
3126
+ }
3127
+ function resetExecPolicy() {
3128
+ allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
3129
+ }
3130
+ function isExecCommandAllowed(cmd) {
3131
+ return allowedCommands.has(normalizeCmd(cmd));
3132
+ }
3133
+ function getExecAllowlist() {
3134
+ return [...allowedCommands].sort();
3135
+ }
2826
3136
  var MAX_ARGS = 20;
2827
3137
  var MAX_OUTPUT2 = 2e5;
2828
3138
  var DEFAULT_TIMEOUT_MS2 = 3e4;
@@ -2884,7 +3194,7 @@ var execTool = {
2884
3194
  name: "exec",
2885
3195
  category: "Shell",
2886
3196
  description: "Execute a **whitelisted, restricted set of commands** with strict argument validation. This is the **preferred and safer** alternative to the `bash` tool for running development tools (node, npm, pnpm, tsc, git, tests, linters, etc.). It prevents arbitrary command injection and limits what the model can do.",
2887
- usageHint: "PREFERRED SHELL TOOL for most cases.\n\nUse this instead of `bash` whenever possible.\n- `command` must be one of the allowed commands (node, npm, pnpm, git, tsc, eslint, vitest, etc.).\n- Arguments are passed as a clean array (no shell interpretation).\n- `cwd` is validated to stay inside the project.\n- For anything that requires real shell features (pipes, complex redirection, arbitrary commands), fall back to `bash` (with strong justification).\nThis tool significantly reduces the risk compared to full shell access.",
3197
+ usageHint: "PREFERRED SHELL TOOL for most cases.\n\nUse this instead of `bash` whenever possible.\n- `command` must be in the allowlist. Defaults cover JS (node/npm/pnpm/yarn/bun/deno/tsc/vitest/eslint/biome), Go (`go build`/`go test`), Rust (cargo), Python (python/pip), Ruby (gem/bundle), JVM (java/mvn/gradle), .NET (dotnet), native (make/cmake), and git. Users can extend it via `tools.exec.allow` in config.\n- Arguments are passed as a clean array (no shell interpretation).\n- `cwd` is validated to stay inside the project.\n- If a command is not allowlisted, the error explains how to add it; for one-off arbitrary commands, fall back to `bash` (with strong justification).\nThis tool significantly reduces the risk compared to full shell access.",
2888
3198
  permission: "confirm",
2889
3199
  mutating: true,
2890
3200
  riskTier: "standard",
@@ -2938,12 +3248,12 @@ var execTool = {
2938
3248
  truncated: false,
2939
3249
  allowed: false
2940
3250
  };
2941
- if (!(cmd in ALLOWED_COMMANDS)) {
3251
+ if (!isExecCommandAllowed(cmd)) {
2942
3252
  return {
2943
3253
  command: cmd,
2944
3254
  args: input.args ?? [],
2945
3255
  stdout: "",
2946
- stderr: `Command "${cmd}" not in allowlist. Use the bash tool for arbitrary commands.`,
3256
+ stderr: `Command "${cmd}" not in allowlist. Add it to your ~/.wrongstack/config.json under "tools": { "exec": { "allow": ["${cmd}"] } }, or use the bash tool for one-off arbitrary commands.`,
2947
3257
  exitCode: 1,
2948
3258
  truncated: false,
2949
3259
  allowed: false
@@ -2986,19 +3296,58 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
2986
3296
  let stdout = "";
2987
3297
  let stderr = "";
2988
3298
  let killed = false;
3299
+ const resolvedOnce = { value: false };
3300
+ const finish = (result) => {
3301
+ if (resolvedOnce.value) return;
3302
+ resolvedOnce.value = true;
3303
+ resolve6(result);
3304
+ };
2989
3305
  const startedAt = Date.now();
2990
3306
  const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
2991
3307
  const resolved = resolveWin32Command(cmd);
2992
3308
  const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
2993
3309
  const spawnCmd = needsShell ? cmd : resolved;
2994
3310
  if (needsShell) assertSafeWin32ShellArgs(args);
2995
- const child = spawn(spawnCmd, args, {
2996
- cwd,
2997
- env: buildChildEnv(sessionId),
2998
- stdio: ["ignore", "pipe", "pipe"],
2999
- windowsHide: true,
3000
- ...isWin ? {} : { signal },
3001
- ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
3311
+ let child;
3312
+ try {
3313
+ child = spawn(spawnCmd, args, {
3314
+ cwd,
3315
+ env: buildChildEnv(sessionId),
3316
+ stdio: ["ignore", "pipe", "pipe"],
3317
+ windowsHide: true,
3318
+ ...isWin ? {} : { signal },
3319
+ ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
3320
+ });
3321
+ } catch (err) {
3322
+ spool.finalize();
3323
+ finish({
3324
+ command: cmd,
3325
+ args,
3326
+ stdout: "",
3327
+ stderr: `spawn failed: ${toErrorMessage$1(err)}`,
3328
+ exitCode: 1,
3329
+ truncated: false,
3330
+ allowed: true
3331
+ });
3332
+ return;
3333
+ }
3334
+ child.on("error", (err) => {
3335
+ const isAbort = err && err.code === "ABORT_ERR";
3336
+ const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
3337
+ clearTimeout(timer);
3338
+ if (isWin) signal.removeEventListener("abort", onAbort);
3339
+ if (typeof pid === "number") registry.unregister(pid);
3340
+ registry.afterCall(Date.now() - startedAt, true);
3341
+ spool.finalize();
3342
+ finish({
3343
+ command: cmd,
3344
+ args,
3345
+ stdout: normalizeCommandOutput(stdout),
3346
+ stderr: stderrText,
3347
+ exitCode: isAbort ? 124 : 1,
3348
+ truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
3349
+ allowed: true
3350
+ });
3002
3351
  });
3003
3352
  const registry = getProcessRegistry();
3004
3353
  const pid = child.pid;
@@ -3038,7 +3387,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
3038
3387
  const exitCode = killed ? 124 : code ?? 1;
3039
3388
  registry.afterCall(durationMs, exitCode !== 0);
3040
3389
  const spooled = spool.finalize();
3041
- resolve6({
3390
+ finish({
3042
3391
  command: cmd,
3043
3392
  args,
3044
3393
  stdout: normalizeCommandOutput(stdout) + (spooled ? spoolNote(spooled) : ""),
@@ -3048,22 +3397,6 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
3048
3397
  allowed: true
3049
3398
  });
3050
3399
  });
3051
- child.on("error", (err) => {
3052
- clearTimeout(timer);
3053
- if (isWin) signal.removeEventListener("abort", onAbort);
3054
- if (typeof pid === "number") registry.unregister(pid);
3055
- registry.afterCall(Date.now() - startedAt, true);
3056
- spool.finalize();
3057
- resolve6({
3058
- command: cmd,
3059
- args,
3060
- stdout: normalizeCommandOutput(stdout),
3061
- stderr: err.message,
3062
- exitCode: 1,
3063
- truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
3064
- allowed: true
3065
- });
3066
- });
3067
3400
  });
3068
3401
  }
3069
3402
  var TD = new TurndownService({
@@ -3428,7 +3761,7 @@ async function duckduckgoSearch(query2, num, signal) {
3428
3761
  truncated: results.length >= num
3429
3762
  };
3430
3763
  } catch (err) {
3431
- console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error: toErrorMessage$1(err) }));
3764
+ console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error: toErrorMessage$2(err) }));
3432
3765
  return {
3433
3766
  query: query2,
3434
3767
  results: [{ title: "Search unavailable", url: "", snippet: "Could not reach DuckDuckGo" }],
@@ -3916,13 +4249,13 @@ ${formatTaskList(taskFile.tasks)}`
3916
4249
  }
3917
4250
  };
3918
4251
  function mkResult(plan, ok, message, todos) {
3919
- const open = plan.items.filter((i) => i.status !== "done").length;
4252
+ const open2 = plan.items.filter((i) => i.status !== "done").length;
3920
4253
  const result = {
3921
4254
  ok,
3922
4255
  message,
3923
4256
  plan: formatPlan(plan),
3924
4257
  count: plan.items.length,
3925
- open
4258
+ open: open2
3926
4259
  };
3927
4260
  if (todos !== void 0) result.todos = todos;
3928
4261
  return result;
@@ -7667,7 +8000,7 @@ function loadDatabaseSync() {
7667
8000
  DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
7668
8001
  } catch (err) {
7669
8002
  throw new Error(
7670
- `The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${toErrorMessage$1(err)}`
8003
+ `The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${toErrorMessage$2(err)}`
7671
8004
  );
7672
8005
  }
7673
8006
  return DatabaseSyncCtor;
@@ -7794,6 +8127,8 @@ var IndexStore = class {
7794
8127
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)");
7795
8128
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)");
7796
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)");
7797
8132
  this.db.exec(`
7798
8133
  CREATE TABLE IF NOT EXISTS refs (
7799
8134
  id INTEGER PRIMARY KEY,
@@ -8706,9 +9041,9 @@ async function syncGoParse(filePath, content, lang) {
8706
9041
  }
8707
9042
  }
8708
9043
  async function parseSymbols3(opts) {
8709
- const { file, lang } = opts;
9044
+ const { file, content, lang } = opts;
8710
9045
  try {
8711
- return await syncPyParse(file, lang);
9046
+ return await syncPyParse(file, content, lang);
8712
9047
  } catch {
8713
9048
  return { file, lang, symbols: [], mtimeMs: Date.now() };
8714
9049
  }
@@ -8776,8 +9111,7 @@ syms = []
8776
9111
  errors = []
8777
9112
 
8778
9113
  try:
8779
- with open(sys.argv[1], "r", encoding="utf-8") as f:
8780
- source = f.read()
9114
+ source = sys.stdin.read()
8781
9115
  tree = ast.parse(source, filename=sys.argv[1])
8782
9116
  except Exception as e:
8783
9117
  errors.append(str(e))
@@ -8917,16 +9251,21 @@ visitor.visit(tree)
8917
9251
 
8918
9252
  print(json.dumps([s.to_dict() for s in syms]))
8919
9253
  `;
8920
- async function syncPyParse(filePath, lang) {
9254
+ var _cachedScriptPath = null;
9255
+ async function syncPyParse(filePath, content, lang) {
8921
9256
  try {
8922
- const tmpDir = path.join(os2.tmpdir(), "ws-py-parse");
8923
- await fs7.mkdir(tmpDir, { recursive: true });
8924
- const scriptPath = path.join(tmpDir, "parse.py");
8925
- await fs7.writeFile(scriptPath, PY_PARSE_SCRIPT, "utf8");
8926
- 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], {
8927
9264
  stdio: ["pipe", "pipe", "pipe"],
8928
9265
  windowsHide: true
8929
9266
  });
9267
+ proc.stdin?.write(content);
9268
+ proc.stdin?.end();
8930
9269
  let stdout = "";
8931
9270
  proc.stdout?.on("data", (chunk) => {
8932
9271
  stdout += chunk.toString();
@@ -9593,7 +9932,7 @@ async function parseFile(file, content, lang) {
9593
9932
  case "go":
9594
9933
  return parseSymbols2({ file, content, lang: "go" });
9595
9934
  case "py":
9596
- return parseSymbols3({ file, lang: "py" });
9935
+ return parseSymbols3({ file, content, lang: "py" });
9597
9936
  case "rs":
9598
9937
  return parseSymbols4({ file, content, lang: "rs" });
9599
9938
  case "json":
@@ -9624,10 +9963,12 @@ async function runIndexerWithStore(store, opts) {
9624
9963
  let symbolsIndexed = 0;
9625
9964
  const isGitIgnored = await loadGitignoreMatcher(projectRoot);
9626
9965
  let files;
9966
+ let discoveredFiles = null;
9627
9967
  if (opts.files && opts.files.length > 0) {
9628
9968
  files = opts.files.map((f) => path.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path.relative(projectRoot, f).replace(/\\/g, "/"), false));
9629
9969
  } else {
9630
9970
  files = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
9971
+ discoveredFiles = new Set(files);
9631
9972
  }
9632
9973
  if (langs && langs.length > 0) {
9633
9974
  const langSet = new Set(langs);
@@ -9745,11 +10086,11 @@ async function runIndexerWithStore(store, opts) {
9745
10086
  filesIndexed++;
9746
10087
  }
9747
10088
  }
9748
- for (const [file_] of existingMeta) {
9749
- try {
9750
- await fs7.stat(file_);
9751
- } catch {
9752
- store.deleteFile(file_);
10089
+ if (discoveredFiles) {
10090
+ for (const [file_] of existingMeta) {
10091
+ if (!discoveredFiles.has(file_)) {
10092
+ store.deleteFile(file_);
10093
+ }
9753
10094
  }
9754
10095
  }
9755
10096
  const durationMs = Date.now() - startMs;
@@ -10358,7 +10699,7 @@ var setWorkingDirTool = {
10358
10699
  } catch (err) {
10359
10700
  return {
10360
10701
  current: ctx.workingDir,
10361
- error: toErrorMessage$1(err)
10702
+ error: toErrorMessage$2(err)
10362
10703
  };
10363
10704
  }
10364
10705
  try {
@@ -11004,6 +11345,6 @@ var TOOL_ICON_CONFIG = {
11004
11345
  };
11005
11346
  var FALLBACK_ICON = "fallback";
11006
11347
 
11007
- export { CircuitBreaker, CircuitOpenError, FALLBACK_ICON, IndexCircuitBreaker, IndexTimeoutError, OPTIONAL_TOOLS, TIER1_TOOLS, TIER2_TOOLS, TIER3_TOOLS, TOOL_ICON_CONFIG, TOOL_ICON_MAP, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, cancelPendingReindexes, codebaseIndexStats, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, createGlobalPsSlashCommand, createModeTool, diffTool, documentTool, editTool, enqueueReindex, execTool, fetchTool, forgetTool, formatGlobalStatus, formatInstanceList, formatInstanceSummary, formatTool, getIndexState, getInstanceCount, getPersistentProcessRegistry, getProcessGuardian, getProcessRegistry, getToolIcon, gitTool, globTool, grepTool, indexCircuitBreaker, installTool, isIndexReady, isIndexableFile, isIndexing, jsonTool, lintTool, listInstances, logsTool, onIndexStateChange, outdatedTool, patchTool, planTool, readTool, relatedMemoryTool, rememberTool, replaceTool, resetIndexCircuitBreaker, resetPersistentProcessRegistry, runStartupIndex, scaffoldTool, searchCodebaseIndex, searchMemoryTool, searchTool, shutdownCodebaseIndexHost, startProcessGuardian, stopProcessGuardian, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
11348
+ export { CircuitBreaker, CircuitOpenError, FALLBACK_ICON, IndexCircuitBreaker, IndexTimeoutError, OPTIONAL_TOOLS, TIER1_TOOLS, TIER2_TOOLS, TIER3_TOOLS, TOOL_ICON_CONFIG, TOOL_ICON_MAP, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, cancelPendingReindexes, codebaseIndexStats, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, configureExecPolicy, createGlobalPsSlashCommand, createModeTool, diffTool, documentTool, editTool, enqueueReindex, ensureSessionShell, execTool, fetchTool, forgetTool, formatGlobalStatus, formatInstanceList, formatInstanceSummary, formatTool, getExecAllowlist, getIndexState, getInstanceCount, getPersistentProcessRegistry, getProcessGuardian, getProcessRegistry, getToolIcon, gitTool, globTool, grepTool, indexCircuitBreaker, installTool, isExecCommandAllowed, isIndexReady, isIndexableFile, isIndexing, jsonTool, lintTool, listInstances, logsTool, normalizeShell, onIndexStateChange, outdatedTool, patchTool, planTool, readTool, relatedMemoryTool, rememberTool, replaceTool, resetExecPolicy, resetIndexCircuitBreaker, resetPersistentProcessRegistry, resolveSessionShell, runStartupIndex, scaffoldTool, searchCodebaseIndex, searchMemoryTool, searchTool, shutdownCodebaseIndexHost, startProcessGuardian, stopProcessGuardian, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
11008
11349
  //# sourceMappingURL=index.js.map
11009
11350
  //# sourceMappingURL=index.js.map