@contentful/experience-design-system-cli 2.35.1-dev-build-a91cc5b7.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";
@@ -238,7 +256,6 @@ var init_resolve_skill_file = __esm({
238
256
  components: "generate-components.md",
239
257
  tokens: "generate-tokens.md",
240
258
  select: "select-components.md",
241
- "debate-select": "debate-select.md",
242
259
  "map-tokens": "map-tokens.md"
243
260
  };
244
261
  }
@@ -500,31 +517,6 @@ Rules:
500
517
  - Emit set_group calls before the set_token calls that fall under them.
501
518
  - You may emit prose lines (not starting with {) anywhere \u2014 they are ignored by the parser and serve as your reasoning log.`;
502
519
  }
503
- function buildDebateSelectAutonomousPreamble(inputBlock, debateRole, disagreementInline) {
504
- const role = debateRole ?? "for";
505
- return [
506
- "You are the " + role.toUpperCase() + " participant in an adversarial review of one component-selection disagreement. This is an automatic escalation stage; do not ask the developer for confirmation.",
507
- "",
508
- "Your role is to argue the " + role.toUpperCase() + " position on the exact disagreement below. Address the claim literally, cite the supplied component evidence, and do not substitute a nearby component or field. The opposing participant is evaluating the same evidence independently.",
509
- "",
510
- "Component and source data:" + inputBlock,
511
- "",
512
- "Disagreement under review (JSON):",
513
- "~~~json",
514
- disagreementInline ?? "{}",
515
- "~~~",
516
- "",
517
- "## Output protocol",
518
- "",
519
- "Emit exactly one JSON object on one line and do not write files:",
520
- "",
521
- "~~~json",
522
- '{"role":"' + role + '","disagreement_id":"<id>","argument":"<position and reasoning>","evidence":[{"source":"<path>","line":"<line-or-range>","quote":"<verbatim evidence>"}]}',
523
- "~~~",
524
- "",
525
- "The role and disagreement_id must match the values in this prompt. Use an empty evidence array only when the supplied context contains no citable source evidence."
526
- ].join("\n");
527
- }
528
520
  var init_build_skill_preambles = __esm({
529
521
  "packages/experience-design-system-generation/dist/src/services/prompts/helpers/build-skill-preambles.js"() {
530
522
  "use strict";
@@ -533,7 +525,7 @@ var init_build_skill_preambles = __esm({
533
525
 
534
526
  // packages/experience-design-system-generation/dist/src/services/prompts/helpers/build-preamble.js
535
527
  function buildPreamble(options) {
536
- const { skill, rawComponentsInline, propBucketsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline, componentAllowlistInline, debateRole, disagreementInline } = options;
528
+ const { skill, rawComponentsInline, propBucketsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline, componentAllowlistInline } = options;
537
529
  const sections = [];
538
530
  if (existingComponentsInline) {
539
531
  sections.push(`Existing components in the target Contentful space (JSON) \u2014 use for alignment, not as a hard filter:
@@ -605,8 +597,6 @@ ${sections.join("\n\n")}` : "";
605
597
  return buildComponentsAutonomousPreamble(inputBlock);
606
598
  if (skill === "select")
607
599
  return buildSelectAutonomousPreamble(inputBlock);
608
- if (skill === "debate-select")
609
- return buildDebateSelectAutonomousPreamble(inputBlock, debateRole, disagreementInline);
610
600
  if (skill === "map-tokens")
611
601
  return buildMapTokensAutonomousPreamble(inputBlock);
612
602
  return buildTokensAutonomousPreamble(inputBlock);
@@ -640,6 +630,17 @@ var init_build_prompt = __esm({
640
630
  });
641
631
 
642
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
+ }
643
644
  var init_execute_generation_orchestrator = __esm({
644
645
  "packages/experience-design-system-generation/dist/src/orchestrator/execute-generation-orchestrator.js"() {
645
646
  "use strict";
@@ -740,6 +741,22 @@ var init_describe_agent_failure = __esm({
740
741
  });
741
742
 
742
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
+ }
743
760
  var init_extract_sentinel_output = __esm({
744
761
  "packages/experience-design-system-generation/dist/src/services/agent/helpers/extract-sentinel-output.js"() {
745
762
  "use strict";
@@ -764,7 +781,7 @@ async function runAgent(options) {
764
781
  promptLen: prompt.length,
765
782
  promptHead: prompt.slice(0, 500)
766
783
  });
767
- return new Promise((resolve20) => {
784
+ return new Promise((resolve21) => {
768
785
  const bedrockEnv = bedrock ? BEDROCK_ENV_BY_AGENT[agent] : void 0;
769
786
  const child = spawn(binary, args, {
770
787
  stdio: ["pipe", "pipe", "pipe"],
@@ -813,7 +830,7 @@ async function runAgent(options) {
813
830
  stderrLen: stderr.length,
814
831
  stderrTail: stderr.slice(-1e3)
815
832
  });
816
- resolve20(result);
833
+ resolve21(result);
817
834
  });
818
835
  });
819
836
  }
@@ -830,7 +847,7 @@ var init_run_agent = __esm({
830
847
  // packages/experience-design-system-generation/dist/src/services/agent/helpers/check-claude-auth.js
831
848
  import { spawn as spawn2 } from "node:child_process";
832
849
  function checkClaudeAuth(binary) {
833
- return new Promise((resolve20) => {
850
+ return new Promise((resolve21) => {
834
851
  const child = spawn2(binary, ["auth", "status", "--json"], {
835
852
  stdio: ["ignore", "pipe", "pipe"]
836
853
  });
@@ -840,7 +857,7 @@ function checkClaudeAuth(binary) {
840
857
  if (!done) {
841
858
  done = true;
842
859
  child.kill("SIGTERM");
843
- resolve20("unauthenticated");
860
+ resolve21("unauthenticated");
844
861
  }
845
862
  }, 5e3);
846
863
  child.stdout?.on("data", (chunk) => {
@@ -852,14 +869,14 @@ function checkClaudeAuth(binary) {
852
869
  done = true;
853
870
  clearTimeout(timer);
854
871
  if (code !== 0) {
855
- resolve20("unauthenticated");
872
+ resolve21("unauthenticated");
856
873
  return;
857
874
  }
858
875
  try {
859
876
  const status = JSON.parse(stdout);
860
- resolve20(status.loggedIn ? "ok" : "unauthenticated");
877
+ resolve21(status.loggedIn ? "ok" : "unauthenticated");
861
878
  } catch {
862
- resolve20("unauthenticated");
879
+ resolve21("unauthenticated");
863
880
  }
864
881
  });
865
882
  });
@@ -874,13 +891,13 @@ var init_check_claude_auth = __esm({
874
891
  import { spawn as spawn3 } from "node:child_process";
875
892
  async function checkAgentAuth(agent) {
876
893
  const binary = resolveAgentBinary(agent);
877
- const binaryExists2 = await new Promise((resolve20) => {
894
+ const binaryExists2 = await new Promise((resolve21) => {
878
895
  if (binary.startsWith("/")) {
879
- 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)));
880
897
  return;
881
898
  }
882
899
  const child = spawn3(process.platform === "win32" ? "where" : "which", [binary], { stdio: "ignore" });
883
- child.on("close", (code) => resolve20(code === 0));
900
+ child.on("close", (code) => resolve21(code === 0));
884
901
  });
885
902
  if (!binaryExists2)
886
903
  return "not-found";
@@ -917,6 +934,20 @@ var init_local_cli_agent_invoker = __esm({
917
934
  });
918
935
 
919
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
+ }
920
951
  var init_create_generation_endpoint = __esm({
921
952
  "packages/experience-design-system-generation/dist/src/controller/create-generation-endpoint.js"() {
922
953
  "use strict";
@@ -1301,6 +1332,32 @@ var init_progress = __esm({
1301
1332
  });
1302
1333
 
1303
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
+ });
1304
1361
  var init_src = __esm({
1305
1362
  "packages/experience-design-system-generation/dist/src/index.js"() {
1306
1363
  "use strict";
@@ -10336,6 +10393,41 @@ var init_read3 = __esm({
10336
10393
  }
10337
10394
  });
10338
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
+
10339
10431
  // packages/experience-design-system-cli-legacy/src/session/repositories/tokens/write.ts
10340
10432
  function deleteRawTokensForSession(db, sessionId2) {
10341
10433
  db.prepare("DELETE FROM raw_token_groups WHERE session_id = ?").run(sessionId2);
@@ -10499,17 +10591,47 @@ var init_replace_raw_prop_token_paths = __esm({
10499
10591
  }
10500
10592
  });
10501
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
+
10502
10623
  // packages/experience-design-system-cli-legacy/src/session/db.ts
10503
10624
  import { DatabaseSync } from "node:sqlite";
10504
10625
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
10505
- 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";
10506
10628
  import { homedir as homedir3 } from "node:os";
10507
- import { dirname as dirname14, resolve as resolve8 } from "node:path";
10629
+ import { dirname as dirname14, resolve as resolve9 } from "node:path";
10508
10630
  function getPipelineDbPath() {
10509
10631
  if (process.env.EDS_PIPELINE_DB_PATH) {
10510
- return resolve8(process.env.EDS_PIPELINE_DB_PATH);
10632
+ return resolve9(process.env.EDS_PIPELINE_DB_PATH);
10511
10633
  }
10512
- return resolve8(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10634
+ return resolve9(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10513
10635
  }
10514
10636
  function openPipelineDb(dbPath) {
10515
10637
  const path = dbPath ?? getPipelineDbPath();
@@ -10643,8 +10765,134 @@ function applyDbMigrations(db) {
10643
10765
  if (!rawSlotColsForRationale.some((c2) => c2.name === "rationale")) {
10644
10766
  db.exec("ALTER TABLE raw_slots ADD COLUMN rationale TEXT");
10645
10767
  }
10646
- for (const table of REMOVED_CACHE_TABLES) {
10647
- 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
+ `);
10648
10896
  }
