@contentful/experience-design-system-cli 2.35.1-dev-build-a2149178.0 → 2.35.1-dev-build-36aa669d.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";
@@ -10308,6 +10365,41 @@ var init_read3 = __esm({
10308
10365
  }
10309
10366
  });
10310
10367
 
10368
+ // packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts
10369
+ import { createHash as createHash2 } from "node:crypto";
10370
+ function hashComponentShape(component) {
10371
+ const payload = {
10372
+ framework: component.framework,
10373
+ name: component.name,
10374
+ source: component.source,
10375
+ props: component.props.map((p) => ({
10376
+ name: p.name,
10377
+ type: p.type
10378
+ })),
10379
+ slots: component.slots.map((s) => ({
10380
+ name: s.name,
10381
+ isDefault: s.isDefault
10382
+ }))
10383
+ };
10384
+ return createHash2("sha256").update(JSON.stringify(payload)).digest("hex");
10385
+ }
10386
+ var init_hash_component_shape = __esm({
10387
+ "packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts"() {
10388
+ "use strict";
10389
+ }
10390
+ });
10391
+
10392
+ // packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts
10393
+ import { createHash as createHash3 } from "node:crypto";
10394
+ function hashTokenContent(rawTokenContent) {
10395
+ return createHash3("sha256").update(rawTokenContent.trim()).digest("hex");
10396
+ }
10397
+ var init_hash_token_content = __esm({
10398
+ "packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts"() {
10399
+ "use strict";
10400
+ }
10401
+ });
10402
+
10311
10403
  // packages/experience-design-system-cli-legacy/src/session/repositories/tokens/write.ts
10312
10404
  function deleteRawTokensForSession(db, sessionId2) {
10313
10405
  db.prepare("DELETE FROM raw_token_groups WHERE session_id = ?").run(sessionId2);
@@ -10471,17 +10563,47 @@ var init_replace_raw_prop_token_paths = __esm({
10471
10563
  }
10472
10564
  });
10473
10565
 
10566
+ // packages/experience-design-system-cli-legacy/src/session/cache-keys.ts
10567
+ var cache_keys_exports = {};
10568
+ __export(cache_keys_exports, {
10569
+ hashContent: () => hashContent,
10570
+ hashFile: () => hashFile,
10571
+ hashPromptForSkill: () => hashPromptForSkill
10572
+ });
10573
+ import { createHash as createHash4 } from "node:crypto";
10574
+ import { readFile as readFile7 } from "node:fs/promises";
10575
+ import { resolve as resolve8 } from "node:path";
10576
+ function hashContent(content) {
10577
+ return createHash4("sha256").update(content).digest("hex");
10578
+ }
10579
+ async function hashFile(filePath) {
10580
+ const content = await readFile7(filePath, "utf8");
10581
+ return hashContent(content);
10582
+ }
10583
+ async function hashPromptForSkill(skill, agent, model, skillPathOverride, extraInputs = [], skillContentOverride) {
10584
+ const promptHash = skillContentOverride ? hashContent(skillContentOverride) : skillPathOverride ? await hashFile(resolve8(skillPathOverride)) : await hashFile(resolveSkillPath(skill));
10585
+ const extraSegment = extraInputs.length > 0 ? `|${extraInputs.join("|")}` : "";
10586
+ return hashContent(`${promptHash}|${agent}|${model ?? ""}${extraSegment}`);
10587
+ }
10588
+ var init_cache_keys = __esm({
10589
+ "packages/experience-design-system-cli-legacy/src/session/cache-keys.ts"() {
10590
+ "use strict";
10591
+ init_src();
10592
+ }
10593
+ });
10594
+
10474
10595
  // packages/experience-design-system-cli-legacy/src/session/db.ts
10475
10596
  import { DatabaseSync } from "node:sqlite";
10476
10597
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
10477
- import { readFile as readFile7 } from "node:fs/promises";
10598
+ import { readFile as readFile8 } from "node:fs/promises";
10599
+ import { createHash as createHash5 } from "node:crypto";
10478
10600
  import { homedir as homedir3 } from "node:os";
