@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
@@ -4,14 +4,14 @@ import {
4
4
  } from "./chunk-Q5JPPCZC.js";
5
5
  import {
6
6
  mapWithConcurrency
7
- } from "./chunk-NWZ6XGBJ.js";
7
+ } from "./chunk-ZBMANLYH.js";
8
8
  import {
9
9
  loadGitignoreMatcher
10
10
  } from "./chunk-DTQ75VBC.js";
11
11
  import {
12
12
  isBinaryBuffer,
13
13
  safeResolveReal
14
- } from "./chunk-QLF74MSV.js";
14
+ } from "./chunk-ZUFHAHTE.js";
15
15
 
16
16
  // src/grep.ts
17
17
  import { spawn } from "node:child_process";
@@ -99,7 +99,8 @@ var grepTool = {
99
99
  }
100
100
  const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
101
101
  const mode = input.output_mode ?? "content";
102
- const limit = Math.max(1, Math.min(input.limit ?? 200, 2e3));
102
+ const rawLimit = typeof input.limit === "number" && Number.isFinite(input.limit) ? input.limit : 200;
103
+ const limit = Math.max(1, Math.min(Math.floor(rawLimit), 2e3));
103
104
  const validation = compileUserRegex(input.pattern, input.case_insensitive ? "i" : "");
