@wrongstack/tools 0.257.0 → 0.257.2

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/pack.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn, execFileSync, spawnSync } from 'node:child_process';
2
2
  import * as Core from '@wrongstack/core';
3
- import { buildChildEnv, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, expectDefined, recordPackageAction, detectPackageEcosystem, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, loadTasks, emptyTaskFile, saveTasks, formatTaskList, formatPlan, mutateTasks, loadPlan, emptyPlan, savePlan, computeTaskItemProgress, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
3
+ import { buildChildEnv, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, isPrivateIPv4, isPrivateIPv6, compileGlob, expectDefined, recordPackageAction, detectPackageEcosystem, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, loadTasks, emptyTaskFile, saveTasks, formatTaskList, formatPlan, mutateTasks, loadPlan, emptyPlan, savePlan, computeTaskItemProgress, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
4
4
  import * as fs from 'node:fs';
5
5
  import { statSync, mkdirSync, createWriteStream, writeFileSync } from 'node:fs';
6
6
  import * as fs14 from 'node:fs/promises';
@@ -14,6 +14,7 @@ import * as ts from 'typescript';
14
14
  import * as dns from 'node:dns/promises';
15
15
  import * as net from 'node:net';
16
16
  import { Agent } from 'undici';
17
+ import TurndownService from 'turndown';
17
18
  import { randomUUID } from 'node:crypto';
18
19
 
19
20
  // src/_spawn-stream.ts
@@ -277,7 +278,7 @@ var SENSITIVE_FLAG_PATTERNS = [
277
278
  // --flag=value or --flag "value" (value captured up to next space or comma)
278
279
  /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
279
280
  // -f "value" style short flags
280
- /(?<!\w)-t(?:\s+|\s*=\s*)[^\s,]+/g,
281
+ /(?<!\w)-t(?:\s+|\s*=\s*)[^\s,]+/,
281
282
  /(?<!\w)-p(?:ssword)?(?:\s+|\s*=\s*)[^\s,]+/gi,
282
283
  // env var–style secrets: TOKEN=x, API_KEY=y, etc.
283
284
  /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
@@ -413,8 +414,8 @@ var ProcessRegistryImpl = class {
413
414
  if (p.killed) return true;
414
415
  if (p.protected) return false;
415
416
  const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
416
- const isWin = os.platform() === "win32";
417
- if (isWin) {
417
+ const isWin3 = os.platform() === "win32";
418
+ if (isWin3) {
418
419
  const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
419
420
  if (liveRealChild && killWin32Tree(pid)) {
420
421
  const fallback = setTimeout(() => {
@@ -502,7 +503,7 @@ function getProcessRegistry() {
502
503
  }
503
504
  function resolveWin32Command(cmd) {
504
505
  if (process.platform !== "win32") return cmd;
505
- if (cmd.includes("/") || cmd.includes("\\") || path3.extname(cmd)) {
506
+ if (cmd.includes("/") || cmd.includes("\\") || path3.extname(cmd.replace(/\//g, "\\"))) {
506
507
  return cmd;
507
508
  }
508
509
  const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
@@ -522,6 +523,7 @@ function resolveWin32Command(cmd) {
522
523
  }
523
524
 
524
525
  // src/_spawn-stream.ts
526
+ var isWin = process.platform === "win32";
525
527
  async function* spawnStream(opts) {
526
528
  const max = opts.maxBytes ?? 2e5;
527
529
  const flushAt = opts.flushBytes ?? 4 * 1024;
@@ -532,7 +534,6 @@ async function* spawnStream(opts) {
532
534
  let error;
533
535
  const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
534
536
  const cmd = resolveWin32Command(opts.cmd);
535
- const isWin = process.platform === "win32";
536
537
  const needsShell = isWin && (cmd.endsWith(".cmd") || cmd.endsWith(".bat"));
537
538
  const child = spawn(cmd, opts.args, {
538
539
  cwd: opts.cwd,
@@ -909,7 +910,7 @@ function parseAuditOutput(json, exitCode) {
909
910
  total,
910
911
  summary,
911
912
  output: json,
912
- truncated: json.length >= 1e5
913
+ truncated: json.length > 1e5
913
914
  };
914
915
  } catch {
915
916
  return {
@@ -1002,11 +1003,11 @@ var bashTool = {
1002
1003
  }));
1003
1004
  }
1004
1005
  const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
1005
- const isWin = os.platform() === "win32";
1006
+ const isWin3 = os.platform() === "win32";
1006
1007
  const shell = (() => {
1007
- const explicit = process.env[isWin ? "WRONGSTACK_COMSPEC" : "WRONGSTACK_SHELL"];
1008
+ const explicit = process.env[isWin3 ? "WRONGSTACK_COMSPEC" : "WRONGSTACK_SHELL"];
1008
1009
  if (explicit) return explicit;
1009
- if (isWin) return process.env["COMSPEC"] ?? "cmd.exe";
1010
+ if (isWin3) return process.env["COMSPEC"] ?? "cmd.exe";
1010
1011
  const fromEnv = process.env["SHELL"];
1011
1012
  if (fromEnv) {
1012
1013
  const name = fromEnv.split("/").pop() ?? "";
@@ -1014,9 +1015,9 @@ var bashTool = {
1014
1015
  }
1015
1016
  return "/bin/bash";
1016
1017
  })();
1017
- const args = isWin ? ["/c", input.command] : ["-c", input.command];
1018
+ const args = isWin3 ? ["/c", input.command] : ["-c", input.command];
1018
1019
  const env = buildChildEnv(ctx.session?.id);
1019
- const detached = !isWin;
1020
+ const detached = !isWin3;
1020
1021
  const startedAt = Date.now();
1021
1022
  if (input.background) {
1022
1023
  let buf2 = "";
@@ -1032,7 +1033,7 @@ var bashTool = {
1032
1033
  // apply: the child gets a hidden console that grandchildren inherit.
1033
1034
  // Windows children survive parent exit either way. POSIX keeps
1034
1035
  // detached for the process-group kill semantics.
1035
- detached: !isWin,
1036
+ detached: !isWin3,
1036
1037
  windowsHide: true,
1037
1038
  signal: opts.signal
1038
1039
  });
@@ -1062,8 +1063,6 @@ var bashTool = {
1062
1063
  };
1063
1064
  child2.stdout?.on("data", onBgData);
1064
1065
  child2.stderr?.on("data", onBgData);
1065
- child2.stdout?.unref?.();
1066
- child2.stderr?.unref?.();
1067
1066
  child2.on("close", () => {
1068
1067
  registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
1069
1068
  });
@@ -1085,7 +1084,7 @@ var bashTool = {
1085
1084
  stdio: ["ignore", "pipe", "pipe"],
1086
1085
  detached,
1087
1086
  windowsHide: true,
1088
- ...isWin ? {} : { signal: opts.signal }
1087
+ ...isWin3 ? {} : { signal: opts.signal }
1089
1088
  });
1090
1089
  const pid = child.pid;
1091
1090
  if (typeof pid === "number") {
@@ -1104,7 +1103,7 @@ var bashTool = {
1104
1103
  const timers = [];
1105
1104
  const spool = createOutputSpool({ tool: "bash", thresholdBytes: MAX_OUTPUT });
1106
1105
  function killWithTimeout(child2, timeoutMs2) {
1107
- if (isWin) {
1106
+ if (isWin3) {
1108
1107
  if (typeof child2.pid === "number" && child2.exitCode === null && killWin32Tree(child2.pid)) {
1109
1108
  const fallback = setTimeout(() => {
1110
1109
  if (child2.exitCode === null) {
@@ -1160,7 +1159,7 @@ var bashTool = {
1160
1159
  timers.push(timer);
1161
1160
  timer.unref?.();
1162
1161
  const onAbort = () => killWithTimeout(child, 2e3);
1163
- if (isWin) {
1162
+ if (isWin3) {
1164
1163
  if (opts.signal.aborted) onAbort();
1165
1164
  else opts.signal.addEventListener("abort", onAbort, { once: true });
1166
1165
  }
@@ -1256,7 +1255,7 @@ var bashTool = {
1256
1255
  } finally {
1257
1256
  for (const t of timers) clearTimeout(t);
1258
1257
  spool.finalize();
1259
- if (isWin) opts.signal.removeEventListener("abort", onAbort);
1258
+ if (isWin3) opts.signal.removeEventListener("abort", onAbort);
1260
1259
  child.stdout?.off("data", onData);
1261
1260
  child.stderr?.off("data", onData);
1262
1261
  child.stdout?.destroy();
@@ -1326,7 +1325,7 @@ var batchToolUseTool = {
1326
1325
  failed = allResults.filter((r) => !r.success).length;
1327
1326
  } else {
1328
1327
  for (const call of input.calls) {
1329
- const result = await executeSingle(call, ctx, opts);
1328
+ const result = await executeSingle(call, ctx, opts ?? { signal: void 0 });
1330
1329
  results.push(result);
1331
1330
  if (result.success) {
1332
1331
  succeeded++;
@@ -4272,10 +4271,6 @@ var documentTool = {
4272
4271
  enum: ["jsdoc", "tsdoc", "block"],
4273
4272
  description: "Documentation style (default: jsdoc)"
4274
4273
  },
4275
- overwrite: {
4276
- type: "boolean",
4277
- description: "Overwrite existing docstrings (default: false)"
4278
- },
4279
4274
  cwd: { type: "string", description: "Working directory (default: cwd)" }
4280
4275
  }
4281
4276
  },
@@ -4333,10 +4328,10 @@ async function resolveFiles(filesInput, cwd) {
4333
4328
  }
4334
4329
  function processFile(content, absPath, _style, _overwrite, target) {
4335
4330
  const results = [];
4336
- const functionRegex = /(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g;
4337
- const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/g;
4338
- const classRegex = /class\s+(\w+)/g;
4339
- const typeRegex = /(?:type|interface)\s+(\w+)\s*[=<]/g;
4331
+ const functionRegex = /(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/;
4332
+ const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/;
4333
+ const classRegex = /class\s+(\w+)/;
4334
+ const typeRegex = /(?:type|interface)\s+(\w+)\s*[=<]/;
4340
4335
  const allMatches = [];
4341
4336
  if (target === "all" || target === "function") {
4342
4337
  for (const m of content.matchAll(functionRegex)) {
@@ -4510,6 +4505,7 @@ function findSimilarity(haystack, needle) {
4510
4505
  }
4511
4506
  return line;
4512
4507
  }
4508
+ var isWin2 = process.platform === "win32";
4513
4509
  var ALLOWED_COMMANDS = {
4514
4510
  node: ["--version", "-r", "--input-type=module"],
4515
4511
  npm: ["--version", "list", "pkg", "doctor", "view", "outdated", "audit"],
@@ -4718,14 +4714,13 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
4718
4714
  const startedAt = Date.now();
4719
4715
  const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
4720
4716
  const resolved = resolveWin32Command(cmd);
4721
- const isWin = process.platform === "win32";
4722
- const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
4717
+ const needsShell = isWin2 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
4723
4718
  const child = spawn(resolved, args, {
4724
4719
  cwd,
4725
4720
  env: buildChildEnv(sessionId),
4726
4721
  stdio: ["ignore", "pipe", "pipe"],
4727
4722
  windowsHide: true,
4728
- ...isWin ? {} : { signal },
4723
+ ...isWin2 ? {} : { signal },
4729
4724
  ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
4730
4725
  });
4731
4726
  const registry = getProcessRegistry();
@@ -4744,7 +4739,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
4744
4739
  if (typeof pid === "number") registry.kill(pid, { force: true });
4745
4740
  else child.kill("SIGTERM");
4746
4741
  };
4747
- if (isWin) {
4742
+ if (isWin2) {
4748
4743
  if (signal.aborted) onAbort();
4749
4744
  else signal.addEventListener("abort", onAbort, { once: true });
4750
4745
  }
@@ -4760,7 +4755,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
4760
4755
  });
4761
4756
  child.on("close", (code) => {
4762
4757
  clearTimeout(timer);
4763
- if (isWin) signal.removeEventListener("abort", onAbort);
4758
+ if (isWin2) signal.removeEventListener("abort", onAbort);
4764
4759
  if (typeof pid === "number") registry.unregister(pid);
4765
4760
  const durationMs = Date.now() - startedAt;
4766
4761
  const exitCode = killed ? 124 : code ?? 1;
@@ -4778,7 +4773,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
4778
4773
  });
4779
4774
  child.on("error", (err) => {
4780
4775
  clearTimeout(timer);
4781
- if (isWin) signal.removeEventListener("abort", onAbort);
4776
+ if (isWin2) signal.removeEventListener("abort", onAbort);
4782
4777
  if (typeof pid === "number") registry.unregister(pid);
4783
4778
  registry.afterCall(Date.now() - startedAt, true);
4784
4779
  spool.finalize();
@@ -4794,6 +4789,16 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
4794
4789
  });
4795
4790
  });
4796
4791
  }
4792
+ var TD = new TurndownService({
4793
+ // Use `# Title` for headings, not setext underline style (`Title\n=====`).
4794
+ headingStyle: "atx",
4795
+ // Don't wrap code blocks in <pre> — render them as triple-backtick blocks.
4796
+ codeBlockStyle: "fenced"
4797
+ });
4798
+ TD.addRule("stripDangerousElements", {
4799
+ filter: ["script", "style", "noscript"],
4800
+ replacement: () => ""
4801
+ });
4797
4802
  var MAX_BYTES = 131072;
4798
4803
  var TIMEOUT_MS = 2e4;
4799
4804
  var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
@@ -4828,7 +4833,7 @@ function guardedLookup(hostname, options, callback) {
4828
4833
  );
4829
4834
  return;
4830
4835
  }
4831
- const first = list[0];
4836
+ const first = list.at(0);
4832
4837
  if (!first) {
4833
4838
  callback(
4834
4839
  Object.assign(new Error(`fetch: no address for ${hostname}`), { code: "ENOTFOUND" })
@@ -4968,7 +4973,7 @@ var fetchTool = {
4968
4973
  pendingBytes += value.byteLength;
4969
4974
  chunks.push(value);
4970
4975
  if (pendingBytes >= FLUSH_AT) {
4971
- const recent = Buffer.from(value).toString("utf8");
4976
+ const recent = Buffer.from(value).toString("utf-8");
4972
4977
  yield {
4973
4978
  type: "partial_output",
4974
4979
  text: recent,
@@ -4983,7 +4988,7 @@ var fetchTool = {
4983
4988
  const format = input.format ?? (ct.includes("text/html") ? "markdown" : "text");
4984
4989
  let content;
4985
4990
  if (format === "raw") content = text;
4986
- else if (format === "markdown" && ct.includes("text/html")) content = htmlToMarkdown(text);
4991
+ else if (format === "markdown" && ct.includes("text/html")) content = TD.turndown(text);
4987
4992
  else if (ct.includes("application/json")) content = prettyJson(text);
4988
4993
  else content = text;
4989
4994
  yield {
@@ -5029,67 +5034,6 @@ async function assertNotPrivate(hostname) {
5029
5034
  }
5030
5035
  }
5031
5036
  }
5032
- function isPrivateIPv4(addr) {
5033
- const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
5034
- if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
5035
- return true;
5036
- }
5037
- const [a, b, c] = parts;
5038
- if (a === 0) return true;
5039
- if (a === 10) return true;
5040
- if (a === 127) return true;
5041
- if (a === 169 && b === 254) return true;
5042
- if (a === 172 && b >= 16 && b <= 31) return true;
5043
- if (a === 192 && b === 168) return true;
5044
- if (a === 192 && b === 0 && c === 0) return true;
5045
- if (a === 100 && b >= 64 && b <= 127) return true;
5046
- if (a >= 224) return true;
5047
- return false;
5048
- }
5049
- function isPrivateIPv6(addr) {
5050
- const lower = addr.toLowerCase();
5051
- if (lower === "::" || lower === "::1") return true;
5052
- const groups = expandIPv6(lower);
5053
- if (!groups) return true;
5054
- if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
5055
- const a = (groups[6] ?? 0) >> 8;
5056
- const b = (groups[6] ?? 0) & 255;
5057
- const c = (groups[7] ?? 0) >> 8;
5058
- const d = (groups[7] ?? 0) & 255;
5059
- return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
5060
- }
5061
- const high = groups[0] ?? 0;
5062
- if ((high & 65024) === 64512) return true;
5063
- if ((high & 65472) === 65152) return true;
5064
- if ((high & 65280) === 65280) return true;
5065
- return false;
5066
- }
5067
- function expandIPv6(addr) {
5068
- const parts = addr.split("::");
5069
- if (parts.length > 2) return null;
5070
- const parseGroups = (s) => {
5071
- if (s === "") return [];
5072
- const out = [];
5073
- for (const g of s.split(":")) {
5074
- if (g.length === 0 || g.length > 4) return null;
5075
- const n = Number.parseInt(g, 16);
5076
- if (Number.isNaN(n) || n < 0 || n > 65535) return null;
5077
- out.push(n);
5078
- }
5079
- return out;
5080
- };
5081
- if (parts.length === 1) {
5082
- const groups = parseGroups(parts[0] ?? "");
5083
- if (!groups || groups.length !== 8) return null;
5084
- return groups;
5085
- }
5086
- const head = parseGroups(parts[0] ?? "");
5087
- const tail = parseGroups(parts[1] ?? "");
5088
- if (!head || !tail) return null;
5089
- const fill = 8 - head.length - tail.length;
5090
- if (fill < 0) return null;
5091
- return [...head, ...new Array(fill).fill(0), ...tail];
5092
- }
5093
5037
  function prettyJson(s) {
5094
5038
  try {
5095
5039
  return JSON.stringify(JSON.parse(s), null, 2);
@@ -5097,32 +5041,6 @@ function prettyJson(s) {
5097
5041
  return s;
5098
5042
  }
5099
5043
  }
5100
- function htmlToMarkdown(html) {
5101
- let s = html;
5102
- s = s.replace(/<script[\s\S]*?<\/script>/gi, "");
5103
- s = s.replace(/<style[\s\S]*?<\/style>/gi, "");
5104
- s = s.replace(/<noscript[\s\S]*?<\/noscript>/gi, "");
5105
- s = s.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, n, c) => {
5106
- return "\n" + "#".repeat(Number(n)) + " " + stripTags(c).trim() + "\n";
5107
- });
5108
- s = s.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
5109
- s = s.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
5110
- s = s.replace(/<a [^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi, (_m, href, text) => {
5111
- const safe = /^(https?|ftps?):\/\//i.test(href) && !/^(javascript|data|vbscript):/i.test(href);
5112
- return safe ? `[${text}](${href})` : text;
5113
- });
5114
- s = s.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_m, c) => "\n```\n" + stripTags(c) + "\n```\n");
5115
- s = s.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, "`$1`");
5116
- s = s.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "- $1\n");
5117
- s = s.replace(/<br\s*\/?>/gi, "\n");
5118
- s = s.replace(/<\/p>/gi, "\n\n");
5119
- s = stripTags(s);
5120
- s = s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ");
5121
- return s.replace(/\n{3,}/g, "\n\n").trim();
5122
- }
5123
- function stripTags(s) {
5124
- return s.replace(/<[^>]+>/g, "");
5125
- }
5126
5044
 
5127
5045
  // src/format.ts
5128
5046
  var formatTool = {
@@ -5198,7 +5116,7 @@ var formatTool = {
5198
5116
  signal: opts.signal,
5199
5117
  maxBytes: 1e5
5200
5118
  });
5201
- const changed = (result.stdout.match(/changed/g) || []).length;
5119
+ const changed = [...result.stdout.matchAll(/\bchanged\b/gi)].length;
5202
5120
  yield {
5203
5121
  type: "final",
5204
5122
  output: {
@@ -5397,7 +5315,7 @@ function buildArgs(input) {
5397
5315
  ...files.length ? ["--", ...files] : []
5398
5316
  ];
5399
5317
  case "branch":
5400
- return input.branch ? ["branch", ...input.branch.startsWith("-") ? [] : [input.branch]] : ["branch"];
5318
+ return input.branch ? ["branch", ...input.branch.startsWith("-") || input.branch.includes(" --") ? [] : [input.branch]] : ["branch"];
5401
5319
  case "checkout":
5402
5320
  return [
5403
5321
  "checkout",
@@ -5413,7 +5331,7 @@ function buildArgs(input) {
5413
5331
  case "fetch":
5414
5332
  return ["fetch", ...input.branch ? [input.branch] : ["--all"]];
5415
5333
  case "reset":
5416
- return ["reset"];
5334
+ return ["reset", ...files.length ? ["--", ...files] : []];
5417
5335
  case "worktree":
5418
5336
  switch (input.worktreeAction) {
5419
5337
  case "list":
@@ -6225,8 +6143,8 @@ var lintTool = {
6225
6143
  }
6226
6144
  const cmd = detected === "biome" ? "biome" : detected;
6227
6145
  const result = yield* spawnStream({ cmd, args, cwd, signal: opts.signal, maxBytes: 1e5 });
6228
- const errors = (result.stdout.match(/error/g) || []).length;
6229
- const warnings = (result.stdout.match(/warning/g) || []).length;
6146
+ const errors = [...result.stdout.matchAll(/\berror\b/gi)].length;
6147
+ const warnings = [...result.stdout.matchAll(/\bwarning\b/gi)].length;
6230
6148
  yield {
6231
6149
  type: "final",
6232
6150
  output: {
@@ -6365,9 +6283,11 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
6365
6283
  child.stderr?.on("data", (c) => {
6366
6284
  if (stderr.length < MAX) stderr += c.toString();
6367
6285
  });
6368
- child.stdout?.on("error", () => {
6286
+ child.stdout?.on("error", (e) => {
6287
+ console.log(JSON.stringify({ level: "debug", event: "pipe_error", stream: "stdout", error: e.message }));
6369
6288
  });
6370
- child.stderr?.on("error", () => {
6289
+ child.stderr?.on("error", (e) => {
6290
+ console.log(JSON.stringify({ level: "debug", event: "pipe_error", stream: "stderr", error: e.message }));
6371
6291
  });
6372
6292
  child.on("close", () => {
6373
6293
  const output = stdout + stderr;
@@ -6645,14 +6565,16 @@ function extractDiffTargets(patch) {
6645
6565
  const out = [];
6646
6566
  const re = /^\+\+\+\s+([^\t\r\n]+)/gm;
6647
6567
  for (const m of patch.matchAll(re)) {
6648
- const target = m[1]?.trim();
6568
+ const raw = m[1];
6569
+ if (!raw) continue;
6570
+ const target = raw.length > 4096 ? raw.slice(0, 4096).trim() : raw.trim();
6649
6571
  if (!target || target === "/dev/null") continue;
6650
6572
  out.push(target);
6651
6573
  }
6652
6574
  return out;
6653
6575
  }
6654
6576
  function stripPathComponents(p, strip) {
6655
- const parts = p.replace(/\\/g, "/").split("/");
6577
+ const parts = p.replace(/\\/g, "/").split("/").filter((s) => s !== "" && s !== ".");
6656
6578
  if (parts.length <= strip) return void 0;
6657
6579
  return parts.slice(strip).join("/");
6658
6580
  }
@@ -7444,7 +7366,8 @@ async function duckduckgoSearch(query2, num, signal) {
7444
7366
  source: "duckduckgo",
7445
7367
  truncated: results.length >= num
7446
7368
  };
7447
- } catch {
7369
+ } catch (err) {
7370
+ console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error: err instanceof Error ? err.message : String(err) }));
7448
7371
  return {
7449
7372
  query: query2,
7450
7373
  results: [{ title: "Search unavailable", url: "", snippet: "Could not reach DuckDuckGo" }],
@@ -7466,11 +7389,11 @@ function parseDuckDuckGo(html, num) {
7466
7389
  const snippetRegex = /<a class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/gi;
7467
7390
  const snippet2Regex = /<a class="result-snippet"[^>]*>([^<]+)<\/a>/gi;
7468
7391
  const linkMatches = takeFrom(
7469
- [...html.matchAll(snippetRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags2(expectDefined(m[2])) })),
7392
+ [...html.matchAll(snippetRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags(expectDefined(m[2])) })),
7470
7393
  num
7471
7394
  );
7472
7395
  const snippetMatches = takeFrom(
7473
- [...html.matchAll(snippet2Regex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1]))),
7396
+ [...html.matchAll(snippet2Regex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
7474
7397
  num
7475
7398
  );
7476
7399
  for (let i = 0; i < linkMatches.length && i < num; i++) {
@@ -7501,15 +7424,15 @@ function parseGoogleResults(html, num) {
7501
7424
  const urlRegex = /<cite[^>]*>([^<]+)<\/cite>/gi;
7502
7425
  const snippetRegex = /<span[^>]*class="[^"]*aXCZ0b[^>]*>([^<]+)<\/span>/gi;
7503
7426
  const titles = takeFrom(
7504
- [...html.matchAll(titleRegex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1]))),
7427
+ [...html.matchAll(titleRegex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
7505
7428
  num
7506
7429
  );
7507
7430
  const urls = takeFrom(
7508
- [...html.matchAll(urlRegex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1])).replace(/^\*(https?:\/\/[^\s]+).*$/, "$1")).filter((u) => u.startsWith("http")),
7431
+ [...html.matchAll(urlRegex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1])).replace(/^\*(https?:\/\/[^\s]+).*$/, "$1")).filter((u) => u.startsWith("http")),
7509
7432
  num
7510
7433
  );
7511
7434
  const snippets = takeFrom(
7512
- [...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1]))),
7435
+ [...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
7513
7436
  num
7514
7437
  );
7515
7438
  for (let i = 0; i < Math.min(titles.length, num); i++) {
@@ -7538,11 +7461,11 @@ function parseBingResults(html, num) {
7538
7461
  const titleRegex = /<h2[^>]*>\s*<a[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>\s*<\/h2>/gi;
7539
7462
  const snippetRegex = /<p[^>]*class="[^"]*b_paractl[^"]*"[^>]*>([^<]+)<\/p>/gi;
7540
7463
  const entries = takeFrom(
7541
- [...html.matchAll(titleRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags2(expectDefined(m[2])) })),
7464
+ [...html.matchAll(titleRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags(expectDefined(m[2])) })),
7542
7465
  num
7543
7466
  );
7544
7467
  const snippets = takeFrom(
7545
- [...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1]))),
7468
+ [...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
7546
7469
  num
7547
7470
  );
7548
7471
  for (let i = 0; i < entries.length; i++) {
@@ -7572,7 +7495,7 @@ async function fetchWithTimeout(url, signal, timeoutMs) {
7572
7495
  function anySignal(...signals) {
7573
7496
  return AbortSignal.any(signals);
7574
7497
  }
7575
- function stripTags2(html) {
7498
+ function stripTags(html) {
7576
7499
  return html.replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
7577
7500
  }
7578
7501
  var setWorkingDirTool = {
@@ -7727,6 +7650,7 @@ var taskTool = {
7727
7650
  const promoteMeta = { count: 0, title: "" };
7728
7651
  const planifyMeta = { title: "", details: "" };
7729
7652
  let didPlanify = false;
7653
+ let todosToReplace = null;
7730
7654
  const file = await mutateTasks(taskPath, sessionId, async (f) => {
7731
7655
  switch (input.action) {
7732
7656
  case "show":
@@ -7782,7 +7706,7 @@ var taskTool = {
7782
7706
  }
7783
7707
  const now = (/* @__PURE__ */ new Date()).toISOString();
7784
7708
  const newTask = {
7785
- id: `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
7709
+ id: `task_${Date.now()}_${randomUUID().slice(0, 8)}`,
7786
7710
  title: t.title,
7787
7711
  description: t.description,
7788
7712
  type: t.type || "feature",
@@ -7859,7 +7783,7 @@ var taskTool = {
7859
7783
  });
7860
7784
  }
7861
7785
  }
7862
- ctx.state.replaceTodos(todos);
7786
+ todosToReplace = todos;
7863
7787
  promoteMeta.count = todos.length;
7864
7788
  promoteMeta.title = match.title;
7865
7789
  break;
@@ -7891,6 +7815,7 @@ var taskTool = {
7891
7815
  }
7892
7816
  return f;
7893
7817
  });
7818
+ if (todosToReplace) ctx.state.replaceTodos(todosToReplace);
7894
7819
  if (early) return early;
7895
7820
  if (didPlanify) {
7896
7821
  const { title, details } = planifyMeta;
@@ -8655,6 +8580,10 @@ var typecheckTool = {
8655
8580
  all: {
8656
8581
  type: "boolean",
8657
8582
  description: "Type-check all projects (pnpm -r) (default: false)"
8583
+ },
8584
+ json: {
8585
+ type: "boolean",
8586
+ description: "Emit JSON output from tsc (default: false)"
8658
8587
  }
8659
8588
  }
8660
8589
  },
@@ -8682,6 +8611,7 @@ var typecheckTool = {
8682
8611
  if (tsconfig) args.push("--project", tsconfig);
8683
8612
  project = tsconfig ?? "default";
8684
8613
  }
8614
+ if (input.json) args.push("--json");
8685
8615
  yield { type: "log", text: `tsc ${args.join(" ")}`, data: { project } };
8686
8616
  const result = yield* spawnStream({
8687
8617
  cmd: "npx",
@@ -8690,8 +8620,8 @@ var typecheckTool = {
8690
8620
  signal: opts.signal,
8691
8621
  maxBytes: 2e5
8692
8622
  });
8693
- const errors = (result.stdout.match(/error TS/g) || []).length;
8694
- const warnings = (result.stdout.match(/warning/g) || []).length;
8623
+ const errors = [...result.stdout.matchAll(/\berror\b/gi)].length;
8624
+ const warnings = [...result.stdout.matchAll(/\bwarning\b/gi)].length;
8695
8625
  yield {
8696
8626
  type: "final",
8697
8627
  output: {