@wrongstack/tools 0.310.1 → 0.313.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 (135) hide show
  1. package/dist/_shell-pick.d.ts +5 -10
  2. package/dist/audit.js +10 -4055
  3. package/dist/bash.js +9 -2332
  4. package/dist/batch-tool-use.js +2 -187
  5. package/dist/browser/index.js +25 -1339
  6. package/dist/builtin.js +69 -33985
  7. package/dist/chunk-2CTB3UK5.js +197 -0
  8. package/dist/chunk-2ME7ZWDD.js +1272 -0
  9. package/dist/chunk-2TFQDEPU.js +113 -0
  10. package/dist/chunk-2ZCJNKFE.js +315 -0
  11. package/dist/chunk-3PVYQFRQ.js +472 -0
  12. package/dist/chunk-3QMQBO5Z.js +89 -0
  13. package/dist/chunk-42AKKX7O.js +385 -0
  14. package/dist/chunk-42SKEETB.js +113 -0
  15. package/dist/chunk-4BHUSMNL.js +104 -0
  16. package/dist/chunk-4I4LWFOO.js +431 -0
  17. package/dist/chunk-553IQUMO.js +478 -0
  18. package/dist/chunk-55KF77OK.js +1944 -0
  19. package/dist/chunk-6HLZK37X.js +359 -0
  20. package/dist/chunk-6RNIFLG4.js +305 -0
  21. package/dist/chunk-A6BRWIXN.js +317 -0
  22. package/dist/chunk-AHHDWNXC.js +200 -0
  23. package/dist/chunk-B2CIOKWP.js +899 -0
  24. package/dist/chunk-BTSFTGIL.js +7 -0
  25. package/dist/chunk-D33S4RQG.js +222 -0
  26. package/dist/chunk-DIJKHSUO.js +134 -0
  27. package/dist/chunk-DPL47QGS.js +3515 -0
  28. package/dist/chunk-EMRGN4KM.js +170 -0
  29. package/dist/chunk-FGOABE4Z.js +955 -0
  30. package/dist/chunk-FW65TP4G.js +520 -0
  31. package/dist/chunk-GOIZNNOA.js +221 -0
  32. package/dist/chunk-H5MJUAA6.js +295 -0
  33. package/dist/chunk-H5OHH6F4.js +223 -0
  34. package/dist/chunk-HC5RM4WY.js +199 -0
  35. package/dist/chunk-HEQFMLZ7.js +190 -0
  36. package/dist/chunk-HFXSNKCV.js +196 -0
  37. package/dist/chunk-HXSBQ2SE.js +1723 -0
  38. package/dist/chunk-ILESNGNM.js +163 -0
  39. package/dist/chunk-INF5CGAO.js +137 -0
  40. package/dist/chunk-IWDFZ6XR.js +337 -0
  41. package/dist/chunk-IXMNTNW5.js +238 -0
  42. package/dist/chunk-J4SRJKBW.js +428 -0
  43. package/dist/chunk-J5SKD6LN.js +235 -0
  44. package/dist/chunk-JRJJX3L5.js +424 -0
  45. package/dist/chunk-KXNDHMUB.js +366 -0
  46. package/dist/chunk-LIJQMYYX.js +59 -0
  47. package/dist/chunk-MAN26PZ5.js +260 -0
  48. package/dist/chunk-MKRQMLZA.js +474 -0
  49. package/dist/chunk-NBDZMMG2.js +169 -0
  50. package/dist/chunk-NHA6BEIH.js +101 -0
  51. package/dist/chunk-OA6YJIWL.js +4412 -0
  52. package/dist/chunk-OZC255IC.js +2830 -0
  53. package/dist/chunk-PUR6WU2B.js +154 -0
  54. package/dist/chunk-Q5JPPCZC.js +12 -0
  55. package/dist/chunk-QBV725YG.js +128 -0
  56. package/dist/chunk-QHVW4AAM.js +892 -0
  57. package/dist/chunk-QNJ7AQ4B.js +1000 -0
  58. package/dist/chunk-RGISBZBD.js +231 -0
  59. package/dist/chunk-RJZQWEMS.js +84 -0
  60. package/dist/chunk-RP2ZA47V.js +15 -0
  61. package/dist/chunk-RXISBFR5.js +315 -0
  62. package/dist/chunk-S5CCLZL3.js +99 -0
  63. package/dist/chunk-TVTCSBJF.js +122 -0
  64. package/dist/chunk-U4D5S3VQ.js +21 -0
  65. package/dist/chunk-U4VAATKP.js +193 -0
  66. package/dist/chunk-UKU2ZX4C.js +203 -0
  67. package/dist/chunk-UNSKG6DC.js +159 -0
  68. package/dist/chunk-URFKEIHM.js +1091 -0
  69. package/dist/chunk-UTV7YAW7.js +178 -0
  70. package/dist/chunk-V43EQ6VR.js +68 -0
  71. package/dist/chunk-VB3MJGCV.js +610 -0
  72. package/dist/chunk-WHIIIREL.js +223 -0
  73. package/dist/chunk-XMIFYKUT.js +1371 -0
  74. package/dist/chunk-YF2RSV34.js +54 -0
  75. package/dist/chunk-Z6DAKQ3U.js +1 -0
  76. package/dist/chunk-ZIGAZFWE.js +522 -0
  77. package/dist/circuit-breaker.js +3 -191
  78. package/dist/codebase-index/index.js +84 -13688
  79. package/dist/codebase-index/parser-worker-script.js +8 -3995
  80. package/dist/codebase-index/project-server.js +60 -8548
  81. package/dist/codebase-index/worker.js +20 -8416
  82. package/dist/design.js +2 -299
  83. package/dist/diff.js +4 -256
  84. package/dist/document.js +4 -222
  85. package/dist/e2e.js +5 -476
  86. package/dist/edit.js +14 -10200
  87. package/dist/exec.js +16 -3131
  88. package/dist/fetch.js +8 -398
  89. package/dist/format.js +10 -3750
  90. package/dist/generic-parser-VUXDIAOZ.js +13 -0
  91. package/dist/git.js +4 -380
  92. package/dist/glob.js +6 -280
  93. package/dist/go-parser-6CA77WZ4.js +469 -0
  94. package/dist/grep.js +7 -604
  95. package/dist/index.js +318 -35423
  96. package/dist/install.js +10 -4091
  97. package/dist/json-parser-7YMDJSBJ.js +213 -0
  98. package/dist/json.js +4 -665
  99. package/dist/kanban.js +3 -2096
  100. package/dist/languages/index.js +23 -4664
  101. package/dist/legacy-bridge-7UKZT46G.js +18 -0
  102. package/dist/lint.js +10 -3706
  103. package/dist/logs.js +4 -287
  104. package/dist/memory.js +6 -215
  105. package/dist/mode.js +3 -99
  106. package/dist/outdated.js +4 -5170
  107. package/dist/pack.js +65 -33933
  108. package/dist/parser-dispatch-VPOFDO2U.js +14 -0
  109. package/dist/patch.js +13 -9935
  110. package/dist/plan.js +5 -3286
  111. package/dist/process-registry.js +8 -651
  112. package/dist/ps-slash.js +12 -1439
  113. package/dist/py-parser-GPM5VYRG.js +16 -0
  114. package/dist/read.js +13 -9816
  115. package/dist/replace.js +15 -10014
  116. package/dist/rs-parser-WVQ4KYCX.js +81 -0
  117. package/dist/scaffold.js +4 -253
  118. package/dist/search.js +5 -585
  119. package/dist/session-kanban.js +30 -972
  120. package/dist/skill.js +2 -215
  121. package/dist/task.js +5 -3410
  122. package/dist/test.js +10 -3773
  123. package/dist/todo.js +4 -2891
  124. package/dist/tool-help.js +3 -123
  125. package/dist/tool-search.js +3 -79
  126. package/dist/tool-tier.js +68 -34020
  127. package/dist/tool-use.js +2 -93
  128. package/dist/tree-sitter-parser-YCILHGZD.js +17 -0
  129. package/dist/tree.js +4 -288
  130. package/dist/ts-parser-YH4ZMWMH.js +232 -0
  131. package/dist/typecheck.js +10 -3753
  132. package/dist/win32.js +6 -60
  133. package/dist/write.js +14 -9882
  134. package/dist/yaml-parser-GFAOKV53.js +157 -0
  135. package/package.json +5 -5
