@contentful/experience-design-system-cli 2.35.1-dev-build-a2149178.0 → 2.35.1-dev-build-925d6206.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/package.json +1 -5
  2. package/dist/src/tui/home/home.d.ts +0 -1
  3. package/dist/src/tui/home/home.theme.d.ts +0 -11
  4. package/legacy/dist/src/analyze/composition/candidate-files.d.ts +34 -0
  5. package/legacy/dist/src/analyze/composition/composition-cache-key.d.ts +18 -0
  6. package/legacy/dist/src/analyze/composition/interchange-schema.d.ts +1 -7
  7. package/legacy/dist/src/analyze/composition/parse-map-edges.d.ts +16 -0
  8. package/legacy/dist/src/analyze/composition/prompt-loader.d.ts +3 -0
  9. package/legacy/dist/src/analyze/composition/resolve-mapping-cli.d.ts +10 -0
  10. package/legacy/dist/src/analyze/composition/resolve-mapping.d.ts +34 -11
  11. package/legacy/dist/src/analyze/extract-command.d.ts +4 -0
  12. package/legacy/dist/src/import/tui/WizardApp.d.ts +7 -1
  13. package/legacy/dist/src/import/tui/run-selection-agent.d.ts +1 -0
  14. package/legacy/dist/src/import/tui/wizard-state-transitions.d.ts +3 -0
  15. package/legacy/dist/src/index.js +1669 -449
  16. package/legacy/dist/src/map-tokens/tui/MapTokensView.d.ts +1 -0
  17. package/legacy/dist/src/session/cache-keys.d.ts +22 -0
  18. package/legacy/dist/src/session/core/components/hash-component-shape.d.ts +5 -0
  19. package/legacy/dist/src/session/core/tokens/hash-token-content.d.ts +1 -0
  20. package/legacy/dist/src/session/db.d.ts +72 -2
  21. package/legacy/package.json +1 -5
  22. package/legacy/prompts/composition-edges.md +11 -0
  23. package/package.json +3 -7
  24. package/legacy/dist/src/analyze/composition/source-call-site-evidence.d.ts +0 -30
  25. package/legacy/skills/debate-select.md +0 -23
