@wrongstack/tools 0.318.0 → 0.319.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 (101) hide show
  1. package/dist/_edit-match.d.ts +1 -3
  2. package/dist/_util.d.ts +20 -0
  3. package/dist/audit.js +4 -4
  4. package/dist/bash.js +2 -2
  5. package/dist/builtin.d.ts +10 -0
  6. package/dist/builtin.js +46 -44
  7. package/dist/{chunk-TKA4VEX2.js → chunk-2OXHS3KZ.js} +17 -5
  8. package/dist/{chunk-X5K3U6GI.js → chunk-3M7LLN7W.js} +71 -7
  9. package/dist/{chunk-4AEZIFOW.js → chunk-5EUJF3WC.js} +36 -8
  10. package/dist/{chunk-CVALEVGA.js → chunk-64VIJPFV.js} +34 -13
  11. package/dist/{chunk-ZE6KLBUO.js → chunk-6AAOT4PW.js} +58 -10
  12. package/dist/{chunk-A6BRWIXN.js → chunk-6IEBD27U.js} +20 -5
  13. package/dist/{chunk-HTYEO67J.js → chunk-AKHYMB4V.js} +36 -12
  14. package/dist/{chunk-4E42D7UF.js → chunk-ATSKPZUK.js} +2 -2
  15. package/dist/{chunk-H6AWAZ2Y.js → chunk-B4HTSV6Z.js} +57 -6
  16. package/dist/{chunk-5ZYEGP2P.js → chunk-BK732J7I.js} +72 -27
  17. package/dist/{chunk-WI3VDXHD.js → chunk-BSJSNVGA.js} +108 -19
  18. package/dist/{chunk-UKGMDCXT.js → chunk-C2DQKVLO.js} +61 -28
  19. package/dist/{chunk-CIFAIIHI.js → chunk-CFLJFDYV.js} +100 -58
  20. package/dist/{chunk-JJQB6R7Y.js → chunk-CMOUCKGC.js} +6 -6
  21. package/dist/{chunk-MX25MTDJ.js → chunk-DC4KMPJO.js} +2 -2
  22. package/dist/{chunk-VZLX2JZG.js → chunk-DP6CXMJZ.js} +3 -3
  23. package/dist/{chunk-3QQSBUXR.js → chunk-EADULOZV.js} +36 -12
  24. package/dist/{chunk-6RNIFLG4.js → chunk-ELRRTWNP.js} +38 -6
  25. package/dist/{chunk-CPSAF2AU.js → chunk-FQNXYA6W.js} +45 -6
  26. package/dist/{chunk-ZZT45EHF.js → chunk-GMZYW52L.js} +3 -3
  27. package/dist/{chunk-ZLTFLC25.js → chunk-IIF5HIVF.js} +2 -2
  28. package/dist/{chunk-EMRGN4KM.js → chunk-IU4DV2PA.js} +18 -8
  29. package/dist/{chunk-AGUSOFNI.js → chunk-IWS56OY7.js} +30 -7
  30. package/dist/{chunk-MRZOVQ5D.js → chunk-IYKXK356.js} +98 -11
  31. package/dist/{chunk-6GZPSWP3.js → chunk-JDPQOR7C.js} +14 -7
  32. package/dist/{chunk-BLCSYKNN.js → chunk-KY4PXYSO.js} +10 -15
  33. package/dist/{chunk-WJYXMB2R.js → chunk-LOBRWKCR.js} +63 -14
  34. package/dist/{chunk-J5SKD6LN.js → chunk-MNP3GE7H.js} +38 -27
  35. package/dist/{chunk-S2GBLQ5S.js → chunk-O44IMQCN.js} +31 -7
  36. package/dist/{chunk-P7ZQ2FAJ.js → chunk-OG2BEUBY.js} +69 -34
  37. package/dist/{chunk-K23JNFE3.js → chunk-OTINMOKK.js} +233 -45
  38. package/dist/{chunk-PQTONQ4P.js → chunk-QPNYDH5B.js} +19 -5
  39. package/dist/{chunk-UE3UBMCP.js → chunk-RLMX75EM.js} +19 -8
  40. package/dist/{chunk-3U4H6ZRF.js → chunk-RNWUCZ2U.js} +53 -25
  41. package/dist/{chunk-YF3D5INN.js → chunk-RTZWBNTY.js} +121 -91
  42. package/dist/{chunk-SOG7XIRF.js → chunk-TTZOK6IY.js} +2 -2
  43. package/dist/{chunk-6GVXZCQV.js → chunk-UDVKRX5R.js} +2 -2
  44. package/dist/{chunk-CPVLVQYT.js → chunk-UQQS3VNT.js} +43 -9
  45. package/dist/{chunk-VB3MJGCV.js → chunk-VD2LV5WO.js} +82 -13
  46. package/dist/{chunk-5WNDJHT6.js → chunk-VPALDFYM.js} +2 -2
  47. package/dist/{chunk-FJEQZHZN.js → chunk-W2CECGTG.js} +24 -14
  48. package/dist/{chunk-CISOLTOP.js → chunk-X5FOFGXX.js} +72 -33
  49. package/dist/{chunk-CWALNI6K.js → chunk-X6KXNHUO.js} +1 -1
  50. package/dist/{chunk-EEOFQDT6.js → chunk-XRNN44QY.js} +46 -2
  51. package/dist/{chunk-EOOFNYIT.js → chunk-YOKD4GWJ.js} +67 -27
  52. package/dist/{chunk-D33S4RQG.js → chunk-ZUFHAHTE.js} +18 -6
  53. package/dist/codebase-index/generic-parser.d.ts +8 -1
  54. package/dist/codebase-index/index.js +9 -9
  55. package/dist/codebase-index/parser-worker-script.js +3 -3
  56. package/dist/codebase-index/project-server.js +5 -5
  57. package/dist/codebase-index/worker.js +4 -4
  58. package/dist/codebase-index/writer-refs.d.ts +1 -1
  59. package/dist/design.d.ts +1 -1
  60. package/dist/design.js +1 -1
  61. package/dist/diff.js +3 -2
  62. package/dist/document.js +2 -2
  63. package/dist/e2e.js +2 -2
  64. package/dist/edit.js +8 -8
  65. package/dist/exec.js +2 -2
  66. package/dist/fetch.js +2 -2
  67. package/dist/format.js +4 -4
  68. package/dist/{generic-parser-VUXDIAOZ.js → generic-parser-WFMIRHBX.js} +2 -2
  69. package/dist/git.js +2 -2
  70. package/dist/glob.js +2 -2
  71. package/dist/grep.d.ts +2 -0
  72. package/dist/grep.js +4 -2
  73. package/dist/index.js +46 -46
  74. package/dist/install.js +4 -4
  75. package/dist/json.js +2 -2
  76. package/dist/kanban-tool-types.d.ts +2 -0
  77. package/dist/kanban.js +2 -2
  78. package/dist/languages/index.js +2 -2
  79. package/dist/{legacy-bridge-SFNXIRAP.js → legacy-bridge-3T7SR4SK.js} +4 -4
  80. package/dist/lint.js +4 -4
  81. package/dist/logs.js +2 -2
  82. package/dist/outdated.js +4 -2
  83. package/dist/pack.js +45 -45
  84. package/dist/{parser-dispatch-X62UWXJS.js → parser-dispatch-QGC4HSP5.js} +4 -4
  85. package/dist/patch.js +8 -8
  86. package/dist/plan.js +4 -4
  87. package/dist/{py-parser-TDXSQZBC.js → py-parser-RFWG2HYI.js} +3 -3
  88. package/dist/read.js +8 -8
  89. package/dist/replace.js +9 -8
  90. package/dist/scaffold.js +2 -2
  91. package/dist/search.js +1 -1
  92. package/dist/session-kanban.js +1 -1
  93. package/dist/task.js +4 -4
  94. package/dist/test.js +4 -4
  95. package/dist/todo.js +3 -3
  96. package/dist/tool-tier.js +46 -46
  97. package/dist/{tree-sitter-parser-AIERW3YI.js → tree-sitter-parser-GILYQLS4.js} +2 -2
  98. package/dist/tree.js +2 -2
  99. package/dist/typecheck.js +4 -4
  100. package/dist/write.js +8 -8
  101. package/package.json +5 -5