104
105
  if (!validation.ok) {
105
106
  throw new ToolValidationError({
@@ -125,6 +126,9 @@ var rgAvailabilityCache;
125
126
  function __resetRgDetectionForTests() {
126
127
  rgAvailabilityCache = void 0;
127
128
  }
129
+ function __setRgAvailableForTests(available) {
130
+ rgAvailabilityCache = available !== void 0 ? Promise.resolve(available) : void 0;
131
+ }
128
132
  function detectRg() {
129
133
  rgAvailabilityCache ??= new Promise((resolve) => {
130
134
  try {
@@ -143,7 +147,19 @@ function detectRg() {
143
147
  return rgAvailabilityCache;
144
148
  }
145
149
  async function* runRgStream(input, base, mode, limit, signal) {
146
- const args = ["--no-heading"];
150
+ if (signal.aborted) {
151
+ yield {
152
+ type: "final",
153
+ output: {
154
+ matches: [],
155
+ count: 0,
156
+ truncated: false,
157
+ used: "rg"
158
+ }
159
+ };
160
+ return;
161
+ }
162
+ const args = ["--no-heading", "--with-filename"];
147
163
  if (input.case_insensitive) args.push("-i");
148
164
  if (mode === "files_with_matches") args.push("-l");
149
165
  if (mode === "count") args.push("-c");
@@ -161,7 +177,14 @@ async function* runRgStream(input, base, mode, limit, signal) {
161
177
  )) {
162
178
  args.push("--ignore-file", gitignorePath);
163
179
  }
164
- if (input.glob) args.push("--glob", input.glob);
180
+ if (input.glob) {
181
+ const g = input.glob.trim();
182
+ if (g.startsWith("!") || g.startsWith("**/") || g.startsWith("*")) {
183
+ args.push("--glob", g);
184
+ } else {
185
+ args.push("--glob", `**/${g}`);
186
+ }
187
+ }
165
188
  args.push("--", input.pattern, base);
166
189
  const matches = [];
167
190
  let buf = "";
@@ -319,11 +342,22 @@ async function* runRgStream(input, base, mode, limit, signal) {
319
342
  }
320
343
  function parseRgCountLine(line) {
321
344
  const idx = line.lastIndexOf(":");
322
- if (idx === -1) return 0;
345
+ if (idx === -1) {
346
+ const direct = Number.parseInt(line.trim(), 10);
347
+ return Number.isFinite(direct) ? direct : 0;
348
+ }
323
349
  const n = Number.parseInt(line.slice(idx + 1), 10);
324
350
  return Number.isFinite(n) ? n : 0;
325
351
  }
326
352
  async function runNative(input, base, mode, limit, signal) {
353
+ if (signal.aborted) {
354
+ return {
355
+ matches: [],
356
+ count: 0,
357
+ truncated: false,
358
+ used: "native"
359
+ };
360
+ }
327
361
  const flags = input.case_insensitive ? "i" : "";
328
362
  const compiled = compileUserRegex(input.pattern, flags);
329
363
  if (!compiled.ok) {
@@ -337,13 +371,19 @@ async function runNative(input, base, mode, limit, signal) {
337
371
  const isGitIgnored = await loadGitignoreMatcher(base);
338
372
  const matches = [];
339
373
  const countOnlyFirstHit = mode === "count" && limit === 1;
340
- const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
374
+ const maxBytes = NATIVE_MAX_FILE_BYTES;
341
375
  let total = 0;
342
376
  let stopped = false;
343
- const scanFile = async (full, name) => {
377
+ const scanFile = async (full, name, rel) => {
344
378
  if (stopped || signal.aborted) return;
345
- if (globRe && !globRe.test(name) && !globRe.test(full)) return;
346
- if (globRe) globRe.lastIndex = 0;
379
+ if (globRe) {
380
+ globRe.lastIndex = 0;
381
+ const normRel = rel.replace(/\\/g, "/");
382
+ const normFull = full.replace(/\\/g, "/");
383
+ const matchesGlob = globRe.test(name) || globRe.test(normRel) || globRe.test(normFull);
384
+ globRe.lastIndex = 0;
385
+ if (!matchesGlob) return;
386
+ }
347
387
  try {
348
388
  const stat2 = await fs.stat(full);
349
389
  if (!stat2.isFile() || stat2.size > maxBytes || stopped || signal.aborted) return;
@@ -376,8 +416,8 @@ async function runNative(input, base, mode, limit, signal) {
376
416
  for (const rawLine of lines) {
377
417
  if (stopped || signal.aborted) break;
378
418
  lineNumber++;
379
- const ln = capSubject(rawLine);
380
- re.lastIndex = 0;
419
+ const ln = rawLine.length > 4096 ? capSubject(rawLine) : rawLine;
420
+ if (re.global || re.sticky) re.lastIndex = 0;
381
421
  if (!re.test(ln)) continue;
382
422
  fileHits++;
383
423
  total++;
@@ -386,10 +426,8 @@ async function runNative(input, base, mode, limit, signal) {
386
426
  } else if (mode === "files_with_matches") {
387
427
  if (fileHits === 1 && matches.length < limit) matches.push(full);
388
428
  break;
389
- } else if (fileHits === 1 && matches.length < limit) {
390
- matches.push(`${full}:${countOnlyFirstHit ? 1 : 0}`);
391
429
  }
392
- if (countOnlyFirstHit || mode !== "content" && matches.length >= limit) {
430
+ if (countOnlyFirstHit || mode === "content" && matches.length >= limit) {
393
431
  stopped = true;
394
432
  break;
395
433
  }
@@ -398,8 +436,8 @@ async function runNative(input, base, mode, limit, signal) {
398
436
  }
399
437
  if (!stopped && !signal.aborted && leftover.length > 0) {
400
438
  lineNumber++;
401
- const ln = capSubject(leftover);
402
- re.lastIndex = 0;
439
+ const ln = leftover.length > 4096 ? capSubject(leftover) : leftover;
440
+ if (re.global || re.sticky) re.lastIndex = 0;
403
441
  if (re.test(ln)) {
404
442
  fileHits++;
405
443
  total++;
@@ -407,21 +445,17 @@ async function runNative(input, base, mode, limit, signal) {
407
445
  if (matches.length < limit) matches.push(`${full}:${lineNumber}:${ln}`);
408
446
  } else if (mode === "files_with_matches") {
409
447
  if (matches.length < limit) matches.push(full);
410
- } else if (matches.length < limit) {
411
- matches.push(`${full}:${countOnlyFirstHit ? 1 : fileHits}`);
412
448
  }
413
449
  }
414
450
  }
415
451
  if (fileHits > 0) {
416
- if (mode === "count") {
417
- const idx = matches.findIndex((entry) => entry.startsWith(`${full}:`));
418
- if (idx !== -1) matches[idx] = `${full}:${fileHits}`;
452
+ if (mode === "count" && matches.length < limit) {
453
+ matches.push(`${full}:${fileHits}`);
419
454
  }
420
455
  if (mode === "files_with_matches" && matches.length >= limit) stopped = true;
421
456
  }
422
457
  if (mode === "content" && matches.length >= limit) stopped = true;
423
- if (mode === "count" && matches.length >= limit && (countOnlyFirstHit || mode !== "count"))
424
- stopped = true;
458
+ if (mode === "count" && matches.length >= limit && countOnlyFirstHit) stopped = true;
425
459
  } finally {
426
460
  await file.close();
427
461
  }
@@ -449,13 +483,13 @@ async function runNative(input, base, mode, limit, signal) {
449
483
  subdirs.push({ full, rel });
450
484
  } else if (e.isFile()) {
451
485
  if (isGitIgnored(rel, false)) continue;
452
- files.push({ full, name: e.name });
486
+ files.push({ full, name: e.name, rel });
453
487
  }
454
488
  }
455
489
  await mapWithConcurrency(
456
490
  files,
457
491
  NATIVE_SCAN_CONCURRENCY,
458
- ({ full, name }) => scanFile(full, name)
492
+ ({ full, name, rel }) => scanFile(full, name, rel)
459
493
  );
460
494
  await mapWithConcurrency(
461
495
  subdirs,
@@ -463,7 +497,13 @@ async function runNative(input, base, mode, limit, signal) {
463
497
  ({ full, rel }) => walk(full, rel)
464
498
  );
465
499
  };
466
- await walk(base, "");
500
+ const baseStat = await fs.stat(base).catch(() => null);
501
+ if (baseStat?.isFile()) {
502
+ const rel = input.path ? input.path.replace(/\\/g, "/") : path.basename(base);
503
+ await scanFile(base, path.basename(base), rel);
504
+ } else {
505
+ await walk(base, "");
506
+ }
467
507
  return {
468
508
  matches,
469
509
  count: total,
@@ -474,6 +514,7 @@ async function runNative(input, base, mode, limit, signal) {
474
514
 
475
515
  export {
476
516
  grepTool,
477
- __resetRgDetectionForTests
517
+ __resetRgDetectionForTests,
518
+ __setRgAvailableForTests
478
519
  };
479
- //# sourceMappingURL=chunk-N5O3FK5Q.js.map
520
+ //# sourceMappingURL=chunk-XSSFBS5I.js.map
@@ -1,16 +1,17 @@
1
1
  import {
2
2
  tryLegacyCodeOperation
3
- } from "./chunk-ZDUGI5BC.js";
3
+ } from "./chunk-I7FBZOEP.js";
4
4
  import {
5
5
  spawnStream
6
- } from "./chunk-ZHH3BEWU.js";
6
+ } from "./chunk-OYP27E3I.js";
7
7
  import {
8
8
  normalizeCommandOutput,
9
9
  safeResolveReal
10
- } from "./chunk-QLF74MSV.js";
10
+ } from "./chunk-ZUFHAHTE.js";
11
11
 
12
12
  // src/test.ts
13
13
  import * as path from "node:path";
14
+ import { ToolValidationError } from "@wrongstack/core/types";
14
15
  var testTool = {
15
16
  name: "test",
16
17
  category: "Code Quality",
@@ -57,6 +58,14 @@ var testTool = {
57
58
  async *executeStream(input, ctx, opts) {
58
59
  const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
59
60
  const signal = opts?.signal ?? ctx.signal ?? new AbortController().signal;
61
+ signal.throwIfAborted();
62
+ const VALID_RUNNERS = /* @__PURE__ */ new Set(["vitest", "jest", "mocha", "auto", "none"]);
63
+ if (input.runner !== void 0 && !VALID_RUNNERS.has(input.runner)) {
64
+ throw new ToolValidationError({
65
+ message: `test: unsupported runner "${input.runner}". Allowed runners: vitest, jest, mocha, auto, none`,
66
+ field: "runner"
67
+ });
68
+ }
60
69
  const runner = input.runner ?? "auto";
61
70
  if (runner === "auto") {
62
71
  const bridge = await tryLegacyCodeOperation("test", {
@@ -83,7 +92,7 @@ var testTool = {
83
92
  }
84
93
  }
85
94
  const detected = runner === "auto" ? await detectRunner(cwd) : runner;
86
- if (!detected) {
95
+ if (!detected || detected === "none") {
87
96
  yield {
88
97
  type: "final",
89
98
  output: {
@@ -115,7 +124,21 @@ var testTool = {
115
124
  };
116
125
  async function detectRunner(cwd) {
117
126
  const { stat } = await import("node:fs/promises");
118
- const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
127
+ const candidates = [
128
+ "vitest.config.ts",
129
+ "vitest.config.js",
130
+ "vitest.config.mjs",
131
+ "vitest.config.mts",
132
+ "vitest.config.cjs",
133
+ "vitest.config.cts",
134
+ "jest.config.ts",
135
+ "jest.config.js",
136
+ "jest.config.mjs",
137
+ "jest.config.cjs",
138
+ ".mocharc.json",
139
+ ".mocharc.js",
140
+ ".mocharc.cjs"
141
+ ];
119
142
  for (const f of candidates) {
120
143
  try {
121
144
  await stat(path.join(cwd, f));
@@ -129,7 +152,8 @@ async function detectRunner(cwd) {
129
152
  }
130
153
  function buildArgs(runner, input) {
131
154
  const args = [];
132
- const timeout = input.timeout ?? 3e4;
155
+ const rawTimeout = typeof input.timeout === "number" && Number.isFinite(input.timeout) && input.timeout > 0 ? Math.floor(input.timeout) : 3e4;
156
+ const timeout = Math.max(100, rawTimeout);
133
157
  switch (runner) {
134
158
  case "vitest":
135
159
  args.push(input.watch ? "watch" : "run");
@@ -152,8 +176,10 @@ function buildArgs(runner, input) {
152
176
  break;
153
177
  }
154
178
  if (input.files) {
155
- const files = Array.isArray(input.files) ? input.files : input.files.split(",");
156
- args.push("--", ...files.map((f) => f.trim()));
179
+ const files = (Array.isArray(input.files) ? input.files : input.files.split(",")).map((f) => f.trim().replace(/\\/g, "/")).filter(Boolean);
180
+ if (files.length > 0) {
181
+ args.push("--", ...files);
182
+ }
157
183
  }
158
184
  return args;
159
185
  }
@@ -175,7 +201,15 @@ function parseResult(runner, result, duration) {
175
201
  tests_run = Number.parseInt(suitesMatch?.[1] ?? "0", 10);
176
202
  passed = Number.parseInt(passedMatch?.[1] ?? "0", 10);
177
203
  failed = Number.parseInt(failedMatch?.[1] ?? "0", 10);
204
+ } else if (runner === "mocha") {
205
+ const passedMatch = out.match(/(\d+)\s+passing/);
206
+ const failedMatch = out.match(/(\d+)\s+failing/);
207
+ if (passedMatch?.[1]) passed = Number.parseInt(passedMatch[1], 10);
208
+ if (failedMatch?.[1]) failed = Number.parseInt(failedMatch[1], 10);
209
+ tests_run = passed + failed;
178
210
  }
211
+ const rawOutput = result.stdout && result.stderr ? `${result.stdout}
212
+ ${result.stderr}` : result.stdout || result.stderr || result.error || "";
179
213
  return {
180
214
  runner,
181
215
  exit_code: result.exitCode,
@@ -187,7 +221,7 @@ function parseResult(runner, result, duration) {
187
221
  // already parsed above and the FULL log is on disk (spool marker rides
188
222
  // the stdout tail). Failures keep the standard command-output cap so
189
223
  // the agent sees the failure details inline.
190
- output: normalizeCommandOutput(result.stdout || result.error || "", {
224
+ output: normalizeCommandOutput(rawOutput, {
191
225
  maxBytes: result.exitCode === 0 ? 4096 : void 0
192
226
  }),
193
227
  truncated: result.truncated
@@ -197,4 +231,4 @@ function parseResult(runner, result, duration) {
197
231
  export {
198
232
  testTool
199
233
  };
200
- //# sourceMappingURL=chunk-Y7QXSE7N.js.map
234
+ //# sourceMappingURL=chunk-YTKSNTT4.js.map
@@ -9,7 +9,7 @@ import {
9
9
  projectIndexServerBuildId,
10
10
  projectIndexServerEndpoint,
11
11
  projectIndexServerMetadataPath
12
- } from "./chunk-W4GAAI3M.js";
12
+ } from "./chunk-PV6HIZQD.js";
13
13
  import {
14
14
  CircuitOpenError,
15
15
  IndexTimeoutError,
@@ -24,7 +24,7 @@ import {
24
24
  searchService,
25
25
  statsService,
26
26
  symbolGraphService
27
- } from "./chunk-TFMVHYJI.js";
27
+ } from "./chunk-J43O3BOX.js";
28
28
  import {
29
29
  isIndexablePath
30
30
  } from "./chunk-YOHO4DVT.js";
@@ -1269,4 +1269,4 @@ export {
1269
1269
  checkCodebaseIndexServerHealth,
1270
1270
  ensureCodebaseIndexServer
1271
1271
  };
1272
- //# sourceMappingURL=chunk-MRKWZCHZ.js.map
1272
+ //# sourceMappingURL=chunk-Z5Q3TXRY.js.map
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  checkSyntax
3
- } from "./chunk-FQZOHLG3.js";
3
+ } from "./chunk-JSNSMG5R.js";
4
4
  import {
5
5
  enqueueReindex
6
- } from "./chunk-MRKWZCHZ.js";
6
+ } from "./chunk-Z5Q3TXRY.js";
7
7
  import {
8
8
  safeResolveReal,
9
9
  sha256hex,
10
10
  truncateDiffPayload
11
- } from "./chunk-QLF74MSV.js";
11
+ } from "./chunk-ZUFHAHTE.js";
12
12
 
13
13
  // src/edit.ts
14
14
  import * as fs from "node:fs/promises";
@@ -39,45 +39,55 @@ var FUZZY_MIN_SIMILARITY = 0.9;
39
39
  var FUZZY_AMBIGUITY_MARGIN = 0.05;
40
40
  var FUZZY_MAX_INTERIOR_CHARS = 2e3;
41
41
  function findLadderMatches(fileLf, oldLf) {
42
+ if (typeof fileLf !== "string" || typeof oldLf !== "string" || oldLf.length === 0) {
43
+ return void 0;
44
+ }
42
45
  const exact = [];
43
46
  let idx = fileLf.indexOf(oldLf);
47
+ let currentLine = 1;
48
+ let lastPos = 0;
44
49
  while (idx !== -1) {
45
- exact.push({ start: idx, end: idx + oldLf.length, startLine: lineAt(fileLf, idx) });
50
+ while (lastPos < idx) {
51
+ const nextNewline = fileLf.indexOf("\n", lastPos);
52
+ if (nextNewline === -1 || nextNewline >= idx) break;
53
+ currentLine++;
54
+ lastPos = nextNewline + 1;
55
+ }
56
+ exact.push({ start: idx, end: idx + oldLf.length, startLine: currentLine });
46
57
  idx = fileLf.indexOf(oldLf, idx + 1);
47
58
  }
48
59
  if (exact.length > 0) return { tier: "exact", matches: exact };
49
60
  const fileLines = fileLf.split("\n");
50
- const needleLines = oldLf.split("\n");
51
- if (needleLines.length > fileLines.length) return void 0;
61
+ const needleEndsWithNewline = oldLf.endsWith("\n");
62
+ const rawNeedleLines = oldLf.split("\n");
63
+ const needleLines = needleEndsWithNewline && rawNeedleLines[rawNeedleLines.length - 1] === "" ? rawNeedleLines.slice(0, -1) : rawNeedleLines;
64
+ if (needleLines.length === 0 || needleLines.length > fileLines.length) return void 0;
52
65
  const offsets = lineOffsets(fileLines);
53
66
  const fileTrimEnd = fileLines.map((l) => l.trimEnd());
54
67
  const needleTrimEnd = needleLines.map((l) => l.trimEnd());
55
- const trailing = windowScan(fileTrimEnd, needleTrimEnd, offsets, fileLines, (a, b) => a === b);
68
+ const trailing = windowScan(
69
+ fileTrimEnd,
70
+ needleTrimEnd,
71
+ offsets,
72
+ fileLines,
73
+ (a, b) => a === b,
74
+ needleEndsWithNewline
75
+ );
56
76
  if (trailing.length > 0) return { tier: "trailing-whitespace", matches: trailing };
57
77
  const normalizedLen = needleTrimEnd.reduce((n, l) => n + l.trimStart().length, 0);
58
78
  if (normalizedLen < MIN_NORMALIZED_NEEDLE_CHARS) return void 0;
59
79
  const fileTrimmed = fileTrimEnd.map((l) => l.trimStart());
60
80
  const needleTrimmed = needleTrimEnd.map((l) => l.trimStart());
61
- const normalized = windowScan(fileTrimmed, needleTrimmed, offsets, fileLines, (a, b) => a === b);
81
+ const normalized = windowScan(
82
+ fileTrimmed,
83
+ needleTrimmed,
84
+ offsets,
85
+ fileLines,
86
+ (a, b) => a === b,
87
+ needleEndsWithNewline
88
+ );
62
89
  if (normalized.length > 0) return { tier: "whitespace-normalized", matches: normalized };
63
- return fuzzyScan(fileLines, needleLines, offsets);
64
- }
65
- function lineAt(text, pos) {
66
- if (pos < 512) {
67
- let line2 = 1;
68
- for (let i = 0; i < pos; i++) {
69
- if (text.charCodeAt(i) === 10) line2++;
70
- }
71
- return line2;
72
- }
73
- let line = 1;
74
- let search = 0;
75
- while (true) {
76
- const idx = text.indexOf("\n", search);
77
- if (idx === -1 || idx >= pos) return line;
78
- search = idx + 1;
79
- line++;
80
- }
90
+ return fuzzyScan(fileLines, needleLines, offsets, needleEndsWithNewline);
81
91
  }
82
92
  function lineOffsets(lines) {
83
93
  const out = new Array(lines.length);
@@ -88,15 +98,17 @@ function lineOffsets(lines) {
88
98
  }
89
99
  return out;
90
100
  }
91
- function windowToMatch(fileLines, offsets, start, windowLen) {
101
+ function windowToMatch(fileLines, offsets, start, windowLen, includeTrailingNewline = false) {
92
102
  const lastLine = start + windowLen - 1;
103
+ const lineEnd = offsets[lastLine] + fileLines[lastLine].length;
104
+ const end = includeTrailingNewline && lastLine < fileLines.length - 1 ? lineEnd + 1 : lineEnd;
93
105
  return {
94
106
  start: offsets[start],
95
- end: offsets[lastLine] + fileLines[lastLine].length,
107
+ end,
96
108
  startLine: start + 1
97
109
  };
98
110
  }
99
- function windowScan(comparisonLines, needleLines, offsets, originalLines, eq) {
111
+ function windowScan(comparisonLines, needleLines, offsets, originalLines, eq, includeTrailingNewline = false) {
100
112
  const n = needleLines.length;
101
113
  const out = [];
102
114
  for (let i = 0; i + n <= comparisonLines.length; i++) {
@@ -108,28 +120,32 @@ function windowScan(comparisonLines, needleLines, offsets, originalLines, eq) {
108
120
  }
109
121
  }
110
122
  if (all) {
111
- out.push(windowToMatch(originalLines, offsets, i, n));
123
+ out.push(windowToMatch(originalLines, offsets, i, n, includeTrailingNewline));
112
124
  i += n - 1;
113
125
  }
114
126
  }
115
127
  return out;
116
128
  }
117
- function fuzzyScan(fileLines, needleLines, offsets) {
129
+ function fuzzyScan(fileLines, needleLines, offsets, includeTrailingNewline = false) {
118
130
  const n = needleLines.length;
119
131
  if (n < 3) return void 0;
120
132
  const firstNeedle = needleLines[0].trim();
121
133
  const lastNeedle = needleLines[n - 1].trim();
122
134
  const needleInterior = needleLines.slice(1, -1).map((l) => l.trim()).join("\n");
123
135
  if (needleInterior.length > FUZZY_MAX_INTERIOR_CHARS) return void 0;
136
+ const fileTrimmed = fileLines.map((l) => l.trim());
124
137
  const candidates = [];
125
138
  for (let i = 0; i + n <= fileLines.length; i++) {
126
- if (fileLines[i].trim() !== firstNeedle) continue;
127
- if (fileLines[i + n - 1].trim() !== lastNeedle) continue;
128
- const windowInterior = fileLines.slice(i + 1, i + n - 1).map((l) => l.trim()).join("\n");
139
+ if (fileTrimmed[i] !== firstNeedle) continue;
140
+ if (fileTrimmed[i + n - 1] !== lastNeedle) continue;
141
+ const windowInterior = fileTrimmed.slice(i + 1, i + n - 1).join("\n");
129
142
  if (windowInterior.length > FUZZY_MAX_INTERIOR_CHARS) continue;
130
143
  const score = similarity(needleInterior, windowInterior);
131
144
  if (score >= FUZZY_MIN_SIMILARITY) {
132
- candidates.push({ match: windowToMatch(fileLines, offsets, i, n), score });
145
+ candidates.push({
146
+ match: windowToMatch(fileLines, offsets, i, n, includeTrailingNewline),
147
+ score
148
+ });
133
149
  }
134
150
  }
135
151
  if (candidates.length === 0) return void 0;
@@ -150,21 +166,29 @@ function similarity(a, b) {
150
166
  if (a === b) return 1;
151
167
  const max = Math.max(a.length, b.length);
152
168
  if (max === 0) return 1;
153
- if (Math.abs(a.length - b.length) / max > 1 - FUZZY_MIN_SIMILARITY + 0.05) {
154
- return 1 - Math.abs(a.length - b.length) / max;
169
+ const lenDiff = Math.abs(a.length - b.length);
170
+ if (lenDiff / max > 1 - FUZZY_MIN_SIMILARITY) {
171
+ return 1 - lenDiff / max;
155
172
  }
156
173
  return 1 - levenshtein(a, b) / max;
157
174
  }
158
175
  function levenshtein(a, b) {
159
176
  if (a.length === 0) return b.length;
160
177
  if (b.length === 0) return a.length;
161
- let prev = new Array(b.length + 1);
162
- let cur = new Array(b.length + 1);
163
- for (let j = 0; j <= b.length; j++) prev[j] = j;
164
- for (let i = 1; i <= a.length; i++) {
178
+ if (a.length < b.length) {
179
+ const tmp = a;
180
+ a = b;
181
+ b = tmp;
182
+ }
183
+ const bLen = b.length;
184
+ const aLen = a.length;
185
+ let prev = new Int32Array(bLen + 1);
186
+ let cur = new Int32Array(bLen + 1);
187
+ for (let j = 0; j <= bLen; j++) prev[j] = j;
188
+ for (let i = 1; i <= aLen; i++) {
165
189
  cur[0] = i;
166
190
  const ca = a.charCodeAt(i - 1);
167
- for (let j = 1; j <= b.length; j++) {
191
+ for (let j = 1; j <= bLen; j++) {
168
192
  const cost = ca === b.charCodeAt(j - 1) ? 0 : 1;
169
193
  cur[j] = Math.min(
170
194
  prev[j] + 1,
@@ -172,9 +196,11 @@ function levenshtein(a, b) {
172
196
  prev[j - 1] + cost
173
197
  );
174
198
  }
175
- [prev, cur] = [cur, prev];
199
+ const tmp = prev;
200
+ prev = cur;
201
+ cur = tmp;
176
202
  }
177
- return prev[b.length];
203
+ return prev[bLen];
178
204
  }
179
205
  function firstLineIndent(text) {
180
206
  for (const line of text.split("\n")) {
@@ -189,14 +215,17 @@ function adjustIndent(newLf, fromIndent, toIndent) {
189
215
  if (toIndent.startsWith(fromIndent)) {
190
216
  const extra = toIndent.slice(fromIndent.length);
191
217
  return {
192
- text: lines.map((l) => l.trim() === "" ? l : extra + l).join("\n"),
218
+ text: lines.map((l) => l.trim() === "" ? "" : extra + l).join("\n"),
193
219
  adjusted: true
194
220
  };
195
221
  }
196
222
  if (fromIndent.startsWith(toIndent)) {
197
223
  const remove = fromIndent.slice(toIndent.length);
198
224
  return {
199
- text: lines.map((l) => l.startsWith(remove) ? l.slice(remove.length) : l).join("\n"),
225
+ text: lines.map((l) => {
226
+ if (l.trim() === "") return "";
227
+ return l.startsWith(remove) ? l.slice(remove.length) : l;
228
+ }).join("\n"),
200
229
  adjusted: true
201
230
  };
202
231
  }
@@ -204,34 +233,50 @@ function adjustIndent(newLf, fromIndent, toIndent) {
204
233
  }
205
234
  function nearestMatchHint(fileLf, oldLf) {
206
235
  const fileLines = fileLf.split("\n");
207
- const needleLines = oldLf.split("\n");
208
- if (needleLines.length > fileLines.length) return void 0;
236
+ const rawNeedle = oldLf.split("\n");
237
+ const needleLines = oldLf.endsWith("\n") && rawNeedle[rawNeedle.length - 1] === "" ? rawNeedle.slice(0, -1) : rawNeedle;
238
+ const n = needleLines.length;
239
+ if (n === 0 || n > fileLines.length) return void 0;
209
240
  const needleTrimmed = needleLines.map((l) => l.trim());
241
+ const fileTrimmed = fileLines.map((l) => l.trim());
210
242
  let bestScore = 0;
211
243
  let bestStart = -1;
212
- for (let i = 0; i + needleLines.length <= fileLines.length; i++) {
244
+ const maxI = fileLines.length - n;
245
+ for (let i = 0; i <= maxI; i++) {
213
246
  let sum = 0;
214
- for (let j = 0; j < needleLines.length; j++) {
215
- sum += prefixSimilarity(needleTrimmed[j], fileLines[i + j].trim());
247
+ let possible = true;
248
+ for (let j = 0; j < n; j++) {
249
+ sum += lineSimilarity(needleTrimmed[j], fileTrimmed[i + j]);
250
+ if (sum + (n - 1 - j) <= bestScore * n) {
251
+ possible = false;
252
+ break;
253
+ }
216
254
  }
217
- const score = sum / needleLines.length;
218
- if (score > bestScore) {
219
- bestScore = score;
220
- bestStart = i;
255
+ if (possible) {
256
+ const score = sum / n;
257
+ if (score > bestScore) {
258
+ bestScore = score;
259
+ bestStart = i;
260
+ }
221
261
  }
222
262
  }
223
263
  if (bestStart === -1 || bestScore < 0.5) return void 0;
224
- const snippet = fileLines.slice(bestStart, bestStart + Math.min(3, needleLines.length)).map((l) => l.length > 120 ? `${l.slice(0, 120)}\u2026` : l).join("\n");
264
+ const snippet = fileLines.slice(bestStart, bestStart + Math.min(3, n)).map((l) => l.length > 120 ? `${l.slice(0, 120)}\u2026` : l).join("\n");
225
265
  return { line: bestStart + 1, snippet };
226
266
  }
227
- function prefixSimilarity(a, b) {
267
+ function lineSimilarity(a, b) {
228
268
  if (a === b) return 1;
229
269
  const max = Math.max(a.length, b.length);
230
270
  if (max === 0) return 1;
271
+ if (a.includes(b) || b.includes(a)) {
272
+ return Math.min(a.length, b.length) / max;
273
+ }
231
274
  let p = 0;
232
275
  const lim = Math.min(a.length, b.length);
233
276
  while (p < lim && a.charCodeAt(p) === b.charCodeAt(p)) p++;
234
- return p / max;
277
+ let s = 0;
278
+ while (s < lim - p && a.charCodeAt(a.length - 1 - s) === b.charCodeAt(b.length - 1 - s)) s++;
279
+ return (p + s) / max;
235
280
  }
236
281
 
237
282
  // src/edit.ts
@@ -473,4 +518,4 @@ Compare this against your old_string and retry with the file's actual text.` : "
473
518
  export {
474
519
  editTool
475
520
  };
476
- //# sourceMappingURL=chunk-PRXZOIEW.js.map
521
+ //# sourceMappingURL=chunk-Z6IHMJMC.js.map
@@ -1,8 +1,10 @@
1
1
  // src/_concurrency.ts
2
2
  async function mapWithConcurrency(items, limit, fn) {
3
- if (items.length === 0) return [];
3
+ if (!Array.isArray(items) || items.length === 0) return [];
4
+ if (typeof fn !== "function") return [];
4
5
  if (items.length === 1) return [await fn(items[0])];
5
- const effectiveLimit = Math.max(1, Math.min(limit | 0, items.length));
6
+ const parsedLimit = Number.isFinite(limit) ? Math.floor(limit) : items.length;
7
+ const effectiveLimit = Math.max(1, Math.min(parsedLimit, items.length));
6
8
  const results = new Array(items.length);
7
9
  let nextIndex = 0;
8
10
  const worker = async () => {
@@ -19,4 +21,4 @@ async function mapWithConcurrency(items, limit, fn) {
19
21
  export {
20
22
  mapWithConcurrency
21
23
  };
22
- //# sourceMappingURL=chunk-NWZ6XGBJ.js.map
24
+ //# sourceMappingURL=chunk-ZBMANLYH.js.map