@agent-scope/cli 1.7.0 → 1.8.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/index.cjs CHANGED
@@ -607,6 +607,471 @@ function csvEscape(value) {
607
607
  }
608
608
  return value;
609
609
  }
610
+
611
+ // src/instrument/renders.ts
612
+ var MANIFEST_PATH2 = ".reactscope/manifest.json";
613
+ function determineTrigger(event) {
614
+ if (event.forceUpdate) return "force_update";
615
+ if (event.stateChanged) return "state_change";
616
+ if (event.propsChanged) return "props_change";
617
+ if (event.contextChanged) return "context_change";
618
+ if (event.hookDepsChanged) return "hook_dependency";
619
+ return "parent_rerender";
620
+ }
621
+ function isWastedRender(event) {
622
+ return !event.propsChanged && !event.stateChanged && !event.contextChanged && !event.memoized;
623
+ }
624
+ function buildCausalityChains(rawEvents) {
625
+ const result = [];
626
+ const componentLastRender = /* @__PURE__ */ new Map();
627
+ for (const raw of rawEvents) {
628
+ const trigger = determineTrigger(raw);
629
+ const wasted = isWastedRender(raw);
630
+ const chain = [];
631
+ let current = raw;
632
+ const visited = /* @__PURE__ */ new Set();
633
+ while (true) {
634
+ if (visited.has(current.component)) break;
635
+ visited.add(current.component);
636
+ const currentTrigger = determineTrigger(current);
637
+ chain.unshift({
638
+ component: current.component,
639
+ trigger: currentTrigger,
640
+ propsChanged: current.propsChanged,
641
+ stateChanged: current.stateChanged,
642
+ contextChanged: current.contextChanged
643
+ });
644
+ if (currentTrigger !== "parent_rerender" || current.parentComponent === null) {
645
+ break;
646
+ }
647
+ const parentEvent = componentLastRender.get(current.parentComponent);
648
+ if (parentEvent === void 0) break;
649
+ current = parentEvent;
650
+ }
651
+ const rootCause = chain[0];
652
+ const cascadeRenders = rawEvents.filter((e) => {
653
+ if (rootCause === void 0) return false;
654
+ return e.component !== rootCause.component && !e.stateChanged && !e.propsChanged;
655
+ });
656
+ result.push({
657
+ component: raw.component,
658
+ renderIndex: raw.renderIndex,
659
+ trigger,
660
+ propsChanged: raw.propsChanged,
661
+ stateChanged: raw.stateChanged,
662
+ contextChanged: raw.contextChanged,
663
+ memoized: raw.memoized,
664
+ wasted,
665
+ chain,
666
+ cascade: {
667
+ totalRendersTriggered: cascadeRenders.length,
668
+ uniqueComponents: new Set(cascadeRenders.map((e) => e.component)).size,
669
+ unchangedPropRenders: cascadeRenders.filter((e) => !e.propsChanged).length
670
+ }
671
+ });
672
+ componentLastRender.set(raw.component, raw);
673
+ }
674
+ return result;
675
+ }
676
+ function applyHeuristicFlags(renders) {
677
+ const flags = [];
678
+ const byComponent = /* @__PURE__ */ new Map();
679
+ for (const r of renders) {
680
+ if (!byComponent.has(r.component)) byComponent.set(r.component, []);
681
+ byComponent.get(r.component).push(r);
682
+ }
683
+ for (const [component, events] of byComponent) {
684
+ const wastedCount = events.filter((e) => e.wasted).length;
685
+ const totalCount = events.length;
686
+ if (wastedCount > 0) {
687
+ flags.push({
688
+ id: "WASTED_RENDER",
689
+ severity: "warning",
690
+ component,
691
+ detail: `${wastedCount}/${totalCount} renders were wasted \u2014 unchanged props/state/context, not memoized`,
692
+ data: { wastedCount, totalCount, wastedRatio: wastedCount / totalCount }
693
+ });
694
+ }
695
+ for (const event of events) {
696
+ if (event.cascade.totalRendersTriggered > 50) {
697
+ flags.push({
698
+ id: "RENDER_CASCADE",
699
+ severity: "warning",
700
+ component,
701
+ detail: `State change in ${component} triggered ${event.cascade.totalRendersTriggered} downstream re-renders`,
702
+ data: {
703
+ totalRendersTriggered: event.cascade.totalRendersTriggered,
704
+ uniqueComponents: event.cascade.uniqueComponents,
705
+ unchangedPropRenders: event.cascade.unchangedPropRenders
706
+ }
707
+ });
708
+ break;
709
+ }
710
+ }
711
+ }
712
+ return flags;
713
+ }
714
+ function buildInstrumentationScript() {
715
+ return (
716
+ /* js */
717
+ `
718
+ (function installScopeRenderInstrumentation() {
719
+ window.__SCOPE_RENDER_EVENTS__ = [];
720
+ window.__SCOPE_RENDER_INDEX__ = 0;
721
+
722
+ var hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
723
+ if (!hook) return;
724
+
725
+ var originalOnCommit = hook.onCommitFiberRoot;
726
+ var renderedComponents = new Map(); // componentName -> { lastProps, lastState }
727
+
728
+ function extractName(fiber) {
729
+ if (!fiber) return 'Unknown';
730
+ var type = fiber.type;
731
+ if (!type) return 'Unknown';
732
+ if (typeof type === 'string') return type; // host element
733
+ if (typeof type === 'function') return type.displayName || type.name || 'Anonymous';
734
+ if (type.displayName) return type.displayName;
735
+ if (type.render && typeof type.render === 'function') {
736
+ return type.render.displayName || type.render.name || 'Anonymous';
737
+ }
738
+ return 'Anonymous';
739
+ }
740
+
741
+ function isMemoized(fiber) {
742
+ // MemoComponent = 14, SimpleMemoComponent = 15
743
+ return fiber.tag === 14 || fiber.tag === 15;
744
+ }
745
+
746
+ function isComponent(fiber) {
747
+ // FunctionComponent=0, ClassComponent=1, MemoComponent=14, SimpleMemoComponent=15
748
+ return fiber.tag === 0 || fiber.tag === 1 || fiber.tag === 14 || fiber.tag === 15;
749
+ }
750
+
751
+ function shallowEqual(a, b) {
752
+ if (a === b) return true;
753
+ if (!a || !b) return a === b;
754
+ var keysA = Object.keys(a);
755
+ var keysB = Object.keys(b);
756
+ if (keysA.length !== keysB.length) return false;
757
+ for (var i = 0; i < keysA.length; i++) {
758
+ var k = keysA[i];
759
+ if (k === 'children') continue; // ignore children prop
760
+ if (a[k] !== b[k]) return false;
761
+ }
762
+ return true;
763
+ }
764
+
765
+ function getParentComponentName(fiber) {
766
+ var parent = fiber.return;
767
+ while (parent) {
768
+ if (isComponent(parent)) {
769
+ var name = extractName(parent);
770
+ if (name && name !== 'Unknown' && !/^[a-z]/.test(name)) return name;
771
+ }
772
+ parent = parent.return;
773
+ }
774
+ return null;
775
+ }
776
+
777
+ function walkCommit(fiber) {
778
+ if (!fiber) return;
779
+
780
+ if (isComponent(fiber)) {
781
+ var name = extractName(fiber);
782
+ if (name && name !== 'Unknown' && !/^[a-z]/.test(name)) {
783
+ var memoized = isMemoized(fiber);
784
+ var currentProps = fiber.memoizedProps || {};
785
+ var prev = renderedComponents.get(name);
786
+
787
+ var propsChanged = true;
788
+ var stateChanged = false;
789
+ var contextChanged = false;
790
+ var hookDepsChanged = false;
791
+ var forceUpdate = false;
792
+
793
+ if (prev) {
794
+ propsChanged = !shallowEqual(prev.lastProps, currentProps);
795
+ }
796
+
797
+ // State: check memoizedState chain
798
+ var memoizedState = fiber.memoizedState;
799
+ if (prev && prev.lastStateSerialized !== undefined) {
800
+ try {
801
+ var stateSig = JSON.stringify(memoizedState ? memoizedState.memoizedState : null);
802
+ stateChanged = stateSig !== prev.lastStateSerialized;
803
+ } catch (_) {
804
+ stateChanged = false;
805
+ }
806
+ }
807
+
808
+ // Context: use _debugHookTypes or check dependencies
809
+ var deps = fiber.dependencies;
810
+ if (deps && deps.firstContext) {
811
+ contextChanged = true; // conservative: context dep present = may have changed
812
+ }
813
+
814
+ var stateSig;
815
+ try {
816
+ stateSig = JSON.stringify(memoizedState ? memoizedState.memoizedState : null);
817
+ } catch (_) {
818
+ stateSig = null;
819
+ }
820
+
821
+ renderedComponents.set(name, {
822
+ lastProps: currentProps,
823
+ lastStateSerialized: stateSig,
824
+ });
825
+
826
+ var parentName = getParentComponentName(fiber);
827
+
828
+ window.__SCOPE_RENDER_EVENTS__.push({
829
+ component: name,
830
+ renderIndex: window.__SCOPE_RENDER_INDEX__++,
831
+ propsChanged: prev ? propsChanged : false,
832
+ stateChanged: stateChanged,
833
+ contextChanged: contextChanged,
834
+ memoized: memoized,
835
+ parentComponent: parentName,
836
+ hookDepsChanged: hookDepsChanged,
837
+ forceUpdate: forceUpdate,
838
+ });
839
+ }
840
+ }
841
+
842
+ walkCommit(fiber.child);
843
+ walkCommit(fiber.sibling);
844
+ }
845
+
846
+ hook.onCommitFiberRoot = function(rendererID, root, priorityLevel) {
847
+ if (typeof originalOnCommit === 'function') {
848
+ originalOnCommit.call(hook, rendererID, root, priorityLevel);
849
+ }
850
+ var wipRoot = root && root.current && root.current.alternate;
851
+ if (wipRoot) walkCommit(wipRoot);
852
+ };
853
+ })();
854
+ `
855
+ );
856
+ }
857
+ async function replayInteraction(page, steps) {
858
+ for (const step of steps) {
859
+ switch (step.action) {
860
+ case "click":
861
+ if (step.target !== void 0) {
862
+ await page.click(step.target, { timeout: 5e3 }).catch(() => {
863
+ });
864
+ }
865
+ break;
866
+ case "type":
867
+ if (step.target !== void 0 && step.text !== void 0) {
868
+ await page.fill(step.target, step.text, { timeout: 5e3 }).catch(async () => {
869
+ await page.type(step.target, step.text, { timeout: 5e3 }).catch(() => {
870
+ });
871
+ });
872
+ }
873
+ break;
874
+ case "hover":
875
+ if (step.target !== void 0) {
876
+ await page.hover(step.target, { timeout: 5e3 }).catch(() => {
877
+ });
878
+ }
879
+ break;
880
+ case "blur":
881
+ if (step.target !== void 0) {
882
+ await page.locator(step.target).blur({ timeout: 5e3 }).catch(() => {
883
+ });
884
+ }
885
+ break;
886
+ case "focus":
887
+ if (step.target !== void 0) {
888
+ await page.focus(step.target, { timeout: 5e3 }).catch(() => {
889
+ });
890
+ }
891
+ break;
892
+ case "scroll":
893
+ if (step.target !== void 0) {
894
+ await page.locator(step.target).scrollIntoViewIfNeeded({ timeout: 5e3 }).catch(() => {
895
+ });
896
+ }
897
+ break;
898
+ case "wait": {
899
+ const timeout = step.timeout ?? 1e3;
900
+ if (step.condition === "idle") {
901
+ await page.waitForLoadState("networkidle", { timeout }).catch(() => {
902
+ });
903
+ } else {
904
+ await page.waitForTimeout(timeout);
905
+ }
906
+ break;
907
+ }
908
+ }
909
+ }
910
+ }
911
+ var _pool = null;
912
+ async function getPool() {
913
+ if (_pool === null) {
914
+ _pool = new render.BrowserPool({
915
+ size: { browsers: 1, pagesPerBrowser: 2 },
916
+ viewportWidth: 1280,
917
+ viewportHeight: 800
918
+ });
919
+ await _pool.init();
920
+ }
921
+ return _pool;
922
+ }
923
+ async function shutdownPool() {
924
+ if (_pool !== null) {
925
+ await _pool.close();
926
+ _pool = null;
927
+ }
928
+ }
929
+ async function analyzeRenders(options) {
930
+ const manifestPath = options.manifestPath ?? MANIFEST_PATH2;
931
+ const manifest = loadManifest(manifestPath);
932
+ const descriptor = manifest.components[options.componentName];
933
+ if (descriptor === void 0) {
934
+ const available = Object.keys(manifest.components).slice(0, 5).join(", ");
935
+ throw new Error(
936
+ `Component "${options.componentName}" not found in manifest.
937
+ Available: ${available}`
938
+ );
939
+ }
940
+ const rootDir = process.cwd();
941
+ const filePath = path.resolve(rootDir, descriptor.filePath);
942
+ const htmlHarness = await buildComponentHarness(filePath, options.componentName, {}, 1280);
943
+ const pool = await getPool();
944
+ const slot = await pool.acquire();
945
+ const { page } = slot;
946
+ const startMs = performance.now();
947
+ try {
948
+ await page.addInitScript(buildInstrumentationScript());
949
+ await page.setContent(htmlHarness, { waitUntil: "load" });
950
+ await page.waitForFunction(
951
+ () => window.__SCOPE_RENDER_COMPLETE__ === true,
952
+ { timeout: 15e3 }
953
+ );
954
+ await page.waitForTimeout(100);
955
+ await page.evaluate(() => {
956
+ window.__SCOPE_RENDER_EVENTS__ = [];
957
+ window.__SCOPE_RENDER_INDEX__ = 0;
958
+ });
959
+ await replayInteraction(page, options.interaction);
960
+ await page.waitForTimeout(200);
961
+ const interactionDurationMs = performance.now() - startMs;
962
+ const rawEvents = await page.evaluate(() => {
963
+ return window.__SCOPE_RENDER_EVENTS__ ?? [];
964
+ });
965
+ const renders = buildCausalityChains(rawEvents);
966
+ const flags = applyHeuristicFlags(renders);
967
+ const uniqueComponents = new Set(renders.map((r) => r.component)).size;
968
+ const wastedRenders = renders.filter((r) => r.wasted).length;
969
+ return {
970
+ component: options.componentName,
971
+ interaction: options.interaction,
972
+ summary: {
973
+ totalRenders: renders.length,
974
+ uniqueComponents,
975
+ wastedRenders,
976
+ interactionDurationMs: Math.round(interactionDurationMs)
977
+ },
978
+ renders,
979
+ flags
980
+ };
981
+ } finally {
982
+ pool.release(slot);
983
+ }
984
+ }
985
+ function formatRendersTable(result) {
986
+ const lines = [];
987
+ lines.push(`
988
+ \u{1F50D} Re-render Analysis: ${result.component}`);
989
+ lines.push(`${"\u2500".repeat(60)}`);
990
+ lines.push(`Total renders: ${result.summary.totalRenders}`);
991
+ lines.push(`Unique components: ${result.summary.uniqueComponents}`);
992
+ lines.push(`Wasted renders: ${result.summary.wastedRenders}`);
993
+ lines.push(`Duration: ${result.summary.interactionDurationMs}ms`);
994
+ lines.push("");
995
+ if (result.renders.length === 0) {
996
+ lines.push("No re-renders captured during interaction.");
997
+ } else {
998
+ lines.push("Re-renders:");
999
+ lines.push(
1000
+ `${"#".padEnd(4)} ${"Component".padEnd(30)} ${"Trigger".padEnd(18)} ${"Wasted".padEnd(7)} ${"Chain Depth"}`
1001
+ );
1002
+ lines.push("\u2500".repeat(80));
1003
+ for (const r of result.renders) {
1004
+ const wasted = r.wasted ? "\u26A0 yes" : "no";
1005
+ const idx = String(r.renderIndex).padEnd(4);
1006
+ const comp = r.component.slice(0, 29).padEnd(30);
1007
+ const trig = r.trigger.padEnd(18);
1008
+ const w = wasted.padEnd(7);
1009
+ const depth = r.chain.length;
1010
+ lines.push(`${idx} ${comp} ${trig} ${w} ${depth}`);
1011
+ }
1012
+ }
1013
+ if (result.flags.length > 0) {
1014
+ lines.push("");
1015
+ lines.push("Flags:");
1016
+ for (const flag of result.flags) {
1017
+ const icon = flag.severity === "error" ? "\u2717" : flag.severity === "warning" ? "\u26A0" : "\u2139";
1018
+ lines.push(` ${icon} [${flag.id}] ${flag.component}: ${flag.detail}`);
1019
+ }
1020
+ }
1021
+ return lines.join("\n");
1022
+ }
1023
+ function createInstrumentRendersCommand() {
1024
+ return new commander.Command("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(
1025
+ "--interaction <json>",
1026
+ `Interaction sequence JSON, e.g. '[{"action":"click","target":"button"}]'`,
1027
+ "[]"
1028
+ ).option("--json", "Output as JSON regardless of TTY", false).option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH2).action(
1029
+ async (componentName, opts) => {
1030
+ let interaction = [];
1031
+ try {
1032
+ interaction = JSON.parse(opts.interaction);
1033
+ if (!Array.isArray(interaction)) {
1034
+ throw new Error("Interaction must be a JSON array");
1035
+ }
1036
+ } catch {
1037
+ process.stderr.write(`Error: Invalid --interaction JSON: ${opts.interaction}
1038
+ `);
1039
+ process.exit(1);
1040
+ }
1041
+ try {
1042
+ process.stderr.write(
1043
+ `Instrumenting ${componentName} (${interaction.length} interaction steps)\u2026
1044
+ `
1045
+ );
1046
+ const result = await analyzeRenders({
1047
+ componentName,
1048
+ interaction,
1049
+ manifestPath: opts.manifest
1050
+ });
1051
+ await shutdownPool();
1052
+ if (opts.json || !isTTY()) {
1053
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
1054
+ `);
1055
+ } else {
1056
+ process.stdout.write(`${formatRendersTable(result)}
1057
+ `);
1058
+ }
1059
+ } catch (err) {
1060
+ await shutdownPool();
1061
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1062
+ `);
1063
+ process.exit(1);
1064
+ }
1065
+ }
1066
+ );
1067
+ }
1068
+ function createInstrumentCommand() {
1069
+ const instrumentCmd = new commander.Command("instrument").description(
1070
+ "Structured instrumentation commands for React component analysis"
1071
+ );
1072
+ instrumentCmd.addCommand(createInstrumentRendersCommand());
1073
+ return instrumentCmd;
1074
+ }
610
1075
  var CONFIG_FILENAMES = [
611
1076
  ".reactscope/config.json",
612
1077
  ".reactscope/config.js",
@@ -724,24 +1189,24 @@ async function getCompiledCssForClasses(cwd, classes) {
724
1189
  }
725
1190
 
726
1191
  // src/render-commands.ts
727
- var MANIFEST_PATH2 = ".reactscope/manifest.json";
1192
+ var MANIFEST_PATH3 = ".reactscope/manifest.json";
728
1193
  var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
729
- var _pool = null;
730
- async function getPool(viewportWidth, viewportHeight) {
731
- if (_pool === null) {
732
- _pool = new render.BrowserPool({
1194
+ var _pool2 = null;
1195
+ async function getPool2(viewportWidth, viewportHeight) {
1196
+ if (_pool2 === null) {
1197
+ _pool2 = new render.BrowserPool({
733
1198
  size: { browsers: 1, pagesPerBrowser: 4 },
734
1199
  viewportWidth,
735
1200
  viewportHeight
736
1201
  });
737
- await _pool.init();
1202
+ await _pool2.init();
738
1203
  }
739
- return _pool;
1204
+ return _pool2;
740
1205
  }
741
- async function shutdownPool() {
742
- if (_pool !== null) {
743
- await _pool.close();
744
- _pool = null;
1206
+ async function shutdownPool2() {
1207
+ if (_pool2 !== null) {
1208
+ await _pool2.close();
1209
+ _pool2 = null;
745
1210
  }
746
1211
  }
747
1212
  function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
@@ -752,7 +1217,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
752
1217
  _satori: satori,
753
1218
  async renderCell(props, _complexityClass) {
754
1219
  const startMs = performance.now();
755
- const pool = await getPool(viewportWidth, viewportHeight);
1220
+ const pool = await getPool2(viewportWidth, viewportHeight);
756
1221
  const htmlHarness = await buildComponentHarness(
757
1222
  filePath,
758
1223
  componentName,
@@ -849,7 +1314,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
849
1314
  };
850
1315
  }
851
1316
  function registerRenderSingle(renderCmd) {
852
- renderCmd.command("component <component>", { isDefault: true }).description("Render a single component to PNG or JSON").option("--props <json>", `Inline props JSON, e.g. '{"variant":"primary"}'`).option("--viewport <WxH>", "Viewport size e.g. 1280x720", "375x812").option("--theme <name>", "Theme name from the token system").option("-o, --output <path>", "Write PNG to file instead of stdout").option("--format <fmt>", "Output format: png or json (default: auto)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH2).action(
1317
+ renderCmd.command("component <component>", { isDefault: true }).description("Render a single component to PNG or JSON").option("--props <json>", `Inline props JSON, e.g. '{"variant":"primary"}'`).option("--viewport <WxH>", "Viewport size e.g. 1280x720", "375x812").option("--theme <name>", "Theme name from the token system").option("-o, --output <path>", "Write PNG to file instead of stdout").option("--format <fmt>", "Output format: png or json (default: auto)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH3).action(
853
1318
  async (componentName, opts) => {
854
1319
  try {
855
1320
  const manifest = loadManifest(opts.manifest);
@@ -888,7 +1353,7 @@ Available: ${available}`
888
1353
  }
889
1354
  }
890
1355
  );
891
- await shutdownPool();
1356
+ await shutdownPool2();
892
1357
  if (outcome.crashed) {
893
1358
  process.stderr.write(`\u2717 Render failed: ${outcome.error.message}
894
1359
  `);
@@ -936,7 +1401,7 @@ Available: ${available}`
936
1401
  );
937
1402
  }
938
1403
  } catch (err) {
939
- await shutdownPool();
1404
+ await shutdownPool2();
940
1405
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
941
1406
  `);
942
1407
  process.exit(1);
@@ -948,7 +1413,7 @@ function registerRenderMatrix(renderCmd) {
948
1413
  renderCmd.command("matrix <component>").description("Render a component across a matrix of prop axes").option("--axes <spec>", "Axis definitions e.g. 'variant:primary,secondary size:sm,md,lg'").option(
949
1414
  "--contexts <ids>",
950
1415
  "Composition context IDs, comma-separated (e.g. centered,rtl,sidebar)"
951
- ).option("--stress <ids>", "Stress preset IDs, comma-separated (e.g. text.long,text.unicode)").option("--sprite <path>", "Write sprite sheet PNG to file").option("--format <fmt>", "Output format: json|png|html|csv (default: auto)").option("--concurrency <n>", "Max parallel renders", "8").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH2).action(
1416
+ ).option("--stress <ids>", "Stress preset IDs, comma-separated (e.g. text.long,text.unicode)").option("--sprite <path>", "Write sprite sheet PNG to file").option("--format <fmt>", "Output format: json|png|html|csv (default: auto)").option("--concurrency <n>", "Max parallel renders", "8").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH3).action(
952
1417
  async (componentName, opts) => {
953
1418
  try {
954
1419
  const manifest = loadManifest(opts.manifest);
@@ -1021,7 +1486,7 @@ Available: ${available}`
1021
1486
  concurrency
1022
1487
  });
1023
1488
  const result = await matrix.render();
1024
- await shutdownPool();
1489
+ await shutdownPool2();
1025
1490
  process.stderr.write(
1026
1491
  `Done. ${result.stats.totalCells} cells, avg ${result.stats.avgRenderTimeMs.toFixed(1)}ms
1027
1492
  `
@@ -1066,7 +1531,7 @@ Available: ${available}`
1066
1531
  process.stdout.write(formatMatrixCsv(componentName, result));
1067
1532
  }
1068
1533
  } catch (err) {
1069
- await shutdownPool();
1534
+ await shutdownPool2();
1070
1535
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1071
1536
  `);
1072
1537
  process.exit(1);
@@ -1075,7 +1540,7 @@ Available: ${available}`
1075
1540
  );
1076
1541
  }
1077
1542
  function registerRenderAll(renderCmd) {
1078
- renderCmd.command("all").description("Render all components from the manifest").option("--concurrency <n>", "Max parallel renders", "4").option("--output-dir <dir>", "Output directory for renders", DEFAULT_OUTPUT_DIR).option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH2).option("--format <fmt>", "Output format: json|png (default: png)", "png").action(
1543
+ renderCmd.command("all").description("Render all components from the manifest").option("--concurrency <n>", "Max parallel renders", "4").option("--output-dir <dir>", "Output directory for renders", DEFAULT_OUTPUT_DIR).option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH3).option("--format <fmt>", "Output format: json|png (default: png)", "png").action(
1079
1544
  async (opts) => {
1080
1545
  try {
1081
1546
  const manifest = loadManifest(opts.manifest);
@@ -1163,13 +1628,13 @@ function registerRenderAll(renderCmd) {
1163
1628
  workers.push(worker());
1164
1629
  }
1165
1630
  await Promise.all(workers);
1166
- await shutdownPool();
1631
+ await shutdownPool2();
1167
1632
  process.stderr.write("\n");
1168
1633
  const summary = formatSummaryText(results, outputDir);
1169
1634
  process.stderr.write(`${summary}
1170
1635
  `);
1171
1636
  } catch (err) {
1172
- await shutdownPool();
1637
+ await shutdownPool2();
1173
1638
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
1174
1639
  `);
1175
1640
  process.exit(1);
@@ -1918,6 +2383,7 @@ function createProgram(options = {}) {
1918
2383
  program.addCommand(createManifestCommand());
1919
2384
  program.addCommand(createRenderCommand());
1920
2385
  program.addCommand(createTokensCommand());
2386
+ program.addCommand(createInstrumentCommand());
1921
2387
  return program;
1922
2388
  }
1923
2389