@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,6 +1,9 @@
1
+ import {
2
+ mapWithConcurrency
3
+ } from "./chunk-NWZ6XGBJ.js";
1
4
  import {
2
5
  safeResolveReal
3
- } from "./chunk-D33S4RQG.js";
6
+ } from "./chunk-ZUFHAHTE.js";
4
7
 
5
8
  // src/diff.ts
6
9
  import { spawn } from "node:child_process";
@@ -59,6 +62,7 @@ var diffTool = {
59
62
  },
60
63
  async execute(input, ctx, opts) {
61
64
  const signal = opts?.signal ?? ctx.signal ?? new AbortController().signal;
65
+ signal.throwIfAborted();
62
66
  if (input.a !== void 0 || input.b !== void 0) {
63
67
  return await gitDiff(input, ctx, signal);
64
68
  }
@@ -66,13 +70,25 @@ var diffTool = {
66
70
  }
67
71
  };
68
72
  async function gitDiff(input, ctx, signal) {
69
- if (input.a?.startsWith("-")) {
73
+ if (input.a !== void 0 && (!input.a || !input.a.trim())) {
74
+ throw new ToolValidationError({
75
+ message: "diff: ref 'a' cannot be empty or whitespace-only",
76
+ field: "a"
77
+ });
78
+ }
79
+ if (input.b !== void 0 && (!input.b || !input.b.trim())) {
80
+ throw new ToolValidationError({
81
+ message: "diff: ref 'b' cannot be empty or whitespace-only",
82
+ field: "b"
83
+ });
84
+ }
85
+ if (input.a?.trim().startsWith("-")) {
70
86
  throw new ToolValidationError({
71
87
  message: `diff: unsafe ref "${input.a}" \u2014 refs may not begin with '-' (flag injection)`,
72
88
  field: "a"
73
89
  });
74
90
  }
75
- if (input.b?.startsWith("-")) {
91
+ if (input.b?.trim().startsWith("-")) {
76
92
  throw new ToolValidationError({
77
93
  message: `diff: unsafe ref "${input.b}" \u2014 refs may not begin with '-' (flag injection)`,
78
94
  field: "b"
@@ -82,9 +98,16 @@ async function gitDiff(input, ctx, signal) {
82
98
  const statMode = requestedMode === "stat";
83
99
  const effectiveMode = statMode ? "stat" : "unified";
84
100
  const sideBySideNote = requestedMode === "side-by-side" ? "side-by-side output is not supported; a unified diff was produced instead." : void 0;
85
- const gitDir = findGitDir(ctx.cwd);
101
+ const basePath = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
102
+ const gitDir = findGitDir(basePath);
86
103
  if (!gitDir) {
87
- return { diff: "", files: [], truncated: false, mode: effectiveMode };
104
+ return {
105
+ diff: "",
106
+ files: [],
107
+ truncated: false,
108
+ mode: effectiveMode,
109
+ note: "Not a git repository (or any parent up to root)."
110
+ };
88
111
  }
89
112
  const args = ["diff", "--no-color"];
90
113
  if (statMode) args.push("--stat");
@@ -93,11 +116,11 @@ async function gitDiff(input, ctx, signal) {
93
116
  if (Number.isFinite(contextLines)) args.push(`-U${contextLines}`);
94
117
  }
95
118
  if (input.staged) args.push("--staged");
96
- if (input.a) args.push(input.a);
97
- if (input.b) args.push(input.b);
119
+ if (input.a) args.push(input.a.trim());
120
+ if (input.b) args.push(input.b.trim());
98
121
  if (input.files) {
99
122
  const files = Array.isArray(input.files) ? input.files : input.files.split(",");
100
- args.push("--", ...files.map((f) => f.trim()));
123
+ args.push("--", ...files.map((f) => f.trim().replace(/\\/g, "/")));
101
124
  }
102
125
  const result = await runGit(args, gitDir, signal);
103
126
  let diff = result.stdout;
@@ -110,12 +133,14 @@ async function gitDiff(input, ctx, signal) {
110
133
  \u2026[git diff truncated: ${result.stdout.length - clipped.length} of ${result.stdout.length} characters omitted]`;
111
134
  truncated = true;
112
135
  }
136
+ const exitNote = result.exitCode !== 0 ? `git diff exited with code ${result.exitCode}: ${result.stderr.trim() || "command failed"}` : void 0;
137
+ const combinedNote = [sideBySideNote, exitNote].filter(Boolean).join("\n") || void 0;
113
138
  return {
114
139
  diff,
115
140
  files: [],
116
141
  truncated,
117
142
  mode: effectiveMode,
118
- note: sideBySideNote
143
+ note: combinedNote
119
144
  };
120
145
  }
121
146
  function findGitDir(cwd) {
@@ -133,7 +158,10 @@ function findGitDir(cwd) {
133
158
  return null;
134
159
  }
135
160
  function runGit(args, cwd, signal) {
136
- return new Promise((resolve) => {
161
+ if (signal.aborted) {
162
+ return Promise.resolve({ stdout: "", stderr: "Aborted", exitCode: 124 });
163
+ }
164
+ return new Promise((resolve2) => {
137
165
  let stdout = "";
138
166
  let stderr = "";
139
167
  const child = spawn("git", args, {
@@ -149,11 +177,12 @@ function runGit(args, cwd, signal) {
149
177
  child.stderr?.on("data", (c) => {
150
178
  stderr += c.toString();
151
179
  });
152
- child.on("close", (code) => resolve({ stdout, stderr, exitCode: code ?? 0 }));
153
- child.on("error", (e) => resolve({ stdout: "", stderr: e.message, exitCode: 1 }));
180
+ child.on("close", (code) => resolve2({ stdout, stderr, exitCode: code ?? 0 }));
181
+ child.on("error", (e) => resolve2({ stdout: "", stderr: e.message, exitCode: 1 }));
154
182
  });
155
183
  }
156
- async function fileDiff(input, ctx, _signal) {
184
+ async function fileDiff(input, ctx, signal) {
185
+ signal.throwIfAborted();
157
186
  void input.context;
158
187
  const files = input.files ? (Array.isArray(input.files) ? input.files : input.files.split(",")).map((f) => f.trim()).filter(Boolean) : [];
159
188
  if (files.length === 0) {
@@ -164,25 +193,26 @@ async function fileDiff(input, ctx, _signal) {
164
193
  mode: "dump"
165
194
  };
166
195
  }
167
- const fileEntries = await Promise.all(
168
- files.map(async (file) => {
169
- const absPath = await safeResolveReal(file, ctx);
170
- const stat2 = await fs.stat(absPath).catch(() => null);
171
- if (!stat2?.isFile()) return null;
172
- if (stat2.size > MAX_FILE_DUMP_BYTES) {
173
- return {
174
- truncated: true,
175
- output: `--- ${file} (skipped: ${stat2.size} bytes exceeds the ${MAX_FILE_DUMP_BYTES} limit; use the read tool with offset/limit) ---`
176
- };
177
- }
178
- const content = await fs.readFile(absPath, "utf8");
179
- const lines = content.split(/\r?\n/);
196
+ const basePath = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
197
+ const fileEntries = await mapWithConcurrency(files, 16, async (file) => {
198
+ signal.throwIfAborted();
199
+ const fileToResolve = path.isAbsolute(file) ? file : path.resolve(basePath, file);
200
+ const absPath = await safeResolveReal(fileToResolve, ctx);
201
+ const stat2 = await fs.stat(absPath).catch(() => null);
202
+ if (!stat2?.isFile()) return null;
203
+ if (stat2.size > MAX_FILE_DUMP_BYTES) {
180
204
  return {
181
- truncated: false,
182
- output: formatWithLineNumbers(file, lines)
205
+ truncated: true,
206
+ output: `--- ${file} (skipped: ${stat2.size} bytes exceeds the ${MAX_FILE_DUMP_BYTES} limit; use the read tool with offset/limit) ---`
183
207
  };
184
- })
185
- );
208
+ }
209
+ const content = await fs.readFile(absPath, "utf8");
210
+ const lines = content.split(/\r?\n/);
211
+ return {
212
+ truncated: false,
213
+ output: formatWithLineNumbers(file, lines)
214
+ };
215
+ });
186
216
  const results = [];
187
217
  let truncated = false;
188
218
  for (const entry of fileEntries) {
@@ -201,13 +231,18 @@ async function fileDiff(input, ctx, _signal) {
201
231
  };
202
232
  }
203
233
  function formatWithLineNumbers(file, lines) {
204
- const width = String(lines.length).length;
205
- const numbered = lines.map((line, i) => `${String(i + 1).padStart(width)} | ${line}`).join("\n");
234
+ const count = lines.length;
235
+ const width = String(count).length;
236
+ const parts = new Array(count);
237
+ for (let i = 0; i < count; i++) {
238
+ const numStr = String(i + 1).padStart(width, " ");
239
+ parts[i] = `${numStr} | ${lines[i]}`;
240
+ }
206
241
  return `--- ${file} (line-numbered dump, not a unified diff) ---
207
- ${numbered}`;
242
+ ${parts.join("\n")}`;
208
243
  }
209
244
 
210
245
  export {
211
246
  diffTool
212
247
  };
213
- //# sourceMappingURL=chunk-P7ZQ2FAJ.js.map
248
+ //# sourceMappingURL=chunk-OG2BEUBY.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  e2ePlanTool
3
- } from "./chunk-5WNDJHT6.js";
3
+ } from "./chunk-VPALDFYM.js";
4
4
  import {
5
5
  codebaseAstReplaceTool,
6
6
  codebaseImpactAnalysisTool,
@@ -14,13 +14,13 @@ import {
14
14
  codebaseStatsTool,
15
15
  codebaseTargetedTestTool,
16
16
  deadCodeScanTool
17
- } from "./chunk-YF3D5INN.js";
17
+ } from "./chunk-RTZWBNTY.js";
18
18
  import {
19
19
  planTool
20
- } from "./chunk-H6AWAZ2Y.js";
20
+ } from "./chunk-B4HTSV6Z.js";
21
21
  import {
22
22
  taskTool
23
- } from "./chunk-X5K3U6GI.js";
23
+ } from "./chunk-3M7LLN7W.js";
24
24
  import {
25
25
  toolSearchTool
26
26
  } from "./chunk-RJZQWEMS.js";
@@ -35,76 +35,76 @@ import {
35
35
  } from "./chunk-QBV725YG.js";
36
36
  import {
37
37
  designTool
38
- } from "./chunk-6RNIFLG4.js";
38
+ } from "./chunk-ELRRTWNP.js";
39
39
  import {
40
40
  testTool
41
- } from "./chunk-CPVLVQYT.js";
41
+ } from "./chunk-UQQS3VNT.js";
42
42
  import {
43
43
  installTool
44
- } from "./chunk-UE3UBMCP.js";
44
+ } from "./chunk-RLMX75EM.js";
45
45
  import {
46
46
  auditTool
47
- } from "./chunk-CVALEVGA.js";
47
+ } from "./chunk-64VIJPFV.js";
48
48
  import {
49
49
  outdatedTool
50
- } from "./chunk-MRZOVQ5D.js";
50
+ } from "./chunk-IYKXK356.js";
51
51
  import {
52
52
  logsTool
53
- } from "./chunk-CPSAF2AU.js";
53
+ } from "./chunk-FQNXYA6W.js";
54
54
  import {
55
55
  documentTool
56
- } from "./chunk-EMRGN4KM.js";
56
+ } from "./chunk-IU4DV2PA.js";
57
57
  import {
58
58
  scaffoldTool
59
- } from "./chunk-TKA4VEX2.js";
59
+ } from "./chunk-2OXHS3KZ.js";
60
60
  import {
61
61
  gitTool
62
- } from "./chunk-ZE6KLBUO.js";
62
+ } from "./chunk-6AAOT4PW.js";
63
63
  import {
64
64
  patchTool
65
- } from "./chunk-AGUSOFNI.js";
65
+ } from "./chunk-IWS56OY7.js";
66
66
  import {
67
67
  jsonTool
68
- } from "./chunk-VB3MJGCV.js";
68
+ } from "./chunk-VD2LV5WO.js";
69
69
  import {
70
70
  diffTool
71
- } from "./chunk-P7ZQ2FAJ.js";
71
+ } from "./chunk-OG2BEUBY.js";
72
72
  import {
73
73
  treeTool
74
- } from "./chunk-J5SKD6LN.js";
74
+ } from "./chunk-MNP3GE7H.js";
75
75
  import {
76
76
  lintTool
77
- } from "./chunk-WJYXMB2R.js";
77
+ } from "./chunk-LOBRWKCR.js";
78
78
  import {
79
79
  formatTool
80
- } from "./chunk-3QQSBUXR.js";
80
+ } from "./chunk-EADULOZV.js";
81
81
  import {
82
82
  typecheckTool
83
- } from "./chunk-4AEZIFOW.js";
83
+ } from "./chunk-5EUJF3WC.js";
84
84
  import {
85
85
  languageInfoTool,
86
86
  languagePackageTool,
87
87
  languageTool
88
- } from "./chunk-SOG7XIRF.js";
88
+ } from "./chunk-TTZOK6IY.js";
89
89
  import {
90
90
  replaceTool
91
- } from "./chunk-CISOLTOP.js";
91
+ } from "./chunk-X5FOFGXX.js";
92
92
  import {
93
93
  globTool
94
- } from "./chunk-6GZPSWP3.js";
94
+ } from "./chunk-JDPQOR7C.js";
95
95
  import {
96
96
  grepTool
97
- } from "./chunk-EOOFNYIT.js";
97
+ } from "./chunk-YOKD4GWJ.js";
98
98
  import {
99
99
  bashTool,
100
100
  checkAndBlockKillCommand,
101
101
  diagnoseBashism,
102
102
  shellArgs
103
- } from "./chunk-HTYEO67J.js";
103
+ } from "./chunk-AKHYMB4V.js";
104
104
  import {
105
105
  detectDanger,
106
106
  execTool
107
- } from "./chunk-WI3VDXHD.js";
107
+ } from "./chunk-BSJSNVGA.js";
108
108
  import {
109
109
  buildChildEnv
110
110
  } from "./chunk-BTSFTGIL.js";
