@wrongstack/tools 1.0.6 → 1.0.8

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 (89) hide show
  1. package/dist/audit.js +4 -4
  2. package/dist/bash.js +2 -2
  3. package/dist/builtin.js +39 -39
  4. package/dist/{chunk-HKR56VNF.js → chunk-2IAEJRLT.js} +4 -3
  5. package/dist/{chunk-5UA4TNJJ.js → chunk-2NTHMZV3.js} +2 -2
  6. package/dist/{chunk-JQ43NVFU.js → chunk-3LQJXJDM.js} +2 -2
  7. package/dist/{chunk-47GYABZW.js → chunk-4SW6BXUR.js} +2 -2
  8. package/dist/{chunk-4KI4HHID.js → chunk-5KRKL3WD.js} +3 -3
  9. package/dist/{chunk-7EQ2LUXU.js → chunk-5MP76C72.js} +3 -3
  10. package/dist/{chunk-7PEZU5Y4.js → chunk-7E55DAFW.js} +8 -8
  11. package/dist/{chunk-D4HWMWGE.js → chunk-AH7J5HWL.js} +3 -3
  12. package/dist/{chunk-OZVGRDCM.js → chunk-B62LKMWQ.js} +3 -3
  13. package/dist/{chunk-WKXL5XHU.js → chunk-BVHKBBGA.js} +2 -2
  14. package/dist/{chunk-54SIRJ6D.js → chunk-C4DSQBTT.js} +3 -3
  15. package/dist/{chunk-2ZWQIVEW.js → chunk-CAF4L6KN.js} +2 -2
  16. package/dist/{chunk-MG7CUN6R.js → chunk-EGT5QLCB.js} +6 -3
  17. package/dist/{chunk-ZSW5KO5S.js → chunk-FI6SOIVE.js} +31 -7
  18. package/dist/{chunk-TL5T4A4E.js → chunk-GF4JHLCO.js} +3 -3
  19. package/dist/{chunk-OVFQC6FU.js → chunk-GNLHBDFO.js} +341 -146
  20. package/dist/{chunk-ZETZHHV5.js → chunk-H2VB7GCR.js} +3 -3
  21. package/dist/{chunk-C64UUXTA.js → chunk-HCPSVK7J.js} +3 -3
  22. package/dist/{chunk-K7Y2IWEB.js → chunk-HIRVCXLP.js} +4 -4
  23. package/dist/{chunk-IMIHTHF7.js → chunk-HOFVYIPI.js} +38 -10
  24. package/dist/{chunk-CYPYJIAF.js → chunk-JB5JLY7V.js} +4 -4
  25. package/dist/{chunk-S6E2AM4U.js → chunk-KF7GMVNU.js} +3 -3
  26. package/dist/{chunk-IOOFLBVM.js → chunk-KMXPP32M.js} +3 -3
  27. package/dist/{chunk-REWOZIA4.js → chunk-M2W36JT7.js} +2 -2
  28. package/dist/{chunk-FRIUJ534.js → chunk-MF6ZUBI7.js} +3 -3
  29. package/dist/{chunk-BU6VUXWG.js → chunk-N3OTTQNC.js} +2 -2
  30. package/dist/{chunk-YJQQ5F5J.js → chunk-NL45OWEO.js} +3 -3
  31. package/dist/{chunk-NKOTOW6X.js → chunk-O6AGFJBO.js} +3 -3
  32. package/dist/{chunk-YILT3CSO.js → chunk-OGQMPX3J.js} +18 -7
  33. package/dist/{chunk-OBVXDVLE.js → chunk-QJARSK2G.js} +3 -3
  34. package/dist/{chunk-R36RWYXE.js → chunk-R3HIZVFS.js} +3 -3
  35. package/dist/{chunk-TGVQMKVH.js → chunk-S6CGSFLX.js} +3 -3
  36. package/dist/{chunk-JK5WXW4S.js → chunk-SEONDNLN.js} +2 -2
  37. package/dist/{chunk-6IEBD27U.js → chunk-SKRMUHTU.js} +2 -2
  38. package/dist/{chunk-DOHU3XHS.js → chunk-TU6UVATU.js} +3 -3
  39. package/dist/{chunk-G35NLPG5.js → chunk-WKLUOXR4.js} +3 -3
  40. package/dist/{chunk-6ZRP5IKQ.js → chunk-YUK3WLHJ.js} +2 -2
  41. package/dist/{chunk-5BKQVP56.js → chunk-YZECCW3F.js} +34 -14
  42. package/dist/{chunk-3F7HQ3KS.js → chunk-YZQSUL5G.js} +2 -2
  43. package/dist/{chunk-2IC5FRD7.js → chunk-ZMCV3XOV.js} +2 -2
  44. package/dist/{chunk-WBAPU3AB.js → chunk-ZOUG3KKS.js} +2 -2
  45. package/dist/clarify.d.ts +26 -19
  46. package/dist/codebase-index/index.js +8 -8
  47. package/dist/codebase-index/parser-worker-script.js +3 -3
  48. package/dist/codebase-index/project-server.js +5 -5
  49. package/dist/codebase-index/worker.js +4 -4
  50. package/dist/diff.js +2 -2
  51. package/dist/e2e.js +2 -2
  52. package/dist/edit.js +8 -8
  53. package/dist/exec.js +2 -2
  54. package/dist/fetch.js +2 -2
  55. package/dist/format.js +4 -4
  56. package/dist/{generic-parser-WFMIRHBX.js → generic-parser-N2PLGBC2.js} +2 -2
  57. package/dist/git.js +2 -2
  58. package/dist/glob.js +2 -2
  59. package/dist/{go-parser-EMKTAAD3.js → go-parser-VDPRQEGM.js} +5 -5
  60. package/dist/grep.js +2 -2
  61. package/dist/index.d.ts +1 -1
  62. package/dist/index.js +41 -41
  63. package/dist/install.js +4 -4
  64. package/dist/{json-parser-34DHYCPW.js → json-parser-IJD77VL4.js} +11 -17
  65. package/dist/json.js +2 -2
  66. package/dist/kanban.js +2 -2
  67. package/dist/languages/index.js +2 -2
  68. package/dist/{legacy-bridge-XM2GV665.js → legacy-bridge-4T4OTSQP.js} +4 -4
  69. package/dist/lint.js +4 -4
  70. package/dist/logs.js +2 -2
  71. package/dist/outdated.js +2 -2
  72. package/dist/pack.js +40 -40
  73. package/dist/{parser-dispatch-HZDZSIYH.js → parser-dispatch-4E2SDQUO.js} +4 -4
  74. package/dist/patch.js +8 -8
  75. package/dist/plan.js +4 -4
  76. package/dist/{py-parser-DDG53RXI.js → py-parser-MAW6DHFE.js} +3 -3
  77. package/dist/read.js +8 -8
  78. package/dist/replace.js +8 -8
  79. package/dist/session-kanban.d.ts +7 -0
  80. package/dist/session-kanban.js +3 -1
  81. package/dist/task.js +4 -4
  82. package/dist/test.js +4 -4
  83. package/dist/todo.js +3 -3
  84. package/dist/tool-tier.js +41 -41
  85. package/dist/tree.js +2 -2
  86. package/dist/typecheck.js +4 -4
  87. package/dist/write.js +8 -8
  88. package/dist/{yaml-parser-RGBXEZE4.js → yaml-parser-DFBC5FWH.js} +7 -6
  89. package/package.json +5 -5
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  e2ePlanTool
3
- } from "./chunk-WKXL5XHU.js";
3
+ } from "./chunk-BVHKBBGA.js";
4
4
  import {
5
5
  codebaseAstReplaceTool,
6
6
  codebaseContextTool,
@@ -15,31 +15,31 @@ import {
15
15
  codebaseStatsTool,
16
16
  codebaseTargetedTestTool,
17
17
  deadCodeScanTool
18
- } from "./chunk-CYPYJIAF.js";
18
+ } from "./chunk-JB5JLY7V.js";
19
19
  import {
20
20
  designTool
21
21
  } from "./chunk-IXIS7RSG.js";