10479
- import { dirname as dirname14, resolve as resolve8 } from "node:path";
10601
+ import { dirname as dirname14, resolve as resolve9 } from "node:path";
10480
10602
  function getPipelineDbPath() {
10481
10603
  if (process.env.EDS_PIPELINE_DB_PATH) {
10482
- return resolve8(process.env.EDS_PIPELINE_DB_PATH);
10604
+ return resolve9(process.env.EDS_PIPELINE_DB_PATH);
10483
10605
  }
10484
- return resolve8(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10606
+ return resolve9(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10485
10607
  }
10486
10608
  function openPipelineDb(dbPath) {
10487
10609
  const path = dbPath ?? getPipelineDbPath();
@@ -10615,8 +10737,134 @@ function applyDbMigrations(db) {
10615
10737
  if (!rawSlotColsForRationale.some((c2) => c2.name === "rationale")) {
10616
10738
  db.exec("ALTER TABLE raw_slots ADD COLUMN rationale TEXT");
10617
10739
  }
10618
- for (const table of REMOVED_CACHE_TABLES) {
10619
- db.exec(`DROP TABLE IF EXISTS ${table}`);
10740
+ const hasExtractCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='extract_cache'`).all();
10741
+ if (hasExtractCache.length === 0) {
10742
+ db.exec(`
10743
+ CREATE TABLE extract_cache (
10744
+ file_path TEXT NOT NULL,
10745
+ file_hash TEXT NOT NULL,
10746
+ cli_version TEXT NOT NULL,
10747
+ created_at TEXT NOT NULL,
10748
+ updated_at TEXT NOT NULL,
10749
+ components_json TEXT NOT NULL,
10750
+ PRIMARY KEY (file_hash, cli_version)
10751
+ );
10752
+ CREATE INDEX idx_extract_cache_file ON extract_cache(file_path);
10753
+ `);
10754
+ }
10755
+ const hasSelectCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='select_cache'`).all();
10756
+ if (hasSelectCache.length === 0) {
10757
+ db.exec(`
10758
+ CREATE TABLE select_cache (
10759
+ component_hash TEXT NOT NULL,
10760
+ prompt_hash TEXT NOT NULL,
10761
+ cli_version TEXT NOT NULL,
10762
+ decision TEXT NOT NULL CHECK (decision IN ('accepted','rejected')),
10763
+ reason TEXT,
10764
+ created_at TEXT NOT NULL,
10765
+ updated_at TEXT NOT NULL,
10766
+ PRIMARY KEY (component_hash, prompt_hash, cli_version)
10767
+ );
10768
+ `);
10769
+ }
10770
+ const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='generation_cache'`).all();
10771
+ if (tables.length === 0) {
10772
+ db.exec(`
10773
+ CREATE TABLE generation_cache (
10774
+ input_hash TEXT NOT NULL,
10775
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10776
+ entity_id TEXT NOT NULL,
10777
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10778
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10779
+ created_at TEXT NOT NULL,
10780
+ updated_at TEXT NOT NULL,
10781
+ PRIMARY KEY (input_hash, entity_type, entity_id)
10782
+ );
10783
+ CREATE INDEX idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10784
+ CREATE INDEX idx_generation_cache_session ON generation_cache(source_session_id);
10785
+ `);
10786
+ }
10787
+ const genCacheCols = db.prepare(`PRAGMA table_info(generation_cache)`).all();
10788
+ const hasPromptHash = genCacheCols.some((c2) => c2.name === "prompt_hash");
10789
+ const oldPkCols = genCacheCols.filter((c2) => c2.pk > 0).map((c2) => c2.name);
10790
+ const promptHashInPk = oldPkCols.includes("prompt_hash");
10791
+ if (!hasPromptHash) {
10792
+ db.exec(`ALTER TABLE generation_cache ADD COLUMN prompt_hash TEXT NOT NULL DEFAULT ''`);
10793
+ }
10794
+ if (!promptHashInPk) {
10795
+ db.exec("BEGIN");
10796
+ try {
10797
+ db.exec(`
10798
+ CREATE TABLE generation_cache__new (
10799
+ input_hash TEXT NOT NULL,
10800
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10801
+ entity_id TEXT NOT NULL,
10802
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10803
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10804
+ created_at TEXT NOT NULL,
10805
+ updated_at TEXT NOT NULL,
10806
+ prompt_hash TEXT NOT NULL DEFAULT '',
10807
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10808
+ );
10809
+ INSERT INTO generation_cache__new
10810
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10811
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10812
+ FROM generation_cache;
10813
+ DROP TABLE generation_cache;
10814
+ ALTER TABLE generation_cache__new RENAME TO generation_cache;
10815
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10816
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10817
+ `);
10818
+ db.exec("COMMIT");
10819
+ } catch (e) {
10820
+ db.exec("ROLLBACK");
10821
+ throw e;
10822
+ }
10823
+ }
10824
+ const genCacheTableSql = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'generation_cache'`).get();
10825
+ const hasTokenMappingEntityType = genCacheTableSql?.sql.includes("'token_mapping'") ?? true;
10826
+ if (!hasTokenMappingEntityType) {
10827
+ db.exec("BEGIN");
10828
+ try {
10829
+ db.exec(`
10830
+ CREATE TABLE generation_cache__new2 (
10831
+ input_hash TEXT NOT NULL,
10832
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10833
+ entity_id TEXT NOT NULL,
10834
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10835
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10836
+ created_at TEXT NOT NULL,
10837
+ updated_at TEXT NOT NULL,
10838
+ prompt_hash TEXT NOT NULL DEFAULT '',
10839
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10840
+ );
10841
+ INSERT INTO generation_cache__new2
10842
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10843
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10844
+ FROM generation_cache;
10845
+ DROP TABLE generation_cache;
10846
+ ALTER TABLE generation_cache__new2 RENAME TO generation_cache;
10847
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10848
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10849
+ `);
10850
+ db.exec("COMMIT");
10851
+ } catch (e) {
10852
+ db.exec("ROLLBACK");
10853
+ throw e;
10854
+ }
10855
+ }
10856
+ const hasCompositionCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='composition_cache'`).all();
10857
+ if (hasCompositionCache.length === 0) {
10858
+ db.exec(`
10859
+ CREATE TABLE composition_cache (
10860
+ input_hash TEXT NOT NULL,
10861
+ cli_version TEXT NOT NULL,
10862
+ agent_output TEXT NOT NULL,
10863
+ created_at TEXT NOT NULL,
10864
+ updated_at TEXT NOT NULL,
10865
+ PRIMARY KEY (input_hash, cli_version)
10866
+ );
10867
+ `);
10620
10868
  }
10621
10869
  }
10622
10870
  function loadComponentReviewMetadata(db, sessionId2, componentName) {
@@ -11156,16 +11404,16 @@ function extractRelativeImportPaths(sourceText) {
11156
11404
  return [...specifiers];
11157
11405
  }
11158
11406
  async function resolveRelativeImport(specifier, fromDir) {
11159
- const basePath = resolve8(fromDir, specifier);
11407
+ const basePath = resolve9(fromDir, specifier);
11160
11408
  const candidates = [
11161
11409
  basePath,
11162
11410
  ...rewrittenSourcePaths(basePath),
11163
11411
  ...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
11164
- ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve8(basePath, `index${ext}`))
11412
+ ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve9(basePath, `index${ext}`))
11165
11413
  ];
11166
11414
  for (const candidate of candidates) {
11167
11415
  try {
11168
- await readFile7(candidate, "utf8");
11416
+ await readFile8(candidate, "utf8");
11169
11417
  return candidate;
11170
11418
  } catch {
11171
11419
  }
@@ -11181,7 +11429,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
11181
11429
  return resolvedPaths;
11182
11430
  }
11183
11431
  async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateTypeNames = []) {
11184
- const visited = /* @__PURE__ */ new Set([resolve8(sourcePath)]);
11432
+ const visited = /* @__PURE__ */ new Set([resolve9(sourcePath)]);
11185
11433
  const discovered = [];
11186
11434
  let frontier = [{ text: sourceText, dir: dirname14(sourcePath) }];
11187
11435
  for (let hop = 0; hop < MAX_SIBLING_DEPTH && discovered.length < MAX_SIBLING_CANDIDATES_EXPLORED; hop++) {
@@ -11195,7 +11443,7 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
11195
11443
  if (discovered.length >= MAX_SIBLING_CANDIDATES_EXPLORED) break;
11196
11444
  let content;
11197
11445
  try {
11198
- content = await readFile7(resolvedPath, "utf8");
11446
+ content = await readFile8(resolvedPath, "utf8");
11199
11447
  } catch {
11200
11448
  continue;
11201
11449
  }
@@ -11232,7 +11480,7 @@ async function loadComponentSourceRef(name, sourcePath, propNames = [], propType
11232
11480
  let truncatedSiblingCount = 0;
11233
11481
  const usesNotShown = /* @__PURE__ */ new Set();
11234
11482
  try {
11235
- const rawText = await readFile7(sourcePath, "utf8");
11483
+ const rawText = await readFile8(sourcePath, "utf8");
11236
11484
  let siblingUsesNotShown;
11237
11485
  let declaredTypeNames;
11238
11486
  ({
@@ -11668,6 +11916,108 @@ function backfillUnclassifiedProps(db, sessionId2) {
11668
11916
  ).run(sessionId2, sessionId2);
11669
11917
  return Number(withCategory.changes) + Number(withoutCategory.changes);
11670
11918
  }
11919
+ function computeMapTokensInputHash(db, sessionId2, componentSourceRefs = []) {
11920
+ const props = db.prepare(
11921
+ `SELECT rc.name AS component_name, rp.name AS prop_name, rp.cdf_token_kind, rp.default_value
11922
+ FROM raw_props rp
11923
+ JOIN raw_components rc ON rc.session_id = rp.session_id AND rc.component_id = rp.component_id
11924
+ WHERE rp.session_id = ? AND rp.cdf_type = 'token' AND rp.cdf_category = 'design'
11925
+ ORDER BY rc.name, rp.name`
11926
+ ).all(sessionId2);
11927
+ const tokens = db.prepare(`SELECT path, type FROM raw_tokens WHERE session_id = ? ORDER BY path`).all(sessionId2);
11928
+ const defaultMappings = db.prepare("SELECT raw_name, path, source FROM raw_token_name_paths WHERE session_id = ? ORDER BY raw_name").all(sessionId2);
11929
+ const payload = {
11930
+ props: props.map((p) => ({
11931
+ component: p.component_name,
11932
+ prop: p.prop_name,
11933
+ tokenKind: p.cdf_token_kind,
11934
+ rawDefault: p.default_value
11935
+ })),
11936
+ tokens,
11937
+ defaultMappings,
11938
+ // The agent's evidence, not just its inputs: a comment or allowlist edit
11939
+ // in a source file (or a sibling it reads) changes the correct answer
11940
+ // without touching props or tokens, so it must invalidate the cache too.
11941
+ sourceRefs: [...componentSourceRefs].sort((a, b) => a.component.localeCompare(b.component)).map((ref) => ({
11942
+ component: ref.component,
11943
+ content: ref.content,
11944
+ siblingFiles: (ref.siblingFiles ?? []).map((s) => ({ path: s.path, content: s.content })).sort((a, b) => a.path.localeCompare(b.path)),
11945
+ usesNotShown: [...ref.usesNotShown ?? []].sort()
11946
+ }))
11947
+ };
11948
+ return createHash5("sha256").update(JSON.stringify(payload)).digest("hex");
11949
+ }
11950
+ function lookupCache(db, inputHash, entityType, entityId, promptHash = "") {
11951
+ const row = db.prepare(
11952
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11953
+ FROM generation_cache
11954
+ WHERE input_hash = ? AND entity_type = ? AND entity_id = ? AND prompt_hash = ?`
11955
+ ).get(inputHash, entityType, entityId, promptHash);
11956
+ if (!row) return null;
11957
+ return {
11958
+ inputHash: row.input_hash,
11959
+ entityType: row.entity_type,
11960
+ entityId: row.entity_id,
11961
+ sourceSessionId: row.source_session_id,
11962
+ humanEdited: row.human_edited === 1,
11963
+ promptHash: row.prompt_hash,
11964
+ createdAt: row.created_at,
11965
+ updatedAt: row.updated_at
11966
+ };
11967
+ }
11968
+ function lookupCacheByEntity(db, entityType, entityId) {
11969
+ const row = db.prepare(
11970
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11971
+ FROM generation_cache
11972
+ WHERE entity_type = ? AND entity_id = ?
11973
+ ORDER BY updated_at DESC LIMIT 1`
11974
+ ).get(entityType, entityId);
11975
+ if (!row) return null;
11976
+ return {
11977
+ inputHash: row.input_hash,
11978
+ entityType: row.entity_type,
11979
+ entityId: row.entity_id,
11980
+ sourceSessionId: row.source_session_id,
11981
+ humanEdited: row.human_edited === 1,
11982
+ promptHash: row.prompt_hash,
11983
+ createdAt: row.created_at,
11984
+ updatedAt: row.updated_at
11985
+ };
11986
+ }
11987
+ function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash = "") {
11988
+ storeCaches(db, [{ inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash }]);
11989
+ }
11990
+ function storeCaches(db, entries) {
11991
+ if (entries.length === 0) return;
11992
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11993
+ const insert = db.prepare(
11994
+ `INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
11995
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
11996
+ ON CONFLICT(input_hash, prompt_hash, entity_type, entity_id) DO UPDATE SET
11997
+ source_session_id = excluded.source_session_id,
11998
+ human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
11999
+ updated_at = excluded.updated_at`
12000
+ );
12001
+ db.exec("BEGIN");
12002
+ try {
12003
+ for (const entry of entries) {
12004
+ insert.run(
12005
+ entry.inputHash,
12006
+ entry.entityType,
12007
+ entry.entityId,
12008
+ entry.sourceSessionId,
12009
+ entry.humanEdited ? 1 : 0,
12010
+ now,
12011
+ now,
12012
+ entry.promptHash ?? ""
12013
+ );
12014
+ }
12015
+ db.exec("COMMIT");
12016
+ } catch (e) {
12017
+ db.exec("ROLLBACK");
12018
+ throw e;
12019
+ }
12020
+ }
11671
12021
  function storeScannedFiles(db, sessionId2, filePaths) {
11672
12022
  db.exec("BEGIN");
11673
12023
  try {
@@ -11750,7 +12100,105 @@ function filterUnknownSlotAllowedComponents(db, sessionId2, allowedComponentName
11750
12100
  }
11751
12101
  return unknown;
11752
12102
  }
11753
- function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
12103
+ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId, manageTransaction = true, options = {}) {
12104
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12105
+ if (manageTransaction) db.exec("BEGIN");
12106
+ try {
12107
+ const srcComp = db.prepare(
12108
+ `SELECT description, status, component_description_rationale, props_rationale, slots_rationale
12109
+ FROM raw_components WHERE session_id = ? AND component_id = ?`
12110
+ ).get(sourceSessionId, componentId);
12111
+ if (srcComp) {
12112
+ db.prepare(
12113
+ `UPDATE raw_components
12114
+ SET description = ?, status = ?, component_description_rationale = ?, props_rationale = ?, slots_rationale = ?, extracted_at = ?
12115
+ WHERE session_id = ? AND component_id = ?`
12116
+ ).run(
12117
+ srcComp.description,
12118
+ srcComp.status,
12119
+ srcComp.component_description_rationale,
12120
+ srcComp.props_rationale,
12121
+ srcComp.slots_rationale,
12122
+ now,
12123
+ targetSessionId,
12124
+ componentId
12125
+ );
12126
+ }
12127
+ const srcProps = db.prepare(
12128
+ `SELECT name, cdf_type, cdf_category, cdf_token_kind, required, description, default_value, rationale
12129
+ FROM raw_props WHERE session_id = ? AND component_id = ?`
12130
+ ).all(sourceSessionId, componentId);
12131
+ for (const p of srcProps) {
12132
+ db.prepare(
12133
+ `UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, default_value = ?, rationale = ?
12134
+ WHERE session_id = ? AND component_id = ? AND name = ?`
12135
+ ).run(
12136
+ p.cdf_type,
12137
+ p.cdf_category,
12138
+ p.cdf_token_kind,
12139
+ p.required,
12140
+ p.description,
12141
+ p.default_value,
12142
+ p.rationale,
12143
+ targetSessionId,
12144
+ componentId,
12145
+ p.name
12146
+ );
12147
+ }
12148
+ db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`).run(
12149
+ targetSessionId,
12150
+ componentId
12151
+ );
12152
+ const srcAV = db.prepare(
12153
+ `SELECT prop_name, position, value FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`
12154
+ ).all(sourceSessionId, componentId);
12155
+ const insertAV = db.prepare(
12156
+ `INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value) VALUES (?, ?, ?, ?, ?)`
12157
+ );
12158
+ for (const av of srcAV) {
12159
+ insertAV.run(targetSessionId, componentId, av.prop_name, av.position, av.value);
12160
+ }
12161
+ const srcSlots = db.prepare(`SELECT name, required, description, rationale FROM raw_slots WHERE session_id = ? AND component_id = ?`).all(sourceSessionId, componentId);
12162
+ for (const s of srcSlots) {
12163
+ db.prepare(
12164
+ `UPDATE raw_slots SET required = ?, description = ?, rationale = ? WHERE session_id = ? AND component_id = ? AND name = ?`
12165
+ ).run(s.required, s.description, s.rationale, targetSessionId, componentId, s.name);
12166
+ }
12167
+ db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`).run(
12168
+ targetSessionId,
12169
+ componentId
12170
+ );
12171
+ const srcSAC = db.prepare(
12172
+ `SELECT slot_name, position, allowed_component FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`
12173
+ ).all(sourceSessionId, componentId);
12174
+ const insertSAC = db.prepare(
12175
+ `INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`
12176
+ );
12177
+ const allowedSAC = options.allowedComponentNames ? srcSAC.filter((sac) => options.allowedComponentNames.has(sac.allowed_component)) : srcSAC;
12178
+ for (const sac of allowedSAC) {
12179
+ insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
12180
+ }
12181
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12182
+ if (manageTransaction) db.exec("COMMIT");
12183
+ } catch (e) {
12184
+ if (manageTransaction) db.exec("ROLLBACK");
12185
+ throw e;
12186
+ }
12187
+ }
12188
+ function copyComponentsFromCache(db, entries, options = {}) {
12189
+ if (entries.length === 0) return;
12190
+ db.exec("BEGIN");
12191
+ try {
12192
+ for (const entry of entries) {
12193
+ copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false, options);
12194
+ }
12195
+ db.exec("COMMIT");
12196
+ } catch (e) {
12197
+ db.exec("ROLLBACK");
12198
+ throw e;
12199
+ }
12200
+ }
12201
+ function copyTokensFromCache(db, sourceSessionId, targetSessionId) {
11754
12202
  const now = (/* @__PURE__ */ new Date()).toISOString();
11755
12203
  db.exec("BEGIN");
11756
12204
  try {
@@ -11771,7 +12219,139 @@ function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
11771
12219
  throw e;
11772
12220
  }
11773
12221
  }
11774
- 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;
12222
+ function copyMapTokensFromCache(db, sourceSessionId, targetSessionId) {
12223
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12224
+ let copiedCount = 0;
12225
+ db.exec("BEGIN");
12226
+ try {
12227
+ const sourceRows = db.prepare(
12228
+ `SELECT rc.name AS component_name, rptp.prop_name, rptp.source, rptp.position, rptp.path
12229
+ FROM raw_prop_token_paths rptp
12230
+ JOIN raw_components rc ON rc.session_id = rptp.session_id AND rc.component_id = rptp.component_id
12231
+ WHERE rptp.session_id = ?
12232
+ ORDER BY rc.name, rptp.prop_name, rptp.position`
12233
+ ).all(sourceSessionId);
12234
+ const targetComponents = db.prepare(`SELECT component_id, name FROM raw_components WHERE session_id = ?`).all(targetSessionId);
12235
+ const targetIdByName = new Map(targetComponents.map((c2) => [c2.name, c2.component_id]));
12236
+ const targetProps = new Set(
12237
+ db.prepare(`SELECT component_id, name FROM raw_props WHERE session_id = ?`).all(targetSessionId).map((p) => `${p.component_id}::${p.name}`)
12238
+ );
12239
+ const insertPath = db.prepare(
12240
+ `INSERT INTO raw_prop_token_paths (session_id, component_id, prop_name, source, position, path)
12241
+ VALUES (?, ?, ?, ?, ?, ?)`
12242
+ );
12243
+ const clearedProps = /* @__PURE__ */ new Set();
12244
+ const copiedProps = /* @__PURE__ */ new Set();
12245
+ for (const row of sourceRows) {
12246
+ const targetComponentId = targetIdByName.get(row.component_name);
12247
+ if (!targetComponentId) continue;
12248
+ if (!targetProps.has(`${targetComponentId}::${row.prop_name}`)) continue;
12249
+ const propKey = `${targetComponentId}::${row.prop_name}`;
12250
+ if (!clearedProps.has(propKey)) {
12251
+ clearedProps.add(propKey);
12252
+ db.prepare(`DELETE FROM raw_prop_token_paths WHERE session_id = ? AND component_id = ? AND prop_name = ?`).run(
12253
+ targetSessionId,
12254
+ targetComponentId,
12255
+ row.prop_name
12256
+ );
12257
+ }
12258
+ insertPath.run(targetSessionId, targetComponentId, row.prop_name, row.source, row.position, row.path);
12259
+ copiedProps.add(propKey);
12260
+ }
12261
+ copiedCount = copiedProps.size;
12262
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12263
+ db.exec("COMMIT");
12264
+ } catch (e) {
12265
+ db.exec("ROLLBACK");
12266
+ throw e;
12267
+ }
12268
+ return copiedCount;
12269
+ }
12270
+ async function getCliCacheVersion() {
12271
+ if (_cliCacheVersionCache) return _cliCacheVersionCache;
12272
+ try {
12273
+ const { hashContent: hashContent2 } = await Promise.resolve().then(() => (init_cache_keys(), cache_keys_exports));
12274
+ const { resolveSkillPath: resolveSkillPath2 } = await Promise.resolve().then(() => (init_src(), src_exports));
12275
+ const skills = ["components", "tokens", "select"];
12276
+ const parts = [];
12277
+ for (const s of skills) {
12278
+ try {
12279
+ const p = resolveSkillPath2(s);
12280
+ parts.push(readFileSync5(p, "utf8"));
12281
+ } catch {
12282
+ parts.push(`<missing:${s}>`);
12283
+ }
12284
+ }
12285
+ _cliCacheVersionCache = hashContent2(parts.join("\n---\n"));
12286
+ return _cliCacheVersionCache;
12287
+ } catch {
12288
+ _cliCacheVersionCache = "fallback";
12289
+ return _cliCacheVersionCache;
12290
+ }
12291
+ }
12292
+ function storeExtractCache(db, filePath, fileHash, cliVersion2, components) {
12293
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12294
+ const componentsJson = JSON.stringify(components);
12295
+ db.prepare(
12296
+ `INSERT INTO extract_cache (file_path, file_hash, cli_version, created_at, updated_at, components_json)
12297
+ VALUES (?, ?, ?, ?, ?, ?)
12298
+ ON CONFLICT(file_hash, cli_version) DO UPDATE SET
12299
+ file_path = excluded.file_path,
12300
+ updated_at = excluded.updated_at,
12301
+ components_json = excluded.components_json`
12302
+ ).run(filePath, fileHash, cliVersion2, now, now, componentsJson);
12303
+ }
12304
+ function lookupExtractCache(db, fileHash, cliVersion2) {
12305
+ const row = db.prepare(
12306
+ `SELECT file_path, file_hash, cli_version, created_at, updated_at, components_json
12307
+ FROM extract_cache
12308
+ WHERE file_hash = ? AND cli_version = ?`
12309
+ ).get(fileHash, cliVersion2);
12310
+ if (!row) return null;
12311
+ let components = [];
12312
+ try {
12313
+ components = JSON.parse(row.components_json);
12314
+ } catch {
12315
+ return null;
12316
+ }
12317
+ return {
12318
+ filePath: row.file_path,
12319
+ fileHash: row.file_hash,
12320
+ cliVersion: row.cli_version,
12321
+ createdAt: row.created_at,
12322
+ updatedAt: row.updated_at,
12323
+ components
12324
+ };
12325
+ }
12326
+ function storeSelectCache(db, componentHash, promptHash, cliVersion2, decision, reason) {
12327
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12328
+ db.prepare(
12329
+ `INSERT INTO select_cache (component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at)
12330
+ VALUES (?, ?, ?, ?, ?, ?, ?)
12331
+ ON CONFLICT(component_hash, prompt_hash, cli_version) DO UPDATE SET
12332
+ decision = excluded.decision,
12333
+ reason = excluded.reason,
12334
+ updated_at = excluded.updated_at`
12335
+ ).run(componentHash, promptHash, cliVersion2, decision, reason, now, now);
12336
+ }
12337
+ function lookupSelectCache(db, componentHash, promptHash, cliVersion2) {
12338
+ const row = db.prepare(
12339
+ `SELECT component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at
12340
+ FROM select_cache
12341
+ WHERE component_hash = ? AND prompt_hash = ? AND cli_version = ?`
12342
+ ).get(componentHash, promptHash, cliVersion2);
12343
+ if (!row) return null;
12344
+ return {
12345
+ componentHash: row.component_hash,
12346
+ promptHash: row.prompt_hash,
12347
+ cliVersion: row.cli_version,
12348
+ decision: row.decision,
12349
+ reason: row.reason,
12350
+ createdAt: row.created_at,
12351
+ updatedAt: row.updated_at
12352
+ };
12353
+ }
12354
+ 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;
11775
12355
  var init_db = __esm({
11776
12356
  "packages/experience-design-system-cli-legacy/src/session/db.ts"() {
11777
12357
  "use strict";
@@ -11785,6 +12365,8 @@ var init_db = __esm({
11785
12365
  init_write2();
11786
12366
  init_session_resolver();
11787
12367
  init_read3();
12368
+ init_hash_component_shape();
12369
+ init_hash_token_content();
11788
12370
  init_read3();
11789
12371
  init_store_dtcg_tokens();
11790
12372
  init_apply_tool_calls();
@@ -11908,6 +12490,17 @@ CREATE TABLE IF NOT EXISTS raw_tokens (
11908
12490
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
11909
12491
  );
11910
12492
 
12493
+ CREATE TABLE IF NOT EXISTS generation_cache (
12494
+ input_hash TEXT NOT NULL,
12495
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
12496
+ entity_id TEXT NOT NULL,
12497
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
12498
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
12499
+ created_at TEXT NOT NULL,
12500
+ updated_at TEXT NOT NULL,
12501
+ PRIMARY KEY (input_hash, entity_type, entity_id)
12502
+ );
12503
+
11911
12504
  CREATE TABLE IF NOT EXISTS scanned_files (
11912
12505
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
11913
12506
  path TEXT NOT NULL,
@@ -11953,9 +12546,10 @@ CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id,
11953
12546
  CREATE INDEX IF NOT EXISTS idx_raw_slots_session ON raw_slots(session_id, component_id);
11954
12547
  CREATE INDEX IF NOT EXISTS idx_raw_tokens_session ON raw_tokens(session_id);
11955
12548
  CREATE INDEX IF NOT EXISTS idx_raw_token_groups_session ON raw_token_groups(session_id);
12549
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
12550
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
11956
12551
  CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session_id);
11957
12552
  `;
11958
- REMOVED_CACHE_TABLES = ["generation_cache", "extract_cache", "composition_cache", "select_cache"];
11959
12553
  MAX_COMPONENT_SOURCE_CHARS = 8e3;
11960
12554
  MAX_SIBLING_FILES = 5;
11961
12555
  MAX_SIBLING_SNIPPET_CHARS = 1200;
@@ -11972,15 +12566,16 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
11972
12566
  [".cjs", [".cts", ".cjs"]]
11973
12567
  ];
11974
12568
  IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
12569
+ _cliCacheVersionCache = null;
11975
12570
  }
