@contentful/experience-design-system-cli 2.34.5-dev-build-f6f44159.0 → 2.34.5-dev-build-ab8465d2.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/src/index.js CHANGED
@@ -110,41 +110,58 @@ var init_credentials_store = __esm({
110
110
  var render_with_goodbye_exports = {};
111
111
  __export(render_with_goodbye_exports, {
112
112
  GoodbyeBoundary: () => GoodbyeBoundary,
113
- renderWithGoodbye: () => renderWithGoodbye
113
+ renderWithGoodbye: () => renderWithGoodbye,
114
+ useScreenTransitionClear: () => useScreenTransitionClear
114
115
  });
115
- import { useEffect, useRef, useState } from "react";
116
- import { render, Text } from "ink";
116
+ import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
117
+ import { render, Text, useInput } from "ink";
117
118
  import { Fragment, jsx } from "react/jsx-runtime";
119
+ function useScreenTransitionClear() {
120
+ return useContext(ScreenTransitionClearContext);
121
+ }
118
122
  function GoodbyeBoundary({ children }) {
119
123
  const [isExiting, setIsExiting] = useState(false);
120
124
  const isExitingRef = useRef(false);
125
+ const exitTimerRef = useRef(void 0);
126
+ const requestExit = useCallback(() => {
127
+ if (isExitingRef.current) return;
128
+ isExitingRef.current = true;
129
+ setIsExiting(true);
130
+ exitTimerRef.current = setTimeout(() => process.exit(0), GOODBYE_RENDER_DELAY_MS);
131
+ }, []);
132
+ useInput((input, key) => {
133
+ if (key.ctrl && input === "c") requestExit();
134
+ });
121
135
  useEffect(() => {
122
- let exitTimer;
123
- const handleSigint = () => {
124
- if (isExitingRef.current) return;
125
- isExitingRef.current = true;
126
- setIsExiting(true);
127
- exitTimer = setTimeout(() => process.exit(0), GOODBYE_RENDER_DELAY_MS);
128
- };
129
- process.on("SIGINT", handleSigint);
136
+ process.on("SIGINT", requestExit);
130
137
  return () => {
131
- process.off("SIGINT", handleSigint);
132
- if (exitTimer) clearTimeout(exitTimer);
138
+ process.off("SIGINT", requestExit);
139
+ if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
133
140
  };
134
- }, []);
141
+ }, [requestExit]);
135
142
  return isExiting ? /* @__PURE__ */ jsx(Text, { children: "Goodbye!" }) : /* @__PURE__ */ jsx(Fragment, { children });
136
143
  }
137
144
  function renderWithGoodbye(element, options) {
138
- return render(/* @__PURE__ */ jsx(GoodbyeBoundary, { children: element }), {
139
- ...options,
140
- exitOnCtrlC: false
141
- });
145
+ const instanceRef = {};
146
+ const clearScreen = () => {
147
+ instanceRef.current?.clear();
148
+ };
149
+ const instance = render(
150
+ /* @__PURE__ */ jsx(ScreenTransitionClearContext.Provider, { value: clearScreen, children: /* @__PURE__ */ jsx(GoodbyeBoundary, { children: element }) }),
151
+ {
152
+ ...options,
153
+ exitOnCtrlC: false
154
+ }
155
+ );
156
+ instanceRef.current = instance;
157
+ return instance;
142
158
  }
143
- var GOODBYE_RENDER_DELAY_MS;
159
+ var GOODBYE_RENDER_DELAY_MS, ScreenTransitionClearContext;
144
160
  var init_render_with_goodbye = __esm({
145
161
  "packages/experience-design-system-cli/src/tui/render-with-goodbye.tsx"() {
146
162
  "use strict";
147
163
  GOODBYE_RENDER_DELAY_MS = 50;
164
+ ScreenTransitionClearContext = createContext(() => void 0);
148
165
  }
149
166
  });
150
167
 
@@ -764,7 +781,7 @@ function renderTokenCandidateSection(kind, entries) {
764
781
  ${formatTokenCandidateLines(entries)}`;
765
782
  }
766
783
  function buildPreamble(options) {
767
- const { skill, rawComponentsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline } = options;
784
+ const { skill, rawComponentsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline, componentAllowlistInline } = options;
768
785
  const sections = [];
769
786
  if (existingComponentsInline) {
770
787
  sections.push(`Existing components in the target Contentful space (JSON) \u2014 use for alignment, not as a hard filter:
@@ -776,6 +793,12 @@ ${existingComponentsInline}
776
793
  sections.push(`Existing design tokens in the target Contentful space (JSON) \u2014 prefer binding to these paths over inventing new ones:
777
794
  \`\`\`json
778
795
  ${existingTokensInline}
796
+ \`\`\``);
797
+ }
798
+ if (componentAllowlistInline) {
799
+ sections.push(`Known component names for slot allowed_components (hard allowlist; never use any other name):
800
+ \`\`\`json
801
+ ${componentAllowlistInline}
779
802
  \`\`\``);
780
803
  }
781
804
  if (rawComponentsInline) {
@@ -918,6 +941,7 @@ Rules:
918
941
  - Q2: is the value written straight into a style or attribute (\`rx={radius}\`, \`padding: \${padding}\`)? no \u2192 not \`token\`, apply the type rules.
919
942
  - Q3: is there a design-token reference at that use \u2014 a \`tokens.*\` parameter default, a \`tokenReference\`, an inline \`tokens.*\` / \`var(--*)\`? yes \u2192 \`token\` (cite both lines in "reason"); no \u2192 \`string\`. "Ambiguity resolves to \`enum\`" applies only when Q1\u2013Q3 cannot be answered from the source shown.
920
943
  - CSS design props (className, style, styles, positional/geometric props: top, bottom, left, right, rotation, offset, etc.) \u2192 classify_prop, cdf_type: "string", cdf_category: "design".
944
+ - For \`classify_slot.allowed_components\`, use only exact names from the hard allowlist provided below. Never invent a child from an import, JSX element, type name, icon name, or implementation helper; if a child is not listed, omit it.
921
945
  - On classify_component, "rationale" fields are operator-facing (read-only) but may surface in customer-facing exports. The "rationale.description" field is subject to the description content rules in the skill prompt (no internal initiative names). "rationale.props" and "rationale.slots" describe your reasoning about scope; "classify_slot.rationale" explains why each slot was kept.
922
946
  - On classify_prop, "reason" is REQUIRED and is the LLM's internal rationale \u2014 shown to the developer reviewing the import, never to end-users. "description" is the customer-facing copy and is subject to the description content rules in the skill prompt. Keep them distinct: "description" is short and customer-facing; "reason" explains your reasoning in detail.
923
947
  - You may emit prose lines (not starting with {) anywhere \u2014 they are ignored by the parser and serve as your reasoning log.`;
@@ -1952,8 +1976,7 @@ function hashComponentShape(component) {
1952
1976
  })),
1953
1977
  slots: component.slots.map((s) => ({
1954
1978
  name: s.name,
1955
- isDefault: s.isDefault,
1956
- allowedComponents: [...s.allowedComponents ?? []].sort()
1979
+ isDefault: s.isDefault
1957
1980
  }))
1958
1981
  };
1959
1982
  return createHash2("sha256").update(JSON.stringify(payload)).digest("hex");
@@ -2498,7 +2521,7 @@ function loadComponentRationale(db, sessionId2, componentName) {
2498
2521
  }))
2499
2522
  };
2500
2523
  }
2501
- function applyToolCalls(db, sessionId2, componentId, componentName, calls, incomingWarnings) {
2524
+ function applyToolCalls(db, sessionId2, componentId, componentName, calls, incomingWarnings, options = {}) {
2502
2525
  const now = (/* @__PURE__ */ new Date()).toISOString();
2503
2526
  const warnings = [...incomingWarnings];
2504
2527
  let classified = 0;
@@ -2633,9 +2656,18 @@ function applyToolCalls(db, sessionId2, componentId, componentName, calls, incom
2633
2656
  }
2634
2657
  if (call.allowed_components !== void 0) {
2635
2658
  deleteAllowedComponents.run(sessionId2, componentId, call.slot);
2636
- call.allowed_components.forEach(
2637
- (ac, i) => insertAllowedComponent.run(sessionId2, componentId, call.slot, ac, i)
2638
- );
2659
+ const allowedComponents = options.allowedComponentNames ? call.allowed_components.filter((allowedComponent) => options.allowedComponentNames.has(allowedComponent)) : call.allowed_components;
2660
+ if (options.allowedComponentNames) {
2661
+ const dropped = [
2662
+ ...new Set(call.allowed_components.filter((ac) => !options.allowedComponentNames.has(ac)))
2663
+ ];
2664
+ for (const allowedComponent of dropped) {
2665
+ warnings.push(
2666
+ `${componentName}: classify_slot '${call.slot}' \u2014 dropped unknown allowed component '${allowedComponent}'`
2667
+ );
2668
+ }
2669
+ }
2670
+ allowedComponents.forEach((ac, i) => insertAllowedComponent.run(sessionId2, componentId, call.slot, ac, i));
2639
2671
  }
2640
2672
  slots++;
2641
2673
  }
@@ -3185,9 +3217,7 @@ function storeCDFComponents(db, sessionId2, components) {
3185
3217
  }
3186
3218
  }
3187
3219
  const existingDefaults = new Map(
3188
- readExistingSlotDefaults.all(sessionId2, componentId).map(
3189
- (r) => [r.name, r.is_default]
3190
- )
3220
+ readExistingSlotDefaults.all(sessionId2, componentId).map((r) => [r.name, r.is_default])
3191
3221
  );
3192
3222
  deleteSlotAllowedComponents.run(sessionId2, componentId);
3193
3223
  deleteSlots.run(sessionId2, componentId);