@@ -1,16 +1,17 @@
1
1
  import {
2
2
  tryLegacyCodeOperation
3
- } from "./chunk-4E42D7UF.js";
3
+ } from "./chunk-ATSKPZUK.js";
4
4
  import {
5
5
  spawnStream
6
6
  } from "./chunk-ZHH3BEWU.js";
7
7
  import {
8
8
  normalizeCommandOutput,
9
9
  safeResolveReal
10
- } from "./chunk-D33S4RQG.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-CPVLVQYT.js.map
234
+ //# sourceMappingURL=chunk-UQQS3VNT.js.map
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-Q5JPPCZC.js";
5
5
  import {
6
6
  safeResolveReal
7
- } from "./chunk-D33S4RQG.js";
7
+ } from "./chunk-ZUFHAHTE.js";
8
8
 
9
9
  // src/json.ts
10
10
  import * as fs from "node:fs/promises";
@@ -19,9 +19,18 @@ var JsonFileTooLargeError = class extends Error {
19
19
  this.name = "JsonFileTooLargeError";
20
20
  }
21
21
  };
22
+ var JsonFileIsDirectoryError = class extends Error {
23
+ constructor(filePath) {
24
+ super(`json: "${filePath}" is a directory, not a file`);
25
+ this.name = "JsonFileIsDirectoryError";
26
+ }
27
+ };
22
28
  async function readJsonFileBounded(filePath, ctx) {
23
29
  const resolved = await safeResolveReal(filePath, ctx);
24
30
  const stat2 = await fs.stat(resolved);
31
+ if (stat2.isDirectory()) {
32
+ throw new JsonFileIsDirectoryError(filePath);
33
+ }
25
34
  if (stat2.size > MAX_JSON_FILE_BYTES) {
26
35
  throw new JsonFileTooLargeError(filePath, stat2.size);
27
36
  }
@@ -82,8 +91,26 @@ var jsonTool = {
82
91
  }
83
92
  }
