@wrongstack/tools 0.319.0 → 0.320.1

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 (119) hide show
  1. package/dist/_edit-match.d.ts +1 -3
  2. package/dist/_util.d.ts +7 -0
  3. package/dist/audit.js +8 -8
  4. package/dist/bash.js +6 -6
  5. package/dist/browser/index.js +1 -1
  6. package/dist/builtin.d.ts +10 -0
  7. package/dist/builtin.js +54 -52
  8. package/dist/{chunk-65EMYN5G.js → chunk-23EX5WDO.js} +6 -3
  9. package/dist/{chunk-2472NT76.js → chunk-2OXHS3KZ.js} +17 -5
  10. package/dist/{chunk-7WV3D4S3.js → chunk-34VLZ35Z.js} +75 -36
  11. package/dist/{chunk-MYRMD6PZ.js → chunk-3GIKYHLA.js} +32 -8
  12. package/dist/{chunk-X5K3U6GI.js → chunk-3M7LLN7W.js} +71 -7
  13. package/dist/{chunk-VALQFAY2.js → chunk-4BABEJC2.js} +158 -45
  14. package/dist/{chunk-42BHI7TN.js → chunk-4CQWNZ62.js} +2 -2
  15. package/dist/{chunk-WNUJR3H4.js → chunk-5UFVCIUB.js} +40 -16
  16. package/dist/{chunk-UMXVT7ST.js → chunk-6AAOT4PW.js} +58 -10
  17. package/dist/{chunk-H6AWAZ2Y.js → chunk-B4HTSV6Z.js} +57 -6
  18. package/dist/{chunk-5ZYEGP2P.js → chunk-BK732J7I.js} +72 -27
  19. package/dist/{chunk-KD5EHYA2.js → chunk-C2DQKVLO.js} +61 -28
  20. package/dist/{chunk-Z4VBTVUM.js → chunk-DFQDJT22.js} +99 -12
  21. package/dist/{chunk-6RNIFLG4.js → chunk-ELRRTWNP.js} +38 -6
  22. package/dist/{chunk-3PVYQFRQ.js → chunk-ESLPBT5A.js} +2 -1
  23. package/dist/{chunk-YZHK4TLF.js → chunk-FQNXYA6W.js} +45 -6
  24. package/dist/{chunk-OYVPBPCL.js → chunk-HR67OWEP.js} +72 -33
  25. package/dist/{chunk-ZDUGI5BC.js → chunk-I7FBZOEP.js} +2 -2
  26. package/dist/{chunk-HBM4QNXU.js → chunk-IU4DV2PA.js} +18 -8
  27. package/dist/{chunk-TFMVHYJI.js → chunk-J43O3BOX.js} +9 -15
  28. package/dist/{chunk-ODFVG54U.js → chunk-J74TMK4M.js} +37 -13
  29. package/dist/{chunk-COUNXG4X.js → chunk-JLYA4X7K.js} +75 -8
  30. package/dist/{chunk-FQZOHLG3.js → chunk-JSNSMG5R.js} +3 -2
  31. package/dist/{chunk-SYXW3Y5Z.js → chunk-JVRLTEO7.js} +2 -2
  32. package/dist/{chunk-V43EQ6VR.js → chunk-KE7A76VC.js} +3 -2
  33. package/dist/{chunk-ZKSBHXVB.js → chunk-KWXIKGE3.js} +64 -15
  34. package/dist/{chunk-RK47IWO3.js → chunk-LRDIRJJL.js} +62 -30
  35. package/dist/{chunk-UC3RKRIV.js → chunk-NDIT3SBZ.js} +3 -3
  36. package/dist/{chunk-C3W3V54D.js → chunk-NSPNFOJ7.js} +57 -27
  37. package/dist/{chunk-TX3R5NVV.js → chunk-NU2IZ72J.js} +7 -7
  38. package/dist/{chunk-WHIIIREL.js → chunk-NYRLWDT5.js} +7 -3
  39. package/dist/{chunk-PMHVSHMU.js → chunk-OTPTQBYE.js} +80 -19
  40. package/dist/{chunk-ZHH3BEWU.js → chunk-OYP27E3I.js} +4 -4
  41. package/dist/{chunk-W4GAAI3M.js → chunk-PV6HIZQD.js} +2 -2
  42. package/dist/{chunk-NKNOBUBU.js → chunk-QGF2C7TF.js} +2 -2
  43. package/dist/{chunk-PQTONQ4P.js → chunk-QPNYDH5B.js} +19 -5
  44. package/dist/{chunk-VFF3Q5Z3.js → chunk-QSNVDHOZ.js} +37 -9
  45. package/dist/{chunk-45JPRMC2.js → chunk-QWKSKFKZ.js} +7 -3
  46. package/dist/{chunk-2VSZC6QM.js → chunk-SCMRM6NL.js} +6 -4
  47. package/dist/{chunk-RJZQWEMS.js → chunk-T77XOX7E.js} +3 -2
  48. package/dist/{chunk-QZDOQRF7.js → chunk-UZFY3CKD.js} +35 -14
  49. package/dist/{chunk-D4CXUZIF.js → chunk-VD2LV5WO.js} +82 -13
  50. package/dist/{chunk-DPKTXJQS.js → chunk-VPALDFYM.js} +2 -2
  51. package/dist/{chunk-IIF5HIVF.js → chunk-VVZPQPTY.js} +2 -2
  52. package/dist/{chunk-HWDB2G7J.js → chunk-W232EFEM.js} +49 -36
  53. package/dist/{chunk-FJEQZHZN.js → chunk-W2CECGTG.js} +24 -14
  54. package/dist/{chunk-4Q7LFU4T.js → chunk-WAQASTXB.js} +20 -9
  55. package/dist/{chunk-CWALNI6K.js → chunk-X6KXNHUO.js} +1 -1
  56. package/dist/{chunk-OCUXN6Y3.js → chunk-XRNN44QY.js} +1 -1
  57. package/dist/{chunk-N5O3FK5Q.js → chunk-XSSFBS5I.js} +70 -29
  58. package/dist/{chunk-Y7QXSE7N.js → chunk-YTKSNTT4.js} +44 -10
  59. package/dist/{chunk-MRKWZCHZ.js → chunk-Z5Q3TXRY.js} +3 -3
  60. package/dist/{chunk-PRXZOIEW.js → chunk-Z6IHMJMC.js} +104 -59
  61. package/dist/{chunk-NWZ6XGBJ.js → chunk-ZBMANLYH.js} +5 -3
  62. package/dist/{chunk-OJ4FIBAN.js → chunk-ZMW6WJFT.js} +30 -23
  63. package/dist/{chunk-QLF74MSV.js → chunk-ZUFHAHTE.js} +12 -6
  64. package/dist/codebase-index/index.js +12 -12
  65. package/dist/codebase-index/parser-worker-script.js +3 -3
  66. package/dist/codebase-index/project-server.js +5 -5
  67. package/dist/codebase-index/worker.js +4 -4
  68. package/dist/design.d.ts +1 -1
  69. package/dist/design.js +1 -1
  70. package/dist/diff.js +3 -2
  71. package/dist/document.js +2 -2
  72. package/dist/e2e.js +2 -2
  73. package/dist/edit.js +9 -9
  74. package/dist/exec.js +6 -6
  75. package/dist/fetch.js +2 -2
  76. package/dist/format.js +8 -8
  77. package/dist/git.js +2 -2
  78. package/dist/glob.js +3 -3
  79. package/dist/{go-parser-IKHW67WE.js → go-parser-D7ELWQ7F.js} +2 -2
  80. package/dist/grep.d.ts +2 -0
  81. package/dist/grep.js +5 -3
  82. package/dist/index.js +56 -56
  83. package/dist/install.js +8 -8
  84. package/dist/{json-parser-2EOVP5MR.js → json-parser-34DHYCPW.js} +1 -18
  85. package/dist/json.js +2 -2
  86. package/dist/kanban-tool-types.d.ts +2 -0
  87. package/dist/kanban.js +2 -2
  88. package/dist/languages/index.js +6 -6
  89. package/dist/legacy-bridge-FOV5ODDI.js +18 -0
  90. package/dist/lint.js +8 -8
  91. package/dist/logs.js +2 -2
  92. package/dist/memory.js +1 -1
  93. package/dist/next-steps.d.ts +7 -0
  94. package/dist/next-steps.js +82 -38
  95. package/dist/outdated.js +5 -3
  96. package/dist/pack.js +53 -53
  97. package/dist/{parser-dispatch-3PDBKIQ7.js → parser-dispatch-BDVCJXTV.js} +4 -4
  98. package/dist/patch.js +8 -8
  99. package/dist/plan.js +4 -4
  100. package/dist/process-registry.js +1 -1
  101. package/dist/ps-slash.js +3 -3
  102. package/dist/{py-parser-RFWG2HYI.js → py-parser-QC6MR4GB.js} +3 -3
  103. package/dist/read.js +8 -8
  104. package/dist/replace.js +9 -8
  105. package/dist/scaffold.js +2 -2
  106. package/dist/search.js +1 -1
  107. package/dist/session-kanban.js +1 -1
  108. package/dist/task.js +4 -4
  109. package/dist/test.js +8 -8
  110. package/dist/todo.js +3 -3
  111. package/dist/tool-search.js +1 -1
  112. package/dist/tool-tier.js +54 -54
  113. package/dist/{tree-sitter-parser-FGKHJGSX.js → tree-sitter-parser-GILYQLS4.js} +2 -2
  114. package/dist/tree.js +2 -2
  115. package/dist/typecheck.js +8 -8
  116. package/dist/win32.js +1 -1
  117. package/dist/write.js +9 -9
  118. package/package.json +5 -5
  119. package/dist/legacy-bridge-EC7GM2IW.js +0 -18