@@ -118,35 +118,36 @@ import {
118
118
  } from "./chunk-3PVYQFRQ.js";
119
119
  import {
120
120
  fetchTool
121
- } from "./chunk-UKGMDCXT.js";
121
+ } from "./chunk-C2DQKVLO.js";
122
122
  import {
123
123
  searchTool
124
- } from "./chunk-PQTONQ4P.js";
124
+ } from "./chunk-QPNYDH5B.js";
125
125
  import {
126
126
  todoTool
127
- } from "./chunk-5ZYEGP2P.js";
127
+ } from "./chunk-BK732J7I.js";
128
128
  import {
129
129
  kanbanTool
130
- } from "./chunk-FJEQZHZN.js";
130
+ } from "./chunk-W2CECGTG.js";
131
131
  import {
132
132
  browserTools
133
133
  } from "./chunk-45JPRMC2.js";
134
134
  import {
135
135
  readTool
136
- } from "./chunk-3U4H6ZRF.js";
136
+ } from "./chunk-RNWUCZ2U.js";
137
137
  import {
138
138
  writeTool
139
- } from "./chunk-S2GBLQ5S.js";
139
+ } from "./chunk-O44IMQCN.js";
140
140
  import {
141
141
  editTool
142
- } from "./chunk-CIFAIIHI.js";
142
+ } from "./chunk-CFLJFDYV.js";
143
143
  import {
144
144
  resolvePowerShell
145
145
  } from "./chunk-V43EQ6VR.js";