@@ -164,11 +164,12 @@ var init_render_with_goodbye = __esm({
164
164
  function isAgentName(value) {
165
165
  return AGENT_NAMES.includes(value);
166
166
  }
167
- var AGENT_NAMES, AGENT_BINARIES;
167
+ var AGENT_NAMES, DEFAULT_AGENT_NAME, AGENT_BINARIES;
168
168
  var init_agent_names = __esm({
169
169
  "packages/experience-design-system-generation/dist/src/agent-names.js"() {
170
170
  "use strict";
171
171
  AGENT_NAMES = ["claude", "codex", "opencode", "cursor", "copilot"];
172
+ DEFAULT_AGENT_NAME = "claude";
172
173
  AGENT_BINARIES = {
173
174
  claude: "claude",
174
175
  codex: "codex",
@@ -180,6 +181,23 @@ var init_agent_names = __esm({
180
181
  });
181
182
 
182
183
  // packages/experience-design-system-generation/dist/src/controller/helpers/validate-generation-request.js
184
+ function validateGenerationRequest(request2) {
185
+ if (request2 === null || typeof request2 !== "object") {
186
+ throw new TypeError("createGenerationEndpoint requires a request object");
187
+ }
188
+ if (!isAgentName(request2.agent)) {
189
+ throw new TypeError(`createGenerationEndpoint requires agent to be a valid agent name, got: ${String(request2.agent)}`);
190
+ }
191
+ if (typeof request2.timeoutMs !== "number" || request2.timeoutMs <= 0) {
192
+ throw new TypeError("createGenerationEndpoint requires timeoutMs to be a positive number");
193
+ }
194
+ if (request2.promptOptions === null || typeof request2.promptOptions !== "object") {
195
+ throw new TypeError("createGenerationEndpoint requires promptOptions to be an object");
196
+ }
197
+ if (request2.onOutput !== void 0 && typeof request2.onOutput !== "function") {
198
+ throw new TypeError("createGenerationEndpoint requires onOutput to be a function when provided");
199
+ }
200
+ }
183
201
  var init_validate_generation_request = __esm({
184
202
  "packages/experience-design-system-generation/dist/src/controller/helpers/validate-generation-request.js"() {
185
203
  "use strict";
@@ -238,7 +256,6 @@ var init_resolve_skill_file = __esm({
238
256
  components: "generate-components.md",
239
257
  tokens: "generate-tokens.md",
240
258
  select: "select-components.md",
241
- "debate-select": "debate-select.md",
242
259
  "map-tokens": "map-tokens.md"
243
260
  };
244
261
  }
@@ -500,31 +517,6 @@ Rules:
500
517
  - Emit set_group calls before the set_token calls that fall under them.
501
518
  - You may emit prose lines (not starting with {) anywhere \u2014 they are ignored by the parser and serve as your reasoning log.`;
502
519
  }
503
- function buildDebateSelectAutonomousPreamble(inputBlock, debateRole, disagreementInline) {
504
- const role = debateRole ?? "for";
505
- return [
506
- "You are the " + role.toUpperCase() + " participant in an adversarial review of one component-selection disagreement. This is an automatic escalation stage; do not ask the developer for confirmation.",
507
- "",
508
- "Your role is to argue the " + role.toUpperCase() + " position on the exact disagreement below. Address the claim literally, cite the supplied component evidence, and do not substitute a nearby component or field. The opposing participant is evaluating the same evidence independently.",
509
- "",
510
- "Component and source data:" + inputBlock,
511
- "",
512
- "Disagreement under review (JSON):",
513
- "~~~json",
514
- disagreementInline ?? "{}",
515
- "~~~",
516
- "",
517
- "## Output protocol",
518
- "",
519
- "Emit exactly one JSON object on one line and do not write files:",
520
- "",
521
- "~~~json",
522
- '{"role":"' + role + '","disagreement_id":"<id>","argument":"<position and reasoning>","evidence":[{"source":"<path>","line":"<line-or-range>","quote":"<verbatim evidence>"}]}',
523
- "~~~",
524
- "",
525
- "The role and disagreement_id must match the values in this prompt. Use an empty evidence array only when the supplied context contains no citable source evidence."
526
- ].join("\n");
527
- }
528
520
  var init_build_skill_preambles = __esm({
529
521
  "packages/experience-design-system-generation/dist/src/services/prompts/helpers/build-skill-preambles.js"() {
530
522
  "use strict";
@@ -533,7 +525,7 @@ var init_build_skill_preambles = __esm({
533
525
 
534
526
  // packages/experience-design-system-generation/dist/src/services/prompts/helpers/build-preamble.js
535
527
  function buildPreamble(options) {
536
- const { skill, rawComponentsInline, propBucketsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline, componentAllowlistInline, debateRole, disagreementInline } = options;
528
+ const { skill, rawComponentsInline, propBucketsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline, componentAllowlistInline } = options;
537
529
  const sections = [];
538
530
  if (existingComponentsInline) {
539
531
  sections.push(`Existing components in the target Contentful space (JSON) \u2014 use for alignment, not as a hard filter:
@@ -605,8 +597,6 @@ ${sections.join("\n\n")}` : "";
605
597
  return buildComponentsAutonomousPreamble(inputBlock);
606
598
  if (skill === "select")
607
599
  return buildSelectAutonomousPreamble(inputBlock);
608
- if (skill === "debate-select")
609
- return buildDebateSelectAutonomousPreamble(inputBlock, debateRole, disagreementInline);
610
600
  if (skill === "map-tokens")
611
601
  return buildMapTokensAutonomousPreamble(inputBlock);
612
602
  return buildTokensAutonomousPreamble(inputBlock);
@@ -640,6 +630,17 @@ var init_build_prompt = __esm({
640
630
  });
641
631
 
642
632
  // packages/experience-design-system-generation/dist/src/orchestrator/execute-generation-orchestrator.js
633
+ async function executeGenerationOrchestrator(request2) {
634
+ const prompt = await buildPrompt(request2.promptOptions);
635
+ return request2.invoker.invoke({
636
+ agent: request2.agent,
637
+ model: request2.model,
638
+ bedrock: request2.bedrock,
639
+ prompt,
640
+ timeoutMs: request2.timeoutMs,
641
+ onOutput: request2.onOutput
642
+ });
643
+ }
643
644
  var init_execute_generation_orchestrator = __esm({
644
645
  "packages/experience-design-system-generation/dist/src/orchestrator/execute-generation-orchestrator.js"() {
645
646
  "use strict";
@@ -740,6 +741,22 @@ var init_describe_agent_failure = __esm({
740
741
  });
741
742
 
742
743
  // packages/experience-design-system-generation/dist/src/services/agent/helpers/extract-sentinel-output.js
744
+ function extractSentinelOutput(stdout) {
745
+ const START = "<<<EDS_OUTPUT_START>>>";
746
+ const END = "<<<EDS_OUTPUT_END>>>";
747
+ const startIdx = stdout.indexOf(START);
748
+ const endIdx = stdout.indexOf(END);
749
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx)
750
+ return null;
751
+ const secondStart = stdout.indexOf(START, startIdx + START.length);
752
+ if (secondStart !== -1 && secondStart < endIdx)
753
+ return "multiple";
754
+ const afterStart = stdout.indexOf(END, startIdx);
755
+ const secondEnd = stdout.indexOf(END, afterStart + END.length);
756
+ if (secondEnd !== -1)
757
+ return "multiple";
758
+ return stdout.slice(startIdx + START.length, endIdx).trim();
759
+ }
743
760
  var init_extract_sentinel_output = __esm({
744
761
  "packages/experience-design-system-generation/dist/src/services/agent/helpers/extract-sentinel-output.js"() {
745
762
  "use strict";
@@ -764,7 +781,7 @@ async function runAgent(options) {
764
781
  promptLen: prompt.length,
765
782
  promptHead: prompt.slice(0, 500)
766
783
  });
767
- return new Promise((resolve20) => {
784
+ return new Promise((resolve22) => {
768
785
  const bedrockEnv = bedrock ? BEDROCK_ENV_BY_AGENT[agent] : void 0;
769
786
  const child = spawn(binary, args, {
770
787
  stdio: ["pipe", "pipe", "pipe"],
@@ -813,7 +830,7 @@ async function runAgent(options) {
813
830
  stderrLen: stderr.length,
814
831
  stderrTail: stderr.slice(-1e3)
815
832
  });
816
- resolve20(result);
833
+ resolve22(result);
817
834
  });
818
835
  });
819
836
  }
@@ -830,7 +847,7 @@ var init_run_agent = __esm({
830
847
  // packages/experience-design-system-generation/dist/src/services/agent/helpers/check-claude-auth.js
831
848
  import { spawn as spawn2 } from "node:child_process";
832
849
  function checkClaudeAuth(binary) {
833
- return new Promise((resolve20) => {
850
+ return new Promise((resolve22) => {
834
851
  const child = spawn2(binary, ["auth", "status", "--json"], {
835
852
  stdio: ["ignore", "pipe", "pipe"]
836
853
  });
@@ -840,7 +857,7 @@ function checkClaudeAuth(binary) {
840
857
  if (!done) {
841
858
  done = true;
842
859
  child.kill("SIGTERM");
843
- resolve20("unauthenticated");
860
+ resolve22("unauthenticated");
844
861
  }
845
862
  }, 5e3);
846
863
  child.stdout?.on("data", (chunk) => {
@@ -852,14 +869,14 @@ function checkClaudeAuth(binary) {
852
869
  done = true;
853
870
  clearTimeout(timer);
854
871
  if (code !== 0) {
855
- resolve20("unauthenticated");
872
+ resolve22("unauthenticated");
856
873
  return;
857
874
  }
858
875
  try {
859
876
  const status = JSON.parse(stdout);
860
- resolve20(status.loggedIn ? "ok" : "unauthenticated");
877
+ resolve22(status.loggedIn ? "ok" : "unauthenticated");
861
878
  } catch {
862
- resolve20("unauthenticated");
879
+ resolve22("unauthenticated");
863
880
  }
864
881
  });
865
882
  });
@@ -874,13 +891,13 @@ var init_check_claude_auth = __esm({
874
891
  import { spawn as spawn3 } from "node:child_process";
875
892
  async function checkAgentAuth(agent) {
876
893
  const binary = resolveAgentBinary(agent);
877
- const binaryExists2 = await new Promise((resolve20) => {
894
+ const binaryExists2 = await new Promise((resolve22) => {
878
895
  if (binary.startsWith("/")) {
879
- import("node:fs/promises").then((fs2) => fs2.access(binary).then(() => resolve20(true), () => resolve20(false)));
896
+ import("node:fs/promises").then((fs2) => fs2.access(binary).then(() => resolve22(true), () => resolve22(false)));
880
897
  return;
881
898
  }
882
899
  const child = spawn3(process.platform === "win32" ? "where" : "which", [binary], { stdio: "ignore" });
883
- child.on("close", (code) => resolve20(code === 0));
900
+ child.on("close", (code) => resolve22(code === 0));
884
901
  });
885
902
  if (!binaryExists2)
886
903
  return "not-found";
@@ -917,6 +934,20 @@ var init_local_cli_agent_invoker = __esm({
917
934
  });
918
935
 
919
936
  // packages/experience-design-system-generation/dist/src/controller/create-generation-endpoint.js
937
+ function createGenerationEndpoint(options = {}) {
938
+ const invoker2 = createLocalCliAgentInvoker({
939
+ onDebugEvent: options.onDebugEvent
940
+ });
941
+ return {
942
+ async run(request2) {
943
+ validateGenerationRequest(request2);
944
+ return executeGenerationOrchestrator({ ...request2, invoker: invoker2 });
945
+ },
946
+ checkAuth(agent) {
947
+ return invoker2.checkAuth(agent);
948
+ }
949
+ };
950
+ }
920
951
  var init_create_generation_endpoint = __esm({
921
952
  "packages/experience-design-system-generation/dist/src/controller/create-generation-endpoint.js"() {
922
953
  "use strict";
@@ -1301,6 +1332,32 @@ var init_progress = __esm({
1301
1332
  });
1302
1333
 
1303
1334
  // packages/experience-design-system-generation/dist/src/index.js
1335
+ var src_exports = {};
1336
+ __export(src_exports, {
1337
+ AGENT_BINARIES: () => AGENT_BINARIES,
1338
+ AGENT_NAMES: () => AGENT_NAMES,
1339
+ DEFAULT_AGENT_NAME: () => DEFAULT_AGENT_NAME,
1340
+ agentSupportsBedrock: () => agentSupportsBedrock,
1341
+ buildArgs: () => buildAgentArgs,
1342
+ buildPrompt: () => buildPrompt,
1343
+ checkAgentAuth: () => checkAgentAuth,
1344
+ createGenerationEndpoint: () => createGenerationEndpoint,
1345
+ createLocalCliAgentInvoker: () => createLocalCliAgentInvoker,
1346
+ describeAgentFailure: () => describeAgentFailure,
1347
+ executeGenerationOrchestrator: () => executeGenerationOrchestrator,
1348
+ extractSentinelOutput: () => extractSentinelOutput,
1349
+ formatCustomPromptBanner: () => formatCustomPromptBanner,
1350
+ formatGenerateProgressLine: () => formatGenerateProgressLine,
1351
+ isAgentName: () => isAgentName,
1352
+ parseMapTokenPropToolCallLines: () => parseMapTokenPropToolCallLines,
1353
+ parseSelectToolCallLines: () => parseSelectToolCallLines,
1354
+ parseTokenToolCallLines: () => parseTokenToolCallLines,
1355
+ parseToolCallLines: () => parsePropToolCallLines,
1356
+ resolveAgentModel: () => resolveAgentModel,
1357
+ resolveBinary: () => resolveAgentBinary,
1358
+ resolveSkillPath: () => resolveSkillPath,
1359
+ runAgent: () => runAgent
1360
+ });
1304
1361
  var init_src = __esm({
1305
1362
  "packages/experience-design-system-generation/dist/src/index.js"() {
1306
1363
  "use strict";
@@ -10308,6 +10365,41 @@ var init_read3 = __esm({
10308
10365
  }
10309
10366
  });
10310
10367
 
10368
+ // packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts
10369
+ import { createHash as createHash2 } from "node:crypto";
10370
+ function hashComponentShape(component) {
10371
+ const payload = {
10372
+ framework: component.framework,
10373
+ name: component.name,
10374
+ source: component.source,
10375
+ props: component.props.map((p) => ({
10376
+ name: p.name,
10377
+ type: p.type
10378
+ })),
10379
+ slots: component.slots.map((s) => ({
10380
+ name: s.name,
10381
+ isDefault: s.isDefault
10382
+ }))
10383
+ };
10384
+ return createHash2("sha256").update(JSON.stringify(payload)).digest("hex");
10385
+ }
10386
+ var init_hash_component_shape = __esm({
10387
+ "packages/experience-design-system-cli-legacy/src/session/core/components/hash-component-shape.ts"() {
10388
+ "use strict";
10389
+ }
10390
+ });
10391
+
10392
+ // packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts
10393
+ import { createHash as createHash3 } from "node:crypto";
10394
+ function hashTokenContent(rawTokenContent) {
10395
+ return createHash3("sha256").update(rawTokenContent.trim()).digest("hex");
10396
+ }
10397
+ var init_hash_token_content = __esm({
10398
+ "packages/experience-design-system-cli-legacy/src/session/core/tokens/hash-token-content.ts"() {
10399
+ "use strict";
10400
+ }
10401
+ });
10402
+
10311
10403
  // packages/experience-design-system-cli-legacy/src/session/repositories/tokens/write.ts
10312
10404
  function deleteRawTokensForSession(db, sessionId2) {
10313
10405
  db.prepare("DELETE FROM raw_token_groups WHERE session_id = ?").run(sessionId2);
@@ -10471,17 +10563,47 @@ var init_replace_raw_prop_token_paths = __esm({
10471
10563
  }
10472
10564
  });
10473
10565
 
10566
+ // packages/experience-design-system-cli-legacy/src/session/cache-keys.ts
10567
+ var cache_keys_exports = {};
10568
+ __export(cache_keys_exports, {
10569
+ hashContent: () => hashContent,
10570
+ hashFile: () => hashFile,
10571
+ hashPromptForSkill: () => hashPromptForSkill
10572
+ });
10573
+ import { createHash as createHash4 } from "node:crypto";
10574
+ import { readFile as readFile7 } from "node:fs/promises";
10575
+ import { resolve as resolve8 } from "node:path";
10576
+ function hashContent(content) {
10577
+ return createHash4("sha256").update(content).digest("hex");
10578
+ }
10579
+ async function hashFile(filePath) {
10580
+ const content = await readFile7(filePath, "utf8");
10581
+ return hashContent(content);
10582
+ }
10583
+ async function hashPromptForSkill(skill, agent, model, skillPathOverride, extraInputs = [], skillContentOverride) {
10584
+ const promptHash = skillContentOverride ? hashContent(skillContentOverride) : skillPathOverride ? await hashFile(resolve8(skillPathOverride)) : await hashFile(resolveSkillPath(skill));
10585
+ const extraSegment = extraInputs.length > 0 ? `|${extraInputs.join("|")}` : "";
10586
+ return hashContent(`${promptHash}|${agent}|${model ?? ""}${extraSegment}`);
10587
+ }
10588
+ var init_cache_keys = __esm({
10589
+ "packages/experience-design-system-cli-legacy/src/session/cache-keys.ts"() {
10590
+ "use strict";
10591
+ init_src();
10592
+ }
10593
+ });
10594
+
10474
10595
  // packages/experience-design-system-cli-legacy/src/session/db.ts
10475
10596
  import { DatabaseSync } from "node:sqlite";
10476
10597
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
10477
- import { readFile as readFile7 } from "node:fs/promises";
10598
+ import { readFile as readFile8 } from "node:fs/promises";
10599
+ import { createHash as createHash5 } from "node:crypto";
10478
10600
  import { homedir as homedir3 } from "node:os";
10479
- import { dirname as dirname14, resolve as resolve8 } from "node:path";
10601
+ import { dirname as dirname14, resolve as resolve9 } from "node:path";
10480
10602
  function getPipelineDbPath() {
10481
10603
  if (process.env.EDS_PIPELINE_DB_PATH) {
10482
- return resolve8(process.env.EDS_PIPELINE_DB_PATH);
10604
+ return resolve9(process.env.EDS_PIPELINE_DB_PATH);
10483
10605
  }
10484
- return resolve8(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10606
+ return resolve9(homedir3(), ".contentful", "experience-design-system-cli", "pipeline.db");
10485
10607
  }
10486
10608
  function openPipelineDb(dbPath) {
10487
10609
  const path = dbPath ?? getPipelineDbPath();
@@ -10615,8 +10737,134 @@ function applyDbMigrations(db) {
10615
10737
  if (!rawSlotColsForRationale.some((c2) => c2.name === "rationale")) {
10616
10738
  db.exec("ALTER TABLE raw_slots ADD COLUMN rationale TEXT");
10617
10739
  }
10618
- for (const table of REMOVED_CACHE_TABLES) {
10619
- db.exec(`DROP TABLE IF EXISTS ${table}`);
10740
+ const hasExtractCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='extract_cache'`).all();
10741
+ if (hasExtractCache.length === 0) {
10742
+ db.exec(`
10743
+ CREATE TABLE extract_cache (
10744
+ file_path TEXT NOT NULL,
10745
+ file_hash TEXT NOT NULL,
10746
+ cli_version TEXT NOT NULL,
10747
+ created_at TEXT NOT NULL,
10748
+ updated_at TEXT NOT NULL,
10749
+ components_json TEXT NOT NULL,
10750
+ PRIMARY KEY (file_hash, cli_version)
10751
+ );
10752
+ CREATE INDEX idx_extract_cache_file ON extract_cache(file_path);
10753
+ `);
10754
+ }
10755
+ const hasSelectCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='select_cache'`).all();
10756
+ if (hasSelectCache.length === 0) {
10757
+ db.exec(`
10758
+ CREATE TABLE select_cache (
10759
+ component_hash TEXT NOT NULL,
10760
+ prompt_hash TEXT NOT NULL,
10761
+ cli_version TEXT NOT NULL,
10762
+ decision TEXT NOT NULL CHECK (decision IN ('accepted','rejected')),
10763
+ reason TEXT,
10764
+ created_at TEXT NOT NULL,
10765
+ updated_at TEXT NOT NULL,
10766
+ PRIMARY KEY (component_hash, prompt_hash, cli_version)
10767
+ );
10768
+ `);
10769
+ }
10770
+ const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='generation_cache'`).all();
10771
+ if (tables.length === 0) {
10772
+ db.exec(`
10773
+ CREATE TABLE generation_cache (
10774
+ input_hash TEXT NOT NULL,
10775
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10776
+ entity_id TEXT NOT NULL,
10777
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10778
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10779
+ created_at TEXT NOT NULL,
10780
+ updated_at TEXT NOT NULL,
10781
+ PRIMARY KEY (input_hash, entity_type, entity_id)
10782
+ );
10783
+ CREATE INDEX idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10784
+ CREATE INDEX idx_generation_cache_session ON generation_cache(source_session_id);
10785
+ `);
10786
+ }
10787
+ const genCacheCols = db.prepare(`PRAGMA table_info(generation_cache)`).all();
10788
+ const hasPromptHash = genCacheCols.some((c2) => c2.name === "prompt_hash");
10789
+ const oldPkCols = genCacheCols.filter((c2) => c2.pk > 0).map((c2) => c2.name);
10790
+ const promptHashInPk = oldPkCols.includes("prompt_hash");
10791
+ if (!hasPromptHash) {
10792
+ db.exec(`ALTER TABLE generation_cache ADD COLUMN prompt_hash TEXT NOT NULL DEFAULT ''`);
10793
+ }
10794
+ if (!promptHashInPk) {
10795
+ db.exec("BEGIN");
10796
+ try {
10797
+ db.exec(`
10798
+ CREATE TABLE generation_cache__new (
10799
+ input_hash TEXT NOT NULL,
10800
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10801
+ entity_id TEXT NOT NULL,
10802
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10803
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10804
+ created_at TEXT NOT NULL,
10805
+ updated_at TEXT NOT NULL,
10806
+ prompt_hash TEXT NOT NULL DEFAULT '',
10807
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10808
+ );
10809
+ INSERT INTO generation_cache__new
10810
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10811
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10812
+ FROM generation_cache;
10813
+ DROP TABLE generation_cache;
10814
+ ALTER TABLE generation_cache__new RENAME TO generation_cache;
10815
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10816
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10817
+ `);
10818
+ db.exec("COMMIT");
10819
+ } catch (e) {
10820
+ db.exec("ROLLBACK");
10821
+ throw e;
10822
+ }
10823
+ }
10824
+ const genCacheTableSql = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'generation_cache'`).get();
10825
+ const hasTokenMappingEntityType = genCacheTableSql?.sql.includes("'token_mapping'") ?? true;
10826
+ if (!hasTokenMappingEntityType) {
10827
+ db.exec("BEGIN");
10828
+ try {
10829
+ db.exec(`
10830
+ CREATE TABLE generation_cache__new2 (
10831
+ input_hash TEXT NOT NULL,
10832
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
10833
+ entity_id TEXT NOT NULL,
10834
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
10835
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
10836
+ created_at TEXT NOT NULL,
10837
+ updated_at TEXT NOT NULL,
10838
+ prompt_hash TEXT NOT NULL DEFAULT '',
10839
+ PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
10840
+ );
10841
+ INSERT INTO generation_cache__new2
10842
+ (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
10843
+ SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
10844
+ FROM generation_cache;
10845
+ DROP TABLE generation_cache;
10846
+ ALTER TABLE generation_cache__new2 RENAME TO generation_cache;
10847
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
10848
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
10849
+ `);
10850
+ db.exec("COMMIT");
10851
+ } catch (e) {
10852
+ db.exec("ROLLBACK");
10853
+ throw e;
10854
+ }
10855
+ }
10856
+ const hasCompositionCache = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='composition_cache'`).all();
10857
+ if (hasCompositionCache.length === 0) {
10858
+ db.exec(`
10859
+ CREATE TABLE composition_cache (
10860
+ input_hash TEXT NOT NULL,
10861
+ cli_version TEXT NOT NULL,
10862
+ agent_output TEXT NOT NULL,
10863
+ created_at TEXT NOT NULL,
10864
+ updated_at TEXT NOT NULL,
10865
+ PRIMARY KEY (input_hash, cli_version)
10866
+ );
10867
+ `);
10620
10868
  }
10621
10869
  }
10622
10870
  function loadComponentReviewMetadata(db, sessionId2, componentName) {
@@ -11156,16 +11404,16 @@ function extractRelativeImportPaths(sourceText) {
11156
11404
  return [...specifiers];
11157
11405
  }
11158
11406
  async function resolveRelativeImport(specifier, fromDir) {
11159
- const basePath = resolve8(fromDir, specifier);
11407
+ const basePath = resolve9(fromDir, specifier);
11160
11408
  const candidates = [
11161
11409
  basePath,
11162
11410
  ...rewrittenSourcePaths(basePath),
11163
11411
  ...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
11164
- ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve8(basePath, `index${ext}`))
11412
+ ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve9(basePath, `index${ext}`))
11165
11413
  ];
11166
11414
  for (const candidate of candidates) {
11167
11415
  try {
11168
- await readFile7(candidate, "utf8");
11416
+ await readFile8(candidate, "utf8");
11169
11417
  return candidate;
11170
11418
  } catch {
11171
11419
  }
@@ -11181,7 +11429,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
11181
11429
  return resolvedPaths;
11182
11430
  }
11183
11431
  async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateTypeNames = []) {
11184
- const visited = /* @__PURE__ */ new Set([resolve8(sourcePath)]);
11432
+ const visited = /* @__PURE__ */ new Set([resolve9(sourcePath)]);
11185
11433
  const discovered = [];
11186
11434
  let frontier = [{ text: sourceText, dir: dirname14(sourcePath) }];
11187
11435
  for (let hop = 0; hop < MAX_SIBLING_DEPTH && discovered.length < MAX_SIBLING_CANDIDATES_EXPLORED; hop++) {
@@ -11195,7 +11443,7 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
11195
11443
  if (discovered.length >= MAX_SIBLING_CANDIDATES_EXPLORED) break;
11196
11444
  let content;
11197
11445
  try {
11198
- content = await readFile7(resolvedPath, "utf8");
11446
+ content = await readFile8(resolvedPath, "utf8");
11199
11447
  } catch {
11200
11448
  continue;
11201
11449
  }
@@ -11232,7 +11480,7 @@ async function loadComponentSourceRef(name, sourcePath, propNames = [], propType
11232
11480
  let truncatedSiblingCount = 0;
11233
11481
  const usesNotShown = /* @__PURE__ */ new Set();
11234
11482
  try {
11235
- const rawText = await readFile7(sourcePath, "utf8");
11483
+ const rawText = await readFile8(sourcePath, "utf8");
11236
11484
  let siblingUsesNotShown;
11237
11485
  let declaredTypeNames;
11238
11486
  ({
@@ -11668,6 +11916,108 @@ function backfillUnclassifiedProps(db, sessionId2) {
11668
11916
  ).run(sessionId2, sessionId2);
11669
11917
  return Number(withCategory.changes) + Number(withoutCategory.changes);
11670
11918
  }
11919
+ function computeMapTokensInputHash(db, sessionId2, componentSourceRefs = []) {
11920
+ const props = db.prepare(
11921
+ `SELECT rc.name AS component_name, rp.name AS prop_name, rp.cdf_token_kind, rp.default_value
11922
+ FROM raw_props rp
11923
+ JOIN raw_components rc ON rc.session_id = rp.session_id AND rc.component_id = rp.component_id
11924
+ WHERE rp.session_id = ? AND rp.cdf_type = 'token' AND rp.cdf_category = 'design'
11925
+ ORDER BY rc.name, rp.name`
11926
+ ).all(sessionId2);
11927
+ const tokens = db.prepare(`SELECT path, type FROM raw_tokens WHERE session_id = ? ORDER BY path`).all(sessionId2);
11928
+ const defaultMappings = db.prepare("SELECT raw_name, path, source FROM raw_token_name_paths WHERE session_id = ? ORDER BY raw_name").all(sessionId2);
11929
+ const payload = {
11930
+ props: props.map((p) => ({
11931
+ component: p.component_name,
11932
+ prop: p.prop_name,
11933
+ tokenKind: p.cdf_token_kind,
11934
+ rawDefault: p.default_value
11935
+ })),
11936
+ tokens,
11937
+ defaultMappings,
11938
+ // The agent's evidence, not just its inputs: a comment or allowlist edit
11939
+ // in a source file (or a sibling it reads) changes the correct answer
11940
+ // without touching props or tokens, so it must invalidate the cache too.
11941
+ sourceRefs: [...componentSourceRefs].sort((a, b) => a.component.localeCompare(b.component)).map((ref) => ({
11942
+ component: ref.component,
11943
+ content: ref.content,
11944
+ siblingFiles: (ref.siblingFiles ?? []).map((s) => ({ path: s.path, content: s.content })).sort((a, b) => a.path.localeCompare(b.path)),
11945
+ usesNotShown: [...ref.usesNotShown ?? []].sort()
11946
+ }))
11947
+ };
11948
+ return createHash5("sha256").update(JSON.stringify(payload)).digest("hex");
11949
+ }
11950
+ function lookupCache(db, inputHash, entityType, entityId, promptHash = "") {
11951
+ const row = db.prepare(
11952
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11953
+ FROM generation_cache
11954
+ WHERE input_hash = ? AND entity_type = ? AND entity_id = ? AND prompt_hash = ?`
11955
+ ).get(inputHash, entityType, entityId, promptHash);
11956
+ if (!row) return null;
11957
+ return {
11958
+ inputHash: row.input_hash,
11959
+ entityType: row.entity_type,
11960
+ entityId: row.entity_id,
11961
+ sourceSessionId: row.source_session_id,
11962
+ humanEdited: row.human_edited === 1,
11963
+ promptHash: row.prompt_hash,
11964
+ createdAt: row.created_at,
11965
+ updatedAt: row.updated_at
11966
+ };
11967
+ }
11968
+ function lookupCacheByEntity(db, entityType, entityId) {
11969
+ const row = db.prepare(
11970
+ `SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
11971
+ FROM generation_cache
11972
+ WHERE entity_type = ? AND entity_id = ?
11973
+ ORDER BY updated_at DESC LIMIT 1`
11974
+ ).get(entityType, entityId);
11975
+ if (!row) return null;
11976
+ return {
11977
+ inputHash: row.input_hash,
11978
+ entityType: row.entity_type,
11979
+ entityId: row.entity_id,
11980
+ sourceSessionId: row.source_session_id,
11981
+ humanEdited: row.human_edited === 1,
11982
+ promptHash: row.prompt_hash,
11983
+ createdAt: row.created_at,
11984
+ updatedAt: row.updated_at
11985
+ };
11986
+ }
11987
+ function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash = "") {
11988
+ storeCaches(db, [{ inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash }]);
11989
+ }
11990
+ function storeCaches(db, entries) {
11991
+ if (entries.length === 0) return;
11992
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11993
+ const insert = db.prepare(
11994
+ `INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
11995
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
11996
+ ON CONFLICT(input_hash, prompt_hash, entity_type, entity_id) DO UPDATE SET
11997
+ source_session_id = excluded.source_session_id,
11998
+ human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
11999
+ updated_at = excluded.updated_at`
12000
+ );
12001
+ db.exec("BEGIN");
12002
+ try {
12003
+ for (const entry of entries) {
12004
+ insert.run(
12005
+ entry.inputHash,
12006
+ entry.entityType,
12007
+ entry.entityId,
12008
+ entry.sourceSessionId,
12009
+ entry.humanEdited ? 1 : 0,
12010
+ now,
12011
+ now,
12012
+ entry.promptHash ?? ""
12013
+ );
12014
+ }
12015
+ db.exec("COMMIT");
12016
+ } catch (e) {
12017
+ db.exec("ROLLBACK");
12018
+ throw e;
12019
+ }
12020
+ }
11671
12021
  function storeScannedFiles(db, sessionId2, filePaths) {
11672
12022
  db.exec("BEGIN");
11673
12023
  try {
@@ -11750,7 +12100,105 @@ function filterUnknownSlotAllowedComponents(db, sessionId2, allowedComponentName
11750
12100
  }
11751
12101
  return unknown;
11752
12102
  }
11753
- function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
12103
+ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId, manageTransaction = true, options = {}) {
12104
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12105
+ if (manageTransaction) db.exec("BEGIN");
12106
+ try {
12107
+ const srcComp = db.prepare(
12108
+ `SELECT description, status, component_description_rationale, props_rationale, slots_rationale
12109
+ FROM raw_components WHERE session_id = ? AND component_id = ?`
12110
+ ).get(sourceSessionId, componentId);
12111
+ if (srcComp) {
12112
+ db.prepare(
12113
+ `UPDATE raw_components
12114
+ SET description = ?, status = ?, component_description_rationale = ?, props_rationale = ?, slots_rationale = ?, extracted_at = ?
12115
+ WHERE session_id = ? AND component_id = ?`
12116
+ ).run(
12117
+ srcComp.description,
12118
+ srcComp.status,
12119
+ srcComp.component_description_rationale,
12120
+ srcComp.props_rationale,
12121
+ srcComp.slots_rationale,
12122
+ now,
12123
+ targetSessionId,
12124
+ componentId
12125
+ );
12126
+ }
12127
+ const srcProps = db.prepare(
12128
+ `SELECT name, cdf_type, cdf_category, cdf_token_kind, required, description, default_value, rationale
12129
+ FROM raw_props WHERE session_id = ? AND component_id = ?`
12130
+ ).all(sourceSessionId, componentId);
12131
+ for (const p of srcProps) {
12132
+ db.prepare(
12133
+ `UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, default_value = ?, rationale = ?
12134
+ WHERE session_id = ? AND component_id = ? AND name = ?`
12135
+ ).run(
12136
+ p.cdf_type,
12137
+ p.cdf_category,
12138
+ p.cdf_token_kind,
12139
+ p.required,
12140
+ p.description,
12141
+ p.default_value,
12142
+ p.rationale,
12143
+ targetSessionId,
12144
+ componentId,
12145
+ p.name
12146
+ );
12147
+ }
12148
+ db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`).run(
12149
+ targetSessionId,
12150
+ componentId
12151
+ );
12152
+ const srcAV = db.prepare(
12153
+ `SELECT prop_name, position, value FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`
12154
+ ).all(sourceSessionId, componentId);
12155
+ const insertAV = db.prepare(
12156
+ `INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value) VALUES (?, ?, ?, ?, ?)`
12157
+ );
12158
+ for (const av of srcAV) {
12159
+ insertAV.run(targetSessionId, componentId, av.prop_name, av.position, av.value);
12160
+ }
12161
+ const srcSlots = db.prepare(`SELECT name, required, description, rationale FROM raw_slots WHERE session_id = ? AND component_id = ?`).all(sourceSessionId, componentId);
12162
+ for (const s of srcSlots) {
12163
+ db.prepare(
12164
+ `UPDATE raw_slots SET required = ?, description = ?, rationale = ? WHERE session_id = ? AND component_id = ? AND name = ?`
12165
+ ).run(s.required, s.description, s.rationale, targetSessionId, componentId, s.name);
12166
+ }
12167
+ db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`).run(
12168
+ targetSessionId,
12169
+ componentId
12170
+ );
12171
+ const srcSAC = db.prepare(
12172
+ `SELECT slot_name, position, allowed_component FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`
12173
+ ).all(sourceSessionId, componentId);
12174
+ const insertSAC = db.prepare(
12175
+ `INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`
12176
+ );
12177
+ const allowedSAC = options.allowedComponentNames ? srcSAC.filter((sac) => options.allowedComponentNames.has(sac.allowed_component)) : srcSAC;
12178
+ for (const sac of allowedSAC) {
12179
+ insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
12180
+ }
12181
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12182
+ if (manageTransaction) db.exec("COMMIT");
12183
+ } catch (e) {
12184
+ if (manageTransaction) db.exec("ROLLBACK");
12185
+ throw e;
12186
+ }
12187
+ }
12188
+ function copyComponentsFromCache(db, entries, options = {}) {
12189
+ if (entries.length === 0) return;
12190
+ db.exec("BEGIN");
12191
+ try {
12192
+ for (const entry of entries) {
12193
+ copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false, options);
12194
+ }
12195
+ db.exec("COMMIT");
12196
+ } catch (e) {
12197
+ db.exec("ROLLBACK");
12198
+ throw e;
12199
+ }
12200
+ }
12201
+ function copyTokensFromCache(db, sourceSessionId, targetSessionId) {
11754
12202
  const now = (/* @__PURE__ */ new Date()).toISOString();
11755
12203
  db.exec("BEGIN");
11756
12204
  try {
@@ -11771,7 +12219,153 @@ function copyTokensBetweenSessions(db, sourceSessionId, targetSessionId) {
11771
12219
  throw e;
11772
12220
  }
11773
12221
  }
11774
- var SCHEMA, REMOVED_CACHE_TABLES, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS, MAX_TYPE_DECLARING_SIBLING_CHARS, MAX_ENLARGED_SIBLINGS, MAX_SIBLING_DEPTH, MAX_SIBLING_CANDIDATES_EXPLORED, RELATIVE_IMPORT_PATTERN, SIBLING_FILE_EXTENSIONS, TS_ESM_EXTENSION_REWRITES, IDENTIFIER_PATTERN;
12222
+ function copyMapTokensFromCache(db, sourceSessionId, targetSessionId) {
12223
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12224
+ let copiedCount = 0;
12225
+ db.exec("BEGIN");
12226
+ try {
12227
+ const sourceRows = db.prepare(
12228
+ `SELECT rc.name AS component_name, rptp.prop_name, rptp.source, rptp.position, rptp.path
12229
+ FROM raw_prop_token_paths rptp
12230
+ JOIN raw_components rc ON rc.session_id = rptp.session_id AND rc.component_id = rptp.component_id
12231
+ WHERE rptp.session_id = ?
12232
+ ORDER BY rc.name, rptp.prop_name, rptp.position`
12233
+ ).all(sourceSessionId);
12234
+ const targetComponents = db.prepare(`SELECT component_id, name FROM raw_components WHERE session_id = ?`).all(targetSessionId);
12235
+ const targetIdByName = new Map(targetComponents.map((c2) => [c2.name, c2.component_id]));
12236
+ const targetProps = new Set(
12237
+ db.prepare(`SELECT component_id, name FROM raw_props WHERE session_id = ?`).all(targetSessionId).map((p) => `${p.component_id}::${p.name}`)
12238
+ );
12239
+ const insertPath = db.prepare(
12240
+ `INSERT INTO raw_prop_token_paths (session_id, component_id, prop_name, source, position, path)
12241
+ VALUES (?, ?, ?, ?, ?, ?)`
12242
+ );
12243
+ const clearedProps = /* @__PURE__ */ new Set();
12244
+ const copiedProps = /* @__PURE__ */ new Set();
12245
+ for (const row of sourceRows) {
12246
+ const targetComponentId = targetIdByName.get(row.component_name);
12247
+ if (!targetComponentId) continue;
12248
+ if (!targetProps.has(`${targetComponentId}::${row.prop_name}`)) continue;
12249
+ const propKey = `${targetComponentId}::${row.prop_name}`;
12250
+ if (!clearedProps.has(propKey)) {
12251
+ clearedProps.add(propKey);
12252
+ db.prepare(`DELETE FROM raw_prop_token_paths WHERE session_id = ? AND component_id = ? AND prop_name = ?`).run(
12253
+ targetSessionId,
12254
+ targetComponentId,
12255
+ row.prop_name
12256
+ );
12257
+ }
12258
+ insertPath.run(targetSessionId, targetComponentId, row.prop_name, row.source, row.position, row.path);
12259
+ copiedProps.add(propKey);
12260
+ }
12261
+ copiedCount = copiedProps.size;
12262
+ db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
12263
+ db.exec("COMMIT");
12264
+ } catch (e) {
12265
+ db.exec("ROLLBACK");
12266
+ throw e;
12267
+ }
12268
+ return copiedCount;
12269
+ }
12270
+ async function getCliCacheVersion() {
12271
+ if (_cliCacheVersionCache) return _cliCacheVersionCache;
12272
+ try {
12273
+ const { hashContent: hashContent2 } = await Promise.resolve().then(() => (init_cache_keys(), cache_keys_exports));
12274
+ const { resolveSkillPath: resolveSkillPath2 } = await Promise.resolve().then(() => (init_src(), src_exports));
12275
+ const skills = ["components", "tokens", "select"];
12276
+ const parts = [];
12277
+ for (const s of skills) {
12278
+ try {
12279
+ const p = resolveSkillPath2(s);
12280
+ parts.push(readFileSync5(p, "utf8"));
12281
+ } catch {
12282
+ parts.push(`<missing:${s}>`);
12283
+ }
12284
+ }
12285
+ _cliCacheVersionCache = hashContent2(parts.join("\n---\n"));
12286
+ return _cliCacheVersionCache;
12287
+ } catch {
12288
+ _cliCacheVersionCache = "fallback";
12289
+ return _cliCacheVersionCache;
12290
+ }
12291
+ }
12292
+ function storeExtractCache(db, filePath, fileHash, cliVersion2, components) {
12293
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12294
+ const componentsJson = JSON.stringify(components);
12295
+ db.prepare(
12296
+ `INSERT INTO extract_cache (file_path, file_hash, cli_version, created_at, updated_at, components_json)
12297
+ VALUES (?, ?, ?, ?, ?, ?)
12298
+ ON CONFLICT(file_hash, cli_version) DO UPDATE SET
12299
+ file_path = excluded.file_path,
12300
+ updated_at = excluded.updated_at,
12301
+ components_json = excluded.components_json`
12302
+ ).run(filePath, fileHash, cliVersion2, now, now, componentsJson);
12303
+ }
12304
+ function lookupExtractCache(db, fileHash, cliVersion2) {
12305
+ const row = db.prepare(
12306
+ `SELECT file_path, file_hash, cli_version, created_at, updated_at, components_json
12307
+ FROM extract_cache
12308
+ WHERE file_hash = ? AND cli_version = ?`
12309
+ ).get(fileHash, cliVersion2);
12310
+ if (!row) return null;
12311
+ let components = [];
12312
+ try {
12313
+ components = JSON.parse(row.components_json);
12314
+ } catch {
12315
+ return null;
12316
+ }
12317
+ return {
12318
+ filePath: row.file_path,
12319
+ fileHash: row.file_hash,
12320
+ cliVersion: row.cli_version,
12321
+ createdAt: row.created_at,
12322
+ updatedAt: row.updated_at,
12323
+ components
12324
+ };
12325
+ }
12326
+ function storeCompositionCache(db, inputHash, cliVersion2, agentOutput) {
12327
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12328
+ db.prepare(
12329
+ `INSERT INTO composition_cache (input_hash, cli_version, agent_output, created_at, updated_at)
12330
+ VALUES (?, ?, ?, ?, ?)
12331
+ ON CONFLICT(input_hash, cli_version) DO UPDATE SET
12332
+ agent_output = excluded.agent_output,
12333
+ updated_at = excluded.updated_at`
12334
+ ).run(inputHash, cliVersion2, agentOutput, now, now);
12335
+ }
12336
+ function lookupCompositionCache(db, inputHash, cliVersion2) {
12337
+ const row = db.prepare(`SELECT agent_output FROM composition_cache WHERE input_hash = ? AND cli_version = ?`).get(inputHash, cliVersion2);
12338
+ return row ? row.agent_output : null;
12339
+ }
12340
+ function storeSelectCache(db, componentHash, promptHash, cliVersion2, decision, reason) {
12341
+ const now = (/* @__PURE__ */ new Date()).toISOString();
12342
+ db.prepare(
12343
+ `INSERT INTO select_cache (component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at)
12344
+ VALUES (?, ?, ?, ?, ?, ?, ?)
12345
+ ON CONFLICT(component_hash, prompt_hash, cli_version) DO UPDATE SET
12346
+ decision = excluded.decision,
12347
+ reason = excluded.reason,
12348
+ updated_at = excluded.updated_at`
12349
+ ).run(componentHash, promptHash, cliVersion2, decision, reason, now, now);
12350
+ }
12351
+ function lookupSelectCache(db, componentHash, promptHash, cliVersion2) {
12352
+ const row = db.prepare(
12353
+ `SELECT component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at
12354
+ FROM select_cache
12355
+ WHERE component_hash = ? AND prompt_hash = ? AND cli_version = ?`
12356
+ ).get(componentHash, promptHash, cliVersion2);
12357
+ if (!row) return null;
12358
+ return {
12359
+ componentHash: row.component_hash,
12360
+ promptHash: row.prompt_hash,
12361
+ cliVersion: row.cli_version,
12362
+ decision: row.decision,
12363
+ reason: row.reason,
12364
+ createdAt: row.created_at,
12365
+ updatedAt: row.updated_at
12366
+ };
12367
+ }
12368
+ var SCHEMA, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS, MAX_TYPE_DECLARING_SIBLING_CHARS, MAX_ENLARGED_SIBLINGS, MAX_SIBLING_DEPTH, MAX_SIBLING_CANDIDATES_EXPLORED, RELATIVE_IMPORT_PATTERN, SIBLING_FILE_EXTENSIONS, TS_ESM_EXTENSION_REWRITES, IDENTIFIER_PATTERN, _cliCacheVersionCache;
11775
12369
  var init_db = __esm({
11776
12370
  "packages/experience-design-system-cli-legacy/src/session/db.ts"() {
11777
12371
  "use strict";
@@ -11785,6 +12379,8 @@ var init_db = __esm({
11785
12379
  init_write2();
11786
12380
  init_session_resolver();
11787
12381
  init_read3();
12382
+ init_hash_component_shape();
12383
+ init_hash_token_content();
11788
12384
  init_read3();
11789
12385
  init_store_dtcg_tokens();
11790
12386
  init_apply_tool_calls();
@@ -11908,6 +12504,17 @@ CREATE TABLE IF NOT EXISTS raw_tokens (
11908
12504
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
11909
12505
  );
11910
12506
 
12507
+ CREATE TABLE IF NOT EXISTS generation_cache (
12508
+ input_hash TEXT NOT NULL,
12509
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set', 'token_mapping')),
12510
+ entity_id TEXT NOT NULL,
12511
+ source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
12512
+ human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
12513
+ created_at TEXT NOT NULL,
12514
+ updated_at TEXT NOT NULL,
12515
+ PRIMARY KEY (input_hash, entity_type, entity_id)
12516
+ );
12517
+
11911
12518
  CREATE TABLE IF NOT EXISTS scanned_files (
11912
12519
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
11913
12520
  path TEXT NOT NULL,
@@ -11953,9 +12560,10 @@ CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id,
11953
12560
  CREATE INDEX IF NOT EXISTS idx_raw_slots_session ON raw_slots(session_id, component_id);
11954
12561
  CREATE INDEX IF NOT EXISTS idx_raw_tokens_session ON raw_tokens(session_id);
11955
12562
  CREATE INDEX IF NOT EXISTS idx_raw_token_groups_session ON raw_token_groups(session_id);
12563
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
12564
+ CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
11956
12565
  CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session_id);
11957
12566
  `;
11958
- REMOVED_CACHE_TABLES = ["generation_cache", "extract_cache", "composition_cache", "select_cache"];
11959
12567
  MAX_COMPONENT_SOURCE_CHARS = 8e3;
11960
12568
  MAX_SIBLING_FILES = 5;
11961
12569
  MAX_SIBLING_SNIPPET_CHARS = 1200;
@@ -11972,15 +12580,16 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
11972
12580
  [".cjs", [".cts", ".cjs"]]
11973
12581
  ];
11974
12582
  IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
12583
+ _cliCacheVersionCache = null;
11975
12584
  }
11976
12585
  });
11977
12586
 
11978
12587
  // packages/experience-design-system-cli-legacy/src/helpers/read-existing-contentful-entities-from-session.ts
11979
- import { readFile as readFile8 } from "node:fs/promises";
12588
+ import { readFile as readFile9 } from "node:fs/promises";
11980
12589
  async function readExistingContentfulEntitiesFromSession(path) {
11981
12590
  if (!path) return void 0;
11982
12591
  try {
11983
- const raw = await readFile8(path, "utf8");
12592
+ const raw = await readFile9(path, "utf8");
11984
12593
  const parsed = JSON.parse(raw);
11985
12594
  if (!Array.isArray(parsed.components) || !Array.isArray(parsed.tokens)) return void 0;
11986
12595
  return parsed;
@@ -12104,21 +12713,21 @@ var init_resolve_session_id = __esm({
12104
12713
  });
12105
12714
 
12106
12715
  // packages/experience-design-system-cli-legacy/src/analyze/select/persistence.ts
12107
- import { access, appendFile, mkdir as mkdir2, readFile as readFile9, writeFile as writeFile2 } from "node:fs/promises";
12716
+ import { access, appendFile, mkdir as mkdir2, readFile as readFile10, writeFile as writeFile2 } from "node:fs/promises";
12108
12717
  import { homedir as homedir4 } from "node:os";
12109
- import { resolve as resolve9 } from "node:path";
12718
+ import { resolve as resolve10 } from "node:path";
12110
12719
  function getRefineArtifactsRoot() {
12111
12720
  if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
12112
- return resolve9(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12721
+ return resolve10(process.env.EDS_REVIEW_ARTIFACTS_DIR);
12113
12722
  }
12114
- return resolve9(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12723
+ return resolve10(homedir4(), ".contentful", "experience-design-system-cli", "reviews");
12115
12724
  }
12116
12725
  async function getRefineSessionPaths(sessionId2, artifactsRoot) {
12117
- const sessionDir = resolve9(artifactsRoot, sessionId2);
12726
+ const sessionDir = resolve10(artifactsRoot, sessionId2);
12118
12727
  return {
12119
12728
  sessionDir,
12120
- eventsPath: resolve9(sessionDir, "events.jsonl"),
12121
- statePath: resolve9(sessionDir, "current-review-state.json")
12729
+ eventsPath: resolve10(sessionDir, "events.jsonl"),
12730
+ statePath: resolve10(sessionDir, "current-review-state.json")
12122
12731
  };
12123
12732
  }
12124
12733
  async function saveReviewState(statePath, session) {
@@ -12129,7 +12738,7 @@ async function ensureRefineSession(sessionId2, artifactsRoot, initialSnapshot) {
12129
12738
  await mkdir2(paths.sessionDir, { recursive: true });
12130
12739
  try {
12131
12740
  await access(paths.statePath);
12132
- const savedState = await readFile9(paths.statePath, "utf8");
12741
+ const savedState = await readFile10(paths.statePath, "utf8");
12133
12742
  return JSON.parse(savedState);
12134
12743
  } catch {
12135
12744
  await writeFile2(paths.statePath, JSON.stringify(initialSnapshot, null, 2), "utf8");
@@ -12614,8 +13223,8 @@ var init_path_exists = __esm({
12614
13223
  });
12615
13224
 
12616
13225
  // packages/experience-design-system-cli-legacy/src/lib/prompt-overrides.ts
12617
- import { readFile as readFile10 } from "node:fs/promises";
12618
- import { resolve as resolve10 } from "node:path";
13226
+ import { readFile as readFile11 } from "node:fs/promises";
13227
+ import { resolve as resolve11 } from "node:path";
12619
13228
  function looksLikePath(value) {
12620
13229
  if (value.includes("/") || value.includes("\\")) return true;
12621
13230
  if (value.startsWith("~")) return true;
@@ -12647,9 +13256,9 @@ function parsePromptOverrides(inputs) {
12647
13256
  }
12648
13257
  async function resolvePromptOverride(override) {
12649
13258
  if (override.kind === "text") return override.value;
12650
- const abs = resolve10(override.value);
13259
+ const abs = resolve11(override.value);
12651
13260
  try {
12652
- return await readFile10(abs, "utf8");
13261
+ return await readFile11(abs, "utf8");
12653
13262
  } catch {
12654
13263
  throw new Error(`--prompt: could not read prompt file: ${abs}`);
12655
13264
  }
@@ -12664,21 +13273,21 @@ var init_prompt_overrides = __esm({
12664
13273
 
12665
13274
  // packages/experience-design-system-cli-legacy/src/generate/command.ts
12666
13275
  import { createElement } from "react";
12667
- import { readFile as readFile11, readdir, stat } from "node:fs/promises";
12668
- import { join as join14, resolve as resolve11 } from "node:path";
13276
+ import { readFile as readFile12, readdir, stat } from "node:fs/promises";
13277
+ import { join as join14, resolve as resolve12 } from "node:path";
12669
13278
  async function assertFileExists(flag, p) {
12670
13279
  if (!await pathExists(p)) die(`Error: file not found: ${p} (from ${flag})`);
12671
13280
  }
12672
13281
  async function readFileInline(path) {
12673
13282
  if (!path) return void 0;
12674
- const resolved = resolve11(path);
13283
+ const resolved = resolve12(path);
12675
13284
  let s;
12676
13285
  try {
12677
13286
  s = await stat(resolved);
12678
13287
  } catch {
12679
13288
  return void 0;
12680
13289
  }
12681
- if (!s.isDirectory()) return readFile11(resolved, "utf8");
13290
+ if (!s.isDirectory()) return readFile12(resolved, "utf8");
12682
13291
  const files = [];
12683
13292
  async function walk(dir) {
12684
13293
  let entries;
@@ -12704,7 +13313,7 @@ async function readFileInline(path) {
12704
13313
  }
12705
13314
  await walk(resolved);
12706
13315
  if (files.length === 0) return void 0;
12707
- const parts = await Promise.all(files.map((f) => readFile11(f, "utf8").catch(() => "")));
13316
+ const parts = await Promise.all(files.map((f) => readFile12(f, "utf8").catch(() => "")));
12708
13317
  return parts.filter(Boolean).join("\n\n");
12709
13318
  }
12710
13319
  function printFallbackInstructions(options) {
@@ -12726,6 +13335,23 @@ function printFallbackInstructions(options) {
12726
13335
  lines.push(` Re-run the generate command with the agent available, or use --dry-run to inspect the prompt.`);
12727
13336
  process.stderr.write(lines.join("\n") + "\n");
12728
13337
  }
13338
+ function createCachedComponentResult(componentName, warnings = []) {
13339
+ return {
13340
+ componentName,
13341
+ classified: 0,
13342
+ excluded: 0,
13343
+ slots: 0,
13344
+ warnings,
13345
+ failed: false,
13346
+ cached: true,
13347
+ renamedSlotsCount: 0
13348
+ };
13349
+ }
13350
+ function writeCachedComponentStatus(position, componentName, pinned) {
13351
+ const status = pinned ? c.cyan("pinned (human-edited)") : c.green("cached");
13352
+ process.stderr.write(` ${position} ${c.bold(componentName)} ${status}
13353
+ `);
13354
+ }
12729
13355
  async function showGenerateView(result) {
12730
13356
  if (process.stdout.isTTY) {
12731
13357
  const { waitUntilExit } = renderWithGoodbye(
@@ -12746,6 +13372,29 @@ session=${result.sessionId}
12746
13372
  );
12747
13373
  await exitWithAnalytics(0);
12748
13374
  }
13375
+ function normalizeComponentForCache(component) {
13376
+ const slots = component.slots.map((slot, index, allSlots) => ({
13377
+ ...slot,
13378
+ name: slot.name.trim() || (allSlots.length === 1 ? "children" : `slot_${index}`)
13379
+ }));
13380
+ return { ...component, slots };
13381
+ }
13382
+ function lookupComponentCache(db, component, promptHash) {
13383
+ const normalized = normalizeComponentForCache(component);
13384
+ const inputHash = hashComponentShape(normalized);
13385
+ const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
13386
+ if (cached) return cached;
13387
+ const legacyInputHash = hashComponentShape(component);
13388
+ return legacyInputHash === inputHash ? null : lookupCache(db, legacyInputHash, "component", component.component_id, promptHash);
13389
+ }
13390
+ function resolveComponentCache(db, component, promptHash, allowedComponentNames2) {
13391
+ const cached = lookupComponentCache(db, component, promptHash);
13392
+ if (cached && findUnknownSlotAllowedComponents(db, cached.sourceSessionId, allowedComponentNames2, component.component_id).length === 0) {
13393
+ return { entry: cached, humanEdited: cached.humanEdited };
13394
+ }
13395
+ const pinned = lookupCacheByEntity(db, "component", component.component_id);
13396
+ return pinned?.humanEdited && findUnknownSlotAllowedComponents(db, pinned.sourceSessionId, allowedComponentNames2, component.component_id).length === 0 ? { entry: pinned, humanEdited: true } : null;
13397
+ }
12749
13398
  async function runOneComponent(options, component, index, total) {
12750
13399
  const {
12751
13400
  agent,
@@ -12755,10 +13404,13 @@ async function runOneComponent(options, component, index, total) {
12755
13404
  tokensInline,
12756
13405
  tokenMapInline,
12757
13406
  verbose,
13407
+ noCache,
12758
13408
  skillPathOverride,
12759
13409
  skillContentOverride,
13410
+ promptHash,
12760
13411
  existingContentfulEntities,
12761
13412
  existingTokensInline,
13413
+ precomputedCachedNames,
12762
13414
  allowedComponentNames: allowedComponentNames2
12763
13415
  } = options;
12764
13416
  const pos = c.dim(`[${index + 1}/${total}]`);
@@ -12783,6 +13435,40 @@ async function runOneComponent(options, component, index, total) {
12783
13435
  allowedComponents: slot.allowedComponents.filter((name) => allowedComponentNames2.has(name))
12784
13436
  };
12785
13437
  });
13438
+ const cacheComponent = normalizeComponentForCache(component);
13439
+ if (!noCache && precomputedCachedNames.has(component.name)) {
13440
+ writeCachedComponentStatus(pos, component.name, false);
13441
+ return createCachedComponentResult(component.name);
13442
+ }
13443
+ if (!noCache) {
13444
+ const inputHash = hashComponentShape(cacheComponent);
13445
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13446
+ if (resolution && !resolution.humanEdited) {
13447
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13448
+ allowedComponentNames: allowedComponentNames2
13449
+ });
13450
+ storeCache(
13451
+ db,
13452
+ inputHash,
13453
+ "component",
13454
+ component.component_id,
13455
+ resolution.entry.sourceSessionId,
13456
+ resolution.entry.humanEdited,
13457
+ promptHash
13458
+ );
13459
+ writeCachedComponentStatus(pos, component.name, false);
13460
+ return createCachedComponentResult(component.name);
13461
+ }
13462
+ if (resolution?.humanEdited) {
13463
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
13464
+ allowedComponentNames: allowedComponentNames2
13465
+ });
13466
+ writeCachedComponentStatus(pos, component.name, true);
13467
+ return createCachedComponentResult(`${component.name}`, [
13468
+ `${component.name}: source changed but human edits preserved`
13469
+ ]);
13470
+ }
13471
+ }
12786
13472
  const componentBuckets = bucketComponentProps(component);
12787
13473
  getDebugLogger().event("analyze", "prop-buckets.generate", { ...componentBuckets });
12788
13474
  const rawComponentsInline = JSON.stringify(
@@ -12857,6 +13543,10 @@ ${outputBuf}`);
12857
13543
  const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings, {
12858
13544
  allowedComponentNames: allowedComponentNames2
12859
13545
  });
13546
+ if (!noCache) {
13547
+ const inputHash = hashComponentShape(cacheComponent);
13548
+ storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
13549
+ }
12860
13550
  return {
12861
13551
  componentName: component.name,
12862
13552
  classified: applied.classified,
@@ -12908,7 +13598,7 @@ async function loadAcceptedNames(sessionId2) {
12908
13598
  try {
12909
13599
  const artifactsRoot = getRefineArtifactsRoot();
12910
13600
  const paths = await getRefineSessionPaths(sessionId2, artifactsRoot);
12911
- const raw = await readFile11(paths.statePath, "utf8");
13601
+ const raw = await readFile12(paths.statePath, "utf8");
12912
13602
  const snapshot = JSON.parse(raw);
12913
13603
  const accepted = snapshot.components.filter((c2) => c2.status === "accepted").map((c2) => c2.name);
12914
13604
  if (accepted.length === 0) return null;
@@ -12917,6 +13607,15 @@ async function loadAcceptedNames(sessionId2) {
12917
13607
  return null;
12918
13608
  }
12919
13609
  }
13610
+ function parsePrecomputedCachedNames(value) {
13611
+ if (!value) return /* @__PURE__ */ new Set();
13612
+ try {
13613
+ const parsed = JSON.parse(value);
13614
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : []);
13615
+ } catch {
13616
+ return /* @__PURE__ */ new Set();
13617
+ }
13618
+ }
12920
13619
  async function runGenerateSkill(skill, opts, verbose = false) {
12921
13620
  const savedCreds = await readExperiencesCredentials();
12922
13621
  const agentName = opts.agent ?? savedCreds.agent;
@@ -12945,8 +13644,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12945
13644
  }
12946
13645
  const generatePromptPath = generatePrompt === void 0 ? configuredGeneratePromptPath : void 0;
12947
13646
  if (generatePromptPath) {
12948
- if (!await pathExists(resolve11(generatePromptPath))) {
12949
- die(`Error: custom prompt path not found: ${resolve11(generatePromptPath)}`);
13647
+ if (!await pathExists(resolve12(generatePromptPath))) {
13648
+ die(`Error: custom prompt path not found: ${resolve12(generatePromptPath)}`);
12950
13649
  }
12951
13650
  if (!generatePromptPath.toLowerCase().endsWith(".md")) {
12952
13651
  process.stderr.write(
@@ -12954,7 +13653,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12954
13653
  `
12955
13654
  );
12956
13655
  }
12957
- process.stderr.write(formatCustomPromptBanner("components", resolve11(generatePromptPath)));
13656
+ process.stderr.write(formatCustomPromptBanner("components", resolve12(generatePromptPath)));
12958
13657
  }
12959
13658
  if (skill === "tokens" && !opts.rawTokens) {
12960
13659
  die("Error: --raw-tokens is required when using generate tokens");
@@ -12970,7 +13669,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
12970
13669
  let existingContentfulEntities;
12971
13670
  let existingTokensInline;
12972
13671
  if (opts.existingEntitiesPath) {
12973
- existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve11(opts.existingEntitiesPath));
13672
+ existingContentfulEntities = await readExistingContentfulEntitiesFromSession(resolve12(opts.existingEntitiesPath));
12974
13673
  if (existingContentfulEntities) {
12975
13674
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
12976
13675
  } else {
@@ -13071,7 +13770,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13071
13770
  rawComponentsInline: sampleInline ?? rawTokensInline,
13072
13771
  propBucketsInline: sampleBuckets ? JSON.stringify(sampleBuckets) : void 0,
13073
13772
  rawTokensInline: skill === "tokens" ? rawTokensInline : void 0,
13074
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13773
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13075
13774
  tokensInline,
13076
13775
  tokenMapInline,
13077
13776
  outDir: process.cwd(),
@@ -13097,7 +13796,64 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13097
13796
  if (skill === "components" && allComponents && sessionId2) {
13098
13797
  const db = openPipelineDb();
13099
13798
  let componentResults;
13799
+ const precomputedCachedNames = parsePrecomputedCachedNames(opts.cachedComponents);
13100
13800
  try {
13801
+ const existingContentfulEntitiesHashInputs = [];
13802
+ if (existingTokensInline) existingContentfulEntitiesHashInputs.push(hashContent(existingTokensInline));
13803
+ const promptHash = await hashPromptForSkill(
13804
+ "components",
13805
+ agent,
13806
+ model,
13807
+ generatePromptPath,
13808
+ existingContentfulEntitiesHashInputs,
13809
+ generatePrompt
13810
+ );
13811
+ if (opts.cacheStatus) {
13812
+ const cacheEnabled = opts.cache !== false && process.env.EDS_NO_CACHE !== "1";
13813
+ const cachedComponents = cacheEnabled ? allComponents.flatMap((component) => {
13814
+ const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
13815
+ return resolution ? [{ component, resolution }] : [];
13816
+ }) : [];
13817
+ if (cacheEnabled && opts.restoreCache) {
13818
+ const restorations = [];
13819
+ const rekeyed = [];
13820
+ const componentStatus = db.prepare(
13821
+ "SELECT status FROM raw_components WHERE session_id = ? AND component_id = ?"
13822
+ );
13823
+ for (const { component, resolution } of cachedComponents) {
13824
+ const current = componentStatus.get(sessionId2, component.component_id);
13825
+ if (current?.status === "generated") continue;
13826
+ renameEmptySlots(db, sessionId2, component.component_id, component.name, component.slots.length);
13827
+ restorations.push({
13828
+ sourceSessionId: resolution.entry.sourceSessionId,
13829
+ targetSessionId: sessionId2,
13830
+ componentId: component.component_id
13831
+ });
13832
+ rekeyed.push({ component, cached: resolution.entry });
13833
+ }
13834
+ copyComponentsFromCache(db, restorations, { allowedComponentNames: allowedComponentNames2 });
13835
+ storeCaches(
13836
+ db,
13837
+ rekeyed.map(({ component, cached }) => ({
13838
+ inputHash: hashComponentShape(normalizeComponentForCache(component)),
13839
+ entityType: "component",
13840
+ entityId: component.component_id,
13841
+ sourceSessionId: cached.sourceSessionId,
13842
+ humanEdited: cached.humanEdited,
13843
+ promptHash
13844
+ }))
13845
+ );
13846
+ }
13847
+ const fullyCached = cacheEnabled && cachedComponents.length === allComponents.length;
13848
+ process.stdout.write(`cache-status=${fullyCached ? "hit" : "miss"}
13849
+ `);
13850
+ process.stdout.write(
13851
+ `cache-components=${JSON.stringify(cachedComponents.map(({ component }) => component.name))}
13852
+ `
13853
+ );
13854
+ await exitWithAnalytics(0);
13855
+ return;
13856
+ }
13101
13857
  componentResults = await runAllComponents(
13102
13858
  {
13103
13859
  agent,
@@ -13107,10 +13863,13 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13107
13863
  tokensInline,
13108
13864
  tokenMapInline,
13109
13865
  verbose,
13866
+ noCache: opts.cache === false || process.env.EDS_NO_CACHE === "1",
13110
13867
  skillPathOverride: generatePromptPath,
13111
13868
  skillContentOverride: generatePrompt,
13869
+ promptHash,
13112
13870
  existingContentfulEntities,
13113
13871
  existingTokensInline,
13872
+ precomputedCachedNames,
13114
13873
  allowedComponentNames: allowedComponentNames2
13115
13874
  },
13116
13875
  allComponents
@@ -13119,7 +13878,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13119
13878
  db.close();
13120
13879
  }
13121
13880
  const failed = componentResults.filter((r) => r.failed);
13122
- const generated = componentResults.filter((r) => !r.failed);
13881
+ const cachedResults = componentResults.filter((r) => r.cached);
13882
+ const generated = componentResults.filter((r) => !r.failed && !r.cached);
13123
13883
  const allWarnings = componentResults.flatMap((r) => r.warnings.map((w) => ` ${r.componentName}: ${w}`));
13124
13884
  if (allWarnings.length > 0) {
13125
13885
  process.stderr.write(c.yellow("Warnings:") + "\n" + allWarnings.join("\n") + "\n");
@@ -13135,17 +13895,21 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13135
13895
  const totalExcluded = generated.reduce((s, r) => s + r.excluded, 0);
13136
13896
  const totalRenamedSlots = componentResults.reduce((s, r) => s + r.renamedSlotsCount, 0);
13137
13897
  const allOk = failed.length === 0;
13898
+ const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : "";
13138
13899
  process.stderr.write(
13139
- (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length}/${componentResults.length} components` + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13900
+ (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
13140
13901
  );
13141
13902
  process.stdout.write(`renamed-slots: ${totalRenamedSlots}
13142
13903
  `);
13143
- if (generated.length === 0) {
13904
+ if (generated.length === 0 && cachedResults.length === 0) {
13144
13905
  die(
13145
13906
  `Error: all ${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
13146
13907
  );
13147
13908
  }
13148
13909
  } else if (skill === "tokens") {
13910
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
13911
+ const tokenInputContent = rawTokensInline ?? "";
13912
+ const tokenInputHash = hashTokenContent(tokenInputContent);
13149
13913
  const db = openPipelineDb();
13150
13914
  try {
13151
13915
  let resolvedSessionId = opts.session;
@@ -13172,11 +13936,26 @@ async function runGenerateSkill(skill, opts, verbose = false) {
13172
13936
  }
13173
13937
  sessionId2 = resolvedSessionId;
13174
13938
  await bindAnalyticsSessionId(resolvedSessionId);
13939
+ const tokenPromptHash = await hashPromptForSkill("tokens", agent, model);
13940
+ if (!noCache) {
13941
+ const tokenCached = lookupCache(db, tokenInputHash, "token_set", "__tokens__", tokenPromptHash);
13942
+ if (tokenCached) {
13943
+ copyTokensFromCache(db, tokenCached.sourceSessionId, resolvedSessionId);
13944
+ sessionId2 = resolvedSessionId;
13945
+ process.stderr.write(
13946
+ `Done: tokens reused from cache ${c.dim(`(source: ${tokenCached.sourceSessionId.slice(0, 12)})`)}
13947
+ `
13948
+ );
13949
+ db.close();
13950
+ await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13951
+ return;
13952
+ }
13953
+ }
13175
13954
  const prompt = await buildPrompt({
13176
13955
  skill,
13177
13956
  mode: "autonomous",
13178
13957
  rawTokensInline,
13179
- rawTokensFilename: opts.rawTokens ? resolve11(opts.rawTokens).split("/").pop() : void 0,
13958
+ rawTokensFilename: opts.rawTokens ? resolve12(opts.rawTokens).split("/").pop() : void 0,
13180
13959
  tokensInline,
13181
13960
  tokenMapInline,
13182
13961
  outDir: process.cwd()
@@ -13213,6 +13992,9 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13213
13992
  `);
13214
13993
  }
13215
13994
  applyTokenToolCalls(db, resolvedSessionId, tokenCalls, []);
13995
+ if (!noCache) {
13996
+ storeCache(db, tokenInputHash, "token_set", "__tokens__", resolvedSessionId, false, tokenPromptHash);
13997
+ }
13216
13998
  sessionId2 = resolvedSessionId;
13217
13999
  const groupCount = tokenCalls.filter((tc) => tc.tool === "set_group").length;
13218
14000
  process.stderr.write(`Done: ${tokenCount} tokens, ${groupCount} groups stored
@@ -13224,7 +14006,10 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
13224
14006
  await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
13225
14007
  }
13226
14008
  function addAgentFlags(cmd) {
13227
- return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent");
14009
+ return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent").option(
14010
+ "--no-cache",
14011
+ "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."
14012
+ );
13228
14013
  }
13229
14014
  function registerInternalGenerateCommand(program) {
13230
14015
  const generate = program.command("__generate", { hidden: true }).description("Internal import pipeline generation command");
@@ -13240,6 +14025,12 @@ function registerInternalGenerateCommand(program) {
13240
14025
  "--existing-entities-path <path>",
13241
14026
  "Path to the .existing-entities.json file written after CMA credentials are supplied. When present, per-component prompts include a summary of the target space so classifications can align to existing prop names/tokens. Missing/malformed files are treated as no-op."
13242
14027
  );
14028
+ componentsCmd.option("--cache-status", "Check whether all selected component definitions are cached");
14029
+ componentsCmd.option("--restore-cache", "Restore cached component definitions into the current session");
14030
+ componentsCmd.option(
14031
+ "--cached-components <json>",
14032
+ "Component names already restored by a prior cache-status lookup; skip per-component cache lookup"
14033
+ );
13243
14034
  addAgentFlags(componentsCmd).action(async (opts) => {
13244
14035
  await runGenerateSkill("components", opts, opts.verbose ?? false);
13245
14036
  });
@@ -13265,6 +14056,7 @@ var init_command = __esm({
13265
14056
  init_agent_output();
13266
14057
  init_GenerateView();
13267
14058
  init_db();
14059
+ init_cache_keys();
13268
14060
  init_read_existing_contentful_entities_from_session();
13269
14061
  init_summarize_existing_contentful_entities();
13270
14062
  init_resolve_session_id();
@@ -13980,7 +14772,7 @@ var init_user_agent = __esm({
13980
14772
  // packages/experience-design-system-cli-legacy/src/apply/api-client.ts
13981
14773
  import { parseCDFComponents } from "@contentful/experience-design-system-types";
13982
14774
  function defaultSleep(delayMs) {
13983
- return new Promise((resolve20) => setTimeout(resolve20, delayMs));
14775
+ return new Promise((resolve22) => setTimeout(resolve22, delayMs));
13984
14776
  }
13985
14777
  function isTransientStatus(status) {
13986
14778
  return status >= 500 && status <= 599;
@@ -14370,7 +15162,7 @@ ${trimmed}`;
14370
15162
  const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
14371
15163
  const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
14372
15164
  const jitter = Math.random() * baseDelay * 0.15;
14373
- await new Promise((resolve20) => setTimeout(resolve20, baseDelay + jitter));
15165
+ await new Promise((resolve22) => setTimeout(resolve22, baseDelay + jitter));
14374
15166
  }
14375
15167
  }
14376
15168
  throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
@@ -15180,7 +15972,7 @@ var init_terminal_capabilities = __esm({
15180
15972
 
15181
15973
  // packages/experience-design-system-cli-legacy/src/apply/command.ts
15182
15974
  import { createElement as createElement2 } from "react";
15183
- import { readFile as readFile12, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15975
+ import { readFile as readFile13, readdir as readdir2, stat as stat2 } from "node:fs/promises";
15184
15976
  import { join as join16 } from "node:path";
15185
15977
  import {
15186
15978
  validateCDF,
@@ -15201,7 +15993,7 @@ function dieWithApiError(error) {
15201
15993
  async function readJsonFile(flag, p) {
15202
15994
  let text;
15203
15995
  try {
15204
- text = await readFile12(p, "utf8");
15996
+ text = await readFile13(p, "utf8");
15205
15997
  } catch {
15206
15998
  return await die2(`Error: file not found: ${p} (from ${flag})`);
15207
15999
  }
@@ -15255,7 +16047,7 @@ async function readTokensFromPath(flag, p) {
15255
16047
  for (const file of files.sort()) {
15256
16048
  let text;
15257
16049
  try {
15258
- text = await readFile12(file, "utf8");
16050
+ text = await readFile13(file, "utf8");
15259
16051
  } catch {
15260
16052
  continue;
15261
16053
  }
@@ -15624,11 +16416,11 @@ var init_command2 = __esm({
15624
16416
  });
15625
16417
 
15626
16418
  // packages/experience-design-system-cli-legacy/src/print/validate/validators/read-json-file.ts
15627
- import { readFile as readFile13 } from "node:fs/promises";
16419
+ import { readFile as readFile14 } from "node:fs/promises";
15628
16420
  async function readJsonFile2(filePath) {
15629
16421
  let content;
15630
16422
  try {
15631
- content = await readFile13(filePath, "utf-8");
16423
+ content = await readFile14(filePath, "utf-8");
15632
16424
  } catch (err) {
15633
16425
  return {
15634
16426
  ok: false,
@@ -16039,7 +16831,7 @@ var init_ValidateView = __esm({
16039
16831
  // packages/experience-design-system-cli-legacy/src/print/command.ts
16040
16832
  import { createElement as createElement3 } from "react";
16041
16833
  import { access as access3, mkdir as mkdir3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
16042
- import { basename as basename5, resolve as resolve12 } from "node:path";
16834
+ import { basename as basename5, resolve as resolve13 } from "node:path";
16043
16835
  import { CDF_SCHEMA_URL } from "@contentful/experience-design-system-types";
16044
16836
  async function die3(message) {
16045
16837
  process.stderr.write(`${message}
@@ -16106,7 +16898,7 @@ function registerPrintCommand(program) {
16106
16898
  "--allow-empty",
16107
16899
  "Write an empty-but-present CDF file when no components are accepted (a subsequent push then removes ALL components from the target space). Without this, an empty accepted set is an error."
16108
16900
  ).action(async (opts) => {
16109
- const outPath = resolve12(opts.out);
16901
+ const outPath = resolve13(opts.out);
16110
16902
  await assertOutIsNotDirectory(outPath);
16111
16903
  const sessionId2 = await resolveSession(opts.session, "generate components");
16112
16904
  await bindAnalyticsSessionId(sessionId2);
@@ -16154,7 +16946,7 @@ function registerPrintCommand(program) {
16154
16946
  `
16155
16947
  );
16156
16948
  }
16157
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16949
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16158
16950
  await writeFile3(outPath, `${JSON.stringify(cdfObj, null, 2)}
16159
16951
  `);
16160
16952
  process.stdout.write(
@@ -16163,7 +16955,7 @@ function registerPrintCommand(program) {
16163
16955
  );
16164
16956
  });
16165
16957
  print.command("tokens").description("Write generated tokens to a DTCG JSON file").option("--session <id>", "Session ID to print from (default: most recent generate tokens session)").option("--out <path>", "Output file path", "tokens.json").action(async (opts) => {
16166
- const outPath = resolve12(opts.out);
16958
+ const outPath = resolve13(opts.out);
16167
16959
  await assertOutIsNotDirectory(outPath);
16168
16960
  const sessionId2 = await resolveSession(opts.session, "generate tokens");
16169
16961
  await bindAnalyticsSessionId(sessionId2);
@@ -16178,7 +16970,7 @@ function registerPrintCommand(program) {
16178
16970
  await die3(`Error: no generated tokens in session '${sessionId2}'. Run generate tokens first.`);
16179
16971
  }
16180
16972
  const tree = rebuildDTCGTree(result.groups, result.tokens);
16181
- await mkdir3(resolve12(outPath, ".."), { recursive: true });
16973
+ await mkdir3(resolve13(outPath, ".."), { recursive: true });
16182
16974
  await writeFile3(outPath, `${JSON.stringify(tree, null, 2)}
16183
16975
  `);
16184
16976
  process.stdout.write(
@@ -16327,7 +17119,7 @@ function MapTokensView({ result, onExit }) {
16327
17119
  const timer = setTimeout(onExit, 100);
16328
17120
  return () => clearTimeout(timer);
16329
17121
  }, [onExit]);
16330
- const summary = `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
17122
+ const summary = result.cached ? "reused from cache" : `${result.applied} mapping${result.applied === 1 ? "" : "s"} applied`;
16331
17123
  return /* @__PURE__ */ jsx8(
16332
17124
  CommandCompletionView,
16333
17125
  {
@@ -16461,8 +17253,8 @@ var init_resolve_defaults = __esm({
16461
17253
 
16462
17254
  // packages/experience-design-system-cli-legacy/src/map-tokens/command.ts
16463
17255
  import { createElement as createElement4 } from "react";
16464
- import { readFile as readFile14 } from "node:fs/promises";
16465
- import { resolve as resolve13 } from "node:path";
17256
+ import { readFile as readFile15 } from "node:fs/promises";
17257
+ import { resolve as resolve14 } from "node:path";
16466
17258
  async function renderResult(result) {
16467
17259
  if (process.stdout.isTTY) {
16468
17260
  const { waitUntilExit } = renderWithGoodbye(
@@ -16470,13 +17262,12 @@ async function renderResult(result) {
16470
17262
  );
16471
17263
  await waitUntilExit();
16472
17264
  } else {
16473
- process.stdout.write(
16474
- `map tokens complete
17265
+ const summary = result.cached ? "cached" : `${result.applied} mapping(s) applied`;
17266
+ process.stdout.write(`map tokens complete
16475
17267
  agent: ${result.agent}
16476
17268
  session=${result.sessionId}
16477
- ${result.applied} mapping(s) applied
16478
- `
16479
- );
17269
+ ${summary}
17270
+ `);
16480
17271
  await exitWithAnalytics(0);
16481
17272
  }
16482
17273
  }
@@ -16498,7 +17289,7 @@ async function runMapTokens(opts) {
16498
17289
  let mapPromptPath;
16499
17290
  if (mapPromptOverride?.kind === "text") mapPromptText = mapPromptOverride.value;
16500
17291
  if (mapPromptOverride?.kind === "path") {
16501
- mapPromptPath = resolve13(mapPromptOverride.value);
17292
+ mapPromptPath = resolve14(mapPromptOverride.value);
16502
17293
  try {
16503
17294
  await resolvePromptOverride(mapPromptOverride);
16504
17295
  } catch (error) {
@@ -16518,7 +17309,7 @@ async function runMapTokens(opts) {
16518
17309
  const knownTokenPaths = new Set(tokenLeaves.map((t) => t.path));
16519
17310
  const tokenMapDiagnostics = [];
16520
17311
  if (opts.tokenMap) {
16521
- const fileMappings = JSON.parse(await readFile14(opts.tokenMap, "utf8"));
17312
+ const fileMappings = JSON.parse(await readFile15(opts.tokenMap, "utf8"));
16522
17313
  const validMappings = {};
16523
17314
  for (const [rawName, path] of Object.entries(fileMappings)) {
16524
17315
  if (knownTokenPaths.has(path)) {
@@ -16596,7 +17387,7 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16596
17387
  let existingTokensInline;
16597
17388
  if (opts.existingEntitiesPath) {
16598
17389
  const existingContentfulEntities = await readExistingContentfulEntitiesFromSession(
16599
- resolve13(opts.existingEntitiesPath)
17390
+ resolve14(opts.existingEntitiesPath)
16600
17391
  );
16601
17392
  if (existingContentfulEntities) {
16602
17393
  existingTokensInline = JSON.stringify(summarizeForMapTokens(existingContentfulEntities));
@@ -16631,10 +17422,30 @@ ${defaultDiagnostics.map((diagnostic) => ` ${diagnostic}`).join("\n")}
16631
17422
  skipAgent: "true"
16632
17423
  });
16633
17424
  updateStep(db, stepId2, "complete", { applied: "0", skipAgent: "true" });
16634
- await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0 });
17425
+ await renderResult({ agent: resultAgent, sessionId: sessionId2, applied: 0, cached: false });
16635
17426
  return;
16636
17427
  }
16637
17428
  const agent = configuredAgent;
17429
+ const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
17430
+ const promptHash = await hashPromptForSkill(
17431
+ "map-tokens",
17432
+ agent,
17433
+ model,
17434
+ void 0,
17435
+ existingTokensInline ? [hashContent(existingTokensInline)] : [],
17436
+ mapPromptText
17437
+ );
17438
+ const inputHash = computeMapTokensInputHash(db, sessionId2, componentSourceRefs);
17439
+ if (!noCache) {
17440
+ const cached = lookupCache(db, inputHash, "token_mapping", "__map_tokens__", promptHash);
17441
+ if (cached) {
17442
+ const appliedFromCache = copyMapTokensFromCache(db, cached.sourceSessionId, sessionId2);
17443
+ const stepId2 = createStep(db, sessionId2, "map tokens", { agent, model: model ?? "" });
17444
+ updateStep(db, stepId2, "complete", { cached: "true", applied: String(appliedFromCache) });
17445
+ await renderResult({ agent, sessionId: sessionId2, applied: appliedFromCache, cached: true });
17446
+ return;
17447
+ }
17448
+ }
16638
17449
  const binary = resolveAgentBinary(agent);
16639
17450
  if (!await assertBinaryInPath(binary)) {
16640
17451
  die(
@@ -16654,20 +17465,23 @@ Install it, choose another agent with --agent, or use --print-prompt to run the
16654
17465
  }
16655
17466
  const { calls, warnings: parseWarnings } = parseMapTokenPropToolCallLines(result.stdout);
16656
17467
  const { applied, warnings } = applyMapTokenPropCalls(db, sessionId2, calls, parseWarnings);
17468
+ if (!noCache) {
17469
+ storeCache(db, inputHash, "token_mapping", "__map_tokens__", sessionId2, false, promptHash);
17470
+ }
16657
17471
  updateStep(db, stepId, "complete", { applied: String(applied), warnings: String(warnings.length) });
16658
17472
  if (warnings.length > 0) {
16659
17473
  process.stderr.write(`Warnings:
16660
17474
  ${warnings.map((w) => ` ${w}`).join("\n")}
16661
17475
  `);
16662
17476
  }
16663
- await renderResult({ agent, sessionId: sessionId2, applied });
17477
+ await renderResult({ agent, sessionId: sessionId2, applied, cached: false });
16664
17478
  } finally {
16665
17479
  db.close();
16666
17480
  }
16667
17481
  }
16668
17482
  function registerMapTokensCommand(program) {
16669
17483
  const map = program.command("map", { hidden: true }).description("Suggest token restrictions for generated design-token props");
16670
- const tokensCmd = map.command("tokens").description("Invoke a coding agent to suggest $token.allowed for design-category token props").option("--session <id>", "Session ID from generate components (defaults to most recent)").option("--print-prompt", "Print the prompt without invoking the agent").option("--skip-agent", "Resolve token defaults without agentic $token.allowed inference").option(
17484
+ const tokensCmd = map.command("tokens").description("Invoke a coding agent to suggest $token.allowed for design-category token props").option("--session <id>", "Session ID from generate components (defaults to most recent)").option("--print-prompt", "Print the prompt without invoking the agent").option("--skip-agent", "Resolve token defaults without agentic $token.allowed inference").option("--no-cache", "Bypass the map-tokens cache and force a re-run").option(
16671
17485
  "--prompt <stage=value>",
16672
17486
  "Override a stage prompt (repeatable). Used here for the map-tokens stage.",
16673
17487
  (v, acc) => [...acc, v],
@@ -16687,6 +17501,7 @@ var init_command4 = __esm({
16687
17501
  init_render_with_goodbye();
16688
17502
  init_src();
16689
17503
  init_db();
17504
+ init_cache_keys();
16690
17505
  init_read_existing_contentful_entities_from_session();
16691
17506
  init_summarize_existing_contentful_entities();
16692
17507
  init_command3();
@@ -16703,7 +17518,7 @@ var init_command4 = __esm({
16703
17518
  });
16704
17519
 
16705
17520
  // packages/experience-design-system-cli-legacy/src/import/path-utils.ts
16706
- import { resolve as resolve14 } from "node:path";
17521
+ import { resolve as resolve15 } from "node:path";
16707
17522
  import { homedir as homedir5 } from "node:os";
16708
17523
  function expandTilde(input) {
16709
17524
  if (input === "~" || input.startsWith("~/") || input.startsWith("~\\")) {
@@ -16717,7 +17532,7 @@ function normalizePath(input) {
16717
17532
  p = p.slice(1, -1);
16718
17533
  }
16719
17534
  p = expandTilde(p);
16720
- return resolve14(p);
17535
+ return resolve15(p);
16721
17536
  }
16722
17537
  var init_path_utils = __esm({
16723
17538
  "packages/experience-design-system-cli-legacy/src/import/path-utils.ts"() {
@@ -16942,7 +17757,7 @@ var init_save_path_resolver = __esm({
16942
17757
  });
16943
17758
 
16944
17759
  // packages/experience-design-system-cli-legacy/src/runs/store.ts
16945
- import { readFile as readFile15, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
17760
+ import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4, rename } from "node:fs/promises";
16946
17761
  import { join as join20 } from "node:path";
16947
17762
  import { homedir as homedir6 } from "node:os";
16948
17763
  import { randomBytes as randomBytes2 } from "node:crypto";
@@ -16982,7 +17797,7 @@ function migrateRecord(rec) {
16982
17797
  }
16983
17798
  async function readFileMaybe() {
16984
17799
  try {
16985
- const raw = await readFile15(RUNS_PATH, "utf8");
17800
+ const raw = await readFile16(RUNS_PATH, "utf8");
16986
17801
  const parsed = JSON.parse(raw);
16987
17802
  if (!READABLE_VERSIONS.has(parsed.version)) {
16988
17803
  throw new Error(
@@ -17046,15 +17861,15 @@ var init_store = __esm({
17046
17861
  });
17047
17862
 
17048
17863
  // packages/experience-design-system-cli-legacy/src/runs/fingerprint.ts
17049
- import { createHash as createHash2 } from "node:crypto";
17050
- import { readFile as readFile16, stat as stat4 } from "node:fs/promises";
17051
- import { resolve as resolve15 } from "node:path";
17864
+ import { createHash as createHash6 } from "node:crypto";
17865
+ import { readFile as readFile17, stat as stat4 } from "node:fs/promises";
17866
+ import { resolve as resolve16 } from "node:path";
17052
17867
  function rowField(row, key) {
17053
17868
  const v = row[key];
17054
17869
  return typeof v === "string" ? v : null;
17055
17870
  }
17056
17871
  function sha256Hex(buf) {
17057
- return createHash2("sha256").update(buf).digest("hex");
17872
+ return createHash6("sha256").update(buf).digest("hex");
17058
17873
  }
17059
17874
  async function buildSourceFingerprint(opts) {
17060
17875
  const rows = opts.db.prepare("SELECT name, source_path FROM raw_components WHERE session_id = ?").all(opts.extractSessionId);
@@ -17064,7 +17879,7 @@ async function buildSourceFingerprint(opts) {
17064
17879
  const sourcePath = rowField(row, "source_path");
17065
17880
  const name = rowField(row, "name");
17066
17881
  if (!sourcePath) continue;
17067
- const abs = resolve15(sourcePath);
17882
+ const abs = resolve16(sourcePath);
17068
17883
  if (seen.has(abs)) continue;
17069
17884
  seen.add(abs);
17070
17885
  try {
@@ -17079,10 +17894,10 @@ async function buildSourceFingerprint(opts) {
17079
17894
  let rawTokensMtime = null;
17080
17895
  let rawTokensContentHash = null;
17081
17896
  if (opts.rawTokensPath) {
17082
- const abs = resolve15(opts.rawTokensPath);
17897
+ const abs = resolve16(opts.rawTokensPath);
17083
17898
  rawTokensPath = abs;
17084
17899
  try {
17085
- const [st, buf] = await Promise.all([stat4(abs), readFile16(abs)]);
17900
+ const [st, buf] = await Promise.all([stat4(abs), readFile17(abs)]);
17086
17901
  rawTokensMtime = st.mtime.toISOString();
17087
17902
  rawTokensContentHash = sha256Hex(buf);
17088
17903
  } catch {
@@ -18837,11 +19652,11 @@ var init_slot_graph = __esm({
18837
19652
  });
18838
19653
 
18839
19654
  // packages/experience-design-system-cli-legacy/src/analyze/select/parser.ts
18840
- import { createHash as createHash3 } from "node:crypto";
19655
+ import { createHash as createHash7 } from "node:crypto";
18841
19656
  import { access as access5 } from "node:fs/promises";
18842
- import { isAbsolute, relative, resolve as resolve16 } from "node:path";
19657
+ import { isAbsolute, relative, resolve as resolve17 } from "node:path";
18843
19658
  function createComponentId(name, resolvedSourcePath) {
18844
- const sourceHash = createHash3("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
19659
+ const sourceHash = createHash7("sha256").update(`${name}:${resolvedSourcePath}`).digest("hex").slice(0, 12);
18845
19660
  return `${name}-${sourceHash}`;
18846
19661
  }
18847
19662
  async function resolveComponentSourcePath(source, reviewRoot) {
@@ -18853,7 +19668,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18853
19668
  throw new Error(`Unable to access component source at ${source}`);
18854
19669
  }
18855
19670
  }
18856
- const candidate = resolve16(reviewRoot, source);
19671
+ const candidate = resolve17(reviewRoot, source);
18857
19672
  const relativeToRoot = relative(reviewRoot, candidate);
18858
19673
  if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || isAbsolute(relativeToRoot)) {
18859
19674
  throw new Error(
@@ -18868,7 +19683,7 @@ async function resolveComponentSourcePath(source, reviewRoot) {
18868
19683
  }
18869
19684
  }
18870
19685
  async function loadReviewInput(components, options = {}) {
18871
- const reviewRoot = resolve16(options.reviewRoot ?? process.cwd());
19686
+ const reviewRoot = resolve17(options.reviewRoot ?? process.cwd());
18872
19687
  const records = await Promise.all(
18873
19688
  components.map(async (component) => {
18874
19689
  let resolvedSourcePath;
@@ -18899,7 +19714,7 @@ var init_parser = __esm({
18899
19714
  });
18900
19715
 
18901
19716
  // packages/experience-design-system-cli-legacy/src/analyze/select/command.ts
18902
- import { access as access6, readFile as readFile17 } from "node:fs/promises";
19717
+ import { access as access6, readFile as readFile18 } from "node:fs/promises";
18903
19718
  async function loadAndValidateForReview(sessionId2, projectRoot) {
18904
19719
  const db = openPipelineDb();
18905
19720
  let rawComponents;
@@ -18917,7 +19732,7 @@ async function patchReviewStateWithValidationErrors(sessionId2, errors, opts = {
18917
19732
  let snapshot;
18918
19733
  try {
18919
19734
  await access6(paths.statePath);
18920
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19735
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18921
19736
  } catch {
18922
19737
  snapshot = await loadAndValidateForReview(sessionId2, void 0);
18923
19738
  snapshot = await ensureRefineSession(sessionId2, artifactsRoot, snapshot);
@@ -18968,7 +19783,7 @@ async function rejectComponentsByName(sessionId2, names, opts = {}) {
18968
19783
  const nameSet = new Set(names);
18969
19784
  let snapshot;
18970
19785
  try {
18971
- snapshot = JSON.parse(await readFile17(paths.statePath, "utf8"));
19786
+ snapshot = JSON.parse(await readFile18(paths.statePath, "utf8"));
18972
19787
  } catch {
18973
19788
  return;
18974
19789
  }
@@ -19067,9 +19882,9 @@ function spawnGenerateChild(opts) {
19067
19882
  stderr += chunk;
19068
19883
  opts.onStderr?.(chunk);
19069
19884
  });
19070
- const donePromise = new Promise((resolve20) => {
19885
+ const donePromise = new Promise((resolve22) => {
19071
19886
  child.on("close", (code, signal) => {
19072
- resolve20({
19887
+ resolve22({
19073
19888
  exitCode: code ?? 0,
19074
19889
  signal: signal ?? null,
19075
19890
  stdout,
@@ -19077,7 +19892,7 @@ function spawnGenerateChild(opts) {
19077
19892
  });
19078
19893
  });
19079
19894
  child.on("error", (err) => {
19080
- resolve20({
19895
+ resolve22({
19081
19896
  exitCode: 1,
19082
19897
  signal: null,
19083
19898
  stdout,
@@ -27215,6 +28030,9 @@ function shouldGenerateAfterCredentialsValidated(opts) {
27215
28030
  function shouldSkipFinalReviewAfterCredentials(state) {
27216
28031
  return state.finalReviewPassed && state.generateSessionId != null;
27217
28032
  }
28033
+ function resolveNoCacheForGenerate(opts) {
28034
+ return opts.cliNoCache;
28035
+ }
27218
28036
  function shouldBypassPreview(state) {
27219
28037
  return state.credentialsSkipped === true;
27220
28038
  }
@@ -27241,43 +28059,70 @@ var init_wizard_state_transitions = __esm({
27241
28059
  async function runSelectionAgent(options) {
27242
28060
  const db = openPipelineDb();
27243
28061
  let components;
28062
+ const cachedDecisions = /* @__PURE__ */ new Map();
28063
+ const cliVersion2 = await getCliCacheVersion();
28064
+ const promptHash = await hashPromptForSkill(
28065
+ "select",
28066
+ options.agent,
28067
+ options.model,
28068
+ options.promptPath,
28069
+ [SELECT_PAYLOAD_FORMAT],
28070
+ options.promptText
28071
+ );
27244
28072
  try {
27245
28073
  components = loadRawComponents(db, options.sessionId);
28074
+ if (!options.noCache) {
28075
+ for (const component of components) {
28076
+ const componentHash = hashComponentShape(component);
28077
+ const cached = lookupSelectCache(db, componentHash, promptHash, cliVersion2);
28078
+ if (cached) {
28079
+ cachedDecisions.set(component.name, {
28080
+ decision: cached.decision,
28081
+ reason: cached.reason
28082
+ });
28083
+ }
28084
+ }
28085
+ }
27246
28086
  } finally {
27247
28087
  db.close();
27248
28088
  }
27249
28089
  if (components.length === 0) return;
27250
- const invoker2 = createLocalCliAgentInvoker({
27251
- onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
27252
- });
27253
- const selectBuckets = bucketComponentsProps(components);
27254
- for (const assignment of selectBuckets) {
27255
- getDebugLogger().event("analyze", "prop-buckets.select", { ...assignment });
27256
- }
27257
- const prompt = await buildPrompt({
27258
- skill: "select",
27259
- mode: "autonomous",
27260
- rawComponentsInline: JSON.stringify(
27261
- components.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
27262
- null,
27263
- 2
27264
- ),
27265
- propBucketsInline: JSON.stringify(selectBuckets),
27266
- outDir: process.cwd(),
27267
- ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
27268
- ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
27269
- });
27270
- const { result } = await invokeAgentWithOutput(
27271
- invoker2,
27272
- { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
27273
- false
27274
- );
27275
- if (result.timedOut) throw new Error("selection agent timed out");
27276
- if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
27277
- const parsed = parseSelectToolCallLines(result.stdout);
27278
- for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
28090
+ const componentsToRun = components.filter((component) => !cachedDecisions.has(component.name));
28091
+ let calls = [];
28092
+ if (componentsToRun.length > 0) {
28093
+ const invoker2 = createLocalCliAgentInvoker({
28094
+ onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
28095
+ });
28096
+ const selectBuckets = bucketComponentsProps(componentsToRun);
28097
+ for (const assignment of selectBuckets) {
28098
+ getDebugLogger().event("analyze", "prop-buckets.select", { ...assignment });
28099
+ }
28100
+ const prompt = await buildPrompt({
28101
+ skill: "select",
28102
+ mode: "autonomous",
28103
+ rawComponentsInline: JSON.stringify(
28104
+ componentsToRun.map((component) => ({ ...component, props: component.props.map(stripPropProvenance) })),
28105
+ null,
28106
+ 2
28107
+ ),
28108
+ propBucketsInline: JSON.stringify(selectBuckets),
28109
+ outDir: process.cwd(),
28110
+ ...options.promptText !== void 0 ? { skillContentOverride: options.promptText } : {},
28111
+ ...options.promptText === void 0 && options.promptPath ? { skillPathOverride: options.promptPath } : {}
28112
+ });
28113
+ const { result } = await invokeAgentWithOutput(
28114
+ invoker2,
28115
+ { agent: options.agent, model: options.model, prompt, timeoutMs: DEFAULT_TIMEOUT_MS4 },
28116
+ false
28117
+ );
28118
+ if (result.timedOut) throw new Error("selection agent timed out");
28119
+ if (result.exitCode !== 0) throw new Error(`selection agent exited with code ${result.exitCode}`);
28120
+ const parsed = parseSelectToolCallLines(result.stdout);
28121
+ for (const warning of parsed.warnings) process.stderr.write(`Warning: selection agent: ${warning}
27279
28122
  `);
27280
- const byName = new Map(parsed.calls.map((call) => [call.name, call]));
28123
+ calls = parsed.calls;
28124
+ }
28125
+ const byName = new Map(calls.map((call) => [call.name, call]));
27281
28126
  const writeDb = openPipelineDb();
27282
28127
  try {
27283
28128
  writeDb.exec("BEGIN");
@@ -27285,15 +28130,27 @@ async function runSelectionAgent(options) {
27285
28130
  `UPDATE raw_components SET status = ?, reject_reason = ? WHERE session_id = ? AND component_id = ?`
27286
28131
  );
27287
28132
  for (const component of components) {
28133
+ const cached = cachedDecisions.get(component.name);
27288
28134
  const call = byName.get(component.name);
27289
- if (!call) continue;
27290
- const rejected = call.tool === "reject_component";
28135
+ if (!cached && !call) continue;
28136
+ const rejected = cached ? cached.decision === "rejected" : call.tool === "reject_component";
28137
+ const reason = cached ? cached.reason : call.reason ?? null;
27291
28138
  update.run(
27292
28139
  rejected ? "rejected" : "accepted",
27293
- rejected ? call.reason ?? null : null,
28140
+ rejected ? reason : null,
27294
28141
  options.sessionId,
27295
28142
  component.component_id
27296
28143
  );
28144
+ if (!cached) {
28145
+ storeSelectCache(
28146
+ writeDb,
28147
+ hashComponentShape(component),
28148
+ promptHash,
28149
+ cliVersion2,
28150
+ rejected ? "rejected" : "accepted",
28151
+ rejected ? reason : null
28152
+ );
28153
+ }
27297
28154
  }
27298
28155
  writeDb.exec("COMMIT");
27299
28156
  } catch (error) {
@@ -27303,7 +28160,7 @@ async function runSelectionAgent(options) {
27303
28160
  writeDb.close();
27304
28161
  }
27305
28162
  }
27306
- var DEFAULT_TIMEOUT_MS4;
28163
+ var SELECT_PAYLOAD_FORMAT, DEFAULT_TIMEOUT_MS4;
27307
28164
  var init_run_selection_agent = __esm({
27308
28165
  "packages/experience-design-system-cli-legacy/src/import/tui/run-selection-agent.ts"() {
27309
28166
  "use strict";
@@ -27312,6 +28169,8 @@ var init_run_selection_agent = __esm({
27312
28169
  init_debug_logger();
27313
28170
  init_agent_output();
27314
28171
  init_db();
28172
+ init_cache_keys();
28173
+ SELECT_PAYLOAD_FORMAT = "prop-buckets-v1";
27315
28174
  DEFAULT_TIMEOUT_MS4 = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 5 * 60 * 1e3);
27316
28175
  }
27317
28176
  });
@@ -27330,7 +28189,7 @@ __export(WizardApp_exports, {
27330
28189
  });
27331
28190
  import { useEffect as useEffect15, useRef as useRef10, useState as useState23 } from "react";
27332
28191
  import { Box as Box42, Text as Text46 } from "ink";
27333
- import { join as join23, resolve as resolve17 } from "node:path";
28192
+ import { join as join23, resolve as resolve18 } from "node:path";
27334
28193
  import { appendFileSync as appendFileSync2, writeFileSync } from "node:fs";
27335
28194
  import { access as access7, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
27336
28195
  import { tmpdir } from "node:os";
@@ -27343,21 +28202,28 @@ function buildGenerateComponentsArgs(opts) {
27343
28202
  if (opts.tokensPath) args.push("--tokens", opts.tokensPath);
27344
28203
  if (opts.model) args.push("--model", opts.model);
27345
28204
  if (opts.bedrock) args.push("--bedrock");
28205
+ if (opts.noCache) args.push("--no-cache");
27346
28206
  if (opts.generatePromptPath) args.push("--generate-prompt-path", opts.generatePromptPath);
27347
28207
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27348
28208
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
28209
+ if (opts.restoreCache) args.push("--restore-cache");
28210
+ if (opts.cachedComponents && opts.cachedComponents.length > 0) {
28211
+ args.push("--cached-components", JSON.stringify(opts.cachedComponents));
28212
+ }
27349
28213
  return args;
27350
28214
  }
27351
28215
  function buildGenerateTokensArgs(opts) {
27352
28216
  const args = [findCliPath(), "__generate", "tokens", "--agent", opts.agent, "--raw-tokens", opts.rawTokensPath];
27353
28217
  if (opts.model) args.push("--model", opts.model);
27354
28218
  if (opts.bedrock) args.push("--bedrock");
28219
+ if (opts.noCache) args.push("--no-cache");
27355
28220
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27356
28221
  return args;
27357
28222
  }
27358
28223
  function buildMapTokensArgs(opts) {
27359
28224
  const args = ["map", "tokens", "--session", opts.sessionId, "--agent", opts.agent];
27360
28225
  if (opts.model) args.push("--model", opts.model);
28226
+ if (opts.noCache) args.push("--no-cache");
27361
28227
  if (opts.skipAgent) args.push("--skip-agent");
27362
28228
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
27363
28229
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
@@ -27449,6 +28315,7 @@ function WizardApp({
27449
28315
  initialProjectPath,
27450
28316
  host,
27451
28317
  promptOverrides,
28318
+ noCache = false,
27452
28319
  livePreview = true,
27453
28320
  generatePromptPath,
27454
28321
  skipMapTokens = false,
@@ -27473,6 +28340,7 @@ function WizardApp({
27473
28340
  });
27474
28341
  const allowEmptyDeleteAllRef = useRef10(false);
27475
28342
  const generateChildRef = useRef10(null);
28343
+ const generationCachePreflightRef = useRef10(null);
27476
28344
  const selectionToReviewStartedAtRef = useRef10(null);
27477
28345
  const credentialsValidationIdRef = useRef10(0);
27478
28346
  const pendingCredentialValidationRef = useRef10(null);
@@ -27487,8 +28355,9 @@ function WizardApp({
27487
28355
  const existingEntitiesPromiseRef = useRef10(null);
27488
28356
  const generatePromiseRef = useRef10(null);
27489
28357
  const rawTokensEntryReady = !!initialRawTokensPath;
28358
+ const effectiveNoCache = resolveNoCacheForGenerate({ cliNoCache: noCache });
27490
28359
  const initialStepResolved = rawTokensEntryReady ? initialProjectPath ? "path-validation" : "credentials" : initialProjectPath ? "token-input" : "welcome";
27491
- const initialOutDir = initialProjectPath ? join23(resolve17(initialProjectPath), ".contentful") : "";
28360
+ const initialOutDir = initialProjectPath ? join23(resolve18(initialProjectPath), ".contentful") : "";
27492
28361
  const [state, setState] = useState23({
27493
28362
  step: initialStepResolved,
27494
28363
  agent: initialAgent ?? "claude",
@@ -27664,13 +28533,14 @@ If you are using AWS Bedrock, run:
27664
28533
  if (!deferStep) update({ step: nextStep });
27665
28534
  return true;
27666
28535
  };
27667
- const runGenerateTokens = async (rawTokensPath, outDir) => {
28536
+ const runGenerateTokens = async (rawTokensPath, outDir, forceRegenerate = false) => {
27668
28537
  update({ tokenGenerationStatus: "running" });
27669
28538
  const tokenArgs = buildGenerateTokensArgs({
27670
28539
  rawTokensPath,
27671
28540
  agent: state.agent,
27672
28541
  ...state.agentModel ? { model: state.agentModel } : {},
27673
28542
  ...state.bedrock ? { bedrock: true } : {},
28543
+ noCache: effectiveNoCache || forceRegenerate,
27674
28544
  promptOverrides
27675
28545
  });
27676
28546
  const result = await runSpawnedCli(tokenArgs);
@@ -27719,6 +28589,7 @@ If you are using AWS Bedrock, run:
27719
28589
  const timer = startWizardTimer("token-mapping", {
27720
28590
  sessionId: sessionId2,
27721
28591
  silent,
28592
+ noCache: effectiveNoCache,
27722
28593
  skipAgent: skipMapTokens
27723
28594
  });
27724
28595
  let mappablePropCount = 0;
@@ -27727,7 +28598,7 @@ If you are using AWS Bedrock, run:
27727
28598
  const db = openPipelineDb();
27728
28599
  try {
27729
28600
  if (state.tokenSessionId && state.tokenSessionId !== sessionId2) {
27730
- copyTokensBetweenSessions(db, state.tokenSessionId, sessionId2);
28601
+ copyTokensFromCache(db, state.tokenSessionId, sessionId2);
27731
28602
  } else if (!state.tokenSessionId && state.tokensPath) {
27732
28603
  const tokens = await readTokensFromPath("tokens", state.tokensPath);
27733
28604
  storeDtcgTokens(db, sessionId2, [], tokens);
@@ -27767,6 +28638,7 @@ If you are using AWS Bedrock, run:
27767
28638
  sessionId: sessionId2,
27768
28639
  agent: state.agent,
27769
28640
  ...state.agentModel ? { model: state.agentModel } : {},
28641
+ noCache: effectiveNoCache,
27770
28642
  skipAgent: skipMapTokens,
27771
28643
  promptOverrides,
27772
28644
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {}
@@ -27846,7 +28718,7 @@ If you are using AWS Bedrock, run:
27846
28718
  return promise;
27847
28719
  };
27848
28720
  const runExtract = async (projectPath, showProgress = true) => {
27849
- const outDir = join23(resolve17(projectPath), ".contentful");
28721
+ const outDir = join23(resolve18(projectPath), ".contentful");
27850
28722
  const existingEntitiesExpected = !state.credentialsSkipped && Boolean(state.spaceId && state.environmentId && state.cmaToken && projectPath);
27851
28723
  update({
27852
28724
  ...showProgress ? { step: "extracting" } : {},
@@ -27861,6 +28733,10 @@ If you are using AWS Bedrock, run:
27861
28733
  });
27862
28734
  const existingEntitiesPromise = startExistingEntitiesFetch(projectPath, outDir);
27863
28735
  const extractArgs = [findCliPath(), "__extract", "--project", projectPath];
28736
+ if (effectiveNoCache) extractArgs.push("--composition-refresh", "--no-cache");
28737
+ for (const p of promptOverrides ?? []) extractArgs.push("--prompt", p);
28738
+ if (state.agent) extractArgs.push("--agent", state.agent);
28739
+ if (state.bedrock) extractArgs.push("--bedrock");
27864
28740
  let selectionPromptText;
27865
28741
  let selectionPromptPath;
27866
28742
  try {
@@ -27869,7 +28745,7 @@ If you are using AWS Bedrock, run:
27869
28745
  const selectOverride = overrides.get("select");
27870
28746
  if (selectOverride?.kind === "text") selectionPromptText = selectOverride.value;
27871
28747
  if (selectOverride?.kind === "path") {
27872
- selectionPromptPath = resolve17(selectOverride.value);
28748
+ selectionPromptPath = resolve18(selectOverride.value);
27873
28749
  await resolvePromptOverride(selectOverride);
27874
28750
  }
27875
28751
  } catch (error) {
@@ -27893,7 +28769,8 @@ If you are using AWS Bedrock, run:
27893
28769
  agent: state.agent,
27894
28770
  ...state.agentModel ? { model: state.agentModel } : {},
27895
28771
  ...selectionPromptText !== void 0 ? { promptText: selectionPromptText } : {},
27896
- ...selectionPromptPath ? { promptPath: selectionPromptPath } : {}
28772
+ ...selectionPromptPath ? { promptPath: selectionPromptPath } : {},
28773
+ noCache: effectiveNoCache
27897
28774
  });
27898
28775
  })().catch((error) => {
27899
28776
  selectionError = error;
@@ -28038,17 +28915,136 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28038
28915
  generateProgress: null
28039
28916
  }));
28040
28917
  };
28041
- const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2) => ({
28918
+ const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => ({
28042
28919
  sessionId: extractSessionId,
28043
28920
  tokensPath,
28044
28921
  agent: state.agent,
28045
28922
  ...state.agentModel ? { model: state.agentModel } : {},
28046
28923
  ...state.bedrock ? { bedrock: true } : {},
28924
+ noCache: effectiveNoCache,
28047
28925
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {},
28048
28926
  ...generatePromptPath2 ? { generatePromptPath: generatePromptPath2 } : {},
28049
- promptOverrides
28927
+ promptOverrides,
28928
+ ...cachedComponents && cachedComponents.length > 0 ? { cachedComponents } : {}
28050
28929
  });
28051
- const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2));
28930
+ const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents));
28931
+ const buildGenerateCliArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateComponentsArgs(
28932
+ buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents)
28933
+ );
28934
+ const checkGenerateCache = async (extractSessionId, tokensPath) => {
28935
+ const timer = startWizardTimer("generation-cache-status", {
28936
+ sessionId: extractSessionId,
28937
+ noCache: effectiveNoCache
28938
+ });
28939
+ if (effectiveNoCache) {
28940
+ finishWizardTimer("generation-cache-status", timer, { status: "skipped" });
28941
+ return false;
28942
+ }
28943
+ const result = await runCli([...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"]);
28944
+ const hit = result.exitCode === 0 && /^cache-status=hit$/m.test(result.stdout);
28945
+ finishWizardTimer("generation-cache-status", timer, {
28946
+ status: hit ? "hit" : "miss",
28947
+ exitCode: result.exitCode,
28948
+ ...result.exitCode !== 0 ? { stderrTail: result.stderr.trim().slice(-500) } : {}
28949
+ });
28950
+ return hit;
28951
+ };
28952
+ const checkGenerateCacheComponents = async (extractSessionId, tokensPath, restoreCache = false) => {
28953
+ const timer = startWizardTimer("generation-cache-components", {
28954
+ sessionId: extractSessionId,
28955
+ restoreCache,
28956
+ noCache: effectiveNoCache
28957
+ });
28958
+ if (effectiveNoCache) {
28959
+ finishWizardTimer("generation-cache-components", timer, { status: "skipped" });
28960
+ return /* @__PURE__ */ new Set();
28961
+ }
28962
+ const args = [...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"];
28963
+ if (restoreCache) args.push("--restore-cache");
28964
+ const result = await runCli(args);
28965
+ if (result.exitCode !== 0) {
28966
+ finishWizardTimer("generation-cache-components", timer, {
28967
+ status: "error",
28968
+ exitCode: result.exitCode,
28969
+ stderrTail: result.stderr.trim().slice(-500)
28970
+ });
28971
+ return /* @__PURE__ */ new Set();
28972
+ }
28973
+ const line = /^cache-components=(.+)$/m.exec(result.stdout)?.[1];
28974
+ if (!line) {
28975
+ finishWizardTimer("generation-cache-components", timer, {
28976
+ status: "missing-output",
28977
+ exitCode: result.exitCode,
28978
+ cachedCount: 0
28979
+ });
28980
+ return /* @__PURE__ */ new Set();
28981
+ }
28982
+ try {
28983
+ const names = JSON.parse(line);
28984
+ const cachedNames = new Set(
28985
+ Array.isArray(names) ? names.filter((name) => typeof name === "string") : []
28986
+ );
28987
+ finishWizardTimer("generation-cache-components", timer, {
28988
+ status: "complete",
28989
+ exitCode: result.exitCode,
28990
+ cachedCount: cachedNames.size
28991
+ });
28992
+ return cachedNames;
28993
+ } catch {
28994
+ finishWizardTimer("generation-cache-components", timer, {
28995
+ status: "invalid-output",
28996
+ exitCode: result.exitCode,
28997
+ cachedCount: 0
28998
+ });
28999
+ return /* @__PURE__ */ new Set();
29000
+ }
29001
+ };
29002
+ useEffect15(() => {
29003
+ 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) {
29004
+ return;
29005
+ }
29006
+ const sessionId2 = state.extractSessionId;
29007
+ if (generationCachePreflightRef.current?.sessionId === sessionId2) return;
29008
+ const timer = startWizardTimer("generation-cache-preflight", { sessionId: sessionId2 });
29009
+ const db = openPipelineDb();
29010
+ let generatedNames = null;
29011
+ try {
29012
+ const rows = db.prepare("SELECT name, status FROM raw_components WHERE session_id = ?").all(sessionId2);
29013
+ if (rows.length > 0 && rows.every((row) => row.status === "generated")) {
29014
+ generatedNames = new Set(rows.map((row) => row.name));
29015
+ }
29016
+ } finally {
29017
+ db.close();
29018
+ }
29019
+ if (generatedNames) {
29020
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: Promise.resolve(generatedNames) };
29021
+ finishWizardTimer("generation-cache-preflight", timer, {
29022
+ status: "complete",
29023
+ source: "session-db",
29024
+ cachedCount: generatedNames.size
29025
+ });
29026
+ return;
29027
+ }
29028
+ const promise = checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
29029
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise };
29030
+ void promise.then((cachedNames) => {
29031
+ finishWizardTimer("generation-cache-preflight", timer, {
29032
+ status: "complete",
29033
+ source: "cache-status",
29034
+ cachedCount: cachedNames.size
29035
+ });
29036
+ });
29037
+ }, [
29038
+ state.step,
29039
+ state.extractSessionId,
29040
+ state.tokensPath,
29041
+ state.rawTokensPath,
29042
+ state.tokenGenerationStatus,
29043
+ state.selectionAgentStatus,
29044
+ state.compositionPhase,
29045
+ state.existingEntitiesStatus,
29046
+ state.existingEntitiesPath
29047
+ ]);
28052
29048
  const startGeneratePrefetch = (extractSessionId, tokensPath) => {
28053
29049
  const args = buildGenerateArgs(extractSessionId, tokensPath);
28054
29050
  let progressCursor = null;
@@ -28103,13 +29099,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28103
29099
  });
28104
29100
  return donePromise;
28105
29101
  };
28106
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false) => {
29102
+ const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false, cachedComponents = []) => {
28107
29103
  const timer = startWizardTimer("component-generation", {
28108
29104
  sessionId: extractSessionId,
28109
29105
  acceptedCount,
28110
- suppressScreen
29106
+ suppressScreen,
29107
+ noCache: effectiveNoCache
28111
29108
  });
28112
- const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath);
29109
+ const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath, cachedComponents);
28113
29110
  let progressCursor = state.generateProgress;
28114
29111
  const { donePromise } = spawnGenerateChild({
28115
29112
  command: "node",
@@ -28179,6 +29176,25 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28179
29176
  finishSelectionToReviewTimer("error");
28180
29177
  }
28181
29178
  };
29179
+ const finishCachedGeneration = async (sessionId2, acceptedCount) => {
29180
+ if (await runMapTokens2(sessionId2, true)) {
29181
+ finishSelectionToReviewTimer("cached");
29182
+ update(
29183
+ {
29184
+ acceptedCount,
29185
+ autoRejectedCount: 0,
29186
+ generateSessionId: sessionId2,
29187
+ generatedCount: acceptedCount,
29188
+ generateProgress: null,
29189
+ mapTokensStatus: "complete",
29190
+ step: "final-review"
29191
+ },
29192
+ { clearScreen: false }
29193
+ );
29194
+ } else {
29195
+ finishSelectionToReviewTimer("error");
29196
+ }
29197
+ };
28182
29198
  const advanceToPushFlow = (generatedAcceptedCount) => {
28183
29199
  update({ generatedAcceptedCount });
28184
29200
  void runSaveAndPush();
@@ -28363,8 +29379,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
28363
29379
  }
28364
29380
  }
28365
29381
  if (await runAgentAuthCheck("generating", true)) {
28366
- update({ step: "generating" });
28367
- void runGenerate(sid, state.tokensPath, state.acceptedCount);
29382
+ const cacheHit = await checkGenerateCache(sid, state.tokensPath);
29383
+ if (cacheHit) {
29384
+ update({ step: "generating" });
29385
+ void finishCachedGeneration(sid, state.acceptedCount);
29386
+ } else {
29387
+ update({ step: "generating" });
29388
+ void runGenerate(sid, state.tokensPath, state.acceptedCount);
29389
+ }
28368
29390
  }
28369
29391
  return;
28370
29392
  }
@@ -28728,7 +29750,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28728
29750
  });
28729
29751
  return { ok: false };
28730
29752
  }
28731
- await new Promise((resolve20) => setTimeout(resolve20, SAVE_CONFIRMATION_DELAY_MS));
29753
+ await new Promise((resolve22) => setTimeout(resolve22, SAVE_CONFIRMATION_DELAY_MS));
28732
29754
  update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
28733
29755
  return { ok: true };
28734
29756
  };
@@ -28805,6 +29827,12 @@ If using a custom host configuration, make sure the space exists on that host.`
28805
29827
  tokenReuseChecked.current = true;
28806
29828
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28807
29829
  const existingTokensPath = join23(state.outDir, "tokens.json");
29830
+ if (effectiveNoCache) {
29831
+ tokenGenerationPromiseRef.current = runAgentAuthCheck(tokenNextStep).then(
29832
+ (ok2) => ok2 ? runGenerateTokens(state.rawTokensPath, state.outDir) : false
29833
+ );
29834
+ return;
29835
+ }
28808
29836
  (async () => {
28809
29837
  try {
28810
29838
  await access7(existingTokensPath);
@@ -28888,7 +29916,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28888
29916
  const tokenNextStep = state.projectPath ? "path-validation" : "credentials";
28889
29917
  update({ step: tokenNextStep });
28890
29918
  if (await runAgentAuthCheck(tokenNextStep)) {
28891
- tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir);
29919
+ tokenGenerationPromiseRef.current = runGenerateTokens(state.rawTokensPath, state.outDir, true);
28892
29920
  }
28893
29921
  },
28894
29922
  onQuit: () => process.exit(0)
@@ -28922,7 +29950,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28922
29950
  );
28923
29951
  case "extracting": {
28924
29952
  const ep = state.extractProgress;
28925
- const compositionComplete = state.compositionPhase === "done";
29953
+ const compositionComplete = state.compositionPhase === "done" || state.compositionPhase === "cache-hit";
28926
29954
  const tokenGenerationComplete = !state.rawTokensPath || state.tokenGenerationStatus === "complete";
28927
29955
  const extractionTasksComplete = ep !== null && ep.totalFiles > 0 && compositionComplete && state.selectionAgentStatus === "complete" && tokenGenerationComplete && state.existingEntitiesStatus !== "running";
28928
29956
  if (extractionTasksComplete) return null;
@@ -28937,7 +29965,7 @@ If using a custom host configuration, make sure the space exists on that host.`
28937
29965
  extractDetail = "Scanning files...";
28938
29966
  }
28939
29967
  const selectionDetail = state.selectionAgentStatus === "complete" ? "Selection agent complete" : `Filtering component selection via ${state.agent}...`;
28940
- const compositionDetail = compositionComplete ? "Composition mapping complete" : "Mapping composition from source call sites...";
29968
+ 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}...`;
28941
29969
  return /* @__PURE__ */ jsx50(
28942
29970
  RunningStep,
28943
29971
  {
@@ -28987,6 +30015,29 @@ If using a custom host configuration, make sure the space exists on that host.`
28987
30015
  acceptedCount: decisions.accepted.length,
28988
30016
  rejectedCount: decisions.rejected.length
28989
30017
  }) : null;
30018
+ const preflight = generationCachePreflightRef.current;
30019
+ const cachePromise = preflight?.sessionId === sessionId2 ? preflight.promise : checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
30020
+ if (!preflight || preflight.sessionId !== sessionId2) {
30021
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: cachePromise };
30022
+ }
30023
+ const cacheWaitStartedAt = startWizardTimer("selection-cache-restoration-wait", {
30024
+ sessionId: sessionId2,
30025
+ preflight: Boolean(preflight?.sessionId === sessionId2)
30026
+ });
30027
+ try {
30028
+ const cachedNames = await cachePromise;
30029
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30030
+ status: "complete",
30031
+ cachedCount: cachedNames.size
30032
+ });
30033
+ } catch (error) {
30034
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
30035
+ status: "error",
30036
+ error: error instanceof Error ? error.message : String(error)
30037
+ });
30038
+ finishSelectionToReviewTimer("error");
30039
+ throw error;
30040
+ }
28990
30041
  await runScopeGate({
28991
30042
  sessionId: sessionId2,
28992
30043
  decisions,
@@ -28995,12 +30046,35 @@ If using a custom host configuration, make sure the space exists on that host.`
28995
30046
  if (shouldGenerateAfterScopeGate({ acceptedCount })) {
28996
30047
  const authPromise = runAgentAuthCheck("generating", true);
28997
30048
  if (await authPromise) {
28998
- update({
28999
- step: "generating",
29000
- generateProgress: null,
29001
- acceptedCount
30049
+ const comparisonStartedAt = startWizardTimer("generation-cache-set-comparison", {
30050
+ sessionId: sid,
30051
+ acceptedCount: decisions?.accepted.length ?? 0
30052
+ });
30053
+ let cachedNames = await cachePromise;
30054
+ const acceptedNames = decisions?.accepted ?? [];
30055
+ let cacheHit = !effectiveNoCache && acceptedNames.length > 0 && acceptedNames.every((name) => cachedNames.has(name));
30056
+ if (!cacheHit && !effectiveNoCache && acceptedNames.length > 0) {
30057
+ cachedNames = await checkGenerateCacheComponents(sid, state.tokensPath, true);
30058
+ cacheHit = acceptedNames.every((name) => cachedNames.has(name));
30059
+ }
30060
+ finishWizardTimer("generation-cache-set-comparison", comparisonStartedAt, {
30061
+ status: "complete",
30062
+ cacheHit,
30063
+ cachedCount: cachedNames.size,
30064
+ acceptedCount: acceptedNames.length
29002
30065
  });
29003
- void runGenerate(sid, state.tokensPath, acceptedCount);
30066
+ if (cacheHit) {
30067
+ void finishCachedGeneration(sid, acceptedCount);
30068
+ } else {
30069
+ const acceptedNameSet = new Set(acceptedNames);
30070
+ const restoredCachedNames = [...cachedNames].filter((name) => acceptedNameSet.has(name));
30071
+ update({
30072
+ step: "generating",
30073
+ generateProgress: null,
30074
+ acceptedCount
30075
+ });
30076
+ void runGenerate(sid, state.tokensPath, acceptedCount, false, restoredCachedNames);
30077
+ }
29004
30078
  } else {
29005
30079
  finishSelectionToReviewTimer("error");
29006
30080
  }
@@ -29366,7 +30440,7 @@ function registerImportCommand(program) {
29366
30440
  "Override a composition, selection, token, generation, or token-mapping prompt (repeatable). value is a file path or literal text, e.g. --prompt select=./p.md",
29367
30441
  (v, acc) => [...acc, v],
29368
30442
  []
29369
- ).action(async (opts) => {
30443
+ ).option("--no-cache", "Re-run all steps even if output already exists").action(async (opts) => {
29370
30444
  const interactiveTerminalSupported = getInteractiveTerminalSupport().supported;
29371
30445
  if (opts.tokens !== void 0) {
29372
30446
  const { access: access9 } = await import("node:fs/promises");
@@ -29403,6 +30477,7 @@ function registerImportCommand(program) {
29403
30477
  ...resolvedModel ? { initialModel: resolvedModel } : {},
29404
30478
  initialProjectPath: opts.project !== "." ? normalizePath(opts.project) : void 0,
29405
30479
  ...buildCompositionForwardingOptions(opts),
30480
+ noCache: opts.cache === false,
29406
30481
  skipMapTokens: false,
29407
30482
  livePreview: true,
29408
30483
  generatePromptPath: creds.generatePromptPath,
@@ -29490,7 +30565,7 @@ async function pathExists3(path) {
29490
30565
  return access8(path).then(() => true).catch(() => false);
29491
30566
  }
29492
30567
  function runSpawn(cmd, args, opts = {}) {
29493
- return new Promise((resolve20) => {
30568
+ return new Promise((resolve22) => {
29494
30569
  const child = spawn6(cmd, args, {
29495
30570
  cwd: opts.cwd,
29496
30571
  env: opts.env ?? process.env,
@@ -29502,7 +30577,7 @@ function runSpawn(cmd, args, opts = {}) {
29502
30577
  const settle = (result) => {
29503
30578
  if (settled) return;
29504
30579
  settled = true;
29505
- resolve20(result);
30580
+ resolve22(result);
29506
30581
  };
29507
30582
  child.on("error", (err) => settle({ exitCode: 1, stdout: "", stderr: err.message }));
29508
30583
  child.stdout.on("data", (d) => {
@@ -30703,13 +31778,13 @@ function PrerequisitesScreen({
30703
31778
  const driver = {
30704
31779
  ...base,
30705
31780
  emit: (event) => setEvents((current) => [...current, event]),
30706
- confirm: (question, defaultYes = true) => new Promise((resolve20) => {
31781
+ confirm: (question, defaultYes = true) => new Promise((resolve22) => {
30707
31782
  setConfirm({
30708
31783
  question,
30709
31784
  defaultYes,
30710
31785
  resolve: (answer) => {
30711
31786
  setConfirm(null);
30712
- resolve20(answer);
31787
+ resolve22(answer);
30713
31788
  }
30714
31789
  });
30715
31790
  })
@@ -30784,8 +31859,8 @@ function SetupScreen({
30784
31859
  useEffect21(() => {
30785
31860
  if (startedRef.current) return;
30786
31861
  startedRef.current = true;
30787
- const runScreen = (build) => new Promise((resolve20) => {
30788
- setScreen(build(resolve20));
31862
+ const runScreen = (build) => new Promise((resolve22) => {
31863
+ setScreen(build(resolve22));
30789
31864
  });
30790
31865
  void (async () => {
30791
31866
  const results = [];
@@ -30963,9 +32038,9 @@ var init_command8 = __esm({
30963
32038
  function rank(p) {
30964
32039
  if (p === "typed-slot") return 1;
30965
32040
  if (p === "structural") return 2;
30966
- if (p === "call-site") return 3;
30967
- if (p === "manifest") return 4;
30968
- if (p === "doc") return 5;
32041
+ if (p === "manifest") return 3;
32042
+ if (p === "doc") return 4;
32043
+ if (p.startsWith("adapter:")) return 5;
30969
32044
  return 6;
30970
32045
  }
30971
32046
  function mergeEdges(all) {
@@ -31001,6 +32076,65 @@ var init_merge_edges = __esm({
31001
32076
  }
31002
32077
  });
31003
32078
 
32079
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/parse-map-edges.ts
32080
+ function parseMapEdges(raw, opts) {
32081
+ const edges = [];
32082
+ const warnings = [];
32083
+ const { componentNames } = opts;
32084
+ for (const line of raw.split("\n")) {
32085
+ const trimmed = line.trim();
32086
+ if (!trimmed.startsWith("{")) continue;
32087
+ let obj;
32088
+ try {
32089
+ obj = JSON.parse(trimmed);
32090
+ } catch {
32091
+ warnings.push(`unparseable line: ${trimmed.slice(0, 120)}`);
32092
+ continue;
32093
+ }
32094
+ if (typeof obj !== "object" || obj === null || !("tool" in obj)) continue;
32095
+ const rec = obj;
32096
+ if (!VALID_MAP_TOOL_NAMES.has(rec.tool)) {
32097
+ warnings.push(`unknown tool: ${String(rec.tool)}`);
32098
+ continue;
32099
+ }
32100
+ if (typeof rec.parent !== "string" || !rec.parent || typeof rec.child !== "string" || !rec.child) {
32101
+ warnings.push("map_edge missing parent/child \u2014 skipped");
32102
+ continue;
32103
+ }
32104
+ const { parent, child } = rec;
32105
+ if (!componentNames.has(parent)) {
32106
+ warnings.push(`map_edge names unknown component (parent): ${parent} \u2014 skipped`);
32107
+ continue;
32108
+ }
32109
+ if (!componentNames.has(child)) {
32110
+ warnings.push(`map_edge names unknown component (child): ${child} \u2014 skipped`);
32111
+ continue;
32112
+ }
32113
+ const edge = { parent, child, provenance: "agent" };
32114
+ if (typeof rec.slot === "string" && rec.slot) {
32115
+ edge.slot = rec.slot;
32116
+ }
32117
+ if (rec.confidence !== void 0) {
32118
+ if (typeof rec.confidence === "number" && rec.confidence >= 1 && rec.confidence <= 5) {
32119
+ edge.confidence = rec.confidence;
32120
+ } else {
32121
+ warnings.push(
32122
+ `map_edge (${parent}\u2192${child}): invalid confidence ${String(rec.confidence)} \u2014 edge kept without confidence`
32123
+ );
32124
+ }
32125
+ }
32126
+ edges.push(edge);
32127
+ }
32128
+ return { edges, warnings };
32129
+ }
32130
+ var VALID_MAP_TOOL_NAMES;
32131
+ var init_parse_map_edges = __esm({
32132
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/parse-map-edges.ts"() {
32133
+ "use strict";
32134
+ VALID_MAP_TOOL_NAMES = /* @__PURE__ */ new Set(["map_edge"]);
32135
+ }
32136
+ });
32137
+
31004
32138
  // packages/experience-design-system-cli-legacy/src/analyze/composition/apply-mapping.ts
31005
32139
  function applyMapping(components, edges) {
31006
32140
  const warnings = [];
@@ -31060,9 +32194,44 @@ var init_apply_mapping = __esm({
31060
32194
  }
31061
32195
  });
31062
32196
 
32197
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/prompt-loader.ts
32198
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
32199
+ import { dirname as dirname17, join as join27, resolve as resolve19 } from "node:path";
32200
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
32201
+ function resolvePromptPath(fileName) {
32202
+ const thisDir = dirname17(fileURLToPath3(import.meta.url));
32203
+ let dir = thisDir;
32204
+ for (; ; ) {
32205
+ const candidate = join27(dir, "prompts");
32206
+ if (existsSync10(candidate)) return join27(candidate, fileName);
32207
+ const parent = resolve19(dir, "..");
32208
+ if (parent === dir) {
32209
+ throw new Error(
32210
+ `prompt file missing from CLI installation (could not locate prompts/ directory from: ${thisDir})`
32211
+ );
32212
+ }
32213
+ dir = parent;
32214
+ }
32215
+ }
32216
+ function loadPrompt(fileName) {
32217
+ const path = resolvePromptPath(fileName);
32218
+ try {
32219
+ return readFileSync10(path, "utf8");
32220
+ } catch {
32221
+ throw new Error(`prompt file missing from CLI installation \u2014 try reinstalling the CLI (looked for: ${path})`);
32222
+ }
32223
+ }
32224
+ var init_prompt_loader = __esm({
32225
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/prompt-loader.ts"() {
32226
+ "use strict";
32227
+ }
32228
+ });
32229
+
31063
32230
  // packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping.ts
31064
- function resolveMapping(input) {
32231
+ async function resolveMapping(input) {
32232
+ const componentNames = new Set(input.components.map((c2) => c2.name));
31065
32233
  const collected = [];
32234
+ const agentWarnings = [];
31066
32235
  for (const c2 of input.components) {
31067
32236
  for (const slot of c2.slots) {
31068
32237
  for (const child of slot.allowedComponents ?? []) {
@@ -31080,41 +32249,17 @@ function resolveMapping(input) {
31080
32249
  if (input.extraEdges) {
31081
32250
  collected.push(...input.extraEdges);
31082
32251
  }
31083
- for (const evidence of input.sourceCallSiteEvidence ?? []) {
31084
- collected.push({
31085
- parent: evidence.parent,
31086
- child: evidence.child,
31087
- ...evidence.slot ? { slot: evidence.slot } : {},
31088
- citation: { sourcePath: evidence.sourcePath, startLine: evidence.startLine, endLine: evidence.endLine },
31089
- provenance: "call-site"
31090
- });
31091
- }
31092
32252
  const coveredParents = new Set(collected.map((e) => e.parent));
31093
32253
  const residueParents = input.components.map((c2) => c2.name).filter((n) => !coveredParents.has(n));
31094
- const debug = getDebugLogger();
31095
- const countByProvenance = (edges) => {
31096
- const counts = {};
31097
- for (const edge of edges) counts[edge.provenance] = (counts[edge.provenance] ?? 0) + 1;
31098
- return counts;
31099
- };
31100
- debug.event("analyze", "composition.pre-merge", {
31101
- edgeCountsByProvenance: countByProvenance(collected),
31102
- coveredParents: [...coveredParents].sort(),
31103
- residueParents,
31104
- sourceCallSiteAccepted: input.sourceCallSiteEvidence?.length ?? 0,
31105
- sourceCallSiteRejected: input.sourceCallSiteRejections?.length ?? 0
31106
- });
32254
+ const shouldRunAgent = input.forceAgent || residueParents.length > 0 && input.files.length > 0;
32255
+ if (shouldRunAgent) {
32256
+ const prompt = input.buildPrompt ? input.buildPrompt(input.files, [...componentNames]) : defaultPrompt(input.files, [...componentNames], input.promptOverride);
32257
+ const raw = await input.runAgentFn({ prompt, files: input.files }) ?? "";
32258
+ const parsed = parseMapEdges(raw, { componentNames });
32259
+ collected.push(...parsed.edges);
32260
+ agentWarnings.push(...parsed.warnings);
32261
+ }
31107
32262
  const merged = mergeEdges(collected);
31108
- debug.event("analyze", "composition.merged", {
31109
- edgeCountsByProvenance: countByProvenance(merged.edges),
31110
- conflicts: merged.conflicts,
31111
- edges: merged.edges.map((edge) => ({
31112
- parent: edge.parent,
31113
- child: edge.child,
31114
- slot: edge.slot ?? null,
31115
- provenance: edge.provenance
31116
- }))
31117
- });
31118
32263
  const base = input.components.map((c2) => ({
31119
32264
  ...c2,
31120
32265
  slots: c2.slots.map((s) => {
@@ -31123,32 +32268,127 @@ function resolveMapping(input) {
31123
32268
  })
31124
32269
  }));
31125
32270
  const applied = applyMapping(base, merged.edges);
31126
- debug.event("analyze", "composition.applied", {
31127
- slots: applied.components.flatMap(
31128
- (component) => component.slots.filter((slot) => (slot.allowedComponents?.length ?? 0) > 0).map((slot) => ({ component: component.name, slot: slot.name, allowedComponents: slot.allowedComponents }))
31129
- ),
31130
- warnings: applied.warnings
31131
- });
31132
32271
  return {
31133
32272
  components: applied.components,
31134
32273
  edges: merged.edges,
31135
32274
  conflicts: merged.conflicts,
31136
- warnings: applied.warnings,
31137
- sourceCallSiteEvidence: [...input.sourceCallSiteEvidence ?? []],
31138
- sourceCallSiteRejections: [...input.sourceCallSiteRejections ?? []]
32275
+ warnings: [...agentWarnings, ...applied.warnings]
31139
32276
  };
31140
32277
  }
32278
+ function defaultPrompt(files, componentNames, promptOverride) {
32279
+ const fileBlocks = files.map((f) => `--- ${f.path} ---
32280
+ ${f.content}`).join("\n\n");
32281
+ const instruction = promptOverride?.trim() ? promptOverride.trim() : loadPrompt("composition-edges.md").trim();
32282
+ return [
32283
+ instruction,
32284
+ "",
32285
+ 'Emit one JSON object per line, each: {"tool":"map_edge","parent":"<Name>","child":"<Name>","slot"?:"<slot>","confidence"?:1-5,"reason":"<cite the file + declaration>"}.',
32286
+ "Use ONLY these exact component names (an edge naming anything else is dropped):",
32287
+ componentNames.join(", "),
32288
+ "",
32289
+ "Candidate files:",
32290
+ fileBlocks
32291
+ ].join("\n");
32292
+ }
31141
32293
  var init_resolve_mapping = __esm({
31142
32294
  "packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping.ts"() {
31143
32295
  "use strict";
31144
32296
  init_merge_edges();
32297
+ init_parse_map_edges();
31145
32298
  init_apply_mapping();
31146
- init_debug_logger();
32299
+ init_prompt_loader();
32300
+ }
32301
+ });
32302
+
32303
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping-cli.ts
32304
+ function resolveCompositionSources(opts) {
32305
+ return {
32306
+ forceAgent: opts.compositionRefresh === true || opts.noCache === true
32307
+ };
32308
+ }
32309
+ var init_resolve_mapping_cli = __esm({
32310
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/resolve-mapping-cli.ts"() {
32311
+ "use strict";
32312
+ }
32313
+ });
32314
+
32315
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/candidate-files.ts
32316
+ function matchReason(file) {
32317
+ const segments = file.path.split("/").filter((s) => s !== "");
32318
+ for (const pattern of CANDIDATE_NAME_PATTERNS) {
32319
+ if (segments.some((seg) => pattern.test(seg))) {
32320
+ return `name:${pattern.source.toLowerCase()}`;
32321
+ }
32322
+ }
32323
+ for (const marker of CANDIDATE_CONTENT_MARKERS) {
32324
+ if (file.content.includes(marker)) return `content:${marker}`;
32325
+ }
32326
+ return void 0;
32327
+ }
32328
+ function selectCandidateFiles(files) {
32329
+ const selected = [];
32330
+ const seen = /* @__PURE__ */ new Set();
32331
+ for (const file of files) {
32332
+ if (seen.has(file.path)) continue;
32333
+ const reason = matchReason(file);
32334
+ if (reason === void 0) continue;
32335
+ seen.add(file.path);
32336
+ selected.push({ path: file.path, content: file.content, reason });
32337
+ }
32338
+ return selected;
32339
+ }
32340
+ function capCandidatesToPromptBudget(files, budget = PROMPT_CANDIDATE_TOKEN_BUDGET) {
32341
+ const ordered = [...files].sort((a, b) => a.content.length - b.content.length || a.path.localeCompare(b.path));
32342
+ const kept = [];
32343
+ const dropped = [];
32344
+ let spent = 0;
32345
+ for (const file of ordered) {
32346
+ const cost = tokenCost(file);
32347
+ if (spent + cost > budget) {
32348
+ dropped.push(file);
32349
+ continue;
32350
+ }
32351
+ kept.push(file);
32352
+ spent += cost;
32353
+ }
32354
+ return { kept, dropped };
32355
+ }
32356
+ function tokenCost(file) {
32357
+ return Math.ceil(file.content.length / CHARS_PER_TOKEN);
32358
+ }
32359
+ var CANDIDATE_NAME_PATTERNS, CANDIDATE_CONTENT_MARKERS, PROMPT_CANDIDATE_TOKEN_BUDGET, CHARS_PER_TOKEN;
32360
+ var init_candidate_files = __esm({
32361
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/candidate-files.ts"() {
32362
+ "use strict";
32363
+ CANDIDATE_NAME_PATTERNS = [/mapping/i, /meta/i, /registry/i, /schema/i, /composition/i];
32364
+ CANDIDATE_CONTENT_MARKERS = [
32365
+ "requiredParent",
32366
+ "withParentType",
32367
+ "allowedTagNames",
32368
+ "createContext",
32369
+ "MappingContext",
32370
+ "allowedComponents"
32371
+ ];
32372
+ PROMPT_CANDIDATE_TOKEN_BUDGET = 8e4;
32373
+ CHARS_PER_TOKEN = 4;
32374
+ }
32375
+ });
32376
+
32377
+ // packages/experience-design-system-cli-legacy/src/analyze/composition/composition-cache-key.ts
32378
+ function buildCompositionInputHash(input) {
32379
+ const fileDigest = input.files.map((f) => `${f.path} ${hashContent(f.content)}`).sort().join("");
32380
+ return hashContent(`agent:${input.agent}
32381
+ ${fileDigest}`);
32382
+ }
32383
+ var init_composition_cache_key = __esm({
32384
+ "packages/experience-design-system-cli-legacy/src/analyze/composition/composition-cache-key.ts"() {
32385
+ "use strict";
32386
+ init_cache_keys();
31147
32387
  }
31148
32388
  });
31149
32389
 
31150
32390
  // packages/experience-design-system-cli-legacy/src/analyze/composition/manifest-doc-evidence.ts
31151
- import { dirname as dirname17 } from "node:path";
32391
+ import { dirname as dirname18 } from "node:path";
31152
32392
  function kebabToPascal2(name) {
31153
32393
  return name.split("-").filter(Boolean).map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1)).join("");
31154
32394
  }
@@ -31198,8 +32438,8 @@ function collectAgentsDocEdges(files, components, componentNames) {
31198
32438
  const seen = /* @__PURE__ */ new Set();
31199
32439
  for (const file of files) {
31200
32440
  if (!file.path.endsWith("AGENTS.md")) continue;
31201
- const docDir = dirname17(file.path);
31202
- const parent = components.find((c2) => c2.sourcePath && dirname17(c2.sourcePath) === docDir)?.name;
32441
+ const docDir = dirname18(file.path);
32442
+ const parent = components.find((c2) => c2.sourcePath && dirname18(c2.sourcePath) === docDir)?.name;
31203
32443
  if (!parent || !componentNames.has(parent)) continue;
31204
32444
  for (const line of file.content.split("\n")) {
31205
32445
  const lower = line.toLowerCase();
@@ -31229,172 +32469,12 @@ var init_manifest_doc_evidence = __esm({
31229
32469
  }
31230
32470
  });
31231
32471
 
31232
- // packages/experience-design-system-cli-legacy/src/analyze/composition/source-call-site-evidence.ts
31233
- import { Node as Node49, Project as Project11, SyntaxKind as SyntaxKind15 } from "ts-morph";
31234
- function collectSourceCallSiteEvidence(files, components) {
31235
- const componentNames = new Set(components.map((component) => component.name));
31236
- const componentsBySource = /* @__PURE__ */ new Map();
31237
- for (const component of components) {
31238
- const sourcePath = component.sourcePath ?? component.source;
31239
- const siblings = componentsBySource.get(sourcePath) ?? [];
31240
- siblings.push(component);
31241
- componentsBySource.set(sourcePath, siblings);
31242
- }
31243
- const project = new Project11({
31244
- compilerOptions: { jsx: 1, target: 99, module: 99, moduleResolution: 100, skipLibCheck: true, allowJs: true },
31245
- skipAddingFilesFromTsConfig: true
31246
- });
31247
- const sourceFiles = new Map(
31248
- files.map((file) => [file.path, project.createSourceFile(file.path, file.content, { overwrite: true })])
31249
- );
31250
- const accepted = [];
31251
- const rejected = [];
31252
- for (const [sourcePath, parents] of componentsBySource) {
31253
- const sourceFile = sourceFiles.get(sourcePath);
31254
- if (!sourceFile) continue;
31255
- const boundNames = collectBoundNames(sourceFile);
31256
- for (const parent of parents) {
31257
- for (const jsxElement of [
31258
- ...sourceFile.getDescendantsOfKind(SyntaxKind15.JsxSelfClosingElement),
31259
- ...sourceFile.getDescendantsOfKind(SyntaxKind15.JsxElement)
31260
- ]) {
31261
- const tagName = Node49.isJsxSelfClosingElement(jsxElement) ? jsxElement.getTagNameNode().getText() : jsxElement.getOpeningElement().getTagNameNode().getText();
31262
- const candidate = tagName.split(".").at(-1) ?? tagName;
31263
- if (candidate === parent.name || isIntrinsicTag(candidate)) continue;
31264
- const lines = sourceExcerpt(sourceFile, jsxElement);
31265
- if (!componentNames.has(candidate)) {
31266
- rejected.push({ parent: parent.name, candidate, ...lines, reason: "unknown-component" });
31267
- continue;
31268
- }
31269
- if (!boundNames.has(tagName) && !boundNames.has(candidate)) {
31270
- rejected.push({ parent: parent.name, candidate, ...lines, reason: "unbound-component" });
31271
- continue;
31272
- }
31273
- accepted.push({ parent: parent.name, child: candidate, ...lines, kind: "jsx-render" });
31274
- }
31275
- }
31276
- }
31277
- for (const sourceFile of sourceFiles.values()) {
31278
- collectCallerSlotEvidence(sourceFile, componentNames, collectBoundNames(sourceFile), accepted, rejected);
31279
- }
31280
- const verifiedPairs = new Set(accepted.map((evidence) => `${evidence.parent}\0${evidence.child}`));
31281
- for (const component of components) {
31282
- const candidates = component.slots.flatMap((slot) => [
31283
- ...slot.allowedComponents ?? [],
31284
- ...slot.structuralAllowedComponents ?? []
31285
- ]);
31286
- for (const candidate of candidates) {
31287
- if (verifiedPairs.has(`${component.name}\0${candidate}`)) continue;
31288
- rejected.push({
31289
- parent: component.name,
31290
- candidate,
31291
- sourcePath: component.sourcePath ?? component.source,
31292
- reason: "no-call-site"
31293
- });
31294
- }
31295
- }
31296
- return {
31297
- accepted: dedupeEvidence(accepted),
31298
- rejected: dedupeRejections(rejected)
31299
- };
31300
- }
31301
- function collectCallerSlotEvidence(sourceFile, componentNames, boundNames, accepted, rejected) {
31302
- for (const parentElement of sourceFile.getDescendantsOfKind(SyntaxKind15.JsxElement)) {
31303
- const parentTag = parentElement.getOpeningElement().getTagNameNode().getText();
31304
- const parent = parentTag.split(".").at(-1) ?? parentTag;
31305
- if (!componentNames.has(parent) || !boundNames.has(parentTag)) continue;
31306
- for (const childNode of parentElement.getJsxChildren()) {
31307
- if (!Node49.isJsxElement(childNode) && !Node49.isJsxSelfClosingElement(childNode)) {
31308
- if (Node49.isJsxText(childNode) && childNode.getText().trim()) {
31309
- const lines2 = sourceExcerpt(sourceFile, childNode);
31310
- rejected.push({ parent, candidate: "<text>", ...lines2, reason: "text-only-child" });
31311
- }
31312
- continue;
31313
- }
31314
- const childTag = Node49.isJsxElement(childNode) ? childNode.getOpeningElement().getTagNameNode().getText() : childNode.getTagNameNode().getText();
31315
- const child = childTag.split(".").at(-1) ?? childTag;
31316
- if (isIntrinsicTag(child)) continue;
31317
- const lines = sourceExcerpt(sourceFile, childNode);
31318
- if (!componentNames.has(child)) {
31319
- rejected.push({ parent, candidate: child, ...lines, reason: "unknown-component" });
31320
- continue;
31321
- }
31322
- if (!boundNames.has(childTag) && !boundNames.has(child)) {
31323
- rejected.push({ parent, candidate: child, ...lines, reason: "unbound-component" });
31324
- continue;
31325
- }
31326
- const slotAttribute = Node49.isJsxElement(childNode) ? childNode.getOpeningElement().getAttribute("slot") : childNode.getAttribute("slot");
31327
- const slotValue = slotAttribute && Node49.isJsxAttribute(slotAttribute) ? slotAttribute.getInitializer() : void 0;
31328
- const slot = slotValue && Node49.isStringLiteral(slotValue) ? slotValue.getLiteralValue() : void 0;
31329
- accepted.push({ parent, child, ...slot ? { slot } : {}, ...lines, kind: "jsx-render" });
31330
- }
31331
- }
31332
- }
31333
- function collectBoundNames(sourceFile) {
31334
- const names = /* @__PURE__ */ new Set();
31335
- for (const declaration of sourceFile.getImportDeclarations()) {
31336
- const defaultImport = declaration.getDefaultImport();
31337
- if (defaultImport) names.add(defaultImport.getText());
31338
- const namespaceImport = declaration.getNamespaceImport();
31339
- if (namespaceImport) names.add(namespaceImport.getText());
31340
- for (const namedImport of declaration.getNamedImports()) {
31341
- names.add((namedImport.getAliasNode() ?? namedImport.getNameNode()).getText());
31342
- }
31343
- }
31344
- for (const declaration of sourceFile.getVariableDeclarations()) names.add(declaration.getName());
31345
- for (const declaration of sourceFile.getFunctions()) {
31346
- if (declaration.getName()) names.add(declaration.getName());
31347
- }
31348
- for (const declaration of sourceFile.getClasses()) {
31349
- if (declaration.getName()) names.add(declaration.getName());
31350
- }
31351
- return names;
31352
- }
31353
- function sourceExcerpt(sourceFile, node) {
31354
- const startLine = node.getStartLineNumber();
31355
- const endLine = node.getEndLineNumber();
31356
- const lines = sourceFile.getFullText().split("\n");
31357
- return {
31358
- sourcePath: sourceFile.getFilePath(),
31359
- startLine,
31360
- endLine,
31361
- excerpt: lines.slice(startLine - 1, endLine).join("\n").trim()
31362
- };
31363
- }
31364
- function isIntrinsicTag(tagName) {
31365
- return /^[a-z]/.test(tagName) || tagName.includes("-");
31366
- }
31367
- function dedupeEvidence(evidence) {
31368
- const seen = /* @__PURE__ */ new Set();
31369
- return evidence.filter((item) => {
31370
- const key = `${item.parent}\0${item.child}\0${item.slot ?? ""}\0${item.sourcePath}\0${item.startLine}`;
31371
- if (seen.has(key)) return false;
31372
- seen.add(key);
31373
- return true;
31374
- }).sort(
31375
- (left, right) => `${left.parent}\0${left.child}\0${left.sourcePath}\0${left.startLine}`.localeCompare(
31376
- `${right.parent}\0${right.child}\0${right.sourcePath}\0${right.startLine}`
31377
- )
31378
- );
31379
- }
31380
- function dedupeRejections(rejections) {
31381
- const seen = /* @__PURE__ */ new Set();
31382
- return rejections.filter((item) => {
31383
- const key = `${item.parent}\0${item.candidate}\0${item.sourcePath}\0${item.startLine}`;
31384
- if (seen.has(key)) return false;
31385
- seen.add(key);
31386
- return true;
31387
- });
31388
- }
31389
- var init_source_call_site_evidence = __esm({
31390
- "packages/experience-design-system-cli-legacy/src/analyze/composition/source-call-site-evidence.ts"() {
31391
- "use strict";
31392
- }
31393
- });
31394
-
31395
32472
  // packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts
31396
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile18, stat as stat6 } from "node:fs/promises";
31397
- import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolve18 } from "node:path";
32473
+ import { mkdir as mkdir6, readdir as readdir3, readFile as readFile19, stat as stat6 } from "node:fs/promises";
32474
+ import { isAbsolute as isAbsolute2, join as join28, relative as relative2, resolve as resolve20 } from "node:path";
32475
+ function resolveExtractNoCache(opts) {
32476
+ return opts.noCache === true || opts.cache === false;
32477
+ }
31398
32478
  function isDenylistedNoiseFile(name) {
31399
32479
  return DENYLISTED_EXACT_FILE_NAMES.has(name) || DENYLISTED_FILE_NAME_PATTERNS.some((pattern) => pattern.test(name));
31400
32480
  }
@@ -31408,12 +32488,12 @@ async function retryDatabaseWrite(operation, attempts = 8) {
31408
32488
  } catch (error) {
31409
32489
  const message = error instanceof Error ? error.message : String(error);
31410
32490
  if (!/database is locked|database is busy/i.test(message) || attempt >= attempts) throw error;
31411
- await new Promise((resolve20) => setTimeout(resolve20, Math.min(250 * attempt, 2e3)));
32491
+ await new Promise((resolve22) => setTimeout(resolve22, Math.min(250 * attempt, 2e3)));
31412
32492
  }
31413
32493
  }
31414
32494
  }
31415
32495
  function resolveFromProjectRoot(projectRoot, inputPath) {
31416
- return isAbsolute2(inputPath) ? inputPath : resolve18(projectRoot, inputPath);
32496
+ return isAbsolute2(inputPath) ? inputPath : resolve20(projectRoot, inputPath);
31417
32497
  }
31418
32498
  async function pathExists4(path) {
31419
32499
  return Boolean(await stat6(path).catch(() => null));
@@ -31424,7 +32504,7 @@ async function collectSourceFiles(directory, onProgress) {
31424
32504
  const entries = await readdir3(currentDirectory, { withFileTypes: true });
31425
32505
  const subdirs = [];
31426
32506
  for (const entry of entries) {
31427
- const fullPath = join27(currentDirectory, entry.name);
32507
+ const fullPath = join28(currentDirectory, entry.name);
31428
32508
  if (entry.isDirectory()) {
31429
32509
  if (!IGNORED_DIRECTORY_NAMES.has(entry.name)) {
31430
32510
  subdirs.push(fullPath);
@@ -31451,6 +32531,12 @@ async function collectSourceFiles(directory, onProgress) {
31451
32531
  await visit(directory);
31452
32532
  return files.sort();
31453
32533
  }
32534
+ function resolveCompositionAgentName(flagValue) {
32535
+ if (flagValue && isAgentName(flagValue)) return flagValue;
32536
+ const env = process.env["EDS_COMPOSITION_AGENT"];
32537
+ if (env && isAgentName(env)) return env;
32538
+ return DEFAULT_AGENT_NAME;
32539
+ }
31454
32540
  async function readCandidateFiles(components, extraFiles = []) {
31455
32541
  const paths = new Set(extraFiles);
31456
32542
  for (const c2 of components) {
@@ -31460,7 +32546,7 @@ async function readCandidateFiles(components, extraFiles = []) {
31460
32546
  await Promise.all(
31461
32547
  [...paths].map(async (p) => {
31462
32548
  try {
31463
- const content = await readFile18(p, "utf8");
32549
+ const content = await readFile19(p, "utf8");
31464
32550
  out.push({ path: p, content });
31465
32551
  } catch {
31466
32552
  }
@@ -31469,11 +32555,21 @@ async function readCandidateFiles(components, extraFiles = []) {
31469
32555
  return out;
31470
32556
  }
31471
32557
  function registerInternalExtractCommand(program) {
31472
- 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(
32558
+ 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(
31473
32559
  "--resolve-unreachable <mode>",
31474
32560
  "Retry pass for unresolved Svelte Props types: 'auto' (default), 'always', or 'never'",
31475
32561
  "auto"
31476
- ).action(async (opts) => {
32562
+ ).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(
32563
+ "--prompt <stage=value>",
32564
+ "Override a stage prompt (repeatable). value is a file path or literal text, e.g. --prompt composition=./p.md",
32565
+ (v, acc) => [...acc, v],
32566
+ []
32567
+ );
32568
+ addAgentModelOptions(extractCmd, {
32569
+ includeModel: false,
32570
+ agentDescription: "Coding agent for composition mapping resolution (claude|codex|opencode|cursor)"
32571
+ }).action(async (opts) => {
32572
+ const noCache = resolveExtractNoCache(opts);
31477
32573
  const resolveUnreachable = (() => {
31478
32574
  const v = opts.resolveUnreachable ?? "auto";
31479
32575
  if (v !== "auto" && v !== "always" && v !== "never") {
@@ -31483,8 +32579,16 @@ function registerInternalExtractCommand(program) {
31483
32579
  }
31484
32580
  return v;
31485
32581
  })();
31486
- const projectRoot = resolve18(opts.project);
31487
- const outDir = join27(projectRoot, ".contentful");
32582
+ if (opts.bedrock) {
32583
+ const bedrockAgent = resolveCompositionAgentName(opts.agent);
32584
+ if (!agentSupportsBedrock(bedrockAgent)) {
32585
+ process.stderr.write(`Error: --bedrock is not supported for --agent ${bedrockAgent}
32586
+ `);
32587
+ process.exit(1);
32588
+ }
32589
+ }
32590
+ const projectRoot = resolve20(opts.project);
32591
+ const outDir = join28(projectRoot, ".contentful");
31488
32592
  let sourceDirectory;
31489
32593
  if (opts.dir !== void 0) {
31490
32594
  sourceDirectory = resolveFromProjectRoot(projectRoot, opts.dir);
@@ -31507,16 +32611,50 @@ function registerInternalExtractCommand(program) {
31507
32611
  process.stderr.write(`progress=scan-done:${sourceFiles.length}
31508
32612
  `);
31509
32613
  }
31510
- const extraction = await extractComponents(
31511
- sourceFiles,
31512
- ({ filesProcessed, componentsFound }) => {
32614
+ const extractionCacheDb = openPipelineDb();
32615
+ let extraction;
32616
+ let extractionCacheHits = 0;
32617
+ try {
32618
+ const cacheVersion = await getCliCacheVersion();
32619
+ const cachedByPath = /* @__PURE__ */ new Map();
32620
+ if (!noCache && sourceFiles.length > 0) {
32621
+ const hashes = await Promise.all(
32622
+ sourceFiles.map(async (filePath) => [filePath, await hashFile(filePath)])
32623
+ );
32624
+ for (const [filePath, fileHash] of hashes) {
32625
+ cachedByPath.set(filePath, lookupExtractCache(extractionCacheDb, fileHash, cacheVersion));
32626
+ }
32627
+ }
32628
+ const allFilesCached = !noCache && sourceFiles.length > 0 && sourceFiles.every((filePath) => cachedByPath.get(filePath) !== null);
32629
+ if (allFilesCached) {
32630
+ const cachedComponents = sourceFiles.flatMap((filePath) => cachedByPath.get(filePath).components);
32631
+ extractionCacheHits = sourceFiles.length;
32632
+ extraction = { components: cachedComponents, warnings: [], exclusions: [] };
31513
32633
  if (!process.stdout.isTTY) {
31514
- process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
31515
- `);
32634
+ process.stderr.write(
32635
+ `progress=extract:${sourceFiles.length}/${sourceFiles.length}:${cachedComponents.length}
32636
+ `
32637
+ );
31516
32638
  }
31517
- },
31518
- { resolveUnreachable, projectRoot }
31519
- );
32639
+ getDebugLogger().event("analyze", "extract.cache-hit", {
32640
+ files: sourceFiles.length,
32641
+ components: cachedComponents.length
32642
+ });
32643
+ } else {
32644
+ extraction = await extractComponents(
32645
+ sourceFiles,
32646
+ ({ filesProcessed, componentsFound }) => {
32647
+ if (!process.stdout.isTTY) {
32648
+ process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}
32649
+ `);
32650
+ }
32651
+ },
32652
+ { resolveUnreachable, projectRoot }
32653
+ );
32654
+ }
32655
+ } finally {
32656
+ extractionCacheDb.close();
32657
+ }
31520
32658
  await mkdir6(outDir, { recursive: true });
31521
32659
  const db = openPipelineDb();
31522
32660
  const { sessionId: sessionId2 } = getOrCreateSession(db, void 0, void 0, {
@@ -31531,6 +32669,25 @@ function registerInternalExtractCommand(program) {
31531
32669
  const stepId = createStep(db, sessionId2, "analyze extract", {
31532
32670
  project: projectRoot
31533
32671
  });
32672
+ if (extractionCacheHits === 0 && !noCache) {
32673
+ const cacheVersion = await getCliCacheVersion();
32674
+ const componentsBySourcePath = /* @__PURE__ */ new Map();
32675
+ for (const component of extraction.components) {
32676
+ if (!component.sourcePath) continue;
32677
+ const components = componentsBySourcePath.get(component.sourcePath) ?? [];
32678
+ components.push(component);
32679
+ componentsBySourcePath.set(component.sourcePath, components);
32680
+ }
32681
+ for (const filePath of sourceFiles) {
32682
+ storeExtractCache(
32683
+ db,
32684
+ filePath,
32685
+ await hashFile(filePath),
32686
+ cacheVersion,
32687
+ componentsBySourcePath.get(filePath) ?? []
32688
+ );
32689
+ }
32690
+ }
31534
32691
  const classifiedComponents = extraction.components.map(preClassifyComponent);
31535
32692
  for (const exclusion of extraction.exclusions ?? []) {
31536
32693
  getDebugLogger().event("filter", "extract.excluded", { ...exclusion });
@@ -31554,9 +32711,28 @@ function registerInternalExtractCommand(program) {
31554
32711
  storeRawComponents(db, sessionId2, validatedComponents);
31555
32712
  process.stdout.write(`session=${sessionId2}
31556
32713
  `);
31557
- let sourceCallSiteEvidence = [];
31558
- let sourceCallSiteRejections = [];
31559
32714
  {
32715
+ const sources = resolveCompositionSources({
32716
+ ...opts,
32717
+ noCache
32718
+ });
32719
+ const { overrides: promptOverrides, errors: promptErrors } = parsePromptOverrides(opts.prompt ?? []);
32720
+ for (const err of promptErrors) {
32721
+ process.stderr.write(`Error: ${err}
32722
+ `);
32723
+ process.exit(1);
32724
+ }
32725
+ let compositionPrompt;
32726
+ const compositionOverride = promptOverrides.get("composition");
32727
+ if (compositionOverride) {
32728
+ try {
32729
+ compositionPrompt = await resolvePromptOverride(compositionOverride);
32730
+ } catch (e) {
32731
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : String(e)}
32732
+ `);
32733
+ process.exit(1);
32734
+ }
32735
+ }
31560
32736
  {
31561
32737
  const emitCompositionProgress = (phase) => {
31562
32738
  if (!process.stdout.isTTY) process.stderr.write(`progress=composition:${phase}
@@ -31564,18 +32740,62 @@ function registerInternalExtractCommand(program) {
31564
32740
  };
31565
32741
  emitCompositionProgress("resolving");
31566
32742
  const allFiles = await readCandidateFiles(validatedComponents, sourceFiles);
32743
+ const selectedCandidates = selectCandidateFiles(allFiles).map((c2) => ({ path: c2.path, content: c2.content }));
32744
+ const capped = capCandidatesToPromptBudget(selectedCandidates);
32745
+ const promptFiles = capped.kept;
32746
+ if (capped.dropped.length > 0) {
32747
+ process.stderr.write(
32748
+ `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.
32749
+ `
32750
+ );
32751
+ }
31567
32752
  const runtimeFiles = allFiles.map((c2) => ({ path: c2.path, content: c2.content }));
32753
+ const resolverAgent = resolveCompositionAgentName(opts.agent);
31568
32754
  const componentNameSet = new Set(validatedComponents.map((c2) => c2.name));
32755
+ const cacheVersion = await getCliCacheVersion();
32756
+ let lastAgentExitCode = 0;
32757
+ const spawnAgent = async (prompt) => {
32758
+ const res = await runAgent({
32759
+ agent: resolverAgent,
32760
+ prompt,
32761
+ timeoutMs: 12e4,
32762
+ promptViaStdin: true,
32763
+ onDebugEvent: (name, payload) => getDebugLogger().event("agent", name, payload)
32764
+ });
32765
+ lastAgentExitCode = res.exitCode;
32766
+ if (res.exitCode !== 0 && res.stderr.trim()) {
32767
+ process.stderr.write(`Warning: composition \u2014 agent exited ${res.exitCode}: ${res.stderr.trim()}
32768
+ `);
32769
+ }
32770
+ return res.stdout;
32771
+ };
31569
32772
  const manifestDocEdges = collectManifestDocEdges(runtimeFiles, validatedComponents, componentNameSet);
31570
32773
  const extraEdges = manifestDocEdges;
31571
- const sourceCallSites = collectSourceCallSiteEvidence(runtimeFiles, validatedComponents);
31572
- sourceCallSiteEvidence = sourceCallSites.accepted;
31573
- sourceCallSiteRejections = sourceCallSites.rejected;
31574
- const result = resolveMapping({
32774
+ const agentCacheKey = buildCompositionInputHash({
32775
+ files: promptFiles,
32776
+ agent: resolverAgent
32777
+ });
32778
+ const result = await resolveMapping({
31575
32779
  components: validatedComponents,
31576
32780
  ...extraEdges.length > 0 ? { extraEdges } : {},
31577
- sourceCallSiteEvidence,
31578
- sourceCallSiteRejections
32781
+ forceAgent: sources.forceAgent,
32782
+ files: promptFiles,
32783
+ ...compositionPrompt ? { promptOverride: compositionPrompt } : {},
32784
+ runAgentFn: async ({ prompt }) => {
32785
+ if (!noCache && !opts.compositionRefresh) {
32786
+ const cached = lookupCompositionCache(db, agentCacheKey, cacheVersion);
32787
+ if (cached !== null) {
32788
+ emitCompositionProgress("cache-hit");
32789
+ return cached;
32790
+ }
32791
+ }
32792
+ emitCompositionProgress(`agent:${resolverAgent}`);
32793
+ const stdout = await spawnAgent(prompt);
32794
+ if (lastAgentExitCode === 0) {
32795
+ storeCompositionCache(db, agentCacheKey, cacheVersion, stdout);
32796
+ }
32797
+ return stdout;
32798
+ }
31579
32799
  });
31580
32800
  emitCompositionProgress("done");
31581
32801
  for (const w of result.warnings) process.stderr.write(`Warning: composition \u2014 ${w}
@@ -31605,13 +32825,7 @@ function registerInternalExtractCommand(program) {
31605
32825
  sessionId2,
31606
32826
  sourceFiles.map((f) => relative2(projectRoot, f))
31607
32827
  );
31608
- await retryDatabaseWrite(
31609
- () => updateStep(db, stepId, "complete", {
31610
- sessionId: sessionId2,
31611
- compositionEvidence: JSON.stringify(sourceCallSiteEvidence),
31612
- compositionRejections: JSON.stringify(sourceCallSiteRejections)
31613
- })
31614
- );
32828
+ await retryDatabaseWrite(() => updateStep(db, stepId, "complete", { sessionId: sessionId2 }));
31615
32829
  enrichCommandResult({ extracted_component_count: validatedComponents.length });
31616
32830
  db.close();
31617
32831
  const allWarnings = [...extraction.warnings, ...filterWarnings];
@@ -31633,12 +32847,18 @@ var SCANNED_FILE_EXTENSIONS, DENYLIST_GATED_EXTENSIONS, DENYLISTED_EXACT_FILE_NA
31633
32847
  var init_extract_command = __esm({
31634
32848
  "packages/experience-design-system-cli-legacy/src/analyze/extract-command.ts"() {
31635
32849
  "use strict";
32850
+ init_agent_model_options();
31636
32851
  init_src2();
31637
32852
  init_db();
32853
+ init_cache_keys();
31638
32854
  init_cycle_detection();
31639
32855
  init_resolve_mapping();
32856
+ init_resolve_mapping_cli();
32857
+ init_candidate_files();
32858
+ init_composition_cache_key();
31640
32859
  init_manifest_doc_evidence();
31641
- init_source_call_site_evidence();
32860
+ init_prompt_overrides();
32861
+ init_src();
31642
32862
  init_analytics();
31643
32863
  init_debug_logger();
31644
32864
  SCANNED_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".js", ".jsx", ".svelte", ".ts", ".tsx", ".vue"]);
@@ -31732,9 +32952,9 @@ __export(program_exports, {
31732
32952
  runBuild: () => runBuild
31733
32953
  });
31734
32954
  import { spawn as spawn7 } from "node:child_process";
31735
- import { fileURLToPath as fileURLToPath3 } from "node:url";
31736
- import { dirname as dirname18, join as join28, resolve as resolve19 } from "node:path";
31737
- import { readFileSync as readFileSync10 } from "node:fs";
32955
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
32956
+ import { dirname as dirname19, join as join29, resolve as resolve21 } from "node:path";
32957
+ import { readFileSync as readFileSync11 } from "node:fs";
31738
32958
  import { Command } from "commander";
31739
32959
  async function runBuild(opts) {
31740
32960
  return new Promise((resolvePromise) => {
@@ -31763,7 +32983,7 @@ function resolveBedrockFromAncestors(actionCommand) {
31763
32983
  }
31764
32984
  function registerBuildCommand(program) {
31765
32985
  program.command("build").description("Rebuild from source and re-link exo/experiences binaries to this build").helpOption(false).action(async () => {
31766
- const pkgRoot = resolve19(dirname18(fileURLToPath3(import.meta.url)), "..", "..");
32986
+ const pkgRoot = resolve21(dirname19(fileURLToPath4(import.meta.url)), "..", "..");
31767
32987
  process.stderr.write("\u2699 Building from source...\n");
31768
32988
  const { exitCode } = await runBuild({
31769
32989
  spawnFn: () => spawn7("pnpm", ["build"], { cwd: pkgRoot, stdio: "inherit" }),
@@ -31833,7 +33053,7 @@ var init_program = __esm({
31833
33053
  init_analytics();
31834
33054
  init_credentials_store();
31835
33055
  init_cli_path();
31836
- pkg3 = JSON.parse(readFileSync10(join28(findPkgRoot(), "package.json"), "utf8"));
33056
+ pkg3 = JSON.parse(readFileSync11(join29(findPkgRoot(), "package.json"), "utf8"));
31837
33057
  }
31838
33058
  });
31839
33059