@contentful/experience-design-system-cli 2.28.1-dev-build-a917752.0 → 2.28.1-dev-build-2497ca0.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.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.28.1-dev-build-a917752.0",
3
+ "version": "2.28.1-dev-build-2497ca0.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -38,6 +38,7 @@
38
38
  "lint:fix": "nx lint:fix experience-design-system-cli"
39
39
  },
40
40
  "dependencies": {
41
+ "@contentful/experience-design-system-cli-v2": "workspace:*",
41
42
  "@contentful/experience-design-system-types": "workspace:*",
42
43
  "@segment/analytics-node": "^3.0.0",
43
44
  "@vue/compiler-sfc": "^3.5.31",
package/dist/src/index.js CHANGED
@@ -46,7 +46,6 @@ function findJsonObjectEnd(line) {
46
46
  function readToolCallObjects(stdout) {
47
47
  const objects = [];
48
48
  const warnings = [];
49
- let hadDroppedLine = false;
50
49
  for (const raw of stdout.split("\n")) {
51
50
  const line = raw.trim();
52
51
  if (!line.startsWith("{"))
@@ -54,7 +53,6 @@ function readToolCallObjects(stdout) {
54
53
  const end = findJsonObjectEnd(line);
55
54
  if (end === -1) {
56
55
  warnings.push(`unparseable line: ${line.slice(0, 120)}`);
57
- hadDroppedLine = true;
58
56
  continue;
59
57
  }
60
58
  let parsed;
@@ -62,7 +60,6 @@ function readToolCallObjects(stdout) {
62
60
  parsed = JSON.parse(line.slice(0, end + 1));
63
61
  } catch {
64
62
  warnings.push(`unparseable line: ${line.slice(0, 120)}`);
65
- hadDroppedLine = true;
66
63
  continue;
67
64
  }
68
65
  const trailing = line.slice(end + 1);
@@ -71,11 +68,9 @@ function readToolCallObjects(stdout) {
71
68
  }
72
69
  if (typeof parsed === "object" && parsed !== null && "tool" in parsed) {
73
70
  objects.push(parsed);
74
- } else {
75
- hadDroppedLine = true;
76
71
  }
77
72
  }
78
- return { objects, warnings, hadDroppedLine };
73
+ return { objects, warnings };
79
74
  }
80
75
  function parseSelectToolCallLines(stdout) {
81
76
  const calls = [];
@@ -106,7 +101,7 @@ function parseSelectToolCallLines(stdout) {
106
101
  }
107
102
  function parseToolCallLines(stdout) {
108
103
  const calls = [];
109
- const { objects, warnings, hadDroppedLine } = readToolCallObjects(stdout);
104
+ const { objects, warnings } = readToolCallObjects(stdout);
110
105
  for (const rec of objects) {
111
106
  if (!VALID_TOOL_NAMES.has(rec.tool)) {
112
107
  warnings.push(`unknown tool: ${String(rec.tool)}`);
@@ -191,7 +186,7 @@ function parseToolCallLines(stdout) {
191
186
  calls.push(call);
192
187
  }
193
188
  }
194
- return { calls, warnings, hadDroppedLine };
189
+ return { calls, warnings };
195
190
  }
196
191
  function parseTokenToolCallLines(stdout) {
197
192
  const calls = [];
@@ -9579,26 +9574,6 @@ function loadRawComponents(db, sessionId2, allowedNames) {
9579
9574
  })
9580
9575
  );
9581
9576
  }
9582
- function rewrittenSourcePaths(basePath) {
9583
- for (const [emitted, sources] of TS_ESM_EXTENSION_REWRITES) {
9584
- if (!basePath.endsWith(emitted)) continue;
9585
- const stem = basePath.slice(0, -emitted.length);
9586
- return sources.map((ext) => `${stem}${ext}`);
9587
- }
9588
- return [];
9589
- }
9590
- function identifiersIn(propTypes) {
9591
- const names = /* @__PURE__ */ new Set();
9592
- for (const propType of propTypes) {
9593
- for (const match2 of propType.matchAll(IDENTIFIER_PATTERN)) names.add(match2[0]);
9594
- }
9595
- return [...names];
9596
- }
9597
- function declaresAnyType(text, typeNames) {
9598
- return typeNames.some(
9599
- (name) => new RegExp(`\\b(?:type|interface|enum|const|class)\\s+${escapeForRegExp(name)}\\b`).test(text)
9600
- );
9601
- }
9602
9577
  function extractRelativeImportPaths(sourceText) {
9603
9578
  const specifiers = /* @__PURE__ */ new Set();
9604
9579
  for (const match2 of sourceText.matchAll(RELATIVE_IMPORT_PATTERN)) {
@@ -9610,7 +9585,6 @@ async function resolveRelativeImport(specifier, fromDir) {
9610
9585
  const basePath = resolve7(fromDir, specifier);
9611
9586
  const candidates = [
9612
9587
  basePath,
9613
- ...rewrittenSourcePaths(basePath),
9614
9588
  ...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
9615
9589
  ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve7(basePath, `index${ext}`))
9616
9590
  ];
@@ -9631,7 +9605,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
9631
9605
  }
9632
9606
  return resolvedPaths;
9633
9607
  }
9634
- async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateTypeNames = []) {
9608
+ async function loadSiblingFiles(sourceText, sourcePath, propNames) {
9635
9609
  const visited = /* @__PURE__ */ new Set([resolve7(sourcePath)]);
9636
9610
  const discovered = [];
9637
9611
  let frontier = [{ text: sourceText, dir: dirname7(sourcePath) }];
@@ -9656,16 +9630,9 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
9656
9630
  }
9657
9631
  frontier = nextFrontier;
9658
9632
  }
9659
- const inHand = [sourceText, ...discovered.map((d) => d.content)];
9660
- const declaredTypeNames = candidateTypeNames.filter((name) => inHand.some((text) => declaresAnyType(text, [name])));
9661
9633
  const usesNotShown = /* @__PURE__ */ new Set();
9662
- const searchNames = [.../* @__PURE__ */ new Set([...propNames, ...declaredTypeNames])];
9663
- let enlarged = 0;
9664
9634
  const siblings = discovered.slice(0, MAX_SIBLING_FILES).map((d) => {
9665
- const enlarge = enlarged < MAX_ENLARGED_SIBLINGS && declaresAnyType(d.content, declaredTypeNames);
9666
- if (enlarge) enlarged++;
9667
- const budget = enlarge ? MAX_TYPE_DECLARING_SIBLING_CHARS : MAX_SIBLING_SNIPPET_CHARS;
9668
- const excerpt = excerptAroundNames(d.content, searchNames, budget);
9635
+ const excerpt = excerptAroundNames(d.content, propNames, MAX_SIBLING_SNIPPET_CHARS);
9669
9636
  for (const name of excerpt.usesNotShown) usesNotShown.add(name);
9670
9637
  return { path: d.path, content: excerpt.content };
9671
9638
  });
@@ -9673,32 +9640,25 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
9673
9640
  return {
9674
9641
  siblings,
9675
9642
  truncatedCount,
9676
- usesNotShown: propNames.filter((name) => usesNotShown.has(name)),
9677
- declaredTypeNames
9643
+ usesNotShown: propNames.filter((name) => usesNotShown.has(name))
9678
9644
  };
9679
9645
  }
9680
- async function loadComponentSourceRef(name, sourcePath, propNames = [], propTypes = []) {
9646
+ async function loadComponentSourceRef(name, sourcePath, propNames = []) {
9681
9647
  let content = null;
9682
9648
  let siblingFiles = [];
9683
9649
  let truncatedSiblingCount = 0;
9684
9650
  const usesNotShown = /* @__PURE__ */ new Set();
9685
9651
  try {
9686
9652
  const rawText = await readFile7(sourcePath, "utf8");
9653
+ const mainExcerpt = excerptAroundNames(rawText, propNames, MAX_COMPONENT_SOURCE_CHARS);
9654
+ content = mainExcerpt.content;
9655
+ for (const name2 of mainExcerpt.usesNotShown) usesNotShown.add(name2);
9687
9656
  let siblingUsesNotShown;
9688
- let declaredTypeNames;
9689
9657
  ({
9690
9658
  siblings: siblingFiles,
9691
9659
  truncatedCount: truncatedSiblingCount,
9692
- usesNotShown: siblingUsesNotShown,
9693
- declaredTypeNames
9694
- } = await loadSiblingFiles(rawText, sourcePath, propNames, identifiersIn(propTypes)));
9695
- const mainExcerpt = excerptAroundNames(
9696
- rawText,
9697
- [.../* @__PURE__ */ new Set([...propNames, ...declaredTypeNames])],
9698
- MAX_COMPONENT_SOURCE_CHARS
9699
- );
9700
- content = mainExcerpt.content;
9701
- for (const name2 of mainExcerpt.usesNotShown) usesNotShown.add(name2);
9660
+ usesNotShown: siblingUsesNotShown
9661
+ } = await loadSiblingFiles(rawText, sourcePath, propNames));
9702
9662
  for (const name2 of siblingUsesNotShown) usesNotShown.add(name2);
9703
9663
  } catch {
9704
9664
  }
@@ -9713,18 +9673,13 @@ async function loadComponentSourceRefs(db, sessionId2) {
9713
9673
  const rows = db.prepare(
9714
9674
  `SELECT component_id, name, source, source_path FROM raw_components WHERE session_id = ? AND status = 'generated' ORDER BY rowid`
9715
9675
  ).all(sessionId2);
9716
- const propsFor = db.prepare(
9717
- `SELECT name, type FROM raw_props WHERE session_id = ? AND component_id = ? ORDER BY position`
9676
+ const propNamesFor = db.prepare(
9677
+ `SELECT name FROM raw_props WHERE session_id = ? AND component_id = ? ORDER BY position`
9718
9678
  );
9719
9679
  return Promise.all(
9720
9680
  rows.map((r) => {
9721
- const props = propsFor.all(sessionId2, r.component_id);
9722
- return loadComponentSourceRef(
9723
- r.name,
9724
- r.source_path ?? r.source,
9725
- props.map((p) => p.name),
9726
- props.map((p) => p.type)
9727
- );
9681
+ const propNames = propNamesFor.all(sessionId2, r.component_id).map((p) => p.name);
9682
+ return loadComponentSourceRef(r.name, r.source_path ?? r.source, propNames);
9728
9683
  })
9729
9684
  );
9730
9685
  }
@@ -10607,7 +10562,7 @@ function lookupSelectCache(db, componentHash, promptHash, cliVersion2) {
10607
10562
  updatedAt: row.updated_at
10608
10563
  };
10609
10564
  }
10610
- 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;
10565
+ var SCHEMA, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS, MAX_SIBLING_DEPTH, MAX_SIBLING_CANDIDATES_EXPLORED, RELATIVE_IMPORT_PATTERN, SIBLING_FILE_EXTENSIONS, _cliCacheVersionCache;
10611
10566
  var init_db = __esm({
10612
10567
  "packages/experience-design-system-cli/src/session/db.ts"() {
10613
10568
  "use strict";
@@ -10793,19 +10748,10 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
10793
10748
  MAX_COMPONENT_SOURCE_CHARS = 8e3;
10794
10749
  MAX_SIBLING_FILES = 5;
10795
10750
  MAX_SIBLING_SNIPPET_CHARS = 1200;
10796
- MAX_TYPE_DECLARING_SIBLING_CHARS = 4e3;
10797
- MAX_ENLARGED_SIBLINGS = 2;
10798
10751
  MAX_SIBLING_DEPTH = 2;
10799
10752
  MAX_SIBLING_CANDIDATES_EXPLORED = 25;
10800
10753
  RELATIVE_IMPORT_PATTERN = /from\s+['"](\.[^'"]+)['"]/g;
10801
10754
  SIBLING_FILE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
10802
- TS_ESM_EXTENSION_REWRITES = [
10803
- [".js", [".ts", ".tsx", ".js", ".jsx"]],
10804
- [".jsx", [".tsx", ".jsx"]],
10805
- [".mjs", [".mts", ".mjs"]],
10806
- [".cjs", [".cts", ".cjs"]]
10807
- ];
10808
- IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
10809
10755
  _cliCacheVersionCache = null;
10810
10756
  }
10811
10757
  });
@@ -32522,8 +32468,7 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
32522
32468
  const sourceRef = await loadComponentSourceRef(
32523
32469
  component.name,
32524
32470
  component.sourcePath ?? component.source,
32525
- component.props.map((p) => p.name),
32526
- component.props.map((p) => p.type)
32471
+ component.props.map((p) => p.name)
32527
32472
  );
32528
32473
  const prompt2 = await buildPrompt({
32529
32474
  skill: "components",
@@ -32571,16 +32516,11 @@ ${outputBuf}`);
32571
32516
  lastError = describeAgentFailure(result);
32572
32517
  continue;
32573
32518
  }
32574
- const { calls, warnings, hadDroppedLine } = parseToolCallLines(result.stdout);
32519
+ const { calls, warnings } = parseToolCallLines(result.stdout);
32575
32520
  if (calls.length === 0) {
32576
32521
  lastError = describeAgentFailure(result);
32577
32522
  continue;
32578
32523
  }
32579
- if (hadDroppedLine) {
32580
- lastError = `agent output dropped at least one tool-call line: ${warnings.join("; ").slice(0, 200)}`;
32581
- if (attempt < maxAttempts) continue;
32582
- break;
32583
- }
32584
32524
  const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings);
32585
32525
  if (!noCache) {
32586
32526
  const inputHash = computeComponentInputHash(component);
@@ -32769,8 +32709,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
32769
32709
  const sampleSourceRef = sampleComponent ? await loadComponentSourceRef(
32770
32710
  sampleComponent.name,
32771
32711
  sampleComponent.sourcePath ?? sampleComponent.source,
32772
- sampleComponent.props.map((p) => p.name),
32773
- sampleComponent.props.map((p) => p.type)
32712
+ sampleComponent.props.map((p) => p.name)
32774
32713
  ) : void 0;
32775
32714
  const prompt2 = await buildPrompt({
32776
32715
  skill,
@@ -32846,11 +32785,6 @@ async function runGenerateSkill(skill, opts, verbose = false) {
32846
32785
  `Error: all ${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
32847
32786
  );
32848
32787
  }
32849
- if (failed.length > 0) {
32850
- die2(
32851
- `Error: ${failed.length}/${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
32852
- );
32853
- }
32854
32788
  } else if (skill === "tokens") {
32855
32789
  const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
32856
32790
  const tokenInputContent = rawTokensInline ?? "";
@@ -33478,7 +33412,7 @@ import { basename as basename5, resolve as resolve19 } from "node:path";
33478
33412
 
33479
33413
  // packages/experience-design-system-cli/src/print/validate/validators/cdf-validator.ts
33480
33414
  import { readFile as readFile19 } from "node:fs/promises";
33481
- import { validateCDF as validateCDF2, checkCDFComponentInvariants } from "@contentful/experience-design-system-types";
33415
+ import { validateCDF as validateCDF2 } from "@contentful/experience-design-system-types";
33482
33416
  function extractValue(input, path) {
33483
33417
  if (path === "/") return void 0;
33484
33418
  const parts = path.split("/").filter(Boolean);
@@ -33562,14 +33496,6 @@ async function validateCDFFile(filePath) {
33562
33496
  }).map((e) => rewriteDiagnostic(e, parsed));
33563
33497
  return { valid: false, summary: "", diagnostics };
33564
33498
  }
33565
- const invariantErrors = checkCDFComponentInvariants(cdfResult.components);
33566
- if (invariantErrors.length > 0) {
33567
- return {
33568
- valid: false,
33569
- summary: "",
33570
- diagnostics: invariantErrors.map((e) => rewriteDiagnostic(e, parsed))
33571
- };
33572
- }
33573
33499
  const count = cdfResult.components.length;
33574
33500
  return {
33575
33501
  valid: true,
@@ -36898,6 +36824,15 @@ async function runBuild(opts) {
36898
36824
  child.on("exit", (code) => settle(code ?? 1));
36899
36825
  });
36900
36826
  }
36827
+ function registerImportV2Command(program) {
36828
+ program.command("importv2").description("Launch the v2 import TUI (experience-design-system-cli-v2)").action(async () => {
36829
+ const { render: render7 } = await import("ink");
36830
+ const { createElement: createElement7 } = await import("react");
36831
+ const { App: App2 } = await import("@contentful/experience-design-system-cli-v2/app");
36832
+ const { waitUntilExit } = render7(createElement7(App2));
36833
+ await waitUntilExit();
36834
+ });
36835
+ }
36901
36836
  function registerBuildCommand(program) {
36902
36837
  program.command("build").description("Rebuild from source and re-link exo/experiences binaries to this build").action(async () => {
36903
36838
  const pkgRoot = resolve26(dirname14(fileURLToPath5(import.meta.url)), "..", "..");
@@ -36918,6 +36853,7 @@ function createProgram() {
36918
36853
  registerApplyCommand(program);
36919
36854
  registerSessionCommand(program);
36920
36855
  registerImportCommand(program);
36856
+ registerImportV2Command(program);
36921
36857
  registerSetupCommand(program);
36922
36858
  registerRunsCommand(program);
36923
36859
  registerBuildCommand(program);
@@ -107,7 +107,7 @@ export type RawComponentWithId = RawComponentDefinition & {
107
107
  component_id: string;
108
108
  };
109
109
  export declare function loadRawComponents(db: DatabaseSync, sessionId: string, allowedNames?: Set<string>): RawComponentWithId[];
110
- export declare function loadComponentSourceRef(name: string, sourcePath: string, propNames?: string[], propTypes?: string[]): Promise<ComponentSourceRef>;
110
+ export declare function loadComponentSourceRef(name: string, sourcePath: string, propNames?: string[]): Promise<ComponentSourceRef>;
111
111
  export declare function loadComponentSourceRefs(db: DatabaseSync, sessionId: string): Promise<ComponentSourceRef[]>;
112
112
  export declare function renameEmptySlots(db: DatabaseSync, sessionId: string, componentId: string, componentName: string, slotCount: number): {
113
113
  renames: Array<{
@@ -16,7 +16,6 @@ export interface SourceExcerpt {
16
16
  */
17
17
  usesNotShown: string[];
18
18
  }
19
- export declare function escapeForRegExp(value: string): string;
20
19
  /**
21
20
  * Returns `text` whole when it fits `maxChars`. Otherwise returns windows of
22
21
  * `contextLines` lines around every line that mentions one of `names`, merged
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.28.1-dev-build-a917752.0",
3
+ "version": "2.28.1-dev-build-2497ca0.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,7 +39,8 @@
39
39
  "svelte": "^5.56.4",
40
40
  "ts-morph": "^27.0.2",
41
41
  "typescript": "^5.9.3",
42
- "@contentful/experience-design-system-types": "2.28.1-dev-build-a917752.0"
42
+ "@contentful/experience-design-system-types": "2.28.1-dev-build-2497ca0.0",
43
+ "@contentful/experience-design-system-cli-v2": "2.28.1-dev-build-2497ca0.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@tsconfig/node24": "^24.0.4",
@@ -51,9 +52,9 @@
51
52
  "ink-testing-library": "^4.0.0",
52
53
  "typescript-eslint": "^8.67.0",
53
54
  "vitest": "^4.0.16",
54
- "@contentful/experience-design-system-extraction": "2.28.1-dev-build-a917752.0",
55
- "@contentful/experience-design-system-client": "2.28.1-dev-build-a917752.0",
56
- "@contentful/experience-design-system-generation": "2.28.1-dev-build-a917752.0"
55
+ "@contentful/experience-design-system-client": "2.28.1-dev-build-2497ca0.0",
56
+ "@contentful/experience-design-system-extraction": "2.28.1-dev-build-2497ca0.0",
57
+ "@contentful/experience-design-system-generation": "2.28.1-dev-build-2497ca0.0"
57
58
  },
58
59
  "repository": {
59
60
  "type": "git",
@@ -91,7 +91,7 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
91
91
  - Every prop in the input must produce exactly one call: `classify_prop` OR `exclude_prop`.
92
92
  - Every slot must produce exactly one `classify_slot` call.
93
93
  - Emit `classify_component` once at the start (required). The `description` field is **required** — always provide a brief description of the component's purpose.
94
- - `values` is required for `cdf_type: "enum"` — must be a non-empty string array of the variant names the prop accepts. **There is no legal `enum` with a guessed list.** If the value set is shown to you anywhere in the material below — the type's own declaration, an `allowedValues` list on the raw prop, a variant/lookup map, a default, a sibling excerpt — copy it and emit `enum`. If it is shown nowhere, emit `cdf_type: "string"` and leave `values` off. `string` is an unrestricted enum: the author can still pass the real value. A guessed list silently rejects it.
94
+ - `values` is required for `cdf_type: "enum"` — must be a non-empty string array of the variant names the prop accepts.
95
95
  - **Do NOT include `values` for `cdf_type: "token"`.** The two property types carry different kinds of list. An `enum` prop's list holds *variant names* the component accepts (`"primary"`, `"secondary"`). A `token` prop's list holds *design token paths* (`"color.brand.primary"`), produced by the separate token-mapping step (`$token.allowed`) — never by you. Emitting `values` on a token prop makes the definition invalid.
96
96
  - `token_kind` is required for `cdf_type: "token"` — must be a DTCG `$type` string, e.g. `"color"`.
97
97
  - `required` must be a JSON boolean (`true`/`false`), not a string.
@@ -364,7 +364,7 @@ as enum so the author picks "primary" and the component receives "primary".
364
364
 
365
365
  ---
366
366
 
367
- ## Category and required corrections
367
+ ## Category correction rules
368
368
 
369
369
  The pre-classified `category` is wrong in predictable ways. Correct silently (document in `description`):
370
370
 
@@ -375,11 +375,6 @@ The pre-classified `category` is wrong in predictable ways. Correct silently (do
375
375
 
376
376
  > **Key question for category**: "Who fills this in?" — A content editor fills in `content`. A designer configures `design`. Neither fills in `state` — it comes from routing, runtime behavior, or component infrastructure.
377
377
 
378
- **`required` is also a hint, and it is also wrong.** Verify it against the declaration:
379
- - A prop with a default value — a `$default`, a destructure default, or a `defaultVariants` entry — is **never** `required: true`. The two together are incoherent.
380
- - A prop declared `foo?:` is `required: false`, whatever the hint says.
381
- - Props reached through `VariantProps<typeof x>` or a Radix/`ComponentPropsWithoutRef` base are optional unless the component's own interface redeclares them as required.
382
-
383
378
  ---
384
379
 
385
380
  ## Slot classification
@@ -467,30 +462,14 @@ footer is supplementary and optional
467
462
  {"tool":"classify_slot","slot":"footer","required":false,"description":"Optional footer area for actions or metadata","rationale":"Footer is a supplementary region typically used for actions or metadata. Optional because most cards do not need one and the card renders correctly without it."}
468
463
  ```
469
464
 
470
- ### Named type (HeadingSize, ButtonVariant, etc.) — do NOT infer the values
471
-
472
- When a prop has a named TypeScript type that is not inlined as a union literal,
473
- the value set is whatever that type's declaration says. A type *name* is not
474
- evidence of its members: `HeadingSize` could be `h1`–`h6`, `sm|md|lg`, or
475
- `s|m|l|xl`. Guessing picks one design system's vocabulary and is wrong more
476
- often than it is right, and a wrong name matches no style rule — the author's
477
- setting silently does nothing.
478
-
479
- So: look for the declaration in the source shown. If it is there, copy it. If it
480
- is not, **emit `string`** — `string` is an unrestricted enum, so the author stays
481
- unrestricted but correct.
465
+ ### Named type (HeadingSize, ButtonVariant, etc.)
482
466
 
483
- Members visible in the source shown — copy them, do not paraphrase:
484
- ```
485
- titleSize has type HeadingSize; HeadingSize is declared in the excerpt as 'sm' | 'md' | 'lg'
486
- {"tool":"classify_prop","prop":"titleSize","cdf_type":"enum","cdf_category":"design","required":false,"values":["sm","md","lg"],"description":"Heading size","reason":"Copied from the HeadingSize declaration shown in the excerpt."}
487
- ```
467
+ When a prop has a named TypeScript type that is not inlined as a union literal, reason from the prop name and type name to infer the finite value set.
488
468
 
489
- Members NOT visible in the source shown — degrade to `string`:
490
469
  ```
491
- titleSize has type HeadingSize; HeadingSize is not declared anywhere in the source shown
492
- no evidence for its members, so no values list — string, not a guess
493
- {"tool":"classify_prop","prop":"titleSize","cdf_type":"string","cdf_category":"design","required":false,"description":"Heading size","reason":"HeadingSize is not declared in the source shown, so its members cannot be cited. Emitted as string rather than inventing a value list."}
470
+ titleSize has type HeadingSize — this is a named enum controlling heading size
471
+ inferring likely values: ["h1", "h2", "h3", "h4", "h5", "h6"] — documenting inference
472
+ {"tool":"classify_prop","prop":"titleSize","cdf_type":"enum","cdf_category":"design","required":false,"values":["h1","h2","h3","h4","h5","h6"],"description":"Heading level — inferred from HeadingSize type name; actual values may be h1–h6 or sm/md/lg"}
494
473
  ```
495
474
 
496
475
  ### href prop
@@ -516,7 +495,7 @@ Before emitting any tool calls, verify:
516
495
  1. Every prop in the input has exactly one `classify_prop` or `exclude_prop` call
517
496
  2. Every slot has exactly one `classify_slot` call
518
497
  3. `classify_component` is emitted exactly once
519
- 4. Every `cdf_type: "enum"` has a non-empty `values` array taken from the material shown. A prop whose value set was shown nowhere is `cdf_type: "string"` with no `values` — never an `enum` with a plausible-looking list, and never an `exclude_prop`.
498
+ 4. Every `cdf_type: "enum"` has a non-empty `values` array
520
499
  5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed) and does **not** have `values` set
521
500
  6. No `cdf_type: "link"` — all href/url props use `string`
522
501
  7. `required` values are JSON booleans, not strings
@@ -528,8 +507,7 @@ Before emitting any tool calls, verify:
528
507
  13. `rationale.description` follows the same "Description content rules" as `description` — no internal initiative names (`INTEG-*`, `EDSI`, `DSI`, `M1`, `M2`, wave/phase references, etc.).
529
508
  14. Every `cdf_type: "token"` prop cites a line that interpolates its value into a style **and** a token reference at that use (Q2 yes, Q3 yes), and carries no `values`. If you cannot cite both, it is `enum` or `string`; if you can, it is `token` — its type annotation is not a reason to emit `string`.
530
509
  15. Every prop whose values are friendly names is `enum` with a non-empty `values` array, even when each name resolves to a design token internally; every prop for which Q1–Q3 could not be answered was emitted as `enum` (or `string`) with the ambiguity stated in `reason`, and no answerable prop was called ambiguous.
531
- 16. Every `enum`'s `values` array is copied from evidence in the material shown — a type declaration, an `allowedValues` list on the raw prop, a variant or lookup map, a default, in the component's own file or in any sibling excerpt — and none of it was invented. Where the value set appears nowhere, the prop is `cdf_type: "string"`, with the reason recorded in `reason`. Do not drop a prop merely because you could not enumerate its values.
532
- 17. No prop has `required: true` alongside a default value (`$default`, a destructure default, or a `defaultVariants` entry) — the two are incoherent. A prop declared `foo?:`, or reached through `VariantProps<typeof x>` or a Radix/`ComponentPropsWithoutRef` base without its own required redeclaration, is `required: false`.
510
+ 16. Every `enum`'s `values` array is copied from evidence in the source shown (a type declaration, lookup table, or default) — none of it was invented. Where no evidence exists for the values, the `description` states so (e.g. `"WARNING: values not visible in source"`), not just `reason`.
533
511
 
534
512
  After the run completes, the developer can validate the pipeline output with:
535
513
 
@@ -550,6 +528,4 @@ properties — this is never acceptable.
550
528
  If you are genuinely uncertain about every prop, classify each as:
551
529
  {"tool":"classify_prop","prop":"<name>","cdf_type":"string","cdf_category":"content","required":false,"description":"Uncertain classification — review recommended"}
552
530
 
553
- An imperfect classification is infinitely better than no classification. That is
554
- about **classifying a prop rather than dropping it** — it is not a licence to
555
- invent a `values` list. The uncertain answer is `cdf_type: "string"`, every time.
531
+ An imperfect classification is infinitely better than no classification.