146
146
  import {
147
147
  normalizeCommandOutput,
148
+ safeResolveProjectPath,
148
149
  safeResolveReal
149
- } from "./chunk-D33S4RQG.js";
150
+ } from "./chunk-ZUFHAHTE.js";
150
151
 
151
152
  // src/clarify.ts
152
153
  import { toErrorMessage } from "@wrongstack/core/utils";
@@ -192,8 +193,10 @@ var clarifyTool = {
192
193
  required: ["question", "options"],
193
194
  additionalProperties: false
194
195
  },
195
- async execute(input, ctx) {
196
+ async execute(input, ctx, opts) {
196
197
  try {
198
+ const signal = opts?.signal ?? ctx?.signal;
199
+ signal?.throwIfAborted();
197
200
  const options = input.options;
198
201
  if (!options || options.length < 2) {
199
202
  throw new Error("clarify requires at least 2 distinct options.");
@@ -237,6 +240,7 @@ import {
237
240
  emitProcessOutput,
238
241
  emitProcessStarted
239
242
  } from "@wrongstack/core/observability";
243
+ import { ToolValidationError } from "@wrongstack/core/types";
240
244
  var MAX_OUTPUT = 32768;
241
245
  var DEFAULT_TIMEOUT_MS = 3e5;
242
246
  var STREAM_FLUSH_INTERVAL_MS = 200;
@@ -251,6 +255,75 @@ $ErrorActionPreference = 'Stop'
251
255
  ${command}
252
256
  if ($LASTEXITCODE -is [int]) { exit $LASTEXITCODE }`;
253
257
  }
258
+ function tokenizePwshCommand(command) {
259
+ const tokens = [];
260
+ let current = "";
261
+ const push = () => {
262
+ if (current !== "") {
263
+ tokens.push(current);
264
+ current = "";
265
+ }
266
+ };
267
+ for (let i = 0; i < command.length; i++) {
268
+ const ch = command.charAt(i);
269
+ if (ch === "`") {
270
+ const next = command.charAt(i + 1);
271
+ if (next !== "") {
272
+ current += next;
273
+ i++;
274
+ }
275
+ continue;
276
+ }
277
+ if (ch === "'" || ch === '"') {
278
+ const quote = ch;
279
+ i++;
280
+ while (i < command.length) {
281
+ const qch = command.charAt(i);
282
+ if (qch === quote) {
283
+ if (command.charAt(i + 1) === quote) {
284
+ current += quote;
285
+ i += 2;
286
+ continue;
287
+ }
288
+ break;
289
+ }
290
+ if (quote === '"' && qch === "`") {
291
+ const next = command.charAt(i + 1);
292
+ if (next !== "") {
293
+ current += next;
294
+ i += 2;
295
+ continue;
296
+ }
297
+ }
298
+ current += qch;
299
+ i++;
300
+ }
301
+ continue;
302
+ }
303
+ if (/\s/.test(ch)) {
304
+ push();
305
+ continue;
306
+ }
307
+ current += ch;
308
+ }
309
+ push();
310
+ return tokens;
311
+ }
312
+ var DANGER_LEVEL_RANK = {
313
+ safe: 0,
314
+ caution: 1,
315
+ destructive: 2
316
+ };
317
+ function mergeDanger(...assessments) {
318
+ const top = assessments.reduce(
319
+ (acc, cur) => DANGER_LEVEL_RANK[cur.level] > DANGER_LEVEL_RANK[acc.level] ? cur : acc
320
+ );
321
+ const reasons = [...new Set(assessments.flatMap((a) => a.reasons))];
322
+ if (top.level === "safe") return { level: "safe", reasons: [] };
323
+ const result = { level: top.level, reasons };
324
+ if (top.matchedRule !== void 0) result.matchedRule = top.matchedRule;
325
+ return result;
326
+ }
254
327
  var pwshTool = {
255
328
  name: "pwsh",
256
329
  category: "Shell",
@@ -313,8 +386,38 @@ var pwshTool = {
313
386
  if (!final) throw new Error("pwsh: stream ended without final event");
314
387
  return final;
315
388
  },
389
+ async cleanup(_input, ctx) {
390
+ const registry = getProcessRegistry();
391
+ const sessionId = ctx.session?.id;
392
+ if (!sessionId) return;
393
+ for (const entry of registry.bySession(sessionId)) {
394
+ if (entry.name !== "pwsh") continue;
395
+ if (entry.child && entry.child.exitCode !== null) continue;
396
+ if (entry.protected) continue;
397
+ registry.kill(entry.pid, { force: true });
398
+ }
399
+ },
316
400
  async *executeStream(input, ctx, opts) {
317
- if (!input?.command) throw new Error("pwsh: command is required");
401
+ if (!input?.command || typeof input.command !== "string" || !input.command.trim()) {
402
+ throw new ToolValidationError({
403
+ message: "pwsh: command is required and cannot be empty",
404
+ field: "command"
405
+ });
406
+ }
407
+ const callerSignal = opts?.signal ?? ctx?.signal ?? new AbortController().signal;
408
+ if (callerSignal.aborted) {
409
+ yield {
410
+ type: "final",
411
+ output: {
412
+ output: "",
413
+ exit_code: 124,
414
+ timed_out: false,
415
+ pid: null,
416
+ error: "Aborted"
417
+ }
418
+ };
419
+ return;
420
+ }
318
421
  const isBackground = !!(input.run_in_background || input.background);
319
422
  const registry = getProcessRegistry();
320
423
  if (!registry.beforeCall(isBackground)) {
@@ -368,7 +471,6 @@ var pwshTool = {
368
471
  return;
369
472
  }
370
473
  const startedAt = Date.now();
371
- const callerSignal = opts?.signal ?? ctx.signal ?? new AbortController().signal;
372
474
  const detached = !isWin;
373
475
  if (isBackground) {
374
476
  const child2 = spawn(bin, args, {
@@ -476,7 +578,8 @@ Working directory: ${targetCwd}`,
476
578
  processGroupLeader: detached && child.pid === pid
477
579
  });
478
580
  }
479
- const timeoutMs = Math.min(Math.max(1e3, input.timeout_ms ?? DEFAULT_TIMEOUT_MS), 6e5);
581
+ const rawTimeout = typeof input.timeout_ms === "number" && !Number.isNaN(input.timeout_ms) ? input.timeout_ms : DEFAULT_TIMEOUT_MS;
582
+ const timeoutMs = Math.min(Math.max(1e3, rawTimeout), 6e5);
480
583
  let timedOut = false;
481
584
  const timers = [];
482
585
  const killWithTimeout = (timeout) => {
@@ -539,10 +642,10 @@ Working directory: ${targetCwd}`,
539
642
  queue.push(c);
540
643
  }
541
644
  };
542
- const next = () => new Promise((resolve2) => {
645
+ const next = () => new Promise((resolve) => {
543
646
  const c = queue.shift();
544
- if (c) resolve2(c);
545
- else resolveNext = resolve2;
647
+ if (c) resolve(c);
648
+ else resolveNext = resolve;
546
649
  });
547
650
  let buf = "";
548
651
  let pending = "";
@@ -624,7 +727,12 @@ Working directory: ${targetCwd}`,
624
727
  }
625
728
  }
626
729
  const hint = !timedOut && typeof c.code === "number" && c.code !== 0 ? diagnoseBashism(input.command, "pwsh") : void 0;
627
- const danger = detectDanger("pwsh", [input.command]);
730
+ const tokens = tokenizePwshCommand(input.command);
731
+ const danger = mergeDanger(
732
+ detectDanger("pwsh", tokens),
733
+ detectDanger(tokens[0] ?? "pwsh", tokens.slice(1)),
734
+ detectDanger("pwsh", [input.command])
735
+ );
628
736
  const cautionText = danger.level === "caution" && danger.reasons.length > 0 ? `
629
737
  [caution: ${danger.reasons.join("; ")}]` : "";
630
738
  const finalResultText = formattedOutput + (spooled ? spoolNote(spooled) : "") + (hint ? `
@@ -830,7 +938,7 @@ var securityAstScanTool = {
830
938
  let targetFile = input.file ?? "inline-code.ts";
831
939
  let content = input.content;
832
940
  if (!content && input.file) {
833
- const absPath = path.isAbsolute(input.file) ? input.file : path.resolve(projectRoot, input.file);
941
+ const absPath = await safeResolveProjectPath(input.file, ctx);
834
942
  targetFile = path.relative(projectRoot, absPath).replace(/\\/g, "/");
835
943
  content = await fs.readFile(absPath, "utf8");
836
944
  }
@@ -943,6 +1051,78 @@ var setWorkingDirTool = {
943
1051
  };
944
1052
 
945
1053
  // src/builtin.ts
1054
+ var BUILTIN_TOOL_DESCRIPTIONS = {
1055
+ browser_open: "Create an isolated, agent-owned Playwright browser session, optionally opening an approved HTTP(S) URL. Use it to begin browser QA; private and localhost origins require an explicit allowlist.",
1056
+ browser_list: "List browser sessions owned by this agent, including their state and current page, without exposing sessions owned by other agents.",
1057
+ browser_status: "Check whether the managed Playwright Chromium installation is available before attempting browser automation.",
1058
+ browser_navigate: "Navigate one of this agent\u2019s browser sessions to an approved HTTP(S) URL. Use browser_open first; private and localhost origins require an explicit allowlist.",
1059
+ browser_snapshot: "Inspect the current page through a bounded accessibility snapshot, with redacted console and network summaries. Prefer this before interacting with page elements.",
1060
+ browser_screenshot: "Capture a PNG of the current page or a selected element for visual QA. The result is a sensitive artifact with integrity metadata.",
1061
+ browser_type: "Fill a form control in an owned browser session. Use secretEnv for credentials so secret values never enter tool arguments or the audit trail.",
1062
+ browser_click: "Click a verified page element in an owned browser session. Snapshot first and use the most specific stable selector available.",
1063
+ browser_hover: "Hover over a verified page element in an owned browser session to reveal menus, tooltips, or other hover-driven UI state.",
1064
+ browser_select: "Choose an option in a select control in an owned browser session after confirming the target selector and intended value.",
1065
+ browser_press: "Send a keyboard key or shortcut to an owned browser session, such as Enter after verifying a form is ready to submit.",
1066
+ browser_drag: "Drag one page element onto another in an owned browser session. Use only when the page\u2019s drag-and-drop interaction is the intended action.",
1067
+ browser_wait: "Wait for a selector, navigation condition, or bounded duration in an owned browser session before taking the next browser action.",
1068
+ browser_evaluate: "Run a bounded JavaScript expression in an owned page when browser APIs cannot inspect the needed state. Treat page code as arbitrary and use sparingly.",
1069
+ browser_upload: "Upload project-local files through a page file input in an owned browser session. Verify both the file path and target control before uploading.",
1070
+ browser_close: "Close an owned browser session and reclaim its resources, returning trace-artifact metadata when tracing was enabled.",
1071
+ e2e_plan: "Create an end-to-end test plan from a feature or user flow. Use it to identify scenarios and acceptance coverage; it plans tests rather than executing them.",
1072
+ read: "Read a project file safely, with optional line ranges and binary-aware output. Use it to inspect source before editing; paths must stay within the project.",
1073
+ write: "Create or replace one project file with the complete supplied content. Use for new files or intentional full rewrites, after reading existing content when applicable.",
1074
+ edit: "Make a precise, guarded text edit by replacing an expected block in a project file. Prefer it for small source changes so mismatches prevent accidental overwrites.",
1075
+ clarify: "Record or ask a focused clarification when a missing decision would materially change the implementation. Do not use it for questions that can be answered from the repository.",
1076
+ "codebase-ast-replace": "Replace a named declaration using source-aware structure instead of fragile text matching. Use it for a function, method, class, interface, or variable when the target is unambiguous.",
1077
+ "codebase-invariant-check": "Compare candidate code with its original source and report structural invariants that may have changed. Use before writing a risky refactor; it validates but does not modify files.",
1078
+ "codebase-stats": "Report codebase-index health, indexed file and symbol counts, languages, and freshness. Use it before relying on indexed discovery results.",
1079
+ "codebase-search": "Search indexed symbols, signatures, and documentation with optional language, kind, path, or LSP-kind filters. Use it for semantic discovery before broad text search.",
1080
+ "codebase-skeleton": "Extract a compact structural skeleton from a source file or directory, preserving declarations while omitting implementation detail. Use it to understand unfamiliar code quickly.",
1081
+ "codebase-repo-map": "Generate a concise map of important files, symbols, and relationships in a project area. Use it for orientation before a cross-file change.",
1082
+ "codebase-impact-analysis": "Find likely callers, dependents, related tests, and change risk for a named symbol. Use it before changing a public or widely used declaration.",
1083
+ "codebase-targeted-test": "Discover and run tests that cover a specified symbol, source file, or explicit test files. Use it for focused regression validation after a change.",
1084
+ "security-ast-scan": "Statically scan source code for supported security patterns and return findings with locations. Use it as a focused code check, not as a substitute for a full security assessment.",
1085
+ "codebase-incoming-calls": "Find indexed call sites that invoke a named function, method, or type, optionally scoped to a file. Use it to estimate breakage before changing an API.",
1086
+ "codebase-outgoing-calls": "Find indexed symbols called by a named function, method, or type, optionally scoped to a file. Use it to understand dependencies before refactoring behavior.",
1087
+ "codebase-index": "Build or refresh the local semantic codebase index, optionally for selected languages. Use it when index results are absent or stale; force performs a full reindex.",
1088
+ "dead-code-scan": "Analyze the indexed project for declarations that appear unreachable from configured entry points. Treat results as candidates for review, not automatic deletion instructions.",
1089
+ replace: "Preview or apply a regular-expression replacement across selected project files. Start with dry_run, constrain files and globs carefully, then apply only reviewed changes.",
1090
+ glob: "Find project files by glob pattern, respecting repository boundaries and ignore rules. Use it to locate candidate paths before reading or editing them.",
1091
+ grep: "Search project text with a bounded regular expression and contextual matches. Use it for exact literals or patterns when semantic codebase search is not appropriate.",
1092
+ bash: "Run a shell command in the project with bounded output and timeout controls. Use it for development commands after checking side effects; background mode returns a process handle.",
1093
+ exec: "Execute a command directly without shell interpretation, using explicit program arguments. Prefer it when argument safety and predictable process invocation matter.",
1094
+ pwsh: "Execute a PowerShell command in the project with timeout, output, and background controls. Use it for Windows-native project operations and verify commands that can modify state.",
1095
+ fetch: "Fetch and extract content from an approved HTTP(S) URL for research or integration work. Use it for a known page or endpoint, not for general web discovery.",
1096
+ search: "Search the public web for current external information, then inspect selected results with fetch. Use it when repository evidence is insufficient or the fact may have changed.",
1097
+ todo: "Create, update, or list the session\u2019s concrete work items and their progress. Use it to keep multi-step work visible; it does not implement the tasks itself.",
1098
+ plan: "Create and manage higher-level plan-board items, priorities, and status. Use it for strategic work tracking rather than small immediate edits.",
1099
+ kanban: "Manage project Kanban boards, cards, assignments, and acceptance evidence. Use it for persistent team workflow; changing board state is intentional and reviewable.",
1100
+ task: "Manage structured task records, dependencies, ownership, and promotion into actionable session work. Use it to organize bounded work before delegation or execution.",
1101
+ git: "Inspect or run scoped Git operations in the project, including status, diff, history, branches, and commits. Review the target and working tree before mutating operations.",
1102
+ patch: "Apply a unified diff to project files with patch-style context checking. Use it for a reviewed multi-file change when exact patch content is available.",
1103
+ json: "Read, query, validate, or update JSON files while preserving valid structure. Use it instead of raw text edits when changing structured JSON data.",
1104
+ diff: "Compare two files, revisions, or text inputs and return a readable unified diff. Use it to review intended changes before applying or committing them.",
1105
+ tree: "Render a bounded directory tree with depth, file, hidden-file, and ignore controls. Use it for repository orientation without reading every file.",
1106
+ lint: "Run the project\u2019s configured linter for a target path or working directory and return diagnostics. Use it after code edits to catch style and static-analysis issues.",
1107
+ format: "Run the project\u2019s configured formatter on selected files or directories. Use it after editing code, while reviewing the resulting diff for unintended formatting scope.",
1108
+ typecheck: "Run TypeScript type checking for an auto-detected or specified tsconfig. Use it after type-affecting changes; it reports diagnostics without writing source files.",
1109
+ test: "Run the detected test runner for selected tests, with optional name filtering, coverage, watch, and timeout controls. Prefer focused tests first, then broader validation as needed.",
1110
+ language_info: "Inspect detected language tooling, workspaces, and supported operations for the project or target path. Use it before invoking language-specific tooling.",
1111
+ language: "Run a supported language-tooling operation in a detected workspace. Use it when the language profile provides a safer, structured alternative to an arbitrary shell command.",
1112
+ language_package: "Plan or perform a dependency operation through the detected package ecosystem. Use dry-run first when possible and specify the workspace or dependency scope deliberately.",
1113
+ install: "Install project dependencies with the detected package manager. Use only when dependency changes are required, and inspect lockfile and manifest changes afterward.",
1114
+ audit: "Run the package manager\u2019s dependency vulnerability audit and summarize actionable findings. Use it to assess known dependency advisories, not source-code vulnerabilities.",
1115
+ outdated: "List outdated project dependencies and available versions without changing manifests or lockfiles. Use it to plan dependency maintenance.",
1116
+ logs: "Read or tail configured local, container, or process logs with bounded output. Use it to investigate a known runtime failure or service behavior.",
1117
+ document: "Generate or update project documentation from supplied scope and source context. Use it to record verified behavior; review generated text before treating it as authoritative.",
1118
+ scaffold: "Preview or generate files from a built-in or custom scaffold template. Use dry_run first to inspect paths and content before creating project files.",
1119
+ design: "Create a structured implementation design with goals, constraints, approach, and risks. Use it before a substantial change that needs an explicit technical decision.",
1120
+ tool_search: "Discover registered tools by name, description, tag, permission, or mutating status. Use it to find a lazy capability before calling it through tool-use.",
1121
+ tool_use: "Invoke a registered tool by name with its input object, including a tool discovered lazily. Use it only after confirming the target tool\u2019s schema and side effects.",
1122
+ batch_tool_use: "Invoke several independent registered tools as a batch and return each result. Use it for parallel read-only discovery; avoid batching dependent or destructive operations.",
1123
+ tool_help: "Show a tool\u2019s purpose, input schema, permission, and examples, or summarize the catalog. Use it before invoking an unfamiliar or lazily discovered tool.",
1124
+ set_working_dir: "Show or change the session working directory within the permitted project scope. Use it when subsequent commands must target another project subdirectory."
1125
+ };
946
1126
  var OPTIONAL_TOOLS = [
947
1127
  ...browserTools,
948
1128
  e2ePlanTool,
@@ -1018,7 +1198,7 @@ var TIER3_TOOLS = [
1018
1198
  toolHelpTool,
1019
1199
  setWorkingDirTool
1020
1200
  ];
1021
- var builtinTools = [
1201
+ var rawBuiltinTools = [
1022
1202
  ...browserTools,
1023
1203
  e2ePlanTool,
1024
1204
  readTool,
@@ -1075,6 +1255,13 @@ var builtinTools = [
1075
1255
  toolHelpTool,
1076
1256
  setWorkingDirTool
1077
1257
  ];
1258
+ var builtinTools = rawBuiltinTools.map((tool) => {
1259
+ const description = BUILTIN_TOOL_DESCRIPTIONS[tool.name];
1260
+ if (!description) {
1261
+ throw new Error(`Missing provider-facing description for built-in tool: ${tool.name}`);
1262
+ }
1263
+ return { ...tool, description };
1264
+ });
1078
1265
 
1079
1266
  export {
1080
1267
  clarifyTool,
@@ -1082,6 +1269,7 @@ export {
1082
1269
  pwshTool,
1083
1270
  analyzeSecurityAndPerformance,
1084
1271
  securityAstScanTool,
1272
+ BUILTIN_TOOL_DESCRIPTIONS,
1085
1273
  OPTIONAL_TOOLS,
1086
1274
  OFF_ONLY_TOOLS,
1087
1275
  TIER1_TOOLS,
@@ -1089,4 +1277,4 @@ export {
1089
1277
  TIER3_TOOLS,
1090
1278
  builtinTools
1091
1279
  };
1092
- //# sourceMappingURL=chunk-K23JNFE3.js.map
1280
+ //# sourceMappingURL=chunk-OTINMOKK.js.map