@contentful/experience-design-system-cli 2.35.1-dev-build-98f1ba74.0 → 2.35.1-dev-build-c8bff53c.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.
@@ -164,11 +164,12 @@ var init_render_with_goodbye = __esm({
164
164
  function isAgentName(value) {
165
165
  return AGENT_NAMES.includes(value);
166
166
  }
167
- var AGENT_NAMES, AGENT_BINARIES;
167
+ var AGENT_NAMES, DEFAULT_AGENT_NAME, AGENT_BINARIES;
168
168
  var init_agent_names = __esm({
169
169
  "packages/experience-design-system-generation/dist/src/agent-names.js"() {
170
170
  "use strict";
171
171
  AGENT_NAMES = ["claude", "codex", "opencode", "cursor", "copilot"];
172
+ DEFAULT_AGENT_NAME = "claude";
172
173
  AGENT_BINARIES = {
173
174
  claude: "claude",
174
175
  codex: "codex",
@@ -180,6 +181,23 @@ var init_agent_names = __esm({
180
181
  });
181
182
 
182
183
  // packages/experience-design-system-generation/dist/src/controller/helpers/validate-generation-request.js
184
+ function validateGenerationRequest(request2) {
185
+ if (request2 === null || typeof request2 !== "object") {
186
+ throw new TypeError("createGenerationEndpoint requires a request object");
187
+ }
188
+ if (!isAgentName(request2.agent)) {
189
+ throw new TypeError(`createGenerationEndpoint requires agent to be a valid agent name, got: ${String(request2.agent)}`);
190
+ }
191
+ if (typeof request2.timeoutMs !== "number" || request2.timeoutMs <= 0) {
192
+ throw new TypeError("createGenerationEndpoint requires timeoutMs to be a positive number");
193
+ }
194
+ if (request2.promptOptions === null || typeof request2.promptOptions !== "object") {
195
+ throw new TypeError("createGenerationEndpoint requires promptOptions to be an object");
196
+ }
197
+ if (request2.onOutput !== void 0 && typeof request2.onOutput !== "function") {
198
+ throw new TypeError("createGenerationEndpoint requires onOutput to be a function when provided");
199
+ }
200
+ }
183
201
  var init_validate_generation_request = __esm({
184
202
  "packages/experience-design-system-generation/dist/src/controller/helpers/validate-generation-request.js"() {
185
203
  "use strict";
@@ -612,6 +630,17 @@ var init_build_prompt = __esm({
612
630
  });
613
631
 
614
632
  // packages/experience-design-system-generation/dist/src/orchestrator/execute-generation-orchestrator.js
633
+ async function executeGenerationOrchestrator(request2) {
634
+ const prompt = await buildPrompt(request2.promptOptions);
635
+ return request2.invoker.invoke({
636
+ agent: request2.agent,
637
+ model: request2.model,
638
+ bedrock: request2.bedrock,
639
+ prompt,
640
+ timeoutMs: request2.timeoutMs,
641
+ onOutput: request2.onOutput
642
+ });
643
+ }
615
644
  var init_execute_generation_orchestrator = __esm({
616
645
  "packages/experience-design-system-generation/dist/src/orchestrator/execute-generation-orchestrator.js"() {
617
646
  "use strict";
@@ -712,6 +741,22 @@ var init_describe_agent_failure = __esm({
712
741
  });
713
742
 
714
743
  // packages/experience-design-system-generation/dist/src/services/agent/helpers/extract-sentinel-output.js
744
+ function extractSentinelOutput(stdout) {
745
+ const START = "<<<EDS_OUTPUT_START>>>";
746
+ const END = "<<<EDS_OUTPUT_END>>>";
747
+ const startIdx = stdout.indexOf(START);
748
+ const endIdx = stdout.indexOf(END);
749
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx)
750
+ return null;
751
+ const secondStart = stdout.indexOf(START, startIdx + START.length);
752
+ if (secondStart !== -1 && secondStart < endIdx)
753
+ return "multiple";
754
+ const afterStart = stdout.indexOf(END, startIdx);
755
+ const secondEnd = stdout.indexOf(END, afterStart + END.length);
756
+ if (secondEnd !== -1)
757
+ return "multiple";
758
+ return stdout.slice(startIdx + START.length, endIdx).trim();
759
+ }
715
760
  var init_extract_sentinel_output = __esm({
716
761
  "packages/experience-design-system-generation/dist/src/services/agent/helpers/extract-sentinel-output.js"() {
717
762
  "use strict";
@@ -736,7 +781,7 @@ async function runAgent(options) {
736
781
  promptLen: prompt.length,
737
782
  promptHead: prompt.slice(0, 500)
738
783
  });
739
- return new Promise((resolve20) => {
784
+ return new Promise((resolve21) => {
740
785
  const bedrockEnv = bedrock ? BEDROCK_ENV_BY_AGENT[agent] : void 0;
741
786
  const child = spawn(binary, args, {
742
787
  stdio: ["pipe", "pipe", "pipe"],
@@ -785,7 +830,7 @@ async function runAgent(options) {
785
830
  stderrLen: stderr.length,
786
831
  stderrTail: stderr.slice(-1e3)
787
832
  });
788
- resolve20(result);
833
+ resolve21(result);
789
834
  });
790
835
  });
791
836
  }
@@ -802,7 +847,7 @@ var init_run_agent = __esm({
802
847
  // packages/experience-design-system-generation/dist/src/services/agent/helpers/check-claude-auth.js
803
848
  import { spawn as spawn2 } from "node:child_process";
804
849
  function checkClaudeAuth(binary) {
805
- return new Promise((resolve20) => {
850
+ return new Promise((resolve21) => {
806
851
  const child = spawn2(binary, ["auth", "status", "--json"], {
807
852
  stdio: ["ignore", "pipe", "pipe"]
808
853
  });
@@ -812,7 +857,7 @@ function checkClaudeAuth(binary) {
812
857
  if (!done) {
813
858
  done = true;
814
859
  child.kill("SIGTERM");
815
- resolve20("unauthenticated");
860
+ resolve21("unauthenticated");
816
861
  }
817
862
  }, 5e3);
818
863
  child.stdout?.on("data", (chunk) => {
@@ -824,14 +869,14 @@ function checkClaudeAuth(binary) {
824
869
  done = true;
825
870
  clearTimeout(timer);
826
871
  if (code !== 0) {
827
- resolve20("unauthenticated");
872
+ resolve21("unauthenticated");
828
873
  return;
829
874
  }
830
875
  try {
831
876
  const status = JSON.parse(stdout);
832
- resolve20(status.loggedIn ? "ok" : "unauthenticated");
877
+ resolve21(status.loggedIn ? "ok" : "unauthenticated");
833
878
  } catch {
834
- resolve20("unauthenticated");
879
+ resolve21("unauthenticated");
835
880
  }
836
881
  });
837
882
  });
@@ -846,13 +891,13 @@ var init_check_claude_auth = __esm({
846
891
  import { spawn as spawn3 } from "node:child_process";
847
892
  async function checkAgentAuth(agent) {
848
893
  const binary = resolveAgentBinary(agent);
849
- const binaryExists2 = await new Promise((resolve20) => {
894
+ const binaryExists2 = await new Promise((resolve21) => {
850
895
  if (binary.startsWith("/")) {
851
- import("node:fs/promises").then((fs2) => fs2.access(binary).then(() => resolve20(true), () => resolve20(false)));
896
+ import("node:fs/promises").then((fs2) => fs2.access(binary).then(() => resolve21(true), () => resolve21(false)));
852
897
  return;
853
898
  }
854
899
  const child = spawn3(process.platform === "win32" ? "where" : "which", [binary], { stdio: "ignore" });
855
- child.on("close", (code) => resolve20(code === 0));
900
+ child.on("close", (code) => resolve21(code === 0));
856
901
  });
857
902
  if (!binaryExists2)
858
903
  return "not-found";
@@ -889,6 +934,20 @@ var init_local_cli_agent_invoker = __esm({
889
934
  });
890
935
 
891
936
  // packages/experience-design-system-generation/dist/src/controller/create-generation-endpoint.js
937
+ function createGenerationEndpoint(options = {}) {
938
+ const invoker2 = createLocalCliAgentInvoker({
939
+ onDebugEvent: options.onDebugEvent
940
+ });
941
+ return {
942
+ async run(request2) {
943
+ validateGenerationRequest(request2);
944
+ return executeGenerationOrchestrator({ ...request2, invoker: invoker2 });
945
+ },
946
+ checkAuth(agent) {
947
+ return invoker2.checkAuth(agent);
948
+ }
949
+ };
950
+ }
892
951
  var init_create_generation_endpoint = __esm({
893
952
  "packages/experience-design-system-generation/dist/src/controller/create-generation-endpoint.js"() {
894
953
  "use strict";
@@ -1273,6 +1332,32 @@ var init_progress = __esm({
1273
1332
  });
1274
1333
 
1275
1334
  // packages/experience-design-system-generation/dist/src/index.js
1335
+ var src_exports = {};
1336
+ __export(src_exports, {
1337
+ AGENT_BINARIES: () => AGENT_BINARIES,
1338
+ AGENT_NAMES: () => AGENT_NAMES,
1339
+ DEFAULT_AGENT_NAME: () => DEFAULT_AGENT_NAME,
1340
+ agentSupportsBedrock: () => agentSupportsBedrock,
1341
+ buildArgs: () => buildAgentArgs,
1342
+ buildPrompt: () => buildPrompt,
1343
+ checkAgentAuth: () => checkAgentAuth,
1344
+ createGenerationEndpoint: () => createGenerationEndpoint,
1345
+ createLocalCliAgentInvoker: () => createLocalCliAgentInvoker,
1346
+ describeAgentFailure: () => describeAgentFailure,
1347
+ executeGenerationOrchestrator: () => executeGenerationOrchestrator,
1348
+ extractSentinelOutput: () => extractSentinelOutput,
1349
+ formatCustomPromptBanner: () => formatCustomPromptBanner,
1350
+ formatGenerateProgressLine: () => formatGenerateProgressLine,
1351
+ isAgentName: () => isAgentName,
1352
+ parseMapTokenPropToolCallLines: () => parseMapTokenPropToolCallLines,
1353
+ parseSelectToolCallLines: () => parseSelectToolCallLines,
1354
+ parseTokenToolCallLines: () => parseTokenToolCallLines,
1355
+ parseToolCallLines: () => parsePropToolCallLines,
1356
+ resolveAgentModel: () => resolveAgentModel,
1357
+ resolveBinary: () => resolveAgentBinary,
1358
+ resolveSkillPath: () => resolveSkillPath,
1359
+ runAgent: () => runAgent
1360
+ });
1276
1361
  var init_src = __esm({
1277
1362
  "packages/experience-design-system-generation/dist/src/index.js"() {
1278
1363
  "use strict";
@@ -10272,6 +10357,41 @@ var init_read3 = __esm({
10272
10357
  }
10273
10358
  });
10274
10359
 
10360
+ // packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts
10361
+ import { createHash as createHash2 } from "node:crypto";
10362
+ function hashComponentShape(component) {
10363
+ const payload = {
10364
+ framework: component.framework,
10365
+ name: component.name,
10366
+ source: component.source,
10367
+ props: component.props.map((p) => ({
10368
+ name: p.name,
10369
+ type: p.type
10370
+ })),
10371
+ slots: component.slots.map((s) => ({
10372
+ name: s.name,
10373
+ isDefault: s.isDefault
10374
+ }))
10375
+ };
10376
+ return createHash2("sha256").update(JSON.stringify(payload)).digest("hex");
10377
+ }
10378
+ var init_hash_component_shape = __esm({
10379
+ "packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts"() {
10380
+ "use strict";
10381
+ }
10382
+ });
10383
+
10384
+ // packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts
10385
+ import { createHash as createHash3 } from "node:crypto";
10386
+ function hashTokenContent(rawTokenContent) {
10387
+ return createHash3("sha256").update(rawTokenContent.trim()).digest("hex");
10388
+ }
10389
+ var init_hash_token_content = __esm({
10390
+ "packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts"() {
10391
+ "use strict";
10392
+ }
10393
+ });
10394
+
10275
10395
  // packages/experience-design-system-cli-legacy/src/session/repositories/tokens/write.ts
10276
10396
  function deleteRawTokensForSession(db, sessionId2) {
10277
10397
  db.prepare("DELETE FROM raw_token_groups WHERE session_id = ?").run(sessionId2);
@@ -10435,17 +10555,47 @@ var init_replace_raw_prop_token_paths = __esm({
10435
10555
  }
10436
10556
  });
10437
10557
 
10558
+ // packages/experience-design-system-cli-legacy/src/session/cache-keys.ts
10559
+ var cache_keys_exports = {};
10560
+ __export(cache_keys_exports, {
10561
+ hashContent: () => hashContent,
10562
+ hashFile: () => hashFile,
10563
+ hashPromptForSkill: () => hashPromptForSkill
10564
+ });
10565
+ import { createHash as createHash4 } from "node:crypto";
10566
+ import { readFile as readFile7 } from "node:fs/promises";
10567
+ import { resolve as resolve8 } from "node:path";
10568
+ function hashContent(content) {
10569
+ return createHash4("sha256").update(content).digest("hex");
10570
+ }
10571
+ async function hashFile(filePath) {
10572
+ const content = await readFile7(filePath, "utf8");
10573
+ return hashContent(content);
10574
+ }
10575
+ async function hashPromptForSkill(skill, agent, model, skillPathOverride, extraInputs = [], skillContentOverride) {
10576
+ const promptHash = skillContentOverride ? hashContent(skillContentOverride) : skillPathOverride ? await hashFile(resolve8(skillPathOverride)) : await hashFile(resolveSkillPath(skill));
10577
+ const extraSegment = extraInputs.length > 0 ? `|${extraInputs.join("|")}` : "";
10578
+ return hashContent(`${promptHash}|${agent}|${model ?? ""}${extraSegment}`);
10579
+ }
10580
+ var init_cache_keys = __esm({
10581
+ "packages/experience-design-system-cli-legacy/src/session/cache-keys.ts"() {
10582
+ "use strict";
10583
+ init_src();
10584
+ }
10585
+ });
10586
+
10438
10587
  // packages/experience-design-system-cli-legacy/src/session/db.ts
10439
10588
  import { DatabaseSync } from "node:sqlite";
10440
10589
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
10441
- import { readFile as readFile7 } from "node:fs/promises";
10590
+ import { readFile as readFile8 } from "node:fs/promises";
10591
+ import { createHash as createHash5 } from "node:crypto";
10442
10592
  import { homedir as homedir3 } from "node:os";
10443
- import { dirname as dirname14, resolve as resolve8 } from "node:path";
10593
+ import { dirname as dirname14, resolve as resolve9 } from "node:path";
10444
10594
  function getPipelineDbPath() {
10445
10595
  if (process.env.EDS_PIPELINE_DB_PATH) {
10446
- return resolve8(process.env.EDS_PIPELINE_DB_PATH);
10596
+ return resolve9(process.env.EDS_PIPELINE_DB_PATH);
10447
10597
  }
10448
- return resolve8(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10598
+ return resolve9(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10449
10599
  }
10450
10600
  function openPipelineDb(dbPath) {
10451
10601
  const path = dbPath ?? getPipelineDbPath();
@@ -10579,8 +10729,134 @@ function applyDbMigrations(db) {
10579
10729
  if (!rawSlotColsForRationale.some((c2) => c2.name === "rationale")) {
10580
10730
  db.exec("ALTER TABLE raw_slots ADD COLUMN rationale TEXT");
10581
10731
  }
10582
- for (const table of REMOVED_CACHE_TABLES) {
10583
- db.exec(`DROP TABLE IF EXISTS ${table}`);
10732
+ const hasExtractCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='extract_cache'`).all();
10733
+ if (hasExtractCache.length === 0) {
10734
+ db.exec(`
10735
+ CREATE TABLE extract_cache (
10736
+ file_path TEXT NOT NULL,
10737
+ file_hash TEXT NOT NULL,
10738
+ cli_version TEXT NOT NULL,
10739
+ created_at TEXT NOT NULL,
10740
+ updated_at TEXT NOT NULL,
10741
+ components_json TEXT NOT NULL,
10742
+ PRIMARY KEY (file_hash, cli_version)
10743
+ );
10744
+ CREATE INDEX idx_extract_cache_file ON extract_cache(file_path);
10745
+ `);
10746
+ }
10747
+ const hasSelectCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='select_cache'`).all();
10748
+ if (hasSelectCache.length === 0) {
10749
+ db.exec(`
10750
+ CREATE TABLE select_cache (
10751
+ component_hash TEXT NOT NULL,
10752
+ prompt_hash TEXT NOT NULL,
10753
+ cli_version TEXT NOT NULL,
10754
+ decision TEXT NOT NULL CHECK (decision IN ('accepted','rejected')),
10755
+ reason TEXT,
10756
+ created_at TEXT NOT NULL,
10757
+ updated_at TEXT NOT NULL,
10758
+ PRIMARY KEY (component_hash, prompt_hash, cli_version)
10759
+ );
10760
+ `);
10761
+ }
10762
+ const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='generation_cache'`).all();
10763
+ if (tables.length === 0) {
10764
+ db.exec(`
10765
+ CREATE TABLE generation_cache (
10766
+ input_hash TEXT NOT NULL,
10767
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10768
+ entity_id TEXT NOT NULL,
10769
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10770
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10771
+ created_at TEXT NOT NULL,
10772
+ updated_at TEXT NOT NULL,
10773
+ PRIMARY KEY (input_hash, entity_type, entity_id)
10774
+ );
10775
+ CREATE INDEX idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10776
+ CREATE INDEX idx_generation_cache_session ON generation_cache(source_session_id);
10777
+ `);
10778
+ }
10779
+ const genCacheCols = db.prepare(`PRAGMA table_info(generation_cache)`).all();
10780
+ const hasPromptHash = genCacheCols.some((c2) => c2.name === "prompt_hash");
10781
+ const oldPkCols = genCacheCols.filter((c2) => c2.pk > 0).map((c2) => c2.name);
10782
+ const promptHashInPk = oldPkCols.includes("prompt_hash");
10783
+ if (!hasPromptHash) {
10784
+ db.exec(`ALTER TABLE generation_cache ADD COLUMN prompt_hash TEXT NOT NULL DEFAULT ''`);
10785
+ }
10786
+ if (!promptHashInPk) {
10787
+ db.exec("BEGIN");
10788
+ try {
10789
+ db.exec(`
10790
+ CREATE TABLE generation_cache__new (
10791
+ input_hash TEXT NOT NULL,
10792
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10793
+ entity_id TEXT NOT NULL,
10794
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10795
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10796
+ created_at TEXT NOT NULL,
10797
+ updated_at TEXT NOT NULL,
10798
+ prompt_hash TEXT NOT NULL DEFAULT '',
10799
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10800
+ );
10801
+ INSERT INTO generation_cache__new
10802
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10803
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10804
+ FROM generation_cache;
10805
+ DROP TABLE generation_cache;
10806
+ ALTER TABLE generation_cache__new RENAME TO generation_cache;
10807
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10808
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10809
+ `);
10810
+ db.exec("COMMIT");
10811
+ } catch (e) {
10812
+ db.exec("ROLLBACK");
10813
+ throw e;
10814
+ }
10815
+ }
10816
+ const genCacheTableSql = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'generation_cache'`).get();
10817
+ const hasTokenMappingEntityType = genCacheTableSql?.sql.includes("'token_mapping'") ?? true;
10818
+ if (!hasTokenMappingEntityType) {
10819
+ db.exec("BEGIN");
10820
+ try {
10821
+ db.exec(`
10822
+ CREATE TABLE generation_cache__new2 (
10823
+ input_hash TEXT NOT NULL,
10824
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10825
+ entity_id TEXT NOT NULL,
10826
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10827
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10828
+ created_at TEXT NOT NULL,
10829
+ updated_at TEXT NOT NULL,
10830
+ prompt_hash TEXT NOT NULL DEFAULT '',
10831
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10832
+ );
10833
+ INSERT INTO generation_cache__new2
10834
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10835
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10836
+ FROM generation_cache;
10837
+ DROP TABLE generation_cache;
10838
+ ALTER TABLE generation_cache__new2 RENAME TO generation_cache;
10839
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10840
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10841
+ `);
10842
+ db.exec("COMMIT");
10843
+ } catch (e) {
10844
+ db.exec("ROLLBACK");
10845
+ throw e;
10846
+ }
10847
+ }
10848
+ const hasCompositionCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='composition_cache'`).all();
10849
+ if (hasCompositionCache.length === 0) {
10850
+ db.exec(`
10851
+ CREATE TABLE composition_cache (
10852
+ input_hash TEXT NOT NULL,
10853
+ cli_version TEXT NOT NULL,
10854
+ agent_output TEXT NOT NULL,
10855
+ created_at TEXT NOT NULL,
10856
+ updated_at TEXT NOT NULL,
10857
+ PRIMARY KEY (input_hash, cli_version)
10858
+ );
10859
+ `);
10584
10860
  }
10585
10861
  }
10586
10862
  function loadComponentReviewMetadata(db, sessionId2, componentName) {
@@ -11120,16 +11396,16 @@ function extractRelativeImportPaths(sourceText) {
11120
11396
  return [...specifiers];
11121
11397
  }
11122
11398
  async function resolveRelativeImport(specifier, fromDir) {
11123
- const basePath = resolve8(fromDir, specifier);
11399
+ const basePath = resolve9(fromDir, specifier);
11124
11400
  const candidates = [
11125
11401
  basePath,
11126
11402
  ...rewrittenSourcePaths(basePath),
11127
11403
  ...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
11128
- ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve8(basePath, `index${ext}`))
11404
+ ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve9(basePath, `index${ext}`))
11129
11405
  ];
11130
11406
  for (const candidate of candidates) {
11131
11407
  try {
11132
- await readFile7(candidate, "utf8");
11408
+ await readFile8(candidate, "utf8");
11133
11409
  return candidate;
11134
11410
  } catch {
11135
11411
  }
@@ -11145,7 +11421,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
11145
11421
  return resolvedPaths;
11146
11422
  }
11147
11423
  async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateTypeNames = []) {
11148
- const visited = /* @__PURE__ */ new Set([resolve8(sourcePath)]);
11424
+ const visited = /* @__PURE__ */ new Set([resolve9(sourcePath)]);
11149
11425
  const discovered = [];
11150
11426
  let frontier = [{ text: sourceText, dir: dirname14(sourcePath) }];
11151
11427
  for (let hop = 0; hop < MAX_SIBLING_DEPTH && discovered.length < MAX_SIBLING_CANDIDATES_EXPLORED; hop++) {
@@ -11159,7 +11435,7 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
11159
11435
  if (discovered.length >= MAX_SIBLING_CANDIDATES_EXPLORED) break;
11160
11436
  let content;
11161
11437
  try {
11162
- content = await readFile7(resolvedPath, "utf8");
11438
+ content = await readFile8(resolvedPath, "utf8");
11163
11439
  } catch {
11164
11440
  continue;
11165
11441
  }
@@ -11196,7 +11472,7 @@ async function loadComponentSourceRef(name, sourcePath, propNames = [], propType
11196
11472
  let truncatedSiblingCount = 0;
11197
11473
  const usesNotShown = /* @__PURE__ */ new Set();
11198
11474
  try {
11199
- const rawText = await readFile7(sourcePath, "utf8");
11475
+ const rawText = await readFile8(sourcePath, "utf8");
11200
11476
  let siblingUsesNotShown;
11201
11477
  let declaredTypeNames;
11202
11478
  ({
@@ -11632,6 +11908,108 @@ function backfillUnclassifiedProps(db, sessionId2) {
11632
11908
  ).run(sessionId2, sessionId2);
11633
11909
  return Number(withCategory.changes) + Number(withoutCategory.changes);
11634
11910
  }
11911
+ function computeMapTokensInputHash(db, sessionId2, componentSourceRefs = []) {
11912
+ const props = db.prepare(
11913
+ `SELECT rc.name AS component_name, rp.name AS prop_name, rp.cdf_token_kind, rp.default_value
11914
+ FROM raw_props rp
11915
+ JOIN raw_components rc ON rc.session_id = rp.session_id AND rc.component_id = rp.component_id
11916
+ WHERE rp.session_id = ? AND rp.cdf_type = 'token' AND rp.cdf_category = 'design'
11917
+ ORDER BY rc.name, rp.name`
11918
+ ).all(sessionId2);
11919
+ const tokens = db.prepare(`SELECT path, type FROM raw_tokens WHERE session_id = ? ORDER BY path`).all(sessionId2);
11920
+ const defaultMappings = db.prepare("SELECT raw_name, path, source FROM raw_token_name_paths WHERE session_id = ? ORDER BY raw_name").all(sessionId2);
11921
+ const payload = {
11922
+ props: props.map((p) => ({
11923
+ component: p.component_name,
11924
+ prop: p.prop_name,
11925
+ tokenKind: p.cdf_token_kind,
11926
+ rawDefault: p.default_value
11927
+ })),
11928
+ tokens,
11929
+ defaultMappings,
11930
+ // The agent's evidence, not just its inputs: a comment or allowlist edit
11931
+ // in a source file (or a sibling it reads) changes the correct answer
11932
+ // without touching props or tokens, so it must invalidate the cache too.
11933
+ sourceRefs: [...componentSourceRefs].sort((a, b) => a.component.localeCompare(b.component)).map((ref) => ({
11934
+ component: ref.component,
11935
+ content: ref.content,
11936
+ siblingFiles: (ref.siblingFiles ?? []).map((s) => ({ path: s.path, content: s.content })).sort((a, b) => a.path.localeCompare(b.path)),
11937
+ usesNotShown: [...ref.usesNotShown ?? []].sort()
11938
+ }))
11939
+ };
11940
+ return createHash5("sha256").update(JSON.stringify(payload)).digest("hex");
11941
+ }
11942
+ function lookupCache(db, inputHash, entityType, entityId, promptHash = "") {
11943
+ const row = db.prepare(
11944
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11945
+ FROM generation_cache
11946
+ WHERE input_hash = ? AND entity_type = ? AND entity_id = ? AND prompt_hash = ?`
11947
+ ).get(inputHash, entityType, entityId, promptHash);
11948
+ if (!row) return null;
11949
+ return {
11950
+ inputHash: row.input_hash,
11951
+ entityType: row.entity_type,
11952
+ entityId: row.entity_id,
11953
+ sourceSessionId: row.source_session_id,
11954
+ humanEdited: row.human_edited === 1,
11955
+ promptHash: row.prompt_hash,
11956
+ createdAt: row.created_at,
11957
+ updatedAt: row.updated_at
11958
+ };
11959
+ }
11960
+ function lookupCacheByEntity(db, entityType, entityId) {
11961
+ const row = db.prepare(
11962
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11963
+ FROM generation_cache
11964
+ WHERE entity_type = ? AND entity_id = ?
11965
+ ORDER BY updated_at DESC LIMIT 1`
11966
+ ).get(entityType, entityId);
11967
+ if (!row) return null;
11968
+ return {
11969
+ inputHash: row.input_hash,
11970
+ entityType: row.entity_type,
11971
+ entityId: row.entity_id,
11972
+ sourceSessionId: row.source_session_id,
11973
+ humanEdited: row.human_edited === 1,
11974
+ promptHash: row.prompt_hash,
11975
+ createdAt: row.created_at,
11976
+ updatedAt: row.updated_at
11977
+ };
11978
+ }
11979
+ function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash = "") {
11980
+ storeCaches(db, [{ inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash }]);
11981
+ }
11982
+ function storeCaches(db, entries) {
11983
+ if (entries.length === 0) return;
11984
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11985
+ const insert = db.prepare(
11986
+ `INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
11987
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
11988
+ ON CONFLICT(input_hash, prompt_hash, entity_type, entity_id) DO UPDATE SET
11989
+ source_session_id = excluded.source_session_id,
11990
+ human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
11991
+ updated_at = excluded.updated_at`
11992
+ );
11993
+ db.exec("BEGIN");
11994
+ try {
11995
+ for (const entry of entries) {
11996
+ insert.run(
11997
+ entry.inputHash,
11998
+ entry.entityType,
11999
+ entry.entityId,
12000
+ entry.sourceSessionId,
12001
+ entry.humanEdited ? 1 : 0,
12002
+ now,
12003
+ now,
12004
+ entry.promptHash ?? ""
12005
+ );
12006
+ }
12007
+ db.exec("COMMIT");
12008
+ } catch (e) {
12009
+ db.exec("ROLLBACK");
12010
+ throw e;
12011
+ }
12012
+ }
11635
12013
  function storeScannedFiles(db, sessionId2, filePaths) {
11636
12014
  db.exec("BEGIN");
11637
12015
  try {
@@ -11714,7 +12092,105 @@ function filterUnknownSlotAllowedComponents(db, sessionId2, allowedComponentName
11714
12092
  }
11715
12093
  return unknown;
11716
12094
  }
11717
- function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
12095
+ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId, manageTransaction = true, options = {}) {
12096
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12097
+ if (manageTransaction) db.exec("BEGIN");
12098
+ try {
12099
+ const srcComp = db.prepare(
12100
+ `SELECT description, status, component_description_rationale, props_rationale, slots_rationale
12101
+ FROM raw_components WHERE session_id = ? AND component_id = ?`
12102
+ ).get(sourceSessionId, componentId);
12103
+ if (srcComp) {
12104
+ db.prepare(
12105
+ `UPDATE raw_components
12106
+ SET description = ?, status = ?, component_description_rationale = ?, props_rationale = ?, slots_rationale = ?, extracted_at = ?
12107
+ WHERE session_id = ? AND component_id = ?`
12108
+ ).run(
12109
+ srcComp.description,
12110
+ srcComp.status,
12111
+ srcComp.component_description_rationale,
12112
+ srcComp.props_rationale,
12113
+ srcComp.slots_rationale,
12114
+ now,
12115
+ targetSessionId,
12116
+ componentId
12117
+ );
12118
+ }
12119
+ const srcProps = db.prepare(
12120
+ `SELECT name, cdf_type, cdf_category, cdf_token_kind, required, description, default_value, rationale
12121
+ FROM raw_props WHERE session_id = ? AND component_id = ?`
12122
+ ).all(sourceSessionId, componentId);
12123
+ for (const p of srcProps) {
12124
+ db.prepare(
12125
+ `UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, default_value = ?, rationale = ?
12126
+ WHERE session_id = ? AND component_id = ? AND name = ?`
12127
+ ).run(
12128
+ p.cdf_type,
12129
+ p.cdf_category,
12130
+ p.cdf_token_kind,
12131
+ p.required,
12132
+ p.description,
12133
+ p.default_value,
12134
+ p.rationale,
12135
+ targetSessionId,
12136
+ componentId,
12137
+ p.name
12138
+ );
12139
+ }
12140
+ db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`).run(
12141
+ targetSessionId,
12142
+ componentId
12143
+ );
12144
+ const srcAV = db.prepare(
12145
+ `SELECT prop_name, position, value FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`
12146
+ ).all(sourceSessionId, componentId);
12147
+ const insertAV = db.prepare(
12148
+ `INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value) VALUES (?, ?, ?, ?, ?)`
12149
+ );
12150
+ for (const av of srcAV) {
12151
+ insertAV.run(targetSessionId, componentId, av.prop_name, av.position, av.value);
12152
+ }
12153
+ const srcSlots = db.prepare(`SELECT name, required, description, rationale FROM raw_slots WHERE session_id = ? AND component_id = ?`).all(sourceSessionId, componentId);
12154
+ for (const s of srcSlots) {
12155
+ db.prepare(
12156
+ `UPDATE raw_slots SET required = ?, description = ?, rationale = ? WHERE session_id = ? AND component_id = ? AND name = ?`
12157
+ ).run(s.required, s.description, s.rationale, targetSessionId, componentId, s.name);
12158
+ }
12159
+ db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`).run(
12160
+ targetSessionId,
12161
+ componentId
12162
+ );
12163
+ const srcSAC = db.prepare(
12164
+ `SELECT slot_name, position, allowed_component FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`
12165
+ ).all(sourceSessionId, componentId);
12166
+ const insertSAC = db.prepare(
12167
+ `INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`
12168
+ );
12169
+ const allowedSAC = options.allowedComponentNames ? srcSAC.filter((sac) => options.allowedComponentNames.has(sac.allowed_component)) : srcSAC;
12170
+ for (const sac of allowedSAC) {
12171
+ insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
12172
+ }
12173
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12174
+ if (manageTransaction) db.exec("COMMIT");
12175
+ } catch (e) {
12176
+ if (manageTransaction) db.exec("ROLLBACK");
12177
+ throw e;
12178
+ }
12179
+ }
12180
+ function copyComponentsFromCache(db, entries, options = {}) {
12181
+ if (entries.length === 0) return;
12182
+ db.exec("BEGIN");
12183
+ try {
12184
+ for (const entry of entries) {
12185
+ copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false, options);
12186
+ }
12187
+ db.exec("COMMIT");
12188
+ } catch (e) {
12189
+ db.exec("ROLLBACK");
12190
+ throw e;
12191
+ }
12192
+ }
12193
+ function copyTokensFromCache(db, sourceSessionId, targetSessionId) {
11718
12194
  const now = (/* @__PURE__ */ new Date()).toISOString();
11719
12195
  db.exec("BEGIN");
11720
12196
  try {
@@ -11735,7 +12211,139 @@ function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
11735
12211
  throw e;
11736
12212
  }
11737
12213
  }
11738
- var SCHEMA, REMOVED_CACHE_TABLES, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS, MAX_TYPE_DECLARING_SIBLING_CHARS, MAX_ENLARGED_SIBLINGS, MAX_SIBLING_DEPTH, MAX_SIBLING_CANDIDATES_EXPLORED, RELATIVE_IMPORT_PATTERN, SIBLING_FILE_EXTENSIONS, TS_ESM_EXTENSION_REWRITES, IDENTIFIER_PATTERN;
12214
+ function copyMapTokensFromCache(db, sourceSessionId, targetSessionId) {
12215
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12216
+ let copiedCount = 0;
12217
+ db.exec("BEGIN");
12218
+ try {
12219
+ const sourceRows = db.prepare(
12220
+ `SELECT rc.name AS component_name, rptp.prop_name, rptp.source, rptp.position, rptp.path
12221
+ FROM raw_prop_token_paths rptp
12222
+ JOIN raw_components rc ON rc.session_id = rptp.session_id AND rc.component_id = rptp.component_id
12223
+ WHERE rptp.session_id = ?
12224
+ ORDER BY rc.name, rptp.prop_name, rptp.position`
12225
+ ).all(sourceSessionId);
12226
+ const targetComponents = db.prepare(`SELECT component_id, name FROM raw_components WHERE session_id = ?`).all(targetSessionId);
12227
+ const targetIdByName = new Map(targetComponents.map((c2) => [c2.name, c2.component_id]));
12228
+ const targetProps = new Set(
12229
+ db.prepare(`SELECT component_id, name FROM raw_props WHERE session_id = ?`).all(targetSessionId).map((p) => `${p.component_id}::${p.name}`)
12230
+ );
12231
+ const insertPath = db.prepare(
12232
+ `INSERT INTO raw_prop_token_paths (session_id, component_id, prop_name, source, position, path)
12233
+ VALUES (?, ?, ?, ?, ?, ?)`
12234
+ );
12235
+ const clearedProps = /* @__PURE__ */ new Set();
12236
+ const copiedProps = /* @__PURE__ */ new Set();
12237
+ for (const row of sourceRows) {
12238
+ const targetComponentId = targetIdByName.get(row.component_name);
12239
+ if (!targetComponentId) continue;
12240
+ if (!targetProps.has(`${targetComponentId}::${row.prop_name}`)) continue;
12241
+ const propKey = `${targetComponentId}::${row.prop_name}`;
12242
+ if (!clearedProps.has(propKey)) {
12243
+ clearedProps.add(propKey);
12244
+ db.prepare(`DELETE FROM raw_prop_token_paths WHERE session_id = ? AND component_id = ? AND prop_name = ?`).run(
12245
+ targetSessionId,
12246
+ targetComponentId,
12247
+ row.prop_name
12248
+ );
12249
+ }
12250
+ insertPath.run(targetSessionId, targetComponentId, row.prop_name, row.source, row.position, row.path);
12251
+ copiedProps.add(propKey);
12252
+ }
12253
+ copiedCount = copiedProps.size;
12254
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12255
+ db.exec("COMMIT");
12256
+ } catch (e) {
12257
+ db.exec("ROLLBACK");
12258
+ throw e;
12259
+ }
12260
+ return copiedCount;
12261
+ }
12262
+ async function getCliCacheVersion() {
12263
+ if (_cliCacheVersionCache) return _cliCacheVersionCache;
12264
+ try {
12265
+ const { hashContent: hashContent2 } = await Promise.resolve().then(() => (init_cache_keys(), cache_keys_exports));
12266
+ const { resolveSkillPath: resolveSkillPath2 } = await Promise.resolve().then(() => (init_src(), src_exports));
12267
+ const skills = ["components", "tokens", "select"];
12268
+ const parts = [];
12269
+ for (const s of skills) {
12270
+ try {
12271
+ const p = resolveSkillPath2(s);
12272
+ parts.push(readFileSync5(p, "utf8"));
12273
+ } catch {
12274
+ parts.push(`<missing:${s}>`);
12275
+ }
12276
+ }
12277
+ _cliCacheVersionCache = hashContent2(parts.join("\n---\n"));
12278
+ return _cliCacheVersionCache;
12279
+ } catch {
12280
+ _cliCacheVersionCache = "fallback";
12281
+ return _cliCacheVersionCache;
12282
+ }
12283
+ }
12284
+ function storeExtractCache(db, filePath, fileHash, cliVersion2, components) {
12285
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12286
+ const componentsJson = JSON.stringify(components);
12287
+ db.prepare(
12288
+ `INSERT INTO extract_cache (file_path, file_hash, cli_version, created_at, updated_at, components_json)
12289
+ VALUES (?, ?, ?, ?, ?, ?)
12290
+ ON CONFLICT(file_hash, cli_version) DO UPDATE SET
12291
+ file_path = excluded.file_path,
12292
+ updated_at = excluded.updated_at,
12293
+ components_json = excluded.components_json`
12294
+ ).run(filePath, fileHash, cliVersion2, now, now, componentsJson);
12295
+ }
12296
+ function lookupExtractCache(db, fileHash, cliVersion2) {
12297
+ const row = db.prepare(
12298
+ `SELECT file_path, file_hash, cli_version, created_at, updated_at, components_json
12299
+ FROM extract_cache
12300
+ WHERE file_hash = ? AND cli_version = ?`
12301
+ ).get(fileHash, cliVersion2);
12302
+ if (!row) return null;
12303
+ let components = [];
12304
+ try {
12305
+ components = JSON.parse(row.components_json);
12306
+ } catch {
12307
+ return null;
12308
+ }
12309
+ return {
12310
+ filePath: row.file_path,
12311
+ fileHash: row.file_hash,
12312
+ cliVersion: row.cli_version,
12313
+ createdAt: row.created_at,
12314
+ updatedAt: row.updated_at,
12315
+ components
12316
+ };
12317
+ }
12318
+ function storeSelectCache(db, componentHash, promptHash, cliVersion2, decision, reason) {
12319
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12320
+ db.prepare(
12321
+ `INSERT INTO select_cache (component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at)
12322
+ VALUES (?, ?, ?, ?, ?, ?, ?)
12323
+ ON CONFLICT(component_hash, prompt_hash, cli_version) DO UPDATE SET
12324
+ decision = excluded.decision,
12325
+ reason = excluded.reason,
12326
+ updated_at = excluded.updated_at`
12327
+ ).run(componentHash, promptHash, cliVersion2, decision, reason, now, now);
12328
+ }
12329
+ function lookupSelectCache(db, componentHash, promptHash, cliVersion2) {
12330
+ const row = db.prepare(
12331
+ `SELECT component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at
12332
+ FROM select_cache
12333
+ WHERE component_hash = ? AND prompt_hash = ? AND cli_version = ?`
12334
+ ).get(componentHash, promptHash, cliVersion2);
12335
+ if (!row) return null;
12336
+ return {
12337
+ componentHash: row.component_hash,
12338
+ promptHash: row.prompt_hash,
12339
+ cliVersion: row.cli_version,
12340
+ decision: row.decision,
12341
+ reason: row.reason,
12342
+ createdAt: row.created_at,
12343
+ updatedAt: row.updated_at
12344
+ };
12345
+ }
12346
+ var SCHEMA, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS, MAX_TYPE_DECLARING_SIBLING_CHARS, MAX_ENLARGED_SIBLINGS, MAX_SIBLING_DEPTH, MAX_SIBLING_CANDIDATES_EXPLORED, RELATIVE_IMPORT_PATTERN, SIBLING_FILE_EXTENSIONS, TS_ESM_EXTENSION_REWRITES, IDENTIFIER_PATTERN, _cliCacheVersionCache;
11739
12347
  var init_db = __esm({
11740
12348
  "packages/experience-design-system-cli-legacy/src/session/db.ts"() {
11741
12349
  "use strict";
@@ -11749,6 +12357,8 @@ var init_db = __esm({
11749
12357
  init_write2();
11750
12358
  init_session_resolver();
11751
12359
  init_read3();
12360
+ init_hash_component_shape();
12361
+ init_hash_token_content();
11752
12362
  init_read3();
11753
12363
  init_store_dtcg_tokens();
11754
12364
  init_apply_tool_calls();
@@ -11872,6 +12482,17 @@ CREATE TABLE IF NOT EXISTS raw_tokens (
11872
12482
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
11873
12483
  );
11874
12484
 
12485
+ CREATE TABLE IF NOT EXISTS generation_cache (
12486
+ input_hash TEXT NOT NULL,
12487
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
12488
+ entity_id TEXT NOT NULL,
12489
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
12490
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
12491
+ created_at TEXT NOT NULL,
12492
+ updated_at TEXT NOT NULL,
12493
+ PRIMARY KEY (input_hash, entity_type, entity_id)
12494
+ );
12495
+
11875
12496
  CREATE TABLE IF NOT EXISTS scanned_files (
11876
12497
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
11877
12498
  path TEXT NOT NULL,
@@ -11917,9 +12538,10 @@ CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id,
11917
12538
  CREATE INDEX IF NOT EXISTS idx_raw_slots_session ON raw_slots(session_id, component_id);
11918
12539
  CREATE INDEX IF NOT EXISTS idx_raw_tokens_session ON raw_tokens(session_id);
11919
12540
  CREATE INDEX IF NOT EXISTS idx_raw_token_groups_session ON raw_token_groups(session_id);
12541
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
12542
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
11920
12543
  CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session_id);
11921
12544
  `;
11922
- REMOVED_CACHE_TABLES = ["generation_cache", "extract_cache", "composition_cache", "select_cache"];
11923
12545
  MAX_COMPONENT_SOURCE_CHARS = 8e3;
11924
12546
  MAX_SIBLING_FILES = 5;
11925
12547
  MAX_SIBLING_SNIPPET_CHARS = 1200;
@@ -11936,15 +12558,16 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
11936
12558
  [".cjs", [".cts", ".cjs"]]
11937
12559
  ];
11938
12560
  IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
12561
+ _cliCacheVersionCache = null;
11939
12562
  }
11940
12563
  });
11941
12564
 
11942
12565
  // packages/experience-design-system-cli-legacy/src/helpers/read-existing-contentful-entities-from-session.ts
11943
- import { readFile as readFile8 } from "node:fs/promises";
12566
+ import { readFile as readFile9 } from "node:fs/promises";
11944
12567
  async function readExistingContentfulEntitiesFromSession(path) {
11945
12568
  if (!path) return void 0;
11946
12569
  try {
11947
- const raw = await readFile8(path, "utf8");
12570
+ const raw = await readFile9(path, "utf8");
11948
12571
  const parsed = JSON.parse(raw);
11949
12572
  if (!Array.isArray(parsed.components) || !Array.isArray(parsed.tokens)) return void 0;
11950
12573
  return parsed;
@@ -12068,21 +12691,21 @@ var init_resolve_session_id = __esm({
12068
12691
  });
12069
12692
 
12070
12693
  // packages/experience-design-system-cli-legacy/src/analyze/select/persistence.ts
12071
- import { access, appendFile, mkdir as mkdir2, readFile as readFile9, writeFile as writeFile2 } from "node:fs/promises";
12694
+ import { access, appendFile, mkdir as mkdir2, readFile as readFile10, writeFile as writeFile2 } from "node:fs/promises";
12072
12695
  import { homedir as homedir4 } from "node:os";
12073
- import { resolve as resolve9 } from "node:path";
12696
+ import { resolve as resolve10 } from "node:path";
12074
12697
  function getRefineArtifactsRoot() {
12075
12698
  if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
12076
- return resolve9(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12699
+ return resolve10(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12077
12700
  }
12078
- return resolve9(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12701
+ return resolve10(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12079
12702
  }
12080
12703
  async function getRefineSessionPaths(sessionId2, artifactsRoot) {
12081
- const sessionDir = resolve9(artifactsRoot, sessionId2);
12704
+ const sessionDir = resolve10(artifactsRoot, sessionId2);
12082
12705
  return {
12083
12706
  sessionDir,
12084
- eventsPath: resolve9(sessionDir, "events.jsonl"),
12085
- statePath: resolve9(sessionDir, "current-review-state.json")
12707
+ eventsPath: resolve10(sessionDir, "events.jsonl"),
12708
+ statePath: resolve10(sessionDir, "current-review-state.json")
12086
12709
  };
12087
12710
  }
12088
12711
  async function saveReviewState(statePath, session) {
@@ -12093,7 +12716,7 @@ async function ensureRefineSession(sessionId2, artifactsRoot, initialSnapshot) {
12093
12716
  await mkdir2(paths.sessionDir, { recursive: true });
12094
12717
  try {
12095
12718
  await access(paths.statePath);
12096
- const savedState = await readFile9(paths.statePath, "utf8");
12719
+ const savedState = await readFile10(paths.statePath, "utf8");
12097
12720
  return JSON.parse(savedState);
12098
12721
  } catch {
12099
12722
  await writeFile2(paths.statePath, JSON.stringify(initialSnapshot, null, 2), "utf8");
@@ -12578,8 +13201,8 @@ var init_path_exists = __esm({
12578
13201
  });
12579
13202
 
12580
13203
  // packages/experience-design-system-cli-legacy/src/lib/prompt-overrides.ts
12581
- import { readFile as readFile10 } from "node:fs/promises";
12582
- import { resolve as resolve10 } from "node:path";
13204
+ import { readFile as readFile11 } from "node:fs/promises";
13205
+ import { resolve as resolve11 } from "node:path";
12583
13206
  function looksLikePath(value) {
12584
13207
  if (value.includes("/") || value.includes("\\")) return true;
12585
13208
  if (value.startsWith("~")) return true;
@@ -12611,9 +13234,9 @@ function parsePromptOverrides(inputs) {
12611
13234
  }
12612
13235
  async function resolvePromptOverride(override) {
12613
13236
  if (override.kind === "text") return override.value;
12614
- const abs = resolve10(override.value);
13237
+ const abs = resolve11(override.value);
12615
13238
  try {
12616
- return await readFile10(abs, "utf8");
13239
+ return await readFile11(abs, "utf8");
12617
13240
  } catch {
12618
13241
  throw new Error(`--prompt: could not read prompt file: ${abs}`);
12619
13242
  }
@@ -12628,21 +13251,21 @@ var init_prompt_overrides = __esm({
12628
13251
 
12629
13252
  // packages/experience-design-system-cli-legacy/src/generate/command.ts
12630
13253
  import { createElement } from "react";
12631
- import { readFile as readFile11, readdir, stat } from "node:fs/promises";
12632
- import { join as join14, resolve as resolve11 } from "node:path";
13254
+ import { readFile as readFile12, readdir, stat } from "node:fs/promises";
13255
+ import { join as join14, resolve as resolve12 } from "node:path";
12633
13256
  async function assertFileExists(flag, p) {
12634
13257
  if (!await pathExists(p)) die(`Error: file not found: ${p} (from ${flag})`);
12635
13258
  }
12636
13259
  async function readFileInline(path) {
12637
13260
  if (!path) return void 0;
12638
- const resolved = resolve11(path);
13261
+ const resolved = resolve12(path);
12639
13262
  let s;
12640
13263
  try {
12641
13264
  s = await stat(resolved);
12642
13265
  } catch {
12643
13266
  return void 0;
12644
13267
  }
12645
- if (!s.isDirectory()) return readFile11(resolved, "utf8");
13268
+ if (!s.isDirectory()) return readFile12(resolved, "utf8");
12646
13269
  const files = [];
12647
13270
  async function walk(dir) {
12648
13271
  let entries;
@@ -12668,7 +13291,7 @@ async function readFileInline(path) {
12668
13291
  }
12669
13292
  await walk(resolved);
12670
13293
  if (files.length === 0) return void 0;
12671
- const parts = await Promise.all(files.map((f) => readFile11(f, "utf8").catch(() => "")));
13294
+ const parts = await Promise.all(files.map((f) => readFile12(f, "utf8").catch(() => "")));
12672
13295
  return parts.filter(Boolean).join("\n\n");
12673
13296
  }
12674
13297
  function printFallbackInstructions(options) {
@@ -12690,6 +13313,23 @@ function printFallbackInstructions(options) {
12690
13313
  lines.push(` Re-run the generate command with the agent available, or use --dry-run to inspect the prompt.`);
12691
13314
  process.stderr.write(lines.join("\n") + "\n");
12692
13315
  }
13316
+ function createCachedComponentResult(componentName, warnings = []) {
13317
+ return {
13318
+ componentName,
13319
+ classified: 0,
13320
+ excluded: 0,
13321
+ slots: 0,
13322
+ warnings,
13323
+ failed: false,
13324
+ cached: true,
13325
+ renamedSlotsCount: 0
13326
+ };
13327
+ }
13328
+ function writeCachedComponentStatus(position, componentName, pinned) {
13329
+ const status = pinned ? c.cyan("pinned (human-edited)") : c.green("cached");
13330
+ process.stderr.write(` ${position} ${c.bold(componentName)} ${status}
13331
+ `);
13332
+ }
12693
13333
  async function showGenerateView(result) {
12694
13334
  if (process.stdout.isTTY) {
12695
13335
  const { waitUntilExit } = renderWithGoodbye(
@@ -12710,6 +13350,29 @@ session=${result.sessionId}
12710
13350
  );
12711
13351
  await exitWithAnalytics(0);
12712
13352
  }
13353
+ function normalizeComponentForCache(component) {
13354
+ const slots = component.slots.map((slot, index, allSlots) => ({
13355
+ ...slot,
13356
+ name: slot.name.trim() || (allSlots.length === 1 ? "children" : `slot_${index}`)
13357
+ }));
13358
+ return { ...component, slots };
13359
+ }
13360
+ function lookupComponentCache(db, component, promptHash) {
13361
+ const normalized = normalizeComponentForCache(component);
13362
+ const inputHash = hashComponentShape(normalized);
13363
+ const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
13364
+ if (cached) return cached;
13365
+ const legacyInputHash = hashComponentShape(component);
13366
+ return legacyInputHash === inputHash ? null : lookupCache(db, legacyInputHash, "component", component.component_id, promptHash);
13367
+ }
13368
+ function resolveComponentCache(db, component, promptHash, allowedComponentNames2) {
13369
+ const cached = lookupComponentCache(db, component, promptHash);
13370
+ if (cached && findUnknownSlotAllowedComponents(db, cached.sourceSessionId, allowedComponentNames2, component.component_id).length === 0) {
13371
+ return { entry: cached, humanEdited: cached.humanEdited };
13372
+ }
13373
+ const pinned = lookupCacheByEntity(db, "component", component.component_id);
13374
+ return pinned?.humanEdited && findUnknownSlotAllowedComponents(db, pinned.sourceSessionId, allowedComponentNames2, component.component_id).length === 0 ? { entry: pinned, humanEdited: true } : null;
13375
+ }
12713
13376
  async function runOneComponent(options, component, index, total) {
12714
13377
  const {
12715
13378
  agent,
@@ -12719,10 +13382,13 @@ async function runOneComponent(options, component, index, total) {
12719
13382
  tokensInline,
12720
13383
  tokenMapInline,
12721
13384
  verbose,
13385
+ noCache,
12722
13386
  skillPathOverride,
12723
13387
  skillContentOverride,
13388
+ promptHash,
12724
13389
  existingContentfulEntities,
12725
13390
  existingTokensInline,
13391
+ precomputedCachedNames,
12726
13392
  allowedComponentNames: allowedComponentNames2
12727
13393
  } = options;
12728
13394
  const pos = c.dim(`[${index + 1}/${total}]`);
@@ -12747,6 +13413,40 @@ async function runOneComponent(options, component, index, total) {
12747
13413
  allowedComponents: slot.allowedComponents.filter((name) => allowedComponentNames2.has(name))
12748
13414
  };
12749
13415
  });
13416
+ const cacheComponent = normalizeComponentForCache(component);
13417
+ if (!noCache && precomputedCachedNames.has(component.name)) {
13418
+ writeCachedComponentStatus(pos, component.name, false);
13419
+ return createCachedComponentResult(component.name);
13420
+ }
13421
+ if (!noCache) {
13422
+ const inputHash = hashComponentShape(cacheComponent);
13423
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13424
+ if (resolution && !resolution.humanEdited) {
13425
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13426
+ allowedComponentNames: allowedComponentNames2
13427
+ });
13428
+ storeCache(
13429
+ db,
13430
+ inputHash,
13431
+ "component",
13432
+ component.component_id,
13433
+ resolution.entry.sourceSessionId,
13434
+ resolution.entry.humanEdited,
13435
+ promptHash
13436
+ );
13437
+ writeCachedComponentStatus(pos, component.name, false);
13438
+ return createCachedComponentResult(component.name);
13439
+ }
13440
+ if (resolution?.humanEdited) {
13441
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13442
+ allowedComponentNames: allowedComponentNames2
13443
+ });
13444
+ writeCachedComponentStatus(pos, component.name, true);
13445
+ return createCachedComponentResult(`${component.name}`, [
13446
+ `${component.name}: source changed but human edits preserved`
13447
+ ]);
13448
+ }
13449
+ }
12750
13450
  const componentBuckets = bucketComponentProps(component);
12751
13451
  getDebugLogger().event("analyze", "prop-buckets.generate", { ...componentBuckets });
12752
13452
  const rawComponentsInline = JSON.stringify(
@@ -12821,6 +13521,10 @@ ${outputBuf}`);
12821
13521
  const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings, {
12822
13522
  allowedComponentNames: allowedComponentNames2
12823
13523
  });
13524
+ if (!noCache) {
13525
+ const inputHash = hashComponentShape(cacheComponent);
13526
+ storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
13527
+ }
12824
13528
  return {
12825
13529
  componentName: component.name,
12826
13530
  classified: applied.classified,
@@ -12872,7 +13576,7 @@ async function loadAcceptedNames(sessionId2) {
12872
13576
  try {
12873
13577
  const artifactsRoot = getRefineArtifactsRoot();
12874
13578
  const paths = await getRefineSessionPaths(sessionId2, artifactsRoot);
12875
- const raw = await readFile11(paths.statePath, "utf8");
13579
+ const raw = await readFile12(paths.statePath, "utf8");
12876
13580
  const snapshot = JSON.parse(raw);
12877
13581
  const accepted = snapshot.components.filter((c2) => c2.status === "accepted").map((c2) => c2.name);
12878
13582
  if (accepted.length === 0) return null;
@@ -12881,6 +13585,15 @@ async function loadAcceptedNames(sessionId2) {
12881
13585
  return null;
12882
13586
  }
12883
13587
  }
13588
+ function parsePrecomputedCachedNames(value) {
13589
+ if (!value) return /* @__PURE__ */ new Set();
13590
+ try {
13591
+ const parsed = JSON.parse(value);
13592
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : []);
13593
+ } catch {
13594
+ return /* @__PURE__ */ new Set();
13595
+ }
13596
+ }
12884
13597
  async function runGenerateSkill(skill, opts, verbose = false) {
12885
13598
  const savedCreds = await readExperiencesCredentials();
12886
13599
  const agentName = opts.agent ?? savedCreds.agent;
@@ -12909,8 +13622,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12909
13622
  }
12910
13623
  const generatePromptPath = generatePrompt === void 0 ? configuredGeneratePromptPath : void 0;
12911
13624
  if (generatePromptPath) {
12912
- if (!await pathExists(resolve11(generatePromptPath))) {
12913
- die(`Error: custom prompt path not found: ${resolve11(generatePromptPath)}`);
13625
+ if (!await pathExists(resolve12(generatePromptPath))) {
13626
+ die(`Error: custom prompt path not found: ${resolve12(generatePromptPath)}`);
12914
13627
  }
12915
13628
  if (!generatePromptPath.toLowerCase().endsWith(".md")) {
12916
13629
  process.stderr.write(
@@ -12918,7 +13631,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12918
13631
  `
12919
13632
  );
12920
13633
  }
12921
- process.stderr.write(formatCustomPromptBanner("components", resolve11(generatePromptPath)));
13634
+ process.stderr.write(formatCustomPromptBanner("components", resolve12(generatePromptPath)));
12922
13635
  }
12923
13636
  if (skill === "tokens" && !opts.rawTokens) {
12924
13637
  die("Error: --raw-tokens is required when using generate tokens");
@@ -12934,7 +13647,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12934
13647
  let existingContentfulEntities;
12935
13648
  let existingTokensInline;
12936
13649
  if (opts.existingEntitiesPath) {
12937
- existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve11(opts.existingEntitiesPath));
13650
+ existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve12(opts.existingEntitiesPath));
12938
13651
  if (existingContentfulEntities) {
12939
13652
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
12940
13653
  } else {
@@ -13035,7 +13748,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13035
13748
  rawComponentsInline: sampleInline ?? rawTokensInline,
13036
13749
  propBucketsInline: sampleBuckets ? JSON.stringify(sampleBuckets) : void 0,
13037
13750
  rawTokensInline: skill === "tokens" ? rawTokensInline : void 0,
13038
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13751
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13039
13752
  tokensInline,
13040
13753
  tokenMapInline,
13041
13754
  outDir: process.cwd(),
@@ -13061,7 +13774,64 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13061
13774
  if (skill === "components" && allComponents && sessionId2) {
13062
13775
  const db = openPipelineDb();
13063
13776
  let componentResults;
13777
+ const precomputedCachedNames = parsePrecomputedCachedNames(opts.cachedComponents);
13064
13778
  try {
13779
+ const existingContentfulEntitiesHashInputs = [];
13780
+ if (existingTokensInline) existingContentfulEntitiesHashInputs.push(hashContent(existingTokensInline));
13781
+ const promptHash = await hashPromptForSkill(
13782
+ "components",
13783
+ agent,
13784
+ model,
13785
+ generatePromptPath,
13786
+ existingContentfulEntitiesHashInputs,
13787
+ generatePrompt
13788
+ );
13789
+ if (opts.cacheStatus) {
13790
+ const cacheEnabled = opts.cache !== false && process.env.EDS_NO_CACHE !== "1";
13791
+ const cachedComponents = cacheEnabled ? allComponents.flatMap((component) => {
13792
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13793
+ return resolution ? [{ component, resolution }] : [];
13794
+ }) : [];
13795
+ if (cacheEnabled && opts.restoreCache) {
13796
+ const restorations = [];
13797
+ const rekeyed = [];
13798
+ const componentStatus = db.prepare(
13799
+ "SELECT status FROM raw_components WHERE session_id = ? AND component_id = ?"
13800
+ );
13801
+ for (const { component, resolution } of cachedComponents) {
13802
+ const current = componentStatus.get(sessionId2, component.component_id);
13803
+ if (current?.status === "generated") continue;
13804
+ renameEmptySlots(db, sessionId2, component.component_id, component.name, component.slots.length);
13805
+ restorations.push({
13806
+ sourceSessionId: resolution.entry.sourceSessionId,
13807
+ targetSessionId: sessionId2,
13808
+ componentId: component.component_id
13809
+ });
13810
+ rekeyed.push({ component, cached: resolution.entry });
13811
+ }
13812
+ copyComponentsFromCache(db, restorations, { allowedComponentNames: allowedComponentNames2 });
13813
+ storeCaches(
13814
+ db,
13815
+ rekeyed.map(({ component, cached }) => ({
13816
+ inputHash: hashComponentShape(normalizeComponentForCache(component)),
13817
+ entityType: "component",
13818
+ entityId: component.component_id,
13819
+ sourceSessionId: cached.sourceSessionId,
13820
+ humanEdited: cached.humanEdited,
13821
+ promptHash
13822
+ }))
13823
+ );
13824
+ }
13825
+ const fullyCached = cacheEnabled && cachedComponents.length === allComponents.length;
13826
+ process.stdout.write(`cache-status=${fullyCached ? "hit" : "miss"}
13827
+ `);
13828
+ process.stdout.write(
13829
+ `cache-components=${JSON.stringify(cachedComponents.map(({ component }) => component.name))}
13830
+ `
13831
+ );
13832
+ await exitWithAnalytics(0);
13833
+ return;
13834
+ }
13065
13835
  componentResults = await runAllComponents(
13066
13836
  {
13067
13837
  agent,
@@ -13071,10 +13841,13 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13071
13841
  tokensInline,
13072
13842
  tokenMapInline,
13073
13843
  verbose,
13844
+ noCache: opts.cache === false || process.env.EDS_NO_CACHE === "1",
13074
13845
  skillPathOverride: generatePromptPath,
13075
13846
  skillContentOverride: generatePrompt,
13847
+ promptHash,
13076
13848
  existingContentfulEntities,
13077
13849
  existingTokensInline,
13850
+ precomputedCachedNames,
13078
13851
  allowedComponentNames: allowedComponentNames2
13079
13852
  },
13080
13853
  allComponents
@@ -13083,7 +13856,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13083
13856
  db.close();
13084
13857
  }
13085
13858
  const failed = componentResults.filter((r) => r.failed);
13086
- const generated = componentResults.filter((r) => !r.failed);
13859
+ const cachedResults = componentResults.filter((r) => r.cached);
13860
+ const generated = componentResults.filter((r) => !r.failed && !r.cached);
13087
13861
  const allWarnings = componentResults.flatMap((r) => r.warnings.map((w) => ` ${r.componentName}: ${w}`));
13088
13862
  if (allWarnings.length > 0) {
13089
13863
  process.stderr.write(c.yellow("Warnings:") + "\n" + allWarnings.join("\n") + "\n");
@@ -13099,17 +13873,21 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13099
13873
  const totalExcluded = generated.reduce((s, r) => s + r.excluded, 0);
13100
13874
  const totalRenamedSlots = componentResults.reduce((s, r) => s + r.renamedSlotsCount, 0);
13101
13875
  const allOk = failed.length === 0;
13876
+ const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : "";
13102
13877
  process.stderr.write(
13103
- (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length}/${componentResults.length} components` + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13878
+ (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13104
13879
  );
13105
13880
  process.stdout.write(`renamed-slots: ${totalRenamedSlots}
13106
13881
  `);
13107
- if (generated.length === 0) {
13882
+ if (generated.length === 0 && cachedResults.length === 0) {
13108
13883
  die(
13109
13884
  `Error: all ${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
13110
13885
  );
13111
13886
  }
13112
13887
  } else if (skill === "tokens") {
13888
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
13889
+ const tokenInputContent = rawTokensInline ?? "";
13890
+ const tokenInputHash = hashTokenContent(tokenInputContent);
13113
13891
  const db = openPipelineDb();
13114
13892
  try {
13115
13893
  let resolvedSessionId = opts.session;
@@ -13136,11 +13914,26 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13136
13914
  }
13137
13915
  sessionId2 = resolvedSessionId;
13138
13916
  await bindAnalyticsSessionId(resolvedSessionId);
13917
+ const tokenPromptHash = await hashPromptForSkill("tokens", agent, model);
13918
+ if (!noCache) {
13919
+ const tokenCached = lookupCache(db, tokenInputHash, "token_set", "__tokens__", tokenPromptHash);
13920
+ if (tokenCached) {
13921
+ copyTokensFromCache(db, tokenCached.sourceSessionId, resolvedSessionId);
13922
+ sessionId2 = resolvedSessionId;
13923
+ process.stderr.write(
13924
+ `Done: tokens reused from cache ${c.dim(`(source: ${tokenCached.sourceSessionId.slice(0, 12)})`)}
13925
+ `
13926
+ );
13927
+ db.close();
13928
+ await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13929
+ return;
13930
+ }
13931
+ }
13139
13932
  const prompt = await buildPrompt({
13140
13933
  skill,
13141
13934
  mode: "autonomous",
13142
13935
  rawTokensInline,
13143
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13936
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13144
13937
  tokensInline,
13145
13938
  tokenMapInline,
13146
13939
  outDir: process.cwd()
@@ -13177,6 +13970,9 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13177
13970
  `);
13178
13971
  }
13179
13972
  applyTokenToolCalls(db, resolvedSessionId, tokenCalls, []);
13973
+ if (!noCache) {
13974
+ storeCache(db, tokenInputHash, "token_set", "__tokens__", resolvedSessionId, false, tokenPromptHash);
13975
+ }
13180
13976
  sessionId2 = resolvedSessionId;
13181
13977
  const groupCount = tokenCalls.filter((tc) => tc.tool === "set_group").length;
13182
13978
  process.stderr.write(`Done: ${tokenCount} tokens, ${groupCount} groups stored
@@ -13188,7 +13984,10 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13188
13984
  await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13189
13985
  }
13190
13986
  function addAgentFlags(cmd) {
13191
- return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent");
13987
+ return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent").option(
13988
+ "--no-cache",
13989
+ "Bypass ALL fine-grained caches (extract, select, generate) and force AI re-run. Cache keys now factor in prompt content \u2014 changing the prompt file via --generate-prompt-path or --select-prompt-path will already bust the corresponding stage. Use --no-cache to force a full re-run."
13990
+ );
13192
13991
  }
13193
13992
  function registerInternalGenerateCommand(program) {
13194
13993
  const generate = program.command("__generate", { hidden: true }).description("Internal import pipeline generation command");
@@ -13204,6 +14003,12 @@ function registerInternalGenerateCommand(program) {
13204
14003
  "--existing-entities-path <path>",
13205
14004
  "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."
13206
14005
  );
14006
+ componentsCmd.option("--cache-status", "Check whether all selected component definitions are cached");
14007
+ componentsCmd.option("--restore-cache", "Restore cached component definitions into the current session");
14008
+ componentsCmd.option(
14009
+ "--cached-components <json>",
14010
+ "Component names already restored by a prior cache-status lookup; skip per-component cache lookup"
14011
+ );
13207
14012
  addAgentFlags(componentsCmd).action(async (opts) => {
13208
14013
  await runGenerateSkill("components", opts, opts.verbose ?? false);
13209
14014
  });
@@ -13229,6 +14034,7 @@ var init_command = __esm({
13229
14034
  init_agent_output();
13230
14035
  init_GenerateView();
13231
14036
  init_db();
14037
+ init_cache_keys();
13232
14038
  init_read_existing_contentful_entities_from_session();
13233
14039
  init_summarize_existing_contentful_entities();
13234
14040
  init_resolve_session_id();
@@ -13944,7 +14750,7 @@ var init_user_agent = __esm({
13944
14750
  // packages/experience-design-system-cli-legacy/src/apply/api-client.ts
13945
14751
  import { parseCDFComponents } from "@contentful/experience-design-system-types";
13946
14752
  function defaultSleep(delayMs) {
13947
- return new Promise((resolve20) => setTimeout(resolve20, delayMs));
14753
+ return new Promise((resolve21) => setTimeout(resolve21, delayMs));
13948
14754
  }
13949
14755
  function isTransientStatus(status) {
13950
14756
  return status >= 500 && status <= 599;
@@ -14334,7 +15140,7 @@ ${trimmed}`;
14334
15140
  const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
14335
15141
  const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
14336
15142
  const jitter = Math.random() * baseDelay * 0.15;
14337
- await new Promise((resolve20) => setTimeout(resolve20, baseDelay + jitter));
15143
+ await new Promise((resolve21) => setTimeout(resolve21, baseDelay + jitter));
14338
15144
  }
14339
15145
  }
14340
15146
  throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
@@ -15144,7 +15950,7 @@ var init_terminal_capabilities = __esm({
15144
15950
 
15145
15951
  // packages/experience-design-system-cli-legacy/src/apply/command.ts
15146
15952
  import { createElement as createElement2 } from "react";
15147
- import { readFile as readFile12, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15953
+ import { readFile as readFile13, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15148
15954
  import { join as join16 } from "node:path";
15149
15955
  import {
15150
15956
  validateCDF,
@@ -15165,7 +15971,7 @@ function dieWithApiError(error) {
15165
15971
  async function readJsonFile(flag, p) {
15166
15972
  let text;
15167
15973
  try {
15168
- text = await readFile12(p, "utf8");
15974
+ text = await readFile13(p, "utf8");
15169
15975
  } catch {
15170
15976
  return await die2(`Error: file not found: ${p} (from ${flag})`);
15171
15977
  }
@@ -15219,7 +16025,7 @@ async function readTokensFromPath(flag, p) {
15219
16025
  for (const file of files.sort()) {
15220
16026
  let text;
15221
16027
  try {
15222
- text = await readFile12(file, "utf8");
16028
+ text = await readFile13(file, "utf8");
15223
16029
  } catch {
15224
16030
  continue;
15225
16031
  }
@@ -15588,11 +16394,11 @@ var init_command2 = __esm({
15588
16394
  });
15589
16395
 
15590
16396
  // packages/experience-design-system-cli-legacy/src/print/validate/validators/read-json-file.ts
15591
- import { readFile as readFile13 } from "node:fs/promises";
16397
+ import { readFile as readFile14 } from "node:fs/promises";
15592
16398
  async function readJsonFile2(filePath) {
15593
16399
  let content;
15594
16400
  try {
15595
- content = await readFile13(filePath, "utf-8");
16401
+ content = await readFile14(filePath, "utf-8");
15596
16402
  } catch (err) {
15597
16403
  return {
15598
16404
  ok: false,
@@ -16003,7 +16809,7 @@ var init_ValidateView = __esm({
16003
16809
  // packages/experience-design-system-cli-legacy/src/print/command.ts
16004
16810
  import { createElement as createElement3 } from "react";
16005
16811
  import { access as access3, mkdir as mkdir3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
16006
- import { basename as basename5, resolve as resolve12 } from "node:path";
16812
+ import { basename as basename5, resolve as resolve13 } from "node:path";
16007
16813
  import { CDF_SCHEMA_URL } from "@contentful/experience-design-system-types";
16008
16814
  async function die3(message) {
16009
16815
  process.stderr.write(`${message}
@@ -16070,7 +16876,7 @@ function registerPrintCommand(program) {
16070
16876
  "--allow-empty",
16071
16877
  "Write an empty-but-present CDF file when no components are accepted (a subsequent push then removes ALL components from the target space). Without this, an empty accepted set is an error."
16072
16878
  ).action(async (opts) => {
16073
- const outPath = resolve12(opts.out);
16879
+ const outPath = resolve13(opts.out);
16074
16880
  await assertOutIsNotDirectory(outPath);
16075
16881
  const sessionId2 = await resolveSession(opts.session, "generate components");
16076
16882
  await bindAnalyticsSessionId(sessionId2);
@@ -16118,7 +16924,7 @@ function registerPrintCommand(program) {
16118
16924
  `
16119
16925
  );
16120
16926
  }
16121
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16927
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16122
16928
  await writeFile3(outPath, `${JSON.stringify(cdfObj, null, 2)}
16123
16929
  `);
16124
16930
  process.stdout.write(
@@ -16127,7 +16933,7 @@ function registerPrintCommand(program) {
16127
16933
  );
16128
16934
  });
16129
16935
  print.command("tokens").description("Write generated tokens to a DTCG JSON file").option("--session <id>", "Session ID to print from (default: most recent generate tokens session)").option("--out <path>", "Output file path", "tokens.json").action(async (opts) => {
16130
- const outPath = resolve12(opts.out);
16936
+ const outPath = resolve13(opts.out);
16131
16937
  await assertOutIsNotDirectory(outPath);
16132
16938
  const sessionId2 = await resolveSession(opts.session, "generate tokens");
16133
16939
  await bindAnalyticsSessionId(sessionId2);
@@ -16142,7 +16948,7 @@ function registerPrintCommand(program) {
16142
16948
  await die3(`Error: no generated tokens in session '${sessionId2}'. Run generate tokens first.`);
16143
16949
  }
16144
16950
  const tree = rebuildDTCGTree(result.groups, result.tokens);
16145
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16951
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16146
16952
  await writeFile3(outPath, `${JSON.stringify(tree, null, 2)}
16147
16953
  `);
16148
16954
  process.stdout.write(
@@ -16291,7 +17097,7 @@ function MapTokensView({ result, onExit }) {
16291
17097
  const timer = setTimeout(onExit, 100);
16292
17098
  return () => clearTimeout(timer);
16293
17099
  }, [onExit]);
16294
- const summary = `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
17100
+ const summary = result.cached ? "reused from cache" : `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
16295
17101
  return /* @__PURE__ */ jsx8(
16296
17102
  CommandCompletionView,
16297
17103
  {
@@ -16425,8 +17231,8 @@ var init_resolve_defaults = __esm({
16425
17231
 
16426
17232
  // packages/experience-design-system-cli-legacy/src/map-tokens/command.ts
16427
17233
  import { createElement as createElement4 } from "react";
16428
- import { readFile as readFile14 } from "node:fs/promises";
16429
- import { resolve as resolve13 } from "node:path";
17234
+ import { readFile as readFile15 } from "node:fs/promises";
17235
+ import { resolve as resolve14 } from "node:path";
16430
17236
  async function renderResult(result) {
16431
17237
  if (process.stdout.isTTY) {
16432
17238
  const { waitUntilExit } = renderWithGoodbye(
@@ -16434,13 +17240,12 @@ async function renderResult(result) {
16434
17240
  );
16435
17241
  await waitUntilExit();
16436
17242
  } else {
16437
- process.stdout.write(
16438
- `map tokens complete
17243
+ const summary = result.cached ? "cached" : `${result.applied} mapping(s) applied`;
17244
+ process.stdout.write(`map tokens complete
16439
17245
  agent: ${result.agent}
16440
17246
  session=${result.sessionId}
16441
- ${result.applied} mapping(s) applied
16442
- `
16443
- );
17247
+ ${summary}
17248
+ `);
16444
17249
  await exitWithAnalytics(0);
16445
17250
  }
16446
17251
  }
@@ -16462,7 +17267,7 @@ async function runMapTokens(opts) {
16462
17267
  let mapPromptPath;
16463
17268
  if (mapPromptOverride?.kind === "text") mapPromptText = mapPromptOverride.value;
16464
17269
  if (mapPromptOverride?.kind === "path") {
16465
- mapPromptPath = resolve13(mapPromptOverride.value);
17270
+ mapPromptPath = resolve14(mapPromptOverride.value);
16466
17271
  try {
16467
17272
  await resolvePromptOverride(mapPromptOverride);
16468
17273
  } catch (error) {
@@ -16482,7 +17287,7 @@ async function runMapTokens(opts) {
16482
17287
  const knownTokenPaths = new Set(tokenLeaves.map((t) => t.path));
16483
17288
  const tokenMapDiagnostics = [];
16484
17289
  if (opts.tokenMap) {
16485
- const fileMappings = JSON.parse(await readFile14(opts.tokenMap, "utf8"));
17290
+ const fileMappings = JSON.parse(await readFile15(opts.tokenMap, "utf8"));
16486
17291
  const validMappings = {};
16487
17292
  for (const [rawName, path] of Object.entries(fileMappings)) {
16488
17293
  if (knownTokenPaths.has(path)) {
@@ -16560,7 +17365,7 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16560
17365
  let existingTokensInline;
16561
17366
  if (opts.existingEntitiesPath) {
16562
17367
  const existingContentfulEntities = await readExistingContentfulEntitiesFromSession(
16563
- resolve13(opts.existingEntitiesPath)
17368
+ resolve14(opts.existingEntitiesPath)
16564
17369
  );
16565
17370
  if (existingContentfulEntities) {
16566
17371
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
@@ -16595,10 +17400,30 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16595
17400
  skipAgent: "true"
16596
17401
  });
16597
17402
  updateStep(db, stepId2, "complete", { applied: "0", skipAgent: "true" });
16598
- await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0 });
17403
+ await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0, cached: false });
16599
17404
  return;
16600
17405
  }
16601
17406
  const agent = configuredAgent;
17407
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
17408
+ const promptHash = await hashPromptForSkill(
17409
+ "map-tokens",
17410
+ agent,
17411
+ model,
17412
+ void 0,
17413
+ existingTokensInline ? [hashContent(existingTokensInline)] : [],
17414
+ mapPromptText
17415
+ );
17416
+ const inputHash = computeMapTokensInputHash(db, sessionId2, componentSourceRefs);
17417
+ if (!noCache) {
17418
+ const cached = lookupCache(db, inputHash, "token_mapping", "__map_tokens__", promptHash);
17419
+ if (cached) {
17420
+ const appliedFromCache = copyMapTokensFromCache(db, cached.sourceSessionId, sessionId2);
17421
+ const stepId2 = createStep(db, sessionId2, "map tokens", { agent, model: model ?? "" });
17422
+ updateStep(db, stepId2, "complete", { cached: "true", applied: String(appliedFromCache) });
17423
+ await renderResult({ agent, sessionId: sessionId2, applied: appliedFromCache, cached: true });
17424
+ return;
17425
+ }
17426
+ }
16602
17427
  const binary = resolveAgentBinary(agent);
16603
17428
  if (!await assertBinaryInPath(binary)) {
16604
17429
  die(
@@ -16618,20 +17443,23 @@ Install it, choose another agent with --agent, or use --print-prompt to run the
16618
17443
  }
16619
17444
  const { calls, warnings: parseWarnings } = parseMapTokenPropToolCallLines(result.stdout);
16620
17445
  const { applied, warnings } = applyMapTokenPropCalls(db, sessionId2, calls, parseWarnings);
17446
+ if (!noCache) {
17447
+ storeCache(db, inputHash, "token_mapping", "__map_tokens__", sessionId2, false, promptHash);
17448
+ }
16621
17449
  updateStep(db, stepId, "complete", { applied: String(applied), warnings: String(warnings.length) });
16622
17450
  if (warnings.length > 0) {
16623
17451
  process.stderr.write(`Warnings:
16624
17452
  ${warnings.map((w) => ` ${w}`).join("\n")}
16625
17453
  `);
16626
17454
  }
16627
- await renderResult({ agent, sessionId: sessionId2, applied });
17455
+ await renderResult({ agent, sessionId: sessionId2, applied, cached: false });
16628
17456
  } finally {
16629
17457
  db.close();
16630
17458
  }
16631
17459
  }
16632
17460
  function registerMapTokensCommand(program) {
16633
17461
  const map = program.command("map", { hidden: true }).description("Suggest token restrictions for generated design-token props");
16634
- const tokensCmd = map.command("tokens").description("Invoke a coding agent to suggest $token.allowed for design-category token props").option("--session <id>", "Session ID from generate components (defaults to most recent)").option("--print-prompt", "Print the prompt without invoking the agent").option("--skip-agent", "Resolve token defaults without agentic $token.allowed inference").option(
17462
+ const tokensCmd = map.command("tokens").description("Invoke a coding agent to suggest $token.allowed for design-category token props").option("--session <id>", "Session ID from generate components (defaults to most recent)").option("--print-prompt", "Print the prompt without invoking the agent").option("--skip-agent", "Resolve token defaults without agentic $token.allowed inference").option("--no-cache", "Bypass the map-tokens cache and force a re-run").option(
16635
17463
  "--prompt <stage=value>",
16636
17464
  "Override a stage prompt (repeatable). Used here for the map-tokens stage.",
16637
17465
  (v, acc) => [...acc, v],
@@ -16651,6 +17479,7 @@ var init_command4 = __esm({
16651
17479
  init_render_with_goodbye();
16652
17480
  init_src();
16653
17481
  init_db();
17482
+ init_cache_keys();
16654
17483
  init_read_existing_contentful_entities_from_session();
16655
17484
  init_summarize_existing_contentful_entities();
16656
17485
  init_command3();
@@ -16667,7 +17496,7 @@ var init_command4 = __esm({
16667
17496
  });
16668
17497
 
16669
17498
  // packages/experience-design-system-cli-legacy/src/import/path-utils.ts
16670
- import { resolve as resolve14 } from "node:path";
17499
+ import { resolve as resolve15 } from "node:path";
16671
17500
  import { homedir as homedir5 } from "node:os";
16672
17501
  function expandTilde(input) {
16673
17502
  if (input === "~" || input.startsWith("~/") || input.startsWith("~\\")) {
@@ -16681,7 +17510,7 @@ function normalizePath(input) {
16681
17510
  p = p.slice(1, -1);
16682
17511
  }
16683
17512
  p = expandTilde(p);
16684
- return resolve14(p);
17513
+ return resolve15(p);
16685
17514
  }
16686
17515
  var init_path_utils = __esm({
16687
17516
  "packages/experience-design-system-cli-legacy/src/import/path-utils.ts"() {
@@ -16906,7 +17735,7 @@ var init_save_path_resolver = __esm({
16906
17735
  });
16907
17736
 
16908
17737
  // packages/experience-design-system-cli-legacy/src/runs/store.ts
16909
- import { readFile as readFile15, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
17738
+ import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
16910
17739
  import { join as join20 } from "node:path";
16911
17740
  import { homedir as homedir6 } from "node:os";
16912
17741
  import { randomBytes as randomBytes2 } from "node:crypto";
@@ -16946,7 +17775,7 @@ function migrateRecord(rec) {
16946
17775
  }
16947
17776
  async function readFileMaybe() {
16948
17777
  try {
16949
- const raw = await readFile15(RUNS_PATH, "utf8");
17778
+ const raw = await readFile16(RUNS_PATH, "utf8");
16950
17779
  const parsed = JSON.parse(raw);
16951
17780
  if (!READABLE_VERSIONS.has(parsed.version)) {
16952
17781
  throw new Error(
@@ -17010,15 +17839,15 @@ var init_store = __esm({
17010
17839
  });
17011
17840
 
17012
17841
  // packages/experience-design-system-cli-legacy/src/runs/fingerprint.ts
17013
- import { createHash as createHash2 } from "node:crypto";
17014
- import { readFile as readFile16, stat as stat4 } from "node:fs/promises";
17015
- import { resolve as resolve15 } from "node:path";
17842
+ import { createHash as createHash6 } from "node:crypto";
17843
+ import { readFile as readFile17, stat as stat4 } from "node:fs/promises";
17844
+ import { resolve as resolve16 } from "node:path";
17016
17845
  function rowField(row, key) {
17017
17846
  const v = row[key];
17018
17847
  return typeof v === "string" ? v : null;
17019
17848
  }
17020
17849
  function sha256Hex(buf) {
17021
- return createHash2("sha256").update(buf).digest("hex");
17850
+ return createHash6("sha256").update(buf).digest("hex");
17022
17851
  }
17023
17852
  async function buildSourceFingerprint(opts) {
17024
17853
  const rows = opts.db.prepare("SELECT name, source_path FROM raw_components WHERE session_id = ?").all(opts.extractSessionId);
@@ -17028,7 +17857,7 @@ async function buildSourceFingerprint(opts) {
17028
17857
  const sourcePath = rowField(row, "source_path");
17029
17858
  const name = rowField(row, "name");
17030
17859
  if (!sourcePath) continue;
17031
- const abs = resolve15(sourcePath);
17860
+ const abs = resolve16(sourcePath);
17032
17861
  if (seen.has(abs)) continue;
17033
17862
  seen.add(abs);
17034
17863
  try {
@@ -17043,10 +17872,10 @@ async function buildSourceFingerprint(opts) {
17043
17872
  let rawTokensMtime = null;
17044
17873
  let rawTokensContentHash = null;
17045
17874
  if (opts.rawTokensPath) {
17046
- const abs = resolve15(opts.rawTokensPath);
17875
+ const abs = resolve16(opts.rawTokensPath);
17047
17876
  rawTokensPath = abs;
17048
17877
  try {
17049
- const [st, buf] = await Promise.all([stat4(abs), readFile16(abs)]);
17878
+ const [st, buf] = await Promise.all([stat4(abs), readFile17(abs)]);
17050
17879
  rawTokensMtime = st.mtime.toISOString();
17051
17880
  rawTokensContentHash = sha256Hex(buf);
17052
17881
  } catch {
@@ -18801,11 +19630,11 @@ var init_slot_graph = __esm({
18801
19630
  });
18802
19631
 
18803
19632
  // packages/experience-design-system-cli-legacy/src/analyze/select/parser.ts
18804
- import { createHash as createHash3 } from "node:crypto";
19633
+ import { createHash as createHash7 } from "node:crypto";
18805
19634
  import { access as access5 } from "node:fs/promises";
18806
- import { isAbsolute, relative, resolve as resolve16 } from "node:path";
19635
+ import { isAbsolute, relative, resolve as resolve17 } from "node:path";
18807
19636
  function createComponentId(name, resolvedSourcePath) {
18808
- const sourceHash = createHash3("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
19637
+ const sourceHash = createHash7("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
18809
19638
  return `${name}-${sourceHash}`;
18810
19639
  }
18811
19640
  async function resolveComponentSourcePath(source, reviewRoot) {
@@ -18817,7 +19646,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18817
19646
  throw new Error(`Unable to access component source at ${source}`);
18818
19647
  }
18819
19648
  }
18820
- const candidate = resolve16(reviewRoot, source);
19649
+ const candidate = resolve17(reviewRoot, source);
18821
19650
  const relativeToRoot = relative(reviewRoot, candidate);
18822
19651
  if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || isAbsolute(relativeToRoot)) {
18823
19652
  throw new Error(
@@ -18832,7 +19661,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18832
19661
  }
18833
19662
  }
18834
19663
  async function loadReviewInput(components, options = {}) {
18835
- const reviewRoot = resolve16(options.reviewRoot ?? process.cwd());
19664
+ const reviewRoot = resolve17(options.reviewRoot ?? process.cwd());
18836
19665
  const records = await Promise.all(
18837
19666
  components.map(async (component) => {
18838
19667
  let resolvedSourcePath;
@@ -18863,7 +19692,7 @@ var init_parser = __esm({
18863
19692
  });
18864
19693
 
18865
19694
  // packages/experience-design-system-cli-legacy/src/analyze/select/command.ts
18866
- import { access as access6, readFile as readFile17 } from "node:fs/promises";
19695
+ import { access as access6, readFile as readFile18 } from "node:fs/promises";
18867
19696
  async function loadAndValidateForReview(sessionId2, projectRoot) {
18868
19697
  const db = openPipelineDb();
18869
19698
  let rawComponents;
@@ -18881,7 +19710,7 @@ async function patchReviewStateWithValidationErrors(sessionId2, errors, opts = {
18881
19710
  let snapshot;
18882
19711
  try {
18883
19712
  await access6(paths.statePath);
18884
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19713
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18885
19714
  } catch {
18886
19715
  snapshot = await loadAndValidateForReview(sessionId2, void 0);
18887
19716
  snapshot = await ensureRefineSession(sessionId2, artifactsRoot, snapshot);
@@ -18932,7 +19761,7 @@ async function rejectComponentsByName(sessionId2, names, opts = {}) {
18932
19761
  const nameSet = new Set(names);
18933
19762
  let snapshot;
18934
19763
  try {
18935
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19764
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18936
19765
  } catch {
18937
19766
  return;
18938
19767
  }
@@ -19031,9 +19860,9 @@ function spawnGenerateChild(opts) {
19031
19860
  stderr += chunk;
19032
19861
  opts.onStderr?.(chunk);
19033
19862
  });
19034
- const donePromise = new Promise((resolve20) => {
19863
+ const donePromise = new Promise((resolve21) => {
19035
19864
  child.on("close", (code, signal) => {
19036
- resolve20({
19865
+ resolve21({
19037
19866
  exitCode: code ?? 0,
19038
19867
  signal: signal ?? null,
19039
19868
  stdout,
@@ -19041,7 +19870,7 @@ function spawnGenerateChild(opts) {
19041
19870
  });
19042
19871
  });
19043
19872
  child.on("error", (err) => {
19044
- resolve20({
19873
+ resolve21({
19045
19874
  exitCode: 1,
19046
19875
  signal: null,
19047
19876
  stdout,
@@ -27179,6 +28008,9 @@ function shouldGenerateAfterCredentialsValidated(opts) {
27179
28008
  function shouldSkipFinalReviewAfterCredentials(state) {
27180
28009
  return state.finalReviewPassed && state.generateSessionId != null;
27181
28010
  }
28011
+ function resolveNoCacheForGenerate(opts) {
28012
+ return opts.cliNoCache;
28013
+ }
27182
28014
  function shouldBypassPreview(state) {
27183
28015
  return state.credentialsSkipped === true;
27184
28016
  }
@@ -27205,43 +28037,70 @@ var init_wizard_state_transitions = __esm({
27205
28037
  async function runSelectionAgent(options) {
27206
28038
  const db = openPipelineDb();
27207
28039
  let components;
28040
+ const cachedDecisions = /* @__PURE__ */ new Map();
28041
+ const cliVersion2 = await getCliCacheVersion();
28042
+ const promptHash = await hashPromptForSkill(
28043
+ "select",
28044
+ options.agent,
28045
+ options.model,
28046
+ options.promptPath,
28047
+ [SELECT_PAYLOAD_FORMAT],
28048
+ options.promptText
28049
+ );
27208
28050
  try {
27209
28051
  components = loadRawComponents(db, options.sessionId);
28052
+ if (!options.noCache) {
28053
+ for (const component of components) {
28054
+ const componentHash = hashComponentShape(component);
28055
+ const cached = lookupSelectCache(db, componentHash, promptHash, cliVersion2);
28056
+ if (cached) {
28057
+ cachedDecisions.set(component.name, {
28058
+ decision: cached.decision,
28059
+ reason: cached.reason
28060
+ });
28061
+ }
28062
+ }
28063
+ }
27210
28064
  } finally {
27211
28065
  db.close();
27212
28066
  }
27213
28067
  if (components.length === 0) return;
27214
- const invoker2 = createLocalCliAgentInvoker({
27215
- onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
27216
- });
27217
- const selectBuckets = bucketComponentsProps(components);
27218
- for (const assignment of selectBuckets) {
27219
- getDebugLogger().event("analyze", "prop-buckets.select", { ...assignment });
27220
- }
27221
- const prompt = await buildPrompt({
27222
- skill: "select",
27223
- mode: "autonomous",
27224
- rawComponentsInline: JSON.stringify(
27225
- components.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
27226
- null,
27227
- 2
27228
- ),
27229
- propBucketsInline: JSON.stringify(selectBuckets),
27230
- outDir: process.cwd(),
27231
- ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
27232
- ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
27233
- });
27234
- const { result } = await invokeAgentWithOutput(
27235
- invoker2,
27236
- { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
27237
- false
27238
- );
27239
- if (result.timedOut) throw new Error("selection agent timed out");
27240
- if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
27241
- const parsed = parseSelectToolCallLines(result.stdout);
27242
- for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
28068
+ const componentsToRun = components.filter((component) => !cachedDecisions.has(component.name));
28069
+ let calls = [];
28070
+ if (componentsToRun.length > 0) {
28071
+ const invoker2 = createLocalCliAgentInvoker({
28072
+ onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
28073
+ });
28074
+ const selectBuckets = bucketComponentsProps(componentsToRun);
28075
+ for (const assignment of selectBuckets) {
28076
+ getDebugLogger().event("analyze", "prop-buckets.select", { ...assignment });
28077
+ }
28078
+ const prompt = await buildPrompt({
28079
+ skill: "select",
28080
+ mode: "autonomous",
28081
+ rawComponentsInline: JSON.stringify(
28082
+ componentsToRun.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
28083
+ null,
28084
+ 2
28085
+ ),
28086
+ propBucketsInline: JSON.stringify(selectBuckets),
28087
+ outDir: process.cwd(),
28088
+ ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
28089
+ ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
28090
+ });
28091
+ const { result } = await invokeAgentWithOutput(
28092
+ invoker2,
28093
+ { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
28094
+ false
28095
+ );
28096
+ if (result.timedOut) throw new Error("selection agent timed out");
28097
+ if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
28098
+ const parsed = parseSelectToolCallLines(result.stdout);
28099
+ for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
27243
28100
  `);
27244
- const byName = new Map(parsed.calls.map((call) => [call.name, call]));
28101
+ calls = parsed.calls;
28102
+ }
28103
+ const byName = new Map(calls.map((call) => [call.name, call]));
27245
28104
  const writeDb = openPipelineDb();
27246
28105
  try {
27247
28106
  writeDb.exec("BEGIN");
@@ -27249,15 +28108,27 @@ async function runSelectionAgent(options) {
27249
28108
  `UPDATE raw_components SET status = ?, reject_reason = ? WHERE session_id = ? AND component_id = ?`
27250
28109
  );
27251
28110
  for (const component of components) {
28111
+ const cached = cachedDecisions.get(component.name);
27252
28112
  const call = byName.get(component.name);
27253
- if (!call) continue;
27254
- const rejected = call.tool === "reject_component";
28113
+ if (!cached && !call) continue;
28114
+ const rejected = cached ? cached.decision === "rejected" : call.tool === "reject_component";
28115
+ const reason = cached ? cached.reason : call.reason ?? null;
27255
28116
  update.run(
27256
28117
  rejected ? "rejected" : "accepted",
27257
- rejected ? call.reason ?? null : null,
28118
+ rejected ? reason : null,
27258
28119
  options.sessionId,
27259
28120
  component.component_id
27260
28121
  );
28122
+ if (!cached) {
28123
+ storeSelectCache(
28124
+ writeDb,
28125
+ hashComponentShape(component),
28126
+ promptHash,
28127
+ cliVersion2,
28128
+ rejected ? "rejected" : "accepted",
28129
+ rejected ? reason : null
28130
+ );
28131
+ }
27261
28132
  }
27262
28133
  writeDb.exec("COMMIT");
27263
28134
  } catch (error) {
@@ -27267,7 +28138,7 @@ async function runSelectionAgent(options) {
27267
28138
  writeDb.close();
27268
28139
  }
27269
28140
  }
27270
- var DEFAULT_TIMEOUT_MS4;
28141
+ var SELECT_PAYLOAD_FORMAT, DEFAULT_TIMEOUT_MS4;
27271
28142
  var init_run_selection_agent = __esm({
27272
28143
  "packages/experience-design-system-cli-legacy/src/import/tui/run-selection-agent.ts"() {
27273
28144
  "use strict";
@@ -27276,6 +28147,8 @@ var init_run_selection_agent = __esm({
27276
28147
  init_debug_logger();
27277
28148
  init_agent_output();
27278
28149
  init_db();
28150
+ init_cache_keys();
28151
+ SELECT_PAYLOAD_FORMAT = "prop-buckets-v1";
27279
28152
  DEFAULT_TIMEOUT_MS4 = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 5 * 60 * 1e3);
27280
28153
  }
27281
28154
  });
@@ -27294,7 +28167,7 @@ __export(WizardApp_exports, {
27294
28167
  });
27295
28168
  import { useEffect as useEffect15, useRef as useRef10, useState as useState23 } from "react";
27296
28169
  import { Box as Box42, Text as Text46 } from "ink";
27297
- import { join as join23, resolve as resolve17 } from "node:path";
28170
+ import { join as join23, resolve as resolve18 } from "node:path";
27298
28171
  import { appendFileSync as appendFileSync2, writeFileSync } from "node:fs";
27299
28172
  import { access as access7, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
27300
28173
  import { tmpdir } from "node:os";
@@ -27307,21 +28180,28 @@ function buildGenerateComponentsArgs(opts) {
27307
28180
  if (opts.tokensPath) args.push("--tokens", opts.tokensPath);
27308
28181
  if (opts.model) args.push("--model", opts.model);
27309
28182
  if (opts.bedrock) args.push("--bedrock");
28183
+ if (opts.noCache) args.push("--no-cache");
27310
28184
  if (opts.generatePromptPath) args.push("--generate-prompt-path", opts.generatePromptPath);
27311
28185
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27312
28186
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
28187
+ if (opts.restoreCache) args.push("--restore-cache");
28188
+ if (opts.cachedComponents && opts.cachedComponents.length > 0) {
28189
+ args.push("--cached-components", JSON.stringify(opts.cachedComponents));
28190
+ }
27313
28191
  return args;
27314
28192
  }
27315
28193
  function buildGenerateTokensArgs(opts) {
27316
28194
  const args = [findCliPath(), "__generate", "tokens", "--agent", opts.agent, "--raw-tokens", opts.rawTokensPath];
27317
28195
  if (opts.model) args.push("--model", opts.model);
27318
28196
  if (opts.bedrock) args.push("--bedrock");
28197
+ if (opts.noCache) args.push("--no-cache");
27319
28198
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27320
28199
  return args;
27321
28200
  }
27322
28201
  function buildMapTokensArgs(opts) {
27323
28202
  const args = ["map", "tokens", "--session", opts.sessionId, "--agent", opts.agent];
27324
28203
  if (opts.model) args.push("--model", opts.model);
28204
+ if (opts.noCache) args.push("--no-cache");
27325
28205
  if (opts.skipAgent) args.push("--skip-agent");
27326
28206
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27327
28207
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
@@ -27413,6 +28293,7 @@ function WizardApp({
27413
28293
  initialProjectPath,
27414
28294
  host,
27415
28295
  promptOverrides,
28296
+ noCache = false,
27416
28297
  livePreview = true,
27417
28298
  generatePromptPath,
27418
28299
  skipMapTokens = false,
@@ -27437,6 +28318,7 @@ function WizardApp({
27437
28318
  });
27438
28319
  const allowEmptyDeleteAllRef = useRef10(false);
27439
28320
  const generateChildRef = useRef10(null);
28321
+ const generationCachePreflightRef = useRef10(null);
27440
28322
  const selectionToReviewStartedAtRef = useRef10(null);
27441
28323
  const credentialsValidationIdRef = useRef10(0);
27442
28324
  const pendingCredentialValidationRef = useRef10(null);
@@ -27451,8 +28333,9 @@ function WizardApp({
27451
28333
  const existingEntitiesPromiseRef = useRef10(null);
27452
28334
  const generatePromiseRef = useRef10(null);
27453
28335
  const rawTokensEntryReady = !!initialRawTokensPath;
28336
+ const effectiveNoCache = resolveNoCacheForGenerate({ cliNoCache: noCache });
27454
28337
  const initialStepResolved = rawTokensEntryReady ? initialProjectPath ? "path-validation" : "credentials" : initialProjectPath ? "token-input" : "welcome";
27455
- const initialOutDir = initialProjectPath ? join23(resolve17(initialProjectPath), ".contentful") : "";
28338
+ const initialOutDir = initialProjectPath ? join23(resolve18(initialProjectPath), ".contentful") : "";
27456
28339
  const [state, setState] = useState23({
27457
28340
  step: initialStepResolved,
27458
28341
  agent: initialAgent ?? "claude",
@@ -27628,13 +28511,14 @@ If you are using AWS Bedrock, run:
27628
28511
  if (!deferStep) update({ step: nextStep });
27629
28512
  return true;
27630
28513
  };
27631
- const runGenerateTokens = async (rawTokensPath, outDir) => {
28514
+ const runGenerateTokens = async (rawTokensPath, outDir, forceRegenerate = false) => {
27632
28515
  update({ tokenGenerationStatus: "running" });
27633
28516
  const tokenArgs = buildGenerateTokensArgs({
27634
28517
  rawTokensPath,
27635
28518
  agent: state.agent,
27636
28519
  ...state.agentModel ? { model: state.agentModel } : {},
27637
28520
  ...state.bedrock ? { bedrock: true } : {},
28521
+ noCache: effectiveNoCache || forceRegenerate,
27638
28522
  promptOverrides
27639
28523
  });
27640
28524
  const result = await runSpawnedCli(tokenArgs);
@@ -27683,6 +28567,7 @@ If you are using AWS Bedrock, run:
27683
28567
  const timer = startWizardTimer("token-mapping", {
27684
28568
  sessionId: sessionId2,
27685
28569
  silent,
28570
+ noCache: effectiveNoCache,
27686
28571
  skipAgent: skipMapTokens
27687
28572
  });
27688
28573
  let mappablePropCount = 0;
@@ -27691,7 +28576,7 @@ If you are using AWS Bedrock, run:
27691
28576
  const db = openPipelineDb();
27692
28577
  try {
27693
28578
  if (state.tokenSessionId && state.tokenSessionId !== sessionId2) {
27694
- copyTokensBetweenSessions(db, state.tokenSessionId, sessionId2);
28579
+ copyTokensFromCache(db, state.tokenSessionId, sessionId2);
27695
28580
  } else if (!state.tokenSessionId && state.tokensPath) {
27696
28581
  const tokens = await readTokensFromPath("tokens", state.tokensPath);
27697
28582
  storeDtcgTokens(db, sessionId2, [], tokens);
@@ -27731,6 +28616,7 @@ If you are using AWS Bedrock, run:
27731
28616
  sessionId: sessionId2,
27732
28617
  agent: state.agent,
27733
28618
  ...state.agentModel ? { model: state.agentModel } : {},
28619
+ noCache: effectiveNoCache,
27734
28620
  skipAgent: skipMapTokens,
27735
28621
  promptOverrides,
27736
28622
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {}
@@ -27810,7 +28696,7 @@ If you are using AWS Bedrock, run:
27810
28696
  return promise;
27811
28697
  };
27812
28698
  const runExtract = async (projectPath, showProgress = true) => {
27813
- const outDir = join23(resolve17(projectPath), ".contentful");
28699
+ const outDir = join23(resolve18(projectPath), ".contentful");
27814
28700
  const existingEntitiesExpected = !state.credentialsSkipped && Boolean(state.spaceId && state.environmentId && state.cmaToken && projectPath);
27815
28701
  update({
27816
28702
  ...showProgress ? { step: "extracting" } : {},
@@ -27825,6 +28711,7 @@ If you are using AWS Bedrock, run:
27825
28711
  });
27826
28712
  const existingEntitiesPromise = startExistingEntitiesFetch(projectPath, outDir);
27827
28713
  const extractArgs = [findCliPath(), "__extract", "--project", projectPath];
28714
+ if (effectiveNoCache) extractArgs.push("--no-cache");
27828
28715
  let selectionPromptText;
27829
28716
  let selectionPromptPath;
27830
28717
  try {
@@ -27833,7 +28720,7 @@ If you are using AWS Bedrock, run:
27833
28720
  const selectOverride = overrides.get("select");
27834
28721
  if (selectOverride?.kind === "text") selectionPromptText = selectOverride.value;
27835
28722
  if (selectOverride?.kind === "path") {
27836
- selectionPromptPath = resolve17(selectOverride.value);
28723
+ selectionPromptPath = resolve18(selectOverride.value);
27837
28724
  await resolvePromptOverride(selectOverride);
27838
28725
  }
27839
28726
  } catch (error) {
@@ -27857,7 +28744,8 @@ If you are using AWS Bedrock, run:
27857
28744
  agent: state.agent,
27858
28745
  ...state.agentModel ? { model: state.agentModel } : {},
27859
28746
  ...selectionPromptText !== void 0 ? { promptText: selectionPromptText } : {},
27860
- ...selectionPromptPath ? { promptPath: selectionPromptPath } : {}
28747
+ ...selectionPromptPath ? { promptPath: selectionPromptPath } : {},
28748
+ noCache: effectiveNoCache
27861
28749
  });
27862
28750
  })().catch((error) => {
27863
28751
  selectionError = error;
@@ -28002,17 +28890,136 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28002
28890
  generateProgress: null
28003
28891
  }));
28004
28892
  };
28005
- const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2) => ({
28893
+ const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => ({
28006
28894
  sessionId: extractSessionId,
28007
28895
  tokensPath,
28008
28896
  agent: state.agent,
28009
28897
  ...state.agentModel ? { model: state.agentModel } : {},
28010
28898
  ...state.bedrock ? { bedrock: true } : {},
28899
+ noCache: effectiveNoCache,
28011
28900
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {},
28012
28901
  ...generatePromptPath2 ? { generatePromptPath: generatePromptPath2 } : {},
28013
- promptOverrides
28902
+ promptOverrides,
28903
+ ...cachedComponents && cachedComponents.length > 0 ? { cachedComponents } : {}
28014
28904
  });
28015
- const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2));
28905
+ const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents));
28906
+ const buildGenerateCliArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateComponentsArgs(
28907
+ buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents)
28908
+ );
28909
+ const checkGenerateCache = async (extractSessionId, tokensPath) => {
28910
+ const timer = startWizardTimer("generation-cache-status", {
28911
+ sessionId: extractSessionId,
28912
+ noCache: effectiveNoCache
28913
+ });
28914
+ if (effectiveNoCache) {
28915
+ finishWizardTimer("generation-cache-status", timer, { status: "skipped" });
28916
+ return false;
28917
+ }
28918
+ const result = await runCli([...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"]);
28919
+ const hit = result.exitCode === 0 && /^cache-status=hit$/m.test(result.stdout);
28920
+ finishWizardTimer("generation-cache-status", timer, {
28921
+ status: hit ? "hit" : "miss",
28922
+ exitCode: result.exitCode,
28923
+ ...result.exitCode !== 0 ? { stderrTail: result.stderr.trim().slice(-500) } : {}
28924
+ });
28925
+ return hit;
28926
+ };
28927
+ const checkGenerateCacheComponents = async (extractSessionId, tokensPath, restoreCache = false) => {
28928
+ const timer = startWizardTimer("generation-cache-components", {
28929
+ sessionId: extractSessionId,
28930
+ restoreCache,
28931
+ noCache: effectiveNoCache
28932
+ });
28933
+ if (effectiveNoCache) {
28934
+ finishWizardTimer("generation-cache-components", timer, { status: "skipped" });
28935
+ return /* @__PURE__ */ new Set();
28936
+ }
28937
+ const args = [...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"];
28938
+ if (restoreCache) args.push("--restore-cache");
28939
+ const result = await runCli(args);
28940
+ if (result.exitCode !== 0) {
28941
+ finishWizardTimer("generation-cache-components", timer, {
28942
+ status: "error",
28943
+ exitCode: result.exitCode,
28944
+ stderrTail: result.stderr.trim().slice(-500)
28945
+ });
28946
+ return /* @__PURE__ */ new Set();
28947
+ }
28948
+ const line = /^cache-components=(.+)$/m.exec(result.stdout)?.[1];
28949
+ if (!line) {
28950
+ finishWizardTimer("generation-cache-components", timer, {
28951
+ status: "missing-output",
28952
+ exitCode: result.exitCode,
28953
+ cachedCount: 0
28954
+ });
28955
+ return /* @__PURE__ */ new Set();
28956
+ }
28957
+ try {
28958
+ const names = JSON.parse(line);
28959
+ const cachedNames = new Set(
28960
+ Array.isArray(names) ? names.filter((name) => typeof name === "string") : []
28961
+ );
28962
+ finishWizardTimer("generation-cache-components", timer, {
28963
+ status: "complete",
28964
+ exitCode: result.exitCode,
28965
+ cachedCount: cachedNames.size
28966
+ });
28967
+ return cachedNames;
28968
+ } catch {
28969
+ finishWizardTimer("generation-cache-components", timer, {
28970
+ status: "invalid-output",
28971
+ exitCode: result.exitCode,
28972
+ cachedCount: 0
28973
+ });
28974
+ return /* @__PURE__ */ new Set();
28975
+ }
28976
+ };
28977
+ useEffect15(() => {
28978
+ if (state.step !== "extracting" && state.step !== "scope-gate" || !state.extractSessionId || state.existingEntitiesStatus === "running" || state.existingEntitiesStatus === "idle" || state.selectionAgentStatus !== "complete" || state.compositionPhase !== "done" || state.rawTokensPath && state.tokenGenerationStatus !== "complete" || effectiveNoCache) {
28979
+ return;
28980
+ }
28981
+ const sessionId2 = state.extractSessionId;
28982
+ if (generationCachePreflightRef.current?.sessionId === sessionId2) return;
28983
+ const timer = startWizardTimer("generation-cache-preflight", { sessionId: sessionId2 });
28984
+ const db = openPipelineDb();
28985
+ let generatedNames = null;
28986
+ try {
28987
+ const rows = db.prepare("SELECT name, status FROM raw_components WHERE session_id = ?").all(sessionId2);
28988
+ if (rows.length > 0 && rows.every((row) => row.status === "generated")) {
28989
+ generatedNames = new Set(rows.map((row) => row.name));
28990
+ }
28991
+ } finally {
28992
+ db.close();
28993
+ }
28994
+ if (generatedNames) {
28995
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: Promise.resolve(generatedNames) };
28996
+ finishWizardTimer("generation-cache-preflight", timer, {
28997
+ status: "complete",
28998
+ source: "session-db",
28999
+ cachedCount: generatedNames.size
29000
+ });
29001
+ return;
29002
+ }
29003
+ const promise = checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
29004
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise };
29005
+ void promise.then((cachedNames) => {
29006
+ finishWizardTimer("generation-cache-preflight", timer, {
29007
+ status: "complete",
29008
+ source: "cache-status",
29009
+ cachedCount: cachedNames.size
29010
+ });
29011
+ });
29012
+ }, [
29013
+ state.step,
29014
+ state.extractSessionId,
29015
+ state.tokensPath,
29016
+ state.rawTokensPath,
29017
+ state.tokenGenerationStatus,
29018
+ state.selectionAgentStatus,
29019
+ state.compositionPhase,
29020
+ state.existingEntitiesStatus,
29021
+ state.existingEntitiesPath
29022
+ ]);
28016
29023
  const startGeneratePrefetch = (extractSessionId, tokensPath) => {
28017
29024
  const args = buildGenerateArgs(extractSessionId, tokensPath);
28018
29025
  let progressCursor = null;
@@ -28067,13 +29074,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28067
29074
  });
28068
29075
  return donePromise;
28069
29076
  };
28070
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false) => {
29077
+ const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false, cachedComponents = []) => {
28071
29078
  const timer = startWizardTimer("component-generation", {
28072
29079
  sessionId: extractSessionId,
28073
29080
  acceptedCount,
28074
- suppressScreen
29081
+ suppressScreen,
29082
+ noCache: effectiveNoCache
28075
29083
  });
28076
- const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath);
29084
+ const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath, cachedComponents);
28077
29085
  let progressCursor = state.generateProgress;
28078
29086
  const { donePromise } = spawnGenerateChild({
28079
29087
  command: "node",
@@ -28143,6 +29151,25 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28143
29151
  finishSelectionToReviewTimer("error");
28144
29152
  }
28145
29153
  };
29154
+ const finishCachedGeneration = async (sessionId2, acceptedCount) => {
29155
+ if (await runMapTokens2(sessionId2, true)) {
29156
+ finishSelectionToReviewTimer("cached");
29157
+ update(
29158
+ {
29159
+ acceptedCount,
29160
+ autoRejectedCount: 0,
29161
+ generateSessionId: sessionId2,
29162
+ generatedCount: acceptedCount,
29163
+ generateProgress: null,
29164
+ mapTokensStatus: "complete",
29165
+ step: "final-review"
29166
+ },
29167
+ { clearScreen: false }
29168
+ );
29169
+ } else {
29170
+ finishSelectionToReviewTimer("error");
29171
+ }
29172
+ };
28146
29173
  const advanceToPushFlow = (generatedAcceptedCount) => {
28147
29174
  update({ generatedAcceptedCount });
28148
29175
  void runSaveAndPush();
@@ -28327,8 +29354,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28327
29354
  }
28328
29355
  }
28329
29356
  if (await runAgentAuthCheck("generating", true)) {
28330
- update({ step: "generating" });
28331
- void runGenerate(sid, state.tokensPath, state.acceptedCount);
29357
+ const cacheHit = await checkGenerateCache(sid, state.tokensPath);
29358
+ if (cacheHit) {
29359
+ update({ step: "generating" });
29360
+ void finishCachedGeneration(sid, state.acceptedCount);
29361
+ } else {
29362
+ update({ step: "generating" });
29363
+ void runGenerate(sid, state.tokensPath, state.acceptedCount);
29364
+ }
28332
29365
  }
28333
29366
  return;
28334
29367
  }
@@ -28692,7 +29725,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28692
29725
  });
28693
29726
  return { ok: false };
28694
29727
  }
28695
- await new Promise((resolve20) => setTimeout(resolve20, SAVE_CONFIRMATION_DELAY_MS));
29728
+ await new Promise((resolve21) => setTimeout(resolve21, SAVE_CONFIRMATION_DELAY_MS));
28696
29729
  update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
28697
29730
  return { ok: true };
28698
29731
  };
@@ -28769,6 +29802,12 @@ If using a custom host configuration, make sure the space exists on that host.`
28769
29802
  tokenReuseChecked.current = true;
28770
29803
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28771
29804
  const existingTokensPath = join23(state.outDir, "tokens.json");
29805
+ if (effectiveNoCache) {
29806
+ tokenGenerationPromiseRef.current = runAgentAuthCheck(tokenNextStep).then(
29807
+ (ok2) => ok2 ? runGenerateTokens(state.rawTokensPath, state.outDir) : false
29808
+ );
29809
+ return;
29810
+ }
28772
29811
  (async () => {
28773
29812
  try {
28774
29813
  await access7(existingTokensPath);
@@ -28852,7 +29891,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28852
29891
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28853
29892
  update({ step: tokenNextStep });
28854
29893
  if (await runAgentAuthCheck(tokenNextStep)) {
28855
- tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir);
29894
+ tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir, true);
28856
29895
  }
28857
29896
  },
28858
29897
  onQuit: () => process.exit(0)
@@ -28951,6 +29990,29 @@ If using a custom host configuration, make sure the space exists on that host.`
28951
29990
  acceptedCount: decisions.accepted.length,
28952
29991
  rejectedCount: decisions.rejected.length
28953
29992
  }) : null;
29993
+ const preflight = generationCachePreflightRef.current;
29994
+ const cachePromise = preflight?.sessionId === sessionId2 ? preflight.promise : checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
29995
+ if (!preflight || preflight.sessionId !== sessionId2) {
29996
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: cachePromise };
29997
+ }
29998
+ const cacheWaitStartedAt = startWizardTimer("selection-cache-restoration-wait", {
29999
+ sessionId: sessionId2,
30000
+ preflight: Boolean(preflight?.sessionId === sessionId2)
30001
+ });
30002
+ try {
30003
+ const cachedNames = await cachePromise;
30004
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30005
+ status: "complete",
30006
+ cachedCount: cachedNames.size
30007
+ });
30008
+ } catch (error) {
30009
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30010
+ status: "error",
30011
+ error: error instanceof Error ? error.message : String(error)
30012
+ });
30013
+ finishSelectionToReviewTimer("error");
30014
+ throw error;
30015
+ }
28954
30016
  await runScopeGate({
28955
30017
  sessionId: sessionId2,
28956
30018
  decisions,
@@ -28959,12 +30021,35 @@ If using a custom host configuration, make sure the space exists on that host.`
28959
30021
  if (shouldGenerateAfterScopeGate({ acceptedCount })) {
28960
30022
  const authPromise = runAgentAuthCheck("generating", true);
28961
30023
  if (await authPromise) {
28962
- update({
28963
- step: "generating",
28964
- generateProgress: null,
28965
- acceptedCount
30024
+ const comparisonStartedAt = startWizardTimer("generation-cache-set-comparison", {
30025
+ sessionId: sid,
30026
+ acceptedCount: decisions?.accepted.length ?? 0
30027
+ });
30028
+ let cachedNames = await cachePromise;
30029
+ const acceptedNames = decisions?.accepted ?? [];
30030
+ let cacheHit = !effectiveNoCache && acceptedNames.length > 0 && acceptedNames.every((name) => cachedNames.has(name));
30031
+ if (!cacheHit && !effectiveNoCache && acceptedNames.length > 0) {
30032
+ cachedNames = await checkGenerateCacheComponents(sid, state.tokensPath, true);
30033
+ cacheHit = acceptedNames.every((name) => cachedNames.has(name));
30034
+ }
30035
+ finishWizardTimer("generation-cache-set-comparison", comparisonStartedAt, {
30036
+ status: "complete",
30037
+ cacheHit,
30038
+ cachedCount: cachedNames.size,
30039
+ acceptedCount: acceptedNames.length
28966
30040
  });
28967
- void runGenerate(sid, state.tokensPath, acceptedCount);
30041
+ if (cacheHit) {
30042
+ void finishCachedGeneration(sid, acceptedCount);
30043
+ } else {
30044
+ const acceptedNameSet = new Set(acceptedNames);
30045
+ const restoredCachedNames = [...cachedNames].filter((name) => acceptedNameSet.has(name));
30046
+ update({
30047
+ step: "generating",
30048
+ generateProgress: null,
30049
+ acceptedCount
30050
+ });
30051
+ void runGenerate(sid, state.tokensPath, acceptedCount, false, restoredCachedNames);
30052
+ }
28968
30053
  } else {
28969
30054
  finishSelectionToReviewTimer("error");
28970
30055
  }
@@ -29330,7 +30415,7 @@ function registerImportCommand(program) {
29330
30415
  "Override a composition, selection, token, generation, or token-mapping prompt (repeatable). value is a file path or literal text, e.g. --prompt select=./p.md",
29331
30416
  (v, acc) => [...acc, v],
29332
30417
  []
29333
- ).action(async (opts) => {
30418
+ ).option("--no-cache", "Re-run all steps even if output already exists").action(async (opts) => {
29334
30419
  const interactiveTerminalSupported = getInteractiveTerminalSupport().supported;
29335
30420
  if (opts.tokens !== void 0) {
29336
30421
  const { access: access9 } = await import("node:fs/promises");
@@ -29367,6 +30452,7 @@ function registerImportCommand(program) {
29367
30452
  ...resolvedModel ? { initialModel: resolvedModel } : {},
29368
30453
  initialProjectPath: opts.project !== "." ? normalizePath(opts.project) : void 0,
29369
30454
  ...buildCompositionForwardingOptions(opts),
30455
+ noCache: opts.cache === false,
29370
30456
  skipMapTokens: false,
29371
30457
  livePreview: true,
29372
30458
  generatePromptPath: creds.generatePromptPath,
@@ -29454,7 +30540,7 @@ async function pathExists3(path) {
29454
30540
  return access8(path).then(() => true).catch(() => false);
29455
30541
  }
29456
30542
  function runSpawn(cmd, args, opts = {}) {
29457
- return new Promise((resolve20) => {
30543
+ return new Promise((resolve21) => {
29458
30544
  const child = spawn6(cmd, args, {
29459
30545
  cwd: opts.cwd,
29460
30546
  env: opts.env ?? process.env,
@@ -29466,7 +30552,7 @@ function runSpawn(cmd, args, opts = {}) {
29466
30552
  const settle = (result) => {
29467
30553
  if (settled) return;
29468
30554
  settled = true;
29469
- resolve20(result);
30555
+ resolve21(result);
29470
30556
  };
29471
30557
  child.on("error", (err) => settle({ exitCode: 1, stdout: "", stderr: err.message }));
29472
30558
  child.stdout.on("data", (d) => {
@@ -30667,13 +31753,13 @@ function PrerequisitesScreen({
30667
31753
  const driver = {
30668
31754
  ...base,
30669
31755
  emit: (event) => setEvents((current) => [...current, event]),
30670
- confirm: (question, defaultYes = true) => new Promise((resolve20) => {
31756
+ confirm: (question, defaultYes = true) => new Promise((resolve21) => {
30671
31757
  setConfirm({
30672
31758
  question,
30673
31759
  defaultYes,
30674
31760
  resolve: (answer) => {
30675
31761
  setConfirm(null);
30676
- resolve20(answer);
31762
+ resolve21(answer);
30677
31763
  }
30678
31764
  });
30679
31765
  })
@@ -30748,8 +31834,8 @@ function SetupScreen({
30748
31834
  useEffect21(() => {
30749
31835
  if (startedRef.current) return;
30750
31836
  startedRef.current = true;
30751
- const runScreen = (build) => new Promise((resolve20) => {
30752
- setScreen(build(resolve20));
31837
+ const runScreen = (build) => new Promise((resolve21) => {
31838
+ setScreen(build(resolve21));
30753
31839
  });
30754
31840
  void (async () => {
30755
31841
  const results = [];
@@ -31357,8 +32443,11 @@ var init_source_call_site_evidence = __esm({
31357
32443
  });
31358
32444
 
31359
32445
  // packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts
31360
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile18, stat as stat6 } from "node:fs/promises";
31361
- import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolve18 } from "node:path";
32446
+ import { mkdir as mkdir6, readdir as readdir3, readFile as readFile19, stat as stat6 } from "node:fs/promises";
32447
+ import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolve19 } from "node:path";
32448
+ function resolveExtractNoCache(opts) {
32449
+ return opts.noCache === true || opts.cache === false;
32450
+ }
31362
32451
  function isDenylistedNoiseFile(name) {
31363
32452
  return DENYLISTED_EXACT_FILE_NAMES.has(name) || DENYLISTED_FILE_NAME_PATTERNS.some((pattern) => pattern.test(name));
31364
32453
  }
@@ -31372,12 +32461,12 @@ async function retryDatabaseWrite(operation, attempts = 8) {
31372
32461
  } catch (error) {
31373
32462
  const message = error instanceof Error ? error.message : String(error);
31374
32463
  if (!/database is locked|database is busy/i.test(message) || attempt >= attempts) throw error;
31375
- await new Promise((resolve20) => setTimeout(resolve20, Math.min(250 * attempt, 2e3)));
32464
+ await new Promise((resolve21) => setTimeout(resolve21, Math.min(250 * attempt, 2e3)));
31376
32465
  }
31377
32466
  }
31378
32467
  }
31379
32468
  function resolveFromProjectRoot(projectRoot, inputPath) {
31380
- return isAbsolute2(inputPath) ? inputPath : resolve18(projectRoot, inputPath);
32469
+ return isAbsolute2(inputPath) ? inputPath : resolve19(projectRoot, inputPath);
31381
32470
  }
31382
32471
  async function pathExists4(path) {
31383
32472
  return Boolean(await stat6(path).catch(() => null));
@@ -31424,7 +32513,7 @@ async function readCandidateFiles(components, extraFiles = []) {
31424
32513
  await Promise.all(
31425
32514
  [...paths].map(async (p) => {
31426
32515
  try {
31427
- const content = await readFile18(p, "utf8");
32516
+ const content = await readFile19(p, "utf8");
31428
32517
  out.push({ path: p, content });
31429
32518
  } catch {
31430
32519
  }
@@ -31437,7 +32526,8 @@ function registerInternalExtractCommand(program) {
31437
32526
  "--resolve-unreachable <mode>",
31438
32527
  "Retry pass for unresolved Svelte Props types: 'auto' (default), 'always', or 'never'",
31439
32528
  "auto"
31440
- ).action(async (opts) => {
32529
+ ).option("--no-cache", "Re-run extraction even when all source files are unchanged").action(async (opts) => {
32530
+ const noCache = resolveExtractNoCache(opts);
31441
32531
  const resolveUnreachable = (() => {
31442
32532
  const v = opts.resolveUnreachable ?? "auto";
31443
32533
  if (v !== "auto" && v !== "always" && v !== "never") {
@@ -31447,7 +32537,7 @@ function registerInternalExtractCommand(program) {
31447
32537
  }
31448
32538
  return v;
31449
32539
  })();
31450
- const projectRoot = resolve18(opts.project);
32540
+ const projectRoot = resolve19(opts.project);
31451
32541
  const outDir = join27(projectRoot, ".contentful");
31452
32542
  let sourceDirectory;
31453
32543
  if (opts.dir !== void 0) {
@@ -31471,16 +32561,50 @@ function registerInternalExtractCommand(program) {
31471
32561
  process.stderr.write(`progress=scan-done:${sourceFiles.length}
31472
32562
  `);
31473
32563
  }
31474
- const extraction = await extractComponents(
31475
- sourceFiles,
31476
- ({ filesProcessed, componentsFound }) => {
32564
+ const extractionCacheDb = openPipelineDb();
32565
+ let extraction;
32566
+ let extractionCacheHits = 0;
32567
+ try {
32568
+ const cacheVersion = await getCliCacheVersion();
32569
+ const cachedByPath = /* @__PURE__ */ new Map();
32570
+ if (!noCache && sourceFiles.length > 0) {
32571
+ const hashes = await Promise.all(
32572
+ sourceFiles.map(async (filePath) => [filePath, await hashFile(filePath)])
32573
+ );
32574
+ for (const [filePath, fileHash] of hashes) {
32575
+ cachedByPath.set(filePath, lookupExtractCache(extractionCacheDb, fileHash, cacheVersion));
32576
+ }
32577
+ }
32578
+ const allFilesCached = !noCache && sourceFiles.length > 0 && sourceFiles.every((filePath) => cachedByPath.get(filePath) !== null);
32579
+ if (allFilesCached) {
32580
+ const cachedComponents = sourceFiles.flatMap((filePath) => cachedByPath.get(filePath).components);
32581
+ extractionCacheHits = sourceFiles.length;
32582
+ extraction = { components: cachedComponents, warnings: [], exclusions: [] };
31477
32583
  if (!process.stdout.isTTY) {
31478
- process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
31479
- `);
32584
+ process.stderr.write(
32585
+ `progress=extract:${sourceFiles.length}/${sourceFiles.length}:${cachedComponents.length}
32586
+ `
32587
+ );
31480
32588
  }
31481
- },
31482
- { resolveUnreachable, projectRoot }
31483
- );
32589
+ getDebugLogger().event("analyze", "extract.cache-hit", {
32590
+ files: sourceFiles.length,
32591
+ components: cachedComponents.length
32592
+ });
32593
+ } else {
32594
+ extraction = await extractComponents(
32595
+ sourceFiles,
32596
+ ({ filesProcessed, componentsFound }) => {
32597
+ if (!process.stdout.isTTY) {
32598
+ process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
32599
+ `);
32600
+ }
32601
+ },
32602
+ { resolveUnreachable, projectRoot }
32603
+ );
32604
+ }
32605
+ } finally {
32606
+ extractionCacheDb.close();
32607
+ }
31484
32608
  await mkdir6(outDir, { recursive: true });
31485
32609
  const db = openPipelineDb();
31486
32610
  const { sessionId: sessionId2 } = getOrCreateSession(db, void 0, void 0, {
@@ -31495,6 +32619,25 @@ function registerInternalExtractCommand(program) {
31495
32619
  const stepId = createStep(db, sessionId2, "analyze extract", {
31496
32620
  project: projectRoot
31497
32621
  });
32622
+ if (extractionCacheHits === 0 && !noCache) {
32623
+ const cacheVersion = await getCliCacheVersion();
32624
+ const componentsBySourcePath = /* @__PURE__ */ new Map();
32625
+ for (const component of extraction.components) {
32626
+ if (!component.sourcePath) continue;
32627
+ const components = componentsBySourcePath.get(component.sourcePath) ?? [];
32628
+ components.push(component);
32629
+ componentsBySourcePath.set(component.sourcePath, components);
32630
+ }
32631
+ for (const filePath of sourceFiles) {
32632
+ storeExtractCache(
32633
+ db,
32634
+ filePath,
32635
+ await hashFile(filePath),
32636
+ cacheVersion,
32637
+ componentsBySourcePath.get(filePath) ?? []
32638
+ );
32639
+ }
32640
+ }
31498
32641
  const classifiedComponents = extraction.components.map(preClassifyComponent);
31499
32642
  for (const exclusion of extraction.exclusions ?? []) {
31500
32643
  getDebugLogger().event("filter", "extract.excluded", { ...exclusion });
@@ -31599,6 +32742,7 @@ var init_extract_command = __esm({
31599
32742
  "use strict";
31600
32743
  init_src2();
31601
32744
  init_db();
32745
+ init_cache_keys();
31602
32746
  init_cycle_detection();
31603
32747
  init_resolve_mapping();
31604
32748
  init_manifest_doc_evidence();
@@ -31697,7 +32841,7 @@ __export(program_exports, {
31697
32841
  });
31698
32842
  import { spawn as spawn7 } from "node:child_process";
31699
32843
  import { fileURLToPath as fileURLToPath3 } from "node:url";
31700
- import { dirname as dirname18, join as join28, resolve as resolve19 } from "node:path";
32844
+ import { dirname as dirname18, join as join28, resolve as resolve20 } from "node:path";
31701
32845
  import { readFileSync as readFileSync10 } from "node:fs";
31702
32846
  import { Command } from "commander";
31703
32847
  async function runBuild(opts) {
@@ -31727,7 +32871,7 @@ function resolveBedrockFromAncestors(actionCommand) {
31727
32871
  }
31728
32872
  function registerBuildCommand(program) {
31729
32873
  program.command("build").description("Rebuild from source and re-link exo/experiences binaries to this build").helpOption(false).action(async () => {
31730
- const pkgRoot = resolve19(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
32874
+ const pkgRoot = resolve20(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
31731
32875
  process.stderr.write("\u2699 Building from source...\n");
31732
32876
  const { exitCode } = await runBuild({
31733
32877
  spawnFn: () => spawn7("pnpm", ["build"], { cwd: pkgRoot, stdio: "inherit" }),