@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/index.js CHANGED
@@ -2,7 +2,7 @@ import * as fs4 from 'node:fs/promises';
2
2
  import * as path from 'node:path';
3
3
  import { resolve, sep, dirname, join } from 'node:path';
4
4
  import * as Core from '@wrongstack/core';
5
- import { atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, expectDefined, buildChildEnv, loadPlan, setPlanItemStatus, savePlan, loadTasks, saveTasks, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, emptyTaskFile, formatTaskList, formatPlan, recordPackageAction, detectPackageEcosystem, mutateTasks, emptyPlan, computeTaskItemProgress, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
5
+ import { atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, expectDefined, buildChildEnv, isPrivateIPv4, isPrivateIPv6, loadPlan, setPlanItemStatus, savePlan, loadTasks, saveTasks, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, emptyTaskFile, formatTaskList, formatPlan, recordPackageAction, detectPackageEcosystem, mutateTasks, emptyPlan, computeTaskItemProgress, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
6
6
  import { spawn, execFileSync, spawnSync } from 'node:child_process';
7
7
  import * as os from 'node:os';
8
8
  import * as fs7 from 'node:fs';
@@ -10,6 +10,7 @@ import { statSync, mkdirSync, createWriteStream, writeFileSync } from 'node:fs';
10
10
  import * as dns from 'node:dns/promises';
11
11
  import * as net from 'node:net';
12
12
  import { Agent } from 'undici';
13
+ import TurndownService from 'turndown';
13
14
  import { createRequire } from 'node:module';
14
15
  import { fileURLToPath } from 'node:url';
15
16
  import { Worker } from 'node:worker_threads';
@@ -1297,7 +1298,7 @@ var SENSITIVE_FLAG_PATTERNS = [
1297
1298
  // --flag=value or --flag "value" (value captured up to next space or comma)
1298
1299
  /--(?: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,
1299
1300
  // -f "value" style short flags
1300
- /(?<!\w)-t(?:\s+|\s*=\s*)[^\s,]+/g,
1301
+ /(?<!\w)-t(?:\s+|\s*=\s*)[^\s,]+/,
1301
1302
  /(?<!\w)-p(?:ssword)?(?:\s+|\s*=\s*)[^\s,]+/gi,
1302
1303
  // env var–style secrets: TOKEN=x, API_KEY=y, etc.
1303
1304
  /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
@@ -1433,8 +1434,8 @@ var ProcessRegistryImpl = class {
1433
1434
  if (p.killed) return true;
1434
1435
  if (p.protected) return false;
1435
1436
  const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
1436
- const isWin = os.platform() === "win32";
1437
- if (isWin) {
1437
+ const isWin3 = os.platform() === "win32";
1438
+ if (isWin3) {
1438
1439
  const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
1439
1440
  if (liveRealChild && killWin32Tree(pid)) {
1440
1441
  const fallback = setTimeout(() => {
@@ -1603,11 +1604,11 @@ var bashTool = {
1603
1604
  }));
1604
1605
  }
1605
1606
  const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
1606
- const isWin = os.platform() === "win32";
1607
+ const isWin3 = os.platform() === "win32";
1607
1608
  const shell = (() => {
1608
- const explicit = process.env[isWin ? "WRONGSTACK_COMSPEC" : "WRONGSTACK_SHELL"];
1609
+ const explicit = process.env[isWin3 ? "WRONGSTACK_COMSPEC" : "WRONGSTACK_SHELL"];
1609
1610
  if (explicit) return explicit;
1610
- if (isWin) return process.env["COMSPEC"] ?? "cmd.exe";
1611
+ if (isWin3) return process.env["COMSPEC"] ?? "cmd.exe";
1611
1612
  const fromEnv = process.env["SHELL"];
1612
1613
  if (fromEnv) {
1613
1614
  const name = fromEnv.split("/").pop() ?? "";
@@ -1615,9 +1616,9 @@ var bashTool = {
1615
1616
  }
1616
1617
  return "/bin/bash";
1617
1618
  })();
1618
- const args = isWin ? ["/c", input.command] : ["-c", input.command];
1619
+ const args = isWin3 ? ["/c", input.command] : ["-c", input.command];
1619
1620
  const env = buildChildEnv(ctx.session?.id);
1620
- const detached = !isWin;
1621
+ const detached = !isWin3;
1621
1622
  const startedAt = Date.now();
1622
1623
  if (input.background) {
1623
1624
  let buf2 = "";
@@ -1633,7 +1634,7 @@ var bashTool = {
1633
1634
  // apply: the child gets a hidden console that grandchildren inherit.
1634
1635
  // Windows children survive parent exit either way. POSIX keeps
1635
1636
  // detached for the process-group kill semantics.
1636
- detached: !isWin,
1637
+ detached: !isWin3,
1637
1638
  windowsHide: true,
1638
1639
  signal: opts.signal
1639
1640
  });
@@ -1663,8 +1664,6 @@ var bashTool = {
1663
1664
  };
1664
1665
  child2.stdout?.on("data", onBgData);
1665
1666
  child2.stderr?.on("data", onBgData);
1666
- child2.stdout?.unref?.();
1667
- child2.stderr?.unref?.();
1668
1667
  child2.on("close", () => {
1669
1668
  registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
1670
1669
  });
@@ -1686,7 +1685,7 @@ var bashTool = {
1686
1685
  stdio: ["ignore", "pipe", "pipe"],
1687
1686
  detached,
1688
1687
  windowsHide: true,
1689
- ...isWin ? {} : { signal: opts.signal }
1688
+ ...isWin3 ? {} : { signal: opts.signal }
1690
1689
  });
1691
1690
  const pid = child.pid;
1692
1691
  if (typeof pid === "number") {
@@ -1705,7 +1704,7 @@ var bashTool = {
1705
1704
  const timers = [];
1706
1705
  const spool = createOutputSpool({ tool: "bash", thresholdBytes: MAX_OUTPUT });
1707
1706
  function killWithTimeout(child2, timeoutMs2) {
1708
- if (isWin) {
1707
+ if (isWin3) {
1709
1708
  if (typeof child2.pid === "number" && child2.exitCode === null && killWin32Tree(child2.pid)) {
1710
1709
  const fallback = setTimeout(() => {
1711
1710
  if (child2.exitCode === null) {
@@ -1761,7 +1760,7 @@ var bashTool = {
1761
1760
  timers.push(timer);
1762
1761
  timer.unref?.();
1763
1762
  const onAbort = () => killWithTimeout(child, 2e3);
1764
- if (isWin) {
1763
+ if (isWin3) {
1765
1764
  if (opts.signal.aborted) onAbort();
1766
1765
  else opts.signal.addEventListener("abort", onAbort, { once: true });
1767
1766
  }
@@ -1857,7 +1856,7 @@ var bashTool = {
1857
1856
  } finally {
1858
1857
  for (const t of timers) clearTimeout(t);
1859
1858
  spool.finalize();
1860
- if (isWin) opts.signal.removeEventListener("abort", onAbort);
1859
+ if (isWin3) opts.signal.removeEventListener("abort", onAbort);
1861
1860
  child.stdout?.off("data", onData);
1862
1861
  child.stderr?.off("data", onData);
1863
1862
  child.stdout?.destroy();
@@ -1871,7 +1870,7 @@ var bashTool = {
1871
1870
  };
1872
1871
  function resolveWin32Command(cmd) {
1873
1872
  if (process.platform !== "win32") return cmd;
1874
- if (cmd.includes("/") || cmd.includes("\\") || path.extname(cmd)) {
1873
+ if (cmd.includes("/") || cmd.includes("\\") || path.extname(cmd.replace(/\//g, "\\"))) {
1875
1874
  return cmd;
1876
1875
  }
1877
1876
  const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
@@ -1891,6 +1890,7 @@ function resolveWin32Command(cmd) {
1891
1890
  }
1892
1891
 
1893
1892
  // src/exec.ts
1893
+ var isWin = process.platform === "win32";
1894
1894
  var ALLOWED_COMMANDS = {
1895
1895
  node: ["--version", "-r", "--input-type=module"],
1896
1896
  npm: ["--version", "list", "pkg", "doctor", "view", "outdated", "audit"],
@@ -2099,7 +2099,6 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
2099
2099
  const startedAt = Date.now();
2100
2100
  const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
2101
2101
  const resolved = resolveWin32Command(cmd);
2102
- const isWin = process.platform === "win32";
2103
2102
  const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
2104
2103
  const child = spawn(resolved, args, {
2105
2104
  cwd,
@@ -2175,6 +2174,16 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
2175
2174
  });
2176
2175
  });
2177
2176
  }
2177
+ var TD = new TurndownService({
2178
+ // Use `# Title` for headings, not setext underline style (`Title\n=====`).
2179
+ headingStyle: "atx",
2180
+ // Don't wrap code blocks in <pre> — render them as triple-backtick blocks.
2181
+ codeBlockStyle: "fenced"
2182
+ });
2183
+ TD.addRule("stripDangerousElements", {
2184
+ filter: ["script", "style", "noscript"],
2185
+ replacement: () => ""
2186
+ });
2178
2187
  var MAX_BYTES2 = 131072;
2179
2188
  var TIMEOUT_MS = 2e4;
2180
2189
  var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
@@ -2209,7 +2218,7 @@ function guardedLookup(hostname, options, callback) {
2209
2218
  );
2210
2219
  return;
2211
2220
  }
2212
- const first = list[0];
2221
+ const first = list.at(0);
2213
2222
  if (!first) {
2214
2223
  callback(
2215
2224
  Object.assign(new Error(`fetch: no address for ${hostname}`), { code: "ENOTFOUND" })
@@ -2349,7 +2358,7 @@ var fetchTool = {
2349
2358
  pendingBytes += value.byteLength;
2350
2359
  chunks.push(value);
2351
2360
  if (pendingBytes >= FLUSH_AT) {
2352
- const recent = Buffer.from(value).toString("utf8");
2361
+ const recent = Buffer.from(value).toString("utf-8");
2353
2362
  yield {
2354
2363
  type: "partial_output",
2355
2364
  text: recent,
@@ -2364,7 +2373,7 @@ var fetchTool = {
2364
2373
  const format = input.format ?? (ct.includes("text/html") ? "markdown" : "text");
2365
2374
  let content;
2366
2375
  if (format === "raw") content = text;
2367
- else if (format === "markdown" && ct.includes("text/html")) content = htmlToMarkdown(text);
2376
+ else if (format === "markdown" && ct.includes("text/html")) content = TD.turndown(text);
2368
2377
  else if (ct.includes("application/json")) content = prettyJson(text);
2369
2378
  else content = text;
2370
2379
  yield {
@@ -2410,67 +2419,6 @@ async function assertNotPrivate(hostname) {
2410
2419
  }
2411
2420
  }
2412
2421
  }
2413
- function isPrivateIPv4(addr) {
2414
- const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
2415
- if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
2416
- return true;
2417
- }
2418
- const [a, b, c] = parts;
2419
- if (a === 0) return true;
2420
- if (a === 10) return true;
2421
- if (a === 127) return true;
2422
- if (a === 169 && b === 254) return true;
2423
- if (a === 172 && b >= 16 && b <= 31) return true;
2424
- if (a === 192 && b === 168) return true;
2425
- if (a === 192 && b === 0 && c === 0) return true;
2426
- if (a === 100 && b >= 64 && b <= 127) return true;
2427
- if (a >= 224) return true;
2428
- return false;
2429
- }
2430
- function isPrivateIPv6(addr) {
2431
- const lower = addr.toLowerCase();
2432
- if (lower === "::" || lower === "::1") return true;
2433
- const groups = expandIPv6(lower);
2434
- if (!groups) return true;
2435
- if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
2436
- const a = (groups[6] ?? 0) >> 8;
2437
- const b = (groups[6] ?? 0) & 255;
2438
- const c = (groups[7] ?? 0) >> 8;
2439
- const d = (groups[7] ?? 0) & 255;
2440
- return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
2441
- }
2442
- const high = groups[0] ?? 0;
2443
- if ((high & 65024) === 64512) return true;
2444
- if ((high & 65472) === 65152) return true;
2445
- if ((high & 65280) === 65280) return true;
2446
- return false;
2447
- }
2448
- function expandIPv6(addr) {
2449
- const parts = addr.split("::");
2450
- if (parts.length > 2) return null;
2451
- const parseGroups = (s) => {
2452
- if (s === "") return [];
2453
- const out = [];
2454
- for (const g of s.split(":")) {
2455
- if (g.length === 0 || g.length > 4) return null;
2456
- const n = Number.parseInt(g, 16);
2457
- if (Number.isNaN(n) || n < 0 || n > 65535) return null;
2458
- out.push(n);
2459
- }
2460
- return out;
2461
- };
2462
- if (parts.length === 1) {
2463
- const groups = parseGroups(parts[0] ?? "");
2464
- if (!groups || groups.length !== 8) return null;
2465
- return groups;
2466
- }
2467
- const head = parseGroups(parts[0] ?? "");
2468
- const tail = parseGroups(parts[1] ?? "");
2469
- if (!head || !tail) return null;
2470
- const fill = 8 - head.length - tail.length;
2471
- if (fill < 0) return null;
2472
- return [...head, ...new Array(fill).fill(0), ...tail];
2473
- }
2474
2422
  function prettyJson(s) {
2475
2423
  try {
2476
2424
  return JSON.stringify(JSON.parse(s), null, 2);
@@ -2478,32 +2426,6 @@ function prettyJson(s) {
2478
2426
  return s;
2479
2427
  }
2480
2428
  }
2481
- function htmlToMarkdown(html) {
2482
- let s = html;
2483
- s = s.replace(/<script[\s\S]*?<\/script>/gi, "");
2484
- s = s.replace(/<style[\s\S]*?<\/style>/gi, "");
2485
- s = s.replace(/<noscript[\s\S]*?<\/noscript>/gi, "");
2486
- s = s.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, n, c) => {
2487
- return "\n" + "#".repeat(Number(n)) + " " + stripTags(c).trim() + "\n";
2488
- });
2489
- s = s.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
2490
- s = s.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
2491
- s = s.replace(/<a [^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi, (_m, href, text) => {
2492
- const safe = /^(https?|ftps?):\/\//i.test(href) && !/^(javascript|data|vbscript):/i.test(href);
2493
- return safe ? `[${text}](${href})` : text;
2494
- });
2495
- s = s.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_m, c) => "\n```\n" + stripTags(c) + "\n```\n");
2496
- s = s.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, "`$1`");
2497
- s = s.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "- $1\n");
2498
- s = s.replace(/<br\s*\/?>/gi, "\n");
2499
- s = s.replace(/<\/p>/gi, "\n\n");
2500
- s = stripTags(s);
2501
- s = s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ");
2502
- return s.replace(/\n{3,}/g, "\n\n").trim();
2503
- }
2504
- function stripTags(s) {
2505
- return s.replace(/<[^>]+>/g, "");
2506
- }
2507
2429
  var DEFAULT_NUM = 10;
2508
2430
  var MAX_RESULTS = 50;
2509
2431
  var TIMEOUT_MS2 = 15e3;
@@ -2588,7 +2510,8 @@ async function duckduckgoSearch(query2, num, signal) {
2588
2510
  source: "duckduckgo",
2589
2511
  truncated: results.length >= num
2590
2512
  };
2591
- } catch {
2513
+ } catch (err) {
2514
+ console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error: err instanceof Error ? err.message : String(err) }));
2592
2515
  return {
2593
2516
  query: query2,
2594
2517
  results: [{ title: "Search unavailable", url: "", snippet: "Could not reach DuckDuckGo" }],
@@ -2610,11 +2533,11 @@ function parseDuckDuckGo(html, num) {
2610
2533
  const snippetRegex = /<a class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/gi;
2611
2534
  const snippet2Regex = /<a class="result-snippet"[^>]*>([^<]+)<\/a>/gi;
2612
2535
  const linkMatches = takeFrom(
2613
- [...html.matchAll(snippetRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags2(expectDefined(m[2])) })),
2536
+ [...html.matchAll(snippetRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags(expectDefined(m[2])) })),
2614
2537
  num
2615
2538
  );
2616
2539
  const snippetMatches = takeFrom(
2617
- [...html.matchAll(snippet2Regex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1]))),
2540
+ [...html.matchAll(snippet2Regex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
2618
2541
  num
2619
2542
  );
2620
2543
  for (let i = 0; i < linkMatches.length && i < num; i++) {
@@ -2645,15 +2568,15 @@ function parseGoogleResults(html, num) {
2645
2568
  const urlRegex = /<cite[^>]*>([^<]+)<\/cite>/gi;
2646
2569
  const snippetRegex = /<span[^>]*class="[^"]*aXCZ0b[^>]*>([^<]+)<\/span>/gi;
2647
2570
  const titles = takeFrom(
2648
- [...html.matchAll(titleRegex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1]))),
2571
+ [...html.matchAll(titleRegex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
2649
2572
  num
2650
2573
  );
2651
2574
  const urls = takeFrom(
2652
- [...html.matchAll(urlRegex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1])).replace(/^\*(https?:\/\/[^\s]+).*$/, "$1")).filter((u) => u.startsWith("http")),
2575
+ [...html.matchAll(urlRegex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1])).replace(/^\*(https?:\/\/[^\s]+).*$/, "$1")).filter((u) => u.startsWith("http")),
2653
2576
  num
2654
2577
  );
2655
2578
  const snippets = takeFrom(
2656
- [...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1]))),
2579
+ [...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
2657
2580
  num
2658
2581
  );
2659
2582
  for (let i = 0; i < Math.min(titles.length, num); i++) {
@@ -2682,11 +2605,11 @@ function parseBingResults(html, num) {
2682
2605
  const titleRegex = /<h2[^>]*>\s*<a[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>\s*<\/h2>/gi;
2683
2606
  const snippetRegex = /<p[^>]*class="[^"]*b_paractl[^"]*"[^>]*>([^<]+)<\/p>/gi;
2684
2607
  const entries = takeFrom(
2685
- [...html.matchAll(titleRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags2(expectDefined(m[2])) })),
2608
+ [...html.matchAll(titleRegex)].filter((m) => m[1] && m[2]).map((m) => ({ url: expectDefined(m[1]), title: stripTags(expectDefined(m[2])) })),
2686
2609
  num
2687
2610
  );
2688
2611
  const snippets = takeFrom(
2689
- [...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags2(expectDefined(m[1]))),
2612
+ [...html.matchAll(snippetRegex)].filter((m) => m[1]).map((m) => stripTags(expectDefined(m[1]))),
2690
2613
  num
2691
2614
  );
2692
2615
  for (let i = 0; i < entries.length; i++) {
@@ -2716,7 +2639,7 @@ async function fetchWithTimeout(url, signal, timeoutMs) {
2716
2639
  function anySignal(...signals) {
2717
2640
  return AbortSignal.any(signals);
2718
2641
  }
2719
- function stripTags2(html) {
2642
+ function stripTags(html) {
2720
2643
  return html.replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
2721
2644
  }
2722
2645
  var todoTool = {
@@ -3222,7 +3145,7 @@ function buildArgs(input) {
3222
3145
  ...files.length ? ["--", ...files] : []
3223
3146
  ];
3224
3147
  case "branch":
3225
- return input.branch ? ["branch", ...input.branch.startsWith("-") ? [] : [input.branch]] : ["branch"];
3148
+ return input.branch ? ["branch", ...input.branch.startsWith("-") || input.branch.includes(" --") ? [] : [input.branch]] : ["branch"];
3226
3149
  case "checkout":
3227
3150
  return [
3228
3151
  "checkout",
@@ -3238,7 +3161,7 @@ function buildArgs(input) {
3238
3161
  case "fetch":
3239
3162
  return ["fetch", ...input.branch ? [input.branch] : ["--all"]];
3240
3163
  case "reset":
3241
- return ["reset"];
3164
+ return ["reset", ...files.length ? ["--", ...files] : []];
3242
3165
  case "worktree":
3243
3166
  switch (input.worktreeAction) {
3244
3167
  case "list":
@@ -3383,14 +3306,16 @@ function extractDiffTargets(patch) {
3383
3306
  const out = [];
3384
3307
  const re = /^\+\+\+\s+([^\t\r\n]+)/gm;
3385
3308
  for (const m of patch.matchAll(re)) {
3386
- const target = m[1]?.trim();
3309
+ const raw = m[1];
3310
+ if (!raw) continue;
3311
+ const target = raw.length > 4096 ? raw.slice(0, 4096).trim() : raw.trim();
3387
3312
  if (!target || target === "/dev/null") continue;
3388
3313
  out.push(target);
3389
3314
  }
3390
3315
  return out;
3391
3316
  }
3392
3317
  function stripPathComponents(p, strip) {
3393
- const parts = p.replace(/\\/g, "/").split("/");
3318
+ const parts = p.replace(/\\/g, "/").split("/").filter((s) => s !== "" && s !== ".");
3394
3319
  if (parts.length <= strip) return void 0;
3395
3320
  return parts.slice(strip).join("/");
3396
3321
  }
@@ -3862,6 +3787,7 @@ async function walkDir(dir, depth, opts) {
3862
3787
  }
3863
3788
  }
3864
3789
  }
3790
+ var isWin2 = process.platform === "win32";
3865
3791
  async function* spawnStream(opts) {
3866
3792
  const max = opts.maxBytes ?? 2e5;
3867
3793
  const flushAt = opts.flushBytes ?? 4 * 1024;
@@ -3872,14 +3798,13 @@ async function* spawnStream(opts) {
3872
3798
  let error;
3873
3799
  const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
3874
3800
  const cmd = resolveWin32Command(opts.cmd);
3875
- const isWin = process.platform === "win32";
3876
- const needsShell = isWin && (cmd.endsWith(".cmd") || cmd.endsWith(".bat"));
3801
+ const needsShell = isWin2 && (cmd.endsWith(".cmd") || cmd.endsWith(".bat"));
3877
3802
  const child = spawn(cmd, opts.args, {
3878
3803
  cwd: opts.cwd,
3879
3804
  env: buildChildEnv(),
3880
3805
  stdio: ["ignore", "pipe", "pipe"],
3881
3806
  windowsHide: true,
3882
- ...isWin ? {} : { signal: opts.signal },
3807
+ ...isWin2 ? {} : { signal: opts.signal },
3883
3808
  ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
3884
3809
  });
3885
3810
  const registry = getProcessRegistry();
@@ -4085,8 +4010,8 @@ var lintTool = {
4085
4010
  }
4086
4011
  const cmd = detected === "biome" ? "biome" : detected;
4087
4012
  const result = yield* spawnStream({ cmd, args, cwd, signal: opts.signal, maxBytes: 1e5 });
4088
- const errors = (result.stdout.match(/error/g) || []).length;
4089
- const warnings = (result.stdout.match(/warning/g) || []).length;
4013
+ const errors = [...result.stdout.matchAll(/\berror\b/gi)].length;
4014
+ const warnings = [...result.stdout.matchAll(/\bwarning\b/gi)].length;
4090
4015
  yield {
4091
4016
  type: "final",
4092
4017
  output: {
@@ -4190,7 +4115,7 @@ var formatTool = {
4190
4115
  signal: opts.signal,
4191
4116
  maxBytes: 1e5
4192
4117
  });
4193
- const changed = (result.stdout.match(/changed/g) || []).length;
4118
+ const changed = [...result.stdout.matchAll(/\bchanged\b/gi)].length;
4194
4119
  yield {
4195
4120
  type: "final",
4196
4121
  output: {
@@ -4238,6 +4163,10 @@ var typecheckTool = {
4238
4163
  all: {
4239
4164
  type: "boolean",
4240
4165
  description: "Type-check all projects (pnpm -r) (default: false)"
4166
+ },
4167
+ json: {
4168
+ type: "boolean",
4169
+ description: "Emit JSON output from tsc (default: false)"
4241
4170
  }
4242
4171
  }
4243
4172
  },
@@ -4265,6 +4194,7 @@ var typecheckTool = {
4265
4194
  if (tsconfig) args.push("--project", tsconfig);
4266
4195
  project = tsconfig ?? "default";
4267
4196
  }
4197
+ if (input.json) args.push("--json");
4268
4198
  yield { type: "log", text: `tsc ${args.join(" ")}`, data: { project } };
4269
4199
  const result = yield* spawnStream({
4270
4200
  cmd: "npx",
@@ -4273,8 +4203,8 @@ var typecheckTool = {
4273
4203
  signal: opts.signal,
4274
4204
  maxBytes: 2e5
4275
4205
  });
4276
- const errors = (result.stdout.match(/error TS/g) || []).length;
4277
- const warnings = (result.stdout.match(/warning/g) || []).length;
4206
+ const errors = [...result.stdout.matchAll(/\berror\b/gi)].length;
4207
+ const warnings = [...result.stdout.matchAll(/\bwarning\b/gi)].length;
4278
4208
  yield {
4279
4209
  type: "final",
4280
4210
  output: {
@@ -4678,7 +4608,7 @@ function parseAuditOutput(json, exitCode) {
4678
4608
  total,
4679
4609
  summary,
4680
4610
  output: json,
4681
- truncated: json.length >= 1e5
4611
+ truncated: json.length > 1e5
4682
4612
  };
4683
4613
  } catch {
4684
4614
  return {
@@ -4914,9 +4844,11 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
4914
4844
  child.stderr?.on("data", (c) => {
4915
4845
  if (stderr.length < MAX) stderr += c.toString();
4916
4846
  });
4917
- child.stdout?.on("error", () => {
4847
+ child.stdout?.on("error", (e) => {
4848
+ console.log(JSON.stringify({ level: "debug", event: "pipe_error", stream: "stdout", error: e.message }));
4918
4849
  });
4919
- child.stderr?.on("error", () => {
4850
+ child.stderr?.on("error", (e) => {
4851
+ console.log(JSON.stringify({ level: "debug", event: "pipe_error", stream: "stderr", error: e.message }));
4920
4852
  });
4921
4853
  child.on("close", () => {
4922
4854
  const output = stdout + stderr;
@@ -5036,10 +4968,6 @@ var documentTool = {
5036
4968
  enum: ["jsdoc", "tsdoc", "block"],
5037
4969
  description: "Documentation style (default: jsdoc)"
5038
4970
  },
5039
- overwrite: {
5040
- type: "boolean",
5041
- description: "Overwrite existing docstrings (default: false)"
5042
- },
5043
4971
  cwd: { type: "string", description: "Working directory (default: cwd)" }
5044
4972
  }
5045
4973
  },
@@ -5097,10 +5025,10 @@ async function resolveFiles2(filesInput, cwd) {
5097
5025
  }
5098
5026
  function processFile(content, absPath, _style, _overwrite, target) {
5099
5027
  const results = [];
5100
- const functionRegex = /(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g;
5101
- const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/g;
5102
- const classRegex = /class\s+(\w+)/g;
5103
- const typeRegex = /(?:type|interface)\s+(\w+)\s*[=<]/g;
5028
+ const functionRegex = /(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/;
5029
+ const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/;
5030
+ const classRegex = /class\s+(\w+)/;
5031
+ const typeRegex = /(?:type|interface)\s+(\w+)\s*[=<]/;
5104
5032
  const allMatches = [];
5105
5033
  if (target === "all" || target === "function") {
5106
5034
  for (const m of content.matchAll(functionRegex)) {
@@ -5545,7 +5473,7 @@ var batchToolUseTool = {
5545
5473
  failed = allResults.filter((r) => !r.success).length;
5546
5474
  } else {
5547
5475
  for (const call of input.calls) {
5548
- const result = await executeSingle(call, ctx, opts);
5476
+ const result = await executeSingle(call, ctx, opts ?? { signal: void 0 });
5549
5477
  results.push(result);
5550
5478
  if (result.success) {
5551
5479
  succeeded++;
@@ -8911,6 +8839,7 @@ var taskTool = {
8911
8839
  const promoteMeta = { count: 0, title: "" };
8912
8840
  const planifyMeta = { title: "", details: "" };
8913
8841
  let didPlanify = false;
8842
+ let todosToReplace = null;
8914
8843
  const file = await mutateTasks(taskPath, sessionId, async (f) => {
8915
8844
  switch (input.action) {
8916
8845
  case "show":
@@ -8966,7 +8895,7 @@ var taskTool = {
8966
8895
  }
8967
8896
  const now = (/* @__PURE__ */ new Date()).toISOString();
8968
8897
  const newTask = {
8969
- id: `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
8898
+ id: `task_${Date.now()}_${randomUUID().slice(0, 8)}`,
8970
8899
  title: t.title,
8971
8900
  description: t.description,
8972
8901
  type: t.type || "feature",
@@ -9043,7 +8972,7 @@ var taskTool = {
9043
8972
  });
9044
8973
  }
9045
8974
  }
9046
- ctx.state.replaceTodos(todos);
8975
+ todosToReplace = todos;
9047
8976
  promoteMeta.count = todos.length;
9048
8977
  promoteMeta.title = match.title;
9049
8978
  break;
@@ -9075,6 +9004,7 @@ var taskTool = {
9075
9004
  }
9076
9005
  return f;
9077
9006
  });
9007
+ if (todosToReplace) ctx.state.replaceTodos(todosToReplace);
9078
9008
  if (early) return early;
9079
9009
  if (didPlanify) {
9080
9010
  const { title, details } = planifyMeta;