@@ -0,0 +1,359 @@
1
+ import {
2
+ compileUserRegex
3
+ } from "./chunk-Q5JPPCZC.js";
4
+ import {
5
+ enqueueReindex
6
+ } from "./chunk-2ME7ZWDD.js";
7
+ import {
8
+ isBinaryBuffer,
9
+ safeResolveReal,
10
+ sha256hex,
11
+ truncateDiffPayload
12
+ } from "./chunk-D33S4RQG.js";
13
+
14
+ // src/replace.ts
15
+ import { spawn } from "node:child_process";
16
+ import * as fs from "node:fs/promises";
17
+ import * as path from "node:path";
18
+ import { ToolValidationError } from "@wrongstack/core/types";
19
+ import {
20
+ atomicWrite,
21
+ buildChildEnv,
22
+ compileGlob,
23
+ detectNewlineStyle,
24
+ expectDefined,
25
+ normalizeToLf,
26
+ toStyle,
27
+ unifiedDiff
28
+ } from "@wrongstack/core/utils";
29
+ var MAX_DIFF_BYTES = 262144;
30
+ var DEFAULT_IGNORE = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
31
+ var replaceTool = {
32
+ name: "replace",
33
+ category: "Transform",
34
+ description: "Perform a search-and-replace across multiple files using a regex pattern. This is a powerful bulk transformation tool. Dry-run is ON by default \u2014 set `dry_run: false` to apply changes.",
35
+ usageHint: "DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\n\nRecommended workflow:\n1. Run without `dry_run: false` first to see exactly what would change (dry-run is the default).\n2. Review the diff output, then re-run with `dry_run: false` to apply.\n3. Use a specific enough `pattern` (and `glob` / `files`) to avoid accidental broad changes.\n4. `replace_all` controls whether only the first match per file or all matches are replaced.\n5. `replacement` supports regex substitutions: `$1`\u2013`$9` insert capture groups, `$&` inserts the whole match, and `$$` inserts a literal dollar sign.\nThis tool is excellent for large-scale refactors (renaming, import updates, etc.) but must be used with caution.",
36
+ permission: "confirm",
37
+ // WS-046: gives permission decisions something to key on.
38
+ // The file scope being rewritten, not the pattern: a trust rule should say
39
+ // where a bulk replace may run.
40
+ subjectKey: "files",
41
+ mutating: true,
42
+ capabilities: ["fs.write"],
43
+ icon: "edit",
44
+ timeoutMs: 3e4,
45
+ maxOutputBytes: 262144,
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ pattern: { type: "string", description: "Regex pattern to match" },
50
+ replacement: {
51
+ type: "string",
52
+ description: "Replacement string. Supports `$1`\u2013`$9` (capture groups), `$&` (whole match), and `$$` (literal dollar sign) \u2014 same semantics as JavaScript String.replace."
53
+ },
54
+ files: {
55
+ type: "string",
56
+ description: "File(s) to target: single path, comma-separated list, or glob pattern"
57
+ },
58
+ glob: { type: "string", description: 'Additional glob filter (e.g. "*.ts")' },
59
+ replace_all: {
60
+ type: "boolean",
61
+ description: "Replace all occurrences in each file (default: true)"
62
+ },
63
+ dry_run: { type: "boolean", description: "Preview changes without writing (default: true)" }
64
+ },
65
+ required: ["pattern", "replacement", "files"]
66
+ },
67
+ async execute(input, ctx) {
68
+ if (!input?.pattern) {
69
+ throw new ToolValidationError({
70
+ message: "replace: pattern is required",
71
+ field: "pattern"
72
+ });
73
+ }
74
+ if (input.replacement === void 0) {
75
+ throw new ToolValidationError({
76
+ message: "replace: replacement is required",
77
+ field: "replacement"
78
+ });
79
+ }
80
+ if (!input?.files) {
81
+ throw new ToolValidationError({
82
+ message: "replace: files is required",
83
+ field: "files"
84
+ });
85
+ }
86
+ const replaceAll = input.replace_all ?? true;
87
+ const compiled = compileUserRegex(input.pattern, "g");
88
+ if (!compiled.ok) {
89
+ throw new ToolValidationError({
90
+ message: `replace: ${compiled.reason}`,
91
+ field: "pattern"
92
+ });
93
+ }
94
+ const re = compiled.regex;
95
+ const globRe = input.glob ? compileGlob(input.glob) : null;
96
+ const dryRun = input.dry_run ?? true;
97
+ const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
98
+ const fileList = await resolveFiles(filesInput, ctx, globRe);
99
+ const realRoot = await fs.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
100
+ const results = [];
101
+ let totalReplacements = 0;
102
+ let diffBytesUsed = 0;
103
+ let diffsOmitted = 0;
104
+ let diffsTruncated = 0;
105
+ for (const absPath of fileList) {
106
+ const lstat2 = await fs.lstat(absPath).catch((err) => {
107
+ if (err.code === "ENOENT") return null;
108
+ throw err;
109
+ });
110
+ if (!lstat2?.isFile()) continue;
111
+ if (lstat2.isSymbolicLink()) continue;
112
+ let realPath;
113
+ try {
114
+ realPath = await fs.realpath(absPath);
115
+ } catch {
116
+ continue;
117
+ }
118
+ const rel = path.relative(realRoot, realPath);
119
+ if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
120
+ const stat2 = await fs.stat(realPath).catch(() => null);
121
+ if (!stat2?.isFile()) continue;
122
+ let content;
123
+ try {
124
+ const buf = await fs.readFile(realPath);
125
+ if (isBinaryBuffer(buf)) continue;
126
+ content = buf.toString("utf8");
127
+ } catch {
128
+ continue;
129
+ }
130
+ const style = detectNewlineStyle(content);
131
+ const contentLf = normalizeToLf(content);
132
+ re.lastIndex = 0;
133
+ const allMatches = [...contentLf.matchAll(re)];
134
+ if (allMatches.length === 0) continue;
135
+ const matches = replaceAll ? allMatches : allMatches.slice(0, 1);
136
+ const count = matches.length;
137
+ let newContentLf = contentLf;
138
+ for (let i = matches.length - 1; i >= 0; i--) {
139
+ const m = expectDefined(matches[i]);
140
+ newContentLf = newContentLf.slice(0, m.index) + expandReplacement(input.replacement, m) + newContentLf.slice(expectDefined(m.index) + m[0].length);
141
+ }
142
+ re.lastIndex = 0;
143
+ totalReplacements += count;
144
+ if (!dryRun) {
145
+ const newContent = toStyle(newContentLf, style);
146
+ await atomicWrite(realPath, newContent, { mode: stat2.mode & 511 });
147
+ const written = await fs.stat(realPath).catch(() => null);
148
+ if (written) {
149
+ ctx.recordRead?.(realPath, written.mtimeMs, "write", sha256hex(newContent));
150
+ }
151
+ ctx.session?.recordFileChange?.({
152
+ path: realPath,
153
+ action: "modified",
154
+ before: content,
155
+ after: newContent
156
+ });
157
+ }
158
+ let diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
159
+ fromFile: absPath,
160
+ toFile: absPath
161
+ }) : void 0;
162
+ if (diff !== void 0) {
163
+ const remaining = MAX_DIFF_BYTES - diffBytesUsed;
164
+ if (remaining <= 0) {
165
+ diff = void 0;
166
+ diffsOmitted++;
167
+ } else {
168
+ const capped = truncateDiffPayload(diff, remaining);
169
+ if (capped.truncated) diffsTruncated++;
170
+ diff = capped.text;
171
+ diffBytesUsed += Buffer.byteLength(diff, "utf8");
172
+ }
173
+ }
174
+ results.push({
175
+ path: absPath,
176
+ replacements: matches.length,
177
+ diff
178
+ });
179
+ }
180
+ if (!dryRun && results.length > 0) {
181
+ try {
182
+ enqueueReindex({
183
+ projectRoot: ctx.projectRoot,
184
+ files: results.map((r) => r.path)
185
+ });
186
+ } catch {
187
+ }
188
+ }
189
+ const overBudget = diffsOmitted > 0 || diffsTruncated > 0;
190
+ return {
191
+ files_modified: results.length,
192
+ total_replacements: totalReplacements,
193
+ results,
194
+ dry_run: dryRun,
195
+ 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
196
+ };
197
+ }
198
+ };
199
+ function expandReplacement(template, match) {
200
+ if (!template.includes("$")) return template;
201
+ let out = "";
202
+ for (let i = 0; i < template.length; i++) {
203
+ const ch = template[i];
204
+ if (ch !== "$") {
205
+ out += ch;
206
+ continue;
207
+ }
208
+ const next = template[i + 1];
209
+ if (next === "$") {
210
+ out += "$";
211
+ i++;
212
+ } else if (next === "&") {
213
+ out += match[0];
214
+ i++;
215
+ } else if (next !== void 0 && next >= "1" && next <= "9") {
216
+ const idx = next.charCodeAt(0) - 48;
217
+ if (idx < match.length) {
218
+ out += match[idx] ?? "";
219
+ i++;
220
+ } else {
221
+ out += "$";
222
+ }
223
+ } else {
224
+ out += "$";
225
+ }
226
+ }
227
+ return out;
228
+ }
229
+ function passesExtraGlob(extraGlob, name, full) {
230
+ extraGlob.lastIndex = 0;
231
+ const nameMatch = extraGlob.test(name);
232
+ extraGlob.lastIndex = 0;
233
+ const fullMatch = extraGlob.test(full);
234
+ extraGlob.lastIndex = 0;
235
+ return nameMatch || fullMatch;
236
+ }
237
+ async function resolveFiles(filesInput, ctx, extraGlob) {
238
+ const base = ctx.cwd;
239
+ const normalized = filesInput.trim();
240
+ if (normalized.startsWith("**/") || normalized.startsWith("*") || normalized.includes("**")) {
241
+ return await globFiles(normalized, base, extraGlob);
242
+ }
243
+ const parts = normalized.split(",").map((s) => s.trim()).filter(Boolean);
244
+ const resolved = [];
245
+ for (const p of parts) {
246
+ const absPath = await safeResolveReal(p, ctx);
247
+ if (extraGlob && !passesExtraGlob(extraGlob, path.basename(absPath), absPath)) continue;
248
+ const stat2 = await fs.stat(absPath).catch(() => null);
249
+ if (stat2?.isFile()) {
250
+ resolved.push(absPath);
251
+ }
252
+ }
253
+ return resolved;
254
+ }
255
+ async function globFiles(pattern, base, extraGlob) {
256
+ const rgAvailable = await checkRg();
257
+ if (rgAvailable) {
258
+ try {
259
+ const { promise } = spawnRgFind(pattern, base);
260
+ const files = await promise;
261
+ if (extraGlob) {
262
+ return files.filter((f) => passesExtraGlob(extraGlob, path.basename(f), f));
263
+ }
264
+ return files;
265
+ } catch {
266
+ }
267
+ }
268
+ return await globNative(pattern, base, extraGlob);
269
+ }
270
+ var rgAvailabilityCache;
271
+ function __resetRgDetectionForTests() {
272
+ rgAvailabilityCache = void 0;
273
+ }
274
+ function checkRg() {
275
+ rgAvailabilityCache ??= new Promise((resolve) => {
276
+ try {
277
+ const p = spawn("rg", ["--version"], {
278
+ env: buildChildEnv(),
279
+ stdio: "ignore",
280
+ windowsHide: true
281
+ });
282
+ p.on("error", () => resolve(false));
283
+ p.on("close", (code) => resolve(code === 0));
284
+ } catch {
285
+ resolve(false);
286
+ }
287
+ });
288
+ return rgAvailabilityCache;
289
+ }
290
+ function spawnRgFind(pattern, base) {
291
+ const args = ["--files", "--glob", pattern, base];
292
+ const child = spawn("rg", args, {
293
+ signal: AbortSignal.timeout(3e4),
294
+ env: buildChildEnv(),
295
+ stdio: ["ignore", "pipe", "pipe"],
296
+ windowsHide: true
297
+ });
298
+ const MAX_BUF_CHARS = 8 * 1024 * 1024;
299
+ let buf = "";
300
+ let truncated = false;
301
+ child.stdout?.on("data", (chunk) => {
302
+ if (truncated) return;
303
+ buf += chunk.toString();
304
+ if (buf.length > MAX_BUF_CHARS) {
305
+ truncated = true;
306
+ buf = buf.slice(0, buf.lastIndexOf("\n") + 1);
307
+ child.kill();
308
+ }
309
+ });
310
+ return {
311
+ promise: new Promise((resolve, reject) => {
312
+ child.on("error", reject);
313
+ child.on("close", () => {
314
+ resolve(buf.split("\n").filter(Boolean));
315
+ });
316
+ })
317
+ };
318
+ }
319
+ async function globNative(pattern, base, extraGlob) {
320
+ const results = [];
321
+ const globRe = compileGlob(pattern);
322
+ const walk = async (dir) => {
323
+ let entries;
324
+ try {
325
+ entries = await fs.readdir(dir, { withFileTypes: true });
326
+ } catch {
327
+ return;
328
+ }
329
+ for (const e of entries) {
330
+ if (DEFAULT_IGNORE.includes(e.name)) continue;
331
+ const full = path.join(dir, e.name);
332
+ try {
333
+ const stat2 = await fs.lstat(full);
334
+ if (stat2.isSymbolicLink()) continue;
335
+ } catch {
336
+ continue;
337
+ }
338
+ if (e.isDirectory()) {
339
+ await walk(full);
340
+ } else if (e.isFile()) {
341
+ const name = e.name;
342
+ if (globRe.test(name) || globRe.test(full)) {
343
+ if (extraGlob && !extraGlob.test(name) && !extraGlob.test(full)) continue;
344
+ results.push(full);
345
+ }
346
+ globRe.lastIndex = 0;
347
+ if (extraGlob) extraGlob.lastIndex = 0;
348
+ }
349
+ }
350
+ };
351
+ await walk(base);
352
+ return results;
353
+ }
354
+
355
+ export {
356
+ replaceTool,
357
+ __resetRgDetectionForTests
358
+ };
359
+ //# sourceMappingURL=chunk-6HLZK37X.js.map
@@ -0,0 +1,305 @@
1
+ // src/design.ts
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ import {
5
+ applyTokenOverrides,
6
+ getDesignKitLoader,
7
+ isDesignStack,
8
+ loadActiveKit,
9
+ materializeTokens,
10
+ recordKitChoice,
11
+ recordOverrides,
12
+ resolveSemanticTune,
13
+ runDesignVerify,
14
+ setActiveKit,
15
+ setDesignOverrides
16
+ } from "@wrongstack/core/design";
17
+ async function resolveReal(p) {
18
+ const resolved = path.resolve(p);
19
+ let probe = resolved;
20
+ const missing = [];
21
+ for (; ; ) {
22
+ try {
23
+ return path.resolve(await fs.realpath(probe), ...missing);
24
+ } catch (err) {
25
+ if (err.code === "ENOENT") {
26
+ const parent = path.dirname(probe);
27
+ if (parent === probe) return resolved;
28
+ missing.unshift(path.basename(probe));
29
+ probe = parent;
30
+ continue;
31
+ }
32
+ return resolved;
33
+ }
34
+ }
35
+ }
36
+ function normalizeOverrides(set) {
37
+ const out = {};
38
+ if (set && typeof set === "object") {
39
+ for (const [k, v] of Object.entries(set)) {
40
+ if (typeof v === "string") out[k] = v;
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+ var designTool = {
46
+ name: "design",
47
+ category: "Design",
48
+ description: 'Browse, load, customize, and enforce curated frontend/mobile UI design kits. Use BEFORE writing UI code to commit to one coherent, modern, responsive, dark/light, accessible design. Actions: "list" (menu), "use" (load+pin a kit for a stack), "foundations" (baseline), "set" (override kit colors/tokens), "materialize" (write the tokens to a real theme file \u2014 CSS @theme/OKLCH or native), "verify" (scan UI files for off-palette colors).',
49
+ usageHint: 'Flow: `design {action:"use", kit:"minimal-clarity", stack:"web"}` \u2192 optionally `design {action:"set", set:{primary:"oklch(62% 0.2 25)"}}` \u2192 `design {action:"materialize"}` to write tokens to disk \u2192 implement against them \u2192 `design {action:"verify"}`.',
50
+ permission: "confirm",
51
+ // WS-046: gives permission decisions something to key on.
52
+ // The action performed; `out` is optional so it cannot be the subject.
53
+ subjectKey: "action",
54
+ mutating: true,
55
+ capabilities: ["fs.write"],
56
+ timeoutMs: 15e3,
57
+ inputSchema: {
58
+ type: "object",
59
+ properties: {
60
+ action: {
61
+ type: "string",
62
+ enum: ["list", "use", "foundations", "set", "tune", "materialize", "verify"],
63
+ description: "list = menu; use = load+pin a kit; foundations = baseline; set = override colors/tokens; tune = high-level knobs (radius/density/font/motion); materialize = write tokens to a theme file; verify = scan UI for token drift. Default: list."
64
+ },
65
+ kit: {
66
+ type: "string",
67
+ description: 'Kit id (required for "use"), e.g. "minimal-clarity", "neo-brutalist".'
68
+ },
69
+ stack: {
70
+ type: "string",
71
+ enum: ["web", "react-native", "flutter", "swiftui", "compose"],
72
+ description: "Target stack \u2014 narrows guidance + materialize format. Default: web."
73
+ },
74
+ set: {
75
+ type: "object",
76
+ description: 'Token overrides for "set"/"use": { "primary": "oklch(\u2026)", "dark.bg": "#111" }. Bare key = both themes; "light."/"dark." prefix = that theme only. Empty value clears an override.',
77
+ additionalProperties: { type: "string" }
78
+ },
79
+ tune: {
80
+ type: "object",
81
+ description: 'High-level knobs for "tune": { "radius": "lg", "density": "compact", "font": "Space Grotesk", "motion": "snappy" }. radius = none|sm|md|lg|xl|full or a base length; density = compact|cozy|comfortable; motion = snappy|smooth|none. Resolved to concrete token overrides.',
82
+ properties: {
83
+ radius: { type: "string" },
84
+ density: { type: "string" },
85
+ font: { type: "string" },
86
+ motion: { type: "string" }
87
+ }
88
+ },
89
+ out: {
90
+ type: "string",
91
+ description: "Materialize output path (project-relative). Defaults to a per-stack convention."
92
+ },
93
+ force: {
94
+ type: "boolean",
95
+ description: "Materialize: overwrite an existing file (default false \u2014 refuses to clobber)."
96
+ },
97
+ files: {
98
+ type: "array",
99
+ items: { type: "string" },
100
+ description: "Verify: explicit project-relative files to scan. Default: a bounded UI-file walk."
101
+ }
102
+ },
103
+ required: []
104
+ },
105
+ async execute(input, ctx) {
106
+ const loader = getDesignKitLoader(ctx.projectRoot);
107
+ const action = input.action ?? "list";
108
+ const stack = input.stack && isDesignStack(input.stack) ? input.stack : void 0;
109
+ if (action === "foundations") {
110
+ const text = await loader.foundationsText(stack);
111
+ return { action, stack, output: text || "No foundations document is installed." };
112
+ }
113
+ if (action === "use") {
114
+ const kitId = input.kit?.trim();
115
+ if (!kitId) {
116
+ const menu2 = await loader.menuText();
117
+ return { action, output: `No kit id provided.
118
+
119
+ ${menu2}` };
120
+ }
121
+ const manifest = await loader.find(kitId);
122
+ if (!manifest) {
123
+ const menu2 = await loader.menuText();
124
+ return { action, kit: kitId, output: `Kit "${kitId}" not found.
125
+
126
+ ${menu2}` };
127
+ }
128
+ const resolvedStack = stack ?? manifest.stacks[0] ?? "web";
129
+ const body = await loader.readBody(manifest.id, resolvedStack);
130
+ const rawTokens = await loader.readTokens(manifest.id);
131
+ const persisted = await loadActiveKit(ctx.projectRoot);
132
+ const keepOverrides = persisted?.kit === manifest.id ? persisted.overrides ?? {} : {};
133
+ const overrides = { ...keepOverrides, ...normalizeOverrides(input.set) };
134
+ const tokens = rawTokens ? applyTokenOverrides(rawTokens, overrides) : rawTokens;
135
+ setActiveKit(ctx, manifest.id, resolvedStack, overrides);
136
+ await recordKitChoice(
137
+ ctx.projectRoot,
138
+ manifest.id,
139
+ resolvedStack,
140
+ "design-tool",
141
+ (/* @__PURE__ */ new Date()).toISOString(),
142
+ Object.keys(overrides).length ? overrides : void 0
143
+ );
144
+ const ovLine = Object.keys(overrides).length ? `
145
+ Active color overrides: ${Object.entries(overrides).map(([k, v]) => `${k}=${v}`).join(", ")}
146
+ ` : "";
147
+ const header = `# Active design kit: ${manifest.name} (${manifest.id}) \u2014 stack: ${resolvedStack}
148
+ ${manifest.aesthetic}
149
+ ${ovLine}
150
+ Implement the UI faithfully to this spec. Keep light/dark, responsive, and WCAG AA.
151
+ `;
152
+ const tokenBlock = tokens ? `
153
+ ## Token snapshot (overrides applied)
154
+ \`\`\`json
155
+ ${JSON.stringify(tokens, null, 2)}
156
+ \`\`\`
157
+ Tip: run \`design {action:"materialize"}\` to write these tokens to a real theme file.
158
+ ` : "";
159
+ return {
160
+ action,
161
+ kit: manifest.id,
162
+ stack: resolvedStack,
163
+ output: `${header}${tokenBlock}
164
+ ${body}`
165
+ };
166
+ }
167
+ if (action === "set") {
168
+ const patch = normalizeOverrides(input.set);
169
+ if (Object.keys(patch).length === 0) {
170
+ return { action, output: 'No overrides given. Pass set:{ "primary": "oklch(\u2026)" }.' };
171
+ }
172
+ const merged = await recordOverrides(ctx.projectRoot, patch, (/* @__PURE__ */ new Date()).toISOString());
173
+ if (!merged) {
174
+ return {
175
+ action,
176
+ output: 'No active kit. Pick one first: `design {action:"use", kit:"<id>"}`.'
177
+ };
178
+ }
179
+ setDesignOverrides(ctx, merged);
180
+ return {
181
+ action,
182
+ output: `Overrides updated. Active overrides: ${Object.entries(merged).map(([k, v]) => `${k}=${v}`).join(", ")}
183
+ These win over kit tokens. Run \`design {action:"materialize"}\` to write them to a theme file.`
184
+ };
185
+ }
186
+ if (action === "tune") {
187
+ const patch = resolveSemanticTune(input.tune ?? {});
188
+ if (Object.keys(patch).length === 0) {
189
+ return {
190
+ action,
191
+ output: 'No recognized knobs. Pass tune:{ radius:"lg", density:"compact", font:"\u2026", motion:"snappy" }.'
192
+ };
193
+ }
194
+ const merged = await recordOverrides(ctx.projectRoot, patch, (/* @__PURE__ */ new Date()).toISOString());
195
+ if (!merged) {
196
+ return {
197
+ action,
198
+ output: 'No active kit. Pick one first: `design {action:"use", kit:"<id>"}`.'
199
+ };
200
+ }
201
+ setDesignOverrides(ctx, merged);
202
+ return {
203
+ action,
204
+ output: `Tuned. Resolved ${Object.keys(patch).length} token override(s): ${Object.entries(patch).map(([k, v]) => `${k}=${v}`).join(", ")}
205
+ Run \`design {action:"materialize"}\` to write the tuned tokens to your theme file.`
206
+ };
207
+ }
208
+ if (action === "materialize") {
209
+ const active = await loadActiveKit(ctx.projectRoot);
210
+ if (!active) {
211
+ return {
212
+ action,
213
+ output: 'No active kit. Pick one first: `design {action:"use", kit:"<id>"}`.'
214
+ };
215
+ }
216
+ const resolvedStack = stack ?? (active.stack && isDesignStack(active.stack) ? active.stack : "web");
217
+ const rawTokens = await loader.readTokens(active.kit);
218
+ if (!rawTokens) {
219
+ return { action, kit: active.kit, output: `Kit "${active.kit}" has no tokens.json.` };
220
+ }
221
+ const tokens = applyTokenOverrides(rawTokens, active.overrides);
222
+ const result = materializeTokens({
223
+ tokens,
224
+ stack: resolvedStack,
225
+ kitId: active.kit,
226
+ outPath: input.out
227
+ });
228
+ const root = await resolveReal(ctx.projectRoot);
229
+ const absResolved = path.isAbsolute(result.path) ? path.resolve(result.path) : path.resolve(path.join(ctx.projectRoot, result.path));
230
+ const absParent = await resolveReal(path.dirname(absResolved));
231
+ const abs = path.join(absParent, path.basename(absResolved));
232
+ const rel = path.relative(root, abs);
233
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
234
+ throw new Error(`design: materialize path "${result.path}" would escape the project root`);
235
+ }
236
+ let exists = false;
237
+ try {
238
+ await fs.access(abs);
239
+ exists = true;
240
+ } catch {
241
+ }
242
+ if (exists && !input.force) {
243
+ return {
244
+ action,
245
+ kit: active.kit,
246
+ stack: resolvedStack,
247
+ path: result.path,
248
+ output: `${result.path} already exists. Re-run with force:true to overwrite, or write this ${result.format} yourself:
249
+
250
+ \`\`\`
251
+ ${result.content}
252
+ \`\`\``
253
+ };
254
+ }
255
+ await fs.mkdir(path.dirname(abs), { recursive: true });
256
+ await fs.writeFile(abs, result.content);
257
+ return {
258
+ action,
259
+ kit: active.kit,
260
+ stack: resolvedStack,
261
+ path: result.path,
262
+ output: `Wrote ${result.format} to ${result.path}. Import these tokens in your UI so the kit palette is the source of truth. ${exists ? "(overwrote existing file)" : ""}`
263
+ };
264
+ }
265
+ if (action === "verify") {
266
+ const active = await loadActiveKit(ctx.projectRoot);
267
+ if (!active) {
268
+ return {
269
+ action,
270
+ output: 'No active kit to verify against. Pick one: `design {action:"use", kit:"<id>"}`.'
271
+ };
272
+ }
273
+ const rawTokens = await loader.readTokens(active.kit);
274
+ if (!rawTokens) {
275
+ return { action, kit: active.kit, output: `Kit "${active.kit}" has no tokens.json.` };
276
+ }
277
+ const tokens = applyTokenOverrides(rawTokens, active.overrides);
278
+ const report = await runDesignVerify(ctx.projectRoot, tokens, input.files);
279
+ const pct = Math.round(report.score * 100);
280
+ const top = report.violations.slice(0, 25).map((v) => ` ${v.file}:${v.line} \u2014 ${v.reason}: ${v.snippet}`).join("\n");
281
+ const summary = `Adherence: ${pct}% on-palette across ${report.filesScanned} file(s). ${report.violations.length} violation(s).` + (report.violations.length ? `
282
+ ${top}${report.violations.length > 25 ? `
283
+ \u2026and ${report.violations.length - 25} more` : ""}
284
+
285
+ Replace off-palette colors with kit tokens (or the materialized CSS vars / token utilities).` : "\nNo off-palette colors found \u2014 UI adheres to the kit palette.");
286
+ return {
287
+ action,
288
+ kit: active.kit,
289
+ output: summary,
290
+ score: report.score,
291
+ violations: report.violations.length
292
+ };
293
+ }
294
+ const menu = await loader.menuText();
295
+ return {
296
+ action: "list",
297
+ output: (menu || "No design kits are installed.") + '\n\nLoad one with `design {action:"use", kit:"<id>", stack:"<stack>"}`.'
298
+ };
299
+ }
300
+ };
301
+
302
+ export {
303
+ designTool
304
+ };
305
+ //# sourceMappingURL=chunk-6RNIFLG4.js.map