@wrongstack/tools 0.306.3 → 0.306.4

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/logs.js CHANGED
@@ -171,8 +171,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
171
171
  }
172
172
  throw err;
173
173
  }
174
- if (isInsideAny(real, realRoots)) {
175
- return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
174
+ const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
175
+ if (isInsideAny(candidate, realRoots)) {
176
+ return candidate;
176
177
  }
177
178
  throw new Error(
178
179
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
@@ -226,7 +227,7 @@ var logsTool = {
226
227
  }
227
228
  },
228
229
  async execute(input, ctx, opts) {
229
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
230
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
230
231
  const lines = input.lines ?? 100;
231
232
  let filterRe = null;
232
233
  if (input.filter) {
package/dist/outdated.js CHANGED
@@ -102,8 +102,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
102
102
  }
103
103
  throw err;
104
104
  }
105
- if (isInsideAny(real, realRoots)) {
106
- return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
105
+ const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
106
+ if (isInsideAny(candidate, realRoots)) {
107
+ return candidate;
107
108
  }
108
109
  throw new Error(
109
110
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
@@ -5020,7 +5021,7 @@ var outdatedTool = {
5020
5021
  }
5021
5022
  },
5022
5023
  async execute(input, ctx, opts) {
5023
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
5024
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
5024
5025
  const manager = await detectPackageManager(cwd, ctx.projectRoot);
5025
5026
  if (manager === "npm") {
5026
5027
  try {
package/dist/pack.js CHANGED
@@ -1202,8 +1202,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
1202
1202
  }
1203
1203
  throw err;
1204
1204
  }
1205
- if (isInsideAny(real, realRoots)) {
1206
- return pendingTail.length > 0 ? path3.join(real, ...pendingTail) : real;
1205
+ const candidate = pendingTail.length > 0 ? path3.join(real, ...pendingTail) : real;
1206
+ if (isInsideAny(candidate, realRoots)) {
1207
+ return candidate;
1207
1208
  }
1208
1209
  throw new Error(
1209
1210
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
@@ -7737,7 +7738,7 @@ var auditTool = {
7737
7738
  "audit: `fix: true` is not supported \u2014 this tool is read-only (mutating: false). To remediate vulnerabilities, upgrade the affected packages with the `install` tool (or `language_package` for non-JS ecosystems)."
7738
7739
  );
7739
7740
  }
7740
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
7741
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
7741
7742
  const bridge = await tryLegacyPackageOperation("package-audit", {
7742
7743
  cwd,
7743
7744
  projectRoot: ctx.projectRoot,
@@ -18062,7 +18063,7 @@ var documentTool = {
18062
18063
  }
18063
18064
  },
18064
18065
  async execute(input, ctx) {
18065
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
18066
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
18066
18067
  const style = input.style ?? "jsdoc";
18067
18068
  const results = [];
18068
18069
  let filesProcessed = 0;
@@ -18071,7 +18072,7 @@ var documentTool = {
18071
18072
  Array.isArray(input.files) ? input.files.join(",") : input.files,
18072
18073
  cwd,
18073
18074
  ctx
18074
- ) : input.path ? [safeResolve(input.path, ctx)] : [];
18075
+ ) : input.path ? [await safeResolveReal(input.path, ctx)] : [];
18075
18076
  for (const absPath of fileList) {
18076
18077
  try {
18077
18078
  const content = await fs23.readFile(absPath, "utf8");
@@ -21187,7 +21188,7 @@ var formatTool = {
21187
21188
  return final;
21188
21189
  },
21189
21190
  async *executeStream(input, ctx, opts) {
21190
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
21191
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
21191
21192
  const fixer = input.fixer ?? "auto";
21192
21193
  if (fixer === "auto" && !input.files) {
21193
21194
  const op = input.check ? "format-check" : "format-write";
@@ -22405,7 +22406,7 @@ var installTool = {
22405
22406
  return final;
22406
22407
  },
22407
22408
  async *executeStream(input, ctx, opts) {
22408
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
22409
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
22409
22410
  if (!input.global) {
22410
22411
  const pkgList2 = input.packages ? Array.isArray(input.packages) ? input.packages : input.packages.split(",").map((p) => p.trim()) : [];
22411
22412
  const bridge = await tryLegacyPackageOperation(
@@ -25581,7 +25582,7 @@ var lintTool = {
25581
25582
  return final;
25582
25583
  },
25583
25584
  async *executeStream(input, ctx, opts) {
25584
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
25585
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
25585
25586
  const linter = input.linter ?? "auto";
25586
25587
  if (linter === "auto" && !input.files) {
25587
25588
  const bridge = await tryLegacyCodeOperation("lint", {
@@ -25707,7 +25708,7 @@ var logsTool = {
25707
25708
  }
25708
25709
  },
25709
25710
  async execute(input, ctx, opts) {
25710
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
25711
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
25711
25712
  const lines = input.lines ?? 100;
25712
25713
  let filterRe = null;
25713
25714
  if (input.filter) {
@@ -25914,7 +25915,7 @@ var outdatedTool = {
25914
25915
  }
25915
25916
  },
25916
25917
  async execute(input, ctx, opts) {
25917
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
25918
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
25918
25919
  const manager = await detectPackageManager(cwd, ctx.projectRoot);
25919
25920
  if (manager === "npm") {
25920
25921
  try {
@@ -27628,7 +27629,7 @@ async function resolveFiles2(filesInput, ctx, extraGlob) {
27628
27629
  const parts = normalized.split(",").map((s) => s.trim()).filter(Boolean);
27629
27630
  const resolved = [];
27630
27631
  for (const p of parts) {
27631
- const absPath = safeResolve(p, ctx);
27632
+ const absPath = await safeResolveReal(p, ctx);
27632
27633
  if (extraGlob && !passesExtraGlob(extraGlob, path35.basename(absPath), absPath)) continue;
27633
27634
  const stat19 = await fs30.stat(absPath).catch(() => null);
27634
27635
  if (stat19?.isFile()) {
@@ -27866,7 +27867,7 @@ var scaffoldTool = {
27866
27867
  required: ["template", "name"]
27867
27868
  },
27868
27869
  async execute(input, ctx) {
27869
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
27870
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
27870
27871
  const name = input.name;
27871
27872
  const vars = { name, ...input.vars };
27872
27873
  const builtIn = BUILT_IN_TEMPLATES[input.template];
@@ -28954,7 +28955,7 @@ var testTool = {
28954
28955
  return final;
28955
28956
  },
28956
28957
  async *executeStream(input, ctx, opts) {
28957
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
28958
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
28958
28959
  const runner = input.runner ?? "auto";
28959
28960
  if (runner === "auto") {
28960
28961
  const bridge = await tryLegacyCodeOperation("test", {
@@ -29469,7 +29470,7 @@ var treeTool = {
29469
29470
  return final;
29470
29471
  },
29471
29472
  async *executeStream(input, ctx, opts) {
29472
- const basePath = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
29473
+ const basePath = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
29473
29474
  const maxDepth = input.depth ?? 3;
29474
29475
  const showFiles = input.show_files ?? true;
29475
29476
  const showDirs = input.show_dirs ?? true;
@@ -29649,7 +29650,7 @@ var typecheckTool = {
29649
29650
  return final;
29650
29651
  },
29651
29652
  async *executeStream(input, ctx, opts) {
29652
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
29653
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
29653
29654
  const bridge = await tryLegacyCodeOperation("semantic", {
29654
29655
  cwd,
29655
29656
  projectRoot: ctx.projectRoot,
@@ -29686,7 +29687,7 @@ var typecheckTool = {
29686
29687
  cmdArgs = ["tsc", ...tscArgs];
29687
29688
  }
29688
29689
  } else {
29689
- const tsconfig = input.project ? safeResolve(input.project, ctx) : await findTsConfig(cwd);
29690
+ const tsconfig = input.project ? await safeResolveReal(input.project, ctx) : await findTsConfig(cwd);
29690
29691
  const tscArgs = ["--noEmit"];
29691
29692
  if (input.strict) tscArgs.push("--strict");
29692
29693
  if (tsconfig) tscArgs.push("--project", tsconfig);
package/dist/patch.js CHANGED
@@ -55,8 +55,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
55
55
  }
56
56
  throw err;
57
57
  }
58
- if (isInsideAny(real, realRoots)) {
59
- return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
58
+ const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
59
+ if (isInsideAny(candidate, realRoots)) {
60
+ return candidate;
60
61
  }
61
62
  throw new Error(
62
63
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
package/dist/read.js CHANGED
@@ -2753,8 +2753,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
2753
2753
  }
2754
2754
  throw err;
2755
2755
  }
2756
- if (isInsideAny(real, realRoots)) {
2757
- return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
2756
+ const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
2757
+ if (isInsideAny(candidate, realRoots)) {
2758
+ return candidate;
2758
2759
  }
2759
2760
  throw new Error(
2760
2761
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
package/dist/replace.js CHANGED
@@ -139,6 +139,7 @@ var MAX_SUBJECT_LEN = 64 * 1024;
139
139
 
140
140
  // src/_util.ts
141
141
  import { createHash } from "node:crypto";
142
+ import * as fsp from "node:fs/promises";
142
143
  import * as path from "node:path";
143
144
  import * as Core from "@wrongstack/core/utils";
144
145
  function sha256hex(content) {
@@ -165,6 +166,40 @@ function ensureInsideRoot(absPath, ctx) {
165
166
  function safeResolve(input, ctx) {
166
167
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
167
168
  }
169
+ async function resolveRealInsideRoot(absPath, ctx) {
170
+ if (ctx.allowOutsideProjectRoot) return absPath;
171
+ const realRoots = await Promise.all(
172
+ allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
173
+ );
174
+ let probe = absPath;
175
+ const pendingTail = [];
176
+ for (; ; ) {
177
+ let real;
178
+ try {
179
+ real = await fsp.realpath(probe);
180
+ } catch (err) {
181
+ if (err.code === "ENOENT") {
182
+ const parent = path.dirname(probe);
183
+ if (parent === probe) return absPath;
184
+ pendingTail.unshift(path.basename(probe));
185
+ probe = parent;
186
+ continue;
187
+ }
188
+ throw err;
189
+ }
190
+ const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
191
+ if (isInsideAny(candidate, realRoots)) {
192
+ return candidate;
193
+ }
194
+ throw new Error(
195
+ `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
196
+ );
197
+ }
198
+ }
199
+ async function safeResolveReal(input, ctx) {
200
+ const abs = safeResolve(input, ctx);
201
+ return await resolveRealInsideRoot(abs, ctx);
202
+ }
168
203
  function truncateDiffPayload(diff, maxBytes) {
169
204
  const total = Buffer.byteLength(diff, "utf8");
170
205
  if (total <= maxBytes) return { text: diff, truncated: false };
@@ -408,7 +443,7 @@ async function resolveFiles(filesInput, ctx, extraGlob) {
408
443
  const parts = normalized.split(",").map((s) => s.trim()).filter(Boolean);
409
444
  const resolved = [];
410
445
  for (const p of parts) {
411
- const absPath = safeResolve(p, ctx);
446
+ const absPath = await safeResolveReal(p, ctx);
412
447
  if (extraGlob && !passesExtraGlob(extraGlob, path2.basename(absPath), absPath)) continue;
413
448
  const stat2 = await fs.stat(absPath).catch(() => null);
414
449
  if (stat2?.isFile()) {
package/dist/scaffold.js CHANGED
@@ -4,6 +4,7 @@ import * as path2 from "node:path";
4
4
  import { atomicWrite } from "@wrongstack/core/utils";
5
5
 
6
6
  // src/_util.ts
7
+ import * as fsp from "node:fs/promises";
7
8
  import * as path from "node:path";
8
9
  import * as Core from "@wrongstack/core/utils";
9
10
  function resolvePath(input, ctx) {
@@ -27,6 +28,40 @@ function ensureInsideRoot(absPath, ctx) {
27
28
  function safeResolve(input, ctx) {
28
29
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
29
30
  }
31
+ async function resolveRealInsideRoot(absPath, ctx) {
32
+ if (ctx.allowOutsideProjectRoot) return absPath;
33
+ const realRoots = await Promise.all(
34
+ allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
35
+ );
36
+ let probe = absPath;
37
+ const pendingTail = [];
38
+ for (; ; ) {
39
+ let real;
40
+ try {
41
+ real = await fsp.realpath(probe);
42
+ } catch (err) {
43
+ if (err.code === "ENOENT") {
44
+ const parent = path.dirname(probe);
45
+ if (parent === probe) return absPath;
46
+ pendingTail.unshift(path.basename(probe));
47
+ probe = parent;
48
+ continue;
49
+ }
50
+ throw err;
51
+ }
52
+ const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
53
+ if (isInsideAny(candidate, realRoots)) {
54
+ return candidate;
55
+ }
56
+ throw new Error(
57
+ `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
58
+ );
59
+ }
60
+ }
61
+ async function safeResolveReal(input, ctx) {
62
+ const abs = safeResolve(input, ctx);
63
+ return await resolveRealInsideRoot(abs, ctx);
64
+ }
30
65
 
31
66
  // src/scaffold.ts
32
67
  var BUILT_IN_TEMPLATES = {
@@ -156,7 +191,7 @@ var scaffoldTool = {
156
191
  required: ["template", "name"]
157
192
  },
158
193
  async execute(input, ctx) {
159
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
194
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
160
195
  const name = input.name;
161
196
  const vars = { name, ...input.vars };
162
197
  const builtIn = BUILT_IN_TEMPLATES[input.template];
package/dist/test.js CHANGED
@@ -1044,6 +1044,7 @@ function utf8Prefix(text, maxBytes) {
1044
1044
  }
1045
1045
 
1046
1046
  // src/_util.ts
1047
+ import * as fsp2 from "node:fs/promises";
1047
1048
  import * as path3 from "node:path";
1048
1049
  import * as Core from "@wrongstack/core/utils";
1049
1050
  function resolvePath(input, ctx) {
@@ -1067,6 +1068,40 @@ function ensureInsideRoot(absPath, ctx) {
1067
1068
  function safeResolve(input, ctx) {
1068
1069
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
1069
1070
  }
1071
+ async function resolveRealInsideRoot(absPath, ctx) {
1072
+ if (ctx.allowOutsideProjectRoot) return absPath;
1073
+ const realRoots = await Promise.all(
1074
+ allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path3.resolve(r)))
1075
+ );
1076
+ let probe = absPath;
1077
+ const pendingTail = [];
1078
+ for (; ; ) {
1079
+ let real;
1080
+ try {
1081
+ real = await fsp2.realpath(probe);
1082
+ } catch (err) {
1083
+ if (err.code === "ENOENT") {
1084
+ const parent = path3.dirname(probe);
1085
+ if (parent === probe) return absPath;
1086
+ pendingTail.unshift(path3.basename(probe));
1087
+ probe = parent;
1088
+ continue;
1089
+ }
1090
+ throw err;
1091
+ }
1092
+ const candidate = pendingTail.length > 0 ? path3.join(real, ...pendingTail) : real;
1093
+ if (isInsideAny(candidate, realRoots)) {
1094
+ return candidate;
1095
+ }
1096
+ throw new Error(
1097
+ `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
1098
+ );
1099
+ }
1100
+ }
1101
+ async function safeResolveReal(input, ctx) {
1102
+ const abs = safeResolve(input, ctx);
1103
+ return await resolveRealInsideRoot(abs, ctx);
1104
+ }
1070
1105
  var COMMAND_OUTPUT_MAX_BYTES = 32768;
1071
1106
  var REPEAT_RUN_THRESHOLD = 3;
1072
1107
  function collapseCarriageReturns(text) {
@@ -3594,7 +3629,7 @@ var testTool = {
3594
3629
  return final;
3595
3630
  },
3596
3631
  async *executeStream(input, ctx, opts) {
3597
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
3632
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
3598
3633
  const runner = input.runner ?? "auto";
3599
3634
  if (runner === "auto") {
3600
3635
  const bridge = await tryLegacyCodeOperation("test", {
package/dist/tool-tier.js CHANGED
@@ -1202,8 +1202,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
1202
1202
  }
1203
1203
  throw err;
1204
1204
  }
1205
- if (isInsideAny(real, realRoots)) {
1206
- return pendingTail.length > 0 ? path3.join(real, ...pendingTail) : real;
1205
+ const candidate = pendingTail.length > 0 ? path3.join(real, ...pendingTail) : real;
1206
+ if (isInsideAny(candidate, realRoots)) {
1207
+ return candidate;
1207
1208
  }
1208
1209
  throw new Error(
1209
1210
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
@@ -7737,7 +7738,7 @@ var auditTool = {
7737
7738
  "audit: `fix: true` is not supported \u2014 this tool is read-only (mutating: false). To remediate vulnerabilities, upgrade the affected packages with the `install` tool (or `language_package` for non-JS ecosystems)."
7738
7739
  );
7739
7740
  }
7740
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
7741
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
7741
7742
  const bridge = await tryLegacyPackageOperation("package-audit", {
7742
7743
  cwd,
7743
7744
  projectRoot: ctx.projectRoot,
@@ -18062,7 +18063,7 @@ var documentTool = {
18062
18063
  }
18063
18064
  },
18064
18065
  async execute(input, ctx) {
18065
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
18066
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
18066
18067
  const style = input.style ?? "jsdoc";
18067
18068
  const results = [];
18068
18069
  let filesProcessed = 0;
@@ -18071,7 +18072,7 @@ var documentTool = {
18071
18072
  Array.isArray(input.files) ? input.files.join(",") : input.files,
18072
18073
  cwd,
18073
18074
  ctx
18074
- ) : input.path ? [safeResolve(input.path, ctx)] : [];
18075
+ ) : input.path ? [await safeResolveReal(input.path, ctx)] : [];
18075
18076
  for (const absPath of fileList) {
18076
18077
  try {
18077
18078
  const content = await fs23.readFile(absPath, "utf8");
@@ -21187,7 +21188,7 @@ var formatTool = {
21187
21188
  return final;
21188
21189
  },
21189
21190
  async *executeStream(input, ctx, opts) {
21190
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
21191
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
21191
21192
  const fixer = input.fixer ?? "auto";
21192
21193
  if (fixer === "auto" && !input.files) {
21193
21194
  const op = input.check ? "format-check" : "format-write";
@@ -22405,7 +22406,7 @@ var installTool = {
22405
22406
  return final;
22406
22407
  },
22407
22408
  async *executeStream(input, ctx, opts) {
22408
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
22409
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
22409
22410
  if (!input.global) {
22410
22411
  const pkgList2 = input.packages ? Array.isArray(input.packages) ? input.packages : input.packages.split(",").map((p) => p.trim()) : [];
22411
22412
  const bridge = await tryLegacyPackageOperation(
@@ -25581,7 +25582,7 @@ var lintTool = {
25581
25582
  return final;
25582
25583
  },
25583
25584
  async *executeStream(input, ctx, opts) {
25584
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
25585
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
25585
25586
  const linter = input.linter ?? "auto";
25586
25587
  if (linter === "auto" && !input.files) {
25587
25588
  const bridge = await tryLegacyCodeOperation("lint", {
@@ -25707,7 +25708,7 @@ var logsTool = {
25707
25708
  }
25708
25709
  },
25709
25710
  async execute(input, ctx, opts) {
25710
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
25711
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
25711
25712
  const lines = input.lines ?? 100;
25712
25713
  let filterRe = null;
25713
25714
  if (input.filter) {
@@ -25914,7 +25915,7 @@ var outdatedTool = {
25914
25915
  }
25915
25916
  },
25916
25917
  async execute(input, ctx, opts) {
25917
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
25918
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
25918
25919
  const manager = await detectPackageManager(cwd, ctx.projectRoot);
25919
25920
  if (manager === "npm") {
25920
25921
  try {
@@ -27628,7 +27629,7 @@ async function resolveFiles2(filesInput, ctx, extraGlob) {
27628
27629
  const parts = normalized.split(",").map((s) => s.trim()).filter(Boolean);
27629
27630
  const resolved = [];
27630
27631
  for (const p of parts) {
27631
- const absPath = safeResolve(p, ctx);
27632
+ const absPath = await safeResolveReal(p, ctx);
27632
27633
  if (extraGlob && !passesExtraGlob(extraGlob, path35.basename(absPath), absPath)) continue;
27633
27634
  const stat19 = await fs30.stat(absPath).catch(() => null);
27634
27635
  if (stat19?.isFile()) {
@@ -27866,7 +27867,7 @@ var scaffoldTool = {
27866
27867
  required: ["template", "name"]
27867
27868
  },
27868
27869
  async execute(input, ctx) {
27869
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
27870
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
27870
27871
  const name = input.name;
27871
27872
  const vars = { name, ...input.vars };
27872
27873
  const builtIn = BUILT_IN_TEMPLATES[input.template];
@@ -28954,7 +28955,7 @@ var testTool = {
28954
28955
  return final;
28955
28956
  },
28956
28957
  async *executeStream(input, ctx, opts) {
28957
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
28958
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
28958
28959
  const runner = input.runner ?? "auto";
28959
28960
  if (runner === "auto") {
28960
28961
  const bridge = await tryLegacyCodeOperation("test", {
@@ -29469,7 +29470,7 @@ var treeTool = {
29469
29470
  return final;
29470
29471
  },
29471
29472
  async *executeStream(input, ctx, opts) {
29472
- const basePath = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
29473
+ const basePath = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
29473
29474
  const maxDepth = input.depth ?? 3;
29474
29475
  const showFiles = input.show_files ?? true;
29475
29476
  const showDirs = input.show_dirs ?? true;
@@ -29649,7 +29650,7 @@ var typecheckTool = {
29649
29650
  return final;
29650
29651
  },
29651
29652
  async *executeStream(input, ctx, opts) {
29652
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
29653
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
29653
29654
  const bridge = await tryLegacyCodeOperation("semantic", {
29654
29655
  cwd,
29655
29656
  projectRoot: ctx.projectRoot,
@@ -29686,7 +29687,7 @@ var typecheckTool = {
29686
29687
  cmdArgs = ["tsc", ...tscArgs];
29687
29688
  }
29688
29689
  } else {
29689
- const tsconfig = input.project ? safeResolve(input.project, ctx) : await findTsConfig(cwd);
29690
+ const tsconfig = input.project ? await safeResolveReal(input.project, ctx) : await findTsConfig(cwd);
29690
29691
  const tscArgs = ["--noEmit"];
29691
29692
  if (input.strict) tscArgs.push("--strict");
29692
29693
  if (tsconfig) tscArgs.push("--project", tsconfig);
package/dist/tree.js CHANGED
@@ -4,6 +4,7 @@ import * as path2 from "node:path";
4
4
  import { DEFAULT_WALK_IGNORE_DIRS, expectDefined } from "@wrongstack/core/utils";
5
5
 
6
6
  // src/_util.ts
7
+ import * as fsp from "node:fs/promises";
7
8
  import * as path from "node:path";
8
9
  import * as Core from "@wrongstack/core/utils";
9
10
  function resolvePath(input, ctx) {
@@ -27,6 +28,40 @@ function ensureInsideRoot(absPath, ctx) {
27
28
  function safeResolve(input, ctx) {
28
29
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
29
30
  }
31
+ async function resolveRealInsideRoot(absPath, ctx) {
32
+ if (ctx.allowOutsideProjectRoot) return absPath;
33
+ const realRoots = await Promise.all(
34
+ allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
35
+ );
36
+ let probe = absPath;
37
+ const pendingTail = [];
38
+ for (; ; ) {
39
+ let real;
40
+ try {
41
+ real = await fsp.realpath(probe);
42
+ } catch (err) {
43
+ if (err.code === "ENOENT") {
44
+ const parent = path.dirname(probe);
45
+ if (parent === probe) return absPath;
46
+ pendingTail.unshift(path.basename(probe));
47
+ probe = parent;
48
+ continue;
49
+ }
50
+ throw err;
51
+ }
52
+ const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
53
+ if (isInsideAny(candidate, realRoots)) {
54
+ return candidate;
55
+ }
56
+ throw new Error(
57
+ `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
58
+ );
59
+ }
60
+ }
61
+ async function safeResolveReal(input, ctx) {
62
+ const abs = safeResolve(input, ctx);
63
+ return await resolveRealInsideRoot(abs, ctx);
64
+ }
30
65
 
31
66
  // src/tree.ts
32
67
  var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
@@ -102,7 +137,7 @@ var treeTool = {
102
137
  return final;
103
138
  },
104
139
  async *executeStream(input, ctx, opts) {
105
- const basePath = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
140
+ const basePath = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
106
141
  const maxDepth = input.depth ?? 3;
107
142
  const showFiles = input.show_files ?? true;
108
143
  const showDirs = input.show_dirs ?? true;
package/dist/typecheck.js CHANGED
@@ -1044,6 +1044,7 @@ function utf8Prefix(text, maxBytes) {
1044
1044
  }
1045
1045
 
1046
1046
  // src/_util.ts
1047
+ import * as fsp2 from "node:fs/promises";
1047
1048
  import * as path3 from "node:path";
1048
1049
  import * as Core from "@wrongstack/core/utils";
1049
1050
  async function detectPackageManager(cwd, stopAt) {
@@ -1110,6 +1111,40 @@ function ensureInsideRoot(absPath, ctx) {
1110
1111
  function safeResolve(input, ctx) {
1111
1112
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
1112
1113
  }
1114
+ async function resolveRealInsideRoot(absPath, ctx) {
1115
+ if (ctx.allowOutsideProjectRoot) return absPath;
1116
+ const realRoots = await Promise.all(
1117
+ allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path3.resolve(r)))
1118
+ );
1119
+ let probe = absPath;
1120
+ const pendingTail = [];
1121
+ for (; ; ) {
1122
+ let real;
1123
+ try {
1124
+ real = await fsp2.realpath(probe);
1125
+ } catch (err) {
1126
+ if (err.code === "ENOENT") {
1127
+ const parent = path3.dirname(probe);
1128
+ if (parent === probe) return absPath;
1129
+ pendingTail.unshift(path3.basename(probe));
1130
+ probe = parent;
1131
+ continue;
1132
+ }
1133
+ throw err;
1134
+ }
1135
+ const candidate = pendingTail.length > 0 ? path3.join(real, ...pendingTail) : real;
1136
+ if (isInsideAny(candidate, realRoots)) {
1137
+ return candidate;
1138
+ }
1139
+ throw new Error(
1140
+ `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
1141
+ );
1142
+ }
1143
+ }
1144
+ async function safeResolveReal(input, ctx) {
1145
+ const abs = safeResolve(input, ctx);
1146
+ return await resolveRealInsideRoot(abs, ctx);
1147
+ }
1113
1148
  var COMMAND_OUTPUT_MAX_BYTES = 32768;
1114
1149
  var REPEAT_RUN_THRESHOLD = 3;
1115
1150
  function collapseCarriageReturns(text) {
@@ -3629,7 +3664,7 @@ var typecheckTool = {
3629
3664
  return final;
3630
3665
  },
3631
3666
  async *executeStream(input, ctx, opts) {
3632
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
3667
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
3633
3668
  const bridge = await tryLegacyCodeOperation("semantic", {
3634
3669
  cwd,
3635
3670
  projectRoot: ctx.projectRoot,
@@ -3666,7 +3701,7 @@ var typecheckTool = {
3666
3701
  cmdArgs = ["tsc", ...tscArgs];
3667
3702
  }
3668
3703
  } else {
3669
- const tsconfig = input.project ? safeResolve(input.project, ctx) : await findTsConfig(cwd);
3704
+ const tsconfig = input.project ? await safeResolveReal(input.project, ctx) : await findTsConfig(cwd);
3670
3705
  const tscArgs = ["--noEmit"];
3671
3706
  if (input.strict) tscArgs.push("--strict");
3672
3707
  if (tsconfig) tscArgs.push("--project", tsconfig);
package/dist/write.js CHANGED
@@ -144,8 +144,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
144
144
  }
145
145
  throw err;
146
146
  }
147
- if (isInsideAny(real, realRoots)) {
148
- return pendingTail.length > 0 ? path2.join(real, ...pendingTail) : real;
147
+ const candidate = pendingTail.length > 0 ? path2.join(real, ...pendingTail) : real;
148
+ if (isInsideAny(candidate, realRoots)) {
149
+ return candidate;
149
150
  }
150
151
  throw new Error(
151
152
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/tools",
3
- "version": "0.306.3",
3
+ "version": "0.306.4",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack built-in tools: read/write/edit, bash/exec, grep/glob, git, fetch, test, lint, and more.",
6
6
  "repository": {
@@ -253,9 +253,9 @@
253
253
  "turndown": "^7.2.4",
254
254
  "undici": "^8.9.0",
255
255
  "web-tree-sitter": "0.26.12",
256
- "@wrongstack/core": "0.306.3",
257
- "@wrongstack/persistence": "0.306.3",
258
- "@wrongstack/kanban": "0.306.3"
256
+ "@wrongstack/kanban": "0.306.4",
257
+ "@wrongstack/core": "0.306.4",
258
+ "@wrongstack/persistence": "0.306.4"
259
259
  },
260
260
  "devDependencies": {
261
261
  "@types/node": "^26.1.2",