22
22
  import {
23
23
  planTool
24
- } from "./chunk-NKOTOW6X.js";
24
+ } from "./chunk-O6AGFJBO.js";
25
25
  import {
26
26
  taskTool
27
- } from "./chunk-OZVGRDCM.js";
27
+ } from "./chunk-B62LKMWQ.js";
28
28
  import {
29
29
  testTool
30
- } from "./chunk-S6E2AM4U.js";
30
+ } from "./chunk-KF7GMVNU.js";
31
31
  import {
32
32
  installTool
33
- } from "./chunk-ZETZHHV5.js";
33
+ } from "./chunk-H2VB7GCR.js";
34
34
  import {
35
35
  auditTool
36
- } from "./chunk-C64UUXTA.js";
36
+ } from "./chunk-HCPSVK7J.js";
37
37
  import {
38
38
  outdatedTool
39
- } from "./chunk-YJQQ5F5J.js";
39
+ } from "./chunk-NL45OWEO.js";
40
40
  import {
41
41
  logsTool
42
- } from "./chunk-2ZWQIVEW.js";
42
+ } from "./chunk-CAF4L6KN.js";
43
43
  import {
44
44
  toolSearchTool
45
45
  } from "./chunk-YA2ZYCJY.js";
@@ -48,52 +48,52 @@ import {
48
48
  } from "./chunk-B3MGJBFI.js";