84
93
  },
85
- async execute(input, ctx) {
94
+ async execute(input, ctx, opts) {
95
+ const signal = opts?.signal ?? ctx?.signal;
96
+ signal?.throwIfAborted();
86
97
  const action = input.action ?? "parse";
98
+ const ALLOWED_ACTIONS = /* @__PURE__ */ new Set([
99
+ "parse",
100
+ "query",
101
+ "validate",
102
+ "transform",
103
+ "merge"
104
+ ]);
105
+ if (input.action !== void 0 && !ALLOWED_ACTIONS.has(input.action)) {
106
+ return {
107
+ data: null,
108
+ formatted: "",
109
+ type: "unknown",
110
+ action: String(input.action),
111
+ error: `Unknown action "${input.action}". Allowed actions: parse, query, validate, transform, merge`
112
+ };
113
+ }
87
114
  switch (action) {
88
115
  case "query":
89
116
  return executeQuery(input, ctx);
@@ -111,7 +138,7 @@ async function executeParse(input, ctx) {
111
138
  formatted: "",
112
139
  type: "unknown",
113
140
  action: "parse",
114
- error: error instanceof JsonFileTooLargeError ? error.message : "Could not read file"
141
+ error: error instanceof JsonFileTooLargeError || error instanceof JsonFileIsDirectoryError ? error.message : "Could not read file"
115
142
  };
116
143
  }
117
144
  } else if (input.data) {
@@ -188,7 +215,7 @@ async function executeQuery(input, ctx) {
188
215
  formatted: "",
189
216
  type: "unknown",
190
217
  action: "query",
191
- error: error instanceof JsonFileTooLargeError ? error.message : "Could not read/parse file"
218
+ error: error instanceof JsonFileTooLargeError || error instanceof JsonFileIsDirectoryError ? error.message : "Could not read/parse file"
192
219
  };
193
220
  }
194
221
  } else if (input.data) {
@@ -254,7 +281,7 @@ async function executeValidate(input, ctx) {
254
281
  formatted: "",
255
282
  type: "unknown",
256
283
  action: "validate",
257
- error: error instanceof JsonFileTooLargeError ? error.message : "Could not read/parse file"
284
+ error: error instanceof JsonFileTooLargeError || error instanceof JsonFileIsDirectoryError ? error.message : "Could not read/parse file"
258
285
  };
259
286
  }
