@agent-scope/cli 1.18.1 → 1.19.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 bun
2
2
 
3
3
  // src/program.ts
4
- import { readFileSync as readFileSync12 } from "fs";
4
+ import { readFileSync as readFileSync13 } from "fs";
5
5
  import { generateTest, loadTrace } from "@agent-scope/playwright";
6
- import { Command as Command10 } from "commander";
6
+ import { Command as Command11 } from "commander";
7
7
 
8
8
  // src/browser.ts
9
9
  import { writeFileSync } from "fs";
@@ -569,10 +569,10 @@ async function getCompiledCssForClasses(cwd, classes) {
569
569
  return build3(deduped);
570
570
  }
571
571
  async function compileGlobalCssFile(cssFilePath, cwd) {
572
- const { existsSync: existsSync15, readFileSync: readFileSync13 } = await import("fs");
572
+ const { existsSync: existsSync16, readFileSync: readFileSync14 } = await import("fs");
573
573
  const { createRequire: createRequire3 } = await import("module");
574
- if (!existsSync15(cssFilePath)) return null;
575
- const raw = readFileSync13(cssFilePath, "utf-8");
574
+ if (!existsSync16(cssFilePath)) return null;
575
+ const raw = readFileSync14(cssFilePath, "utf-8");
576
576
  const needsCompile = /@tailwind|@import\s+['"]tailwindcss/.test(raw);
577
577
  if (!needsCompile) {
578
578
  return raw;
@@ -1042,18 +1042,174 @@ function createCiCommand() {
1042
1042
  );
1043
1043
  }
1044
1044
 
1045
+ // src/doctor-commands.ts
1046
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "fs";
1047
+ import { join, resolve as resolve3 } from "path";
1048
+ import { Command as Command2 } from "commander";
1049
+ function collectSourceFiles(dir) {
1050
+ if (!existsSync3(dir)) return [];
1051
+ const results = [];
1052
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1053
+ const full = join(dir, entry.name);
1054
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".reactscope") {
1055
+ results.push(...collectSourceFiles(full));
1056
+ } else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
1057
+ results.push(full);
1058
+ }
1059
+ }
1060
+ return results;
1061
+ }
1062
+ function checkConfig(cwd) {
1063
+ const configPath = resolve3(cwd, "reactscope.config.json");
1064
+ if (!existsSync3(configPath)) {
1065
+ return {
1066
+ name: "config",
1067
+ status: "error",
1068
+ message: "reactscope.config.json not found \u2014 run `scope init`"
1069
+ };
1070
+ }
1071
+ try {
1072
+ JSON.parse(readFileSync3(configPath, "utf-8"));
1073
+ return { name: "config", status: "ok", message: "reactscope.config.json valid" };
1074
+ } catch {
1075
+ return { name: "config", status: "error", message: "reactscope.config.json is not valid JSON" };
1076
+ }
1077
+ }
1078
+ function checkTokens(cwd) {
1079
+ const configPath = resolve3(cwd, "reactscope.config.json");
1080
+ let tokensPath = resolve3(cwd, "reactscope.tokens.json");
1081
+ if (existsSync3(configPath)) {
1082
+ try {
1083
+ const cfg = JSON.parse(readFileSync3(configPath, "utf-8"));
1084
+ if (cfg.tokens?.file) tokensPath = resolve3(cwd, cfg.tokens.file);
1085
+ } catch {
1086
+ }
1087
+ }
1088
+ if (!existsSync3(tokensPath)) {
1089
+ return {
1090
+ name: "tokens",
1091
+ status: "warn",
1092
+ message: `Token file not found at ${tokensPath} \u2014 run \`scope init\``
1093
+ };
1094
+ }
1095
+ try {
1096
+ const raw = JSON.parse(readFileSync3(tokensPath, "utf-8"));
1097
+ if (!raw.version) {
1098
+ return { name: "tokens", status: "warn", message: "Token file is missing a `version` field" };
1099
+ }
1100
+ return { name: "tokens", status: "ok", message: "Token file valid" };
1101
+ } catch {
1102
+ return { name: "tokens", status: "error", message: "Token file is not valid JSON" };
1103
+ }
1104
+ }
1105
+ function checkGlobalCss(cwd) {
1106
+ const configPath = resolve3(cwd, "reactscope.config.json");
1107
+ let globalCss = [];
1108
+ if (existsSync3(configPath)) {
1109
+ try {
1110
+ const cfg = JSON.parse(readFileSync3(configPath, "utf-8"));
1111
+ globalCss = cfg.components?.wrappers?.globalCSS ?? [];
1112
+ } catch {
1113
+ }
1114
+ }
1115
+ if (globalCss.length === 0) {
1116
+ return {
1117
+ name: "globalCSS",
1118
+ status: "warn",
1119
+ message: "No globalCSS configured \u2014 Tailwind styles won't apply to renders. Add `components.wrappers.globalCSS` to reactscope.config.json"
1120
+ };
1121
+ }
1122
+ const missing = globalCss.filter((f) => !existsSync3(resolve3(cwd, f)));
1123
+ if (missing.length > 0) {
1124
+ return {
1125
+ name: "globalCSS",
1126
+ status: "error",
1127
+ message: `globalCSS file(s) not found: ${missing.join(", ")}`
1128
+ };
1129
+ }
1130
+ return {
1131
+ name: "globalCSS",
1132
+ status: "ok",
1133
+ message: `${globalCss.length} globalCSS file(s) present`
1134
+ };
1135
+ }
1136
+ function checkManifest(cwd) {
1137
+ const manifestPath = resolve3(cwd, ".reactscope", "manifest.json");
1138
+ if (!existsSync3(manifestPath)) {
1139
+ return {
1140
+ name: "manifest",
1141
+ status: "warn",
1142
+ message: "Manifest not found \u2014 run `scope manifest generate`"
1143
+ };
1144
+ }
1145
+ const manifestMtime = statSync(manifestPath).mtimeMs;
1146
+ const sourceDir = resolve3(cwd, "src");
1147
+ const sourceFiles = collectSourceFiles(sourceDir);
1148
+ const stale = sourceFiles.filter((f) => statSync(f).mtimeMs > manifestMtime);
1149
+ if (stale.length > 0) {
1150
+ return {
1151
+ name: "manifest",
1152
+ status: "warn",
1153
+ message: `Manifest may be stale \u2014 ${stale.length} source file(s) modified since last generate. Run \`scope manifest generate\``
1154
+ };
1155
+ }
1156
+ return { name: "manifest", status: "ok", message: "Manifest present and up to date" };
1157
+ }
1158
+ var ICONS = { ok: "\u2713", warn: "!", error: "\u2717" };
1159
+ function formatCheck(check) {
1160
+ return ` [${ICONS[check.status]}] ${check.name.padEnd(12)} ${check.message}`;
1161
+ }
1162
+ function createDoctorCommand() {
1163
+ return new Command2("doctor").description("Check the health of your Scope setup (config, tokens, CSS, manifest)").option("--json", "Emit structured JSON output", false).action((opts) => {
1164
+ const cwd = process.cwd();
1165
+ const checks = [
1166
+ checkConfig(cwd),
1167
+ checkTokens(cwd),
1168
+ checkGlobalCss(cwd),
1169
+ checkManifest(cwd)
1170
+ ];
1171
+ const errors = checks.filter((c) => c.status === "error").length;
1172
+ const warnings = checks.filter((c) => c.status === "warn").length;
1173
+ if (opts.json) {
1174
+ process.stdout.write(
1175
+ `${JSON.stringify({ passed: checks.length - errors - warnings, warnings, errors, checks }, null, 2)}
1176
+ `
1177
+ );
1178
+ if (errors > 0) process.exit(1);
1179
+ return;
1180
+ }
1181
+ process.stdout.write("\nScope Doctor\n");
1182
+ process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
1183
+ for (const check of checks) process.stdout.write(`${formatCheck(check)}
1184
+ `);
1185
+ process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
1186
+ if (errors > 0) {
1187
+ process.stdout.write(` ${errors} error(s), ${warnings} warning(s)
1188
+
1189
+ `);
1190
+ process.exit(1);
1191
+ } else if (warnings > 0) {
1192
+ process.stdout.write(` ${warnings} warning(s) \u2014 everything works but could be better
1193
+
1194
+ `);
1195
+ } else {
1196
+ process.stdout.write(" All checks passed!\n\n");
1197
+ }
1198
+ });
1199
+ }
1200
+
1045
1201
  // src/init/index.ts
