@contentful/experience-design-system-cli 2.35.1-dev-build-5446cce7.0 → 2.35.1-dev-build-4f1ad8cb.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";
@@ -10308,6 +10393,41 @@ var init_read3 = __esm({
10308
10393
  }
10309
10394
  });
10310
10395
 
10396
+ // packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts
10397
+ import { createHash as createHash2 } from "node:crypto";
10398
+ function hashComponentShape(component) {
10399
+ const payload = {
10400
+ framework: component.framework,
10401
+ name: component.name,
10402
+ source: component.source,
10403
+ props: component.props.map((p) => ({
10404
+ name: p.name,
10405
+ type: p.type
10406
+ })),
10407
+ slots: component.slots.map((s) => ({
10408
+ name: s.name,
10409
+ isDefault: s.isDefault
10410
+ }))
10411
+ };
10412
+ return createHash2("sha256").update(JSON.stringify(payload)).digest("hex");
10413
+ }
10414
+ var init_hash_component_shape = __esm({
10415
+ "packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts"() {
10416
+ "use strict";
10417
+ }
10418
+ });
10419
+
10420
+ // packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts
10421
+ import { createHash as createHash3 } from "node:crypto";
10422
+ function hashTokenContent(rawTokenContent) {
10423
+ return createHash3("sha256").update(rawTokenContent.trim()).digest("hex");
10424
+ }
10425
+ var init_hash_token_content = __esm({
10426
+ "packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts"() {
10427
+ "use strict";
10428
+ }
10429
+ });
10430
+
10311
10431
  // packages/experience-design-system-cli-legacy/src/session/repositories/tokens/write.ts
10312
10432
  function deleteRawTokensForSession(db, sessionId2) {
10313
10433
  db.prepare("DELETE FROM raw_token_groups WHERE session_id = ?").run(sessionId2);
@@ -10471,17 +10591,47 @@ var init_replace_raw_prop_token_paths = __esm({
10471
10591
  }
10472
10592
  });
10473
10593
 
10594
+ // packages/experience-design-system-cli-legacy/src/session/cache-keys.ts
10595
+ var cache_keys_exports = {};
10596
+ __export(cache_keys_exports, {
10597
+ hashContent: () => hashContent,
10598
+ hashFile: () => hashFile,
10599
+ hashPromptForSkill: () => hashPromptForSkill
10600
+ });
10601
+ import { createHash as createHash4 } from "node:crypto";
10602
+ import { readFile as readFile7 } from "node:fs/promises";
10603
+ import { resolve as resolve8 } from "node:path";
10604
+ function hashContent(content) {
10605
+ return createHash4("sha256").update(content).digest("hex");
10606
+ }
10607
+ async function hashFile(filePath) {
10608
+ const content = await readFile7(filePath, "utf8");
10609
+ return hashContent(content);
10610
+ }
10611
+ async function hashPromptForSkill(skill, agent, model, skillPathOverride, extraInputs = [], skillContentOverride) {
10612
+ const promptHash = skillContentOverride ? hashContent(skillContentOverride) : skillPathOverride ? await hashFile(resolve8(skillPathOverride)) : await hashFile(resolveSkillPath(skill));
10613
+ const extraSegment = extraInputs.length > 0 ? `|${extraInputs.join("|")}` : "";
10614
+ return hashContent(`${promptHash}|${agent}|${model ?? ""}${extraSegment}`);
10615
+ }
10616
+ var init_cache_keys = __esm({
10617
+ "packages/experience-design-system-cli-legacy/src/session/cache-keys.ts"() {
10618
+ "use strict";
10619
+ init_src();
10620
+ }
10621
+ });
10622
+
10474
10623
  // packages/experience-design-system-cli-legacy/src/session/db.ts
10475
10624
  import { DatabaseSync } from "node:sqlite";
10476
10625
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
10477
- import { readFile as readFile7 } from "node:fs/promises";
10626
+ import { readFile as readFile8 } from "node:fs/promises";
10627
+ import { createHash as createHash5 } from "node:crypto";
10478
10628
  import { homedir as homedir3 } from "node:os";