10649
10897
  }
10650
10898
  function loadComponentReviewMetadata(db, sessionId2, componentName) {
@@ -11180,16 +11428,16 @@ function extractRelativeImportPaths(sourceText) {
11180
11428
  return [...specifiers];
11181
11429
  }
11182
11430
  async function resolveRelativeImport(specifier, fromDir) {
11183
- const basePath = resolve8(fromDir, specifier);
11431
+ const basePath = resolve9(fromDir, specifier);
11184
11432
  const candidates = [
11185
11433
  basePath,
11186
11434
  ...rewrittenSourcePaths(basePath),
11187
11435
  ...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
11188
- ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve8(basePath, `index${ext}`))
11436
+ ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve9(basePath, `index${ext}`))
11189
11437
  ];
11190
11438
  for (const candidate of candidates) {
11191
11439
  try {
11192
- await readFile7(candidate, "utf8");
11440
+ await readFile8(candidate, "utf8");
11193
11441
  return candidate;
11194
11442
  } catch {
11195
11443
  }
@@ -11205,7 +11453,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
11205
11453
  return resolvedPaths;
11206
11454
  }
11207
11455
  async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateTypeNames = []) {
11208
- const visited = /* @__PURE__ */ new Set([resolve8(sourcePath)]);
11456
+ const visited = /* @__PURE__ */ new Set([resolve9(sourcePath)]);
11209
11457
  const discovered = [];
11210
11458
  let frontier = [{ text: sourceText, dir: dirname14(sourcePath) }];
11211
11459
  for (let hop = 0; hop < MAX_SIBLING_DEPTH && discovered.length < MAX_SIBLING_CANDIDATES_EXPLORED; hop++) {
@@ -11219,7 +11467,7 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
11219
11467
  if (discovered.length >= MAX_SIBLING_CANDIDATES_EXPLORED) break;
11220
11468
  let content;
11221
11469
  try {
11222
- content = await readFile7(resolvedPath, "utf8");
11470
+ content = await readFile8(resolvedPath, "utf8");
11223
11471
  } catch {
11224
11472
  continue;
11225
11473
  }
@@ -11256,7 +11504,7 @@ async function loadComponentSourceRef(name, sourcePath, propNames = [], propType
11256
11504
  let truncatedSiblingCount = 0;
11257
11505
  const usesNotShown = /* @__PURE__ */ new Set();
11258
11506
  try {
11259
- const rawText = await readFile7(sourcePath, "utf8");
11507
+ const rawText = await readFile8(sourcePath, "utf8");
11260
11508
  let siblingUsesNotShown;
11261
11509
  let declaredTypeNames;
11262
11510
  ({
@@ -11692,6 +11940,108 @@ function backfillUnclassifiedProps(db, sessionId2) {
11692
11940
  ).run(sessionId2, sessionId2);
11693
11941
  return Number(withCategory.changes) + Number(withoutCategory.changes);
11694
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
+ }
11695
12045
  function storeScannedFiles(db, sessionId2, filePaths) {
11696
12046
  db.exec("BEGIN");
11697
12047
  try {
@@ -11774,7 +12124,105 @@ function filterUnknownSlotAllowedComponents(db, sessionId2, allowedComponentName
11774
12124
  }
11775
12125
  return unknown;
11776
12126
  }
11777
- 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) {
11778
12226
  const now = (/* @__PURE__ */ new Date()).toISOString();
11779
12227
  db.exec("BEGIN");
11780
12228
  try {
@@ -11795,7 +12243,139 @@ function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
11795
12243
  throw e;
11796
12244
  }
11797
12245
  }
11798
- 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;
11799
12379
  var init_db = __esm({
11800
12380
  "packages/experience-design-system-cli-legacy/src/session/db.ts"() {
11801
12381
  "use strict";
@@ -11809,6 +12389,8 @@ var init_db = __esm({
11809
12389
  init_write2();
11810
12390
  init_session_resolver();
11811
12391
  init_read3();
12392
+ init_hash_component_shape();
12393
+ init_hash_token_content();
11812
12394
  init_read3();
11813
12395
  init_store_dtcg_tokens();
11814
12396
  init_apply_tool_calls();
@@ -11932,6 +12514,17 @@ CREATE TABLE IF NOT EXISTS raw_tokens (
11932
12514
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
11933
12515
  );
11934
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
+
11935
12528
  CREATE TABLE IF NOT EXISTS scanned_files (
11936
12529
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
11937
12530
  path TEXT NOT NULL,
@@ -11977,9 +12570,10 @@ CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id,
11977
12570
  CREATE INDEX IF NOT EXISTS idx_raw_slots_session ON raw_slots(session_id, component_id);
11978
12571
  CREATE INDEX IF NOT EXISTS idx_raw_tokens_session ON raw_tokens(session_id);
11979
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);
11980
12575
  CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session_id);
11981
12576
  `;
11982
- REMOVED_CACHE_TABLES = ["generation_cache", "extract_cache", "composition_cache", "select_cache"];
11983
12577
  MAX_COMPONENT_SOURCE_CHARS = 8e3;
11984
12578
  MAX_SIBLING_FILES = 5;
11985
12579
  MAX_SIBLING_SNIPPET_CHARS = 1200;
@@ -11996,15 +12590,16 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
11996
12590
  [".cjs", [".cts", ".cjs"]]
11997
12591
  ];
11998
12592
  IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
12593
+ _cliCacheVersionCache = null;
11999
12594
  }
12000
12595
  });
12001
12596
 
12002
12597
  // packages/experience-design-system-cli-legacy/src/helpers/read-existing-contentful-entities-from-session.ts
12003
- import { readFile as readFile8 } from "node:fs/promises";
12598
+ import { readFile as readFile9 } from "node:fs/promises";
12004
12599
  async function readExistingContentfulEntitiesFromSession(path) {
12005
12600
  if (!path) return void 0;
12006
12601
  try {
12007
- const raw = await readFile8(path, "utf8");
12602
+ const raw = await readFile9(path, "utf8");
12008
12603
  const parsed = JSON.parse(raw);
12009
12604
  if (!Array.isArray(parsed.components) || !Array.isArray(parsed.tokens)) return void 0;
12010
12605
  return parsed;
@@ -12128,21 +12723,21 @@ var init_resolve_session_id = __esm({
12128
12723
  });
12129
12724
 
12130
12725
  // packages/experience-design-system-cli-legacy/src/analyze/select/persistence.ts
12131
- 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";
12132
12727
  import { homedir as homedir4 } from "node:os";
12133
- import { resolve as resolve9 } from "node:path";
12728
+ import { resolve as resolve10 } from "node:path";
12134
12729
  function getRefineArtifactsRoot() {
12135
12730
  if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
12136
- return resolve9(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12731
+ return resolve10(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12137
12732
  }
12138
- return resolve9(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12733
+ return resolve10(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12139
12734
  }
12140
12735
  async function getRefineSessionPaths(sessionId2, artifactsRoot) {
12141
- const sessionDir = resolve9(artifactsRoot, sessionId2);
12736
+ const sessionDir = resolve10(artifactsRoot, sessionId2);
12142
12737
  return {
12143
12738
  sessionDir,
12144
- eventsPath: resolve9(sessionDir, "events.jsonl"),
12145
- statePath: resolve9(sessionDir, "current-review-state.json")
12739
+ eventsPath: resolve10(sessionDir, "events.jsonl"),
12740
+ statePath: resolve10(sessionDir, "current-review-state.json")
12146
12741
  };
12147
12742
  }
12148
12743
  async function saveReviewState(statePath, session) {
@@ -12153,7 +12748,7 @@ async function ensureRefineSession(sessionId2, artifactsRoot, initialSnapshot) {
12153
12748
  await mkdir2(paths.sessionDir, { recursive: true });
12154
12749
  try {
12155
12750
  await access(paths.statePath);
12156
- const savedState = await readFile9(paths.statePath, "utf8");
12751
+ const savedState = await readFile10(paths.statePath, "utf8");
12157
12752
  return JSON.parse(savedState);
12158
12753
  } catch {
12159
12754
  await writeFile2(paths.statePath, JSON.stringify(initialSnapshot, null, 2), "utf8");
@@ -12638,8 +13233,8 @@ var init_path_exists = __esm({
12638
13233
  });
12639
13234
 
12640
13235
  // packages/experience-design-system-cli-legacy/src/lib/prompt-overrides.ts
12641
- import { readFile as readFile10 } from "node:fs/promises";
12642
- import { resolve as resolve10 } from "node:path";
13236
+ import { readFile as readFile11 } from "node:fs/promises";
13237
+ import { resolve as resolve11 } from "node:path";
12643
13238
  function looksLikePath(value) {
12644
13239
  if (value.includes("/") || value.includes("\\")) return true;
12645
13240
  if (value.startsWith("~")) return true;
@@ -12671,9 +13266,9 @@ function parsePromptOverrides(inputs) {
12671
13266
  }
12672
13267
  async function resolvePromptOverride(override) {
12673
13268
  if (override.kind === "text") return override.value;
12674
- const abs = resolve10(override.value);
13269
+ const abs = resolve11(override.value);
12675
13270
  try {
12676
- return await readFile10(abs, "utf8");
13271
+ return await readFile11(abs, "utf8");
12677
13272
  } catch {
12678
13273
  throw new Error(`--prompt: could not read prompt file: ${abs}`);
12679
13274
  }
@@ -12688,21 +13283,21 @@ var init_prompt_overrides = __esm({
12688
13283
 
12689
13284
  // packages/experience-design-system-cli-legacy/src/generate/command.ts
12690
13285
  import { createElement } from "react";
12691
- import { readFile as readFile11, readdir, stat } from "node:fs/promises";
12692
- 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";
12693
13288
  async function assertFileExists(flag, p) {
12694
13289
  if (!await pathExists(p)) die(`Error: file not found: ${p} (from ${flag})`);
12695
13290
  }
12696
13291
  async function readFileInline(path) {
12697
13292
  if (!path) return void 0;
12698
- const resolved = resolve11(path);
13293
+ const resolved = resolve12(path);
12699
13294
  let s;
12700
13295
  try {
12701
13296
  s = await stat(resolved);
12702
13297
  } catch {
12703
13298
  return void 0;
12704
13299
  }
12705
- if (!s.isDirectory()) return readFile11(resolved, "utf8");
13300
+ if (!s.isDirectory()) return readFile12(resolved, "utf8");
12706
13301
  const files = [];
12707
13302
  async function walk(dir) {
12708
13303
  let entries;
@@ -12728,7 +13323,7 @@ async function readFileInline(path) {
12728
13323
  }
12729
13324
  await walk(resolved);
12730
13325
  if (files.length === 0) return void 0;
12731
- 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(() => "")));
12732
13327
  return parts.filter(Boolean).join("\n\n");
12733
13328
  }
12734
13329
  function printFallbackInstructions(options) {
@@ -12753,6 +13348,23 @@ function printFallbackInstructions(options) {
12753
13348
  function generatePropBuckets(component) {
12754
13349
  return logPropBuckets("generate", [bucketComponentProps(component)]);
12755
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
+ }
12756
13368
  async function showGenerateView(result) {
12757
13369
  if (process.stdout.isTTY) {
12758
13370
  const { waitUntilExit } = renderWithGoodbye(
@@ -12773,6 +13385,29 @@ session=${result.sessionId}
12773
13385
  );
12774
13386
  await exitWithAnalytics(0);
12775
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
+ }
12776
13411
  async function runOneComponent(options, component, index, total) {
12777
13412
  const {
12778
13413
  agent,
@@ -12782,10 +13417,13 @@ async function runOneComponent(options, component, index, total) {
12782
13417
  tokensInline,
12783
13418
  tokenMapInline,
12784
13419
  verbose,
13420
+ noCache,
12785
13421
  skillPathOverride,
12786
13422
  skillContentOverride,
13423
+ promptHash,
12787
13424
  existingContentfulEntities,
12788
13425
  existingTokensInline,
13426
+ precomputedCachedNames,
12789
13427
  allowedComponentNames: allowedComponentNames2
12790
13428
  } = options;
12791
13429
  const pos = c.dim(`[${index + 1}/${total}]`);
@@ -12810,6 +13448,40 @@ async function runOneComponent(options, component, index, total) {
12810
13448
  allowedComponents: slot.allowedComponents.filter((name) => allowedComponentNames2.has(name))
12811
13449
  };
12812
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
+ }
12813
13485
  const rawComponentsInline = JSON.stringify(
12814
13486
  [
12815
13487
  {
@@ -12882,6 +13554,10 @@ ${outputBuf}`);
12882
13554
  const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings, {
12883
13555
  allowedComponentNames: allowedComponentNames2
12884
13556
  });
13557
+ if (!noCache) {
13558
+ const inputHash = hashComponentShape(cacheComponent);
13559
+ storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
13560
+ }
12885
13561
  return {
12886
13562
  componentName: component.name,
12887
13563
  classified: applied.classified,
@@ -12933,7 +13609,7 @@ async function loadAcceptedNames(sessionId2) {
12933
13609
  try {
12934
13610
  const artifactsRoot = getRefineArtifactsRoot();
12935
13611
  const paths = await getRefineSessionPaths(sessionId2, artifactsRoot);
12936
- const raw = await readFile11(paths.statePath, "utf8");
13612
+ const raw = await readFile12(paths.statePath, "utf8");
12937
13613
  const snapshot = JSON.parse(raw);
12938
13614
  const accepted = snapshot.components.filter((c2) => c2.status === "accepted").map((c2) => c2.name);
12939
13615
  if (accepted.length === 0) return null;
@@ -12942,6 +13618,15 @@ async function loadAcceptedNames(sessionId2) {
12942
13618
  return null;
12943
13619
  }
12944
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
+ }
12945
13630
  async function runGenerateSkill(skill, opts, verbose = false) {
12946
13631
  const savedCreds = await readExperiencesCredentials();
12947
13632
  const agentName = opts.agent ?? savedCreds.agent;
@@ -12970,8 +13655,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12970
13655
  }
12971
13656
  const generatePromptPath = generatePrompt === void 0 ? configuredGeneratePromptPath : void 0;
12972
13657
  if (generatePromptPath) {
12973
- if (!await pathExists(resolve11(generatePromptPath))) {
12974
- 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)}`);
12975
13660
  }
12976
13661
  if (!generatePromptPath.toLowerCase().endsWith(".md")) {
12977
13662
  process.stderr.write(
@@ -12979,7 +13664,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12979
13664
  `
12980
13665
  );
12981
13666
  }
12982
- process.stderr.write(formatCustomPromptBanner("components", resolve11(generatePromptPath)));
13667
+ process.stderr.write(formatCustomPromptBanner("components", resolve12(generatePromptPath)));
12983
13668
  }
12984
13669
  if (skill === "tokens" && !opts.rawTokens) {
12985
13670
  die("Error: --raw-tokens is required when using generate tokens");
@@ -12995,7 +13680,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12995
13680
  let existingContentfulEntities;
12996
13681
  let existingTokensInline;
12997
13682
  if (opts.existingEntitiesPath) {
12998
- existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve11(opts.existingEntitiesPath));
13683
+ existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve12(opts.existingEntitiesPath));
12999
13684
  if (existingContentfulEntities) {
13000
13685
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
13001
13686
  } else {
@@ -13094,7 +13779,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13094
13779
  rawComponentsInline: sampleInline ?? rawTokensInline,
13095
13780
  propBucketsInline: sampleComponent ? generatePropBuckets(sampleComponent) : void 0,
13096
13781
  rawTokensInline: skill === "tokens" ? rawTokensInline : void 0,
13097
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13782
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13098
13783
  tokensInline,
13099
13784
  tokenMapInline,
13100
13785
  outDir: process.cwd(),
@@ -13120,7 +13805,64 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13120
13805
  if (skill === "components" && allComponents && sessionId2) {
13121
13806
  const db = openPipelineDb();
13122
13807
  let componentResults;
13808
+ const precomputedCachedNames = parsePrecomputedCachedNames(opts.cachedComponents);
13123
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
+ }
13124
13866
  componentResults = await runAllComponents(
13125
13867
  {
13126
13868
  agent,
@@ -13130,10 +13872,13 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13130
13872
  tokensInline,
13131
13873
  tokenMapInline,
13132
13874
  verbose,
13875
+ noCache: opts.cache === false || process.env.EDS_NO_CACHE === "1",
13133
13876
  skillPathOverride: generatePromptPath,
13134
13877
  skillContentOverride: generatePrompt,
13878
+ promptHash,
13135
13879
  existingContentfulEntities,
13136
13880
  existingTokensInline,
13881
+ precomputedCachedNames,
13137
13882
  allowedComponentNames: allowedComponentNames2
13138
13883
  },
13139
13884
  allComponents
@@ -13142,7 +13887,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13142
13887
  db.close();
13143
13888
  }
13144
13889
  const failed = componentResults.filter((r) => r.failed);
13145
- 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);
13146
13892
  const allWarnings = componentResults.flatMap((r) => r.warnings.map((w) => ` ${r.componentName}: ${w}`));
13147
13893
  if (allWarnings.length > 0) {
13148
13894
  process.stderr.write(c.yellow("Warnings:") + "\n" + allWarnings.join("\n") + "\n");
@@ -13158,17 +13904,21 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13158
13904
  const totalExcluded = generated.reduce((s, r) => s + r.excluded, 0);
13159
13905
  const totalRenamedSlots = componentResults.reduce((s, r) => s + r.renamedSlotsCount, 0);
13160
13906
  const allOk = failed.length === 0;
13907
+ const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : "";
13161
13908
  process.stderr.write(
13162
- (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"
13163
13910
  );
13164
13911
  process.stdout.write(`renamed-slots: ${totalRenamedSlots}
13165
13912
  `);
13166
- if (generated.length === 0) {
13913
+ if (generated.length === 0 && cachedResults.length === 0) {
13167
13914
  die(
13168
13915
  `Error: all ${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
13169
13916
  );
13170
13917
  }
13171
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);
13172
13922
  const db = openPipelineDb();
13173
13923
  try {
13174
13924
  let resolvedSessionId = opts.session;
@@ -13195,11 +13945,26 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13195
13945
  }
13196
13946
  sessionId2 = resolvedSessionId;
13197
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
+ }
13198
13963
  const prompt = await buildPrompt({
13199
13964
  skill,
13200
13965
  mode: "autonomous",
13201
13966
  rawTokensInline,
13202
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13967
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13203
13968
  tokensInline,
13204
13969
  tokenMapInline,
13205
13970
  outDir: process.cwd()
@@ -13236,6 +14001,9 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13236
14001
  `);
13237
14002
  }
13238
14003
  applyTokenToolCalls(db, resolvedSessionId, tokenCalls, []);
14004
+ if (!noCache) {
14005
+ storeCache(db, tokenInputHash, "token_set", "__tokens__", resolvedSessionId, false, tokenPromptHash);
14006
+ }
13239
14007
  sessionId2 = resolvedSessionId;
13240
14008
  const groupCount = tokenCalls.filter((tc) => tc.tool === "set_group").length;
13241
14009
  process.stderr.write(`Done: ${tokenCount} tokens, ${groupCount} groups stored
@@ -13247,7 +14015,10 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13247
14015
  await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13248
14016
  }
13249
14017
  function addAgentFlags(cmd) {
13250
- 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
+ );
13251
14022
  }
13252
14023
  function registerInternalGenerateCommand(program) {
13253
14024
  const generate = program.command("__generate", { hidden: true }).description("Internal import pipeline generation command");
@@ -13263,6 +14034,12 @@ function registerInternalGenerateCommand(program) {
13263
14034
  "--existing-entities-path <path>",
13264
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."
13265
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
+ );
13266
14043
  addAgentFlags(componentsCmd).action(async (opts) => {
13267
14044
  await runGenerateSkill("components", opts, opts.verbose ?? false);
13268
14045
  });
@@ -13290,6 +14067,7 @@ var init_command = __esm({
13290
14067
  init_agent_output();
13291
14068
  init_GenerateView();
13292
14069
  init_db();
14070
+ init_cache_keys();
13293
14071
  init_read_existing_contentful_entities_from_session();
13294
14072
  init_summarize_existing_contentful_entities();
13295
14073
  init_resolve_session_id();
@@ -14005,7 +14783,7 @@ var init_user_agent = __esm({
14005
14783
  // packages/experience-design-system-cli-legacy/src/apply/api-client.ts
14006
14784
  import { parseCDFComponents } from "@contentful/experience-design-system-types";
14007
14785
  function defaultSleep(delayMs) {
14008
- return new Promise((resolve20) => setTimeout(resolve20, delayMs));
14786
+ return new Promise((resolve21) => setTimeout(resolve21, delayMs));
14009
14787
  }
14010
14788
  function isTransientStatus(status) {
14011
14789
  return status >= 500 && status <= 599;
@@ -14395,7 +15173,7 @@ ${trimmed}`;
14395
15173
  const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
14396
15174
  const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
14397
15175
  const jitter = Math.random() * baseDelay * 0.15;
14398
- await new Promise((resolve20) => setTimeout(resolve20, baseDelay + jitter));
15176
+ await new Promise((resolve21) => setTimeout(resolve21, baseDelay + jitter));
14399
15177
  }
14400
15178
  }
14401
15179
  throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
@@ -15205,7 +15983,7 @@ var init_terminal_capabilities = __esm({
15205
15983
 
15206
15984
  // packages/experience-design-system-cli-legacy/src/apply/command.ts
15207
15985
  import { createElement as createElement2 } from "react";
15208
- 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";
15209
15987
  import { join as join16 } from "node:path";
15210
15988
  import {
15211
15989
  validateCDF,
@@ -15226,7 +16004,7 @@ function dieWithApiError(error) {
15226
16004
  async function readJsonFile(flag, p) {
15227
16005
  let text;
15228
16006
  try {
15229
- text = await readFile12(p, "utf8");
16007
+ text = await readFile13(p, "utf8");
15230
16008
  } catch {
15231
16009
  return await die2(`Error: file not found: ${p} (from ${flag})`);
15232
16010
  }
@@ -15280,7 +16058,7 @@ async function readTokensFromPath(flag, p) {
15280
16058
  for (const file of files.sort()) {
15281
16059
  let text;
15282
16060
  try {
15283
- text = await readFile12(file, "utf8");
16061
+ text = await readFile13(file, "utf8");
15284
16062
  } catch {
15285
16063
  continue;
15286
16064
  }
@@ -15649,11 +16427,11 @@ var init_command2 = __esm({
15649
16427
  });
15650
16428
 
15651
16429
  // packages/experience-design-system-cli-legacy/src/print/validate/validators/read-json-file.ts
15652
- import { readFile as readFile13 } from "node:fs/promises";
16430
+ import { readFile as readFile14 } from "node:fs/promises";
15653
16431
  async function readJsonFile2(filePath) {
15654
16432
  let content;
15655
16433
  try {
15656
- content = await readFile13(filePath, "utf-8");
16434
+ content = await readFile14(filePath, "utf-8");
15657
16435
  } catch (err) {
15658
16436
  return {
15659
16437
  ok: false,
@@ -16064,7 +16842,7 @@ var init_ValidateView = __esm({
16064
16842
  // packages/experience-design-system-cli-legacy/src/print/command.ts
16065
16843
  import { createElement as createElement3 } from "react";
16066
16844
  import { access as access3, mkdir as mkdir3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
16067
- import { basename as basename5, resolve as resolve12 } from "node:path";
16845
+ import { basename as basename5, resolve as resolve13 } from "node:path";
16068
16846
  import { CDF_SCHEMA_URL } from "@contentful/experience-design-system-types";
16069
16847
  async function die3(message) {
16070
16848
  process.stderr.write(`${message}
@@ -16131,7 +16909,7 @@ function registerPrintCommand(program) {
16131
16909
  "--allow-empty",
16132
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."
16133
16911
  ).action(async (opts) => {
16134
- const outPath = resolve12(opts.out);
16912
+ const outPath = resolve13(opts.out);
16135
16913
  await assertOutIsNotDirectory(outPath);
16136
16914
  const sessionId2 = await resolveSession(opts.session, "generate components");
16137
16915
  await bindAnalyticsSessionId(sessionId2);
@@ -16179,7 +16957,7 @@ function registerPrintCommand(program) {
16179
16957
  `
16180
16958
  );
16181
16959
  }
16182
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16960
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16183
16961
  await writeFile3(outPath, `${JSON.stringify(cdfObj, null, 2)}
16184
16962
  `);
16185
16963
  process.stdout.write(
@@ -16188,7 +16966,7 @@ function registerPrintCommand(program) {
16188
16966
  );
16189
16967
  });
16190
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) => {
16191
- const outPath = resolve12(opts.out);
16969
+ const outPath = resolve13(opts.out);
16192
16970
  await assertOutIsNotDirectory(outPath);
16193
16971
  const sessionId2 = await resolveSession(opts.session, "generate tokens");
16194
16972
  await bindAnalyticsSessionId(sessionId2);
@@ -16203,7 +16981,7 @@ function registerPrintCommand(program) {
16203
16981
  await die3(`Error: no generated tokens in session '${sessionId2}'. Run generate tokens first.`);
16204
16982
  }
16205
16983
  const tree = rebuildDTCGTree(result.groups, result.tokens);
16206
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16984
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16207
16985
  await writeFile3(outPath, `${JSON.stringify(tree, null, 2)}
16208
16986
  `);
16209
16987
  process.stdout.write(
@@ -16352,7 +17130,7 @@ function MapTokensView({ result, onExit }) {
16352
17130
  const timer = setTimeout(onExit, 100);
16353
17131
  return () => clearTimeout(timer);
16354
17132
  }, [onExit]);
16355
- 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`;
16356
17134
  return /* @__PURE__ */ jsx8(
16357
17135
  CommandCompletionView,
16358
17136
  {
@@ -16486,8 +17264,8 @@ var init_resolve_defaults = __esm({
16486
17264
 
16487
17265
  // packages/experience-design-system-cli-legacy/src/map-tokens/command.ts
16488
17266
  import { createElement as createElement4 } from "react";
16489
- import { readFile as readFile14 } from "node:fs/promises";
16490
- import { resolve as resolve13 } from "node:path";
17267
+ import { readFile as readFile15 } from "node:fs/promises";
17268
+ import { resolve as resolve14 } from "node:path";
16491
17269
  async function renderResult(result) {
16492
17270
  if (process.stdout.isTTY) {
16493
17271
  const { waitUntilExit } = renderWithGoodbye(
@@ -16495,13 +17273,12 @@ async function renderResult(result) {
16495
17273
  );
16496
17274
  await waitUntilExit();
16497
17275
  } else {
16498
- process.stdout.write(
16499
- `map tokens complete
17276
+ const summary = result.cached ? "cached" : `${result.applied} mapping(s) applied`;
17277
+ process.stdout.write(`map tokens complete
16500
17278
  agent: ${result.agent}
16501
17279
  session=${result.sessionId}
16502
- ${result.applied} mapping(s) applied
16503
- `
16504
- );
17280
+ ${summary}
17281
+ `);
16505
17282
  await exitWithAnalytics(0);
16506
17283
  }
16507
17284
  }
@@ -16523,7 +17300,7 @@ async function runMapTokens(opts) {
16523
17300
  let mapPromptPath;
16524
17301
  if (mapPromptOverride?.kind === "text") mapPromptText = mapPromptOverride.value;
16525
17302
  if (mapPromptOverride?.kind === "path") {
16526
- mapPromptPath = resolve13(mapPromptOverride.value);
17303
+ mapPromptPath = resolve14(mapPromptOverride.value);
16527
17304
  try {
16528
17305
  await resolvePromptOverride(mapPromptOverride);
16529
17306
  } catch (error) {
@@ -16543,7 +17320,7 @@ async function runMapTokens(opts) {
16543
17320
  const knownTokenPaths = new Set(tokenLeaves.map((t) => t.path));
16544
17321
  const tokenMapDiagnostics = [];
16545
17322
  if (opts.tokenMap) {
16546
- const fileMappings = JSON.parse(await readFile14(opts.tokenMap, "utf8"));
17323
+ const fileMappings = JSON.parse(await readFile15(opts.tokenMap, "utf8"));
16547
17324
  const validMappings = {};
16548
17325
  for (const [rawName, path] of Object.entries(fileMappings)) {
16549
17326
  if (knownTokenPaths.has(path)) {
@@ -16621,7 +17398,7 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16621
17398
  let existingTokensInline;
16622
17399
  if (opts.existingEntitiesPath) {
16623
17400
  const existingContentfulEntities = await readExistingContentfulEntitiesFromSession(
16624
- resolve13(opts.existingEntitiesPath)
17401
+ resolve14(opts.existingEntitiesPath)
16625
17402
  );
16626
17403
  if (existingContentfulEntities) {
16627
17404
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
@@ -16656,10 +17433,30 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16656
17433
  skipAgent: "true"
16657
17434
  });
16658
17435
  updateStep(db, stepId2, "complete", { applied: "0", skipAgent: "true" });
16659
- await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0 });
17436
+ await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0, cached: false });
16660
17437
  return;
16661
17438
  }
16662
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
+ }
16663
17460
  const binary = resolveAgentBinary(agent);
16664
17461
  if (!await assertBinaryInPath(binary)) {
16665
17462
  die(
@@ -16679,20 +17476,23 @@ Install it, choose another agent with --agent, or use --print-prompt to run the
16679
17476
  }
16680
17477
  const { calls, warnings: parseWarnings } = parseMapTokenPropToolCallLines(result.stdout);
16681
17478
  const { applied, warnings } = applyMapTokenPropCalls(db, sessionId2, calls, parseWarnings);
17479
+ if (!noCache) {
17480
+ storeCache(db, inputHash, "token_mapping", "__map_tokens__", sessionId2, false, promptHash);
17481
+ }
16682
17482
  updateStep(db, stepId, "complete", { applied: String(applied), warnings: String(warnings.length) });
16683
17483
  if (warnings.length > 0) {
16684
17484
  process.stderr.write(`Warnings:
16685
17485
  ${warnings.map((w) => ` ${w}`).join("\n")}
16686
17486
  `);
16687
17487
  }
16688
- await renderResult({ agent, sessionId: sessionId2, applied });
17488
+ await renderResult({ agent, sessionId: sessionId2, applied, cached: false });
16689
17489
  } finally {
16690
17490
  db.close();
16691
17491
  }
16692
17492
  }
16693
17493
  function registerMapTokensCommand(program) {
16694
17494
  const map = program.command("map", { hidden: true }).description("Suggest token restrictions for generated design-token props");
16695
- 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(
16696
17496
  "--prompt <stage=value>",
16697
17497
  "Override a stage prompt (repeatable). Used here for the map-tokens stage.",
16698
17498
  (v, acc) => [...acc, v],
@@ -16712,6 +17512,7 @@ var init_command4 = __esm({
16712
17512
  init_render_with_goodbye();
16713
17513
  init_src();
16714
17514
  init_db();
17515
+ init_cache_keys();
16715
17516
  init_read_existing_contentful_entities_from_session();
16716
17517
  init_summarize_existing_contentful_entities();
16717
17518
  init_command3();
@@ -16728,7 +17529,7 @@ var init_command4 = __esm({
16728
17529
  });
16729
17530
 
16730
17531
  // packages/experience-design-system-cli-legacy/src/import/path-utils.ts
16731
- import { resolve as resolve14 } from "node:path";
17532
+ import { resolve as resolve15 } from "node:path";
16732
17533
  import { homedir as homedir5 } from "node:os";
16733
17534
  function expandTilde(input) {
16734
17535
  if (input === "~" || input.startsWith("~/") || input.startsWith("~\\")) {
@@ -16742,7 +17543,7 @@ function normalizePath(input) {
16742
17543
  p = p.slice(1, -1);
16743
17544
  }
16744
17545
  p = expandTilde(p);
16745
- return resolve14(p);
17546
+ return resolve15(p);
16746
17547
  }
16747
17548
  var init_path_utils = __esm({
16748
17549
  "packages/experience-design-system-cli-legacy/src/import/path-utils.ts"() {
@@ -16967,7 +17768,7 @@ var init_save_path_resolver = __esm({
16967
17768
  });
16968
17769
 
16969
17770
  // packages/experience-design-system-cli-legacy/src/runs/store.ts
16970
- 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";
16971
17772
  import { join as join20 } from "node:path";
16972
17773
  import { homedir as homedir6 } from "node:os";
16973
17774
  import { randomBytes as randomBytes2 } from "node:crypto";
@@ -17007,7 +17808,7 @@ function migrateRecord(rec) {
17007
17808
  }
17008
17809
  async function readFileMaybe() {
17009
17810
  try {
17010
- const raw = await readFile15(RUNS_PATH, "utf8");
17811
+ const raw = await readFile16(RUNS_PATH, "utf8");
17011
17812
  const parsed = JSON.parse(raw);
17012
17813
  if (!READABLE_VERSIONS.has(parsed.version)) {
17013
17814
  throw new Error(
@@ -17071,15 +17872,15 @@ var init_store = __esm({
17071
17872
  });
17072
17873
 
17073
17874
  // packages/experience-design-system-cli-legacy/src/runs/fingerprint.ts
17074
- import { createHash as createHash2 } from "node:crypto";
17075
- import { readFile as readFile16, stat as stat4 } from "node:fs/promises";
17076
- 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";
17077
17878
  function rowField(row, key) {
17078
17879
  const v = row[key];
17079
17880
  return typeof v === "string" ? v : null;
17080
17881
  }
17081
17882
  function sha256Hex(buf) {
17082
- return createHash2("sha256").update(buf).digest("hex");
17883
+ return createHash6("sha256").update(buf).digest("hex");
17083
17884
  }
17084
17885
  async function buildSourceFingerprint(opts) {
17085
17886
  const rows = opts.db.prepare("SELECT name, source_path FROM raw_components WHERE session_id = ?").all(opts.extractSessionId);
@@ -17089,7 +17890,7 @@ async function buildSourceFingerprint(opts) {
17089
17890
  const sourcePath = rowField(row, "source_path");
17090
17891
  const name = rowField(row, "name");
17091
17892
  if (!sourcePath) continue;
17092
- const abs = resolve15(sourcePath);
17893
+ const abs = resolve16(sourcePath);
17093
17894
  if (seen.has(abs)) continue;
17094
17895
  seen.add(abs);
17095
17896
  try {
@@ -17104,10 +17905,10 @@ async function buildSourceFingerprint(opts) {
17104
17905
  let rawTokensMtime = null;
17105
17906
  let rawTokensContentHash = null;
17106
17907
  if (opts.rawTokensPath) {
17107
- const abs = resolve15(opts.rawTokensPath);
17908
+ const abs = resolve16(opts.rawTokensPath);
17108
17909
  rawTokensPath = abs;
17109
17910
  try {
17110
- const [st, buf] = await Promise.all([stat4(abs), readFile16(abs)]);
17911
+ const [st, buf] = await Promise.all([stat4(abs), readFile17(abs)]);
17111
17912
  rawTokensMtime = st.mtime.toISOString();
17112
17913
  rawTokensContentHash = sha256Hex(buf);
17113
17914
  } catch {
@@ -18862,11 +19663,11 @@ var init_slot_graph = __esm({
18862
19663
  });
18863
19664
 
18864
19665
  // packages/experience-design-system-cli-legacy/src/analyze/select/parser.ts
18865
- import { createHash as createHash3 } from "node:crypto";
19666
+ import { createHash as createHash7 } from "node:crypto";
18866
19667
  import { access as access5 } from "node:fs/promises";
18867
- import { isAbsolute, relative, resolve as resolve16 } from "node:path";
19668
+ import { isAbsolute, relative, resolve as resolve17 } from "node:path";
18868
19669
  function createComponentId(name, resolvedSourcePath) {
18869
- 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);
18870
19671
  return `${name}-${sourceHash}`;
18871
19672
  }
18872
19673
  async function resolveComponentSourcePath(source, reviewRoot) {
@@ -18878,7 +19679,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18878
19679
  throw new Error(`Unable to access component source at ${source}`);
18879
19680
  }
18880
19681
  }
18881
- const candidate = resolve16(reviewRoot, source);
19682
+ const candidate = resolve17(reviewRoot, source);
18882
19683
  const relativeToRoot = relative(reviewRoot, candidate);
18883
19684
  if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || isAbsolute(relativeToRoot)) {
18884
19685
  throw new Error(
@@ -18893,7 +19694,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18893
19694
  }
18894
19695
  }
18895
19696
  async function loadReviewInput(components, options = {}) {
18896
- const reviewRoot = resolve16(options.reviewRoot ?? process.cwd());
19697
+ const reviewRoot = resolve17(options.reviewRoot ?? process.cwd());
18897
19698
  const records = await Promise.all(
18898
19699
  components.map(async (component) => {
18899
19700
  let resolvedSourcePath;
@@ -18924,7 +19725,7 @@ var init_parser = __esm({
18924
19725
  });
18925
19726
 
18926
19727
  // packages/experience-design-system-cli-legacy/src/analyze/select/command.ts
18927
- import { access as access6, readFile as readFile17 } from "node:fs/promises";
19728
+ import { access as access6, readFile as readFile18 } from "node:fs/promises";
18928
19729
  async function loadAndValidateForReview(sessionId2, projectRoot) {
18929
19730
  const db = openPipelineDb();
18930
19731
  let rawComponents;
@@ -18942,7 +19743,7 @@ async function patchReviewStateWithValidationErrors(sessionId2, errors, opts = {
18942
19743
  let snapshot;
18943
19744
  try {
18944
19745
  await access6(paths.statePath);
18945
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19746
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18946
19747
  } catch {
18947
19748
  snapshot = await loadAndValidateForReview(sessionId2, void 0);
18948
19749
  snapshot = await ensureRefineSession(sessionId2, artifactsRoot, snapshot);
@@ -18993,7 +19794,7 @@ async function rejectComponentsByName(sessionId2, names, opts = {}) {
18993
19794
  const nameSet = new Set(names);
18994
19795
  let snapshot;
18995
19796
  try {
18996
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19797
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18997
19798
  } catch {
18998
19799
  return;
18999
19800
  }
@@ -19092,9 +19893,9 @@ function spawnGenerateChild(opts) {
19092
19893
  stderr += chunk;
19093
19894
  opts.onStderr?.(chunk);
19094
19895
  });
19095
- const donePromise = new Promise((resolve20) => {
19896
+ const donePromise = new Promise((resolve21) => {
19096
19897
  child.on("close", (code, signal) => {
19097
- resolve20({
19898
+ resolve21({
19098
19899
  exitCode: code ?? 0,
19099
19900
  signal: signal ?? null,
19100
19901
  stdout,
@@ -19102,7 +19903,7 @@ function spawnGenerateChild(opts) {
19102
19903
  });
19103
19904
  });
19104
19905
  child.on("error", (err) => {
19105
- resolve20({
19906
+ resolve21({
19106
19907
  exitCode: 1,
19107
19908
  signal: null,
19108
19909
  stdout,
@@ -27240,6 +28041,9 @@ function shouldGenerateAfterCredentialsValidated(opts) {
27240
28041
  function shouldSkipFinalReviewAfterCredentials(state) {
27241
28042
  return state.finalReviewPassed && state.generateSessionId != null;
27242
28043
  }
28044
+ function resolveNoCacheForGenerate(opts) {
28045
+ return opts.cliNoCache;
28046
+ }
27243
28047
  function shouldBypassPreview(state) {
27244
28048
  return state.credentialsSkipped === true;
27245
28049
  }
@@ -27266,39 +28070,66 @@ var init_wizard_state_transitions = __esm({
27266
28070
  async function runSelectionAgent(options) {
27267
28071
  const db = openPipelineDb();
27268
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
+ );
27269
28083
  try {
27270
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
+ }
27271
28097
  } finally {
27272
28098
  db.close();
27273
28099
  }
27274
28100
  if (components.length === 0) return;
27275
- const invoker2 = createLocalCliAgentInvoker({
27276
- onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
27277
- });
27278
- const prompt = await buildPrompt({
27279
- skill: "select",
27280
- mode: "autonomous",
27281
- rawComponentsInline: JSON.stringify(
27282
- components.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
27283
- null,
27284
- 2
27285
- ),
27286
- propBucketsInline: logPropBuckets("select", bucketComponentsProps(components)),
27287
- outDir: process.cwd(),
27288
- ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
27289
- ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
27290
- });
27291
- const { result } = await invokeAgentWithOutput(
27292
- invoker2,
27293
- { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
27294
- false
27295
- );
27296
- if (result.timedOut) throw new Error("selection agent timed out");
27297
- if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
27298
- const parsed = parseSelectToolCallLines(result.stdout);
27299
- 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}
27300
28129
  `);
27301
- 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]));
27302
28133
  const writeDb = openPipelineDb();
27303
28134
  try {
27304
28135
  writeDb.exec("BEGIN");
@@ -27306,15 +28137,27 @@ async function runSelectionAgent(options) {
27306
28137
  `UPDATE raw_components SET status = ?, reject_reason = ? WHERE session_id = ? AND component_id = ?`
27307
28138
  );
27308
28139
  for (const component of components) {
28140
+ const cached = cachedDecisions.get(component.name);
27309
28141
  const call = byName.get(component.name);
27310
- if (!call) continue;
27311
- 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;
27312
28145
  update.run(
27313
28146
  rejected ? "rejected" : "accepted",
27314
- rejected ? call.reason ?? null : null,
28147
+ rejected ? reason : null,
27315
28148
  options.sessionId,
27316
28149
  component.component_id
27317
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
+ }
27318
28161
  }
27319
28162
  writeDb.exec("COMMIT");
27320
28163
  } catch (error) {
@@ -27324,7 +28167,7 @@ async function runSelectionAgent(options) {
27324
28167
  writeDb.close();
27325
28168
  }
27326
28169
  }
27327
- var DEFAULT_TIMEOUT_MS4;
28170
+ var SELECT_PAYLOAD_FORMAT, DEFAULT_TIMEOUT_MS4;
27328
28171
  var init_run_selection_agent = __esm({
27329
28172
  "packages/experience-design-system-cli-legacy/src/import/tui/run-selection-agent.ts"() {
27330
28173
  "use strict";
@@ -27335,6 +28178,8 @@ var init_run_selection_agent = __esm({
27335
28178
  init_debug_logger();
27336
28179
  init_agent_output();
27337
28180
  init_db();
28181
+ init_cache_keys();
28182
+ SELECT_PAYLOAD_FORMAT = "prop-buckets-v1";
27338
28183
  DEFAULT_TIMEOUT_MS4 = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 5 * 60 * 1e3);
27339
28184
  }
27340
28185
  });
@@ -27353,7 +28198,7 @@ __export(WizardApp_exports, {
27353
28198
  });
27354
28199
  import { useEffect as useEffect15, useRef as useRef10, useState as useState23 } from "react";
27355
28200
  import { Box as Box42, Text as Text46 } from "ink";
27356
- import { join as join23, resolve as resolve17 } from "node:path";
28201
+ import { join as join23, resolve as resolve18 } from "node:path";
27357
28202
  import { appendFileSync as appendFileSync2, writeFileSync } from "node:fs";
27358
28203
  import { access as access7, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
27359
28204
  import { tmpdir } from "node:os";
@@ -27366,21 +28211,28 @@ function buildGenerateComponentsArgs(opts) {
27366
28211
  if (opts.tokensPath) args.push("--tokens", opts.tokensPath);
27367
28212
  if (opts.model) args.push("--model", opts.model);
27368
28213
  if (opts.bedrock) args.push("--bedrock");
28214
+ if (opts.noCache) args.push("--no-cache");
27369
28215
  if (opts.generatePromptPath) args.push("--generate-prompt-path", opts.generatePromptPath);
27370
28216
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27371
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
+ }
27372
28222
  return args;
27373
28223
  }
27374
28224
  function buildGenerateTokensArgs(opts) {
27375
28225
  const args = [findCliPath(), "__generate", "tokens", "--agent", opts.agent, "--raw-tokens", opts.rawTokensPath];
27376
28226
  if (opts.model) args.push("--model", opts.model);
27377
28227
  if (opts.bedrock) args.push("--bedrock");
28228
+ if (opts.noCache) args.push("--no-cache");
27378
28229
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27379
28230
  return args;
27380
28231
  }
27381
28232
  function buildMapTokensArgs(opts) {
27382
28233
  const args = ["map", "tokens", "--session", opts.sessionId, "--agent", opts.agent];
27383
28234
  if (opts.model) args.push("--model", opts.model);
28235
+ if (opts.noCache) args.push("--no-cache");
27384
28236
  if (opts.skipAgent) args.push("--skip-agent");
27385
28237
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27386
28238
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
@@ -27472,6 +28324,7 @@ function WizardApp({
27472
28324
  initialProjectPath,
27473
28325
  host,
27474
28326
  promptOverrides,
28327
+ noCache = false,
27475
28328
  livePreview = true,
27476
28329
  generatePromptPath,
27477
28330
  skipMapTokens = false,
@@ -27496,6 +28349,7 @@ function WizardApp({
27496
28349
  });
27497
28350
  const allowEmptyDeleteAllRef = useRef10(false);
27498
28351
  const generateChildRef = useRef10(null);
28352
+ const generationCachePreflightRef = useRef10(null);
27499
28353
  const selectionToReviewStartedAtRef = useRef10(null);
27500
28354
  const credentialsValidationIdRef = useRef10(0);
27501
28355
  const pendingCredentialValidationRef = useRef10(null);
@@ -27510,8 +28364,9 @@ function WizardApp({
27510
28364
  const existingEntitiesPromiseRef = useRef10(null);
27511
28365
  const generatePromiseRef = useRef10(null);
27512
28366
  const rawTokensEntryReady = !!initialRawTokensPath;
28367
+ const effectiveNoCache = resolveNoCacheForGenerate({ cliNoCache: noCache });
27513
28368
  const initialStepResolved = rawTokensEntryReady ? initialProjectPath ? "path-validation" : "credentials" : initialProjectPath ? "token-input" : "welcome";
27514
- const initialOutDir = initialProjectPath ? join23(resolve17(initialProjectPath), ".contentful") : "";
28369
+ const initialOutDir = initialProjectPath ? join23(resolve18(initialProjectPath), ".contentful") : "";
27515
28370
  const [state, setState] = useState23({
27516
28371
  step: initialStepResolved,
27517
28372
  agent: initialAgent ?? "claude",
@@ -27687,13 +28542,14 @@ If you are using AWS Bedrock, run:
27687
28542
  if (!deferStep) update({ step: nextStep });
27688
28543
  return true;
27689
28544
  };
27690
- const runGenerateTokens = async (rawTokensPath, outDir) => {
28545
+ const runGenerateTokens = async (rawTokensPath, outDir, forceRegenerate = false) => {
27691
28546
  update({ tokenGenerationStatus: "running" });
27692
28547
  const tokenArgs = buildGenerateTokensArgs({
27693
28548
  rawTokensPath,
27694
28549
  agent: state.agent,
27695
28550
  ...state.agentModel ? { model: state.agentModel } : {},
27696
28551
  ...state.bedrock ? { bedrock: true } : {},
28552
+ noCache: effectiveNoCache || forceRegenerate,
27697
28553
  promptOverrides
27698
28554
  });
27699
28555
  const result = await runSpawnedCli(tokenArgs);
@@ -27742,6 +28598,7 @@ If you are using AWS Bedrock, run:
27742
28598
  const timer = startWizardTimer("token-mapping", {
27743
28599
  sessionId: sessionId2,
27744
28600
  silent,
28601
+ noCache: effectiveNoCache,
27745
28602
  skipAgent: skipMapTokens
27746
28603
  });
27747
28604
  let mappablePropCount = 0;
@@ -27750,7 +28607,7 @@ If you are using AWS Bedrock, run:
27750
28607
  const db = openPipelineDb();
27751
28608
  try {
27752
28609
  if (state.tokenSessionId && state.tokenSessionId !== sessionId2) {
27753
- copyTokensBetweenSessions(db, state.tokenSessionId, sessionId2);
28610
+ copyTokensFromCache(db, state.tokenSessionId, sessionId2);
27754
28611
  } else if (!state.tokenSessionId && state.tokensPath) {
27755
28612
  const tokens = await readTokensFromPath("tokens", state.tokensPath);
27756
28613
  storeDtcgTokens(db, sessionId2, [], tokens);
@@ -27790,6 +28647,7 @@ If you are using AWS Bedrock, run:
27790
28647
  sessionId: sessionId2,
27791
28648
  agent: state.agent,
27792
28649
  ...state.agentModel ? { model: state.agentModel } : {},
28650
+ noCache: effectiveNoCache,
27793
28651
  skipAgent: skipMapTokens,
27794
28652
  promptOverrides,
27795
28653
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {}
@@ -27869,7 +28727,7 @@ If you are using AWS Bedrock, run:
27869
28727
  return promise;
27870
28728
  };
27871
28729
  const runExtract = async (projectPath, showProgress = true) => {
27872
- const outDir = join23(resolve17(projectPath), ".contentful");
28730
+ const outDir = join23(resolve18(projectPath), ".contentful");
27873
28731
  const existingEntitiesExpected = !state.credentialsSkipped && Boolean(state.spaceId && state.environmentId && state.cmaToken && projectPath);
27874
28732
  update({
27875
28733
  ...showProgress ? { step: "extracting" } : {},
@@ -27884,6 +28742,7 @@ If you are using AWS Bedrock, run:
27884
28742
  });
27885
28743
  const existingEntitiesPromise = startExistingEntitiesFetch(projectPath, outDir);
27886
28744
  const extractArgs = [findCliPath(), "__extract", "--project", projectPath];
28745
+ if (effectiveNoCache) extractArgs.push("--no-cache");
27887
28746
  let selectionPromptText;
27888
28747
  let selectionPromptPath;
27889
28748
  try {
@@ -27892,7 +28751,7 @@ If you are using AWS Bedrock, run:
27892
28751
  const selectOverride = overrides.get("select");
27893
28752
  if (selectOverride?.kind === "text") selectionPromptText = selectOverride.value;
27894
28753
  if (selectOverride?.kind === "path") {
27895
- selectionPromptPath = resolve17(selectOverride.value);
28754
+ selectionPromptPath = resolve18(selectOverride.value);
27896
28755
  await resolvePromptOverride(selectOverride);
27897
28756
  }
27898
28757
  } catch (error) {
@@ -27916,7 +28775,8 @@ If you are using AWS Bedrock, run:
27916
28775
  agent: state.agent,
27917
28776
  ...state.agentModel ? { model: state.agentModel } : {},
27918
28777
  ...selectionPromptText !== void 0 ? { promptText: selectionPromptText } : {},
27919
- ...selectionPromptPath ? { promptPath: selectionPromptPath } : {}
28778
+ ...selectionPromptPath ? { promptPath: selectionPromptPath } : {},
28779
+ noCache: effectiveNoCache
27920
28780
  });
27921
28781
  })().catch((error) => {
27922
28782
  selectionError = error;
@@ -28061,17 +28921,136 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28061
28921
  generateProgress: null
28062
28922
  }));
28063
28923
  };
28064
- const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2) => ({
28924
+ const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => ({
28065
28925
  sessionId: extractSessionId,
28066
28926
  tokensPath,
28067
28927
  agent: state.agent,
28068
28928
  ...state.agentModel ? { model: state.agentModel } : {},
28069
28929
  ...state.bedrock ? { bedrock: true } : {},
28930
+ noCache: effectiveNoCache,
28070
28931
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {},
28071
28932
  ...generatePromptPath2 ? { generatePromptPath: generatePromptPath2 } : {},
28072
- promptOverrides
28933
+ promptOverrides,
28934
+ ...cachedComponents && cachedComponents.length > 0 ? { cachedComponents } : {}
28073
28935
  });
28074
- 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
+ ]);
28075
29054
  const startGeneratePrefetch = (extractSessionId, tokensPath) => {
28076
29055
  const args = buildGenerateArgs(extractSessionId, tokensPath);
28077
29056
  let progressCursor = null;
@@ -28126,13 +29105,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28126
29105
  });
28127
29106
  return donePromise;
28128
29107
  };
28129
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false) => {
29108
+ const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false, cachedComponents = []) => {
28130
29109
  const timer = startWizardTimer("component-generation", {
28131
29110
  sessionId: extractSessionId,
28132
29111
  acceptedCount,
28133
- suppressScreen
29112
+ suppressScreen,
29113
+ noCache: effectiveNoCache
28134
29114
  });
28135
- const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath);
29115
+ const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath, cachedComponents);
28136
29116
  let progressCursor = state.generateProgress;
28137
29117
  const { donePromise } = spawnGenerateChild({
28138
29118
  command: "node",
@@ -28202,6 +29182,25 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28202
29182
  finishSelectionToReviewTimer("error");
28203
29183
  }
28204
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
+ };
28205
29204
  const advanceToPushFlow = (generatedAcceptedCount) => {
28206
29205
  update({ generatedAcceptedCount });
28207
29206
  void runSaveAndPush();
@@ -28386,8 +29385,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28386
29385
  }
28387
29386
  }
28388
29387
  if (await runAgentAuthCheck("generating", true)) {
28389
- update({ step: "generating" });
28390
- 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
+ }
28391
29396
  }
28392
29397
  return;
28393
29398
  }
@@ -28751,7 +29756,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28751
29756
  });
28752
29757
  return { ok: false };
28753
29758
  }
28754
- await new Promise((resolve20) => setTimeout(resolve20, SAVE_CONFIRMATION_DELAY_MS));
29759
+ await new Promise((resolve21) => setTimeout(resolve21, SAVE_CONFIRMATION_DELAY_MS));
28755
29760
  update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
28756
29761
  return { ok: true };
28757
29762
  };
@@ -28828,6 +29833,12 @@ If using a custom host configuration, make sure the space exists on that host.`
28828
29833
  tokenReuseChecked.current = true;
28829
29834
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28830
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
+ }
28831
29842
  (async () => {
28832
29843
  try {
28833
29844
  await access7(existingTokensPath);
@@ -28911,7 +29922,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28911
29922
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28912
29923
  update({ step: tokenNextStep });
28913
29924
  if (await runAgentAuthCheck(tokenNextStep)) {
28914
- tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir);
29925
+ tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir, true);
28915
29926
  }
28916
29927
  },
28917
29928
  onQuit: () => process.exit(0)
@@ -29010,6 +30021,29 @@ If using a custom host configuration, make sure the space exists on that host.`
29010
30021
  acceptedCount: decisions.accepted.length,
29011
30022
  rejectedCount: decisions.rejected.length
29012
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
+ }
29013
30047
  await runScopeGate({
29014
30048
  sessionId: sessionId2,
29015
30049
  decisions,
@@ -29018,12 +30052,35 @@ If using a custom host configuration, make sure the space exists on that host.`
29018
30052
  if (shouldGenerateAfterScopeGate({ acceptedCount })) {
29019
30053
  const authPromise = runAgentAuthCheck("generating", true);
29020
30054
  if (await authPromise) {
29021
- update({
29022
- step: "generating",
29023
- generateProgress: null,
29024
- acceptedCount
30055
+ const comparisonStartedAt = startWizardTimer("generation-cache-set-comparison", {
30056
+ sessionId: sid,
30057
+ acceptedCount: decisions?.accepted.length ?? 0
30058
+ });
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
29025
30071
  });
29026
- void runGenerate(sid, state.tokensPath, acceptedCount);
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
+ }
29027
30084
  } else {
29028
30085
  finishSelectionToReviewTimer("error");
29029
30086
  }
@@ -29389,7 +30446,7 @@ function registerImportCommand(program) {
29389
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",
29390
30447
  (v, acc) => [...acc, v],
29391
30448
  []
29392
- ).action(async (opts) => {
30449
+ ).option("--no-cache", "Re-run all steps even if output already exists").action(async (opts) => {
29393
30450
  const interactiveTerminalSupported = getInteractiveTerminalSupport().supported;
29394
30451
  if (opts.tokens !== void 0) {
29395
30452
  const { access: access9 } = await import("node:fs/promises");
@@ -29426,6 +30483,7 @@ function registerImportCommand(program) {
29426
30483
  ...resolvedModel ? { initialModel: resolvedModel } : {},
29427
30484
  initialProjectPath: opts.project !== "." ? normalizePath(opts.project) : void 0,
29428
30485
  ...buildCompositionForwardingOptions(opts),
30486
+ noCache: opts.cache === false,
29429
30487
  skipMapTokens: false,
29430
30488
  livePreview: true,
29431
30489
  generatePromptPath: creds.generatePromptPath,
@@ -29513,7 +30571,7 @@ async function pathExists3(path) {
29513
30571
  return access8(path).then(() => true).catch(() => false);
29514
30572
  }
29515
30573
  function runSpawn(cmd, args, opts = {}) {
29516
- return new Promise((resolve20) => {
30574
+ return new Promise((resolve21) => {
29517
30575
  const child = spawn6(cmd, args, {
29518
30576
  cwd: opts.cwd,
29519
30577
  env: opts.env ?? process.env,
@@ -29525,7 +30583,7 @@ function runSpawn(cmd, args, opts = {}) {
29525
30583
  const settle = (result) => {
29526
30584
  if (settled) return;
29527
30585
  settled = true;
29528
- resolve20(result);
30586
+ resolve21(result);
29529
30587
  };
29530
30588
  child.on("error", (err) => settle({ exitCode: 1, stdout: "", stderr: err.message }));
29531
30589
  child.stdout.on("data", (d) => {
@@ -30726,13 +31784,13 @@ function PrerequisitesScreen({
30726
31784
  const driver = {
30727
31785
  ...base,
30728
31786
  emit: (event) => setEvents((current) => [...current, event]),
30729
- confirm: (question, defaultYes = true) => new Promise((resolve20) => {
31787
+ confirm: (question, defaultYes = true) => new Promise((resolve21) => {
30730
31788
  setConfirm({
30731
31789
  question,
30732
31790
  defaultYes,
30733
31791
  resolve: (answer) => {
30734
31792
  setConfirm(null);
30735
- resolve20(answer);
31793
+ resolve21(answer);
30736
31794
  }
30737
31795
  });
30738
31796
  })
@@ -30807,8 +31865,8 @@ function SetupScreen({
30807
31865
  useEffect21(() => {
30808
31866
  if (startedRef.current) return;
30809
31867
  startedRef.current = true;
30810
- const runScreen = (build) => new Promise((resolve20) => {
30811
- setScreen(build(resolve20));
31868
+ const runScreen = (build) => new Promise((resolve21) => {
31869
+ setScreen(build(resolve21));
30812
31870
  });
30813
31871
  void (async () => {
30814
31872
  const results = [];
@@ -31416,8 +32474,11 @@ var init_source_call_site_evidence = __esm({
31416
32474
  });
31417
32475
 
31418
32476
  // packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts
31419
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile18, stat as stat6 } from "node:fs/promises";
31420
- 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
+ }
31421
32482
  function isDenylistedNoiseFile(name) {
31422
32483
  return DENYLISTED_EXACT_FILE_NAMES.has(name) || DENYLISTED_FILE_NAME_PATTERNS.some((pattern) => pattern.test(name));
31423
32484
  }
@@ -31431,12 +32492,12 @@ async function retryDatabaseWrite(operation, attempts = 8) {
31431
32492
  } catch (error) {
31432
32493
  const message = error instanceof Error ? error.message : String(error);
31433
32494
  if (!/database is locked|database is busy/i.test(message) || attempt >= attempts) throw error;
31434
- await new Promise((resolve20) => setTimeout(resolve20, Math.min(250 * attempt, 2e3)));
32495
+ await new Promise((resolve21) => setTimeout(resolve21, Math.min(250 * attempt, 2e3)));
31435
32496
  }
31436
32497
  }
31437
32498
  }
31438
32499
  function resolveFromProjectRoot(projectRoot, inputPath) {
31439
- return isAbsolute2(inputPath) ? inputPath : resolve18(projectRoot, inputPath);
32500
+ return isAbsolute2(inputPath) ? inputPath : resolve19(projectRoot, inputPath);
31440
32501
  }
31441
32502
  async function pathExists4(path) {
31442
32503
  return Boolean(await stat6(path).catch(() => null));
@@ -31483,7 +32544,7 @@ async function readCandidateFiles(components, extraFiles = []) {
31483
32544
  await Promise.all(
31484
32545
  [...paths].map(async (p) => {
31485
32546
  try {
31486
- const content = await readFile18(p, "utf8");
32547
+ const content = await readFile19(p, "utf8");
31487
32548
  out.push({ path: p, content });
31488
32549
  } catch {
31489
32550
  }
@@ -31496,7 +32557,8 @@ function registerInternalExtractCommand(program) {
31496
32557
  "--resolve-unreachable <mode>",
31497
32558
  "Retry pass for unresolved Svelte Props types: 'auto' (default), 'always', or 'never'",
31498
32559
  "auto"
31499
- ).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);
31500
32562
  const resolveUnreachable = (() => {
31501
32563
  const v = opts.resolveUnreachable ?? "auto";
31502
32564
  if (v !== "auto" && v !== "always" && v !== "never") {
@@ -31506,7 +32568,7 @@ function registerInternalExtractCommand(program) {
31506
32568
  }
31507
32569
  return v;
31508
32570
  })();
31509
- const projectRoot = resolve18(opts.project);
32571
+ const projectRoot = resolve19(opts.project);
31510
32572
  const outDir = join27(projectRoot, ".contentful");
31511
32573
  let sourceDirectory;
31512
32574
  if (opts.dir !== void 0) {
@@ -31530,16 +32592,50 @@ function registerInternalExtractCommand(program) {
31530
32592
  process.stderr.write(`progress=scan-done:${sourceFiles.length}
31531
32593
  `);
31532
32594
  }
31533
- const extraction = await extractComponents(
31534
- sourceFiles,
31535
- ({ 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: [] };
31536
32614
  if (!process.stdout.isTTY) {
31537
- process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
31538
- `);
32615
+ process.stderr.write(
32616
+ `progress=extract:${sourceFiles.length}/${sourceFiles.length}:${cachedComponents.length}
32617
+ `
32618
+ );
31539
32619
  }
31540
- },
31541
- { resolveUnreachable, projectRoot }
31542
- );
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
+ }
31543
32639
  await mkdir6(outDir, { recursive: true });
31544
32640
  const db = openPipelineDb();
31545
32641
  const { sessionId: sessionId2 } = getOrCreateSession(db, void 0, void 0, {
@@ -31554,6 +32650,25 @@ function registerInternalExtractCommand(program) {
31554
32650
  const stepId = createStep(db, sessionId2, "analyze extract", {
31555
32651
  project: projectRoot
31556
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
+ }
31557
32672
  const classifiedComponents = extraction.components.map(preClassifyComponent);
31558
32673
  for (const exclusion of extraction.exclusions ?? []) {
31559
32674
  getDebugLogger().event("filter", "extract.excluded", { ...exclusion });
@@ -31658,6 +32773,7 @@ var init_extract_command = __esm({
31658
32773
  "use strict";
31659
32774
  init_src2();
31660
32775
  init_db();
32776
+ init_cache_keys();
31661
32777
  init_cycle_detection();
31662
32778
  init_resolve_mapping();
31663
32779
  init_manifest_doc_evidence();
@@ -31756,7 +32872,7 @@ __export(program_exports, {
31756
32872
  });
31757
32873
  import { spawn as spawn7 } from "node:child_process";
31758
32874
  import { fileURLToPath as fileURLToPath3 } from "node:url";
31759
- 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";
31760
32876
  import { readFileSync as readFileSync10 } from "node:fs";
31761
32877
  import { Command } from "commander";
31762
32878
  async function runBuild(opts) {
@@ -31786,7 +32902,7 @@ function resolveBedrockFromAncestors(actionCommand) {
31786
32902
  }
31787
32903
  function registerBuildCommand(program) {
31788
32904
  program.command("build").description("Rebuild from source and re-link exo/experiences binaries to this build").helpOption(false).action(async () => {
31789
- const pkgRoot = resolve19(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
32905
+ const pkgRoot = resolve20(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
31790
32906
  process.stderr.write("\u2699 Building from source...\n");
31791
32907
  const { exitCode } = await runBuild({
31792
32908
  spawnFn: () => spawn7("pnpm", ["build"], { cwd: pkgRoot, stdio: "inherit" }),