260
287
  } else if (input.data) {
@@ -320,7 +347,7 @@ async function executeTransform(input, ctx) {
320
347
  formatted: "",
321
348
  type: "unknown",
322
349
  action: "transform",
323
- error: error instanceof JsonFileTooLargeError ? error.message : "Could not read/parse file"
350
+ error: error instanceof JsonFileTooLargeError || error instanceof JsonFileIsDirectoryError ? error.message : "Could not read/parse file"
324
351
  };
325
352
  }
326
353
  } else if (input.data) {
@@ -409,6 +436,9 @@ function jmespathSearch(data, query) {
409
436
  const dotMatch = query.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(?:\.(.+))?$/);
410
437
  if (dotMatch) {
411
438
  const key = dotMatch[1];
439
+ if (key === "__proto__" || key === "prototype" || key === "constructor") {
440
+ return void 0;
441
+ }
412
442
  const rest = dotMatch[2];
413
443
  const val = data?.[key];
414
444
  if (rest === void 0) return val;
@@ -539,9 +569,23 @@ function validateJsonSchema(data, schema) {
539
569
  if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
540
570
  errors.push(`${path}: above maximum ${s["maximum"]}`);
541
571
  }
542
- if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
543
- for (let i = 0; i < value.length; i++) {
544
- check(value[i], s["items"], `${path}[${i}]`);
572
+ if (Array.isArray(value) && s["items"]) {
573
+ if (Array.isArray(s["items"])) {
574
+ for (let i = 0; i < Math.min(value.length, s["items"].length); i++) {
575
+ check(value[i], s["items"][i], `${path}[${i}]`);
576
+ }
577
+ } else if (typeof s["items"] === "object" && s["items"] !== null) {
578
+ for (let i = 0; i < value.length; i++) {
579
+ check(value[i], s["items"], `${path}[${i}]`);
580
+ }
581
+ }
582
+ }
583
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && Array.isArray(s["required"])) {
584
+ const obj = value;
585
+ for (const req of s["required"]) {
586
+ if (typeof req === "string" && !(req in obj)) {
587
+ errors.push(`${path}: missing required property "${req}"`);
588
+ }
545
589
  }
546
590
  }
547
591
  if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
@@ -559,6 +603,9 @@ function simpleQuery(data, path) {
559
603
  let current = data;
560
604
  for (const part of parts) {
561
605
  if (current === null || current === void 0) return void 0;
606
+ if (part === "__proto__" || part === "prototype" || part === "constructor") {
607
+ return void 0;
608
+ }
562
609
  const idx = Number(part);
563
610
  if (!Number.isNaN(idx) && Array.isArray(current)) {
564
611
  current = current[idx];
@@ -585,7 +632,7 @@ function toYaml(data, indent = 0) {
585
632
  if (typeof data === "boolean") return String(data) + "\n";
586
633
  if (typeof data === "number") return String(data) + "\n";
587
634
  if (typeof data === "string") {
588
- if (data.includes("\n") || data.includes(":") || data.includes("#")) {
635
+ if (data.includes("\n") || data.includes(":") || data.includes("#") || data.startsWith("-")) {
589
636
  return `"${data.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"
590
637
  `;
591
638
  }
@@ -594,12 +641,34 @@ function toYaml(data, indent = 0) {
594
641
  if (Array.isArray(data)) {
595
642
  if (data.length === 0) return "[]\n";
596
643
  const prefix = " ".repeat(indent);
597
- return data.map((item) => `${prefix}- ${toYaml(item, indent + 1).trimStart()}`).join("");
644
+ return data.map((item) => {
645
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
646
+ const itemEntries = Object.entries(item);
647
+ if (itemEntries.length === 0) return `${prefix}- {}
648
+ `;
649
+ const [firstK, firstV] = itemEntries[0];
650
+ const rest = itemEntries.slice(1);
651
+ let itemYaml = `${prefix}- ${firstK}: ${toYaml(firstV, indent + 2).trimStart()}`;
652
+ for (const [k, v] of rest) {
653
+ itemYaml += `${prefix} ${k}: ${toYaml(v, indent + 2)}`;
654
+ }
655
+ return itemYaml;
656
+ }
657
+ return `${prefix}- ${toYaml(item, indent + 1).trimStart()}`;
658
+ }).join("");
598
659
  }
599
660
  if (typeof data === "object") {
600
661
  const prefix = " ".repeat(indent);
601
662
  const entries = Object.entries(data);
602
- return entries.map(([k, v]) => `${prefix}${k}: ${toYaml(v, indent + 1)}`).join("");
663
+ if (entries.length === 0) return "{}\n";
664
+ return entries.map(([k, v]) => {
665
+ const safeKey = /[:#\s]/.test(k) ? `"${k.replace(/"/g, '\\"')}"` : k;
666
+ if (typeof v === "object" && v !== null && (Array.isArray(v) ? v.length > 0 : Object.keys(v).length > 0)) {
667
+ return `${prefix}${safeKey}:
668
+ ${toYaml(v, indent + 1)}`;
669
+ }
670
+ return `${prefix}${safeKey}: ${toYaml(v, indent + 1)}`;
671
+ }).join("");
603
672
  }
604
673
  return String(data) + "\n";
605
674
  }
@@ -607,4 +676,4 @@ function toYaml(data, indent = 0) {
607
676
  export {
608
677
  jsonTool
609
678
  };
610
- //# sourceMappingURL=chunk-VB3MJGCV.js.map
679
+ //# sourceMappingURL=chunk-VD2LV5WO.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  safeResolveReal
3
- } from "./chunk-D33S4RQG.js";
3
+ } from "./chunk-ZUFHAHTE.js";
4
4
 
5
5
  // src/e2e.ts
6
6
  import { open, readdir } from "node:fs/promises";
@@ -422,4 +422,4 @@ export {
422
422
  discoverE2EProjects,
423
423
  e2ePlanTool
424
424
  };
425
- //# sourceMappingURL=chunk-5WNDJHT6.js.map
425
+ //# sourceMappingURL=chunk-VPALDFYM.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  applySessionKanbanTaskToSource,
3
3
  taskFileToSerializedGraph
4
- } from "./chunk-CWALNI6K.js";
4
+ } from "./chunk-X6KXNHUO.js";
5
5
 
6
6
  // src/kanban.ts
7
7
  import { stripLifecycleIssues as stripLifecycleIssues2 } from "@wrongstack/kanban";
@@ -349,7 +349,7 @@ async function handleKanbanBoardAction(projectRoot, input, ctx) {
349
349
  assignedAgent: input.agentId,
350
350
  status: input.status,
351
351
  priority: input.priority,
352
- label: input.labels?.[0],
352
+ label: input.label ?? input.labels?.[0],
353
353
  chainId: input.chainId
354
354
  });
355
355
  return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
@@ -360,7 +360,7 @@ async function handleKanbanBoardAction(projectRoot, input, ctx) {
360
360
  boardId: input.boardId,
361
361
  assignedAgent: input.agentId,
362
362
  priority: input.priority,
363
- label: input.labels?.[0],
363
+ label: input.label ?? input.labels?.[0],
364
364
  chainId: input.chainId,
365
365
  limit: input.limit
366
366
  });
@@ -373,7 +373,7 @@ async function handleKanbanBoardAction(projectRoot, input, ctx) {
373
373
  assignedAgent: input.agentId,
374
374
  status: input.status,
375
375
  priority: input.priority,
376
- label: input.labels?.[0],
376
+ label: input.label ?? input.labels?.[0],
377
377
  chainId: input.chainId
378
378
  });
379
379
  return {
@@ -2070,29 +2070,39 @@ var kanbanTool = {
2070
2070
  timeoutMs: 3e4,
2071
2071
  inputSchema: KANBAN_INPUT_SCHEMA,
2072
2072
  async execute(input, ctx, _opts) {
2073
+ const signal = _opts?.signal ?? ctx.signal ?? new AbortController().signal;
2074
+ if (signal.aborted) return fail("Operation aborted.");
2075
+ if (!input || typeof input !== "object" || typeof input.action !== "string" || !input.action.trim()) {
2076
+ return fail("kanban: action is required and must be a non-empty string.");
2077
+ }
2078
+ const normalizedInput = {
2079
+ ...input,
2080
+ action: input.action.trim()
2081
+ };
2073
2082
  const projectRoot = ctx.projectRoot;
2074
2083
  if (!projectRoot) return fail("No project root is available.");
2075
- const withPresence = createKanbanPresenceWrapper(projectRoot, input, ctx);
2084
+ const withPresence = createKanbanPresenceWrapper(projectRoot, normalizedInput, ctx);
2076
2085
  try {
2077
2086
  const result = await (async () => {
2078
- const decompositionResult = await handleKanbanDecompositionAction(projectRoot, input, ctx);
2087
+ const decompositionResult = await handleKanbanDecompositionAction(projectRoot, normalizedInput, ctx);
2079
2088
  if (decompositionResult !== void 0) return decompositionResult;
2080
- const boardResult = await handleKanbanBoardAction(projectRoot, input, ctx);
2089
+ const boardResult = await handleKanbanBoardAction(projectRoot, normalizedInput, ctx);
2081
2090
  if (boardResult !== void 0) return boardResult;
2082
- const lifecycleResult = await handleKanbanLifecycleAction(projectRoot, input, ctx);
2091
+ const lifecycleResult = await handleKanbanLifecycleAction(projectRoot, normalizedInput, ctx);
2083
2092
  if (lifecycleResult !== void 0) return lifecycleResult;
2084
2093
  const contractResult = await handleKanbanContractAction(
2085
2094
  projectRoot,
2086
- input,
2087
- input.author ?? input.agentId,
2095
+ normalizedInput,
2096
+ normalizedInput.author ?? normalizedInput.agentId,
2088
2097
  ctx.eventSessionId?.() ?? ctx.session?.id ?? "default-session"
2089
2098
  );
2090
2099
  if (contractResult !== void 0) return contractResult;
2091
- const detailResult = await handleKanbanDetailAction(projectRoot, input, ctx);
2100
+ const detailResult = await handleKanbanDetailAction(projectRoot, normalizedInput, ctx);
2092
2101
  if (detailResult !== void 0) return detailResult;
2093
- return fail(`Unknown kanban action: ${input.action}`);
2102
+ return fail(`Unknown kanban action: ${normalizedInput.action}`);
2094
2103
  })();
2095
- return withPresence(result);
2104
+ if (signal.aborted) return fail("Operation aborted.");
2105
+ return await withPresence(result);
2096
2106
  } catch (err) {
2097
2107
  return fail(stripLifecycleIssues2(err instanceof Error ? err.message : String(err)));
2098
2108
  }
@@ -2108,4 +2118,4 @@ export {
2108
2118
  recordKanbanVerificationEvidence,
2109
2119
  kanbanTool
2110
2120
  };
2111
- //# sourceMappingURL=chunk-FJEQZHZN.js.map
2121
+ //# sourceMappingURL=chunk-W2CECGTG.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-NWZ6XGBJ.js";
4
7
  import {
5
8
  enqueueReindex
6
- } from "./chunk-VZLX2JZG.js";
9
+ } from "./chunk-DP6CXMJZ.js";
7
10
  import {
8
11
  isBinaryBuffer,
9
12
  safeResolveReal,
10
13
  sha256hex,
11
14
  truncateDiffPayload
12
- } from "./chunk-D33S4RQG.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-CISOLTOP.js.map
416
+ //# sourceMappingURL=chunk-X5FOFGXX.js.map
@@ -997,4 +997,4 @@ export {
997
997
  applyManagedKanbanBoardToTodos2 as applyManagedKanbanBoardToTodos,
998
998
  applySessionKanbanTaskToSource2 as applySessionKanbanTaskToSource
999
999
  };
1000
- //# sourceMappingURL=chunk-CWALNI6K.js.map
1000
+ //# sourceMappingURL=chunk-X6KXNHUO.js.map