10479
- import { dirname as dirname14, resolve as resolve8 } from "node:path";
10629
+ import { dirname as dirname14, resolve as resolve9 } from "node:path";
10480
10630
  function getPipelineDbPath() {
10481
10631
  if (process.env.EDS_PIPELINE_DB_PATH) {
10482
- return resolve8(process.env.EDS_PIPELINE_DB_PATH);
10632
+ return resolve9(process.env.EDS_PIPELINE_DB_PATH);
10483
10633
  }
10484
- return resolve8(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10634
+ return resolve9(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10485
10635
  }
10486
10636
  function openPipelineDb(dbPath) {
10487
10637
  const path = dbPath ?? getPipelineDbPath();
@@ -10615,8 +10765,134 @@ function applyDbMigrations(db) {
10615
10765
  if (!rawSlotColsForRationale.some((c2) => c2.name === "rationale")) {
10616
10766
  db.exec("ALTER TABLE raw_slots ADD COLUMN rationale TEXT");
10617
10767
  }
10618
- for (const table of REMOVED_CACHE_TABLES) {
10619
- db.exec(`DROP TABLE IF EXISTS ${table}`);
10768
+ const hasExtractCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='extract_cache'`).all();
10769
+ if (hasExtractCache.length === 0) {
10770
+ db.exec(`
10771
+ CREATE TABLE extract_cache (
10772
+ file_path TEXT NOT NULL,
10773
+ file_hash TEXT NOT NULL,
10774
+ cli_version TEXT NOT NULL,
10775
+ created_at TEXT NOT NULL,
10776
+ updated_at TEXT NOT NULL,
10777
+ components_json TEXT NOT NULL,
10778
+ PRIMARY KEY (file_hash, cli_version)
10779
+ );
10780
+ CREATE INDEX idx_extract_cache_file ON extract_cache(file_path);
10781
+ `);
10782
+ }
10783
+ const hasSelectCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='select_cache'`).all();
10784
+ if (hasSelectCache.length === 0) {
10785
+ db.exec(`
10786
+ CREATE TABLE select_cache (
10787
+ component_hash TEXT NOT NULL,
10788
+ prompt_hash TEXT NOT NULL,
10789
+ cli_version TEXT NOT NULL,
10790
+ decision TEXT NOT NULL CHECK (decision IN ('accepted','rejected')),
10791
+ reason TEXT,
10792
+ created_at TEXT NOT NULL,
10793
+ updated_at TEXT NOT NULL,
10794
+ PRIMARY KEY (component_hash, prompt_hash, cli_version)
10795
+ );
10796
+ `);
10797
+ }
10798
+ const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='generation_cache'`).all();
10799
+ if (tables.length === 0) {
10800
+ db.exec(`
10801
+ CREATE TABLE generation_cache (
10802
+ input_hash TEXT NOT NULL,
10803
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10804
+ entity_id TEXT NOT NULL,
10805
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10806
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10807
+ created_at TEXT NOT NULL,
10808
+ updated_at TEXT NOT NULL,
10809
+ PRIMARY KEY (input_hash, entity_type, entity_id)
10810
+ );
10811
+ CREATE INDEX idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10812
+ CREATE INDEX idx_generation_cache_session ON generation_cache(source_session_id);
10813
+ `);
10814
+ }
10815
+ const genCacheCols = db.prepare(`PRAGMA table_info(generation_cache)`).all();
10816
+ const hasPromptHash = genCacheCols.some((c2) => c2.name === "prompt_hash");
10817
+ const oldPkCols = genCacheCols.filter((c2) => c2.pk > 0).map((c2) => c2.name);
10818
+ const promptHashInPk = oldPkCols.includes("prompt_hash");
10819
+ if (!hasPromptHash) {
10820
+ db.exec(`ALTER TABLE generation_cache ADD COLUMN prompt_hash TEXT NOT NULL DEFAULT ''`);
10821
+ }
10822
+ if (!promptHashInPk) {
10823
+ db.exec("BEGIN");
10824
+ try {
10825
+ db.exec(`
10826
+ CREATE TABLE generation_cache__new (
10827
+ input_hash TEXT NOT NULL,
10828
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10829
+ entity_id TEXT NOT NULL,
10830
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10831
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10832
+ created_at TEXT NOT NULL,
10833
+ updated_at TEXT NOT NULL,
10834
+ prompt_hash TEXT NOT NULL DEFAULT '',
10835
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10836
+ );
10837
+ INSERT INTO generation_cache__new
10838
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10839
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10840
+ FROM generation_cache;
10841
+ DROP TABLE generation_cache;
10842
+ ALTER TABLE generation_cache__new RENAME TO generation_cache;
10843
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10844
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10845
+ `);
10846
+ db.exec("COMMIT");
10847
+ } catch (e) {
10848
+ db.exec("ROLLBACK");
10849
+ throw e;
10850
+ }
10851
+ }
10852
+ const genCacheTableSql = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'generation_cache'`).get();
10853
+ const hasTokenMappingEntityType = genCacheTableSql?.sql.includes("'token_mapping'") ?? true;
10854
+ if (!hasTokenMappingEntityType) {
10855
+ db.exec("BEGIN");
10856
+ try {
10857
+ db.exec(`
10858
+ CREATE TABLE generation_cache__new2 (
10859
+ input_hash TEXT NOT NULL,
10860
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10861
+ entity_id TEXT NOT NULL,
10862
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10863
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10864
+ created_at TEXT NOT NULL,
10865
+ updated_at TEXT NOT NULL,
10866
+ prompt_hash TEXT NOT NULL DEFAULT '',
10867
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10868
+ );
10869
+ INSERT INTO generation_cache__new2
10870
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10871
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10872
+ FROM generation_cache;
10873
+ DROP TABLE generation_cache;
10874
+ ALTER TABLE generation_cache__new2 RENAME TO generation_cache;
10875
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10876
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10877
+ `);
10878
+ db.exec("COMMIT");
10879
+ } catch (e) {
10880
+ db.exec("ROLLBACK");
10881
+ throw e;
10882
+ }
10883
+ }
10884
+ const hasCompositionCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='composition_cache'`).all();
10885
+ if (hasCompositionCache.length === 0) {
10886
+ db.exec(`
10887
+ CREATE TABLE composition_cache (
10888
+ input_hash TEXT NOT NULL,
10889
+ cli_version TEXT NOT NULL,
10890
+ agent_output TEXT NOT NULL,
10891
+ created_at TEXT NOT NULL,
10892
+ updated_at TEXT NOT NULL,
10893
+ PRIMARY KEY (input_hash, cli_version)
10894
+ );
10895
+ `);
10620
10896
  }
10621
10897
  }
10622
10898
  function loadComponentReviewMetadata(db, sessionId2, componentName) {
@@ -11152,16 +11428,16 @@ function extractRelativeImportPaths(sourceText) {
11152
11428
  return [...specifiers];
11153
11429
  }
11154
11430
  async function resolveRelativeImport(specifier, fromDir) {
11155
- const basePath = resolve8(fromDir, specifier);
11431
+ const basePath = resolve9(fromDir, specifier);
11156
11432
  const candidates = [
11157
11433
  basePath,
11158
11434
  ...rewrittenSourcePaths(basePath),
11159
11435
  ...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
11160
- ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve8(basePath, `index${ext}`))
11436
+ ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve9(basePath, `index${ext}`))
11161
11437
  ];
11162
11438
  for (const candidate of candidates) {
11163
11439
  try {
11164
- await readFile7(candidate, "utf8");
11440
+ await readFile8(candidate, "utf8");
11165
11441
  return candidate;
11166
11442
  } catch {
11167
11443
  }
@@ -11177,7 +11453,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
11177
11453
  return resolvedPaths;
11178
11454
  }
11179
11455
  async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateTypeNames = []) {
11180
- const visited = /* @__PURE__ */ new Set([resolve8(sourcePath)]);
11456
+ const visited = /* @__PURE__ */ new Set([resolve9(sourcePath)]);
11181
11457
  const discovered = [];
11182
11458
  let frontier = [{ text: sourceText, dir: dirname14(sourcePath) }];
11183
11459
  for (let hop = 0; hop < MAX_SIBLING_DEPTH && discovered.length < MAX_SIBLING_CANDIDATES_EXPLORED; hop++) {
@@ -11191,7 +11467,7 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
11191
11467
  if (discovered.length >= MAX_SIBLING_CANDIDATES_EXPLORED) break;
11192
11468
  let content;
11193
11469
  try {
11194
- content = await readFile7(resolvedPath, "utf8");
11470
+ content = await readFile8(resolvedPath, "utf8");
11195
11471
  } catch {
11196
11472
  continue;
11197
11473
  }
@@ -11228,7 +11504,7 @@ async function loadComponentSourceRef(name, sourcePath, propNames = [], propType
11228
11504
  let truncatedSiblingCount = 0;
11229
11505
  const usesNotShown = /* @__PURE__ */ new Set();
11230
11506
  try {
11231
- const rawText = await readFile7(sourcePath, "utf8");
11507
+ const rawText = await readFile8(sourcePath, "utf8");
11232
11508
  let siblingUsesNotShown;
11233
11509
  let declaredTypeNames;
11234
11510
  ({
@@ -11664,6 +11940,108 @@ function backfillUnclassifiedProps(db, sessionId2) {
11664
11940
  ).run(sessionId2, sessionId2);
11665
11941
  return Number(withCategory.changes) + Number(withoutCategory.changes);
11666
11942
  }
11943
+ function computeMapTokensInputHash(db, sessionId2, componentSourceRefs = []) {
11944
+ const props = db.prepare(
11945
+ `SELECT rc.name AS component_name, rp.name AS prop_name, rp.cdf_token_kind, rp.default_value
11946
+ FROM raw_props rp
11947
+ JOIN raw_components rc ON rc.session_id = rp.session_id AND rc.component_id = rp.component_id
11948
+ WHERE rp.session_id = ? AND rp.cdf_type = 'token' AND rp.cdf_category = 'design'
11949
+ ORDER BY rc.name, rp.name`
11950
+ ).all(sessionId2);
11951
+ const tokens = db.prepare(`SELECT path, type FROM raw_tokens WHERE session_id = ? ORDER BY path`).all(sessionId2);
11952
+ const defaultMappings = db.prepare("SELECT raw_name, path, source FROM raw_token_name_paths WHERE session_id = ? ORDER BY raw_name").all(sessionId2);
11953
+ const payload = {
11954
+ props: props.map((p) => ({
11955
+ component: p.component_name,
11956
+ prop: p.prop_name,
11957
+ tokenKind: p.cdf_token_kind,
11958
+ rawDefault: p.default_value
11959
+ })),
11960
+ tokens,
11961
+ defaultMappings,
11962
+ // The agent's evidence, not just its inputs: a comment or allowlist edit
11963
+ // in a source file (or a sibling it reads) changes the correct answer
11964
+ // without touching props or tokens, so it must invalidate the cache too.
11965
+ sourceRefs: [...componentSourceRefs].sort((a, b) => a.component.localeCompare(b.component)).map((ref) => ({
11966
+ component: ref.component,
11967
+ content: ref.content,
11968
+ siblingFiles: (ref.siblingFiles ?? []).map((s) => ({ path: s.path, content: s.content })).sort((a, b) => a.path.localeCompare(b.path)),
11969
+ usesNotShown: [...ref.usesNotShown ?? []].sort()
11970
+ }))
11971
+ };
11972
+ return createHash5("sha256").update(JSON.stringify(payload)).digest("hex");
11973
+ }
11974
+ function lookupCache(db, inputHash, entityType, entityId, promptHash = "") {
11975
+ const row = db.prepare(
11976
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11977
+ FROM generation_cache
11978
+ WHERE input_hash = ? AND entity_type = ? AND entity_id = ? AND prompt_hash = ?`
11979
+ ).get(inputHash, entityType, entityId, promptHash);
11980
+ if (!row) return null;
11981
+ return {
11982
+ inputHash: row.input_hash,
11983
+ entityType: row.entity_type,
11984
+ entityId: row.entity_id,
11985
+ sourceSessionId: row.source_session_id,
11986
+ humanEdited: row.human_edited === 1,
11987
+ promptHash: row.prompt_hash,
11988
+ createdAt: row.created_at,
11989
+ updatedAt: row.updated_at
11990
+ };
11991
+ }
11992
+ function lookupCacheByEntity(db, entityType, entityId) {
11993
+ const row = db.prepare(
11994
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11995
+ FROM generation_cache
11996
+ WHERE entity_type = ? AND entity_id = ?
11997
+ ORDER BY updated_at DESC LIMIT 1`
11998
+ ).get(entityType, entityId);
11999
+ if (!row) return null;
12000
+ return {
12001
+ inputHash: row.input_hash,
12002
+ entityType: row.entity_type,
12003
+ entityId: row.entity_id,
12004
+ sourceSessionId: row.source_session_id,
12005
+ humanEdited: row.human_edited === 1,
12006
+ promptHash: row.prompt_hash,
12007
+ createdAt: row.created_at,
12008
+ updatedAt: row.updated_at
12009
+ };
12010
+ }
12011
+ function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash = "") {
12012
+ storeCaches(db, [{ inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash }]);
12013
+ }
12014
+ function storeCaches(db, entries) {
12015
+ if (entries.length === 0) return;
12016
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12017
+ const insert = db.prepare(
12018
+ `INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
12019
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
12020
+ ON CONFLICT(input_hash, prompt_hash, entity_type, entity_id) DO UPDATE SET
12021
+ source_session_id = excluded.source_session_id,
12022
+ human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
12023
+ updated_at = excluded.updated_at`
12024
+ );
12025
+ db.exec("BEGIN");
12026
+ try {
12027
+ for (const entry of entries) {
12028
+ insert.run(
12029
+ entry.inputHash,
12030
+ entry.entityType,
12031
+ entry.entityId,
12032
+ entry.sourceSessionId,
12033
+ entry.humanEdited ? 1 : 0,
12034
+ now,
12035
+ now,
12036
+ entry.promptHash ?? ""
12037
+ );
12038
+ }
12039
+ db.exec("COMMIT");
12040
+ } catch (e) {
12041
+ db.exec("ROLLBACK");
12042
+ throw e;
12043
+ }
12044
+ }
11667
12045
  function storeScannedFiles(db, sessionId2, filePaths) {
11668
12046
  db.exec("BEGIN");
11669
12047
  try {
@@ -11746,7 +12124,105 @@ function filterUnknownSlotAllowedComponents(db, sessionId2, allowedComponentName
11746
12124
  }
11747
12125
  return unknown;
11748
12126
  }
11749
- function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
12127
+ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId, manageTransaction = true, options = {}) {
12128
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12129
+ if (manageTransaction) db.exec("BEGIN");
12130
+ try {
12131
+ const srcComp = db.prepare(
12132
+ `SELECT description, status, component_description_rationale, props_rationale, slots_rationale
12133
+ FROM raw_components WHERE session_id = ? AND component_id = ?`
12134
+ ).get(sourceSessionId, componentId);
12135
+ if (srcComp) {
12136
+ db.prepare(
12137
+ `UPDATE raw_components
12138
+ SET description = ?, status = ?, component_description_rationale = ?, props_rationale = ?, slots_rationale = ?, extracted_at = ?
12139
+ WHERE session_id = ? AND component_id = ?`
12140
+ ).run(
12141
+ srcComp.description,
12142
+ srcComp.status,
12143
+ srcComp.component_description_rationale,
12144
+ srcComp.props_rationale,
12145
+ srcComp.slots_rationale,
12146
+ now,
12147
+ targetSessionId,
12148
+ componentId
12149
+ );
12150
+ }
12151
+ const srcProps = db.prepare(
12152
+ `SELECT name, cdf_type, cdf_category, cdf_token_kind, required, description, default_value, rationale
12153
+ FROM raw_props WHERE session_id = ? AND component_id = ?`
12154
+ ).all(sourceSessionId, componentId);
12155
+ for (const p of srcProps) {
12156
+ db.prepare(
12157
+ `UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, default_value = ?, rationale = ?
12158
+ WHERE session_id = ? AND component_id = ? AND name = ?`
12159
+ ).run(
12160
+ p.cdf_type,
12161
+ p.cdf_category,
12162
+ p.cdf_token_kind,
12163
+ p.required,
12164
+ p.description,
12165
+ p.default_value,
12166
+ p.rationale,
12167
+ targetSessionId,
12168
+ componentId,
12169
+ p.name
12170
+ );
12171
+ }
12172
+ db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`).run(
12173
+ targetSessionId,
12174
+ componentId
12175
+ );
12176
+ const srcAV = db.prepare(
12177
+ `SELECT prop_name, position, value FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`
12178
+ ).all(sourceSessionId, componentId);
12179
+ const insertAV = db.prepare(
12180
+ `INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value) VALUES (?, ?, ?, ?, ?)`
12181
+ );
12182
+ for (const av of srcAV) {
12183
+ insertAV.run(targetSessionId, componentId, av.prop_name, av.position, av.value);
12184
+ }
12185
+ const srcSlots = db.prepare(`SELECT name, required, description, rationale FROM raw_slots WHERE session_id = ? AND component_id = ?`).all(sourceSessionId, componentId);
12186
+ for (const s of srcSlots) {
12187
+ db.prepare(
12188
+ `UPDATE raw_slots SET required = ?, description = ?, rationale = ? WHERE session_id = ? AND component_id = ? AND name = ?`
12189
+ ).run(s.required, s.description, s.rationale, targetSessionId, componentId, s.name);
12190
+ }
12191
+ db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`).run(
12192
+ targetSessionId,
12193
+ componentId
12194
+ );
12195
+ const srcSAC = db.prepare(
12196
+ `SELECT slot_name, position, allowed_component FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`
12197
+ ).all(sourceSessionId, componentId);
12198
+ const insertSAC = db.prepare(
12199
+ `INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`
12200
+ );
12201
+ const allowedSAC = options.allowedComponentNames ? srcSAC.filter((sac) => options.allowedComponentNames.has(sac.allowed_component)) : srcSAC;
12202
+ for (const sac of allowedSAC) {
12203
+ insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
12204
+ }
12205
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12206
+ if (manageTransaction) db.exec("COMMIT");
12207
+ } catch (e) {
12208
+ if (manageTransaction) db.exec("ROLLBACK");
12209
+ throw e;
12210
+ }
12211
+ }
12212
+ function copyComponentsFromCache(db, entries, options = {}) {
12213
+ if (entries.length === 0) return;
12214
+ db.exec("BEGIN");
12215
+ try {
12216
+ for (const entry of entries) {
12217
+ copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false, options);
12218
+ }
12219
+ db.exec("COMMIT");
12220
+ } catch (e) {
12221
+ db.exec("ROLLBACK");
12222
+ throw e;
12223
+ }
12224
+ }
12225
+ function copyTokensFromCache(db, sourceSessionId, targetSessionId) {
11750
12226
  const now = (/* @__PURE__ */ new Date()).toISOString();
11751
12227
  db.exec("BEGIN");
11752
12228
  try {
@@ -11767,7 +12243,139 @@ function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
11767
12243
  throw e;
11768
12244
  }
11769
12245
  }
11770
- 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;
12246
+ function copyMapTokensFromCache(db, sourceSessionId, targetSessionId) {
12247
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12248
+ let copiedCount = 0;
12249
+ db.exec("BEGIN");
12250
+ try {
12251
+ const sourceRows = db.prepare(
12252
+ `SELECT rc.name AS component_name, rptp.prop_name, rptp.source, rptp.position, rptp.path
12253
+ FROM raw_prop_token_paths rptp
12254
+ JOIN raw_components rc ON rc.session_id = rptp.session_id AND rc.component_id = rptp.component_id
12255
+ WHERE rptp.session_id = ?
12256
+ ORDER BY rc.name, rptp.prop_name, rptp.position`
12257
+ ).all(sourceSessionId);
12258
+ const targetComponents = db.prepare(`SELECT component_id, name FROM raw_components WHERE session_id = ?`).all(targetSessionId);
12259
+ const targetIdByName = new Map(targetComponents.map((c2) => [c2.name, c2.component_id]));
12260
+ const targetProps = new Set(
12261
+ db.prepare(`SELECT component_id, name FROM raw_props WHERE session_id = ?`).all(targetSessionId).map((p) => `${p.component_id}::${p.name}`)
12262
+ );
12263
+ const insertPath = db.prepare(
12264
+ `INSERT INTO raw_prop_token_paths (session_id, component_id, prop_name, source, position, path)
12265
+ VALUES (?, ?, ?, ?, ?, ?)`
12266
+ );
12267
+ const clearedProps = /* @__PURE__ */ new Set();
12268
+ const copiedProps = /* @__PURE__ */ new Set();
12269
+ for (const row of sourceRows) {
12270
+ const targetComponentId = targetIdByName.get(row.component_name);
12271
+ if (!targetComponentId) continue;
12272
+ if (!targetProps.has(`${targetComponentId}::${row.prop_name}`)) continue;
12273
+ const propKey = `${targetComponentId}::${row.prop_name}`;
12274
+ if (!clearedProps.has(propKey)) {
12275
+ clearedProps.add(propKey);
12276
+ db.prepare(`DELETE FROM raw_prop_token_paths WHERE session_id = ? AND component_id = ? AND prop_name = ?`).run(
12277
+ targetSessionId,
12278
+ targetComponentId,
12279
+ row.prop_name
12280
+ );
12281
+ }
12282
+ insertPath.run(targetSessionId, targetComponentId, row.prop_name, row.source, row.position, row.path);
12283
+ copiedProps.add(propKey);
12284
+ }
12285
+ copiedCount = copiedProps.size;
12286
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12287
+ db.exec("COMMIT");
12288
+ } catch (e) {
12289
+ db.exec("ROLLBACK");
12290
+ throw e;
12291
+ }
12292
+ return copiedCount;
12293
+ }
12294
+ async function getCliCacheVersion() {
12295
+ if (_cliCacheVersionCache) return _cliCacheVersionCache;
12296
+ try {
12297
+ const { hashContent: hashContent2 } = await Promise.resolve().then(() => (init_cache_keys(), cache_keys_exports));
12298
+ const { resolveSkillPath: resolveSkillPath2 } = await Promise.resolve().then(() => (init_src(), src_exports));
12299
+ const skills = ["components", "tokens", "select"];
12300
+ const parts = [];
12301
+ for (const s of skills) {
12302
+ try {
12303
+ const p = resolveSkillPath2(s);
12304
+ parts.push(readFileSync5(p, "utf8"));
12305
+ } catch {
12306
+ parts.push(`<missing:${s}>`);
12307
+ }
12308
+ }
12309
+ _cliCacheVersionCache = hashContent2(parts.join("\n---\n"));
12310
+ return _cliCacheVersionCache;
12311
+ } catch {
12312
+ _cliCacheVersionCache = "fallback";
12313
+ return _cliCacheVersionCache;
12314
+ }
12315
+ }
12316
+ function storeExtractCache(db, filePath, fileHash, cliVersion2, components) {
12317
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12318
+ const componentsJson = JSON.stringify(components);
12319
+ db.prepare(
12320
+ `INSERT INTO extract_cache (file_path, file_hash, cli_version, created_at, updated_at, components_json)
12321
+ VALUES (?, ?, ?, ?, ?, ?)
12322
+ ON CONFLICT(file_hash, cli_version) DO UPDATE SET
12323
+ file_path = excluded.file_path,
12324
+ updated_at = excluded.updated_at,
12325
+ components_json = excluded.components_json`
12326
+ ).run(filePath, fileHash, cliVersion2, now, now, componentsJson);
12327
+ }
12328
+ function lookupExtractCache(db, fileHash, cliVersion2) {
12329
+ const row = db.prepare(
12330
+ `SELECT file_path, file_hash, cli_version, created_at, updated_at, components_json
12331
+ FROM extract_cache
12332
+ WHERE file_hash = ? AND cli_version = ?`
12333
+ ).get(fileHash, cliVersion2);
12334
+ if (!row) return null;
12335
+ let components = [];
12336
+ try {
12337
+ components = JSON.parse(row.components_json);
12338
+ } catch {
12339
+ return null;
12340
+ }
12341
+ return {
12342
+ filePath: row.file_path,
12343
+ fileHash: row.file_hash,
12344
+ cliVersion: row.cli_version,
12345
+ createdAt: row.created_at,
12346
+ updatedAt: row.updated_at,
12347
+ components
12348
+ };
12349
+ }
12350
+ function storeSelectCache(db, componentHash, promptHash, cliVersion2, decision, reason) {
12351
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12352
+ db.prepare(
12353
+ `INSERT INTO select_cache (component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at)
12354
+ VALUES (?, ?, ?, ?, ?, ?, ?)
12355
+ ON CONFLICT(component_hash, prompt_hash, cli_version) DO UPDATE SET
12356
+ decision = excluded.decision,
12357
+ reason = excluded.reason,
12358
+ updated_at = excluded.updated_at`
12359
+ ).run(componentHash, promptHash, cliVersion2, decision, reason, now, now);
12360
+ }
12361
+ function lookupSelectCache(db, componentHash, promptHash, cliVersion2) {
12362
+ const row = db.prepare(
12363
+ `SELECT component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at
12364
+ FROM select_cache
12365
+ WHERE component_hash = ? AND prompt_hash = ? AND cli_version = ?`
12366
+ ).get(componentHash, promptHash, cliVersion2);
12367
+ if (!row) return null;
12368
+ return {
12369
+ componentHash: row.component_hash,
12370
+ promptHash: row.prompt_hash,
12371
+ cliVersion: row.cli_version,
12372
+ decision: row.decision,
12373
+ reason: row.reason,
12374
+ createdAt: row.created_at,
12375
+ updatedAt: row.updated_at
12376
+ };
12377
+ }
12378
+ 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;
11771
12379
  var init_db = __esm({
11772
12380
  "packages/experience-design-system-cli-legacy/src/session/db.ts"() {
11773
12381
  "use strict";
@@ -11781,6 +12389,8 @@ var init_db = __esm({
11781
12389
  init_write2();
11782
12390
  init_session_resolver();
11783
12391
  init_read3();
12392
+ init_hash_component_shape();
12393
+ init_hash_token_content();
11784
12394
  init_read3();
11785
12395
  init_store_dtcg_tokens();
11786
12396
  init_apply_tool_calls();
@@ -11904,6 +12514,17 @@ CREATE TABLE IF NOT EXISTS raw_tokens (
11904
12514
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
11905
12515
  );
11906
12516
 
12517
+ CREATE TABLE IF NOT EXISTS generation_cache (
12518
+ input_hash TEXT NOT NULL,
12519
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
12520
+ entity_id TEXT NOT NULL,
12521
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
12522
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
12523
+ created_at TEXT NOT NULL,
12524
+ updated_at TEXT NOT NULL,
12525
+ PRIMARY KEY (input_hash, entity_type, entity_id)
12526
+ );
12527
+
11907
12528
  CREATE TABLE IF NOT EXISTS scanned_files (
11908
12529
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
11909
12530
  path TEXT NOT NULL,
@@ -11949,9 +12570,10 @@ CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id,
11949
12570
  CREATE INDEX IF NOT EXISTS idx_raw_slots_session ON raw_slots(session_id, component_id);
11950
12571
  CREATE INDEX IF NOT EXISTS idx_raw_tokens_session ON raw_tokens(session_id);
11951
12572
  CREATE INDEX IF NOT EXISTS idx_raw_token_groups_session ON raw_token_groups(session_id);
12573
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
12574
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
11952
12575
  CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session_id);
11953
12576
  `;
11954
- REMOVED_CACHE_TABLES = ["generation_cache", "extract_cache", "composition_cache", "select_cache"];
11955
12577
  MAX_COMPONENT_SOURCE_CHARS = 8e3;
11956
12578
  MAX_SIBLING_FILES = 5;
11957
12579
  MAX_SIBLING_SNIPPET_CHARS = 1200;
@@ -11968,15 +12590,16 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
11968
12590
  [".cjs", [".cts", ".cjs"]]
11969
12591
  ];
11970
12592
  IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
12593
+ _cliCacheVersionCache = null;
11971
12594
  }