@@ -3543,15 +3573,38 @@ function lookupCacheByEntity(db, entityType, entityId) {
3543
3573
  };
3544
3574
  }
3545
3575
  function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash = "") {
3576
+ storeCaches(db, [{ inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash }]);
3577
+ }
3578
+ function storeCaches(db, entries) {
3579
+ if (entries.length === 0) return;
3546
3580
  const now = (/* @__PURE__ */ new Date()).toISOString();
3547
- db.prepare(
3581
+ const insert = db.prepare(
3548
3582
  `INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
3549
3583
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3550
3584
  ON CONFLICT(input_hash, prompt_hash, entity_type, entity_id) DO UPDATE SET
3551
3585
  source_session_id = excluded.source_session_id,
3552
3586
  human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
3553
3587
  updated_at = excluded.updated_at`
3554
- ).run(inputHash, entityType, entityId, sourceSessionId, humanEdited ? 1 : 0, now, now, promptHash);
3588
+ );
3589
+ db.exec("BEGIN");
3590
+ try {
3591
+ for (const entry of entries) {
3592
+ insert.run(
3593
+ entry.inputHash,
3594
+ entry.entityType,
3595
+ entry.entityId,
3596
+ entry.sourceSessionId,
3597
+ entry.humanEdited ? 1 : 0,
3598
+ now,
3599
+ now,
3600
+ entry.promptHash ?? ""
3601
+ );
3602
+ }
3603
+ db.exec("COMMIT");
3604
+ } catch (e) {
3605
+ db.exec("ROLLBACK");
3606
+ throw e;
3607
+ }
3555
3608
  }
3556
3609
  function storeScannedFiles(db, sessionId2, filePaths) {
3557
3610
  db.exec("BEGIN");
@@ -3601,9 +3654,43 @@ function loadSlotCycles(db, sessionId2) {
3601
3654
  suggestedBreak: r.suggested_break_json ? JSON.parse(r.suggested_break_json) : null
3602
3655
  }));
3603
3656
  }
3604
- function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId) {
3605
- const now = (/* @__PURE__ */ new Date()).toISOString();
3657
+ function findUnknownSlotAllowedComponents(db, sessionId2, allowedComponentNames2, componentId) {
3658
+ const rows = db.prepare(
3659
+ `SELECT a.component_id, c.name AS component_name, a.slot_name, a.allowed_component
3660
+ FROM raw_slot_allowed_components a
3661
+ JOIN raw_components c ON c.session_id = a.session_id AND c.component_id = a.component_id
3662
+ WHERE a.session_id = ? AND (? IS NULL OR a.component_id = ?)
3663
+ ORDER BY a.component_id, a.slot_name, a.position`
3664
+ ).all(sessionId2, componentId ?? null, componentId ?? null);
3665
+ return rows.filter((row) => !allowedComponentNames2.has(row.allowed_component)).map((row) => ({
3666
+ componentId: row.component_id,
3667
+ componentName: row.component_name,
3668
+ slotName: row.slot_name,
3669
+ allowedComponent: row.allowed_component
3670
+ }));
3671
+ }
3672
+ function filterUnknownSlotAllowedComponents(db, sessionId2, allowedComponentNames2) {
3673
+ const unknown = findUnknownSlotAllowedComponents(db, sessionId2, allowedComponentNames2);
3674
+ if (unknown.length === 0) return unknown;
3606
3675
  db.exec("BEGIN");
3676
+ try {
3677
+ const remove = db.prepare(
3678
+ `DELETE FROM raw_slot_allowed_components
3679
+ WHERE session_id = ? AND component_id = ? AND slot_name = ? AND allowed_component = ?`
3680
+ );
3681
+ for (const row of unknown) {
3682
+ remove.run(sessionId2, row.componentId, row.slotName, row.allowedComponent);
3683
+ }
3684
+ db.exec("COMMIT");
3685
+ } catch (e) {
3686
+ db.exec("ROLLBACK");
3687
+ throw e;
3688
+ }
3689
+ return unknown;
3690
+ }
3691
+ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId, manageTransaction = true, options = {}) {
3692
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3693
+ if (manageTransaction) db.exec("BEGIN");
3607
3694
  try {
3608
3695
  const srcComp = db.prepare(
3609
3696
  `SELECT description, status, component_description_rationale, props_rationale, slots_rationale
@@ -3675,10 +3762,24 @@ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentI
3675
3762
  const insertSAC = db.prepare(
3676
3763
  `INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`
3677
3764
  );
3678
- for (const sac of srcSAC) {
3765
+ const allowedSAC = options.allowedComponentNames ? srcSAC.filter((sac) => options.allowedComponentNames.has(sac.allowed_component)) : srcSAC;
3766
+ for (const sac of allowedSAC) {
3679
3767
  insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
3680
3768
  }
3681
3769
  db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
3770
+ if (manageTransaction) db.exec("COMMIT");
3771
+ } catch (e) {
3772
+ if (manageTransaction) db.exec("ROLLBACK");
3773
+ throw e;
3774
+ }
3775
+ }
3776
+ function copyComponentsFromCache(db, entries, options = {}) {
3777
+ if (entries.length === 0) return;
3778
+ db.exec("BEGIN");
3779
+ try {
3780
+ for (const entry of entries) {
3781
+ copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false, options);
3782
+ }
3682
3783
  db.exec("COMMIT");
3683
3784
  } catch (e) {
3684
3785
  db.exec("ROLLBACK");
@@ -4821,43 +4922,85 @@ function printFallbackInstructions(options) {
4821
4922
  lines.push(` Re-run the generate command with the agent available, or use --dry-run to inspect the prompt.`);
4822
4923
  process.stderr.write(lines.join("\n") + "\n");
4823
4924
  }
4824
- async function runOneComponent(agent, model, db, sessionId2, component, tokensInline, tokenMapInline, index, total, verbose, noCache, skillPathOverride, skillContentOverride, promptHash, existingContentfulEntities, existingTokensInline) {
4825
- const pos = c.dim(`[${index + 1}/${total}]`);
4826
- if (!noCache) {
4827
- const inputHash = hashComponentShape(component);
4828
- const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
4829
- if (cached) {
4830
- copyComponentFromCache(db, cached.sourceSessionId, sessionId2, component.component_id);
4831
- process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.green("cached")}
4832
- `);
4833
- return {
4834
- componentName: component.name,
4835
- classified: 0,
4836
- excluded: 0,
4837
- slots: 0,
4838
- warnings: [],
4839
- failed: false,
4840
- cached: true,
4841
- renamedSlotsCount: 0
4842
- };
4843
- }
4844
- const pinned = lookupCacheByEntity(db, "component", component.component_id);
4845
- if (pinned?.humanEdited) {
4846
- copyComponentFromCache(db, pinned.sourceSessionId, sessionId2, component.component_id);
4847
- process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.cyan("pinned (human-edited)")}
4925
+ function createCachedComponentResult(componentName, warnings = []) {
4926
+ return {
4927
+ componentName,
4928
+ classified: 0,
4929
+ excluded: 0,
4930
+ slots: 0,
4931
+ warnings,
4932
+ failed: false,
4933
+ cached: true,
4934
+ renamedSlotsCount: 0
4935
+ };
4936
+ }
4937
+ async function showGenerateView(result) {
4938
+ if (process.stdout.isTTY) {
4939
+ const { waitUntilExit } = renderWithGoodbye(
4940
+ createElement(GenerateView, {
4941
+ result,
4942
+ onExit: () => void exitWithAnalytics(0)
4943
+ })
4944
+ );
4945
+ await waitUntilExit();
4946
+ return;
4947
+ }
4948
+ process.stdout.write(
4949
+ `generate complete
4950
+ skill: ${result.skill}
4951
+ agent: ${result.agent}
4952
+ session=${result.sessionId}
4953
+ `
4954
+ );
4955
+ await exitWithAnalytics(0);
4956
+ }
4957
+ function writeCachedComponentStatus(position, componentName, pinned) {
4958
+ const status = pinned ? c.cyan("pinned (human-edited)") : c.green("cached");
4959
+ process.stderr.write(` ${position} ${c.bold(componentName)} ${status}
4848
4960
  `);
4849
- return {
4850
- componentName: component.name,
4851
- classified: 0,
4852
- excluded: 0,
4853
- slots: 0,
4854
- warnings: [`${component.name}: source changed but human edits preserved`],
4855
- failed: false,
4856
- cached: true,
4857
- renamedSlotsCount: 0
4858
- };
4859
- }
4961
+ }
4962
+ function normalizeComponentForCache(component) {
4963
+ const slots = component.slots.map((slot, index, allSlots) => ({
4964
+ ...slot,
4965
+ name: slot.name.trim() || (allSlots.length === 1 ? "children" : `slot_${index}`)
4966
+ }));
4967
+ return { ...component, slots };
4968
+ }
4969
+ function lookupComponentCache(db, component, promptHash) {
4970
+ const normalized = normalizeComponentForCache(component);
4971
+ const inputHash = hashComponentShape(normalized);
4972
+ const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
4973
+ if (cached) return cached;
4974
+ const legacyInputHash = hashComponentShape(component);
4975
+ return legacyInputHash === inputHash ? null : lookupCache(db, legacyInputHash, "component", component.component_id, promptHash);
4976
+ }
4977
+ function resolveComponentCache(db, component, promptHash, allowedComponentNames2) {
4978
+ const cached = lookupComponentCache(db, component, promptHash);
4979
+ if (cached && findUnknownSlotAllowedComponents(db, cached.sourceSessionId, allowedComponentNames2, component.component_id).length === 0) {
4980
+ return { entry: cached, humanEdited: cached.humanEdited };
4860
4981
  }
4982
+ const pinned = lookupCacheByEntity(db, "component", component.component_id);
4983
+ return pinned?.humanEdited && findUnknownSlotAllowedComponents(db, pinned.sourceSessionId, allowedComponentNames2, component.component_id).length === 0 ? { entry: pinned, humanEdited: true } : null;
4984
+ }
4985
+ async function runOneComponent(options, component, index, total) {
4986
+ const {
4987
+ agent,
4988
+ model,
4989
+ db,
4990
+ sessionId: sessionId2,
4991
+ tokensInline,
4992
+ tokenMapInline,
4993
+ verbose,
4994
+ noCache,
4995
+ skillPathOverride,
4996
+ skillContentOverride,
4997
+ promptHash,
4998
+ existingContentfulEntities,
4999
+ existingTokensInline,
5000
+ precomputedCachedNames,
5001
+ allowedComponentNames: allowedComponentNames2
5002
+ } = options;
5003
+ const pos = c.dim(`[${index + 1}/${total}]`);
4861
5004
  const { renames, warnings: renameWarnings } = renameEmptySlots(
4862
5005
  db,
4863
5006
  sessionId2,
@@ -4872,6 +5015,47 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
4872
5015
  for (const w of renameWarnings) process.stderr.write(` ${c.yellow("\u26A0")} ${w}
4873
5016
  `);
4874
5017
  }
5018
+ effectiveSlots = effectiveSlots.map((slot) => {
5019
+ if (!slot.allowedComponents) return slot;
5020
+ return {
5021
+ ...slot,
5022
+ allowedComponents: slot.allowedComponents.filter((name) => allowedComponentNames2.has(name))
5023
+ };
5024
+ });
5025
+ const cacheComponent = normalizeComponentForCache(component);
5026
+ if (!noCache && precomputedCachedNames.has(component.name)) {
5027
+ writeCachedComponentStatus(pos, component.name, false);
5028
+ return createCachedComponentResult(component.name);
5029
+ }
5030
+ if (!noCache) {
5031
+ const inputHash = hashComponentShape(cacheComponent);
5032
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
5033
+ if (resolution && !resolution.humanEdited) {
5034
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
5035
+ allowedComponentNames: allowedComponentNames2
5036
+ });
5037
+ storeCache(
5038
+ db,
5039
+ inputHash,
5040
+ "component",
5041
+ component.component_id,
5042
+ resolution.entry.sourceSessionId,
5043
+ resolution.entry.humanEdited,
5044
+ promptHash
5045
+ );
5046
+ writeCachedComponentStatus(pos, component.name, false);
5047
+ return createCachedComponentResult(component.name);
5048
+ }
5049
+ if (resolution?.humanEdited) {
5050
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
5051
+ allowedComponentNames: allowedComponentNames2
5052
+ });
5053
+ writeCachedComponentStatus(pos, component.name, true);
5054
+ return createCachedComponentResult(component.name, [
5055
+ `${component.name}: source changed but human edits preserved`
5056
+ ]);
5057
+ }
5058
+ }
4875
5059
  const rawComponentsInline = JSON.stringify(
4876
5060
  [
4877
5061
  {
@@ -4904,7 +5088,8 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
4904
5088
  skillPathOverride,
4905
5089
  skillContentOverride,
4906
5090
  existingComponentsInline,
4907
- existingTokensInline
5091
+ existingTokensInline,
5092
+ componentAllowlistInline: JSON.stringify([...allowedComponentNames2].sort())
4908
5093
  });
4909
5094
  const maxAttempts = 2;
4910
5095
  let lastError = "";
@@ -4939,9 +5124,11 @@ ${outputBuf}`);
4939
5124
  lastError = describeAgentFailure(result);
4940
5125
  continue;
4941
5126
  }
4942
- const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings);
5127
+ const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings, {
5128
+ allowedComponentNames: allowedComponentNames2
5129
+ });
4943
5130
  if (!noCache) {
4944
- const inputHash = hashComponentShape(component);
5131
+ const inputHash = hashComponentShape(cacheComponent);
4945
5132
  storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
4946
5133
  }
4947
5134
  return {
@@ -4965,7 +5152,7 @@ ${outputBuf}`);
4965
5152
  renamedSlotsCount: renames.length
