@wrongstack/tools 0.119.1 → 0.148.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.
Files changed (49) hide show
  1. package/dist/audit.js +36 -10
  2. package/dist/audit.js.map +1 -1
  3. package/dist/bash.js +30 -28
  4. package/dist/bash.js.map +1 -1
  5. package/dist/builtin.js +178 -154
  6. package/dist/builtin.js.map +1 -1
  7. package/dist/codebase-index/index.js +1 -1
  8. package/dist/codebase-index/index.js.map +1 -1
  9. package/dist/diff.js.map +1 -1
  10. package/dist/document.js +1 -1
  11. package/dist/document.js.map +1 -1
  12. package/dist/edit.js.map +1 -1
  13. package/dist/exec.js +28 -4
  14. package/dist/exec.js.map +1 -1
  15. package/dist/fetch.js +1 -13
  16. package/dist/fetch.js.map +1 -1
  17. package/dist/format.js +39 -13
  18. package/dist/format.js.map +1 -1
  19. package/dist/git.js +3 -3
  20. package/dist/git.js.map +1 -1
  21. package/dist/glob.js.map +1 -1
  22. package/dist/grep.js +1 -1
  23. package/dist/grep.js.map +1 -1
  24. package/dist/index.js +87 -63
  25. package/dist/index.js.map +1 -1
  26. package/dist/install.js +39 -13
  27. package/dist/install.js.map +1 -1
  28. package/dist/lint.js +39 -13
  29. package/dist/lint.js.map +1 -1
  30. package/dist/logs.js +1 -1
  31. package/dist/logs.js.map +1 -1
  32. package/dist/outdated.js +24 -1
  33. package/dist/outdated.js.map +1 -1
  34. package/dist/pack.js +178 -154
  35. package/dist/pack.js.map +1 -1
  36. package/dist/patch.js.map +1 -1
  37. package/dist/read.js +1 -1
  38. package/dist/read.js.map +1 -1
  39. package/dist/replace.js +6 -2
  40. package/dist/replace.js.map +1 -1
  41. package/dist/scaffold.js.map +1 -1
  42. package/dist/search.js.map +1 -1
  43. package/dist/test.js +42 -16
  44. package/dist/test.js.map +1 -1
  45. package/dist/tree.js.map +1 -1
  46. package/dist/typecheck.js +41 -15
  47. package/dist/typecheck.js.map +1 -1
  48. package/dist/write.js.map +1 -1
  49. package/package.json +4 -4
package/dist/pack.js CHANGED
@@ -1,18 +1,40 @@
1
+ import { spawn, execFileSync, spawnSync } from 'node:child_process';
1
2
  import * as Core from '@wrongstack/core';
2
3
  import { buildChildEnv, expectDefined, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan, loadTasks, emptyTaskFile, saveTasks, computeTaskItemProgress, formatTaskList, resolveWstackPaths } from '@wrongstack/core';
3
- import { spawn, execFileSync, spawnSync } from 'node:child_process';
4
- import * as fs12 from 'node:fs/promises';
5
- import * as path from 'node:path';
4
+ import * as fs from 'node:fs';
5
+ import { statSync, writeFileSync, mkdirSync } from 'node:fs';
6
+ import * as path2 from 'node:path';
6
7
  import { resolve, sep, dirname } from 'node:path';
8
+ import * as fs13 from 'node:fs/promises';
7
9
  import * as os from 'node:os';
8
10
  import { createRequire } from 'node:module';
9
- import * as fs from 'node:fs';
10
- import { statSync, writeFileSync, mkdirSync } from 'node:fs';
11
11
  import * as ts from 'typescript';
12
12
  import * as dns from 'node:dns/promises';
13
13
  import * as net from 'node:net';
14
14
  import { Agent } from 'undici';
15
15
 
16
+ // src/_spawn-stream.ts
17
+ function resolveWin32Command(cmd) {
18
+ if (process.platform !== "win32") return cmd;
19
+ if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd)) {
20
+ return cmd;
21
+ }
22
+ const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
23
+ const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
24
+ for (const dir of pathDirs) {
25
+ const base = path2.join(dir, cmd);
26
+ for (const ext of pathext) {
27
+ const full = `${base}${ext}`;
28
+ try {
29
+ fs.accessSync(full, fs.constants.X_OK);
30
+ return full;
31
+ } catch {
32
+ }
33
+ }
34
+ }
35
+ return cmd;
36
+ }
37
+
16
38
  // src/_spawn-stream.ts