1046
- import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1047
- import { join as join2 } from "path";
1202
+ import { appendFileSync, existsSync as existsSync5, mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1203
+ import { join as join3 } from "path";
1048
1204
  import * as readline from "readline";
1049
1205
 
1050
1206
  // src/init/detect.ts
1051
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
1052
- import { join } from "path";
1207
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
1208
+ import { join as join2 } from "path";
1053
1209
  function hasConfigFile(dir, stem) {
1054
- if (!existsSync3(dir)) return false;
1210
+ if (!existsSync4(dir)) return false;
1055
1211
  try {
1056
- const entries = readdirSync(dir);
1212
+ const entries = readdirSync2(dir);
1057
1213
  return entries.some((f) => f === stem || f.startsWith(`${stem}.`));
1058
1214
  } catch {
1059
1215
  return false;
@@ -1061,7 +1217,7 @@ function hasConfigFile(dir, stem) {
1061
1217
  }
1062
1218
  function readSafe(path) {
1063
1219
  try {
1064
- return readFileSync3(path, "utf-8");
1220
+ return readFileSync4(path, "utf-8");
1065
1221
  } catch {
1066
1222
  return null;
1067
1223
  }
@@ -1074,15 +1230,15 @@ function detectFramework(rootDir, packageDeps) {
1074
1230
  return "unknown";
1075
1231
  }
1076
1232
  function detectPackageManager(rootDir) {
1077
- if (existsSync3(join(rootDir, "bun.lock"))) return "bun";
1078
- if (existsSync3(join(rootDir, "yarn.lock"))) return "yarn";
1079
- if (existsSync3(join(rootDir, "pnpm-lock.yaml"))) return "pnpm";
1080
- if (existsSync3(join(rootDir, "package-lock.json"))) return "npm";
1233
+ if (existsSync4(join2(rootDir, "bun.lock"))) return "bun";
1234
+ if (existsSync4(join2(rootDir, "yarn.lock"))) return "yarn";
1235
+ if (existsSync4(join2(rootDir, "pnpm-lock.yaml"))) return "pnpm";
1236
+ if (existsSync4(join2(rootDir, "package-lock.json"))) return "npm";
1081
1237
  return "npm";
1082
1238
  }
1083
1239
  function detectTypeScript(rootDir) {
1084
- const candidate = join(rootDir, "tsconfig.json");
1085
- if (existsSync3(candidate)) {
1240
+ const candidate = join2(rootDir, "tsconfig.json");
1241
+ if (existsSync4(candidate)) {
1086
1242
  return { typescript: true, tsconfigPath: candidate };
1087
1243
  }
1088
1244
  return { typescript: false, tsconfigPath: null };
@@ -1094,11 +1250,11 @@ function detectComponentPatterns(rootDir, typescript) {
1094
1250
  const ext = typescript ? "tsx" : "jsx";
1095
1251
  const altExt = typescript ? "jsx" : "jsx";
1096
1252
  for (const dir of COMPONENT_DIRS) {
1097
- const absDir = join(rootDir, dir);
1098
- if (!existsSync3(absDir)) continue;
1253
+ const absDir = join2(rootDir, dir);
1254
+ if (!existsSync4(absDir)) continue;
1099
1255
  let hasComponents = false;
1100
1256
  try {
1101
- const entries = readdirSync(absDir, { withFileTypes: true });
1257
+ const entries = readdirSync2(absDir, { withFileTypes: true });
1102
1258
  hasComponents = entries.some(
1103
1259
  (e) => e.isFile() && COMPONENT_EXTS.some((x) => e.name.endsWith(x))
1104
1260
  );
@@ -1106,7 +1262,7 @@ function detectComponentPatterns(rootDir, typescript) {
1106
1262
  hasComponents = entries.some(
1107
1263
  (e) => e.isDirectory() && (() => {
1108
1264
  try {
1109
- return readdirSync(join(absDir, e.name)).some(
1265
+ return readdirSync2(join2(absDir, e.name)).some(
1110
1266
  (f) => COMPONENT_EXTS.some((x) => f.endsWith(x))
1111
1267
  );
1112
1268
  } catch {
@@ -1143,7 +1299,7 @@ var GLOBAL_CSS_CANDIDATES = [
1143
1299
  "styles/index.css"
1144
1300
  ];
1145
1301
  function detectGlobalCSSFiles(rootDir) {
1146
- return GLOBAL_CSS_CANDIDATES.filter((rel) => existsSync3(join(rootDir, rel)));
1302
+ return GLOBAL_CSS_CANDIDATES.filter((rel) => existsSync4(join2(rootDir, rel)));
1147
1303
  }
1148
1304
  var TAILWIND_STEMS = ["tailwind.config"];
1149
1305
  var CSS_EXTS = [".css", ".scss", ".sass", ".less"];
@@ -1154,23 +1310,23 @@ function detectTokenSources(rootDir) {
1154
1310
  for (const stem of TAILWIND_STEMS) {
1155
1311
  if (hasConfigFile(rootDir, stem)) {
1156
1312
  try {
1157
- const entries = readdirSync(rootDir);
1313
+ const entries = readdirSync2(rootDir);
1158
1314
  const match = entries.find((f) => f === stem || f.startsWith(`${stem}.`));
1159
1315
  if (match) {
1160
- sources.push({ kind: "tailwind-config", path: join(rootDir, match) });
1316
+ sources.push({ kind: "tailwind-config", path: join2(rootDir, match) });
1161
1317
  }
1162
1318
  } catch {
1163
1319
  }
1164
1320
  }
1165
1321
  }
1166
- const srcDir = join(rootDir, "src");
1167
- const dirsToScan = existsSync3(srcDir) ? [srcDir] : [];
1322
+ const srcDir = join2(rootDir, "src");
1323
+ const dirsToScan = existsSync4(srcDir) ? [srcDir] : [];
1168
1324
  for (const scanDir of dirsToScan) {
1169
1325
  try {
1170
- const entries = readdirSync(scanDir, { withFileTypes: true });
1326
+ const entries = readdirSync2(scanDir, { withFileTypes: true });
1171
1327
  for (const entry of entries) {
1172
1328
  if (entry.isFile() && CSS_EXTS.some((x) => entry.name.endsWith(x))) {
1173
- const filePath = join(scanDir, entry.name);
1329
+ const filePath = join2(scanDir, entry.name);
1174
1330
  const content = readSafe(filePath);
1175
1331
  if (content !== null && CSS_CUSTOM_PROPS_RE.test(content)) {
1176
1332
  sources.push({ kind: "css-custom-properties", path: filePath });
@@ -1180,12 +1336,12 @@ function detectTokenSources(rootDir) {
1180
1336
  } catch {
1181
1337
  }
1182
1338
  }
1183
- if (existsSync3(srcDir)) {
1339
+ if (existsSync4(srcDir)) {
1184
1340
  try {
1185
- const entries = readdirSync(srcDir);
1341
+ const entries = readdirSync2(srcDir);
1186
1342
  for (const entry of entries) {
1187
1343
  if (THEME_SUFFIXES.some((s) => entry.endsWith(s))) {
1188
- sources.push({ kind: "theme-file", path: join(srcDir, entry) });
1344
+ sources.push({ kind: "theme-file", path: join2(srcDir, entry) });
1189
1345
  }
1190
1346
  }
1191
1347
  } catch {
@@ -1194,7 +1350,7 @@ function detectTokenSources(rootDir) {
1194
1350
  return sources;
1195
1351
  }
1196
1352
  function detectProject(rootDir) {
1197
- const pkgPath = join(rootDir, "package.json");
1353
+ const pkgPath = join2(rootDir, "package.json");
1198
1354
  let packageDeps = {};
1199
1355
  const pkgContent = readSafe(pkgPath);
1200
1356
  if (pkgContent !== null) {
@@ -1225,7 +1381,8 @@ function detectProject(rootDir) {
1225
1381
  }
1226
1382
 
1227
1383
  // src/init/index.ts
1228
- import { Command as Command2 } from "commander";
1384
+ import { generateManifest as generateManifest2 } from "@agent-scope/manifest";
1385
+ import { Command as Command3 } from "commander";
1229
1386
  function buildDefaultConfig(detected, tokenFile, outputDir) {
1230
1387
  const include = detected.componentPatterns.length > 0 ? detected.componentPatterns : ["src/**/*.tsx"];
1231
1388
  return {
@@ -1263,9 +1420,9 @@ function createRL() {
1263
1420
  });
1264
1421
  }
1265
1422
  async function ask(rl, question) {
1266
- return new Promise((resolve18) => {
1423
+ return new Promise((resolve19) => {
1267
1424
  rl.question(question, (answer) => {
1268
- resolve18(answer.trim());
1425
+ resolve19(answer.trim());
1269
1426
  });
1270
1427
  });
1271
1428
  }
@@ -1274,9 +1431,9 @@ async function askWithDefault(rl, label, defaultValue) {
1274
1431
  return answer.length > 0 ? answer : defaultValue;
1275
1432
  }
1276
1433
  function ensureGitignoreEntry(rootDir, entry) {
1277
- const gitignorePath = join2(rootDir, ".gitignore");
1278
- if (existsSync4(gitignorePath)) {
1279
- const content = readFileSync4(gitignorePath, "utf-8");
1434
+ const gitignorePath = join3(rootDir, ".gitignore");
1435
+ if (existsSync5(gitignorePath)) {
1436
+ const content = readFileSync5(gitignorePath, "utf-8");
1280
1437
  const normalised = entry.replace(/\/$/, "");
1281
1438
  const lines = content.split("\n").map((l) => l.trim());
1282
1439
  if (lines.includes(entry) || lines.includes(normalised)) {
@@ -1305,7 +1462,7 @@ function extractTailwindTokens(tokenSources) {
1305
1462
  return result;
1306
1463
  };
1307
1464
  var parseBlock = parseBlock2;
1308
- const raw = readFileSync4(tailwindSource.path, "utf-8");
1465
+ const raw = readFileSync5(tailwindSource.path, "utf-8");
1309
1466
  const tokens = {};
1310
1467
  const colorsKeyIdx = raw.indexOf("colors:");
1311
1468
  if (colorsKeyIdx !== -1) {
@@ -1340,7 +1497,7 @@ function extractTailwindTokens(tokenSources) {
1340
1497
  }
1341
1498
  }
1342
1499
  if (Object.keys(colorTokens).length > 0) {
1343
- tokens["color"] = colorTokens;
1500
+ tokens.color = colorTokens;
1344
1501
  }
1345
1502
  }
1346
1503
  }
@@ -1353,7 +1510,7 @@ function extractTailwindTokens(tokenSources) {
1353
1510
  for (const [key, val] of Object.entries(spacingValues)) {
1354
1511
  spacingTokens[key] = { value: val, type: "dimension" };
1355
1512
  }
1356
- tokens["spacing"] = spacingTokens;
1513
+ tokens.spacing = spacingTokens;
1357
1514
  }
1358
1515
  }
1359
1516
  const fontFamilyMatch = raw.match(/fontFamily\s*:\s*\{([\s\S]*?)\n\s*\}/);
@@ -1366,7 +1523,7 @@ function extractTailwindTokens(tokenSources) {
1366
1523
  }
1367
1524
  }
1368
1525
  if (Object.keys(fontTokens).length > 0) {
1369
- tokens["font"] = fontTokens;
1526
+ tokens.font = fontTokens;
1370
1527
  }
1371
1528
  }
1372
1529
  const borderRadiusMatch = raw.match(/borderRadius\s*:\s*\{([\s\S]*?)\n\s*\}/);
@@ -1377,7 +1534,7 @@ function extractTailwindTokens(tokenSources) {
1377
1534
  for (const [key, val] of Object.entries(radiusValues)) {
1378
1535
  radiusTokens[key] = { value: val, type: "dimension" };
1379
1536
  }
1380
- tokens["radius"] = radiusTokens;
1537
+ tokens.radius = radiusTokens;
1381
1538
  }
1382
1539
  }
1383
1540
  return Object.keys(tokens).length > 0 ? tokens : null;
@@ -1386,14 +1543,14 @@ function extractTailwindTokens(tokenSources) {
1386
1543
  }
1387
1544
  }
1388
1545
  function scaffoldConfig(rootDir, config) {
1389
- const path = join2(rootDir, "reactscope.config.json");
1546
+ const path = join3(rootDir, "reactscope.config.json");
1390
1547
  writeFileSync3(path, `${JSON.stringify(config, null, 2)}
1391
1548
  `);
1392
1549
  return path;
1393
1550
  }
1394
1551
  function scaffoldTokenFile(rootDir, tokenFile, extractedTokens) {
1395
- const path = join2(rootDir, tokenFile);
1396
- if (!existsSync4(path)) {
1552
+ const path = join3(rootDir, tokenFile);
1553
+ if (!existsSync5(path)) {
1397
1554
  const stub = {
1398
1555
  $schema: "https://raw.githubusercontent.com/FlatFilers/Scope/main/packages/tokens/schema.json",
1399
1556
  version: "1.0.0",
@@ -1409,19 +1566,19 @@ function scaffoldTokenFile(rootDir, tokenFile, extractedTokens) {
1409
1566
  return path;
1410
1567
  }
1411
1568
  function scaffoldOutputDir(rootDir, outputDir) {
1412
- const dirPath = join2(rootDir, outputDir);
1569
+ const dirPath = join3(rootDir, outputDir);
1413
1570
  mkdirSync(dirPath, { recursive: true });
1414
- const keepPath = join2(dirPath, ".gitkeep");
1415
- if (!existsSync4(keepPath)) {
1571
+ const keepPath = join3(dirPath, ".gitkeep");
1572
+ if (!existsSync5(keepPath)) {
1416
1573
  writeFileSync3(keepPath, "");
1417
1574
  }
1418
1575
  return dirPath;
1419
1576
  }
1420
1577
  async function runInit(options) {
1421
1578
  const rootDir = options.cwd ?? process.cwd();
1422
- const configPath = join2(rootDir, "reactscope.config.json");
1579
+ const configPath = join3(rootDir, "reactscope.config.json");
1423
1580
  const created = [];
1424
- if (existsSync4(configPath) && !options.force) {
1581
+ if (existsSync5(configPath) && !options.force) {
1425
1582
  const msg = "reactscope.config.json already exists. Run with --force to overwrite.";
1426
1583
  process.stderr.write(`\u26A0\uFE0F ${msg}
1427
1584
  `);
@@ -1496,7 +1653,28 @@ async function runInit(options) {
1496
1653
  process.stdout.write(` ${p}
1497
1654
  `);
1498
1655
  }
1499
- process.stdout.write("\n Next steps: run `scope manifest` to scan your components.\n\n");
1656
+ process.stdout.write("\n Scanning components...\n");
1657
+ try {
1658
+ const manifestConfig = {
1659
+ include: config.components.include,
1660
+ rootDir
1661
+ };
1662
+ const manifest = await generateManifest2(manifestConfig);
1663
+ const manifestCount = Object.keys(manifest.components).length;
1664
+ const manifestOutPath = join3(rootDir, config.output.dir, "manifest.json");
1665
+ mkdirSync(join3(rootDir, config.output.dir), { recursive: true });
1666
+ writeFileSync3(manifestOutPath, `${JSON.stringify(manifest, null, 2)}
1667
+ `);
1668
+ process.stdout.write(
1669
+ ` Found ${manifestCount} component(s) \u2014 manifest written to ${manifestOutPath}
1670
+ `
1671
+ );
1672
+ } catch {
1673
+ process.stdout.write(
1674
+ " (manifest generate skipped \u2014 run `scope manifest generate` manually)\n"
1675
+ );
1676
+ }
1677
+ process.stdout.write("\n");
1500
1678
  return {
1501
1679
  success: true,
1502
1680
  message: "Project initialised successfully.",
@@ -1505,7 +1683,7 @@ async function runInit(options) {
1505
1683
  };
1506
1684
  }
1507
1685
  function createInitCommand() {
1508
- 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) => {
1686
+ return new Command3("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) => {
1509
1687
  try {
1510
1688
  const result = await runInit({ yes: opts.yes, force: opts.force });
1511
1689
  if (!result.success && !result.skipped) {
@@ -1520,24 +1698,24 @@ function createInitCommand() {
1520
1698
  }
1521
1699
 
1522
1700
  // src/instrument/renders.ts
1523
- import { resolve as resolve7 } from "path";
1701
+ import { resolve as resolve8 } from "path";
1524
1702
  import { getBrowserEntryScript as getBrowserEntryScript5 } from "@agent-scope/playwright";
1525
1703
  import { BrowserPool as BrowserPool2 } from "@agent-scope/render";
1526
- import { Command as Command5 } from "commander";
1704
+ import { Command as Command6 } from "commander";
1527
1705
 
1528
1706
  // src/manifest-commands.ts
1529
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1530
- import { resolve as resolve3 } from "path";
1531
- import { generateManifest as generateManifest2 } from "@agent-scope/manifest";
1532
- import { Command as Command3 } from "commander";
1707
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1708
+ import { resolve as resolve4 } from "path";
1709
+ import { generateManifest as generateManifest3 } from "@agent-scope/manifest";
1710
+ import { Command as Command4 } from "commander";
1533
1711
  var MANIFEST_PATH = ".reactscope/manifest.json";
1534
1712
  function loadManifest(manifestPath = MANIFEST_PATH) {
1535
- const absPath = resolve3(process.cwd(), manifestPath);
1536
- if (!existsSync5(absPath)) {
1713
+ const absPath = resolve4(process.cwd(), manifestPath);
1714
+ if (!existsSync6(absPath)) {
1537
1715
  throw new Error(`Manifest not found at ${absPath}.
1538
1716
  Run \`scope manifest generate\` first.`);
1539
1717
  }
1540
- const raw = readFileSync5(absPath, "utf-8");
1718
+ const raw = readFileSync6(absPath, "utf-8");
1541
1719
  return JSON.parse(raw);
1542
1720
  }
1543
1721
  function resolveFormat(formatFlag) {
@@ -1686,13 +1864,13 @@ function registerGenerate(manifestCmd) {
1686
1864
  "Generate the component manifest from source and write to .reactscope/manifest.json"
1687
1865
  ).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) => {
1688
1866
  try {
1689
- const rootDir = resolve3(process.cwd(), opts.root ?? ".");
1690
- const outputPath = resolve3(process.cwd(), opts.output);
1867
+ const rootDir = resolve4(process.cwd(), opts.root ?? ".");
1868
+ const outputPath = resolve4(process.cwd(), opts.output);
1691
1869
  const include = opts.include?.split(",").map((s) => s.trim());
1692
1870
  const exclude = opts.exclude?.split(",").map((s) => s.trim());
1693
1871
  process.stderr.write(`Scanning ${rootDir} for React components...
1694
1872
  `);
1695
- const manifest = await generateManifest2({
1873
+ const manifest = await generateManifest3({
1696
1874
  rootDir,
1697
1875
  ...include !== void 0 && { include },
1698
1876
  ...exclude !== void 0 && { exclude }
@@ -1701,7 +1879,7 @@ function registerGenerate(manifestCmd) {
1701
1879
  process.stderr.write(`Found ${componentCount} components.
1702
1880
  `);
1703
1881
  const outputDir = outputPath.replace(/\/[^/]+$/, "");
1704
- if (!existsSync5(outputDir)) {
1882
+ if (!existsSync6(outputDir)) {
1705
1883
  mkdirSync2(outputDir, { recursive: true });
1706
1884
  }
1707
1885
  writeFileSync4(outputPath, JSON.stringify(manifest, null, 2), "utf-8");
@@ -1717,7 +1895,7 @@ function registerGenerate(manifestCmd) {
1717
1895
  });
1718
1896
  }
1719
1897
  function createManifestCommand() {
1720
- const manifestCmd = new Command3("manifest").description(
1898
+ const manifestCmd = new Command4("manifest").description(
1721
1899
  "Query and explore the component manifest"
1722
1900
  );
1723
1901
  registerList(manifestCmd);
@@ -1728,7 +1906,7 @@ function createManifestCommand() {
1728
1906
  }
1729
1907
 
1730
1908
  // src/instrument/hooks.ts
1731
- import { resolve as resolve4 } from "path";
1909
+ import { resolve as resolve5 } from "path";
1732
1910
  import { getBrowserEntryScript as getBrowserEntryScript2 } from "@agent-scope/playwright";
1733
1911
  import { Command as Cmd } from "commander";
1734
1912
  import { chromium as chromium2 } from "playwright";
@@ -2060,7 +2238,7 @@ Available: ${available}`
2060
2238
  throw new Error(`Invalid props JSON: ${opts.props}`);
2061
2239
  }
2062
2240
  const rootDir = process.cwd();
2063
- const filePath = resolve4(rootDir, descriptor.filePath);
2241
+ const filePath = resolve5(rootDir, descriptor.filePath);
2064
2242
  process.stderr.write(`Instrumenting hooks for ${componentName}\u2026
2065
2243
  `);
2066
2244
  const result = await runHooksProfiling(componentName, filePath, props);
@@ -2088,7 +2266,7 @@ Available: ${available}`
2088
2266
  }
2089
2267
 
2090
2268
  // src/instrument/profile.ts
2091
- import { resolve as resolve5 } from "path";
2269
+ import { resolve as resolve6 } from "path";
2092
2270
  import { getBrowserEntryScript as getBrowserEntryScript3 } from "@agent-scope/playwright";
2093
2271
  import { Command as Cmd2 } from "commander";
2094
2272
  import { chromium as chromium3 } from "playwright";
@@ -2363,7 +2541,7 @@ Available: ${available}`
2363
2541
  throw new Error(`Invalid interaction JSON: ${opts.interaction}`);
2364
2542
  }
2365
2543
  const rootDir = process.cwd();
2366
- const filePath = resolve5(rootDir, descriptor.filePath);
2544
+ const filePath = resolve6(rootDir, descriptor.filePath);
2367
2545
  process.stderr.write(`Profiling interaction for ${componentName}\u2026
2368
2546
  `);
2369
2547
  const result = await runInteractionProfile(componentName, filePath, props, interaction);
@@ -2391,9 +2569,9 @@ Available: ${available}`
2391
2569
  }
2392
2570
 
2393
2571
  // src/instrument/tree.ts
2394
- import { resolve as resolve6 } from "path";
2572
+ import { resolve as resolve7 } from "path";
2395
2573
  import { getBrowserEntryScript as getBrowserEntryScript4 } from "@agent-scope/playwright";
2396
- import { Command as Command4 } from "commander";
2574
+ import { Command as Command5 } from "commander";
2397
2575
  import { chromium as chromium4 } from "playwright";
2398
2576
  var MANIFEST_PATH4 = ".reactscope/manifest.json";
2399
2577
  var DEFAULT_VIEWPORT_WIDTH = 375;
@@ -2674,7 +2852,7 @@ async function runInstrumentTree(options) {
2674
2852
  }
2675
2853
  }
2676
2854
  function createInstrumentTreeCommand() {
2677
- 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(
2855
+ return new Command5("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(
2678
2856
  "--wasted-renders",
2679
2857
  "Filter to components with wasted renders (no prop/state/context changes, not memoized)",
2680
2858
  false
@@ -2698,7 +2876,7 @@ Available: ${available}`
2698
2876
  }
2699
2877
  }
2700
2878
  const rootDir = process.cwd();
2701
- const filePath = resolve6(rootDir, descriptor.filePath);
2879
+ const filePath = resolve7(rootDir, descriptor.filePath);
2702
2880
  process.stderr.write(`Instrumenting ${componentName}\u2026
2703
2881
  `);
2704
2882
  const instrumentRoot = await runInstrumentTree({
@@ -3069,7 +3247,7 @@ Available: ${available}`
3069
3247
  );
3070
3248
  }
3071
3249
  const rootDir = process.cwd();
3072
- const filePath = resolve7(rootDir, descriptor.filePath);
3250
+ const filePath = resolve8(rootDir, descriptor.filePath);
3073
3251
  const preScript = getBrowserEntryScript5() + "\n" + buildInstrumentationScript();
3074
3252
  const htmlHarness = await buildComponentHarness(
3075
3253
  filePath,
@@ -3159,7 +3337,7 @@ function formatRendersTable(result) {
3159
3337
  return lines.join("\n");
3160
3338
  }
3161
3339
  function createInstrumentRendersCommand() {
3162
- 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(
3340
+ return new Command6("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(
3163
3341
  "--interaction <json>",
3164
3342
  `Interaction sequence JSON, e.g. '[{"action":"click","target":"button"}]'`,
3165
3343
  "[]"
@@ -3204,7 +3382,7 @@ function createInstrumentRendersCommand() {
3204
3382
  );
3205
3383
  }
3206
3384
  function createInstrumentCommand() {
3207
- const instrumentCmd = new Command5("instrument").description(
3385
+ const instrumentCmd = new Command6("instrument").description(
3208
3386
  "Structured instrumentation commands for React component analysis"
3209
3387
  );
3210
3388
  instrumentCmd.addCommand(createInstrumentRendersCommand());
@@ -3215,8 +3393,8 @@ function createInstrumentCommand() {
3215
3393
  }
3216
3394
 
3217
3395
  // src/render-commands.ts
3218
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3219
- import { resolve as resolve9 } from "path";
3396
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3397
+ import { resolve as resolve10 } from "path";
3220
3398
  import {
3221
3399
  ALL_CONTEXT_IDS,
3222
3400
  ALL_STRESS_IDS,
@@ -3227,13 +3405,13 @@ import {
3227
3405
  safeRender as safeRender2,
3228
3406
  stressAxis
3229
3407
  } from "@agent-scope/render";
3230
- import { Command as Command6 } from "commander";
3408
+ import { Command as Command7 } from "commander";
3231
3409
 
3232
3410
  // src/scope-file.ts
3233
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync } from "fs";
3411
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync } from "fs";
3234
3412
  import { createRequire as createRequire2 } from "module";
3235
3413
  import { tmpdir } from "os";
3236
- import { dirname as dirname2, join as join3, resolve as resolve8 } from "path";
3414
+ import { dirname as dirname2, join as join4, resolve as resolve9 } from "path";
3237
3415
  import * as esbuild2 from "esbuild";
3238
3416
  var SCOPE_EXTENSIONS = [".scope.tsx", ".scope.ts", ".scope.jsx", ".scope.js"];
3239
3417
  function findScopeFile(componentFilePath) {
@@ -3241,15 +3419,15 @@ function findScopeFile(componentFilePath) {
3241
3419
  const stem = componentFilePath.replace(/\.(tsx?|jsx?)$/, "");
3242
3420
  const baseName = stem.slice(dir.length + 1);
3243
3421
  for (const ext of SCOPE_EXTENSIONS) {
3244
- const candidate = join3(dir, `${baseName}${ext}`);
3245
- if (existsSync6(candidate)) return candidate;
3422
+ const candidate = join4(dir, `${baseName}${ext}`);
3423
+ if (existsSync7(candidate)) return candidate;
3246
3424
  }
3247
3425
  return null;
3248
3426
  }
3249
3427
  async function loadScopeFile(scopeFilePath) {
3250
- const tmpDir = join3(tmpdir(), `scope-file-${Date.now()}-${Math.random().toString(36).slice(2)}`);
3428
+ const tmpDir = join4(tmpdir(), `scope-file-${Date.now()}-${Math.random().toString(36).slice(2)}`);
3251
3429
  mkdirSync3(tmpDir, { recursive: true });
3252
- const outFile = join3(tmpDir, "scope-file.cjs");
3430
+ const outFile = join4(tmpDir, "scope-file.cjs");
3253
3431
  try {
3254
3432
  const result = await esbuild2.build({
3255
3433
  entryPoints: [scopeFilePath],
@@ -3274,7 +3452,7 @@ async function loadScopeFile(scopeFilePath) {
3274
3452
  ${msg}`);
3275
3453
  }
3276
3454
  const req = createRequire2(import.meta.url);
3277
- delete req.cache[resolve8(outFile)];
3455
+ delete req.cache[resolve9(outFile)];
3278
3456
  const mod = req(outFile);
3279
3457
  const scenarios = extractScenarios(mod, scopeFilePath);
3280
3458
  const hasWrapper = typeof mod.wrapper === "function" || typeof mod.default?.wrapper === "function";
@@ -3352,10 +3530,10 @@ ${msg}`);
3352
3530
 
3353
3531
  // src/render-commands.ts
3354
3532
  function loadGlobalCssFilesFromConfig(cwd) {
3355
- const configPath = resolve9(cwd, "reactscope.config.json");
3356
- if (!existsSync7(configPath)) return [];
3533
+ const configPath = resolve10(cwd, "reactscope.config.json");
3534
+ if (!existsSync8(configPath)) return [];
3357
3535
  try {
3358
- const raw = readFileSync6(configPath, "utf-8");
3536
+ const raw = readFileSync7(configPath, "utf-8");
3359
3537
  const cfg = JSON.parse(raw);
3360
3538
  return cfg.components?.wrappers?.globalCSS ?? [];
3361
3539
  } catch {
@@ -3426,7 +3604,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, g
3426
3604
  }
3427
3605
  });
3428
3606
  return [...set];
3429
- });
3607
+ }) ?? [];
3430
3608
  const projectCss2 = await getCompiledCssForClasses(rootDir, classes);
3431
3609
  if (projectCss2 != null && projectCss2.length > 0) {
3432
3610
  await page.addStyleTag({ content: projectCss2 });
@@ -3439,49 +3617,147 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, g
3439
3617
  `Component "${componentName}" rendered with zero bounding box \u2014 it may be invisible or not mounted`
3440
3618
  );
3441
3619
  }
3442
- const PAD = 24;
3443
- const MIN_W = 320;
3444
- const MIN_H = 200;
3620
+ const PAD = 8;
3445
3621
  const clipX = Math.max(0, boundingBox.x - PAD);
3446
3622
  const clipY = Math.max(0, boundingBox.y - PAD);
3447
3623
  const rawW = boundingBox.width + PAD * 2;
3448
3624
  const rawH = boundingBox.height + PAD * 2;
3449
- const clipW = Math.max(rawW, MIN_W);
3450
- const clipH = Math.max(rawH, MIN_H);
3451
- const safeW = Math.min(clipW, viewportWidth - clipX);
3452
- const safeH = Math.min(clipH, viewportHeight - clipY);
3625
+ const safeW = Math.min(rawW, viewportWidth - clipX);
3626
+ const safeH = Math.min(rawH, viewportHeight - clipY);
3453
3627
  const screenshot = await page.screenshot({
3454
3628
  clip: { x: clipX, y: clipY, width: safeW, height: safeH },
3455
3629
  type: "png"
3456
3630
  });
3631
+ const STYLE_PROPS = [
3632
+ "display",
3633
+ "width",
3634
+ "height",
3635
+ "color",
3636
+ "backgroundColor",
3637
+ "fontSize",
3638
+ "fontFamily",
3639
+ "fontWeight",
3640
+ "lineHeight",
3641
+ "padding",
3642
+ "paddingTop",
3643
+ "paddingRight",
3644
+ "paddingBottom",
3645
+ "paddingLeft",
3646
+ "margin",
3647
+ "marginTop",
3648
+ "marginRight",
3649
+ "marginBottom",
3650
+ "marginLeft",
3651
+ "gap",
3652
+ "borderRadius",
3653
+ "borderWidth",
3654
+ "borderColor",
3655
+ "borderStyle",
3656
+ "boxShadow",
3657
+ "opacity",
3658
+ "position",
3659
+ "flexDirection",
3660
+ "alignItems",
3661
+ "justifyContent",
3662
+ "overflow"
3663
+ ];
3664
+ const _domResult = await page.evaluate(
3665
+ (args) => {
3666
+ let count = 0;
3667
+ const styles = {};
3668
+ function captureStyles(el, id, propList) {
3669
+ const computed = window.getComputedStyle(el);
3670
+ const out = {};
3671
+ for (const prop of propList) {
3672
+ const val = computed[prop] ?? "";
3673
+ if (val && val !== "none" && val !== "normal" && val !== "auto") out[prop] = val;
3674
+ }
3675
+ styles[id] = out;
3676
+ }
3677
+ function walk(node) {
3678
+ if (node.nodeType === Node.TEXT_NODE) {
3679
+ return {
3680
+ tag: "#text",
3681
+ attrs: {},
3682
+ text: node.textContent?.trim() ?? "",
3683
+ children: []
3684
+ };
3685
+ }
3686
+ const el = node;
3687
+ const id = count++;
3688
+ captureStyles(el, id, args.props);
3689
+ const attrs = {};
3690
+ for (const attr of Array.from(el.attributes)) {
3691
+ attrs[attr.name] = attr.value;
3692
+ }
3693
+ const children = Array.from(el.childNodes).filter(
3694
+ (n) => n.nodeType === Node.ELEMENT_NODE || n.nodeType === Node.TEXT_NODE && (n.textContent?.trim() ?? "").length > 0
3695
+ ).map(walk);
3696
+ return { tag: el.tagName.toLowerCase(), attrs, nodeId: id, children };
3697
+ }
3698
+ const root = document.querySelector(args.sel);
3699
+ if (!root)
3700
+ return {
3701
+ tree: { tag: "div", attrs: {}, children: [] },
3702
+ elementCount: 0,
3703
+ nodeStyles: {}
3704
+ };
3705
+ return { tree: walk(root), elementCount: count, nodeStyles: styles };
3706
+ },
3707
+ { sel: "[data-reactscope-root] > *", props: STYLE_PROPS }
3708
+ );
3709
+ const domTree = _domResult?.tree ?? { tag: "div", attrs: {}, children: [] };
3710
+ const elementCount = _domResult?.elementCount ?? 0;
3711
+ const nodeStyles = _domResult?.nodeStyles ?? {};
3457
3712
  const computedStyles = {};
3458
- const styles = await page.evaluate((sel) => {
3459
- const el = document.querySelector(sel);
3460
- if (el === null) return {};
3461
- const computed = window.getComputedStyle(el);
3462
- const out = {};
3463
- for (const prop of [
3464
- "display",
3465
- "width",
3466
- "height",
3467
- "color",
3468
- "backgroundColor",
3469
- "fontSize",
3470
- "fontFamily",
3471
- "padding",
3472
- "margin"
3473
- ]) {
3474
- out[prop] = computed.getPropertyValue(prop);
3713
+ if (nodeStyles[0]) computedStyles["[data-reactscope-root] > *"] = nodeStyles[0];
3714
+ for (const [nodeId, styles] of Object.entries(nodeStyles)) {
3715
+ computedStyles[`#node-${nodeId}`] = styles;
3716
+ }
3717
+ const dom = {
3718
+ tree: domTree,
3719
+ elementCount,
3720
+ boundingBox: {
3721
+ x: boundingBox.x,
3722
+ y: boundingBox.y,
3723
+ width: boundingBox.width,
3724
+ height: boundingBox.height
3475
3725
  }
3476
- return out;
3477
- }, "[data-reactscope-root] > *");
3478
- computedStyles["[data-reactscope-root] > *"] = styles;
3726
+ };
3727
+ const a11yInfo = await page.evaluate((sel) => {
3728
+ const wrapper = document.querySelector(sel);
3729
+ const el = wrapper?.firstElementChild ?? wrapper;
3730
+ if (!el) return { role: "generic", name: "" };
3731
+ return {
3732
+ role: el.getAttribute("role") ?? el.tagName.toLowerCase() ?? "generic",
3733
+ name: el.getAttribute("aria-label") ?? el.getAttribute("aria-labelledby") ?? el.textContent?.trim().slice(0, 100) ?? ""
3734
+ };
3735
+ }, "[data-reactscope-root]") ?? {
3736
+ role: "generic",
3737
+ name: ""
3738
+ };
3739
+ const imgViolations = await page.evaluate((sel) => {
3740
+ const container = document.querySelector(sel);
3741
+ if (!container) return [];
3742
+ const issues = [];
3743
+ container.querySelectorAll("img").forEach((img) => {
3744
+ if (!img.alt) issues.push("Image missing accessible name");
3745
+ });
3746
+ return issues;
3747
+ }, "[data-reactscope-root]") ?? [];
3748
+ const accessibility = {
3749
+ role: a11yInfo.role,
3750
+ name: a11yInfo.name,
3751
+ violations: imgViolations
3752
+ };
3479
3753
  return {
3480
3754
  screenshot,
3481
3755
  width: Math.round(safeW),
3482
3756
  height: Math.round(safeH),
3483
3757
  renderTimeMs,
3484
- computedStyles
3758
+ computedStyles,
3759
+ dom,
3760
+ accessibility
3485
3761
  };
3486
3762
  } finally {
3487
3763
  pool.release(slot);
@@ -3557,11 +3833,16 @@ Available: ${available}`
3557
3833
  }
3558
3834
  const { width, height } = parseViewport(opts.viewport);
3559
3835
  const rootDir = process.cwd();
3560
- const filePath = resolve9(rootDir, descriptor.filePath);
3836
+ const filePath = resolve10(rootDir, descriptor.filePath);
3561
3837
  const scopeData = await loadScopeFileForComponent(filePath);
3562
3838
  const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
3563
3839
  const scenarios = buildScenarioMap(opts, scopeData);
3564
3840
  const globalCssFiles = loadGlobalCssFilesFromConfig(rootDir);
3841
+ if (globalCssFiles.length === 0) {
3842
+ process.stderr.write(
3843
+ "warning: No globalCSS files configured. Tailwind/CSS styles will not be applied to renders.\n Add `components.wrappers.globalCSS` to reactscope.config.json\n"
3844
+ );
3845
+ }
3565
3846
  const renderer = buildRenderer(
3566
3847
  filePath,
3567
3848
  componentName,
@@ -3605,7 +3886,7 @@ Available: ${available}`
3605
3886
  const result = outcome.result;
3606
3887
  const outFileName = isNamed ? `${componentName}-${scenarioName}.png` : `${componentName}.png`;
3607
3888
  if (opts.output !== void 0 && !isNamed) {
3608
- const outPath = resolve9(process.cwd(), opts.output);
3889
+ const outPath = resolve10(process.cwd(), opts.output);
3609
3890
  writeFileSync5(outPath, result.screenshot);
3610
3891
  process.stdout.write(
3611
3892
  `\u2713 ${label} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
@@ -3616,9 +3897,9 @@ Available: ${available}`
3616
3897
  process.stdout.write(`${JSON.stringify(json, null, 2)}
3617
3898
  `);
3618
3899
  } else {
3619
- const dir = resolve9(process.cwd(), DEFAULT_OUTPUT_DIR);
3900
+ const dir = resolve10(process.cwd(), DEFAULT_OUTPUT_DIR);
3620
3901
  mkdirSync4(dir, { recursive: true });
3621
- const outPath = resolve9(dir, outFileName);
3902
+ const outPath = resolve10(dir, outFileName);
3622
3903
  writeFileSync5(outPath, result.screenshot);
3623
3904
  const relPath = `${DEFAULT_OUTPUT_DIR}/${outFileName}`;
3624
3905
  process.stdout.write(
@@ -3660,7 +3941,7 @@ Available: ${available}`
3660
3941
  const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 8);
3661
3942
  const { width, height } = { width: 375, height: 812 };
3662
3943
  const rootDir = process.cwd();
3663
- const filePath = resolve9(rootDir, descriptor.filePath);
3944
+ const filePath = resolve10(rootDir, descriptor.filePath);
3664
3945
  const matrixCssFiles = loadGlobalCssFilesFromConfig(rootDir);
3665
3946
  const renderer = buildRenderer(
3666
3947
  filePath,
@@ -3753,7 +4034,7 @@ Available: ${available}`
3753
4034
  const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
3754
4035
  const gen = new SpriteSheetGenerator2();
3755
4036
  const sheet = await gen.generate(result);
3756
- const spritePath = resolve9(process.cwd(), opts.sprite);
4037
+ const spritePath = resolve10(process.cwd(), opts.sprite);
3757
4038
  writeFileSync5(spritePath, sheet.png);
3758
4039
  process.stderr.write(`Sprite sheet saved to ${spritePath}
3759
4040
  `);
@@ -3763,9 +4044,9 @@ Available: ${available}`
3763
4044
  const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
3764
4045
  const gen = new SpriteSheetGenerator2();
3765
4046
  const sheet = await gen.generate(result);
3766
- const dir = resolve9(process.cwd(), DEFAULT_OUTPUT_DIR);
4047
+ const dir = resolve10(process.cwd(), DEFAULT_OUTPUT_DIR);
3767
4048
  mkdirSync4(dir, { recursive: true });
3768
- const outPath = resolve9(dir, `${componentName}-matrix.png`);
4049
+ const outPath = resolve10(dir, `${componentName}-matrix.png`);
3769
4050
  writeFileSync5(outPath, sheet.png);
3770
4051
  const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}-matrix.png`;
3771
4052
  process.stdout.write(
@@ -3809,23 +4090,37 @@ function registerRenderAll(renderCmd) {
3809
4090
  return;
3810
4091
  }
3811
4092
  const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 4);
3812
- const outputDir = resolve9(process.cwd(), opts.outputDir);
4093
+ const outputDir = resolve10(process.cwd(), opts.outputDir);
3813
4094
  mkdirSync4(outputDir, { recursive: true });
3814
4095
  const rootDir = process.cwd();
3815
4096
  process.stderr.write(`Rendering ${total} components (concurrency: ${concurrency})\u2026
3816
4097
  `);
3817
4098
  const results = [];
4099
+ const complianceStylesMap = {};
3818
4100
  let completed = 0;
3819
4101
  const renderOne = async (name) => {
3820
4102
  const descriptor = manifest.components[name];
3821
4103
  if (descriptor === void 0) return;
3822
- const filePath = resolve9(rootDir, descriptor.filePath);
4104
+ const filePath = resolve10(rootDir, descriptor.filePath);
3823
4105
  const allCssFiles = loadGlobalCssFilesFromConfig(process.cwd());
3824
- const renderer = buildRenderer(filePath, name, 375, 812, allCssFiles, process.cwd());
4106
+ const scopeData = await loadScopeFileForComponent(filePath);
4107
+ const scenarioEntries = scopeData !== null ? Object.entries(scopeData.scenarios) : [];
4108
+ const defaultEntry = scenarioEntries.find(([k]) => k === "default") ?? scenarioEntries[0];
4109
+ const renderProps = defaultEntry !== void 0 ? defaultEntry[1] : {};
4110
+ const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
4111
+ const renderer = buildRenderer(
4112
+ filePath,
4113
+ name,
4114
+ 375,
4115
+ 812,
4116
+ allCssFiles,
4117
+ process.cwd(),
4118
+ wrapperScript
4119
+ );
3825
4120
  const outcome = await safeRender2(
3826
- () => renderer.renderCell({}, descriptor.complexityClass),
4121
+ () => renderer.renderCell(renderProps, descriptor.complexityClass),
3827
4122
  {
3828
- props: {},
4123
+ props: renderProps,
3829
4124
  sourceLocation: {
3830
4125
  file: descriptor.filePath,
3831
4126
  line: descriptor.loc.start,
@@ -3843,7 +4138,7 @@ function registerRenderAll(renderCmd) {
3843
4138
  success: false,
3844
4139
  errorMessage: outcome.error.message
3845
4140
  });
3846
- const errPath = resolve9(outputDir, `${name}.error.json`);
4141
+ const errPath = resolve10(outputDir, `${name}.error.json`);
3847
4142
  writeFileSync5(
3848
4143
  errPath,
3849
4144
  JSON.stringify(
@@ -3861,10 +4156,81 @@ function registerRenderAll(renderCmd) {
3861
4156
  }
3862
4157
  const result = outcome.result;
3863
4158
  results.push({ name, renderTimeMs: result.renderTimeMs, success: true });
3864
- const pngPath = resolve9(outputDir, `${name}.png`);
4159
+ const pngPath = resolve10(outputDir, `${name}.png`);
3865
4160
  writeFileSync5(pngPath, result.screenshot);
3866
- const jsonPath = resolve9(outputDir, `${name}.json`);
4161
+ const jsonPath = resolve10(outputDir, `${name}.json`);
3867
4162
  writeFileSync5(jsonPath, JSON.stringify(formatRenderJson(name, {}, result), null, 2));
4163
+ const rawStyles = result.computedStyles["[data-reactscope-root] > *"] ?? {};
4164
+ const compStyles = {
4165
+ colors: {},
4166
+ spacing: {},
4167
+ typography: {},
4168
+ borders: {},
4169
+ shadows: {}
4170
+ };
4171
+ for (const [prop, val] of Object.entries(rawStyles)) {
4172
+ if (!val || val === "none" || val === "") continue;
4173
+ const lower = prop.toLowerCase();
4174
+ if (lower.includes("color") || lower.includes("background")) {
4175
+ compStyles.colors[prop] = val;
4176
+ } else if (lower.includes("padding") || lower.includes("margin") || lower.includes("gap") || lower.includes("width") || lower.includes("height")) {
4177
+ compStyles.spacing[prop] = val;
4178
+ } else if (lower.includes("font") || lower.includes("lineheight") || lower.includes("letterspacing") || lower.includes("texttransform")) {
4179
+ compStyles.typography[prop] = val;
4180
+ } else if (lower.includes("border") || lower.includes("radius") || lower.includes("outline")) {
4181
+ compStyles.borders[prop] = val;
4182
+ } else if (lower.includes("shadow")) {
4183
+ compStyles.shadows[prop] = val;
4184
+ }
4185
+ }
4186
+ complianceStylesMap[name] = compStyles;
4187
+ if (scopeData !== null && Object.keys(scopeData.scenarios).length >= 2) {
4188
+ try {
4189
+ const scenarioEntries2 = Object.entries(scopeData.scenarios);
4190
+ const scenarioAxis = {
4191
+ name: "scenario",
4192
+ values: scenarioEntries2.map(([k]) => k)
4193
+ };
4194
+ const scenarioPropsMap = Object.fromEntries(scenarioEntries2);
4195
+ const matrixRenderer = buildRenderer(
4196
+ filePath,
4197
+ name,
4198
+ 375,
4199
+ 812,
4200
+ allCssFiles,
4201
+ process.cwd(),
4202
+ wrapperScript
4203
+ );
4204
+ const wrappedRenderer = {
4205
+ _satori: matrixRenderer._satori,
4206
+ async renderCell(props, cc) {
4207
+ const scenarioName = props.scenario;
4208
+ const realProps = scenarioName !== void 0 ? scenarioPropsMap[scenarioName] ?? props : props;
4209
+ return matrixRenderer.renderCell(realProps, cc ?? "simple");
4210
+ }
4211
+ };
4212
+ const matrix = new RenderMatrix(wrappedRenderer, [scenarioAxis], {
4213
+ concurrency: 2
4214
+ });
4215
+ const matrixResult = await matrix.render();
4216
+ const matrixCells = matrixResult.cells.map((cell) => ({
4217
+ axisValues: [scenarioEntries2[cell.axisIndices[0] ?? 0]?.[0] ?? ""],
4218
+ screenshot: cell.result.screenshot.toString("base64"),
4219
+ width: cell.result.width,
4220
+ height: cell.result.height,
4221
+ renderTimeMs: cell.result.renderTimeMs
4222
+ }));
4223
+ const existingJson = JSON.parse(readFileSync7(jsonPath, "utf-8"));
4224
+ existingJson.cells = matrixCells;
4225
+ existingJson.axisLabels = [scenarioAxis.values];
4226
+ writeFileSync5(jsonPath, JSON.stringify(existingJson, null, 2));
4227
+ } catch (matrixErr) {
4228
+ process.stderr.write(
4229
+ ` [warn] Matrix render for ${name} failed: ${matrixErr instanceof Error ? matrixErr.message : String(matrixErr)}
4230
+ `
4231
+ );
4232
+ }
4233
+ }
3868
4234
  if (isTTY()) {
3869
4235
  process.stdout.write(
3870
4236
  `\u2713 ${name} \u2192 ${opts.outputDir}/${name}.png (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
@@ -3888,6 +4254,14 @@ function registerRenderAll(renderCmd) {
3888
4254
  }
3889
4255
  await Promise.all(workers);
3890
4256
  await shutdownPool3();
4257
+ const compStylesPath = resolve10(
4258
+ resolve10(process.cwd(), opts.outputDir),
4259
+ "..",
4260
+ "compliance-styles.json"
4261
+ );
4262
+ writeFileSync5(compStylesPath, JSON.stringify(complianceStylesMap, null, 2));
4263
+ process.stderr.write(`[scope/render] \u2713 Wrote compliance-styles.json
4264
+ `);
3891
4265
  process.stderr.write("\n");
3892
4266
  const summary = formatSummaryText(results, outputDir);
3893
4267
  process.stderr.write(`${summary}
@@ -3926,7 +4300,7 @@ function resolveMatrixFormat(formatFlag, spriteAlreadyWritten) {
3926
4300
  return "json";
3927
4301
  }
3928
4302
  function createRenderCommand() {
3929
- const renderCmd = new Command6("render").description(
4303
+ const renderCmd = new Command7("render").description(
3930
4304
  "Render components to PNG or JSON via esbuild + BrowserPool"
3931
4305
  );
3932
4306
  registerRenderSingle(renderCmd);
@@ -3936,9 +4310,9 @@ function createRenderCommand() {
3936
4310
  }
3937
4311
 
3938
4312
  // src/report/baseline.ts
3939
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
3940
- import { resolve as resolve10 } from "path";
3941
- import { generateManifest as generateManifest3 } from "@agent-scope/manifest";
4313
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
4314
+ import { resolve as resolve11 } from "path";
4315
+ import { generateManifest as generateManifest4 } from "@agent-scope/manifest";
3942
4316
  import { BrowserPool as BrowserPool4, safeRender as safeRender3 } from "@agent-scope/render";
3943
4317
  import { ComplianceEngine as ComplianceEngine2, TokenResolver as TokenResolver2 } from "@agent-scope/tokens";
3944
4318
  var DEFAULT_BASELINE_DIR = ".reactscope/baseline";
@@ -4086,30 +4460,30 @@ async function runBaseline(options = {}) {
4086
4460
  } = options;
4087
4461
  const startTime = performance.now();
4088
4462
  const rootDir = process.cwd();
4089
- const baselineDir = resolve10(rootDir, outputDir);
4090
- const rendersDir = resolve10(baselineDir, "renders");
4091
- if (existsSync8(baselineDir)) {
4463
+ const baselineDir = resolve11(rootDir, outputDir);
4464
+ const rendersDir = resolve11(baselineDir, "renders");
4465
+ if (existsSync9(baselineDir)) {
4092
4466
  rmSync2(baselineDir, { recursive: true, force: true });
4093
4467
  }
4094
4468
  mkdirSync5(rendersDir, { recursive: true });
4095
4469
  let manifest;
4096
4470
  if (manifestPath !== void 0) {
4097
- const { readFileSync: readFileSync13 } = await import("fs");
4098
- const absPath = resolve10(rootDir, manifestPath);
4099
- if (!existsSync8(absPath)) {
4471
+ const { readFileSync: readFileSync14 } = await import("fs");
4472
+ const absPath = resolve11(rootDir, manifestPath);
4473
+ if (!existsSync9(absPath)) {
4100
4474
  throw new Error(`Manifest not found at ${absPath}.`);
4101
4475
  }
4102
- manifest = JSON.parse(readFileSync13(absPath, "utf-8"));
4476
+ manifest = JSON.parse(readFileSync14(absPath, "utf-8"));
4103
4477
  process.stderr.write(`Loaded manifest from ${manifestPath}
4104
4478
  `);
4105
4479
  } else {
4106
4480
  process.stderr.write("Scanning for React components\u2026\n");
4107
- manifest = await generateManifest3({ rootDir });
4481
+ manifest = await generateManifest4({ rootDir });
4108
4482
  const count = Object.keys(manifest.components).length;
4109
4483
  process.stderr.write(`Found ${count} components.
4110
4484
  `);
4111
4485
  }
4112
- writeFileSync6(resolve10(baselineDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
4486
+ writeFileSync6(resolve11(baselineDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
4113
4487
  let componentNames = Object.keys(manifest.components);
4114
4488
  if (componentsGlob !== void 0) {
4115
4489
  componentNames = componentNames.filter((name) => matchGlob(componentsGlob, name));
@@ -4130,7 +4504,7 @@ async function runBaseline(options = {}) {
4130
4504
  auditedAt: (/* @__PURE__ */ new Date()).toISOString()
4131
4505
  };
4132
4506
  writeFileSync6(
4133
- resolve10(baselineDir, "compliance.json"),
4507
+ resolve11(baselineDir, "compliance.json"),
4134
4508
  JSON.stringify(emptyReport, null, 2),
4135
4509
  "utf-8"
4136
4510
  );
@@ -4151,7 +4525,7 @@ async function runBaseline(options = {}) {
4151
4525
  const renderOne = async (name) => {
4152
4526
  const descriptor = manifest.components[name];
4153
4527
  if (descriptor === void 0) return;
4154
- const filePath = resolve10(rootDir, descriptor.filePath);
4528
+ const filePath = resolve11(rootDir, descriptor.filePath);
4155
4529
  const outcome = await safeRender3(
4156
4530
  () => renderComponent2(filePath, name, {}, viewportWidth, viewportHeight),
4157
4531
  {
@@ -4170,7 +4544,7 @@ async function runBaseline(options = {}) {
4170
4544
  }
4171
4545
  if (outcome.crashed) {
4172
4546
  failureCount++;
4173
- const errPath = resolve10(rendersDir, `${name}.error.json`);
4547
+ const errPath = resolve11(rendersDir, `${name}.error.json`);
4174
4548
  writeFileSync6(
4175
4549
  errPath,
4176
4550
  JSON.stringify(
@@ -4188,10 +4562,10 @@ async function runBaseline(options = {}) {
4188
4562
  return;
4189
4563
  }
4190
4564
  const result = outcome.result;
4191
- writeFileSync6(resolve10(rendersDir, `${name}.png`), result.screenshot);
4565
+ writeFileSync6(resolve11(rendersDir, `${name}.png`), result.screenshot);
4192
4566
  const jsonOutput = formatRenderJson(name, {}, result);
4193
4567
  writeFileSync6(
4194
- resolve10(rendersDir, `${name}.json`),
4568
+ resolve11(rendersDir, `${name}.json`),
4195
4569
  JSON.stringify(jsonOutput, null, 2),
4196
4570
  "utf-8"
4197
4571
  );
@@ -4219,7 +4593,7 @@ async function runBaseline(options = {}) {
4219
4593
  const engine = new ComplianceEngine2(resolver);
4220
4594
  const batchReport = engine.auditBatch(computedStylesMap);
4221
4595
  writeFileSync6(
4222
- resolve10(baselineDir, "compliance.json"),
4596
+ resolve11(baselineDir, "compliance.json"),
4223
4597
  JSON.stringify(batchReport, null, 2),
4224
4598
  "utf-8"
4225
4599
  );
@@ -4262,22 +4636,22 @@ function registerBaselineSubCommand(reportCmd) {
4262
4636
  }
4263
4637
 
4264
4638
  // src/report/diff.ts
4265
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
4266
- import { resolve as resolve11 } from "path";
4267
- import { generateManifest as generateManifest4 } from "@agent-scope/manifest";
4639
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
4640
+ import { resolve as resolve12 } from "path";
4641
+ import { generateManifest as generateManifest5 } from "@agent-scope/manifest";
4268
4642
  import { BrowserPool as BrowserPool5, safeRender as safeRender4 } from "@agent-scope/render";
4269
4643
  import { ComplianceEngine as ComplianceEngine3, TokenResolver as TokenResolver3 } from "@agent-scope/tokens";
4270
4644
  var DEFAULT_BASELINE_DIR2 = ".reactscope/baseline";
4271
4645
  function loadBaselineCompliance(baselineDir) {
4272
- const compliancePath = resolve11(baselineDir, "compliance.json");
4273
- if (!existsSync9(compliancePath)) return null;
4274
- const raw = JSON.parse(readFileSync7(compliancePath, "utf-8"));
4646
+ const compliancePath = resolve12(baselineDir, "compliance.json");
4647
+ if (!existsSync10(compliancePath)) return null;
4648
+ const raw = JSON.parse(readFileSync8(compliancePath, "utf-8"));
4275
4649
  return raw;
4276
4650
  }
4277
4651
  function loadBaselineRenderJson2(baselineDir, componentName) {
4278
- const jsonPath = resolve11(baselineDir, "renders", `${componentName}.json`);
4279
- if (!existsSync9(jsonPath)) return null;
4280
- return JSON.parse(readFileSync7(jsonPath, "utf-8"));
4652
+ const jsonPath = resolve12(baselineDir, "renders", `${componentName}.json`);
4653
+ if (!existsSync10(jsonPath)) return null;
4654
+ return JSON.parse(readFileSync8(jsonPath, "utf-8"));
4281
4655
  }
4282
4656
  var _pool5 = null;
4283
4657
  async function getPool5(viewportWidth, viewportHeight) {
@@ -4444,19 +4818,19 @@ async function runDiff(options = {}) {
4444
4818
  } = options;
4445
4819
  const startTime = performance.now();
4446
4820
  const rootDir = process.cwd();
4447
- const baselineDir = resolve11(rootDir, baselineDirRaw);
4448
- if (!existsSync9(baselineDir)) {
4821
+ const baselineDir = resolve12(rootDir, baselineDirRaw);
4822
+ if (!existsSync10(baselineDir)) {
4449
4823
  throw new Error(
4450
4824
  `Baseline directory not found at "${baselineDir}". Run \`scope report baseline\` first to create a baseline snapshot.`
4451
4825
  );
4452
4826
  }
4453
- const baselineManifestPath = resolve11(baselineDir, "manifest.json");
4454
- if (!existsSync9(baselineManifestPath)) {
4827
+ const baselineManifestPath = resolve12(baselineDir, "manifest.json");
4828
+ if (!existsSync10(baselineManifestPath)) {
4455
4829
  throw new Error(
4456
4830
  `Baseline manifest.json not found at "${baselineManifestPath}". The baseline directory may be incomplete \u2014 re-run \`scope report baseline\`.`
4457
4831
  );
4458
4832
  }
4459
- const baselineManifest = JSON.parse(readFileSync7(baselineManifestPath, "utf-8"));
4833
+ const baselineManifest = JSON.parse(readFileSync8(baselineManifestPath, "utf-8"));
4460
4834
  const baselineCompliance = loadBaselineCompliance(baselineDir);
4461
4835
  const baselineComponentNames = new Set(Object.keys(baselineManifest.components));
4462
4836
  process.stderr.write(
@@ -4465,16 +4839,16 @@ async function runDiff(options = {}) {
4465
4839
  );
4466
4840
  let currentManifest;
4467
4841
  if (manifestPath !== void 0) {
4468
- const absPath = resolve11(rootDir, manifestPath);
4469
- if (!existsSync9(absPath)) {
4842
+ const absPath = resolve12(rootDir, manifestPath);
4843
+ if (!existsSync10(absPath)) {
4470
4844
  throw new Error(`Manifest not found at "${absPath}".`);
4471
4845
  }
4472
- currentManifest = JSON.parse(readFileSync7(absPath, "utf-8"));
4846
+ currentManifest = JSON.parse(readFileSync8(absPath, "utf-8"));
4473
4847
  process.stderr.write(`Loaded manifest from ${manifestPath}
4474
4848
  `);
4475
4849
  } else {
4476
4850
  process.stderr.write("Scanning for React components\u2026\n");
4477
- currentManifest = await generateManifest4({ rootDir });
4851
+ currentManifest = await generateManifest5({ rootDir });
4478
4852
  const count = Object.keys(currentManifest.components).length;
4479
4853
  process.stderr.write(`Found ${count} components.
4480
4854
  `);
@@ -4502,7 +4876,7 @@ async function runDiff(options = {}) {
4502
4876
  const renderOne = async (name) => {
4503
4877
  const descriptor = currentManifest.components[name];
4504
4878
  if (descriptor === void 0) return;
4505
- const filePath = resolve11(rootDir, descriptor.filePath);
4879
+ const filePath = resolve12(rootDir, descriptor.filePath);
4506
4880
  const outcome = await safeRender4(
4507
4881
  () => renderComponent3(filePath, name, {}, viewportWidth, viewportHeight),
4508
4882
  {
@@ -4742,8 +5116,8 @@ function registerDiffSubCommand(reportCmd) {
4742
5116
  }
4743
5117
 
4744
5118
  // src/report/pr-comment.ts
4745
- import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
4746
- import { resolve as resolve12 } from "path";
5119
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
5120
+ import { resolve as resolve13 } from "path";
4747
5121
  var STATUS_BADGE = {
4748
5122
  added: "\u2705 added",
4749
5123
  removed: "\u{1F5D1}\uFE0F removed",
@@ -4826,13 +5200,13 @@ function formatPrComment(diff) {
4826
5200
  return lines.join("\n");
4827
5201
  }
4828
5202
  function loadDiffResult(filePath) {
4829
- const abs = resolve12(filePath);
4830
- if (!existsSync10(abs)) {
5203
+ const abs = resolve13(filePath);
5204
+ if (!existsSync11(abs)) {
4831
5205
  throw new Error(`DiffResult file not found: ${abs}`);
4832
5206
  }
4833
5207
  let raw;
4834
5208
  try {
4835
- raw = readFileSync8(abs, "utf-8");
5209
+ raw = readFileSync9(abs, "utf-8");
4836
5210
  } catch (err) {
4837
5211
  throw new Error(
4838
5212
  `Failed to read DiffResult file: ${err instanceof Error ? err.message : String(err)}`
@@ -4859,7 +5233,7 @@ function registerPrCommentSubCommand(reportCmd) {
4859
5233
  const diff = loadDiffResult(opts.input);
4860
5234
  const comment = formatPrComment(diff);
4861
5235
  if (opts.output !== void 0) {
4862
- writeFileSync8(resolve12(opts.output), comment, "utf-8");
5236
+ writeFileSync8(resolve13(opts.output), comment, "utf-8");
4863
5237
  process.stderr.write(`PR comment written to ${opts.output}
4864
5238
  `);
4865
5239
  } else {
@@ -5154,11 +5528,11 @@ function buildStructuredReport(report) {
5154
5528
  }
5155
5529
 
5156
5530
  // src/site-commands.ts
5157
- import { createReadStream, existsSync as existsSync11, statSync } from "fs";
5531
+ import { createReadStream, existsSync as existsSync12, statSync as statSync2 } from "fs";
5158
5532
  import { createServer } from "http";
5159
- import { extname, join as join4, resolve as resolve13 } from "path";
5533
+ import { extname, join as join5, resolve as resolve14 } from "path";
5160
5534
  import { buildSite } from "@agent-scope/site";
5161
- import { Command as Command7 } from "commander";
5535
+ import { Command as Command8 } from "commander";
5162
5536
  var MIME_TYPES = {
5163
5537
  ".html": "text/html; charset=utf-8",
5164
5538
  ".css": "text/css; charset=utf-8",
@@ -5174,16 +5548,16 @@ function registerBuild(siteCmd) {
5174
5548
  siteCmd.command("build").description("Build a static HTML gallery from .reactscope/ output").option("-i, --input <path>", "Path to .reactscope input directory", ".reactscope").option("-o, --output <path>", "Output directory for generated site", ".reactscope/site").option("--base-path <path>", "Base URL path prefix for subdirectory deployment", "/").option("--compliance <path>", "Path to compliance batch report JSON").option("--title <text>", "Site title", "Scope \u2014 Component Gallery").action(
5175
5549
  async (opts) => {
5176
5550
  try {
5177
- const inputDir = resolve13(process.cwd(), opts.input);
5178
- const outputDir = resolve13(process.cwd(), opts.output);
5179
- if (!existsSync11(inputDir)) {
5551
+ const inputDir = resolve14(process.cwd(), opts.input);
5552
+ const outputDir = resolve14(process.cwd(), opts.output);
5553
+ if (!existsSync12(inputDir)) {
5180
5554
  throw new Error(
5181
5555
  `Input directory not found: ${inputDir}
5182
5556
  Run \`scope manifest generate\` and \`scope render\` first.`
5183
5557
  );
5184
5558
  }
5185
- const manifestPath = join4(inputDir, "manifest.json");
5186
- if (!existsSync11(manifestPath)) {
5559
+ const manifestPath = join5(inputDir, "manifest.json");
5560
+ if (!existsSync12(manifestPath)) {
5187
5561
  throw new Error(
5188
5562
  `Manifest not found at ${manifestPath}
5189
5563
  Run \`scope manifest generate\` first.`
@@ -5196,7 +5570,7 @@ Run \`scope manifest generate\` first.`
5196
5570
  outputDir,
5197
5571
  basePath: opts.basePath,
5198
5572
  ...opts.compliance !== void 0 && {
5199
- compliancePath: resolve13(process.cwd(), opts.compliance)
5573
+ compliancePath: resolve14(process.cwd(), opts.compliance)
5200
5574
  },
5201
5575
  title: opts.title
5202
5576
  });
@@ -5219,8 +5593,8 @@ function registerServe(siteCmd) {
5219
5593
  if (Number.isNaN(port) || port < 1 || port > 65535) {
5220
5594
  throw new Error(`Invalid port: ${opts.port}`);
5221
5595
  }
5222
- const serveDir = resolve13(process.cwd(), opts.dir);
5223
- if (!existsSync11(serveDir)) {
5596
+ const serveDir = resolve14(process.cwd(), opts.dir);
5597
+ if (!existsSync12(serveDir)) {
5224
5598
  throw new Error(
5225
5599
  `Serve directory not found: ${serveDir}
5226
5600
  Run \`scope site build\` first.`
@@ -5229,13 +5603,13 @@ Run \`scope site build\` first.`
5229
5603
  const server = createServer((req, res) => {
5230
5604
  const rawUrl = req.url ?? "/";
5231
5605
  const urlPath = decodeURIComponent(rawUrl.split("?")[0] ?? "/");
5232
- const filePath = join4(serveDir, urlPath.endsWith("/") ? `${urlPath}index.html` : urlPath);
5606
+ const filePath = join5(serveDir, urlPath.endsWith("/") ? `${urlPath}index.html` : urlPath);
5233
5607
  if (!filePath.startsWith(serveDir)) {
5234
5608
  res.writeHead(403, { "Content-Type": "text/plain" });
5235
5609
  res.end("Forbidden");
5236
5610
  return;
5237
5611
  }
5238
- if (existsSync11(filePath) && statSync(filePath).isFile()) {
5612
+ if (existsSync12(filePath) && statSync2(filePath).isFile()) {
5239
5613
  const ext = extname(filePath).toLowerCase();
5240
5614
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
5241
5615
  res.writeHead(200, { "Content-Type": contentType });
@@ -5243,7 +5617,7 @@ Run \`scope site build\` first.`
5243
5617
  return;
5244
5618
  }
5245
5619
  const htmlPath = `${filePath}.html`;
5246
- if (existsSync11(htmlPath) && statSync(htmlPath).isFile()) {
5620
+ if (existsSync12(htmlPath) && statSync2(htmlPath).isFile()) {
5247
5621
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5248
5622
  createReadStream(htmlPath).pipe(res);
5249
5623
  return;
@@ -5276,7 +5650,7 @@ Run \`scope site build\` first.`
5276
5650
  });
5277
5651
  }
5278
5652
  function createSiteCommand() {
5279
- const siteCmd = new Command7("site").description(
5653
+ const siteCmd = new Command8("site").description(
5280
5654
  "Build and serve the static component gallery site"
5281
5655
  );
5282
5656
  registerBuild(siteCmd);
@@ -5285,8 +5659,8 @@ function createSiteCommand() {
5285
5659
  }
5286
5660
 
5287
5661
  // src/tokens/commands.ts
5288
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
5289
- import { resolve as resolve17 } from "path";
5662
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
5663
+ import { resolve as resolve18 } from "path";
5290
5664
  import {
5291
5665
  parseTokenFileSync as parseTokenFileSync2,
5292
5666
  TokenParseError,
@@ -5294,26 +5668,26 @@ import {
5294
5668
  TokenValidationError,
5295
5669
  validateTokenFile
5296
5670
  } from "@agent-scope/tokens";
5297
- import { Command as Command9 } from "commander";
5671
+ import { Command as Command10 } from "commander";
5298
5672
 
5299
5673
  // src/tokens/compliance.ts
5300
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
5301
- import { resolve as resolve14 } from "path";
5674
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
5675
+ import { resolve as resolve15 } from "path";
5302
5676
  import {
5303
5677
  ComplianceEngine as ComplianceEngine4,
5304
5678
  TokenResolver as TokenResolver4
5305
5679
  } from "@agent-scope/tokens";
5306
5680
  var DEFAULT_STYLES_PATH = ".reactscope/compliance-styles.json";
5307
5681
  function loadStylesFile(stylesPath) {
5308
- const absPath = resolve14(process.cwd(), stylesPath);
5309
- if (!existsSync12(absPath)) {
5682
+ const absPath = resolve15(process.cwd(), stylesPath);
5683
+ if (!existsSync13(absPath)) {
5310
5684
  throw new Error(
5311
5685
  `Compliance styles file not found at ${absPath}.
5312
5686
  Run \`scope render all\` first to generate component styles, or use --styles to specify a path.
5313
5687
  Expected format: { "ComponentName": { colors: {}, spacing: {}, typography: {}, borders: {}, shadows: {} } }`
5314
5688
  );
5315
5689
  }
5316
- const raw = readFileSync9(absPath, "utf-8");
5690
+ const raw = readFileSync10(absPath, "utf-8");
5317
5691
  let parsed;
5318
5692
  try {
5319
5693
  parsed = JSON.parse(raw);
@@ -5473,38 +5847,38 @@ function registerCompliance(tokensCmd) {
5473
5847
  }
5474
5848
 
5475
5849
  // src/tokens/export.ts
5476
- import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
5477
- import { resolve as resolve15 } from "path";
5850
+ import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
5851
+ import { resolve as resolve16 } from "path";
5478
5852
  import {
5479
5853
  exportTokens,
5480
5854
  parseTokenFileSync,
5481
5855
  ThemeResolver,
5482
5856
  TokenResolver as TokenResolver5
5483
5857
  } from "@agent-scope/tokens";
5484
- import { Command as Command8 } from "commander";
5858
+ import { Command as Command9 } from "commander";
5485
5859
  var DEFAULT_TOKEN_FILE = "reactscope.tokens.json";
5486
5860
  var CONFIG_FILE = "reactscope.config.json";
5487
5861
  var SUPPORTED_FORMATS = ["css", "ts", "scss", "tailwind", "flat-json", "figma"];
5488
5862
  function resolveTokenFilePath2(fileFlag) {
5489
5863
  if (fileFlag !== void 0) {
5490
- return resolve15(process.cwd(), fileFlag);
5864
+ return resolve16(process.cwd(), fileFlag);
5491
5865
  }
5492
- const configPath = resolve15(process.cwd(), CONFIG_FILE);
5493
- if (existsSync13(configPath)) {
5866
+ const configPath = resolve16(process.cwd(), CONFIG_FILE);
5867
+ if (existsSync14(configPath)) {
5494
5868
  try {
5495
- const raw = readFileSync10(configPath, "utf-8");
5869
+ const raw = readFileSync11(configPath, "utf-8");
5496
5870
  const config = JSON.parse(raw);
5497
5871
  if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
5498
5872
  const file = config.tokens.file;
5499
- return resolve15(process.cwd(), file);
5873
+ return resolve16(process.cwd(), file);
5500
5874
  }
5501
5875
  } catch {
5502
5876
  }
5503
5877
  }
5504
- return resolve15(process.cwd(), DEFAULT_TOKEN_FILE);
5878
+ return resolve16(process.cwd(), DEFAULT_TOKEN_FILE);
5505
5879
  }
5506
5880
  function createTokensExportCommand() {
5507
- return new Command8("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(
5881
+ return new Command9("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(
5508
5882
  "--theme <name>",
5509
5883
  "Include theme overrides for the named theme (applies to css, ts, scss, tailwind, figma)"
5510
5884
  ).action(
@@ -5530,13 +5904,13 @@ Supported formats: ${SUPPORTED_FORMATS.join(", ")}
5530
5904
  const format = opts.format;
5531
5905
  try {
5532
5906
  const filePath = resolveTokenFilePath2(opts.file);
5533
- if (!existsSync13(filePath)) {
5907
+ if (!existsSync14(filePath)) {
5534
5908
  throw new Error(
5535
5909
  `Token file not found at ${filePath}.
5536
5910
  Create a reactscope.tokens.json file or use --file to specify a path.`
5537
5911
  );
5538
5912
  }
5539
- const raw = readFileSync10(filePath, "utf-8");
5913
+ const raw = readFileSync11(filePath, "utf-8");
5540
5914
  const { tokens, rawFile } = parseTokenFileSync(raw);
5541
5915
  let themesMap;
5542
5916
  if (opts.theme !== void 0) {
@@ -5575,7 +5949,7 @@ Available themes: ${themeNames.join(", ")}`
5575
5949
  themes: themesMap
5576
5950
  });
5577
5951
  if (opts.out !== void 0) {
5578
- const outPath = resolve15(process.cwd(), opts.out);
5952
+ const outPath = resolve16(process.cwd(), opts.out);
5579
5953
  writeFileSync9(outPath, output, "utf-8");
5580
5954
  process.stderr.write(`Exported ${tokens.length} tokens to ${outPath}
5581
5955
  `);
@@ -5691,7 +6065,7 @@ ${formatImpactSummary(report)}
5691
6065
 
5692
6066
  // src/tokens/preview.ts
5693
6067
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "fs";
5694
- import { resolve as resolve16 } from "path";
6068
+ import { resolve as resolve17 } from "path";
5695
6069
  import { BrowserPool as BrowserPool6, SpriteSheetGenerator } from "@agent-scope/render";
5696
6070
  import { ComplianceEngine as ComplianceEngine6, ImpactAnalyzer as ImpactAnalyzer2, TokenResolver as TokenResolver7 } from "@agent-scope/tokens";
5697
6071
  var DEFAULT_STYLES_PATH3 = ".reactscope/compliance-styles.json";
@@ -5872,8 +6246,8 @@ function registerPreview(tokensCmd) {
5872
6246
  });
5873
6247
  const spriteResult = await generator.generate(matrixResult);
5874
6248
  const tokenLabel = tokenPath.replace(/\./g, "-");
5875
- const outputPath = opts.output ?? resolve16(process.cwd(), DEFAULT_OUTPUT_DIR2, `preview-${tokenLabel}.png`);
5876
- const outputDir = resolve16(outputPath, "..");
6249
+ const outputPath = opts.output ?? resolve17(process.cwd(), DEFAULT_OUTPUT_DIR2, `preview-${tokenLabel}.png`);
6250
+ const outputDir = resolve17(outputPath, "..");
5877
6251
  mkdirSync6(outputDir, { recursive: true });
5878
6252
  writeFileSync10(outputPath, spriteResult.png);
5879
6253
  const useJson = opts.format === "json" || opts.format !== "text" && !isTTY();
@@ -5934,30 +6308,30 @@ function buildTable2(headers, rows) {
5934
6308
  }
5935
6309
  function resolveTokenFilePath(fileFlag) {
5936
6310
  if (fileFlag !== void 0) {
5937
- return resolve17(process.cwd(), fileFlag);
6311
+ return resolve18(process.cwd(), fileFlag);
5938
6312
  }
5939
- const configPath = resolve17(process.cwd(), CONFIG_FILE2);
5940
- if (existsSync14(configPath)) {
6313
+ const configPath = resolve18(process.cwd(), CONFIG_FILE2);
6314
+ if (existsSync15(configPath)) {
5941
6315
  try {
5942
- const raw = readFileSync11(configPath, "utf-8");
6316
+ const raw = readFileSync12(configPath, "utf-8");
5943
6317
  const config = JSON.parse(raw);
5944
6318
  if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
5945
6319
  const file = config.tokens.file;
5946
- return resolve17(process.cwd(), file);
6320
+ return resolve18(process.cwd(), file);
5947
6321
  }
5948
6322
  } catch {
5949
6323
  }
5950
6324
  }
5951
- return resolve17(process.cwd(), DEFAULT_TOKEN_FILE2);
6325
+ return resolve18(process.cwd(), DEFAULT_TOKEN_FILE2);
5952
6326
  }
5953
6327
  function loadTokens(absPath) {
5954
- if (!existsSync14(absPath)) {
6328
+ if (!existsSync15(absPath)) {
5955
6329
  throw new Error(
5956
6330
  `Token file not found at ${absPath}.
5957
6331
  Create a reactscope.tokens.json file or use --file to specify a path.`
5958
6332
  );
5959
6333
  }
5960
- const raw = readFileSync11(absPath, "utf-8");
6334
+ const raw = readFileSync12(absPath, "utf-8");
5961
6335
  return parseTokenFileSync2(raw);
5962
6336
  }
5963
6337
  function getRawValue(node, segments) {
@@ -6171,13 +6545,13 @@ function registerValidate(tokensCmd) {
6171
6545
  ).option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((opts) => {
6172
6546
  try {
6173
6547
  const filePath = resolveTokenFilePath(opts.file);
6174
- if (!existsSync14(filePath)) {
6548
+ if (!existsSync15(filePath)) {
6175
6549
  throw new Error(
6176
6550
  `Token file not found at ${filePath}.
6177
6551
  Create a reactscope.tokens.json file or use --file to specify a path.`
6178
6552
  );
6179
6553
  }
6180
- const raw = readFileSync11(filePath, "utf-8");
6554
+ const raw = readFileSync12(filePath, "utf-8");
6181
6555
  const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
6182
6556
  const errors = [];
6183
6557
  let parsed;
@@ -6245,7 +6619,7 @@ function outputValidationResult(filePath, errors, useJson) {
6245
6619
  }
6246
6620
  }
6247
6621
  function createTokensCommand() {
6248
- const tokensCmd = new Command9("tokens").description(
6622
+ const tokensCmd = new Command10("tokens").description(
6249
6623
  "Query and validate design tokens from a reactscope.tokens.json file"
6250
6624
  );
6251
6625
  registerGet2(tokensCmd);
@@ -6262,7 +6636,7 @@ function createTokensCommand() {
6262
6636
 
6263
6637
  // src/program.ts
6264
6638
  function createProgram(options = {}) {
6265
- const program2 = new Command10("scope").version(options.version ?? "0.1.0").description("Scope \u2014 React instrumentation toolkit");
6639
+ const program2 = new Command11("scope").version(options.version ?? "0.1.0").description("Scope \u2014 React instrumentation toolkit");
6266
6640
  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(
6267
6641
  async (url, opts) => {
6268
6642
  try {
@@ -6335,7 +6709,7 @@ function createProgram(options = {}) {
6335
6709
  }
6336
6710
  );
6337
6711
  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) => {
6338
- const raw = readFileSync12(tracePath, "utf-8");
6712
+ const raw = readFileSync13(tracePath, "utf-8");
6339
6713
  const trace = loadTrace(raw);
6340
6714
  const source = generateTest(trace, {
6341
6715
  description: opts.description,
@@ -6350,6 +6724,7 @@ function createProgram(options = {}) {
6350
6724
  program2.addCommand(createInstrumentCommand());
6351
6725
  program2.addCommand(createInitCommand());
6352
6726
  program2.addCommand(createCiCommand());
6727
+ program2.addCommand(createDoctorCommand());
6353
6728
  const existingReportCmd = program2.commands.find((c) => c.name() === "report");
6354
6729
  if (existingReportCmd !== void 0) {
6355
6730
  registerBaselineSubCommand(existingReportCmd);