@agent-scope/cli 1.14.0 → 1.16.0

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/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/program.ts
4
- import { readFileSync as readFileSync9 } from "fs";
4
+ import { readFileSync as readFileSync11 } from "fs";
5
5
  import { generateTest, loadTrace } from "@agent-scope/playwright";
6
- import { Command as Command8 } from "commander";
6
+ import { Command as Command9 } from "commander";
7
7
 
8
8
  // src/browser.ts
9
9
  import { writeFileSync } from "fs";
@@ -39,27 +39,946 @@ async function browserCapture(options) {
39
39
  if (raw !== null && typeof raw === "object" && "error" in raw && typeof raw.error === "string") {
40
40
  throw new Error(`Scope capture failed: ${raw.error}`);
41
41
  }
42
- const report = { ...raw, route: null };
43
- return { report };
44
- } finally {
45
- await browser.close();
42
+ const report = { ...raw, route: null };
43
+ return { report };
44
+ } finally {
45
+ await browser.close();
46
+ }
47
+ }
48
+ function writeReportToFile(report, outputPath, pretty) {
49
+ const json = pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
50
+ writeFileSync(outputPath, json, "utf-8");
51
+ }
52
+
53
+ // src/ci/commands.ts
54
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
55
+ import { resolve as resolve2 } from "path";
56
+ import { generateManifest } from "@agent-scope/manifest";
57
+ import { BrowserPool, safeRender } from "@agent-scope/render";
58
+ import { ComplianceEngine, TokenResolver } from "@agent-scope/tokens";
59
+ import { Command } from "commander";
60
+
61
+ // src/component-bundler.ts
62
+ import { dirname } from "path";
63
+ import * as esbuild from "esbuild";
64
+ async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss) {
65
+ const bundledScript = await bundleComponentToIIFE(filePath, componentName, props);
66
+ return wrapInHtml(bundledScript, viewportWidth, projectCss);
67
+ }
68
+ async function bundleComponentToIIFE(filePath, componentName, props) {
69
+ const propsJson = JSON.stringify(props).replace(/<\/script>/gi, "<\\/script>");
70
+ const wrapperCode = (
71
+ /* ts */
72
+ `
73
+ import * as __scopeMod from ${JSON.stringify(filePath)};
74
+ import { createRoot } from "react-dom/client";
75
+ import { createElement } from "react";
76
+
77
+ (function scopeRenderHarness() {
78
+ var Component =
79
+ __scopeMod["default"] ||
80
+ __scopeMod[${JSON.stringify(componentName)}] ||
81
+ (Object.values(__scopeMod).find(
82
+ function(v) { return typeof v === "function" && /^[A-Z]/.test(v.name || ""); }
83
+ ));
84
+
85
+ if (!Component) {
86
+ window.__SCOPE_RENDER_ERROR__ =
87
+ "No renderable component found. Checked: default, " +
88
+ ${JSON.stringify(componentName)} + ", and PascalCase named exports. " +
89
+ "Available exports: " + Object.keys(__scopeMod).join(", ");
90
+ window.__SCOPE_RENDER_COMPLETE__ = true;
91
+ return;
92
+ }
93
+
94
+ try {
95
+ var props = ${propsJson};
96
+ var rootEl = document.getElementById("scope-root");
97
+ if (!rootEl) {
98
+ window.__SCOPE_RENDER_ERROR__ = "#scope-root element not found";
99
+ window.__SCOPE_RENDER_COMPLETE__ = true;
100
+ return;
101
+ }
102
+ createRoot(rootEl).render(createElement(Component, props));
103
+ // Use requestAnimationFrame to let React flush the render
104
+ requestAnimationFrame(function() {
105
+ window.__SCOPE_RENDER_COMPLETE__ = true;
106
+ });
107
+ } catch (err) {
108
+ window.__SCOPE_RENDER_ERROR__ = err instanceof Error ? err.message : String(err);
109
+ window.__SCOPE_RENDER_COMPLETE__ = true;
110
+ }
111
+ })();
112
+ `
113
+ );
114
+ const result = await esbuild.build({
115
+ stdin: {
116
+ contents: wrapperCode,
117
+ // Resolve relative imports (within the component's dir)
118
+ resolveDir: dirname(filePath),
119
+ loader: "tsx",
120
+ sourcefile: "__scope_harness__.tsx"
121
+ },
122
+ bundle: true,
123
+ format: "iife",
124
+ write: false,
125
+ platform: "browser",
126
+ jsx: "automatic",
127
+ jsxImportSource: "react",
128
+ target: "es2020",
129
+ // Bundle everything — no externals
130
+ external: [],
131
+ define: {
132
+ "process.env.NODE_ENV": '"development"',
133
+ global: "globalThis"
134
+ },
135
+ logLevel: "silent",
136
+ // Suppress "React must be in scope" warnings from old JSX (we use automatic)
137
+ banner: {
138
+ js: "/* @agent-scope/cli component harness */"
139
+ }
140
+ });
141
+ if (result.errors.length > 0) {
142
+ const msg = result.errors.map((e) => `${e.text}${e.location ? ` (${e.location.file}:${e.location.line})` : ""}`).join("\n");
143
+ throw new Error(`esbuild failed to bundle component:
144
+ ${msg}`);
145
+ }
146
+ const outputFile = result.outputFiles?.[0];
147
+ if (outputFile === void 0 || outputFile.text.length === 0) {
148
+ throw new Error("esbuild produced no output");
149
+ }
150
+ return outputFile.text;
151
+ }
152
+ function wrapInHtml(bundledScript, viewportWidth, projectCss) {
153
+ const projectStyleBlock = projectCss != null && projectCss.length > 0 ? `<style id="scope-project-css">
154
+ ${projectCss.replace(/<\/style>/gi, "<\\/style>")}
155
+ </style>` : "";
156
+ return `<!DOCTYPE html>
157
+ <html lang="en">
158
+ <head>
159
+ <meta charset="UTF-8" />
160
+ <meta name="viewport" content="width=${viewportWidth}, initial-scale=1.0" />
161
+ <style>
162
+ *, *::before, *::after { box-sizing: border-box; }
163
+ html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, sans-serif; }
164
+ #scope-root { display: inline-block; min-width: 1px; min-height: 1px; }
165
+ </style>
166
+ ${projectStyleBlock}
167
+ </head>
168
+ <body>
169
+ <div id="scope-root" data-reactscope-root></div>
170
+ <script>${bundledScript}</script>
171
+ </body>
172
+ </html>`;
173
+ }
174
+
175
+ // src/manifest-formatter.ts
176
+ function isTTY() {
177
+ return process.stdout.isTTY === true;
178
+ }
179
+ function pad(value, width) {
180
+ return value.length >= width ? value.slice(0, width) : value + " ".repeat(width - value.length);
181
+ }
182
+ function buildTable(headers, rows) {
183
+ const colWidths = headers.map(
184
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
185
+ );
186
+ const divider = colWidths.map((w) => "-".repeat(w)).join(" ");
187
+ const headerRow = headers.map((h, i) => pad(h, colWidths[i] ?? 0)).join(" ");
188
+ const dataRows = rows.map(
189
+ (row2) => row2.map((cell, i) => pad(cell ?? "", colWidths[i] ?? 0)).join(" ")
190
+ );
191
+ return [headerRow, divider, ...dataRows].join("\n");
192
+ }
193
+ function formatListTable(rows) {
194
+ if (rows.length === 0) return "No components found.";
195
+ const headers = ["NAME", "FILE", "COMPLEXITY", "HOOKS", "CONTEXTS"];
196
+ const tableRows = rows.map((r) => [
197
+ r.name,
198
+ r.file,
199
+ r.complexityClass,
200
+ String(r.hookCount),
201
+ String(r.contextCount)
202
+ ]);
203
+ return buildTable(headers, tableRows);
204
+ }
205
+ function formatListJson(rows) {
206
+ return JSON.stringify(rows, null, 2);
207
+ }
208
+ function formatSideEffects(se) {
209
+ const parts = [];
210
+ if (se.fetches.length > 0) parts.push(`fetches: ${se.fetches.join(", ")}`);
211
+ if (se.timers) parts.push("timers");
212
+ if (se.subscriptions.length > 0) parts.push(`subscriptions: ${se.subscriptions.join(", ")}`);
213
+ if (se.globalListeners) parts.push("globalListeners");
214
+ return parts.length > 0 ? parts.join(" | ") : "none";
215
+ }
216
+ function formatGetTable(name, descriptor) {
217
+ const propNames = Object.keys(descriptor.props);
218
+ const lines = [
219
+ `Component: ${name}`,
220
+ ` File: ${descriptor.filePath}`,
221
+ ` Export: ${descriptor.exportType}`,
222
+ ` Display Name: ${descriptor.displayName}`,
223
+ ` Complexity: ${descriptor.complexityClass}`,
224
+ ` Memoized: ${descriptor.memoized}`,
225
+ ` Forwarded Ref: ${descriptor.forwardedRef}`,
226
+ ` HOC Wrappers: ${descriptor.hocWrappers.join(", ") || "none"}`,
227
+ ` Hooks: ${descriptor.detectedHooks.join(", ") || "none"}`,
228
+ ` Contexts: ${descriptor.requiredContexts.join(", ") || "none"}`,
229
+ ` Composes: ${descriptor.composes.join(", ") || "none"}`,
230
+ ` Composed By: ${descriptor.composedBy.join(", ") || "none"}`,
231
+ ` Side Effects: ${formatSideEffects(descriptor.sideEffects)}`,
232
+ "",
233
+ ` Props (${propNames.length}):`
234
+ ];
235
+ if (propNames.length === 0) {
236
+ lines.push(" (none)");
237
+ } else {
238
+ for (const propName of propNames) {
239
+ const p = descriptor.props[propName];
240
+ if (p === void 0) continue;
241
+ const req = p.required ? "required" : "optional";
242
+ const def = p.default !== void 0 ? ` [default: ${p.default}]` : "";
243
+ const vals = p.values !== void 0 ? ` (${p.values.join(" | ")})` : "";
244
+ lines.push(` ${propName}: ${p.rawType}${vals} \u2014 ${req}${def}`);
245
+ }
246
+ }
247
+ return lines.join("\n");
248
+ }
249
+ function formatGetJson(name, descriptor) {
250
+ return JSON.stringify({ name, ...descriptor }, null, 2);
251
+ }
252
+ function formatQueryTable(rows, queryDesc) {
253
+ if (rows.length === 0) return `No components match: ${queryDesc}`;
254
+ const headers = ["NAME", "FILE", "COMPLEXITY", "HOOKS", "CONTEXTS"];
255
+ const tableRows = rows.map((r) => [r.name, r.file, r.complexityClass, r.hooks, r.contexts]);
256
+ return `Query: ${queryDesc}
257
+
258
+ ${buildTable(headers, tableRows)}`;
259
+ }
260
+ function formatQueryJson(rows) {
261
+ return JSON.stringify(rows, null, 2);
262
+ }
263
+ function matchGlob(pattern, value) {
264
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
265
+ const regexStr = escaped.replace(/\*\*/g, "\xA7GLOBSTAR\xA7").replace(/\*/g, "[^/]*").replace(/§GLOBSTAR§/g, ".*");
266
+ const regex = new RegExp(`^${regexStr}$`, "i");
267
+ return regex.test(value);
268
+ }
269
+
270
+ // src/render-formatter.ts
271
+ function parseViewport(spec) {
272
+ const lower = spec.toLowerCase();
273
+ const match = /^(\d+)[x×](\d+)$/.exec(lower);
274
+ if (!match) {
275
+ throw new Error(`Invalid viewport "${spec}". Expected format: WIDTHxHEIGHT (e.g. 1280x720)`);
276
+ }
277
+ const width = parseInt(match[1] ?? "0", 10);
278
+ const height = parseInt(match[2] ?? "0", 10);
279
+ if (width <= 0 || height <= 0) {
280
+ throw new Error(`Viewport dimensions must be positive integers, got: ${spec}`);
281
+ }
282
+ return { width, height };
283
+ }
284
+ function formatRenderJson(componentName, props, result) {
285
+ return {
286
+ component: componentName,
287
+ props,
288
+ width: result.width,
289
+ height: result.height,
290
+ renderTimeMs: result.renderTimeMs,
291
+ computedStyles: result.computedStyles,
292
+ screenshot: result.screenshot.toString("base64"),
293
+ dom: result.dom,
294
+ console: result.console,
295
+ accessibility: result.accessibility
296
+ };
297
+ }
298
+ function formatMatrixJson(result) {
299
+ return {
300
+ axes: result.axes.map((axis) => ({
301
+ name: axis.name,
302
+ values: axis.values.map((v) => String(v))
303
+ })),
304
+ stats: { ...result.stats },
305
+ cells: result.cells.map((cell) => ({
306
+ index: cell.index,
307
+ axisIndices: cell.axisIndices,
308
+ props: cell.props,
309
+ renderTimeMs: cell.result.renderTimeMs,
310
+ width: cell.result.width,
311
+ height: cell.result.height,
312
+ screenshot: cell.result.screenshot.toString("base64")
313
+ }))
314
+ };
315
+ }
316
+ function formatMatrixHtml(componentName, result) {
317
+ const cellsHtml = result.cells.map((cell) => {
318
+ const b64 = cell.result.screenshot.toString("base64");
319
+ const propLabel = escapeHtml(
320
+ Object.entries(cell.props).map(([k, v]) => `${k}: ${String(v)}`).join(", ")
321
+ );
322
+ return ` <div class="cell">
323
+ <img src="data:image/png;base64,${b64}" alt="${propLabel}" width="${cell.result.width}" height="${cell.result.height}" />
324
+ <div class="label">${propLabel}</div>
325
+ <div class="meta">${cell.result.renderTimeMs.toFixed(1)}ms</div>
326
+ </div>`;
327
+ }).join("\n");
328
+ const axesDesc = result.axes.map((a) => `${a.name}: ${a.values.map((v) => String(v)).join(", ")}`).join(" | ");
329
+ return `<!DOCTYPE html>
330
+ <html lang="en">
331
+ <head>
332
+ <meta charset="UTF-8" />
333
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
334
+ <title>${escapeHtml(componentName)} \u2014 Render Matrix</title>
335
+ <style>
336
+ body { font-family: system-ui, sans-serif; background: #f8fafc; margin: 0; padding: 24px; }
337
+ h1 { font-size: 1.25rem; color: #1e293b; margin-bottom: 8px; }
338
+ .axes { font-size: 0.8rem; color: #64748b; margin-bottom: 20px; }
339
+ .grid { display: flex; flex-wrap: wrap; gap: 16px; }
340
+ .cell { background: #fff; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; }
341
+ .cell img { display: block; }
342
+ .label { padding: 6px 8px; font-size: 0.75rem; color: #374151; border-top: 1px solid #f1f5f9; }
343
+ .meta { padding: 2px 8px 6px; font-size: 0.7rem; color: #94a3b8; }
344
+ .stats { margin-top: 24px; font-size: 0.8rem; color: #64748b; }
345
+ </style>
346
+ </head>
347
+ <body>
348
+ <h1>${escapeHtml(componentName)} \u2014 Render Matrix</h1>
349
+ <div class="axes">Axes: ${escapeHtml(axesDesc)}</div>
350
+ <div class="grid">
351
+ ${cellsHtml}
352
+ </div>
353
+ <div class="stats">
354
+ ${result.stats.totalCells} cells \xB7
355
+ avg ${result.stats.avgRenderTimeMs.toFixed(1)}ms \xB7
356
+ min ${result.stats.minRenderTimeMs.toFixed(1)}ms \xB7
357
+ max ${result.stats.maxRenderTimeMs.toFixed(1)}ms \xB7
358
+ wall ${result.stats.wallClockTimeMs.toFixed(0)}ms
359
+ </div>
360
+ </body>
361
+ </html>
362
+ `;
363
+ }
364
+ function formatMatrixCsv(componentName, result) {
365
+ const axisNames = result.axes.map((a) => a.name);
366
+ const headers = ["component", ...axisNames, "renderTimeMs", "width", "height"];
367
+ const rows = result.cells.map((cell) => {
368
+ const axisVals = result.axes.map((_, i) => {
369
+ const axisIdx = cell.axisIndices[i];
370
+ const axis = result.axes[i];
371
+ if (axisIdx === void 0 || axis === void 0) return "";
372
+ const val = axis.values[axisIdx];
373
+ return val !== void 0 ? csvEscape(String(val)) : "";
374
+ });
375
+ return [
376
+ csvEscape(componentName),
377
+ ...axisVals,
378
+ cell.result.renderTimeMs.toFixed(3),
379
+ String(cell.result.width),
380
+ String(cell.result.height)
381
+ ].join(",");
382
+ });
383
+ return `${[headers.join(","), ...rows].join("\n")}
384
+ `;
385
+ }
386
+ function renderProgressBar(completed, total, currentName, pct, barWidth = 20) {
387
+ const filled = Math.round(pct / 100 * barWidth);
388
+ const empty = barWidth - filled;
389
+ const bar = "=".repeat(Math.max(0, filled - 1)) + (filled > 0 ? ">" : "") + " ".repeat(empty);
390
+ const nameSlice = currentName.slice(0, 25).padEnd(25);
391
+ return `Rendering ${completed}/${total} ${nameSlice} [${bar}] ${pct}%`;
392
+ }
393
+ function formatSummaryText(results, outputDir) {
394
+ const total = results.length;
395
+ const passed = results.filter((r) => r.success).length;
396
+ const failed = total - passed;
397
+ const successTimes = results.filter((r) => r.success).map((r) => r.renderTimeMs);
398
+ const avgMs = successTimes.length > 0 ? successTimes.reduce((a, b) => a + b, 0) / successTimes.length : 0;
399
+ const lines = [
400
+ "\u2500".repeat(60),
401
+ `Render Summary`,
402
+ "\u2500".repeat(60),
403
+ ` Total components : ${total}`,
404
+ ` Passed : ${passed}`,
405
+ ` Failed : ${failed}`,
406
+ ` Avg render time : ${avgMs.toFixed(1)}ms`,
407
+ ` Output dir : ${outputDir}`
408
+ ];
409
+ if (failed > 0) {
410
+ lines.push("", " Failed components:");
411
+ for (const r of results) {
412
+ if (!r.success) {
413
+ lines.push(` \u2717 ${r.name}: ${r.errorMessage ?? "unknown error"}`);
414
+ }
415
+ }
416
+ }
417
+ lines.push("\u2500".repeat(60));
418
+ return lines.join("\n");
419
+ }
420
+ function escapeHtml(str) {
421
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
422
+ }
423
+ function csvEscape(value) {
424
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
425
+ return `"${value.replace(/"/g, '""')}"`;
426
+ }
427
+ return value;
428
+ }
429
+
430
+ // src/tailwind-css.ts
431
+ import { existsSync, readFileSync } from "fs";
432
+ import { createRequire } from "module";
433
+ import { resolve } from "path";
434
+ var CONFIG_FILENAMES = [
435
+ ".reactscope/config.json",
436
+ ".reactscope/config.js",
437
+ ".reactscope/config.mjs"
438
+ ];
439
+ var STYLE_ENTRY_CANDIDATES = [
440
+ "src/index.css",
441
+ "src/globals.css",
442
+ "app/globals.css",
443
+ "app/index.css",
444
+ "styles/index.css",
445
+ "index.css"
446
+ ];
447
+ var TAILWIND_IMPORT = /@import\s+["']tailwindcss["']\s*;?/;
448
+ var compilerCache = null;
449
+ function getCachedBuild(cwd) {
450
+ if (compilerCache !== null && resolve(compilerCache.cwd) === resolve(cwd)) {
451
+ return compilerCache.build;
452
+ }
453
+ return null;
454
+ }
455
+ function findStylesEntry(cwd) {
456
+ for (const name of CONFIG_FILENAMES) {
457
+ const p = resolve(cwd, name);
458
+ if (!existsSync(p)) continue;
459
+ try {
460
+ if (name.endsWith(".json")) {
461
+ const raw = readFileSync(p, "utf-8");
462
+ const data = JSON.parse(raw);
463
+ const scope = data.scope;
464
+ const entry = scope?.stylesEntry ?? data.stylesEntry;
465
+ if (typeof entry === "string") {
466
+ const full = resolve(cwd, entry);
467
+ if (existsSync(full)) return full;
468
+ }
469
+ }
470
+ } catch {
471
+ }
472
+ }
473
+ const pkgPath = resolve(cwd, "package.json");
474
+ if (existsSync(pkgPath)) {
475
+ try {
476
+ const raw = readFileSync(pkgPath, "utf-8");
477
+ const pkg = JSON.parse(raw);
478
+ const entry = pkg.scope?.stylesEntry;
479
+ if (typeof entry === "string") {
480
+ const full = resolve(cwd, entry);
481
+ if (existsSync(full)) return full;
482
+ }
483
+ } catch {
484
+ }
485
+ }
486
+ for (const candidate of STYLE_ENTRY_CANDIDATES) {
487
+ const full = resolve(cwd, candidate);
488
+ if (existsSync(full)) {
489
+ try {
490
+ const content = readFileSync(full, "utf-8");
491
+ if (TAILWIND_IMPORT.test(content)) return full;
492
+ } catch {
493
+ }
494
+ }
495
+ }
496
+ return null;
497
+ }
498
+ async function getTailwindCompiler(cwd) {
499
+ const cached = getCachedBuild(cwd);
500
+ if (cached !== null) return cached;
501
+ const entryPath = findStylesEntry(cwd);
502
+ if (entryPath === null) return null;
503
+ let compile;
504
+ try {
505
+ const require2 = createRequire(resolve(cwd, "package.json"));
506
+ const tailwind = require2("tailwindcss");
507
+ const fn = tailwind.compile;
508
+ if (typeof fn !== "function") return null;
509
+ compile = fn;
510
+ } catch {
511
+ return null;
512
+ }
513
+ const entryContent = readFileSync(entryPath, "utf-8");
514
+ const loadStylesheet = async (id, base) => {
515
+ if (id === "tailwindcss") {
516
+ const nodeModules = resolve(cwd, "node_modules");
517
+ const tailwindCssPath = resolve(nodeModules, "tailwindcss", "index.css");
518
+ if (!existsSync(tailwindCssPath)) {
519
+ throw new Error(
520
+ `Tailwind v4: tailwindcss package not found at ${tailwindCssPath}. Install with: npm install tailwindcss`
521
+ );
522
+ }
523
+ const content = readFileSync(tailwindCssPath, "utf-8");
524
+ return { path: "virtual:tailwindcss/index.css", base, content };
525
+ }
526
+ const full = resolve(base, id);
527
+ if (existsSync(full)) {
528
+ const content = readFileSync(full, "utf-8");
529
+ return { path: full, base: resolve(full, ".."), content };
530
+ }
531
+ throw new Error(`Tailwind v4: could not load stylesheet: ${id} (base: ${base})`);
532
+ };
533
+ const result = await compile(entryContent, {
534
+ base: cwd,
535
+ from: entryPath,
536
+ loadStylesheet
537
+ });
538
+ const build2 = result.build.bind(result);
539
+ compilerCache = { cwd, build: build2 };
540
+ return build2;
541
+ }
542
+ async function getCompiledCssForClasses(cwd, classes) {
543
+ const build2 = await getTailwindCompiler(cwd);
544
+ if (build2 === null) return null;
545
+ const deduped = [...new Set(classes)].filter(Boolean);
546
+ if (deduped.length === 0) return null;
547
+ return build2(deduped);
548
+ }
549
+
550
+ // src/ci/commands.ts
551
+ var CI_EXIT = {
552
+ /** All checks passed */
553
+ OK: 0,
554
+ /** Compliance score below threshold */
555
+ COMPLIANCE_BELOW_THRESHOLD: 1,
556
+ /** Accessibility violations found */
557
+ A11Y_VIOLATIONS: 2,
558
+ /** Console errors detected during render */
559
+ CONSOLE_ERRORS: 3,
560
+ /** Visual regression detected against baseline */
561
+ VISUAL_REGRESSION: 4,
562
+ /** One or more components failed to render */
563
+ RENDER_FAILURES: 5
564
+ };
565
+ var ALL_CHECKS = ["compliance", "a11y", "console-errors", "visual-regression"];
566
+ var _pool = null;
567
+ async function getPool(viewportWidth, viewportHeight) {
568
+ if (_pool === null) {
569
+ _pool = new BrowserPool({
570
+ size: { browsers: 1, pagesPerBrowser: 4 },
571
+ viewportWidth,
572
+ viewportHeight
573
+ });
574
+ await _pool.init();
575
+ }
576
+ return _pool;
577
+ }
578
+ async function shutdownPool() {
579
+ if (_pool !== null) {
580
+ await _pool.close();
581
+ _pool = null;
582
+ }
583
+ }
584
+ async function renderComponent(filePath, componentName, props, viewportWidth, viewportHeight) {
585
+ const pool = await getPool(viewportWidth, viewportHeight);
586
+ const htmlHarness = await buildComponentHarness(filePath, componentName, props, viewportWidth);
587
+ const slot = await pool.acquire();
588
+ const { page } = slot;
589
+ try {
590
+ await page.setContent(htmlHarness, { waitUntil: "load" });
591
+ await page.waitForFunction(
592
+ () => {
593
+ const w = window;
594
+ return w.__SCOPE_RENDER_COMPLETE__ === true;
595
+ },
596
+ { timeout: 15e3 }
597
+ );
598
+ const renderError = await page.evaluate(() => {
599
+ return window.__SCOPE_RENDER_ERROR__ ?? null;
600
+ });
601
+ if (renderError !== null) {
602
+ throw new Error(`Component render error: ${renderError}`);
603
+ }
604
+ const rootDir = process.cwd();
605
+ const classes = await page.evaluate(() => {
606
+ const set = /* @__PURE__ */ new Set();
607
+ document.querySelectorAll("[class]").forEach((el) => {
608
+ for (const c of el.className.split(/\s+/)) {
609
+ if (c) set.add(c);
610
+ }
611
+ });
612
+ return [...set];
613
+ });
614
+ const projectCss = await getCompiledCssForClasses(rootDir, classes);
615
+ if (projectCss != null && projectCss.length > 0) {
616
+ await page.addStyleTag({ content: projectCss });
617
+ }
618
+ const startMs = performance.now();
619
+ const rootLocator = page.locator("[data-reactscope-root]");
620
+ const boundingBox = await rootLocator.boundingBox();
621
+ if (boundingBox === null || boundingBox.width === 0 || boundingBox.height === 0) {
622
+ throw new Error(
623
+ `Component "${componentName}" rendered with zero bounding box \u2014 it may be invisible or not mounted`
624
+ );
625
+ }
626
+ const PAD = 24;
627
+ const MIN_W = 320;
628
+ const MIN_H = 200;
629
+ const clipX = Math.max(0, boundingBox.x - PAD);
630
+ const clipY = Math.max(0, boundingBox.y - PAD);
631
+ const rawW = boundingBox.width + PAD * 2;
632
+ const rawH = boundingBox.height + PAD * 2;
633
+ const clipW = Math.max(rawW, MIN_W);
634
+ const clipH = Math.max(rawH, MIN_H);
635
+ const safeW = Math.min(clipW, viewportWidth - clipX);
636
+ const safeH = Math.min(clipH, viewportHeight - clipY);
637
+ const screenshot = await page.screenshot({
638
+ clip: { x: clipX, y: clipY, width: safeW, height: safeH },
639
+ type: "png"
640
+ });
641
+ const computedStylesRaw = {};
642
+ const styles = await page.evaluate((sel) => {
643
+ const el = document.querySelector(sel);
644
+ if (el === null) return {};
645
+ const computed = window.getComputedStyle(el);
646
+ const out = {};
647
+ for (const prop of [
648
+ "display",
649
+ "width",
650
+ "height",
651
+ "color",
652
+ "backgroundColor",
653
+ "fontSize",
654
+ "fontFamily",
655
+ "padding",
656
+ "margin"
657
+ ]) {
658
+ out[prop] = computed.getPropertyValue(prop);
659
+ }
660
+ return out;
661
+ }, "[data-reactscope-root] > *");
662
+ computedStylesRaw["[data-reactscope-root] > *"] = styles;
663
+ const renderTimeMs = performance.now() - startMs;
664
+ return {
665
+ screenshot,
666
+ width: Math.round(safeW),
667
+ height: Math.round(safeH),
668
+ renderTimeMs,
669
+ computedStyles: computedStylesRaw
670
+ };
671
+ } finally {
672
+ pool.release(slot);
673
+ }
674
+ }
675
+ function extractComputedStyles(computedStylesRaw) {
676
+ const flat = {};
677
+ for (const styles of Object.values(computedStylesRaw)) {
678
+ Object.assign(flat, styles);
679
+ }
680
+ const colors = {};
681
+ const spacing = {};
682
+ const typography = {};
683
+ const borders = {};
684
+ const shadows = {};
685
+ for (const [prop, value] of Object.entries(flat)) {
686
+ if (prop === "color" || prop === "backgroundColor") {
687
+ colors[prop] = value;
688
+ } else if (prop === "padding" || prop === "margin") {
689
+ spacing[prop] = value;
690
+ } else if (prop === "fontSize" || prop === "fontFamily" || prop === "fontWeight" || prop === "lineHeight") {
691
+ typography[prop] = value;
692
+ } else if (prop === "borderRadius" || prop === "borderWidth") {
693
+ borders[prop] = value;
694
+ } else if (prop === "boxShadow") {
695
+ shadows[prop] = value;
696
+ }
697
+ }
698
+ return { colors, spacing, typography, borders, shadows };
699
+ }
700
+ function loadBaselineRenderJson(baselineDir, componentName) {
701
+ const jsonPath = resolve2(baselineDir, "renders", `${componentName}.json`);
702
+ if (!existsSync2(jsonPath)) return null;
703
+ return JSON.parse(readFileSync2(jsonPath, "utf-8"));
704
+ }
705
+ async function runCi(options = {}) {
706
+ const {
707
+ baselineDir: baselineDirRaw,
708
+ checks = ALL_CHECKS,
709
+ complianceThreshold = 0.9,
710
+ viewportWidth = 375,
711
+ viewportHeight = 812
712
+ } = options;
713
+ const startTime = performance.now();
714
+ const rootDir = process.cwd();
715
+ const checksSet = new Set(checks);
716
+ const baselineDir = baselineDirRaw !== void 0 ? resolve2(rootDir, baselineDirRaw) : void 0;
717
+ process.stderr.write("Scanning for React components\u2026\n");
718
+ const manifest = await generateManifest({ rootDir });
719
+ const componentNames = Object.keys(manifest.components);
720
+ const total = componentNames.length;
721
+ process.stderr.write(`Found ${total} components.
722
+ `);
723
+ process.stderr.write(`Rendering ${total} components\u2026
724
+ `);
725
+ const computedStylesMap = /* @__PURE__ */ new Map();
726
+ const currentRenderMeta = /* @__PURE__ */ new Map();
727
+ const renderFailures = /* @__PURE__ */ new Set();
728
+ let completed = 0;
729
+ const CONCURRENCY = 4;
730
+ let nextIdx = 0;
731
+ const renderOne = async (name) => {
732
+ const descriptor = manifest.components[name];
733
+ if (descriptor === void 0) return;
734
+ const filePath = resolve2(rootDir, descriptor.filePath);
735
+ const outcome = await safeRender(
736
+ () => renderComponent(filePath, name, {}, viewportWidth, viewportHeight),
737
+ {
738
+ props: {},
739
+ sourceLocation: {
740
+ file: descriptor.filePath,
741
+ line: descriptor.loc.start,
742
+ column: 0
743
+ }
744
+ }
745
+ );
746
+ completed++;
747
+ const pct = Math.round(completed / total * 100);
748
+ if (isTTY()) {
749
+ process.stderr.write(`${renderProgressBar(completed, total, name, pct)}\r`);
750
+ }
751
+ if (outcome.crashed) {
752
+ renderFailures.add(name);
753
+ return;
754
+ }
755
+ const result = outcome.result;
756
+ currentRenderMeta.set(name, { width: result.width, height: result.height });
757
+ computedStylesMap.set(name, extractComputedStyles(result.computedStyles));
758
+ };
759
+ if (total > 0) {
760
+ const worker = async () => {
761
+ while (nextIdx < componentNames.length) {
762
+ const i = nextIdx++;
763
+ const name = componentNames[i];
764
+ if (name !== void 0) {
765
+ await renderOne(name);
766
+ }
767
+ }
768
+ };
769
+ const workers = [];
770
+ for (let w = 0; w < Math.min(CONCURRENCY, total); w++) {
771
+ workers.push(worker());
772
+ }
773
+ await Promise.all(workers);
774
+ }
775
+ await shutdownPool();
776
+ if (isTTY() && total > 0) {
777
+ process.stderr.write("\n");
778
+ }
779
+ const resolver = new TokenResolver([]);
780
+ const engine = new ComplianceEngine(resolver);
781
+ const batchReport = engine.auditBatch(computedStylesMap);
782
+ const complianceScore = batchReport.aggregateCompliance;
783
+ const checkResults = [];
784
+ const renderFailureCount = renderFailures.size;
785
+ const rendersPassed = renderFailureCount === 0;
786
+ if (checksSet.has("compliance")) {
787
+ const compliancePassed = complianceScore >= complianceThreshold;
788
+ checkResults.push({
789
+ check: "compliance",
790
+ passed: compliancePassed,
791
+ message: compliancePassed ? `Compliance ${(complianceScore * 100).toFixed(1)}% >= threshold ${(complianceThreshold * 100).toFixed(1)}%` : `Compliance ${(complianceScore * 100).toFixed(1)}% < threshold ${(complianceThreshold * 100).toFixed(1)}%`,
792
+ value: complianceScore,
793
+ threshold: complianceThreshold
794
+ });
795
+ }
796
+ if (checksSet.has("a11y")) {
797
+ checkResults.push({
798
+ check: "a11y",
799
+ passed: true,
800
+ message: "Accessibility audit not yet implemented \u2014 skipped"
801
+ });
802
+ }
803
+ if (checksSet.has("console-errors")) {
804
+ const consoleErrorsPassed = rendersPassed;
805
+ checkResults.push({
806
+ check: "console-errors",
807
+ passed: consoleErrorsPassed,
808
+ message: consoleErrorsPassed ? "No console errors detected" : `Console errors likely \u2014 ${renderFailureCount} component(s) failed to render`
809
+ });
810
+ }
811
+ let hasVisualRegression = false;
812
+ if (checksSet.has("visual-regression") && baselineDir !== void 0) {
813
+ if (!existsSync2(baselineDir)) {
814
+ process.stderr.write(
815
+ `Warning: baseline directory not found at "${baselineDir}" \u2014 skipping visual regression check.
816
+ `
817
+ );
818
+ checkResults.push({
819
+ check: "visual-regression",
820
+ passed: true,
821
+ message: `Baseline not found at ${baselineDir} \u2014 skipped`
822
+ });
823
+ } else {
824
+ const regressions = [];
825
+ for (const name of componentNames) {
826
+ const baselineMeta = loadBaselineRenderJson(baselineDir, name);
827
+ const currentMeta = currentRenderMeta.get(name);
828
+ if (baselineMeta !== null && currentMeta !== void 0) {
829
+ const dw = Math.abs(currentMeta.width - baselineMeta.width);
830
+ const dh = Math.abs(currentMeta.height - baselineMeta.height);
831
+ if (dw > 10 || dh > 10) {
832
+ regressions.push(name);
833
+ }
834
+ }
835
+ }
836
+ hasVisualRegression = regressions.length > 0;
837
+ checkResults.push({
838
+ check: "visual-regression",
839
+ passed: !hasVisualRegression,
840
+ message: hasVisualRegression ? `Visual regression detected in ${regressions.length} component(s): ${regressions.slice(0, 5).join(", ")}${regressions.length > 5 ? "..." : ""}` : "No visual regressions detected"
841
+ });
842
+ }
843
+ }
844
+ let exitCode = CI_EXIT.OK;
845
+ const complianceResult = checkResults.find((r) => r.check === "compliance");
846
+ const a11yResult = checkResults.find((r) => r.check === "a11y");
847
+ const consoleResult = checkResults.find((r) => r.check === "console-errors");
848
+ const visualResult = checkResults.find((r) => r.check === "visual-regression");
849
+ if (complianceResult !== void 0 && !complianceResult.passed) {
850
+ exitCode = CI_EXIT.COMPLIANCE_BELOW_THRESHOLD;
851
+ } else if (a11yResult !== void 0 && !a11yResult.passed) {
852
+ exitCode = CI_EXIT.A11Y_VIOLATIONS;
853
+ } else if (consoleResult !== void 0 && !consoleResult.passed) {
854
+ exitCode = CI_EXIT.CONSOLE_ERRORS;
855
+ } else if (visualResult !== void 0 && !visualResult.passed) {
856
+ exitCode = CI_EXIT.VISUAL_REGRESSION;
857
+ } else if (!rendersPassed) {
858
+ exitCode = CI_EXIT.RENDER_FAILURES;
859
+ }
860
+ const passed = exitCode === CI_EXIT.OK;
861
+ const wallClockMs = performance.now() - startTime;
862
+ return {
863
+ ranAt: (/* @__PURE__ */ new Date()).toISOString(),
864
+ passed,
865
+ exitCode,
866
+ checks: checkResults,
867
+ components: {
868
+ total,
869
+ rendered: total - renderFailureCount,
870
+ failed: renderFailureCount
871
+ },
872
+ complianceScore,
873
+ complianceThreshold,
874
+ baselineCompared: baselineDir !== void 0 && existsSync2(baselineDir),
875
+ wallClockMs
876
+ };
877
+ }
878
+ function formatCiReport(result) {
879
+ const lines = [];
880
+ const title = "Scope CI Report";
881
+ const rule2 = "=".repeat(Math.max(title.length, 40));
882
+ lines.push(title, rule2);
883
+ const { total, rendered, failed } = result.components;
884
+ lines.push(
885
+ `Components: ${total} total ${rendered} rendered${failed > 0 ? ` ${failed} failed` : ""}`
886
+ );
887
+ lines.push("");
888
+ for (const check of result.checks) {
889
+ const icon = check.passed ? "pass" : "FAIL";
890
+ lines.push(` [${icon}] ${check.message}`);
891
+ }
892
+ if (result.components.failed > 0) {
893
+ lines.push(` [FAIL] ${result.components.failed} component(s) failed to render`);
894
+ }
895
+ lines.push("");
896
+ lines.push(rule2);
897
+ if (result.passed) {
898
+ lines.push(
899
+ `CI passed in ${(result.wallClockMs / 1e3).toFixed(1)}s (exit code ${result.exitCode})`
900
+ );
901
+ } else {
902
+ lines.push(
903
+ `CI failed in ${(result.wallClockMs / 1e3).toFixed(1)}s (exit code ${result.exitCode})`
904
+ );
905
+ }
906
+ return lines.join("\n");
907
+ }
908
+ function parseChecks(raw) {
909
+ if (raw === void 0) return void 0;
910
+ const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
911
+ if (parts.length === 0) return void 0;
912
+ const valid = new Set(ALL_CHECKS);
913
+ const parsed = [];
914
+ for (const part of parts) {
915
+ if (!valid.has(part)) {
916
+ process.stderr.write(
917
+ `Warning: unknown check "${part}" \u2014 valid checks are: ${ALL_CHECKS.join(", ")}
918
+ `
919
+ );
920
+ continue;
921
+ }
922
+ parsed.push(part);
46
923
  }
924
+ return parsed.length > 0 ? parsed : void 0;
47
925
  }
48
- function writeReportToFile(report, outputPath, pretty) {
49
- const json = pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
50
- writeFileSync(outputPath, json, "utf-8");
926
+ function createCiCommand() {
927
+ return new Command("ci").description(
928
+ "Run a non-interactive CI pipeline (manifest -> render -> compliance -> regression) with exit codes"
929
+ ).option(
930
+ "-b, --baseline <dir>",
931
+ "Baseline directory for visual regression comparison (omit to skip)"
932
+ ).option(
933
+ "--checks <list>",
934
+ `Comma-separated checks to run (default: all). Valid: ${ALL_CHECKS.join(", ")}`
935
+ ).option("--threshold <n>", "Compliance pass threshold (0-1, default: 0.90)", "0.90").option("--viewport <WxH>", "Viewport size, e.g. 1280x720", "375x812").option("--json", "Emit structured JSON to stdout in addition to the summary", false).option("-o, --output <path>", "Write the CI result JSON to a file").action(
936
+ async (opts) => {
937
+ try {
938
+ const [wStr, hStr] = opts.viewport.split("x");
939
+ const viewportWidth = Number.parseInt(wStr ?? "375", 10);
940
+ const viewportHeight = Number.parseInt(hStr ?? "812", 10);
941
+ const complianceThreshold = Number.parseFloat(opts.threshold);
942
+ const checks = parseChecks(opts.checks);
943
+ const result = await runCi({
944
+ baselineDir: opts.baseline,
945
+ checks,
946
+ complianceThreshold,
947
+ viewportWidth,
948
+ viewportHeight
949
+ });
950
+ if (opts.output !== void 0) {
951
+ const outPath = resolve2(process.cwd(), opts.output);
952
+ writeFileSync2(outPath, JSON.stringify(result, null, 2), "utf-8");
953
+ process.stderr.write(`CI result written to ${opts.output}
954
+ `);
955
+ }
956
+ process.stdout.write(`${formatCiReport(result)}
957
+ `);
958
+ if (opts.json) {
959
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
960
+ `);
961
+ }
962
+ process.exit(result.exitCode);
963
+ } catch (err) {
964
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
965
+ `);
966
+ process.exit(CI_EXIT.RENDER_FAILURES);
967
+ }
968
+ }
969
+ );
51
970
  }
52
971
 
53
972
  // src/init/index.ts
54
- import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
973
+ import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
55
974
  import { join as join2 } from "path";
56
975
  import * as readline from "readline";
57
976
 
58
977
  // src/init/detect.ts
59
- import { existsSync, readdirSync, readFileSync } from "fs";
978
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
60
979
  import { join } from "path";
61
980
  function hasConfigFile(dir, stem) {
62
- if (!existsSync(dir)) return false;
981
+ if (!existsSync3(dir)) return false;
63
982
  try {
64
983
  const entries = readdirSync(dir);
65
984
  return entries.some((f) => f === stem || f.startsWith(`${stem}.`));
@@ -69,7 +988,7 @@ function hasConfigFile(dir, stem) {
69
988
  }
70
989
  function readSafe(path) {
71
990
  try {
72
- return readFileSync(path, "utf-8");
991
+ return readFileSync3(path, "utf-8");
73
992
  } catch {
74
993
  return null;
75
994
  }
@@ -82,15 +1001,15 @@ function detectFramework(rootDir, packageDeps) {
82
1001
  return "unknown";
83
1002
  }
84
1003
  function detectPackageManager(rootDir) {
85
- if (existsSync(join(rootDir, "bun.lock"))) return "bun";
86
- if (existsSync(join(rootDir, "yarn.lock"))) return "yarn";
87
- if (existsSync(join(rootDir, "pnpm-lock.yaml"))) return "pnpm";
88
- if (existsSync(join(rootDir, "package-lock.json"))) return "npm";
1004
+ if (existsSync3(join(rootDir, "bun.lock"))) return "bun";
1005
+ if (existsSync3(join(rootDir, "yarn.lock"))) return "yarn";
1006
+ if (existsSync3(join(rootDir, "pnpm-lock.yaml"))) return "pnpm";
1007
+ if (existsSync3(join(rootDir, "package-lock.json"))) return "npm";
89
1008
  return "npm";
90
1009
  }
91
1010
  function detectTypeScript(rootDir) {
92
1011
  const candidate = join(rootDir, "tsconfig.json");
93
- if (existsSync(candidate)) {
1012
+ if (existsSync3(candidate)) {
94
1013
  return { typescript: true, tsconfigPath: candidate };
95
1014
  }
96
1015
  return { typescript: false, tsconfigPath: null };
@@ -103,7 +1022,7 @@ function detectComponentPatterns(rootDir, typescript) {
103
1022
  const altExt = typescript ? "jsx" : "jsx";
104
1023
  for (const dir of COMPONENT_DIRS) {
105
1024
  const absDir = join(rootDir, dir);
106
- if (!existsSync(absDir)) continue;
1025
+ if (!existsSync3(absDir)) continue;
107
1026
  let hasComponents = false;
108
1027
  try {
109
1028
  const entries = readdirSync(absDir, { withFileTypes: true });
@@ -158,7 +1077,7 @@ function detectTokenSources(rootDir) {
158
1077
  }
159
1078
  }
160
1079
  const srcDir = join(rootDir, "src");
161
- const dirsToScan = existsSync(srcDir) ? [srcDir] : [];
1080
+ const dirsToScan = existsSync3(srcDir) ? [srcDir] : [];
162
1081
  for (const scanDir of dirsToScan) {
163
1082
  try {
164
1083
  const entries = readdirSync(scanDir, { withFileTypes: true });
@@ -174,7 +1093,7 @@ function detectTokenSources(rootDir) {
174
1093
  } catch {
175
1094
  }
176
1095
  }
177
- if (existsSync(srcDir)) {
1096
+ if (existsSync3(srcDir)) {
178
1097
  try {
179
1098
  const entries = readdirSync(srcDir);
180
1099
  for (const entry of entries) {
@@ -217,7 +1136,7 @@ function detectProject(rootDir) {
217
1136
  }
218
1137
 
219
1138
  // src/init/index.ts
220
- import { Command } from "commander";
1139
+ import { Command as Command2 } from "commander";
221
1140
  function buildDefaultConfig(detected, tokenFile, outputDir) {
222
1141
  const include = detected.componentPatterns.length > 0 ? detected.componentPatterns : ["src/**/*.tsx"];
223
1142
  return {
@@ -255,9 +1174,9 @@ function createRL() {
255
1174
  });
256
1175
  }
257
1176
  async function ask(rl, question) {
258
- return new Promise((resolve14) => {
1177
+ return new Promise((resolve16) => {
259
1178
  rl.question(question, (answer) => {
260
- resolve14(answer.trim());
1179
+ resolve16(answer.trim());
261
1180
  });
262
1181
  });
263
1182
  }
@@ -267,8 +1186,8 @@ async function askWithDefault(rl, label, defaultValue) {
267
1186
  }
268
1187
  function ensureGitignoreEntry(rootDir, entry) {
269
1188
  const gitignorePath = join2(rootDir, ".gitignore");
270
- if (existsSync2(gitignorePath)) {
271
- const content = readFileSync2(gitignorePath, "utf-8");
1189
+ if (existsSync4(gitignorePath)) {
1190
+ const content = readFileSync4(gitignorePath, "utf-8");
272
1191
  const normalised = entry.replace(/\/$/, "");
273
1192
  const lines = content.split("\n").map((l) => l.trim());
274
1193
  if (lines.includes(entry) || lines.includes(normalised)) {
@@ -278,24 +1197,24 @@ function ensureGitignoreEntry(rootDir, entry) {
278
1197
  appendFileSync(gitignorePath, `${suffix}${entry}
279
1198
  `);
280
1199
  } else {
281
- writeFileSync2(gitignorePath, `${entry}
1200
+ writeFileSync3(gitignorePath, `${entry}
282
1201
  `);
283
1202
  }
284
1203
  }
285
1204
  function scaffoldConfig(rootDir, config) {
286
1205
  const path = join2(rootDir, "reactscope.config.json");
287
- writeFileSync2(path, `${JSON.stringify(config, null, 2)}
1206
+ writeFileSync3(path, `${JSON.stringify(config, null, 2)}
288
1207
  `);
289
1208
  return path;
290
1209
  }
291
1210
  function scaffoldTokenFile(rootDir, tokenFile) {
292
1211
  const path = join2(rootDir, tokenFile);
293
- if (!existsSync2(path)) {
1212
+ if (!existsSync4(path)) {
294
1213
  const stub = {
295
1214
  $schema: "https://raw.githubusercontent.com/FlatFilers/Scope/main/packages/tokens/schema.json",
296
1215
  tokens: {}
297
1216
  };
298
- writeFileSync2(path, `${JSON.stringify(stub, null, 2)}
1217
+ writeFileSync3(path, `${JSON.stringify(stub, null, 2)}
299
1218
  `);
300
1219
  }
301
1220
  return path;
@@ -304,8 +1223,8 @@ function scaffoldOutputDir(rootDir, outputDir) {
304
1223
  const dirPath = join2(rootDir, outputDir);
305
1224
  mkdirSync(dirPath, { recursive: true });
306
1225
  const keepPath = join2(dirPath, ".gitkeep");
307
- if (!existsSync2(keepPath)) {
308
- writeFileSync2(keepPath, "");
1226
+ if (!existsSync4(keepPath)) {
1227
+ writeFileSync3(keepPath, "");
309
1228
  }
310
1229
  return dirPath;
311
1230
  }
@@ -313,7 +1232,7 @@ async function runInit(options) {
313
1232
  const rootDir = options.cwd ?? process.cwd();
314
1233
  const configPath = join2(rootDir, "reactscope.config.json");
315
1234
  const created = [];
316
- if (existsSync2(configPath) && !options.force) {
1235
+ if (existsSync4(configPath) && !options.force) {
317
1236
  const msg = "reactscope.config.json already exists. Run with --force to overwrite.";
318
1237
  process.stderr.write(`\u26A0\uFE0F ${msg}
319
1238
  `);
@@ -386,254 +1305,43 @@ async function runInit(options) {
386
1305
  return {
387
1306
  success: true,
388
1307
  message: "Project initialised successfully.",
389
- created,
390
- skipped: false
391
- };
392
- }
393
- function createInitCommand() {
394
- return new Command("init").description("Initialise a Scope project \u2014 scaffold reactscope.config.json and friends").option("-y, --yes", "Accept all detected defaults without prompting", false).option("--force", "Overwrite existing reactscope.config.json if present", false).action(async (opts) => {
395
- try {
396
- const result = await runInit({ yes: opts.yes, force: opts.force });
397
- if (!result.success && !result.skipped) {
398
- process.exit(1);
399
- }
400
- } catch (err) {
401
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
402
- `);
403
- process.exit(1);
404
- }
405
- });
406
- }
407
-
408
- // src/instrument/renders.ts
409
- import { resolve as resolve5 } from "path";
410
- import { BrowserPool as BrowserPool2 } from "@agent-scope/render";
411
- import { Command as Command4 } from "commander";
412
-
413
- // src/component-bundler.ts
414
- import { dirname } from "path";
415
- import * as esbuild from "esbuild";
416
- async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss) {
417
- const bundledScript = await bundleComponentToIIFE(filePath, componentName, props);
418
- return wrapInHtml(bundledScript, viewportWidth, projectCss);
419
- }
420
- async function bundleComponentToIIFE(filePath, componentName, props) {
421
- const propsJson = JSON.stringify(props).replace(/<\/script>/gi, "<\\/script>");
422
- const wrapperCode = (
423
- /* ts */
424
- `
425
- import * as __scopeMod from ${JSON.stringify(filePath)};
426
- import { createRoot } from "react-dom/client";
427
- import { createElement } from "react";
428
-
429
- (function scopeRenderHarness() {
430
- var Component =
431
- __scopeMod["default"] ||
432
- __scopeMod[${JSON.stringify(componentName)}] ||
433
- (Object.values(__scopeMod).find(
434
- function(v) { return typeof v === "function" && /^[A-Z]/.test(v.name || ""); }
435
- ));
436
-
437
- if (!Component) {
438
- window.__SCOPE_RENDER_ERROR__ =
439
- "No renderable component found. Checked: default, " +
440
- ${JSON.stringify(componentName)} + ", and PascalCase named exports. " +
441
- "Available exports: " + Object.keys(__scopeMod).join(", ");
442
- window.__SCOPE_RENDER_COMPLETE__ = true;
443
- return;
444
- }
445
-
446
- try {
447
- var props = ${propsJson};
448
- var rootEl = document.getElementById("scope-root");
449
- if (!rootEl) {
450
- window.__SCOPE_RENDER_ERROR__ = "#scope-root element not found";
451
- window.__SCOPE_RENDER_COMPLETE__ = true;
452
- return;
453
- }
454
- createRoot(rootEl).render(createElement(Component, props));
455
- // Use requestAnimationFrame to let React flush the render
456
- requestAnimationFrame(function() {
457
- window.__SCOPE_RENDER_COMPLETE__ = true;
458
- });
459
- } catch (err) {
460
- window.__SCOPE_RENDER_ERROR__ = err instanceof Error ? err.message : String(err);
461
- window.__SCOPE_RENDER_COMPLETE__ = true;
462
- }
463
- })();
464
- `
465
- );
466
- const result = await esbuild.build({
467
- stdin: {
468
- contents: wrapperCode,
469
- // Resolve relative imports (within the component's dir)
470
- resolveDir: dirname(filePath),
471
- loader: "tsx",
472
- sourcefile: "__scope_harness__.tsx"
473
- },
474
- bundle: true,
475
- format: "iife",
476
- write: false,
477
- platform: "browser",
478
- jsx: "automatic",
479
- jsxImportSource: "react",
480
- target: "es2020",
481
- // Bundle everything — no externals
482
- external: [],
483
- define: {
484
- "process.env.NODE_ENV": '"development"',
485
- global: "globalThis"
486
- },
487
- logLevel: "silent",
488
- // Suppress "React must be in scope" warnings from old JSX (we use automatic)
489
- banner: {
490
- js: "/* @agent-scope/cli component harness */"
491
- }
492
- });
493
- if (result.errors.length > 0) {
494
- const msg = result.errors.map((e) => `${e.text}${e.location ? ` (${e.location.file}:${e.location.line})` : ""}`).join("\n");
495
- throw new Error(`esbuild failed to bundle component:
496
- ${msg}`);
497
- }
498
- const outputFile = result.outputFiles?.[0];
499
- if (outputFile === void 0 || outputFile.text.length === 0) {
500
- throw new Error("esbuild produced no output");
501
- }
502
- return outputFile.text;
503
- }
504
- function wrapInHtml(bundledScript, viewportWidth, projectCss) {
505
- const projectStyleBlock = projectCss != null && projectCss.length > 0 ? `<style id="scope-project-css">
506
- ${projectCss.replace(/<\/style>/gi, "<\\/style>")}
507
- </style>` : "";
508
- return `<!DOCTYPE html>
509
- <html lang="en">
510
- <head>
511
- <meta charset="UTF-8" />
512
- <meta name="viewport" content="width=${viewportWidth}, initial-scale=1.0" />
513
- <style>
514
- *, *::before, *::after { box-sizing: border-box; }
515
- html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, sans-serif; }
516
- #scope-root { display: inline-block; min-width: 1px; min-height: 1px; }
517
- </style>
518
- ${projectStyleBlock}
519
- </head>
520
- <body>
521
- <div id="scope-root" data-reactscope-root></div>
522
- <script>${bundledScript}</script>
523
- </body>
524
- </html>`;
525
- }
526
-
527
- // src/manifest-commands.ts
528
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
529
- import { resolve } from "path";
530
- import { generateManifest } from "@agent-scope/manifest";
531
- import { Command as Command2 } from "commander";
532
-
533
- // src/manifest-formatter.ts
534
- function isTTY() {
535
- return process.stdout.isTTY === true;
536
- }
537
- function pad(value, width) {
538
- return value.length >= width ? value.slice(0, width) : value + " ".repeat(width - value.length);
539
- }
540
- function buildTable(headers, rows) {
541
- const colWidths = headers.map(
542
- (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
543
- );
544
- const divider = colWidths.map((w) => "-".repeat(w)).join(" ");
545
- const headerRow = headers.map((h, i) => pad(h, colWidths[i] ?? 0)).join(" ");
546
- const dataRows = rows.map(
547
- (row2) => row2.map((cell, i) => pad(cell ?? "", colWidths[i] ?? 0)).join(" ")
548
- );
549
- return [headerRow, divider, ...dataRows].join("\n");
550
- }
551
- function formatListTable(rows) {
552
- if (rows.length === 0) return "No components found.";
553
- const headers = ["NAME", "FILE", "COMPLEXITY", "HOOKS", "CONTEXTS"];
554
- const tableRows = rows.map((r) => [
555
- r.name,
556
- r.file,
557
- r.complexityClass,
558
- String(r.hookCount),
559
- String(r.contextCount)
560
- ]);
561
- return buildTable(headers, tableRows);
562
- }
563
- function formatListJson(rows) {
564
- return JSON.stringify(rows, null, 2);
565
- }
566
- function formatSideEffects(se) {
567
- const parts = [];
568
- if (se.fetches.length > 0) parts.push(`fetches: ${se.fetches.join(", ")}`);
569
- if (se.timers) parts.push("timers");
570
- if (se.subscriptions.length > 0) parts.push(`subscriptions: ${se.subscriptions.join(", ")}`);
571
- if (se.globalListeners) parts.push("globalListeners");
572
- return parts.length > 0 ? parts.join(" | ") : "none";
573
- }
574
- function formatGetTable(name, descriptor) {
575
- const propNames = Object.keys(descriptor.props);
576
- const lines = [
577
- `Component: ${name}`,
578
- ` File: ${descriptor.filePath}`,
579
- ` Export: ${descriptor.exportType}`,
580
- ` Display Name: ${descriptor.displayName}`,
581
- ` Complexity: ${descriptor.complexityClass}`,
582
- ` Memoized: ${descriptor.memoized}`,
583
- ` Forwarded Ref: ${descriptor.forwardedRef}`,
584
- ` HOC Wrappers: ${descriptor.hocWrappers.join(", ") || "none"}`,
585
- ` Hooks: ${descriptor.detectedHooks.join(", ") || "none"}`,
586
- ` Contexts: ${descriptor.requiredContexts.join(", ") || "none"}`,
587
- ` Composes: ${descriptor.composes.join(", ") || "none"}`,
588
- ` Composed By: ${descriptor.composedBy.join(", ") || "none"}`,
589
- ` Side Effects: ${formatSideEffects(descriptor.sideEffects)}`,
590
- "",
591
- ` Props (${propNames.length}):`
592
- ];
593
- if (propNames.length === 0) {
594
- lines.push(" (none)");
595
- } else {
596
- for (const propName of propNames) {
597
- const p = descriptor.props[propName];
598
- if (p === void 0) continue;
599
- const req = p.required ? "required" : "optional";
600
- const def = p.default !== void 0 ? ` [default: ${p.default}]` : "";
601
- const vals = p.values !== void 0 ? ` (${p.values.join(" | ")})` : "";
602
- lines.push(` ${propName}: ${p.rawType}${vals} \u2014 ${req}${def}`);
603
- }
604
- }
605
- return lines.join("\n");
1308
+ created,
1309
+ skipped: false
1310
+ };
606
1311
  }
607
- function formatGetJson(name, descriptor) {
608
- return JSON.stringify({ name, ...descriptor }, null, 2);
1312
+ function createInitCommand() {
1313
+ return new Command2("init").description("Initialise a Scope project \u2014 scaffold reactscope.config.json and friends").option("-y, --yes", "Accept all detected defaults without prompting", false).option("--force", "Overwrite existing reactscope.config.json if present", false).action(async (opts) => {
1314
+ try {
1315
+ const result = await runInit({ yes: opts.yes, force: opts.force });
1316
+ if (!result.success && !result.skipped) {
1317
+ process.exit(1);
1318
+ }
1319
+ } catch (err) {
1320
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1321
+ `);
1322
+ process.exit(1);
1323
+ }
1324
+ });
609
1325
  }
610
- function formatQueryTable(rows, queryDesc) {
611
- if (rows.length === 0) return `No components match: ${queryDesc}`;
612
- const headers = ["NAME", "FILE", "COMPLEXITY", "HOOKS", "CONTEXTS"];
613
- const tableRows = rows.map((r) => [r.name, r.file, r.complexityClass, r.hooks, r.contexts]);
614
- return `Query: ${queryDesc}
615
1326
 
616
- ${buildTable(headers, tableRows)}`;
617
- }
618
- function formatQueryJson(rows) {
619
- return JSON.stringify(rows, null, 2);
620
- }
621
- function matchGlob(pattern, value) {
622
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
623
- const regexStr = escaped.replace(/\*\*/g, "\xA7GLOBSTAR\xA7").replace(/\*/g, "[^/]*").replace(/§GLOBSTAR§/g, ".*");
624
- const regex = new RegExp(`^${regexStr}$`, "i");
625
- return regex.test(value);
626
- }
1327
+ // src/instrument/renders.ts
1328
+ import { resolve as resolve7 } from "path";
1329
+ import { BrowserPool as BrowserPool3 } from "@agent-scope/render";
1330
+ import { Command as Command5 } from "commander";
627
1331
 
628
1332
  // src/manifest-commands.ts
1333
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1334
+ import { resolve as resolve3 } from "path";
1335
+ import { generateManifest as generateManifest2 } from "@agent-scope/manifest";
1336
+ import { Command as Command3 } from "commander";
629
1337
  var MANIFEST_PATH = ".reactscope/manifest.json";
630
1338
  function loadManifest(manifestPath = MANIFEST_PATH) {
631
- const absPath = resolve(process.cwd(), manifestPath);
632
- if (!existsSync3(absPath)) {
1339
+ const absPath = resolve3(process.cwd(), manifestPath);
1340
+ if (!existsSync5(absPath)) {
633
1341
  throw new Error(`Manifest not found at ${absPath}.
634
1342
  Run \`scope manifest generate\` first.`);
635
1343
  }
636
- const raw = readFileSync3(absPath, "utf-8");
1344
+ const raw = readFileSync5(absPath, "utf-8");
637
1345
  return JSON.parse(raw);
638
1346
  }
639
1347
  function resolveFormat(formatFlag) {
@@ -718,247 +1426,87 @@ function registerQuery(manifestCmd) {
718
1426
  }
719
1427
  if (opts.hook !== void 0) {
720
1428
  const hook = opts.hook;
721
- entries = entries.filter(([, d]) => d.detectedHooks.includes(hook));
722
- }
723
- if (opts.complexity !== void 0) {
724
- const cls = opts.complexity;
725
- entries = entries.filter(([, d]) => d.complexityClass === cls);
726
- }
727
- if (opts.sideEffects) {
728
- entries = entries.filter(([, d]) => {
729
- const se = d.sideEffects;
730
- return se.fetches.length > 0 || se.timers || se.subscriptions.length > 0 || se.globalListeners;
731
- });
732
- }
733
- if (opts.hasFetch) {
734
- entries = entries.filter(([, d]) => d.sideEffects.fetches.length > 0);
735
- }
736
- const rows = entries.map(([name, d]) => ({
737
- name,
738
- file: d.filePath,
739
- complexityClass: d.complexityClass,
740
- hooks: d.detectedHooks.join(", ") || "\u2014",
741
- contexts: d.requiredContexts.join(", ") || "\u2014"
742
- }));
743
- const output = format === "json" ? formatQueryJson(rows) : formatQueryTable(rows, queryDesc);
744
- process.stdout.write(`${output}
745
- `);
746
- } catch (err) {
747
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
748
- `);
749
- process.exit(1);
750
- }
751
- }
752
- );
753
- }
754
- function registerGenerate(manifestCmd) {
755
- manifestCmd.command("generate").description(
756
- "Generate the component manifest from source and write to .reactscope/manifest.json"
757
- ).option("--root <path>", "Project root directory (default: cwd)").option("--output <path>", "Output path for manifest.json", MANIFEST_PATH).option("--include <globs>", "Comma-separated glob patterns to include").option("--exclude <globs>", "Comma-separated glob patterns to exclude").action(async (opts) => {
758
- try {
759
- const rootDir = resolve(process.cwd(), opts.root ?? ".");
760
- const outputPath = resolve(process.cwd(), opts.output);
761
- const include = opts.include?.split(",").map((s) => s.trim());
762
- const exclude = opts.exclude?.split(",").map((s) => s.trim());
763
- process.stderr.write(`Scanning ${rootDir} for React components...
764
- `);
765
- const manifest = await generateManifest({
766
- rootDir,
767
- ...include !== void 0 && { include },
768
- ...exclude !== void 0 && { exclude }
769
- });
770
- const componentCount = Object.keys(manifest.components).length;
771
- process.stderr.write(`Found ${componentCount} components.
772
- `);
773
- const outputDir = outputPath.replace(/\/[^/]+$/, "");
774
- if (!existsSync3(outputDir)) {
775
- mkdirSync2(outputDir, { recursive: true });
776
- }
777
- writeFileSync3(outputPath, JSON.stringify(manifest, null, 2), "utf-8");
778
- process.stderr.write(`Manifest written to ${outputPath}
779
- `);
780
- process.stdout.write(`${outputPath}
781
- `);
782
- } catch (err) {
783
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
784
- `);
785
- process.exit(1);
786
- }
787
- });
788
- }
789
- function createManifestCommand() {
790
- const manifestCmd = new Command2("manifest").description(
791
- "Query and explore the component manifest"
792
- );
793
- registerList(manifestCmd);
794
- registerGet(manifestCmd);
795
- registerQuery(manifestCmd);
796
- registerGenerate(manifestCmd);
797
- return manifestCmd;
798
- }
799
-
800
- // src/render-formatter.ts
801
- function parseViewport(spec) {
802
- const lower = spec.toLowerCase();
803
- const match = /^(\d+)[x×](\d+)$/.exec(lower);
804
- if (!match) {
805
- throw new Error(`Invalid viewport "${spec}". Expected format: WIDTHxHEIGHT (e.g. 1280x720)`);
806
- }
807
- const width = parseInt(match[1] ?? "0", 10);
808
- const height = parseInt(match[2] ?? "0", 10);
809
- if (width <= 0 || height <= 0) {
810
- throw new Error(`Viewport dimensions must be positive integers, got: ${spec}`);
811
- }
812
- return { width, height };
813
- }
814
- function formatRenderJson(componentName, props, result) {
815
- return {
816
- component: componentName,
817
- props,
818
- width: result.width,
819
- height: result.height,
820
- renderTimeMs: result.renderTimeMs,
821
- computedStyles: result.computedStyles,
822
- screenshot: result.screenshot.toString("base64"),
823
- dom: result.dom,
824
- console: result.console,
825
- accessibility: result.accessibility
826
- };
827
- }
828
- function formatMatrixJson(result) {
829
- return {
830
- axes: result.axes.map((axis) => ({
831
- name: axis.name,
832
- values: axis.values.map((v) => String(v))
833
- })),
834
- stats: { ...result.stats },
835
- cells: result.cells.map((cell) => ({
836
- index: cell.index,
837
- axisIndices: cell.axisIndices,
838
- props: cell.props,
839
- renderTimeMs: cell.result.renderTimeMs,
840
- width: cell.result.width,
841
- height: cell.result.height,
842
- screenshot: cell.result.screenshot.toString("base64")
843
- }))
844
- };
845
- }
846
- function formatMatrixHtml(componentName, result) {
847
- const cellsHtml = result.cells.map((cell) => {
848
- const b64 = cell.result.screenshot.toString("base64");
849
- const propLabel = escapeHtml(
850
- Object.entries(cell.props).map(([k, v]) => `${k}: ${String(v)}`).join(", ")
851
- );
852
- return ` <div class="cell">
853
- <img src="data:image/png;base64,${b64}" alt="${propLabel}" width="${cell.result.width}" height="${cell.result.height}" />
854
- <div class="label">${propLabel}</div>
855
- <div class="meta">${cell.result.renderTimeMs.toFixed(1)}ms</div>
856
- </div>`;
857
- }).join("\n");
858
- const axesDesc = result.axes.map((a) => `${a.name}: ${a.values.map((v) => String(v)).join(", ")}`).join(" | ");
859
- return `<!DOCTYPE html>
860
- <html lang="en">
861
- <head>
862
- <meta charset="UTF-8" />
863
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
864
- <title>${escapeHtml(componentName)} \u2014 Render Matrix</title>
865
- <style>
866
- body { font-family: system-ui, sans-serif; background: #f8fafc; margin: 0; padding: 24px; }
867
- h1 { font-size: 1.25rem; color: #1e293b; margin-bottom: 8px; }
868
- .axes { font-size: 0.8rem; color: #64748b; margin-bottom: 20px; }
869
- .grid { display: flex; flex-wrap: wrap; gap: 16px; }
870
- .cell { background: #fff; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; }
871
- .cell img { display: block; }
872
- .label { padding: 6px 8px; font-size: 0.75rem; color: #374151; border-top: 1px solid #f1f5f9; }
873
- .meta { padding: 2px 8px 6px; font-size: 0.7rem; color: #94a3b8; }
874
- .stats { margin-top: 24px; font-size: 0.8rem; color: #64748b; }
875
- </style>
876
- </head>
877
- <body>
878
- <h1>${escapeHtml(componentName)} \u2014 Render Matrix</h1>
879
- <div class="axes">Axes: ${escapeHtml(axesDesc)}</div>
880
- <div class="grid">
881
- ${cellsHtml}
882
- </div>
883
- <div class="stats">
884
- ${result.stats.totalCells} cells \xB7
885
- avg ${result.stats.avgRenderTimeMs.toFixed(1)}ms \xB7
886
- min ${result.stats.minRenderTimeMs.toFixed(1)}ms \xB7
887
- max ${result.stats.maxRenderTimeMs.toFixed(1)}ms \xB7
888
- wall ${result.stats.wallClockTimeMs.toFixed(0)}ms
889
- </div>
890
- </body>
891
- </html>
892
- `;
893
- }
894
- function formatMatrixCsv(componentName, result) {
895
- const axisNames = result.axes.map((a) => a.name);
896
- const headers = ["component", ...axisNames, "renderTimeMs", "width", "height"];
897
- const rows = result.cells.map((cell) => {
898
- const axisVals = result.axes.map((_, i) => {
899
- const axisIdx = cell.axisIndices[i];
900
- const axis = result.axes[i];
901
- if (axisIdx === void 0 || axis === void 0) return "";
902
- const val = axis.values[axisIdx];
903
- return val !== void 0 ? csvEscape(String(val)) : "";
904
- });
905
- return [
906
- csvEscape(componentName),
907
- ...axisVals,
908
- cell.result.renderTimeMs.toFixed(3),
909
- String(cell.result.width),
910
- String(cell.result.height)
911
- ].join(",");
912
- });
913
- return `${[headers.join(","), ...rows].join("\n")}
914
- `;
915
- }
916
- function renderProgressBar(completed, total, currentName, pct, barWidth = 20) {
917
- const filled = Math.round(pct / 100 * barWidth);
918
- const empty = barWidth - filled;
919
- const bar = "=".repeat(Math.max(0, filled - 1)) + (filled > 0 ? ">" : "") + " ".repeat(empty);
920
- const nameSlice = currentName.slice(0, 25).padEnd(25);
921
- return `Rendering ${completed}/${total} ${nameSlice} [${bar}] ${pct}%`;
922
- }
923
- function formatSummaryText(results, outputDir) {
924
- const total = results.length;
925
- const passed = results.filter((r) => r.success).length;
926
- const failed = total - passed;
927
- const successTimes = results.filter((r) => r.success).map((r) => r.renderTimeMs);
928
- const avgMs = successTimes.length > 0 ? successTimes.reduce((a, b) => a + b, 0) / successTimes.length : 0;
929
- const lines = [
930
- "\u2500".repeat(60),
931
- `Render Summary`,
932
- "\u2500".repeat(60),
933
- ` Total components : ${total}`,
934
- ` Passed : ${passed}`,
935
- ` Failed : ${failed}`,
936
- ` Avg render time : ${avgMs.toFixed(1)}ms`,
937
- ` Output dir : ${outputDir}`
938
- ];
939
- if (failed > 0) {
940
- lines.push("", " Failed components:");
941
- for (const r of results) {
942
- if (!r.success) {
943
- lines.push(` \u2717 ${r.name}: ${r.errorMessage ?? "unknown error"}`);
1429
+ entries = entries.filter(([, d]) => d.detectedHooks.includes(hook));
1430
+ }
1431
+ if (opts.complexity !== void 0) {
1432
+ const cls = opts.complexity;
1433
+ entries = entries.filter(([, d]) => d.complexityClass === cls);
1434
+ }
1435
+ if (opts.sideEffects) {
1436
+ entries = entries.filter(([, d]) => {
1437
+ const se = d.sideEffects;
1438
+ return se.fetches.length > 0 || se.timers || se.subscriptions.length > 0 || se.globalListeners;
1439
+ });
1440
+ }
1441
+ if (opts.hasFetch) {
1442
+ entries = entries.filter(([, d]) => d.sideEffects.fetches.length > 0);
1443
+ }
1444
+ const rows = entries.map(([name, d]) => ({
1445
+ name,
1446
+ file: d.filePath,
1447
+ complexityClass: d.complexityClass,
1448
+ hooks: d.detectedHooks.join(", ") || "\u2014",
1449
+ contexts: d.requiredContexts.join(", ") || "\u2014"
1450
+ }));
1451
+ const output = format === "json" ? formatQueryJson(rows) : formatQueryTable(rows, queryDesc);
1452
+ process.stdout.write(`${output}
1453
+ `);
1454
+ } catch (err) {
1455
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1456
+ `);
1457
+ process.exit(1);
944
1458
  }
945
1459
  }
946
- }
947
- lines.push("\u2500".repeat(60));
948
- return lines.join("\n");
1460
+ );
949
1461
  }
950
- function escapeHtml(str) {
951
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1462
+ function registerGenerate(manifestCmd) {
1463
+ manifestCmd.command("generate").description(
1464
+ "Generate the component manifest from source and write to .reactscope/manifest.json"
1465
+ ).option("--root <path>", "Project root directory (default: cwd)").option("--output <path>", "Output path for manifest.json", MANIFEST_PATH).option("--include <globs>", "Comma-separated glob patterns to include").option("--exclude <globs>", "Comma-separated glob patterns to exclude").action(async (opts) => {
1466
+ try {
1467
+ const rootDir = resolve3(process.cwd(), opts.root ?? ".");
1468
+ const outputPath = resolve3(process.cwd(), opts.output);
1469
+ const include = opts.include?.split(",").map((s) => s.trim());
1470
+ const exclude = opts.exclude?.split(",").map((s) => s.trim());
1471
+ process.stderr.write(`Scanning ${rootDir} for React components...
1472
+ `);
1473
+ const manifest = await generateManifest2({
1474
+ rootDir,
1475
+ ...include !== void 0 && { include },
1476
+ ...exclude !== void 0 && { exclude }
1477
+ });
1478
+ const componentCount = Object.keys(manifest.components).length;
1479
+ process.stderr.write(`Found ${componentCount} components.
1480
+ `);
1481
+ const outputDir = outputPath.replace(/\/[^/]+$/, "");
1482
+ if (!existsSync5(outputDir)) {
1483
+ mkdirSync2(outputDir, { recursive: true });
1484
+ }
1485
+ writeFileSync4(outputPath, JSON.stringify(manifest, null, 2), "utf-8");
1486
+ process.stderr.write(`Manifest written to ${outputPath}
1487
+ `);
1488
+ process.stdout.write(`${outputPath}
1489
+ `);
1490
+ } catch (err) {
1491
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1492
+ `);
1493
+ process.exit(1);
1494
+ }
1495
+ });
952
1496
  }
953
- function csvEscape(value) {
954
- if (value.includes(",") || value.includes('"') || value.includes("\n")) {
955
- return `"${value.replace(/"/g, '""')}"`;
956
- }
957
- return value;
1497
+ function createManifestCommand() {
1498
+ const manifestCmd = new Command3("manifest").description(
1499
+ "Query and explore the component manifest"
1500
+ );
1501
+ registerList(manifestCmd);
1502
+ registerGet(manifestCmd);
1503
+ registerQuery(manifestCmd);
1504
+ registerGenerate(manifestCmd);
1505
+ return manifestCmd;
958
1506
  }
959
1507
 
960
1508
  // src/instrument/hooks.ts
961
- import { resolve as resolve2 } from "path";
1509
+ import { resolve as resolve4 } from "path";
962
1510
  import { Command as Cmd } from "commander";
963
1511
  import { chromium as chromium2 } from "playwright";
964
1512
  var MANIFEST_PATH2 = ".reactscope/manifest.json";
@@ -1281,7 +1829,7 @@ Available: ${available}`
1281
1829
  throw new Error(`Invalid props JSON: ${opts.props}`);
1282
1830
  }
1283
1831
  const rootDir = process.cwd();
1284
- const filePath = resolve2(rootDir, descriptor.filePath);
1832
+ const filePath = resolve4(rootDir, descriptor.filePath);
1285
1833
  process.stderr.write(`Instrumenting hooks for ${componentName}\u2026
1286
1834
  `);
1287
1835
  const result = await runHooksProfiling(componentName, filePath, props);
@@ -1309,7 +1857,7 @@ Available: ${available}`
1309
1857
  }
1310
1858
 
1311
1859
  // src/instrument/profile.ts
1312
- import { resolve as resolve3 } from "path";
1860
+ import { resolve as resolve5 } from "path";
1313
1861
  import { Command as Cmd2 } from "commander";
1314
1862
  import { chromium as chromium3 } from "playwright";
1315
1863
  var MANIFEST_PATH3 = ".reactscope/manifest.json";
@@ -1575,7 +2123,7 @@ Available: ${available}`
1575
2123
  throw new Error(`Invalid interaction JSON: ${opts.interaction}`);
1576
2124
  }
1577
2125
  const rootDir = process.cwd();
1578
- const filePath = resolve3(rootDir, descriptor.filePath);
2126
+ const filePath = resolve5(rootDir, descriptor.filePath);
1579
2127
  process.stderr.write(`Profiling interaction for ${componentName}\u2026
1580
2128
  `);
1581
2129
  const result = await runInteractionProfile(componentName, filePath, props, interaction);
@@ -1603,29 +2151,29 @@ Available: ${available}`
1603
2151
  }
1604
2152
 
1605
2153
  // src/instrument/tree.ts
1606
- import { resolve as resolve4 } from "path";
2154
+ import { resolve as resolve6 } from "path";
1607
2155
  import { getBrowserEntryScript as getBrowserEntryScript2 } from "@agent-scope/playwright";
1608
- import { BrowserPool } from "@agent-scope/render";
1609
- import { Command as Command3 } from "commander";
2156
+ import { BrowserPool as BrowserPool2 } from "@agent-scope/render";
2157
+ import { Command as Command4 } from "commander";
1610
2158
  var MANIFEST_PATH4 = ".reactscope/manifest.json";
1611
2159
  var DEFAULT_VIEWPORT_WIDTH = 375;
1612
2160
  var DEFAULT_VIEWPORT_HEIGHT = 812;
1613
- var _pool = null;
1614
- async function getPool() {
1615
- if (_pool === null) {
1616
- _pool = new BrowserPool({
2161
+ var _pool2 = null;
2162
+ async function getPool2() {
2163
+ if (_pool2 === null) {
2164
+ _pool2 = new BrowserPool2({
1617
2165
  size: { browsers: 1, pagesPerBrowser: 1 },
1618
2166
  viewportWidth: DEFAULT_VIEWPORT_WIDTH,
1619
2167
  viewportHeight: DEFAULT_VIEWPORT_HEIGHT
1620
2168
  });
1621
- await _pool.init();
2169
+ await _pool2.init();
1622
2170
  }
1623
- return _pool;
2171
+ return _pool2;
1624
2172
  }
1625
- async function shutdownPool() {
1626
- if (_pool !== null) {
1627
- await _pool.close();
1628
- _pool = null;
2173
+ async function shutdownPool2() {
2174
+ if (_pool2 !== null) {
2175
+ await _pool2.close();
2176
+ _pool2 = null;
1629
2177
  }
1630
2178
  }
1631
2179
  function mapNodeType(node) {
@@ -1839,7 +2387,7 @@ function formatInstrumentTree(root, showProviderDepth = false) {
1839
2387
  }
1840
2388
  async function runInstrumentTree(options) {
1841
2389
  const { componentName, filePath } = options;
1842
- const pool = await getPool();
2390
+ const pool = await getPool2();
1843
2391
  const slot = await pool.acquire();
1844
2392
  const { page } = slot;
1845
2393
  try {
@@ -1900,7 +2448,7 @@ async function runInstrumentTree(options) {
1900
2448
  }
1901
2449
  }
1902
2450
  function createInstrumentTreeCommand() {
1903
- return new Command3("tree").description("Render a component via BrowserPool and output a structured instrumentation tree").argument("<component>", "Component name to instrument (must exist in the manifest)").option("--sort-by <field>", "Sort nodes by field: renderCount | depth").option("--limit <n>", "Limit output to the first N nodes (depth-first)").option("--uses-context <name>", "Filter to components that use a specific context").option("--provider-depth", "Annotate each node with its context-provider nesting depth", false).option(
2451
+ return new Command4("tree").description("Render a component via BrowserPool and output a structured instrumentation tree").argument("<component>", "Component name to instrument (must exist in the manifest)").option("--sort-by <field>", "Sort nodes by field: renderCount | depth").option("--limit <n>", "Limit output to the first N nodes (depth-first)").option("--uses-context <name>", "Filter to components that use a specific context").option("--provider-depth", "Annotate each node with its context-provider nesting depth", false).option(
1904
2452
  "--wasted-renders",
1905
2453
  "Filter to components with wasted renders (no prop/state/context changes, not memoized)",
1906
2454
  false
@@ -1924,7 +2472,7 @@ Available: ${available}`
1924
2472
  }
1925
2473
  }
1926
2474
  const rootDir = process.cwd();
1927
- const filePath = resolve4(rootDir, descriptor.filePath);
2475
+ const filePath = resolve6(rootDir, descriptor.filePath);
1928
2476
  process.stderr.write(`Instrumenting ${componentName}\u2026
1929
2477
  `);
1930
2478
  const instrumentRoot = await runInstrumentTree({
@@ -1936,9 +2484,9 @@ Available: ${available}`
1936
2484
  providerDepth: opts.providerDepth,
1937
2485
  wastedRenders: opts.wastedRenders
1938
2486
  });
1939
- await shutdownPool();
1940
- const fmt = resolveFormat2(opts.format);
1941
- if (fmt === "json") {
2487
+ await shutdownPool2();
2488
+ const fmt2 = resolveFormat2(opts.format);
2489
+ if (fmt2 === "json") {
1942
2490
  process.stdout.write(`${JSON.stringify(instrumentRoot, null, 2)}
1943
2491
  `);
1944
2492
  } else {
@@ -1947,7 +2495,7 @@ Available: ${available}`
1947
2495
  `);
1948
2496
  }
1949
2497
  } catch (err) {
1950
- await shutdownPool();
2498
+ await shutdownPool2();
1951
2499
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1952
2500
  `);
1953
2501
  process.exit(1);
@@ -2267,22 +2815,22 @@ async function replayInteraction2(page, steps) {
2267
2815
  }
2268
2816
  }
2269
2817
  }
2270
- var _pool2 = null;
2271
- async function getPool2() {
2272
- if (_pool2 === null) {
2273
- _pool2 = new BrowserPool2({
2818
+ var _pool3 = null;
2819
+ async function getPool3() {
2820
+ if (_pool3 === null) {
2821
+ _pool3 = new BrowserPool3({
2274
2822
  size: { browsers: 1, pagesPerBrowser: 2 },
2275
2823
  viewportWidth: 1280,
2276
2824
  viewportHeight: 800
2277
2825
  });
2278
- await _pool2.init();
2826
+ await _pool3.init();
2279
2827
  }
2280
- return _pool2;
2828
+ return _pool3;
2281
2829
  }
2282
- async function shutdownPool2() {
2283
- if (_pool2 !== null) {
2284
- await _pool2.close();
2285
- _pool2 = null;
2830
+ async function shutdownPool3() {
2831
+ if (_pool3 !== null) {
2832
+ await _pool3.close();
2833
+ _pool3 = null;
2286
2834
  }
2287
2835
  }
2288
2836
  async function analyzeRenders(options) {
@@ -2297,9 +2845,9 @@ Available: ${available}`
2297
2845
  );
2298
2846
  }
2299
2847
  const rootDir = process.cwd();
2300
- const filePath = resolve5(rootDir, descriptor.filePath);
2848
+ const filePath = resolve7(rootDir, descriptor.filePath);
2301
2849
  const htmlHarness = await buildComponentHarness(filePath, options.componentName, {}, 1280);
2302
- const pool = await getPool2();
2850
+ const pool = await getPool3();
2303
2851
  const slot = await pool.acquire();
2304
2852
  const { page } = slot;
2305
2853
  const startMs = performance.now();
@@ -2380,215 +2928,93 @@ function formatRendersTable(result) {
2380
2928
  return lines.join("\n");
2381
2929
  }
2382
2930
  function createInstrumentRendersCommand() {
2383
- return new Command4("renders").description("Trace re-render causality chains for a component during an interaction sequence").argument("<component>", "Component name to instrument (must be in manifest)").option(
2931
+ return new Command5("renders").description("Trace re-render causality chains for a component during an interaction sequence").argument("<component>", "Component name to instrument (must be in manifest)").option(
2384
2932
  "--interaction <json>",
2385
2933
  `Interaction sequence JSON, e.g. '[{"action":"click","target":"button"}]'`,
2386
2934
  "[]"
2387
2935
  ).option("--json", "Output as JSON regardless of TTY", false).option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH5).action(
2388
- async (componentName, opts) => {
2389
- let interaction = [];
2390
- try {
2391
- interaction = JSON.parse(opts.interaction);
2392
- if (!Array.isArray(interaction)) {
2393
- throw new Error("Interaction must be a JSON array");
2394
- }
2395
- } catch {
2396
- process.stderr.write(`Error: Invalid --interaction JSON: ${opts.interaction}
2397
- `);
2398
- process.exit(1);
2399
- }
2400
- try {
2401
- process.stderr.write(
2402
- `Instrumenting ${componentName} (${interaction.length} interaction steps)\u2026
2403
- `
2404
- );
2405
- const result = await analyzeRenders({
2406
- componentName,
2407
- interaction,
2408
- manifestPath: opts.manifest
2409
- });
2410
- await shutdownPool2();
2411
- if (opts.json || !isTTY()) {
2412
- process.stdout.write(`${JSON.stringify(result, null, 2)}
2413
- `);
2414
- } else {
2415
- process.stdout.write(`${formatRendersTable(result)}
2416
- `);
2417
- }
2418
- } catch (err) {
2419
- await shutdownPool2();
2420
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2421
- `);
2422
- process.exit(1);
2423
- }
2424
- }
2425
- );
2426
- }
2427
- function createInstrumentCommand() {
2428
- const instrumentCmd = new Command4("instrument").description(
2429
- "Structured instrumentation commands for React component analysis"
2430
- );
2431
- instrumentCmd.addCommand(createInstrumentRendersCommand());
2432
- instrumentCmd.addCommand(createInstrumentHooksCommand());
2433
- instrumentCmd.addCommand(createInstrumentProfileCommand());
2434
- instrumentCmd.addCommand(createInstrumentTreeCommand());
2435
- return instrumentCmd;
2436
- }
2437
-
2438
- // src/render-commands.ts
2439
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2440
- import { resolve as resolve7 } from "path";
2441
- import {
2442
- ALL_CONTEXT_IDS,
2443
- ALL_STRESS_IDS,
2444
- BrowserPool as BrowserPool3,
2445
- contextAxis,
2446
- RenderMatrix,
2447
- SatoriRenderer,
2448
- safeRender,
2449
- stressAxis
2450
- } from "@agent-scope/render";
2451
- import { Command as Command5 } from "commander";
2452
-
2453
- // src/tailwind-css.ts
2454
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
2455
- import { createRequire } from "module";
2456
- import { resolve as resolve6 } from "path";
2457
- var CONFIG_FILENAMES = [
2458
- ".reactscope/config.json",
2459
- ".reactscope/config.js",
2460
- ".reactscope/config.mjs"
2461
- ];
2462
- var STYLE_ENTRY_CANDIDATES = [
2463
- "src/index.css",
2464
- "src/globals.css",
2465
- "app/globals.css",
2466
- "app/index.css",
2467
- "styles/index.css",
2468
- "index.css"
2469
- ];
2470
- var TAILWIND_IMPORT = /@import\s+["']tailwindcss["']\s*;?/;
2471
- var compilerCache = null;
2472
- function getCachedBuild(cwd) {
2473
- if (compilerCache !== null && resolve6(compilerCache.cwd) === resolve6(cwd)) {
2474
- return compilerCache.build;
2475
- }
2476
- return null;
2477
- }
2478
- function findStylesEntry(cwd) {
2479
- for (const name of CONFIG_FILENAMES) {
2480
- const p = resolve6(cwd, name);
2481
- if (!existsSync4(p)) continue;
2482
- try {
2483
- if (name.endsWith(".json")) {
2484
- const raw = readFileSync4(p, "utf-8");
2485
- const data = JSON.parse(raw);
2486
- const scope = data.scope;
2487
- const entry = scope?.stylesEntry ?? data.stylesEntry;
2488
- if (typeof entry === "string") {
2489
- const full = resolve6(cwd, entry);
2490
- if (existsSync4(full)) return full;
2491
- }
2492
- }
2493
- } catch {
2494
- }
2495
- }
2496
- const pkgPath = resolve6(cwd, "package.json");
2497
- if (existsSync4(pkgPath)) {
2498
- try {
2499
- const raw = readFileSync4(pkgPath, "utf-8");
2500
- const pkg = JSON.parse(raw);
2501
- const entry = pkg.scope?.stylesEntry;
2502
- if (typeof entry === "string") {
2503
- const full = resolve6(cwd, entry);
2504
- if (existsSync4(full)) return full;
2505
- }
2506
- } catch {
2507
- }
2508
- }
2509
- for (const candidate of STYLE_ENTRY_CANDIDATES) {
2510
- const full = resolve6(cwd, candidate);
2511
- if (existsSync4(full)) {
2512
- try {
2513
- const content = readFileSync4(full, "utf-8");
2514
- if (TAILWIND_IMPORT.test(content)) return full;
2515
- } catch {
2516
- }
2517
- }
2518
- }
2519
- return null;
2520
- }
2521
- async function getTailwindCompiler(cwd) {
2522
- const cached = getCachedBuild(cwd);
2523
- if (cached !== null) return cached;
2524
- const entryPath = findStylesEntry(cwd);
2525
- if (entryPath === null) return null;
2526
- let compile;
2527
- try {
2528
- const require2 = createRequire(resolve6(cwd, "package.json"));
2529
- const tailwind = require2("tailwindcss");
2530
- const fn = tailwind.compile;
2531
- if (typeof fn !== "function") return null;
2532
- compile = fn;
2533
- } catch {
2534
- return null;
2535
- }
2536
- const entryContent = readFileSync4(entryPath, "utf-8");
2537
- const loadStylesheet = async (id, base) => {
2538
- if (id === "tailwindcss") {
2539
- const nodeModules = resolve6(cwd, "node_modules");
2540
- const tailwindCssPath = resolve6(nodeModules, "tailwindcss", "index.css");
2541
- if (!existsSync4(tailwindCssPath)) {
2542
- throw new Error(
2543
- `Tailwind v4: tailwindcss package not found at ${tailwindCssPath}. Install with: npm install tailwindcss`
2936
+ async (componentName, opts) => {
2937
+ let interaction = [];
2938
+ try {
2939
+ interaction = JSON.parse(opts.interaction);
2940
+ if (!Array.isArray(interaction)) {
2941
+ throw new Error("Interaction must be a JSON array");
2942
+ }
2943
+ } catch {
2944
+ process.stderr.write(`Error: Invalid --interaction JSON: ${opts.interaction}
2945
+ `);
2946
+ process.exit(1);
2947
+ }
2948
+ try {
2949
+ process.stderr.write(
2950
+ `Instrumenting ${componentName} (${interaction.length} interaction steps)\u2026
2951
+ `
2544
2952
  );
2953
+ const result = await analyzeRenders({
2954
+ componentName,
2955
+ interaction,
2956
+ manifestPath: opts.manifest
2957
+ });
2958
+ await shutdownPool3();
2959
+ if (opts.json || !isTTY()) {
2960
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
2961
+ `);
2962
+ } else {
2963
+ process.stdout.write(`${formatRendersTable(result)}
2964
+ `);
2965
+ }
2966
+ } catch (err) {
2967
+ await shutdownPool3();
2968
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2969
+ `);
2970
+ process.exit(1);
2545
2971
  }
2546
- const content = readFileSync4(tailwindCssPath, "utf-8");
2547
- return { path: "virtual:tailwindcss/index.css", base, content };
2548
- }
2549
- const full = resolve6(base, id);
2550
- if (existsSync4(full)) {
2551
- const content = readFileSync4(full, "utf-8");
2552
- return { path: full, base: resolve6(full, ".."), content };
2553
2972
  }
2554
- throw new Error(`Tailwind v4: could not load stylesheet: ${id} (base: ${base})`);
2555
- };
2556
- const result = await compile(entryContent, {
2557
- base: cwd,
2558
- from: entryPath,
2559
- loadStylesheet
2560
- });
2561
- const build2 = result.build.bind(result);
2562
- compilerCache = { cwd, build: build2 };
2563
- return build2;
2973
+ );
2564
2974
  }
2565
- async function getCompiledCssForClasses(cwd, classes) {
2566
- const build2 = await getTailwindCompiler(cwd);
2567
- if (build2 === null) return null;
2568
- const deduped = [...new Set(classes)].filter(Boolean);
2569
- if (deduped.length === 0) return null;
2570
- return build2(deduped);
2975
+ function createInstrumentCommand() {
2976
+ const instrumentCmd = new Command5("instrument").description(
2977
+ "Structured instrumentation commands for React component analysis"
2978
+ );
2979
+ instrumentCmd.addCommand(createInstrumentRendersCommand());
2980
+ instrumentCmd.addCommand(createInstrumentHooksCommand());
2981
+ instrumentCmd.addCommand(createInstrumentProfileCommand());
2982
+ instrumentCmd.addCommand(createInstrumentTreeCommand());
2983
+ return instrumentCmd;
2571
2984
  }
2572
2985
 
2573
2986
  // src/render-commands.ts
2987
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
2988
+ import { resolve as resolve8 } from "path";
2989
+ import {
2990
+ ALL_CONTEXT_IDS,
2991
+ ALL_STRESS_IDS,
2992
+ BrowserPool as BrowserPool4,
2993
+ contextAxis,
2994
+ RenderMatrix,
2995
+ SatoriRenderer,
2996
+ safeRender as safeRender2,
2997
+ stressAxis
2998
+ } from "@agent-scope/render";
2999
+ import { Command as Command6 } from "commander";
2574
3000
  var MANIFEST_PATH6 = ".reactscope/manifest.json";
2575
3001
  var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
2576
- var _pool3 = null;
2577
- async function getPool3(viewportWidth, viewportHeight) {
2578
- if (_pool3 === null) {
2579
- _pool3 = new BrowserPool3({
3002
+ var _pool4 = null;
3003
+ async function getPool4(viewportWidth, viewportHeight) {
3004
+ if (_pool4 === null) {
3005
+ _pool4 = new BrowserPool4({
2580
3006
  size: { browsers: 1, pagesPerBrowser: 4 },
2581
3007
  viewportWidth,
2582
3008
  viewportHeight
2583
3009
  });
2584
- await _pool3.init();
3010
+ await _pool4.init();
2585
3011
  }
2586
- return _pool3;
3012
+ return _pool4;
2587
3013
  }
2588
- async function shutdownPool3() {
2589
- if (_pool3 !== null) {
2590
- await _pool3.close();
2591
- _pool3 = null;
3014
+ async function shutdownPool4() {
3015
+ if (_pool4 !== null) {
3016
+ await _pool4.close();
3017
+ _pool4 = null;
2592
3018
  }
2593
3019
  }
2594
3020
  function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
@@ -2599,7 +3025,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
2599
3025
  _satori: satori,
2600
3026
  async renderCell(props, _complexityClass) {
2601
3027
  const startMs = performance.now();
2602
- const pool = await getPool3(viewportWidth, viewportHeight);
3028
+ const pool = await getPool4(viewportWidth, viewportHeight);
2603
3029
  const htmlHarness = await buildComponentHarness(
2604
3030
  filePath,
2605
3031
  componentName,
@@ -2718,13 +3144,13 @@ Available: ${available}`
2718
3144
  }
2719
3145
  const { width, height } = parseViewport(opts.viewport);
2720
3146
  const rootDir = process.cwd();
2721
- const filePath = resolve7(rootDir, descriptor.filePath);
3147
+ const filePath = resolve8(rootDir, descriptor.filePath);
2722
3148
  const renderer = buildRenderer(filePath, componentName, width, height);
2723
3149
  process.stderr.write(
2724
3150
  `Rendering ${componentName} [${descriptor.complexityClass}] at ${width}\xD7${height}\u2026
2725
3151
  `
2726
3152
  );
2727
- const outcome = await safeRender(
3153
+ const outcome = await safeRender2(
2728
3154
  () => renderer.renderCell(props, descriptor.complexityClass),
2729
3155
  {
2730
3156
  props,
@@ -2735,7 +3161,7 @@ Available: ${available}`
2735
3161
  }
2736
3162
  }
2737
3163
  );
2738
- await shutdownPool3();
3164
+ await shutdownPool4();
2739
3165
  if (outcome.crashed) {
2740
3166
  process.stderr.write(`\u2717 Render failed: ${outcome.error.message}
2741
3167
  `);
@@ -2748,34 +3174,34 @@ Available: ${available}`
2748
3174
  }
2749
3175
  const result = outcome.result;
2750
3176
  if (opts.output !== void 0) {
2751
- const outPath = resolve7(process.cwd(), opts.output);
2752
- writeFileSync4(outPath, result.screenshot);
3177
+ const outPath = resolve8(process.cwd(), opts.output);
3178
+ writeFileSync5(outPath, result.screenshot);
2753
3179
  process.stdout.write(
2754
3180
  `\u2713 ${componentName} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
2755
3181
  `
2756
3182
  );
2757
3183
  return;
2758
3184
  }
2759
- const fmt = resolveSingleFormat(opts.format);
2760
- if (fmt === "json") {
3185
+ const fmt2 = resolveSingleFormat(opts.format);
3186
+ if (fmt2 === "json") {
2761
3187
  const json = formatRenderJson(componentName, props, result);
2762
3188
  process.stdout.write(`${JSON.stringify(json, null, 2)}
2763
3189
  `);
2764
- } else if (fmt === "file") {
2765
- const dir = resolve7(process.cwd(), DEFAULT_OUTPUT_DIR);
3190
+ } else if (fmt2 === "file") {
3191
+ const dir = resolve8(process.cwd(), DEFAULT_OUTPUT_DIR);
2766
3192
  mkdirSync3(dir, { recursive: true });
2767
- const outPath = resolve7(dir, `${componentName}.png`);
2768
- writeFileSync4(outPath, result.screenshot);
3193
+ const outPath = resolve8(dir, `${componentName}.png`);
3194
+ writeFileSync5(outPath, result.screenshot);
2769
3195
  const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
2770
3196
  process.stdout.write(
2771
3197
  `\u2713 ${componentName} \u2192 ${relPath} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
2772
3198
  `
2773
3199
  );
2774
3200
  } else {
2775
- const dir = resolve7(process.cwd(), DEFAULT_OUTPUT_DIR);
3201
+ const dir = resolve8(process.cwd(), DEFAULT_OUTPUT_DIR);
2776
3202
  mkdirSync3(dir, { recursive: true });
2777
- const outPath = resolve7(dir, `${componentName}.png`);
2778
- writeFileSync4(outPath, result.screenshot);
3203
+ const outPath = resolve8(dir, `${componentName}.png`);
3204
+ writeFileSync5(outPath, result.screenshot);
2779
3205
  const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
2780
3206
  process.stdout.write(
2781
3207
  `\u2713 ${componentName} \u2192 ${relPath} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
@@ -2783,7 +3209,7 @@ Available: ${available}`
2783
3209
  );
2784
3210
  }
2785
3211
  } catch (err) {
2786
- await shutdownPool3();
3212
+ await shutdownPool4();
2787
3213
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2788
3214
  `);
2789
3215
  process.exit(1);
@@ -2810,7 +3236,7 @@ Available: ${available}`
2810
3236
  const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 8);
2811
3237
  const { width, height } = { width: 375, height: 812 };
2812
3238
  const rootDir = process.cwd();
2813
- const filePath = resolve7(rootDir, descriptor.filePath);
3239
+ const filePath = resolve8(rootDir, descriptor.filePath);
2814
3240
  const renderer = buildRenderer(filePath, componentName, width, height);
2815
3241
  const axes = [];
2816
3242
  if (opts.axes !== void 0) {
@@ -2868,7 +3294,7 @@ Available: ${available}`
2868
3294
  concurrency
2869
3295
  });
2870
3296
  const result = await matrix.render();
2871
- await shutdownPool3();
3297
+ await shutdownPool4();
2872
3298
  process.stderr.write(
2873
3299
  `Done. ${result.stats.totalCells} cells, avg ${result.stats.avgRenderTimeMs.toFixed(1)}ms
2874
3300
  `
@@ -2877,29 +3303,29 @@ Available: ${available}`
2877
3303
  const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
2878
3304
  const gen = new SpriteSheetGenerator2();
2879
3305
  const sheet = await gen.generate(result);
2880
- const spritePath = resolve7(process.cwd(), opts.sprite);
2881
- writeFileSync4(spritePath, sheet.png);
3306
+ const spritePath = resolve8(process.cwd(), opts.sprite);
3307
+ writeFileSync5(spritePath, sheet.png);
2882
3308
  process.stderr.write(`Sprite sheet saved to ${spritePath}
2883
3309
  `);
2884
3310
  }
2885
- const fmt = resolveMatrixFormat(opts.format, opts.sprite !== void 0);
2886
- if (fmt === "file") {
3311
+ const fmt2 = resolveMatrixFormat(opts.format, opts.sprite !== void 0);
3312
+ if (fmt2 === "file") {
2887
3313
  const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
2888
3314
  const gen = new SpriteSheetGenerator2();
2889
3315
  const sheet = await gen.generate(result);
2890
- const dir = resolve7(process.cwd(), DEFAULT_OUTPUT_DIR);
3316
+ const dir = resolve8(process.cwd(), DEFAULT_OUTPUT_DIR);
2891
3317
  mkdirSync3(dir, { recursive: true });
2892
- const outPath = resolve7(dir, `${componentName}-matrix.png`);
2893
- writeFileSync4(outPath, sheet.png);
3318
+ const outPath = resolve8(dir, `${componentName}-matrix.png`);
3319
+ writeFileSync5(outPath, sheet.png);
2894
3320
  const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}-matrix.png`;
2895
3321
  process.stdout.write(
2896
3322
  `\u2713 ${componentName} matrix (${result.stats.totalCells} cells) \u2192 ${relPath} (${result.stats.wallClockTimeMs.toFixed(0)}ms total)
2897
3323
  `
2898
3324
  );
2899
- } else if (fmt === "json") {
3325
+ } else if (fmt2 === "json") {
2900
3326
  process.stdout.write(`${JSON.stringify(formatMatrixJson(result), null, 2)}
2901
3327
  `);
2902
- } else if (fmt === "png") {
3328
+ } else if (fmt2 === "png") {
2903
3329
  if (opts.sprite !== void 0) {
2904
3330
  } else {
2905
3331
  const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
@@ -2907,13 +3333,13 @@ Available: ${available}`
2907
3333
  const sheet = await gen.generate(result);
2908
3334
  process.stdout.write(sheet.png);
2909
3335
  }
2910
- } else if (fmt === "html") {
3336
+ } else if (fmt2 === "html") {
2911
3337
  process.stdout.write(formatMatrixHtml(componentName, result));
2912
- } else if (fmt === "csv") {
3338
+ } else if (fmt2 === "csv") {
2913
3339
  process.stdout.write(formatMatrixCsv(componentName, result));
2914
3340
  }
2915
3341
  } catch (err) {
2916
- await shutdownPool3();
3342
+ await shutdownPool4();
2917
3343
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2918
3344
  `);
2919
3345
  process.exit(1);
@@ -2933,7 +3359,7 @@ function registerRenderAll(renderCmd) {
2933
3359
  return;
2934
3360
  }
2935
3361
  const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 4);
2936
- const outputDir = resolve7(process.cwd(), opts.outputDir);
3362
+ const outputDir = resolve8(process.cwd(), opts.outputDir);
2937
3363
  mkdirSync3(outputDir, { recursive: true });
2938
3364
  const rootDir = process.cwd();
2939
3365
  process.stderr.write(`Rendering ${total} components (concurrency: ${concurrency})\u2026
@@ -2943,9 +3369,9 @@ function registerRenderAll(renderCmd) {
2943
3369
  const renderOne = async (name) => {
2944
3370
  const descriptor = manifest.components[name];
2945
3371
  if (descriptor === void 0) return;
2946
- const filePath = resolve7(rootDir, descriptor.filePath);
3372
+ const filePath = resolve8(rootDir, descriptor.filePath);
2947
3373
  const renderer = buildRenderer(filePath, name, 375, 812);
2948
- const outcome = await safeRender(
3374
+ const outcome = await safeRender2(
2949
3375
  () => renderer.renderCell({}, descriptor.complexityClass),
2950
3376
  {
2951
3377
  props: {},
@@ -2966,8 +3392,8 @@ function registerRenderAll(renderCmd) {
2966
3392
  success: false,
2967
3393
  errorMessage: outcome.error.message
2968
3394
  });
2969
- const errPath = resolve7(outputDir, `${name}.error.json`);
2970
- writeFileSync4(
3395
+ const errPath = resolve8(outputDir, `${name}.error.json`);
3396
+ writeFileSync5(
2971
3397
  errPath,
2972
3398
  JSON.stringify(
2973
3399
  {
@@ -2984,10 +3410,10 @@ function registerRenderAll(renderCmd) {
2984
3410
  }
2985
3411
  const result = outcome.result;
2986
3412
  results.push({ name, renderTimeMs: result.renderTimeMs, success: true });
2987
- const pngPath = resolve7(outputDir, `${name}.png`);
2988
- writeFileSync4(pngPath, result.screenshot);
2989
- const jsonPath = resolve7(outputDir, `${name}.json`);
2990
- writeFileSync4(jsonPath, JSON.stringify(formatRenderJson(name, {}, result), null, 2));
3413
+ const pngPath = resolve8(outputDir, `${name}.png`);
3414
+ writeFileSync5(pngPath, result.screenshot);
3415
+ const jsonPath = resolve8(outputDir, `${name}.json`);
3416
+ writeFileSync5(jsonPath, JSON.stringify(formatRenderJson(name, {}, result), null, 2));
2991
3417
  if (isTTY()) {
2992
3418
  process.stdout.write(
2993
3419
  `\u2713 ${name} \u2192 ${opts.outputDir}/${name}.png (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
@@ -3010,13 +3436,13 @@ function registerRenderAll(renderCmd) {
3010
3436
  workers.push(worker());
3011
3437
  }
3012
3438
  await Promise.all(workers);
3013
- await shutdownPool3();
3439
+ await shutdownPool4();
3014
3440
  process.stderr.write("\n");
3015
3441
  const summary = formatSummaryText(results, outputDir);
3016
3442
  process.stderr.write(`${summary}
3017
3443
  `);
3018
3444
  } catch (err) {
3019
- await shutdownPool3();
3445
+ await shutdownPool4();
3020
3446
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
3021
3447
  `);
3022
3448
  process.exit(1);
@@ -3049,7 +3475,7 @@ function resolveMatrixFormat(formatFlag, spriteAlreadyWritten) {
3049
3475
  return "json";
3050
3476
  }
3051
3477
  function createRenderCommand() {
3052
- const renderCmd = new Command5("render").description(
3478
+ const renderCmd = new Command6("render").description(
3053
3479
  "Render components to PNG or JSON via esbuild + BrowserPool"
3054
3480
  );
3055
3481
  registerRenderSingle(renderCmd);
@@ -3059,32 +3485,32 @@ function createRenderCommand() {
3059
3485
  }
3060
3486
 
3061
3487
  // src/report/baseline.ts
3062
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync5 } from "fs";
3063
- import { resolve as resolve8 } from "path";
3064
- import { generateManifest as generateManifest2 } from "@agent-scope/manifest";
3065
- import { BrowserPool as BrowserPool4, safeRender as safeRender2 } from "@agent-scope/render";
3066
- import { ComplianceEngine, TokenResolver } from "@agent-scope/tokens";
3488
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync6 } from "fs";
3489
+ import { resolve as resolve9 } from "path";
3490
+ import { generateManifest as generateManifest3 } from "@agent-scope/manifest";
3491
+ import { BrowserPool as BrowserPool5, safeRender as safeRender3 } from "@agent-scope/render";
3492
+ import { ComplianceEngine as ComplianceEngine2, TokenResolver as TokenResolver2 } from "@agent-scope/tokens";
3067
3493
  var DEFAULT_BASELINE_DIR = ".reactscope/baseline";
3068
- var _pool4 = null;
3069
- async function getPool4(viewportWidth, viewportHeight) {
3070
- if (_pool4 === null) {
3071
- _pool4 = new BrowserPool4({
3494
+ var _pool5 = null;
3495
+ async function getPool5(viewportWidth, viewportHeight) {
3496
+ if (_pool5 === null) {
3497
+ _pool5 = new BrowserPool5({
3072
3498
  size: { browsers: 1, pagesPerBrowser: 4 },
3073
3499
  viewportWidth,
3074
3500
  viewportHeight
3075
3501
  });
3076
- await _pool4.init();
3502
+ await _pool5.init();
3077
3503
  }
3078
- return _pool4;
3504
+ return _pool5;
3079
3505
  }
3080
- async function shutdownPool4() {
3081
- if (_pool4 !== null) {
3082
- await _pool4.close();
3083
- _pool4 = null;
3506
+ async function shutdownPool5() {
3507
+ if (_pool5 !== null) {
3508
+ await _pool5.close();
3509
+ _pool5 = null;
3084
3510
  }
3085
3511
  }
3086
- async function renderComponent(filePath, componentName, props, viewportWidth, viewportHeight) {
3087
- const pool = await getPool4(viewportWidth, viewportHeight);
3512
+ async function renderComponent2(filePath, componentName, props, viewportWidth, viewportHeight) {
3513
+ const pool = await getPool5(viewportWidth, viewportHeight);
3088
3514
  const htmlHarness = await buildComponentHarness(filePath, componentName, props, viewportWidth);
3089
3515
  const slot = await pool.acquire();
3090
3516
  const { page } = slot;
@@ -3174,7 +3600,7 @@ async function renderComponent(filePath, componentName, props, viewportWidth, vi
3174
3600
  pool.release(slot);
3175
3601
  }
3176
3602
  }
3177
- function extractComputedStyles(computedStylesRaw) {
3603
+ function extractComputedStyles2(computedStylesRaw) {
3178
3604
  const flat = {};
3179
3605
  for (const styles of Object.values(computedStylesRaw)) {
3180
3606
  Object.assign(flat, styles);
@@ -3209,30 +3635,30 @@ async function runBaseline(options = {}) {
3209
3635
  } = options;
3210
3636
  const startTime = performance.now();
3211
3637
  const rootDir = process.cwd();
3212
- const baselineDir = resolve8(rootDir, outputDir);
3213
- const rendersDir = resolve8(baselineDir, "renders");
3214
- if (existsSync5(baselineDir)) {
3638
+ const baselineDir = resolve9(rootDir, outputDir);
3639
+ const rendersDir = resolve9(baselineDir, "renders");
3640
+ if (existsSync6(baselineDir)) {
3215
3641
  rmSync(baselineDir, { recursive: true, force: true });
3216
3642
  }
3217
3643
  mkdirSync4(rendersDir, { recursive: true });
3218
3644
  let manifest;
3219
3645
  if (manifestPath !== void 0) {
3220
- const { readFileSync: readFileSync10 } = await import("fs");
3221
- const absPath = resolve8(rootDir, manifestPath);
3222
- if (!existsSync5(absPath)) {
3646
+ const { readFileSync: readFileSync12 } = await import("fs");
3647
+ const absPath = resolve9(rootDir, manifestPath);
3648
+ if (!existsSync6(absPath)) {
3223
3649
  throw new Error(`Manifest not found at ${absPath}.`);
3224
3650
  }
3225
- manifest = JSON.parse(readFileSync10(absPath, "utf-8"));
3651
+ manifest = JSON.parse(readFileSync12(absPath, "utf-8"));
3226
3652
  process.stderr.write(`Loaded manifest from ${manifestPath}
3227
3653
  `);
3228
3654
  } else {
3229
3655
  process.stderr.write("Scanning for React components\u2026\n");
3230
- manifest = await generateManifest2({ rootDir });
3656
+ manifest = await generateManifest3({ rootDir });
3231
3657
  const count = Object.keys(manifest.components).length;
3232
3658
  process.stderr.write(`Found ${count} components.
3233
3659
  `);
3234
3660
  }
3235
- writeFileSync5(resolve8(baselineDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
3661
+ writeFileSync6(resolve9(baselineDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
3236
3662
  let componentNames = Object.keys(manifest.components);
3237
3663
  if (componentsGlob !== void 0) {
3238
3664
  componentNames = componentNames.filter((name) => matchGlob(componentsGlob, name));
@@ -3252,8 +3678,8 @@ async function runBaseline(options = {}) {
3252
3678
  aggregateCompliance: 1,
3253
3679
  auditedAt: (/* @__PURE__ */ new Date()).toISOString()
3254
3680
  };
3255
- writeFileSync5(
3256
- resolve8(baselineDir, "compliance.json"),
3681
+ writeFileSync6(
3682
+ resolve9(baselineDir, "compliance.json"),
3257
3683
  JSON.stringify(emptyReport, null, 2),
3258
3684
  "utf-8"
3259
3685
  );
@@ -3274,9 +3700,9 @@ async function runBaseline(options = {}) {
3274
3700
  const renderOne = async (name) => {
3275
3701
  const descriptor = manifest.components[name];
3276
3702
  if (descriptor === void 0) return;
3277
- const filePath = resolve8(rootDir, descriptor.filePath);
3278
- const outcome = await safeRender2(
3279
- () => renderComponent(filePath, name, {}, viewportWidth, viewportHeight),
3703
+ const filePath = resolve9(rootDir, descriptor.filePath);
3704
+ const outcome = await safeRender3(
3705
+ () => renderComponent2(filePath, name, {}, viewportWidth, viewportHeight),
3280
3706
  {
3281
3707
  props: {},
3282
3708
  sourceLocation: {
@@ -3293,8 +3719,8 @@ async function runBaseline(options = {}) {
3293
3719
  }
3294
3720
  if (outcome.crashed) {
3295
3721
  failureCount++;
3296
- const errPath = resolve8(rendersDir, `${name}.error.json`);
3297
- writeFileSync5(
3722
+ const errPath = resolve9(rendersDir, `${name}.error.json`);
3723
+ writeFileSync6(
3298
3724
  errPath,
3299
3725
  JSON.stringify(
3300
3726
  {
@@ -3311,14 +3737,14 @@ async function runBaseline(options = {}) {
3311
3737
  return;
3312
3738
  }
3313
3739
  const result = outcome.result;
3314
- writeFileSync5(resolve8(rendersDir, `${name}.png`), result.screenshot);
3740
+ writeFileSync6(resolve9(rendersDir, `${name}.png`), result.screenshot);
3315
3741
  const jsonOutput = formatRenderJson(name, {}, result);
3316
- writeFileSync5(
3317
- resolve8(rendersDir, `${name}.json`),
3742
+ writeFileSync6(
3743
+ resolve9(rendersDir, `${name}.json`),
3318
3744
  JSON.stringify(jsonOutput, null, 2),
3319
3745
  "utf-8"
3320
3746
  );
3321
- computedStylesMap.set(name, extractComputedStyles(result.computedStyles));
3747
+ computedStylesMap.set(name, extractComputedStyles2(result.computedStyles));
3322
3748
  };
3323
3749
  const worker = async () => {
3324
3750
  while (nextIdx < componentNames.length) {
@@ -3334,15 +3760,15 @@ async function runBaseline(options = {}) {
3334
3760
  workers.push(worker());
3335
3761
  }
3336
3762
  await Promise.all(workers);
3337
- await shutdownPool4();
3763
+ await shutdownPool5();
3338
3764
  if (isTTY()) {
3339
3765
  process.stderr.write("\n");
3340
3766
  }
3341
- const resolver = new TokenResolver([]);
3342
- const engine = new ComplianceEngine(resolver);
3767
+ const resolver = new TokenResolver2([]);
3768
+ const engine = new ComplianceEngine2(resolver);
3343
3769
  const batchReport = engine.auditBatch(computedStylesMap);
3344
- writeFileSync5(
3345
- resolve8(baselineDir, "compliance.json"),
3770
+ writeFileSync6(
3771
+ resolve9(baselineDir, "compliance.json"),
3346
3772
  JSON.stringify(batchReport, null, 2),
3347
3773
  "utf-8"
3348
3774
  );
@@ -3385,43 +3811,43 @@ function registerBaselineSubCommand(reportCmd) {
3385
3811
  }
3386
3812
 
3387
3813
  // src/report/diff.ts
3388
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
3389
- import { resolve as resolve9 } from "path";
3390
- import { generateManifest as generateManifest3 } from "@agent-scope/manifest";
3391
- import { BrowserPool as BrowserPool5, safeRender as safeRender3 } from "@agent-scope/render";
3392
- import { ComplianceEngine as ComplianceEngine2, TokenResolver as TokenResolver2 } from "@agent-scope/tokens";
3814
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
3815
+ import { resolve as resolve10 } from "path";
3816
+ import { generateManifest as generateManifest4 } from "@agent-scope/manifest";
3817
+ import { BrowserPool as BrowserPool6, safeRender as safeRender4 } from "@agent-scope/render";
3818
+ import { ComplianceEngine as ComplianceEngine3, TokenResolver as TokenResolver3 } from "@agent-scope/tokens";
3393
3819
  var DEFAULT_BASELINE_DIR2 = ".reactscope/baseline";
3394
3820
  function loadBaselineCompliance(baselineDir) {
3395
- const compliancePath = resolve9(baselineDir, "compliance.json");
3396
- if (!existsSync6(compliancePath)) return null;
3397
- const raw = JSON.parse(readFileSync5(compliancePath, "utf-8"));
3821
+ const compliancePath = resolve10(baselineDir, "compliance.json");
3822
+ if (!existsSync7(compliancePath)) return null;
3823
+ const raw = JSON.parse(readFileSync6(compliancePath, "utf-8"));
3398
3824
  return raw;
3399
3825
  }
3400
- function loadBaselineRenderJson(baselineDir, componentName) {
3401
- const jsonPath = resolve9(baselineDir, "renders", `${componentName}.json`);
3402
- if (!existsSync6(jsonPath)) return null;
3403
- return JSON.parse(readFileSync5(jsonPath, "utf-8"));
3826
+ function loadBaselineRenderJson2(baselineDir, componentName) {
3827
+ const jsonPath = resolve10(baselineDir, "renders", `${componentName}.json`);
3828
+ if (!existsSync7(jsonPath)) return null;
3829
+ return JSON.parse(readFileSync6(jsonPath, "utf-8"));
3404
3830
  }
3405
- var _pool5 = null;
3406
- async function getPool5(viewportWidth, viewportHeight) {
3407
- if (_pool5 === null) {
3408
- _pool5 = new BrowserPool5({
3831
+ var _pool6 = null;
3832
+ async function getPool6(viewportWidth, viewportHeight) {
3833
+ if (_pool6 === null) {
3834
+ _pool6 = new BrowserPool6({
3409
3835
  size: { browsers: 1, pagesPerBrowser: 4 },
3410
3836
  viewportWidth,
3411
3837
  viewportHeight
3412
3838
  });
3413
- await _pool5.init();
3839
+ await _pool6.init();
3414
3840
  }
3415
- return _pool5;
3841
+ return _pool6;
3416
3842
  }
3417
- async function shutdownPool5() {
3418
- if (_pool5 !== null) {
3419
- await _pool5.close();
3420
- _pool5 = null;
3843
+ async function shutdownPool6() {
3844
+ if (_pool6 !== null) {
3845
+ await _pool6.close();
3846
+ _pool6 = null;
3421
3847
  }
3422
3848
  }
3423
- async function renderComponent2(filePath, componentName, props, viewportWidth, viewportHeight) {
3424
- const pool = await getPool5(viewportWidth, viewportHeight);
3849
+ async function renderComponent3(filePath, componentName, props, viewportWidth, viewportHeight) {
3850
+ const pool = await getPool6(viewportWidth, viewportHeight);
3425
3851
  const htmlHarness = await buildComponentHarness(filePath, componentName, props, viewportWidth);
3426
3852
  const slot = await pool.acquire();
3427
3853
  const { page } = slot;
@@ -3511,7 +3937,7 @@ async function renderComponent2(filePath, componentName, props, viewportWidth, v
3511
3937
  pool.release(slot);
3512
3938
  }
3513
3939
  }
3514
- function extractComputedStyles2(computedStylesRaw) {
3940
+ function extractComputedStyles3(computedStylesRaw) {
3515
3941
  const flat = {};
3516
3942
  for (const styles of Object.values(computedStylesRaw)) {
3517
3943
  Object.assign(flat, styles);
@@ -3567,19 +3993,19 @@ async function runDiff(options = {}) {
3567
3993
  } = options;
3568
3994
  const startTime = performance.now();
3569
3995
  const rootDir = process.cwd();
3570
- const baselineDir = resolve9(rootDir, baselineDirRaw);
3571
- if (!existsSync6(baselineDir)) {
3996
+ const baselineDir = resolve10(rootDir, baselineDirRaw);
3997
+ if (!existsSync7(baselineDir)) {
3572
3998
  throw new Error(
3573
3999
  `Baseline directory not found at "${baselineDir}". Run \`scope report baseline\` first to create a baseline snapshot.`
3574
4000
  );
3575
4001
  }
3576
- const baselineManifestPath = resolve9(baselineDir, "manifest.json");
3577
- if (!existsSync6(baselineManifestPath)) {
4002
+ const baselineManifestPath = resolve10(baselineDir, "manifest.json");
4003
+ if (!existsSync7(baselineManifestPath)) {
3578
4004
  throw new Error(
3579
4005
  `Baseline manifest.json not found at "${baselineManifestPath}". The baseline directory may be incomplete \u2014 re-run \`scope report baseline\`.`
3580
4006
  );
3581
4007
  }
3582
- const baselineManifest = JSON.parse(readFileSync5(baselineManifestPath, "utf-8"));
4008
+ const baselineManifest = JSON.parse(readFileSync6(baselineManifestPath, "utf-8"));
3583
4009
  const baselineCompliance = loadBaselineCompliance(baselineDir);
3584
4010
  const baselineComponentNames = new Set(Object.keys(baselineManifest.components));
3585
4011
  process.stderr.write(
@@ -3588,16 +4014,16 @@ async function runDiff(options = {}) {
3588
4014
  );
3589
4015
  let currentManifest;
3590
4016
  if (manifestPath !== void 0) {
3591
- const absPath = resolve9(rootDir, manifestPath);
3592
- if (!existsSync6(absPath)) {
4017
+ const absPath = resolve10(rootDir, manifestPath);
4018
+ if (!existsSync7(absPath)) {
3593
4019
  throw new Error(`Manifest not found at "${absPath}".`);
3594
4020
  }
3595
- currentManifest = JSON.parse(readFileSync5(absPath, "utf-8"));
4021
+ currentManifest = JSON.parse(readFileSync6(absPath, "utf-8"));
3596
4022
  process.stderr.write(`Loaded manifest from ${manifestPath}
3597
4023
  `);
3598
4024
  } else {
3599
4025
  process.stderr.write("Scanning for React components\u2026\n");
3600
- currentManifest = await generateManifest3({ rootDir });
4026
+ currentManifest = await generateManifest4({ rootDir });
3601
4027
  const count = Object.keys(currentManifest.components).length;
3602
4028
  process.stderr.write(`Found ${count} components.
3603
4029
  `);
@@ -3625,9 +4051,9 @@ async function runDiff(options = {}) {
3625
4051
  const renderOne = async (name) => {
3626
4052
  const descriptor = currentManifest.components[name];
3627
4053
  if (descriptor === void 0) return;
3628
- const filePath = resolve9(rootDir, descriptor.filePath);
3629
- const outcome = await safeRender3(
3630
- () => renderComponent2(filePath, name, {}, viewportWidth, viewportHeight),
4054
+ const filePath = resolve10(rootDir, descriptor.filePath);
4055
+ const outcome = await safeRender4(
4056
+ () => renderComponent3(filePath, name, {}, viewportWidth, viewportHeight),
3631
4057
  {
3632
4058
  props: {},
3633
4059
  sourceLocation: {
@@ -3652,7 +4078,7 @@ async function runDiff(options = {}) {
3652
4078
  height: result.height,
3653
4079
  renderTimeMs: result.renderTimeMs
3654
4080
  });
3655
- computedStylesMap.set(name, extractComputedStyles2(result.computedStyles));
4081
+ computedStylesMap.set(name, extractComputedStyles3(result.computedStyles));
3656
4082
  };
3657
4083
  if (total > 0) {
3658
4084
  const worker = async () => {
@@ -3670,18 +4096,18 @@ async function runDiff(options = {}) {
3670
4096
  }
3671
4097
  await Promise.all(workers);
3672
4098
  }
3673
- await shutdownPool5();
4099
+ await shutdownPool6();
3674
4100
  if (isTTY() && total > 0) {
3675
4101
  process.stderr.write("\n");
3676
4102
  }
3677
- const resolver = new TokenResolver2([]);
3678
- const engine = new ComplianceEngine2(resolver);
4103
+ const resolver = new TokenResolver3([]);
4104
+ const engine = new ComplianceEngine3(resolver);
3679
4105
  const currentBatchReport = engine.auditBatch(computedStylesMap);
3680
4106
  const entries = [];
3681
4107
  for (const name of componentNames) {
3682
4108
  const baselineComp = baselineCompliance?.components[name] ?? null;
3683
4109
  const currentComp = currentBatchReport.components[name] ?? null;
3684
- const baselineMeta = loadBaselineRenderJson(baselineDir, name);
4110
+ const baselineMeta = loadBaselineRenderJson2(baselineDir, name);
3685
4111
  const currentMeta = currentRenderMeta.get(name) ?? null;
3686
4112
  const failed = renderFailures.has(name);
3687
4113
  const baselineComplianceScore = baselineComp?.aggregateCompliance ?? null;
@@ -3701,7 +4127,7 @@ async function runDiff(options = {}) {
3701
4127
  }
3702
4128
  for (const name of removedNames) {
3703
4129
  const baselineComp = baselineCompliance?.components[name] ?? null;
3704
- const baselineMeta = loadBaselineRenderJson(baselineDir, name);
4130
+ const baselineMeta = loadBaselineRenderJson2(baselineDir, name);
3705
4131
  entries.push({
3706
4132
  name,
3707
4133
  status: "removed",
@@ -3843,7 +4269,7 @@ function registerDiffSubCommand(reportCmd) {
3843
4269
  regressionThreshold
3844
4270
  });
3845
4271
  if (opts.output !== void 0) {
3846
- writeFileSync6(opts.output, JSON.stringify(result, null, 2), "utf-8");
4272
+ writeFileSync7(opts.output, JSON.stringify(result, null, 2), "utf-8");
3847
4273
  process.stderr.write(`Diff written to ${opts.output}
3848
4274
  `);
3849
4275
  }
@@ -3864,6 +4290,139 @@ function registerDiffSubCommand(reportCmd) {
3864
4290
  );
3865
4291
  }
3866
4292
 
4293
+ // src/report/pr-comment.ts
4294
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
4295
+ import { resolve as resolve11 } from "path";
4296
+ var STATUS_BADGE = {
4297
+ added: "\u2705 added",
4298
+ removed: "\u{1F5D1}\uFE0F removed",
4299
+ unchanged: "\u2014 unchanged",
4300
+ compliance_regressed: "\u274C regressed",
4301
+ compliance_improved: "\u{1F4C8} improved",
4302
+ size_changed: "\u{1F4D0} resized"
4303
+ };
4304
+ function fmt(n) {
4305
+ return `${(n * 100).toFixed(1)}%`;
4306
+ }
4307
+ function fmtDelta(delta) {
4308
+ if (delta === null) return "\u2014";
4309
+ const sign = delta >= 0 ? "+" : "";
4310
+ return `${sign}${(delta * 100).toFixed(1)}%`;
4311
+ }
4312
+ function fmtDimensions(d) {
4313
+ if (d === null) return "\u2014";
4314
+ return `${d.width} \xD7 ${d.height}`;
4315
+ }
4316
+ function complianceDeltaArrow(diff) {
4317
+ const delta = diff.currentAggregateCompliance - diff.baselineAggregateCompliance;
4318
+ if (Math.abs(delta) < 5e-4) return `\u2192 ${fmt(diff.currentAggregateCompliance)} (no change)`;
4319
+ const arrow = delta > 0 ? "\u2191" : "\u2193";
4320
+ return `${arrow} ${fmtDelta(delta)}`;
4321
+ }
4322
+ function formatPrComment(diff) {
4323
+ const { summary, components } = diff;
4324
+ const lines = [];
4325
+ const hasRegressions = diff.hasRegressions;
4326
+ const headerEmoji = hasRegressions ? "\u26A0\uFE0F" : "\u2705";
4327
+ lines.push(`## ${headerEmoji} Scope Report`);
4328
+ lines.push("");
4329
+ lines.push("| Metric | Value |");
4330
+ lines.push("|---|---|");
4331
+ lines.push(`| Baseline compliance | ${fmt(diff.baselineAggregateCompliance)} |`);
4332
+ lines.push(`| Current compliance | ${fmt(diff.currentAggregateCompliance)} |`);
4333
+ lines.push(`| Delta | ${complianceDeltaArrow(diff)} |`);
4334
+ lines.push(
4335
+ `| Components | ${summary.total} total \xB7 ${summary.added} added \xB7 ${summary.removed} removed \xB7 ${summary.complianceRegressed} regressed |`
4336
+ );
4337
+ if (summary.renderFailed > 0) {
4338
+ lines.push(`| Render failures | ${summary.renderFailed} |`);
4339
+ }
4340
+ lines.push("");
4341
+ const changed = components.filter((c) => c.status !== "unchanged");
4342
+ const unchanged = components.filter((c) => c.status === "unchanged");
4343
+ if (changed.length > 0) {
4344
+ lines.push("### Changes");
4345
+ lines.push("");
4346
+ lines.push("| Component | Status | Compliance \u0394 | Dimensions |");
4347
+ lines.push("|---|---|---|---|");
4348
+ for (const c of changed) {
4349
+ const badge = STATUS_BADGE[c.status];
4350
+ const delta = fmtDelta(c.complianceDelta);
4351
+ const dims = fmtDimensions(c.currentDimensions ?? c.baselineDimensions);
4352
+ lines.push(`| \`${c.name}\` | ${badge} | ${delta} | ${dims} |`);
4353
+ }
4354
+ lines.push("");
4355
+ }
4356
+ if (unchanged.length > 0) {
4357
+ lines.push(
4358
+ `<details><summary>${unchanged.length} unchanged component${unchanged.length === 1 ? "" : "s"}</summary>`
4359
+ );
4360
+ lines.push("");
4361
+ lines.push("| Component | Compliance |");
4362
+ lines.push("|---|---|");
4363
+ for (const c of unchanged) {
4364
+ lines.push(
4365
+ `| \`${c.name}\` | ${c.currentCompliance !== null ? fmt(c.currentCompliance) : "\u2014"} |`
4366
+ );
4367
+ }
4368
+ lines.push("");
4369
+ lines.push("</details>");
4370
+ lines.push("");
4371
+ }
4372
+ lines.push(
4373
+ `> Generated by [Scope](https://github.com/FlatFilers/Scope) \xB7 diffed at ${diff.diffedAt}`
4374
+ );
4375
+ return lines.join("\n");
4376
+ }
4377
+ function loadDiffResult(filePath) {
4378
+ const abs = resolve11(filePath);
4379
+ if (!existsSync8(abs)) {
4380
+ throw new Error(`DiffResult file not found: ${abs}`);
4381
+ }
4382
+ let raw;
4383
+ try {
4384
+ raw = readFileSync7(abs, "utf-8");
4385
+ } catch (err) {
4386
+ throw new Error(
4387
+ `Failed to read DiffResult file: ${err instanceof Error ? err.message : String(err)}`
4388
+ );
4389
+ }
4390
+ let parsed;
4391
+ try {
4392
+ parsed = JSON.parse(raw);
4393
+ } catch {
4394
+ throw new Error(`DiffResult file is not valid JSON: ${abs}`);
4395
+ }
4396
+ if (typeof parsed !== "object" || parsed === null || !("diffedAt" in parsed) || !("components" in parsed) || !("summary" in parsed)) {
4397
+ throw new Error(
4398
+ `DiffResult file does not match expected shape (missing diffedAt/components/summary): ${abs}`
4399
+ );
4400
+ }
4401
+ return parsed;
4402
+ }
4403
+ function registerPrCommentSubCommand(reportCmd) {
4404
+ reportCmd.command("pr-comment").description(
4405
+ "Format a DiffResult JSON file as a GitHub PR comment (Markdown, written to stdout)"
4406
+ ).requiredOption("-i, --input <path>", "Path to DiffResult JSON (from scope report diff --json)").option("-o, --output <path>", "Write comment to file instead of stdout").action(async (opts) => {
4407
+ try {
4408
+ const diff = loadDiffResult(opts.input);
4409
+ const comment = formatPrComment(diff);
4410
+ if (opts.output !== void 0) {
4411
+ writeFileSync8(resolve11(opts.output), comment, "utf-8");
4412
+ process.stderr.write(`PR comment written to ${opts.output}
4413
+ `);
4414
+ } else {
4415
+ process.stdout.write(`${comment}
4416
+ `);
4417
+ }
4418
+ } catch (err) {
4419
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
4420
+ `);
4421
+ process.exit(1);
4422
+ }
4423
+ });
4424
+ }
4425
+
3867
4426
  // src/tree-formatter.ts
3868
4427
  var BRANCH2 = "\u251C\u2500\u2500 ";
3869
4428
  var LAST_BRANCH2 = "\u2514\u2500\u2500 ";
@@ -4144,35 +4703,35 @@ function buildStructuredReport(report) {
4144
4703
  }
4145
4704
 
4146
4705
  // src/tokens/commands.ts
4147
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
4148
- import { resolve as resolve13 } from "path";
4706
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
4707
+ import { resolve as resolve15 } from "path";
4149
4708
  import {
4150
4709
  parseTokenFileSync as parseTokenFileSync2,
4151
4710
  TokenParseError,
4152
- TokenResolver as TokenResolver7,
4711
+ TokenResolver as TokenResolver8,
4153
4712
  TokenValidationError,
4154
4713
  validateTokenFile
4155
4714
  } from "@agent-scope/tokens";
4156
- import { Command as Command7 } from "commander";
4715
+ import { Command as Command8 } from "commander";
4157
4716
 
4158
4717
  // src/tokens/compliance.ts
4159
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
4160
- import { resolve as resolve10 } from "path";
4718
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
4719
+ import { resolve as resolve12 } from "path";
4161
4720
  import {
4162
- ComplianceEngine as ComplianceEngine3,
4163
- TokenResolver as TokenResolver3
4721
+ ComplianceEngine as ComplianceEngine4,
4722
+ TokenResolver as TokenResolver4
4164
4723
  } from "@agent-scope/tokens";
4165
4724
  var DEFAULT_STYLES_PATH = ".reactscope/compliance-styles.json";
4166
4725
  function loadStylesFile(stylesPath) {
4167
- const absPath = resolve10(process.cwd(), stylesPath);
4168
- if (!existsSync7(absPath)) {
4726
+ const absPath = resolve12(process.cwd(), stylesPath);
4727
+ if (!existsSync9(absPath)) {
4169
4728
  throw new Error(
4170
4729
  `Compliance styles file not found at ${absPath}.
4171
4730
  Run \`scope render all\` first to generate component styles, or use --styles to specify a path.
4172
4731
  Expected format: { "ComponentName": { colors: {}, spacing: {}, typography: {}, borders: {}, shadows: {} } }`
4173
4732
  );
4174
4733
  }
4175
- const raw = readFileSync6(absPath, "utf-8");
4734
+ const raw = readFileSync8(absPath, "utf-8");
4176
4735
  let parsed;
4177
4736
  try {
4178
4737
  parsed = JSON.parse(raw);
@@ -4298,8 +4857,8 @@ function registerCompliance(tokensCmd) {
4298
4857
  try {
4299
4858
  const tokenFilePath = resolveTokenFilePath(opts.file);
4300
4859
  const { tokens } = loadTokens(tokenFilePath);
4301
- const resolver = new TokenResolver3(tokens);
4302
- const engine = new ComplianceEngine3(resolver);
4860
+ const resolver = new TokenResolver4(tokens);
4861
+ const engine = new ComplianceEngine4(resolver);
4303
4862
  const stylesPath = opts.styles ?? DEFAULT_STYLES_PATH;
4304
4863
  const stylesFile = loadStylesFile(stylesPath);
4305
4864
  const componentMap = /* @__PURE__ */ new Map();
@@ -4332,38 +4891,38 @@ function registerCompliance(tokensCmd) {
4332
4891
  }
4333
4892
 
4334
4893
  // src/tokens/export.ts
4335
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
4336
- import { resolve as resolve11 } from "path";
4894
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
4895
+ import { resolve as resolve13 } from "path";
4337
4896
  import {
4338
4897
  exportTokens,
4339
4898
  parseTokenFileSync,
4340
4899
  ThemeResolver,
4341
- TokenResolver as TokenResolver4
4900
+ TokenResolver as TokenResolver5
4342
4901
  } from "@agent-scope/tokens";
4343
- import { Command as Command6 } from "commander";
4902
+ import { Command as Command7 } from "commander";
4344
4903
  var DEFAULT_TOKEN_FILE = "reactscope.tokens.json";
4345
4904
  var CONFIG_FILE = "reactscope.config.json";
4346
4905
  var SUPPORTED_FORMATS = ["css", "ts", "scss", "tailwind", "flat-json", "figma"];
4347
4906
  function resolveTokenFilePath2(fileFlag) {
4348
4907
  if (fileFlag !== void 0) {
4349
- return resolve11(process.cwd(), fileFlag);
4908
+ return resolve13(process.cwd(), fileFlag);
4350
4909
  }
4351
- const configPath = resolve11(process.cwd(), CONFIG_FILE);
4352
- if (existsSync8(configPath)) {
4910
+ const configPath = resolve13(process.cwd(), CONFIG_FILE);
4911
+ if (existsSync10(configPath)) {
4353
4912
  try {
4354
- const raw = readFileSync7(configPath, "utf-8");
4913
+ const raw = readFileSync9(configPath, "utf-8");
4355
4914
  const config = JSON.parse(raw);
4356
4915
  if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
4357
4916
  const file = config.tokens.file;
4358
- return resolve11(process.cwd(), file);
4917
+ return resolve13(process.cwd(), file);
4359
4918
  }
4360
4919
  } catch {
4361
4920
  }
4362
4921
  }
4363
- return resolve11(process.cwd(), DEFAULT_TOKEN_FILE);
4922
+ return resolve13(process.cwd(), DEFAULT_TOKEN_FILE);
4364
4923
  }
4365
4924
  function createTokensExportCommand() {
4366
- return new Command6("export").description("Export design tokens to a downstream format").requiredOption("--format <fmt>", `Output format: ${SUPPORTED_FORMATS.join(", ")}`).option("--file <path>", "Path to token file (overrides config)").option("--out <path>", "Write output to file instead of stdout").option("--prefix <prefix>", "CSS/SCSS: prefix for variable names (e.g. 'scope')").option("--selector <selector>", "CSS: custom root selector (default: ':root')").option(
4925
+ return new Command7("export").description("Export design tokens to a downstream format").requiredOption("--format <fmt>", `Output format: ${SUPPORTED_FORMATS.join(", ")}`).option("--file <path>", "Path to token file (overrides config)").option("--out <path>", "Write output to file instead of stdout").option("--prefix <prefix>", "CSS/SCSS: prefix for variable names (e.g. 'scope')").option("--selector <selector>", "CSS: custom root selector (default: ':root')").option(
4367
4926
  "--theme <name>",
4368
4927
  "Include theme overrides for the named theme (applies to css, ts, scss, tailwind, figma)"
4369
4928
  ).action(
@@ -4379,13 +4938,13 @@ Supported formats: ${SUPPORTED_FORMATS.join(", ")}
4379
4938
  const format = opts.format;
4380
4939
  try {
4381
4940
  const filePath = resolveTokenFilePath2(opts.file);
4382
- if (!existsSync8(filePath)) {
4941
+ if (!existsSync10(filePath)) {
4383
4942
  throw new Error(
4384
4943
  `Token file not found at ${filePath}.
4385
4944
  Create a reactscope.tokens.json file or use --file to specify a path.`
4386
4945
  );
4387
4946
  }
4388
- const raw = readFileSync7(filePath, "utf-8");
4947
+ const raw = readFileSync9(filePath, "utf-8");
4389
4948
  const { tokens, rawFile } = parseTokenFileSync(raw);
4390
4949
  let themesMap;
4391
4950
  if (opts.theme !== void 0) {
@@ -4396,7 +4955,7 @@ Create a reactscope.tokens.json file or use --file to specify a path.`
4396
4955
  Available themes: ${available}`
4397
4956
  );
4398
4957
  }
4399
- const baseResolver = new TokenResolver4(tokens);
4958
+ const baseResolver = new TokenResolver5(tokens);
4400
4959
  const themeResolver = ThemeResolver.fromTokenFile(
4401
4960
  baseResolver,
4402
4961
  rawFile
@@ -4424,8 +4983,8 @@ Available themes: ${themeNames.join(", ")}`
4424
4983
  themes: themesMap
4425
4984
  });
4426
4985
  if (opts.out !== void 0) {
4427
- const outPath = resolve11(process.cwd(), opts.out);
4428
- writeFileSync7(outPath, output, "utf-8");
4986
+ const outPath = resolve13(process.cwd(), opts.out);
4987
+ writeFileSync9(outPath, output, "utf-8");
4429
4988
  process.stderr.write(`Exported ${tokens.length} tokens to ${outPath}
4430
4989
  `);
4431
4990
  } else {
@@ -4445,9 +5004,9 @@ Available themes: ${themeNames.join(", ")}`
4445
5004
 
4446
5005
  // src/tokens/impact.ts
4447
5006
  import {
4448
- ComplianceEngine as ComplianceEngine4,
5007
+ ComplianceEngine as ComplianceEngine5,
4449
5008
  ImpactAnalyzer,
4450
- TokenResolver as TokenResolver5
5009
+ TokenResolver as TokenResolver6
4451
5010
  } from "@agent-scope/tokens";
4452
5011
  var DEFAULT_STYLES_PATH2 = ".reactscope/compliance-styles.json";
4453
5012
  var SEVERITY_EMOJI = {
@@ -4505,8 +5064,8 @@ function registerImpact(tokensCmd) {
4505
5064
  try {
4506
5065
  const tokenFilePath = resolveTokenFilePath(opts.file);
4507
5066
  const { tokens } = loadTokens(tokenFilePath);
4508
- const resolver = new TokenResolver5(tokens);
4509
- const engine = new ComplianceEngine4(resolver);
5067
+ const resolver = new TokenResolver6(tokens);
5068
+ const engine = new ComplianceEngine5(resolver);
4510
5069
  const stylesPath = opts.styles ?? DEFAULT_STYLES_PATH2;
4511
5070
  const stylesFile = loadStylesFile(stylesPath);
4512
5071
  const componentMap = new Map(Object.entries(stylesFile));
@@ -4539,10 +5098,10 @@ ${formatImpactSummary(report)}
4539
5098
  }
4540
5099
 
4541
5100
  // src/tokens/preview.ts
4542
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "fs";
4543
- import { resolve as resolve12 } from "path";
4544
- import { BrowserPool as BrowserPool6, SpriteSheetGenerator } from "@agent-scope/render";
4545
- import { ComplianceEngine as ComplianceEngine5, ImpactAnalyzer as ImpactAnalyzer2, TokenResolver as TokenResolver6 } from "@agent-scope/tokens";
5101
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync10 } from "fs";
5102
+ import { resolve as resolve14 } from "path";
5103
+ import { BrowserPool as BrowserPool7, SpriteSheetGenerator } from "@agent-scope/render";
5104
+ import { ComplianceEngine as ComplianceEngine6, ImpactAnalyzer as ImpactAnalyzer2, TokenResolver as TokenResolver7 } from "@agent-scope/tokens";
4546
5105
  var DEFAULT_STYLES_PATH3 = ".reactscope/compliance-styles.json";
4547
5106
  var DEFAULT_MANIFEST_PATH = ".reactscope/manifest.json";
4548
5107
  var DEFAULT_OUTPUT_DIR2 = ".reactscope/previews";
@@ -4556,7 +5115,7 @@ async function renderComponentWithCssOverride(filePath, componentName, cssOverri
4556
5115
  cssOverride
4557
5116
  // injected as <style>
4558
5117
  );
4559
- const pool = new BrowserPool6({
5118
+ const pool = new BrowserPool7({
4560
5119
  size: { browsers: 1, pagesPerBrowser: 1 },
4561
5120
  viewportWidth: vpWidth,
4562
5121
  viewportHeight: vpHeight
@@ -4600,8 +5159,8 @@ function registerPreview(tokensCmd) {
4600
5159
  try {
4601
5160
  const tokenFilePath = resolveTokenFilePath(opts.file);
4602
5161
  const { tokens } = loadTokens(tokenFilePath);
4603
- const resolver = new TokenResolver6(tokens);
4604
- const engine = new ComplianceEngine5(resolver);
5162
+ const resolver = new TokenResolver7(tokens);
5163
+ const engine = new ComplianceEngine6(resolver);
4605
5164
  const stylesPath = opts.styles ?? DEFAULT_STYLES_PATH3;
4606
5165
  const stylesFile = loadStylesFile(stylesPath);
4607
5166
  const componentMap = new Map(Object.entries(stylesFile));
@@ -4721,10 +5280,10 @@ function registerPreview(tokensCmd) {
4721
5280
  });
4722
5281
  const spriteResult = await generator.generate(matrixResult);
4723
5282
  const tokenLabel = tokenPath.replace(/\./g, "-");
4724
- const outputPath = opts.output ?? resolve12(process.cwd(), DEFAULT_OUTPUT_DIR2, `preview-${tokenLabel}.png`);
4725
- const outputDir = resolve12(outputPath, "..");
5283
+ const outputPath = opts.output ?? resolve14(process.cwd(), DEFAULT_OUTPUT_DIR2, `preview-${tokenLabel}.png`);
5284
+ const outputDir = resolve14(outputPath, "..");
4726
5285
  mkdirSync5(outputDir, { recursive: true });
4727
- writeFileSync8(outputPath, spriteResult.png);
5286
+ writeFileSync10(outputPath, spriteResult.png);
4728
5287
  const useJson = opts.format === "json" || opts.format !== "text" && !isTTY();
4729
5288
  if (useJson) {
4730
5289
  process.stdout.write(
@@ -4783,30 +5342,30 @@ function buildTable2(headers, rows) {
4783
5342
  }
4784
5343
  function resolveTokenFilePath(fileFlag) {
4785
5344
  if (fileFlag !== void 0) {
4786
- return resolve13(process.cwd(), fileFlag);
5345
+ return resolve15(process.cwd(), fileFlag);
4787
5346
  }
4788
- const configPath = resolve13(process.cwd(), CONFIG_FILE2);
4789
- if (existsSync9(configPath)) {
5347
+ const configPath = resolve15(process.cwd(), CONFIG_FILE2);
5348
+ if (existsSync11(configPath)) {
4790
5349
  try {
4791
- const raw = readFileSync8(configPath, "utf-8");
5350
+ const raw = readFileSync10(configPath, "utf-8");
4792
5351
  const config = JSON.parse(raw);
4793
5352
  if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
4794
5353
  const file = config.tokens.file;
4795
- return resolve13(process.cwd(), file);
5354
+ return resolve15(process.cwd(), file);
4796
5355
  }
4797
5356
  } catch {
4798
5357
  }
4799
5358
  }
4800
- return resolve13(process.cwd(), DEFAULT_TOKEN_FILE2);
5359
+ return resolve15(process.cwd(), DEFAULT_TOKEN_FILE2);
4801
5360
  }
4802
5361
  function loadTokens(absPath) {
4803
- if (!existsSync9(absPath)) {
5362
+ if (!existsSync11(absPath)) {
4804
5363
  throw new Error(
4805
5364
  `Token file not found at ${absPath}.
4806
5365
  Create a reactscope.tokens.json file or use --file to specify a path.`
4807
5366
  );
4808
5367
  }
4809
- const raw = readFileSync8(absPath, "utf-8");
5368
+ const raw = readFileSync10(absPath, "utf-8");
4810
5369
  return parseTokenFileSync2(raw);
4811
5370
  }
4812
5371
  function getRawValue(node, segments) {
@@ -4846,7 +5405,7 @@ function registerGet2(tokensCmd) {
4846
5405
  try {
4847
5406
  const filePath = resolveTokenFilePath(opts.file);
4848
5407
  const { tokens } = loadTokens(filePath);
4849
- const resolver = new TokenResolver7(tokens);
5408
+ const resolver = new TokenResolver8(tokens);
4850
5409
  const resolvedValue = resolver.resolve(tokenPath);
4851
5410
  const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
4852
5411
  if (useJson) {
@@ -4872,7 +5431,7 @@ function registerList2(tokensCmd) {
4872
5431
  try {
4873
5432
  const filePath = resolveTokenFilePath(opts.file);
4874
5433
  const { tokens } = loadTokens(filePath);
4875
- const resolver = new TokenResolver7(tokens);
5434
+ const resolver = new TokenResolver8(tokens);
4876
5435
  const filtered = resolver.list(opts.type, category);
4877
5436
  const useJson = opts.format === "json" || opts.format !== "table" && !isTTY2();
4878
5437
  if (useJson) {
@@ -4902,7 +5461,7 @@ function registerSearch(tokensCmd) {
4902
5461
  try {
4903
5462
  const filePath = resolveTokenFilePath(opts.file);
4904
5463
  const { tokens } = loadTokens(filePath);
4905
- const resolver = new TokenResolver7(tokens);
5464
+ const resolver = new TokenResolver8(tokens);
4906
5465
  const useJson = opts.format === "json" || opts.format !== "table" && !isTTY2();
4907
5466
  const typesToSearch = opts.type ? [opts.type] : [
4908
5467
  "color",
@@ -4985,7 +5544,7 @@ function registerResolve(tokensCmd) {
4985
5544
  const filePath = resolveTokenFilePath(opts.file);
4986
5545
  const absFilePath = filePath;
4987
5546
  const { tokens, rawFile } = loadTokens(absFilePath);
4988
- const resolver = new TokenResolver7(tokens);
5547
+ const resolver = new TokenResolver8(tokens);
4989
5548
  resolver.resolve(tokenPath);
4990
5549
  const chain = buildResolutionChain(tokenPath, rawFile.tokens);
4991
5550
  const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
@@ -5020,13 +5579,13 @@ function registerValidate(tokensCmd) {
5020
5579
  ).option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((opts) => {
5021
5580
  try {
5022
5581
  const filePath = resolveTokenFilePath(opts.file);
5023
- if (!existsSync9(filePath)) {
5582
+ if (!existsSync11(filePath)) {
5024
5583
  throw new Error(
5025
5584
  `Token file not found at ${filePath}.
5026
5585
  Create a reactscope.tokens.json file or use --file to specify a path.`
5027
5586
  );
5028
5587
  }
5029
- const raw = readFileSync8(filePath, "utf-8");
5588
+ const raw = readFileSync10(filePath, "utf-8");
5030
5589
  const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
5031
5590
  const errors = [];
5032
5591
  let parsed;
@@ -5094,7 +5653,7 @@ function outputValidationResult(filePath, errors, useJson) {
5094
5653
  }
5095
5654
  }
5096
5655
  function createTokensCommand() {
5097
- const tokensCmd = new Command7("tokens").description(
5656
+ const tokensCmd = new Command8("tokens").description(
5098
5657
  "Query and validate design tokens from a reactscope.tokens.json file"
5099
5658
  );
5100
5659
  registerGet2(tokensCmd);
@@ -5111,7 +5670,7 @@ function createTokensCommand() {
5111
5670
 
5112
5671
  // src/program.ts
5113
5672
  function createProgram(options = {}) {
5114
- const program2 = new Command8("scope").version(options.version ?? "0.1.0").description("Scope \u2014 React instrumentation toolkit");
5673
+ const program2 = new Command9("scope").version(options.version ?? "0.1.0").description("Scope \u2014 React instrumentation toolkit");
5115
5674
  program2.command("capture <url>").description("Capture a React component tree from a live URL and output as JSON").option("-o, --output <path>", "Write JSON to file instead of stdout").option("--pretty", "Pretty-print JSON output (default: minified)", false).option("--timeout <ms>", "Max wait time for React to mount (ms)", "10000").option("--wait <ms>", "Additional wait after page load before capture (ms)", "0").action(
5116
5675
  async (url, opts) => {
5117
5676
  try {
@@ -5184,7 +5743,7 @@ function createProgram(options = {}) {
5184
5743
  }
5185
5744
  );
5186
5745
  program2.command("generate").description("Generate a Playwright test from a Scope trace file").argument("<trace>", "Path to a serialized Scope trace (.json)").option("-o, --output <path>", "Output file path", "scope.spec.ts").option("-d, --description <text>", "Test description").action((tracePath, opts) => {
5187
- const raw = readFileSync9(tracePath, "utf-8");
5746
+ const raw = readFileSync11(tracePath, "utf-8");
5188
5747
  const trace = loadTrace(raw);
5189
5748
  const source = generateTest(trace, {
5190
5749
  description: opts.description,
@@ -5198,10 +5757,12 @@ function createProgram(options = {}) {
5198
5757
  program2.addCommand(createTokensCommand());
5199
5758
  program2.addCommand(createInstrumentCommand());
5200
5759
  program2.addCommand(createInitCommand());
5760
+ program2.addCommand(createCiCommand());
5201
5761
  const existingReportCmd = program2.commands.find((c) => c.name() === "report");
5202
5762
  if (existingReportCmd !== void 0) {
5203
5763
  registerBaselineSubCommand(existingReportCmd);
5204
5764
  registerDiffSubCommand(existingReportCmd);
5765
+ registerPrCommentSubCommand(existingReportCmd);
5205
5766
  }
5206
5767
  return program2;
5207
5768
  }