11972
12595
  });
11973
12596
 
11974
12597
  // packages/experience-design-system-cli-legacy/src/helpers/read-existing-contentful-entities-from-session.ts
11975
- import { readFile as readFile8 } from "node:fs/promises";
12598
+ import { readFile as readFile9 } from "node:fs/promises";
11976
12599
  async function readExistingContentfulEntitiesFromSession(path) {
11977
12600
  if (!path) return void 0;
11978
12601
  try {
11979
- const raw = await readFile8(path, "utf8");
12602
+ const raw = await readFile9(path, "utf8");
11980
12603
  const parsed = JSON.parse(raw);
11981
12604
  if (!Array.isArray(parsed.components) || !Array.isArray(parsed.tokens)) return void 0;
11982
12605
  return parsed;
@@ -12100,21 +12723,21 @@ var init_resolve_session_id = __esm({
12100
12723
  });
12101
12724
 
12102
12725
  // packages/experience-design-system-cli-legacy/src/analyze/select/persistence.ts
12103
- import { access, appendFile, mkdir as mkdir2, readFile as readFile9, writeFile as writeFile2 } from "node:fs/promises";
12726
+ import { access, appendFile, mkdir as mkdir2, readFile as readFile10, writeFile as writeFile2 } from "node:fs/promises";
12104
12727
  import { homedir as homedir4 } from "node:os";
12105
- import { resolve as resolve9 } from "node:path";
12728
+ import { resolve as resolve10 } from "node:path";
12106
12729
  function getRefineArtifactsRoot() {
12107
12730
  if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
12108
- return resolve9(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12731
+ return resolve10(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12109
12732
  }
12110
- return resolve9(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12733
+ return resolve10(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12111
12734
  }
12112
12735
  async function getRefineSessionPaths(sessionId2, artifactsRoot) {
12113
- const sessionDir = resolve9(artifactsRoot, sessionId2);
12736
+ const sessionDir = resolve10(artifactsRoot, sessionId2);
12114
12737
  return {
12115
12738
  sessionDir,
12116
- eventsPath: resolve9(sessionDir, "events.jsonl"),
12117
- statePath: resolve9(sessionDir, "current-review-state.json")
12739
+ eventsPath: resolve10(sessionDir, "events.jsonl"),
12740
+ statePath: resolve10(sessionDir, "current-review-state.json")
12118
12741
  };
12119
12742
  }
12120
12743
  async function saveReviewState(statePath, session) {
@@ -12125,7 +12748,7 @@ async function ensureRefineSession(sessionId2, artifactsRoot, initialSnapshot) {
12125
12748
  await mkdir2(paths.sessionDir, { recursive: true });
12126
12749
  try {
12127
12750
  await access(paths.statePath);
12128
- const savedState = await readFile9(paths.statePath, "utf8");
12751
+ const savedState = await readFile10(paths.statePath, "utf8");
12129
12752
  return JSON.parse(savedState);
12130
12753
  } catch {
12131
12754
  await writeFile2(paths.statePath, JSON.stringify(initialSnapshot, null, 2), "utf8");
@@ -12610,8 +13233,8 @@ var init_path_exists = __esm({
12610
13233
  });
12611
13234
 
12612
13235
  // packages/experience-design-system-cli-legacy/src/lib/prompt-overrides.ts
12613
- import { readFile as readFile10 } from "node:fs/promises";
12614
- import { resolve as resolve10 } from "node:path";
13236
+ import { readFile as readFile11 } from "node:fs/promises";
13237
+ import { resolve as resolve11 } from "node:path";
12615
13238
  function looksLikePath(value) {
12616
13239
  if (value.includes("/") || value.includes("\\")) return true;
12617
13240
  if (value.startsWith("~")) return true;
@@ -12643,9 +13266,9 @@ function parsePromptOverrides(inputs) {
12643
13266
  }
12644
13267
  async function resolvePromptOverride(override) {
12645
13268
  if (override.kind === "text") return override.value;
12646
- const abs = resolve10(override.value);
13269
+ const abs = resolve11(override.value);
12647
13270
  try {
12648
- return await readFile10(abs, "utf8");
13271
+ return await readFile11(abs, "utf8");
12649
13272
  } catch {
12650
13273
  throw new Error(`--prompt: could not read prompt file: ${abs}`);
12651
13274
  }
@@ -12660,21 +13283,21 @@ var init_prompt_overrides = __esm({
12660
13283
 
12661
13284
  // packages/experience-design-system-cli-legacy/src/generate/command.ts
12662
13285
  import { createElement } from "react";
12663
- import { readFile as readFile11, readdir, stat } from "node:fs/promises";
12664
- import { join as join14, resolve as resolve11 } from "node:path";
13286
+ import { readFile as readFile12, readdir, stat } from "node:fs/promises";
13287
+ import { join as join14, resolve as resolve12 } from "node:path";
12665
13288
  async function assertFileExists(flag, p) {
12666
13289
  if (!await pathExists(p)) die(`Error: file not found: ${p} (from ${flag})`);
12667
13290
  }
12668
13291
  async function readFileInline(path) {
12669
13292
  if (!path) return void 0;
12670
- const resolved = resolve11(path);
13293
+ const resolved = resolve12(path);
12671
13294
  let s;
12672
13295
  try {
12673
13296
  s = await stat(resolved);
12674
13297
  } catch {
12675
13298
  return void 0;
12676
13299
  }
12677
- if (!s.isDirectory()) return readFile11(resolved, "utf8");
13300
+ if (!s.isDirectory()) return readFile12(resolved, "utf8");
12678
13301
  const files = [];
12679
13302
  async function walk(dir) {
12680
13303
  let entries;
@@ -12700,7 +13323,7 @@ async function readFileInline(path) {
12700
13323
  }
12701
13324
  await walk(resolved);
12702
13325
  if (files.length === 0) return void 0;
12703
- const parts = await Promise.all(files.map((f) => readFile11(f, "utf8").catch(() => "")));
13326
+ const parts = await Promise.all(files.map((f) => readFile12(f, "utf8").catch(() => "")));
12704
13327
  return parts.filter(Boolean).join("\n\n");
12705
13328
  }
12706
13329
  function printFallbackInstructions(options) {
@@ -12725,6 +13348,23 @@ function printFallbackInstructions(options) {
12725
13348
  function generatePropBuckets(component) {
12726
13349
  return logPropBuckets("generate", [bucketComponentProps(component)]);
12727
13350
  }
13351
+ function createCachedComponentResult(componentName, warnings = []) {
13352
+ return {
13353
+ componentName,
13354
+ classified: 0,
13355
+ excluded: 0,
13356
+ slots: 0,
13357
+ warnings,
13358
+ failed: false,
13359
+ cached: true,
13360
+ renamedSlotsCount: 0
13361
+ };
13362
+ }
13363
+ function writeCachedComponentStatus(position, componentName, pinned) {
13364
+ const status = pinned ? c.cyan("pinned (human-edited)") : c.green("cached");
13365
+ process.stderr.write(` ${position} ${c.bold(componentName)} ${status}
13366
+ `);
13367
+ }
12728
13368
  async function showGenerateView(result) {
12729
13369
  if (process.stdout.isTTY) {
12730
13370
  const { waitUntilExit } = renderWithGoodbye(
@@ -12745,6 +13385,29 @@ session=${result.sessionId}
12745
13385
  );
12746
13386
  await exitWithAnalytics(0);
12747
13387
  }
13388
+ function normalizeComponentForCache(component) {
13389
+ const slots = component.slots.map((slot, index, allSlots) => ({
13390
+ ...slot,
13391
+ name: slot.name.trim() || (allSlots.length === 1 ? "children" : `slot_${index}`)
13392
+ }));
13393
+ return { ...component, slots };
13394
+ }
13395
+ function lookupComponentCache(db, component, promptHash) {
13396
+ const normalized = normalizeComponentForCache(component);
13397
+ const inputHash = hashComponentShape(normalized);
13398
+ const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
13399
+ if (cached) return cached;
13400
+ const legacyInputHash = hashComponentShape(component);
13401
+ return legacyInputHash === inputHash ? null : lookupCache(db, legacyInputHash, "component", component.component_id, promptHash);
13402
+ }
13403
+ function resolveComponentCache(db, component, promptHash, allowedComponentNames2) {
13404
+ const cached = lookupComponentCache(db, component, promptHash);
13405
+ if (cached && findUnknownSlotAllowedComponents(db, cached.sourceSessionId, allowedComponentNames2, component.component_id).length === 0) {
13406
+ return { entry: cached, humanEdited: cached.humanEdited };
13407
+ }
13408
+ const pinned = lookupCacheByEntity(db, "component", component.component_id);
13409
+ return pinned?.humanEdited && findUnknownSlotAllowedComponents(db, pinned.sourceSessionId, allowedComponentNames2, component.component_id).length === 0 ? { entry: pinned, humanEdited: true } : null;
13410
+ }
12748
13411
  async function runOneComponent(options, component, index, total) {
12749
13412
  const {
12750
13413
  agent,
@@ -12754,10 +13417,13 @@ async function runOneComponent(options, component, index, total) {
12754
13417
  tokensInline,
12755
13418
  tokenMapInline,
12756
13419
  verbose,
13420
+ noCache,
12757
13421
  skillPathOverride,
12758
13422
  skillContentOverride,
13423
+ promptHash,
12759
13424
  existingContentfulEntities,
12760
13425
  existingTokensInline,
13426
+ precomputedCachedNames,
12761
13427
  allowedComponentNames: allowedComponentNames2
12762
13428
  } = options;
12763
13429
  const pos = c.dim(`[${index + 1}/${total}]`);
@@ -12782,6 +13448,40 @@ async function runOneComponent(options, component, index, total) {
12782
13448
  allowedComponents: slot.allowedComponents.filter((name) => allowedComponentNames2.has(name))
12783
13449
  };
12784
13450
  });
13451
+ const cacheComponent = normalizeComponentForCache(component);
13452
+ if (!noCache && precomputedCachedNames.has(component.name)) {
13453
+ writeCachedComponentStatus(pos, component.name, false);
13454
+ return createCachedComponentResult(component.name);
13455
+ }
13456
+ if (!noCache) {
13457
+ const inputHash = hashComponentShape(cacheComponent);
13458
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13459
+ if (resolution && !resolution.humanEdited) {
13460
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13461
+ allowedComponentNames: allowedComponentNames2
13462
+ });
13463
+ storeCache(
13464
+ db,
13465
+ inputHash,
13466
+ "component",
13467
+ component.component_id,
13468
+ resolution.entry.sourceSessionId,
13469
+ resolution.entry.humanEdited,
13470
+ promptHash
13471
+ );
13472
+ writeCachedComponentStatus(pos, component.name, false);
13473
+ return createCachedComponentResult(component.name);
13474
+ }
13475
+ if (resolution?.humanEdited) {
13476
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13477
+ allowedComponentNames: allowedComponentNames2
13478
+ });
13479
+ writeCachedComponentStatus(pos, component.name, true);
13480
+ return createCachedComponentResult(`${component.name}`, [
13481
+ `${component.name}: source changed but human edits preserved`
13482
+ ]);
13483
+ }
13484
+ }
12785
13485
  const rawComponentsInline = JSON.stringify(
12786
13486
  [
12787
13487
  {
@@ -12854,6 +13554,10 @@ ${outputBuf}`);
12854
13554
  const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings, {
12855
13555
  allowedComponentNames: allowedComponentNames2
12856
13556
  });
13557
+ if (!noCache) {
13558
+ const inputHash = hashComponentShape(cacheComponent);
13559
+ storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
13560
+ }
12857
13561
  return {
12858
13562
  componentName: component.name,
12859
13563
  classified: applied.classified,
@@ -12905,7 +13609,7 @@ async function loadAcceptedNames(sessionId2) {
12905
13609
  try {
12906
13610
  const artifactsRoot = getRefineArtifactsRoot();
12907
13611
  const paths = await getRefineSessionPaths(sessionId2, artifactsRoot);
12908
- const raw = await readFile11(paths.statePath, "utf8");
13612
+ const raw = await readFile12(paths.statePath, "utf8");
12909
13613
  const snapshot = JSON.parse(raw);
12910
13614
  const accepted = snapshot.components.filter((c2) => c2.status === "accepted").map((c2) => c2.name);
12911
13615
  if (accepted.length === 0) return null;
@@ -12914,6 +13618,15 @@ async function loadAcceptedNames(sessionId2) {
12914
13618
  return null;
12915
13619
  }
12916
13620
  }
13621
+ function parsePrecomputedCachedNames(value) {
13622
+ if (!value) return /* @__PURE__ */ new Set();
13623
+ try {
13624
+ const parsed = JSON.parse(value);
13625
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : []);
13626
+ } catch {
13627
+ return /* @__PURE__ */ new Set();
13628
+ }
13629
+ }
12917
13630
  async function runGenerateSkill(skill, opts, verbose = false) {
12918
13631
  const savedCreds = await readExperiencesCredentials();
12919
13632
  const agentName = opts.agent ?? savedCreds.agent;
@@ -12942,8 +13655,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12942
13655
  }
12943
13656
  const generatePromptPath = generatePrompt === void 0 ? configuredGeneratePromptPath : void 0;
12944
13657
  if (generatePromptPath) {
12945
- if (!await pathExists(resolve11(generatePromptPath))) {
12946
- die(`Error: custom prompt path not found: ${resolve11(generatePromptPath)}`);
13658
+ if (!await pathExists(resolve12(generatePromptPath))) {
13659
+ die(`Error: custom prompt path not found: ${resolve12(generatePromptPath)}`);
12947
13660
  }
12948
13661
  if (!generatePromptPath.toLowerCase().endsWith(".md")) {
12949
13662
  process.stderr.write(
@@ -12951,7 +13664,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12951
13664
  `
12952
13665
  );
12953
13666
  }
12954
- process.stderr.write(formatCustomPromptBanner("components", resolve11(generatePromptPath)));
13667
+ process.stderr.write(formatCustomPromptBanner("components", resolve12(generatePromptPath)));
12955
13668
  }
12956
13669
  if (skill === "tokens" && !opts.rawTokens) {
12957
13670
  die("Error: --raw-tokens is required when using generate tokens");
@@ -12967,7 +13680,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12967
13680
  let existingContentfulEntities;
12968
13681
  let existingTokensInline;
12969
13682
  if (opts.existingEntitiesPath) {
12970
- existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve11(opts.existingEntitiesPath));
13683
+ existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve12(opts.existingEntitiesPath));
12971
13684
  if (existingContentfulEntities) {
12972
13685
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
12973
13686
  } else {
@@ -13066,7 +13779,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13066
13779
  rawComponentsInline: sampleInline ?? rawTokensInline,
13067
13780
  propBucketsInline: sampleComponent ? generatePropBuckets(sampleComponent) : void 0,
13068
13781
  rawTokensInline: skill === "tokens" ? rawTokensInline : void 0,
13069
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13782
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13070
13783
  tokensInline,
13071
13784
  tokenMapInline,
13072
13785
  outDir: process.cwd(),
@@ -13092,7 +13805,64 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13092
13805
  if (skill === "components" && allComponents && sessionId2) {
13093
13806
  const db = openPipelineDb();
13094
13807
  let componentResults;
13808
+ const precomputedCachedNames = parsePrecomputedCachedNames(opts.cachedComponents);
13095
13809
  try {
13810
+ const existingContentfulEntitiesHashInputs = [];
13811
+ if (existingTokensInline) existingContentfulEntitiesHashInputs.push(hashContent(existingTokensInline));
13812
+ const promptHash = await hashPromptForSkill(
13813
+ "components",
13814
+ agent,
13815
+ model,
13816
+ generatePromptPath,
13817
+ existingContentfulEntitiesHashInputs,
13818
+ generatePrompt
13819
+ );
13820
+ if (opts.cacheStatus) {
13821
+ const cacheEnabled = opts.cache !== false && process.env.EDS_NO_CACHE !== "1";
13822
+ const cachedComponents = cacheEnabled ? allComponents.flatMap((component) => {
13823
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13824
+ return resolution ? [{ component, resolution }] : [];
13825
+ }) : [];
13826
+ if (cacheEnabled && opts.restoreCache) {
13827
+ const restorations = [];
13828
+ const rekeyed = [];
13829
+ const componentStatus = db.prepare(
13830
+ "SELECT status FROM raw_components WHERE session_id = ? AND component_id = ?"
13831
+ );
13832
+ for (const { component, resolution } of cachedComponents) {
13833
+ const current = componentStatus.get(sessionId2, component.component_id);
13834
+ if (current?.status === "generated") continue;
13835
+ renameEmptySlots(db, sessionId2, component.component_id, component.name, component.slots.length);
13836
+ restorations.push({
13837
+ sourceSessionId: resolution.entry.sourceSessionId,
13838
+ targetSessionId: sessionId2,
13839
+ componentId: component.component_id
13840
+ });
13841
+ rekeyed.push({ component, cached: resolution.entry });
13842
+ }
13843
+ copyComponentsFromCache(db, restorations, { allowedComponentNames: allowedComponentNames2 });
13844
+ storeCaches(
13845
+ db,
13846
+ rekeyed.map(({ component, cached }) => ({
13847
+ inputHash: hashComponentShape(normalizeComponentForCache(component)),
13848
+ entityType: "component",
13849
+ entityId: component.component_id,
13850
+ sourceSessionId: cached.sourceSessionId,
13851
+ humanEdited: cached.humanEdited,
13852
+ promptHash
13853
+ }))
13854
+ );
13855
+ }
13856
+ const fullyCached = cacheEnabled && cachedComponents.length === allComponents.length;
13857
+ process.stdout.write(`cache-status=${fullyCached ? "hit" : "miss"}
13858
+ `);
13859
+ process.stdout.write(
13860
+ `cache-components=${JSON.stringify(cachedComponents.map(({ component }) => component.name))}
13861
+ `
13862
+ );
13863
+ await exitWithAnalytics(0);
13864
+ return;
13865
+ }
13096
13866
  componentResults = await runAllComponents(
13097
13867
  {
13098
13868
  agent,
@@ -13102,10 +13872,13 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13102
13872
  tokensInline,
13103
13873
  tokenMapInline,
13104
13874
  verbose,
13875
+ noCache: opts.cache === false || process.env.EDS_NO_CACHE === "1",
13105
13876
  skillPathOverride: generatePromptPath,
13106
13877
  skillContentOverride: generatePrompt,
13878
+ promptHash,
13107
13879
  existingContentfulEntities,
13108
13880
  existingTokensInline,
13881
+ precomputedCachedNames,
13109
13882
  allowedComponentNames: allowedComponentNames2
13110
13883
  },
13111
13884
  allComponents
@@ -13114,7 +13887,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13114
13887
  db.close();
13115
13888
  }
13116
13889
  const failed = componentResults.filter((r) => r.failed);
13117
- const generated = componentResults.filter((r) => !r.failed);
13890
+ const cachedResults = componentResults.filter((r) => r.cached);
13891
+ const generated = componentResults.filter((r) => !r.failed && !r.cached);
13118
13892
  const allWarnings = componentResults.flatMap((r) => r.warnings.map((w) => ` ${r.componentName}: ${w}`));
13119
13893
  if (allWarnings.length > 0) {
13120
13894
  process.stderr.write(c.yellow("Warnings:") + "\n" + allWarnings.join("\n") + "\n");
@@ -13130,17 +13904,21 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13130
13904
  const totalExcluded = generated.reduce((s, r) => s + r.excluded, 0);
13131
13905
  const totalRenamedSlots = componentResults.reduce((s, r) => s + r.renamedSlotsCount, 0);
13132
13906
  const allOk = failed.length === 0;
13907
+ const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : "";
13133
13908
  process.stderr.write(
13134
- (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length}/${componentResults.length} components` + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13909
+ (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13135
13910
  );
13136
13911
  process.stdout.write(`renamed-slots: ${totalRenamedSlots}
13137
13912
  `);
13138
- if (generated.length === 0) {
13913
+ if (generated.length === 0 && cachedResults.length === 0) {
13139
13914
  die(
13140
13915
  `Error: all ${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
13141
13916
  );
13142
13917
  }
13143
13918
  } else if (skill === "tokens") {
13919
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
13920
+ const tokenInputContent = rawTokensInline ?? "";
13921
+ const tokenInputHash = hashTokenContent(tokenInputContent);
13144
13922
  const db = openPipelineDb();
13145
13923
  try {
13146
13924
  let resolvedSessionId = opts.session;
@@ -13167,11 +13945,26 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13167
13945
  }
13168
13946
  sessionId2 = resolvedSessionId;
13169
13947
  await bindAnalyticsSessionId(resolvedSessionId);
13948
+ const tokenPromptHash = await hashPromptForSkill("tokens", agent, model);
13949
+ if (!noCache) {
13950
+ const tokenCached = lookupCache(db, tokenInputHash, "token_set", "__tokens__", tokenPromptHash);
13951
+ if (tokenCached) {
13952
+ copyTokensFromCache(db, tokenCached.sourceSessionId, resolvedSessionId);
13953
+ sessionId2 = resolvedSessionId;
13954
+ process.stderr.write(
13955
+ `Done: tokens reused from cache ${c.dim(`(source: ${tokenCached.sourceSessionId.slice(0, 12)})`)}
13956
+ `
13957
+ );
13958
+ db.close();
13959
+ await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13960
+ return;
13961
+ }
13962
+ }
13170
13963
  const prompt = await buildPrompt({
13171
13964
  skill,
13172
13965
  mode: "autonomous",
13173
13966
  rawTokensInline,
13174
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13967
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13175
13968
  tokensInline,
13176
13969
  tokenMapInline,
13177
13970
  outDir: process.cwd()
@@ -13208,6 +14001,9 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13208
14001
  `);
13209
14002
  }
13210
14003
  applyTokenToolCalls(db, resolvedSessionId, tokenCalls, []);
14004
+ if (!noCache) {
14005
+ storeCache(db, tokenInputHash, "token_set", "__tokens__", resolvedSessionId, false, tokenPromptHash);
14006
+ }
13211
14007
  sessionId2 = resolvedSessionId;
13212
14008
  const groupCount = tokenCalls.filter((tc) => tc.tool === "set_group").length;
13213
14009
  process.stderr.write(`Done: ${tokenCount} tokens, ${groupCount} groups stored
@@ -13219,7 +14015,10 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13219
14015
  await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13220
14016
  }
13221
14017
  function addAgentFlags(cmd) {
13222
- return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent");
14018
+ return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent").option(
14019
+ "--no-cache",
14020
+ "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."
14021
+ );
13223
14022
  }
13224
14023
  function registerInternalGenerateCommand(program) {
13225
14024
  const generate = program.command("__generate", { hidden: true }).description("Internal import pipeline generation command");
@@ -13235,6 +14034,12 @@ function registerInternalGenerateCommand(program) {
13235
14034
  "--existing-entities-path <path>",
13236
14035
  "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."
13237
14036
  );
14037
+ componentsCmd.option("--cache-status", "Check whether all selected component definitions are cached");
14038
+ componentsCmd.option("--restore-cache", "Restore cached component definitions into the current session");
14039
+ componentsCmd.option(
14040
+ "--cached-components <json>",
14041
+ "Component names already restored by a prior cache-status lookup; skip per-component cache lookup"
14042
+ );
13238
14043
  addAgentFlags(componentsCmd).action(async (opts) => {
13239
14044
  await runGenerateSkill("components", opts, opts.verbose ?? false);
13240
14045
  });
@@ -13262,6 +14067,7 @@ var init_command = __esm({
13262
14067
  init_agent_output();
13263
14068
  init_GenerateView();
13264
14069
  init_db();
14070
+ init_cache_keys();
13265
14071
  init_read_existing_contentful_entities_from_session();
13266
14072
  init_summarize_existing_contentful_entities();
13267
14073
  init_resolve_session_id();
@@ -13977,7 +14783,7 @@ var init_user_agent = __esm({
13977
14783
  // packages/experience-design-system-cli-legacy/src/apply/api-client.ts
13978
14784
  import { parseCDFComponents } from "@contentful/experience-design-system-types";
13979
14785
  function defaultSleep(delayMs) {
13980
- return new Promise((resolve20) => setTimeout(resolve20, delayMs));
14786
+ return new Promise((resolve21) => setTimeout(resolve21, delayMs));
13981
14787
  }
13982
14788
  function isTransientStatus(status) {
13983
14789
  return status >= 500 && status <= 599;
@@ -14367,7 +15173,7 @@ ${trimmed}`;
14367
15173
  const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
14368
15174
  const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
14369
15175
  const jitter = Math.random() * baseDelay * 0.15;
14370
- await new Promise((resolve20) => setTimeout(resolve20, baseDelay + jitter));
15176
+ await new Promise((resolve21) => setTimeout(resolve21, baseDelay + jitter));
14371
15177
  }
14372
15178
  }
14373
15179
  throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
@@ -15177,7 +15983,7 @@ var init_terminal_capabilities = __esm({
15177
15983
 
15178
15984
  // packages/experience-design-system-cli-legacy/src/apply/command.ts
15179
15985
  import { createElement as createElement2 } from "react";
15180
- import { readFile as readFile12, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15986
+ import { readFile as readFile13, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15181
15987
  import { join as join16 } from "node:path";
15182
15988
  import {
15183
15989
  validateCDF,
@@ -15198,7 +16004,7 @@ function dieWithApiError(error) {
15198
16004
  async function readJsonFile(flag, p) {
15199
16005
  let text;
15200
16006
  try {
15201
- text = await readFile12(p, "utf8");
16007
+ text = await readFile13(p, "utf8");
15202
16008
  } catch {
15203
16009
  return await die2(`Error: file not found: ${p} (from ${flag})`);
15204
16010
  }
@@ -15252,7 +16058,7 @@ async function readTokensFromPath(flag, p) {
15252
16058
  for (const file of files.sort()) {
15253
16059
  let text;
15254
16060
  try {
15255
- text = await readFile12(file, "utf8");
16061
+ text = await readFile13(file, "utf8");
15256
16062
  } catch {
15257
16063
  continue;
15258
16064
  }
@@ -15621,11 +16427,11 @@ var init_command2 = __esm({
15621
16427
  });
15622
16428
 
15623
16429
  // packages/experience-design-system-cli-legacy/src/print/validate/validators/read-json-file.ts
15624
- import { readFile as readFile13 } from "node:fs/promises";
16430
+ import { readFile as readFile14 } from "node:fs/promises";
15625
16431
  async function readJsonFile2(filePath) {
15626
16432
  let content;
15627
16433
  try {
15628
- content = await readFile13(filePath, "utf-8");
16434
+ content = await readFile14(filePath, "utf-8");
15629
16435
  } catch (err) {
15630
16436
  return {
15631
16437
  ok: false,
@@ -16036,7 +16842,7 @@ var init_ValidateView = __esm({
16036
16842
  // packages/experience-design-system-cli-legacy/src/print/command.ts
16037
16843
  import { createElement as createElement3 } from "react";
16038
16844
  import { access as access3, mkdir as mkdir3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
16039
- import { basename as basename5, resolve as resolve12 } from "node:path";
16845
+ import { basename as basename5, resolve as resolve13 } from "node:path";
16040
16846
  import { CDF_SCHEMA_URL } from "@contentful/experience-design-system-types";
16041
16847
  async function die3(message) {
16042
16848
  process.stderr.write(`${message}
@@ -16103,7 +16909,7 @@ function registerPrintCommand(program) {
16103
16909
  "--allow-empty",
16104
16910
  "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."
16105
16911
  ).action(async (opts) => {
16106
- const outPath = resolve12(opts.out);
16912
+ const outPath = resolve13(opts.out);
16107
16913
  await assertOutIsNotDirectory(outPath);
16108
16914
  const sessionId2 = await resolveSession(opts.session, "generate components");
16109
16915
  await bindAnalyticsSessionId(sessionId2);
@@ -16151,7 +16957,7 @@ function registerPrintCommand(program) {
16151
16957
  `
16152
16958
  );
16153
16959
  }
16154
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16960
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16155
16961
  await writeFile3(outPath, `${JSON.stringify(cdfObj, null, 2)}
16156
16962
  `);
16157
16963
  process.stdout.write(
@@ -16160,7 +16966,7 @@ function registerPrintCommand(program) {
16160
16966
  );
16161
16967
  });
16162
16968
  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) => {
16163
- const outPath = resolve12(opts.out);
16969
+ const outPath = resolve13(opts.out);
16164
16970
  await assertOutIsNotDirectory(outPath);
16165
16971
  const sessionId2 = await resolveSession(opts.session, "generate tokens");
16166
16972
  await bindAnalyticsSessionId(sessionId2);
@@ -16175,7 +16981,7 @@ function registerPrintCommand(program) {
16175
16981
  await die3(`Error: no generated tokens in session '${sessionId2}'. Run generate tokens first.`);
16176
16982
  }
16177
16983
  const tree = rebuildDTCGTree(result.groups, result.tokens);
16178
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16984
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16179
16985
  await writeFile3(outPath, `${JSON.stringify(tree, null, 2)}
16180
16986
  `);
16181
16987
  process.stdout.write(
@@ -16324,7 +17130,7 @@ function MapTokensView({ result, onExit }) {
16324
17130
  const timer = setTimeout(onExit, 100);
16325
17131
  return () => clearTimeout(timer);
16326
17132
  }, [onExit]);
16327
- const summary = `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
17133
+ const summary = result.cached ? "reused from cache" : `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
16328
17134
  return /* @__PURE__ */ jsx8(
16329
17135
  CommandCompletionView,
16330
17136
  {
@@ -16458,8 +17264,8 @@ var init_resolve_defaults = __esm({
16458
17264
 
16459
17265
  // packages/experience-design-system-cli-legacy/src/map-tokens/command.ts
16460
17266
  import { createElement as createElement4 } from "react";
16461
- import { readFile as readFile14 } from "node:fs/promises";
16462
- import { resolve as resolve13 } from "node:path";
17267
+ import { readFile as readFile15 } from "node:fs/promises";
17268
+ import { resolve as resolve14 } from "node:path";
16463
17269
  async function renderResult(result) {
16464
17270
  if (process.stdout.isTTY) {
16465
17271
  const { waitUntilExit } = renderWithGoodbye(
@@ -16467,13 +17273,12 @@ async function renderResult(result) {
16467
17273
  );
16468
17274
  await waitUntilExit();
16469
17275
  } else {
16470
- process.stdout.write(
16471
- `map tokens complete
17276
+ const summary = result.cached ? "cached" : `${result.applied} mapping(s) applied`;
17277
+ process.stdout.write(`map tokens complete
16472
17278
  agent: ${result.agent}
16473
17279
  session=${result.sessionId}
16474
- ${result.applied} mapping(s) applied
16475
- `
16476
- );
17280
+ ${summary}
17281
+ `);
16477
17282
  await exitWithAnalytics(0);
16478
17283
  }
16479
17284
  }
@@ -16495,7 +17300,7 @@ async function runMapTokens(opts) {
16495
17300
  let mapPromptPath;
16496
17301
  if (mapPromptOverride?.kind === "text") mapPromptText = mapPromptOverride.value;
16497
17302
  if (mapPromptOverride?.kind === "path") {
16498
- mapPromptPath = resolve13(mapPromptOverride.value);
17303
+ mapPromptPath = resolve14(mapPromptOverride.value);
16499
17304
  try {
16500
17305
  await resolvePromptOverride(mapPromptOverride);
16501
17306
  } catch (error) {
@@ -16515,7 +17320,7 @@ async function runMapTokens(opts) {
16515
17320
  const knownTokenPaths = new Set(tokenLeaves.map((t) => t.path));
16516
17321
  const tokenMapDiagnostics = [];
16517
17322
  if (opts.tokenMap) {
16518
- const fileMappings = JSON.parse(await readFile14(opts.tokenMap, "utf8"));
17323
+ const fileMappings = JSON.parse(await readFile15(opts.tokenMap, "utf8"));
16519
17324
  const validMappings = {};
16520
17325
  for (const [rawName, path] of Object.entries(fileMappings)) {
16521
17326
  if (knownTokenPaths.has(path)) {
@@ -16593,7 +17398,7 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16593
17398
  let existingTokensInline;
16594
17399
  if (opts.existingEntitiesPath) {
16595
17400
  const existingContentfulEntities = await readExistingContentfulEntitiesFromSession(
16596
- resolve13(opts.existingEntitiesPath)
17401
+ resolve14(opts.existingEntitiesPath)
16597
17402
  );
16598
17403
  if (existingContentfulEntities) {
16599
17404
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
@@ -16628,10 +17433,30 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16628
17433
  skipAgent: "true"
16629
17434
  });
16630
17435
  updateStep(db, stepId2, "complete", { applied: "0", skipAgent: "true" });
16631
- await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0 });
17436
+ await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0, cached: false });
16632
17437
  return;
16633
17438
  }
16634
17439
  const agent = configuredAgent;
17440
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
17441
+ const promptHash = await hashPromptForSkill(
17442
+ "map-tokens",
17443
+ agent,
17444
+ model,
17445
+ void 0,
17446
+ existingTokensInline ? [hashContent(existingTokensInline)] : [],
17447
+ mapPromptText
17448
+ );
17449
+ const inputHash = computeMapTokensInputHash(db, sessionId2, componentSourceRefs);
17450
+ if (!noCache) {
17451
+ const cached = lookupCache(db, inputHash, "token_mapping", "__map_tokens__", promptHash);
17452
+ if (cached) {
17453
+ const appliedFromCache = copyMapTokensFromCache(db, cached.sourceSessionId, sessionId2);
17454
+ const stepId2 = createStep(db, sessionId2, "map tokens", { agent, model: model ?? "" });
17455
+ updateStep(db, stepId2, "complete", { cached: "true", applied: String(appliedFromCache) });
17456
+ await renderResult({ agent, sessionId: sessionId2, applied: appliedFromCache, cached: true });
17457
+ return;
17458
+ }
17459
+ }
16635
17460
  const binary = resolveAgentBinary(agent);
16636
17461
  if (!await assertBinaryInPath(binary)) {
16637
17462
  die(
@@ -16651,20 +17476,23 @@ Install it, choose another agent with --agent, or use --print-prompt to run the
16651
17476
  }
16652
17477
  const { calls, warnings: parseWarnings } = parseMapTokenPropToolCallLines(result.stdout);
16653
17478
  const { applied, warnings } = applyMapTokenPropCalls(db, sessionId2, calls, parseWarnings);
17479
+ if (!noCache) {
17480
+ storeCache(db, inputHash, "token_mapping", "__map_tokens__", sessionId2, false, promptHash);
17481
+ }
16654
17482
  updateStep(db, stepId, "complete", { applied: String(applied), warnings: String(warnings.length) });
16655
17483
  if (warnings.length > 0) {
16656
17484
  process.stderr.write(`Warnings:
16657
17485
  ${warnings.map((w) => ` ${w}`).join("\n")}
16658
17486
  `);
16659
17487
  }
16660
- await renderResult({ agent, sessionId: sessionId2, applied });
17488
+ await renderResult({ agent, sessionId: sessionId2, applied, cached: false });
16661
17489
  } finally {
16662
17490
  db.close();
16663
17491
  }
16664
17492
  }
16665
17493
  function registerMapTokensCommand(program) {
16666
17494
  const map = program.command("map", { hidden: true }).description("Suggest token restrictions for generated design-token props");
16667
- 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(
17495
+ 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(
16668
17496
  "--prompt <stage=value>",
16669
17497
  "Override a stage prompt (repeatable). Used here for the map-tokens stage.",
16670
17498
  (v, acc) => [...acc, v],
@@ -16684,6 +17512,7 @@ var init_command4 = __esm({
16684
17512
  init_render_with_goodbye();
16685
17513
  init_src();
16686
17514
  init_db();
17515
+ init_cache_keys();
16687
17516
  init_read_existing_contentful_entities_from_session();
16688
17517
  init_summarize_existing_contentful_entities();
16689
17518
  init_command3();
@@ -16700,7 +17529,7 @@ var init_command4 = __esm({
16700
17529
  });
16701
17530
 
16702
17531
  // packages/experience-design-system-cli-legacy/src/import/path-utils.ts
16703
- import { resolve as resolve14 } from "node:path";
17532
+ import { resolve as resolve15 } from "node:path";
16704
17533
  import { homedir as homedir5 } from "node:os";
16705
17534
  function expandTilde(input) {
16706
17535
  if (input === "~" || input.startsWith("~/") || input.startsWith("~\\")) {
@@ -16714,7 +17543,7 @@ function normalizePath(input) {
16714
17543
  p = p.slice(1, -1);
16715
17544
  }
16716
17545
  p = expandTilde(p);
16717
- return resolve14(p);
17546
+ return resolve15(p);
16718
17547
  }
16719
17548
  var init_path_utils = __esm({
16720
17549
  "packages/experience-design-system-cli-legacy/src/import/path-utils.ts"() {
@@ -16939,7 +17768,7 @@ var init_save_path_resolver = __esm({
16939
17768
  });
16940
17769
 
16941
17770
  // packages/experience-design-system-cli-legacy/src/runs/store.ts
16942
- import { readFile as readFile15, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
17771
+ import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
16943
17772
  import { join as join20 } from "node:path";
16944
17773
  import { homedir as homedir6 } from "node:os";
16945
17774
  import { randomBytes as randomBytes2 } from "node:crypto";
@@ -16979,7 +17808,7 @@ function migrateRecord(rec) {
16979
17808
  }
16980
17809
  async function readFileMaybe() {
16981
17810
  try {
16982
- const raw = await readFile15(RUNS_PATH, "utf8");
17811
+ const raw = await readFile16(RUNS_PATH, "utf8");
16983
17812
  const parsed = JSON.parse(raw);
16984
17813
  if (!READABLE_VERSIONS.has(parsed.version)) {
16985
17814
  throw new Error(
@@ -17043,15 +17872,15 @@ var init_store = __esm({
17043
17872
  });
17044
17873
 
17045
17874
  // packages/experience-design-system-cli-legacy/src/runs/fingerprint.ts
17046
- import { createHash as createHash2 } from "node:crypto";
17047
- import { readFile as readFile16, stat as stat4 } from "node:fs/promises";
17048
- import { resolve as resolve15 } from "node:path";
17875
+ import { createHash as createHash6 } from "node:crypto";
17876
+ import { readFile as readFile17, stat as stat4 } from "node:fs/promises";
17877
+ import { resolve as resolve16 } from "node:path";
17049
17878
  function rowField(row, key) {
17050
17879
  const v = row[key];
17051
17880
  return typeof v === "string" ? v : null;
17052
17881
  }
17053
17882
  function sha256Hex(buf) {
17054
- return createHash2("sha256").update(buf).digest("hex");
17883
+ return createHash6("sha256").update(buf).digest("hex");
17055
17884
  }
17056
17885
  async function buildSourceFingerprint(opts) {
17057
17886
  const rows = opts.db.prepare("SELECT name, source_path FROM raw_components WHERE session_id = ?").all(opts.extractSessionId);
@@ -17061,7 +17890,7 @@ async function buildSourceFingerprint(opts) {
17061
17890
  const sourcePath = rowField(row, "source_path");
17062
17891
  const name = rowField(row, "name");
17063
17892
  if (!sourcePath) continue;
17064
- const abs = resolve15(sourcePath);
17893
+ const abs = resolve16(sourcePath);
17065
17894
  if (seen.has(abs)) continue;
17066
17895
  seen.add(abs);
17067
17896
  try {
@@ -17076,10 +17905,10 @@ async function buildSourceFingerprint(opts) {
17076
17905
  let rawTokensMtime = null;
17077
17906
  let rawTokensContentHash = null;
17078
17907
  if (opts.rawTokensPath) {
17079
- const abs = resolve15(opts.rawTokensPath);
17908
+ const abs = resolve16(opts.rawTokensPath);
17080
17909
  rawTokensPath = abs;
17081
17910
  try {
17082
- const [st, buf] = await Promise.all([stat4(abs), readFile16(abs)]);
17911
+ const [st, buf] = await Promise.all([stat4(abs), readFile17(abs)]);
17083
17912
  rawTokensMtime = st.mtime.toISOString();
17084
17913
  rawTokensContentHash = sha256Hex(buf);
17085
17914
  } catch {
@@ -18834,11 +19663,11 @@ var init_slot_graph = __esm({
18834
19663
  });
18835
19664
 
18836
19665
  // packages/experience-design-system-cli-legacy/src/analyze/select/parser.ts
18837
- import { createHash as createHash3 } from "node:crypto";
19666
+ import { createHash as createHash7 } from "node:crypto";
18838
19667
  import { access as access5 } from "node:fs/promises";
18839
- import { isAbsolute, relative, resolve as resolve16 } from "node:path";
19668
+ import { isAbsolute, relative, resolve as resolve17 } from "node:path";
18840
19669
  function createComponentId(name, resolvedSourcePath) {
18841
- const sourceHash = createHash3("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
19670
+ const sourceHash = createHash7("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
18842
19671
  return `${name}-${sourceHash}`;
18843
19672
  }
18844
19673
  async function resolveComponentSourcePath(source, reviewRoot) {
@@ -18850,7 +19679,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18850
19679
  throw new Error(`Unable to access component source at ${source}`);
18851
19680
  }
18852
19681
  }
18853
- const candidate = resolve16(reviewRoot, source);
19682
+ const candidate = resolve17(reviewRoot, source);
18854
19683
  const relativeToRoot = relative(reviewRoot, candidate);
18855
19684
  if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || isAbsolute(relativeToRoot)) {
18856
19685
  throw new Error(
@@ -18865,7 +19694,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18865
19694
  }
18866
19695
  }
18867
19696
  async function loadReviewInput(components, options = {}) {
18868
- const reviewRoot = resolve16(options.reviewRoot ?? process.cwd());
19697
+ const reviewRoot = resolve17(options.reviewRoot ?? process.cwd());
18869
19698
  const records = await Promise.all(
18870
19699
  components.map(async (component) => {
18871
19700
  let resolvedSourcePath;
@@ -18896,7 +19725,7 @@ var init_parser = __esm({
18896
19725
  });
18897
19726
 
18898
19727
  // packages/experience-design-system-cli-legacy/src/analyze/select/command.ts
18899
- import { access as access6, readFile as readFile17 } from "node:fs/promises";
19728
+ import { access as access6, readFile as readFile18 } from "node:fs/promises";
18900
19729
  async function loadAndValidateForReview(sessionId2, projectRoot) {
18901
19730
  const db = openPipelineDb();
18902
19731
  let rawComponents;
@@ -18914,7 +19743,7 @@ async function patchReviewStateWithValidationErrors(sessionId2, errors, opts = {
18914
19743
  let snapshot;
18915
19744
  try {
18916
19745
  await access6(paths.statePath);
18917
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19746
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18918
19747
  } catch {
18919
19748
  snapshot = await loadAndValidateForReview(sessionId2, void 0);
18920
19749
  snapshot = await ensureRefineSession(sessionId2, artifactsRoot, snapshot);
@@ -18965,7 +19794,7 @@ async function rejectComponentsByName(sessionId2, names, opts = {}) {
18965
19794
  const nameSet = new Set(names);
18966
19795
  let snapshot;
18967
19796
  try {
18968
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19797
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18969
19798
  } catch {
18970
19799
  return;
18971
19800
  }
@@ -19064,9 +19893,9 @@ function spawnGenerateChild(opts) {
19064
19893
  stderr += chunk;
19065
19894
  opts.onStderr?.(chunk);
19066
19895
  });
19067
- const donePromise = new Promise((resolve20) => {
19896
+ const donePromise = new Promise((resolve21) => {
19068
19897
  child.on("close", (code, signal) => {
19069
- resolve20({
19898
+ resolve21({
19070
19899
  exitCode: code ?? 0,
19071
19900
  signal: signal ?? null,
19072
19901
  stdout,
@@ -19074,7 +19903,7 @@ function spawnGenerateChild(opts) {
19074
19903
  });
19075
19904
  });
19076
19905
  child.on("error", (err) => {
19077
- resolve20({
19906
+ resolve21({
19078
19907
  exitCode: 1,
19079
19908
  signal: null,
19080
19909
  stdout,
@@ -27212,6 +28041,9 @@ function shouldGenerateAfterCredentialsValidated(opts) {
27212
28041
  function shouldSkipFinalReviewAfterCredentials(state) {
27213
28042
  return state.finalReviewPassed && state.generateSessionId != null;
27214
28043
  }
28044
+ function resolveNoCacheForGenerate(opts) {
28045
+ return opts.cliNoCache;
28046
+ }
27215
28047
  function shouldBypassPreview(state) {
27216
28048
  return state.credentialsSkipped === true;
27217
28049
  }
@@ -27238,39 +28070,66 @@ var init_wizard_state_transitions = __esm({
27238
28070
  async function runSelectionAgent(options) {
27239
28071
  const db = openPipelineDb();
27240
28072
  let components;
28073
+ const cachedDecisions = /* @__PURE__ */ new Map();
28074
+ const cliVersion2 = await getCliCacheVersion();
28075
+ const promptHash = await hashPromptForSkill(
28076
+ "select",
28077
+ options.agent,
28078
+ options.model,
28079
+ options.promptPath,
28080
+ [SELECT_PAYLOAD_FORMAT],
28081
+ options.promptText
28082
+ );
27241
28083
  try {
27242
28084
  components = loadRawComponents(db, options.sessionId);
28085
+ if (!options.noCache) {
28086
+ for (const component of components) {
28087
+ const componentHash = hashComponentShape(component);
28088
+ const cached = lookupSelectCache(db, componentHash, promptHash, cliVersion2);
28089
+ if (cached) {
28090
+ cachedDecisions.set(component.name, {
28091
+ decision: cached.decision,
28092
+ reason: cached.reason
28093
+ });
28094
+ }
28095
+ }
28096
+ }
27243
28097
  } finally {
27244
28098
  db.close();
27245
28099
  }
27246
28100
  if (components.length === 0) return;
27247
- const invoker2 = createLocalCliAgentInvoker({
27248
- onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
27249
- });
27250
- const prompt = await buildPrompt({
27251
- skill: "select",
27252
- mode: "autonomous",
27253
- rawComponentsInline: JSON.stringify(
27254
- components.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
27255
- null,
27256
- 2
27257
- ),
27258
- propBucketsInline: logPropBuckets("select", bucketComponentsProps(components)),
27259
- outDir: process.cwd(),
27260
- ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
27261
- ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
27262
- });
27263
- const { result } = await invokeAgentWithOutput(
27264
- invoker2,
27265
- { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
27266
- false
27267
- );
27268
- if (result.timedOut) throw new Error("selection agent timed out");
27269
- if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
27270
- const parsed = parseSelectToolCallLines(result.stdout);
27271
- for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
28101
+ const componentsToRun = components.filter((component) => !cachedDecisions.has(component.name));
28102
+ let calls = [];
28103
+ if (componentsToRun.length > 0) {
28104
+ const invoker2 = createLocalCliAgentInvoker({
28105
+ onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
28106
+ });
28107
+ const prompt = await buildPrompt({
28108
+ skill: "select",
28109
+ mode: "autonomous",
28110
+ rawComponentsInline: JSON.stringify(
28111
+ componentsToRun.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
28112
+ null,
28113
+ 2
28114
+ ),
28115
+ propBucketsInline: logPropBuckets("select", bucketComponentsProps(componentsToRun)),
28116
+ outDir: process.cwd(),
28117
+ ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
28118
+ ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
28119
+ });
28120
+ const { result } = await invokeAgentWithOutput(
28121
+ invoker2,
28122
+ { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
28123
+ false
28124
+ );
28125
+ if (result.timedOut) throw new Error("selection agent timed out");
28126
+ if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
28127
+ const parsed = parseSelectToolCallLines(result.stdout);
28128
+ for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
27272
28129
  `);
27273
- const byName = new Map(parsed.calls.map((call) => [call.name, call]));
28130
+ calls = parsed.calls;
28131
+ }
28132
+ const byName = new Map(calls.map((call) => [call.name, call]));
27274
28133
  const writeDb = openPipelineDb();
27275
28134
  try {
27276
28135
  writeDb.exec("BEGIN");
@@ -27278,15 +28137,27 @@ async function runSelectionAgent(options) {
27278
28137
  `UPDATE raw_components SET status = ?, reject_reason = ? WHERE session_id = ? AND component_id = ?`
27279
28138
  );
27280
28139
  for (const component of components) {
28140
+ const cached = cachedDecisions.get(component.name);
27281
28141
  const call = byName.get(component.name);
27282
- if (!call) continue;
27283
- const rejected = call.tool === "reject_component";
28142
+ if (!cached && !call) continue;
28143
+ const rejected = cached ? cached.decision === "rejected" : call.tool === "reject_component";
28144
+ const reason = cached ? cached.reason : call.reason ?? null;
27284
28145
  update.run(
27285
28146
  rejected ? "rejected" : "accepted",
27286
- rejected ? call.reason ?? null : null,
28147
+ rejected ? reason : null,
27287
28148
  options.sessionId,
27288
28149
  component.component_id
27289
28150
  );
28151
+ if (!cached) {
28152
+ storeSelectCache(
28153
+ writeDb,
28154
+ hashComponentShape(component),
28155
+ promptHash,
28156
+ cliVersion2,
28157
+ rejected ? "rejected" : "accepted",
28158
+ rejected ? reason : null
28159
+ );
28160
+ }
27290
28161
  }
27291
28162
  writeDb.exec("COMMIT");
27292
28163
  } catch (error) {
@@ -27296,7 +28167,7 @@ async function runSelectionAgent(options) {
27296
28167
  writeDb.close();
27297
28168
  }
27298
28169
  }
27299
- var DEFAULT_TIMEOUT_MS4;
28170
+ var SELECT_PAYLOAD_FORMAT, DEFAULT_TIMEOUT_MS4;
27300
28171
  var init_run_selection_agent = __esm({
27301
28172
  "packages/experience-design-system-cli-legacy/src/import/tui/run-selection-agent.ts"() {
27302
28173
  "use strict";
@@ -27307,6 +28178,8 @@ var init_run_selection_agent = __esm({
27307
28178
  init_debug_logger();
27308
28179
  init_agent_output();
27309
28180
  init_db();
28181
+ init_cache_keys();
28182
+ SELECT_PAYLOAD_FORMAT = "prop-buckets-v1";
27310
28183
  DEFAULT_TIMEOUT_MS4 = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 5 * 60 * 1e3);
27311
28184
  }
27312
28185
  });
@@ -27325,7 +28198,7 @@ __export(WizardApp_exports, {
27325
28198
  });
27326
28199
  import { useEffect as useEffect15, useRef as useRef10, useState as useState23 } from "react";
27327
28200
  import { Box as Box42, Text as Text46 } from "ink";
27328
- import { join as join23, resolve as resolve17 } from "node:path";
28201
+ import { join as join23, resolve as resolve18 } from "node:path";
27329
28202
  import { appendFileSync as appendFileSync2, writeFileSync } from "node:fs";
27330
28203
  import { access as access7, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
27331
28204
  import { tmpdir } from "node:os";
@@ -27338,21 +28211,28 @@ function buildGenerateComponentsArgs(opts) {
27338
28211
  if (opts.tokensPath) args.push("--tokens", opts.tokensPath);
27339
28212
  if (opts.model) args.push("--model", opts.model);
27340
28213
  if (opts.bedrock) args.push("--bedrock");
28214
+ if (opts.noCache) args.push("--no-cache");
27341
28215
  if (opts.generatePromptPath) args.push("--generate-prompt-path", opts.generatePromptPath);
27342
28216
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27343
28217
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
28218
+ if (opts.restoreCache) args.push("--restore-cache");
28219
+ if (opts.cachedComponents && opts.cachedComponents.length > 0) {
28220
+ args.push("--cached-components", JSON.stringify(opts.cachedComponents));
28221
+ }
27344
28222
  return args;
27345
28223
  }
27346
28224
  function buildGenerateTokensArgs(opts) {
27347
28225
  const args = [findCliPath(), "__generate", "tokens", "--agent", opts.agent, "--raw-tokens", opts.rawTokensPath];
27348
28226
  if (opts.model) args.push("--model", opts.model);
27349
28227
  if (opts.bedrock) args.push("--bedrock");
28228
+ if (opts.noCache) args.push("--no-cache");
27350
28229
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27351
28230
  return args;
27352
28231
  }
27353
28232
  function buildMapTokensArgs(opts) {
27354
28233
  const args = ["map", "tokens", "--session", opts.sessionId, "--agent", opts.agent];
27355
28234
  if (opts.model) args.push("--model", opts.model);
28235
+ if (opts.noCache) args.push("--no-cache");
27356
28236
  if (opts.skipAgent) args.push("--skip-agent");
27357
28237
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27358
28238
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
@@ -27444,6 +28324,7 @@ function WizardApp({
27444
28324
  initialProjectPath,
27445
28325
  host,
27446
28326
  promptOverrides,
28327
+ noCache = false,
27447
28328
  livePreview = true,
27448
28329
  generatePromptPath,
27449
28330
  skipMapTokens = false,
@@ -27468,6 +28349,7 @@ function WizardApp({
27468
28349
  });
27469
28350
  const allowEmptyDeleteAllRef = useRef10(false);
27470
28351
  const generateChildRef = useRef10(null);
28352
+ const generationCachePreflightRef = useRef10(null);
27471
28353
  const selectionToReviewStartedAtRef = useRef10(null);
27472
28354
  const credentialsValidationIdRef = useRef10(0);
27473
28355
  const pendingCredentialValidationRef = useRef10(null);
@@ -27482,8 +28364,9 @@ function WizardApp({
27482
28364
  const existingEntitiesPromiseRef = useRef10(null);
27483
28365
  const generatePromiseRef = useRef10(null);
27484
28366
  const rawTokensEntryReady = !!initialRawTokensPath;
28367
+ const effectiveNoCache = resolveNoCacheForGenerate({ cliNoCache: noCache });
27485
28368
  const initialStepResolved = rawTokensEntryReady ? initialProjectPath ? "path-validation" : "credentials" : initialProjectPath ? "token-input" : "welcome";
27486
- const initialOutDir = initialProjectPath ? join23(resolve17(initialProjectPath), ".contentful") : "";
28369
+ const initialOutDir = initialProjectPath ? join23(resolve18(initialProjectPath), ".contentful") : "";
27487
28370
  const [state, setState] = useState23({
27488
28371
  step: initialStepResolved,
27489
28372
  agent: initialAgent ?? "claude",
@@ -27659,13 +28542,14 @@ If you are using AWS Bedrock, run:
27659
28542
  if (!deferStep) update({ step: nextStep });
27660
28543
  return true;
27661
28544
  };
27662
- const runGenerateTokens = async (rawTokensPath, outDir) => {
28545
+ const runGenerateTokens = async (rawTokensPath, outDir, forceRegenerate = false) => {
27663
28546
  update({ tokenGenerationStatus: "running" });
27664
28547
  const tokenArgs = buildGenerateTokensArgs({
27665
28548
  rawTokensPath,
27666
28549
  agent: state.agent,
27667
28550
  ...state.agentModel ? { model: state.agentModel } : {},
27668
28551
  ...state.bedrock ? { bedrock: true } : {},
28552
+ noCache: effectiveNoCache || forceRegenerate,
27669
28553
  promptOverrides
27670
28554
  });
27671
28555
  const result = await runSpawnedCli(tokenArgs);
@@ -27714,6 +28598,7 @@ If you are using AWS Bedrock, run:
27714
28598
  const timer = startWizardTimer("token-mapping", {
27715
28599
  sessionId: sessionId2,
27716
28600
  silent,
28601
+ noCache: effectiveNoCache,
27717
28602
  skipAgent: skipMapTokens
27718
28603
  });
27719
28604
  let mappablePropCount = 0;
@@ -27722,7 +28607,7 @@ If you are using AWS Bedrock, run:
27722
28607
  const db = openPipelineDb();
27723
28608
  try {
27724
28609
  if (state.tokenSessionId && state.tokenSessionId !== sessionId2) {
27725
- copyTokensBetweenSessions(db, state.tokenSessionId, sessionId2);
28610
+ copyTokensFromCache(db, state.tokenSessionId, sessionId2);
27726
28611
  } else if (!state.tokenSessionId && state.tokensPath) {
27727
28612
  const tokens = await readTokensFromPath("tokens", state.tokensPath);
27728
28613
  storeDtcgTokens(db, sessionId2, [], tokens);
@@ -27762,6 +28647,7 @@ If you are using AWS Bedrock, run:
27762
28647
  sessionId: sessionId2,
27763
28648
  agent: state.agent,
27764
28649
  ...state.agentModel ? { model: state.agentModel } : {},
28650
+ noCache: effectiveNoCache,
27765
28651
  skipAgent: skipMapTokens,
27766
28652
  promptOverrides,
27767
28653
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {}
@@ -27841,7 +28727,7 @@ If you are using AWS Bedrock, run:
27841
28727
  return promise;
27842
28728
  };
27843
28729
  const runExtract = async (projectPath, showProgress = true) => {
27844
- const outDir = join23(resolve17(projectPath), ".contentful");
28730
+ const outDir = join23(resolve18(projectPath), ".contentful");
27845
28731
  const existingEntitiesExpected = !state.credentialsSkipped && Boolean(state.spaceId && state.environmentId && state.cmaToken && projectPath);
27846
28732
  update({
27847
28733
  ...showProgress ? { step: "extracting" } : {},
@@ -27856,6 +28742,7 @@ If you are using AWS Bedrock, run:
27856
28742
  });
27857
28743
  const existingEntitiesPromise = startExistingEntitiesFetch(projectPath, outDir);
27858
28744
  const extractArgs = [findCliPath(), "__extract", "--project", projectPath];
28745
+ if (effectiveNoCache) extractArgs.push("--no-cache");
27859
28746
  let selectionPromptText;
27860
28747
  let selectionPromptPath;
27861
28748
  try {
@@ -27864,7 +28751,7 @@ If you are using AWS Bedrock, run:
27864
28751
  const selectOverride = overrides.get("select");
27865
28752
  if (selectOverride?.kind === "text") selectionPromptText = selectOverride.value;
27866
28753
  if (selectOverride?.kind === "path") {
27867
- selectionPromptPath = resolve17(selectOverride.value);
28754
+ selectionPromptPath = resolve18(selectOverride.value);
27868
28755
  await resolvePromptOverride(selectOverride);
27869
28756
  }
27870
28757
  } catch (error) {
@@ -27888,7 +28775,8 @@ If you are using AWS Bedrock, run:
27888
28775
  agent: state.agent,
27889
28776
  ...state.agentModel ? { model: state.agentModel } : {},
27890
28777
  ...selectionPromptText !== void 0 ? { promptText: selectionPromptText } : {},
27891
- ...selectionPromptPath ? { promptPath: selectionPromptPath } : {}
28778
+ ...selectionPromptPath ? { promptPath: selectionPromptPath } : {},
28779
+ noCache: effectiveNoCache
27892
28780
  });
27893
28781
  })().catch((error) => {
27894
28782
  selectionError = error;
@@ -28033,17 +28921,136 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28033
28921
  generateProgress: null
28034
28922
  }));
28035
28923
  };
28036
- const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2) => ({
28924
+ const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => ({
28037
28925
  sessionId: extractSessionId,
28038
28926
  tokensPath,
28039
28927
  agent: state.agent,
28040
28928
  ...state.agentModel ? { model: state.agentModel } : {},
28041
28929
  ...state.bedrock ? { bedrock: true } : {},
28930
+ noCache: effectiveNoCache,
28042
28931
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {},
28043
28932
  ...generatePromptPath2 ? { generatePromptPath: generatePromptPath2 } : {},
28044
- promptOverrides
28933
+ promptOverrides,
28934
+ ...cachedComponents && cachedComponents.length > 0 ? { cachedComponents } : {}
28045
28935
  });
28046
- const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2));
28936
+ const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents));
28937
+ const buildGenerateCliArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateComponentsArgs(
28938
+ buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents)
28939
+ );
28940
+ const checkGenerateCache = async (extractSessionId, tokensPath) => {
28941
+ const timer = startWizardTimer("generation-cache-status", {
28942
+ sessionId: extractSessionId,
28943
+ noCache: effectiveNoCache
28944
+ });
28945
+ if (effectiveNoCache) {
28946
+ finishWizardTimer("generation-cache-status", timer, { status: "skipped" });
28947
+ return false;
28948
+ }
28949
+ const result = await runCli([...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"]);
28950
+ const hit = result.exitCode === 0 && /^cache-status=hit$/m.test(result.stdout);
28951
+ finishWizardTimer("generation-cache-status", timer, {
28952
+ status: hit ? "hit" : "miss",
28953
+ exitCode: result.exitCode,
28954
+ ...result.exitCode !== 0 ? { stderrTail: result.stderr.trim().slice(-500) } : {}
28955
+ });
28956
+ return hit;
28957
+ };
28958
+ const checkGenerateCacheComponents = async (extractSessionId, tokensPath, restoreCache = false) => {
28959
+ const timer = startWizardTimer("generation-cache-components", {
28960
+ sessionId: extractSessionId,
28961
+ restoreCache,
28962
+ noCache: effectiveNoCache
28963
+ });
28964
+ if (effectiveNoCache) {
28965
+ finishWizardTimer("generation-cache-components", timer, { status: "skipped" });
28966
+ return /* @__PURE__ */ new Set();
28967
+ }
28968
+ const args = [...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"];
28969
+ if (restoreCache) args.push("--restore-cache");
28970
+ const result = await runCli(args);
28971
+ if (result.exitCode !== 0) {
28972
+ finishWizardTimer("generation-cache-components", timer, {
28973
+ status: "error",
28974
+ exitCode: result.exitCode,
28975
+ stderrTail: result.stderr.trim().slice(-500)
28976
+ });
28977
+ return /* @__PURE__ */ new Set();
28978
+ }
28979
+ const line = /^cache-components=(.+)$/m.exec(result.stdout)?.[1];
28980
+ if (!line) {
28981
+ finishWizardTimer("generation-cache-components", timer, {
28982
+ status: "missing-output",
28983
+ exitCode: result.exitCode,
28984
+ cachedCount: 0
28985
+ });
28986
+ return /* @__PURE__ */ new Set();
28987
+ }
28988
+ try {
28989
+ const names = JSON.parse(line);
28990
+ const cachedNames = new Set(
28991
+ Array.isArray(names) ? names.filter((name) => typeof name === "string") : []
28992
+ );
28993
+ finishWizardTimer("generation-cache-components", timer, {
28994
+ status: "complete",
28995
+ exitCode: result.exitCode,
28996
+ cachedCount: cachedNames.size
28997
+ });
28998
+ return cachedNames;
28999
+ } catch {
29000
+ finishWizardTimer("generation-cache-components", timer, {
29001
+ status: "invalid-output",
29002
+ exitCode: result.exitCode,
29003
+ cachedCount: 0
29004
+ });
29005
+ return /* @__PURE__ */ new Set();
29006
+ }
29007
+ };
29008
+ useEffect15(() => {
29009
+ 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) {
29010
+ return;
29011
+ }
29012
+ const sessionId2 = state.extractSessionId;
29013
+ if (generationCachePreflightRef.current?.sessionId === sessionId2) return;
29014
+ const timer = startWizardTimer("generation-cache-preflight", { sessionId: sessionId2 });
29015
+ const db = openPipelineDb();
29016
+ let generatedNames = null;
29017
+ try {
29018
+ const rows = db.prepare("SELECT name, status FROM raw_components WHERE session_id = ?").all(sessionId2);
29019
+ if (rows.length > 0 && rows.every((row) => row.status === "generated")) {
29020
+ generatedNames = new Set(rows.map((row) => row.name));
29021
+ }
29022
+ } finally {
29023
+ db.close();
29024
+ }
29025
+ if (generatedNames) {
29026
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: Promise.resolve(generatedNames) };
29027
+ finishWizardTimer("generation-cache-preflight", timer, {
29028
+ status: "complete",
29029
+ source: "session-db",
29030
+ cachedCount: generatedNames.size
29031
+ });
29032
+ return;
29033
+ }
29034
+ const promise = checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
29035
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise };
29036
+ void promise.then((cachedNames) => {
29037
+ finishWizardTimer("generation-cache-preflight", timer, {
29038
+ status: "complete",
29039
+ source: "cache-status",
29040
+ cachedCount: cachedNames.size
29041
+ });
29042
+ });
29043
+ }, [
29044
+ state.step,
29045
+ state.extractSessionId,
29046
+ state.tokensPath,
29047
+ state.rawTokensPath,
29048
+ state.tokenGenerationStatus,
29049
+ state.selectionAgentStatus,
29050
+ state.compositionPhase,
29051
+ state.existingEntitiesStatus,
29052
+ state.existingEntitiesPath
29053
+ ]);
28047
29054
  const startGeneratePrefetch = (extractSessionId, tokensPath) => {
28048
29055
  const args = buildGenerateArgs(extractSessionId, tokensPath);
28049
29056
  let progressCursor = null;
@@ -28098,13 +29105,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28098
29105
  });
28099
29106
  return donePromise;
28100
29107
  };
28101
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false) => {
29108
+ const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false, cachedComponents = []) => {
28102
29109
  const timer = startWizardTimer("component-generation", {
28103
29110
  sessionId: extractSessionId,
28104
29111
  acceptedCount,
28105
- suppressScreen
29112
+ suppressScreen,
29113
+ noCache: effectiveNoCache
28106
29114
  });
28107
- const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath);
29115
+ const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath, cachedComponents);
28108
29116
  let progressCursor = state.generateProgress;
28109
29117
  const { donePromise } = spawnGenerateChild({
28110
29118
  command: "node",
@@ -28174,6 +29182,25 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28174
29182
  finishSelectionToReviewTimer("error");
28175
29183
  }
28176
29184
  };
29185
+ const finishCachedGeneration = async (sessionId2, acceptedCount) => {
29186
+ if (await runMapTokens2(sessionId2, true)) {
29187
+ finishSelectionToReviewTimer("cached");
29188
+ update(
29189
+ {
29190
+ acceptedCount,
29191
+ autoRejectedCount: 0,
29192
+ generateSessionId: sessionId2,
29193
+ generatedCount: acceptedCount,
29194
+ generateProgress: null,
29195
+ mapTokensStatus: "complete",
29196
+ step: "final-review"
29197
+ },
29198
+ { clearScreen: false }
29199
+ );
29200
+ } else {
29201
+ finishSelectionToReviewTimer("error");
29202
+ }
29203
+ };
28177
29204
  const advanceToPushFlow = (generatedAcceptedCount) => {
28178
29205
  update({ generatedAcceptedCount });
28179
29206
  void runSaveAndPush();
@@ -28358,8 +29385,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28358
29385
  }
28359
29386
  }
28360
29387
  if (await runAgentAuthCheck("generating", true)) {
28361
- update({ step: "generating" });
28362
- void runGenerate(sid, state.tokensPath, state.acceptedCount);
29388
+ const cacheHit = await checkGenerateCache(sid, state.tokensPath);
29389
+ if (cacheHit) {
29390
+ update({ step: "generating" });
29391
+ void finishCachedGeneration(sid, state.acceptedCount);
29392
+ } else {
29393
+ update({ step: "generating" });
29394
+ void runGenerate(sid, state.tokensPath, state.acceptedCount);
29395
+ }
28363
29396
  }
28364
29397
  return;
28365
29398
  }
@@ -28723,7 +29756,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28723
29756
  });
28724
29757
  return { ok: false };
28725
29758
  }
28726
- await new Promise((resolve20) => setTimeout(resolve20, SAVE_CONFIRMATION_DELAY_MS));
29759
+ await new Promise((resolve21) => setTimeout(resolve21, SAVE_CONFIRMATION_DELAY_MS));
28727
29760
  update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
28728
29761
  return { ok: true };
28729
29762
  };
@@ -28800,6 +29833,12 @@ If using a custom host configuration, make sure the space exists on that host.`
28800
29833
  tokenReuseChecked.current = true;
28801
29834
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28802
29835
  const existingTokensPath = join23(state.outDir, "tokens.json");
29836
+ if (effectiveNoCache) {
29837
+ tokenGenerationPromiseRef.current = runAgentAuthCheck(tokenNextStep).then(
29838
+ (ok2) => ok2 ? runGenerateTokens(state.rawTokensPath, state.outDir) : false
29839
+ );
29840
+ return;
29841
+ }
28803
29842
  (async () => {
28804
29843
  try {
28805
29844
  await access7(existingTokensPath);
@@ -28883,7 +29922,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28883
29922
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28884
29923
  update({ step: tokenNextStep });
28885
29924
  if (await runAgentAuthCheck(tokenNextStep)) {
28886
- tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir);
29925
+ tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir, true);
28887
29926
  }
28888
29927
  },
28889
29928
  onQuit: () => process.exit(0)
@@ -28982,6 +30021,29 @@ If using a custom host configuration, make sure the space exists on that host.`
28982
30021
  acceptedCount: decisions.accepted.length,
28983
30022
  rejectedCount: decisions.rejected.length
28984
30023
  }) : null;
30024
+ const preflight = generationCachePreflightRef.current;
30025
+ const cachePromise = preflight?.sessionId === sessionId2 ? preflight.promise : checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
30026
+ if (!preflight || preflight.sessionId !== sessionId2) {
30027
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: cachePromise };
30028
+ }
30029
+ const cacheWaitStartedAt = startWizardTimer("selection-cache-restoration-wait", {
30030
+ sessionId: sessionId2,
30031
+ preflight: Boolean(preflight?.sessionId === sessionId2)
30032
+ });
30033
+ try {
30034
+ const cachedNames = await cachePromise;
30035
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30036
+ status: "complete",
30037
+ cachedCount: cachedNames.size
30038
+ });
30039
+ } catch (error) {
30040
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30041
+ status: "error",
30042
+ error: error instanceof Error ? error.message : String(error)
30043
+ });
30044
+ finishSelectionToReviewTimer("error");
30045
+ throw error;
30046
+ }
28985
30047
  await runScopeGate({
28986
30048
  sessionId: sessionId2,
28987
30049
  decisions,
@@ -28990,12 +30052,35 @@ If using a custom host configuration, make sure the space exists on that host.`
28990
30052
  if (shouldGenerateAfterScopeGate({ acceptedCount })) {
28991
30053
  const authPromise = runAgentAuthCheck("generating", true);
28992
30054
  if (await authPromise) {
28993
- update({
28994
- step: "generating",
28995
- generateProgress: null,
28996
- acceptedCount
30055
+ const comparisonStartedAt = startWizardTimer("generation-cache-set-comparison", {
30056
+ sessionId: sid,
30057
+ acceptedCount: decisions?.accepted.length ?? 0
28997
30058
  });
28998
- void runGenerate(sid, state.tokensPath, acceptedCount);
30059
+ let cachedNames = await cachePromise;
30060
+ const acceptedNames = decisions?.accepted ?? [];
30061
+ let cacheHit = !effectiveNoCache && acceptedNames.length > 0 && acceptedNames.every((name) => cachedNames.has(name));
30062
+ if (!cacheHit && !effectiveNoCache && acceptedNames.length > 0) {
30063
+ cachedNames = await checkGenerateCacheComponents(sid, state.tokensPath, true);
30064
+ cacheHit = acceptedNames.every((name) => cachedNames.has(name));
30065
+ }
30066
+ finishWizardTimer("generation-cache-set-comparison", comparisonStartedAt, {
30067
+ status: "complete",
30068
+ cacheHit,
30069
+ cachedCount: cachedNames.size,
30070
+ acceptedCount: acceptedNames.length
30071
+ });
30072
+ if (cacheHit) {
30073
+ void finishCachedGeneration(sid, acceptedCount);
30074
+ } else {
30075
+ const acceptedNameSet = new Set(acceptedNames);
30076
+ const restoredCachedNames = [...cachedNames].filter((name) => acceptedNameSet.has(name));
30077
+ update({
30078
+ step: "generating",
30079
+ generateProgress: null,
30080
+ acceptedCount
30081
+ });
30082
+ void runGenerate(sid, state.tokensPath, acceptedCount, false, restoredCachedNames);
30083
+ }
28999
30084
  } else {
29000
30085
  finishSelectionToReviewTimer("error");
29001
30086
  }
@@ -29361,7 +30446,7 @@ function registerImportCommand(program) {
29361
30446
  "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",
29362
30447
  (v, acc) => [...acc, v],
29363
30448
  []
29364
- ).action(async (opts) => {
30449
+ ).option("--no-cache", "Re-run all steps even if output already exists").action(async (opts) => {
29365
30450
  const interactiveTerminalSupported = getInteractiveTerminalSupport().supported;
29366
30451
  if (opts.tokens !== void 0) {
29367
30452
  const { access: access9 } = await import("node:fs/promises");
@@ -29398,6 +30483,7 @@ function registerImportCommand(program) {
29398
30483
  ...resolvedModel ? { initialModel: resolvedModel } : {},
29399
30484
  initialProjectPath: opts.project !== "." ? normalizePath(opts.project) : void 0,
29400
30485
  ...buildCompositionForwardingOptions(opts),
30486
+ noCache: opts.cache === false,
29401
30487
  skipMapTokens: false,
29402
30488
  livePreview: true,
29403
30489
  generatePromptPath: creds.generatePromptPath,
@@ -29485,7 +30571,7 @@ async function pathExists3(path) {
29485
30571
  return access8(path).then(() => true).catch(() => false);
29486
30572
  }
29487
30573
  function runSpawn(cmd, args, opts = {}) {
29488
- return new Promise((resolve20) => {
30574
+ return new Promise((resolve21) => {
29489
30575
  const child = spawn6(cmd, args, {
29490
30576
  cwd: opts.cwd,
29491
30577
  env: opts.env ?? process.env,
@@ -29497,7 +30583,7 @@ function runSpawn(cmd, args, opts = {}) {
29497
30583
  const settle = (result) => {
29498
30584
  if (settled) return;
29499
30585
  settled = true;
29500
- resolve20(result);
30586
+ resolve21(result);
29501
30587
  };
29502
30588
  child.on("error", (err) => settle({ exitCode: 1, stdout: "", stderr: err.message }));
29503
30589
  child.stdout.on("data", (d) => {
@@ -30698,13 +31784,13 @@ function PrerequisitesScreen({
30698
31784
  const driver = {
30699
31785
  ...base,
30700
31786
  emit: (event) => setEvents((current) => [...current, event]),
30701
- confirm: (question, defaultYes = true) => new Promise((resolve20) => {
31787
+ confirm: (question, defaultYes = true) => new Promise((resolve21) => {
30702
31788
  setConfirm({
30703
31789
  question,
30704
31790
  defaultYes,
30705
31791
  resolve: (answer) => {
30706
31792
  setConfirm(null);
30707
- resolve20(answer);
31793
+ resolve21(answer);
30708
31794
  }
30709
31795
  });
30710
31796
  })
@@ -30779,8 +31865,8 @@ function SetupScreen({
30779
31865
  useEffect21(() => {
30780
31866
  if (startedRef.current) return;
30781
31867
  startedRef.current = true;
30782
- const runScreen = (build) => new Promise((resolve20) => {
30783
- setScreen(build(resolve20));
31868
+ const runScreen = (build) => new Promise((resolve21) => {
31869
+ setScreen(build(resolve21));
30784
31870
  });
30785
31871
  void (async () => {
30786
31872
  const results = [];
@@ -31388,8 +32474,11 @@ var init_source_call_site_evidence = __esm({
31388
32474
  });
31389
32475
 
31390
32476
  // packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts
31391
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile18, stat as stat6 } from "node:fs/promises";
31392
- import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolve18 } from "node:path";
32477
+ import { mkdir as mkdir6, readdir as readdir3, readFile as readFile19, stat as stat6 } from "node:fs/promises";
32478
+ import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolve19 } from "node:path";
32479
+ function resolveExtractNoCache(opts) {
32480
+ return opts.noCache === true || opts.cache === false;
32481
+ }
31393
32482
  function isDenylistedNoiseFile(name) {
31394
32483
  return DENYLISTED_EXACT_FILE_NAMES.has(name) || DENYLISTED_FILE_NAME_PATTERNS.some((pattern) => pattern.test(name));
31395
32484
  }
@@ -31403,12 +32492,12 @@ async function retryDatabaseWrite(operation, attempts = 8) {
31403
32492
  } catch (error) {
31404
32493
  const message = error instanceof Error ? error.message : String(error);
31405
32494
  if (!/database is locked|database is busy/i.test(message) || attempt >= attempts) throw error;
31406
- await new Promise((resolve20) => setTimeout(resolve20, Math.min(250 * attempt, 2e3)));
32495
+ await new Promise((resolve21) => setTimeout(resolve21, Math.min(250 * attempt, 2e3)));
31407
32496
  }
31408
32497
  }
31409
32498
  }
31410
32499
  function resolveFromProjectRoot(projectRoot, inputPath) {
31411
- return isAbsolute2(inputPath) ? inputPath : resolve18(projectRoot, inputPath);
32500
+ return isAbsolute2(inputPath) ? inputPath : resolve19(projectRoot, inputPath);
31412
32501
  }
31413
32502
  async function pathExists4(path) {
31414
32503
  return Boolean(await stat6(path).catch(() => null));
@@ -31455,7 +32544,7 @@ async function readCandidateFiles(components, extraFiles = []) {
31455
32544
  await Promise.all(
31456
32545
  [...paths].map(async (p) => {
31457
32546
  try {
31458
- const content = await readFile18(p, "utf8");
32547
+ const content = await readFile19(p, "utf8");
31459
32548
  out.push({ path: p, content });
31460
32549
  } catch {
31461
32550
  }
@@ -31468,7 +32557,8 @@ function registerInternalExtractCommand(program) {
31468
32557
  "--resolve-unreachable <mode>",
31469
32558
  "Retry pass for unresolved Svelte Props types: 'auto' (default), 'always', or 'never'",
31470
32559
  "auto"
31471
- ).action(async (opts) => {
32560
+ ).option("--no-cache", "Re-run extraction even when all source files are unchanged").action(async (opts) => {
32561
+ const noCache = resolveExtractNoCache(opts);
31472
32562
  const resolveUnreachable = (() => {
31473
32563
  const v = opts.resolveUnreachable ?? "auto";
31474
32564
  if (v !== "auto" && v !== "always" && v !== "never") {
@@ -31478,7 +32568,7 @@ function registerInternalExtractCommand(program) {
31478
32568
  }
31479
32569
  return v;
31480
32570
  })();
31481
- const projectRoot = resolve18(opts.project);
32571
+ const projectRoot = resolve19(opts.project);
31482
32572
  const outDir = join27(projectRoot, ".contentful");
31483
32573
  let sourceDirectory;
31484
32574
  if (opts.dir !== void 0) {
@@ -31502,16 +32592,50 @@ function registerInternalExtractCommand(program) {
31502
32592
  process.stderr.write(`progress=scan-done:${sourceFiles.length}
31503
32593
  `);
31504
32594
  }
31505
- const extraction = await extractComponents(
31506
- sourceFiles,
31507
- ({ filesProcessed, componentsFound }) => {
32595
+ const extractionCacheDb = openPipelineDb();
32596
+ let extraction;
32597
+ let extractionCacheHits = 0;
32598
+ try {
32599
+ const cacheVersion = await getCliCacheVersion();
32600
+ const cachedByPath = /* @__PURE__ */ new Map();
32601
+ if (!noCache && sourceFiles.length > 0) {
32602
+ const hashes = await Promise.all(
32603
+ sourceFiles.map(async (filePath) => [filePath, await hashFile(filePath)])
32604
+ );
32605
+ for (const [filePath, fileHash] of hashes) {
32606
+ cachedByPath.set(filePath, lookupExtractCache(extractionCacheDb, fileHash, cacheVersion));
32607
+ }
32608
+ }
32609
+ const allFilesCached = !noCache && sourceFiles.length > 0 && sourceFiles.every((filePath) => cachedByPath.get(filePath) !== null);
32610
+ if (allFilesCached) {
32611
+ const cachedComponents = sourceFiles.flatMap((filePath) => cachedByPath.get(filePath).components);
32612
+ extractionCacheHits = sourceFiles.length;
32613
+ extraction = { components: cachedComponents, warnings: [], exclusions: [] };
31508
32614
  if (!process.stdout.isTTY) {
31509
- process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
31510
- `);
32615
+ process.stderr.write(
32616
+ `progress=extract:${sourceFiles.length}/${sourceFiles.length}:${cachedComponents.length}
32617
+ `
32618
+ );
31511
32619
  }
31512
- },
31513
- { resolveUnreachable, projectRoot }
31514
- );
32620
+ getDebugLogger().event("analyze", "extract.cache-hit", {
32621
+ files: sourceFiles.length,
32622
+ components: cachedComponents.length
32623
+ });
32624
+ } else {
32625
+ extraction = await extractComponents(
32626
+ sourceFiles,
32627
+ ({ filesProcessed, componentsFound }) => {
32628
+ if (!process.stdout.isTTY) {
32629
+ process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
32630
+ `);
32631
+ }
32632
+ },
32633
+ { resolveUnreachable, projectRoot }
32634
+ );
32635
+ }
32636
+ } finally {
32637
+ extractionCacheDb.close();
32638
+ }
31515
32639
  await mkdir6(outDir, { recursive: true });
31516
32640
  const db = openPipelineDb();
31517
32641
  const { sessionId: sessionId2 } = getOrCreateSession(db, void 0, void 0, {
@@ -31526,6 +32650,25 @@ function registerInternalExtractCommand(program) {
31526
32650
  const stepId = createStep(db, sessionId2, "analyze extract", {
31527
32651
  project: projectRoot
31528
32652
  });
32653
+ if (extractionCacheHits === 0 && !noCache) {
32654
+ const cacheVersion = await getCliCacheVersion();
32655
+ const componentsBySourcePath = /* @__PURE__ */ new Map();
32656
+ for (const component of extraction.components) {
32657
+ if (!component.sourcePath) continue;
32658
+ const components = componentsBySourcePath.get(component.sourcePath) ?? [];
32659
+ components.push(component);
32660
+ componentsBySourcePath.set(component.sourcePath, components);
32661
+ }
32662
+ for (const filePath of sourceFiles) {
32663
+ storeExtractCache(
32664
+ db,
32665
+ filePath,
32666
+ await hashFile(filePath),
32667
+ cacheVersion,
32668
+ componentsBySourcePath.get(filePath) ?? []
32669
+ );
32670
+ }
32671
+ }
31529
32672
  const classifiedComponents = extraction.components.map(preClassifyComponent);
31530
32673
  for (const exclusion of extraction.exclusions ?? []) {
31531
32674
  getDebugLogger().event("filter", "extract.excluded", { ...exclusion });
@@ -31630,6 +32773,7 @@ var init_extract_command = __esm({
31630
32773
  "use strict";
31631
32774
  init_src2();
31632
32775
  init_db();
32776
+ init_cache_keys();
31633
32777
  init_cycle_detection();
31634
32778
  init_resolve_mapping();
31635
32779
  init_manifest_doc_evidence();
@@ -31728,7 +32872,7 @@ __export(program_exports, {
31728
32872
  });
31729
32873
  import { spawn as spawn7 } from "node:child_process";
31730
32874
  import { fileURLToPath as fileURLToPath3 } from "node:url";
31731
- import { dirname as dirname18, join as join28, resolve as resolve19 } from "node:path";
32875
+ import { dirname as dirname18, join as join28, resolve as resolve20 } from "node:path";
31732
32876
  import { readFileSync as readFileSync10 } from "node:fs";
31733
32877
  import { Command } from "commander";
31734
32878
  async function runBuild(opts) {
@@ -31758,7 +32902,7 @@ function resolveBedrockFromAncestors(actionCommand) {
31758
32902
  }
31759
32903
  function registerBuildCommand(program) {
31760
32904
  program.command("build").description("Rebuild from source and re-link exo/experiences binaries to this build").helpOption(false).action(async () => {
31761
- const pkgRoot = resolve19(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
32905
+ const pkgRoot = resolve20(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
31762
32906
  process.stderr.write("\u2699 Building from source...\n");
31763
32907
  const { exitCode } = await runBuild({
31764
32908
  spawnFn: () => spawn7("pnpm", ["build"], { cwd: pkgRoot, stdio: "inherit" }),