49
49
  import {
50
50
  gitTool
51
- } from "./chunk-2IC5FRD7.js";
51
+ } from "./chunk-ZMCV3XOV.js";
52
52
  import {
53
53
  patchTool
54
- } from "./chunk-7EQ2LUXU.js";
54
+ } from "./chunk-5MP76C72.js";
55
55
  import {
56
56
  jsonTool
57
- } from "./chunk-MG7CUN6R.js";
57
+ } from "./chunk-EGT5QLCB.js";
58
58
  import {
59
59
  diffTool
60
- } from "./chunk-BU6VUXWG.js";
60
+ } from "./chunk-N3OTTQNC.js";
61
61
  import {
62
62
  treeTool
63
- } from "./chunk-3F7HQ3KS.js";
63
+ } from "./chunk-YZQSUL5G.js";
64
64
  import {
65
65
  lintTool
66
- } from "./chunk-R36RWYXE.js";
66
+ } from "./chunk-R3HIZVFS.js";
67
67
  import {
68
68
  formatTool
69
- } from "./chunk-OBVXDVLE.js";
69
+ } from "./chunk-QJARSK2G.js";
70
70
  import {
71
71
  typecheckTool
72
- } from "./chunk-D4HWMWGE.js";
72
+ } from "./chunk-AH7J5HWL.js";
73
73
  import {
74
74
  languageInfoTool,
75
75
  languagePackageTool,
76
76
  languageTool
77
- } from "./chunk-JK5WXW4S.js";
77
+ } from "./chunk-SEONDNLN.js";
78
78
  import {
79
79
  replaceTool
80
- } from "./chunk-IOOFLBVM.js";
80
+ } from "./chunk-KMXPP32M.js";
81
81
  import {
82
82
  globTool
83
- } from "./chunk-IMIHTHF7.js";
83
+ } from "./chunk-HOFVYIPI.js";
84
84
  import {
85
85
  grepTool
86
- } from "./chunk-ZSW5KO5S.js";
86
+ } from "./chunk-FI6SOIVE.js";
87
87
  import {
88
88
  bashTool,
89
89
  checkAndBlockKillCommand,
90
90
  diagnoseBashism,
91
91
  shellArgs
92
- } from "./chunk-4KI4HHID.js";
92
+ } from "./chunk-5KRKL3WD.js";
93
93
  import {
94
94
  detectDanger,
95
95
  execTool
96
- } from "./chunk-YILT3CSO.js";
96
+ } from "./chunk-OGQMPX3J.js";
97
97
  import {
98
98
  buildChildEnv
99
99
  } from "./chunk-BTSFTGIL.js";
@@ -107,7 +107,7 @@ import {
107
107
  } from "./chunk-HDYUBRMF.js";
108
108
  import {
109
109
  fetchTool
110
- } from "./chunk-47GYABZW.js";
110
+ } from "./chunk-4SW6BXUR.js";
111
111
  import {
112
112
  searchTool
113
113
  } from "./chunk-62OAECEQ.js";
@@ -116,22 +116,22 @@ import {
116
116
  } from "./chunk-NBDZMMG2.js";
117
117
  import {
118
118
  todoTool
119
- } from "./chunk-TGVQMKVH.js";
119
+ } from "./chunk-S6CGSFLX.js";
120
120
  import {
121
121
  kanbanTool
122
- } from "./chunk-5UA4TNJJ.js";
122
+ } from "./chunk-2NTHMZV3.js";
123
123
  import {
124
124
  browserTools
125
125
  } from "./chunk-6XYFYLN7.js";
126
126
  import {
127
127
  readTool
128
- } from "./chunk-K7Y2IWEB.js";
128
+ } from "./chunk-HIRVCXLP.js";
129
129
  import {
130
130
  writeTool
131
- } from "./chunk-TL5T4A4E.js";
131
+ } from "./chunk-GF4JHLCO.js";
132
132
  import {
133
133
  editTool
134
- } from "./chunk-G35NLPG5.js";
134
+ } from "./chunk-WKLUOXR4.js";
135
135
  import {
136
136
  resolvePowerShell
137
137
  } from "./chunk-5U5ULQX2.js";
@@ -139,9 +139,10 @@ import {
139
139
  normalizeCommandOutput,
140
140
  safeResolveProjectPath,
141
141
  safeResolveReal
142
- } from "./chunk-HKR56VNF.js";
142
+ } from "./chunk-2IAEJRLT.js";
143
143
 
144
144
  // src/clarify.ts
145
+ import { randomUUID } from "node:crypto";
145
146
  import { toErrorMessage } from "@wrongstack/core/utils";
