@contentful/experience-design-system-cli 2.35.1-dev-build-a91cc5b7.0 → 2.35.1-dev-build-f520d7cb.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 +1665 -445
  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";
@@ -10336,6 +10393,41 @@ var init_read3 = __esm({
10336
10393
  }
10337
10394
  });
10338
10395
 
10396
+ // packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts
10397
+ import { createHash as createHash2 } from "node:crypto";
10398
+ function hashComponentShape(component) {
10399
+ const payload = {
10400
+ framework: component.framework,
10401
+ name: component.name,
10402
+ source: component.source,
10403
+ props: component.props.map((p) => ({
10404
+ name: p.name,
10405
+ type: p.type
10406
+ })),
10407
+ slots: component.slots.map((s) => ({
10408
+ name: s.name,
10409
+ isDefault: s.isDefault
10410
+ }))
10411
+ };
10412
+ return createHash2("sha256").update(JSON.stringify(payload)).digest("hex");
10413
+ }
10414
+ var init_hash_component_shape = __esm({
10415
+ "packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts"() {
10416
+ "use strict";
10417
+ }
10418
+ });
10419
+
10420
+ // packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts
10421
+ import { createHash as createHash3 } from "node:crypto";
10422
+ function hashTokenContent(rawTokenContent) {
10423
+ return createHash3("sha256").update(rawTokenContent.trim()).digest("hex");
10424
+ }
10425
+ var init_hash_token_content = __esm({
10426
+ "packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts"() {
10427
+ "use strict";
10428
+ }
10429
+ });
10430
+
10339
10431
  // packages/experience-design-system-cli-legacy/src/session/repositories/tokens/write.ts
10340
10432
  function deleteRawTokensForSession(db, sessionId2) {
10341
10433
  db.prepare("DELETE FROM raw_token_groups WHERE session_id = ?").run(sessionId2);
@@ -10499,17 +10591,47 @@ var init_replace_raw_prop_token_paths = __esm({
10499
10591
  }
10500
10592
  });
10501
10593
 
10594
+ // packages/experience-design-system-cli-legacy/src/session/cache-keys.ts
10595
+ var cache_keys_exports = {};
10596
+ __export(cache_keys_exports, {
10597
+ hashContent: () => hashContent,
10598
+ hashFile: () => hashFile,
10599
+ hashPromptForSkill: () => hashPromptForSkill
10600
+ });
10601
+ import { createHash as createHash4 } from "node:crypto";
10602
+ import { readFile as readFile7 } from "node:fs/promises";
10603
+ import { resolve as resolve8 } from "node:path";
10604
+ function hashContent(content) {
10605
+ return createHash4("sha256").update(content).digest("hex");
10606
+ }
10607
+ async function hashFile(filePath) {
10608
+ const content = await readFile7(filePath, "utf8");
10609
+ return hashContent(content);
10610
+ }
10611
+ async function hashPromptForSkill(skill, agent, model, skillPathOverride, extraInputs = [], skillContentOverride) {
10612
+ const promptHash = skillContentOverride ? hashContent(skillContentOverride) : skillPathOverride ? await hashFile(resolve8(skillPathOverride)) : await hashFile(resolveSkillPath(skill));
10613
+ const extraSegment = extraInputs.length > 0 ? `|${extraInputs.join("|")}` : "";
10614
+ return hashContent(`${promptHash}|${agent}|${model ?? ""}${extraSegment}`);
10615
+ }
10616
+ var init_cache_keys = __esm({
10617
+ "packages/experience-design-system-cli-legacy/src/session/cache-keys.ts"() {
10618
+ "use strict";
10619
+ init_src();
10620
+ }
10621
+ });
10622
+
10502
10623
  // packages/experience-design-system-cli-legacy/src/session/db.ts
10503
10624
  import { DatabaseSync } from "node:sqlite";
10504
10625
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
10505
- import { readFile as readFile7 } from "node:fs/promises";
10626
+ import { readFile as readFile8 } from "node:fs/promises";
10627
+ import { createHash as createHash5 } from "node:crypto";
10506
10628
  import { homedir as homedir3 } from "node:os";
10507
- import { dirname as dirname14, resolve as resolve8 } from "node:path";
10629
+ import { dirname as dirname14, resolve as resolve9 } from "node:path";
10508
10630
  function getPipelineDbPath() {
10509
10631
  if (process.env.EDS_PIPELINE_DB_PATH) {
10510
- return resolve8(process.env.EDS_PIPELINE_DB_PATH);
10632
+ return resolve9(process.env.EDS_PIPELINE_DB_PATH);
10511
10633
  }
10512
- return resolve8(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10634
+ return resolve9(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10513
10635
  }
10514
10636
  function openPipelineDb(dbPath) {
10515
10637
  const path = dbPath ?? getPipelineDbPath();
@@ -10643,8 +10765,134 @@ function applyDbMigrations(db) {
10643
10765
  if (!rawSlotColsForRationale.some((c2) => c2.name === "rationale")) {
10644
10766
  db.exec("ALTER TABLE raw_slots ADD COLUMN rationale TEXT");
10645
10767
  }
10646
- for (const table of REMOVED_CACHE_TABLES) {
10647
- db.exec(`DROP TABLE IF EXISTS ${table}`);
10768
+ const hasExtractCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='extract_cache'`).all();
10769
+ if (hasExtractCache.length === 0) {
10770
+ db.exec(`
10771
+ CREATE TABLE extract_cache (
10772
+ file_path TEXT NOT NULL,
10773
+ file_hash TEXT NOT NULL,
10774
+ cli_version TEXT NOT NULL,
10775
+ created_at TEXT NOT NULL,
10776
+ updated_at TEXT NOT NULL,
10777
+ components_json TEXT NOT NULL,
10778
+ PRIMARY KEY (file_hash, cli_version)
10779
+ );
10780
+ CREATE INDEX idx_extract_cache_file ON extract_cache(file_path);
10781
+ `);
10782
+ }
10783
+ const hasSelectCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='select_cache'`).all();
10784
+ if (hasSelectCache.length === 0) {
10785
+ db.exec(`
10786
+ CREATE TABLE select_cache (
10787
+ component_hash TEXT NOT NULL,
10788
+ prompt_hash TEXT NOT NULL,
10789
+ cli_version TEXT NOT NULL,
10790
+ decision TEXT NOT NULL CHECK (decision IN ('accepted','rejected')),
10791
+ reason TEXT,
10792
+ created_at TEXT NOT NULL,
10793
+ updated_at TEXT NOT NULL,
10794
+ PRIMARY KEY (component_hash, prompt_hash, cli_version)
10795
+ );
10796
+ `);
10797
+ }
10798
+ const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='generation_cache'`).all();
10799
+ if (tables.length === 0) {
10800
+ db.exec(`
10801
+ CREATE TABLE generation_cache (
10802
+ input_hash TEXT NOT NULL,
10803
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10804
+ entity_id TEXT NOT NULL,
10805
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10806
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10807
+ created_at TEXT NOT NULL,
10808
+ updated_at TEXT NOT NULL,
10809
+ PRIMARY KEY (input_hash, entity_type, entity_id)
10810
+ );
10811
+ CREATE INDEX idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10812
+ CREATE INDEX idx_generation_cache_session ON generation_cache(source_session_id);
10813
+ `);
10814
+ }
10815
+ const genCacheCols = db.prepare(`PRAGMA table_info(generation_cache)`).all();
10816
+ const hasPromptHash = genCacheCols.some((c2) => c2.name === "prompt_hash");
10817
+ const oldPkCols = genCacheCols.filter((c2) => c2.pk > 0).map((c2) => c2.name);
10818
+ const promptHashInPk = oldPkCols.includes("prompt_hash");
10819
+ if (!hasPromptHash) {
10820
+ db.exec(`ALTER TABLE generation_cache ADD COLUMN prompt_hash TEXT NOT NULL DEFAULT ''`);
10821
+ }
10822
+ if (!promptHashInPk) {
10823
+ db.exec("BEGIN");
10824
+ try {
10825
+ db.exec(`
10826
+ CREATE TABLE generation_cache__new (
10827
+ input_hash TEXT NOT NULL,
10828
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10829
+ entity_id TEXT NOT NULL,
10830
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10831
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10832
+ created_at TEXT NOT NULL,
10833
+ updated_at TEXT NOT NULL,
10834
+ prompt_hash TEXT NOT NULL DEFAULT '',
10835
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10836
+ );
10837
+ INSERT INTO generation_cache__new
10838
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10839
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10840
+ FROM generation_cache;
10841
+ DROP TABLE generation_cache;
10842
+ ALTER TABLE generation_cache__new RENAME TO generation_cache;
10843
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10844
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10845
+ `);
10846
+ db.exec("COMMIT");
10847
+ } catch (e) {
10848
+ db.exec("ROLLBACK");
10849
+ throw e;
10850
+ }
10851
+ }
10852
+ const genCacheTableSql = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'generation_cache'`).get();
10853
+ const hasTokenMappingEntityType = genCacheTableSql?.sql.includes("'token_mapping'") ?? true;
10854
+ if (!hasTokenMappingEntityType) {
10855
+ db.exec("BEGIN");
10856
+ try {
10857
+ db.exec(`
10858
+ CREATE TABLE generation_cache__new2 (
10859
+ input_hash TEXT NOT NULL,
10860
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10861
+ entity_id TEXT NOT NULL,
10862
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10863
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10864
+ created_at TEXT NOT NULL,
10865
+ updated_at TEXT NOT NULL,
10866
+ prompt_hash TEXT NOT NULL DEFAULT '',
10867
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10868
+ );
10869
+ INSERT INTO generation_cache__new2
10870
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10871
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10872
+ FROM generation_cache;
10873
+ DROP TABLE generation_cache;
10874
+ ALTER TABLE generation_cache__new2 RENAME TO generation_cache;
10875
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10876
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10877
+ `);
10878
+ db.exec("COMMIT");
10879
+ } catch (e) {
10880
+ db.exec("ROLLBACK");
10881
+ throw e;
10882
+ }
10883
+ }
10884
+ const hasCompositionCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='composition_cache'`).all();
10885
+ if (hasCompositionCache.length === 0) {
10886
+ db.exec(`
10887
+ CREATE TABLE composition_cache (
10888
+ input_hash TEXT NOT NULL,
10889
+ cli_version TEXT NOT NULL,
10890
+ agent_output TEXT NOT NULL,
10891
+ created_at TEXT NOT NULL,
10892
+ updated_at TEXT NOT NULL,
10893
+ PRIMARY KEY (input_hash, cli_version)
10894
+ );
10895
+ `);
10648
10896
  }
10649
10897
  }
10650
10898
  function loadComponentReviewMetadata(db, sessionId2, componentName) {
@@ -11180,16 +11428,16 @@ function extractRelativeImportPaths(sourceText) {
11180
11428
  return [...specifiers];
11181
11429
  }
11182
11430
  async function resolveRelativeImport(specifier, fromDir) {
11183
- const basePath = resolve8(fromDir, specifier);
11431
+ const basePath = resolve9(fromDir, specifier);
11184
11432
  const candidates = [
11185
11433
  basePath,
11186
11434
  ...rewrittenSourcePaths(basePath),
11187
11435
  ...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
11188
- ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve8(basePath, `index${ext}`))
11436
+ ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve9(basePath, `index${ext}`))
11189
11437
  ];
11190
11438
  for (const candidate of candidates) {
11191
11439
  try {
11192
- await readFile7(candidate, "utf8");
11440
+ await readFile8(candidate, "utf8");
11193
11441
  return candidate;
11194
11442
  } catch {
11195
11443
  }
@@ -11205,7 +11453,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
11205
11453
  return resolvedPaths;
11206
11454
  }
11207
11455
  async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateTypeNames = []) {
11208
- const visited = /* @__PURE__ */ new Set([resolve8(sourcePath)]);
11456
+ const visited = /* @__PURE__ */ new Set([resolve9(sourcePath)]);
11209
11457
  const discovered = [];
11210
11458
  let frontier = [{ text: sourceText, dir: dirname14(sourcePath) }];
11211
11459
  for (let hop = 0; hop < MAX_SIBLING_DEPTH && discovered.length < MAX_SIBLING_CANDIDATES_EXPLORED; hop++) {
@@ -11219,7 +11467,7 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
11219
11467
  if (discovered.length >= MAX_SIBLING_CANDIDATES_EXPLORED) break;
11220
11468
  let content;
11221
11469
  try {
11222
- content = await readFile7(resolvedPath, "utf8");
11470
+ content = await readFile8(resolvedPath, "utf8");
11223
11471
  } catch {
11224
11472
  continue;
11225
11473
  }
@@ -11256,7 +11504,7 @@ async function loadComponentSourceRef(name, sourcePath, propNames = [], propType
11256
11504
  let truncatedSiblingCount = 0;
11257
11505
  const usesNotShown = /* @__PURE__ */ new Set();
11258
11506
  try {
11259
- const rawText = await readFile7(sourcePath, "utf8");
11507
+ const rawText = await readFile8(sourcePath, "utf8");
11260
11508
  let siblingUsesNotShown;
11261
11509
  let declaredTypeNames;
11262
11510
  ({
@@ -11692,6 +11940,108 @@ function backfillUnclassifiedProps(db, sessionId2) {
11692
11940
  ).run(sessionId2, sessionId2);
11693
11941
  return Number(withCategory.changes) + Number(withoutCategory.changes);
11694
11942
  }
11943
+ function computeMapTokensInputHash(db, sessionId2, componentSourceRefs = []) {
11944
+ const props = db.prepare(
11945
+ `SELECT rc.name AS component_name, rp.name AS prop_name, rp.cdf_token_kind, rp.default_value
11946
+ FROM raw_props rp
11947
+ JOIN raw_components rc ON rc.session_id = rp.session_id AND rc.component_id = rp.component_id
11948
+ WHERE rp.session_id = ? AND rp.cdf_type = 'token' AND rp.cdf_category = 'design'
11949
+ ORDER BY rc.name, rp.name`
11950
+ ).all(sessionId2);
11951
+ const tokens = db.prepare(`SELECT path, type FROM raw_tokens WHERE session_id = ? ORDER BY path`).all(sessionId2);
11952
+ const defaultMappings = db.prepare("SELECT raw_name, path, source FROM raw_token_name_paths WHERE session_id = ? ORDER BY raw_name").all(sessionId2);
11953
+ const payload = {
11954
+ props: props.map((p) => ({
11955
+ component: p.component_name,
11956
+ prop: p.prop_name,
11957
+ tokenKind: p.cdf_token_kind,
11958
+ rawDefault: p.default_value
11959
+ })),
11960
+ tokens,
11961
+ defaultMappings,
11962
+ // The agent's evidence, not just its inputs: a comment or allowlist edit
11963
+ // in a source file (or a sibling it reads) changes the correct answer
11964
+ // without touching props or tokens, so it must invalidate the cache too.
11965
+ sourceRefs: [...componentSourceRefs].sort((a, b) => a.component.localeCompare(b.component)).map((ref) => ({
11966
+ component: ref.component,
11967
+ content: ref.content,
11968
+ siblingFiles: (ref.siblingFiles ?? []).map((s) => ({ path: s.path, content: s.content })).sort((a, b) => a.path.localeCompare(b.path)),
11969
+ usesNotShown: [...ref.usesNotShown ?? []].sort()
11970
+ }))
11971
+ };
11972
+ return createHash5("sha256").update(JSON.stringify(payload)).digest("hex");
11973
+ }
11974
+ function lookupCache(db, inputHash, entityType, entityId, promptHash = "") {
11975
+ const row = db.prepare(
11976
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11977
+ FROM generation_cache
11978
+ WHERE input_hash = ? AND entity_type = ? AND entity_id = ? AND prompt_hash = ?`
11979
+ ).get(inputHash, entityType, entityId, promptHash);
11980
+ if (!row) return null;
11981
+ return {
11982
+ inputHash: row.input_hash,
11983
+ entityType: row.entity_type,
11984
+ entityId: row.entity_id,
11985
+ sourceSessionId: row.source_session_id,
11986
+ humanEdited: row.human_edited === 1,
11987
+ promptHash: row.prompt_hash,
11988
+ createdAt: row.created_at,
11989
+ updatedAt: row.updated_at
11990
+ };
11991
+ }
11992
+ function lookupCacheByEntity(db, entityType, entityId) {
11993
+ const row = db.prepare(
11994
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11995
+ FROM generation_cache
11996
+ WHERE entity_type = ? AND entity_id = ?
11997
+ ORDER BY updated_at DESC LIMIT 1`
11998
+ ).get(entityType, entityId);
11999
+ if (!row) return null;
12000
+ return {
12001
+ inputHash: row.input_hash,
12002
+ entityType: row.entity_type,
12003
+ entityId: row.entity_id,
12004
+ sourceSessionId: row.source_session_id,
12005
+ humanEdited: row.human_edited === 1,
12006
+ promptHash: row.prompt_hash,
12007
+ createdAt: row.created_at,
12008
+ updatedAt: row.updated_at
12009
+ };
12010
+ }
12011
+ function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash = "") {
12012
+ storeCaches(db, [{ inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash }]);
12013
+ }
12014
+ function storeCaches(db, entries) {
12015
+ if (entries.length === 0) return;
12016
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12017
+ const insert = db.prepare(
12018
+ `INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
12019
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
12020
+ ON CONFLICT(input_hash, prompt_hash, entity_type, entity_id) DO UPDATE SET
12021
+ source_session_id = excluded.source_session_id,
12022
+ human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
12023
+ updated_at = excluded.updated_at`
12024
+ );
12025
+ db.exec("BEGIN");
12026
+ try {
12027
+ for (const entry of entries) {
12028
+ insert.run(
12029
+ entry.inputHash,
12030
+ entry.entityType,
12031
+ entry.entityId,
12032
+ entry.sourceSessionId,
12033
+ entry.humanEdited ? 1 : 0,
12034
+ now,
12035
+ now,
12036
+ entry.promptHash ?? ""
12037
+ );
12038
+ }
12039
+ db.exec("COMMIT");
12040
+ } catch (e) {
12041
+ db.exec("ROLLBACK");
12042
+ throw e;
12043
+ }
12044
+ }
11695
12045
  function storeScannedFiles(db, sessionId2, filePaths) {
11696
12046
  db.exec("BEGIN");
11697
12047
  try {
@@ -11774,7 +12124,105 @@ function filterUnknownSlotAllowedComponents(db, sessionId2, allowedComponentName
11774
12124
  }
11775
12125
  return unknown;
11776
12126
  }
11777
- function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
12127
+ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId, manageTransaction = true, options = {}) {
12128
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12129
+ if (manageTransaction) db.exec("BEGIN");
12130
+ try {
12131
+ const srcComp = db.prepare(
12132
+ `SELECT description, status, component_description_rationale, props_rationale, slots_rationale
12133
+ FROM raw_components WHERE session_id = ? AND component_id = ?`
12134
+ ).get(sourceSessionId, componentId);
12135
+ if (srcComp) {
12136
+ db.prepare(
12137
+ `UPDATE raw_components
12138
+ SET description = ?, status = ?, component_description_rationale = ?, props_rationale = ?, slots_rationale = ?, extracted_at = ?
12139
+ WHERE session_id = ? AND component_id = ?`
12140
+ ).run(
12141
+ srcComp.description,
12142
+ srcComp.status,
12143
+ srcComp.component_description_rationale,
12144
+ srcComp.props_rationale,
12145
+ srcComp.slots_rationale,
12146
+ now,
12147
+ targetSessionId,
12148
+ componentId
12149
+ );
12150
+ }
12151
+ const srcProps = db.prepare(
12152
+ `SELECT name, cdf_type, cdf_category, cdf_token_kind, required, description, default_value, rationale
12153
+ FROM raw_props WHERE session_id = ? AND component_id = ?`
12154
+ ).all(sourceSessionId, componentId);
12155
+ for (const p of srcProps) {
12156
+ db.prepare(
12157
+ `UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, default_value = ?, rationale = ?
12158
+ WHERE session_id = ? AND component_id = ? AND name = ?`
12159
+ ).run(
12160
+ p.cdf_type,
12161
+ p.cdf_category,
12162
+ p.cdf_token_kind,
12163
+ p.required,
12164
+ p.description,
12165
+ p.default_value,
12166
+ p.rationale,
12167
+ targetSessionId,
12168
+ componentId,
12169
+ p.name
12170
+ );
12171
+ }
12172
+ db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`).run(
12173
+ targetSessionId,
12174
+ componentId
12175
+ );
12176
+ const srcAV = db.prepare(
12177
+ `SELECT prop_name, position, value FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`
12178
+ ).all(sourceSessionId, componentId);
12179
+ const insertAV = db.prepare(
12180
+ `INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value) VALUES (?, ?, ?, ?, ?)`
12181
+ );
12182
+ for (const av of srcAV) {
12183
+ insertAV.run(targetSessionId, componentId, av.prop_name, av.position, av.value);
12184
+ }
12185
+ const srcSlots = db.prepare(`SELECT name, required, description, rationale FROM raw_slots WHERE session_id = ? AND component_id = ?`).all(sourceSessionId, componentId);
12186
+ for (const s of srcSlots) {
12187
+ db.prepare(
12188
+ `UPDATE raw_slots SET required = ?, description = ?, rationale = ? WHERE session_id = ? AND component_id = ? AND name = ?`
12189
+ ).run(s.required, s.description, s.rationale, targetSessionId, componentId, s.name);
12190
+ }
12191
+ db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`).run(
12192
+ targetSessionId,
12193
+ componentId
12194
+ );
12195
+ const srcSAC = db.prepare(
12196
+ `SELECT slot_name, position, allowed_component FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`
12197
+ ).all(sourceSessionId, componentId);
12198
+ const insertSAC = db.prepare(
12199
+ `INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`
12200
+ );
12201
+ const allowedSAC = options.allowedComponentNames ? srcSAC.filter((sac) => options.allowedComponentNames.has(sac.allowed_component)) : srcSAC;
12202
+ for (const sac of allowedSAC) {
12203
+ insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
12204
+ }
12205
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12206
+ if (manageTransaction) db.exec("COMMIT");
12207
+ } catch (e) {
12208
+ if (manageTransaction) db.exec("ROLLBACK");
12209
+ throw e;
12210
+ }
12211
+ }
12212
+ function copyComponentsFromCache(db, entries, options = {}) {
12213
+ if (entries.length === 0) return;
12214
+ db.exec("BEGIN");
12215
+ try {
12216
+ for (const entry of entries) {
12217
+ copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false, options);
12218
+ }
12219
+ db.exec("COMMIT");
12220
+ } catch (e) {
12221
+ db.exec("ROLLBACK");
12222
+ throw e;
12223
+ }
12224
+ }
12225
+ function copyTokensFromCache(db, sourceSessionId, targetSessionId) {
11778
12226
  const now = (/* @__PURE__ */ new Date()).toISOString();
11779
12227
  db.exec("BEGIN");
11780
12228
  try {
@@ -11795,7 +12243,153 @@ function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
11795
12243
  throw e;
11796
12244
  }
11797
12245
  }
11798
- var SCHEMA, REMOVED_CACHE_TABLES, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS, MAX_TYPE_DECLARING_SIBLING_CHARS, MAX_ENLARGED_SIBLINGS, MAX_SIBLING_DEPTH, MAX_SIBLING_CANDIDATES_EXPLORED, RELATIVE_IMPORT_PATTERN, SIBLING_FILE_EXTENSIONS, TS_ESM_EXTENSION_REWRITES, IDENTIFIER_PATTERN;
12246
+ function copyMapTokensFromCache(db, sourceSessionId, targetSessionId) {
12247
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12248
+ let copiedCount = 0;
12249
+ db.exec("BEGIN");
12250
+ try {
12251
+ const sourceRows = db.prepare(
12252
+ `SELECT rc.name AS component_name, rptp.prop_name, rptp.source, rptp.position, rptp.path
12253
+ FROM raw_prop_token_paths rptp
12254
+ JOIN raw_components rc ON rc.session_id = rptp.session_id AND rc.component_id = rptp.component_id
12255
+ WHERE rptp.session_id = ?
12256
+ ORDER BY rc.name, rptp.prop_name, rptp.position`
12257
+ ).all(sourceSessionId);
12258
+ const targetComponents = db.prepare(`SELECT component_id, name FROM raw_components WHERE session_id = ?`).all(targetSessionId);
12259
+ const targetIdByName = new Map(targetComponents.map((c2) => [c2.name, c2.component_id]));
12260
+ const targetProps = new Set(
12261
+ db.prepare(`SELECT component_id, name FROM raw_props WHERE session_id = ?`).all(targetSessionId).map((p) => `${p.component_id}::${p.name}`)
12262
+ );
12263
+ const insertPath = db.prepare(
12264
+ `INSERT INTO raw_prop_token_paths (session_id, component_id, prop_name, source, position, path)
12265
+ VALUES (?, ?, ?, ?, ?, ?)`
12266
+ );
12267
+ const clearedProps = /* @__PURE__ */ new Set();
12268
+ const copiedProps = /* @__PURE__ */ new Set();
12269
+ for (const row of sourceRows) {
12270
+ const targetComponentId = targetIdByName.get(row.component_name);
12271
+ if (!targetComponentId) continue;
12272
+ if (!targetProps.has(`${targetComponentId}::${row.prop_name}`)) continue;
12273
+ const propKey = `${targetComponentId}::${row.prop_name}`;
12274
+ if (!clearedProps.has(propKey)) {
12275
+ clearedProps.add(propKey);
12276
+ db.prepare(`DELETE FROM raw_prop_token_paths WHERE session_id = ? AND component_id = ? AND prop_name = ?`).run(
12277
+ targetSessionId,
12278
+ targetComponentId,
12279
+ row.prop_name
12280
+ );
12281
+ }
12282
+ insertPath.run(targetSessionId, targetComponentId, row.prop_name, row.source, row.position, row.path);
12283
+ copiedProps.add(propKey);
12284
+ }
12285
+ copiedCount = copiedProps.size;
12286
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12287
+ db.exec("COMMIT");
12288
+ } catch (e) {
12289
+ db.exec("ROLLBACK");
12290
+ throw e;
12291
+ }
12292
+ return copiedCount;
12293
+ }
12294
+ async function getCliCacheVersion() {
12295
+ if (_cliCacheVersionCache) return _cliCacheVersionCache;
12296
+ try {
12297
+ const { hashContent: hashContent2 } = await Promise.resolve().then(() => (init_cache_keys(), cache_keys_exports));
12298
+ const { resolveSkillPath: resolveSkillPath2 } = await Promise.resolve().then(() => (init_src(), src_exports));
12299
+ const skills = ["components", "tokens", "select"];
12300
+ const parts = [];
12301
+ for (const s of skills) {
12302
+ try {
12303
+ const p = resolveSkillPath2(s);
12304
+ parts.push(readFileSync5(p, "utf8"));
12305
+ } catch {
12306
+ parts.push(`<missing:${s}>`);
12307
+ }
12308
+ }
12309
+ _cliCacheVersionCache = hashContent2(parts.join("\n---\n"));
12310
+ return _cliCacheVersionCache;
12311
+ } catch {
12312
+ _cliCacheVersionCache = "fallback";
12313
+ return _cliCacheVersionCache;
12314
+ }
12315
+ }
12316
+ function storeExtractCache(db, filePath, fileHash, cliVersion2, components) {
12317
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12318
+ const componentsJson = JSON.stringify(components);
12319
+ db.prepare(
12320
+ `INSERT INTO extract_cache (file_path, file_hash, cli_version, created_at, updated_at, components_json)
12321
+ VALUES (?, ?, ?, ?, ?, ?)
12322
+ ON CONFLICT(file_hash, cli_version) DO UPDATE SET
12323
+ file_path = excluded.file_path,
12324
+ updated_at = excluded.updated_at,
12325
+ components_json = excluded.components_json`
12326
+ ).run(filePath, fileHash, cliVersion2, now, now, componentsJson);
12327
+ }
12328
+ function lookupExtractCache(db, fileHash, cliVersion2) {
12329
+ const row = db.prepare(
12330
+ `SELECT file_path, file_hash, cli_version, created_at, updated_at, components_json
12331
+ FROM extract_cache
12332
+ WHERE file_hash = ? AND cli_version = ?`
12333
+ ).get(fileHash, cliVersion2);
12334
+ if (!row) return null;
12335
+ let components = [];
12336
+ try {
12337
+ components = JSON.parse(row.components_json);
12338
+ } catch {
12339
+ return null;
12340
+ }
12341
+ return {
12342
+ filePath: row.file_path,
12343
+ fileHash: row.file_hash,
12344
+ cliVersion: row.cli_version,
12345
+ createdAt: row.created_at,
12346
+ updatedAt: row.updated_at,
12347
+ components
12348
+ };
12349
+ }
12350
+ function storeCompositionCache(db, inputHash, cliVersion2, agentOutput) {
12351
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12352
+ db.prepare(
12353
+ `INSERT INTO composition_cache (input_hash, cli_version, agent_output, created_at, updated_at)
12354
+ VALUES (?, ?, ?, ?, ?)
12355
+ ON CONFLICT(input_hash, cli_version) DO UPDATE SET
12356
+ agent_output = excluded.agent_output,
12357
+ updated_at = excluded.updated_at`
12358
+ ).run(inputHash, cliVersion2, agentOutput, now, now);
12359
+ }
12360
+ function lookupCompositionCache(db, inputHash, cliVersion2) {
12361
+ const row = db.prepare(`SELECT agent_output FROM composition_cache WHERE input_hash = ? AND cli_version = ?`).get(inputHash, cliVersion2);
12362
+ return row ? row.agent_output : null;
12363
+ }
12364
+ function storeSelectCache(db, componentHash, promptHash, cliVersion2, decision, reason) {
12365
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12366
+ db.prepare(
12367
+ `INSERT INTO select_cache (component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at)
12368
+ VALUES (?, ?, ?, ?, ?, ?, ?)
12369
+ ON CONFLICT(component_hash, prompt_hash, cli_version) DO UPDATE SET
12370
+ decision = excluded.decision,
12371
+ reason = excluded.reason,
12372
+ updated_at = excluded.updated_at`
12373
+ ).run(componentHash, promptHash, cliVersion2, decision, reason, now, now);
12374
+ }
12375
+ function lookupSelectCache(db, componentHash, promptHash, cliVersion2) {
12376
+ const row = db.prepare(
12377
+ `SELECT component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at
12378
+ FROM select_cache
12379
+ WHERE component_hash = ? AND prompt_hash = ? AND cli_version = ?`
12380
+ ).get(componentHash, promptHash, cliVersion2);
12381
+ if (!row) return null;
12382
+ return {
12383
+ componentHash: row.component_hash,
12384
+ promptHash: row.prompt_hash,
12385
+ cliVersion: row.cli_version,
12386
+ decision: row.decision,
12387
+ reason: row.reason,
12388
+ createdAt: row.created_at,
12389
+ updatedAt: row.updated_at
12390
+ };
12391
+ }
12392
+ var SCHEMA, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS, MAX_TYPE_DECLARING_SIBLING_CHARS, MAX_ENLARGED_SIBLINGS, MAX_SIBLING_DEPTH, MAX_SIBLING_CANDIDATES_EXPLORED, RELATIVE_IMPORT_PATTERN, SIBLING_FILE_EXTENSIONS, TS_ESM_EXTENSION_REWRITES, IDENTIFIER_PATTERN, _cliCacheVersionCache;
11799
12393
  var init_db = __esm({
11800
12394
  "packages/experience-design-system-cli-legacy/src/session/db.ts"() {
11801
12395
  "use strict";
@@ -11809,6 +12403,8 @@ var init_db = __esm({
11809
12403
  init_write2();
11810
12404
  init_session_resolver();
11811
12405
  init_read3();
12406
+ init_hash_component_shape();
12407
+ init_hash_token_content();
11812
12408
  init_read3();
11813
12409
  init_store_dtcg_tokens();
11814
12410
  init_apply_tool_calls();
@@ -11932,6 +12528,17 @@ CREATE TABLE IF NOT EXISTS raw_tokens (
11932
12528
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
11933
12529
  );
11934
12530
 
12531
+ CREATE TABLE IF NOT EXISTS generation_cache (
12532
+ input_hash TEXT NOT NULL,
12533
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
12534
+ entity_id TEXT NOT NULL,
12535
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
12536
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
12537
+ created_at TEXT NOT NULL,
12538
+ updated_at TEXT NOT NULL,
12539
+ PRIMARY KEY (input_hash, entity_type, entity_id)
12540
+ );
12541
+
11935
12542
  CREATE TABLE IF NOT EXISTS scanned_files (
11936
12543
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
11937
12544
  path TEXT NOT NULL,
@@ -11977,9 +12584,10 @@ CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id,
11977
12584
  CREATE INDEX IF NOT EXISTS idx_raw_slots_session ON raw_slots(session_id, component_id);
11978
12585
  CREATE INDEX IF NOT EXISTS idx_raw_tokens_session ON raw_tokens(session_id);
11979
12586
  CREATE INDEX IF NOT EXISTS idx_raw_token_groups_session ON raw_token_groups(session_id);
12587
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
12588
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
11980
12589
  CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session_id);
11981
12590
  `;
11982
- REMOVED_CACHE_TABLES = ["generation_cache", "extract_cache", "composition_cache", "select_cache"];
11983
12591
  MAX_COMPONENT_SOURCE_CHARS = 8e3;
11984
12592
  MAX_SIBLING_FILES = 5;
11985
12593
  MAX_SIBLING_SNIPPET_CHARS = 1200;
@@ -11996,15 +12604,16 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
11996
12604
  [".cjs", [".cts", ".cjs"]]
11997
12605
  ];
11998
12606
  IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
12607
+ _cliCacheVersionCache = null;
11999
12608
  }