11976
12571
  });
11977
12572
 
11978
12573
  // packages/experience-design-system-cli-legacy/src/helpers/read-existing-contentful-entities-from-session.ts
11979
- import { readFile as readFile8 } from "node:fs/promises";
12574
+ import { readFile as readFile9 } from "node:fs/promises";
11980
12575
  async function readExistingContentfulEntitiesFromSession(path) {
11981
12576
  if (!path) return void 0;
11982
12577
  try {
11983
- const raw = await readFile8(path, "utf8");
12578
+ const raw = await readFile9(path, "utf8");
11984
12579
  const parsed = JSON.parse(raw);
11985
12580
  if (!Array.isArray(parsed.components) || !Array.isArray(parsed.tokens)) return void 0;
11986
12581
  return parsed;
@@ -12104,21 +12699,21 @@ var init_resolve_session_id = __esm({
12104
12699
  });
12105
12700
 
12106
12701
  // packages/experience-design-system-cli-legacy/src/analyze/select/persistence.ts
12107
- import { access, appendFile, mkdir as mkdir2, readFile as readFile9, writeFile as writeFile2 } from "node:fs/promises";
12702
+ import { access, appendFile, mkdir as mkdir2, readFile as readFile10, writeFile as writeFile2 } from "node:fs/promises";
12108
12703
  import { homedir as homedir4 } from "node:os";
12109
- import { resolve as resolve9 } from "node:path";
12704
+ import { resolve as resolve10 } from "node:path";
12110
12705
  function getRefineArtifactsRoot() {
12111
12706
  if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
12112
- return resolve9(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12707
+ return resolve10(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12113
12708
  }
12114
- return resolve9(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12709
+ return resolve10(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12115
12710
  }
12116
12711
  async function getRefineSessionPaths(sessionId2, artifactsRoot) {
12117
- const sessionDir = resolve9(artifactsRoot, sessionId2);
12712
+ const sessionDir = resolve10(artifactsRoot, sessionId2);
12118
12713
  return {
12119
12714
  sessionDir,
12120
- eventsPath: resolve9(sessionDir, "events.jsonl"),
12121
- statePath: resolve9(sessionDir, "current-review-state.json")
12715
+ eventsPath: resolve10(sessionDir, "events.jsonl"),
12716
+ statePath: resolve10(sessionDir, "current-review-state.json")
12122
12717
  };
12123
12718
  }
12124
12719
  async function saveReviewState(statePath, session) {
@@ -12129,7 +12724,7 @@ async function ensureRefineSession(sessionId2, artifactsRoot, initialSnapshot) {
12129
12724
  await mkdir2(paths.sessionDir, { recursive: true });
12130
12725
  try {
12131
12726
  await access(paths.statePath);
12132
- const savedState = await readFile9(paths.statePath, "utf8");
12727
+ const savedState = await readFile10(paths.statePath, "utf8");
12133
12728
  return JSON.parse(savedState);
12134
12729
  } catch {
12135
12730
  await writeFile2(paths.statePath, JSON.stringify(initialSnapshot, null, 2), "utf8");
@@ -12614,8 +13209,8 @@ var init_path_exists = __esm({
12614
13209
  });
12615
13210
 
12616
13211
  // packages/experience-design-system-cli-legacy/src/lib/prompt-overrides.ts
12617
- import { readFile as readFile10 } from "node:fs/promises";
12618
- import { resolve as resolve10 } from "node:path";
13212
+ import { readFile as readFile11 } from "node:fs/promises";
13213
+ import { resolve as resolve11 } from "node:path";
12619
13214
  function looksLikePath(value) {
12620
13215
  if (value.includes("/") || value.includes("\\")) return true;
12621
13216
  if (value.startsWith("~")) return true;
@@ -12647,9 +13242,9 @@ function parsePromptOverrides(inputs) {
12647
13242
  }
12648
13243
  async function resolvePromptOverride(override) {
12649
13244
  if (override.kind === "text") return override.value;
12650
- const abs = resolve10(override.value);
13245
+ const abs = resolve11(override.value);
12651
13246
  try {
12652
- return await readFile10(abs, "utf8");
13247
+ return await readFile11(abs, "utf8");
12653
13248
  } catch {
12654
13249
  throw new Error(`--prompt: could not read prompt file: ${abs}`);
12655
13250
  }
@@ -12664,21 +13259,21 @@ var init_prompt_overrides = __esm({
12664
13259
 
12665
13260
  // packages/experience-design-system-cli-legacy/src/generate/command.ts
12666
13261
  import { createElement } from "react";
12667
- import { readFile as readFile11, readdir, stat } from "node:fs/promises";
12668
- import { join as join14, resolve as resolve11 } from "node:path";
13262
+ import { readFile as readFile12, readdir, stat } from "node:fs/promises";
13263
+ import { join as join14, resolve as resolve12 } from "node:path";
12669
13264
  async function assertFileExists(flag, p) {
12670
13265
  if (!await pathExists(p)) die(`Error: file not found: ${p} (from ${flag})`);
12671
13266
  }
12672
13267
  async function readFileInline(path) {
12673
13268
  if (!path) return void 0;
12674
- const resolved = resolve11(path);
13269
+ const resolved = resolve12(path);
12675
13270
  let s;
12676
13271
  try {
12677
13272
  s = await stat(resolved);
12678
13273
  } catch {
12679
13274
  return void 0;
12680
13275
  }
12681
- if (!s.isDirectory()) return readFile11(resolved, "utf8");
13276
+ if (!s.isDirectory()) return readFile12(resolved, "utf8");
12682
13277
  const files = [];
12683
13278
  async function walk(dir) {
12684
13279
  let entries;
@@ -12704,7 +13299,7 @@ async function readFileInline(path) {
12704
13299
  }
12705
13300
  await walk(resolved);
12706
13301
  if (files.length === 0) return void 0;
12707
- const parts = await Promise.all(files.map((f) => readFile11(f, "utf8").catch(() => "")));
13302
+ const parts = await Promise.all(files.map((f) => readFile12(f, "utf8").catch(() => "")));
12708
13303
  return parts.filter(Boolean).join("\n\n");
12709
13304
  }
12710
13305
  function printFallbackInstructions(options) {
@@ -12726,6 +13321,23 @@ function printFallbackInstructions(options) {
12726
13321
  lines.push(` Re-run the generate command with the agent available, or use --dry-run to inspect the prompt.`);
12727
13322
  process.stderr.write(lines.join("\n") + "\n");
12728
13323
  }
13324
+ function createCachedComponentResult(componentName, warnings = []) {
13325
+ return {
13326
+ componentName,
13327
+ classified: 0,
13328
+ excluded: 0,
13329
+ slots: 0,
13330
+ warnings,
13331
+ failed: false,
13332
+ cached: true,
13333
+ renamedSlotsCount: 0
13334
+ };
13335
+ }
13336
+ function writeCachedComponentStatus(position, componentName, pinned) {
13337
+ const status = pinned ? c.cyan("pinned (human-edited)") : c.green("cached");
13338
+ process.stderr.write(` ${position} ${c.bold(componentName)} ${status}
13339
+ `);
13340
+ }
12729
13341
  async function showGenerateView(result) {
12730
13342
  if (process.stdout.isTTY) {
12731
13343
  const { waitUntilExit } = renderWithGoodbye(
@@ -12746,6 +13358,29 @@ session=${result.sessionId}
12746
13358
  );
12747
13359
  await exitWithAnalytics(0);
12748
13360
  }
13361
+ function normalizeComponentForCache(component) {
13362
+ const slots = component.slots.map((slot, index, allSlots) => ({
13363
+ ...slot,
13364
+ name: slot.name.trim() || (allSlots.length === 1 ? "children" : `slot_${index}`)
13365
+ }));
13366
+ return { ...component, slots };
13367
+ }
13368
+ function lookupComponentCache(db, component, promptHash) {
13369
+ const normalized = normalizeComponentForCache(component);
13370
+ const inputHash = hashComponentShape(normalized);
13371
+ const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
13372
+ if (cached) return cached;
13373
+ const legacyInputHash = hashComponentShape(component);
13374
+ return legacyInputHash === inputHash ? null : lookupCache(db, legacyInputHash, "component", component.component_id, promptHash);
13375
+ }
13376
+ function resolveComponentCache(db, component, promptHash, allowedComponentNames2) {
13377
+ const cached = lookupComponentCache(db, component, promptHash);
13378
+ if (cached && findUnknownSlotAllowedComponents(db, cached.sourceSessionId, allowedComponentNames2, component.component_id).length === 0) {
13379
+ return { entry: cached, humanEdited: cached.humanEdited };
13380
+ }
13381
+ const pinned = lookupCacheByEntity(db, "component", component.component_id);
13382
+ return pinned?.humanEdited && findUnknownSlotAllowedComponents(db, pinned.sourceSessionId, allowedComponentNames2, component.component_id).length === 0 ? { entry: pinned, humanEdited: true } : null;
13383
+ }
12749
13384
  async function runOneComponent(options, component, index, total) {
12750
13385
  const {
12751
13386
  agent,
@@ -12755,10 +13390,13 @@ async function runOneComponent(options, component, index, total) {
12755
13390
  tokensInline,
12756
13391
  tokenMapInline,
12757
13392
  verbose,
13393
+ noCache,
12758
13394
  skillPathOverride,
12759
13395
  skillContentOverride,
13396
+ promptHash,
12760
13397
  existingContentfulEntities,
12761
13398
  existingTokensInline,
13399
+ precomputedCachedNames,
12762
13400
  allowedComponentNames: allowedComponentNames2
12763
13401
  } = options;
12764
13402
  const pos = c.dim(`[${index + 1}/${total}]`);
@@ -12783,6 +13421,40 @@ async function runOneComponent(options, component, index, total) {
12783
13421
  allowedComponents: slot.allowedComponents.filter((name) => allowedComponentNames2.has(name))
12784
13422
  };
12785
13423
  });
13424
+ const cacheComponent = normalizeComponentForCache(component);
13425
+ if (!noCache && precomputedCachedNames.has(component.name)) {
13426
+ writeCachedComponentStatus(pos, component.name, false);
13427
+ return createCachedComponentResult(component.name);
13428
+ }
13429
+ if (!noCache) {
13430
+ const inputHash = hashComponentShape(cacheComponent);
13431
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13432
+ if (resolution && !resolution.humanEdited) {
13433
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13434
+ allowedComponentNames: allowedComponentNames2
13435
+ });
13436
+ storeCache(
13437
+ db,
13438
+ inputHash,
13439
+ "component",
13440
+ component.component_id,
13441
+ resolution.entry.sourceSessionId,
13442
+ resolution.entry.humanEdited,
13443
+ promptHash
13444
+ );
13445
+ writeCachedComponentStatus(pos, component.name, false);
13446
+ return createCachedComponentResult(component.name);
13447
+ }
13448
+ if (resolution?.humanEdited) {
13449
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13450
+ allowedComponentNames: allowedComponentNames2
13451
+ });
13452
+ writeCachedComponentStatus(pos, component.name, true);
13453
+ return createCachedComponentResult(`${component.name}`, [
13454
+ `${component.name}: source changed but human edits preserved`
13455
+ ]);
13456
+ }
13457
+ }
12786
13458
  const componentBuckets = bucketComponentProps(component);
12787
13459
  getDebugLogger().event("analyze", "prop-buckets.generate", { ...componentBuckets });
12788
13460
  const rawComponentsInline = JSON.stringify(
@@ -12857,6 +13529,10 @@ ${outputBuf}`);
12857
13529
  const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings, {
12858
13530
  allowedComponentNames: allowedComponentNames2
12859
13531
  });
13532
+ if (!noCache) {
13533
+ const inputHash = hashComponentShape(cacheComponent);
13534
+ storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
13535
+ }
12860
13536
  return {
12861
13537
  componentName: component.name,
12862
13538
  classified: applied.classified,
@@ -12908,7 +13584,7 @@ async function loadAcceptedNames(sessionId2) {
12908
13584
  try {
12909
13585
  const artifactsRoot = getRefineArtifactsRoot();
12910
13586
  const paths = await getRefineSessionPaths(sessionId2, artifactsRoot);
12911
- const raw = await readFile11(paths.statePath, "utf8");
13587
+ const raw = await readFile12(paths.statePath, "utf8");
12912
13588
  const snapshot = JSON.parse(raw);
12913
13589
  const accepted = snapshot.components.filter((c2) => c2.status === "accepted").map((c2) => c2.name);
12914
13590
  if (accepted.length === 0) return null;
@@ -12917,6 +13593,15 @@ async function loadAcceptedNames(sessionId2) {
12917
13593
  return null;
12918
13594
  }
12919
13595
  }
13596
+ function parsePrecomputedCachedNames(value) {
13597
+ if (!value) return /* @__PURE__ */ new Set();
13598
+ try {
13599
+ const parsed = JSON.parse(value);
13600
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : []);
13601
+ } catch {
13602
+ return /* @__PURE__ */ new Set();
13603
+ }
13604
+ }
12920
13605
  async function runGenerateSkill(skill, opts, verbose = false) {
12921
13606
  const savedCreds = await readExperiencesCredentials();
12922
13607
  const agentName = opts.agent ?? savedCreds.agent;
@@ -12945,8 +13630,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12945
13630
  }
12946
13631
  const generatePromptPath = generatePrompt === void 0 ? configuredGeneratePromptPath : void 0;
12947
13632
  if (generatePromptPath) {
12948
- if (!await pathExists(resolve11(generatePromptPath))) {
12949
- die(`Error: custom prompt path not found: ${resolve11(generatePromptPath)}`);
13633
+ if (!await pathExists(resolve12(generatePromptPath))) {
13634
+ die(`Error: custom prompt path not found: ${resolve12(generatePromptPath)}`);
12950
13635
  }
12951
13636
  if (!generatePromptPath.toLowerCase().endsWith(".md")) {
12952
13637
  process.stderr.write(
@@ -12954,7 +13639,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12954
13639
  `
12955
13640
  );
12956
13641
  }
12957
- process.stderr.write(formatCustomPromptBanner("components", resolve11(generatePromptPath)));
13642
+ process.stderr.write(formatCustomPromptBanner("components", resolve12(generatePromptPath)));
12958
13643
  }
12959
13644
  if (skill === "tokens" && !opts.rawTokens) {
12960
13645
  die("Error: --raw-tokens is required when using generate tokens");
@@ -12970,7 +13655,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12970
13655
  let existingContentfulEntities;
12971
13656
  let existingTokensInline;
12972
13657
  if (opts.existingEntitiesPath) {
12973
- existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve11(opts.existingEntitiesPath));
13658
+ existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve12(opts.existingEntitiesPath));
12974
13659
  if (existingContentfulEntities) {
12975
13660
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
12976
13661
  } else {
@@ -13071,7 +13756,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13071
13756
  rawComponentsInline: sampleInline ?? rawTokensInline,
13072
13757
  propBucketsInline: sampleBuckets ? JSON.stringify(sampleBuckets) : void 0,
13073
13758
  rawTokensInline: skill === "tokens" ? rawTokensInline : void 0,
13074
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13759
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13075
13760
  tokensInline,
13076
13761
  tokenMapInline,
13077
13762
  outDir: process.cwd(),
@@ -13097,7 +13782,64 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13097
13782
  if (skill === "components" && allComponents && sessionId2) {
13098
13783
  const db = openPipelineDb();
13099
13784
  let componentResults;
13785
+ const precomputedCachedNames = parsePrecomputedCachedNames(opts.cachedComponents);
13100
13786
  try {
13787
+ const existingContentfulEntitiesHashInputs = [];
13788
+ if (existingTokensInline) existingContentfulEntitiesHashInputs.push(hashContent(existingTokensInline));
13789
+ const promptHash = await hashPromptForSkill(
13790
+ "components",
13791
+ agent,
13792
+ model,
13793
+ generatePromptPath,
13794
+ existingContentfulEntitiesHashInputs,
13795
+ generatePrompt
13796
+ );
13797
+ if (opts.cacheStatus) {
13798
+ const cacheEnabled = opts.cache !== false && process.env.EDS_NO_CACHE !== "1";
13799
+ const cachedComponents = cacheEnabled ? allComponents.flatMap((component) => {
13800
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13801
+ return resolution ? [{ component, resolution }] : [];
13802
+ }) : [];
13803
+ if (cacheEnabled && opts.restoreCache) {
13804
+ const restorations = [];
13805
+ const rekeyed = [];
13806
+ const componentStatus = db.prepare(
13807
+ "SELECT status FROM raw_components WHERE session_id = ? AND component_id = ?"
13808
+ );
13809
+ for (const { component, resolution } of cachedComponents) {
13810
+ const current = componentStatus.get(sessionId2, component.component_id);
13811
+ if (current?.status === "generated") continue;
13812
+ renameEmptySlots(db, sessionId2, component.component_id, component.name, component.slots.length);
13813
+ restorations.push({
13814
+ sourceSessionId: resolution.entry.sourceSessionId,
13815
+ targetSessionId: sessionId2,
13816
+ componentId: component.component_id
13817
+ });
13818
+ rekeyed.push({ component, cached: resolution.entry });
13819
+ }
13820
+ copyComponentsFromCache(db, restorations, { allowedComponentNames: allowedComponentNames2 });
13821
+ storeCaches(
13822
+ db,
13823
+ rekeyed.map(({ component, cached }) => ({
13824
+ inputHash: hashComponentShape(normalizeComponentForCache(component)),
13825
+ entityType: "component",
13826
+ entityId: component.component_id,
13827
+ sourceSessionId: cached.sourceSessionId,
13828
+ humanEdited: cached.humanEdited,
13829
+ promptHash
13830
+ }))
13831
+ );
13832
+ }
13833
+ const fullyCached = cacheEnabled && cachedComponents.length === allComponents.length;
13834
+ process.stdout.write(`cache-status=${fullyCached ? "hit" : "miss"}
13835
+ `);
13836
+ process.stdout.write(
13837
+ `cache-components=${JSON.stringify(cachedComponents.map(({ component }) => component.name))}
13838
+ `
13839
+ );
13840
+ await exitWithAnalytics(0);
13841
+ return;
13842
+ }
13101
13843
  componentResults = await runAllComponents(
13102
13844
  {
13103
13845
  agent,
@@ -13107,10 +13849,13 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13107
13849
  tokensInline,
13108
13850
  tokenMapInline,
13109
13851
  verbose,
13852
+ noCache: opts.cache === false || process.env.EDS_NO_CACHE === "1",
13110
13853
  skillPathOverride: generatePromptPath,
13111
13854
  skillContentOverride: generatePrompt,
13855
+ promptHash,
13112
13856
  existingContentfulEntities,
13113
13857
  existingTokensInline,
13858
+ precomputedCachedNames,
13114
13859
  allowedComponentNames: allowedComponentNames2
13115
13860
  },
13116
13861
  allComponents
@@ -13119,7 +13864,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13119
13864
  db.close();
13120
13865
  }
13121
13866
  const failed = componentResults.filter((r) => r.failed);
13122
- const generated = componentResults.filter((r) => !r.failed);
13867
+ const cachedResults = componentResults.filter((r) => r.cached);
13868
+ const generated = componentResults.filter((r) => !r.failed && !r.cached);
13123
13869
  const allWarnings = componentResults.flatMap((r) => r.warnings.map((w) => ` ${r.componentName}: ${w}`));
13124
13870
  if (allWarnings.length > 0) {
13125
13871
  process.stderr.write(c.yellow("Warnings:") + "\n" + allWarnings.join("\n") + "\n");
@@ -13135,17 +13881,21 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13135
13881
  const totalExcluded = generated.reduce((s, r) => s + r.excluded, 0);
13136
13882
  const totalRenamedSlots = componentResults.reduce((s, r) => s + r.renamedSlotsCount, 0);
13137
13883
  const allOk = failed.length === 0;
13884
+ const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : "";
13138
13885
  process.stderr.write(
13139
- (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length}/${componentResults.length} components` + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13886
+ (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13140
13887
  );
13141
13888
  process.stdout.write(`renamed-slots: ${totalRenamedSlots}
13142
13889
  `);
13143
- if (generated.length === 0) {
13890
+ if (generated.length === 0 && cachedResults.length === 0) {
13144
13891
  die(
13145
13892
  `Error: all ${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
13146
13893
  );
13147
13894
  }
13148
13895
  } else if (skill === "tokens") {
13896
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
13897
+ const tokenInputContent = rawTokensInline ?? "";
13898
+ const tokenInputHash = hashTokenContent(tokenInputContent);
13149
13899
  const db = openPipelineDb();
13150
13900
  try {
13151
13901
  let resolvedSessionId = opts.session;
@@ -13172,11 +13922,26 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13172
13922
  }
13173
13923
  sessionId2 = resolvedSessionId;
13174
13924
  await bindAnalyticsSessionId(resolvedSessionId);
13925
+ const tokenPromptHash = await hashPromptForSkill("tokens", agent, model);
13926
+ if (!noCache) {
13927
+ const tokenCached = lookupCache(db, tokenInputHash, "token_set", "__tokens__", tokenPromptHash);
13928
+ if (tokenCached) {
13929
+ copyTokensFromCache(db, tokenCached.sourceSessionId, resolvedSessionId);
13930
+ sessionId2 = resolvedSessionId;
13931
+ process.stderr.write(
13932
+ `Done: tokens reused from cache ${c.dim(`(source: ${tokenCached.sourceSessionId.slice(0, 12)})`)}
13933
+ `
13934
+ );
13935
+ db.close();
13936
+ await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13937
+ return;
13938
+ }
13939
+ }
13175
13940
  const prompt = await buildPrompt({
13176
13941
  skill,
13177
13942
  mode: "autonomous",
13178
13943
  rawTokensInline,
13179
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13944
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13180
13945
  tokensInline,
13181
13946
  tokenMapInline,
13182
13947
  outDir: process.cwd()
@@ -13213,6 +13978,9 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13213
13978
  `);
13214
13979
  }
13215
13980
  applyTokenToolCalls(db, resolvedSessionId, tokenCalls, []);
13981
+ if (!noCache) {
13982
+ storeCache(db, tokenInputHash, "token_set", "__tokens__", resolvedSessionId, false, tokenPromptHash);
13983
+ }
13216
13984
  sessionId2 = resolvedSessionId;
13217
13985
  const groupCount = tokenCalls.filter((tc) => tc.tool === "set_group").length;
13218
13986
  process.stderr.write(`Done: ${tokenCount} tokens, ${groupCount} groups stored
@@ -13224,7 +13992,10 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13224
13992
  await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13225
13993
  }
13226
13994
  function addAgentFlags(cmd) {
13227
- return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent");
13995
+ return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent").option(
13996
+ "--no-cache",
13997
+ "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."
13998
+ );
13228
13999
  }
13229
14000
  function registerInternalGenerateCommand(program) {
13230
14001
  const generate = program.command("__generate", { hidden: true }).description("Internal import pipeline generation command");
@@ -13240,6 +14011,12 @@ function registerInternalGenerateCommand(program) {
13240
14011
  "--existing-entities-path <path>",
13241
14012
  "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."
13242
14013
  );
14014
+ componentsCmd.option("--cache-status", "Check whether all selected component definitions are cached");
14015
+ componentsCmd.option("--restore-cache", "Restore cached component definitions into the current session");
14016
+ componentsCmd.option(
14017
+ "--cached-components <json>",
14018
+ "Component names already restored by a prior cache-status lookup; skip per-component cache lookup"
14019
+ );
13243
14020
  addAgentFlags(componentsCmd).action(async (opts) => {
13244
14021
  await runGenerateSkill("components", opts, opts.verbose ?? false);
13245
14022
  });
@@ -13265,6 +14042,7 @@ var init_command = __esm({
13265
14042
  init_agent_output();
13266
14043
  init_GenerateView();
13267
14044
  init_db();
14045
+ init_cache_keys();
13268
14046
  init_read_existing_contentful_entities_from_session();
13269
14047
  init_summarize_existing_contentful_entities();
13270
14048
  init_resolve_session_id();
@@ -13980,7 +14758,7 @@ var init_user_agent = __esm({
13980
14758
  // packages/experience-design-system-cli-legacy/src/apply/api-client.ts
13981
14759
  import { parseCDFComponents } from "@contentful/experience-design-system-types";
13982
14760
  function defaultSleep(delayMs) {
13983
- return new Promise((resolve20) => setTimeout(resolve20, delayMs));
14761
+ return new Promise((resolve21) => setTimeout(resolve21, delayMs));
13984
14762
  }
13985
14763
  function isTransientStatus(status) {
13986
14764
  return status >= 500 && status <= 599;
@@ -14370,7 +15148,7 @@ ${trimmed}`;
14370
15148
  const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
14371
15149
  const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
14372
15150
  const jitter = Math.random() * baseDelay * 0.15;
14373
- await new Promise((resolve20) => setTimeout(resolve20, baseDelay + jitter));
15151
+ await new Promise((resolve21) => setTimeout(resolve21, baseDelay + jitter));
14374
15152
  }
14375
15153
  }
14376
15154
  throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
@@ -15180,7 +15958,7 @@ var init_terminal_capabilities = __esm({
15180
15958
 
15181
15959
  // packages/experience-design-system-cli-legacy/src/apply/command.ts
15182
15960
  import { createElement as createElement2 } from "react";
15183
- import { readFile as readFile12, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15961
+ import { readFile as readFile13, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15184
15962
  import { join as join16 } from "node:path";
15185
15963
  import {
15186
15964
  validateCDF,
@@ -15201,7 +15979,7 @@ function dieWithApiError(error) {
15201
15979
  async function readJsonFile(flag, p) {
15202
15980
  let text;
15203
15981
  try {
15204
- text = await readFile12(p, "utf8");
15982
+ text = await readFile13(p, "utf8");
15205
15983
  } catch {
15206
15984
  return await die2(`Error: file not found: ${p} (from ${flag})`);
15207
15985
  }
@@ -15255,7 +16033,7 @@ async function readTokensFromPath(flag, p) {
15255
16033
  for (const file of files.sort()) {
15256
16034
  let text;
15257
16035
  try {
15258
- text = await readFile12(file, "utf8");
16036
+ text = await readFile13(file, "utf8");
15259
16037
  } catch {
15260
16038
  continue;
15261
16039
  }
@@ -15624,11 +16402,11 @@ var init_command2 = __esm({
15624
16402
  });
15625
16403
 
15626
16404
  // packages/experience-design-system-cli-legacy/src/print/validate/validators/read-json-file.ts
15627
- import { readFile as readFile13 } from "node:fs/promises";
16405
+ import { readFile as readFile14 } from "node:fs/promises";
15628
16406
  async function readJsonFile2(filePath) {
15629
16407
  let content;
15630
16408
  try {
15631
- content = await readFile13(filePath, "utf-8");
16409
+ content = await readFile14(filePath, "utf-8");
15632
16410
  } catch (err) {
15633
16411
  return {
15634
16412
  ok: false,
@@ -16039,7 +16817,7 @@ var init_ValidateView = __esm({
16039
16817
  // packages/experience-design-system-cli-legacy/src/print/command.ts
16040
16818
  import { createElement as createElement3 } from "react";
16041
16819
  import { access as access3, mkdir as mkdir3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
16042
- import { basename as basename5, resolve as resolve12 } from "node:path";
16820
+ import { basename as basename5, resolve as resolve13 } from "node:path";
16043
16821
  import { CDF_SCHEMA_URL } from "@contentful/experience-design-system-types";
16044
16822
  async function die3(message) {
16045
16823
  process.stderr.write(`${message}
@@ -16106,7 +16884,7 @@ function registerPrintCommand(program) {
16106
16884
  "--allow-empty",
16107
16885
  "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."
16108
16886
  ).action(async (opts) => {
16109
- const outPath = resolve12(opts.out);
16887
+ const outPath = resolve13(opts.out);
16110
16888
  await assertOutIsNotDirectory(outPath);
16111
16889
  const sessionId2 = await resolveSession(opts.session, "generate components");
16112
16890
  await bindAnalyticsSessionId(sessionId2);
@@ -16154,7 +16932,7 @@ function registerPrintCommand(program) {
16154
16932
  `
16155
16933
  );
16156
16934
  }
16157
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16935
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16158
16936
  await writeFile3(outPath, `${JSON.stringify(cdfObj, null, 2)}
16159
16937
  `);
16160
16938
  process.stdout.write(
@@ -16163,7 +16941,7 @@ function registerPrintCommand(program) {
16163
16941
  );
16164
16942
  });
16165
16943
  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) => {
16166
- const outPath = resolve12(opts.out);
16944
+ const outPath = resolve13(opts.out);
16167
16945
  await assertOutIsNotDirectory(outPath);
16168
16946
  const sessionId2 = await resolveSession(opts.session, "generate tokens");
16169
16947
  await bindAnalyticsSessionId(sessionId2);
@@ -16178,7 +16956,7 @@ function registerPrintCommand(program) {
16178
16956
  await die3(`Error: no generated tokens in session '${sessionId2}'. Run generate tokens first.`);
16179
16957
  }
16180
16958
  const tree = rebuildDTCGTree(result.groups, result.tokens);
16181
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16959
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16182
16960
  await writeFile3(outPath, `${JSON.stringify(tree, null, 2)}
16183
16961
  `);
16184
16962
  process.stdout.write(
@@ -16327,7 +17105,7 @@ function MapTokensView({ result, onExit }) {
16327
17105
  const timer = setTimeout(onExit, 100);
16328
17106
  return () => clearTimeout(timer);
16329
17107
  }, [onExit]);
16330
- const summary = `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
17108
+ const summary = result.cached ? "reused from cache" : `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
16331
17109
  return /* @__PURE__ */ jsx8(
16332
17110
  CommandCompletionView,
16333
17111
  {
@@ -16461,8 +17239,8 @@ var init_resolve_defaults = __esm({
16461
17239
 
16462
17240
  // packages/experience-design-system-cli-legacy/src/map-tokens/command.ts
16463
17241
  import { createElement as createElement4 } from "react";
16464
- import { readFile as readFile14 } from "node:fs/promises";
16465
- import { resolve as resolve13 } from "node:path";
17242
+ import { readFile as readFile15 } from "node:fs/promises";
17243
+ import { resolve as resolve14 } from "node:path";
16466
17244
  async function renderResult(result) {
16467
17245
  if (process.stdout.isTTY) {
16468
17246
  const { waitUntilExit } = renderWithGoodbye(
@@ -16470,13 +17248,12 @@ async function renderResult(result) {
16470
17248
  );
16471
17249
  await waitUntilExit();
16472
17250
  } else {
16473
- process.stdout.write(
16474
- `map tokens complete
17251
+ const summary = result.cached ? "cached" : `${result.applied} mapping(s) applied`;
17252
+ process.stdout.write(`map tokens complete
16475
17253
  agent: ${result.agent}
16476
17254
  session=${result.sessionId}
16477
- ${result.applied} mapping(s) applied
16478
- `
16479
- );
17255
+ ${summary}
17256
+ `);
16480
17257
  await exitWithAnalytics(0);
16481
17258
  }
16482
17259
  }
@@ -16498,7 +17275,7 @@ async function runMapTokens(opts) {
16498
17275
  let mapPromptPath;
16499
17276
  if (mapPromptOverride?.kind === "text") mapPromptText = mapPromptOverride.value;
16500
17277
  if (mapPromptOverride?.kind === "path") {
16501
- mapPromptPath = resolve13(mapPromptOverride.value);
17278
+ mapPromptPath = resolve14(mapPromptOverride.value);
16502
17279
  try {
16503
17280
  await resolvePromptOverride(mapPromptOverride);
16504
17281
  } catch (error) {
@@ -16518,7 +17295,7 @@ async function runMapTokens(opts) {
16518
17295
  const knownTokenPaths = new Set(tokenLeaves.map((t) => t.path));
16519
17296
  const tokenMapDiagnostics = [];
16520
17297
  if (opts.tokenMap) {
16521
- const fileMappings = JSON.parse(await readFile14(opts.tokenMap, "utf8"));
17298
+ const fileMappings = JSON.parse(await readFile15(opts.tokenMap, "utf8"));
16522
17299
  const validMappings = {};
16523
17300
  for (const [rawName, path] of Object.entries(fileMappings)) {
16524
17301
  if (knownTokenPaths.has(path)) {
@@ -16596,7 +17373,7 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16596
17373
  let existingTokensInline;
16597
17374
  if (opts.existingEntitiesPath) {
16598
17375
  const existingContentfulEntities = await readExistingContentfulEntitiesFromSession(
16599
- resolve13(opts.existingEntitiesPath)
17376
+ resolve14(opts.existingEntitiesPath)
16600
17377
  );
16601
17378
  if (existingContentfulEntities) {
16602
17379
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
@@ -16631,10 +17408,30 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16631
17408
  skipAgent: "true"
16632
17409
  });
16633
17410
  updateStep(db, stepId2, "complete", { applied: "0", skipAgent: "true" });
16634
- await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0 });
17411
+ await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0, cached: false });
16635
17412
  return;
16636
17413
  }
16637
17414
  const agent = configuredAgent;
17415
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
17416
+ const promptHash = await hashPromptForSkill(
17417
+ "map-tokens",
17418
+ agent,
17419
+ model,
17420
+ void 0,
17421
+ existingTokensInline ? [hashContent(existingTokensInline)] : [],
17422
+ mapPromptText
17423
+ );
17424
+ const inputHash = computeMapTokensInputHash(db, sessionId2, componentSourceRefs);
17425
+ if (!noCache) {
17426
+ const cached = lookupCache(db, inputHash, "token_mapping", "__map_tokens__", promptHash);
17427
+ if (cached) {
17428
+ const appliedFromCache = copyMapTokensFromCache(db, cached.sourceSessionId, sessionId2);
17429
+ const stepId2 = createStep(db, sessionId2, "map tokens", { agent, model: model ?? "" });
17430
+ updateStep(db, stepId2, "complete", { cached: "true", applied: String(appliedFromCache) });
17431
+ await renderResult({ agent, sessionId: sessionId2, applied: appliedFromCache, cached: true });
17432
+ return;
17433
+ }
17434
+ }
16638
17435
  const binary = resolveAgentBinary(agent);
16639
17436
  if (!await assertBinaryInPath(binary)) {
16640
17437
  die(
@@ -16654,20 +17451,23 @@ Install it, choose another agent with --agent, or use --print-prompt to run the
16654
17451
  }
16655
17452
  const { calls, warnings: parseWarnings } = parseMapTokenPropToolCallLines(result.stdout);
16656
17453
  const { applied, warnings } = applyMapTokenPropCalls(db, sessionId2, calls, parseWarnings);
17454
+ if (!noCache) {
17455
+ storeCache(db, inputHash, "token_mapping", "__map_tokens__", sessionId2, false, promptHash);
17456
+ }
16657
17457
  updateStep(db, stepId, "complete", { applied: String(applied), warnings: String(warnings.length) });
16658
17458
  if (warnings.length > 0) {
16659
17459
  process.stderr.write(`Warnings:
16660
17460
  ${warnings.map((w) => ` ${w}`).join("\n")}
16661
17461
  `);
16662
17462
  }
16663
- await renderResult({ agent, sessionId: sessionId2, applied });
17463
+ await renderResult({ agent, sessionId: sessionId2, applied, cached: false });
16664
17464
  } finally {
16665
17465
  db.close();
16666
17466
  }
16667
17467
  }
16668
17468
  function registerMapTokensCommand(program) {
16669
17469
  const map = program.command("map", { hidden: true }).description("Suggest token restrictions for generated design-token props");
16670
- 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(
17470
+ 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(
16671
17471
  "--prompt <stage=value>",
16672
17472
  "Override a stage prompt (repeatable). Used here for the map-tokens stage.",
16673
17473
  (v, acc) => [...acc, v],
@@ -16687,6 +17487,7 @@ var init_command4 = __esm({
16687
17487
  init_render_with_goodbye();
16688
17488
  init_src();
16689
17489
  init_db();
17490
+ init_cache_keys();
16690
17491
  init_read_existing_contentful_entities_from_session();
16691
17492
  init_summarize_existing_contentful_entities();
16692
17493
  init_command3();
@@ -16703,7 +17504,7 @@ var init_command4 = __esm({
16703
17504
  });
16704
17505
 
16705
17506
  // packages/experience-design-system-cli-legacy/src/import/path-utils.ts
16706
- import { resolve as resolve14 } from "node:path";
17507
+ import { resolve as resolve15 } from "node:path";
16707
17508
  import { homedir as homedir5 } from "node:os";
16708
17509
  function expandTilde(input) {
16709
17510
  if (input === "~" || input.startsWith("~/") || input.startsWith("~\\")) {
@@ -16717,7 +17518,7 @@ function normalizePath(input) {
16717
17518
  p = p.slice(1, -1);
16718
17519
  }
16719
17520
  p = expandTilde(p);
16720
- return resolve14(p);
17521
+ return resolve15(p);
16721
17522
  }
16722
17523
  var init_path_utils = __esm({
16723
17524
  "packages/experience-design-system-cli-legacy/src/import/path-utils.ts"() {
@@ -16942,7 +17743,7 @@ var init_save_path_resolver = __esm({
16942
17743
  });
16943
17744
 
16944
17745
  // packages/experience-design-system-cli-legacy/src/runs/store.ts
16945
- import { readFile as readFile15, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
17746
+ import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
16946
17747
  import { join as join20 } from "node:path";
16947
17748
  import { homedir as homedir6 } from "node:os";
16948
17749
  import { randomBytes as randomBytes2 } from "node:crypto";
@@ -16982,7 +17783,7 @@ function migrateRecord(rec) {
16982
17783
  }
16983
17784
  async function readFileMaybe() {
16984
17785
  try {
16985
- const raw = await readFile15(RUNS_PATH, "utf8");
17786
+ const raw = await readFile16(RUNS_PATH, "utf8");
16986
17787
  const parsed = JSON.parse(raw);
16987
17788
  if (!READABLE_VERSIONS.has(parsed.version)) {
16988
17789
  throw new Error(
@@ -17046,15 +17847,15 @@ var init_store = __esm({
17046
17847
  });
17047
17848
 
17048
17849
  // packages/experience-design-system-cli-legacy/src/runs/fingerprint.ts
17049
- import { createHash as createHash2 } from "node:crypto";
17050
- import { readFile as readFile16, stat as stat4 } from "node:fs/promises";
17051
- import { resolve as resolve15 } from "node:path";
17850
+ import { createHash as createHash6 } from "node:crypto";
17851
+ import { readFile as readFile17, stat as stat4 } from "node:fs/promises";
17852
+ import { resolve as resolve16 } from "node:path";
17052
17853
  function rowField(row, key) {
17053
17854
  const v = row[key];
17054
17855
  return typeof v === "string" ? v : null;
17055
17856
  }
17056
17857
  function sha256Hex(buf) {
17057
- return createHash2("sha256").update(buf).digest("hex");
17858
+ return createHash6("sha256").update(buf).digest("hex");
17058
17859
  }
17059
17860
  async function buildSourceFingerprint(opts) {
17060
17861
  const rows = opts.db.prepare("SELECT name, source_path FROM raw_components WHERE session_id = ?").all(opts.extractSessionId);
@@ -17064,7 +17865,7 @@ async function buildSourceFingerprint(opts) {
17064
17865
  const sourcePath = rowField(row, "source_path");
17065
17866
  const name = rowField(row, "name");
17066
17867
  if (!sourcePath) continue;
17067
- const abs = resolve15(sourcePath);
17868
+ const abs = resolve16(sourcePath);
17068
17869
  if (seen.has(abs)) continue;
17069
17870
  seen.add(abs);
17070
17871
  try {
@@ -17079,10 +17880,10 @@ async function buildSourceFingerprint(opts) {
17079
17880
  let rawTokensMtime = null;
17080
17881
  let rawTokensContentHash = null;
17081
17882
  if (opts.rawTokensPath) {
17082
- const abs = resolve15(opts.rawTokensPath);
17883
+ const abs = resolve16(opts.rawTokensPath);
17083
17884
  rawTokensPath = abs;
17084
17885
  try {
17085
- const [st, buf] = await Promise.all([stat4(abs), readFile16(abs)]);
17886
+ const [st, buf] = await Promise.all([stat4(abs), readFile17(abs)]);
17086
17887
  rawTokensMtime = st.mtime.toISOString();
17087
17888
  rawTokensContentHash = sha256Hex(buf);
17088
17889
  } catch {
@@ -18837,11 +19638,11 @@ var init_slot_graph = __esm({
18837
19638
  });
18838
19639
 
18839
19640
  // packages/experience-design-system-cli-legacy/src/analyze/select/parser.ts
18840
- import { createHash as createHash3 } from "node:crypto";
19641
+ import { createHash as createHash7 } from "node:crypto";
18841
19642
  import { access as access5 } from "node:fs/promises";
18842
- import { isAbsolute, relative, resolve as resolve16 } from "node:path";
19643
+ import { isAbsolute, relative, resolve as resolve17 } from "node:path";
18843
19644
  function createComponentId(name, resolvedSourcePath) {
18844
- const sourceHash = createHash3("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
19645
+ const sourceHash = createHash7("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
18845
19646
  return `${name}-${sourceHash}`;
18846
19647
  }
18847
19648
  async function resolveComponentSourcePath(source, reviewRoot) {
@@ -18853,7 +19654,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18853
19654
  throw new Error(`Unable to access component source at ${source}`);
18854
19655
  }
18855
19656
  }
18856
- const candidate = resolve16(reviewRoot, source);
19657
+ const candidate = resolve17(reviewRoot, source);
18857
19658
  const relativeToRoot = relative(reviewRoot, candidate);
18858
19659
  if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || isAbsolute(relativeToRoot)) {
18859
19660
  throw new Error(
@@ -18868,7 +19669,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18868
19669
  }
18869
19670
  }
18870
19671
  async function loadReviewInput(components, options = {}) {
18871
- const reviewRoot = resolve16(options.reviewRoot ?? process.cwd());
19672
+ const reviewRoot = resolve17(options.reviewRoot ?? process.cwd());
18872
19673
  const records = await Promise.all(
18873
19674
  components.map(async (component) => {
18874
19675
  let resolvedSourcePath;
@@ -18899,7 +19700,7 @@ var init_parser = __esm({
18899
19700
  });
18900
19701
 
18901
19702
  // packages/experience-design-system-cli-legacy/src/analyze/select/command.ts
18902
- import { access as access6, readFile as readFile17 } from "node:fs/promises";
19703
+ import { access as access6, readFile as readFile18 } from "node:fs/promises";
18903
19704
  async function loadAndValidateForReview(sessionId2, projectRoot) {
18904
19705
  const db = openPipelineDb();
18905
19706
  let rawComponents;
@@ -18917,7 +19718,7 @@ async function patchReviewStateWithValidationErrors(sessionId2, errors, opts = {
18917
19718
  let snapshot;
18918
19719
  try {
18919
19720
  await access6(paths.statePath);
18920
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19721
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18921
19722
  } catch {
18922
19723
  snapshot = await loadAndValidateForReview(sessionId2, void 0);
18923
19724
  snapshot = await ensureRefineSession(sessionId2, artifactsRoot, snapshot);
@@ -18968,7 +19769,7 @@ async function rejectComponentsByName(sessionId2, names, opts = {}) {
18968
19769
  const nameSet = new Set(names);
18969
19770
  let snapshot;
18970
19771
  try {
18971
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19772
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18972
19773
  } catch {
18973
19774
  return;
18974
19775
  }
@@ -19067,9 +19868,9 @@ function spawnGenerateChild(opts) {
19067
19868
  stderr += chunk;
19068
19869
  opts.onStderr?.(chunk);
19069
19870
  });
19070
- const donePromise = new Promise((resolve20) => {
19871
+ const donePromise = new Promise((resolve21) => {
19071
19872
  child.on("close", (code, signal) => {
19072
- resolve20({
19873
+ resolve21({
19073
19874
  exitCode: code ?? 0,
19074
19875
  signal: signal ?? null,
19075
19876
  stdout,
@@ -19077,7 +19878,7 @@ function spawnGenerateChild(opts) {
19077
19878
  });
19078
19879
  });
19079
19880
  child.on("error", (err) => {
19080
- resolve20({
19881
+ resolve21({
19081
19882
  exitCode: 1,
19082
19883
  signal: null,
19083
19884
  stdout,
@@ -27215,6 +28016,9 @@ function shouldGenerateAfterCredentialsValidated(opts) {
27215
28016
  function shouldSkipFinalReviewAfterCredentials(state) {
27216
28017
  return state.finalReviewPassed && state.generateSessionId != null;
27217
28018
  }
28019
+ function resolveNoCacheForGenerate(opts) {
28020
+ return opts.cliNoCache;
28021
+ }
27218
28022
  function shouldBypassPreview(state) {
27219
28023
  return state.credentialsSkipped === true;
27220
28024
  }
@@ -27241,43 +28045,70 @@ var init_wizard_state_transitions = __esm({
27241
28045
  async function runSelectionAgent(options) {
27242
28046
  const db = openPipelineDb();
27243
28047
  let components;
28048
+ const cachedDecisions = /* @__PURE__ */ new Map();
28049
+ const cliVersion2 = await getCliCacheVersion();
28050
+ const promptHash = await hashPromptForSkill(
28051
+ "select",
28052
+ options.agent,
28053
+ options.model,
28054
+ options.promptPath,
28055
+ [SELECT_PAYLOAD_FORMAT],
28056
+ options.promptText
28057
+ );
27244
28058
  try {
27245
28059
  components = loadRawComponents(db, options.sessionId);
28060
+ if (!options.noCache) {
28061
+ for (const component of components) {
28062
+ const componentHash = hashComponentShape(component);
28063
+ const cached = lookupSelectCache(db, componentHash, promptHash, cliVersion2);
28064
+ if (cached) {
28065
+ cachedDecisions.set(component.name, {
28066
+ decision: cached.decision,
28067
+ reason: cached.reason
28068
+ });
28069
+ }
28070
+ }
28071
+ }
27246
28072
  } finally {
27247
28073
  db.close();
27248
28074
  }
27249
28075
  if (components.length === 0) return;
27250
- const invoker2 = createLocalCliAgentInvoker({
27251
- onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
27252
- });
27253
- const selectBuckets = bucketComponentsProps(components);
27254
- for (const assignment of selectBuckets) {
27255
- getDebugLogger().event("analyze", "prop-buckets.select", { ...assignment });
27256
- }
27257
- const prompt = await buildPrompt({
27258
- skill: "select",
27259
- mode: "autonomous",
27260
- rawComponentsInline: JSON.stringify(
27261
- components.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
27262
- null,
27263
- 2
27264
- ),
27265
- propBucketsInline: JSON.stringify(selectBuckets),
27266
- outDir: process.cwd(),
27267
- ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
27268
- ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
27269
- });
27270
- const { result } = await invokeAgentWithOutput(
27271
- invoker2,
27272
- { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
27273
- false
27274
- );
27275
- if (result.timedOut) throw new Error("selection agent timed out");
27276
- if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
27277
- const parsed = parseSelectToolCallLines(result.stdout);
27278
- for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
28076
+ const componentsToRun = components.filter((component) => !cachedDecisions.has(component.name));
28077
+ let calls = [];
28078
+ if (componentsToRun.length > 0) {
28079
+ const invoker2 = createLocalCliAgentInvoker({
28080
+ onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
28081
+ });
28082
+ const selectBuckets = bucketComponentsProps(componentsToRun);
28083
+ for (const assignment of selectBuckets) {
28084
+ getDebugLogger().event("analyze", "prop-buckets.select", { ...assignment });
28085
+ }
28086
+ const prompt = await buildPrompt({
28087
+ skill: "select",
28088
+ mode: "autonomous",
28089
+ rawComponentsInline: JSON.stringify(
28090
+ componentsToRun.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
28091
+ null,
28092
+ 2
28093
+ ),
28094
+ propBucketsInline: JSON.stringify(selectBuckets),
28095
+ outDir: process.cwd(),
28096
+ ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
28097
+ ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
28098
+ });
28099
+ const { result } = await invokeAgentWithOutput(
28100
+ invoker2,
28101
+ { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
28102
+ false
28103
+ );
28104
+ if (result.timedOut) throw new Error("selection agent timed out");
28105
+ if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
28106
+ const parsed = parseSelectToolCallLines(result.stdout);
28107
+ for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
27279
28108
  `);
27280
- const byName = new Map(parsed.calls.map((call) => [call.name, call]));
28109
+ calls = parsed.calls;
28110
+ }
28111
+ const byName = new Map(calls.map((call) => [call.name, call]));
27281
28112
  const writeDb = openPipelineDb();
27282
28113
  try {
27283
28114
  writeDb.exec("BEGIN");
@@ -27285,15 +28116,27 @@ async function runSelectionAgent(options) {
27285
28116
  `UPDATE raw_components SET status = ?, reject_reason = ? WHERE session_id = ? AND component_id = ?`
27286
28117
  );
27287
28118
  for (const component of components) {
28119
+ const cached = cachedDecisions.get(component.name);
27288
28120
  const call = byName.get(component.name);
27289
- if (!call) continue;
27290
- const rejected = call.tool === "reject_component";
28121
+ if (!cached && !call) continue;
28122
+ const rejected = cached ? cached.decision === "rejected" : call.tool === "reject_component";
28123
+ const reason = cached ? cached.reason : call.reason ?? null;
27291
28124
  update.run(
27292
28125
  rejected ? "rejected" : "accepted",
27293
- rejected ? call.reason ?? null : null,
28126
+ rejected ? reason : null,
27294
28127
  options.sessionId,
27295
28128
  component.component_id
27296
28129
  );
28130
+ if (!cached) {
28131
+ storeSelectCache(
28132
+ writeDb,
28133
+ hashComponentShape(component),
28134
+ promptHash,
28135
+ cliVersion2,
28136
+ rejected ? "rejected" : "accepted",
28137
+ rejected ? reason : null
28138
+ );
28139
+ }
27297
28140
  }
27298
28141
  writeDb.exec("COMMIT");
27299
28142
  } catch (error) {
@@ -27303,7 +28146,7 @@ async function runSelectionAgent(options) {
27303
28146
  writeDb.close();
27304
28147
  }
27305
28148
  }
27306
- var DEFAULT_TIMEOUT_MS4;
28149
+ var SELECT_PAYLOAD_FORMAT, DEFAULT_TIMEOUT_MS4;
27307
28150
  var init_run_selection_agent = __esm({
27308
28151
  "packages/experience-design-system-cli-legacy/src/import/tui/run-selection-agent.ts"() {
27309
28152
  "use strict";
@@ -27312,6 +28155,8 @@ var init_run_selection_agent = __esm({
27312
28155
  init_debug_logger();
27313
28156
  init_agent_output();
27314
28157
  init_db();
28158
+ init_cache_keys();
28159
+ SELECT_PAYLOAD_FORMAT = "prop-buckets-v1";
27315
28160
  DEFAULT_TIMEOUT_MS4 = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 5 * 60 * 1e3);
27316
28161
  }
27317
28162
  });
@@ -27330,7 +28175,7 @@ __export(WizardApp_exports, {
27330
28175
  });
27331
28176
  import { useEffect as useEffect15, useRef as useRef10, useState as useState23 } from "react";
27332
28177
  import { Box as Box42, Text as Text46 } from "ink";
27333
- import { join as join23, resolve as resolve17 } from "node:path";
28178
+ import { join as join23, resolve as resolve18 } from "node:path";
27334
28179
  import { appendFileSync as appendFileSync2, writeFileSync } from "node:fs";
27335
28180
  import { access as access7, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
27336
28181
  import { tmpdir } from "node:os";
@@ -27343,21 +28188,28 @@ function buildGenerateComponentsArgs(opts) {
27343
28188
  if (opts.tokensPath) args.push("--tokens", opts.tokensPath);
27344
28189
  if (opts.model) args.push("--model", opts.model);
27345
28190
  if (opts.bedrock) args.push("--bedrock");
28191
+ if (opts.noCache) args.push("--no-cache");
27346
28192
  if (opts.generatePromptPath) args.push("--generate-prompt-path", opts.generatePromptPath);
27347
28193
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27348
28194
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
28195
+ if (opts.restoreCache) args.push("--restore-cache");
28196
+ if (opts.cachedComponents && opts.cachedComponents.length > 0) {
28197
+ args.push("--cached-components", JSON.stringify(opts.cachedComponents));
28198
+ }
27349
28199
  return args;
27350
28200
  }
27351
28201
  function buildGenerateTokensArgs(opts) {
27352
28202
  const args = [findCliPath(), "__generate", "tokens", "--agent", opts.agent, "--raw-tokens", opts.rawTokensPath];
27353
28203
  if (opts.model) args.push("--model", opts.model);
27354
28204
  if (opts.bedrock) args.push("--bedrock");
28205
+ if (opts.noCache) args.push("--no-cache");
27355
28206
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27356
28207
  return args;
27357
28208
  }
27358
28209
  function buildMapTokensArgs(opts) {
27359
28210
  const args = ["map", "tokens", "--session", opts.sessionId, "--agent", opts.agent];
27360
28211
  if (opts.model) args.push("--model", opts.model);
28212
+ if (opts.noCache) args.push("--no-cache");
27361
28213
  if (opts.skipAgent) args.push("--skip-agent");
27362
28214
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27363
28215
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
@@ -27449,6 +28301,7 @@ function WizardApp({
27449
28301
  initialProjectPath,
27450
28302
  host,
27451
28303
  promptOverrides,
28304
+ noCache = false,
27452
28305
  livePreview = true,
27453
28306
  generatePromptPath,
27454
28307
  skipMapTokens = false,
@@ -27473,6 +28326,7 @@ function WizardApp({
27473
28326
  });
27474
28327
  const allowEmptyDeleteAllRef = useRef10(false);
27475
28328
  const generateChildRef = useRef10(null);
28329
+ const generationCachePreflightRef = useRef10(null);
27476
28330
  const selectionToReviewStartedAtRef = useRef10(null);
27477
28331
  const credentialsValidationIdRef = useRef10(0);
27478
28332
  const pendingCredentialValidationRef = useRef10(null);
@@ -27487,8 +28341,9 @@ function WizardApp({
27487
28341
  const existingEntitiesPromiseRef = useRef10(null);
27488
28342
  const generatePromiseRef = useRef10(null);
27489
28343
  const rawTokensEntryReady = !!initialRawTokensPath;
28344
+ const effectiveNoCache = resolveNoCacheForGenerate({ cliNoCache: noCache });
27490
28345
  const initialStepResolved = rawTokensEntryReady ? initialProjectPath ? "path-validation" : "credentials" : initialProjectPath ? "token-input" : "welcome";
27491
- const initialOutDir = initialProjectPath ? join23(resolve17(initialProjectPath), ".contentful") : "";
28346
+ const initialOutDir = initialProjectPath ? join23(resolve18(initialProjectPath), ".contentful") : "";
27492
28347
  const [state, setState] = useState23({
27493
28348
  step: initialStepResolved,
27494
28349
  agent: initialAgent ?? "claude",
@@ -27664,13 +28519,14 @@ If you are using AWS Bedrock, run:
27664
28519
  if (!deferStep) update({ step: nextStep });
27665
28520
  return true;
27666
28521
  };
27667
- const runGenerateTokens = async (rawTokensPath, outDir) => {
28522
+ const runGenerateTokens = async (rawTokensPath, outDir, forceRegenerate = false) => {
27668
28523
  update({ tokenGenerationStatus: "running" });
27669
28524
  const tokenArgs = buildGenerateTokensArgs({
27670
28525
  rawTokensPath,
27671
28526
  agent: state.agent,
27672
28527
  ...state.agentModel ? { model: state.agentModel } : {},
27673
28528
  ...state.bedrock ? { bedrock: true } : {},
28529
+ noCache: effectiveNoCache || forceRegenerate,
27674
28530
  promptOverrides
27675
28531
  });
27676
28532
  const result = await runSpawnedCli(tokenArgs);
@@ -27719,6 +28575,7 @@ If you are using AWS Bedrock, run:
27719
28575
  const timer = startWizardTimer("token-mapping", {
27720
28576
  sessionId: sessionId2,
27721
28577
  silent,
28578
+ noCache: effectiveNoCache,
27722
28579
  skipAgent: skipMapTokens
27723
28580
  });
27724
28581
  let mappablePropCount = 0;
@@ -27727,7 +28584,7 @@ If you are using AWS Bedrock, run:
27727
28584
  const db = openPipelineDb();
27728
28585
  try {
27729
28586
  if (state.tokenSessionId && state.tokenSessionId !== sessionId2) {
27730
- copyTokensBetweenSessions(db, state.tokenSessionId, sessionId2);
28587
+ copyTokensFromCache(db, state.tokenSessionId, sessionId2);
27731
28588
  } else if (!state.tokenSessionId && state.tokensPath) {
27732
28589
  const tokens = await readTokensFromPath("tokens", state.tokensPath);
27733
28590
  storeDtcgTokens(db, sessionId2, [], tokens);
@@ -27767,6 +28624,7 @@ If you are using AWS Bedrock, run:
27767
28624
  sessionId: sessionId2,
27768
28625
  agent: state.agent,
27769
28626
  ...state.agentModel ? { model: state.agentModel } : {},
28627
+ noCache: effectiveNoCache,
27770
28628
  skipAgent: skipMapTokens,
27771
28629
  promptOverrides,
27772
28630
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {}
@@ -27846,7 +28704,7 @@ If you are using AWS Bedrock, run:
27846
28704
  return promise;
27847
28705
  };
27848
28706
  const runExtract = async (projectPath, showProgress = true) => {
27849
- const outDir = join23(resolve17(projectPath), ".contentful");
28707
+ const outDir = join23(resolve18(projectPath), ".contentful");
27850
28708
  const existingEntitiesExpected = !state.credentialsSkipped && Boolean(state.spaceId && state.environmentId && state.cmaToken && projectPath);
27851
28709
  update({
27852
28710
  ...showProgress ? { step: "extracting" } : {},
@@ -27861,6 +28719,7 @@ If you are using AWS Bedrock, run:
27861
28719
  });
27862
28720
  const existingEntitiesPromise = startExistingEntitiesFetch(projectPath, outDir);
27863
28721
  const extractArgs = [findCliPath(), "__extract", "--project", projectPath];
28722
+ if (effectiveNoCache) extractArgs.push("--no-cache");
27864
28723
  let selectionPromptText;
27865
28724
  let selectionPromptPath;
27866
28725
  try {
@@ -27869,7 +28728,7 @@ If you are using AWS Bedrock, run:
27869
28728
  const selectOverride = overrides.get("select");
27870
28729
  if (selectOverride?.kind === "text") selectionPromptText = selectOverride.value;
27871
28730
  if (selectOverride?.kind === "path") {
27872
- selectionPromptPath = resolve17(selectOverride.value);
28731
+ selectionPromptPath = resolve18(selectOverride.value);
27873
28732
  await resolvePromptOverride(selectOverride);
27874
28733
  }
27875
28734
  } catch (error) {
@@ -27893,7 +28752,8 @@ If you are using AWS Bedrock, run:
27893
28752
  agent: state.agent,
27894
28753
  ...state.agentModel ? { model: state.agentModel } : {},
27895
28754
  ...selectionPromptText !== void 0 ? { promptText: selectionPromptText } : {},
27896
- ...selectionPromptPath ? { promptPath: selectionPromptPath } : {}
28755
+ ...selectionPromptPath ? { promptPath: selectionPromptPath } : {},
28756
+ noCache: effectiveNoCache
27897
28757
  });
27898
28758
  })().catch((error) => {
27899
28759
  selectionError = error;
@@ -28038,17 +28898,136 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28038
28898
  generateProgress: null
28039
28899
  }));
28040
28900
  };
28041
- const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2) => ({
28901
+ const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => ({
28042
28902
  sessionId: extractSessionId,
28043
28903
  tokensPath,
28044
28904
  agent: state.agent,
28045
28905
  ...state.agentModel ? { model: state.agentModel } : {},
28046
28906
  ...state.bedrock ? { bedrock: true } : {},
28907
+ noCache: effectiveNoCache,
28047
28908
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {},
28048
28909
  ...generatePromptPath2 ? { generatePromptPath: generatePromptPath2 } : {},
28049
- promptOverrides
28910
+ promptOverrides,
28911
+ ...cachedComponents && cachedComponents.length > 0 ? { cachedComponents } : {}
28050
28912
  });
28051
- const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2));
28913
+ const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents));
28914
+ const buildGenerateCliArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateComponentsArgs(
28915
+ buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents)
28916
+ );
28917
+ const checkGenerateCache = async (extractSessionId, tokensPath) => {
28918
+ const timer = startWizardTimer("generation-cache-status", {
28919
+ sessionId: extractSessionId,
28920
+ noCache: effectiveNoCache
28921
+ });
28922
+ if (effectiveNoCache) {
28923
+ finishWizardTimer("generation-cache-status", timer, { status: "skipped" });
28924
+ return false;
28925
+ }
28926
+ const result = await runCli([...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"]);
28927
+ const hit = result.exitCode === 0 && /^cache-status=hit$/m.test(result.stdout);
28928
+ finishWizardTimer("generation-cache-status", timer, {
28929
+ status: hit ? "hit" : "miss",
28930
+ exitCode: result.exitCode,
28931
+ ...result.exitCode !== 0 ? { stderrTail: result.stderr.trim().slice(-500) } : {}
28932
+ });
28933
+ return hit;
28934
+ };
28935
+ const checkGenerateCacheComponents = async (extractSessionId, tokensPath, restoreCache = false) => {
28936
+ const timer = startWizardTimer("generation-cache-components", {
28937
+ sessionId: extractSessionId,
28938
+ restoreCache,
28939
+ noCache: effectiveNoCache
28940
+ });
28941
+ if (effectiveNoCache) {
28942
+ finishWizardTimer("generation-cache-components", timer, { status: "skipped" });
28943
+ return /* @__PURE__ */ new Set();
28944
+ }
28945
+ const args = [...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"];
28946
+ if (restoreCache) args.push("--restore-cache");
28947
+ const result = await runCli(args);
28948
+ if (result.exitCode !== 0) {
28949
+ finishWizardTimer("generation-cache-components", timer, {
28950
+ status: "error",
28951
+ exitCode: result.exitCode,
28952
+ stderrTail: result.stderr.trim().slice(-500)
28953
+ });
28954
+ return /* @__PURE__ */ new Set();
28955
+ }
28956
+ const line = /^cache-components=(.+)$/m.exec(result.stdout)?.[1];
28957
+ if (!line) {
28958
+ finishWizardTimer("generation-cache-components", timer, {
28959
+ status: "missing-output",
28960
+ exitCode: result.exitCode,
28961
+ cachedCount: 0
28962
+ });
28963
+ return /* @__PURE__ */ new Set();
28964
+ }
28965
+ try {
28966
+ const names = JSON.parse(line);
28967
+ const cachedNames = new Set(
28968
+ Array.isArray(names) ? names.filter((name) => typeof name === "string") : []
28969
+ );
28970
+ finishWizardTimer("generation-cache-components", timer, {
28971
+ status: "complete",
28972
+ exitCode: result.exitCode,
28973
+ cachedCount: cachedNames.size
28974
+ });
28975
+ return cachedNames;
28976
+ } catch {
28977
+ finishWizardTimer("generation-cache-components", timer, {
28978
+ status: "invalid-output",
28979
+ exitCode: result.exitCode,
28980
+ cachedCount: 0
28981
+ });
28982
+ return /* @__PURE__ */ new Set();
28983
+ }
28984
+ };
28985
+ useEffect15(() => {
28986
+ 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) {
28987
+ return;
28988
+ }
28989
+ const sessionId2 = state.extractSessionId;
28990
+ if (generationCachePreflightRef.current?.sessionId === sessionId2) return;
28991
+ const timer = startWizardTimer("generation-cache-preflight", { sessionId: sessionId2 });
28992
+ const db = openPipelineDb();
28993
+ let generatedNames = null;
28994
+ try {
28995
+ const rows = db.prepare("SELECT name, status FROM raw_components WHERE session_id = ?").all(sessionId2);
28996
+ if (rows.length > 0 && rows.every((row) => row.status === "generated")) {
28997
+ generatedNames = new Set(rows.map((row) => row.name));
28998
+ }
28999
+ } finally {
29000
+ db.close();
29001
+ }
29002
+ if (generatedNames) {
29003
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: Promise.resolve(generatedNames) };
29004
+ finishWizardTimer("generation-cache-preflight", timer, {
29005
+ status: "complete",
29006
+ source: "session-db",
29007
+ cachedCount: generatedNames.size
29008
+ });
29009
+ return;
29010
+ }
29011
+ const promise = checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
29012
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise };
29013
+ void promise.then((cachedNames) => {
29014
+ finishWizardTimer("generation-cache-preflight", timer, {
29015
+ status: "complete",
29016
+ source: "cache-status",
29017
+ cachedCount: cachedNames.size
29018
+ });
29019
+ });
29020
+ }, [
29021
+ state.step,
29022
+ state.extractSessionId,
29023
+ state.tokensPath,
29024
+ state.rawTokensPath,
29025
+ state.tokenGenerationStatus,
29026
+ state.selectionAgentStatus,
29027
+ state.compositionPhase,
29028
+ state.existingEntitiesStatus,
29029
+ state.existingEntitiesPath
29030
+ ]);
28052
29031
  const startGeneratePrefetch = (extractSessionId, tokensPath) => {
28053
29032
  const args = buildGenerateArgs(extractSessionId, tokensPath);
28054
29033
  let progressCursor = null;
@@ -28103,13 +29082,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28103
29082
  });
28104
29083
  return donePromise;
28105
29084
  };
28106
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false) => {
29085
+ const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false, cachedComponents = []) => {
28107
29086
  const timer = startWizardTimer("component-generation", {
28108
29087
  sessionId: extractSessionId,
28109
29088
  acceptedCount,
28110
- suppressScreen
29089
+ suppressScreen,
29090
+ noCache: effectiveNoCache
28111
29091
  });
28112
- const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath);
29092
+ const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath, cachedComponents);
28113
29093
  let progressCursor = state.generateProgress;
28114
29094
  const { donePromise } = spawnGenerateChild({
28115
29095
  command: "node",
@@ -28179,6 +29159,25 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28179
29159
  finishSelectionToReviewTimer("error");
28180
29160
  }
28181
29161
  };
29162
+ const finishCachedGeneration = async (sessionId2, acceptedCount) => {
29163
+ if (await runMapTokens2(sessionId2, true)) {
29164
+ finishSelectionToReviewTimer("cached");
29165
+ update(
29166
+ {
29167
+ acceptedCount,
29168
+ autoRejectedCount: 0,
29169
+ generateSessionId: sessionId2,
29170
+ generatedCount: acceptedCount,
29171
+ generateProgress: null,
29172
+ mapTokensStatus: "complete",
29173
+ step: "final-review"
29174
+ },
29175
+ { clearScreen: false }
29176
+ );
29177
+ } else {
29178
+ finishSelectionToReviewTimer("error");
29179
+ }
29180
+ };
28182
29181
  const advanceToPushFlow = (generatedAcceptedCount) => {
28183
29182
  update({ generatedAcceptedCount });
28184
29183
  void runSaveAndPush();
@@ -28363,8 +29362,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28363
29362
  }
28364
29363
  }
28365
29364
  if (await runAgentAuthCheck("generating", true)) {
28366
- update({ step: "generating" });
28367
- void runGenerate(sid, state.tokensPath, state.acceptedCount);
29365
+ const cacheHit = await checkGenerateCache(sid, state.tokensPath);
29366
+ if (cacheHit) {
29367
+ update({ step: "generating" });
29368
+ void finishCachedGeneration(sid, state.acceptedCount);
29369
+ } else {
29370
+ update({ step: "generating" });
29371
+ void runGenerate(sid, state.tokensPath, state.acceptedCount);
29372
+ }
28368
29373
  }
28369
29374
  return;
28370
29375
  }
@@ -28728,7 +29733,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28728
29733
  });
28729
29734
  return { ok: false };
28730
29735
  }
28731
- await new Promise((resolve20) => setTimeout(resolve20, SAVE_CONFIRMATION_DELAY_MS));
29736
+ await new Promise((resolve21) => setTimeout(resolve21, SAVE_CONFIRMATION_DELAY_MS));
28732
29737
  update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
28733
29738
  return { ok: true };
28734
29739
  };
@@ -28805,6 +29810,12 @@ If using a custom host configuration, make sure the space exists on that host.`
28805
29810
  tokenReuseChecked.current = true;
28806
29811
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28807
29812
  const existingTokensPath = join23(state.outDir, "tokens.json");
29813
+ if (effectiveNoCache) {
29814
+ tokenGenerationPromiseRef.current = runAgentAuthCheck(tokenNextStep).then(
29815
+ (ok2) => ok2 ? runGenerateTokens(state.rawTokensPath, state.outDir) : false
29816
+ );
29817
+ return;
29818
+ }
28808
29819
  (async () => {
28809
29820
  try {
28810
29821
  await access7(existingTokensPath);
@@ -28888,7 +29899,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28888
29899
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28889
29900
  update({ step: tokenNextStep });
28890
29901
  if (await runAgentAuthCheck(tokenNextStep)) {
28891
- tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir);
29902
+ tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir, true);
28892
29903
  }
28893
29904
  },
28894
29905
  onQuit: () => process.exit(0)
@@ -28987,6 +29998,29 @@ If using a custom host configuration, make sure the space exists on that host.`
28987
29998
  acceptedCount: decisions.accepted.length,
28988
29999
  rejectedCount: decisions.rejected.length
28989
30000
  }) : null;
30001
+ const preflight = generationCachePreflightRef.current;
30002
+ const cachePromise = preflight?.sessionId === sessionId2 ? preflight.promise : checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
30003
+ if (!preflight || preflight.sessionId !== sessionId2) {
30004
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: cachePromise };
30005
+ }
30006
+ const cacheWaitStartedAt = startWizardTimer("selection-cache-restoration-wait", {
30007
+ sessionId: sessionId2,
30008
+ preflight: Boolean(preflight?.sessionId === sessionId2)
30009
+ });
30010
+ try {
30011
+ const cachedNames = await cachePromise;
30012
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30013
+ status: "complete",
30014
+ cachedCount: cachedNames.size
30015
+ });
30016
+ } catch (error) {
30017
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30018
+ status: "error",
30019
+ error: error instanceof Error ? error.message : String(error)
30020
+ });
30021
+ finishSelectionToReviewTimer("error");
30022
+ throw error;
30023
+ }
28990
30024
  await runScopeGate({
28991
30025
  sessionId: sessionId2,
28992
30026
  decisions,
@@ -28995,12 +30029,35 @@ If using a custom host configuration, make sure the space exists on that host.`
28995
30029
  if (shouldGenerateAfterScopeGate({ acceptedCount })) {
28996
30030
  const authPromise = runAgentAuthCheck("generating", true);
28997
30031
  if (await authPromise) {
28998
- update({
28999
- step: "generating",
29000
- generateProgress: null,
29001
- acceptedCount
30032
+ const comparisonStartedAt = startWizardTimer("generation-cache-set-comparison", {
30033
+ sessionId: sid,
30034
+ acceptedCount: decisions?.accepted.length ?? 0
30035
+ });
30036
+ let cachedNames = await cachePromise;
30037
+ const acceptedNames = decisions?.accepted ?? [];
30038
+ let cacheHit = !effectiveNoCache && acceptedNames.length > 0 && acceptedNames.every((name) => cachedNames.has(name));
30039
+ if (!cacheHit && !effectiveNoCache && acceptedNames.length > 0) {
30040
+ cachedNames = await checkGenerateCacheComponents(sid, state.tokensPath, true);
30041
+ cacheHit = acceptedNames.every((name) => cachedNames.has(name));
30042
+ }
30043
+ finishWizardTimer("generation-cache-set-comparison", comparisonStartedAt, {
30044
+ status: "complete",
30045
+ cacheHit,
30046
+ cachedCount: cachedNames.size,
30047
+ acceptedCount: acceptedNames.length
29002
30048
  });
29003
- void runGenerate(sid, state.tokensPath, acceptedCount);
30049
+ if (cacheHit) {
30050
+ void finishCachedGeneration(sid, acceptedCount);
30051
+ } else {
30052
+ const acceptedNameSet = new Set(acceptedNames);
30053
+ const restoredCachedNames = [...cachedNames].filter((name) => acceptedNameSet.has(name));
30054
+ update({
30055
+ step: "generating",
30056
+ generateProgress: null,
30057
+ acceptedCount
30058
+ });
30059
+ void runGenerate(sid, state.tokensPath, acceptedCount, false, restoredCachedNames);
30060
+ }
29004
30061
  } else {
29005
30062
  finishSelectionToReviewTimer("error");
29006
30063
  }
@@ -29366,7 +30423,7 @@ function registerImportCommand(program) {
29366
30423
  "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",
29367
30424
  (v, acc) => [...acc, v],
29368
30425
  []
29369
- ).action(async (opts) => {
30426
+ ).option("--no-cache", "Re-run all steps even if output already exists").action(async (opts) => {
29370
30427
  const interactiveTerminalSupported = getInteractiveTerminalSupport().supported;
29371
30428
  if (opts.tokens !== void 0) {
29372
30429
  const { access: access9 } = await import("node:fs/promises");
@@ -29403,6 +30460,7 @@ function registerImportCommand(program) {
29403
30460
  ...resolvedModel ? { initialModel: resolvedModel } : {},
29404
30461
  initialProjectPath: opts.project !== "." ? normalizePath(opts.project) : void 0,
29405
30462
  ...buildCompositionForwardingOptions(opts),
30463
+ noCache: opts.cache === false,
29406
30464
  skipMapTokens: false,
29407
30465
  livePreview: true,
29408
30466
  generatePromptPath: creds.generatePromptPath,
@@ -29490,7 +30548,7 @@ async function pathExists3(path) {
29490
30548
  return access8(path).then(() => true).catch(() => false);
29491
30549
  }
29492
30550
  function runSpawn(cmd, args, opts = {}) {
29493
- return new Promise((resolve20) => {
30551
+ return new Promise((resolve21) => {
29494
30552
  const child = spawn6(cmd, args, {
29495
30553
  cwd: opts.cwd,
29496
30554
  env: opts.env ?? process.env,
@@ -29502,7 +30560,7 @@ function runSpawn(cmd, args, opts = {}) {
29502
30560
  const settle = (result) => {
29503
30561
  if (settled) return;
29504
30562
  settled = true;
29505
- resolve20(result);
30563
+ resolve21(result);
29506
30564
  };
29507
30565
  child.on("error", (err) => settle({ exitCode: 1, stdout: "", stderr: err.message }));
29508
30566
  child.stdout.on("data", (d) => {
@@ -30703,13 +31761,13 @@ function PrerequisitesScreen({
30703
31761
  const driver = {
30704
31762
  ...base,
30705
31763
  emit: (event) => setEvents((current) => [...current, event]),
30706
- confirm: (question, defaultYes = true) => new Promise((resolve20) => {
31764
+ confirm: (question, defaultYes = true) => new Promise((resolve21) => {
30707
31765
  setConfirm({
30708
31766
  question,
30709
31767
  defaultYes,
30710
31768
  resolve: (answer) => {
30711
31769
  setConfirm(null);
30712
- resolve20(answer);
31770
+ resolve21(answer);
30713
31771
  }
30714
31772
  });
30715
31773
  })
@@ -30784,8 +31842,8 @@ function SetupScreen({
30784
31842
  useEffect21(() => {
30785
31843
  if (startedRef.current) return;
30786
31844
  startedRef.current = true;
30787
- const runScreen = (build) => new Promise((resolve20) => {
30788
- setScreen(build(resolve20));
31845
+ const runScreen = (build) => new Promise((resolve21) => {
31846
+ setScreen(build(resolve21));
30789
31847
  });
30790
31848
  void (async () => {
30791
31849
  const results = [];
@@ -31393,8 +32451,11 @@ var init_source_call_site_evidence = __esm({
31393
32451
  });
31394
32452
 
31395
32453
  // packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts
31396
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile18, stat as stat6 } from "node:fs/promises";
31397
- import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolve18 } from "node:path";
32454
+ import { mkdir as mkdir6, readdir as readdir3, readFile as readFile19, stat as stat6 } from "node:fs/promises";
32455
+ import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolve19 } from "node:path";
32456
+ function resolveExtractNoCache(opts) {
32457
+ return opts.noCache === true || opts.cache === false;
32458
+ }
31398
32459
  function isDenylistedNoiseFile(name) {
31399
32460
  return DENYLISTED_EXACT_FILE_NAMES.has(name) || DENYLISTED_FILE_NAME_PATTERNS.some((pattern) => pattern.test(name));
31400
32461
  }
@@ -31408,12 +32469,12 @@ async function retryDatabaseWrite(operation, attempts = 8) {
31408
32469
  } catch (error) {
31409
32470
  const message = error instanceof Error ? error.message : String(error);
31410
32471
  if (!/database is locked|database is busy/i.test(message) || attempt >= attempts) throw error;
31411
- await new Promise((resolve20) => setTimeout(resolve20, Math.min(250 * attempt, 2e3)));
32472
+ await new Promise((resolve21) => setTimeout(resolve21, Math.min(250 * attempt, 2e3)));
31412
32473
  }
31413
32474
  }
31414
32475
  }
31415
32476
  function resolveFromProjectRoot(projectRoot, inputPath) {
31416
- return isAbsolute2(inputPath) ? inputPath : resolve18(projectRoot, inputPath);
32477
+ return isAbsolute2(inputPath) ? inputPath : resolve19(projectRoot, inputPath);
31417
32478
  }
31418
32479
  async function pathExists4(path) {
31419
32480
  return Boolean(await stat6(path).catch(() => null));
@@ -31460,7 +32521,7 @@ async function readCandidateFiles(components, extraFiles = []) {
31460
32521
  await Promise.all(
31461
32522
  [...paths].map(async (p) => {
31462
32523
  try {
31463
- const content = await readFile18(p, "utf8");
32524
+ const content = await readFile19(p, "utf8");
31464
32525
  out.push({ path: p, content });
31465
32526
  } catch {
31466
32527
  }
@@ -31473,7 +32534,8 @@ function registerInternalExtractCommand(program) {
31473
32534
  "--resolve-unreachable <mode>",
31474
32535
  "Retry pass for unresolved Svelte Props types: 'auto' (default), 'always', or 'never'",
31475
32536
  "auto"
31476
- ).action(async (opts) => {
32537
+ ).option("--no-cache", "Re-run extraction even when all source files are unchanged").action(async (opts) => {
32538
+ const noCache = resolveExtractNoCache(opts);
31477
32539
  const resolveUnreachable = (() => {
31478
32540
  const v = opts.resolveUnreachable ?? "auto";
31479
32541
  if (v !== "auto" && v !== "always" && v !== "never") {
@@ -31483,7 +32545,7 @@ function registerInternalExtractCommand(program) {
31483
32545
  }
31484
32546
  return v;
31485
32547
  })();
31486
- const projectRoot = resolve18(opts.project);
32548
+ const projectRoot = resolve19(opts.project);
31487
32549
  const outDir = join27(projectRoot, ".contentful");
31488
32550
  let sourceDirectory;
31489
32551
  if (opts.dir !== void 0) {
@@ -31507,16 +32569,50 @@ function registerInternalExtractCommand(program) {
31507
32569
  process.stderr.write(`progress=scan-done:${sourceFiles.length}
31508
32570
  `);
31509
32571
  }
31510
- const extraction = await extractComponents(
31511
- sourceFiles,
31512
- ({ filesProcessed, componentsFound }) => {
32572
+ const extractionCacheDb = openPipelineDb();
32573
+ let extraction;
32574
+ let extractionCacheHits = 0;
32575
+ try {
32576
+ const cacheVersion = await getCliCacheVersion();
32577
+ const cachedByPath = /* @__PURE__ */ new Map();
32578
+ if (!noCache && sourceFiles.length > 0) {
32579
+ const hashes = await Promise.all(
32580
+ sourceFiles.map(async (filePath) => [filePath, await hashFile(filePath)])
32581
+ );
32582
+ for (const [filePath, fileHash] of hashes) {
32583
+ cachedByPath.set(filePath, lookupExtractCache(extractionCacheDb, fileHash, cacheVersion));
32584
+ }
32585
+ }
32586
+ const allFilesCached = !noCache && sourceFiles.length > 0 && sourceFiles.every((filePath) => cachedByPath.get(filePath) !== null);
32587
+ if (allFilesCached) {
32588
+ const cachedComponents = sourceFiles.flatMap((filePath) => cachedByPath.get(filePath).components);
32589
+ extractionCacheHits = sourceFiles.length;
32590
+ extraction = { components: cachedComponents, warnings: [], exclusions: [] };
31513
32591
  if (!process.stdout.isTTY) {
31514
- process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
31515
- `);
32592
+ process.stderr.write(
32593
+ `progress=extract:${sourceFiles.length}/${sourceFiles.length}:${cachedComponents.length}
32594
+ `
32595
+ );
31516
32596
  }
31517
- },
31518
- { resolveUnreachable, projectRoot }
31519
- );
32597
+ getDebugLogger().event("analyze", "extract.cache-hit", {
32598
+ files: sourceFiles.length,
32599
+ components: cachedComponents.length
32600
+ });
32601
+ } else {
32602
+ extraction = await extractComponents(
32603
+ sourceFiles,
32604
+ ({ filesProcessed, componentsFound }) => {
32605
+ if (!process.stdout.isTTY) {
32606
+ process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
32607
+ `);
32608
+ }
32609
+ },
32610
+ { resolveUnreachable, projectRoot }
32611
+ );
32612
+ }
32613
+ } finally {
32614
+ extractionCacheDb.close();
32615
+ }
31520
32616
  await mkdir6(outDir, { recursive: true });
31521
32617
  const db = openPipelineDb();
31522
32618
  const { sessionId: sessionId2 } = getOrCreateSession(db, void 0, void 0, {
@@ -31531,6 +32627,25 @@ function registerInternalExtractCommand(program) {
31531
32627
  const stepId = createStep(db, sessionId2, "analyze extract", {
31532
32628
  project: projectRoot
31533
32629
  });
32630
+ if (extractionCacheHits === 0 && !noCache) {
32631
+ const cacheVersion = await getCliCacheVersion();
32632
+ const componentsBySourcePath = /* @__PURE__ */ new Map();
32633
+ for (const component of extraction.components) {
32634
+ if (!component.sourcePath) continue;
32635
+ const components = componentsBySourcePath.get(component.sourcePath) ?? [];
32636
+ components.push(component);
32637
+ componentsBySourcePath.set(component.sourcePath, components);
32638
+ }
32639
+ for (const filePath of sourceFiles) {
32640
+ storeExtractCache(
32641
+ db,
32642
+ filePath,
32643
+ await hashFile(filePath),
32644
+ cacheVersion,
32645
+ componentsBySourcePath.get(filePath) ?? []
32646
+ );
32647
+ }
32648
+ }
31534
32649
  const classifiedComponents = extraction.components.map(preClassifyComponent);
31535
32650
  for (const exclusion of extraction.exclusions ?? []) {
31536
32651
  getDebugLogger().event("filter", "extract.excluded", { ...exclusion });
@@ -31635,6 +32750,7 @@ var init_extract_command = __esm({
31635
32750
  "use strict";
31636
32751
  init_src2();
31637
32752
  init_db();
32753
+ init_cache_keys();
31638
32754
  init_cycle_detection();
31639
32755
  init_resolve_mapping();
31640
32756
  init_manifest_doc_evidence();
@@ -31733,7 +32849,7 @@ __export(program_exports, {
31733
32849
  });
31734
32850
  import { spawn as spawn7 } from "node:child_process";
31735
32851
  import { fileURLToPath as fileURLToPath3 } from "node:url";
31736
- import { dirname as dirname18, join as join28, resolve as resolve19 } from "node:path";
32852
+ import { dirname as dirname18, join as join28, resolve as resolve20 } from "node:path";
31737
32853
  import { readFileSync as readFileSync10 } from "node:fs";
31738
32854
  import { Command } from "commander";
31739
32855
  async function runBuild(opts) {
@@ -31763,7 +32879,7 @@ function resolveBedrockFromAncestors(actionCommand) {
31763
32879
  }
31764
32880
  function registerBuildCommand(program) {
31765
32881
  program.command("build").description("Rebuild from source and re-link exo/experiences binaries to this build").helpOption(false).action(async () => {
31766
- const pkgRoot = resolve19(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
32882
+ const pkgRoot = resolve20(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
31767
32883
  process.stderr.write("\u2699 Building from source...\n");
31768
32884
  const { exitCode } = await runBuild({
31769
32885
  spawnFn: () => spawn7("pnpm", ["build"], { cwd: pkgRoot, stdio: "inherit" }),