@contentful/experience-design-system-cli 2.35.1-dev-build-459d7e23.0 → 2.35.1-dev-build-9e5b25da.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.
Files changed (25) hide show
  1. package/dist/package.json +1 -5
  2. package/dist/src/tui/home/home.d.ts +0 -1
  3. package/dist/src/tui/home/home.theme.d.ts +0 -11
  4. package/legacy/dist/src/analyze/composition/candidate-files.d.ts +34 -0
  5. package/legacy/dist/src/analyze/composition/composition-cache-key.d.ts +18 -0
  6. package/legacy/dist/src/analyze/composition/interchange-schema.d.ts +1 -7
  7. package/legacy/dist/src/analyze/composition/parse-map-edges.d.ts +16 -0
  8. package/legacy/dist/src/analyze/composition/prompt-loader.d.ts +3 -0
  9. package/legacy/dist/src/analyze/composition/resolve-mapping-cli.d.ts +10 -0
  10. package/legacy/dist/src/analyze/composition/resolve-mapping.d.ts +34 -11
  11. package/legacy/dist/src/analyze/extract-command.d.ts +4 -0
  12. package/legacy/dist/src/import/tui/WizardApp.d.ts +7 -1
  13. package/legacy/dist/src/import/tui/run-selection-agent.d.ts +1 -0
  14. package/legacy/dist/src/import/tui/wizard-state-transitions.d.ts +3 -0
  15. package/legacy/dist/src/index.js +1669 -449
  16. package/legacy/dist/src/map-tokens/tui/MapTokensView.d.ts +1 -0
  17. package/legacy/dist/src/session/cache-keys.d.ts +22 -0
  18. package/legacy/dist/src/session/core/components/hash-component-shape.d.ts +5 -0
  19. package/legacy/dist/src/session/core/tokens/hash-token-content.d.ts +1 -0
  20. package/legacy/dist/src/session/db.d.ts +72 -2
  21. package/legacy/package.json +1 -5
  22. package/legacy/prompts/composition-edges.md +11 -0
  23. package/package.json +3 -7
  24. package/legacy/dist/src/analyze/composition/source-call-site-evidence.d.ts +0 -30
  25. package/legacy/skills/debate-select.md +0 -23