12000
12609
  });
12001
12610
 
12002
12611
  // packages/experience-design-system-cli-legacy/src/helpers/read-existing-contentful-entities-from-session.ts
12003
- import { readFile as readFile8 } from "node:fs/promises";
12612
+ import { readFile as readFile9 } from "node:fs/promises";
12004
12613
  async function readExistingContentfulEntitiesFromSession(path) {
12005
12614
  if (!path) return void 0;
12006
12615
  try {
12007
- const raw = await readFile8(path, "utf8");
12616
+ const raw = await readFile9(path, "utf8");
12008
12617
  const parsed = JSON.parse(raw);
12009
12618
  if (!Array.isArray(parsed.components) || !Array.isArray(parsed.tokens)) return void 0;
12010
12619
  return parsed;
@@ -12128,21 +12737,21 @@ var init_resolve_session_id = __esm({
12128
12737
  });
12129
12738
 
12130
12739
  // packages/experience-design-system-cli-legacy/src/analyze/select/persistence.ts
12131
- import { access, appendFile, mkdir as mkdir2, readFile as readFile9, writeFile as writeFile2 } from "node:fs/promises";
12740
+ import { access, appendFile, mkdir as mkdir2, readFile as readFile10, writeFile as writeFile2 } from "node:fs/promises";
12132
12741
  import { homedir as homedir4 } from "node:os";
12133
- import { resolve as resolve9 } from "node:path";
12742
+ import { resolve as resolve10 } from "node:path";
12134
12743
  function getRefineArtifactsRoot() {
12135
12744
  if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
12136
- return resolve9(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12745
+ return resolve10(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12137
12746
  }
12138
- return resolve9(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12747
+ return resolve10(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12139
12748
  }
12140
12749
  async function getRefineSessionPaths(sessionId2, artifactsRoot) {
12141
- const sessionDir = resolve9(artifactsRoot, sessionId2);
12750
+ const sessionDir = resolve10(artifactsRoot, sessionId2);
12142
12751
  return {
12143
12752
  sessionDir,
12144
- eventsPath: resolve9(sessionDir, "events.jsonl"),
12145
- statePath: resolve9(sessionDir, "current-review-state.json")
12753
+ eventsPath: resolve10(sessionDir, "events.jsonl"),
12754
+ statePath: resolve10(sessionDir, "current-review-state.json")
12146
12755
  };
12147
12756
  }
12148
12757
  async function saveReviewState(statePath, session) {
@@ -12153,7 +12762,7 @@ async function ensureRefineSession(sessionId2, artifactsRoot, initialSnapshot) {
12153
12762
  await mkdir2(paths.sessionDir, { recursive: true });
12154
12763
  try {
12155
12764
  await access(paths.statePath);
12156
- const savedState = await readFile9(paths.statePath, "utf8");
12765
+ const savedState = await readFile10(paths.statePath, "utf8");
12157
12766
  return JSON.parse(savedState);
12158
12767
  } catch {
12159
12768
  await writeFile2(paths.statePath, JSON.stringify(initialSnapshot, null, 2), "utf8");
@@ -12638,8 +13247,8 @@ var init_path_exists = __esm({
12638
13247
  });
12639
13248
 
12640
13249
  // packages/experience-design-system-cli-legacy/src/lib/prompt-overrides.ts
12641
- import { readFile as readFile10 } from "node:fs/promises";
12642
- import { resolve as resolve10 } from "node:path";
13250
+ import { readFile as readFile11 } from "node:fs/promises";
13251
+ import { resolve as resolve11 } from "node:path";
12643
13252
  function looksLikePath(value) {
12644
13253
  if (value.includes("/") || value.includes("\\")) return true;
12645
13254
  if (value.startsWith("~")) return true;
@@ -12671,9 +13280,9 @@ function parsePromptOverrides(inputs) {
12671
13280
  }
12672
13281
  async function resolvePromptOverride(override) {
12673
13282
  if (override.kind === "text") return override.value;
12674
- const abs = resolve10(override.value);
13283
+ const abs = resolve11(override.value);
12675
13284
  try {
12676
- return await readFile10(abs, "utf8");
13285
+ return await readFile11(abs, "utf8");
12677
13286
  } catch {
12678
13287
  throw new Error(`--prompt: could not read prompt file: ${abs}`);
12679
13288
  }
@@ -12688,21 +13297,21 @@ var init_prompt_overrides = __esm({
12688
13297
 
12689
13298
  // packages/experience-design-system-cli-legacy/src/generate/command.ts
12690
13299
  import { createElement } from "react";
12691
- import { readFile as readFile11, readdir, stat } from "node:fs/promises";
12692
- import { join as join14, resolve as resolve11 } from "node:path";
13300
+ import { readFile as readFile12, readdir, stat } from "node:fs/promises";
13301
+ import { join as join14, resolve as resolve12 } from "node:path";
12693
13302
  async function assertFileExists(flag, p) {
12694
13303
  if (!await pathExists(p)) die(`Error: file not found: ${p} (from ${flag})`);
12695
13304
  }
12696
13305
  async function readFileInline(path) {
12697
13306
  if (!path) return void 0;
12698
- const resolved = resolve11(path);
13307
+ const resolved = resolve12(path);
12699
13308
  let s;
12700
13309
  try {
12701
13310
  s = await stat(resolved);
12702
13311
  } catch {
12703
13312
  return void 0;
12704
13313
  }
12705
- if (!s.isDirectory()) return readFile11(resolved, "utf8");
13314
+ if (!s.isDirectory()) return readFile12(resolved, "utf8");
12706
13315
  const files = [];
12707
13316
  async function walk(dir) {
12708
13317
  let entries;
@@ -12728,7 +13337,7 @@ async function readFileInline(path) {
12728
13337
  }
12729
13338
  await walk(resolved);
12730
13339
  if (files.length === 0) return void 0;
12731
- const parts = await Promise.all(files.map((f) => readFile11(f, "utf8").catch(() => "")));
13340
+ const parts = await Promise.all(files.map((f) => readFile12(f, "utf8").catch(() => "")));
12732
13341
  return parts.filter(Boolean).join("\n\n");
12733
13342
  }
12734
13343
  function printFallbackInstructions(options) {
@@ -12753,6 +13362,23 @@ function printFallbackInstructions(options) {
12753
13362
  function generatePropBuckets(component) {
12754
13363
  return logPropBuckets("generate", [bucketComponentProps(component)]);
12755
13364
  }
13365
+ function createCachedComponentResult(componentName, warnings = []) {
13366
+ return {
13367
+ componentName,
13368
+ classified: 0,
13369
+ excluded: 0,
13370
+ slots: 0,
13371
+ warnings,
13372
+ failed: false,
13373
+ cached: true,
13374
+ renamedSlotsCount: 0
13375
+ };
13376
+ }
13377
+ function writeCachedComponentStatus(position, componentName, pinned) {
13378
+ const status = pinned ? c.cyan("pinned (human-edited)") : c.green("cached");
13379
+ process.stderr.write(` ${position} ${c.bold(componentName)} ${status}
13380
+ `);
13381
+ }
12756
13382
  async function showGenerateView(result) {
12757
13383
  if (process.stdout.isTTY) {
12758
13384
  const { waitUntilExit } = renderWithGoodbye(
@@ -12773,6 +13399,29 @@ session=${result.sessionId}
12773
13399
  );
12774
13400
  await exitWithAnalytics(0);
12775
13401
  }
13402
+ function normalizeComponentForCache(component) {
13403
+ const slots = component.slots.map((slot, index, allSlots) => ({
13404
+ ...slot,
13405
+ name: slot.name.trim() || (allSlots.length === 1 ? "children" : `slot_${index}`)
13406
+ }));
13407
+ return { ...component, slots };
13408
+ }
13409
+ function lookupComponentCache(db, component, promptHash) {
13410
+ const normalized = normalizeComponentForCache(component);
13411
+ const inputHash = hashComponentShape(normalized);
13412
+ const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
13413
+ if (cached) return cached;
13414
+ const legacyInputHash = hashComponentShape(component);
13415
+ return legacyInputHash === inputHash ? null : lookupCache(db, legacyInputHash, "component", component.component_id, promptHash);
13416
+ }
13417
+ function resolveComponentCache(db, component, promptHash, allowedComponentNames2) {
13418
+ const cached = lookupComponentCache(db, component, promptHash);
13419
+ if (cached && findUnknownSlotAllowedComponents(db, cached.sourceSessionId, allowedComponentNames2, component.component_id).length === 0) {
13420
+ return { entry: cached, humanEdited: cached.humanEdited };
13421
+ }
13422
+ const pinned = lookupCacheByEntity(db, "component", component.component_id);
13423
+ return pinned?.humanEdited && findUnknownSlotAllowedComponents(db, pinned.sourceSessionId, allowedComponentNames2, component.component_id).length === 0 ? { entry: pinned, humanEdited: true } : null;
13424
+ }
12776
13425
  async function runOneComponent(options, component, index, total) {
12777
13426
  const {
12778
13427
  agent,
@@ -12782,10 +13431,13 @@ async function runOneComponent(options, component, index, total) {
12782
13431
  tokensInline,
12783
13432
  tokenMapInline,
12784
13433
  verbose,
13434
+ noCache,
12785
13435
  skillPathOverride,
12786
13436
  skillContentOverride,
13437
+ promptHash,
12787
13438
  existingContentfulEntities,
12788
13439
  existingTokensInline,
13440
+ precomputedCachedNames,
12789
13441
  allowedComponentNames: allowedComponentNames2
12790
13442
  } = options;
12791
13443
  const pos = c.dim(`[${index + 1}/${total}]`);
@@ -12810,6 +13462,40 @@ async function runOneComponent(options, component, index, total) {
12810
13462
  allowedComponents: slot.allowedComponents.filter((name) => allowedComponentNames2.has(name))
12811
13463
  };
12812
13464
  });
13465
+ const cacheComponent = normalizeComponentForCache(component);
13466
+ if (!noCache && precomputedCachedNames.has(component.name)) {
13467
+ writeCachedComponentStatus(pos, component.name, false);
13468
+ return createCachedComponentResult(component.name);
13469
+ }
13470
+ if (!noCache) {
13471
+ const inputHash = hashComponentShape(cacheComponent);
13472
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13473
+ if (resolution && !resolution.humanEdited) {
13474
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13475
+ allowedComponentNames: allowedComponentNames2
13476
+ });
13477
+ storeCache(
13478
+ db,
13479
+ inputHash,
13480
+ "component",
13481
+ component.component_id,
13482
+ resolution.entry.sourceSessionId,
13483
+ resolution.entry.humanEdited,
13484
+ promptHash
13485
+ );
13486
+ writeCachedComponentStatus(pos, component.name, false);
13487
+ return createCachedComponentResult(component.name);
13488
+ }
13489
+ if (resolution?.humanEdited) {
13490
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13491
+ allowedComponentNames: allowedComponentNames2
13492
+ });
13493
+ writeCachedComponentStatus(pos, component.name, true);
13494
+ return createCachedComponentResult(`${component.name}`, [
13495
+ `${component.name}: source changed but human edits preserved`
13496
+ ]);
13497
+ }
13498
+ }
12813
13499
  const rawComponentsInline = JSON.stringify(
12814
13500
  [
12815
13501
  {
@@ -12882,6 +13568,10 @@ ${outputBuf}`);
12882
13568
  const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings, {
12883
13569
  allowedComponentNames: allowedComponentNames2
12884
13570
  });
13571
+ if (!noCache) {
13572
+ const inputHash = hashComponentShape(cacheComponent);
13573
+ storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
13574
+ }
12885
13575
  return {
12886
13576
  componentName: component.name,
12887
13577
  classified: applied.classified,
@@ -12933,7 +13623,7 @@ async function loadAcceptedNames(sessionId2) {
12933
13623
  try {
12934
13624
  const artifactsRoot = getRefineArtifactsRoot();
12935
13625
  const paths = await getRefineSessionPaths(sessionId2, artifactsRoot);
12936
- const raw = await readFile11(paths.statePath, "utf8");
13626
+ const raw = await readFile12(paths.statePath, "utf8");
12937
13627
  const snapshot = JSON.parse(raw);
12938
13628
  const accepted = snapshot.components.filter((c2) => c2.status === "accepted").map((c2) => c2.name);
12939
13629
  if (accepted.length === 0) return null;
@@ -12942,6 +13632,15 @@ async function loadAcceptedNames(sessionId2) {
12942
13632
  return null;
12943
13633
  }
12944
13634
  }
13635
+ function parsePrecomputedCachedNames(value) {
13636
+ if (!value) return /* @__PURE__ */ new Set();
13637
+ try {
13638
+ const parsed = JSON.parse(value);
13639
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : []);
13640
+ } catch {
13641
+ return /* @__PURE__ */ new Set();
13642
+ }
13643
+ }
12945
13644
  async function runGenerateSkill(skill, opts, verbose = false) {
12946
13645
  const savedCreds = await readExperiencesCredentials();
12947
13646
  const agentName = opts.agent ?? savedCreds.agent;
@@ -12970,8 +13669,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12970
13669
  }
12971
13670
  const generatePromptPath = generatePrompt === void 0 ? configuredGeneratePromptPath : void 0;
12972
13671
  if (generatePromptPath) {
12973
- if (!await pathExists(resolve11(generatePromptPath))) {
12974
- die(`Error: custom prompt path not found: ${resolve11(generatePromptPath)}`);
13672
+ if (!await pathExists(resolve12(generatePromptPath))) {
13673
+ die(`Error: custom prompt path not found: ${resolve12(generatePromptPath)}`);
12975
13674
  }
12976
13675
  if (!generatePromptPath.toLowerCase().endsWith(".md")) {
12977
13676
  process.stderr.write(
@@ -12979,7 +13678,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12979
13678
  `
12980
13679
  );
12981
13680
  }
12982
- process.stderr.write(formatCustomPromptBanner("components", resolve11(generatePromptPath)));
13681
+ process.stderr.write(formatCustomPromptBanner("components", resolve12(generatePromptPath)));
12983
13682
  }
12984
13683
  if (skill === "tokens" && !opts.rawTokens) {
12985
13684
  die("Error: --raw-tokens is required when using generate tokens");
@@ -12995,7 +13694,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12995
13694
  let existingContentfulEntities;
12996
13695
  let existingTokensInline;
12997
13696
  if (opts.existingEntitiesPath) {
12998
- existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve11(opts.existingEntitiesPath));
13697
+ existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve12(opts.existingEntitiesPath));
12999
13698
  if (existingContentfulEntities) {
13000
13699
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
13001
13700
  } else {
@@ -13094,7 +13793,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13094
13793
  rawComponentsInline: sampleInline ?? rawTokensInline,
13095
13794
  propBucketsInline: sampleComponent ? generatePropBuckets(sampleComponent) : void 0,
13096
13795
  rawTokensInline: skill === "tokens" ? rawTokensInline : void 0,
13097
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13796
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13098
13797
  tokensInline,
13099
13798
  tokenMapInline,
13100
13799
  outDir: process.cwd(),
@@ -13120,7 +13819,64 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13120
13819
  if (skill === "components" && allComponents && sessionId2) {
13121
13820
  const db = openPipelineDb();
13122
13821
  let componentResults;
13822
+ const precomputedCachedNames = parsePrecomputedCachedNames(opts.cachedComponents);
13123
13823
  try {
13824
+ const existingContentfulEntitiesHashInputs = [];
13825
+ if (existingTokensInline) existingContentfulEntitiesHashInputs.push(hashContent(existingTokensInline));
13826
+ const promptHash = await hashPromptForSkill(
13827
+ "components",
13828
+ agent,
13829
+ model,
13830
+ generatePromptPath,
13831
+ existingContentfulEntitiesHashInputs,
13832
+ generatePrompt
13833
+ );
13834
+ if (opts.cacheStatus) {
13835
+ const cacheEnabled = opts.cache !== false && process.env.EDS_NO_CACHE !== "1";
13836
+ const cachedComponents = cacheEnabled ? allComponents.flatMap((component) => {
13837
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13838
+ return resolution ? [{ component, resolution }] : [];
13839
+ }) : [];
13840
+ if (cacheEnabled && opts.restoreCache) {
13841
+ const restorations = [];
13842
+ const rekeyed = [];
13843
+ const componentStatus = db.prepare(
13844
+ "SELECT status FROM raw_components WHERE session_id = ? AND component_id = ?"
13845
+ );
13846
+ for (const { component, resolution } of cachedComponents) {
13847
+ const current = componentStatus.get(sessionId2, component.component_id);
13848
+ if (current?.status === "generated") continue;
13849
+ renameEmptySlots(db, sessionId2, component.component_id, component.name, component.slots.length);
13850
+ restorations.push({
13851
+ sourceSessionId: resolution.entry.sourceSessionId,
13852
+ targetSessionId: sessionId2,
13853
+ componentId: component.component_id
13854
+ });
13855
+ rekeyed.push({ component, cached: resolution.entry });
13856
+ }
13857
+ copyComponentsFromCache(db, restorations, { allowedComponentNames: allowedComponentNames2 });
13858
+ storeCaches(
13859
+ db,
13860
+ rekeyed.map(({ component, cached }) => ({
13861
+ inputHash: hashComponentShape(normalizeComponentForCache(component)),
13862
+ entityType: "component",
13863
+ entityId: component.component_id,
13864
+ sourceSessionId: cached.sourceSessionId,
13865
+ humanEdited: cached.humanEdited,
13866
+ promptHash
13867
+ }))
13868
+ );
13869
+ }
13870
+ const fullyCached = cacheEnabled && cachedComponents.length === allComponents.length;
13871
+ process.stdout.write(`cache-status=${fullyCached ? "hit" : "miss"}
13872
+ `);
13873
+ process.stdout.write(
13874
+ `cache-components=${JSON.stringify(cachedComponents.map(({ component }) => component.name))}
13875
+ `
13876
+ );
13877
+ await exitWithAnalytics(0);
13878
+ return;
13879
+ }
13124
13880
  componentResults = await runAllComponents(
13125
13881
  {
13126
13882
  agent,
@@ -13130,10 +13886,13 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13130
13886
  tokensInline,
13131
13887
  tokenMapInline,
13132
13888
  verbose,
13889
+ noCache: opts.cache === false || process.env.EDS_NO_CACHE === "1",
13133
13890
  skillPathOverride: generatePromptPath,
13134
13891
  skillContentOverride: generatePrompt,
13892
+ promptHash,
13135
13893
  existingContentfulEntities,
13136
13894
  existingTokensInline,
13895
+ precomputedCachedNames,
13137
13896
  allowedComponentNames: allowedComponentNames2
13138
13897
  },
13139
13898
  allComponents
@@ -13142,7 +13901,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13142
13901
  db.close();
13143
13902
  }
13144
13903
  const failed = componentResults.filter((r) => r.failed);
13145
- const generated = componentResults.filter((r) => !r.failed);
13904
+ const cachedResults = componentResults.filter((r) => r.cached);
13905
+ const generated = componentResults.filter((r) => !r.failed && !r.cached);
13146
13906
  const allWarnings = componentResults.flatMap((r) => r.warnings.map((w) => ` ${r.componentName}: ${w}`));
13147
13907
  if (allWarnings.length > 0) {
13148
13908
  process.stderr.write(c.yellow("Warnings:") + "\n" + allWarnings.join("\n") + "\n");
@@ -13158,17 +13918,21 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13158
13918
  const totalExcluded = generated.reduce((s, r) => s + r.excluded, 0);
13159
13919
  const totalRenamedSlots = componentResults.reduce((s, r) => s + r.renamedSlotsCount, 0);
13160
13920
  const allOk = failed.length === 0;
13921
+ const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : "";
13161
13922
  process.stderr.write(
13162
- (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length}/${componentResults.length} components` + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13923
+ (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13163
13924
  );
13164
13925
  process.stdout.write(`renamed-slots: ${totalRenamedSlots}
13165
13926
  `);
13166
- if (generated.length === 0) {
13927
+ if (generated.length === 0 && cachedResults.length === 0) {
13167
13928
  die(
13168
13929
  `Error: all ${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
13169
13930
  );
13170
13931
  }
13171
13932
  } else if (skill === "tokens") {
13933
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
13934
+ const tokenInputContent = rawTokensInline ?? "";
13935
+ const tokenInputHash = hashTokenContent(tokenInputContent);
13172
13936
  const db = openPipelineDb();
13173
13937
  try {
13174
13938
  let resolvedSessionId = opts.session;
@@ -13195,11 +13959,26 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13195
13959
  }
13196
13960
  sessionId2 = resolvedSessionId;
13197
13961
  await bindAnalyticsSessionId(resolvedSessionId);
13962
+ const tokenPromptHash = await hashPromptForSkill("tokens", agent, model);
13963
+ if (!noCache) {
13964
+ const tokenCached = lookupCache(db, tokenInputHash, "token_set", "__tokens__", tokenPromptHash);
13965
+ if (tokenCached) {
13966
+ copyTokensFromCache(db, tokenCached.sourceSessionId, resolvedSessionId);
13967
+ sessionId2 = resolvedSessionId;
13968
+ process.stderr.write(
13969
+ `Done: tokens reused from cache ${c.dim(`(source: ${tokenCached.sourceSessionId.slice(0, 12)})`)}
13970
+ `
13971
+ );
13972
+ db.close();
13973
+ await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13974
+ return;
13975
+ }
13976
+ }
13198
13977
  const prompt = await buildPrompt({
13199
13978
  skill,
13200
13979
  mode: "autonomous",
13201
13980
  rawTokensInline,
13202
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13981
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13203
13982
  tokensInline,
13204
13983
  tokenMapInline,
13205
13984
  outDir: process.cwd()
@@ -13236,6 +14015,9 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13236
14015
  `);
13237
14016
  }
13238
14017
  applyTokenToolCalls(db, resolvedSessionId, tokenCalls, []);
14018
+ if (!noCache) {
14019
+ storeCache(db, tokenInputHash, "token_set", "__tokens__", resolvedSessionId, false, tokenPromptHash);
14020
+ }
13239
14021
  sessionId2 = resolvedSessionId;
13240
14022
  const groupCount = tokenCalls.filter((tc) => tc.tool === "set_group").length;
13241
14023
  process.stderr.write(`Done: ${tokenCount} tokens, ${groupCount} groups stored
@@ -13247,7 +14029,10 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13247
14029
  await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13248
14030
  }
13249
14031
  function addAgentFlags(cmd) {
13250
- return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent");
14032
+ return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent").option(
14033
+ "--no-cache",
14034
+ "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."
14035
+ );
13251
14036
  }
13252
14037
  function registerInternalGenerateCommand(program) {
13253
14038
  const generate = program.command("__generate", { hidden: true }).description("Internal import pipeline generation command");
@@ -13263,6 +14048,12 @@ function registerInternalGenerateCommand(program) {
13263
14048
  "--existing-entities-path <path>",
13264
14049
  "Path to the .existing-entities.json file written after CMA credentials are supplied. When present, per-component prompts include a summary of the target space so classifications can align to existing prop names/tokens. Missing/malformed files are treated as no-op."
13265
14050
  );
14051
+ componentsCmd.option("--cache-status", "Check whether all selected component definitions are cached");
14052
+ componentsCmd.option("--restore-cache", "Restore cached component definitions into the current session");
14053
+ componentsCmd.option(
14054
+ "--cached-components <json>",
14055
+ "Component names already restored by a prior cache-status lookup; skip per-component cache lookup"
14056
+ );
13266
14057
  addAgentFlags(componentsCmd).action(async (opts) => {
13267
14058
  await runGenerateSkill("components", opts, opts.verbose ?? false);
13268
14059
  });
@@ -13290,6 +14081,7 @@ var init_command = __esm({
13290
14081
  init_agent_output();
13291
14082
  init_GenerateView();
13292
14083
  init_db();
14084
+ init_cache_keys();
13293
14085
  init_read_existing_contentful_entities_from_session();
13294
14086
  init_summarize_existing_contentful_entities();
13295
14087
  init_resolve_session_id();
@@ -14005,7 +14797,7 @@ var init_user_agent = __esm({
14005
14797
  // packages/experience-design-system-cli-legacy/src/apply/api-client.ts
14006
14798
  import { parseCDFComponents } from "@contentful/experience-design-system-types";
14007
14799
  function defaultSleep(delayMs) {
14008
- return new Promise((resolve20) => setTimeout(resolve20, delayMs));
14800
+ return new Promise((resolve22) => setTimeout(resolve22, delayMs));
14009
14801
  }
14010
14802
  function isTransientStatus(status) {
14011
14803
  return status >= 500 && status <= 599;
@@ -14395,7 +15187,7 @@ ${trimmed}`;
14395
15187
  const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
14396
15188
  const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
14397
15189
  const jitter = Math.random() * baseDelay * 0.15;
14398
- await new Promise((resolve20) => setTimeout(resolve20, baseDelay + jitter));
15190
+ await new Promise((resolve22) => setTimeout(resolve22, baseDelay + jitter));
14399
15191
  }
14400
15192
  }
14401
15193
  throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
@@ -15205,7 +15997,7 @@ var init_terminal_capabilities = __esm({
15205
15997
 
15206
15998
  // packages/experience-design-system-cli-legacy/src/apply/command.ts
15207
15999
  import { createElement as createElement2 } from "react";
15208
- import { readFile as readFile12, readdir as readdir2, stat as stat2 } from "node:fs/promises";
16000
+ import { readFile as readFile13, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15209
16001
  import { join as join16 } from "node:path";
15210
16002
  import {
15211
16003
  validateCDF,
@@ -15226,7 +16018,7 @@ function dieWithApiError(error) {
15226
16018
  async function readJsonFile(flag, p) {
15227
16019
  let text;
15228
16020
  try {
15229
- text = await readFile12(p, "utf8");
16021
+ text = await readFile13(p, "utf8");
15230
16022
  } catch {
15231
16023
  return await die2(`Error: file not found: ${p} (from ${flag})`);
15232
16024
  }
@@ -15280,7 +16072,7 @@ async function readTokensFromPath(flag, p) {
15280
16072
  for (const file of files.sort()) {
15281
16073
  let text;
15282
16074
  try {
15283
- text = await readFile12(file, "utf8");
16075
+ text = await readFile13(file, "utf8");
15284
16076
  } catch {
15285
16077
  continue;
15286
16078
  }
@@ -15649,11 +16441,11 @@ var init_command2 = __esm({
15649
16441
  });
15650
16442
 
15651
16443
  // packages/experience-design-system-cli-legacy/src/print/validate/validators/read-json-file.ts
15652
- import { readFile as readFile13 } from "node:fs/promises";
16444
+ import { readFile as readFile14 } from "node:fs/promises";
15653
16445
  async function readJsonFile2(filePath) {
15654
16446
  let content;
15655
16447
  try {
15656
- content = await readFile13(filePath, "utf-8");
16448
+ content = await readFile14(filePath, "utf-8");
15657
16449
  } catch (err) {
15658
16450
  return {
15659
16451
  ok: false,
@@ -16064,7 +16856,7 @@ var init_ValidateView = __esm({
16064
16856
  // packages/experience-design-system-cli-legacy/src/print/command.ts
16065
16857
  import { createElement as createElement3 } from "react";
16066
16858
  import { access as access3, mkdir as mkdir3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
16067
- import { basename as basename5, resolve as resolve12 } from "node:path";
16859
+ import { basename as basename5, resolve as resolve13 } from "node:path";
16068
16860
  import { CDF_SCHEMA_URL } from "@contentful/experience-design-system-types";
16069
16861
  async function die3(message) {
16070
16862
  process.stderr.write(`${message}
@@ -16131,7 +16923,7 @@ function registerPrintCommand(program) {
16131
16923
  "--allow-empty",
16132
16924
  "Write an empty-but-present CDF file when no components are accepted (a subsequent push then removes ALL components from the target space). Without this, an empty accepted set is an error."
16133
16925
  ).action(async (opts) => {
16134
- const outPath = resolve12(opts.out);
16926
+ const outPath = resolve13(opts.out);
16135
16927
  await assertOutIsNotDirectory(outPath);
16136
16928
  const sessionId2 = await resolveSession(opts.session, "generate components");
16137
16929
  await bindAnalyticsSessionId(sessionId2);
@@ -16179,7 +16971,7 @@ function registerPrintCommand(program) {
16179
16971
  `
16180
16972
  );
16181
16973
  }
16182
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16974
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16183
16975
  await writeFile3(outPath, `${JSON.stringify(cdfObj, null, 2)}
16184
16976
  `);
16185
16977
  process.stdout.write(
@@ -16188,7 +16980,7 @@ function registerPrintCommand(program) {
16188
16980
  );
16189
16981
  });
16190
16982
  print.command("tokens").description("Write generated tokens to a DTCG JSON file").option("--session <id>", "Session ID to print from (default: most recent generate tokens session)").option("--out <path>", "Output file path", "tokens.json").action(async (opts) => {
16191
- const outPath = resolve12(opts.out);
16983
+ const outPath = resolve13(opts.out);
16192
16984
  await assertOutIsNotDirectory(outPath);
16193
16985
  const sessionId2 = await resolveSession(opts.session, "generate tokens");
16194
16986
  await bindAnalyticsSessionId(sessionId2);
@@ -16203,7 +16995,7 @@ function registerPrintCommand(program) {
16203
16995
  await die3(`Error: no generated tokens in session '${sessionId2}'. Run generate tokens first.`);
16204
16996
  }
16205
16997
  const tree = rebuildDTCGTree(result.groups, result.tokens);
16206
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16998
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16207
16999
  await writeFile3(outPath, `${JSON.stringify(tree, null, 2)}
16208
17000
  `);
16209
17001
  process.stdout.write(
@@ -16352,7 +17144,7 @@ function MapTokensView({ result, onExit }) {
16352
17144
  const timer = setTimeout(onExit, 100);
16353
17145
  return () => clearTimeout(timer);
16354
17146
  }, [onExit]);
16355
- const summary = `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
17147
+ const summary = result.cached ? "reused from cache" : `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
16356
17148
  return /* @__PURE__ */ jsx8(
16357
17149
  CommandCompletionView,
16358
17150
  {
@@ -16486,8 +17278,8 @@ var init_resolve_defaults = __esm({
16486
17278
 
16487
17279
  // packages/experience-design-system-cli-legacy/src/map-tokens/command.ts
16488
17280
  import { createElement as createElement4 } from "react";
16489
- import { readFile as readFile14 } from "node:fs/promises";
16490
- import { resolve as resolve13 } from "node:path";
17281
+ import { readFile as readFile15 } from "node:fs/promises";
17282
+ import { resolve as resolve14 } from "node:path";
16491
17283
  async function renderResult(result) {
16492
17284
  if (process.stdout.isTTY) {
16493
17285
  const { waitUntilExit } = renderWithGoodbye(
@@ -16495,13 +17287,12 @@ async function renderResult(result) {
16495
17287
  );
16496
17288
  await waitUntilExit();
16497
17289
  } else {
16498
- process.stdout.write(
16499
- `map tokens complete
17290
+ const summary = result.cached ? "cached" : `${result.applied} mapping(s) applied`;
17291
+ process.stdout.write(`map tokens complete
16500
17292
  agent: ${result.agent}
16501
17293
  session=${result.sessionId}
16502
- ${result.applied} mapping(s) applied
16503
- `
16504
- );
17294
+ ${summary}
17295
+ `);
16505
17296
  await exitWithAnalytics(0);
16506
17297
  }
16507
17298
  }
@@ -16523,7 +17314,7 @@ async function runMapTokens(opts) {
16523
17314
  let mapPromptPath;
16524
17315
  if (mapPromptOverride?.kind === "text") mapPromptText = mapPromptOverride.value;
16525
17316
  if (mapPromptOverride?.kind === "path") {
16526
- mapPromptPath = resolve13(mapPromptOverride.value);
17317
+ mapPromptPath = resolve14(mapPromptOverride.value);
16527
17318
  try {
16528
17319
  await resolvePromptOverride(mapPromptOverride);
16529
17320
  } catch (error) {
@@ -16543,7 +17334,7 @@ async function runMapTokens(opts) {
16543
17334
  const knownTokenPaths = new Set(tokenLeaves.map((t) => t.path));
16544
17335
  const tokenMapDiagnostics = [];
16545
17336
  if (opts.tokenMap) {
16546
- const fileMappings = JSON.parse(await readFile14(opts.tokenMap, "utf8"));
17337
+ const fileMappings = JSON.parse(await readFile15(opts.tokenMap, "utf8"));
16547
17338
  const validMappings = {};
16548
17339
  for (const [rawName, path] of Object.entries(fileMappings)) {
16549
17340
  if (knownTokenPaths.has(path)) {
@@ -16621,7 +17412,7 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16621
17412
  let existingTokensInline;
16622
17413
  if (opts.existingEntitiesPath) {
16623
17414
  const existingContentfulEntities = await readExistingContentfulEntitiesFromSession(
16624
- resolve13(opts.existingEntitiesPath)
17415
+ resolve14(opts.existingEntitiesPath)
16625
17416
  );
16626
17417
  if (existingContentfulEntities) {
16627
17418
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
@@ -16656,10 +17447,30 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16656
17447
  skipAgent: "true"
16657
17448
  });
16658
17449
  updateStep(db, stepId2, "complete", { applied: "0", skipAgent: "true" });
16659
- await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0 });
17450
+ await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0, cached: false });
16660
17451
  return;
16661
17452
  }
16662
17453
  const agent = configuredAgent;
17454
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
17455
+ const promptHash = await hashPromptForSkill(
17456
+ "map-tokens",
17457
+ agent,
17458
+ model,
17459
+ void 0,
17460
+ existingTokensInline ? [hashContent(existingTokensInline)] : [],
17461
+ mapPromptText
17462
+ );
17463
+ const inputHash = computeMapTokensInputHash(db, sessionId2, componentSourceRefs);
17464
+ if (!noCache) {
17465
+ const cached = lookupCache(db, inputHash, "token_mapping", "__map_tokens__", promptHash);
17466
+ if (cached) {
17467
+ const appliedFromCache = copyMapTokensFromCache(db, cached.sourceSessionId, sessionId2);
17468
+ const stepId2 = createStep(db, sessionId2, "map tokens", { agent, model: model ?? "" });
17469
+ updateStep(db, stepId2, "complete", { cached: "true", applied: String(appliedFromCache) });
17470
+ await renderResult({ agent, sessionId: sessionId2, applied: appliedFromCache, cached: true });
17471
+ return;
17472
+ }
17473
+ }
16663
17474
  const binary = resolveAgentBinary(agent);
16664
17475
  if (!await assertBinaryInPath(binary)) {
16665
17476
  die(
@@ -16679,20 +17490,23 @@ Install it, choose another agent with --agent, or use --print-prompt to run the
16679
17490
  }
16680
17491
  const { calls, warnings: parseWarnings } = parseMapTokenPropToolCallLines(result.stdout);
16681
17492
  const { applied, warnings } = applyMapTokenPropCalls(db, sessionId2, calls, parseWarnings);
17493
+ if (!noCache) {
17494
+ storeCache(db, inputHash, "token_mapping", "__map_tokens__", sessionId2, false, promptHash);
17495
+ }
16682
17496
  updateStep(db, stepId, "complete", { applied: String(applied), warnings: String(warnings.length) });
16683
17497
  if (warnings.length > 0) {
16684
17498
  process.stderr.write(`Warnings:
16685
17499
  ${warnings.map((w) => ` ${w}`).join("\n")}
16686
17500
  `);
16687
17501
  }
16688
- await renderResult({ agent, sessionId: sessionId2, applied });
17502
+ await renderResult({ agent, sessionId: sessionId2, applied, cached: false });
16689
17503
  } finally {
16690
17504
  db.close();
16691
17505
  }
16692
17506
  }
16693
17507
  function registerMapTokensCommand(program) {
16694
17508
  const map = program.command("map", { hidden: true }).description("Suggest token restrictions for generated design-token props");
16695
- const tokensCmd = map.command("tokens").description("Invoke a coding agent to suggest $token.allowed for design-category token props").option("--session <id>", "Session ID from generate components (defaults to most recent)").option("--print-prompt", "Print the prompt without invoking the agent").option("--skip-agent", "Resolve token defaults without agentic $token.allowed inference").option(
17509
+ const tokensCmd = map.command("tokens").description("Invoke a coding agent to suggest $token.allowed for design-category token props").option("--session <id>", "Session ID from generate components (defaults to most recent)").option("--print-prompt", "Print the prompt without invoking the agent").option("--skip-agent", "Resolve token defaults without agentic $token.allowed inference").option("--no-cache", "Bypass the map-tokens cache and force a re-run").option(
16696
17510
  "--prompt <stage=value>",
16697
17511
  "Override a stage prompt (repeatable). Used here for the map-tokens stage.",
16698
17512
  (v, acc) => [...acc, v],
@@ -16712,6 +17526,7 @@ var init_command4 = __esm({
16712
17526
  init_render_with_goodbye();
16713
17527
  init_src();
16714
17528
  init_db();
17529
+ init_cache_keys();
16715
17530
  init_read_existing_contentful_entities_from_session();
16716
17531
  init_summarize_existing_contentful_entities();
16717
17532
  init_command3();
@@ -16728,7 +17543,7 @@ var init_command4 = __esm({
16728
17543
  });
16729
17544
 
16730
17545
  // packages/experience-design-system-cli-legacy/src/import/path-utils.ts
16731
- import { resolve as resolve14 } from "node:path";
17546
+ import { resolve as resolve15 } from "node:path";
16732
17547
  import { homedir as homedir5 } from "node:os";
16733
17548
  function expandTilde(input) {
16734
17549
  if (input === "~" || input.startsWith("~/") || input.startsWith("~\\")) {
@@ -16742,7 +17557,7 @@ function normalizePath(input) {
16742
17557
  p = p.slice(1, -1);
16743
17558
  }
16744
17559
  p = expandTilde(p);
16745
- return resolve14(p);
17560
+ return resolve15(p);
16746
17561
  }
16747
17562
  var init_path_utils = __esm({
16748
17563
  "packages/experience-design-system-cli-legacy/src/import/path-utils.ts"() {
@@ -16967,7 +17782,7 @@ var init_save_path_resolver = __esm({
16967
17782
  });
16968
17783
 
16969
17784
  // packages/experience-design-system-cli-legacy/src/runs/store.ts
16970
- import { readFile as readFile15, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
17785
+ import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
16971
17786
  import { join as join20 } from "node:path";
16972
17787
  import { homedir as homedir6 } from "node:os";
16973
17788
  import { randomBytes as randomBytes2 } from "node:crypto";
@@ -17007,7 +17822,7 @@ function migrateRecord(rec) {
17007
17822
  }
17008
17823
  async function readFileMaybe() {
17009
17824
  try {
17010
- const raw = await readFile15(RUNS_PATH, "utf8");
17825
+ const raw = await readFile16(RUNS_PATH, "utf8");
17011
17826
  const parsed = JSON.parse(raw);
17012
17827
  if (!READABLE_VERSIONS.has(parsed.version)) {
17013
17828
  throw new Error(
@@ -17071,15 +17886,15 @@ var init_store = __esm({
17071
17886
  });
17072
17887
 
17073
17888
  // packages/experience-design-system-cli-legacy/src/runs/fingerprint.ts
17074
- import { createHash as createHash2 } from "node:crypto";
17075
- import { readFile as readFile16, stat as stat4 } from "node:fs/promises";
17076
- import { resolve as resolve15 } from "node:path";
17889
+ import { createHash as createHash6 } from "node:crypto";
17890
+ import { readFile as readFile17, stat as stat4 } from "node:fs/promises";
17891
+ import { resolve as resolve16 } from "node:path";
17077
17892
  function rowField(row, key) {
17078
17893
  const v = row[key];
17079
17894
  return typeof v === "string" ? v : null;
17080
17895
  }
17081
17896
  function sha256Hex(buf) {
17082
- return createHash2("sha256").update(buf).digest("hex");
17897
+ return createHash6("sha256").update(buf).digest("hex");
17083
17898
  }
17084
17899
  async function buildSourceFingerprint(opts) {
17085
17900
  const rows = opts.db.prepare("SELECT name, source_path FROM raw_components WHERE session_id = ?").all(opts.extractSessionId);
@@ -17089,7 +17904,7 @@ async function buildSourceFingerprint(opts) {
17089
17904
  const sourcePath = rowField(row, "source_path");
17090
17905
  const name = rowField(row, "name");
17091
17906
  if (!sourcePath) continue;
17092
- const abs = resolve15(sourcePath);
17907
+ const abs = resolve16(sourcePath);
17093
17908
  if (seen.has(abs)) continue;
17094
17909
  seen.add(abs);
17095
17910
  try {
@@ -17104,10 +17919,10 @@ async function buildSourceFingerprint(opts) {
17104
17919
  let rawTokensMtime = null;
17105
17920
  let rawTokensContentHash = null;
17106
17921
  if (opts.rawTokensPath) {
17107
- const abs = resolve15(opts.rawTokensPath);
17922
+ const abs = resolve16(opts.rawTokensPath);
17108
17923
  rawTokensPath = abs;
17109
17924
  try {
17110
- const [st, buf] = await Promise.all([stat4(abs), readFile16(abs)]);
17925
+ const [st, buf] = await Promise.all([stat4(abs), readFile17(abs)]);
17111
17926
  rawTokensMtime = st.mtime.toISOString();
17112
17927
  rawTokensContentHash = sha256Hex(buf);
17113
17928
  } catch {
@@ -18862,11 +19677,11 @@ var init_slot_graph = __esm({
18862
19677
  });
18863
19678
 
18864
19679
  // packages/experience-design-system-cli-legacy/src/analyze/select/parser.ts
18865
- import { createHash as createHash3 } from "node:crypto";
19680
+ import { createHash as createHash7 } from "node:crypto";
18866
19681
  import { access as access5 } from "node:fs/promises";
18867
- import { isAbsolute, relative, resolve as resolve16 } from "node:path";
19682
+ import { isAbsolute, relative, resolve as resolve17 } from "node:path";
18868
19683
  function createComponentId(name, resolvedSourcePath) {
18869
- const sourceHash = createHash3("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
19684
+ const sourceHash = createHash7("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
18870
19685
  return `${name}-${sourceHash}`;
18871
19686
  }
18872
19687
  async function resolveComponentSourcePath(source, reviewRoot) {
@@ -18878,7 +19693,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18878
19693
  throw new Error(`Unable to access component source at ${source}`);
18879
19694
  }
18880
19695
  }
18881
- const candidate = resolve16(reviewRoot, source);
19696
+ const candidate = resolve17(reviewRoot, source);
18882
19697
  const relativeToRoot = relative(reviewRoot, candidate);
18883
19698
  if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || isAbsolute(relativeToRoot)) {
18884
19699
  throw new Error(
@@ -18893,7 +19708,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18893
19708
  }
18894
19709
  }
18895
19710
  async function loadReviewInput(components, options = {}) {
18896
- const reviewRoot = resolve16(options.reviewRoot ?? process.cwd());
19711
+ const reviewRoot = resolve17(options.reviewRoot ?? process.cwd());
18897
19712
  const records = await Promise.all(
18898
19713
  components.map(async (component) => {
18899
19714
  let resolvedSourcePath;
@@ -18924,7 +19739,7 @@ var init_parser = __esm({
18924
19739
  });
18925
19740
 
18926
19741
  // packages/experience-design-system-cli-legacy/src/analyze/select/command.ts
18927
- import { access as access6, readFile as readFile17 } from "node:fs/promises";
19742
+ import { access as access6, readFile as readFile18 } from "node:fs/promises";
18928
19743
  async function loadAndValidateForReview(sessionId2, projectRoot) {
18929
19744
  const db = openPipelineDb();
18930
19745
  let rawComponents;
@@ -18942,7 +19757,7 @@ async function patchReviewStateWithValidationErrors(sessionId2, errors, opts = {
18942
19757
  let snapshot;
18943
19758
  try {
18944
19759
  await access6(paths.statePath);
18945
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19760
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18946
19761
  } catch {
18947
19762
  snapshot = await loadAndValidateForReview(sessionId2, void 0);
18948
19763
  snapshot = await ensureRefineSession(sessionId2, artifactsRoot, snapshot);
@@ -18993,7 +19808,7 @@ async function rejectComponentsByName(sessionId2, names, opts = {}) {
18993
19808
  const nameSet = new Set(names);
18994
19809
  let snapshot;
18995
19810
  try {
18996
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19811
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18997
19812
  } catch {
18998
19813
  return;
18999
19814
  }
@@ -19092,9 +19907,9 @@ function spawnGenerateChild(opts) {
19092
19907
  stderr += chunk;
19093
19908
  opts.onStderr?.(chunk);
19094
19909
  });
19095
- const donePromise = new Promise((resolve20) => {
19910
+ const donePromise = new Promise((resolve22) => {
19096
19911
  child.on("close", (code, signal) => {
19097
- resolve20({
19912
+ resolve22({
19098
19913
  exitCode: code ?? 0,
19099
19914
  signal: signal ?? null,
19100
19915
  stdout,
@@ -19102,7 +19917,7 @@ function spawnGenerateChild(opts) {
19102
19917
  });
19103
19918
  });
19104
19919
  child.on("error", (err) => {
19105
- resolve20({
19920
+ resolve22({
19106
19921
  exitCode: 1,
19107
19922
  signal: null,
19108
19923
  stdout,
@@ -27240,6 +28055,9 @@ function shouldGenerateAfterCredentialsValidated(opts) {
27240
28055
  function shouldSkipFinalReviewAfterCredentials(state) {
27241
28056
  return state.finalReviewPassed && state.generateSessionId != null;
27242
28057
  }
28058
+ function resolveNoCacheForGenerate(opts) {
28059
+ return opts.cliNoCache;
28060
+ }
27243
28061
  function shouldBypassPreview(state) {
27244
28062
  return state.credentialsSkipped === true;
27245
28063
  }
@@ -27266,39 +28084,66 @@ var init_wizard_state_transitions = __esm({
27266
28084
  async function runSelectionAgent(options) {
27267
28085
  const db = openPipelineDb();
27268
28086
  let components;
28087
+ const cachedDecisions = /* @__PURE__ */ new Map();
28088
+ const cliVersion2 = await getCliCacheVersion();
28089
+ const promptHash = await hashPromptForSkill(
28090
+ "select",
28091
+ options.agent,
28092
+ options.model,
28093
+ options.promptPath,
28094
+ [SELECT_PAYLOAD_FORMAT],
28095
+ options.promptText
28096
+ );
27269
28097
  try {
27270
28098
  components = loadRawComponents(db, options.sessionId);
28099
+ if (!options.noCache) {
28100
+ for (const component of components) {
28101
+ const componentHash = hashComponentShape(component);
28102
+ const cached = lookupSelectCache(db, componentHash, promptHash, cliVersion2);
28103
+ if (cached) {
28104
+ cachedDecisions.set(component.name, {
28105
+ decision: cached.decision,
28106
+ reason: cached.reason
28107
+ });
28108
+ }
28109
+ }
28110
+ }
27271
28111
  } finally {
27272
28112
  db.close();
27273
28113
  }
27274
28114
  if (components.length === 0) return;
27275
- const invoker2 = createLocalCliAgentInvoker({
27276
- onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
27277
- });
27278
- const prompt = await buildPrompt({
27279
- skill: "select",
27280
- mode: "autonomous",
27281
- rawComponentsInline: JSON.stringify(
27282
- components.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
27283
- null,
27284
- 2
27285
- ),
27286
- propBucketsInline: logPropBuckets("select", bucketComponentsProps(components)),
27287
- outDir: process.cwd(),
27288
- ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
27289
- ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
27290
- });
27291
- const { result } = await invokeAgentWithOutput(
27292
- invoker2,
27293
- { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
27294
- false
27295
- );
27296
- if (result.timedOut) throw new Error("selection agent timed out");
27297
- if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
27298
- const parsed = parseSelectToolCallLines(result.stdout);
27299
- for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
28115
+ const componentsToRun = components.filter((component) => !cachedDecisions.has(component.name));
28116
+ let calls = [];
28117
+ if (componentsToRun.length > 0) {
28118
+ const invoker2 = createLocalCliAgentInvoker({
28119
+ onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
28120
+ });
28121
+ const prompt = await buildPrompt({
28122
+ skill: "select",
28123
+ mode: "autonomous",
28124
+ rawComponentsInline: JSON.stringify(
28125
+ componentsToRun.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
28126
+ null,
28127
+ 2
28128
+ ),
28129
+ propBucketsInline: logPropBuckets("select", bucketComponentsProps(componentsToRun)),
28130
+ outDir: process.cwd(),
28131
+ ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
28132
+ ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
28133
+ });
28134
+ const { result } = await invokeAgentWithOutput(
28135
+ invoker2,
28136
+ { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
28137
+ false
28138
+ );
28139
+ if (result.timedOut) throw new Error("selection agent timed out");
28140
+ if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
28141
+ const parsed = parseSelectToolCallLines(result.stdout);
28142
+ for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
27300
28143
  `);
27301
- const byName = new Map(parsed.calls.map((call) => [call.name, call]));
28144
+ calls = parsed.calls;
28145
+ }
28146
+ const byName = new Map(calls.map((call) => [call.name, call]));
27302
28147
  const writeDb = openPipelineDb();
27303
28148
  try {
27304
28149
  writeDb.exec("BEGIN");
@@ -27306,15 +28151,27 @@ async function runSelectionAgent(options) {
27306
28151
  `UPDATE raw_components SET status = ?, reject_reason = ? WHERE session_id = ? AND component_id = ?`
27307
28152
  );
27308
28153
  for (const component of components) {
28154
+ const cached = cachedDecisions.get(component.name);
27309
28155
  const call = byName.get(component.name);
27310
- if (!call) continue;
27311
- const rejected = call.tool === "reject_component";
28156
+ if (!cached && !call) continue;
28157
+ const rejected = cached ? cached.decision === "rejected" : call.tool === "reject_component";
28158
+ const reason = cached ? cached.reason : call.reason ?? null;
27312
28159
  update.run(
27313
28160
  rejected ? "rejected" : "accepted",
27314
- rejected ? call.reason ?? null : null,
28161
+ rejected ? reason : null,
27315
28162
  options.sessionId,
27316
28163
  component.component_id
27317
28164
  );
28165
+ if (!cached) {
28166
+ storeSelectCache(
28167
+ writeDb,
28168
+ hashComponentShape(component),
28169
+ promptHash,
28170
+ cliVersion2,
28171
+ rejected ? "rejected" : "accepted",
28172
+ rejected ? reason : null
28173
+ );
28174
+ }
27318
28175
  }
27319
28176
  writeDb.exec("COMMIT");
27320
28177
  } catch (error) {
@@ -27324,7 +28181,7 @@ async function runSelectionAgent(options) {
27324
28181
  writeDb.close();
27325
28182
  }
27326
28183
  }
27327
- var DEFAULT_TIMEOUT_MS4;
28184
+ var SELECT_PAYLOAD_FORMAT, DEFAULT_TIMEOUT_MS4;
27328
28185
  var init_run_selection_agent = __esm({
27329
28186
  "packages/experience-design-system-cli-legacy/src/import/tui/run-selection-agent.ts"() {
27330
28187
  "use strict";
@@ -27335,6 +28192,8 @@ var init_run_selection_agent = __esm({
27335
28192
  init_debug_logger();
27336
28193
  init_agent_output();
27337
28194
  init_db();
28195
+ init_cache_keys();
28196
+ SELECT_PAYLOAD_FORMAT = "prop-buckets-v1";
27338
28197
  DEFAULT_TIMEOUT_MS4 = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 5 * 60 * 1e3);
27339
28198
  }
27340
28199
  });
@@ -27353,7 +28212,7 @@ __export(WizardApp_exports, {
27353
28212
  });
27354
28213
  import { useEffect as useEffect15, useRef as useRef10, useState as useState23 } from "react";
27355
28214
  import { Box as Box42, Text as Text46 } from "ink";
27356
- import { join as join23, resolve as resolve17 } from "node:path";
28215
+ import { join as join23, resolve as resolve18 } from "node:path";
27357
28216
  import { appendFileSync as appendFileSync2, writeFileSync } from "node:fs";
27358
28217
  import { access as access7, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
27359
28218
  import { tmpdir } from "node:os";
@@ -27366,21 +28225,28 @@ function buildGenerateComponentsArgs(opts) {
27366
28225
  if (opts.tokensPath) args.push("--tokens", opts.tokensPath);
27367
28226
  if (opts.model) args.push("--model", opts.model);
27368
28227
  if (opts.bedrock) args.push("--bedrock");
28228
+ if (opts.noCache) args.push("--no-cache");
27369
28229
  if (opts.generatePromptPath) args.push("--generate-prompt-path", opts.generatePromptPath);
27370
28230
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27371
28231
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
28232
+ if (opts.restoreCache) args.push("--restore-cache");
28233
+ if (opts.cachedComponents && opts.cachedComponents.length > 0) {
28234
+ args.push("--cached-components", JSON.stringify(opts.cachedComponents));
28235
+ }
27372
28236
  return args;
27373
28237
  }
27374
28238
  function buildGenerateTokensArgs(opts) {
27375
28239
  const args = [findCliPath(), "__generate", "tokens", "--agent", opts.agent, "--raw-tokens", opts.rawTokensPath];
27376
28240
  if (opts.model) args.push("--model", opts.model);
27377
28241
  if (opts.bedrock) args.push("--bedrock");
28242
+ if (opts.noCache) args.push("--no-cache");
27378
28243
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27379
28244
  return args;
27380
28245
  }
27381
28246
  function buildMapTokensArgs(opts) {
27382
28247
  const args = ["map", "tokens", "--session", opts.sessionId, "--agent", opts.agent];
27383
28248
  if (opts.model) args.push("--model", opts.model);
28249
+ if (opts.noCache) args.push("--no-cache");
27384
28250
  if (opts.skipAgent) args.push("--skip-agent");
27385
28251
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27386
28252
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
@@ -27472,6 +28338,7 @@ function WizardApp({
27472
28338
  initialProjectPath,
27473
28339
  host,
27474
28340
  promptOverrides,
28341
+ noCache = false,
27475
28342
  livePreview = true,
27476
28343
  generatePromptPath,
27477
28344
  skipMapTokens = false,
@@ -27496,6 +28363,7 @@ function WizardApp({
27496
28363
  });
27497
28364
  const allowEmptyDeleteAllRef = useRef10(false);
27498
28365
  const generateChildRef = useRef10(null);
28366
+ const generationCachePreflightRef = useRef10(null);
27499
28367
  const selectionToReviewStartedAtRef = useRef10(null);
27500
28368
  const credentialsValidationIdRef = useRef10(0);
27501
28369
  const pendingCredentialValidationRef = useRef10(null);
@@ -27510,8 +28378,9 @@ function WizardApp({
27510
28378
  const existingEntitiesPromiseRef = useRef10(null);
27511
28379
  const generatePromiseRef = useRef10(null);
27512
28380
  const rawTokensEntryReady = !!initialRawTokensPath;
28381
+ const effectiveNoCache = resolveNoCacheForGenerate({ cliNoCache: noCache });
27513
28382
  const initialStepResolved = rawTokensEntryReady ? initialProjectPath ? "path-validation" : "credentials" : initialProjectPath ? "token-input" : "welcome";
27514
- const initialOutDir = initialProjectPath ? join23(resolve17(initialProjectPath), ".contentful") : "";
28383
+ const initialOutDir = initialProjectPath ? join23(resolve18(initialProjectPath), ".contentful") : "";
27515
28384
  const [state, setState] = useState23({
27516
28385
  step: initialStepResolved,
27517
28386
  agent: initialAgent ?? "claude",
@@ -27687,13 +28556,14 @@ If you are using AWS Bedrock, run:
27687
28556
  if (!deferStep) update({ step: nextStep });
27688
28557
  return true;
27689
28558
  };
27690
- const runGenerateTokens = async (rawTokensPath, outDir) => {
28559
+ const runGenerateTokens = async (rawTokensPath, outDir, forceRegenerate = false) => {
27691
28560
  update({ tokenGenerationStatus: "running" });
27692
28561
  const tokenArgs = buildGenerateTokensArgs({
27693
28562
  rawTokensPath,
27694
28563
  agent: state.agent,
27695
28564
  ...state.agentModel ? { model: state.agentModel } : {},
27696
28565
  ...state.bedrock ? { bedrock: true } : {},
28566
+ noCache: effectiveNoCache || forceRegenerate,
27697
28567
  promptOverrides
27698
28568
  });
27699
28569
  const result = await runSpawnedCli(tokenArgs);
@@ -27742,6 +28612,7 @@ If you are using AWS Bedrock, run:
27742
28612
  const timer = startWizardTimer("token-mapping", {
27743
28613
  sessionId: sessionId2,
27744
28614
  silent,
28615
+ noCache: effectiveNoCache,
27745
28616
  skipAgent: skipMapTokens
27746
28617
  });
27747
28618
  let mappablePropCount = 0;
@@ -27750,7 +28621,7 @@ If you are using AWS Bedrock, run:
27750
28621
  const db = openPipelineDb();
27751
28622
  try {
27752
28623
  if (state.tokenSessionId && state.tokenSessionId !== sessionId2) {
27753
- copyTokensBetweenSessions(db, state.tokenSessionId, sessionId2);
28624
+ copyTokensFromCache(db, state.tokenSessionId, sessionId2);
27754
28625
  } else if (!state.tokenSessionId && state.tokensPath) {
27755
28626
  const tokens = await readTokensFromPath("tokens", state.tokensPath);
27756
28627
  storeDtcgTokens(db, sessionId2, [], tokens);
@@ -27790,6 +28661,7 @@ If you are using AWS Bedrock, run:
27790
28661
  sessionId: sessionId2,
27791
28662
  agent: state.agent,
27792
28663
  ...state.agentModel ? { model: state.agentModel } : {},
28664
+ noCache: effectiveNoCache,
27793
28665
  skipAgent: skipMapTokens,
27794
28666
  promptOverrides,
27795
28667
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {}
@@ -27869,7 +28741,7 @@ If you are using AWS Bedrock, run:
27869
28741
  return promise;
27870
28742
  };
27871
28743
  const runExtract = async (projectPath, showProgress = true) => {
27872
- const outDir = join23(resolve17(projectPath), ".contentful");
28744
+ const outDir = join23(resolve18(projectPath), ".contentful");
27873
28745
  const existingEntitiesExpected = !state.credentialsSkipped && Boolean(state.spaceId && state.environmentId && state.cmaToken && projectPath);
27874
28746
  update({
27875
28747
  ...showProgress ? { step: "extracting" } : {},
@@ -27884,6 +28756,10 @@ If you are using AWS Bedrock, run:
27884
28756
  });
27885
28757
  const existingEntitiesPromise = startExistingEntitiesFetch(projectPath, outDir);
27886
28758
  const extractArgs = [findCliPath(), "__extract", "--project", projectPath];
28759
+ if (effectiveNoCache) extractArgs.push("--composition-refresh", "--no-cache");
28760
+ for (const p of promptOverrides ?? []) extractArgs.push("--prompt", p);
28761
+ if (state.agent) extractArgs.push("--agent", state.agent);
28762
+ if (state.bedrock) extractArgs.push("--bedrock");
27887
28763
  let selectionPromptText;
27888
28764
  let selectionPromptPath;
27889
28765
  try {
@@ -27892,7 +28768,7 @@ If you are using AWS Bedrock, run:
27892
28768
  const selectOverride = overrides.get("select");
27893
28769
  if (selectOverride?.kind === "text") selectionPromptText = selectOverride.value;
27894
28770
  if (selectOverride?.kind === "path") {
27895
- selectionPromptPath = resolve17(selectOverride.value);
28771
+ selectionPromptPath = resolve18(selectOverride.value);
27896
28772
  await resolvePromptOverride(selectOverride);
27897
28773
  }
27898
28774
  } catch (error) {
@@ -27916,7 +28792,8 @@ If you are using AWS Bedrock, run:
27916
28792
  agent: state.agent,
27917
28793
  ...state.agentModel ? { model: state.agentModel } : {},
27918
28794
  ...selectionPromptText !== void 0 ? { promptText: selectionPromptText } : {},
27919
- ...selectionPromptPath ? { promptPath: selectionPromptPath } : {}
28795
+ ...selectionPromptPath ? { promptPath: selectionPromptPath } : {},
28796
+ noCache: effectiveNoCache
27920
28797
  });
27921
28798
  })().catch((error) => {
27922
28799
  selectionError = error;
@@ -28061,17 +28938,136 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28061
28938
  generateProgress: null
28062
28939
  }));
28063
28940
  };
28064
- const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2) => ({
28941
+ const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => ({
28065
28942
  sessionId: extractSessionId,
28066
28943
  tokensPath,
28067
28944
  agent: state.agent,
28068
28945
  ...state.agentModel ? { model: state.agentModel } : {},
28069
28946
  ...state.bedrock ? { bedrock: true } : {},
28947
+ noCache: effectiveNoCache,
28070
28948
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {},
28071
28949
  ...generatePromptPath2 ? { generatePromptPath: generatePromptPath2 } : {},
28072
- promptOverrides
28950
+ promptOverrides,
28951
+ ...cachedComponents && cachedComponents.length > 0 ? { cachedComponents } : {}
28073
28952
  });
28074
- const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2));
28953
+ const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents));
28954
+ const buildGenerateCliArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateComponentsArgs(
28955
+ buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents)
28956
+ );
28957
+ const checkGenerateCache = async (extractSessionId, tokensPath) => {
28958
+ const timer = startWizardTimer("generation-cache-status", {
28959
+ sessionId: extractSessionId,
28960
+ noCache: effectiveNoCache
28961
+ });
28962
+ if (effectiveNoCache) {
28963
+ finishWizardTimer("generation-cache-status", timer, { status: "skipped" });
28964
+ return false;
28965
+ }
28966
+ const result = await runCli([...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"]);
28967
+ const hit = result.exitCode === 0 && /^cache-status=hit$/m.test(result.stdout);
28968
+ finishWizardTimer("generation-cache-status", timer, {
28969
+ status: hit ? "hit" : "miss",
28970
+ exitCode: result.exitCode,
28971
+ ...result.exitCode !== 0 ? { stderrTail: result.stderr.trim().slice(-500) } : {}
28972
+ });
28973
+ return hit;
28974
+ };
28975
+ const checkGenerateCacheComponents = async (extractSessionId, tokensPath, restoreCache = false) => {
28976
+ const timer = startWizardTimer("generation-cache-components", {
28977
+ sessionId: extractSessionId,
28978
+ restoreCache,
28979
+ noCache: effectiveNoCache
28980
+ });
28981
+ if (effectiveNoCache) {
28982
+ finishWizardTimer("generation-cache-components", timer, { status: "skipped" });
28983
+ return /* @__PURE__ */ new Set();
28984
+ }
28985
+ const args = [...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"];
28986
+ if (restoreCache) args.push("--restore-cache");
28987
+ const result = await runCli(args);
28988
+ if (result.exitCode !== 0) {
28989
+ finishWizardTimer("generation-cache-components", timer, {
28990
+ status: "error",
28991
+ exitCode: result.exitCode,
28992
+ stderrTail: result.stderr.trim().slice(-500)
28993
+ });
28994
+ return /* @__PURE__ */ new Set();
28995
+ }
28996
+ const line = /^cache-components=(.+)$/m.exec(result.stdout)?.[1];
28997
+ if (!line) {
28998
+ finishWizardTimer("generation-cache-components", timer, {
28999
+ status: "missing-output",
29000
+ exitCode: result.exitCode,
29001
+ cachedCount: 0
29002
+ });
29003
+ return /* @__PURE__ */ new Set();
29004
+ }
29005
+ try {
29006
+ const names = JSON.parse(line);
29007
+ const cachedNames = new Set(
29008
+ Array.isArray(names) ? names.filter((name) => typeof name === "string") : []
29009
+ );
29010
+ finishWizardTimer("generation-cache-components", timer, {
29011
+ status: "complete",
29012
+ exitCode: result.exitCode,
29013
+ cachedCount: cachedNames.size
29014
+ });
29015
+ return cachedNames;
29016
+ } catch {
29017
+ finishWizardTimer("generation-cache-components", timer, {
29018
+ status: "invalid-output",
29019
+ exitCode: result.exitCode,
29020
+ cachedCount: 0
29021
+ });
29022
+ return /* @__PURE__ */ new Set();
29023
+ }
29024
+ };
29025
+ useEffect15(() => {
29026
+ 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) {
29027
+ return;
29028
+ }
29029
+ const sessionId2 = state.extractSessionId;
29030
+ if (generationCachePreflightRef.current?.sessionId === sessionId2) return;
29031
+ const timer = startWizardTimer("generation-cache-preflight", { sessionId: sessionId2 });
29032
+ const db = openPipelineDb();
29033
+ let generatedNames = null;
29034
+ try {
29035
+ const rows = db.prepare("SELECT name, status FROM raw_components WHERE session_id = ?").all(sessionId2);
29036
+ if (rows.length > 0 && rows.every((row) => row.status === "generated")) {
29037
+ generatedNames = new Set(rows.map((row) => row.name));
29038
+ }
29039
+ } finally {
29040
+ db.close();
29041
+ }
29042
+ if (generatedNames) {
29043
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: Promise.resolve(generatedNames) };
29044
+ finishWizardTimer("generation-cache-preflight", timer, {
29045
+ status: "complete",
29046
+ source: "session-db",
29047
+ cachedCount: generatedNames.size
29048
+ });
29049
+ return;
29050
+ }
29051
+ const promise = checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
29052
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise };
29053
+ void promise.then((cachedNames) => {
29054
+ finishWizardTimer("generation-cache-preflight", timer, {
29055
+ status: "complete",
29056
+ source: "cache-status",
29057
+ cachedCount: cachedNames.size
29058
+ });
29059
+ });
29060
+ }, [
29061
+ state.step,
29062
+ state.extractSessionId,
29063
+ state.tokensPath,
29064
+ state.rawTokensPath,
29065
+ state.tokenGenerationStatus,
29066
+ state.selectionAgentStatus,
29067
+ state.compositionPhase,
29068
+ state.existingEntitiesStatus,
29069
+ state.existingEntitiesPath
29070
+ ]);
28075
29071
  const startGeneratePrefetch = (extractSessionId, tokensPath) => {
28076
29072
  const args = buildGenerateArgs(extractSessionId, tokensPath);
28077
29073
  let progressCursor = null;
@@ -28126,13 +29122,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28126
29122
  });
28127
29123
  return donePromise;
28128
29124
  };
28129
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false) => {
29125
+ const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false, cachedComponents = []) => {
28130
29126
  const timer = startWizardTimer("component-generation", {
28131
29127
  sessionId: extractSessionId,
28132
29128
  acceptedCount,
28133
- suppressScreen
29129
+ suppressScreen,
29130
+ noCache: effectiveNoCache
28134
29131
  });
28135
- const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath);
29132
+ const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath, cachedComponents);
28136
29133
  let progressCursor = state.generateProgress;
28137
29134
  const { donePromise } = spawnGenerateChild({
28138
29135
  command: "node",
@@ -28202,6 +29199,25 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28202
29199
  finishSelectionToReviewTimer("error");
28203
29200
  }
28204
29201
  };
29202
+ const finishCachedGeneration = async (sessionId2, acceptedCount) => {
29203
+ if (await runMapTokens2(sessionId2, true)) {
29204
+ finishSelectionToReviewTimer("cached");
29205
+ update(
29206
+ {
29207
+ acceptedCount,
29208
+ autoRejectedCount: 0,
29209
+ generateSessionId: sessionId2,
29210
+ generatedCount: acceptedCount,
29211
+ generateProgress: null,
29212
+ mapTokensStatus: "complete",
29213
+ step: "final-review"
29214
+ },
29215
+ { clearScreen: false }
29216
+ );
29217
+ } else {
29218
+ finishSelectionToReviewTimer("error");
29219
+ }
29220
+ };
28205
29221
  const advanceToPushFlow = (generatedAcceptedCount) => {
28206
29222
  update({ generatedAcceptedCount });
28207
29223
  void runSaveAndPush();
@@ -28386,8 +29402,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28386
29402
  }
28387
29403
  }
28388
29404
  if (await runAgentAuthCheck("generating", true)) {
28389
- update({ step: "generating" });
28390
- void runGenerate(sid, state.tokensPath, state.acceptedCount);
29405
+ const cacheHit = await checkGenerateCache(sid, state.tokensPath);
29406
+ if (cacheHit) {
29407
+ update({ step: "generating" });
29408
+ void finishCachedGeneration(sid, state.acceptedCount);
29409
+ } else {
29410
+ update({ step: "generating" });
29411
+ void runGenerate(sid, state.tokensPath, state.acceptedCount);
29412
+ }
28391
29413
  }
28392
29414
  return;
28393
29415
  }
@@ -28751,7 +29773,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28751
29773
  });
28752
29774
  return { ok: false };
28753
29775
  }
28754
- await new Promise((resolve20) => setTimeout(resolve20, SAVE_CONFIRMATION_DELAY_MS));
29776
+ await new Promise((resolve22) => setTimeout(resolve22, SAVE_CONFIRMATION_DELAY_MS));
28755
29777
  update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
28756
29778
  return { ok: true };
28757
29779
  };
@@ -28828,6 +29850,12 @@ If using a custom host configuration, make sure the space exists on that host.`
28828
29850
  tokenReuseChecked.current = true;
28829
29851
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28830
29852
  const existingTokensPath = join23(state.outDir, "tokens.json");
29853
+ if (effectiveNoCache) {
29854
+ tokenGenerationPromiseRef.current = runAgentAuthCheck(tokenNextStep).then(
29855
+ (ok2) => ok2 ? runGenerateTokens(state.rawTokensPath, state.outDir) : false
29856
+ );
29857
+ return;
29858
+ }
28831
29859
  (async () => {
28832
29860
  try {
28833
29861
  await access7(existingTokensPath);
@@ -28911,7 +29939,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28911
29939
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28912
29940
  update({ step: tokenNextStep });
28913
29941
  if (await runAgentAuthCheck(tokenNextStep)) {
28914
- tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir);
29942
+ tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir, true);
28915
29943
  }
28916
29944
  },
28917
29945
  onQuit: () => process.exit(0)
@@ -28945,7 +29973,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28945
29973
  );
28946
29974
  case "extracting": {
28947
29975
  const ep = state.extractProgress;
28948
- const compositionComplete = state.compositionPhase === "done";
29976
+ const compositionComplete = state.compositionPhase === "done" || state.compositionPhase === "cache-hit";
28949
29977
  const tokenGenerationComplete = !state.rawTokensPath || state.tokenGenerationStatus === "complete";
28950
29978
  const extractionTasksComplete = ep !== null && ep.totalFiles > 0 && compositionComplete && state.selectionAgentStatus === "complete" && tokenGenerationComplete && state.existingEntitiesStatus !== "running";
28951
29979
  if (extractionTasksComplete) return null;
@@ -28960,7 +29988,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28960
29988
  extractDetail = "Scanning files...";
28961
29989
  }
28962
29990
  const selectionDetail = state.selectionAgentStatus === "complete" ? "Selection agent complete" : `Filtering component selection via ${state.agent}...`;
28963
- const compositionDetail = compositionComplete ? "Composition mapping complete" : "Mapping composition from source call sites...";
29991
+ 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}...`;
28964
29992
  return /* @__PURE__ */ jsx50(
28965
29993
  RunningStep,
28966
29994
  {
@@ -29010,6 +30038,29 @@ If using a custom host configuration, make sure the space exists on that host.`
29010
30038
  acceptedCount: decisions.accepted.length,
29011
30039
  rejectedCount: decisions.rejected.length
29012
30040
  }) : null;
30041
+ const preflight = generationCachePreflightRef.current;
30042
+ const cachePromise = preflight?.sessionId === sessionId2 ? preflight.promise : checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
30043
+ if (!preflight || preflight.sessionId !== sessionId2) {
30044
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: cachePromise };
30045
+ }
30046
+ const cacheWaitStartedAt = startWizardTimer("selection-cache-restoration-wait", {
30047
+ sessionId: sessionId2,
30048
+ preflight: Boolean(preflight?.sessionId === sessionId2)
30049
+ });
30050
+ try {
30051
+ const cachedNames = await cachePromise;
30052
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30053
+ status: "complete",
30054
+ cachedCount: cachedNames.size
30055
+ });
30056
+ } catch (error) {
30057
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30058
+ status: "error",
30059
+ error: error instanceof Error ? error.message : String(error)
30060
+ });
30061
+ finishSelectionToReviewTimer("error");
30062
+ throw error;
30063
+ }
29013
30064
  await runScopeGate({
29014
30065
  sessionId: sessionId2,
29015
30066
  decisions,
@@ -29018,12 +30069,35 @@ If using a custom host configuration, make sure the space exists on that host.`
29018
30069
  if (shouldGenerateAfterScopeGate({ acceptedCount })) {
29019
30070
  const authPromise = runAgentAuthCheck("generating", true);
29020
30071
  if (await authPromise) {
29021
- update({
29022
- step: "generating",
29023
- generateProgress: null,
29024
- acceptedCount
30072
+ const comparisonStartedAt = startWizardTimer("generation-cache-set-comparison", {
30073
+ sessionId: sid,
30074
+ acceptedCount: decisions?.accepted.length ?? 0
29025
30075
  });
29026
- void runGenerate(sid, state.tokensPath, acceptedCount);
30076
+ let cachedNames = await cachePromise;
30077
+ const acceptedNames = decisions?.accepted ?? [];
30078
+ let cacheHit = !effectiveNoCache && acceptedNames.length > 0 && acceptedNames.every((name) => cachedNames.has(name));
30079
+ if (!cacheHit && !effectiveNoCache && acceptedNames.length > 0) {
30080
+ cachedNames = await checkGenerateCacheComponents(sid, state.tokensPath, true);
30081
+ cacheHit = acceptedNames.every((name) => cachedNames.has(name));
30082
+ }
30083
+ finishWizardTimer("generation-cache-set-comparison", comparisonStartedAt, {
30084
+ status: "complete",
30085
+ cacheHit,
30086
+ cachedCount: cachedNames.size,
30087
+ acceptedCount: acceptedNames.length
30088
+ });
30089
+ if (cacheHit) {
30090
+ void finishCachedGeneration(sid, acceptedCount);
30091
+ } else {
30092
+ const acceptedNameSet = new Set(acceptedNames);
30093
+ const restoredCachedNames = [...cachedNames].filter((name) => acceptedNameSet.has(name));
30094
+ update({
30095
+ step: "generating",
30096
+ generateProgress: null,
30097
+ acceptedCount
30098
+ });
30099
+ void runGenerate(sid, state.tokensPath, acceptedCount, false, restoredCachedNames);
30100
+ }
29027
30101
  } else {
29028
30102
  finishSelectionToReviewTimer("error");
29029
30103
  }
@@ -29389,7 +30463,7 @@ function registerImportCommand(program) {
29389
30463
  "Override a composition, selection, token, generation, or token-mapping prompt (repeatable). value is a file path or literal text, e.g. --prompt select=./p.md",
29390
30464
  (v, acc) => [...acc, v],
29391
30465
  []
29392
- ).action(async (opts) => {
30466
+ ).option("--no-cache", "Re-run all steps even if output already exists").action(async (opts) => {
29393
30467
  const interactiveTerminalSupported = getInteractiveTerminalSupport().supported;
29394
30468
  if (opts.tokens !== void 0) {
29395
30469
  const { access: access9 } = await import("node:fs/promises");
@@ -29426,6 +30500,7 @@ function registerImportCommand(program) {
29426
30500
  ...resolvedModel ? { initialModel: resolvedModel } : {},
29427
30501
  initialProjectPath: opts.project !== "." ? normalizePath(opts.project) : void 0,
29428
30502
  ...buildCompositionForwardingOptions(opts),
30503
+ noCache: opts.cache === false,
29429
30504
  skipMapTokens: false,
29430
30505
  livePreview: true,
29431
30506
  generatePromptPath: creds.generatePromptPath,
@@ -29513,7 +30588,7 @@ async function pathExists3(path) {
29513
30588
  return access8(path).then(() => true).catch(() => false);
29514
30589
  }
29515
30590
  function runSpawn(cmd, args, opts = {}) {
29516
- return new Promise((resolve20) => {
30591
+ return new Promise((resolve22) => {
29517
30592
  const child = spawn6(cmd, args, {
29518
30593
  cwd: opts.cwd,
29519
30594
  env: opts.env ?? process.env,
@@ -29525,7 +30600,7 @@ function runSpawn(cmd, args, opts = {}) {
29525
30600
  const settle = (result) => {
29526
30601
  if (settled) return;
29527
30602
  settled = true;
29528
- resolve20(result);
30603
+ resolve22(result);
29529
30604
  };
29530
30605
  child.on("error", (err) => settle({ exitCode: 1, stdout: "", stderr: err.message }));
29531
30606
  child.stdout.on("data", (d) => {
@@ -30726,13 +31801,13 @@ function PrerequisitesScreen({
30726
31801
  const driver = {
30727
31802
  ...base,
30728
31803
  emit: (event) => setEvents((current) => [...current, event]),
30729
- confirm: (question, defaultYes = true) => new Promise((resolve20) => {
31804
+ confirm: (question, defaultYes = true) => new Promise((resolve22) => {
30730
31805
  setConfirm({
30731
31806
  question,
30732
31807
  defaultYes,
30733
31808
  resolve: (answer) => {
30734
31809
  setConfirm(null);
30735
- resolve20(answer);
31810
+ resolve22(answer);
30736
31811
  }
30737
31812
  });
30738
31813
  })
@@ -30807,8 +31882,8 @@ function SetupScreen({
30807
31882
  useEffect21(() => {
30808
31883
  if (startedRef.current) return;
30809
31884
  startedRef.current = true;
30810
- const runScreen = (build) => new Promise((resolve20) => {
30811
- setScreen(build(resolve20));
31885
+ const runScreen = (build) => new Promise((resolve22) => {
31886
+ setScreen(build(resolve22));
30812
31887
  });
30813
31888
  void (async () => {
30814
31889
  const results = [];
@@ -30986,9 +32061,9 @@ var init_command8 = __esm({
30986
32061
  function rank(p) {
30987
32062
  if (p === "typed-slot") return 1;
30988
32063
  if (p === "structural") return 2;
30989
- if (p === "call-site") return 3;
30990
- if (p === "manifest") return 4;
30991
- if (p === "doc") return 5;
32064
+ if (p === "manifest") return 3;
32065
+ if (p === "doc") return 4;
32066
+ if (p.startsWith("adapter:")) return 5;
30992
32067
  return 6;
30993
32068
  }
30994
32069
  function mergeEdges(all) {
@@ -31024,6 +32099,65 @@ var init_merge_edges = __esm({
31024
32099
  }
31025
32100
  });
31026
32101
 
32102
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/parse-map-edges.ts
32103
+ function parseMapEdges(raw, opts) {
32104
+ const edges = [];
32105
+ const warnings = [];
32106
+ const { componentNames } = opts;
32107
+ for (const line of raw.split("\n")) {
32108
+ const trimmed = line.trim();
32109
+ if (!trimmed.startsWith("{")) continue;
32110
+ let obj;
32111
+ try {
32112
+ obj = JSON.parse(trimmed);
32113
+ } catch {
32114
+ warnings.push(`unparseable line: ${trimmed.slice(0, 120)}`);
32115
+ continue;
32116
+ }
32117
+ if (typeof obj !== "object" || obj === null || !("tool" in obj)) continue;
32118
+ const rec = obj;
32119
+ if (!VALID_MAP_TOOL_NAMES.has(rec.tool)) {
32120
+ warnings.push(`unknown tool: ${String(rec.tool)}`);
32121
+ continue;
32122
+ }
32123
+ if (typeof rec.parent !== "string" || !rec.parent || typeof rec.child !== "string" || !rec.child) {
32124
+ warnings.push("map_edge missing parent/child \u2014 skipped");
32125
+ continue;
32126
+ }
32127
+ const { parent, child } = rec;
32128
+ if (!componentNames.has(parent)) {
32129
+ warnings.push(`map_edge names unknown component (parent): ${parent} \u2014 skipped`);
32130
+ continue;
32131
+ }
32132
+ if (!componentNames.has(child)) {
32133
+ warnings.push(`map_edge names unknown component (child): ${child} \u2014 skipped`);
32134
+ continue;
32135
+ }
32136
+ const edge = { parent, child, provenance: "agent" };
32137
+ if (typeof rec.slot === "string" && rec.slot) {
32138
+ edge.slot = rec.slot;
32139
+ }
32140
+ if (rec.confidence !== void 0) {
32141
+ if (typeof rec.confidence === "number" && rec.confidence >= 1 && rec.confidence <= 5) {
32142
+ edge.confidence = rec.confidence;
32143
+ } else {
32144
+ warnings.push(
32145
+ `map_edge (${parent}\u2192${child}): invalid confidence ${String(rec.confidence)} \u2014 edge kept without confidence`
32146
+ );
32147
+ }
32148
+ }
32149
+ edges.push(edge);
32150
+ }
32151
+ return { edges, warnings };
32152
+ }
32153
+ var VALID_MAP_TOOL_NAMES;
32154
+ var init_parse_map_edges = __esm({
32155
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/parse-map-edges.ts"() {
32156
+ "use strict";
32157
+ VALID_MAP_TOOL_NAMES = /* @__PURE__ */ new Set(["map_edge"]);
32158
+ }
32159
+ });
32160
+
31027
32161
  // packages/experience-design-system-cli-legacy/src/analyze/composition/apply-mapping.ts
31028
32162
  function applyMapping(components, edges) {
31029
32163
  const warnings = [];
@@ -31083,9 +32217,44 @@ var init_apply_mapping = __esm({
31083
32217
  }
31084
32218
  });
31085
32219
 
32220
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/prompt-loader.ts
32221
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
32222
+ import { dirname as dirname17, join as join27, resolve as resolve19 } from "node:path";
32223
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
32224
+ function resolvePromptPath(fileName) {
32225
+ const thisDir = dirname17(fileURLToPath3(import.meta.url));
32226
+ let dir = thisDir;
32227
+ for (; ; ) {
32228
+ const candidate = join27(dir, "prompts");
32229
+ if (existsSync10(candidate)) return join27(candidate, fileName);
32230
+ const parent = resolve19(dir, "..");
32231
+ if (parent === dir) {
32232
+ throw new Error(
32233
+ `prompt file missing from CLI installation (could not locate prompts/ directory from: ${thisDir})`
32234
+ );
32235
+ }
32236
+ dir = parent;
32237
+ }
32238
+ }
32239
+ function loadPrompt(fileName) {
32240
+ const path = resolvePromptPath(fileName);
32241
+ try {
32242
+ return readFileSync10(path, "utf8");
32243
+ } catch {
32244
+ throw new Error(`prompt file missing from CLI installation \u2014 try reinstalling the CLI (looked for: ${path})`);
32245
+ }
32246
+ }
32247
+ var init_prompt_loader = __esm({
32248
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/prompt-loader.ts"() {
32249
+ "use strict";
32250
+ }
32251
+ });
32252
+
31086
32253
  // packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping.ts
31087
- function resolveMapping(input) {
32254
+ async function resolveMapping(input) {
32255
+ const componentNames = new Set(input.components.map((c2) => c2.name));
31088
32256
  const collected = [];
32257
+ const agentWarnings = [];
31089
32258
  for (const c2 of input.components) {
31090
32259
  for (const slot of c2.slots) {
31091
32260
  for (const child of slot.allowedComponents ?? []) {
@@ -31103,41 +32272,17 @@ function resolveMapping(input) {
31103
32272
  if (input.extraEdges) {
31104
32273
  collected.push(...input.extraEdges);
31105
32274
  }
31106
- for (const evidence of input.sourceCallSiteEvidence ?? []) {
31107
- collected.push({
31108
- parent: evidence.parent,
31109
- child: evidence.child,
31110
- ...evidence.slot ? { slot: evidence.slot } : {},
31111
- citation: { sourcePath: evidence.sourcePath, startLine: evidence.startLine, endLine: evidence.endLine },
31112
- provenance: "call-site"
31113
- });
31114
- }
31115
32275
  const coveredParents = new Set(collected.map((e) => e.parent));
31116
32276
  const residueParents = input.components.map((c2) => c2.name).filter((n) => !coveredParents.has(n));
31117
- const debug = getDebugLogger();
31118
- const countByProvenance = (edges) => {
31119
- const counts = {};
31120
- for (const edge of edges) counts[edge.provenance] = (counts[edge.provenance] ?? 0) + 1;
31121
- return counts;
31122
- };
31123
- debug.event("analyze", "composition.pre-merge", {
31124
- edgeCountsByProvenance: countByProvenance(collected),
31125
- coveredParents: [...coveredParents].sort(),
31126
- residueParents,
31127
- sourceCallSiteAccepted: input.sourceCallSiteEvidence?.length ?? 0,
31128
- sourceCallSiteRejected: input.sourceCallSiteRejections?.length ?? 0
31129
- });
32277
+ const shouldRunAgent = input.forceAgent || residueParents.length > 0 && input.files.length > 0;
32278
+ if (shouldRunAgent) {
32279
+ const prompt = input.buildPrompt ? input.buildPrompt(input.files, [...componentNames]) : defaultPrompt(input.files, [...componentNames], input.promptOverride);
32280
+ const raw = await input.runAgentFn({ prompt, files: input.files }) ?? "";
32281
+ const parsed = parseMapEdges(raw, { componentNames });
32282
+ collected.push(...parsed.edges);
32283
+ agentWarnings.push(...parsed.warnings);
32284
+ }
31130
32285
  const merged = mergeEdges(collected);
31131
- debug.event("analyze", "composition.merged", {
31132
- edgeCountsByProvenance: countByProvenance(merged.edges),
31133
- conflicts: merged.conflicts,
31134
- edges: merged.edges.map((edge) => ({
31135
- parent: edge.parent,
31136
- child: edge.child,
31137
- slot: edge.slot ?? null,
31138
- provenance: edge.provenance
31139
- }))
31140
- });
31141
32286
  const base = input.components.map((c2) => ({
31142
32287
  ...c2,
31143
32288
  slots: c2.slots.map((s) => {
@@ -31146,32 +32291,127 @@ function resolveMapping(input) {
31146
32291
  })
31147
32292
  }));
31148
32293
  const applied = applyMapping(base, merged.edges);
31149
- debug.event("analyze", "composition.applied", {
31150
- slots: applied.components.flatMap(
31151
- (component) => component.slots.filter((slot) => (slot.allowedComponents?.length ?? 0) > 0).map((slot) => ({ component: component.name, slot: slot.name, allowedComponents: slot.allowedComponents }))
31152
- ),
31153
- warnings: applied.warnings
31154
- });
31155
32294
  return {
31156
32295
  components: applied.components,
31157
32296
  edges: merged.edges,
31158
32297
  conflicts: merged.conflicts,
31159
- warnings: applied.warnings,
31160
- sourceCallSiteEvidence: [...input.sourceCallSiteEvidence ?? []],
31161
- sourceCallSiteRejections: [...input.sourceCallSiteRejections ?? []]
32298
+ warnings: [...agentWarnings, ...applied.warnings]
31162
32299
  };
31163
32300
  }
32301
+ function defaultPrompt(files, componentNames, promptOverride) {
32302
+ const fileBlocks = files.map((f) => `--- ${f.path} ---
32303
+ ${f.content}`).join("\n\n");
32304
+ const instruction = promptOverride?.trim() ? promptOverride.trim() : loadPrompt("composition-edges.md").trim();
32305
+ return [
32306
+ instruction,
32307
+ "",
32308
+ 'Emit one JSON object per line, each: {"tool":"map_edge","parent":"<Name>","child":"<Name>","slot"?:"<slot>","confidence"?:1-5,"reason":"<cite the file + declaration>"}.',
32309
+ "Use ONLY these exact component names (an edge naming anything else is dropped):",
32310
+ componentNames.join(", "),
32311
+ "",
32312
+ "Candidate files:",
32313
+ fileBlocks
32314
+ ].join("\n");
32315
+ }
31164
32316
  var init_resolve_mapping = __esm({
31165
32317
  "packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping.ts"() {
31166
32318
  "use strict";
31167
32319
  init_merge_edges();
32320
+ init_parse_map_edges();
31168
32321
  init_apply_mapping();
31169
- init_debug_logger();
32322
+ init_prompt_loader();
32323
+ }
32324
+ });
32325
+
32326
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping-cli.ts
32327
+ function resolveCompositionSources(opts) {
32328
+ return {
32329
+ forceAgent: opts.compositionRefresh === true || opts.noCache === true
32330
+ };
32331
+ }
32332
+ var init_resolve_mapping_cli = __esm({
32333
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping-cli.ts"() {
32334
+ "use strict";
32335
+ }
32336
+ });
32337
+
32338
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/candidate-files.ts
32339
+ function matchReason(file) {
32340
+ const segments = file.path.split("/").filter((s) => s !== "");
32341
+ for (const pattern of CANDIDATE_NAME_PATTERNS) {
32342
+ if (segments.some((seg) => pattern.test(seg))) {
32343
+ return `name:${pattern.source.toLowerCase()}`;
32344
+ }
32345
+ }
32346
+ for (const marker of CANDIDATE_CONTENT_MARKERS) {
32347
+ if (file.content.includes(marker)) return `content:${marker}`;
32348
+ }
32349
+ return void 0;
32350
+ }
32351
+ function selectCandidateFiles(files) {
32352
+ const selected = [];
32353
+ const seen = /* @__PURE__ */ new Set();
32354
+ for (const file of files) {
32355
+ if (seen.has(file.path)) continue;
32356
+ const reason = matchReason(file);
32357
+ if (reason === void 0) continue;
32358
+ seen.add(file.path);
32359
+ selected.push({ path: file.path, content: file.content, reason });
32360
+ }
32361
+ return selected;
32362
+ }
32363
+ function capCandidatesToPromptBudget(files, budget = PROMPT_CANDIDATE_TOKEN_BUDGET) {
32364
+ const ordered = [...files].sort((a, b) => a.content.length - b.content.length || a.path.localeCompare(b.path));
32365
+ const kept = [];
32366
+ const dropped = [];
32367
+ let spent = 0;
32368
+ for (const file of ordered) {
32369
+ const cost = tokenCost(file);
32370
+ if (spent + cost > budget) {
32371
+ dropped.push(file);
32372
+ continue;
32373
+ }
32374
+ kept.push(file);
32375
+ spent += cost;
32376
+ }
32377
+ return { kept, dropped };
32378
+ }
32379
+ function tokenCost(file) {
32380
+ return Math.ceil(file.content.length / CHARS_PER_TOKEN);
32381
+ }
32382
+ var CANDIDATE_NAME_PATTERNS, CANDIDATE_CONTENT_MARKERS, PROMPT_CANDIDATE_TOKEN_BUDGET, CHARS_PER_TOKEN;
32383
+ var init_candidate_files = __esm({
32384
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/candidate-files.ts"() {
32385
+ "use strict";
32386
+ CANDIDATE_NAME_PATTERNS = [/mapping/i, /meta/i, /registry/i, /schema/i, /composition/i];
32387
+ CANDIDATE_CONTENT_MARKERS = [
32388
+ "requiredParent",
32389
+ "withParentType",
32390
+ "allowedTagNames",
32391
+ "createContext",
32392
+ "MappingContext",
32393
+ "allowedComponents"
32394
+ ];
32395
+ PROMPT_CANDIDATE_TOKEN_BUDGET = 8e4;
32396
+ CHARS_PER_TOKEN = 4;
32397
+ }
32398
+ });
32399
+
32400
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/composition-cache-key.ts
32401
+ function buildCompositionInputHash(input) {
32402
+ const fileDigest = input.files.map((f) => `${f.path} ${hashContent(f.content)}`).sort().join("");
32403
+ return hashContent(`agent:${input.agent}
32404
+ ${fileDigest}`);
32405
+ }
32406
+ var init_composition_cache_key = __esm({
32407
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/composition-cache-key.ts"() {
32408
+ "use strict";
32409
+ init_cache_keys();
31170
32410
  }
31171
32411
  });
31172
32412
 
31173
32413
  // packages/experience-design-system-cli-legacy/src/analyze/composition/manifest-doc-evidence.ts
31174
- import { dirname as dirname17 } from "node:path";
32414
+ import { dirname as dirname18 } from "node:path";
31175
32415
  function kebabToPascal2(name) {
31176
32416
  return name.split("-").filter(Boolean).map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1)).join("");
31177
32417
  }
@@ -31221,8 +32461,8 @@ function collectAgentsDocEdges(files, components, componentNames) {
31221
32461
  const seen = /* @__PURE__ */ new Set();
31222
32462
  for (const file of files) {
31223
32463
  if (!file.path.endsWith("AGENTS.md")) continue;
31224
- const docDir = dirname17(file.path);
31225
- const parent = components.find((c2) => c2.sourcePath && dirname17(c2.sourcePath) === docDir)?.name;
32464
+ const docDir = dirname18(file.path);
32465
+ const parent = components.find((c2) => c2.sourcePath && dirname18(c2.sourcePath) === docDir)?.name;
31226
32466
  if (!parent || !componentNames.has(parent)) continue;
31227
32467
  for (const line of file.content.split("\n")) {
31228
32468
  const lower = line.toLowerCase();
@@ -31252,172 +32492,12 @@ var init_manifest_doc_evidence = __esm({
31252
32492
  }
31253
32493
  });
31254
32494
 
31255
- // packages/experience-design-system-cli-legacy/src/analyze/composition/source-call-site-evidence.ts
31256
- import { Node as Node49, Project as Project11, SyntaxKind as SyntaxKind15 } from "ts-morph";
31257
- function collectSourceCallSiteEvidence(files, components) {
31258
- const componentNames = new Set(components.map((component) => component.name));
31259
- const componentsBySource = /* @__PURE__ */ new Map();
31260
- for (const component of components) {
31261
- const sourcePath = component.sourcePath ?? component.source;
31262
- const siblings = componentsBySource.get(sourcePath) ?? [];
31263
- siblings.push(component);
31264
- componentsBySource.set(sourcePath, siblings);
31265
- }
31266
- const project = new Project11({
31267
- compilerOptions: { jsx: 1, target: 99, module: 99, moduleResolution: 100, skipLibCheck: true, allowJs: true },
31268
- skipAddingFilesFromTsConfig: true
31269
- });
31270
- const sourceFiles = new Map(
31271
- files.map((file) => [file.path, project.createSourceFile(file.path, file.content, { overwrite: true })])
31272
- );
31273
- const accepted = [];
31274
- const rejected = [];
31275
- for (const [sourcePath, parents] of componentsBySource) {
31276
- const sourceFile = sourceFiles.get(sourcePath);
31277
- if (!sourceFile) continue;
31278
- const boundNames = collectBoundNames(sourceFile);
31279
- for (const parent of parents) {
31280
- for (const jsxElement of [
31281
- ...sourceFile.getDescendantsOfKind(SyntaxKind15.JsxSelfClosingElement),
31282
- ...sourceFile.getDescendantsOfKind(SyntaxKind15.JsxElement)
31283
- ]) {
31284
- const tagName = Node49.isJsxSelfClosingElement(jsxElement) ? jsxElement.getTagNameNode().getText() : jsxElement.getOpeningElement().getTagNameNode().getText();
31285
- const candidate = tagName.split(".").at(-1) ?? tagName;
31286
- if (candidate === parent.name || isIntrinsicTag(candidate)) continue;
31287
- const lines = sourceExcerpt(sourceFile, jsxElement);
31288
- if (!componentNames.has(candidate)) {
31289
- rejected.push({ parent: parent.name, candidate, ...lines, reason: "unknown-component" });
31290
- continue;
31291
- }
31292
- if (!boundNames.has(tagName) && !boundNames.has(candidate)) {
31293
- rejected.push({ parent: parent.name, candidate, ...lines, reason: "unbound-component" });
31294
- continue;
31295
- }
31296
- accepted.push({ parent: parent.name, child: candidate, ...lines, kind: "jsx-render" });
31297
- }
31298
- }
31299
- }
31300
- for (const sourceFile of sourceFiles.values()) {
31301
- collectCallerSlotEvidence(sourceFile, componentNames, collectBoundNames(sourceFile), accepted, rejected);
31302
- }
31303
- const verifiedPairs = new Set(accepted.map((evidence) => `${evidence.parent}\0${evidence.child}`));
31304
- for (const component of components) {
31305
- const candidates = component.slots.flatMap((slot) => [
31306
- ...slot.allowedComponents ?? [],
31307
- ...slot.structuralAllowedComponents ?? []
31308
- ]);
31309
- for (const candidate of candidates) {
31310
- if (verifiedPairs.has(`${component.name}\0${candidate}`)) continue;
31311
- rejected.push({
31312
- parent: component.name,
31313
- candidate,
31314
- sourcePath: component.sourcePath ?? component.source,
31315
- reason: "no-call-site"
31316
- });
31317
- }
31318
- }
31319
- return {
31320
- accepted: dedupeEvidence(accepted),
31321
- rejected: dedupeRejections(rejected)
31322
- };
31323
- }
31324
- function collectCallerSlotEvidence(sourceFile, componentNames, boundNames, accepted, rejected) {
31325
- for (const parentElement of sourceFile.getDescendantsOfKind(SyntaxKind15.JsxElement)) {
31326
- const parentTag = parentElement.getOpeningElement().getTagNameNode().getText();
31327
- const parent = parentTag.split(".").at(-1) ?? parentTag;
31328
- if (!componentNames.has(parent) || !boundNames.has(parentTag)) continue;
31329
- for (const childNode of parentElement.getJsxChildren()) {
31330
- if (!Node49.isJsxElement(childNode) && !Node49.isJsxSelfClosingElement(childNode)) {
31331
- if (Node49.isJsxText(childNode) && childNode.getText().trim()) {
31332
- const lines2 = sourceExcerpt(sourceFile, childNode);
31333
- rejected.push({ parent, candidate: "<text>", ...lines2, reason: "text-only-child" });
31334
- }
31335
- continue;
31336
- }
31337
- const childTag = Node49.isJsxElement(childNode) ? childNode.getOpeningElement().getTagNameNode().getText() : childNode.getTagNameNode().getText();
31338
- const child = childTag.split(".").at(-1) ?? childTag;
31339
- if (isIntrinsicTag(child)) continue;
31340
- const lines = sourceExcerpt(sourceFile, childNode);
31341
- if (!componentNames.has(child)) {
31342
- rejected.push({ parent, candidate: child, ...lines, reason: "unknown-component" });
31343
- continue;
31344
- }
31345
- if (!boundNames.has(childTag) && !boundNames.has(child)) {
31346
- rejected.push({ parent, candidate: child, ...lines, reason: "unbound-component" });
31347
- continue;
31348
- }
31349
- const slotAttribute = Node49.isJsxElement(childNode) ? childNode.getOpeningElement().getAttribute("slot") : childNode.getAttribute("slot");
31350
- const slotValue = slotAttribute && Node49.isJsxAttribute(slotAttribute) ? slotAttribute.getInitializer() : void 0;
31351
- const slot = slotValue && Node49.isStringLiteral(slotValue) ? slotValue.getLiteralValue() : void 0;
31352
- accepted.push({ parent, child, ...slot ? { slot } : {}, ...lines, kind: "jsx-render" });
31353
- }
31354
- }
31355
- }
31356
- function collectBoundNames(sourceFile) {
31357
- const names = /* @__PURE__ */ new Set();
31358
- for (const declaration of sourceFile.getImportDeclarations()) {
31359
- const defaultImport = declaration.getDefaultImport();
31360
- if (defaultImport) names.add(defaultImport.getText());
31361
- const namespaceImport = declaration.getNamespaceImport();
31362
- if (namespaceImport) names.add(namespaceImport.getText());
31363
- for (const namedImport of declaration.getNamedImports()) {
31364
- names.add((namedImport.getAliasNode() ?? namedImport.getNameNode()).getText());
31365
- }
31366
- }
31367
- for (const declaration of sourceFile.getVariableDeclarations()) names.add(declaration.getName());
31368
- for (const declaration of sourceFile.getFunctions()) {
31369
- if (declaration.getName()) names.add(declaration.getName());
31370
- }
31371
- for (const declaration of sourceFile.getClasses()) {
31372
- if (declaration.getName()) names.add(declaration.getName());
31373
- }
31374
- return names;
31375
- }
31376
- function sourceExcerpt(sourceFile, node) {
31377
- const startLine = node.getStartLineNumber();
31378
- const endLine = node.getEndLineNumber();
31379
- const lines = sourceFile.getFullText().split("\n");
31380
- return {
31381
- sourcePath: sourceFile.getFilePath(),
31382
- startLine,
31383
- endLine,
31384
- excerpt: lines.slice(startLine - 1, endLine).join("\n").trim()
31385
- };
31386
- }
31387
- function isIntrinsicTag(tagName) {
31388
- return /^[a-z]/.test(tagName) || tagName.includes("-");
31389
- }
31390
- function dedupeEvidence(evidence) {
31391
- const seen = /* @__PURE__ */ new Set();
31392
- return evidence.filter((item) => {
31393
- const key = `${item.parent}\0${item.child}\0${item.slot ?? ""}\0${item.sourcePath}\0${item.startLine}`;
31394
- if (seen.has(key)) return false;
31395
- seen.add(key);
31396
- return true;
31397
- }).sort(
31398
- (left, right) => `${left.parent}\0${left.child}\0${left.sourcePath}\0${left.startLine}`.localeCompare(
31399
- `${right.parent}\0${right.child}\0${right.sourcePath}\0${right.startLine}`
31400
- )
31401
- );
31402
- }
31403
- function dedupeRejections(rejections) {
31404
- const seen = /* @__PURE__ */ new Set();
31405
- return rejections.filter((item) => {
31406
- const key = `${item.parent}\0${item.candidate}\0${item.sourcePath}\0${item.startLine}`;
31407
- if (seen.has(key)) return false;
31408
- seen.add(key);
31409
- return true;
31410
- });
31411
- }
31412
- var init_source_call_site_evidence = __esm({
31413
- "packages/experience-design-system-cli-legacy/src/analyze/composition/source-call-site-evidence.ts"() {
31414
- "use strict";
31415
- }
31416
- });
31417
-
31418
32495
  // packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts
31419
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile18, stat as stat6 } from "node:fs/promises";
31420
- import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolve18 } from "node:path";
32496
+ import { mkdir as mkdir6, readdir as readdir3, readFile as readFile19, stat as stat6 } from "node:fs/promises";
32497
+ import { isAbsolute as isAbsolute2, join as join28, relative as relative2, resolve as resolve20 } from "node:path";
32498
+ function resolveExtractNoCache(opts) {
32499
+ return opts.noCache === true || opts.cache === false;
32500
+ }
31421
32501
  function isDenylistedNoiseFile(name) {
31422
32502
  return DENYLISTED_EXACT_FILE_NAMES.has(name) || DENYLISTED_FILE_NAME_PATTERNS.some((pattern) => pattern.test(name));
31423
32503
  }
@@ -31431,12 +32511,12 @@ async function retryDatabaseWrite(operation, attempts = 8) {
31431
32511
  } catch (error) {
31432
32512
  const message = error instanceof Error ? error.message : String(error);
31433
32513
  if (!/database is locked|database is busy/i.test(message) || attempt >= attempts) throw error;
31434
- await new Promise((resolve20) => setTimeout(resolve20, Math.min(250 * attempt, 2e3)));
32514
+ await new Promise((resolve22) => setTimeout(resolve22, Math.min(250 * attempt, 2e3)));
31435
32515
  }
31436
32516
  }
31437
32517
  }
31438
32518
  function resolveFromProjectRoot(projectRoot, inputPath) {
31439
- return isAbsolute2(inputPath) ? inputPath : resolve18(projectRoot, inputPath);
32519
+ return isAbsolute2(inputPath) ? inputPath : resolve20(projectRoot, inputPath);
31440
32520
  }
31441
32521
  async function pathExists4(path) {
31442
32522
  return Boolean(await stat6(path).catch(() => null));
@@ -31447,7 +32527,7 @@ async function collectSourceFiles(directory, onProgress) {
31447
32527
  const entries = await readdir3(currentDirectory, { withFileTypes: true });
31448
32528
  const subdirs = [];
31449
32529
  for (const entry of entries) {
31450
- const fullPath = join27(currentDirectory, entry.name);
32530
+ const fullPath = join28(currentDirectory, entry.name);
31451
32531
  if (entry.isDirectory()) {
31452
32532
  if (!IGNORED_DIRECTORY_NAMES.has(entry.name)) {
31453
32533
  subdirs.push(fullPath);
@@ -31474,6 +32554,12 @@ async function collectSourceFiles(directory, onProgress) {
31474
32554
  await visit(directory);
31475
32555
  return files.sort();
31476
32556
  }
32557
+ function resolveCompositionAgentName(flagValue) {
32558
+ if (flagValue && isAgentName(flagValue)) return flagValue;
32559
+ const env = process.env["EDS_COMPOSITION_AGENT"];
32560
+ if (env && isAgentName(env)) return env;
32561
+ return DEFAULT_AGENT_NAME;
32562
+ }
31477
32563
  async function readCandidateFiles(components, extraFiles = []) {
31478
32564
  const paths = new Set(extraFiles);
31479
32565
  for (const c2 of components) {
@@ -31483,7 +32569,7 @@ async function readCandidateFiles(components, extraFiles = []) {
31483
32569
  await Promise.all(
31484
32570
  [...paths].map(async (p) => {
31485
32571
  try {
31486
- const content = await readFile18(p, "utf8");
32572
+ const content = await readFile19(p, "utf8");
31487
32573
  out.push({ path: p, content });
31488
32574
  } catch {
31489
32575
  }
@@ -31492,11 +32578,21 @@ async function readCandidateFiles(components, extraFiles = []) {
31492
32578
  return out;
31493
32579
  }
31494
32580
  function registerInternalExtractCommand(program) {
31495
- 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(
32581
+ 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(
31496
32582
  "--resolve-unreachable <mode>",
31497
32583
  "Retry pass for unresolved Svelte Props types: 'auto' (default), 'always', or 'never'",
31498
32584
  "auto"
31499
- ).action(async (opts) => {
32585
+ ).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(
32586
+ "--prompt <stage=value>",
32587
+ "Override a stage prompt (repeatable). value is a file path or literal text, e.g. --prompt composition=./p.md",
32588
+ (v, acc) => [...acc, v],
32589
+ []
32590
+ );
32591
+ addAgentModelOptions(extractCmd, {
32592
+ includeModel: false,
32593
+ agentDescription: "Coding agent for composition mapping resolution (claude|codex|opencode|cursor)"
32594
+ }).action(async (opts) => {
32595
+ const noCache = resolveExtractNoCache(opts);
31500
32596
  const resolveUnreachable = (() => {
31501
32597
  const v = opts.resolveUnreachable ?? "auto";
31502
32598
  if (v !== "auto" && v !== "always" && v !== "never") {
@@ -31506,8 +32602,16 @@ function registerInternalExtractCommand(program) {
31506
32602
  }
31507
32603
  return v;
31508
32604
  })();
31509
- const projectRoot = resolve18(opts.project);
31510
- const outDir = join27(projectRoot, ".contentful");
32605
+ if (opts.bedrock) {
32606
+ const bedrockAgent = resolveCompositionAgentName(opts.agent);
32607
+ if (!agentSupportsBedrock(bedrockAgent)) {
32608
+ process.stderr.write(`Error: --bedrock is not supported for --agent ${bedrockAgent}
32609
+ `);
32610
+ process.exit(1);
32611
+ }
32612
+ }
32613
+ const projectRoot = resolve20(opts.project);
32614
+ const outDir = join28(projectRoot, ".contentful");
31511
32615
  let sourceDirectory;
31512
32616
  if (opts.dir !== void 0) {
31513
32617
  sourceDirectory = resolveFromProjectRoot(projectRoot, opts.dir);
@@ -31530,16 +32634,50 @@ function registerInternalExtractCommand(program) {
31530
32634
  process.stderr.write(`progress=scan-done:${sourceFiles.length}
31531
32635
  `);
31532
32636
  }
31533
- const extraction = await extractComponents(
31534
- sourceFiles,
31535
- ({ filesProcessed, componentsFound }) => {
32637
+ const extractionCacheDb = openPipelineDb();
32638
+ let extraction;
32639
+ let extractionCacheHits = 0;
32640
+ try {
32641
+ const cacheVersion = await getCliCacheVersion();
32642
+ const cachedByPath = /* @__PURE__ */ new Map();
32643
+ if (!noCache && sourceFiles.length > 0) {
32644
+ const hashes = await Promise.all(
32645
+ sourceFiles.map(async (filePath) => [filePath, await hashFile(filePath)])
32646
+ );
32647
+ for (const [filePath, fileHash] of hashes) {
32648
+ cachedByPath.set(filePath, lookupExtractCache(extractionCacheDb, fileHash, cacheVersion));
32649
+ }
32650
+ }
32651
+ const allFilesCached = !noCache && sourceFiles.length > 0 && sourceFiles.every((filePath) => cachedByPath.get(filePath) !== null);
32652
+ if (allFilesCached) {
32653
+ const cachedComponents = sourceFiles.flatMap((filePath) => cachedByPath.get(filePath).components);
32654
+ extractionCacheHits = sourceFiles.length;
32655
+ extraction = { components: cachedComponents, warnings: [], exclusions: [] };
31536
32656
  if (!process.stdout.isTTY) {
31537
- process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
31538
- `);
32657
+ process.stderr.write(
32658
+ `progress=extract:${sourceFiles.length}/${sourceFiles.length}:${cachedComponents.length}
32659
+ `
32660
+ );
31539
32661
  }
31540
- },
31541
- { resolveUnreachable, projectRoot }
31542
- );
32662
+ getDebugLogger().event("analyze", "extract.cache-hit", {
32663
+ files: sourceFiles.length,
32664
+ components: cachedComponents.length
32665
+ });
32666
+ } else {
32667
+ extraction = await extractComponents(
32668
+ sourceFiles,
32669
+ ({ filesProcessed, componentsFound }) => {
32670
+ if (!process.stdout.isTTY) {
32671
+ process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
32672
+ `);
32673
+ }
32674
+ },
32675
+ { resolveUnreachable, projectRoot }
32676
+ );
32677
+ }
32678
+ } finally {
32679
+ extractionCacheDb.close();
32680
+ }
31543
32681
  await mkdir6(outDir, { recursive: true });
31544
32682
  const db = openPipelineDb();
31545
32683
  const { sessionId: sessionId2 } = getOrCreateSession(db, void 0, void 0, {
@@ -31554,6 +32692,25 @@ function registerInternalExtractCommand(program) {
31554
32692
  const stepId = createStep(db, sessionId2, "analyze extract", {
31555
32693
  project: projectRoot
31556
32694
  });
32695
+ if (extractionCacheHits === 0 && !noCache) {
32696
+ const cacheVersion = await getCliCacheVersion();
32697
+ const componentsBySourcePath = /* @__PURE__ */ new Map();
32698
+ for (const component of extraction.components) {
32699
+ if (!component.sourcePath) continue;
32700
+ const components = componentsBySourcePath.get(component.sourcePath) ?? [];
32701
+ components.push(component);
32702
+ componentsBySourcePath.set(component.sourcePath, components);
32703
+ }
32704
+ for (const filePath of sourceFiles) {
32705
+ storeExtractCache(
32706
+ db,
32707
+ filePath,
32708
+ await hashFile(filePath),
32709
+ cacheVersion,
32710
+ componentsBySourcePath.get(filePath) ?? []
32711
+ );
32712
+ }
32713
+ }
31557
32714
  const classifiedComponents = extraction.components.map(preClassifyComponent);
31558
32715
  for (const exclusion of extraction.exclusions ?? []) {
31559
32716
  getDebugLogger().event("filter", "extract.excluded", { ...exclusion });
@@ -31577,9 +32734,28 @@ function registerInternalExtractCommand(program) {
31577
32734
  storeRawComponents(db, sessionId2, validatedComponents);
31578
32735
  process.stdout.write(`session=${sessionId2}
31579
32736
  `);
31580
- let sourceCallSiteEvidence = [];
31581
- let sourceCallSiteRejections = [];
31582
32737
  {
32738
+ const sources = resolveCompositionSources({
32739
+ ...opts,
32740
+ noCache
32741
+ });
32742
+ const { overrides: promptOverrides, errors: promptErrors } = parsePromptOverrides(opts.prompt ?? []);
32743
+ for (const err of promptErrors) {
32744
+ process.stderr.write(`Error: ${err}
32745
+ `);
32746
+ process.exit(1);
32747
+ }
32748
+ let compositionPrompt;
32749
+ const compositionOverride = promptOverrides.get("composition");
32750
+ if (compositionOverride) {
32751
+ try {
32752
+ compositionPrompt = await resolvePromptOverride(compositionOverride);
32753
+ } catch (e) {
32754
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : String(e)}
32755
+ `);
32756
+ process.exit(1);
32757
+ }
32758
+ }
31583
32759
  {
31584
32760
  const emitCompositionProgress = (phase) => {
31585
32761
  if (!process.stdout.isTTY) process.stderr.write(`progress=composition:${phase}
@@ -31587,18 +32763,62 @@ function registerInternalExtractCommand(program) {
31587
32763
  };
31588
32764
  emitCompositionProgress("resolving");
31589
32765
  const allFiles = await readCandidateFiles(validatedComponents, sourceFiles);
32766
+ const selectedCandidates = selectCandidateFiles(allFiles).map((c2) => ({ path: c2.path, content: c2.content }));
32767
+ const capped = capCandidatesToPromptBudget(selectedCandidates);
32768
+ const promptFiles = capped.kept;
32769
+ if (capped.dropped.length > 0) {
32770
+ process.stderr.write(
32771
+ `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.
32772
+ `
32773
+ );
32774
+ }
31590
32775
  const runtimeFiles = allFiles.map((c2) => ({ path: c2.path, content: c2.content }));
32776
+ const resolverAgent = resolveCompositionAgentName(opts.agent);
31591
32777
  const componentNameSet = new Set(validatedComponents.map((c2) => c2.name));
32778
+ const cacheVersion = await getCliCacheVersion();
32779
+ let lastAgentExitCode = 0;
32780
+ const spawnAgent = async (prompt) => {
32781
+ const res = await runAgent({
32782
+ agent: resolverAgent,
32783
+ prompt,
32784
+ timeoutMs: 12e4,
32785
+ promptViaStdin: true,
32786
+ onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
32787
+ });
32788
+ lastAgentExitCode = res.exitCode;
32789
+ if (res.exitCode !== 0 && res.stderr.trim()) {
32790
+ process.stderr.write(`Warning: composition \u2014 agent exited ${res.exitCode}: ${res.stderr.trim()}
32791
+ `);
32792
+ }
32793
+ return res.stdout;
32794
+ };
31592
32795
  const manifestDocEdges = collectManifestDocEdges(runtimeFiles, validatedComponents, componentNameSet);
31593
32796
  const extraEdges = manifestDocEdges;
31594
- const sourceCallSites = collectSourceCallSiteEvidence(runtimeFiles, validatedComponents);
31595
- sourceCallSiteEvidence = sourceCallSites.accepted;
31596
- sourceCallSiteRejections = sourceCallSites.rejected;
31597
- const result = resolveMapping({
32797
+ const agentCacheKey = buildCompositionInputHash({
32798
+ files: promptFiles,
32799
+ agent: resolverAgent
32800
+ });
32801
+ const result = await resolveMapping({
31598
32802
  components: validatedComponents,
31599
32803
  ...extraEdges.length > 0 ? { extraEdges } : {},
31600
- sourceCallSiteEvidence,
31601
- sourceCallSiteRejections
32804
+ forceAgent: sources.forceAgent,
32805
+ files: promptFiles,
32806
+ ...compositionPrompt ? { promptOverride: compositionPrompt } : {},
32807
+ runAgentFn: async ({ prompt }) => {
32808
+ if (!noCache && !opts.compositionRefresh) {
32809
+ const cached = lookupCompositionCache(db, agentCacheKey, cacheVersion);
32810
+ if (cached !== null) {
32811
+ emitCompositionProgress("cache-hit");
32812
+ return cached;
32813
+ }
32814
+ }
32815
+ emitCompositionProgress(`agent:${resolverAgent}`);
32816
+ const stdout = await spawnAgent(prompt);
32817
+ if (lastAgentExitCode === 0) {
32818
+ storeCompositionCache(db, agentCacheKey, cacheVersion, stdout);
32819
+ }
32820
+ return stdout;
32821
+ }
31602
32822
  });
31603
32823
  emitCompositionProgress("done");
31604
32824
  for (const w of result.warnings) process.stderr.write(`Warning: composition \u2014 ${w}
@@ -31628,13 +32848,7 @@ function registerInternalExtractCommand(program) {
31628
32848
  sessionId2,
31629
32849
  sourceFiles.map((f) => relative2(projectRoot, f))
31630
32850
  );
31631
- await retryDatabaseWrite(
31632
- () => updateStep(db, stepId, "complete", {
31633
- sessionId: sessionId2,
31634
- compositionEvidence: JSON.stringify(sourceCallSiteEvidence),
31635
- compositionRejections: JSON.stringify(sourceCallSiteRejections)
31636
- })
31637
- );
32851
+ await retryDatabaseWrite(() => updateStep(db, stepId, "complete", { sessionId: sessionId2 }));
31638
32852
  enrichCommandResult({ extracted_component_count: validatedComponents.length });
31639
32853
  db.close();
31640
32854
  const allWarnings = [...extraction.warnings, ...filterWarnings];
@@ -31656,12 +32870,18 @@ var SCANNED_FILE_EXTENSIONS, DENYLIST_GATED_EXTENSIONS, DENYLISTED_EXACT_FILE_NA
31656
32870
  var init_extract_command = __esm({
31657
32871
  "packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts"() {
31658
32872
  "use strict";
32873
+ init_agent_model_options();
31659
32874
  init_src2();
31660
32875
  init_db();
32876
+ init_cache_keys();
31661
32877
  init_cycle_detection();
31662
32878
  init_resolve_mapping();
32879
+ init_resolve_mapping_cli();
32880
+ init_candidate_files();
32881
+ init_composition_cache_key();
31663
32882
  init_manifest_doc_evidence();
31664
- init_source_call_site_evidence();
32883
+ init_prompt_overrides();
32884
+ init_src();
31665
32885
  init_analytics();
31666
32886
  init_debug_logger();
31667
32887
  SCANNED_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".js", ".jsx", ".svelte", ".ts", ".tsx", ".vue"]);
@@ -31755,9 +32975,9 @@ __export(program_exports, {
31755
32975
  runBuild: () => runBuild
31756
32976
  });
31757
32977
  import { spawn as spawn7 } from "node:child_process";
31758
- import { fileURLToPath as fileURLToPath3 } from "node:url";
31759
- import { dirname as dirname18, join as join28, resolve as resolve19 } from "node:path";
31760
- import { readFileSync as readFileSync10 } from "node:fs";
32978
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
32979
+ import { dirname as dirname19, join as join29, resolve as resolve21 } from "node:path";
32980
+ import { readFileSync as readFileSync11 } from "node:fs";
31761
32981
  import { Command } from "commander";
31762
32982
  async function runBuild(opts) {
31763
32983
  return new Promise((resolvePromise) => {
@@ -31786,7 +33006,7 @@ function resolveBedrockFromAncestors(actionCommand) {
31786
33006
  }
31787
33007
  function registerBuildCommand(program) {
31788
33008
  program.command("build").description("Rebuild from source and re-link exo/experiences binaries to this build").helpOption(false).action(async () => {
31789
- const pkgRoot = resolve19(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
33009
+ const pkgRoot = resolve21(dirname19(fileURLToPath4(import.meta.url)), "..", "..");
31790
33010
  process.stderr.write("\u2699 Building from source...\n");
31791
33011
  const { exitCode } = await runBuild({
31792
33012
  spawnFn: () => spawn7("pnpm", ["build"], { cwd: pkgRoot, stdio: "inherit" }),
@@ -31856,7 +33076,7 @@ var init_program = __esm({
31856
33076
  init_analytics();
31857
33077
  init_credentials_store();
31858
33078
  init_cli_path();
31859
- pkg3 = JSON.parse(readFileSync10(join28(findPkgRoot(), "package.json"), "utf8"));
33079
+ pkg3 = JSON.parse(readFileSync11(join29(findPkgRoot(), "package.json"), "utf8"));
31860
33080
  }
31861
33081
  });
31862
33082