4966
5153
  };
4967
5154
  }
4968
- async function runAllComponents(agent, model, db, sessionId2, components, tokensInline, tokenMapInline, verbose, noCache, skillPathOverride, skillContentOverride, promptHash, existingContentfulEntities, existingTokensInline) {
5155
+ async function runAllComponents(options, components) {
4969
5156
  const concurrency = Number(process.env.EDS_GENERATE_CONCURRENCY ?? DEFAULT_COMPONENT_CONCURRENCY);
4970
5157
  process.stderr.write(
4971
5158
  `Categorizing ${c.bold(String(components.length))} component${components.length === 1 ? "" : "s"}` + c.dim(` (concurrency: ${concurrency})`) + "\n"
@@ -4976,24 +5163,7 @@ async function runAllComponents(agent, model, db, sessionId2, components, tokens
4976
5163
  async function worker() {
4977
5164
  while (next < components.length) {
4978
5165
  const i = next++;
4979
- results[i] = await runOneComponent(
4980
- agent,
4981
- model,
4982
- db,
4983
- sessionId2,
4984
- components[i],
4985
- tokensInline,
4986
- tokenMapInline,
4987
- i,
4988
- components.length,
4989
- verbose,
4990
- noCache,
4991
- skillPathOverride,
4992
- skillContentOverride,
4993
- promptHash,
4994
- existingContentfulEntities,
4995
- existingTokensInline
4996
- );
5166
+ results[i] = await runOneComponent(options, components[i], i, components.length);
4997
5167
  completed += 1;
4998
5168
  process.stderr.write(`${formatGenerateProgressLine(completed, components.length, results[i].componentName)}
4999
5169
  `);
@@ -5021,6 +5191,15 @@ async function loadAcceptedNames(sessionId2) {
5021
5191
  return null;
5022
5192
  }
5023
5193
  }
5194
+ function parsePrecomputedCachedNames(value) {
5195
+ if (!value) return /* @__PURE__ */ new Set();
5196
+ try {
5197
+ const parsed = JSON.parse(value);
5198
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : []);
5199
+ } catch {
5200
+ return /* @__PURE__ */ new Set();
5201
+ }
5202
+ }
5024
5203
  async function runGenerateSkill(skill, opts, verbose = false) {
5025
5204
  const savedCreds = await readExperiencesCredentials();
5026
5205
  const agentName = opts.agent ?? savedCreds.agent;
@@ -5086,6 +5265,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
5086
5265
  }
5087
5266
  let sessionId2;
5088
5267
  let allComponents;
5268
+ let allowedComponentNames2;
5089
5269
  if (skill === "components") {
5090
5270
  sessionId2 = await resolveSessionId(opts.session);
5091
5271
  await bindAnalyticsSessionId(sessionId2);
@@ -5099,6 +5279,27 @@ async function runGenerateSkill(skill, opts, verbose = false) {
5099
5279
  if (allComponents.length === 0) {
5100
5280
  die(`Error: session '${sessionId2}' has no raw components. Run analyze extract first.`);
5101
5281
  }
5282
+ allowedComponentNames2 = /* @__PURE__ */ new Set([
5283
+ ...allComponents.map((component) => component.name),
5284
+ ...(existingContentfulEntities?.components ?? []).map((component) => component.name)
5285
+ ]);
5286
+ if (!opts.dryRun) {
5287
+ const dbForReferences = openPipelineDb();
5288
+ try {
5289
+ const dropped = filterUnknownSlotAllowedComponents(dbForReferences, sessionId2, allowedComponentNames2);
5290
+ for (const reference of dropped) {
5291
+ process.stderr.write(
5292
+ `Warning: ${reference.componentName}: slot '${reference.slotName}' \u2014 dropped unknown allowed component '${reference.allowedComponent}'
5293
+ `
5294
+ );
5295
+ }
5296
+ if (dropped.length > 0) {
5297
+ allComponents = loadRawComponents(dbForReferences, sessionId2, acceptedNames ?? void 0);
5298
+ }
5299
+ } finally {
5300
+ dbForReferences.close();
5301
+ }
5302
+ }
5102
5303
  if (acceptedNames) {
5103
5304
  process.stderr.write(`Scope: ${allComponents.length} accepted component(s) from analyze select
5104
5305
  `);
@@ -5158,7 +5359,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
5158
5359
  skillPathOverride: generatePromptPath,
5159
5360
  skillContentOverride: generatePrompt,
5160
5361
  existingComponentsInline: dryRunExistingComponentsInline,
5161
- existingTokensInline: skill === "components" ? existingTokensInline : void 0
5362
+ existingTokensInline: skill === "components" ? existingTokensInline : void 0,
5363
+ componentAllowlistInline: skill === "components" && allowedComponentNames2 ? JSON.stringify([...allowedComponentNames2].sort()) : void 0
5162
5364
  });
5163
5365
  process.stdout.write(prompt + "\n");
5164
5366
  await exitWithAnalytics(0);
@@ -5175,6 +5377,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
5175
5377
  if (skill === "components" && allComponents && sessionId2) {
5176
5378
  const db = openPipelineDb();
5177
5379
  let componentResults;
5380
+ const precomputedCachedNames = parsePrecomputedCachedNames(opts.cachedComponents);
5178
5381
  try {
5179
5382
  const existingContentfulEntitiesHashInputs = [];
5180
5383
  if (existingTokensInline) existingContentfulEntitiesHashInputs.push(hashContent(existingTokensInline));
@@ -5186,21 +5389,71 @@ async function runGenerateSkill(skill, opts, verbose = false) {
5186
5389
  existingContentfulEntitiesHashInputs,
5187
5390
  generatePrompt
5188
5391
  );
5392
+ if (opts.cacheStatus) {
5393
+ const cacheEnabled = opts.cache !== false && process.env.EDS_NO_CACHE !== "1";
5394
+ const cachedComponents = cacheEnabled ? allComponents.flatMap((component) => {
5395
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
5396
+ return resolution ? [{ component, resolution }] : [];
5397
+ }) : [];
5398
+ if (cacheEnabled && opts.restoreCache) {
5399
+ const restorations = [];
5400
+ const rekeyed = [];
5401
+ const componentStatus = db.prepare(
5402
+ "SELECT status FROM raw_components WHERE session_id = ? AND component_id = ?"
5403
+ );
5404
+ for (const { component, resolution } of cachedComponents) {
5405
+ const current = componentStatus.get(sessionId2, component.component_id);
5406
+ if (current?.status === "generated") continue;
5407
+ renameEmptySlots(db, sessionId2, component.component_id, component.name, component.slots.length);
5408
+ restorations.push({
5409
+ sourceSessionId: resolution.entry.sourceSessionId,
5410
+ targetSessionId: sessionId2,
5411
+ componentId: component.component_id
5412
+ });
5413
+ rekeyed.push({ component, cached: resolution.entry });
5414
+ }
5415
+ copyComponentsFromCache(db, restorations, { allowedComponentNames: allowedComponentNames2 });
5416
+ storeCaches(
5417
+ db,
5418
+ rekeyed.map(({ component, cached }) => ({
5419
+ inputHash: hashComponentShape(normalizeComponentForCache(component)),
5420
+ entityType: "component",
5421
+ entityId: component.component_id,
5422
+ sourceSessionId: cached.sourceSessionId,
5423
+ humanEdited: cached.humanEdited,
5424
+ promptHash
5425
+ }))
5426
+ );
5427
+ }
5428
+ const fullyCached = cacheEnabled && cachedComponents.length === allComponents.length;
5429
+ process.stdout.write(`cache-status=${fullyCached ? "hit" : "miss"}
5430
+ `);
5431
+ process.stdout.write(
5432
+ `cache-components=${JSON.stringify(cachedComponents.map(({ component }) => component.name))}
5433
+ `
5434
+ );
5435
+ await exitWithAnalytics(0);
5436
+ return;
5437
+ }
5189
5438
  componentResults = await runAllComponents(
5190
- agent,
5191
- model,
5192
- db,
5193
- sessionId2,
5194
- allComponents,
5195
- tokensInline,
5196
- tokenMapInline,
5197
- verbose,
5198
- opts.cache === false || process.env.EDS_NO_CACHE === "1",
5199
- generatePromptPath,
5200
- generatePrompt,
5201
- promptHash,
5202
- existingContentfulEntities,
5203
- existingTokensInline
5439
+ {
5440
+ agent,
5441
+ model,
5442
+ db,
5443
+ sessionId: sessionId2,
5444
+ tokensInline,
5445
+ tokenMapInline,
5446
+ verbose,
5447
+ noCache: opts.cache === false || process.env.EDS_NO_CACHE === "1",
5448
+ skillPathOverride: generatePromptPath,
5449
+ skillContentOverride: generatePrompt,
5450
+ promptHash,
5451
+ existingContentfulEntities,
5452
+ existingTokensInline,
5453
+ precomputedCachedNames,
5454
+ allowedComponentNames: allowedComponentNames2
5455
+ },
5456
+ allComponents
5204
5457
  );
5205
5458
  } finally {
5206
5459
  db.close();
@@ -5275,20 +5528,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
5275
5528
  `
5276
5529
  );
5277
5530
  db.close();
5278
- const viewResult2 = { skill, agent, sessionId: sessionId2 ?? "" };
5279
- if (process.stdout.isTTY) {
5280
- const { waitUntilExit } = renderWithGoodbye(
5281
- createElement(GenerateView, { result: viewResult2, onExit: () => void exitWithAnalytics(0) })
5282
- );
5283
- await waitUntilExit();
5284
- } else {
5285
- process.stdout.write(`generate complete
5286
- skill: ${skill}
5287
- agent: ${agent}
5288
- session=${sessionId2}
5289
- `);
5290
- await exitWithAnalytics(0);
5291
- }
5531
+ await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
5292
5532
  return;
5293
5533
  }
5294
5534
  }
@@ -5344,27 +5584,7 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
5344
5584
  db.close();
5345
5585
  }
5346
5586
  }
5347
- const viewResult = {
5348
- skill,
5349
- agent,
5350
- sessionId: sessionId2 ?? ""
5351
- };
5352
- if (process.stdout.isTTY) {
5353
- const { waitUntilExit } = renderWithGoodbye(
5354
- createElement(GenerateView, {
5355
- result: viewResult,
5356
- onExit: () => void exitWithAnalytics(0)
5357
- })
5358
- );
5359
- await waitUntilExit();
5360
- } else {
5361
- process.stdout.write(`generate complete
5362
- skill: ${skill}
5363
- agent: ${agent}
5364
- session=${sessionId2 ?? ""}
5365
- `);
5366
- await exitWithAnalytics(0);
5367
- }
5587
+ await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
5368
5588
  }
5369
5589
  function addAgentFlags(cmd) {
5370
5590
  return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent").option(
@@ -5386,6 +5606,12 @@ function registerInternalGenerateCommand(program) {
5386
5606
  "--existing-entities-path <path>",
5387
5607
  "Path to the .existing-entities.json file written after CMA credentials are supplied. When present, per-component prompts include a summary of the target space so classifications can align to existing prop names/tokens. Missing/malformed files are treated as no-op."
5388
5608
  );
5609
+ componentsCmd.option("--cache-status", "Check whether all selected component definitions are cached");
5610
+ componentsCmd.option("--restore-cache", "Restore cached component definitions into the current session");
5611
+ componentsCmd.option(
5612
+ "--cached-components <json>",
5613
+ "Component names already restored by a prior cache-status lookup; skip per-component cache lookup"
5614
+ );
5389
5615
  addAgentFlags(componentsCmd).action(async (opts) => {
5390
5616
  await runGenerateSkill("components", opts, opts.verbose ?? false);
5391
5617
  });
@@ -7145,7 +7371,7 @@ var init_contentful_urls = __esm({
7145
7371
  });
7146
7372
 
7147
7373
  // packages/experience-design-system-cli/src/apply/tui/ServerApplyView.tsx
7148
- import { Box as Box3, Text as Text4, useInput } from "ink";
7374
+ import { Box as Box3, Text as Text4, useInput as useInput2 } from "ink";
7149
7375
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
7150
7376
  function ServerPreviewConfirm({
7151
7377
  preview,
@@ -7155,7 +7381,7 @@ function ServerPreviewConfirm({
7155
7381
  onConfirm,
7156
7382
  onCancel
7157
7383
  }) {
7158
- useInput((input, key) => {
7384
+ useInput2((input, key) => {
7159
7385
  if (key.return) onConfirm(breakingWithImpact);
7160
7386
  else if (key.escape || input === "q") onCancel();
7161
7387
  });
@@ -7184,7 +7410,7 @@ function ServerApplyProgress({
7184
7410
  operationId,
7185
7411
  error
7186
7412
  }) {
7187
- useInput((_input, key) => {
7413
+ useInput2((_input, key) => {
7188
7414
  if (status === "error" && (key.escape || _input === "q")) process.exit(1);
7189
7415
  });
7190
7416
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
@@ -7212,7 +7438,7 @@ function ServerApplyProgress({
7212
7438
  ] });
7213
7439
  }
7214
7440
  function ServerApplyDone({ operation, spaceId, environmentId, host }) {
7215
- useInput((input, key) => {
7441
+ useInput2((input, key) => {
7216
7442
  if (key.escape || input === "q") {
7217
7443
  process.exit(operation.sys.status === "succeeded" ? 0 : 1);
7218
7444
  }
@@ -7326,7 +7552,6 @@ var init_terminal_capabilities = __esm({
7326
7552
 
7327
7553
  // packages/experience-design-system-cli/src/apply/command.ts
7328
7554
  import { createElement as createElement2 } from "react";
7329
- import { render as render2 } from "ink";
7330
7555
  import { readFile as readFile9, readdir as readdir2, stat as stat2 } from "node:fs/promises";
7331
7556
  import { join as join8 } from "node:path";
7332
7557
  import {
@@ -7730,11 +7955,11 @@ function registerApplyCommand(program) {
7730
7955
  environmentId,
7731
7956
  host,
7732
7957
  acknowledgeBreakingChanges: acknowledge,
7733
- rerender: (element) => instance.rerender(element),
7958
+ rerender: (element) => instance.rerender(createElement2(GoodbyeBoundary, null, element)),
7734
7959
  onDone: resolvePromise
7735
7960
  });
7736
7961
  };
7737
- const instance = render2(
7962
+ const instance = renderWithGoodbye(
7738
7963
  createElement2(ServerPreviewConfirm, {
7739
7964
  preview,
7740
7965
  spaceId,
@@ -7756,6 +7981,7 @@ var IGNORE_TOKEN_DIRS;
7756
7981
  var init_command2 = __esm({
7757
7982
  "packages/experience-design-system-cli/src/apply/command.ts"() {
7758
7983
  "use strict";
7984
+ init_render_with_goodbye();
7759
7985
  init_api_client();
7760
7986
  init_error_parser();
7761
7987
  init_cycle_detection();
@@ -10176,7 +10402,7 @@ var init_SpaceEnvironment = __esm({
10176
10402
  });
10177
10403
 
10178
10404
  // packages/experience-design-system-cli/src/import/tui/preview-confirmation-input.ts
10179
- import { useCallback } from "react";
10405
+ import { useCallback as useCallback2 } from "react";
10180
10406
  function handlePreviewConfirmationInput(_input, key, options) {
10181
10407
  if (key.return) {
10182
10408
  options.onConfirm(options.breakingWithImpact);
@@ -10185,7 +10411,7 @@ function handlePreviewConfirmationInput(_input, key, options) {
10185
10411
  return false;
10186
10412
  }
10187
10413
  function usePreviewConfirmationInput(breakingWithImpact, onConfirm) {
10188
- return useCallback(
10414
+ return useCallback2(
10189
10415
  (input, key) => handlePreviewConfirmationInput(input, key, {
10190
10416
  breakingWithImpact,
10191
10417
  onConfirm
@@ -19517,20 +19743,20 @@ var init_SearchMatchSummary = __esm({
19517
19743
  });
19518
19744
 
19519
19745
  // packages/experience-design-system-cli/src/import/tui/hooks/useOverlayPanel.ts
19520
- import { useCallback as useCallback2, useState as useState13 } from "react";
19746
+ import { useCallback as useCallback3, useState as useState13 } from "react";
19521
19747
  function useOverlayPanel(opts) {
19522
19748
  const { toggleKey, onClose } = opts;
19523
19749
  const [isOpen, setIsOpen] = useState13(false);
19524
- const open = useCallback2(() => {
19750
+ const open = useCallback3(() => {
19525
19751
  setIsOpen(true);
19526
19752
  }, []);
19527
- const close = useCallback2(() => {
19753
+ const close = useCallback3(() => {
19528
19754
  setIsOpen((prev) => {
19529
19755
  if (prev && onClose) onClose();
19530
19756
  return false;
19531
19757
  });
19532
19758
  }, [onClose]);
19533
- const handleInput = useCallback2(
19759
+ const handleInput = useCallback3(
19534
19760
  (input, key) => {
19535
19761
  if (!isOpen) return false;
19536
19762
  if (input === toggleKey || key.escape === true) {
@@ -24254,7 +24480,7 @@ var init_useFinalizePreview = __esm({
24254
24480
  });
24255
24481
 
24256
24482
  // packages/experience-design-system-cli/src/import/tui/hooks/useReviewSession.ts
24257
- import { useCallback as useCallback3, useEffect as useEffect10, useRef as useRef6, useState as useState17 } from "react";
24483
+ import { useCallback as useCallback4, useEffect as useEffect10, useRef as useRef6, useState as useState17 } from "react";
24258
24484
  function loadReviewSessionState({
24259
24485
  extractSessionId,
24260
24486
  tokenSessionId,
@@ -24304,7 +24530,7 @@ function useReviewSession({
24304
24530
  const [loading, setLoading] = useState17(true);
24305
24531
  const [loadError, setLoadError] = useState17(null);
24306
24532
  const [availableTokens, setAvailableTokens] = useState17([]);
24307
- const loadInitialState = useCallback3(async () => {
24533
+ const loadInitialState = useCallback4(async () => {
24308
24534
  const result = loadSession();
24309
24535
  if (result.error || !tokensPath) return { result, catalog: result.tokens };
24310
24536
  const catalog = (await readTokensFromPath("tokens", tokensPath)).map((token) => ({
@@ -25005,7 +25231,7 @@ var init_preview_annotations = __esm({
25005
25231
  });
25006
25232
 
25007
25233
  // packages/experience-design-system-cli/src/import/tui/useLivePreview.ts
25008
- import { useCallback as useCallback4, useEffect as useEffect12, useRef as useRef8, useState as useState20 } from "react";
25234
+ import { useCallback as useCallback5, useEffect as useEffect12, useRef as useRef8, useState as useState20 } from "react";
25009
25235
  function useLivePreview(opts) {
25010
25236
  const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
25011
25237
  const [status, setStatus] = useState20("idle");
@@ -25030,7 +25256,7 @@ function useLivePreview(opts) {
25030
25256
  }
25031
25257
  };
25032
25258
  }, []);
25033
- const trigger = useCallback4(() => {
25259
+ const trigger = useCallback5(() => {
25034
25260
  const current = optsRef.current;
25035
25261
  if (!current.enabled) return;
25036
25262
  if (disabledRef.current) return;
@@ -25103,7 +25329,7 @@ var init_useLivePreview = __esm({
25103
25329
  });
25104
25330
 
25105
25331
  // packages/experience-design-system-cli/src/import/tui/hooks/useReviewPreview.ts
25106
- import { useCallback as useCallback5, useEffect as useEffect13, useState as useState21 } from "react";
25332
+ import { useCallback as useCallback6, useEffect as useEffect13, useState as useState21 } from "react";
25107
25333
  function useReviewPreview({
25108
25334
  components,
25109
25335
  loading,
@@ -25119,7 +25345,7 @@ function useReviewPreview({
25119
25345
  }) {
25120
25346
  const [previewAnnotations, setPreviewAnnotations] = useState21(/* @__PURE__ */ new Map());
25121
25347
  const [removedComponents, setRemovedComponents] = useState21([]);
25122
- const handleResult = useCallback5(
25348
+ const handleResult = useCallback6(
25123
25349
  (response) => {
25124
25350
  if (!response) return;
25125
25351
  setPreviewAnnotations(
@@ -25208,7 +25434,7 @@ var init_LivePreviewSummary = __esm({
25208
25434
  });
25209
25435
 
25210
25436
  // packages/experience-design-system-cli/src/import/tui/steps/GenerateReviewStep.tsx
25211
- import { useCallback as useCallback6, useEffect as useEffect14, useMemo as useMemo3, useRef as useRef9, useState as useState22 } from "react";
25437
+ import { useCallback as useCallback7, useEffect as useEffect14, useMemo as useMemo3, useRef as useRef9, useState as useState22 } from "react";
25212
25438
  import figures6 from "figures";
25213
25439
  import { Box as Box41, Text as Text45 } from "ink";
25214
25440
  import { Fragment as Fragment18, jsx as jsx49, jsxs as jsxs42 } from "react/jsx-runtime";
@@ -25374,10 +25600,10 @@ function GenerateReviewStepView({
25374
25600
  }) {
25375
25601
  const { columns: terminalWidth, rows: terminalRows } = useTerminalSize();
25376
25602
  const [slotCycles, setSlotCycles] = useState22([]);
25377
- const setLoadedSlotCycles = useCallback6((cycles) => {
25603
+ const setLoadedSlotCycles = useCallback7((cycles) => {
25378
25604
  setSlotCycles(cycles ?? []);
25379
25605
  }, []);
25380
- const loadSessionState = useCallback6(
25606
+ const loadSessionState = useCallback7(
25381
25607
  () => loadReviewSessionState({
25382
25608
  extractSessionId,
25383
25609
  tokenSessionId,
@@ -25999,7 +26225,7 @@ function GenerateReviewStepView({
25999
26225
  }
26000
26226
  return;
26001
26227
  }
26002
- if (input === "c" && cycleRows.length > 0) {
26228
+ if (input === "c" && !key.ctrl && !key.meta && cycleRows.length > 0) {
26003
26229
  cyclePanel.open();
26004
26230
  setCyclePanelScroll(0);
26005
26231
  setCyclesCursor(0);
@@ -26587,10 +26813,31 @@ var init_GenerateReviewStep = __esm({
26587
26813
 
26588
26814
  // packages/experience-design-system-cli/src/import/tui/runScopeGate.ts
26589
26815
  async function runScopeGate(opts) {
26816
+ const startedAt = Date.now();
26817
+ getDebugLogger().event("wizard", "scope-decision-persistence.start", {
26818
+ sessionId: opts.sessionId,
26819
+ acceptedCount: opts.decisions.accepted.length,
26820
+ rejectedCount: opts.decisions.rejected.length
26821
+ });
26590
26822
  const db = openPipelineDb();
26591
26823
  try {
26592
26824
  applyScopeDecisions(db, opts.sessionId, opts.decisions);
26593
26825
  await writeScopeDecisionsSnapshot(db, opts.sessionId, opts.decisions);
26826
+ getDebugLogger().event("wizard", "scope-decision-persistence.complete", {
26827
+ sessionId: opts.sessionId,
26828
+ acceptedCount: opts.decisions.accepted.length,
26829
+ rejectedCount: opts.decisions.rejected.length,
26830
+ durationMs: Date.now() - startedAt
26831
+ });
26832
+ } catch (error) {
26833
+ getDebugLogger().event("wizard", "scope-decision-persistence.error", {
26834
+ sessionId: opts.sessionId,
26835
+ acceptedCount: opts.decisions.accepted.length,
26836
+ rejectedCount: opts.decisions.rejected.length,
26837
+ durationMs: Date.now() - startedAt,
26838
+ error: error instanceof Error ? error.message : String(error)
26839
+ });
26840
+ throw error;
26594
26841
  } finally {
26595
26842
  db.close();
26596
26843
  }
@@ -26604,6 +26851,7 @@ var init_runScopeGate = __esm({
26604
26851
  "packages/experience-design-system-cli/src/import/tui/runScopeGate.ts"() {
26605
26852
  "use strict";
26606
26853
  init_persistence();
26854
+ init_debug_logger();
26607
26855
  init_db();
26608
26856
  }
26609
26857
  });
@@ -26857,6 +27105,10 @@ function buildGenerateComponentsArgs(opts) {
26857
27105
  if (opts.generatePromptPath) args.push("--generate-prompt-path", opts.generatePromptPath);
26858
27106
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
26859
27107
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
27108
+ if (opts.restoreCache) args.push("--restore-cache");
27109
+ if (opts.cachedComponents && opts.cachedComponents.length > 0) {
27110
+ args.push("--cached-components", JSON.stringify(opts.cachedComponents));
27111
+ }
26860
27112
  return args;
26861
27113
  }
26862
27114
  function buildGenerateTokensArgs(opts) {
@@ -26940,6 +27192,17 @@ function logStep(entry) {
26940
27192
  appendFileSync2(WIZARD_LOG, line);
26941
27193
  getDebugLogger().event("wizard", "step", entry);
26942
27194
  }
27195
+ function startWizardTimer(name, payload = {}) {
27196
+ const startedAt = Date.now();
27197
+ getDebugLogger().event("wizard", `${name}.start`, payload);
27198
+ return startedAt;
27199
+ }
27200
+ function finishWizardTimer(name, startedAt, payload = {}) {
27201
+ getDebugLogger().event("wizard", `${name}.complete`, {
27202
+ ...payload,
27203
+ durationMs: Date.now() - startedAt
27204
+ });
27205
+ }
26943
27206
  function WizardApp({
26944
27207
  initialSpaceId = "",
26945
27208
  initialEnvironmentId = "master",
@@ -26962,6 +27225,7 @@ function WizardApp({
26962
27225
  const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
26963
27226
  const credentialKey = (spaceId, environmentId, cmaToken, hostValue) => [spaceId.trim(), environmentId.trim(), cmaToken.trim(), resolveWizardHost(hostValue)].join("\0");
26964
27227
  const { columns: terminalWidth } = useTerminalSize();
27228
+ const clearScreenOnTransition = useScreenTransitionClear();
26965
27229
  const logInit = useRef10(false);
26966
27230
  const initialAgentAuthRef = useRef10(initialAgentAuth ?? null);
26967
27231
  if (!logInit.current) {
@@ -26975,6 +27239,8 @@ function WizardApp({
26975
27239
  });
26976
27240
  const allowEmptyDeleteAllRef = useRef10(false);
26977
27241
  const generateChildRef = useRef10(null);
27242
+ const generationCachePreflightRef = useRef10(null);
27243
+ const selectionToReviewStartedAtRef = useRef10(null);
26978
27244
  const credentialsValidationIdRef = useRef10(0);
26979
27245
  const pendingCredentialValidationRef = useRef10(null);
26980
27246
  const validatedCredentialsKeyRef = useRef10(null);
@@ -26982,7 +27248,9 @@ function WizardApp({
26982
27248
  const extractPromiseRef = useRef10(null);
26983
27249
  const extractStartedRef = useRef10(false);
26984
27250
  const credentialsReadyRef = useRef10(false);
27251
+ const agentAuthVerifiedRef = useRef10(false);
26985
27252
  const scopeGateCompletedRef = useRef10(false);
27253
+ const scopeComponentsRef = useRef10(null);
26986
27254
  const existingEntitiesPromiseRef = useRef10(null);
26987
27255
  const generatePromiseRef = useRef10(null);
26988
27256
  const rawTokensEntryReady = !!initialRawTokensPath;
@@ -27052,7 +27320,10 @@ function WizardApp({
27052
27320
  tokensPath: state.tokensPath
27053
27321
  };
27054
27322
  }, [state.extractSessionId, state.tokensPath]);
27055
- const update = (partial) => {
27323
+ const update = (partial, options = {}) => {
27324
+ if (partial.step !== void 0 && partial.step !== state.step && options.clearScreen !== false) {
27325
+ clearScreenOnTransition();
27326
+ }
27056
27327
  const sanitized = { ...partial };
27057
27328
  if (sanitized.serverPreview) {
27058
27329
  const p = sanitized.serverPreview;
@@ -27099,12 +27370,41 @@ function WizardApp({
27099
27370
  logStep({ update: sanitized });
27100
27371
  setState((prev) => ({ ...prev, ...partial }));
27101
27372
  };
27102
- const runAgentAuthCheck = async (nextStep) => {
27373
+ const finishSelectionToReviewTimer = (mode) => {
27374
+ const startedAt = selectionToReviewStartedAtRef.current;
27375
+ if (startedAt === null) return;
27376
+ selectionToReviewStartedAtRef.current = null;
27377
+ finishWizardTimer("selection-to-review", startedAt, { mode });
27378
+ };
27379
+ const runAgentAuthCheck = async (nextStep, deferStep = false) => {
27380
+ const timer = startWizardTimer("agent-auth-validation", {
27381
+ agent: state.agent,
27382
+ deferred: deferStep,
27383
+ nextStep
27384
+ });
27385
+ if (agentAuthVerifiedRef.current) {
27386
+ finishWizardTimer("agent-auth-validation", timer, { status: "already-verified" });
27387
+ if (!deferStep) update({ step: nextStep });
27388
+ return true;
27389
+ }
27103
27390
  const authCheckStepNumber = state.tokensPath ? 4 : 3;
27104
27391
  const prefetchedAuth = initialAgentAuthRef.current;
27105
27392
  initialAgentAuthRef.current = null;
27106
- if (!prefetchedAuth) update({ step: "checking-claude-auth", authCheckStepNumber });
27107
- const status = await (prefetchedAuth ?? checkAgentAuth(state.agent));
27393
+ if (!prefetchedAuth && !deferStep) update({ step: "checking-claude-auth", authCheckStepNumber });
27394
+ let status;
27395
+ try {
27396
+ status = await (prefetchedAuth ?? checkAgentAuth(state.agent));
27397
+ } catch (error) {
27398
+ finishWizardTimer("agent-auth-validation", timer, {
27399
+ status: "error",
27400
+ error: error instanceof Error ? error.message : String(error)
27401
+ });
27402
+ throw error;
27403
+ }
27404
+ finishWizardTimer("agent-auth-validation", timer, {
27405
+ status,
27406
+ prefetched: Boolean(prefetchedAuth)
27407
+ });
27108
27408
  if (status === "not-found") {
27109
27409
  update({
27110
27410
  step: "error",
@@ -27128,7 +27428,8 @@ If you are using AWS Bedrock, run:
27128
27428
  });
27129
27429
  return false;
27130
27430
  }
27131
- update({ step: nextStep });
27431
+ agentAuthVerifiedRef.current = true;
27432
+ if (!deferStep) update({ step: nextStep });
27132
27433
  return true;
27133
27434
  };
27134
27435
  const runGenerateTokens = async (rawTokensPath, outDir, forceRegenerate = false) => {
@@ -27183,7 +27484,13 @@ If you are using AWS Bedrock, run:
27183
27484
  update({ tokensPath, tokenSessionId, tokenCount, tokenGenerationStatus: "complete" });
27184
27485
  return true;
27185
27486
  };
27186
- const runMapTokens2 = async (sessionId2) => {
27487
+ const runMapTokens2 = async (sessionId2, silent = false) => {
27488
+ const timer = startWizardTimer("token-mapping", {
27489
+ sessionId: sessionId2,
27490
+ silent,
27491
+ noCache: effectiveNoCache,
27492
+ skipAgent: skipMapTokens
27493
+ });
27187
27494
  let mappablePropCount = 0;
27188
27495
  let rawTokenCount = 0;
27189
27496
  try {
@@ -27210,13 +27517,22 @@ If you are using AWS Bedrock, run:
27210
27517
  errorStep: "map tokens",
27211
27518
  errorMessage: error instanceof Error ? error.message : String(error)
27212
27519
  });
27520
+ finishWizardTimer("token-mapping", timer, {
27521
+ status: "error",
27522
+ error: error instanceof Error ? error.message : String(error)
27523
+ });
27213
27524
  return false;
27214
27525
  }
27215
27526
  if (!shouldRunMapTokens({ mappablePropCount, rawTokenCount })) {
27216
- update({ mapTokensEligible: false, mapTokensStatus: "complete" });
27527
+ if (!silent) update({ mapTokensEligible: false, mapTokensStatus: "complete" });
27528
+ finishWizardTimer("token-mapping", timer, {
27529
+ status: "skipped",
27530
+ mappablePropCount,
27531
+ rawTokenCount
27532
+ });
27217
27533
  return true;
27218
27534
  }
27219
- update({ mapTokensEligible: true, mapTokensStatus: "running" });
27535
+ if (!silent) update({ mapTokensEligible: true, mapTokensStatus: "running", step: "generating" });
27220
27536
  const args = buildMapTokensArgs({
27221
27537
  sessionId: sessionId2,
27222
27538
  agent: state.agent,
@@ -27234,9 +27550,21 @@ If you are using AWS Bedrock, run:
27234
27550
  errorStep: "map tokens",
27235
27551
  errorMessage: result.stderr.trim() || "Unknown error"
27236
27552
  });
27553
+ finishWizardTimer("token-mapping", timer, {
27554
+ status: "error",
27555
+ exitCode: result.exitCode,
27556
+ mappablePropCount,
27557
+ rawTokenCount
27558
+ });
27237
27559
  return false;
27238
27560
  }
27239
- update({ mapTokensStatus: "complete" });
27561
+ if (!silent) update({ mapTokensStatus: "complete" });
27562
+ finishWizardTimer("token-mapping", timer, {
27563
+ status: "complete",
27564
+ exitCode: result.exitCode,
27565
+ mappablePropCount,
27566
+ rawTokenCount
27567
+ });
27240
27568
  return true;
27241
27569
  };
27242
27570
  const startExistingEntitiesFetch = (projectPath, outDir) => {
@@ -27486,7 +27814,7 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27486
27814
  generateProgress: null
27487
27815
  }));
27488
27816
  };
27489
- const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2) => buildGenerateProcessArgs({
27817
+ const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => ({
27490
27818
  sessionId: extractSessionId,
27491
27819
  tokensPath,
27492
27820
  agent: state.agent,
@@ -27495,8 +27823,127 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27495
27823
  noCache: effectiveNoCache,
27496
27824
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {},
27497
27825
  ...generatePromptPath2 ? { generatePromptPath: generatePromptPath2 } : {},
27498
- promptOverrides
27826
+ promptOverrides,
27827
+ ...cachedComponents && cachedComponents.length > 0 ? { cachedComponents } : {}
27499
27828
  });
27829
+ const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents));
27830
+ const buildGenerateCliArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateComponentsArgs(
27831
+ buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents)
27832
+ );
27833
+ const checkGenerateCache = async (extractSessionId, tokensPath) => {
27834
+ const timer = startWizardTimer("generation-cache-status", {
27835
+ sessionId: extractSessionId,
27836
+ noCache: effectiveNoCache
27837
+ });
27838
+ if (effectiveNoCache) {
27839
+ finishWizardTimer("generation-cache-status", timer, { status: "skipped" });
27840
+ return false;
27841
+ }
27842
+ const result = await runCli([...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"]);
27843
+ const hit = result.exitCode === 0 && /^cache-status=hit$/m.test(result.stdout);
27844
+ finishWizardTimer("generation-cache-status", timer, {
27845
+ status: hit ? "hit" : "miss",
27846
+ exitCode: result.exitCode,
27847
+ ...result.exitCode !== 0 ? { stderrTail: result.stderr.trim().slice(-500) } : {}
27848
+ });
27849
+ return hit;
27850
+ };
27851
+ const checkGenerateCacheComponents = async (extractSessionId, tokensPath, restoreCache = false) => {
27852
+ const timer = startWizardTimer("generation-cache-components", {
27853
+ sessionId: extractSessionId,
27854
+ restoreCache,
27855
+ noCache: effectiveNoCache
27856
+ });
27857
+ if (effectiveNoCache) {
27858
+ finishWizardTimer("generation-cache-components", timer, { status: "skipped" });
27859
+ return /* @__PURE__ */ new Set();
27860
+ }
27861
+ const args = [...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"];
27862
+ if (restoreCache) args.push("--restore-cache");
27863
+ const result = await runCli(args);
27864
+ if (result.exitCode !== 0) {
27865
+ finishWizardTimer("generation-cache-components", timer, {
27866
+ status: "error",
27867
+ exitCode: result.exitCode,
27868
+ stderrTail: result.stderr.trim().slice(-500)
27869
+ });
27870
+ return /* @__PURE__ */ new Set();
27871
+ }
27872
+ const line = /^cache-components=(.+)$/m.exec(result.stdout)?.[1];
27873
+ if (!line) {
27874
+ finishWizardTimer("generation-cache-components", timer, {
27875
+ status: "missing-output",
27876
+ exitCode: result.exitCode,
27877
+ cachedCount: 0
27878
+ });
27879
+ return /* @__PURE__ */ new Set();
27880
+ }
27881
+ try {
27882
+ const names = JSON.parse(line);
27883
+ const cachedNames = new Set(
27884
+ Array.isArray(names) ? names.filter((name) => typeof name === "string") : []
27885
+ );
27886
+ finishWizardTimer("generation-cache-components", timer, {
27887
+ status: "complete",
27888
+ exitCode: result.exitCode,
27889
+ cachedCount: cachedNames.size
27890
+ });
27891
+ return cachedNames;
27892
+ } catch {
27893
+ finishWizardTimer("generation-cache-components", timer, {
27894
+ status: "invalid-output",
27895
+ exitCode: result.exitCode,
27896
+ cachedCount: 0
27897
+ });
27898
+ return /* @__PURE__ */ new Set();
27899
+ }
27900
+ };
27901
+ useEffect15(() => {
27902
+ if (state.step !== "extracting" && state.step !== "scope-gate" || !state.extractSessionId || state.existingEntitiesStatus === "running" || state.existingEntitiesStatus === "idle" || state.selectionAgentStatus !== "complete" || state.compositionPhase !== "done" && state.compositionPhase !== "cache-hit" || state.rawTokensPath && state.tokenGenerationStatus !== "complete" || effectiveNoCache) {
27903
+ return;
27904
+ }
27905
+ const sessionId2 = state.extractSessionId;
27906
+ if (generationCachePreflightRef.current?.sessionId === sessionId2) return;
27907
+ const timer = startWizardTimer("generation-cache-preflight", { sessionId: sessionId2 });
27908
+ const db = openPipelineDb();
27909
+ let generatedNames = null;
27910
+ try {
27911
+ const rows = db.prepare("SELECT name, status FROM raw_components WHERE session_id = ?").all(sessionId2);
27912
+ if (rows.length > 0 && rows.every((row) => row.status === "generated")) {
27913
+ generatedNames = new Set(rows.map((row) => row.name));
27914
+ }
27915
+ } finally {
27916
+ db.close();
27917
+ }
27918
+ if (generatedNames) {
27919
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: Promise.resolve(generatedNames) };
27920
+ finishWizardTimer("generation-cache-preflight", timer, {
27921
+ status: "complete",
27922
+ source: "session-db",
27923
+ cachedCount: generatedNames.size
27924
+ });
27925
+ return;
27926
+ }
27927
+ const promise = checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
27928
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise };
27929
+ void promise.then((cachedNames) => {
27930
+ finishWizardTimer("generation-cache-preflight", timer, {
27931
+ status: "complete",
27932
+ source: "cache-status",
27933
+ cachedCount: cachedNames.size
27934
+ });
27935
+ });
27936
+ }, [
27937
+ state.step,
27938
+ state.extractSessionId,
27939
+ state.tokensPath,
27940
+ state.rawTokensPath,
27941
+ state.tokenGenerationStatus,
27942
+ state.selectionAgentStatus,
27943
+ state.compositionPhase,
27944
+ state.existingEntitiesStatus,
27945
+ state.existingEntitiesPath
27946
+ ]);
27500
27947
  const startGeneratePrefetch = (extractSessionId, tokensPath) => {
27501
27948
  const args = buildGenerateArgs(extractSessionId, tokensPath);
27502
27949
  let progressCursor = null;
@@ -27551,8 +27998,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27551
27998
  });
27552
27999
  return donePromise;
27553
28000
  };
27554
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount) => {
27555
- const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath);
28001
+ const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false, cachedComponents = []) => {
28002
+ const timer = startWizardTimer("component-generation", {
28003
+ sessionId: extractSessionId,
28004
+ acceptedCount,
28005
+ suppressScreen,
28006
+ noCache: effectiveNoCache
28007
+ });
28008
+ const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath, cachedComponents);
27556
28009
  let progressCursor = state.generateProgress;
27557
28010
  const { donePromise } = spawnGenerateChild({
27558
28011
  command: "node",
@@ -27561,12 +28014,17 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27561
28014
  const nextProgress = parseGenerateStderrChunk(chunk, progressCursor);
27562
28015
  if (nextProgress !== progressCursor) {
27563
28016
  progressCursor = nextProgress;
27564
- update({ generateProgress: nextProgress });
28017
+ if (!suppressScreen) update({ generateProgress: nextProgress });
27565
28018
  }
27566
28019
  }
27567
28020
  });
27568
28021
  const result = await donePromise;
27569
28022
  if (result.exitCode !== 0) {
28023
+ finishWizardTimer("component-generation", timer, {
28024
+ status: "error",
28025
+ exitCode: result.exitCode
28026
+ });
28027
+ finishSelectionToReviewTimer("error");
27570
28028
  update({
27571
28029
  step: "error",
27572
28030
  errorStep: "generate components",
@@ -27574,14 +28032,67 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27574
28032
  });
27575
28033
  return;
27576
28034
  }
27577
- const { generateSessionId: generateSessionId2, generatedCount } = parseGenerateResult(result, acceptedCount);
28035
+ const { generateSessionId: generateSessionId2, generatedCount, renamedSlotsCount } = parseGenerateResult(result, acceptedCount);
27578
28036
  const mappedSessionId = generateSessionId2 ?? extractSessionId;
27579
- update({
27580
- generateSessionId: mappedSessionId,
28037
+ finishWizardTimer("component-generation", timer, {
28038
+ status: "complete",
28039
+ exitCode: result.exitCode,
27581
28040
  generatedCount,
27582
- generateProgress: null
28041
+ renamedSlotsCount
27583
28042
  });
27584
- if (await runMapTokens2(mappedSessionId)) update({ step: "final-review" });
28043
+ if (!suppressScreen) {
28044
+ update({
28045
+ acceptedCount,
28046
+ autoRejectedCount: 0,
28047
+ generateSessionId: mappedSessionId,
28048
+ generatedCount,
28049
+ generateProgress: null
28050
+ });
28051
+ if (await runMapTokens2(mappedSessionId)) {
28052
+ finishSelectionToReviewTimer("generated");
28053
+ update({ step: "final-review" });
28054
+ } else {
28055
+ finishSelectionToReviewTimer("error");
28056
+ }
28057
+ return;
28058
+ }
28059
+ const mapped = await runMapTokens2(mappedSessionId, true);
28060
+ if (mapped) {
28061
+ finishSelectionToReviewTimer("generated");
28062
+ update(
28063
+ {
28064
+ acceptedCount,
28065
+ autoRejectedCount: 0,
28066
+ generateSessionId: mappedSessionId,
28067
+ generatedCount,
28068
+ generateProgress: null,
28069
+ mapTokensStatus: "complete",
28070
+ step: "final-review"
28071
+ },
28072
+ { clearScreen: false }
28073
+ );
28074
+ } else {
28075
+ finishSelectionToReviewTimer("error");
28076
+ }
28077
+ };
28078
+ const finishCachedGeneration = async (sessionId2, acceptedCount) => {
28079
+ if (await runMapTokens2(sessionId2, true)) {
28080
+ finishSelectionToReviewTimer("cached");
28081
+ update(
28082
+ {
28083
+ acceptedCount,
28084
+ autoRejectedCount: 0,
28085
+ generateSessionId: sessionId2,
28086
+ generatedCount: acceptedCount,
28087
+ generateProgress: null,
28088
+ mapTokensStatus: "complete",
28089
+ step: "final-review"
28090
+ },
28091
+ { clearScreen: false }
28092
+ );
28093
+ } else {
28094
+ finishSelectionToReviewTimer("error");
28095
+ }
27585
28096
  };
27586
28097
  const advanceToPushFlow = (generatedAcceptedCount) => {
27587
28098
  update({ generatedAcceptedCount });
@@ -27766,8 +28277,15 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27766
28277
  return;
27767
28278
  }
27768
28279
  }
27769
- if (await runAgentAuthCheck("generating")) {
27770
- void runGenerate(sid, state.tokensPath, state.acceptedCount);
28280
+ if (await runAgentAuthCheck("generating", true)) {
28281
+ const cacheHit = await checkGenerateCache(sid, state.tokensPath);
28282
+ if (cacheHit) {
28283
+ update({ step: "generating" });
28284
+ void finishCachedGeneration(sid, state.acceptedCount);
28285
+ } else {
28286
+ update({ step: "generating" });
28287
+ void runGenerate(sid, state.tokensPath, state.acceptedCount);
28288
+ }
27771
28289
  }
27772
28290
  return;
27773
28291
  }
@@ -28135,13 +28653,13 @@ If using a custom host configuration, make sure the space exists on that host.`
28135
28653
  update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
28136
28654
  return { ok: true };
28137
28655
  };
28138
- const runSaveAndPush = async () => {
28139
- await startSaveFlow({ skipGate: true, andPush: true });
28656
+ const runSaveAndPush = async (statePatch = {}) => {
28657
+ await startSaveFlow({ skipGate: true, andPush: true }, statePatch);
28140
28658
  };
28141
28659
  const pendingSaveOptionsRef = useRef10({});
28142
- const startSaveFlow = async (opts = {}) => {
28660
+ const startSaveFlow = async (opts = {}, statePatch = {}) => {
28143
28661
  pendingSaveOptionsRef.current = opts;
28144
- setState((prev) => ({ ...prev, step: "path-prompt" }));
28662
+ update({ ...statePatch, step: "path-prompt" });
28145
28663
  };
28146
28664
  const proceedToWrite = async (path) => {
28147
28665
  setState((prev) => ({ ...prev, outDir: path }));
@@ -28373,42 +28891,103 @@ If using a custom host configuration, make sure the space exists on that host.`
28373
28891
  }
28374
28892
  const sessionId2 = state.extractSessionId;
28375
28893
  const db = openPipelineDb();
28376
- let components;
28894
+ let loadedComponents;
28377
28895
  try {
28378
- components = loadScopeComponents(db, sessionId2);
28896
+ loadedComponents = loadScopeComponents(db, sessionId2);
28379
28897
  } finally {
28380
28898
  db.close();
28381
28899
  }
28900
+ const remembered = scopeComponentsRef.current;
28901
+ if (loadedComponents.length > 0 && remembered?.sessionId !== sessionId2) {
28902
+ scopeComponentsRef.current = { sessionId: sessionId2, components: loadedComponents };
28903
+ }
28904
+ const rememberedForSession = scopeComponentsRef.current?.sessionId === sessionId2 ? scopeComponentsRef.current.components : null;
28905
+ const components = scopeGateCompletedRef.current && rememberedForSession ? rememberedForSession : loadedComponents.length > 0 ? loadedComponents : rememberedForSession ?? [];
28382
28906
  return /* @__PURE__ */ jsx50(
28383
28907
  ScopeGateStep,
28384
28908
  {
28385
28909
  components,
28386
28910
  onConfirm: (decisions) => {
28387
- void runScopeGate({
28388
- sessionId: sessionId2,
28389
- decisions,
28390
- onAdvanceToGenerate: async ({ sessionId: sid, acceptedCount }) => {
28391
- scopeGateCompletedRef.current = true;
28392
- if (shouldGenerateAfterScopeGate({ acceptedCount })) {
28393
- update({
28394
- acceptedCount,
28395
- autoRejectedCount: 0,
28396
- step: "checking-claude-auth",
28397
- authCheckStepNumber: state.tokensPath ? 4 : 3
28398
- });
28399
- if (await runAgentAuthCheck("generating")) {
28400
- void runGenerate(sid, state.tokensPath, acceptedCount);
28911
+ void (async () => {
28912
+ selectionToReviewStartedAtRef.current = decisions.accepted.length > 0 ? startWizardTimer("selection-to-review", {
28913
+ sessionId: sessionId2,
28914
+ acceptedCount: decisions.accepted.length,
28915
+ rejectedCount: decisions.rejected.length
28916
+ }) : null;
28917
+ const preflight = generationCachePreflightRef.current;
28918
+ const cachePromise = preflight?.sessionId === sessionId2 ? preflight.promise : checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
28919
+ if (!preflight || preflight.sessionId !== sessionId2) {
28920
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: cachePromise };
28921
+ }
28922
+ const cacheWaitStartedAt = startWizardTimer("selection-cache-restoration-wait", {
28923
+ sessionId: sessionId2,
28924
+ preflight: Boolean(preflight?.sessionId === sessionId2)
28925
+ });
28926
+ try {
28927
+ const cachedNames = await cachePromise;
28928
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
28929
+ status: "complete",
28930
+ cachedCount: cachedNames.size
28931
+ });
28932
+ } catch (error) {
28933
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
28934
+ status: "error",
28935
+ error: error instanceof Error ? error.message : String(error)
28936
+ });
28937
+ finishSelectionToReviewTimer("error");
28938
+ throw error;
28939
+ }
28940
+ await runScopeGate({
28941
+ sessionId: sessionId2,
28942
+ decisions,
28943
+ onAdvanceToGenerate: async ({ sessionId: sid, acceptedCount }) => {
28944
+ scopeGateCompletedRef.current = true;
28945
+ if (shouldGenerateAfterScopeGate({ acceptedCount })) {
28946
+ const authPromise = runAgentAuthCheck("generating", true);
28947
+ if (await authPromise) {
28948
+ const comparisonStartedAt = startWizardTimer("generation-cache-set-comparison", {
28949
+ sessionId: sid,
28950
+ acceptedCount: decisions?.accepted.length ?? 0
28951
+ });
28952
+ let cachedNames = await cachePromise;
28953
+ const acceptedNames = decisions?.accepted ?? [];
28954
+ let cacheHit = !effectiveNoCache && acceptedNames.length > 0 && acceptedNames.every((name) => cachedNames.has(name));
28955
+ if (!cacheHit && !effectiveNoCache && acceptedNames.length > 0) {
28956
+ cachedNames = await checkGenerateCacheComponents(sid, state.tokensPath, true);
28957
+ cacheHit = acceptedNames.every((name) => cachedNames.has(name));
28958
+ }
28959
+ finishWizardTimer("generation-cache-set-comparison", comparisonStartedAt, {
28960
+ status: "complete",
28961
+ cacheHit,
28962
+ cachedCount: cachedNames.size,
28963
+ acceptedCount: acceptedNames.length
28964
+ });
28965
+ if (cacheHit) {
28966
+ void finishCachedGeneration(sid, acceptedCount);
28967
+ } else {
28968
+ const acceptedNameSet = new Set(acceptedNames);
28969
+ const restoredCachedNames = [...cachedNames].filter((name) => acceptedNameSet.has(name));
28970
+ update({
28971
+ step: "generating",
28972
+ generateProgress: null,
28973
+ acceptedCount
28974
+ });
28975
+ void runGenerate(sid, state.tokensPath, acceptedCount, false, restoredCachedNames);
28976
+ }
28977
+ } else {
28978
+ finishSelectionToReviewTimer("error");
28979
+ }
28980
+ return;
28401
28981
  }
28402
- return;
28982
+ void runSaveAndPush();
28983
+ },
28984
+ onAdvanceToPushFlow: (count) => {
28985
+ scopeGateCompletedRef.current = true;
28986
+ update({ acceptedCount: count, autoRejectedCount: 0 });
28987
+ advanceToPushFlow(count);
28403
28988
  }
28404
- void runSaveAndPush();
28405
- },
28406
- onAdvanceToPushFlow: (count) => {
28407
- scopeGateCompletedRef.current = true;
28408
- update({ acceptedCount: count, autoRejectedCount: 0 });
28409
- advanceToPushFlow(count);
28410
- }
28411
- });
28989
+ });
28990
+ })();
28412
28991
  },
28413
28992
  onQuit: () => process.exit(0)
28414
28993
  }
@@ -28495,8 +29074,10 @@ If using a custom host configuration, make sure the space exists on that host.`
28495
29074
  }
28496
29075
  const allowEmptyDeleteAll = acceptedCount === 0;
28497
29076
  allowEmptyDeleteAllRef.current = allowEmptyDeleteAll;
28498
- update({ finalReviewPassed: true, generatedAcceptedCount: acceptedCount });
28499
- void runSaveAndPush();
29077
+ void runSaveAndPush({
29078
+ finalReviewPassed: true,
29079
+ generatedAcceptedCount: acceptedCount
29080
+ });
28500
29081
  },
28501
29082
  onQuit: () => process.exit(0)
28502
29083
  }
@@ -28735,6 +29316,7 @@ var init_WizardApp = __esm({
28735
29316
  init_prompt_overrides();
28736
29317
  init_run_selection_agent();
28737
29318
  init_use_terminal_size();
29319
+ init_render_with_goodbye();
28738
29320
  SAVE_CONFIRMATION_DELAY_MS = 1e3;
28739
29321
  WIZARD_LOG = join20(tmpdir(), "experiences-import-wizard.log");
28740
29322
  }