@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.
- package/dist/_shell-pick.d.ts +5 -10
- package/dist/audit.js +10 -4055
- package/dist/bash.js +9 -2332
- package/dist/batch-tool-use.js +2 -187
- package/dist/browser/index.js +25 -1339
- package/dist/builtin.js +69 -33985
- package/dist/chunk-2CTB3UK5.js +197 -0
- package/dist/chunk-2ME7ZWDD.js +1272 -0
- package/dist/chunk-2TFQDEPU.js +113 -0
- package/dist/chunk-2ZCJNKFE.js +315 -0
- package/dist/chunk-3PVYQFRQ.js +472 -0
- package/dist/chunk-3QMQBO5Z.js +89 -0
- package/dist/chunk-42AKKX7O.js +385 -0
- package/dist/chunk-42SKEETB.js +113 -0
- package/dist/chunk-4BHUSMNL.js +104 -0
- package/dist/chunk-4I4LWFOO.js +431 -0
- package/dist/chunk-553IQUMO.js +478 -0
- package/dist/chunk-55KF77OK.js +1944 -0
- package/dist/chunk-6HLZK37X.js +359 -0
- package/dist/chunk-6RNIFLG4.js +305 -0
- package/dist/chunk-A6BRWIXN.js +317 -0
- package/dist/chunk-AHHDWNXC.js +200 -0
- package/dist/chunk-B2CIOKWP.js +899 -0
- package/dist/chunk-BTSFTGIL.js +7 -0
- package/dist/chunk-D33S4RQG.js +222 -0
- package/dist/chunk-DIJKHSUO.js +134 -0
- package/dist/chunk-DPL47QGS.js +3515 -0
- package/dist/chunk-EMRGN4KM.js +170 -0
- package/dist/chunk-FGOABE4Z.js +955 -0
- package/dist/chunk-FW65TP4G.js +520 -0
- package/dist/chunk-GOIZNNOA.js +221 -0
- package/dist/chunk-H5MJUAA6.js +295 -0
- package/dist/chunk-H5OHH6F4.js +223 -0
- package/dist/chunk-HC5RM4WY.js +199 -0
- package/dist/chunk-HEQFMLZ7.js +190 -0
- package/dist/chunk-HFXSNKCV.js +196 -0
- package/dist/chunk-HXSBQ2SE.js +1723 -0
- package/dist/chunk-ILESNGNM.js +163 -0
- package/dist/chunk-INF5CGAO.js +137 -0
- package/dist/chunk-IWDFZ6XR.js +337 -0
- package/dist/chunk-IXMNTNW5.js +238 -0
- package/dist/chunk-J4SRJKBW.js +428 -0
- package/dist/chunk-J5SKD6LN.js +235 -0
- package/dist/chunk-JRJJX3L5.js +424 -0
- package/dist/chunk-KXNDHMUB.js +366 -0
- package/dist/chunk-LIJQMYYX.js +59 -0
- package/dist/chunk-MAN26PZ5.js +260 -0
- package/dist/chunk-MKRQMLZA.js +474 -0
- package/dist/chunk-NBDZMMG2.js +169 -0
- package/dist/chunk-NHA6BEIH.js +101 -0
- package/dist/chunk-OA6YJIWL.js +4412 -0
- package/dist/chunk-OZC255IC.js +2830 -0
- package/dist/chunk-PUR6WU2B.js +154 -0
- package/dist/chunk-Q5JPPCZC.js +12 -0
- package/dist/chunk-QBV725YG.js +128 -0
- package/dist/chunk-QHVW4AAM.js +892 -0
- package/dist/chunk-QNJ7AQ4B.js +1000 -0
- package/dist/chunk-RGISBZBD.js +231 -0
- package/dist/chunk-RJZQWEMS.js +84 -0
- package/dist/chunk-RP2ZA47V.js +15 -0
- package/dist/chunk-RXISBFR5.js +315 -0
- package/dist/chunk-S5CCLZL3.js +99 -0
- package/dist/chunk-TVTCSBJF.js +122 -0
- package/dist/chunk-U4D5S3VQ.js +21 -0
- package/dist/chunk-U4VAATKP.js +193 -0
- package/dist/chunk-UKU2ZX4C.js +203 -0
- package/dist/chunk-UNSKG6DC.js +159 -0
- package/dist/chunk-URFKEIHM.js +1091 -0
- package/dist/chunk-UTV7YAW7.js +178 -0
- package/dist/chunk-V43EQ6VR.js +68 -0
- package/dist/chunk-VB3MJGCV.js +610 -0
- package/dist/chunk-WHIIIREL.js +223 -0
- package/dist/chunk-XMIFYKUT.js +1371 -0
- package/dist/chunk-YF2RSV34.js +54 -0
- package/dist/chunk-Z6DAKQ3U.js +1 -0
- package/dist/chunk-ZIGAZFWE.js +522 -0
- package/dist/circuit-breaker.js +3 -191
- package/dist/codebase-index/index.js +84 -13688
- package/dist/codebase-index/parser-worker-script.js +8 -3995
- package/dist/codebase-index/project-server.js +60 -8548
- package/dist/codebase-index/worker.js +20 -8416
- package/dist/design.js +2 -299
- package/dist/diff.js +4 -256
- package/dist/document.js +4 -222
- package/dist/e2e.js +5 -476
- package/dist/edit.js +14 -10200
- package/dist/exec.js +16 -3131
- package/dist/fetch.js +8 -398
- package/dist/format.js +10 -3750
- package/dist/generic-parser-VUXDIAOZ.js +13 -0
- package/dist/git.js +4 -380
- package/dist/glob.js +6 -280
- package/dist/go-parser-6CA77WZ4.js +469 -0
- package/dist/grep.js +7 -604
- package/dist/index.js +318 -35423
- package/dist/install.js +10 -4091
- package/dist/json-parser-7YMDJSBJ.js +213 -0
- package/dist/json.js +4 -665
- package/dist/kanban.js +3 -2096
- package/dist/languages/index.js +23 -4664
- package/dist/legacy-bridge-7UKZT46G.js +18 -0
- package/dist/lint.js +10 -3706
- package/dist/logs.js +4 -287
- package/dist/memory.js +6 -215
- package/dist/mode.js +3 -99
- package/dist/outdated.js +4 -5170
- package/dist/pack.js +65 -33933
- package/dist/parser-dispatch-VPOFDO2U.js +14 -0
- package/dist/patch.js +13 -9935
- package/dist/plan.js +5 -3286
- package/dist/process-registry.js +8 -651
- package/dist/ps-slash.js +12 -1439
- package/dist/py-parser-GPM5VYRG.js +16 -0
- package/dist/read.js +13 -9816
- package/dist/replace.js +15 -10014
- package/dist/rs-parser-WVQ4KYCX.js +81 -0
- package/dist/scaffold.js +4 -253
- package/dist/search.js +5 -585
- package/dist/session-kanban.js +30 -972
- package/dist/skill.js +2 -215
- package/dist/task.js +5 -3410
- package/dist/test.js +10 -3773
- package/dist/todo.js +4 -2891
- package/dist/tool-help.js +3 -123
- package/dist/tool-search.js +3 -79
- package/dist/tool-tier.js +68 -34020
- package/dist/tool-use.js +2 -93
- package/dist/tree-sitter-parser-YCILHGZD.js +17 -0
- package/dist/tree.js +4 -288
- package/dist/ts-parser-YH4ZMWMH.js +232 -0
- package/dist/typecheck.js +10 -3753
- package/dist/win32.js +6 -60
- package/dist/write.js +14 -9882
- package/dist/yaml-parser-GFAOKV53.js +157 -0
- package/package.json +5 -5
package/dist/design.js
CHANGED
|
@@ -1,303 +1,6 @@
|
|
|
1
|
-
// src/design.ts
|
|
2
|
-
import * as fs from "node:fs/promises";
|
|
3
|
-
import * as path from "node:path";
|
|
4
1
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
};
|
|
2
|
+
designTool
|
|
3
|
+
} from "./chunk-6RNIFLG4.js";
|
|
301
4
|
export {
|
|
302
5
|
designTool
|
|
303
6
|
};
|
package/dist/diff.js
CHANGED
|
@@ -1,259 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import * as path2 from "node:path";
|
|
6
|
-
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
7
|
-
import { ToolValidationError } from "@wrongstack/core/types";
|
|
8
|
-
|
|
9
|
-
// src/_util.ts
|
|
10
|
-
import * as fsp from "node:fs/promises";
|
|
11
|
-
import * as path from "node:path";
|
|
12
|
-
import * as Core from "@wrongstack/core/utils";
|
|
13
|
-
function resolvePath(input, ctx) {
|
|
14
|
-
return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);
|
|
15
|
-
}
|
|
16
|
-
function allowedRoots(ctx) {
|
|
17
|
-
return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];
|
|
18
|
-
}
|
|
19
|
-
function isInsideAny(target, roots) {
|
|
20
|
-
return roots.some((root) => {
|
|
21
|
-
const rel = path.relative(root, target);
|
|
22
|
-
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
function ensureInsideRoot(absPath, ctx) {
|
|
26
|
-
const target = path.resolve(absPath);
|
|
27
|
-
if (ctx.allowOutsideProjectRoot) return target;
|
|
28
|
-
if (isInsideAny(target, allowedRoots(ctx))) return target;
|
|
29
|
-
throw new Error(`Path "${absPath}" is outside project root "${path.resolve(ctx.projectRoot)}"`);
|
|
30
|
-
}
|
|
31
|
-
function safeResolve(input, ctx) {
|
|
32
|
-
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
33
|
-
}
|
|
34
|
-
async function resolveRealInsideRoot(absPath, ctx) {
|
|
35
|
-
if (ctx.allowOutsideProjectRoot) return absPath;
|
|
36
|
-
const realRoots = await Promise.all(
|
|
37
|
-
allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
|
|
38
|
-
);
|
|
39
|
-
let probe = absPath;
|
|
40
|
-
const pendingTail = [];
|
|
41
|
-
for (; ; ) {
|
|
42
|
-
let real;
|
|
43
|
-
try {
|
|
44
|
-
real = await fsp.realpath(probe);
|
|
45
|
-
} catch (err) {
|
|
46
|
-
if (err.code === "ENOENT") {
|
|
47
|
-
const parent = path.dirname(probe);
|
|
48
|
-
if (parent === probe) return absPath;
|
|
49
|
-
pendingTail.unshift(path.basename(probe));
|
|
50
|
-
probe = parent;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
throw err;
|
|
54
|
-
}
|
|
55
|
-
const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
|
|
56
|
-
if (isInsideAny(candidate, realRoots)) {
|
|
57
|
-
return candidate;
|
|
58
|
-
}
|
|
59
|
-
throw new Error(
|
|
60
|
-
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
async function safeResolveReal(input, ctx) {
|
|
65
|
-
const abs = safeResolve(input, ctx);
|
|
66
|
-
return await resolveRealInsideRoot(abs, ctx);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// src/diff.ts
|
|
70
|
-
var MAX_FILE_DUMP_BYTES = 5 * 1024 * 1024;
|
|
71
|
-
var MAX_GIT_DIFF_CHARS = 1e5;
|
|
72
|
-
var diffTool = {
|
|
73
|
-
name: "diff",
|
|
74
|
-
category: "Filesystem",
|
|
75
|
-
description: "Show file content with line numbers, staged/working-tree diffs via git, or commit/branch diffs. A safer and more structured alternative to raw `git diff` via shell.",
|
|
76
|
-
usageHint: 'USE FOR CODE REVIEW AND CHANGE INSPECTION:\n\n- `files` + no `a`/`b` \u2192 show file content with line numbers (NOT a unified diff; no +/- prefixes). Result `mode` is "dump".\n- `a` and/or `b` \u2192 git-style commit/branch diff (unified format, real +/- prefixes).\n- `staged: true` \u2192 only show staged changes.\n- `mode` only affects the git-diff path: "stat" runs `git diff --stat`; "side-by-side" is not supported and falls back to unified (result `mode` reports what was produced).\n- `context` sets the unified-diff context line count on the git path (`-U<n>`); the dump path has no context notion.\n\nNOTE: For a true file-vs-file unified diff, supply `a` and `b` so the tool delegates to `git diff`. The `files`-only path is a line-numbered dump, not a diff.\n\nThis tool has important safety guards against flag injection (see previous security findings).',
|
|
77
|
-
permission: "auto",
|
|
78
|
-
mutating: false,
|
|
79
|
-
maxOutputBytes: 262144,
|
|
80
|
-
capabilities: ["fs.read"],
|
|
81
|
-
icon: "diff",
|
|
82
|
-
timeoutMs: 1e4,
|
|
83
|
-
inputSchema: {
|
|
84
|
-
type: "object",
|
|
85
|
-
properties: {
|
|
86
|
-
path: {
|
|
87
|
-
type: "string",
|
|
88
|
-
description: "Working directory for the diff operation (defaults to project root)."
|
|
89
|
-
},
|
|
90
|
-
files: {
|
|
91
|
-
type: "string",
|
|
92
|
-
description: 'Files or globs to diff (e.g. "src/**/*.ts" or comma-separated list).'
|
|
93
|
-
},
|
|
94
|
-
a: {
|
|
95
|
-
type: "string",
|
|
96
|
-
description: "First ref/commit/branch for git diff (e.g. HEAD, main, a commit hash)."
|
|
97
|
-
},
|
|
98
|
-
b: {
|
|
99
|
-
type: "string",
|
|
100
|
-
description: "Second ref/commit/branch for git diff."
|
|
101
|
-
},
|
|
102
|
-
staged: {
|
|
103
|
-
type: "boolean",
|
|
104
|
-
description: "If true, only show changes that are staged in git."
|
|
105
|
-
},
|
|
106
|
-
mode: {
|
|
107
|
-
type: "string",
|
|
108
|
-
enum: ["unified", "side-by-side", "stat"],
|
|
109
|
-
description: 'Output format for the git-diff path. "unified" is default; "stat" shows a summary only; "side-by-side" is not supported and falls back to unified. The `files`-only dump path ignores this.'
|
|
110
|
-
},
|
|
111
|
-
context: {
|
|
112
|
-
type: "integer",
|
|
113
|
-
minimum: 0,
|
|
114
|
-
description: "Number of context lines for git unified diffs (default: 3, passed as -U<n>). Ignored by the `files`-only dump path."
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
async execute(input, ctx, opts) {
|
|
119
|
-
if (input.a !== void 0 || input.b !== void 0) {
|
|
120
|
-
return await gitDiff(input, ctx, opts.signal);
|
|
121
|
-
}
|
|
122
|
-
return await fileDiff(input, ctx, opts.signal);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
async function gitDiff(input, ctx, signal) {
|
|
126
|
-
if (input.a?.startsWith("-")) {
|
|
127
|
-
throw new ToolValidationError({
|
|
128
|
-
message: `diff: unsafe ref "${input.a}" \u2014 refs may not begin with '-' (flag injection)`,
|
|
129
|
-
field: "a"
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
if (input.b?.startsWith("-")) {
|
|
133
|
-
throw new ToolValidationError({
|
|
134
|
-
message: `diff: unsafe ref "${input.b}" \u2014 refs may not begin with '-' (flag injection)`,
|
|
135
|
-
field: "b"
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
const requestedMode = input.mode ?? "unified";
|
|
139
|
-
const statMode = requestedMode === "stat";
|
|
140
|
-
const effectiveMode = statMode ? "stat" : "unified";
|
|
141
|
-
const sideBySideNote = requestedMode === "side-by-side" ? "side-by-side output is not supported; a unified diff was produced instead." : void 0;
|
|
142
|
-
const gitDir = findGitDir(ctx.cwd);
|
|
143
|
-
if (!gitDir) {
|
|
144
|
-
return { diff: "", files: [], truncated: false, mode: effectiveMode };
|
|
145
|
-
}
|
|
146
|
-
const args = ["diff", "--no-color"];
|
|
147
|
-
if (statMode) args.push("--stat");
|
|
148
|
-
if (!statMode && input.context !== void 0) {
|
|
149
|
-
const contextLines = Math.max(0, Math.floor(input.context));
|
|
150
|
-
if (Number.isFinite(contextLines)) args.push(`-U${contextLines}`);
|
|
151
|
-
}
|
|
152
|
-
if (input.staged) args.push("--staged");
|
|
153
|
-
if (input.a) args.push(input.a);
|
|
154
|
-
if (input.b) args.push(input.b);
|
|
155
|
-
if (input.files) {
|
|
156
|
-
const files = Array.isArray(input.files) ? input.files : input.files.split(",");
|
|
157
|
-
args.push("--", ...files.map((f) => f.trim()));
|
|
158
|
-
}
|
|
159
|
-
const result = await runGit(args, gitDir, signal);
|
|
160
|
-
let diff = result.stdout;
|
|
161
|
-
let truncated = false;
|
|
162
|
-
if (diff.length > MAX_GIT_DIFF_CHARS) {
|
|
163
|
-
let clipped = diff.slice(0, MAX_GIT_DIFF_CHARS);
|
|
164
|
-
const nl = clipped.lastIndexOf("\n");
|
|
165
|
-
if (nl > 0) clipped = clipped.slice(0, nl);
|
|
166
|
-
diff = `${clipped}
|
|
167
|
-
\u2026[git diff truncated: ${result.stdout.length - clipped.length} of ${result.stdout.length} characters omitted]`;
|
|
168
|
-
truncated = true;
|
|
169
|
-
}
|
|
170
|
-
return {
|
|
171
|
-
diff,
|
|
172
|
-
files: [],
|
|
173
|
-
truncated,
|
|
174
|
-
mode: effectiveMode,
|
|
175
|
-
note: sideBySideNote
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function findGitDir(cwd) {
|
|
179
|
-
let dir = cwd;
|
|
180
|
-
for (let i = 0; i < 20; i++) {
|
|
181
|
-
try {
|
|
182
|
-
const stat2 = statSync(path2.join(dir, ".git"));
|
|
183
|
-
if (stat2.isDirectory()) return dir;
|
|
184
|
-
} catch {
|
|
185
|
-
}
|
|
186
|
-
const parent = path2.dirname(dir);
|
|
187
|
-
if (parent === dir) break;
|
|
188
|
-
dir = parent;
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
function runGit(args, cwd, signal) {
|
|
193
|
-
return new Promise((resolve2) => {
|
|
194
|
-
let stdout = "";
|
|
195
|
-
let stderr = "";
|
|
196
|
-
const child = spawn("git", args, {
|
|
197
|
-
cwd,
|
|
198
|
-
signal,
|
|
199
|
-
env: buildChildEnv(),
|
|
200
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
201
|
-
windowsHide: true
|
|
202
|
-
});
|
|
203
|
-
child.stdout?.on("data", (c) => {
|
|
204
|
-
stdout += c.toString();
|
|
205
|
-
});
|
|
206
|
-
child.stderr?.on("data", (c) => {
|
|
207
|
-
stderr += c.toString();
|
|
208
|
-
});
|
|
209
|
-
child.on("close", (code) => resolve2({ stdout, stderr, exitCode: code ?? 0 }));
|
|
210
|
-
child.on("error", (e) => resolve2({ stdout: "", stderr: e.message, exitCode: 1 }));
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
async function fileDiff(input, ctx, _signal) {
|
|
214
|
-
void input.context;
|
|
215
|
-
const files = input.files ? (Array.isArray(input.files) ? input.files : input.files.split(",")).map((f) => f.trim()).filter(Boolean) : [];
|
|
216
|
-
if (files.length === 0) {
|
|
217
|
-
return {
|
|
218
|
-
diff: "No files specified",
|
|
219
|
-
files: [],
|
|
220
|
-
truncated: false,
|
|
221
|
-
mode: "dump"
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
const results = [];
|
|
225
|
-
let truncated = false;
|
|
226
|
-
for (const file of files) {
|
|
227
|
-
const absPath = await safeResolveReal(file, ctx);
|
|
228
|
-
const stat2 = await fs.stat(absPath).catch(() => null);
|
|
229
|
-
if (!stat2?.isFile()) continue;
|
|
230
|
-
if (stat2.size > MAX_FILE_DUMP_BYTES) {
|
|
231
|
-
truncated = true;
|
|
232
|
-
results.push(
|
|
233
|
-
`--- ${file} (skipped: ${stat2.size} bytes exceeds the ${MAX_FILE_DUMP_BYTES} limit; use the read tool with offset/limit) ---`
|
|
234
|
-
);
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
const content = await fs.readFile(absPath, "utf8");
|
|
238
|
-
const lines = content.split(/\r?\n/);
|
|
239
|
-
results.push(formatWithLineNumbers(file, lines));
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
diff: results.join("\n\n"),
|
|
243
|
-
files,
|
|
244
|
-
truncated,
|
|
245
|
-
// Honest mode: this path always produces a line-numbered dump — it never
|
|
246
|
-
// honors `mode`, so it must not echo the requested value back.
|
|
247
|
-
mode: "dump",
|
|
248
|
-
note: input.mode !== void 0 ? "The `files`-only path is a line-numbered dump; `mode` only affects the git-diff path (`a`/`b`)." : void 0
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
function formatWithLineNumbers(file, lines) {
|
|
252
|
-
const width = String(lines.length).length;
|
|
253
|
-
const numbered = lines.map((line, i) => `${String(i + 1).padStart(width)} | ${line}`).join("\n");
|
|
254
|
-
return `--- ${file} (line-numbered dump, not a unified diff) ---
|
|
255
|
-
${numbered}`;
|
|
256
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
diffTool
|
|
3
|
+
} from "./chunk-UKU2ZX4C.js";
|
|
4
|
+
import "./chunk-D33S4RQG.js";
|
|
257
5
|
export {
|
|
258
6
|
diffTool
|
|
259
7
|
};
|