@@ -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((resolve22) => {
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
+ resolve22(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((resolve22) => {
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
+ resolve22("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
+ resolve22("unauthenticated");
856
873
  return;
857
874
  }
858
875
  try {
859
876
  const status = JSON.parse(stdout);
860
- resolve20(status.loggedIn ? "ok" : "unauthenticated");
877
+ resolve22(status.loggedIn ? "ok" : "unauthenticated");
861
878
  } catch {
862
- resolve20("unauthenticated");
879
+ resolve22("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((resolve22) => {
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(() => resolve22(true), () => resolve22(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) => resolve22(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";
@@ -10300,6 +10357,41 @@ var init_read3 = __esm({
10300
10357
  }
10301
10358
  });
10302
10359
 
10360
+ // packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts
10361
+ import { createHash as createHash2 } from "node:crypto";
10362
+ function hashComponentShape(component) {
10363
+ const payload = {
10364
+ framework: component.framework,
10365
+ name: component.name,
10366
+ source: component.source,
10367
+ props: component.props.map((p) => ({
10368
+ name: p.name,
10369
+ type: p.type
10370
+ })),
10371
+ slots: component.slots.map((s) => ({
10372
+ name: s.name,
10373
+ isDefault: s.isDefault
10374
+ }))
10375
+ };
10376
+ return createHash2("sha256").update(JSON.stringify(payload)).digest("hex");
10377
+ }
10378
+ var init_hash_component_shape = __esm({
10379
+ "packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts"() {
10380
+ "use strict";
10381
+ }
10382
+ });
10383
+
10384
+ // packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts
10385
+ import { createHash as createHash3 } from "node:crypto";
10386
+ function hashTokenContent(rawTokenContent) {
10387
+ return createHash3("sha256").update(rawTokenContent.trim()).digest("hex");
10388
+ }
10389
+ var init_hash_token_content = __esm({
10390
+ "packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts"() {
10391
+ "use strict";
10392
+ }
10393
+ });
10394
+
10303
10395
  // packages/experience-design-system-cli-legacy/src/session/repositories/tokens/write.ts
10304
10396
  function deleteRawTokensForSession(db, sessionId2) {
10305
10397
  db.prepare("DELETE FROM raw_token_groups WHERE session_id = ?").run(sessionId2);
@@ -10463,17 +10555,47 @@ var init_replace_raw_prop_token_paths = __esm({
10463
10555
  }
10464
10556
  });
10465
10557
 
10558
+ // packages/experience-design-system-cli-legacy/src/session/cache-keys.ts
10559
+ var cache_keys_exports = {};
10560
+ __export(cache_keys_exports, {
10561
+ hashContent: () => hashContent,
10562
+ hashFile: () => hashFile,
10563
+ hashPromptForSkill: () => hashPromptForSkill
10564
+ });
10565
+ import { createHash as createHash4 } from "node:crypto";
10566
+ import { readFile as readFile7 } from "node:fs/promises";
10567
+ import { resolve as resolve8 } from "node:path";
10568
+ function hashContent(content) {
10569
+ return createHash4("sha256").update(content).digest("hex");
10570
+ }
10571
+ async function hashFile(filePath) {
10572
+ const content = await readFile7(filePath, "utf8");
10573
+ return hashContent(content);
10574
+ }
10575
+ async function hashPromptForSkill(skill, agent, model, skillPathOverride, extraInputs = [], skillContentOverride) {
10576
+ const promptHash = skillContentOverride ? hashContent(skillContentOverride) : skillPathOverride ? await hashFile(resolve8(skillPathOverride)) : await hashFile(resolveSkillPath(skill));
10577
+ const extraSegment = extraInputs.length > 0 ? `|${extraInputs.join("|")}` : "";
10578
+ return hashContent(`${promptHash}|${agent}|${model ?? ""}${extraSegment}`);
10579
+ }
10580
+ var init_cache_keys = __esm({
10581
+ "packages/experience-design-system-cli-legacy/src/session/cache-keys.ts"() {
10582
+ "use strict";
10583
+ init_src();
10584
+ }
10585
+ });
10586
+
10466
10587
  // packages/experience-design-system-cli-legacy/src/session/db.ts
10467
10588
  import { DatabaseSync } from "node:sqlite";
10468
10589
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
10469
- import { readFile as readFile7 } from "node:fs/promises";
10590
+ import { readFile as readFile8 } from "node:fs/promises";
10591
+ import { createHash as createHash5 } from "node:crypto";
10470
10592
  import { homedir as homedir3 } from "node:os";
10471
- import { dirname as dirname14, resolve as resolve8 } from "node:path";
10593
+ import { dirname as dirname14, resolve as resolve9 } from "node:path";
10472
10594
  function getPipelineDbPath() {
10473
10595
  if (process.env.EDS_PIPELINE_DB_PATH) {
10474
- return resolve8(process.env.EDS_PIPELINE_DB_PATH);
10596
+ return resolve9(process.env.EDS_PIPELINE_DB_PATH);
10475
10597
  }
10476
- return resolve8(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10598
+ return resolve9(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10477
10599
  }
10478
10600
  function openPipelineDb(dbPath) {
10479
10601
  const path = dbPath ?? getPipelineDbPath();
@@ -10607,8 +10729,134 @@ function applyDbMigrations(db) {
10607
10729
  if (!rawSlotColsForRationale.some((c2) => c2.name === "rationale")) {
10608
10730
  db.exec("ALTER TABLE raw_slots ADD COLUMN rationale TEXT");
10609
10731
  }
10610
- for (const table of REMOVED_CACHE_TABLES) {
10611
- db.exec(`DROP TABLE IF EXISTS ${table}`);
10732
+ const hasExtractCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='extract_cache'`).all();
10733
+ if (hasExtractCache.length === 0) {
10734
+ db.exec(`
10735
+ CREATE TABLE extract_cache (
10736
+ file_path TEXT NOT NULL,
10737
+ file_hash TEXT NOT NULL,
10738
+ cli_version TEXT NOT NULL,
10739
+ created_at TEXT NOT NULL,
10740
+ updated_at TEXT NOT NULL,
10741
+ components_json TEXT NOT NULL,
10742
+ PRIMARY KEY (file_hash, cli_version)
10743
+ );
10744
+ CREATE INDEX idx_extract_cache_file ON extract_cache(file_path);
10745
+ `);
10746
+ }
10747
+ const hasSelectCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='select_cache'`).all();
10748
+ if (hasSelectCache.length === 0) {
10749
+ db.exec(`
10750
+ CREATE TABLE select_cache (
10751
+ component_hash TEXT NOT NULL,
10752
+ prompt_hash TEXT NOT NULL,
10753
+ cli_version TEXT NOT NULL,
10754
+ decision TEXT NOT NULL CHECK (decision IN ('accepted','rejected')),
10755
+ reason TEXT,
10756
+ created_at TEXT NOT NULL,
10757
+ updated_at TEXT NOT NULL,
10758
+ PRIMARY KEY (component_hash, prompt_hash, cli_version)
10759
+ );
10760
+ `);
10761
+ }
10762
+ const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='generation_cache'`).all();
10763
+ if (tables.length === 0) {
10764
+ db.exec(`
10765
+ CREATE TABLE generation_cache (
10766
+ input_hash TEXT NOT NULL,
10767
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10768
+ entity_id TEXT NOT NULL,
10769
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10770
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10771
+ created_at TEXT NOT NULL,
10772
+ updated_at TEXT NOT NULL,
10773
+ PRIMARY KEY (input_hash, entity_type, entity_id)
10774
+ );
10775
+ CREATE INDEX idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10776
+ CREATE INDEX idx_generation_cache_session ON generation_cache(source_session_id);
10777
+ `);
10778
+ }
10779
+ const genCacheCols = db.prepare(`PRAGMA table_info(generation_cache)`).all();
10780
+ const hasPromptHash = genCacheCols.some((c2) => c2.name === "prompt_hash");
10781
+ const oldPkCols = genCacheCols.filter((c2) => c2.pk > 0).map((c2) => c2.name);
10782
+ const promptHashInPk = oldPkCols.includes("prompt_hash");
10783
+ if (!hasPromptHash) {
10784
+ db.exec(`ALTER TABLE generation_cache ADD COLUMN prompt_hash TEXT NOT NULL DEFAULT ''`);
10785
+ }
10786
+ if (!promptHashInPk) {
10787
+ db.exec("BEGIN");
10788
+ try {
10789
+ db.exec(`
10790
+ CREATE TABLE generation_cache__new (
10791
+ input_hash TEXT NOT NULL,
10792
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10793
+ entity_id TEXT NOT NULL,
10794
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10795
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10796
+ created_at TEXT NOT NULL,
10797
+ updated_at TEXT NOT NULL,
10798
+ prompt_hash TEXT NOT NULL DEFAULT '',
10799
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10800
+ );
10801
+ INSERT INTO generation_cache__new
10802
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10803
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10804
+ FROM generation_cache;
10805
+ DROP TABLE generation_cache;
10806
+ ALTER TABLE generation_cache__new RENAME TO generation_cache;
10807
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10808
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10809
+ `);
10810
+ db.exec("COMMIT");
10811
+ } catch (e) {
10812
+ db.exec("ROLLBACK");
10813
+ throw e;
10814
+ }
10815
+ }
10816
+ const genCacheTableSql = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'generation_cache'`).get();
10817
+ const hasTokenMappingEntityType = genCacheTableSql?.sql.includes("'token_mapping'") ?? true;
10818
+ if (!hasTokenMappingEntityType) {
10819
+ db.exec("BEGIN");
10820
+ try {
10821
+ db.exec(`
10822
+ CREATE TABLE generation_cache__new2 (
10823
+ input_hash TEXT NOT NULL,
10824
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10825
+ entity_id TEXT NOT NULL,
10826
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10827
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10828
+ created_at TEXT NOT NULL,
10829
+ updated_at TEXT NOT NULL,
10830
+ prompt_hash TEXT NOT NULL DEFAULT '',
10831
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10832
+ );
10833
+ INSERT INTO generation_cache__new2
10834
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10835
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10836
+ FROM generation_cache;
10837
+ DROP TABLE generation_cache;
10838
+ ALTER TABLE generation_cache__new2 RENAME TO generation_cache;
10839
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10840
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10841
+ `);
10842
+ db.exec("COMMIT");
10843
+ } catch (e) {
10844
+ db.exec("ROLLBACK");
10845
+ throw e;
10846
+ }
10847
+ }
10848
+ const hasCompositionCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='composition_cache'`).all();
10849
+ if (hasCompositionCache.length === 0) {
10850
+ db.exec(`
10851
+ CREATE TABLE composition_cache (
10852
+ input_hash TEXT NOT NULL,
10853
+ cli_version TEXT NOT NULL,
10854
+ agent_output TEXT NOT NULL,
10855
+ created_at TEXT NOT NULL,
10856
+ updated_at TEXT NOT NULL,
10857
+ PRIMARY KEY (input_hash, cli_version)
10858
+ );
10859
+ `);
10612
10860
  }
10613
10861
  }
10614
10862
  function loadComponentReviewMetadata(db, sessionId2, componentName) {
@@ -11148,16 +11396,16 @@ function extractRelativeImportPaths(sourceText) {
11148
11396
  return [...specifiers];
11149
11397
  }
11150
11398
  async function resolveRelativeImport(specifier, fromDir) {
11151
- const basePath = resolve8(fromDir, specifier);
11399
+ const basePath = resolve9(fromDir, specifier);
11152
11400
  const candidates = [
11153
11401
  basePath,
11154
11402
  ...rewrittenSourcePaths(basePath),
11155
11403
  ...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
11156
- ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve8(basePath, `index${ext}`))
11404
+ ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve9(basePath, `index${ext}`))
11157
11405
  ];
11158
11406
  for (const candidate of candidates) {
11159
11407
  try {
11160
- await readFile7(candidate, "utf8");
11408
+ await readFile8(candidate, "utf8");
11161
11409
  return candidate;
11162
11410
  } catch {
11163
11411
  }
@@ -11173,7 +11421,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
11173
11421
  return resolvedPaths;
11174
11422
  }
11175
11423
  async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateTypeNames = []) {
11176
- const visited = /* @__PURE__ */ new Set([resolve8(sourcePath)]);
11424
+ const visited = /* @__PURE__ */ new Set([resolve9(sourcePath)]);
11177
11425
  const discovered = [];
11178
11426
  let frontier = [{ text: sourceText, dir: dirname14(sourcePath) }];
11179
11427
  for (let hop = 0; hop < MAX_SIBLING_DEPTH && discovered.length < MAX_SIBLING_CANDIDATES_EXPLORED; hop++) {
@@ -11187,7 +11435,7 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
11187
11435
  if (discovered.length >= MAX_SIBLING_CANDIDATES_EXPLORED) break;
11188
11436
  let content;
11189
11437
  try {
11190
- content = await readFile7(resolvedPath, "utf8");
11438
+ content = await readFile8(resolvedPath, "utf8");
11191
11439
  } catch {
11192
11440
  continue;
11193
11441
  }
@@ -11224,7 +11472,7 @@ async function loadComponentSourceRef(name, sourcePath, propNames = [], propType
11224
11472
  let truncatedSiblingCount = 0;
11225
11473
  const usesNotShown = /* @__PURE__ */ new Set();
11226
11474
  try {
11227
- const rawText = await readFile7(sourcePath, "utf8");
11475
+ const rawText = await readFile8(sourcePath, "utf8");
11228
11476
  let siblingUsesNotShown;
11229
11477
  let declaredTypeNames;
11230
11478
  ({
@@ -11660,6 +11908,108 @@ function backfillUnclassifiedProps(db, sessionId2) {
11660
11908
  ).run(sessionId2, sessionId2);
11661
11909
  return Number(withCategory.changes) + Number(withoutCategory.changes);
11662
11910
  }
11911
+ function computeMapTokensInputHash(db, sessionId2, componentSourceRefs = []) {
11912
+ const props = db.prepare(
11913
+ `SELECT rc.name AS component_name, rp.name AS prop_name, rp.cdf_token_kind, rp.default_value
11914
+ FROM raw_props rp
11915
+ JOIN raw_components rc ON rc.session_id = rp.session_id AND rc.component_id = rp.component_id
11916
+ WHERE rp.session_id = ? AND rp.cdf_type = 'token' AND rp.cdf_category = 'design'
11917
+ ORDER BY rc.name, rp.name`
11918
+ ).all(sessionId2);
11919
+ const tokens = db.prepare(`SELECT path, type FROM raw_tokens WHERE session_id = ? ORDER BY path`).all(sessionId2);
11920
+ const defaultMappings = db.prepare("SELECT raw_name, path, source FROM raw_token_name_paths WHERE session_id = ? ORDER BY raw_name").all(sessionId2);
11921
+ const payload = {
11922
+ props: props.map((p) => ({
11923
+ component: p.component_name,
11924
+ prop: p.prop_name,
11925
+ tokenKind: p.cdf_token_kind,
11926
+ rawDefault: p.default_value
11927
+ })),
11928
+ tokens,
11929
+ defaultMappings,
11930
+ // The agent's evidence, not just its inputs: a comment or allowlist edit
11931
+ // in a source file (or a sibling it reads) changes the correct answer
11932
+ // without touching props or tokens, so it must invalidate the cache too.
11933
+ sourceRefs: [...componentSourceRefs].sort((a, b) => a.component.localeCompare(b.component)).map((ref) => ({
11934
+ component: ref.component,
11935
+ content: ref.content,
11936
+ siblingFiles: (ref.siblingFiles ?? []).map((s) => ({ path: s.path, content: s.content })).sort((a, b) => a.path.localeCompare(b.path)),
11937
+ usesNotShown: [...ref.usesNotShown ?? []].sort()
11938
+ }))
11939
+ };
11940
+ return createHash5("sha256").update(JSON.stringify(payload)).digest("hex");
11941
+ }
11942
+ function lookupCache(db, inputHash, entityType, entityId, promptHash = "") {
11943
+ const row = db.prepare(
11944
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11945
+ FROM generation_cache
11946
+ WHERE input_hash = ? AND entity_type = ? AND entity_id = ? AND prompt_hash = ?`
11947
+ ).get(inputHash, entityType, entityId, promptHash);
11948
+ if (!row) return null;
11949
+ return {
11950
+ inputHash: row.input_hash,
11951
+ entityType: row.entity_type,
11952
+ entityId: row.entity_id,
11953
+ sourceSessionId: row.source_session_id,
11954
+ humanEdited: row.human_edited === 1,
11955
+ promptHash: row.prompt_hash,
11956
+ createdAt: row.created_at,
11957
+ updatedAt: row.updated_at
11958
+ };
11959
+ }
11960
+ function lookupCacheByEntity(db, entityType, entityId) {
11961
+ const row = db.prepare(
11962
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11963
+ FROM generation_cache
11964
+ WHERE entity_type = ? AND entity_id = ?
11965
+ ORDER BY updated_at DESC LIMIT 1`
11966
+ ).get(entityType, entityId);
11967
+ if (!row) return null;
11968
+ return {
11969
+ inputHash: row.input_hash,
11970
+ entityType: row.entity_type,
11971
+ entityId: row.entity_id,
11972
+ sourceSessionId: row.source_session_id,
11973
+ humanEdited: row.human_edited === 1,
11974
+ promptHash: row.prompt_hash,
11975
+ createdAt: row.created_at,
11976
+ updatedAt: row.updated_at
11977
+ };
11978
+ }
11979
+ function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash = "") {
11980
+ storeCaches(db, [{ inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash }]);
11981
+ }
11982
+ function storeCaches(db, entries) {
11983
+ if (entries.length === 0) return;
11984
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11985
+ const insert = db.prepare(
11986
+ `INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
11987
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
11988
+ ON CONFLICT(input_hash, prompt_hash, entity_type, entity_id) DO UPDATE SET
11989
+ source_session_id = excluded.source_session_id,
11990
+ human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
11991
+ updated_at = excluded.updated_at`
11992
+ );
11993
+ db.exec("BEGIN");
11994
+ try {
11995
+ for (const entry of entries) {
11996
+ insert.run(
11997
+ entry.inputHash,
11998
+ entry.entityType,
11999
+ entry.entityId,
12000
+ entry.sourceSessionId,
12001
+ entry.humanEdited ? 1 : 0,
12002
+ now,
12003
+ now,
12004
+ entry.promptHash ?? ""
12005
+ );
12006
+ }
12007
+ db.exec("COMMIT");
12008
+ } catch (e) {
12009
+ db.exec("ROLLBACK");
12010
+ throw e;
12011
+ }
12012
+ }
11663
12013
  function storeScannedFiles(db, sessionId2, filePaths) {
11664
12014
  db.exec("BEGIN");
11665
12015
  try {
@@ -11742,7 +12092,105 @@ function filterUnknownSlotAllowedComponents(db, sessionId2, allowedComponentName
11742
12092
  }
11743
12093
  return unknown;
11744
12094
  }
11745
- function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
12095
+ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId, manageTransaction = true, options = {}) {
12096
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12097
+ if (manageTransaction) db.exec("BEGIN");
12098
+ try {
12099
+ const srcComp = db.prepare(
12100
+ `SELECT description, status, component_description_rationale, props_rationale, slots_rationale
12101
+ FROM raw_components WHERE session_id = ? AND component_id = ?`
12102
+ ).get(sourceSessionId, componentId);
12103
+ if (srcComp) {
12104
+ db.prepare(
12105
+ `UPDATE raw_components
12106
+ SET description = ?, status = ?, component_description_rationale = ?, props_rationale = ?, slots_rationale = ?, extracted_at = ?
12107
+ WHERE session_id = ? AND component_id = ?`
12108
+ ).run(
12109
+ srcComp.description,
12110
+ srcComp.status,
12111
+ srcComp.component_description_rationale,
12112
+ srcComp.props_rationale,
12113
+ srcComp.slots_rationale,
12114
+ now,
12115
+ targetSessionId,
12116
+ componentId
12117
+ );
12118
+ }
12119
+ const srcProps = db.prepare(
12120
+ `SELECT name, cdf_type, cdf_category, cdf_token_kind, required, description, default_value, rationale
12121
+ FROM raw_props WHERE session_id = ? AND component_id = ?`
12122
+ ).all(sourceSessionId, componentId);
12123
+ for (const p of srcProps) {
12124
+ db.prepare(
12125
+ `UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, default_value = ?, rationale = ?
12126
+ WHERE session_id = ? AND component_id = ? AND name = ?`
12127
+ ).run(
12128
+ p.cdf_type,
12129
+ p.cdf_category,
12130
+ p.cdf_token_kind,
12131
+ p.required,
12132
+ p.description,
12133
+ p.default_value,
12134
+ p.rationale,
12135
+ targetSessionId,
12136
+ componentId,
12137
+ p.name
12138
+ );
12139
+ }
12140
+ db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`).run(
12141
+ targetSessionId,
12142
+ componentId
12143
+ );
12144
+ const srcAV = db.prepare(
12145
+ `SELECT prop_name, position, value FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`
12146
+ ).all(sourceSessionId, componentId);
12147
+ const insertAV = db.prepare(
12148
+ `INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value) VALUES (?, ?, ?, ?, ?)`
12149
+ );
12150
+ for (const av of srcAV) {
12151
+ insertAV.run(targetSessionId, componentId, av.prop_name, av.position, av.value);
12152
+ }
12153
+ const srcSlots = db.prepare(`SELECT name, required, description, rationale FROM raw_slots WHERE session_id = ? AND component_id = ?`).all(sourceSessionId, componentId);
12154
+ for (const s of srcSlots) {
12155
+ db.prepare(
12156
+ `UPDATE raw_slots SET required = ?, description = ?, rationale = ? WHERE session_id = ? AND component_id = ? AND name = ?`
12157
+ ).run(s.required, s.description, s.rationale, targetSessionId, componentId, s.name);
12158
+ }
12159
+ db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`).run(
12160
+ targetSessionId,
12161
+ componentId
12162
+ );
12163
+ const srcSAC = db.prepare(
12164
+ `SELECT slot_name, position, allowed_component FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`
12165
+ ).all(sourceSessionId, componentId);
12166
+ const insertSAC = db.prepare(
12167
+ `INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`
12168
+ );
12169
+ const allowedSAC = options.allowedComponentNames ? srcSAC.filter((sac) => options.allowedComponentNames.has(sac.allowed_component)) : srcSAC;
12170
+ for (const sac of allowedSAC) {
12171
+ insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
12172
+ }
12173
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12174
+ if (manageTransaction) db.exec("COMMIT");
12175
+ } catch (e) {
12176
+ if (manageTransaction) db.exec("ROLLBACK");
12177
+ throw e;
12178
+ }
12179
+ }
12180
+ function copyComponentsFromCache(db, entries, options = {}) {
12181
+ if (entries.length === 0) return;
12182
+ db.exec("BEGIN");
12183
+ try {
12184
+ for (const entry of entries) {
12185
+ copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false, options);
12186
+ }
12187
+ db.exec("COMMIT");
12188
+ } catch (e) {
12189
+ db.exec("ROLLBACK");
12190
+ throw e;
12191
+ }
12192
+ }
12193
+ function copyTokensFromCache(db, sourceSessionId, targetSessionId) {
11746
12194
  const now = (/* @__PURE__ */ new Date()).toISOString();
11747
12195
  db.exec("BEGIN");
11748
12196
  try {
@@ -11763,7 +12211,153 @@ function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
11763
12211
  throw e;
11764
12212
  }
11765
12213
  }
11766
- var SCHEMA, REMOVED_CACHE_TABLES, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS, MAX_TYPE_DECLARING_SIBLING_CHARS, MAX_ENLARGED_SIBLINGS, MAX_SIBLING_DEPTH, MAX_SIBLING_CANDIDATES_EXPLORED, RELATIVE_IMPORT_PATTERN, SIBLING_FILE_EXTENSIONS, TS_ESM_EXTENSION_REWRITES, IDENTIFIER_PATTERN;
12214
+ function copyMapTokensFromCache(db, sourceSessionId, targetSessionId) {
12215
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12216
+ let copiedCount = 0;
12217
+ db.exec("BEGIN");
12218
+ try {
12219
+ const sourceRows = db.prepare(
12220
+ `SELECT rc.name AS component_name, rptp.prop_name, rptp.source, rptp.position, rptp.path
12221
+ FROM raw_prop_token_paths rptp
12222
+ JOIN raw_components rc ON rc.session_id = rptp.session_id AND rc.component_id = rptp.component_id
12223
+ WHERE rptp.session_id = ?
12224
+ ORDER BY rc.name, rptp.prop_name, rptp.position`
12225
+ ).all(sourceSessionId);
12226
+ const targetComponents = db.prepare(`SELECT component_id, name FROM raw_components WHERE session_id = ?`).all(targetSessionId);
12227
+ const targetIdByName = new Map(targetComponents.map((c2) => [c2.name, c2.component_id]));
12228
+ const targetProps = new Set(
12229
+ db.prepare(`SELECT component_id, name FROM raw_props WHERE session_id = ?`).all(targetSessionId).map((p) => `${p.component_id}::${p.name}`)
12230
+ );
12231
+ const insertPath = db.prepare(
12232
+ `INSERT INTO raw_prop_token_paths (session_id, component_id, prop_name, source, position, path)
12233
+ VALUES (?, ?, ?, ?, ?, ?)`
12234
+ );
12235
+ const clearedProps = /* @__PURE__ */ new Set();
12236
+ const copiedProps = /* @__PURE__ */ new Set();
12237
+ for (const row of sourceRows) {
12238
+ const targetComponentId = targetIdByName.get(row.component_name);
12239
+ if (!targetComponentId) continue;
12240
+ if (!targetProps.has(`${targetComponentId}::${row.prop_name}`)) continue;
12241
+ const propKey = `${targetComponentId}::${row.prop_name}`;
12242
+ if (!clearedProps.has(propKey)) {
12243
+ clearedProps.add(propKey);
12244
+ db.prepare(`DELETE FROM raw_prop_token_paths WHERE session_id = ? AND component_id = ? AND prop_name = ?`).run(
12245
+ targetSessionId,
12246
+ targetComponentId,
12247
+ row.prop_name
12248
+ );
12249
+ }
12250
+ insertPath.run(targetSessionId, targetComponentId, row.prop_name, row.source, row.position, row.path);
12251
+ copiedProps.add(propKey);
12252
+ }
12253
+ copiedCount = copiedProps.size;
12254
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12255
+ db.exec("COMMIT");
12256
+ } catch (e) {
12257
+ db.exec("ROLLBACK");
12258
+ throw e;
12259
+ }
12260
+ return copiedCount;
12261
+ }
12262
+ async function getCliCacheVersion() {
12263
+ if (_cliCacheVersionCache) return _cliCacheVersionCache;
12264
+ try {
12265
+ const { hashContent: hashContent2 } = await Promise.resolve().then(() => (init_cache_keys(), cache_keys_exports));
12266
+ const { resolveSkillPath: resolveSkillPath2 } = await Promise.resolve().then(() => (init_src(), src_exports));
12267
+ const skills = ["components", "tokens", "select"];
12268
+ const parts = [];
12269
+ for (const s of skills) {
12270
+ try {
12271
+ const p = resolveSkillPath2(s);
12272
+ parts.push(readFileSync5(p, "utf8"));
12273
+ } catch {
12274
+ parts.push(`<missing:${s}>`);
12275
+ }
12276
+ }
12277
+ _cliCacheVersionCache = hashContent2(parts.join("\n---\n"));
12278
+ return _cliCacheVersionCache;
12279
+ } catch {
12280
+ _cliCacheVersionCache = "fallback";
12281
+ return _cliCacheVersionCache;
12282
+ }
12283
+ }
12284
+ function storeExtractCache(db, filePath, fileHash, cliVersion2, components) {
12285
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12286
+ const componentsJson = JSON.stringify(components);
12287
+ db.prepare(
12288
+ `INSERT INTO extract_cache (file_path, file_hash, cli_version, created_at, updated_at, components_json)
12289
+ VALUES (?, ?, ?, ?, ?, ?)
12290
+ ON CONFLICT(file_hash, cli_version) DO UPDATE SET
12291
+ file_path = excluded.file_path,
12292
+ updated_at = excluded.updated_at,
12293
+ components_json = excluded.components_json`
12294
+ ).run(filePath, fileHash, cliVersion2, now, now, componentsJson);
12295
+ }
12296
+ function lookupExtractCache(db, fileHash, cliVersion2) {
12297
+ const row = db.prepare(
12298
+ `SELECT file_path, file_hash, cli_version, created_at, updated_at, components_json
12299
+ FROM extract_cache
12300
+ WHERE file_hash = ? AND cli_version = ?`
12301
+ ).get(fileHash, cliVersion2);
12302
+ if (!row) return null;
12303
+ let components = [];
12304
+ try {
12305
+ components = JSON.parse(row.components_json);
12306
+ } catch {
12307
+ return null;
12308
+ }
12309
+ return {
12310
+ filePath: row.file_path,
12311
+ fileHash: row.file_hash,
12312
+ cliVersion: row.cli_version,
12313
+ createdAt: row.created_at,
12314
+ updatedAt: row.updated_at,
12315
+ components
12316
+ };
12317
+ }
12318
+ function storeCompositionCache(db, inputHash, cliVersion2, agentOutput) {
12319
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12320
+ db.prepare(
12321
+ `INSERT INTO composition_cache (input_hash, cli_version, agent_output, created_at, updated_at)
12322
+ VALUES (?, ?, ?, ?, ?)
12323
+ ON CONFLICT(input_hash, cli_version) DO UPDATE SET
12324
+ agent_output = excluded.agent_output,
12325
+ updated_at = excluded.updated_at`
12326
+ ).run(inputHash, cliVersion2, agentOutput, now, now);
12327
+ }
12328
+ function lookupCompositionCache(db, inputHash, cliVersion2) {
12329
+ const row = db.prepare(`SELECT agent_output FROM composition_cache WHERE input_hash = ? AND cli_version = ?`).get(inputHash, cliVersion2);
12330
+ return row ? row.agent_output : null;
12331
+ }
12332
+ function storeSelectCache(db, componentHash, promptHash, cliVersion2, decision, reason) {
12333
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12334
+ db.prepare(
12335
+ `INSERT INTO select_cache (component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at)
12336
+ VALUES (?, ?, ?, ?, ?, ?, ?)
12337
+ ON CONFLICT(component_hash, prompt_hash, cli_version) DO UPDATE SET
12338
+ decision = excluded.decision,
12339
+ reason = excluded.reason,
12340
+ updated_at = excluded.updated_at`
12341
+ ).run(componentHash, promptHash, cliVersion2, decision, reason, now, now);
12342
+ }
12343
+ function lookupSelectCache(db, componentHash, promptHash, cliVersion2) {
12344
+ const row = db.prepare(
12345
+ `SELECT component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at
12346
+ FROM select_cache
12347
+ WHERE component_hash = ? AND prompt_hash = ? AND cli_version = ?`
12348
+ ).get(componentHash, promptHash, cliVersion2);
12349
+ if (!row) return null;
12350
+ return {
12351
+ componentHash: row.component_hash,
12352
+ promptHash: row.prompt_hash,
12353
+ cliVersion: row.cli_version,
12354
+ decision: row.decision,
12355
+ reason: row.reason,
12356
+ createdAt: row.created_at,
12357
+ updatedAt: row.updated_at
12358
+ };
12359
+ }
12360
+ 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;
11767
12361
  var init_db = __esm({
11768
12362
  "packages/experience-design-system-cli-legacy/src/session/db.ts"() {
11769
12363
  "use strict";
@@ -11777,6 +12371,8 @@ var init_db = __esm({
11777
12371
  init_write2();
11778
12372
  init_session_resolver();
11779
12373
  init_read3();
12374
+ init_hash_component_shape();
12375
+ init_hash_token_content();
11780
12376
  init_read3();
11781
12377
  init_store_dtcg_tokens();
11782
12378
  init_apply_tool_calls();
@@ -11900,6 +12496,17 @@ CREATE TABLE IF NOT EXISTS raw_tokens (
11900
12496
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
11901
12497
  );
11902
12498
 
12499
+ CREATE TABLE IF NOT EXISTS generation_cache (
12500
+ input_hash TEXT NOT NULL,
12501
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
12502
+ entity_id TEXT NOT NULL,
12503
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
12504
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
12505
+ created_at TEXT NOT NULL,
12506
+ updated_at TEXT NOT NULL,
12507
+ PRIMARY KEY (input_hash, entity_type, entity_id)
12508
+ );
12509
+
11903
12510
  CREATE TABLE IF NOT EXISTS scanned_files (
11904
12511
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
11905
12512
  path TEXT NOT NULL,
@@ -11945,9 +12552,10 @@ CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id,
11945
12552
  CREATE INDEX IF NOT EXISTS idx_raw_slots_session ON raw_slots(session_id, component_id);
11946
12553
  CREATE INDEX IF NOT EXISTS idx_raw_tokens_session ON raw_tokens(session_id);
11947
12554
  CREATE INDEX IF NOT EXISTS idx_raw_token_groups_session ON raw_token_groups(session_id);
12555
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
12556
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
11948
12557
  CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session_id);
11949
12558
  `;
11950
- REMOVED_CACHE_TABLES = ["generation_cache", "extract_cache", "composition_cache", "select_cache"];
11951
12559
  MAX_COMPONENT_SOURCE_CHARS = 8e3;
11952
12560
  MAX_SIBLING_FILES = 5;
11953
12561
  MAX_SIBLING_SNIPPET_CHARS = 1200;
@@ -11964,15 +12572,16 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
11964
12572
  [".cjs", [".cts", ".cjs"]]
11965
12573
  ];
11966
12574
  IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
12575
+ _cliCacheVersionCache = null;
11967
12576
  }
11968
12577
  });
11969
12578
 
11970
12579
  // packages/experience-design-system-cli-legacy/src/helpers/read-existing-contentful-entities-from-session.ts
11971
- import { readFile as readFile8 } from "node:fs/promises";
12580
+ import { readFile as readFile9 } from "node:fs/promises";
11972
12581
  async function readExistingContentfulEntitiesFromSession(path) {
11973
12582
  if (!path) return void 0;
11974
12583
  try {
11975
- const raw = await readFile8(path, "utf8");
12584
+ const raw = await readFile9(path, "utf8");
11976
12585
  const parsed = JSON.parse(raw);
11977
12586
  if (!Array.isArray(parsed.components) || !Array.isArray(parsed.tokens)) return void 0;
11978
12587
  return parsed;
@@ -12096,21 +12705,21 @@ var init_resolve_session_id = __esm({
12096
12705
  });
12097
12706
 
12098
12707
  // packages/experience-design-system-cli-legacy/src/analyze/select/persistence.ts
12099
- import { access, appendFile, mkdir as mkdir2, readFile as readFile9, writeFile as writeFile2 } from "node:fs/promises";
12708
+ import { access, appendFile, mkdir as mkdir2, readFile as readFile10, writeFile as writeFile2 } from "node:fs/promises";
12100
12709
  import { homedir as homedir4 } from "node:os";
12101
- import { resolve as resolve9 } from "node:path";
12710
+ import { resolve as resolve10 } from "node:path";
12102
12711
  function getRefineArtifactsRoot() {
12103
12712
  if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
12104
- return resolve9(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12713
+ return resolve10(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12105
12714
  }
12106
- return resolve9(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12715
+ return resolve10(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12107
12716
  }
12108
12717
  async function getRefineSessionPaths(sessionId2, artifactsRoot) {
12109
- const sessionDir = resolve9(artifactsRoot, sessionId2);
12718
+ const sessionDir = resolve10(artifactsRoot, sessionId2);
12110
12719
  return {
12111
12720
  sessionDir,
12112
- eventsPath: resolve9(sessionDir, "events.jsonl"),
12113
- statePath: resolve9(sessionDir, "current-review-state.json")
12721
+ eventsPath: resolve10(sessionDir, "events.jsonl"),
12722
+ statePath: resolve10(sessionDir, "current-review-state.json")
12114
12723
  };
12115
12724
  }
12116
12725
  async function saveReviewState(statePath, session) {
@@ -12121,7 +12730,7 @@ async function ensureRefineSession(sessionId2, artifactsRoot, initialSnapshot) {
12121
12730
  await mkdir2(paths.sessionDir, { recursive: true });
12122
12731
  try {
12123
12732
  await access(paths.statePath);
12124
- const savedState = await readFile9(paths.statePath, "utf8");
12733
+ const savedState = await readFile10(paths.statePath, "utf8");
12125
12734
  return JSON.parse(savedState);
12126
12735
  } catch {
12127
12736
  await writeFile2(paths.statePath, JSON.stringify(initialSnapshot, null, 2), "utf8");
@@ -12606,8 +13215,8 @@ var init_path_exists = __esm({
12606
13215
  });
12607
13216
 
12608
13217
  // packages/experience-design-system-cli-legacy/src/lib/prompt-overrides.ts
12609
- import { readFile as readFile10 } from "node:fs/promises";
12610
- import { resolve as resolve10 } from "node:path";
13218
+ import { readFile as readFile11 } from "node:fs/promises";
13219
+ import { resolve as resolve11 } from "node:path";
12611
13220
  function looksLikePath(value) {
12612
13221
  if (value.includes("/") || value.includes("\\")) return true;
12613
13222
  if (value.startsWith("~")) return true;
@@ -12639,9 +13248,9 @@ function parsePromptOverrides(inputs) {
12639
13248
  }
12640
13249
  async function resolvePromptOverride(override) {
12641
13250
  if (override.kind === "text") return override.value;
12642
- const abs = resolve10(override.value);
13251
+ const abs = resolve11(override.value);
12643
13252
  try {
12644
- return await readFile10(abs, "utf8");
13253
+ return await readFile11(abs, "utf8");
12645
13254
  } catch {
12646
13255
  throw new Error(`--prompt: could not read prompt file: ${abs}`);
12647
13256
  }
@@ -12656,21 +13265,21 @@ var init_prompt_overrides = __esm({
12656
13265
 
12657
13266
  // packages/experience-design-system-cli-legacy/src/generate/command.ts
12658
13267
  import { createElement } from "react";
12659
- import { readFile as readFile11, readdir, stat } from "node:fs/promises";
12660
- import { join as join14, resolve as resolve11 } from "node:path";
13268
+ import { readFile as readFile12, readdir, stat } from "node:fs/promises";
13269
+ import { join as join14, resolve as resolve12 } from "node:path";
12661
13270
  async function assertFileExists(flag, p) {
12662
13271
  if (!await pathExists(p)) die(`Error: file not found: ${p} (from ${flag})`);
12663
13272
  }
12664
13273
  async function readFileInline(path) {
12665
13274
  if (!path) return void 0;
12666
- const resolved = resolve11(path);
13275
+ const resolved = resolve12(path);
12667
13276
  let s;
12668
13277
  try {
12669
13278
  s = await stat(resolved);
12670
13279
  } catch {
12671
13280
  return void 0;
12672
13281
  }
12673
- if (!s.isDirectory()) return readFile11(resolved, "utf8");
13282
+ if (!s.isDirectory()) return readFile12(resolved, "utf8");
12674
13283
  const files = [];
12675
13284
  async function walk(dir) {
12676
13285
  let entries;
@@ -12696,7 +13305,7 @@ async function readFileInline(path) {
12696
13305
  }
12697
13306
  await walk(resolved);
12698
13307
  if (files.length === 0) return void 0;
12699
- const parts = await Promise.all(files.map((f) => readFile11(f, "utf8").catch(() => "")));
13308
+ const parts = await Promise.all(files.map((f) => readFile12(f, "utf8").catch(() => "")));
12700
13309
  return parts.filter(Boolean).join("\n\n");
12701
13310
  }
12702
13311
  function printFallbackInstructions(options) {
@@ -12718,6 +13327,23 @@ function printFallbackInstructions(options) {
12718
13327
  lines.push(` Re-run the generate command with the agent available, or use --dry-run to inspect the prompt.`);
12719
13328
  process.stderr.write(lines.join("\n") + "\n");
12720
13329
  }
13330
+ function createCachedComponentResult(componentName, warnings = []) {
13331
+ return {
13332
+ componentName,
13333
+ classified: 0,
13334
+ excluded: 0,
13335
+ slots: 0,
13336
+ warnings,
13337
+ failed: false,
13338
+ cached: true,
13339
+ renamedSlotsCount: 0
13340
+ };
13341
+ }
13342
+ function writeCachedComponentStatus(position, componentName, pinned) {
13343
+ const status = pinned ? c.cyan("pinned (human-edited)") : c.green("cached");
13344
+ process.stderr.write(` ${position} ${c.bold(componentName)} ${status}
13345
+ `);
13346
+ }
12721
13347
  async function showGenerateView(result) {
12722
13348
  if (process.stdout.isTTY) {
12723
13349
  const { waitUntilExit } = renderWithGoodbye(
@@ -12738,6 +13364,29 @@ session=${result.sessionId}
12738
13364
  );
12739
13365
  await exitWithAnalytics(0);
12740
13366
  }
13367
+ function normalizeComponentForCache(component) {
13368
+ const slots = component.slots.map((slot, index, allSlots) => ({
13369
+ ...slot,
13370
+ name: slot.name.trim() || (allSlots.length === 1 ? "children" : `slot_${index}`)
13371
+ }));
13372
+ return { ...component, slots };
13373
+ }
13374
+ function lookupComponentCache(db, component, promptHash) {
13375
+ const normalized = normalizeComponentForCache(component);
13376
+ const inputHash = hashComponentShape(normalized);
13377
+ const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
13378
+ if (cached) return cached;
13379
+ const legacyInputHash = hashComponentShape(component);
13380
+ return legacyInputHash === inputHash ? null : lookupCache(db, legacyInputHash, "component", component.component_id, promptHash);
13381
+ }
13382
+ function resolveComponentCache(db, component, promptHash, allowedComponentNames2) {
13383
+ const cached = lookupComponentCache(db, component, promptHash);
13384
+ if (cached && findUnknownSlotAllowedComponents(db, cached.sourceSessionId, allowedComponentNames2, component.component_id).length === 0) {
13385
+ return { entry: cached, humanEdited: cached.humanEdited };
13386
+ }
13387
+ const pinned = lookupCacheByEntity(db, "component", component.component_id);
13388
+ return pinned?.humanEdited && findUnknownSlotAllowedComponents(db, pinned.sourceSessionId, allowedComponentNames2, component.component_id).length === 0 ? { entry: pinned, humanEdited: true } : null;
13389
+ }
12741
13390
  async function runOneComponent(options, component, index, total) {
12742
13391
  const {
12743
13392
  agent,
@@ -12747,10 +13396,13 @@ async function runOneComponent(options, component, index, total) {
12747
13396
  tokensInline,
12748
13397
  tokenMapInline,
12749
13398
  verbose,
13399
+ noCache,
12750
13400
  skillPathOverride,
12751
13401
  skillContentOverride,
13402
+ promptHash,
12752
13403
  existingContentfulEntities,
12753
13404
  existingTokensInline,
13405
+ precomputedCachedNames,
12754
13406
  allowedComponentNames: allowedComponentNames2
12755
13407
  } = options;
12756
13408
  const pos = c.dim(`[${index + 1}/${total}]`);
@@ -12775,6 +13427,40 @@ async function runOneComponent(options, component, index, total) {
12775
13427
  allowedComponents: slot.allowedComponents.filter((name) => allowedComponentNames2.has(name))
12776
13428
  };
12777
13429
  });
13430
+ const cacheComponent = normalizeComponentForCache(component);
13431
+ if (!noCache && precomputedCachedNames.has(component.name)) {
13432
+ writeCachedComponentStatus(pos, component.name, false);
13433
+ return createCachedComponentResult(component.name);
13434
+ }
13435
+ if (!noCache) {
13436
+ const inputHash = hashComponentShape(cacheComponent);
13437
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13438
+ if (resolution && !resolution.humanEdited) {
13439
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13440
+ allowedComponentNames: allowedComponentNames2
13441
+ });
13442
+ storeCache(
13443
+ db,
13444
+ inputHash,
13445
+ "component",
13446
+ component.component_id,
13447
+ resolution.entry.sourceSessionId,
13448
+ resolution.entry.humanEdited,
13449
+ promptHash
13450
+ );
13451
+ writeCachedComponentStatus(pos, component.name, false);
13452
+ return createCachedComponentResult(component.name);
13453
+ }
13454
+ if (resolution?.humanEdited) {
13455
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13456
+ allowedComponentNames: allowedComponentNames2
13457
+ });
13458
+ writeCachedComponentStatus(pos, component.name, true);
13459
+ return createCachedComponentResult(`${component.name}`, [
13460
+ `${component.name}: source changed but human edits preserved`
13461
+ ]);
13462
+ }
13463
+ }
12778
13464
  const componentBuckets = bucketComponentProps(component);
12779
13465
  getDebugLogger().event("analyze", "prop-buckets.generate", { ...componentBuckets });
12780
13466
  const rawComponentsInline = JSON.stringify(
@@ -12849,6 +13535,10 @@ ${outputBuf}`);
12849
13535
  const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings, {
12850
13536
  allowedComponentNames: allowedComponentNames2
12851
13537
  });
13538
+ if (!noCache) {
13539
+ const inputHash = hashComponentShape(cacheComponent);
13540
+ storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
13541
+ }
12852
13542
  return {
12853
13543
  componentName: component.name,
12854
13544
  classified: applied.classified,
@@ -12900,7 +13590,7 @@ async function loadAcceptedNames(sessionId2) {
12900
13590
  try {
12901
13591
  const artifactsRoot = getRefineArtifactsRoot();
12902
13592
  const paths = await getRefineSessionPaths(sessionId2, artifactsRoot);
12903
- const raw = await readFile11(paths.statePath, "utf8");
13593
+ const raw = await readFile12(paths.statePath, "utf8");
12904
13594
  const snapshot = JSON.parse(raw);
12905
13595
  const accepted = snapshot.components.filter((c2) => c2.status === "accepted").map((c2) => c2.name);
12906
13596
  if (accepted.length === 0) return null;
@@ -12909,6 +13599,15 @@ async function loadAcceptedNames(sessionId2) {
12909
13599
  return null;
12910
13600
  }
12911
13601
  }
13602
+ function parsePrecomputedCachedNames(value) {
13603
+ if (!value) return /* @__PURE__ */ new Set();
13604
+ try {
13605
+ const parsed = JSON.parse(value);
13606
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : []);
13607
+ } catch {
13608
+ return /* @__PURE__ */ new Set();
13609
+ }
13610
+ }
12912
13611
  async function runGenerateSkill(skill, opts, verbose = false) {
12913
13612
  const savedCreds = await readExperiencesCredentials();
12914
13613
  const agentName = opts.agent ?? savedCreds.agent;
@@ -12937,8 +13636,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12937
13636
  }
12938
13637
  const generatePromptPath = generatePrompt === void 0 ? configuredGeneratePromptPath : void 0;
12939
13638
  if (generatePromptPath) {
12940
- if (!await pathExists(resolve11(generatePromptPath))) {
12941
- die(`Error: custom prompt path not found: ${resolve11(generatePromptPath)}`);
13639
+ if (!await pathExists(resolve12(generatePromptPath))) {
13640
+ die(`Error: custom prompt path not found: ${resolve12(generatePromptPath)}`);
12942
13641
  }
12943
13642
  if (!generatePromptPath.toLowerCase().endsWith(".md")) {
12944
13643
  process.stderr.write(
@@ -12946,7 +13645,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12946
13645
  `
12947
13646
  );
12948
13647
  }
12949
- process.stderr.write(formatCustomPromptBanner("components", resolve11(generatePromptPath)));
13648
+ process.stderr.write(formatCustomPromptBanner("components", resolve12(generatePromptPath)));
12950
13649
  }
12951
13650
  if (skill === "tokens" && !opts.rawTokens) {
12952
13651
  die("Error: --raw-tokens is required when using generate tokens");
@@ -12962,7 +13661,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12962
13661
  let existingContentfulEntities;
12963
13662
  let existingTokensInline;
12964
13663
  if (opts.existingEntitiesPath) {
12965
- existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve11(opts.existingEntitiesPath));
13664
+ existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve12(opts.existingEntitiesPath));
12966
13665
  if (existingContentfulEntities) {
12967
13666
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
12968
13667
  } else {
@@ -13063,7 +13762,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13063
13762
  rawComponentsInline: sampleInline ?? rawTokensInline,
13064
13763
  propBucketsInline: sampleBuckets ? JSON.stringify(sampleBuckets) : void 0,
13065
13764
  rawTokensInline: skill === "tokens" ? rawTokensInline : void 0,
13066
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13765
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13067
13766
  tokensInline,
13068
13767
  tokenMapInline,
13069
13768
  outDir: process.cwd(),
@@ -13089,7 +13788,64 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13089
13788
  if (skill === "components" && allComponents && sessionId2) {
13090
13789
  const db = openPipelineDb();
13091
13790
  let componentResults;
13791
+ const precomputedCachedNames = parsePrecomputedCachedNames(opts.cachedComponents);
13092
13792
  try {
13793
+ const existingContentfulEntitiesHashInputs = [];
13794
+ if (existingTokensInline) existingContentfulEntitiesHashInputs.push(hashContent(existingTokensInline));
13795
+ const promptHash = await hashPromptForSkill(
13796
+ "components",
13797
+ agent,
13798
+ model,
13799
+ generatePromptPath,
13800
+ existingContentfulEntitiesHashInputs,
13801
+ generatePrompt
13802
+ );
13803
+ if (opts.cacheStatus) {
13804
+ const cacheEnabled = opts.cache !== false && process.env.EDS_NO_CACHE !== "1";
13805
+ const cachedComponents = cacheEnabled ? allComponents.flatMap((component) => {
13806
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13807
+ return resolution ? [{ component, resolution }] : [];
13808
+ }) : [];
13809
+ if (cacheEnabled && opts.restoreCache) {
13810
+ const restorations = [];
13811
+ const rekeyed = [];
13812
+ const componentStatus = db.prepare(
13813
+ "SELECT status FROM raw_components WHERE session_id = ? AND component_id = ?"
13814
+ );
13815
+ for (const { component, resolution } of cachedComponents) {
13816
+ const current = componentStatus.get(sessionId2, component.component_id);
13817
+ if (current?.status === "generated") continue;
13818
+ renameEmptySlots(db, sessionId2, component.component_id, component.name, component.slots.length);
13819
+ restorations.push({
13820
+ sourceSessionId: resolution.entry.sourceSessionId,
13821
+ targetSessionId: sessionId2,
13822
+ componentId: component.component_id
13823
+ });
13824
+ rekeyed.push({ component, cached: resolution.entry });
13825
+ }
13826
+ copyComponentsFromCache(db, restorations, { allowedComponentNames: allowedComponentNames2 });
13827
+ storeCaches(
13828
+ db,
13829
+ rekeyed.map(({ component, cached }) => ({
13830
+ inputHash: hashComponentShape(normalizeComponentForCache(component)),
13831
+ entityType: "component",
13832
+ entityId: component.component_id,
13833
+ sourceSessionId: cached.sourceSessionId,
13834
+ humanEdited: cached.humanEdited,
13835
+ promptHash
13836
+ }))
13837
+ );
13838
+ }
13839
+ const fullyCached = cacheEnabled && cachedComponents.length === allComponents.length;
13840
+ process.stdout.write(`cache-status=${fullyCached ? "hit" : "miss"}
13841
+ `);
13842
+ process.stdout.write(
13843
+ `cache-components=${JSON.stringify(cachedComponents.map(({ component }) => component.name))}
13844
+ `
13845
+ );
13846
+ await exitWithAnalytics(0);
13847
+ return;
13848
+ }
13093
13849
  componentResults = await runAllComponents(
13094
13850
  {
13095
13851
  agent,
@@ -13099,10 +13855,13 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13099
13855
  tokensInline,
13100
13856
  tokenMapInline,
13101
13857
  verbose,
13858
+ noCache: opts.cache === false || process.env.EDS_NO_CACHE === "1",
13102
13859
  skillPathOverride: generatePromptPath,
13103
13860
  skillContentOverride: generatePrompt,
13861
+ promptHash,
13104
13862
  existingContentfulEntities,
13105
13863
  existingTokensInline,
13864
+ precomputedCachedNames,
13106
13865
  allowedComponentNames: allowedComponentNames2
13107
13866
  },
13108
13867
  allComponents
@@ -13111,7 +13870,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13111
13870
  db.close();
13112
13871
  }
13113
13872
  const failed = componentResults.filter((r) => r.failed);
13114
- const generated = componentResults.filter((r) => !r.failed);
13873
+ const cachedResults = componentResults.filter((r) => r.cached);
13874
+ const generated = componentResults.filter((r) => !r.failed && !r.cached);
13115
13875
  const allWarnings = componentResults.flatMap((r) => r.warnings.map((w) => ` ${r.componentName}: ${w}`));
13116
13876
  if (allWarnings.length > 0) {
13117
13877
  process.stderr.write(c.yellow("Warnings:") + "\n" + allWarnings.join("\n") + "\n");
@@ -13127,17 +13887,21 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13127
13887
  const totalExcluded = generated.reduce((s, r) => s + r.excluded, 0);
13128
13888
  const totalRenamedSlots = componentResults.reduce((s, r) => s + r.renamedSlotsCount, 0);
13129
13889
  const allOk = failed.length === 0;
13890
+ const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : "";
13130
13891
  process.stderr.write(
13131
- (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length}/${componentResults.length} components` + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13892
+ (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13132
13893
  );
13133
13894
  process.stdout.write(`renamed-slots: ${totalRenamedSlots}
13134
13895
  `);
13135
- if (generated.length === 0) {
13896
+ if (generated.length === 0 && cachedResults.length === 0) {
13136
13897
  die(
13137
13898
  `Error: all ${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
13138
13899
  );
13139
13900
  }
13140
13901
  } else if (skill === "tokens") {
13902
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
13903
+ const tokenInputContent = rawTokensInline ?? "";
13904
+ const tokenInputHash = hashTokenContent(tokenInputContent);
13141
13905
  const db = openPipelineDb();
13142
13906
  try {
13143
13907
  let resolvedSessionId = opts.session;
@@ -13164,11 +13928,26 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13164
13928
  }
13165
13929
  sessionId2 = resolvedSessionId;
13166
13930
  await bindAnalyticsSessionId(resolvedSessionId);
13931
+ const tokenPromptHash = await hashPromptForSkill("tokens", agent, model);
13932
+ if (!noCache) {
13933
+ const tokenCached = lookupCache(db, tokenInputHash, "token_set", "__tokens__", tokenPromptHash);
13934
+ if (tokenCached) {
13935
+ copyTokensFromCache(db, tokenCached.sourceSessionId, resolvedSessionId);
13936
+ sessionId2 = resolvedSessionId;
13937
+ process.stderr.write(
13938
+ `Done: tokens reused from cache ${c.dim(`(source: ${tokenCached.sourceSessionId.slice(0, 12)})`)}
13939
+ `
13940
+ );
13941
+ db.close();
13942
+ await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13943
+ return;
13944
+ }
13945
+ }
13167
13946
  const prompt = await buildPrompt({
13168
13947
  skill,
13169
13948
  mode: "autonomous",
13170
13949
  rawTokensInline,
13171
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13950
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13172
13951
  tokensInline,
13173
13952
  tokenMapInline,
13174
13953
  outDir: process.cwd()
@@ -13205,6 +13984,9 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13205
13984
  `);
13206
13985
  }
13207
13986
  applyTokenToolCalls(db, resolvedSessionId, tokenCalls, []);
13987
+ if (!noCache) {
13988
+ storeCache(db, tokenInputHash, "token_set", "__tokens__", resolvedSessionId, false, tokenPromptHash);
13989
+ }
13208
13990
  sessionId2 = resolvedSessionId;
13209
13991
  const groupCount = tokenCalls.filter((tc) => tc.tool === "set_group").length;
13210
13992
  process.stderr.write(`Done: ${tokenCount} tokens, ${groupCount} groups stored
@@ -13216,7 +13998,10 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13216
13998
  await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13217
13999
  }
13218
14000
  function addAgentFlags(cmd) {
13219
- return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent");
14001
+ return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent").option(
14002
+ "--no-cache",
14003
+ "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."
14004
+ );
13220
14005
  }
13221
14006
  function registerInternalGenerateCommand(program) {
13222
14007
  const generate = program.command("__generate", { hidden: true }).description("Internal import pipeline generation command");
@@ -13232,6 +14017,12 @@ function registerInternalGenerateCommand(program) {
13232
14017
  "--existing-entities-path <path>",
13233
14018
  "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."
13234
14019
  );
14020
+ componentsCmd.option("--cache-status", "Check whether all selected component definitions are cached");
14021
+ componentsCmd.option("--restore-cache", "Restore cached component definitions into the current session");
14022
+ componentsCmd.option(
14023
+ "--cached-components <json>",
14024
+ "Component names already restored by a prior cache-status lookup; skip per-component cache lookup"
14025
+ );
13235
14026
  addAgentFlags(componentsCmd).action(async (opts) => {
13236
14027
  await runGenerateSkill("components", opts, opts.verbose ?? false);
13237
14028
  });
@@ -13257,6 +14048,7 @@ var init_command = __esm({
13257
14048
  init_agent_output();
13258
14049
  init_GenerateView();
13259
14050
  init_db();
14051
+ init_cache_keys();
13260
14052
  init_read_existing_contentful_entities_from_session();
13261
14053
  init_summarize_existing_contentful_entities();
13262
14054
  init_resolve_session_id();
@@ -13972,7 +14764,7 @@ var init_user_agent = __esm({
13972
14764
  // packages/experience-design-system-cli-legacy/src/apply/api-client.ts
13973
14765
  import { parseCDFComponents } from "@contentful/experience-design-system-types";
13974
14766
  function defaultSleep(delayMs) {
13975
- return new Promise((resolve20) => setTimeout(resolve20, delayMs));
14767
+ return new Promise((resolve22) => setTimeout(resolve22, delayMs));
13976
14768
  }
13977
14769
  function isTransientStatus(status) {
13978
14770
  return status >= 500 && status <= 599;
@@ -14362,7 +15154,7 @@ ${trimmed}`;
14362
15154
  const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
14363
15155
  const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
14364
15156
  const jitter = Math.random() * baseDelay * 0.15;
14365
- await new Promise((resolve20) => setTimeout(resolve20, baseDelay + jitter));
15157
+ await new Promise((resolve22) => setTimeout(resolve22, baseDelay + jitter));
14366
15158
  }
14367
15159
  }
14368
15160
  throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
@@ -15172,7 +15964,7 @@ var init_terminal_capabilities = __esm({
15172
15964
 
15173
15965
  // packages/experience-design-system-cli-legacy/src/apply/command.ts
15174
15966
  import { createElement as createElement2 } from "react";
15175
- import { readFile as readFile12, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15967
+ import { readFile as readFile13, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15176
15968
  import { join as join16 } from "node:path";
15177
15969
  import {
15178
15970
  validateCDF,
@@ -15193,7 +15985,7 @@ function dieWithApiError(error) {
15193
15985
  async function readJsonFile(flag, p) {
15194
15986
  let text;
15195
15987
  try {
15196
- text = await readFile12(p, "utf8");
15988
+ text = await readFile13(p, "utf8");
15197
15989
  } catch {
15198
15990
  return await die2(`Error: file not found: ${p} (from ${flag})`);
15199
15991
  }
@@ -15247,7 +16039,7 @@ async function readTokensFromPath(flag, p) {
15247
16039
  for (const file of files.sort()) {
15248
16040
  let text;
15249
16041
  try {
15250
- text = await readFile12(file, "utf8");
16042
+ text = await readFile13(file, "utf8");
15251
16043
  } catch {
15252
16044
  continue;
15253
16045
  }
@@ -15616,11 +16408,11 @@ var init_command2 = __esm({
15616
16408
  });
15617
16409
 
15618
16410
  // packages/experience-design-system-cli-legacy/src/print/validate/validators/read-json-file.ts
15619
- import { readFile as readFile13 } from "node:fs/promises";
16411
+ import { readFile as readFile14 } from "node:fs/promises";
15620
16412
  async function readJsonFile2(filePath) {
15621
16413
  let content;
15622
16414
  try {
15623
- content = await readFile13(filePath, "utf-8");
16415
+ content = await readFile14(filePath, "utf-8");
15624
16416
  } catch (err) {
15625
16417
  return {
15626
16418
  ok: false,
@@ -16031,7 +16823,7 @@ var init_ValidateView = __esm({
16031
16823
  // packages/experience-design-system-cli-legacy/src/print/command.ts
16032
16824
  import { createElement as createElement3 } from "react";
16033
16825
  import { access as access3, mkdir as mkdir3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
16034
- import { basename as basename5, resolve as resolve12 } from "node:path";
16826
+ import { basename as basename5, resolve as resolve13 } from "node:path";
16035
16827
  import { CDF_SCHEMA_URL } from "@contentful/experience-design-system-types";
16036
16828
  async function die3(message) {
16037
16829
  process.stderr.write(`${message}
@@ -16098,7 +16890,7 @@ function registerPrintCommand(program) {
16098
16890
  "--allow-empty",
16099
16891
  "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."
16100
16892
  ).action(async (opts) => {
16101
- const outPath = resolve12(opts.out);
16893
+ const outPath = resolve13(opts.out);
16102
16894
  await assertOutIsNotDirectory(outPath);
16103
16895
  const sessionId2 = await resolveSession(opts.session, "generate components");
16104
16896
  await bindAnalyticsSessionId(sessionId2);
@@ -16146,7 +16938,7 @@ function registerPrintCommand(program) {
16146
16938
  `
16147
16939
  );
16148
16940
  }
16149
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16941
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16150
16942
  await writeFile3(outPath, `${JSON.stringify(cdfObj, null, 2)}
16151
16943
  `);
16152
16944
  process.stdout.write(
@@ -16155,7 +16947,7 @@ function registerPrintCommand(program) {
16155
16947
  );
16156
16948
  });
16157
16949
  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) => {
16158
- const outPath = resolve12(opts.out);
16950
+ const outPath = resolve13(opts.out);
16159
16951
  await assertOutIsNotDirectory(outPath);
16160
16952
  const sessionId2 = await resolveSession(opts.session, "generate tokens");
16161
16953
  await bindAnalyticsSessionId(sessionId2);
@@ -16170,7 +16962,7 @@ function registerPrintCommand(program) {
16170
16962
  await die3(`Error: no generated tokens in session '${sessionId2}'. Run generate tokens first.`);
16171
16963
  }
16172
16964
  const tree = rebuildDTCGTree(result.groups, result.tokens);
16173
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16965
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16174
16966
  await writeFile3(outPath, `${JSON.stringify(tree, null, 2)}
16175
16967
  `);
16176
16968
  process.stdout.write(
@@ -16319,7 +17111,7 @@ function MapTokensView({ result, onExit }) {
16319
17111
  const timer = setTimeout(onExit, 100);
16320
17112
  return () => clearTimeout(timer);
16321
17113
  }, [onExit]);
16322
- const summary = `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
17114
+ const summary = result.cached ? "reused from cache" : `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
16323
17115
  return /* @__PURE__ */ jsx8(
16324
17116
  CommandCompletionView,
16325
17117
  {
@@ -16453,8 +17245,8 @@ var init_resolve_defaults = __esm({
16453
17245
 
16454
17246
  // packages/experience-design-system-cli-legacy/src/map-tokens/command.ts
16455
17247
  import { createElement as createElement4 } from "react";
16456
- import { readFile as readFile14 } from "node:fs/promises";
16457
- import { resolve as resolve13 } from "node:path";
17248
+ import { readFile as readFile15 } from "node:fs/promises";
17249
+ import { resolve as resolve14 } from "node:path";
16458
17250
  async function renderResult(result) {
16459
17251
  if (process.stdout.isTTY) {
16460
17252
  const { waitUntilExit } = renderWithGoodbye(
@@ -16462,13 +17254,12 @@ async function renderResult(result) {
16462
17254
  );
16463
17255
  await waitUntilExit();
16464
17256
  } else {
16465
- process.stdout.write(
16466
- `map tokens complete
17257
+ const summary = result.cached ? "cached" : `${result.applied} mapping(s) applied`;
17258
+ process.stdout.write(`map tokens complete
16467
17259
  agent: ${result.agent}
16468
17260
  session=${result.sessionId}
16469
- ${result.applied} mapping(s) applied
16470
- `
16471
- );
17261
+ ${summary}
17262
+ `);
16472
17263
  await exitWithAnalytics(0);
16473
17264
  }
16474
17265
  }
@@ -16490,7 +17281,7 @@ async function runMapTokens(opts) {
16490
17281
  let mapPromptPath;
16491
17282
  if (mapPromptOverride?.kind === "text") mapPromptText = mapPromptOverride.value;
16492
17283
  if (mapPromptOverride?.kind === "path") {
16493
- mapPromptPath = resolve13(mapPromptOverride.value);
17284
+ mapPromptPath = resolve14(mapPromptOverride.value);
16494
17285
  try {
16495
17286
  await resolvePromptOverride(mapPromptOverride);
16496
17287
  } catch (error) {
@@ -16510,7 +17301,7 @@ async function runMapTokens(opts) {
16510
17301
  const knownTokenPaths = new Set(tokenLeaves.map((t) => t.path));
16511
17302
  const tokenMapDiagnostics = [];
16512
17303
  if (opts.tokenMap) {
16513
- const fileMappings = JSON.parse(await readFile14(opts.tokenMap, "utf8"));
17304
+ const fileMappings = JSON.parse(await readFile15(opts.tokenMap, "utf8"));
16514
17305
  const validMappings = {};
16515
17306
  for (const [rawName, path] of Object.entries(fileMappings)) {
16516
17307
  if (knownTokenPaths.has(path)) {
@@ -16588,7 +17379,7 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16588
17379
  let existingTokensInline;
16589
17380
  if (opts.existingEntitiesPath) {
16590
17381
  const existingContentfulEntities = await readExistingContentfulEntitiesFromSession(
16591
- resolve13(opts.existingEntitiesPath)
17382
+ resolve14(opts.existingEntitiesPath)
16592
17383
  );
16593
17384
  if (existingContentfulEntities) {
16594
17385
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
@@ -16623,10 +17414,30 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16623
17414
  skipAgent: "true"
16624
17415
  });
16625
17416
  updateStep(db, stepId2, "complete", { applied: "0", skipAgent: "true" });
16626
- await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0 });
17417
+ await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0, cached: false });
16627
17418
  return;
16628
17419
  }
16629
17420
  const agent = configuredAgent;
17421
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
17422
+ const promptHash = await hashPromptForSkill(
17423
+ "map-tokens",
17424
+ agent,
17425
+ model,
17426
+ void 0,
17427
+ existingTokensInline ? [hashContent(existingTokensInline)] : [],
17428
+ mapPromptText
17429
+ );
17430
+ const inputHash = computeMapTokensInputHash(db, sessionId2, componentSourceRefs);
17431
+ if (!noCache) {
17432
+ const cached = lookupCache(db, inputHash, "token_mapping", "__map_tokens__", promptHash);
17433
+ if (cached) {
17434
+ const appliedFromCache = copyMapTokensFromCache(db, cached.sourceSessionId, sessionId2);
17435
+ const stepId2 = createStep(db, sessionId2, "map tokens", { agent, model: model ?? "" });
17436
+ updateStep(db, stepId2, "complete", { cached: "true", applied: String(appliedFromCache) });
17437
+ await renderResult({ agent, sessionId: sessionId2, applied: appliedFromCache, cached: true });
17438
+ return;
17439
+ }
17440
+ }
16630
17441
  const binary = resolveAgentBinary(agent);
16631
17442
  if (!await assertBinaryInPath(binary)) {
16632
17443
  die(
@@ -16646,20 +17457,23 @@ Install it, choose another agent with --agent, or use --print-prompt to run the
16646
17457
  }
16647
17458
  const { calls, warnings: parseWarnings } = parseMapTokenPropToolCallLines(result.stdout);
16648
17459
  const { applied, warnings } = applyMapTokenPropCalls(db, sessionId2, calls, parseWarnings);
17460
+ if (!noCache) {
17461
+ storeCache(db, inputHash, "token_mapping", "__map_tokens__", sessionId2, false, promptHash);
17462
+ }
16649
17463
  updateStep(db, stepId, "complete", { applied: String(applied), warnings: String(warnings.length) });
16650
17464
  if (warnings.length > 0) {
16651
17465
  process.stderr.write(`Warnings:
16652
17466
  ${warnings.map((w) => ` ${w}`).join("\n")}
16653
17467
  `);
16654
17468
  }
16655
- await renderResult({ agent, sessionId: sessionId2, applied });
17469
+ await renderResult({ agent, sessionId: sessionId2, applied, cached: false });
16656
17470
  } finally {
16657
17471
  db.close();
16658
17472
  }
16659
17473
  }
16660
17474
  function registerMapTokensCommand(program) {
16661
17475
  const map = program.command("map", { hidden: true }).description("Suggest token restrictions for generated design-token props");
16662
- 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(
17476
+ 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(
16663
17477
  "--prompt <stage=value>",
16664
17478
  "Override a stage prompt (repeatable). Used here for the map-tokens stage.",
16665
17479
  (v, acc) => [...acc, v],
@@ -16679,6 +17493,7 @@ var init_command4 = __esm({
16679
17493
  init_render_with_goodbye();
16680
17494
  init_src();
16681
17495
  init_db();
17496
+ init_cache_keys();
16682
17497
  init_read_existing_contentful_entities_from_session();
16683
17498
  init_summarize_existing_contentful_entities();
16684
17499
  init_command3();
@@ -16695,7 +17510,7 @@ var init_command4 = __esm({
16695
17510
  });
16696
17511
 
16697
17512
  // packages/experience-design-system-cli-legacy/src/import/path-utils.ts
16698
- import { resolve as resolve14 } from "node:path";
17513
+ import { resolve as resolve15 } from "node:path";
16699
17514
  import { homedir as homedir5 } from "node:os";
16700
17515
  function expandTilde(input) {
16701
17516
  if (input === "~" || input.startsWith("~/") || input.startsWith("~\\")) {
@@ -16709,7 +17524,7 @@ function normalizePath(input) {
16709
17524
  p = p.slice(1, -1);
16710
17525
  }
16711
17526
  p = expandTilde(p);
16712
- return resolve14(p);
17527
+ return resolve15(p);
16713
17528
  }
16714
17529
  var init_path_utils = __esm({
16715
17530
  "packages/experience-design-system-cli-legacy/src/import/path-utils.ts"() {
@@ -16934,7 +17749,7 @@ var init_save_path_resolver = __esm({
16934
17749
  });
16935
17750
 
16936
17751
  // packages/experience-design-system-cli-legacy/src/runs/store.ts
16937
- import { readFile as readFile15, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
17752
+ import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
16938
17753
  import { join as join20 } from "node:path";
16939
17754
  import { homedir as homedir6 } from "node:os";
16940
17755
  import { randomBytes as randomBytes2 } from "node:crypto";
@@ -16974,7 +17789,7 @@ function migrateRecord(rec) {
16974
17789
  }
16975
17790
  async function readFileMaybe() {
16976
17791
  try {
16977
- const raw = await readFile15(RUNS_PATH, "utf8");
17792
+ const raw = await readFile16(RUNS_PATH, "utf8");
16978
17793
  const parsed = JSON.parse(raw);
16979
17794
  if (!READABLE_VERSIONS.has(parsed.version)) {
16980
17795
  throw new Error(
@@ -17038,15 +17853,15 @@ var init_store = __esm({
17038
17853
  });
17039
17854
 
17040
17855
  // packages/experience-design-system-cli-legacy/src/runs/fingerprint.ts
17041
- import { createHash as createHash2 } from "node:crypto";
17042
- import { readFile as readFile16, stat as stat4 } from "node:fs/promises";
17043
- import { resolve as resolve15 } from "node:path";
17856
+ import { createHash as createHash6 } from "node:crypto";
17857
+ import { readFile as readFile17, stat as stat4 } from "node:fs/promises";
17858
+ import { resolve as resolve16 } from "node:path";
17044
17859
  function rowField(row, key) {
17045
17860
  const v = row[key];
17046
17861
  return typeof v === "string" ? v : null;
17047
17862
  }
17048
17863
  function sha256Hex(buf) {
17049
- return createHash2("sha256").update(buf).digest("hex");
17864
+ return createHash6("sha256").update(buf).digest("hex");
17050
17865
  }
17051
17866
  async function buildSourceFingerprint(opts) {
17052
17867
  const rows = opts.db.prepare("SELECT name, source_path FROM raw_components WHERE session_id = ?").all(opts.extractSessionId);
@@ -17056,7 +17871,7 @@ async function buildSourceFingerprint(opts) {
17056
17871
  const sourcePath = rowField(row, "source_path");
17057
17872
  const name = rowField(row, "name");
17058
17873
  if (!sourcePath) continue;
17059
- const abs = resolve15(sourcePath);
17874
+ const abs = resolve16(sourcePath);
17060
17875
  if (seen.has(abs)) continue;
17061
17876
  seen.add(abs);
17062
17877
  try {
@@ -17071,10 +17886,10 @@ async function buildSourceFingerprint(opts) {
17071
17886
  let rawTokensMtime = null;
17072
17887
  let rawTokensContentHash = null;
17073
17888
  if (opts.rawTokensPath) {
17074
- const abs = resolve15(opts.rawTokensPath);
17889
+ const abs = resolve16(opts.rawTokensPath);
17075
17890
  rawTokensPath = abs;
17076
17891
  try {
17077
- const [st, buf] = await Promise.all([stat4(abs), readFile16(abs)]);
17892
+ const [st, buf] = await Promise.all([stat4(abs), readFile17(abs)]);
17078
17893
  rawTokensMtime = st.mtime.toISOString();
17079
17894
  rawTokensContentHash = sha256Hex(buf);
17080
17895
  } catch {
@@ -18829,11 +19644,11 @@ var init_slot_graph = __esm({
18829
19644
  });
18830
19645
 
18831
19646
  // packages/experience-design-system-cli-legacy/src/analyze/select/parser.ts
18832
- import { createHash as createHash3 } from "node:crypto";
19647
+ import { createHash as createHash7 } from "node:crypto";
18833
19648
  import { access as access5 } from "node:fs/promises";
18834
- import { isAbsolute, relative, resolve as resolve16 } from "node:path";
19649
+ import { isAbsolute, relative, resolve as resolve17 } from "node:path";
18835
19650
  function createComponentId(name, resolvedSourcePath) {
18836
- const sourceHash = createHash3("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
19651
+ const sourceHash = createHash7("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
18837
19652
  return `${name}-${sourceHash}`;
18838
19653
  }
18839
19654
  async function resolveComponentSourcePath(source, reviewRoot) {
@@ -18845,7 +19660,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18845
19660
  throw new Error(`Unable to access component source at ${source}`);
18846
19661
  }
18847
19662
  }
18848
- const candidate = resolve16(reviewRoot, source);
19663
+ const candidate = resolve17(reviewRoot, source);
18849
19664
  const relativeToRoot = relative(reviewRoot, candidate);
18850
19665
  if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || isAbsolute(relativeToRoot)) {
18851
19666
  throw new Error(
@@ -18860,7 +19675,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18860
19675
  }
18861
19676
  }
18862
19677
  async function loadReviewInput(components, options = {}) {
18863
- const reviewRoot = resolve16(options.reviewRoot ?? process.cwd());
19678
+ const reviewRoot = resolve17(options.reviewRoot ?? process.cwd());
18864
19679
  const records = await Promise.all(
18865
19680
  components.map(async (component) => {
18866
19681
  let resolvedSourcePath;
@@ -18891,7 +19706,7 @@ var init_parser = __esm({
18891
19706
  });
18892
19707
 
18893
19708
  // packages/experience-design-system-cli-legacy/src/analyze/select/command.ts
18894
- import { access as access6, readFile as readFile17 } from "node:fs/promises";
19709
+ import { access as access6, readFile as readFile18 } from "node:fs/promises";
18895
19710
  async function loadAndValidateForReview(sessionId2, projectRoot) {
18896
19711
  const db = openPipelineDb();
18897
19712
  let rawComponents;
@@ -18909,7 +19724,7 @@ async function patchReviewStateWithValidationErrors(sessionId2, errors, opts = {
18909
19724
  let snapshot;
18910
19725
  try {
18911
19726
  await access6(paths.statePath);
18912
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19727
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18913
19728
  } catch {
18914
19729
  snapshot = await loadAndValidateForReview(sessionId2, void 0);
18915
19730
  snapshot = await ensureRefineSession(sessionId2, artifactsRoot, snapshot);
@@ -18960,7 +19775,7 @@ async function rejectComponentsByName(sessionId2, names, opts = {}) {
18960
19775
  const nameSet = new Set(names);
18961
19776
  let snapshot;
18962
19777
  try {
18963
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19778
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18964
19779
  } catch {
18965
19780
  return;
18966
19781
  }
@@ -19059,9 +19874,9 @@ function spawnGenerateChild(opts) {
19059
19874
  stderr += chunk;
19060
19875
  opts.onStderr?.(chunk);
19061
19876
  });
19062
- const donePromise = new Promise((resolve20) => {
19877
+ const donePromise = new Promise((resolve22) => {
19063
19878
  child.on("close", (code, signal) => {
19064
- resolve20({
19879
+ resolve22({
19065
19880
  exitCode: code ?? 0,
19066
19881
  signal: signal ?? null,
19067
19882
  stdout,
@@ -19069,7 +19884,7 @@ function spawnGenerateChild(opts) {
19069
19884
  });
19070
19885
  });
19071
19886
  child.on("error", (err) => {
19072
- resolve20({
19887
+ resolve22({
19073
19888
  exitCode: 1,
19074
19889
  signal: null,
19075
19890
  stdout,
@@ -27207,6 +28022,9 @@ function shouldGenerateAfterCredentialsValidated(opts) {
27207
28022
  function shouldSkipFinalReviewAfterCredentials(state) {
27208
28023
  return state.finalReviewPassed && state.generateSessionId != null;
27209
28024
  }
28025
+ function resolveNoCacheForGenerate(opts) {
28026
+ return opts.cliNoCache;
28027
+ }
27210
28028
  function shouldBypassPreview(state) {
27211
28029
  return state.credentialsSkipped === true;
27212
28030
  }
@@ -27233,43 +28051,70 @@ var init_wizard_state_transitions = __esm({
27233
28051
  async function runSelectionAgent(options) {
27234
28052
  const db = openPipelineDb();
27235
28053
  let components;
28054
+ const cachedDecisions = /* @__PURE__ */ new Map();
28055
+ const cliVersion2 = await getCliCacheVersion();
28056
+ const promptHash = await hashPromptForSkill(
28057
+ "select",
28058
+ options.agent,
28059
+ options.model,
28060
+ options.promptPath,
28061
+ [SELECT_PAYLOAD_FORMAT],
28062
+ options.promptText
28063
+ );
27236
28064
  try {
27237
28065
  components = loadRawComponents(db, options.sessionId);
28066
+ if (!options.noCache) {
28067
+ for (const component of components) {
28068
+ const componentHash = hashComponentShape(component);
28069
+ const cached = lookupSelectCache(db, componentHash, promptHash, cliVersion2);
28070
+ if (cached) {
28071
+ cachedDecisions.set(component.name, {
28072
+ decision: cached.decision,
28073
+ reason: cached.reason
28074
+ });
28075
+ }
28076
+ }
28077
+ }
27238
28078
  } finally {
27239
28079
  db.close();
27240
28080
  }
27241
28081
  if (components.length === 0) return;
27242
- const invoker2 = createLocalCliAgentInvoker({
27243
- onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
27244
- });
27245
- const selectBuckets = bucketComponentsProps(components);
27246
- for (const assignment of selectBuckets) {
27247
- getDebugLogger().event("analyze", "prop-buckets.select", { ...assignment });
27248
- }
27249
- const prompt = await buildPrompt({
27250
- skill: "select",
27251
- mode: "autonomous",
27252
- rawComponentsInline: JSON.stringify(
27253
- components.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
27254
- null,
27255
- 2
27256
- ),
27257
- propBucketsInline: JSON.stringify(selectBuckets),
27258
- outDir: process.cwd(),
27259
- ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
27260
- ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
27261
- });
27262
- const { result } = await invokeAgentWithOutput(
27263
- invoker2,
27264
- { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
27265
- false
27266
- );
27267
- if (result.timedOut) throw new Error("selection agent timed out");
27268
- if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
27269
- const parsed = parseSelectToolCallLines(result.stdout);
27270
- for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
28082
+ const componentsToRun = components.filter((component) => !cachedDecisions.has(component.name));
28083
+ let calls = [];
28084
+ if (componentsToRun.length > 0) {
28085
+ const invoker2 = createLocalCliAgentInvoker({
28086
+ onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
28087
+ });
28088
+ const selectBuckets = bucketComponentsProps(componentsToRun);
28089
+ for (const assignment of selectBuckets) {
28090
+ getDebugLogger().event("analyze", "prop-buckets.select", { ...assignment });
28091
+ }
28092
+ const prompt = await buildPrompt({
28093
+ skill: "select",
28094
+ mode: "autonomous",
28095
+ rawComponentsInline: JSON.stringify(
28096
+ componentsToRun.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
28097
+ null,
28098
+ 2
28099
+ ),
28100
+ propBucketsInline: JSON.stringify(selectBuckets),
28101
+ outDir: process.cwd(),
28102
+ ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
28103
+ ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
28104
+ });
28105
+ const { result } = await invokeAgentWithOutput(
28106
+ invoker2,
28107
+ { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
28108
+ false
28109
+ );
28110
+ if (result.timedOut) throw new Error("selection agent timed out");
28111
+ if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
28112
+ const parsed = parseSelectToolCallLines(result.stdout);
28113
+ for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
27271
28114
  `);
27272
- const byName = new Map(parsed.calls.map((call) => [call.name, call]));
28115
+ calls = parsed.calls;
28116
+ }
28117
+ const byName = new Map(calls.map((call) => [call.name, call]));
27273
28118
  const writeDb = openPipelineDb();
27274
28119
  try {
27275
28120
  writeDb.exec("BEGIN");
@@ -27277,15 +28122,27 @@ async function runSelectionAgent(options) {
27277
28122
  `UPDATE raw_components SET status = ?, reject_reason = ? WHERE session_id = ? AND component_id = ?`
27278
28123
  );
27279
28124
  for (const component of components) {
28125
+ const cached = cachedDecisions.get(component.name);
27280
28126
  const call = byName.get(component.name);
27281
- if (!call) continue;
27282
- const rejected = call.tool === "reject_component";
28127
+ if (!cached && !call) continue;
28128
+ const rejected = cached ? cached.decision === "rejected" : call.tool === "reject_component";
28129
+ const reason = cached ? cached.reason : call.reason ?? null;
27283
28130
  update.run(
27284
28131
  rejected ? "rejected" : "accepted",
27285
- rejected ? call.reason ?? null : null,
28132
+ rejected ? reason : null,
27286
28133
  options.sessionId,
27287
28134
  component.component_id
27288
28135
  );
28136
+ if (!cached) {
28137
+ storeSelectCache(
28138
+ writeDb,
28139
+ hashComponentShape(component),
28140
+ promptHash,
28141
+ cliVersion2,
28142
+ rejected ? "rejected" : "accepted",
28143
+ rejected ? reason : null
28144
+ );
28145
+ }
27289
28146
  }
27290
28147
  writeDb.exec("COMMIT");
27291
28148
  } catch (error) {
@@ -27295,7 +28152,7 @@ async function runSelectionAgent(options) {
27295
28152
  writeDb.close();
27296
28153
  }
27297
28154
  }
27298
- var DEFAULT_TIMEOUT_MS4;
28155
+ var SELECT_PAYLOAD_FORMAT, DEFAULT_TIMEOUT_MS4;
27299
28156
  var init_run_selection_agent = __esm({
27300
28157
  "packages/experience-design-system-cli-legacy/src/import/tui/run-selection-agent.ts"() {
27301
28158
  "use strict";
@@ -27304,6 +28161,8 @@ var init_run_selection_agent = __esm({
27304
28161
  init_debug_logger();
27305
28162
  init_agent_output();
27306
28163
  init_db();
28164
+ init_cache_keys();
28165
+ SELECT_PAYLOAD_FORMAT = "prop-buckets-v1";
27307
28166
  DEFAULT_TIMEOUT_MS4 = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 5 * 60 * 1e3);
27308
28167
  }
27309
28168
  });
@@ -27322,7 +28181,7 @@ __export(WizardApp_exports, {
27322
28181
  });
27323
28182
  import { useEffect as useEffect15, useRef as useRef10, useState as useState23 } from "react";
27324
28183
  import { Box as Box42, Text as Text46 } from "ink";
27325
- import { join as join23, resolve as resolve17 } from "node:path";
28184
+ import { join as join23, resolve as resolve18 } from "node:path";
27326
28185
  import { appendFileSync as appendFileSync2, writeFileSync } from "node:fs";
27327
28186
  import { access as access7, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
27328
28187
  import { tmpdir } from "node:os";
@@ -27335,21 +28194,28 @@ function buildGenerateComponentsArgs(opts) {
27335
28194
  if (opts.tokensPath) args.push("--tokens", opts.tokensPath);
27336
28195
  if (opts.model) args.push("--model", opts.model);
27337
28196
  if (opts.bedrock) args.push("--bedrock");
28197
+ if (opts.noCache) args.push("--no-cache");
27338
28198
  if (opts.generatePromptPath) args.push("--generate-prompt-path", opts.generatePromptPath);
27339
28199
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27340
28200
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
28201
+ if (opts.restoreCache) args.push("--restore-cache");
28202
+ if (opts.cachedComponents && opts.cachedComponents.length > 0) {
28203
+ args.push("--cached-components", JSON.stringify(opts.cachedComponents));
28204
+ }
27341
28205
  return args;
27342
28206
  }
27343
28207
  function buildGenerateTokensArgs(opts) {
27344
28208
  const args = [findCliPath(), "__generate", "tokens", "--agent", opts.agent, "--raw-tokens", opts.rawTokensPath];
27345
28209
  if (opts.model) args.push("--model", opts.model);
27346
28210
  if (opts.bedrock) args.push("--bedrock");
28211
+ if (opts.noCache) args.push("--no-cache");
27347
28212
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27348
28213
  return args;
27349
28214
  }
27350
28215
  function buildMapTokensArgs(opts) {
27351
28216
  const args = ["map", "tokens", "--session", opts.sessionId, "--agent", opts.agent];
27352
28217
  if (opts.model) args.push("--model", opts.model);
28218
+ if (opts.noCache) args.push("--no-cache");
27353
28219
  if (opts.skipAgent) args.push("--skip-agent");
27354
28220
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27355
28221
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
@@ -27441,6 +28307,7 @@ function WizardApp({
27441
28307
  initialProjectPath,
27442
28308
  host,
27443
28309
  promptOverrides,
28310
+ noCache = false,
27444
28311
  livePreview = true,
27445
28312
  generatePromptPath,
27446
28313
  skipMapTokens = false,
@@ -27465,6 +28332,7 @@ function WizardApp({
27465
28332
  });
27466
28333
  const allowEmptyDeleteAllRef = useRef10(false);
27467
28334
  const generateChildRef = useRef10(null);
28335
+ const generationCachePreflightRef = useRef10(null);
27468
28336
  const selectionToReviewStartedAtRef = useRef10(null);
27469
28337
  const credentialsValidationIdRef = useRef10(0);
27470
28338
  const pendingCredentialValidationRef = useRef10(null);
@@ -27479,8 +28347,9 @@ function WizardApp({
27479
28347
  const existingEntitiesPromiseRef = useRef10(null);
27480
28348
  const generatePromiseRef = useRef10(null);
27481
28349
  const rawTokensEntryReady = !!initialRawTokensPath;
28350
+ const effectiveNoCache = resolveNoCacheForGenerate({ cliNoCache: noCache });
27482
28351
  const initialStepResolved = rawTokensEntryReady ? initialProjectPath ? "path-validation" : "credentials" : initialProjectPath ? "token-input" : "welcome";
27483
- const initialOutDir = initialProjectPath ? join23(resolve17(initialProjectPath), ".contentful") : "";
28352
+ const initialOutDir = initialProjectPath ? join23(resolve18(initialProjectPath), ".contentful") : "";
27484
28353
  const [state, setState] = useState23({
27485
28354
  step: initialStepResolved,
27486
28355
  agent: initialAgent ?? "claude",
@@ -27656,13 +28525,14 @@ If you are using AWS Bedrock, run:
27656
28525
  if (!deferStep) update({ step: nextStep });
27657
28526
  return true;
27658
28527
  };
27659
- const runGenerateTokens = async (rawTokensPath, outDir) => {
28528
+ const runGenerateTokens = async (rawTokensPath, outDir, forceRegenerate = false) => {
27660
28529
  update({ tokenGenerationStatus: "running" });
27661
28530
  const tokenArgs = buildGenerateTokensArgs({
27662
28531
  rawTokensPath,
27663
28532
  agent: state.agent,
27664
28533
  ...state.agentModel ? { model: state.agentModel } : {},
27665
28534
  ...state.bedrock ? { bedrock: true } : {},
28535
+ noCache: effectiveNoCache || forceRegenerate,
27666
28536
  promptOverrides
27667
28537
  });
27668
28538
  const result = await runSpawnedCli(tokenArgs);
@@ -27711,6 +28581,7 @@ If you are using AWS Bedrock, run:
27711
28581
  const timer = startWizardTimer("token-mapping", {
27712
28582
  sessionId: sessionId2,
27713
28583
  silent,
28584
+ noCache: effectiveNoCache,
27714
28585
  skipAgent: skipMapTokens
27715
28586
  });
27716
28587
  let mappablePropCount = 0;
@@ -27719,7 +28590,7 @@ If you are using AWS Bedrock, run:
27719
28590
  const db = openPipelineDb();
27720
28591
  try {
27721
28592
  if (state.tokenSessionId && state.tokenSessionId !== sessionId2) {
27722
- copyTokensBetweenSessions(db, state.tokenSessionId, sessionId2);
28593
+ copyTokensFromCache(db, state.tokenSessionId, sessionId2);
27723
28594
  } else if (!state.tokenSessionId && state.tokensPath) {
27724
28595
  const tokens = await readTokensFromPath("tokens", state.tokensPath);
27725
28596
  storeDtcgTokens(db, sessionId2, [], tokens);
@@ -27759,6 +28630,7 @@ If you are using AWS Bedrock, run:
27759
28630
  sessionId: sessionId2,
27760
28631
  agent: state.agent,
27761
28632
  ...state.agentModel ? { model: state.agentModel } : {},
28633
+ noCache: effectiveNoCache,
27762
28634
  skipAgent: skipMapTokens,
27763
28635
  promptOverrides,
27764
28636
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {}
@@ -27838,7 +28710,7 @@ If you are using AWS Bedrock, run:
27838
28710
  return promise;
27839
28711
  };
27840
28712
  const runExtract = async (projectPath, showProgress = true) => {
27841
- const outDir = join23(resolve17(projectPath), ".contentful");
28713
+ const outDir = join23(resolve18(projectPath), ".contentful");
27842
28714
  const existingEntitiesExpected = !state.credentialsSkipped && Boolean(state.spaceId && state.environmentId && state.cmaToken && projectPath);
27843
28715
  update({
27844
28716
  ...showProgress ? { step: "extracting" } : {},
@@ -27853,6 +28725,10 @@ If you are using AWS Bedrock, run:
27853
28725
  });
27854
28726
  const existingEntitiesPromise = startExistingEntitiesFetch(projectPath, outDir);
27855
28727
  const extractArgs = [findCliPath(), "__extract", "--project", projectPath];
28728
+ if (effectiveNoCache) extractArgs.push("--composition-refresh", "--no-cache");
28729
+ for (const p of promptOverrides ?? []) extractArgs.push("--prompt", p);
28730
+ if (state.agent) extractArgs.push("--agent", state.agent);
28731
+ if (state.bedrock) extractArgs.push("--bedrock");
27856
28732
  let selectionPromptText;
27857
28733
  let selectionPromptPath;
27858
28734
  try {
@@ -27861,7 +28737,7 @@ If you are using AWS Bedrock, run:
27861
28737
  const selectOverride = overrides.get("select");
27862
28738
  if (selectOverride?.kind === "text") selectionPromptText = selectOverride.value;
27863
28739
  if (selectOverride?.kind === "path") {
27864
- selectionPromptPath = resolve17(selectOverride.value);
28740
+ selectionPromptPath = resolve18(selectOverride.value);
27865
28741
  await resolvePromptOverride(selectOverride);
27866
28742
  }
27867
28743
  } catch (error) {
@@ -27885,7 +28761,8 @@ If you are using AWS Bedrock, run:
27885
28761
  agent: state.agent,
27886
28762
  ...state.agentModel ? { model: state.agentModel } : {},
27887
28763
  ...selectionPromptText !== void 0 ? { promptText: selectionPromptText } : {},
27888
- ...selectionPromptPath ? { promptPath: selectionPromptPath } : {}
28764
+ ...selectionPromptPath ? { promptPath: selectionPromptPath } : {},
28765
+ noCache: effectiveNoCache
27889
28766
  });
27890
28767
  })().catch((error) => {
27891
28768
  selectionError = error;
@@ -28030,17 +28907,136 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28030
28907
  generateProgress: null
28031
28908
  }));
28032
28909
  };
28033
- const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2) => ({
28910
+ const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => ({
28034
28911
  sessionId: extractSessionId,
28035
28912
  tokensPath,
28036
28913
  agent: state.agent,
28037
28914
  ...state.agentModel ? { model: state.agentModel } : {},
28038
28915
  ...state.bedrock ? { bedrock: true } : {},
28916
+ noCache: effectiveNoCache,
28039
28917
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {},
28040
28918
  ...generatePromptPath2 ? { generatePromptPath: generatePromptPath2 } : {},
28041
- promptOverrides
28919
+ promptOverrides,
28920
+ ...cachedComponents && cachedComponents.length > 0 ? { cachedComponents } : {}
28042
28921
  });
28043
- const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2));
28922
+ const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents));
28923
+ const buildGenerateCliArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateComponentsArgs(
28924
+ buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents)
28925
+ );
28926
+ const checkGenerateCache = async (extractSessionId, tokensPath) => {
28927
+ const timer = startWizardTimer("generation-cache-status", {
28928
+ sessionId: extractSessionId,
28929
+ noCache: effectiveNoCache
28930
+ });
28931
+ if (effectiveNoCache) {
28932
+ finishWizardTimer("generation-cache-status", timer, { status: "skipped" });
28933
+ return false;
28934
+ }
28935
+ const result = await runCli([...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"]);
28936
+ const hit = result.exitCode === 0 && /^cache-status=hit$/m.test(result.stdout);
28937
+ finishWizardTimer("generation-cache-status", timer, {
28938
+ status: hit ? "hit" : "miss",
28939
+ exitCode: result.exitCode,
28940
+ ...result.exitCode !== 0 ? { stderrTail: result.stderr.trim().slice(-500) } : {}
28941
+ });
28942
+ return hit;
28943
+ };
28944
+ const checkGenerateCacheComponents = async (extractSessionId, tokensPath, restoreCache = false) => {
28945
+ const timer = startWizardTimer("generation-cache-components", {
28946
+ sessionId: extractSessionId,
28947
+ restoreCache,
28948
+ noCache: effectiveNoCache
28949
+ });
28950
+ if (effectiveNoCache) {
28951
+ finishWizardTimer("generation-cache-components", timer, { status: "skipped" });
28952
+ return /* @__PURE__ */ new Set();
28953
+ }
28954
+ const args = [...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"];
28955
+ if (restoreCache) args.push("--restore-cache");
28956
+ const result = await runCli(args);
28957
+ if (result.exitCode !== 0) {
28958
+ finishWizardTimer("generation-cache-components", timer, {
28959
+ status: "error",
28960
+ exitCode: result.exitCode,
28961
+ stderrTail: result.stderr.trim().slice(-500)
28962
+ });
28963
+ return /* @__PURE__ */ new Set();
28964
+ }
28965
+ const line = /^cache-components=(.+)$/m.exec(result.stdout)?.[1];
28966
+ if (!line) {
28967
+ finishWizardTimer("generation-cache-components", timer, {
28968
+ status: "missing-output",
28969
+ exitCode: result.exitCode,
28970
+ cachedCount: 0
28971
+ });
28972
+ return /* @__PURE__ */ new Set();
28973
+ }
28974
+ try {
28975
+ const names = JSON.parse(line);
28976
+ const cachedNames = new Set(
28977
+ Array.isArray(names) ? names.filter((name) => typeof name === "string") : []
28978
+ );
28979
+ finishWizardTimer("generation-cache-components", timer, {
28980
+ status: "complete",
28981
+ exitCode: result.exitCode,
28982
+ cachedCount: cachedNames.size
28983
+ });
28984
+ return cachedNames;
28985
+ } catch {
28986
+ finishWizardTimer("generation-cache-components", timer, {
28987
+ status: "invalid-output",
28988
+ exitCode: result.exitCode,
28989
+ cachedCount: 0
28990
+ });
28991
+ return /* @__PURE__ */ new Set();
28992
+ }
28993
+ };
28994
+ useEffect15(() => {
28995
+ if (state.step !== "extracting" && state.step !== "scope-gate" || !state.extractSessionId || state.existingEntitiesStatus === "running" || state.existingEntitiesStatus === "idle" || state.selectionAgentStatus !== "complete" || state.compositionPhase !== "done" && state.compositionPhase !== "cache-hit" || state.rawTokensPath && state.tokenGenerationStatus !== "complete" || effectiveNoCache) {
28996
+ return;
28997
+ }
28998
+ const sessionId2 = state.extractSessionId;
28999
+ if (generationCachePreflightRef.current?.sessionId === sessionId2) return;
29000
+ const timer = startWizardTimer("generation-cache-preflight", { sessionId: sessionId2 });
29001
+ const db = openPipelineDb();
29002
+ let generatedNames = null;
29003
+ try {
29004
+ const rows = db.prepare("SELECT name, status FROM raw_components WHERE session_id = ?").all(sessionId2);
29005
+ if (rows.length > 0 && rows.every((row) => row.status === "generated")) {
29006
+ generatedNames = new Set(rows.map((row) => row.name));
29007
+ }
29008
+ } finally {
29009
+ db.close();
29010
+ }
29011
+ if (generatedNames) {
29012
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: Promise.resolve(generatedNames) };
29013
+ finishWizardTimer("generation-cache-preflight", timer, {
29014
+ status: "complete",
29015
+ source: "session-db",
29016
+ cachedCount: generatedNames.size
29017
+ });
29018
+ return;
29019
+ }
29020
+ const promise = checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
29021
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise };
29022
+ void promise.then((cachedNames) => {
29023
+ finishWizardTimer("generation-cache-preflight", timer, {
29024
+ status: "complete",
29025
+ source: "cache-status",
29026
+ cachedCount: cachedNames.size
29027
+ });
29028
+ });
29029
+ }, [
29030
+ state.step,
29031
+ state.extractSessionId,
29032
+ state.tokensPath,
29033
+ state.rawTokensPath,
29034
+ state.tokenGenerationStatus,
29035
+ state.selectionAgentStatus,
29036
+ state.compositionPhase,
29037
+ state.existingEntitiesStatus,
29038
+ state.existingEntitiesPath
29039
+ ]);
28044
29040
  const startGeneratePrefetch = (extractSessionId, tokensPath) => {
28045
29041
  const args = buildGenerateArgs(extractSessionId, tokensPath);
28046
29042
  let progressCursor = null;
@@ -28095,13 +29091,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28095
29091
  });
28096
29092
  return donePromise;
28097
29093
  };
28098
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false) => {
29094
+ const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false, cachedComponents = []) => {
28099
29095
  const timer = startWizardTimer("component-generation", {
28100
29096
  sessionId: extractSessionId,
28101
29097
  acceptedCount,
28102
- suppressScreen
29098
+ suppressScreen,
29099
+ noCache: effectiveNoCache
28103
29100
  });
28104
- const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath);
29101
+ const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath, cachedComponents);
28105
29102
  let progressCursor = state.generateProgress;
28106
29103
  const { donePromise } = spawnGenerateChild({
28107
29104
  command: "node",
@@ -28171,6 +29168,25 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28171
29168
  finishSelectionToReviewTimer("error");
28172
29169
  }
28173
29170
  };
29171
+ const finishCachedGeneration = async (sessionId2, acceptedCount) => {
29172
+ if (await runMapTokens2(sessionId2, true)) {
29173
+ finishSelectionToReviewTimer("cached");
29174
+ update(
29175
+ {
29176
+ acceptedCount,
29177
+ autoRejectedCount: 0,
29178
+ generateSessionId: sessionId2,
29179
+ generatedCount: acceptedCount,
29180
+ generateProgress: null,
29181
+ mapTokensStatus: "complete",
29182
+ step: "final-review"
29183
+ },
29184
+ { clearScreen: false }
29185
+ );
29186
+ } else {
29187
+ finishSelectionToReviewTimer("error");
29188
+ }
29189
+ };
28174
29190
  const advanceToPushFlow = (generatedAcceptedCount) => {
28175
29191
  update({ generatedAcceptedCount });
28176
29192
  void runSaveAndPush();
@@ -28355,8 +29371,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28355
29371
  }
28356
29372
  }
28357
29373
  if (await runAgentAuthCheck("generating", true)) {
28358
- update({ step: "generating" });
28359
- void runGenerate(sid, state.tokensPath, state.acceptedCount);
29374
+ const cacheHit = await checkGenerateCache(sid, state.tokensPath);
29375
+ if (cacheHit) {
29376
+ update({ step: "generating" });
29377
+ void finishCachedGeneration(sid, state.acceptedCount);
29378
+ } else {
29379
+ update({ step: "generating" });
29380
+ void runGenerate(sid, state.tokensPath, state.acceptedCount);
29381
+ }
28360
29382
  }
28361
29383
  return;
28362
29384
  }
@@ -28720,7 +29742,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28720
29742
  });
28721
29743
  return { ok: false };
28722
29744
  }
28723
- await new Promise((resolve20) => setTimeout(resolve20, SAVE_CONFIRMATION_DELAY_MS));
29745
+ await new Promise((resolve22) => setTimeout(resolve22, SAVE_CONFIRMATION_DELAY_MS));
28724
29746
  update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
28725
29747
  return { ok: true };
28726
29748
  };
@@ -28797,6 +29819,12 @@ If using a custom host configuration, make sure the space exists on that host.`
28797
29819
  tokenReuseChecked.current = true;
28798
29820
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28799
29821
  const existingTokensPath = join23(state.outDir, "tokens.json");
29822
+ if (effectiveNoCache) {
29823
+ tokenGenerationPromiseRef.current = runAgentAuthCheck(tokenNextStep).then(
29824
+ (ok2) => ok2 ? runGenerateTokens(state.rawTokensPath, state.outDir) : false
29825
+ );
29826
+ return;
29827
+ }
28800
29828
  (async () => {
28801
29829
  try {
28802
29830
  await access7(existingTokensPath);
@@ -28880,7 +29908,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28880
29908
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28881
29909
  update({ step: tokenNextStep });
28882
29910
  if (await runAgentAuthCheck(tokenNextStep)) {
28883
- tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir);
29911
+ tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir, true);
28884
29912
  }
28885
29913
  },
28886
29914
  onQuit: () => process.exit(0)
@@ -28914,7 +29942,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28914
29942
  );
28915
29943
  case "extracting": {
28916
29944
  const ep = state.extractProgress;
28917
- const compositionComplete = state.compositionPhase === "done";
29945
+ const compositionComplete = state.compositionPhase === "done" || state.compositionPhase === "cache-hit";
28918
29946
  const tokenGenerationComplete = !state.rawTokensPath || state.tokenGenerationStatus === "complete";
28919
29947
  const extractionTasksComplete = ep !== null && ep.totalFiles > 0 && compositionComplete && state.selectionAgentStatus === "complete" && tokenGenerationComplete && state.existingEntitiesStatus !== "running";
28920
29948
  if (extractionTasksComplete) return null;
@@ -28929,7 +29957,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28929
29957
  extractDetail = "Scanning files...";
28930
29958
  }
28931
29959
  const selectionDetail = state.selectionAgentStatus === "complete" ? "Selection agent complete" : `Filtering component selection via ${state.agent}...`;
28932
- const compositionDetail = compositionComplete ? "Composition mapping complete" : "Mapping composition from source call sites...";
29960
+ const compositionDetail = state.compositionPhase === "cache-hit" ? "Composition mapping (cached)" : compositionComplete ? "Composition mapping complete" : state.compositionPhase?.startsWith("agent:") ? `Mapping composition via ${state.compositionPhase.slice("agent:".length)}...` : `Mapping composition via ${state.agent}...`;
28933
29961
  return /* @__PURE__ */ jsx50(
28934
29962
  RunningStep,
28935
29963
  {
@@ -28979,6 +30007,29 @@ If using a custom host configuration, make sure the space exists on that host.`
28979
30007
  acceptedCount: decisions.accepted.length,
28980
30008
  rejectedCount: decisions.rejected.length
28981
30009
  }) : null;
30010
+ const preflight = generationCachePreflightRef.current;
30011
+ const cachePromise = preflight?.sessionId === sessionId2 ? preflight.promise : checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
30012
+ if (!preflight || preflight.sessionId !== sessionId2) {
30013
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: cachePromise };
30014
+ }
30015
+ const cacheWaitStartedAt = startWizardTimer("selection-cache-restoration-wait", {
30016
+ sessionId: sessionId2,
30017
+ preflight: Boolean(preflight?.sessionId === sessionId2)
30018
+ });
30019
+ try {
30020
+ const cachedNames = await cachePromise;
30021
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30022
+ status: "complete",
30023
+ cachedCount: cachedNames.size
30024
+ });
30025
+ } catch (error) {
30026
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30027
+ status: "error",
30028
+ error: error instanceof Error ? error.message : String(error)
30029
+ });
30030
+ finishSelectionToReviewTimer("error");
30031
+ throw error;
30032
+ }
28982
30033
  await runScopeGate({
28983
30034
  sessionId: sessionId2,
28984
30035
  decisions,
@@ -28987,12 +30038,35 @@ If using a custom host configuration, make sure the space exists on that host.`
28987
30038
  if (shouldGenerateAfterScopeGate({ acceptedCount })) {
28988
30039
  const authPromise = runAgentAuthCheck("generating", true);
28989
30040
  if (await authPromise) {
28990
- update({
28991
- step: "generating",
28992
- generateProgress: null,
28993
- acceptedCount
30041
+ const comparisonStartedAt = startWizardTimer("generation-cache-set-comparison", {
30042
+ sessionId: sid,
30043
+ acceptedCount: decisions?.accepted.length ?? 0
30044
+ });
30045
+ let cachedNames = await cachePromise;
30046
+ const acceptedNames = decisions?.accepted ?? [];
30047
+ let cacheHit = !effectiveNoCache && acceptedNames.length > 0 && acceptedNames.every((name) => cachedNames.has(name));
30048
+ if (!cacheHit && !effectiveNoCache && acceptedNames.length > 0) {
30049
+ cachedNames = await checkGenerateCacheComponents(sid, state.tokensPath, true);
30050
+ cacheHit = acceptedNames.every((name) => cachedNames.has(name));
30051
+ }
30052
+ finishWizardTimer("generation-cache-set-comparison", comparisonStartedAt, {
30053
+ status: "complete",
30054
+ cacheHit,
30055
+ cachedCount: cachedNames.size,
30056
+ acceptedCount: acceptedNames.length
28994
30057
  });
28995
- void runGenerate(sid, state.tokensPath, acceptedCount);
30058
+ if (cacheHit) {
30059
+ void finishCachedGeneration(sid, acceptedCount);
30060
+ } else {
30061
+ const acceptedNameSet = new Set(acceptedNames);
30062
+ const restoredCachedNames = [...cachedNames].filter((name) => acceptedNameSet.has(name));
30063
+ update({
30064
+ step: "generating",
30065
+ generateProgress: null,
30066
+ acceptedCount
30067
+ });
30068
+ void runGenerate(sid, state.tokensPath, acceptedCount, false, restoredCachedNames);
30069
+ }
28996
30070
  } else {
28997
30071
  finishSelectionToReviewTimer("error");
28998
30072
  }
@@ -29358,7 +30432,7 @@ function registerImportCommand(program) {
29358
30432
  "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",
29359
30433
  (v, acc) => [...acc, v],
29360
30434
  []
29361
- ).action(async (opts) => {
30435
+ ).option("--no-cache", "Re-run all steps even if output already exists").action(async (opts) => {
29362
30436
  const interactiveTerminalSupported = getInteractiveTerminalSupport().supported;
29363
30437
  if (opts.tokens !== void 0) {
29364
30438
  const { access: access9 } = await import("node:fs/promises");
@@ -29395,6 +30469,7 @@ function registerImportCommand(program) {
29395
30469
  ...resolvedModel ? { initialModel: resolvedModel } : {},
29396
30470
  initialProjectPath: opts.project !== "." ? normalizePath(opts.project) : void 0,
29397
30471
  ...buildCompositionForwardingOptions(opts),
30472
+ noCache: opts.cache === false,
29398
30473
  skipMapTokens: false,
29399
30474
  livePreview: true,
29400
30475
  generatePromptPath: creds.generatePromptPath,
@@ -29482,7 +30557,7 @@ async function pathExists3(path) {
29482
30557
  return access8(path).then(() => true).catch(() => false);
29483
30558
  }
29484
30559
  function runSpawn(cmd, args, opts = {}) {
29485
- return new Promise((resolve20) => {
30560
+ return new Promise((resolve22) => {
29486
30561
  const child = spawn6(cmd, args, {
29487
30562
  cwd: opts.cwd,
29488
30563
  env: opts.env ?? process.env,
@@ -29494,7 +30569,7 @@ function runSpawn(cmd, args, opts = {}) {
29494
30569
  const settle = (result) => {
29495
30570
  if (settled) return;
29496
30571
  settled = true;
29497
- resolve20(result);
30572
+ resolve22(result);
29498
30573
  };
29499
30574
  child.on("error", (err) => settle({ exitCode: 1, stdout: "", stderr: err.message }));
29500
30575
  child.stdout.on("data", (d) => {
@@ -30695,13 +31770,13 @@ function PrerequisitesScreen({
30695
31770
  const driver = {
30696
31771
  ...base,
30697
31772
  emit: (event) => setEvents((current) => [...current, event]),
30698
- confirm: (question, defaultYes = true) => new Promise((resolve20) => {
31773
+ confirm: (question, defaultYes = true) => new Promise((resolve22) => {
30699
31774
  setConfirm({
30700
31775
  question,
30701
31776
  defaultYes,
30702
31777
  resolve: (answer) => {
30703
31778
  setConfirm(null);
30704
- resolve20(answer);
31779
+ resolve22(answer);
30705
31780
  }
30706
31781
  });
30707
31782
  })
@@ -30776,8 +31851,8 @@ function SetupScreen({
30776
31851
  useEffect21(() => {
30777
31852
  if (startedRef.current) return;
30778
31853
  startedRef.current = true;
30779
- const runScreen = (build) => new Promise((resolve20) => {
30780
- setScreen(build(resolve20));
31854
+ const runScreen = (build) => new Promise((resolve22) => {
31855
+ setScreen(build(resolve22));
30781
31856
  });
30782
31857
  void (async () => {
30783
31858
  const results = [];
@@ -30955,9 +32030,9 @@ var init_command8 = __esm({
30955
32030
  function rank(p) {
30956
32031
  if (p === "typed-slot") return 1;
30957
32032
  if (p === "structural") return 2;
30958
- if (p === "call-site") return 3;
30959
- if (p === "manifest") return 4;
30960
- if (p === "doc") return 5;
32033
+ if (p === "manifest") return 3;
32034
+ if (p === "doc") return 4;
32035
+ if (p.startsWith("adapter:")) return 5;
30961
32036
  return 6;
30962
32037
  }
30963
32038
  function mergeEdges(all) {
@@ -30993,6 +32068,65 @@ var init_merge_edges = __esm({
30993
32068
  }
30994
32069
  });
30995
32070
 
32071
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/parse-map-edges.ts
32072
+ function parseMapEdges(raw, opts) {
32073
+ const edges = [];
32074
+ const warnings = [];
32075
+ const { componentNames } = opts;
32076
+ for (const line of raw.split("\n")) {
32077
+ const trimmed = line.trim();
32078
+ if (!trimmed.startsWith("{")) continue;
32079
+ let obj;
32080
+ try {
32081
+ obj = JSON.parse(trimmed);
32082
+ } catch {
32083
+ warnings.push(`unparseable line: ${trimmed.slice(0, 120)}`);
32084
+ continue;
32085
+ }
32086
+ if (typeof obj !== "object" || obj === null || !("tool" in obj)) continue;
32087
+ const rec = obj;
32088
+ if (!VALID_MAP_TOOL_NAMES.has(rec.tool)) {
32089
+ warnings.push(`unknown tool: ${String(rec.tool)}`);
32090
+ continue;
32091
+ }
32092
+ if (typeof rec.parent !== "string" || !rec.parent || typeof rec.child !== "string" || !rec.child) {
32093
+ warnings.push("map_edge missing parent/child \u2014 skipped");
32094
+ continue;
32095
+ }
32096
+ const { parent, child } = rec;
32097
+ if (!componentNames.has(parent)) {
32098
+ warnings.push(`map_edge names unknown component (parent): ${parent} \u2014 skipped`);
32099
+ continue;
32100
+ }
32101
+ if (!componentNames.has(child)) {
32102
+ warnings.push(`map_edge names unknown component (child): ${child} \u2014 skipped`);
32103
+ continue;
32104
+ }
32105
+ const edge = { parent, child, provenance: "agent" };
32106
+ if (typeof rec.slot === "string" && rec.slot) {
32107
+ edge.slot = rec.slot;
32108
+ }
32109
+ if (rec.confidence !== void 0) {
32110
+ if (typeof rec.confidence === "number" && rec.confidence >= 1 && rec.confidence <= 5) {
32111
+ edge.confidence = rec.confidence;
32112
+ } else {
32113
+ warnings.push(
32114
+ `map_edge (${parent}\u2192${child}): invalid confidence ${String(rec.confidence)} \u2014 edge kept without confidence`
32115
+ );
32116
+ }
32117
+ }
32118
+ edges.push(edge);
32119
+ }
32120
+ return { edges, warnings };
32121
+ }
32122
+ var VALID_MAP_TOOL_NAMES;
32123
+ var init_parse_map_edges = __esm({
32124
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/parse-map-edges.ts"() {
32125
+ "use strict";
32126
+ VALID_MAP_TOOL_NAMES = /* @__PURE__ */ new Set(["map_edge"]);
32127
+ }
32128
+ });
32129
+
30996
32130
  // packages/experience-design-system-cli-legacy/src/analyze/composition/apply-mapping.ts
30997
32131
  function applyMapping(components, edges) {
30998
32132
  const warnings = [];
@@ -31052,9 +32186,44 @@ var init_apply_mapping = __esm({
31052
32186
  }
31053
32187
  });
31054
32188
 
32189
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/prompt-loader.ts
32190
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
32191
+ import { dirname as dirname17, join as join27, resolve as resolve19 } from "node:path";
32192
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
32193
+ function resolvePromptPath(fileName) {
32194
+ const thisDir = dirname17(fileURLToPath3(import.meta.url));
32195
+ let dir = thisDir;
32196
+ for (; ; ) {
32197
+ const candidate = join27(dir, "prompts");
32198
+ if (existsSync10(candidate)) return join27(candidate, fileName);
32199
+ const parent = resolve19(dir, "..");
32200
+ if (parent === dir) {
32201
+ throw new Error(
32202
+ `prompt file missing from CLI installation (could not locate prompts/ directory from: ${thisDir})`
32203
+ );
32204
+ }
32205
+ dir = parent;
32206
+ }
32207
+ }
32208
+ function loadPrompt(fileName) {
32209
+ const path = resolvePromptPath(fileName);
32210
+ try {
32211
+ return readFileSync10(path, "utf8");
32212
+ } catch {
32213
+ throw new Error(`prompt file missing from CLI installation \u2014 try reinstalling the CLI (looked for: ${path})`);
32214
+ }
32215
+ }
32216
+ var init_prompt_loader = __esm({
32217
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/prompt-loader.ts"() {
32218
+ "use strict";
32219
+ }
32220
+ });
32221
+
31055
32222
  // packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping.ts
31056
- function resolveMapping(input) {
32223
+ async function resolveMapping(input) {
32224
+ const componentNames = new Set(input.components.map((c2) => c2.name));
31057
32225
  const collected = [];
32226
+ const agentWarnings = [];
31058
32227
  for (const c2 of input.components) {
31059
32228
  for (const slot of c2.slots) {
31060
32229
  for (const child of slot.allowedComponents ?? []) {
@@ -31072,41 +32241,17 @@ function resolveMapping(input) {
31072
32241
  if (input.extraEdges) {
31073
32242
  collected.push(...input.extraEdges);
31074
32243
  }
31075
- for (const evidence of input.sourceCallSiteEvidence ?? []) {
31076
- collected.push({
31077
- parent: evidence.parent,
31078
- child: evidence.child,
31079
- ...evidence.slot ? { slot: evidence.slot } : {},
31080
- citation: { sourcePath: evidence.sourcePath, startLine: evidence.startLine, endLine: evidence.endLine },
31081
- provenance: "call-site"
31082
- });
31083
- }
31084
32244
  const coveredParents = new Set(collected.map((e) => e.parent));
31085
32245
  const residueParents = input.components.map((c2) => c2.name).filter((n) => !coveredParents.has(n));
31086
- const debug = getDebugLogger();
31087
- const countByProvenance = (edges) => {
31088
- const counts = {};
31089
- for (const edge of edges) counts[edge.provenance] = (counts[edge.provenance] ?? 0) + 1;
31090
- return counts;
31091
- };
31092
- debug.event("analyze", "composition.pre-merge", {
31093
- edgeCountsByProvenance: countByProvenance(collected),
31094
- coveredParents: [...coveredParents].sort(),
31095
- residueParents,
31096
- sourceCallSiteAccepted: input.sourceCallSiteEvidence?.length ?? 0,
31097
- sourceCallSiteRejected: input.sourceCallSiteRejections?.length ?? 0
31098
- });
32246
+ const shouldRunAgent = input.forceAgent || residueParents.length > 0 && input.files.length > 0;
32247
+ if (shouldRunAgent) {
32248
+ const prompt = input.buildPrompt ? input.buildPrompt(input.files, [...componentNames]) : defaultPrompt(input.files, [...componentNames], input.promptOverride);
32249
+ const raw = await input.runAgentFn({ prompt, files: input.files }) ?? "";
32250
+ const parsed = parseMapEdges(raw, { componentNames });
32251
+ collected.push(...parsed.edges);
32252
+ agentWarnings.push(...parsed.warnings);
32253
+ }
31099
32254
  const merged = mergeEdges(collected);
31100
- debug.event("analyze", "composition.merged", {
31101
- edgeCountsByProvenance: countByProvenance(merged.edges),
31102
- conflicts: merged.conflicts,
31103
- edges: merged.edges.map((edge) => ({
31104
- parent: edge.parent,
31105
- child: edge.child,
31106
- slot: edge.slot ?? null,
31107
- provenance: edge.provenance
31108
- }))
31109
- });
31110
32255
  const base = input.components.map((c2) => ({
31111
32256
  ...c2,
31112
32257
  slots: c2.slots.map((s) => {
@@ -31115,32 +32260,127 @@ function resolveMapping(input) {
31115
32260
  })
31116
32261
  }));
31117
32262
  const applied = applyMapping(base, merged.edges);
31118
- debug.event("analyze", "composition.applied", {
31119
- slots: applied.components.flatMap(
31120
- (component) => component.slots.filter((slot) => (slot.allowedComponents?.length ?? 0) > 0).map((slot) => ({ component: component.name, slot: slot.name, allowedComponents: slot.allowedComponents }))
31121
- ),
31122
- warnings: applied.warnings
31123
- });
31124
32263
  return {
31125
32264
  components: applied.components,
31126
32265
  edges: merged.edges,
31127
32266
  conflicts: merged.conflicts,
31128
- warnings: applied.warnings,
31129
- sourceCallSiteEvidence: [...input.sourceCallSiteEvidence ?? []],
31130
- sourceCallSiteRejections: [...input.sourceCallSiteRejections ?? []]
32267
+ warnings: [...agentWarnings, ...applied.warnings]
31131
32268
  };
31132
32269
  }
32270
+ function defaultPrompt(files, componentNames, promptOverride) {
32271
+ const fileBlocks = files.map((f) => `--- ${f.path} ---
32272
+ ${f.content}`).join("\n\n");
32273
+ const instruction = promptOverride?.trim() ? promptOverride.trim() : loadPrompt("composition-edges.md").trim();
32274
+ return [
32275
+ instruction,
32276
+ "",
32277
+ 'Emit one JSON object per line, each: {"tool":"map_edge","parent":"<Name>","child":"<Name>","slot"?:"<slot>","confidence"?:1-5,"reason":"<cite the file + declaration>"}.',
32278
+ "Use ONLY these exact component names (an edge naming anything else is dropped):",
32279
+ componentNames.join(", "),
32280
+ "",
32281
+ "Candidate files:",
32282
+ fileBlocks
32283
+ ].join("\n");
32284
+ }
31133
32285
  var init_resolve_mapping = __esm({
31134
32286
  "packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping.ts"() {
31135
32287
  "use strict";
31136
32288
  init_merge_edges();
32289
+ init_parse_map_edges();
31137
32290
  init_apply_mapping();
31138
- init_debug_logger();
32291
+ init_prompt_loader();
32292
+ }
32293
+ });
32294
+
32295
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping-cli.ts
32296
+ function resolveCompositionSources(opts) {
32297
+ return {
32298
+ forceAgent: opts.compositionRefresh === true || opts.noCache === true
32299
+ };
32300
+ }
32301
+ var init_resolve_mapping_cli = __esm({
32302
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping-cli.ts"() {
32303
+ "use strict";
32304
+ }
32305
+ });
32306
+
32307
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/candidate-files.ts
32308
+ function matchReason(file) {
32309
+ const segments = file.path.split("/").filter((s) => s !== "");
32310
+ for (const pattern of CANDIDATE_NAME_PATTERNS) {
32311
+ if (segments.some((seg) => pattern.test(seg))) {
32312
+ return `name:${pattern.source.toLowerCase()}`;
32313
+ }
32314
+ }
32315
+ for (const marker of CANDIDATE_CONTENT_MARKERS) {
32316
+ if (file.content.includes(marker)) return `content:${marker}`;
32317
+ }
32318
+ return void 0;
32319
+ }
32320
+ function selectCandidateFiles(files) {
32321
+ const selected = [];
32322
+ const seen = /* @__PURE__ */ new Set();
32323
+ for (const file of files) {
32324
+ if (seen.has(file.path)) continue;
32325
+ const reason = matchReason(file);
32326
+ if (reason === void 0) continue;
32327
+ seen.add(file.path);
32328
+ selected.push({ path: file.path, content: file.content, reason });
32329
+ }
32330
+ return selected;
32331
+ }
32332
+ function capCandidatesToPromptBudget(files, budget = PROMPT_CANDIDATE_TOKEN_BUDGET) {
32333
+ const ordered = [...files].sort((a, b) => a.content.length - b.content.length || a.path.localeCompare(b.path));
32334
+ const kept = [];
32335
+ const dropped = [];
32336
+ let spent = 0;
32337
+ for (const file of ordered) {
32338
+ const cost = tokenCost(file);
32339
+ if (spent + cost > budget) {
32340
+ dropped.push(file);
32341
+ continue;
32342
+ }
32343
+ kept.push(file);
32344
+ spent += cost;
32345
+ }
32346
+ return { kept, dropped };
32347
+ }
32348
+ function tokenCost(file) {
32349
+ return Math.ceil(file.content.length / CHARS_PER_TOKEN);
32350
+ }
32351
+ var CANDIDATE_NAME_PATTERNS, CANDIDATE_CONTENT_MARKERS, PROMPT_CANDIDATE_TOKEN_BUDGET, CHARS_PER_TOKEN;
32352
+ var init_candidate_files = __esm({
32353
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/candidate-files.ts"() {
32354
+ "use strict";
32355
+ CANDIDATE_NAME_PATTERNS = [/mapping/i, /meta/i, /registry/i, /schema/i, /composition/i];
32356
+ CANDIDATE_CONTENT_MARKERS = [
32357
+ "requiredParent",
32358
+ "withParentType",
32359
+ "allowedTagNames",
32360
+ "createContext",
32361
+ "MappingContext",
32362
+ "allowedComponents"
32363
+ ];
32364
+ PROMPT_CANDIDATE_TOKEN_BUDGET = 8e4;
32365
+ CHARS_PER_TOKEN = 4;
32366
+ }
32367
+ });
32368
+
32369
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/composition-cache-key.ts
32370
+ function buildCompositionInputHash(input) {
32371
+ const fileDigest = input.files.map((f) => `${f.path} ${hashContent(f.content)}`).sort().join("");
32372
+ return hashContent(`agent:${input.agent}
32373
+ ${fileDigest}`);
32374
+ }
32375
+ var init_composition_cache_key = __esm({
32376
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/composition-cache-key.ts"() {
32377
+ "use strict";
32378
+ init_cache_keys();
31139
32379
  }
31140
32380
  });
31141
32381
 
31142
32382
  // packages/experience-design-system-cli-legacy/src/analyze/composition/manifest-doc-evidence.ts
31143
- import { dirname as dirname17 } from "node:path";
32383
+ import { dirname as dirname18 } from "node:path";
31144
32384
  function kebabToPascal2(name) {
31145
32385
  return name.split("-").filter(Boolean).map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1)).join("");
31146
32386
  }
@@ -31190,8 +32430,8 @@ function collectAgentsDocEdges(files, components, componentNames) {
31190
32430
  const seen = /* @__PURE__ */ new Set();
31191
32431
  for (const file of files) {
31192
32432
  if (!file.path.endsWith("AGENTS.md")) continue;
31193
- const docDir = dirname17(file.path);
31194
- const parent = components.find((c2) => c2.sourcePath && dirname17(c2.sourcePath) === docDir)?.name;
32433
+ const docDir = dirname18(file.path);
32434
+ const parent = components.find((c2) => c2.sourcePath && dirname18(c2.sourcePath) === docDir)?.name;
31195
32435
  if (!parent || !componentNames.has(parent)) continue;
31196
32436
  for (const line of file.content.split("\n")) {
31197
32437
  const lower = line.toLowerCase();
@@ -31221,172 +32461,12 @@ var init_manifest_doc_evidence = __esm({
31221
32461
  }
31222
32462
  });
31223
32463
 
31224
- // packages/experience-design-system-cli-legacy/src/analyze/composition/source-call-site-evidence.ts
31225
- import { Node as Node49, Project as Project11, SyntaxKind as SyntaxKind15 } from "ts-morph";
31226
- function collectSourceCallSiteEvidence(files, components) {
31227
- const componentNames = new Set(components.map((component) => component.name));
31228
- const componentsBySource = /* @__PURE__ */ new Map();
31229
- for (const component of components) {
31230
- const sourcePath = component.sourcePath ?? component.source;
31231
- const siblings = componentsBySource.get(sourcePath) ?? [];
31232
- siblings.push(component);
31233
- componentsBySource.set(sourcePath, siblings);
31234
- }
31235
- const project = new Project11({
31236
- compilerOptions: { jsx: 1, target: 99, module: 99, moduleResolution: 100, skipLibCheck: true, allowJs: true },
31237
- skipAddingFilesFromTsConfig: true
31238
- });
31239
- const sourceFiles = new Map(
31240
- files.map((file) => [file.path, project.createSourceFile(file.path, file.content, { overwrite: true })])
31241
- );
31242
- const accepted = [];
31243
- const rejected = [];
31244
- for (const [sourcePath, parents] of componentsBySource) {
31245
- const sourceFile = sourceFiles.get(sourcePath);
31246
- if (!sourceFile) continue;
31247
- const boundNames = collectBoundNames(sourceFile);
31248
- for (const parent of parents) {
31249
- for (const jsxElement of [
31250
- ...sourceFile.getDescendantsOfKind(SyntaxKind15.JsxSelfClosingElement),
31251
- ...sourceFile.getDescendantsOfKind(SyntaxKind15.JsxElement)
31252
- ]) {
31253
- const tagName = Node49.isJsxSelfClosingElement(jsxElement) ? jsxElement.getTagNameNode().getText() : jsxElement.getOpeningElement().getTagNameNode().getText();
31254
- const candidate = tagName.split(".").at(-1) ?? tagName;
31255
- if (candidate === parent.name || isIntrinsicTag(candidate)) continue;
31256
- const lines = sourceExcerpt(sourceFile, jsxElement);
31257
- if (!componentNames.has(candidate)) {
31258
- rejected.push({ parent: parent.name, candidate, ...lines, reason: "unknown-component" });
31259
- continue;
31260
- }
31261
- if (!boundNames.has(tagName) && !boundNames.has(candidate)) {
31262
- rejected.push({ parent: parent.name, candidate, ...lines, reason: "unbound-component" });
31263
- continue;
31264
- }
31265
- accepted.push({ parent: parent.name, child: candidate, ...lines, kind: "jsx-render" });
31266
- }
31267
- }
31268
- }
31269
- for (const sourceFile of sourceFiles.values()) {
31270
- collectCallerSlotEvidence(sourceFile, componentNames, collectBoundNames(sourceFile), accepted, rejected);
31271
- }
31272
- const verifiedPairs = new Set(accepted.map((evidence) => `${evidence.parent}\0${evidence.child}`));
31273
- for (const component of components) {
31274
- const candidates = component.slots.flatMap((slot) => [
31275
- ...slot.allowedComponents ?? [],
31276
- ...slot.structuralAllowedComponents ?? []
31277
- ]);
31278
- for (const candidate of candidates) {
31279
- if (verifiedPairs.has(`${component.name}\0${candidate}`)) continue;
31280
- rejected.push({
31281
- parent: component.name,
31282
- candidate,
31283
- sourcePath: component.sourcePath ?? component.source,
31284
- reason: "no-call-site"
31285
- });
31286
- }
31287
- }
31288
- return {
31289
- accepted: dedupeEvidence(accepted),
31290
- rejected: dedupeRejections(rejected)
31291
- };
31292
- }
31293
- function collectCallerSlotEvidence(sourceFile, componentNames, boundNames, accepted, rejected) {
31294
- for (const parentElement of sourceFile.getDescendantsOfKind(SyntaxKind15.JsxElement)) {
31295
- const parentTag = parentElement.getOpeningElement().getTagNameNode().getText();
31296
- const parent = parentTag.split(".").at(-1) ?? parentTag;
31297
- if (!componentNames.has(parent) || !boundNames.has(parentTag)) continue;
31298
- for (const childNode of parentElement.getJsxChildren()) {
31299
- if (!Node49.isJsxElement(childNode) && !Node49.isJsxSelfClosingElement(childNode)) {
31300
- if (Node49.isJsxText(childNode) && childNode.getText().trim()) {
31301
- const lines2 = sourceExcerpt(sourceFile, childNode);
31302
- rejected.push({ parent, candidate: "<text>", ...lines2, reason: "text-only-child" });
31303
- }
31304
- continue;
31305
- }
31306
- const childTag = Node49.isJsxElement(childNode) ? childNode.getOpeningElement().getTagNameNode().getText() : childNode.getTagNameNode().getText();
31307
- const child = childTag.split(".").at(-1) ?? childTag;
31308
- if (isIntrinsicTag(child)) continue;
31309
- const lines = sourceExcerpt(sourceFile, childNode);
31310
- if (!componentNames.has(child)) {
31311
- rejected.push({ parent, candidate: child, ...lines, reason: "unknown-component" });
31312
- continue;
31313
- }
31314
- if (!boundNames.has(childTag) && !boundNames.has(child)) {
31315
- rejected.push({ parent, candidate: child, ...lines, reason: "unbound-component" });
31316
- continue;
31317
- }
31318
- const slotAttribute = Node49.isJsxElement(childNode) ? childNode.getOpeningElement().getAttribute("slot") : childNode.getAttribute("slot");
31319
- const slotValue = slotAttribute && Node49.isJsxAttribute(slotAttribute) ? slotAttribute.getInitializer() : void 0;
31320
- const slot = slotValue && Node49.isStringLiteral(slotValue) ? slotValue.getLiteralValue() : void 0;
31321
- accepted.push({ parent, child, ...slot ? { slot } : {}, ...lines, kind: "jsx-render" });
31322
- }
31323
- }
31324
- }
31325
- function collectBoundNames(sourceFile) {
31326
- const names = /* @__PURE__ */ new Set();
31327
- for (const declaration of sourceFile.getImportDeclarations()) {
31328
- const defaultImport = declaration.getDefaultImport();
31329
- if (defaultImport) names.add(defaultImport.getText());
31330
- const namespaceImport = declaration.getNamespaceImport();
31331
- if (namespaceImport) names.add(namespaceImport.getText());
31332
- for (const namedImport of declaration.getNamedImports()) {
31333
- names.add((namedImport.getAliasNode() ?? namedImport.getNameNode()).getText());
31334
- }
31335
- }
31336
- for (const declaration of sourceFile.getVariableDeclarations()) names.add(declaration.getName());
31337
- for (const declaration of sourceFile.getFunctions()) {
31338
- if (declaration.getName()) names.add(declaration.getName());
31339
- }
31340
- for (const declaration of sourceFile.getClasses()) {
31341
- if (declaration.getName()) names.add(declaration.getName());
31342
- }
31343
- return names;
31344
- }
31345
- function sourceExcerpt(sourceFile, node) {
31346
- const startLine = node.getStartLineNumber();
31347
- const endLine = node.getEndLineNumber();
31348
- const lines = sourceFile.getFullText().split("\n");
31349
- return {
31350
- sourcePath: sourceFile.getFilePath(),
31351
- startLine,
31352
- endLine,
31353
- excerpt: lines.slice(startLine - 1, endLine).join("\n").trim()
31354
- };
31355
- }
31356
- function isIntrinsicTag(tagName) {
31357
- return /^[a-z]/.test(tagName) || tagName.includes("-");
31358
- }
31359
- function dedupeEvidence(evidence) {
31360
- const seen = /* @__PURE__ */ new Set();
31361
- return evidence.filter((item) => {
31362
- const key = `${item.parent}\0${item.child}\0${item.slot ?? ""}\0${item.sourcePath}\0${item.startLine}`;
31363
- if (seen.has(key)) return false;
31364
- seen.add(key);
31365
- return true;
31366
- }).sort(
31367
- (left, right) => `${left.parent}\0${left.child}\0${left.sourcePath}\0${left.startLine}`.localeCompare(
31368
- `${right.parent}\0${right.child}\0${right.sourcePath}\0${right.startLine}`
31369
- )
31370
- );
31371
- }
31372
- function dedupeRejections(rejections) {
31373
- const seen = /* @__PURE__ */ new Set();
31374
- return rejections.filter((item) => {
31375
- const key = `${item.parent}\0${item.candidate}\0${item.sourcePath}\0${item.startLine}`;
31376
- if (seen.has(key)) return false;
31377
- seen.add(key);
31378
- return true;
31379
- });
31380
- }
31381
- var init_source_call_site_evidence = __esm({
31382
- "packages/experience-design-system-cli-legacy/src/analyze/composition/source-call-site-evidence.ts"() {
31383
- "use strict";
31384
- }
31385
- });
31386
-
31387
32464
  // packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts
31388
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile18, stat as stat6 } from "node:fs/promises";
31389
- import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolve18 } from "node:path";
32465
+ import { mkdir as mkdir6, readdir as readdir3, readFile as readFile19, stat as stat6 } from "node:fs/promises";
32466
+ import { isAbsolute as isAbsolute2, join as join28, relative as relative2, resolve as resolve20 } from "node:path";
32467
+ function resolveExtractNoCache(opts) {
32468
+ return opts.noCache === true || opts.cache === false;
32469
+ }
31390
32470
  function isDenylistedNoiseFile(name) {
31391
32471
  return DENYLISTED_EXACT_FILE_NAMES.has(name) || DENYLISTED_FILE_NAME_PATTERNS.some((pattern) => pattern.test(name));
31392
32472
  }
@@ -31400,12 +32480,12 @@ async function retryDatabaseWrite(operation, attempts = 8) {
31400
32480
  } catch (error) {
31401
32481
  const message = error instanceof Error ? error.message : String(error);
31402
32482
  if (!/database is locked|database is busy/i.test(message) || attempt >= attempts) throw error;
31403
- await new Promise((resolve20) => setTimeout(resolve20, Math.min(250 * attempt, 2e3)));
32483
+ await new Promise((resolve22) => setTimeout(resolve22, Math.min(250 * attempt, 2e3)));
31404
32484
  }
31405
32485
  }
31406
32486
  }
31407
32487
  function resolveFromProjectRoot(projectRoot, inputPath) {
31408
- return isAbsolute2(inputPath) ? inputPath : resolve18(projectRoot, inputPath);
32488
+ return isAbsolute2(inputPath) ? inputPath : resolve20(projectRoot, inputPath);
31409
32489
  }
31410
32490
  async function pathExists4(path) {
31411
32491
  return Boolean(await stat6(path).catch(() => null));
@@ -31416,7 +32496,7 @@ async function collectSourceFiles(directory, onProgress) {
31416
32496
  const entries = await readdir3(currentDirectory, { withFileTypes: true });
31417
32497
  const subdirs = [];
31418
32498
  for (const entry of entries) {
31419
- const fullPath = join27(currentDirectory, entry.name);
32499
+ const fullPath = join28(currentDirectory, entry.name);
31420
32500
  if (entry.isDirectory()) {
31421
32501
  if (!IGNORED_DIRECTORY_NAMES.has(entry.name)) {
31422
32502
  subdirs.push(fullPath);
@@ -31443,6 +32523,12 @@ async function collectSourceFiles(directory, onProgress) {
31443
32523
  await visit(directory);
31444
32524
  return files.sort();
31445
32525
  }
32526
+ function resolveCompositionAgentName(flagValue) {
32527
+ if (flagValue && isAgentName(flagValue)) return flagValue;
32528
+ const env = process.env["EDS_COMPOSITION_AGENT"];
32529
+ if (env && isAgentName(env)) return env;
32530
+ return DEFAULT_AGENT_NAME;
32531
+ }
31446
32532
  async function readCandidateFiles(components, extraFiles = []) {
31447
32533
  const paths = new Set(extraFiles);
31448
32534
  for (const c2 of components) {
@@ -31452,7 +32538,7 @@ async function readCandidateFiles(components, extraFiles = []) {
31452
32538
  await Promise.all(
31453
32539
  [...paths].map(async (p) => {
31454
32540
  try {
31455
- const content = await readFile18(p, "utf8");
32541
+ const content = await readFile19(p, "utf8");
31456
32542
  out.push({ path: p, content });
31457
32543
  } catch {
31458
32544
  }
@@ -31461,11 +32547,21 @@ async function readCandidateFiles(components, extraFiles = []) {
31461
32547
  return out;
31462
32548
  }
31463
32549
  function registerInternalExtractCommand(program) {
31464
- program.command("__extract", { hidden: true }).description("Extract component definitions from a project").requiredOption("--project <path>", "Path to the project root").option("--dir <path>", "Path to the component source directory relative to the project root").option(
32550
+ const extractCmd = program.command("__extract", { hidden: true }).description("Extract component definitions from a project").requiredOption("--project <path>", "Path to the project root").option("--dir <path>", "Path to the component source directory relative to the project root").option(
31465
32551
  "--resolve-unreachable <mode>",
31466
32552
  "Retry pass for unresolved Svelte Props types: 'auto' (default), 'always', or 'never'",
31467
32553
  "auto"
31468
- ).action(async (opts) => {
32554
+ ).option("--composition-refresh", "Force the mapping agent to run even where deterministic sources answered").option("--no-cache", "Re-run extraction even when all source files are unchanged").option(
32555
+ "--prompt <stage=value>",
32556
+ "Override a stage prompt (repeatable). value is a file path or literal text, e.g. --prompt composition=./p.md",
32557
+ (v, acc) => [...acc, v],
32558
+ []
32559
+ );
32560
+ addAgentModelOptions(extractCmd, {
32561
+ includeModel: false,
32562
+ agentDescription: "Coding agent for composition mapping resolution (claude|codex|opencode|cursor)"
32563
+ }).action(async (opts) => {
32564
+ const noCache = resolveExtractNoCache(opts);
31469
32565
  const resolveUnreachable = (() => {
31470
32566
  const v = opts.resolveUnreachable ?? "auto";
31471
32567
  if (v !== "auto" && v !== "always" && v !== "never") {
@@ -31475,8 +32571,16 @@ function registerInternalExtractCommand(program) {
31475
32571
  }
31476
32572
  return v;
31477
32573
  })();
31478
- const projectRoot = resolve18(opts.project);
31479
- const outDir = join27(projectRoot, ".contentful");
32574
+ if (opts.bedrock) {
32575
+ const bedrockAgent = resolveCompositionAgentName(opts.agent);
32576
+ if (!agentSupportsBedrock(bedrockAgent)) {
32577
+ process.stderr.write(`Error: --bedrock is not supported for --agent ${bedrockAgent}
32578
+ `);
32579
+ process.exit(1);
32580
+ }
32581
+ }
32582
+ const projectRoot = resolve20(opts.project);
32583
+ const outDir = join28(projectRoot, ".contentful");
31480
32584
  let sourceDirectory;
31481
32585
  if (opts.dir !== void 0) {
31482
32586
  sourceDirectory = resolveFromProjectRoot(projectRoot, opts.dir);
@@ -31499,16 +32603,50 @@ function registerInternalExtractCommand(program) {
31499
32603
  process.stderr.write(`progress=scan-done:${sourceFiles.length}
31500
32604
  `);
31501
32605
  }
31502
- const extraction = await extractComponents(
31503
- sourceFiles,
31504
- ({ filesProcessed, componentsFound }) => {
32606
+ const extractionCacheDb = openPipelineDb();
32607
+ let extraction;
32608
+ let extractionCacheHits = 0;
32609
+ try {
32610
+ const cacheVersion = await getCliCacheVersion();
32611
+ const cachedByPath = /* @__PURE__ */ new Map();
32612
+ if (!noCache && sourceFiles.length > 0) {
32613
+ const hashes = await Promise.all(
32614
+ sourceFiles.map(async (filePath) => [filePath, await hashFile(filePath)])
32615
+ );
32616
+ for (const [filePath, fileHash] of hashes) {
32617
+ cachedByPath.set(filePath, lookupExtractCache(extractionCacheDb, fileHash, cacheVersion));
32618
+ }
32619
+ }
32620
+ const allFilesCached = !noCache && sourceFiles.length > 0 && sourceFiles.every((filePath) => cachedByPath.get(filePath) !== null);
32621
+ if (allFilesCached) {
32622
+ const cachedComponents = sourceFiles.flatMap((filePath) => cachedByPath.get(filePath).components);
32623
+ extractionCacheHits = sourceFiles.length;
32624
+ extraction = { components: cachedComponents, warnings: [], exclusions: [] };
31505
32625
  if (!process.stdout.isTTY) {
31506
- process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
31507
- `);
32626
+ process.stderr.write(
32627
+ `progress=extract:${sourceFiles.length}/${sourceFiles.length}:${cachedComponents.length}
32628
+ `
32629
+ );
31508
32630
  }
31509
- },
31510
- { resolveUnreachable, projectRoot }
31511
- );
32631
+ getDebugLogger().event("analyze", "extract.cache-hit", {
32632
+ files: sourceFiles.length,
32633
+ components: cachedComponents.length
32634
+ });
32635
+ } else {
32636
+ extraction = await extractComponents(
32637
+ sourceFiles,
32638
+ ({ filesProcessed, componentsFound }) => {
32639
+ if (!process.stdout.isTTY) {
32640
+ process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
32641
+ `);
32642
+ }
32643
+ },
32644
+ { resolveUnreachable, projectRoot }
32645
+ );
32646
+ }
32647
+ } finally {
32648
+ extractionCacheDb.close();
32649
+ }
31512
32650
  await mkdir6(outDir, { recursive: true });
31513
32651
  const db = openPipelineDb();
31514
32652
  const { sessionId: sessionId2 } = getOrCreateSession(db, void 0, void 0, {
@@ -31523,6 +32661,25 @@ function registerInternalExtractCommand(program) {
31523
32661
  const stepId = createStep(db, sessionId2, "analyze extract", {
31524
32662
  project: projectRoot
31525
32663
  });
32664
+ if (extractionCacheHits === 0 && !noCache) {
32665
+ const cacheVersion = await getCliCacheVersion();
32666
+ const componentsBySourcePath = /* @__PURE__ */ new Map();
32667
+ for (const component of extraction.components) {
32668
+ if (!component.sourcePath) continue;
32669
+ const components = componentsBySourcePath.get(component.sourcePath) ?? [];
32670
+ components.push(component);
32671
+ componentsBySourcePath.set(component.sourcePath, components);
32672
+ }
32673
+ for (const filePath of sourceFiles) {
32674
+ storeExtractCache(
32675
+ db,
32676
+ filePath,
32677
+ await hashFile(filePath),
32678
+ cacheVersion,
32679
+ componentsBySourcePath.get(filePath) ?? []
32680
+ );
32681
+ }
32682
+ }
31526
32683
  const classifiedComponents = extraction.components.map(preClassifyComponent);
31527
32684
  for (const exclusion of extraction.exclusions ?? []) {
31528
32685
  getDebugLogger().event("filter", "extract.excluded", { ...exclusion });
@@ -31546,9 +32703,28 @@ function registerInternalExtractCommand(program) {
31546
32703
  storeRawComponents(db, sessionId2, validatedComponents);
31547
32704
  process.stdout.write(`session=${sessionId2}
31548
32705
  `);
31549
- let sourceCallSiteEvidence = [];
31550
- let sourceCallSiteRejections = [];
31551
32706
  {
32707
+ const sources = resolveCompositionSources({
32708
+ ...opts,
32709
+ noCache
32710
+ });
32711
+ const { overrides: promptOverrides, errors: promptErrors } = parsePromptOverrides(opts.prompt ?? []);
32712
+ for (const err of promptErrors) {
32713
+ process.stderr.write(`Error: ${err}
32714
+ `);
32715
+ process.exit(1);
32716
+ }
32717
+ let compositionPrompt;
32718
+ const compositionOverride = promptOverrides.get("composition");
32719
+ if (compositionOverride) {
32720
+ try {
32721
+ compositionPrompt = await resolvePromptOverride(compositionOverride);
32722
+ } catch (e) {
32723
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : String(e)}
32724
+ `);
32725
+ process.exit(1);
32726
+ }
32727
+ }
31552
32728
  {
31553
32729
  const emitCompositionProgress = (phase) => {
31554
32730
  if (!process.stdout.isTTY) process.stderr.write(`progress=composition:${phase}
@@ -31556,18 +32732,62 @@ function registerInternalExtractCommand(program) {
31556
32732
  };
31557
32733
  emitCompositionProgress("resolving");
31558
32734
  const allFiles = await readCandidateFiles(validatedComponents, sourceFiles);
32735
+ const selectedCandidates = selectCandidateFiles(allFiles).map((c2) => ({ path: c2.path, content: c2.content }));
32736
+ const capped = capCandidatesToPromptBudget(selectedCandidates);
32737
+ const promptFiles = capped.kept;
32738
+ if (capped.dropped.length > 0) {
32739
+ process.stderr.write(
32740
+ `Warning: composition \u2014 ${capped.dropped.length} candidate file(s) omitted from the agent prompt to fit the context budget; resolution runs on the ${promptFiles.length} highest-value files.
32741
+ `
32742
+ );
32743
+ }
31559
32744
  const runtimeFiles = allFiles.map((c2) => ({ path: c2.path, content: c2.content }));
32745
+ const resolverAgent = resolveCompositionAgentName(opts.agent);
31560
32746
  const componentNameSet = new Set(validatedComponents.map((c2) => c2.name));
32747
+ const cacheVersion = await getCliCacheVersion();
32748
+ let lastAgentExitCode = 0;
32749
+ const spawnAgent = async (prompt) => {
32750
+ const res = await runAgent({
32751
+ agent: resolverAgent,
32752
+ prompt,
32753
+ timeoutMs: 12e4,
32754
+ promptViaStdin: true,
32755
+ onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
32756
+ });
32757
+ lastAgentExitCode = res.exitCode;
32758
+ if (res.exitCode !== 0 && res.stderr.trim()) {
32759
+ process.stderr.write(`Warning: composition \u2014 agent exited ${res.exitCode}: ${res.stderr.trim()}
32760
+ `);
32761
+ }
32762
+ return res.stdout;
32763
+ };
31561
32764
  const manifestDocEdges = collectManifestDocEdges(runtimeFiles, validatedComponents, componentNameSet);
31562
32765
  const extraEdges = manifestDocEdges;
31563
- const sourceCallSites = collectSourceCallSiteEvidence(runtimeFiles, validatedComponents);
31564
- sourceCallSiteEvidence = sourceCallSites.accepted;
31565
- sourceCallSiteRejections = sourceCallSites.rejected;
31566
- const result = resolveMapping({
32766
+ const agentCacheKey = buildCompositionInputHash({
32767
+ files: promptFiles,
32768
+ agent: resolverAgent
32769
+ });
32770
+ const result = await resolveMapping({
31567
32771
  components: validatedComponents,
31568
32772
  ...extraEdges.length > 0 ? { extraEdges } : {},
31569
- sourceCallSiteEvidence,
31570
- sourceCallSiteRejections
32773
+ forceAgent: sources.forceAgent,
32774
+ files: promptFiles,
32775
+ ...compositionPrompt ? { promptOverride: compositionPrompt } : {},
32776
+ runAgentFn: async ({ prompt }) => {
32777
+ if (!noCache && !opts.compositionRefresh) {
32778
+ const cached = lookupCompositionCache(db, agentCacheKey, cacheVersion);
32779
+ if (cached !== null) {
32780
+ emitCompositionProgress("cache-hit");
32781
+ return cached;
32782
+ }
32783
+ }
32784
+ emitCompositionProgress(`agent:${resolverAgent}`);
32785
+ const stdout = await spawnAgent(prompt);
32786
+ if (lastAgentExitCode === 0) {
32787
+ storeCompositionCache(db, agentCacheKey, cacheVersion, stdout);
32788
+ }
32789
+ return stdout;
32790
+ }
31571
32791
  });
31572
32792
  emitCompositionProgress("done");
31573
32793
  for (const w of result.warnings) process.stderr.write(`Warning: composition \u2014 ${w}
@@ -31597,13 +32817,7 @@ function registerInternalExtractCommand(program) {
31597
32817
  sessionId2,
31598
32818
  sourceFiles.map((f) => relative2(projectRoot, f))
31599
32819
  );
31600
- await retryDatabaseWrite(
31601
- () => updateStep(db, stepId, "complete", {
31602
- sessionId: sessionId2,
31603
- compositionEvidence: JSON.stringify(sourceCallSiteEvidence),
31604
- compositionRejections: JSON.stringify(sourceCallSiteRejections)
31605
- })
31606
- );
32820
+ await retryDatabaseWrite(() => updateStep(db, stepId, "complete", { sessionId: sessionId2 }));
31607
32821
  enrichCommandResult({ extracted_component_count: validatedComponents.length });
31608
32822
  db.close();
31609
32823
  const allWarnings = [...extraction.warnings, ...filterWarnings];
@@ -31625,12 +32839,18 @@ var SCANNED_FILE_EXTENSIONS, DENYLIST_GATED_EXTENSIONS, DENYLISTED_EXACT_FILE_NA
31625
32839
  var init_extract_command = __esm({
31626
32840
  "packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts"() {
31627
32841
  "use strict";
32842
+ init_agent_model_options();
31628
32843
  init_src2();
31629
32844
  init_db();
32845
+ init_cache_keys();
31630
32846
  init_cycle_detection();
31631
32847
  init_resolve_mapping();
32848
+ init_resolve_mapping_cli();
32849
+ init_candidate_files();
32850
+ init_composition_cache_key();
31632
32851
  init_manifest_doc_evidence();
31633
- init_source_call_site_evidence();
32852
+ init_prompt_overrides();
32853
+ init_src();
31634
32854
  init_analytics();
31635
32855
  init_debug_logger();
31636
32856
  SCANNED_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".js", ".jsx", ".svelte", ".ts", ".tsx", ".vue"]);
@@ -31724,9 +32944,9 @@ __export(program_exports, {
31724
32944
  runBuild: () => runBuild
31725
32945
  });
31726
32946
  import { spawn as spawn7 } from "node:child_process";
31727
- import { fileURLToPath as fileURLToPath3 } from "node:url";
31728
- import { dirname as dirname18, join as join28, resolve as resolve19 } from "node:path";
31729
- import { readFileSync as readFileSync10 } from "node:fs";
32947
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
32948
+ import { dirname as dirname19, join as join29, resolve as resolve21 } from "node:path";
32949
+ import { readFileSync as readFileSync11 } from "node:fs";
31730
32950
  import { Command } from "commander";
31731
32951
  async function runBuild(opts) {
31732
32952
  return new Promise((resolvePromise) => {
@@ -31755,7 +32975,7 @@ function resolveBedrockFromAncestors(actionCommand) {
31755
32975
  }
31756
32976
  function registerBuildCommand(program) {
31757
32977
  program.command("build").description("Rebuild from source and re-link exo/experiences binaries to this build").helpOption(false).action(async () => {
31758
- const pkgRoot = resolve19(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
32978
+ const pkgRoot = resolve21(dirname19(fileURLToPath4(import.meta.url)), "..", "..");
31759
32979
  process.stderr.write("\u2699 Building from source...\n");
31760
32980
  const { exitCode } = await runBuild({
31761
32981
  spawnFn: () => spawn7("pnpm", ["build"], { cwd: pkgRoot, stdio: "inherit" }),
@@ -31825,7 +33045,7 @@ var init_program = __esm({
31825
33045
  init_analytics();
31826
33046
  init_credentials_store();
31827
33047
  init_cli_path();
31828
- pkg3 = JSON.parse(readFileSync10(join28(findPkgRoot(), "package.json"), "utf8"));
33048
+ pkg3 = JSON.parse(readFileSync11(join29(findPkgRoot(), "package.json"), "utf8"));
31829
33049
  }
31830
33050
  });
31831
33051