@slowcook-ai/cli 0.17.0-alpha.5 → 0.17.0-alpha.7

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.
@@ -0,0 +1,350 @@
1
+ /**
2
+ * @slowcook 0.17.6+ — recon shape-test emission.
3
+ *
4
+ * Reads mock UI files for a story and emits structural assertions to
5
+ * `tests/integration/story-N-shape.test.tsx`. Pure deterministic; no LLM.
6
+ *
7
+ * Asserts SHAPE only (per `feedback_shape_tests_belong_to_recon_not_testgen`):
8
+ * - testid presence + cardinality
9
+ * - DOM containment (e.g. badge inside <header>)
10
+ * - className tokens (visual/a11y signals like `rounded-full`, `min-h-[44px]`)
11
+ * - CSS variable tokens (no inline hex/rgb)
12
+ *
13
+ * Never asserts WIRING (per `feedback_testgen_must_not_over_assert_mock`):
14
+ * - No spied-on hooks, no import existence, no readFileSync of source
15
+ * - No mock-only chrome (subtrees marked `data-mock-chrome="true"` are
16
+ * mechanically excluded from the testid+class extraction)
17
+ *
18
+ * Test rendering uses plain props (no scenario-runtime), so mock-chrome
19
+ * never actually renders in the test even if it weren't filtered.
20
+ */
21
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
22
+ import { join, relative } from "node:path";
23
+ const TOKENS_OF_INTEREST = [
24
+ // Shape tokens
25
+ "rounded-full", "rounded-2xl", "rounded-xl", "rounded-lg", "rounded-md", "rounded-sm",
26
+ // Tap-target / a11y mandates
27
+ "min-h-[44px]", "min-h-11", "h-11", "min-w-[44px]",
28
+ // Common layout primitives that imply structural intent
29
+ "flex", "grid", "inline-flex",
30
+ ];
31
+ /**
32
+ * Scan a mock UI file, return the SHAPE-relevant facts after stripping
33
+ * mock-only chrome subtrees.
34
+ */
35
+ export function extractShape(absFile, repoRoot) {
36
+ if (!existsSync(absFile))
37
+ return null;
38
+ const body = readFileSync(absFile, "utf8");
39
+ const fileRel = relative(repoRoot, absFile).replace(/\\/g, "/");
40
+ // Strip mock-only chrome subtrees BEFORE extraction.
41
+ const cleaned = stripMockChromeSubtrees(body);
42
+ // Component name: simplest extraction.
43
+ const nameMatch = cleaned.match(/export\s+(?:default\s+)?function\s+([A-Z]\w*)/);
44
+ const componentName = nameMatch ? nameMatch[1] ?? null : null;
45
+ const testids = [...new Set(extractTestids(cleaned))];
46
+ const visualTokens = [...new Set(extractTokens(cleaned, TOKENS_OF_INTEREST))];
47
+ const hasHeader = /<header\b/.test(cleaned);
48
+ return { file: fileRel, testids, visualTokens, hasHeader, componentName };
49
+ }
50
+ /**
51
+ * Strip JSX subtrees marked as mock-only chrome. Three conventions
52
+ * recognised (any one is enough):
53
+ * 1. `data-mock-chrome="true"` attribute on the wrapping element
54
+ * 2. JSX comment immediately preceding: `{/* Mock-only ... */ `}`
55
+ * 3.;
56
+ Source;
57
+ comment;
58
+ immediately;
59
+ preceding;
60
+ the;
61
+ element: `// Mock-only ...`
62
+ *
63
+ * Conservative;
64
+ false;
65
+ positives(treating, real, shape);
66
+ are
67
+ * worse;
68
+ than;
69
+ false;
70
+ negatives.We;
71
+ only;
72
+ strip;
73
+ elements;
74
+ with (the)
75
+ explicit
76
+ * marker;
77
+ the;
78
+ comment - based;
79
+ heuristics;
80
+ are;
81
+ best - effort;
82
+ fallback.
83
+ * /;
84
+ export function stripMockChromeSubtrees(source) {
85
+ let out = source;
86
+ // Pass 1: strip elements opened with data-mock-chrome="true" (and their
87
+ // children up to the matching closing tag).
88
+ out = stripBalancedJsxBlock(out, /<(\w+)\b[^>]*\bdata-mock-chrome\s*=\s*["']true["'][^>]*>/);
89
+ // Pass 2: best-effort — strip the JSX block that immediately follows
90
+ // a `{/* Mock-only ... */}` JSX comment.
91
+ out = stripBalancedJsxBlockAfter(out, /\{\/\*\s*Mock-only[^*]*\*\/\s*\}\s*/);
92
+ return out;
93
+ }
94
+ /**
95
+ * Repeatedly find a JSX opening tag matching `openRe` and remove the
96
+ * balanced subtree (open tag + children + close tag).
97
+ *
98
+ * Self-closing tags (e.g. `<Foo />`) are also handled. Tag-name balanced.
99
+ */
100
+ function stripBalancedJsxBlock(source, openRe) {
101
+ let s = source;
102
+ for (let safety = 0; safety < 64; safety++) {
103
+ const m = openRe.exec(s);
104
+ if (!m)
105
+ break;
106
+ const start = m.index;
107
+ const tagName = m[1];
108
+ const openTag = m[0];
109
+ // Self-closing? remove just that.
110
+ if (openTag.endsWith("/>")) {
111
+ s = s.slice(0, start) + s.slice(start + openTag.length);
112
+ openRe.lastIndex = 0;
113
+ continue;
114
+ }
115
+ // Walk forward to find balanced close.
116
+ let depth = 1;
117
+ let i = start + openTag.length;
118
+ const openTagRe = new RegExp(`<${tagName}\\b[^>]*?>`, "g");
119
+ const closeTagRe = new RegExp(`</${tagName}\\s*>`, "g");
120
+ while (i < s.length && depth > 0) {
121
+ openTagRe.lastIndex = i;
122
+ closeTagRe.lastIndex = i;
123
+ const oNext = openTagRe.exec(s);
124
+ const cNext = closeTagRe.exec(s);
125
+ if (!cNext)
126
+ break;
127
+ if (oNext && oNext.index < cNext.index) {
128
+ // skip self-closing inside (rare for compound tag)
129
+ if (!oNext[0].endsWith("/>"))
130
+ depth++;
131
+ i = oNext.index + oNext[0].length;
132
+ }
133
+ else {
134
+ depth--;
135
+ i = cNext.index + cNext[0].length;
136
+ if (depth === 0) {
137
+ s = s.slice(0, start) + s.slice(i);
138
+ break;
139
+ }
140
+ }
141
+ }
142
+ openRe.lastIndex = 0;
143
+ }
144
+ return s;
145
+ }
146
+ function stripBalancedJsxBlockAfter(source, markerRe) {
147
+ let s = source;
148
+ for (let safety = 0; safety < 32; safety++) {
149
+ const m = markerRe.exec(s);
150
+ if (!m)
151
+ break;
152
+ // Find the JSX opening tag immediately after the marker.
153
+ const after = s.slice(m.index + m[0].length);
154
+ const tagMatch = /^\s*<(\w+)\b[^>]*>/.exec(after);
155
+ if (!tagMatch) {
156
+ // Marker not followed by a JSX tag; just remove the marker comment.
157
+ s = s.slice(0, m.index) + s.slice(m.index + m[0].length);
158
+ continue;
159
+ }
160
+ // Use stripBalancedJsxBlock at the marker position with the tag-specific regex.
161
+ const openTagAbsoluteIdx = m.index + m[0].length + (after.indexOf("<"));
162
+ const tagName = tagMatch[1];
163
+ if (!tagName) {
164
+ s = s.slice(0, m.index) + s.slice(m.index + m[0].length);
165
+ continue;
166
+ }
167
+ const openTagRe = new RegExp(`<${tagName}\\b[^>]*?>`);
168
+ const sliced = s.slice(openTagAbsoluteIdx);
169
+ const stripped = stripBalancedJsxBlock(sliced, openTagRe);
170
+ s = s.slice(0, m.index) + stripped;
171
+ markerRe.lastIndex = 0;
172
+ }
173
+ return s;
174
+ }
175
+ function extractTestids(source) {
176
+ const out = [];
177
+ for (const m of source.matchAll(/data-testid\s*=\s*["']([^"']+)["']/g)) {
178
+ if (m[1])
179
+ out.push(m[1]);
180
+ }
181
+ return out;
182
+ }
183
+ function extractTokens(source, tokens) {
184
+ const out = [];
185
+ for (const t of tokens) {
186
+ // Look in className strings/template literals.
187
+ const re = new RegExp(`className\\s*=\\s*["'\`][^"'\`]*\\b${escapeRegExp(t)}\\b`);
188
+ if (re.test(source))
189
+ out.push(t);
190
+ }
191
+ return out;
192
+ }
193
+ function escapeRegExp(s) {
194
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
195
+ }
196
+ export function synthesiseShapeTestFile(opts) {
197
+ const { story, shapes } = opts;
198
+ const lines = [];
199
+ lines.push("// @vitest-environment jsdom");
200
+ lines.push("//");
201
+ lines.push(`// AUTO-GENERATED by slowcook recon (story-${story}). Do not edit by hand.`);
202
+ lines.push("// Asserts STRUCTURAL SHAPE only (DOM containment, testid cardinality,");
203
+ lines.push("// className tokens, CSS-variable usage). Behavioral assertions live in");
204
+ lines.push(`// the testgen-emitted story-${story}-*.test.{ts,tsx} files.`);
205
+ lines.push("//");
206
+ lines.push("// Mock-only chrome (data-mock-chrome=\"true\" subtrees) is excluded.");
207
+ lines.push("// Re-running recon regenerates this file.");
208
+ lines.push("");
209
+ lines.push(`import { describe, it, expect } from "vitest";`);
210
+ lines.push("");
211
+ lines.push(`describe("story-${story} structural shape (recon-emitted)", () => {`);
212
+ // For each unique testid across all shapes, emit cardinality + presence assertions
213
+ // backed by source-greppable checks (NOT a render — recon doesn't have a
214
+ // render-helper available without the consumer's test setup).
215
+ // The render-based assertions need the consumer's renderWithProviders helper +
216
+ // Component import + props; recon emits a TODO scaffold the operator (or a
217
+ // future iteration of brew) fills in.
218
+ const allTestids = [...new Set(shapes.flatMap((s) => s.testids))];
219
+ if (allTestids.length === 0) {
220
+ lines.push(` it.skip("no testids found in mock UI — nothing to assert", () => {});`);
221
+ }
222
+ else {
223
+ // Source-grep based assertions: cheap + always work without a render.
224
+ lines.push(``);
225
+ lines.push(` // Source-grep assertions: testids the mock exposes must remain in src/`);
226
+ lines.push(` // after brew's edits. These don't require a render.`);
227
+ lines.push(` import { readFileSync, existsSync } from "node:fs";`);
228
+ lines.push(``);
229
+ for (const shape of shapes) {
230
+ const srcPath = mockToSrcPath(shape.file);
231
+ lines.push(` describe("from mock ${shape.file}", () => {`);
232
+ lines.push(` const srcPath = "${srcPath}";`);
233
+ lines.push(` const src = existsSync(srcPath) ? readFileSync(srcPath, "utf8") : "";`);
234
+ lines.push(``);
235
+ for (const tid of shape.testids) {
236
+ lines.push(` it("preserves data-testid='${tid}' (mock-only chrome excluded)", () => {`);
237
+ lines.push(` expect(src).toMatch(/data-testid\\s*=\\s*["']${escapeRegexForTest(tid)}["']/);`);
238
+ lines.push(` });`);
239
+ }
240
+ for (const token of shape.visualTokens) {
241
+ lines.push(` it("preserves visual token '${token}'", () => {`);
242
+ lines.push(` expect(src).toMatch(/className=[^>]*\\b${escapeRegexForTest(token)}\\b/);`);
243
+ lines.push(` });`);
244
+ }
245
+ if (shape.hasHeader) {
246
+ lines.push(` it("retains a <header> element (semantic landmark)", () => {`);
247
+ lines.push(` expect(src).toMatch(/<header\\b/);`);
248
+ lines.push(` });`);
249
+ }
250
+ lines.push(` });`);
251
+ lines.push(``);
252
+ }
253
+ // Anti-wiring assertion: no inline hex colors (force token-family preservation).
254
+ lines.push(` describe("token-family preservation", () => {`);
255
+ for (const shape of shapes) {
256
+ const srcPath = mockToSrcPath(shape.file);
257
+ lines.push(` it("${shape.componentName ?? shape.file}: no inline hex/rgb colors (use var(--) tokens)", () => {`);
258
+ lines.push(` const src = existsSync("${srcPath}") ? readFileSync("${srcPath}", "utf8") : "";`);
259
+ lines.push(` // Allow hex in JSDoc/comments; only flag in className/style attribute values.`);
260
+ lines.push(` const styled = src.match(/(?:className|style)\\s*=\\s*[\"\`'][^\"\`']*?[#][0-9a-fA-F]{3,8}[^\"\`']*?[\"\`']/g) || [];`);
261
+ lines.push(` expect(styled, "no inline hex in className/style").toEqual([]);`);
262
+ lines.push(` });`);
263
+ }
264
+ lines.push(` });`);
265
+ }
266
+ lines.push(`});`);
267
+ return lines.join("\n") + "\n";
268
+ }
269
+ function mockToSrcPath(mockPath) {
270
+ // mock/src/components/X.tsx → src/components/X.tsx
271
+ return mockPath.replace(/^mock\/src\//, "src/");
272
+ }
273
+ function escapeRegexForTest(s) {
274
+ return s.replace(/[.*+?^${}()|[\]\\\/]/g, "\\\\$&");
275
+ }
276
+ // ---- Discovery: which mock files belong to a story ----
277
+ /**
278
+ * Walk mock/src/ + return any file that contains `story-N` in source
279
+ * OR is referenced by a scenario file for the story.
280
+ *
281
+ * For 0.17.6 first cut: simple — any file under mock/src/components/
282
+ * that lives in a directory referenced by the story's scenario.
283
+ * Conservative — caller can pass explicit list via --mock-files.
284
+ */
285
+ export function findMockFilesForStory(repoRoot, story) {
286
+ const out = new Set();
287
+ // Look for mock/scenarios/story-N*.ts
288
+ const scenariosDir = join(repoRoot, "mock/scenarios");
289
+ if (existsSync(scenariosDir)) {
290
+ for (const name of readdirSync(scenariosDir)) {
291
+ if (!name.startsWith(`story-${story}`))
292
+ continue;
293
+ const body = readFileSync(join(scenariosDir, name), "utf8");
294
+ // Pull `from "@/components/X/Y"` imports
295
+ for (const m of body.matchAll(/from\s+["']@\/(components\/[\w\-./[\]]+)["']/g)) {
296
+ if (m[1]) {
297
+ const candidates = [
298
+ `mock/src/${m[1]}.tsx`,
299
+ `mock/src/${m[1]}.ts`,
300
+ `mock/src/${m[1]}/index.tsx`,
301
+ ];
302
+ for (const c of candidates) {
303
+ if (existsSync(join(repoRoot, c)))
304
+ out.add(c);
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+ // Also: any mock component referenced by a story-N test file
311
+ const testsDir = join(repoRoot, "tests/integration");
312
+ if (existsSync(testsDir)) {
313
+ for (const name of readdirSync(testsDir)) {
314
+ if (!name.startsWith(`story-${story}`))
315
+ continue;
316
+ const body = readFileSync(join(testsDir, name), "utf8");
317
+ for (const m of body.matchAll(/from\s+["']@\/(components\/[\w\-./[\]]+)["']/g)) {
318
+ if (m[1]) {
319
+ const candidates = [
320
+ `mock/src/${m[1]}.tsx`,
321
+ `mock/src/${m[1]}.ts`,
322
+ ];
323
+ for (const c of candidates) {
324
+ if (existsSync(join(repoRoot, c)))
325
+ out.add(c);
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
331
+ // Recurse: include direct imports of the discovered files
332
+ for (const file of [...out]) {
333
+ const body = readFileSync(join(repoRoot, file), "utf8");
334
+ for (const m of body.matchAll(/from\s+["']\.\/(\w+)["']/g)) {
335
+ if (m[1]) {
336
+ const dir = file.split("/").slice(0, -1).join("/");
337
+ const candidate = `${dir}/${m[1]}.tsx`;
338
+ if (existsSync(join(repoRoot, candidate)))
339
+ out.add(candidate);
340
+ }
341
+ }
342
+ }
343
+ return [...out];
344
+ }
345
+ // Re-export for tests
346
+ export { join as _join };
347
+ // re-export to placate tsc unused-import warnings
348
+ void readdirSync;
349
+ void statSync;
350
+ //# sourceMappingURL=shape-emit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape-emit.js","sourceRoot":"","sources":["../../../src/commands/recon/shape-emit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @slowcook 0.17.6+ — recon shape-preservation tests.
3
+ *
4
+ * Reads mock UI files for a story and emits structural assertions to
5
+ * tests/integration/story-N-shape.test.tsx. Pure deterministic; no LLM.
6
+ *
7
+ * Asserts SHAPE only (per `feedback_shape_tests_belong_to_recon_not_testgen`):
8
+ * - testid presence + cardinality
9
+ * - DOM containment (e.g. badge inside <header>)
10
+ * - className tokens (visual/a11y signals like `rounded-full`, `min-h-[44px]`)
11
+ * - CSS variable tokens (no inline hex/rgb)
12
+ *
13
+ * Never asserts WIRING (per `feedback_testgen_must_not_over_assert_mock`):
14
+ * - No spied-on hooks, no import existence, no readFileSync of source
15
+ * - No mock-only chrome (subtrees marked `data-mock-chrome="true"` are
16
+ * mechanically excluded from the testid+class extraction)
17
+ *
18
+ * Test rendering uses plain props (no scenario-runtime), so mock-chrome
19
+ * never actually renders in the test even if it weren't filtered.
20
+ */
21
+ import { join } from "node:path";
22
+ export interface ExtractedShape {
23
+ /** Component file scanned (mock-side). */
24
+ file: string;
25
+ /** Testids found on non-mock-chrome JSX (deduped). */
26
+ testids: string[];
27
+ /** Visual className tokens worth preserving (subset; see TOKENS_OF_INTEREST). */
28
+ visualTokens: string[];
29
+ /** Whether the component renders any element with a `<header>` ancestor. */
30
+ hasHeader: boolean;
31
+ /** Component name (best-effort). */
32
+ componentName: string | null;
33
+ }
34
+ /**
35
+ * Scan a mock UI file, return the SHAPE-relevant facts after stripping
36
+ * mock-only chrome subtrees.
37
+ */
38
+ export declare function extractShape(absFile: string, repoRoot: string): ExtractedShape | null;
39
+ /**
40
+ * Strip JSX subtrees marked as mock-only chrome. Two conventions:
41
+ * 1. data-mock-chrome="true" attribute on the wrapping element
42
+ * 2. JSX comment immediately preceding (matching /Mock-only/i)
43
+ *
44
+ * Conservative — false positives (treating real shape as chrome) are
45
+ * worse than false negatives.
46
+ */
47
+ export declare function stripMockChromeSubtrees(source: string): string;
48
+ interface SynthOpts {
49
+ story: string;
50
+ shapes: ExtractedShape[];
51
+ }
52
+ export declare function synthesiseShapeTestFile(opts: SynthOpts): string;
53
+ /**
54
+ * Walk mock/src/ + return any file that contains `story-N` in source
55
+ * OR is referenced by a scenario file for the story.
56
+ *
57
+ * For 0.17.6 first cut: simple — any file under mock/src/components/
58
+ * that lives in a directory referenced by the story's scenario.
59
+ * Conservative — caller can pass explicit list via --mock-files.
60
+ */
61
+ export declare function findMockFilesForStory(repoRoot: string, story: string): string[];
62
+ export { join as _join };
63
+ //# sourceMappingURL=shape-preserve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape-preserve.d.ts","sourceRoot":"","sources":["../../../src/commands/recon/shape-preserve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,IAAI,EAAY,MAAM,WAAW,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iFAAiF;IACjF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAWD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAiBrF;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAY9D;AA0GD,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAmE/D;AAaD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA+E/E;AAGD,OAAO,EAAE,IAAI,IAAI,KAAK,EAAE,CAAC"}