146
147
  var clarifyTool = {
147
148
  name: "clarify",
@@ -149,62 +150,42 @@ var clarifyTool = {
149
150
  icon: "meta",
150
151
  permission: "auto",
151
152
  mutating: false,
152
- description: "Ask the user structured multiple-choice clarification question(s) when an architectural, database schema, API, or domain decision has mutually exclusive trade-offs. Supports single or batch questions, recommended defaults, multi-select, and custom write-in responses across TUI, CLI, and WebUI.",
153
- usageHint: 'Use when requirements are ambiguous or have critical trade-offs.\n- Single question: pass `question` and `options` (min 2).\n- Multiple questions: pass `questions: [{ question, options, recommendedOption? }]`.\n- Format options as clear user decisions. Prefix best practice choices with "(Recommended)".\n- Autonomous fallback: if running headless without an interactive UI, the recommended option is automatically selected.',
153
+ description: "Pause and ask the user for required decisions in one structured, tabbed form. Supports single-select, multi-select, free text, option descriptions, write-ins, and explicitly recommended answers. Submit returns all answers to the model at once.",
154
+ usageHint: 'Prefer `tabs` with stable snake_case ids. Every decision should include a recommended answer and a short recommendationReason. Use `type: "text"` for free-form input. Bounds: 1-8 tabs, 1-12 questions per tab, and 2-8 labeled options per select question. Blank questions, empty tabs, and description-only options are invalid by design. `type` is canonical; legacy isMultiSelect flags may be used only when they do not conflict. Legacy `question`/`questions` inputs remain supported.',
154
155
  inputSchema: {
155
156
  type: "object",
156
157
  properties: {
157
- question: {
158
- type: "string",
159
- description: "The specific architectural, schema, or contract decision to clarify."
160
- },
161
- context: {
162
- type: "string",
163
- description: "Brief explanation of tradeoffs or why this decision is needed."
164
- },
165
- options: {
166
- type: "array",
167
- items: { type: "string" },
168
- description: "2 to 6 distinct options formatted as clear user decisions.",
169
- minItems: 2,
170
- maxItems: 6
171
- },
172
- recommendedOption: {
173
- type: "string",
174
- description: "The assistant-recommended default choice."
175
- },
176
- isMultiSelect: {
177
- type: "boolean",
178
- description: "Whether multiple options can be chosen simultaneously."
179
- },
180
- is_multi_select: {
181
- type: "boolean",
182
- description: "Snake-case alias for isMultiSelect."
183
- },
184
- allowCustomResponse: {
185
- type: "boolean",
186
- description: "Whether to allow the user to type a custom write-in response (default: true)."
187
- },
188
- questions: {
158
+ title: { type: "string", description: "Short form title." },
159
+ description: { type: "string", description: "Why these answers are needed." },
160
+ submitLabel: { type: "string", description: "Submit button label." },
161
+ question: { type: "string", minLength: 1 },
162
+ context: { type: "string" },
163
+ type: { type: "string", enum: ["single_select", "multi_select", "text"] },
164
+ options: optionArraySchema(),
165
+ recommendedOption: { type: "string" },
166
+ recommendedOptions: { type: "array", items: { type: "string" } },
167
+ recommendedText: { type: "string" },
168
+ recommendationReason: { type: "string" },
169
+ isMultiSelect: { type: "boolean" },
170
+ is_multi_select: { type: "boolean" },
171
+ allowCustomResponse: { type: "boolean" },
172
+ required: { type: "boolean" },
173
+ placeholder: { type: "string" },
174
+ questions: { type: "array", minItems: 1, items: questionSchema() },
175
+ tabs: {
189
176
  type: "array",
190
- description: "List of questions for batch clarification (Antigravity ask_question parity).",
177
+ minItems: 1,
178
+ maxItems: 8,
191
179
  items: {
192
180
  type: "object",
193
181
  properties: {
194
- question: { type: "string" },
195
- context: { type: "string" },
196
- options: {
197
- type: "array",
198
- items: { type: "string" },
199
- minItems: 2,
200
- maxItems: 6
201
- },
202
- recommendedOption: { type: "string" },
203
- isMultiSelect: { type: "boolean" },
204
- is_multi_select: { type: "boolean" },
205
- allowCustomResponse: { type: "boolean" }
182
+ id: { type: "string" },
183
+ label: { type: "string", minLength: 1 },
184
+ description: { type: "string" },
185
+ questions: { type: "array", minItems: 1, maxItems: 12, items: questionSchema() }
206
186
  },
207
- required: ["question", "options"]
187
+ required: ["label", "questions"],
188
+ additionalProperties: false
208
189
  }
209
190
  }
210
191
  },
@@ -212,84 +193,298 @@ var clarifyTool = {
212
193
  },
213
194
  async execute(input, ctx, opts) {
214
195
  try {
215
- const signal = opts?.signal ?? ctx?.signal;
216
- signal?.throwIfAborted();
217
- const items = [];
218
- if (Array.isArray(input.questions) && input.questions.length > 0) {
219
- items.push(...input.questions);
220
- } else if (input.question && Array.isArray(input.options)) {
221
- items.push({
222
- question: input.question,
223
- context: input.context,
224
- options: input.options,
225
- recommendedOption: input.recommendedOption,
226
- isMultiSelect: input.isMultiSelect ?? input.is_multi_select,
227
- allowCustomResponse: input.allowCustomResponse
228
- });
229
- }
230
- if (items.length === 0) {
231
- throw new Error("clarify requires either `question` with `options`, or a `questions` array with at least 1 item.");
232
- }
233
- for (const item of items) {
234
- if (!item.options || item.options.length < 2) {
235
- throw new Error(`clarify question "${item.question}" requires at least 2 distinct options.`);
236
- }
237
- }
238
- const hostAsk = ctx?.askUserChoices;
239
- const answers = [];
240
- let isAutoDecided = false;
241
- for (const item of items) {
242
- const multi = item.isMultiSelect ?? item.is_multi_select ?? false;
243
- const options = item.options;
244
- if (typeof hostAsk === "function") {
245
- const res = await hostAsk(item.question, options, {
246
- isMultiSelect: multi,
247
- ...item.context === void 0 ? {} : { context: item.context },
248
- ...item.recommendedOption === void 0 ? {} : { recommendedOption: item.recommendedOption },
249
- allowCustomResponse: item.allowCustomResponse ?? true
250
- });
251
- const selected = Array.isArray(res?.selected) ? res.selected : [];
252
- answers.push({
253
- question: item.question,
254
- selectedOptions: selected,
255
- customResponse: res?.custom
256
- });
257
- } else {
258
- isAutoDecided = true;
259
- const rec = item.recommendedOption ?? options.find((opt) => /^\(?recommended\)?/i.test(opt)) ?? options[0] ?? "Default";
260
- answers.push({
261
- question: item.question,
262
- selectedOptions: [rec],
263
- customResponse: void 0
264
- });
265
- }
266
- }
196
+ const signal = opts?.signal ?? ctx.signal;
197
+ signal.throwIfAborted();
198
+ const request = normalizeRequest(input);
199
+ let response;
200
+ if (typeof ctx.requestUserInput === "function")
201
+ response = await ctx.requestUserInput(request, signal);
202
+ else response = await legacyHostResponse(ctx, request);
203
+ const autoDecided = response === void 0;
204
+ const resolved = response ?? recommendedResponse(request);
205
+ if (resolved.status === "cancelled")
206
+ return skipped(request, "User cancelled the clarification form.");
207
+ const answers = materializeAnswers(request, resolved.answers);
267
208
  const primary = answers[0];
268
- const summaries = answers.map((ans) => {
269
- const customPart = ans.customResponse ? ` (Custom: "${ans.customResponse}")` : "";
270
- return `"${ans.question}": ${ans.selectedOptions.join(", ")}${customPart}`;
271
- });
272
- const decisionSummary = isAutoDecided ? `Auto-selected recommended option(s) in non-interactive mode: ${summaries.join("; ")}` : `User clarified: ${summaries.join("; ")}`;
209
+ const decisionSummary = `${autoDecided ? "Auto-selected recommended answers in non-interactive mode" : "User clarified"}: ${answers.map((answer) => {
210
+ if (answer.delegatedToModel)
211
+ return `"${answer.question}": model should decide (user delegated)`;
212
+ const values = [
213
+ ...answer.selectedOptions,
214
+ ...answer.customResponse ? [answer.customResponse] : []
215
+ ];
216
+ return `"${answer.question}": ${values.join(", ") || "(blank)"}`;
217
+ }).join("; ")}`;
273
218
  return {
274
- status: isAutoDecided ? "auto_decided" : "answered",
219
+ status: autoDecided ? "auto_decided" : "answered",
275
220
  question: primary.question,
276
221
  selectedOptions: primary.selectedOptions,
277
222
  customResponse: primary.customResponse,
278
223
  answers,
279
224
  decisionSummary
280
225
  };
281
- } catch (err) {
282
- const fallbackQuestion = input.question ?? input.questions?.[0]?.question ?? "";
283
- return {
284
- status: "skipped",
285
- question: fallbackQuestion,
286
- selectedOptions: [],
287
- decisionSummary: "",
288
- error: toErrorMessage(err)
289
- };
226
+ } catch (error) {
227
+ return skippedFromInput(input, toErrorMessage(error));
290
228
  }
291
229
  }
292
230
  };
231
+ function optionArraySchema() {
232
+ return {
233
+ type: "array",
234
+ minItems: 2,
235
+ maxItems: 8,
236
+ items: {
237
+ oneOf: [
238
+ { type: "string" },
239
+ {
240
+ type: "object",
241
+ properties: {
242
+ id: { type: "string" },
243
+ label: { type: "string", minLength: 1 },
244
+ description: { type: "string" }
245
+ },
246
+ required: ["label"],
247
+ additionalProperties: false
248
+ }
249
+ ]
250
+ }
251
+ };
252
+ }
253
+ function questionSchema() {
254
+ return {
255
+ type: "object",
256
+ properties: {
257
+ id: { type: "string" },
258
+ question: { type: "string", minLength: 1 },
259
+ context: { type: "string" },
260
+ type: { type: "string", enum: ["single_select", "multi_select", "text"] },
261
+ options: optionArraySchema(),
262
+ recommendedOption: { type: "string" },
263
+ recommendedOptions: { type: "array", items: { type: "string" } },
264
+ recommendedText: { type: "string" },
265
+ recommendationReason: { type: "string" },
266
+ isMultiSelect: { type: "boolean" },
267
+ is_multi_select: { type: "boolean" },
268
+ allowCustomResponse: { type: "boolean" },
269
+ required: { type: "boolean" },
270
+ placeholder: { type: "string" }
271
+ },
272
+ required: ["question"],
273
+ additionalProperties: false
274
+ };
275
+ }
276
+ function slug(value, fallback) {
277
+ return value.toLocaleLowerCase("tr-TR").replace(/[ıİ]/g, "i").replace(/ğ/g, "g").replace(/ü/g, "u").replace(/ş/g, "s").replace(/ö/g, "o").replace(/ç/g, "c").normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || fallback;
278
+ }
279
+ function normalizeRequest(input) {
280
+ const sourceTabs = input.tabs !== void 0 ? input.tabs : [
281
+ {
282
+ label: "Questions",
283
+ questions: input.questions !== void 0 ? input.questions : [input]
284
+ }
285
+ ];
286
+ if (sourceTabs.length === 0) throw new Error("clarify requires at least one tab.");
287
+ const seen = /* @__PURE__ */ new Set();
288
+ const tabs = sourceTabs.map((tab, tabIndex) => {
289
+ if (!tab.label?.trim()) throw new Error("Every clarify tab requires a non-empty `label`.");
290
+ if (!Array.isArray(tab.questions) || tab.questions.length === 0) {
291
+ throw new Error(`clarify tab "${tab.label}" requires at least one question.`);
292
+ }
293
+ return {
294
+ id: tab.id ?? slug(tab.label, `tab_${tabIndex + 1}`),
295
+ label: tab.label,
296
+ description: tab.description,
297
+ questions: tab.questions.map((question, questionIndex) => {
298
+ if (!question.question?.trim())
299
+ throw new Error("Every clarify question requires non-empty `question`.");
300
+ const id = question.id ?? slug(question.question, `question_${questionIndex + 1}`);
301
+ if (seen.has(id)) throw new Error(`Duplicate clarify question id: ${id}`);
302
+ seen.add(id);
303
+ const legacyMulti = resolveLegacyMultiSelect(question);
304
+ if (question.type !== void 0 && legacyMulti !== void 0 && (question.type === "text" || question.type === "multi_select" !== legacyMulti)) {
305
+ throw new Error(
306
+ `clarify question "${question.question}" has conflicting \`type\` and \`isMultiSelect\` values. Use \`type\` as the canonical field or remove the legacy flag.`
307
+ );
308
+ }
309
+ const kind = question.type ?? (legacyMulti ? "multi_select" : "single_select");
310
+ const seenOptions = /* @__PURE__ */ new Set();
311
+ const options = (question.options ?? []).map((option, index) => {
312
+ const value = typeof option === "string" ? { label: option } : option;
313
+ if (!value?.label?.trim()) {
314
+ throw new Error(
315
+ `clarify question "${question.question}" option ${index + 1} requires a non-empty \`label\`; \`description\` alone is not selectable.`
316
+ );
317
+ }
318
+ const normalized = {
319
+ id: value.id ?? slug(value.label, `option_${index + 1}`),
320
+ label: value.label,
321
+ description: value.description
322
+ };
323
+ if (seenOptions.has(normalized.id)) {
324
+ throw new Error(
325
+ `clarify question "${question.question}" has duplicate option id: ${normalized.id}`
326
+ );
327
+ }
328
+ seenOptions.add(normalized.id);
329
+ return normalized;
330
+ });
331
+ if (kind !== "text" && options.length < 2)
332
+ throw new Error(`clarify question "${question.question}" requires at least 2 options.`);
333
+ const explicitRecommended = question.recommendedOptions ?? (question.recommendedOption ? [question.recommendedOption] : []);
334
+ const recommended = explicitRecommended.length > 0 ? explicitRecommended : options.filter((option) => /^\s*\(?recommended\)?\b/i.test(option.label)).slice(0, kind === "single_select" ? 1 : void 0).map((option) => option.id);
335
+ const recommendedOptionIds = recommended.map(
336
+ (value) => options.find((option) => option.id === value || option.label === value)?.id ?? value
337
+ );
338
+ const unknownRecommendation = recommendedOptionIds.find(
339
+ (recommendedId) => !options.some((option) => option.id === recommendedId)
340
+ );
341
+ if (unknownRecommendation) {
342
+ throw new Error(
343
+ `clarify question "${question.question}" recommends unknown option: ${unknownRecommendation}`
344
+ );
345
+ }
346
+ if (kind === "single_select" && recommendedOptionIds.length > 1)
347
+ throw new Error(
348
+ `Single-select question "${question.question}" can recommend only one option.`
349
+ );
350
+ if (kind === "single_select" && recommendedOptionIds.length > 0 && question.recommendedText?.trim()) {
351
+ throw new Error(
352
+ `Single-select question "${question.question}" cannot recommend both an option and a custom text answer.`
353
+ );
354
+ }
355
+ return {
356
+ id,
357
+ prompt: question.question,
358
+ description: question.context,
359
+ kind,
360
+ required: question.required ?? true,
361
+ options: kind === "text" ? void 0 : options,
362
+ recommendedOptionIds,
363
+ recommendedText: question.recommendedText,
364
+ recommendationReason: question.recommendationReason,
365
+ allowCustomResponse: kind === "text" ? void 0 : question.allowCustomResponse ?? true,
366
+ placeholder: question.placeholder
367
+ };
368
+ })
369
+ };
370
+ });
371
+ return {
372
+ id: `clarify_${randomUUID()}`,
373
+ title: input.title?.trim() || "A few decisions are needed",
374
+ description: input.description,
375
+ submitLabel: input.submitLabel ?? "Submit answers",
376
+ tabs
377
+ };
378
+ }
379
+ function recommendedResponse(request) {
380
+ return {
381
+ requestId: request.id,
382
+ status: "submitted",
383
+ answers: request.tabs.flatMap(
384
+ (tab) => tab.questions.map((question) => ({
385
+ questionId: question.id,
386
+ selectedOptionIds: question.recommendedOptionIds?.length ? question.recommendedOptionIds : question.kind === "text" ? [] : question.options?.slice(0, 1).map((option) => option.id) ?? [],
387
+ text: question.recommendedText,
388
+ delegated: false,
389
+ usedRecommendation: Boolean(
390
+ question.recommendedOptionIds?.length || question.recommendedText
391
+ )
392
+ }))
393
+ )
394
+ };
395
+ }
396
+ function materializeAnswers(request, answers) {
397
+ const byId = new Map(answers.map((answer) => [answer.questionId, answer]));
398
+ return request.tabs.flatMap(
399
+ (tab) => tab.questions.map((question) => {
400
+ const answer = byId.get(question.id) ?? {
401
+ questionId: question.id,
402
+ selectedOptionIds: [],
403
+ usedRecommendation: false
404
+ };
405
+ if (answer.delegated) {
406
+ return {
407
+ questionId: question.id,
408
+ question: question.prompt,
409
+ selectedOptions: [],
410
+ delegatedToModel: true,
411
+ usedRecommendation: false
412
+ };
413
+ }
414
+ return {
415
+ questionId: question.id,
416
+ question: question.prompt,
417
+ selectedOptions: answer.selectedOptionIds.map(
418
+ (id) => question.options?.find((option) => option.id === id)?.label ?? id
419
+ ),
420
+ customResponse: answer.text,
421
+ delegatedToModel: false,
422
+ usedRecommendation: matchesRecommendation(question, answer)
423
+ };
424
+ })
425
+ );
426
+ }
427
+ function resolveLegacyMultiSelect(question) {
428
+ if (question.isMultiSelect !== void 0 && question.is_multi_select !== void 0 && question.isMultiSelect !== question.is_multi_select) {
429
+ throw new Error(
430
+ `clarify question "${question.question}" has conflicting \`isMultiSelect\` and \`is_multi_select\` values.`
431
+ );
432
+ }
433
+ return question.isMultiSelect ?? question.is_multi_select;
434
+ }
435
+ function matchesRecommendation(question, answer) {
436
+ if (answer.delegated) return false;
437
+ const recommendedOptions = question.recommendedOptionIds ?? [];
438
+ const recommendedText = question.recommendedText?.trim() ?? "";
439
+ if (recommendedOptions.length === 0 && recommendedText.length === 0) return false;
440
+ const selected = answer.selectedOptionIds;
441
+ return selected.length === recommendedOptions.length && selected.every((id) => recommendedOptions.includes(id)) && (answer.text?.trim() ?? "") === recommendedText;
442
+ }
443
+ async function legacyHostResponse(ctx, request) {
444
+ const ask = ctx.askUserChoices;
445
+ if (typeof ask !== "function") return void 0;
446
+ const answers = [];
447
+ for (const question of request.tabs.flatMap((tab) => tab.questions)) {
448
+ const result = await ask(
449
+ question.prompt,
450
+ question.options?.map((option) => option.label) ?? [],
451
+ {
452
+ isMultiSelect: question.kind === "multi_select",
453
+ context: question.description,
454
+ recommendedOption: question.recommendedOptionIds?.[0],
455
+ allowCustomResponse: question.allowCustomResponse
456
+ }
457
+ );
458
+ const ids = result.selected.map(
459
+ (value) => question.options?.find((option) => option.label === value || option.id === value)?.id ?? value
460
+ );
461
+ answers.push({
462
+ questionId: question.id,
463
+ selectedOptionIds: ids,
464
+ text: result.custom,
465
+ usedRecommendation: ids.length === (question.recommendedOptionIds?.length ?? 0) && ids.every((id) => question.recommendedOptionIds?.includes(id))
466
+ });
467
+ }
468
+ return { requestId: request.id, status: "submitted", answers };
469
+ }
470
+ function skipped(request, error) {
471
+ return {
472
+ status: "skipped",
473
+ question: request.tabs[0]?.questions[0]?.prompt ?? "",
474
+ selectedOptions: [],
475
+ decisionSummary: "",
476
+ error
477
+ };
478
+ }
479
+ function skippedFromInput(input, error) {
480
+ return {
481
+ status: "skipped",
482
+ question: input.question ?? input.questions?.[0]?.question ?? input.tabs?.[0]?.questions[0]?.question ?? "",
483
+ selectedOptions: [],
484
+ decisionSummary: "",
485
+ error
486
+ };
487
+ }
293
488
 
294
489
  // src/pwsh.ts
295
490
  import { spawn } from "node:child_process";
@@ -1497,4 +1692,4 @@ export {
1497
1692
  TIER3_TOOLS,
1498
1693
  builtinTools
1499
1694
  };
1500
- //# sourceMappingURL=chunk-OVFQC6FU.js.map
1695
+ //# sourceMappingURL=chunk-GNLHBDFO.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  tryLegacyPackageOperation
3
- } from "./chunk-JQ43NVFU.js";
3
+ } from "./chunk-3LQJXJDM.js";
4
4
  import {
5
5
  spawnStream
6
6
  } from "./chunk-EMA2XENR.js";
@@ -8,7 +8,7 @@ import {
8
8
  detectPackageManager,
9
9
  normalizeCommandOutput,
10
10
  safeResolveReal
11
- } from "./chunk-HKR56VNF.js";
11
+ } from "./chunk-2IAEJRLT.js";
12
12
 