17
39
  async function* spawnStream(opts) {
18
40
  const max = opts.maxBytes ?? 2e5;
@@ -21,11 +43,14 @@ async function* spawnStream(opts) {
21
43
  let stderr = "";
22
44
  let pending = "";
23
45
  let error;
24
- const child = spawn(opts.cmd, opts.args, {
46
+ const cmd = resolveWin32Command(opts.cmd);
47
+ const needsShell = process.platform === "win32" && (cmd.endsWith(".cmd") || cmd.endsWith(".bat"));
48
+ const child = spawn(cmd, opts.args, {
25
49
  cwd: opts.cwd,
26
50
  signal: opts.signal,
27
51
  env: buildChildEnv(),
28
- stdio: ["ignore", "pipe", "pipe"]
52
+ stdio: ["ignore", "pipe", "pipe"],
53
+ ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
29
54
  });
30
55
  const queue = [];
31
56
  let waiter;
@@ -65,7 +90,7 @@ async function* spawnStream(opts) {
65
90
  waiter = resolve7;
66
91
  });
67
92
  }
68
- const chunk = expectDefined(queue.shift());
93
+ const chunk = queue.shift();
69
94
  if (chunk.kind === "close") {
70
95
  if (!spawnFailed) exitCode = chunk.code ?? 0;
71
96
  break;
@@ -107,13 +132,13 @@ async function detectPackageManager(cwd) {
107
132
  return "npm";
108
133
  }
109
134
  function resolvePath(input, ctx) {
110
- return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
135
+ return path2.isAbsolute(input) ? path2.normalize(input) : path2.resolve(ctx.cwd, input);
111
136
  }
112
137
  function ensureInsideRoot(absPath, ctx) {
113
- const root = path.resolve(ctx.projectRoot);
114
- const target = path.resolve(absPath);
115
- const rel = path.relative(root, target);
116
- if (rel.startsWith("..") || path.isAbsolute(rel)) {
138
+ const root = path2.resolve(ctx.projectRoot);
139
+ const target = path2.resolve(absPath);
140
+ const rel = path2.relative(root, target);
141
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
117
142
  throw new Error(`Path "${absPath}" is outside project root "${root}"`);
118
143
  }
119
144
  return target;
@@ -122,23 +147,23 @@ function safeResolve(input, ctx) {
122
147
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
123
148
  }
124
149
  async function assertRealInsideRoot(absPath, ctx) {
125
- const realRoot = await fs12.realpath(ctx.projectRoot).catch(() => path.resolve(ctx.projectRoot));
150
+ const realRoot = await fs13.realpath(ctx.projectRoot).catch(() => path2.resolve(ctx.projectRoot));
126
151
  let probe = absPath;
127
152
  for (; ; ) {
128
153
  let real;
129
154
  try {
130
- real = await fs12.realpath(probe);
155
+ real = await fs13.realpath(probe);
131
156
  } catch (err) {
132
157
  if (err.code === "ENOENT") {
133
- const parent = path.dirname(probe);
158
+ const parent = path2.dirname(probe);
134
159
  if (parent === probe) return;
135
160
  probe = parent;
136
161
  continue;
137
162
  }
138
163
  throw err;
139
164
  }
140
- const rel = path.relative(realRoot, real);
141
- if (rel.startsWith("..") || path.isAbsolute(rel)) {
165
+ const rel = path2.relative(realRoot, real);
166
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
142
167
  throw new Error(
143
168
  `Path "${absPath}" resolves through a symlink outside project root "${realRoot}"`
144
169
  );
@@ -181,9 +206,9 @@ function collapseConsecutiveDuplicates(text, minRun = REPEAT_RUN_THRESHOLD) {
181
206
  while (j < lines.length && lines[j] === lines[i]) j++;
182
207
  const run = j - i;
183
208
  if (run >= minRun) {
184
- out.push(expectDefined(lines[i]), `\u2026 \u27E8repeated ${run}\xD7\u27E9`);
209
+ out.push(lines[i], `\u2026 \u27E8repeated ${run}\xD7\u27E9`);
185
210
  } else {
186
- for (let k = i; k < j; k++) out.push(expectDefined(lines[k]));
211
+ for (let k = i; k < j; k++) out.push(lines[k]);
187
212
  }
188
213
  i = j;
189
214
  }
@@ -838,44 +863,46 @@ var bashTool = {
838
863
  let pending = "";
839
864
  let timedOut = false;
840
865
  const timers = [];
841
- const timer = setTimeout(() => {
842
- timedOut = true;
866
+ function killWithTimeout(child2, timeoutMs2) {
843
867
  if (isWin) {
844
868
  try {
845
- child.kill();
869
+ child2.kill();
846
870
  } catch {
847
871
  }
848
- } else {
872
+ return;
873
+ }
874
+ try {
875
+ if (typeof child2.pid === "number") {
876
+ try {
877
+ process.kill(-child2.pid, "SIGTERM");
878
+ } catch {
879
+ child2.kill("SIGTERM");
880
+ }
881
+ } else {
882
+ child2.kill("SIGTERM");
883
+ }
884
+ } catch {
885
+ }
886
+ const killTimer = setTimeout(() => {
849
887
  try {
850
- if (typeof child.pid === "number") {
888
+ if (typeof child2.pid === "number") {
851
889
  try {
852
- process.kill(-child.pid, "SIGTERM");
890
+ process.kill(-child2.pid, "SIGKILL");
853
891
  } catch {
854
- child.kill("SIGTERM");
892
+ child2.kill("SIGKILL");
855
893
  }
856
894
  } else {
857
- child.kill("SIGTERM");
895
+ child2.kill("SIGKILL");
858
896
  }
859
- const killTimer = setTimeout(() => {
860
- try {
861
- if (typeof child.pid === "number") {
862
- try {
863
- process.kill(-child.pid, "SIGKILL");
864
- } catch {
865
- child.kill("SIGKILL");
866
- }
867
- } else {
868
- child.kill("SIGKILL");
869
- }
870
- } catch {
871
- } finally {
872
- killTimer.unref?.();
873
- }
874
- }, 2e3);
875
- timers.push(killTimer);
876
897
  } catch {
877
898
  }
878
- }
899
+ }, timeoutMs2);
900
+ timers.push(killTimer);
901
+ killTimer.unref?.();
902
+ }
903
+ const timer = setTimeout(() => {
904
+ timedOut = true;
905
+ killWithTimeout(child, 2e3);
879
906
  }, timeoutMs);
880
907
  timers.push(timer);
881
908
  timer.unref?.();
@@ -1140,7 +1167,7 @@ var IndexStore = class {
1140
1167
  this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
1141
1168
  fs.mkdirSync(this.indexDir, { recursive: true });
1142
1169
  const Database = loadDatabaseSync();
1143
- this.db = new Database(path.join(this.indexDir, DB_FILE));
1170
+ this.db = new Database(path2.join(this.indexDir, DB_FILE));
1144
1171
  this.initSchema();
1145
1172
  }
1146
1173
  initSchema() {
@@ -1438,7 +1465,7 @@ var IndexStore = class {
1438
1465
  }));
1439
1466
  }
1440
1467
  sizeBytes() {
1441
- const dbPath = path.join(this.indexDir, DB_FILE);
1468
+ const dbPath = path2.join(this.indexDir, DB_FILE);
1442
1469
  try {
1443
1470
  return fs.statSync(dbPath).size;
1444
1471
  } catch {
@@ -1877,10 +1904,10 @@ func formatType(t ast.Expr) string {
1877
1904
  }
1878
1905
  `;
1879
1906
  function syncGoParse(filePath, content, lang) {
1880
- const tmpDir = path.join(os.tmpdir(), "ws-go-parse");
1907
+ const tmpDir = path2.join(os.tmpdir(), "ws-go-parse");
1881
1908
  try {
1882
1909
  mkdirSync(tmpDir, { recursive: true });
1883
- const scriptPath = path.join(tmpDir, "parse.go");
1910
+ const scriptPath = path2.join(tmpDir, "parse.go");
1884
1911
  writeFileSync(scriptPath, GO_PARSE_SCRIPT, "utf8");
1885
1912
  const stdout = execFileSync("go", ["run", scriptPath], {
1886
1913
  input: content,
@@ -2124,9 +2151,9 @@ print(json.dumps([s.to_dict() for s in syms]))
2124
2151
  `;
2125
2152
  function syncPyParse(filePath, lang) {
2126
2153
  try {
2127
- const tmpDir = path.join(os.tmpdir(), "ws-py-parse");
2154
+ const tmpDir = path2.join(os.tmpdir(), "ws-py-parse");
2128
2155
  mkdirSync(tmpDir, { recursive: true });
2129
- const scriptPath = path.join(tmpDir, "parse.py");
2156
+ const scriptPath = path2.join(tmpDir, "parse.py");
2130
2157
  writeFileSync(scriptPath, PY_PARSE_SCRIPT, "utf8");
2131
2158
  const stdout = execFileSync("python", [scriptPath, filePath], {
2132
2159
  timeout: 15e3,
@@ -2167,7 +2194,7 @@ function parseSymbols4(opts) {
2167
2194
  function checkNativeParser() {
2168
2195
  try {
2169
2196
  execFileSync("rustc", ["--version"], { stdio: "pipe" });
2170
- const toolsDir = path.join(process.cwd(), "tools");
2197
+ const toolsDir = path2.join(process.cwd(), "tools");
2171
2198
  try {
2172
2199
  execFileSync(
2173
2200
  "cargo",
@@ -2177,7 +2204,7 @@ function checkNativeParser() {
2177
2204
  "--format-version",
2178
2205
  "1",
2179
2206
  "--manifest-path",
2180
- path.join(toolsDir, "Cargo.toml")
2207
+ path2.join(toolsDir, "Cargo.toml")
2181
2208
  ],
2182
2209
  { stdio: "pipe" }
2183
2210
  );
@@ -2191,13 +2218,13 @@ function checkNativeParser() {
2191
2218
  }
2192
2219
  function tryNativeParse(file, content) {
2193
2220
  try {
2194
- const toolsDir = path.join(process.cwd(), "tools");
2195
- const crateDir = path.join(toolsDir, "syn-parser");
2196
- const tmpFile = path.join(crateDir, "src", "input.rs");
2221
+ const toolsDir = path2.join(process.cwd(), "tools");
2222
+ const crateDir = path2.join(toolsDir, "syn-parser");
2223
+ const tmpFile = path2.join(crateDir, "src", "input.rs");
2197
2224
  writeFileSync(tmpFile, content, "utf8");
2198
2225
  const result = spawnSync(
2199
2226
  "cargo",
2200
- ["run", "--manifest-path", path.join(toolsDir, "Cargo.toml")],
2227
+ ["run", "--manifest-path", path2.join(toolsDir, "Cargo.toml")],
2201
2228
  {
2202
2229
  cwd: process.cwd(),
2203
2230
  encoding: "utf8",
@@ -2295,7 +2322,7 @@ function parseSymbols5(opts) {
2295
2322
  function regexParse2(opts) {
2296
2323
  const { file, content, lang } = opts;
2297
2324
  const symbols = [];
2298
- const basename2 = path.basename(file).toLowerCase();
2325
+ const basename2 = path2.basename(file).toLowerCase();
2299
2326
  const isPackageJson = basename2 === "package.json";
2300
2327
  const isTsconfig = basename2 === "tsconfig.json" || basename2 === "tsconfig.build.json";
2301
2328
  const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
@@ -2321,11 +2348,11 @@ function regexParse2(opts) {
2321
2348
  const line = lineFromOffset(offset);
2322
2349
  symbols.push(
2323
2350
  makeSymbol({
2324
- name: path.basename(file),
2351
+ name: path2.basename(file),
2325
2352
  kind: "object",
2326
2353
  line,
2327
2354
  col: 0,
2328
- signature: `"${path.basename(file)}" = { ... }`,
2355
+ signature: `"${path2.basename(file)}" = { ... }`,
2329
2356
  file,
2330
2357
  lang
2331
2358
  })
@@ -2679,7 +2706,7 @@ function compileGitignore(lines) {
2679
2706
  async function loadGitignoreMatcher(projectRoot) {
2680
2707
  let lines = [];
2681
2708
  try {
2682
- const raw = await fs12.readFile(path.join(projectRoot, ".gitignore"), "utf8");
2709
+ const raw = await fs13.readFile(path2.join(projectRoot, ".gitignore"), "utf8");
2683
2710
  lines = raw.split("\n");
2684
2711
  } catch {
2685
2712
  }
@@ -2750,20 +2777,20 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored) {
2750
2777
  const walk = async (dir) => {
2751
2778
  let entries;
2752
2779
  try {
2753
- entries = await fs12.readdir(dir, { withFileTypes: true });
2780
+ entries = await fs13.readdir(dir, { withFileTypes: true });
2754
2781
  } catch {
2755
2782
  return;
2756
2783
  }
2757
2784
  for (const e of entries) {
2758
2785
  if (ignoreSet.has(e.name)) continue;
2759
- const full = path.join(dir, e.name);
2760
- const rel = path.relative(projectRoot, full).replace(/\\/g, "/");
2786
+ const full = path2.join(dir, e.name);
2787
+ const rel = path2.relative(projectRoot, full).replace(/\\/g, "/");
2761
2788
  if (e.isDirectory()) {
2762
2789
  if (isGitIgnored(rel, true)) continue;
2763
2790
  await walk(full);
2764
2791
  } else if (e.isFile()) {
2765
2792
  if (isGitIgnored(rel, false)) continue;
2766
- const ext = path.extname(e.name);
2793
+ const ext = path2.extname(e.name);
2767
2794
  for (const { ext: extName, pat } of globs) {
2768
2795
  if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
2769
2796
  results.push(full);
@@ -2808,7 +2835,7 @@ async function runIndexer(_ctx, opts) {
2808
2835
  const isGitIgnored = await loadGitignoreMatcher(projectRoot);
2809
2836
  let files;
2810
2837
  if (opts.files && opts.files.length > 0) {
2811
- files = opts.files.map((f) => path.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path.relative(projectRoot, f).replace(/\\/g, "/"), false));
2838
+ files = opts.files.map((f) => path2.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path2.relative(projectRoot, f).replace(/\\/g, "/"), false));
2812
2839
  } else {
2813
2840
  files = await findSourceFiles(projectRoot, ignore, isGitIgnored);
2814
2841
  }
@@ -2832,7 +2859,7 @@ async function runIndexer(_ctx, opts) {
2832
2859
  }
2833
2860
  let stat10;
2834
2861
  try {
2835
- stat10 = await fs12.stat(file);
2862
+ stat10 = await fs13.stat(file);
2836
2863
  } catch {
2837
2864
  store.deleteFile(file);
2838
2865
  continue;
@@ -2851,7 +2878,7 @@ async function runIndexer(_ctx, opts) {
2851
2878
  store.deleteSymbolsForFile(file);
2852
2879
  let content;
2853
2880
  try {
2854
- content = await fs12.readFile(file, "utf8");
2881
+ content = await fs13.readFile(file, "utf8");
2855
2882
  } catch (e) {
2856
2883
  errors.push(`read error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
2857
2884
  continue;
@@ -2901,7 +2928,7 @@ async function runIndexer(_ctx, opts) {
2901
2928
  }
2902
2929
  for (const [file_] of existingMeta) {
2903
2930
  try {
2904
- await fs12.stat(file_);
2931
+ await fs13.stat(file_);
2905
2932
  } catch {
2906
2933
  store.deleteFile(file_);
2907
2934
  }
@@ -2962,7 +2989,7 @@ function tokenise(text) {
2962
2989
  return sanitised.toLowerCase().split(" ").filter(Boolean);
2963
2990
  }
2964
2991
  function splitName(name) {
2965
- return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_\-]+/g, " ").trim();
2992
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
2966
2993
  }
2967
2994
  function buildIndexableText(name, signature, docComment) {
2968
2995
  return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");
@@ -3292,11 +3319,11 @@ function findGitDir(cwd) {
3292
3319
  let dir = cwd;
3293
3320
  for (let i = 0; i < 20; i++) {
3294
3321
  try {
3295
- const stat10 = statSync(path.join(dir, ".git"));
3322
+ const stat10 = statSync(path2.join(dir, ".git"));
3296
3323
  if (stat10.isDirectory()) return dir;
3297
3324
  } catch {
3298
3325
  }
3299
- const parent = path.dirname(dir);
3326
+ const parent = path2.dirname(dir);
3300
3327
  if (parent === dir) break;
3301
3328
  dir = parent;
3302
3329
  }
@@ -3336,9 +3363,9 @@ async function fileDiff(input, ctx, _signal) {
3336
3363
  const results = [];
3337
3364
  for (const file of files) {
3338
3365
  const absPath = safeResolve(file, ctx);
3339
- const stat10 = await fs12.stat(absPath).catch(() => null);
3366
+ const stat10 = await fs13.stat(absPath).catch(() => null);
3340
3367
  if (!stat10?.isFile()) continue;
3341
- const content = await fs12.readFile(absPath, "utf8");
3368
+ const content = await fs13.readFile(absPath, "utf8");
3342
3369
  const lines = content.split(/\r?\n/);
3343
3370
  results.push(formatWithLineNumbers(file, lines));
3344
3371
  }
@@ -3400,7 +3427,7 @@ var documentTool = {
3400
3427
  const fileList = input.files ? await resolveFiles(Array.isArray(input.files) ? input.files.join(",") : input.files, cwd) : input.path ? [safeResolve(input.path, ctx)] : [];
3401
3428
  for (const absPath of fileList) {
3402
3429
  try {
3403
- const content = await fs12.readFile(absPath, "utf8");
3430
+ const content = await fs13.readFile(absPath, "utf8");
3404
3431
  filesProcessed++;
3405
3432
  const processed = processFile(
3406
3433
  content,
@@ -3436,7 +3463,7 @@ async function resolveFiles(filesInput, cwd) {
3436
3463
  for (const f of files) {
3437
3464
  const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
3438
3465
  try {
3439
- const stat10 = await fs12.stat(absPath);
3466
+ const stat10 = await fs13.stat(absPath);
3440
3467
  if (stat10.isFile()) resolved.push(absPath);
3441
3468
  } catch {
3442
3469
  }
@@ -3528,7 +3555,7 @@ var editTool = {
3528
3555
  if (input.new_string === void 0) throw new Error("edit: new_string is required");
3529
3556
  if (input.old_string === "") throw new Error("edit: old_string cannot be empty");
3530
3557
  const absPath = await safeResolveReal(input.path, ctx);
3531
- const stat10 = await fs12.stat(absPath).catch((err) => {
3558
+ const stat10 = await fs13.stat(absPath).catch((err) => {
3532
3559
  if (err.code === "ENOENT") {
3533
3560
  throw new Error(`edit: file "${input.path}" does not exist. Use \`write\` instead.`);
3534
3561
  }
@@ -3538,8 +3565,8 @@ var editTool = {
3538
3565
  if (!ctx.hasRead(absPath)) {
3539
3566
  throw new Error(`edit: file "${input.path}" was not read in this session. Read it first.`);
3540
3567
  }
3541
- const original = await fs12.readFile(absPath, "utf8");
3542
- const updated = await fs12.stat(absPath);
3568
+ const original = await fs13.readFile(absPath, "utf8");
3569
+ const updated = await fs13.stat(absPath);
3543
3570
  const mtimeTolerance = process.platform === "win32" ? 2e3 : 1;
3544
3571
  const lastReadMtime = ctx.lastReadMtime(absPath);
3545
3572
  if (lastReadMtime !== void 0 && updated.mtimeMs > lastReadMtime + mtimeTolerance) {
@@ -3579,7 +3606,7 @@ var editTool = {
3579
3606
  const newFileLf = input.replace_all ? fileLf.split(oldLf).join(newLf) : fileLf.replace(oldLf, newLf);
3580
3607
  const newFile = toStyle(newFileLf, style);
3581
3608
  await atomicWrite(absPath, newFile, { mode: updated.mode & 511 });
3582
- const written = await fs12.stat(absPath);
3609
+ const written = await fs13.stat(absPath);
3583
3610
  ctx.recordRead(absPath, written.mtimeMs);
3584
3611
  ctx.session.recordFileChange({
3585
3612
  path: absPath,
@@ -3804,9 +3831,9 @@ var execTool = {
3804
3831
  allowed: false
3805
3832
  };
3806
3833
  }
3807
- const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;
3808
- const rel = path.relative(ctx.projectRoot, requestedCwd);
3809
- if (rel.startsWith("..") || path.isAbsolute(rel)) {
3834
+ const requestedCwd = input.cwd ? path2.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;
3835
+ const rel = path2.relative(ctx.projectRoot, requestedCwd);
3836
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
3810
3837
  return {
3811
3838
  command: cmd,
3812
3839
  args,
@@ -3828,11 +3855,14 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
3828
3855
  let stderr = "";
3829
3856
  let killed = false;
3830
3857
  const startedAt = Date.now();
3831
- const child = spawn(cmd, args, {
3858
+ const resolved = resolveWin32Command(cmd);
3859
+ const needsShell = process.platform === "win32" && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
3860
+ const child = spawn(resolved, args, {
3832
3861
  cwd,
3833
3862
  signal,
3834
3863
  env: buildChildEnv(sessionId),
3835
- stdio: ["ignore", "pipe", "pipe"]
3864
+ stdio: ["ignore", "pipe", "pipe"],
3865
+ ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
3836
3866
  });
3837
3867
  const registry = getProcessRegistry();
3838
3868
  const pid = child.pid;
@@ -3891,19 +3921,7 @@ if (ALLOW_PRIVATE && !process.env["CI"]) {
3891
3921
  "[WrongStack] WARNING: WRONGSTACK_FETCH_ALLOW_PRIVATE=1 is active \u2014\n fetch tool can now access private IPs (10.x, 192.168.x, 169.254.x),\n cloud metadata endpoints, and plaintext HTTP. Use only on isolated networks."
3892
3922
  );
3893
3923
  }
3894
- function combineSignals(signals) {
3895
- const anyFn = AbortSignal.any;
3896
- if (typeof anyFn === "function") return anyFn(signals);
3897
- const ctrl = new AbortController();
3898
- for (const sig of signals) {
3899
- if (sig.aborted) {
3900
- ctrl.abort(sig.reason);
3901
- return ctrl.signal;
3902
- }
3903
- sig.addEventListener("abort", () => ctrl.abort(sig.reason), { once: true });
3904
- }
3905
- return ctrl.signal;
3906
- }
3924
+ var combineSignals = (signals) => AbortSignal.any(signals);
3907
3925
  function guardedLookup(hostname, options, callback) {
3908
3926
  dns.lookup(hostname, { all: true }).then((records) => {
3909
3927
  const family = options?.family;
@@ -4627,7 +4645,7 @@ var globTool = {
4627
4645
  }
4628
4646
  let entries;
4629
4647
  try {
4630
- entries = await fs12.readdir(dir, { withFileTypes: true });
4648
+ entries = await fs13.readdir(dir, { withFileTypes: true });
4631
4649
  } catch {
4632
4650
  return;
4633
4651
  }
@@ -4636,14 +4654,14 @@ var globTool = {
4636
4654
  if (DEFAULT_IGNORE2.includes(name)) continue;
4637
4655
  if (ignored.includes(name)) continue;
4638
4656
  const rel = relPrefix ? `${relPrefix}/${name}` : name;
4639
- const full = path.join(dir, name);
4657
+ const full = path2.join(dir, name);
4640
4658
  if (e.isDirectory()) {
4641
4659
  await walk(full, rel);
4642
4660
  if (truncated) return;
4643
4661
  } else if (e.isFile()) {
4644
4662
  if (re.test(rel) || re.test(name)) {
4645
4663
  try {
4646
- const st = await fs12.stat(full);
4664
+ const st = await fs13.stat(full);
4647
4665
  results.push({ rel: full, mtime: st.mtimeMs });
4648
4666
  if (results.length >= limit) {
4649
4667
  truncated = true;
@@ -4662,7 +4680,7 @@ var globTool = {
4662
4680
  };
4663
4681
  async function readGitignore(dir) {
4664
4682
  try {
4665
- const raw = await fs12.readFile(path.join(dir, ".gitignore"), "utf8");
4683
+ const raw = await fs13.readFile(path2.join(dir, ".gitignore"), "utf8");
4666
4684
  return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
4667
4685
  } catch {
4668
4686
  return [];
@@ -4680,7 +4698,7 @@ var DANGEROUS_PATTERNS = [
4680
4698
  // Quantifier on alternation with length 2+
4681
4699
  /\([^|)]+\|[^)]+\)[+*][+*]/,
4682
4700
  // Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
4683
- /[\(\[][^)\]]*[+*][^)\]]*[\)\]][^)]*\?\??/
4701
+ /[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
4684
4702
  ];
4685
4703
  function compileUserRegex(pattern, flags) {
4686
4704
  if (typeof pattern !== "string") {
@@ -4946,7 +4964,7 @@ async function runNative(input, base, mode, limit, signal) {
4946
4964
  if (stopped || signal.aborted) return;
4947
4965
  let entries;
4948
4966
  try {
4949
- entries = await fs12.readdir(dir, { withFileTypes: true });
4967
+ entries = await fs13.readdir(dir, { withFileTypes: true });
4950
4968
  } catch {
4951
4969
  return;
4952
4970
  }
@@ -4954,16 +4972,16 @@ async function runNative(input, base, mode, limit, signal) {
4954
4972
  if (stopped) return;
4955
4973
  if (DEFAULT_IGNORE3.includes(e.name)) continue;
4956
4974
  if (e.isSymbolicLink()) continue;
4957
- const full = path.join(dir, e.name);
4975
+ const full = path2.join(dir, e.name);
4958
4976
  if (e.isDirectory()) {
4959
4977
  await walk(full);
4960
4978
  } else if (e.isFile()) {
4961
4979
  if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;
4962
4980
  if (globRe) globRe.lastIndex = 0;
4963
4981
  try {
4964
- const stat10 = await fs12.stat(full);
4982
+ const stat10 = await fs13.stat(full);
4965
4983
  if (stat10.size > 1e6) continue;
4966
- const head = await fs12.readFile(full);
4984
+ const head = await fs13.readFile(full);
4967
4985
  if (isBinaryBuffer(head)) continue;
4968
4986
  const text = head.toString("utf8");
4969
4987
  const lines = text.split(/\r?\n/);
@@ -5144,7 +5162,7 @@ var jsonTool = {
5144
5162
  let raw;
5145
5163
  if (input.file) {
5146
5164
  try {
5147
- raw = await fs12.readFile(input.file, "utf8");
5165
+ raw = await fs13.readFile(input.file, "utf8");
5148
5166
  } catch {
5149
5167
  return { data: null, formatted: "", type: "unknown", error: `Could not read file` };
5150
5168
  }
@@ -5182,8 +5200,8 @@ var jsonTool = {
5182
5200
  };
5183
5201
  }
5184
5202
  };
5185
- function query(data, path19) {
5186
- const parts = path19.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
5203
+ function query(data, path20) {
5204
+ const parts = path20.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
5187
5205
  let current = data;
5188
5206
  for (const part of parts) {
5189
5207
  if (current === null || current === void 0) return void 0;
@@ -5454,7 +5472,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
5454
5472
  }
5455
5473
  var DOCKER_LOGS_TIMEOUT_MS = 3e3;
5456
5474
  var MAX_TAIL_LINES = 1e5;
5457
- async function fileLogs(path19, lines, filterRe, stream) {
5475
+ async function fileLogs(path20, lines, filterRe, stream) {
5458
5476
  const { createInterface } = await import('node:readline');
5459
5477
  const { createReadStream } = await import('node:fs');
5460
5478
  const entries = [];
@@ -5463,7 +5481,7 @@ async function fileLogs(path19, lines, filterRe, stream) {
5463
5481
  let writeIdx = 0;
5464
5482
  let totalLines = 0;
5465
5483
  const rl = createInterface({
5466
- input: createReadStream(path19),
5484
+ input: createReadStream(path20),
5467
5485
  crlfDelay: Number.POSITIVE_INFINITY
5468
5486
  });
5469
5487
  for await (const line of rl) {
@@ -5484,7 +5502,7 @@ async function fileLogs(path19, lines, filterRe, stream) {
5484
5502
  if (parsed) entries.push(parsed);
5485
5503
  }
5486
5504
  return {
5487
- source: path19,
5505
+ source: path20,
5488
5506
  entries,
5489
5507
  total: entries.length,
5490
5508
  truncated: totalLines > effLines,
@@ -5567,7 +5585,9 @@ function runOutdated(manager, args, cwd, signal) {
5567
5585
  let stdout = "";
5568
5586
  let stderr = "";
5569
5587
  const MAX = 1e5;
5570
- const child = spawn(manager, args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
5588
+ const resolved = resolveWin32Command(manager);
5589
+ const needsShell = process.platform === "win32" && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
5590
+ const child = spawn(resolved, args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {} });
5571
5591
  child.stdout?.on("data", (c) => {
5572
5592
  if (stdout.length < MAX) stdout += c.toString();
5573
5593
  });
@@ -5651,9 +5671,9 @@ var patchTool = {
5651
5671
  for (const t of targets) {
5652
5672
  const stripped = stripPathComponents(t, strip);
5653
5673
  if (!stripped) continue;
5654
- const candidate = path.resolve(dir, stripped);
5655
- const rel = path.relative(ctx.projectRoot, candidate);
5656
- if (rel.startsWith("..") || path.isAbsolute(rel)) {
5674
+ const candidate = path2.resolve(dir, stripped);
5675
+ const rel = path2.relative(ctx.projectRoot, candidate);
5676
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
5657
5677
  return {
5658
5678
  applied: 0,
5659
5679
  rejected: 1,
@@ -5663,12 +5683,12 @@ var patchTool = {
5663
5683
  };
5664
5684
  }
5665
5685
  }
5666
- const tmpDir = await fs12.mkdtemp(path.join(os.tmpdir(), ".wstack_patch_"));
5686
+ const tmpDir = await fs13.mkdtemp(path2.join(os.tmpdir(), ".wstack_patch_"));
5667
5687
  try {
5668
- await fs12.chmod(tmpDir, 448).catch(() => {
5688
+ await fs13.chmod(tmpDir, 448).catch(() => {
5669
5689
  });
5670
- const patchFile = path.join(tmpDir, "in.diff");
5671
- await fs12.writeFile(patchFile, input.patch, { mode: 384 });
5690
+ const patchFile = path2.join(tmpDir, "in.diff");
5691
+ await fs13.writeFile(patchFile, input.patch, { mode: 384 });
5672
5692
  const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
5673
5693
  const result = await runPatch(args, dir, opts.signal);
5674
5694
  if (result.exitCode !== 0 && !dryRun) {
@@ -5689,7 +5709,7 @@ var patchTool = {
5689
5709
  message: result.stdout || "patch applied"
5690
5710
  };
5691
5711
  } finally {
5692
- await fs12.rm(tmpDir, { recursive: true, force: true }).catch(() => {
5712
+ await fs13.rm(tmpDir, { recursive: true, force: true }).catch(() => {
5693
5713
  });
5694
5714
  }
5695
5715
  }
@@ -5932,7 +5952,7 @@ var readTool = {
5932
5952
  const absPath = await safeResolveReal(input.path, ctx);
5933
5953
  let stat10;
5934
5954
  try {
5935
- stat10 = await fs12.stat(absPath);
5955
+ stat10 = await fs13.stat(absPath);
5936
5956
  } catch (err) {
5937
5957
  const code = err.code;
5938
5958
  if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
@@ -5944,7 +5964,7 @@ var readTool = {
5944
5964
  if (stat10.size > MAX_BYTES2) {
5945
5965
  throw new Error(`read: file too large (${stat10.size} bytes, limit ${MAX_BYTES2})`);
5946
5966
  }
5947
- const buf = await fs12.readFile(absPath);
5967
+ const buf = await fs13.readFile(absPath);
5948
5968
  if (isBinaryBuffer(buf)) {
5949
5969
  throw new Error(`read: "${input.path}" appears to be binary`);
5950
5970
  }
@@ -6012,11 +6032,11 @@ var replaceTool = {
6012
6032
  const dryRun = input.dry_run ?? false;
6013
6033
  const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
6014
6034
  const fileList = await resolveFiles2(filesInput, ctx, globRe);
6015
- const realRoot = await fs12.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
6035
+ const realRoot = await fs13.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
6016
6036
  const results = [];
6017
6037
  let totalReplacements = 0;
6018
6038
  for (const absPath of fileList) {
6019
- const lstat2 = await fs12.lstat(absPath).catch((err) => {
6039
+ const lstat2 = await fs13.lstat(absPath).catch((err) => {
6020
6040
  if (err.code === "ENOENT") return null;
6021
6041
  throw err;
6022
6042
  });
@@ -6024,17 +6044,17 @@ var replaceTool = {
6024
6044
  if (lstat2.isSymbolicLink()) continue;
6025
6045
  let realPath;
6026
6046
  try {
6027
- realPath = await fs12.realpath(absPath);
6047
+ realPath = await fs13.realpath(absPath);
6028
6048
  } catch {
6029
6049
  continue;
6030
6050
  }
6031
- const rel = path.relative(realRoot, realPath);
6032
- if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
6033
- const stat10 = await fs12.stat(realPath).catch(() => null);
6051
+ const rel = path2.relative(realRoot, realPath);
6052
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) continue;
6053
+ const stat10 = await fs13.stat(realPath).catch(() => null);
6034
6054
  if (!stat10 || !stat10.isFile()) continue;
6035
6055
  let content;
6036
6056
  try {
6037
- const buf = await fs12.readFile(realPath);
6057
+ const buf = await fs13.readFile(realPath);
6038
6058
  if (isBinaryBuffer(buf)) continue;
6039
6059
  content = buf.toString("utf8");
6040
6060
  } catch {
@@ -6086,7 +6106,7 @@ async function resolveFiles2(filesInput, ctx, extraGlob) {
6086
6106
  const resolved = [];
6087
6107
  for (const p of parts) {
6088
6108
  const absPath = safeResolve(p, ctx);
6089
- const stat10 = await fs12.stat(absPath).catch(() => null);
6109
+ const stat10 = await fs13.stat(absPath).catch(() => null);
6090
6110
  if (stat10?.isFile()) {
6091
6111
  resolved.push(absPath);
6092
6112
  }
@@ -6117,7 +6137,11 @@ function checkRg() {
6117
6137
  }
6118
6138
  function spawnRgFind(pattern, base) {
6119
6139
  const args = ["--files", "--glob", pattern, base];
6120
- const child = spawn("rg", args, { env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
6140
+ const child = spawn("rg", args, {
6141
+ signal: AbortSignal.timeout(3e4),
6142
+ env: buildChildEnv(),
6143
+ stdio: ["ignore", "pipe", "pipe"]
6144
+ });
6121
6145
  let buf = "";
6122
6146
  child.stdout?.on("data", (chunk) => {
6123
6147
  buf += chunk.toString();
@@ -6137,15 +6161,15 @@ async function globNative(pattern, base, extraGlob) {
6137
6161
  const walk = async (dir) => {
6138
6162
  let entries;
6139
6163
  try {
6140
- entries = await fs12.readdir(dir, { withFileTypes: true });
6164
+ entries = await fs13.readdir(dir, { withFileTypes: true });
6141
6165
  } catch {
6142
6166
  return;
6143
6167
  }
6144
6168
  for (const e of entries) {
6145
6169
  if (DEFAULT_IGNORE4.includes(e.name)) continue;
6146
- const full = path.join(dir, e.name);
6170
+ const full = path2.join(dir, e.name);
6147
6171
  try {
6148
- const stat10 = await fs12.lstat(full);
6172
+ const stat10 = await fs13.lstat(full);
6149
6173
  if (stat10.isSymbolicLink()) continue;
6150
6174
  } catch {
6151
6175
  continue;
@@ -6313,16 +6337,16 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
6313
6337
  let filesCreated = 0;
6314
6338
  for (const [filePath, content] of Object.entries(templateFiles)) {
6315
6339
  const resolvedPath = substituteVars(filePath, name, vars);
6316
- const joinedPath = path.join(cwd, resolvedPath);
6317
- const root = path.resolve(ctx.projectRoot);
6318
- const target = path.resolve(joinedPath);
6319
- const rel = path.relative(root, target);
6320
- if (rel.startsWith("..") || path.isAbsolute(rel)) {
6340
+ const joinedPath = path2.join(cwd, resolvedPath);
6341
+ const root = path2.resolve(ctx.projectRoot);
6342
+ const target = path2.resolve(joinedPath);
6343
+ const rel = path2.relative(root, target);
6344
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
6321
6345
  throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
6322
6346
  }
6323
6347
  const fullPath = target;
6324
6348
  if (!dryRun) {
6325
- await fs12.mkdir(path.dirname(fullPath), { recursive: true });
6349
+ await fs13.mkdir(path2.dirname(fullPath), { recursive: true });
6326
6350
  await atomicWrite(fullPath, substituteVars(content, name, vars));
6327
6351
  }
6328
6352
  files.push(resolvedPath);
@@ -6748,7 +6772,7 @@ var testTool = {
6748
6772
  type: "final",
6749
6773
  output: {
6750
6774
  runner: "none",
6751
- exit_code: 1,
6775
+ exit_code: 0,
6752
6776
  tests_run: 0,
6753
6777
  passed: 0,
6754
6778
  failed: 0,
@@ -6778,14 +6802,14 @@ async function detectRunner(cwd) {
6778
6802
  const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
6779
6803
  for (const f of candidates) {
6780
6804
  try {
6781
- await stat10(path.join(cwd, f));
6805
+ await stat10(path2.join(cwd, f));
6782
6806
  if (f.includes("vitest")) return "vitest";
6783
6807
  if (f.includes("jest")) return "jest";
6784
6808
  if (f.includes("mocha")) return "mocha";
6785
6809
  } catch {
6786
6810
  }
6787
6811
  }
6788
- return "vitest";
6812
+ return null;
6789
6813
  }
6790
6814
  function buildArgs2(runner, input) {
6791
6815
  const args = [];
@@ -7324,7 +7348,7 @@ var treeTool = {
7324
7348
  }
7325
7349
  };
7326
7350
  async function walkDir(dir, depth, opts) {
7327
- const entries = await fs12.readdir(dir, { withFileTypes: true }).catch(() => []);
7351
+ const entries = await fs13.readdir(dir, { withFileTypes: true }).catch(() => []);
7328
7352
  const filtered = entries.filter((e) => {
7329
7353
  if (!opts.showHidden && e.name.startsWith(".")) return false;
7330
7354
  if (opts.exclude.has(e.name)) return false;
@@ -7354,7 +7378,7 @@ async function walkDir(dir, depth, opts) {
7354
7378
  opts.lines.push(opts.prefix + branch + displayName);
7355
7379
  if (entry.isDirectory() && (opts.maxDepth === 0 || depth < opts.maxDepth)) {
7356
7380
  const childPrefix = opts.prefix + connector;
7357
- await walkDir(path.join(dir, entry.name), depth + 1, {
7381
+ await walkDir(path2.join(dir, entry.name), depth + 1, {
7358
7382
  ...opts,
7359
7383
  prefix: childPrefix,
7360
7384
  isLast
@@ -7437,8 +7461,8 @@ async function findTsConfig(cwd) {
7437
7461
  const candidates = ["tsconfig.json", "tsconfig.base.json"];
7438
7462
  for (const f of candidates) {
7439
7463
  try {
7440
- const s = await stat10(path.join(cwd, f));
7441
- if (s.isFile()) return path.join(cwd, f);
7464
+ const s = await stat10(path2.join(cwd, f));
7465
+ if (s.isFile()) return path2.join(cwd, f);
7442
7466
  } catch {
7443
7467
  }
7444
7468
  }
@@ -7474,14 +7498,14 @@ var writeTool = {
7474
7498
  let existed = false;
7475
7499
  let prev = "";
7476
7500
  try {
7477
- const stat11 = await fs12.stat(absPath);
7501
+ const stat11 = await fs13.stat(absPath);
7478
7502
  existed = stat11.isFile();
7479
7503
  if (existed) {
7480
7504
  if (!ctx.hasRead(absPath)) {
7481
- prev = await fs12.readFile(absPath, "utf8");
7505
+ prev = await fs13.readFile(absPath, "utf8");
7482
7506
  ctx.recordRead(absPath, stat11.mtimeMs);
7483
7507
  } else {
7484
- prev = await fs12.readFile(absPath, "utf8");
7508
+ prev = await fs13.readFile(absPath, "utf8");
7485
7509
  }
7486
7510
  }
7487
7511
  } catch (err) {
@@ -7492,7 +7516,7 @@ var writeTool = {
7492
7516
  await atomicWrite(absPath, input.content);
7493
7517
  const diff = existed ? unifiedDiff(prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
7494
7518
  + (new file, ${input.content.split("\n").length} lines)`;
7495
- const stat10 = await fs12.stat(absPath);
7519
+ const stat10 = await fs13.stat(absPath);
7496
7520
  ctx.recordRead(absPath, stat10.mtimeMs);
7497
7521
  ctx.session.recordFileChange({
7498
7522
  path: absPath,