@@ -1,16 +1,22 @@
1
+ import {
2
+ getProcessRegistry,
3
+ redactCommand
4
+ } from "./chunk-ESLPBT5A.js";
1
5
  import {
2
6
  buildWin32CmdShimInvocation,
3
7
  resolveWin32Command
4
- } from "./chunk-V43EQ6VR.js";
8
+ } from "./chunk-KE7A76VC.js";
5
9
  import {
6
10
  COMMAND_OUTPUT_MAX_BYTES,
7
11
  detectPackageManager,
8
12
  normalizeCommandOutput,
9
13
  safeResolveReal
10
- } from "./chunk-QLF74MSV.js";
14
+ } from "./chunk-ZUFHAHTE.js";
11
15
 
12
16
  // src/outdated.ts
13
17
  import { spawn } from "node:child_process";
18
+ import { StringDecoder } from "node:string_decoder";
19
+ import { ToolValidationError } from "@wrongstack/core/types";
14
20
  import { buildChildEnv } from "@wrongstack/core/utils";
15
21
  var outdatedTool = {
16
22
  name: "outdated",
@@ -48,12 +54,19 @@ var outdatedTool = {
48
54
  }
49
55
  },
50
56
  async execute(input, ctx, opts) {
57
+ if (input.cwd !== void 0 && (typeof input.cwd !== "string" || !input.cwd.trim())) {
58
+ throw new ToolValidationError({
59
+ message: "outdated: cwd must be a non-empty string when provided.",
60
+ field: "cwd"
61
+ });
62
+ }
51
63
  const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
52
64
  const signal = opts?.signal ?? ctx.signal ?? new AbortController().signal;
65
+ signal.throwIfAborted();
53
66
  const manager = await detectPackageManager(cwd, ctx.projectRoot);
54
67
  if (manager === "npm") {
55
68
  try {
56
- const { detectNonJsEcosystem } = await import("./legacy-bridge-EC7GM2IW.js");
69
+ const { detectNonJsEcosystem } = await import("./legacy-bridge-FOV5ODDI.js");
57
70
  const nonJs = await detectNonJsEcosystem(cwd, ctx.projectRoot);
58
71
  if (nonJs) {
59
72
  const { planLanguageOperation, executePackagePlan } = await import("./languages/index.js");
@@ -107,10 +120,23 @@ var outdatedTool = {
107
120
  }
108
121
  };
109
122
  function runOutdated(manager, args, cwd, signal) {
123
+ if (signal.aborted) {
124
+ return Promise.resolve({
125
+ exit_code: 124,
126
+ packages: [],
127
+ total: 0,
128
+ output: "Aborted",
129
+ truncated: false
130
+ });
131
+ }
110
132
  return new Promise((resolve) => {
111
133
  let stdout = "";
112
134
  let stderr = "";
135
+ let stdoutBytes = 0;
136
+ let stderrBytes = 0;
113
137
  const MAX = 1e5;
138
+ const stdoutDecoder = new StringDecoder("utf8");
139
+ const stderrDecoder = new StringDecoder("utf8");
114
140
  const resolved = resolveWin32Command(manager);
115
141
  const needsShell = process.platform === "win32" && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
116
142
  const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
@@ -124,17 +150,78 @@ function runOutdated(manager, args, cwd, signal) {
124
150
  windowsHide: true,
125
151
  ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
126
152
  });
153
+ const registry = getProcessRegistry();
154
+ if (typeof child.pid === "number") {
155
+ registry.register({
156
+ pid: child.pid,
157
+ name: manager,
158
+ command: redactCommand(`${spawnCmd} ${args.join(" ")}`),
159
+ startedAt: Date.now(),
160
+ child
161
+ });
162
+ }
163
+ const onAbort = () => {
164
+ if (typeof child.pid === "number") {
165
+ registry.kill(child.pid, { force: true });
166
+ } else {
167
+ try {
168
+ child.kill("SIGKILL");
169
+ } catch {
170
+ }
171
+ }
172
+ };
173
+ if (signal) {
174
+ if (signal.aborted) onAbort();
175
+ else signal.addEventListener("abort", onAbort, { once: true });
176
+ }
127
177
  child.stdout?.on("data", (c) => {
128
- if (stdout.length < MAX) stdout += c.toString();
178
+ stdoutBytes += c.byteLength;
179
+ if (stdout.length < MAX) {
180
+ const text = stdoutDecoder.write(c);
181
+ stdout += text.slice(0, MAX - stdout.length);
182
+ }
129
183
  });
130
184
  child.stderr?.on("data", (c) => {
131
- if (stderr.length < MAX) stderr += c.toString();
185
+ stderrBytes += c.byteLength;
186
+ if (stderr.length < MAX) {
187
+ const text = stderrDecoder.write(c);
188
+ stderr += text.slice(0, MAX - stderr.length);
189
+ }
132
190
  });
133
191
  child.on("close", (code) => {
134
- const result = parseOutdatedOutput(stdout, code ?? 0);
192
+ if (signal) signal.removeEventListener("abort", onAbort);
193
+ if (typeof child.pid === "number") registry.unregister(child.pid);
194
+ if (signal.aborted) {
195
+ resolve({
196
+ exit_code: 124,
197
+ packages: [],
198
+ total: 0,
199
+ output: "Aborted",
200
+ truncated: false
201
+ });
202
+ return;
203
+ }
204
+ const restOut = stdoutDecoder.end();
205
+ if (restOut && stdout.length < MAX) {
206
+ stdout += restOut.slice(0, MAX - stdout.length);
207
+ }
208
+ const isTruncated = stdoutBytes > MAX || stderrBytes > MAX || Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES;
209
+ const result = parseOutdatedOutput(stdout, code ?? 0, stderr, isTruncated);
135
210
  resolve(result);
136
211
  });
137
212
  child.on("error", (e) => {
213
+ if (signal) signal.removeEventListener("abort", onAbort);
214
+ if (typeof child.pid === "number") registry.unregister(child.pid);
215
+ if (signal.aborted || e.name === "AbortError") {
216
+ resolve({
217
+ exit_code: 124,
218
+ packages: [],
219
+ total: 0,
220
+ output: "Aborted",
221
+ truncated: false
222
+ });
223
+ return;
224
+ }
138
225
  resolve({
139
226
  exit_code: 1,
140
227
  packages: [],
@@ -145,18 +232,18 @@ function runOutdated(manager, args, cwd, signal) {
145
232
  });
146
233
  });
147
234
  }
148
- function parseOutdatedOutput(json, exitCode) {
235
+ function parseOutdatedOutput(json, exitCode, stderr = "", truncated = false) {
149
236
  const packages = [];
150
237
  if (!json) {
151
238
  return {
152
239
  exit_code: exitCode,
153
240
  packages: [],
154
241
  total: 0,
155
- output: exitCode === 0 ? "All packages up to date" : "Could not check outdated packages",
156
- truncated: false
242
+ output: stderr || (exitCode === 0 ? "All packages up to date" : "Could not check outdated packages"),
243
+ truncated
157
244
  };
158
245
  }
159
- const truncated = json.length >= 1e5 || Buffer.byteLength(json, "utf8") > COMMAND_OUTPUT_MAX_BYTES;
246
+ const isTruncated = truncated || json.length >= 1e5 || Buffer.byteLength(json, "utf8") > COMMAND_OUTPUT_MAX_BYTES;
160
247
  let parsedOk = false;
161
248
  try {
162
249
  const data = JSON.parse(json);
@@ -188,11 +275,11 @@ Note: exit code 1 from \`outdated\` means outdated packages were found (expected
188
275
  packages,
189
276
  total: packages.length,
190
277
  output,
191
- truncated
278
+ truncated: isTruncated
192
279
  };
193
280
  }
194
281
 
195
282
  export {
196
283
  outdatedTool
197
284
  };
198
- //# sourceMappingURL=chunk-Z4VBTVUM.js.map
285
+ //# sourceMappingURL=chunk-DFQDJT22.js.map
@@ -14,6 +14,8 @@ import {
14
14
  setActiveKit,
15
15
  setDesignOverrides
16
16
  } from "@wrongstack/core/design";
17
+ import { ToolValidationError } from "@wrongstack/core/types";
18
+ import { atomicWrite } from "@wrongstack/core/utils";
17
19
  async function resolveReal(p) {
18
20
  const resolved = path.resolve(p);
19
21
  let probe = resolved;
@@ -102,10 +104,33 @@ var designTool = {
102
104
  },
103
105
  required: []
104
106
  },
105
- async execute(input, ctx) {
107
+ async execute(input, ctx, _opts) {
108
+ const signal = _opts?.signal ?? ctx?.signal;
109
+ signal?.throwIfAborted();
110
+ const VALID_ACTIONS = /* @__PURE__ */ new Set([
111
+ "list",
112
+ "use",
113
+ "foundations",
114
+ "set",
115
+ "tune",
116
+ "materialize",
117
+ "verify"
118
+ ]);
119
+ if (input.action !== void 0 && !VALID_ACTIONS.has(input.action)) {
120
+ throw new ToolValidationError({
121
+ message: `design: unknown action "${input.action}". Allowed actions: ${[...VALID_ACTIONS].join(", ")}`,
122
+ field: "action"
123
+ });
124
+ }
125
+ if (input.stack !== void 0 && !isDesignStack(input.stack)) {
126
+ throw new ToolValidationError({
127
+ message: `design: invalid stack "${input.stack}". Allowed stacks: web, react-native, flutter, swiftui, compose`,
128
+ field: "stack"
129
+ });
130
+ }
106
131
  const loader = getDesignKitLoader(ctx.projectRoot);
107
132
  const action = input.action ?? "list";
108
- const stack = input.stack && isDesignStack(input.stack) ? input.stack : void 0;
133
+ const stack = input.stack;
109
134
  if (action === "foundations") {
110
135
  const text = await loader.foundationsText(stack);
111
136
  return { action, stack, output: text || "No foundations document is installed." };
@@ -231,7 +256,10 @@ Run \`design {action:"materialize"}\` to write the tuned tokens to your theme fi
231
256
  const abs = path.join(absParent, path.basename(absResolved));
232
257
  const rel = path.relative(root, abs);
233
258
  if (rel.startsWith("..") || path.isAbsolute(rel)) {
234
- throw new Error(`design: materialize path "${result.path}" would escape the project root`);
259
+ throw new ToolValidationError({
260
+ message: `design: materialize path "${result.path}" would escape the project root`,
261
+ field: "out"
262
+ });
235
263
  }
236
264
  let exists = false;
237
265
  try {
@@ -253,7 +281,7 @@ ${result.content}
253
281
  };
254
282
  }
255
283
  await fs.mkdir(path.dirname(abs), { recursive: true });
256
- await fs.writeFile(abs, result.content);
284
+ await atomicWrite(abs, result.content);
257
285
  return {
258
286
  action,
259
287
  kit: active.kit,
@@ -263,6 +291,7 @@ ${result.content}
263
291
  };
264
292
  }
265
293
  if (action === "verify") {
294
+ signal?.throwIfAborted();
266
295
  const active = await loadActiveKit(ctx.projectRoot);
267
296
  if (!active) {
268
297
  return {
@@ -270,12 +299,15 @@ ${result.content}
270
299
  output: 'No active kit to verify against. Pick one: `design {action:"use", kit:"<id>"}`.'
271
300
  };
272
301
  }
302
+ signal?.throwIfAborted();
273
303
  const rawTokens = await loader.readTokens(active.kit);
274
304
  if (!rawTokens) {
275
305
  return { action, kit: active.kit, output: `Kit "${active.kit}" has no tokens.json.` };
276
306
  }
307
+ signal?.throwIfAborted();
277
308
  const tokens = applyTokenOverrides(rawTokens, active.overrides);
278
- const report = await runDesignVerify(ctx.projectRoot, tokens, input.files);
309
+ const normalizedFiles = input.files ? (Array.isArray(input.files) ? input.files : String(input.files).split(",")).map((f) => f.trim().replace(/\\/g, "/")).filter(Boolean) : void 0;
310
+ const report = await runDesignVerify(ctx.projectRoot, tokens, normalizedFiles);
279
311
  const pct = Math.round(report.score * 100);
280
312
  const top = report.violations.slice(0, 25).map((v) => ` ${v.file}:${v.line} \u2014 ${v.reason}: ${v.snippet}`).join("\n");
281
313
  const summary = `Adherence: ${pct}% on-palette across ${report.filesScanned} file(s). ${report.violations.length} violation(s).` + (report.violations.length ? `
@@ -302,4 +334,4 @@ Replace off-palette colors with kit tokens (or the materialized CSS vars / token
302
334
  export {
303
335
  designTool
304
336
  };
305
- //# sourceMappingURL=chunk-6RNIFLG4.js.map
337
+ //# sourceMappingURL=chunk-ELRRTWNP.js.map
@@ -30,6 +30,7 @@ var SENSITIVE_FLAG_PATTERNS = [
30
30
  /--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
31
31
  ];
32
32
  function redactCommand(cmd) {
33
+ if (typeof cmd !== "string") return "";
33
34
  let result = cmd;
34
35
  for (const pattern of SENSITIVE_FLAG_PATTERNS) {
35
36
  result = result.replace(pattern, (match) => {
@@ -469,4 +470,4 @@ export {
469
470
  getProcessRegistry,
470
471
  _resetProcessRegistry
471
472
  };
472
- //# sourceMappingURL=chunk-3PVYQFRQ.js.map
473
+ //# sourceMappingURL=chunk-ESLPBT5A.js.map
@@ -3,11 +3,13 @@ import {
3
3
  } from "./chunk-Q5JPPCZC.js";
4
4
  import {
5
5
  safeResolveReal
6
- } from "./chunk-QLF74MSV.js";
6
+ } from "./chunk-ZUFHAHTE.js";
7
7
 
8
8
  // src/logs.ts
9
9
  import { spawn } from "node:child_process";
10
+ import { StringDecoder } from "node:string_decoder";
10
11
  import { buildChildEnv } from "@wrongstack/core/utils";
12
+ import { ToolValidationError } from "@wrongstack/core/types";
11
13
  var logsTool = {
12
14
  name: "logs",
13
15
  category: "Logs",
@@ -51,12 +53,32 @@ var logsTool = {
51
53
  async execute(input, ctx, opts) {
52
54
  const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
53
55
  const signal = opts?.signal ?? ctx.signal ?? new AbortController().signal;
54
- const lines = input.lines ?? 100;
56
+ if (signal.aborted) {
57
+ return {
58
+ source: "none",
59
+ entries: [],
60
+ total: 0,
61
+ truncated: false,
62
+ stream_mode: false
63
+ };
64
+ }
65
+ const VALID_SINCE = /* @__PURE__ */ new Set(["1h", "6h", "24h", "all"]);
66
+ if (input.since !== void 0 && !VALID_SINCE.has(input.since)) {
67
+ throw new ToolValidationError({
68
+ message: `logs: invalid since option "${input.since}". Allowed: 1h, 6h, 24h, all`,
69
+ field: "since"
70
+ });
71
+ }
72
+ const rawLines = typeof input.lines === "number" && !Number.isNaN(input.lines) ? input.lines : 100;
73
+ const lines = Math.max(0, Math.floor(rawLines));
55
74
  let filterRe = null;
56
75
  if (input.filter) {
57
76
  const compiled = compileUserRegex(input.filter, "i");
58
77
  if (!compiled.ok) {
59
- throw new Error(`logs: ${compiled.reason}`);
78
+ throw new ToolValidationError({
79
+ message: `logs: ${compiled.reason}`,
80
+ field: "filter"
81
+ });
60
82
  }
61
83
  filterRe = compiled.regex;
62
84
  }
@@ -92,10 +114,21 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
92
114
  };
93
115
  }
94
116
  args.push("--timestamps", service);
117
+ if (signal.aborted) {
118
+ return Promise.resolve({
119
+ source: `docker:${service}`,
120
+ entries: [],
121
+ total: 0,
122
+ truncated: false,
123
+ stream_mode: false
124
+ });
125
+ }
95
126
  return new Promise((resolve) => {
96
127
  let stdout = "";
97
128
  let stderr = "";
98
129
  const MAX = 2e5;
130
+ const stdoutDecoder = new StringDecoder("utf8");
131
+ const stderrDecoder = new StringDecoder("utf8");
99
132
  let settled = false;
100
133
  const empty = () => ({
101
134
  source: `docker:${service}`,
@@ -122,10 +155,16 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
122
155
  finish(empty());
123
156
  }, DOCKER_LOGS_TIMEOUT_MS);
124
157
  child.stdout?.on("data", (c) => {
125
- if (stdout.length < MAX) stdout += c.toString();
158
+ if (stdout.length < MAX) {
159
+ const text = stdoutDecoder.write(c);
160
+ stdout += text.slice(0, MAX - stdout.length);
161
+ }
126
162
  });
127
163
  child.stderr?.on("data", (c) => {
128
- if (stderr.length < MAX) stderr += c.toString();
164
+ if (stderr.length < MAX) {
165
+ const text = stderrDecoder.write(c);
166
+ stderr += text.slice(0, MAX - stderr.length);
167
+ }
129
168
  });
130
169
  child.stdout?.on("error", (e) => {
131
170
  console.log(
@@ -229,4 +268,4 @@ function parseLine(line) {
229
268
  export {
230
269
  logsTool
231
270
  };
232
- //# sourceMappingURL=chunk-YZHK4TLF.js.map
271
+ //# sourceMappingURL=chunk-FQNXYA6W.js.map
@@ -1,15 +1,18 @@
1
1
  import {
2
2
  compileUserRegex
3
3
  } from "./chunk-Q5JPPCZC.js";
4
+ import {
5
+ mapWithConcurrency
6
+ } from "./chunk-ZBMANLYH.js";
4
7
  import {
5
8
  enqueueReindex
6
- } from "./chunk-MRKWZCHZ.js";
9
+ } from "./chunk-Z5Q3TXRY.js";
7
10
  import {
8
11
  isBinaryBuffer,
9
12
  safeResolveReal,
10
13
  sha256hex,
11
14
  truncateDiffPayload
12
- } from "./chunk-QLF74MSV.js";
15
+ } from "./chunk-ZUFHAHTE.js";
13
16
 
14
17
  // src/replace.ts
15
18
  import { spawn } from "node:child_process";
@@ -27,7 +30,7 @@ import {
27
30
  unifiedDiff
28
31
  } from "@wrongstack/core/utils";
29
32
  var MAX_DIFF_BYTES = 262144;
30
- var DEFAULT_IGNORE = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
33
+ var DEFAULT_IGNORE = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage"]);
31
34
  var replaceTool = {
32
35
  name: "replace",
33
36
  category: "Transform",
@@ -96,54 +99,51 @@ var replaceTool = {
96
99
  const re = compiled.regex;
97
100
  const globRe = input.glob ? compileGlob(input.glob) : null;
98
101
  const dryRun = input.dry_run ?? true;
99
- const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
100
- const fileList = await resolveFiles(filesInput, ctx, globRe);
102
+ const fileList = await resolveFiles(input.files, ctx, globRe);
101
103
  const realRoot = await fs.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
102
- const results = [];
103
- let totalReplacements = 0;
104
- let diffBytesUsed = 0;
105
- let diffsOmitted = 0;
106
- let diffsTruncated = 0;
107
- for (const absPath of fileList) {
104
+ const fileResults = await mapWithConcurrency(fileList, 16, async (absPath) => {
108
105
  signal?.throwIfAborted();
109
106
  const lstat2 = await fs.lstat(absPath).catch((err) => {
110
107
  if (err.code === "ENOENT") return null;
111
108
  throw err;
112
109
  });
113
- if (!lstat2?.isFile()) continue;
114
- if (lstat2.isSymbolicLink()) continue;
110
+ if (!lstat2?.isFile()) return null;
111
+ if (lstat2.isSymbolicLink()) return null;
115
112
  let realPath;
116
113
  try {
117
114
  realPath = await fs.realpath(absPath);
118
115
  } catch {
119
- continue;
116
+ return null;
120
117
  }
121
118
  const rel = path.relative(realRoot, realPath);
122
- if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
119
+ if (rel.startsWith("..") || path.isAbsolute(rel)) return null;
123
120
  const stat2 = await fs.stat(realPath).catch(() => null);
124
- if (!stat2?.isFile()) continue;
121
+ if (!stat2?.isFile()) return null;
125
122
  let content;
126
123
  try {
127
124
  const buf = await fs.readFile(realPath);
128
- if (isBinaryBuffer(buf)) continue;
125
+ if (isBinaryBuffer(buf)) return null;
129
126
  content = buf.toString("utf8");
130
127
  } catch {
131
- continue;
128
+ return null;
132
129
  }
133
130
  const style = detectNewlineStyle(content);
134
131
  const contentLf = normalizeToLf(content);
135
132
  re.lastIndex = 0;
136
133
  const allMatches = [...contentLf.matchAll(re)];
137
- if (allMatches.length === 0) continue;
134
+ if (allMatches.length === 0) return null;
138
135
  const matches = replaceAll ? allMatches : allMatches.slice(0, 1);
139
136
  const count = matches.length;
140
- let newContentLf = contentLf;
141
- for (let i = matches.length - 1; i >= 0; i--) {
137
+ let newContentLf = "";
138
+ let lastIdx = 0;
139
+ for (let i = 0; i < matches.length; i++) {
142
140
  const m = expectDefined(matches[i]);
143
- newContentLf = newContentLf.slice(0, m.index) + expandReplacement(input.replacement, m) + newContentLf.slice(expectDefined(m.index) + m[0].length);
141
+ const matchIdx = expectDefined(m.index);
142
+ newContentLf += contentLf.slice(lastIdx, matchIdx) + expandReplacement(input.replacement, m);
143
+ lastIdx = matchIdx + m[0].length;
144
144
  }
145
+ newContentLf += contentLf.slice(lastIdx);
145
146
  re.lastIndex = 0;
146
- totalReplacements += count;
147
147
  if (!dryRun) {
148
148
  const newContent = toStyle(newContentLf, style);
149
149
  await atomicWrite(realPath, newContent, { mode: stat2.mode & 511 });
@@ -158,10 +158,27 @@ var replaceTool = {
158
158
  after: newContent
159
159
  });
160
160
  }
161
- let diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
161
+ const isIdentical = newContentLf === contentLf;
162
+ const rawDiff = dryRun || matches.length > 0 ? isIdentical ? "(no-op: replacement produced identical content)" : unifiedDiff(content, toStyle(newContentLf, style), {
162
163
  fromFile: absPath,
163
164
  toFile: absPath
164
165
  }) : void 0;
166
+ return {
167
+ path: absPath,
168
+ replacements: count,
169
+ rawDiff,
170
+ isIdentical
171
+ };
172
+ });
173
+ const results = [];
174
+ let totalReplacements = 0;
175
+ let diffBytesUsed = 0;
176
+ let diffsOmitted = 0;
177
+ let diffsTruncated = 0;
178
+ for (const item of fileResults) {
179
+ if (!item) continue;
180
+ totalReplacements += item.replacements;
181
+ let diff = item.rawDiff;
165
182
  if (diff !== void 0) {
166
183
  const remaining = MAX_DIFF_BYTES - diffBytesUsed;
167
184
  if (remaining <= 0) {
@@ -175,8 +192,8 @@ var replaceTool = {
175
192
  }
176
193
  }
177
194
  results.push({
178
- path: absPath,
179
- replacements: matches.length,
195
+ path: item.path,
196
+ replacements: item.replacements,
180
197
  diff
181
198
  });
182
199
  }
@@ -189,13 +206,25 @@ var replaceTool = {
189
206
  } catch {
190
207
  }
191
208
  }
209
+ const hasIdentical = fileResults.some((r) => r?.isIdentical);
192
210
  const overBudget = diffsOmitted > 0 || diffsTruncated > 0;
211
+ const notes = [];
212
+ if (overBudget) {
213
+ notes.push(
214
+ `Diff payload exceeded the 256 KiB output budget: ${diffsTruncated} diff(s) truncated, ${diffsOmitted} diff(s) omitted. Replacement counts are complete; use the read tool to inspect individual files.`
215
+ );
216
+ }
217
+ if (hasIdentical && !overBudget) {
218
+ notes.push(
219
+ "Some replacements produced content identical to existing files (see no-op diff)."
220
+ );
221
+ }
193
222
  return {
194
223
  files_modified: results.length,
195
224
  total_replacements: totalReplacements,
196
225
  results,
197
226
  dry_run: dryRun,
198
- note: overBudget ? `Diff payload exceeded the 256 KiB output budget: ${diffsTruncated} diff(s) truncated, ${diffsOmitted} diff(s) omitted. Replacement counts are complete; use the read tool to inspect individual files.` : void 0
227
+ note: notes.length > 0 ? notes.join("\n") : void 0
199
228
  };
200
229
  }
201
230
  };
@@ -246,7 +275,7 @@ function passesExtraGlob(extraGlob, name, full, base) {
246
275
  }
247
276
  async function resolveFiles(filesInput, ctx, extraGlob) {
248
277
  const base = ctx.cwd;
249
- const parts = filesInput.trim().split(",").map((s) => s.trim()).filter(Boolean);
278
+ const parts = Array.isArray(filesInput) ? filesInput.map((s) => s.trim()).filter(Boolean) : filesInput.trim().split(",").map((s) => s.trim()).filter(Boolean);
250
279
  const resolved = [];
251
280
  for (const p of parts) {
252
281
  if (p.includes("*") || p.includes("?")) {
@@ -326,8 +355,14 @@ function spawnRgFind(pattern, base) {
326
355
  return {
327
356
  promise: new Promise((resolve, reject) => {
328
357
  child.on("error", reject);
329
- child.on("close", () => {
330
- resolve(buf.split("\n").filter(Boolean));
358
+ child.on("close", (code) => {
359
+ if (code !== 0 && code !== null) {
360
+ reject(new Error(`rg exited with code ${code}`));
361
+ return;
362
+ }
363
+ resolve(
364
+ buf.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
365
+ );
331
366
  });
332
367
  })
333
368
  };
@@ -342,8 +377,9 @@ async function globNative(pattern, base, extraGlob) {
342
377
  } catch {
343
378
  return;
344
379
  }
380
+ const subdirs = [];
345
381
  for (const e of entries) {
346
- if (DEFAULT_IGNORE.includes(e.name)) continue;
382
+ if (DEFAULT_IGNORE.has(e.name)) continue;
347
383
  const full = path.join(dir, e.name);
348
384
  try {
349
385
  const stat2 = await fs.lstat(full);
@@ -352,7 +388,7 @@ async function globNative(pattern, base, extraGlob) {
352
388
  continue;
353
389
  }
354
390
  if (e.isDirectory()) {
355
- await walk(full);
391
+ subdirs.push(full);
356
392
  } else if (e.isFile()) {
357
393
  const name = e.name;
358
394
  const rel = path.relative(base, full).split(path.sep).join("/");
@@ -365,6 +401,9 @@ async function globNative(pattern, base, extraGlob) {
365
401
  globRe.lastIndex = 0;
366
402
  }
367
403
  }
404
+ if (subdirs.length > 0) {
405
+ await mapWithConcurrency(subdirs, 16, (subdir) => walk(subdir));
406
+ }
368
407
  };
369
408
  await walk(base);
370
409
  return results;
@@ -374,4 +413,4 @@ export {
374
413
  replaceTool,
375
414
  __resetRgDetectionForTests
376
415
  };
377
- //# sourceMappingURL=chunk-OYVPBPCL.js.map
416
+ //# sourceMappingURL=chunk-HR67OWEP.js.map
@@ -2,7 +2,7 @@ import {
2
2
  executeLanguagePlan,
3
3
  executePackagePlan,
4
4
  planLanguageOperation
5
- } from "./chunk-UC3RKRIV.js";
5
+ } from "./chunk-NDIT3SBZ.js";
6
6
 
7
7
  // src/languages/legacy-bridge.ts
8
8
  import * as fs from "node:fs/promises";
@@ -98,4 +98,4 @@ export {
98
98
  tryLegacyCodeOperation,
99
99
  tryLegacyPackageOperation
100
100
  };
101
- //# sourceMappingURL=chunk-ZDUGI5BC.js.map
101
+ //# sourceMappingURL=chunk-I7FBZOEP.js.map