13
13
  // src/install.ts
14
14
  import { join } from "node:path";
@@ -253,4 +253,4 @@ function resolveManifestPath(cwd, pkgManager) {
253
253
  export {
254
254
  installTool
255
255
  };
256
- //# sourceMappingURL=chunk-ZETZHHV5.js.map
256
+ //# sourceMappingURL=chunk-H2VB7GCR.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  tryLegacyPackageOperation
3
- } from "./chunk-JQ43NVFU.js";
3
+ } from "./chunk-3LQJXJDM.js";
4
4
  import {
5
5
  spawnStream
6
6
  } from "./chunk-EMA2XENR.js";
@@ -9,7 +9,7 @@ import {
9
9
  detectPackageManager,
10
10
  normalizeCommandOutput,
11
11
  safeResolveReal
12
- } from "./chunk-HKR56VNF.js";
12
+ } from "./chunk-2IAEJRLT.js";
13
13
 
14
14
  // src/audit.ts
15
15
  import { ToolValidationError } from "@wrongstack/core/types";
@@ -220,4 +220,4 @@ function extractAdvisories(data) {
220
220
  export {
221
221
  auditTool
222
222
  };
223
- //# sourceMappingURL=chunk-C64UUXTA.js.map
223
+ //# sourceMappingURL=chunk-HCPSVK7J.js.map