@contentful/experience-design-system-cli 2.30.1-dev-build-87c8cf1.0 → 2.30.1-dev-build-6f93a80.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/README.md CHANGED
@@ -63,7 +63,7 @@ When more than one source speaks to the same relationship, the higher-precedence
63
63
 
64
64
  - `--composition-agent-mode <parser|edges>` — `parser` (default; the sandboxed-parser design above) or `edges` (agent lists relationships directly; less robust).
65
65
  - `--composition-refresh` — ignore the cache and re-resolve from scratch, forcing the agent to run.
66
- - `--agent <name>` — which coding agent authors the parser (`claude`, `codex`, `opencode`, `cursor`).
66
+ - `--agent <name>` — which coding agent authors the parser (`claude`, `codex`, `opencode`, `cursor`, `copilot`).
67
67
  - `--prompt composition=<file-or-text>` — override the composition stage's prompt.
68
68
 
69
69
  Because the agent path spawns a coding agent, it adds latency and cost and is best-effort (parser quality can vary run to run). For reproducible results, prefer `--composition-map`.
@@ -86,6 +86,7 @@ If the resolved graph contains a circular slot dependency (A slots B, B slots A)
86
86
  | **OpenAI Codex** (`codex`) | `npm install -g @openai/codex` | Set `OPENAI_API_KEY` |
87
87
  | **OpenCode** (`opencode`) | `npm install -g opencode-ai` | Configure via `opencode auth` (supports multiple providers) |
88
88
  | **Cursor** (`cursor`) | Install [Cursor](https://cursor.com) | Sign in to Cursor; exposes `cursor-agent` binary |
89
+ | **GitHub Copilot** (`copilot`) | `npm install -g @github/copilot` | Run `copilot` once to complete GitHub OAuth login. Free plan uses Auto model selection by default. Paid plans can pin a specific model via `EDS_AGENT_MODEL_COPILOT=<model-id>` (e.g. `claude-sonnet-4.6`) |
89
90
 
90
91
  The CLI invokes the agent non-interactively in a subprocess. If the binary is not found in `$PATH`, the command exits 1 and prints manual fallback instructions.
91
92
 
@@ -291,7 +292,7 @@ experiences analyze extract --project <path> [--dir <src-dir>] [composition flag
291
292
  | `--composition-agent` | — | Opt into agentic resolution when deterministic sources find no groups (implies `--composite`) |
292
293
  | `--composition-refresh` | — | Bypass the composition cache and re-resolve from scratch, forcing the agent to run (implies `--composite`) |
293
294
  | `--composition-agent-mode <mode>` | `parser` | `parser` (agent writes a sandboxed parser — deterministic) or `edges` (agent lists edges directly) |
294
- | `--agent <name>` | saved by setup | Coding agent for composition resolution: `claude`, `codex`, `opencode`, `cursor` |
295
+ | `--agent <name>` | saved by setup | Coding agent for composition resolution: `claude`, `codex`, `opencode`, `cursor`, `copilot` |
295
296
  | `--prompt <stage=value>` | — | Override a stage prompt (repeatable); value is a file path or literal text, e.g. `--prompt composition=./p.md` |
296
297
 
297
298
  Scans `.tsx`, `.ts`, `.jsx`, `.js`, `.vue`, and `.astro` files. Ignores `node_modules`, `dist`, `build`, `.next`, `.nuxt`, `coverage`, `storybook-static`, `out`, `demo(s)`, and `example(s)` directories. Also ignores `*.stories.*`, `*.story.*`, `*.spec.*`, and `*.test.*` files.
@@ -339,7 +340,7 @@ experiences analyze select-agent [--agent <name>] [--session <id>]
339
340
 
340
341
  | Option | Default | Description |
341
342
  |---|---|---|
342
- | `--agent <name>` | saved by `experiences setup` | Agent: `claude`, `codex`, `opencode`, or `cursor` |
343
+ | `--agent <name>` | saved by `experiences setup` | Agent: `claude`, `codex`, `opencode`, `cursor`, or `copilot` |
343
344
  | `--session <id>` | most recent completed `analyze extract` | Session ID from `analyze extract` |
344
345
  | `--project-root <path>` | `cwd` | Project root for resolving component source files |
345
346
  | `--model <name>` | agent default | Model to use |
@@ -368,7 +369,7 @@ experiences generate components [--agent <name>] [--session <id>]
368
369
 
369
370
  | Option | Default | Description |
370
371
  |---|---|---|
371
- | `--agent <name>` | saved by setup | Agent: `claude`, `codex`, `opencode`, or `cursor` |
372
+ | `--agent <name>` | saved by setup | Agent: `claude`, `codex`, `opencode`, `cursor`, or `copilot` |
372
373
  | `--session <id>` | most recent completed `analyze extract` | Session ID from `analyze extract` |
373
374
  | `--tokens <path>` | — | Path to `tokens.json` for token-linked prop resolution |
374
375
  | `--token-map <path>` | — | Path to `token-name-map.json` sidecar |
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.30.1-dev-build-87c8cf1.0",
3
+ "version": "2.30.1-dev-build-6f93a80.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/dist/src/index.js CHANGED
@@ -16,7 +16,7 @@ var AGENT_NAMES, DEFAULT_AGENT_NAME;
16
16
  var init_agent_names = __esm({
17
17
  "packages/experience-design-system-generation/dist/src/agent-names.js"() {
18
18
  "use strict";
19
- AGENT_NAMES = ["claude", "codex", "opencode", "cursor"];
19
+ AGENT_NAMES = ["claude", "codex", "opencode", "cursor", "copilot"];
20
20
  DEFAULT_AGENT_NAME = "claude";
21
21
  }
22
22
  });
@@ -295,6 +295,11 @@ function buildArgs(agent, prompt2, model, promptViaStdin = false) {
295
295
  return ["run", ...modelArg, ...promptArg];
296
296
  case "cursor":
297
297
  return ["--print", ...modelArg, ...promptArg];
298
+ case "copilot": {
299
+ const copilotModel = resolveAgentModel("copilot", model);
300
+ const copilotModelArg = copilotModel === "Auto" ? [] : ["--model", copilotModel];
301
+ return ["-p", ...promptArg, ...copilotModelArg, "--allow-all-tools"];
302
+ }
298
303
  }
299
304
  }
300
305
  async function runAgent(options) {
@@ -445,15 +450,18 @@ var init_agent_runner = __esm({
445
450
  claude: "claude",
446
451
  codex: "codex",
447
452
  opencode: "opencode",
448
- cursor: "cursor-agent"
453
+ cursor: "cursor-agent",
454
+ copilot: "copilot"
449
455
  };
450
456
  DEFAULT_MODELS = {
451
457
  claude: "haiku",
452
458
  codex: "gpt-5.4-mini",
453
459
  // requires OPENAI_API_KEY; ChatGPT account users must pass --model
454
460
  opencode: "claude-haiku-4-5",
455
- cursor: "gpt-mini"
461
+ cursor: "gpt-mini",
456
462
  // cursor alias for gpt-5.4-mini-medium; haiku not in cursor's catalog
463
+ copilot: "Auto"
464
+ // the only model guaranteed on every Copilot plan (Free/Pro/Business/Enterprise); Pro+ users override via EDS_AGENT_MODEL_COPILOT
457
465
  };
458
466
  }
459
467
  });
@@ -9574,26 +9582,6 @@ function loadRawComponents(db, sessionId2, allowedNames) {
9574
9582
  })
9575
9583
  );
9576
9584
  }
9577
- function rewrittenSourcePaths(basePath) {
9578
- for (const [emitted, sources] of TS_ESM_EXTENSION_REWRITES) {
9579
- if (!basePath.endsWith(emitted)) continue;
9580
- const stem = basePath.slice(0, -emitted.length);
9581
- return sources.map((ext) => `${stem}${ext}`);
9582
- }
9583
- return [];
9584
- }
9585
- function identifiersIn(propTypes) {
9586
- const names = /* @__PURE__ */ new Set();
9587
- for (const propType of propTypes) {
9588
- for (const match2 of propType.matchAll(IDENTIFIER_PATTERN)) names.add(match2[0]);
9589
- }
9590
- return [...names];
9591
- }
9592
- function declaresAnyType(text, typeNames) {
9593
- return typeNames.some(
9594
- (name) => new RegExp(`\\b(?:type|interface|enum|const|class)\\s+${escapeForRegExp(name)}\\b`).test(text)
9595
- );
9596
- }
9597
9585
  function extractRelativeImportPaths(sourceText) {
9598
9586
  const specifiers = /* @__PURE__ */ new Set();
9599
9587
  for (const match2 of sourceText.matchAll(RELATIVE_IMPORT_PATTERN)) {
@@ -9605,7 +9593,6 @@ async function resolveRelativeImport(specifier, fromDir) {
9605
9593
  const basePath = resolve7(fromDir, specifier);
9606
9594
  const candidates = [
9607
9595
  basePath,
9608
- ...rewrittenSourcePaths(basePath),
9609
9596
  ...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
9610
9597
  ...SIBLING_FILE_EXTENSIONS.map((ext) => resolve7(basePath, `index${ext}`))
9611
9598
  ];
@@ -9626,7 +9613,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
9626
9613
  }
9627
9614
  return resolvedPaths;
9628
9615
  }
9629
- async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateTypeNames = []) {
9616
+ async function loadSiblingFiles(sourceText, sourcePath, propNames) {
9630
9617
  const visited = /* @__PURE__ */ new Set([resolve7(sourcePath)]);
9631
9618
  const discovered = [];
9632
9619
  let frontier = [{ text: sourceText, dir: dirname7(sourcePath) }];
@@ -9651,16 +9638,9 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
9651
9638
  }
9652
9639
  frontier = nextFrontier;
9653
9640
  }
9654
- const inHand = [sourceText, ...discovered.map((d) => d.content)];
9655
- const declaredTypeNames = candidateTypeNames.filter((name) => inHand.some((text) => declaresAnyType(text, [name])));
9656
9641
  const usesNotShown = /* @__PURE__ */ new Set();
9657
- const searchNames = [.../* @__PURE__ */ new Set([...propNames, ...declaredTypeNames])];
9658
- let enlarged = 0;
9659
9642
  const siblings = discovered.slice(0, MAX_SIBLING_FILES).map((d) => {
9660
- const enlarge = enlarged < MAX_ENLARGED_SIBLINGS && declaresAnyType(d.content, declaredTypeNames);
9661
- if (enlarge) enlarged++;
9662
- const budget = enlarge ? MAX_TYPE_DECLARING_SIBLING_CHARS : MAX_SIBLING_SNIPPET_CHARS;
9663
- const excerpt = excerptAroundNames(d.content, searchNames, budget);
9643
+ const excerpt = excerptAroundNames(d.content, propNames, MAX_SIBLING_SNIPPET_CHARS);
9664
9644
  for (const name of excerpt.usesNotShown) usesNotShown.add(name);
9665
9645
  return { path: d.path, content: excerpt.content };
9666
9646
  });
@@ -9668,32 +9648,25 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
9668
9648
  return {
9669
9649
  siblings,
9670
9650
  truncatedCount,
9671
- usesNotShown: propNames.filter((name) => usesNotShown.has(name)),
9672
- declaredTypeNames
9651
+ usesNotShown: propNames.filter((name) => usesNotShown.has(name))
9673
9652
  };
9674
9653
  }
9675
- async function loadComponentSourceRef(name, sourcePath, propNames = [], propTypes = []) {
9654
+ async function loadComponentSourceRef(name, sourcePath, propNames = []) {
9676
9655
  let content = null;
9677
9656
  let siblingFiles = [];
9678
9657
  let truncatedSiblingCount = 0;
9679
9658
  const usesNotShown = /* @__PURE__ */ new Set();
9680
9659
  try {
9681
9660
  const rawText = await readFile7(sourcePath, "utf8");
9661
+ const mainExcerpt = excerptAroundNames(rawText, propNames, MAX_COMPONENT_SOURCE_CHARS);
9662
+ content = mainExcerpt.content;
9663
+ for (const name2 of mainExcerpt.usesNotShown) usesNotShown.add(name2);
9682
9664
  let siblingUsesNotShown;
9683
- let declaredTypeNames;
9684
9665
  ({
9685
9666
  siblings: siblingFiles,
9686
9667
  truncatedCount: truncatedSiblingCount,
9687
- usesNotShown: siblingUsesNotShown,
9688
- declaredTypeNames
9689
- } = await loadSiblingFiles(rawText, sourcePath, propNames, identifiersIn(propTypes)));
9690
- const mainExcerpt = excerptAroundNames(
9691
- rawText,
9692
- [.../* @__PURE__ */ new Set([...propNames, ...declaredTypeNames])],
9693
- MAX_COMPONENT_SOURCE_CHARS
9694
- );
9695
- content = mainExcerpt.content;
9696
- for (const name2 of mainExcerpt.usesNotShown) usesNotShown.add(name2);
9668
+ usesNotShown: siblingUsesNotShown
9669
+ } = await loadSiblingFiles(rawText, sourcePath, propNames));
9697
9670
  for (const name2 of siblingUsesNotShown) usesNotShown.add(name2);
9698
9671
  } catch {
9699
9672
  }
@@ -9708,18 +9681,13 @@ async function loadComponentSourceRefs(db, sessionId2) {
9708
9681
  const rows = db.prepare(
9709
9682
  `SELECT component_id, name, source, source_path FROM raw_components WHERE session_id = ? AND status = 'generated' ORDER BY rowid`
9710
9683
  ).all(sessionId2);
9711
- const propsFor = db.prepare(
9712
- `SELECT name, type FROM raw_props WHERE session_id = ? AND component_id = ? ORDER BY position`
9684
+ const propNamesFor = db.prepare(
9685
+ `SELECT name FROM raw_props WHERE session_id = ? AND component_id = ? ORDER BY position`
9713
9686
  );
9714
9687
  return Promise.all(
9715
9688
  rows.map((r) => {
9716
- const props = propsFor.all(sessionId2, r.component_id);
9717
- return loadComponentSourceRef(
9718
- r.name,
9719
- r.source_path ?? r.source,
9720
- props.map((p) => p.name),
9721
- props.map((p) => p.type)
9722
- );
9689
+ const propNames = propNamesFor.all(sessionId2, r.component_id).map((p) => p.name);
9690
+ return loadComponentSourceRef(r.name, r.source_path ?? r.source, propNames);
9723
9691
  })
9724
9692
  );
9725
9693
  }
@@ -10602,7 +10570,7 @@ function lookupSelectCache(db, componentHash, promptHash, cliVersion2) {
10602
10570
  updatedAt: row.updated_at
10603
10571
  };
10604
10572
  }
10605
- 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;
10573
+ 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;
10606
10574
  var init_db = __esm({
10607
10575
  "packages/experience-design-system-cli/src/session/db.ts"() {
10608
10576
  "use strict";
@@ -10788,19 +10756,10 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
10788
10756
  MAX_COMPONENT_SOURCE_CHARS = 8e3;
10789
10757
  MAX_SIBLING_FILES = 5;
10790
10758
  MAX_SIBLING_SNIPPET_CHARS = 1200;
10791
- MAX_TYPE_DECLARING_SIBLING_CHARS = 4e3;
10792
- MAX_ENLARGED_SIBLINGS = 2;
10793
10759
  MAX_SIBLING_DEPTH = 2;
10794
10760
  MAX_SIBLING_CANDIDATES_EXPLORED = 25;
10795
10761
  RELATIVE_IMPORT_PATTERN = /from\s+['"](\.[^'"]+)['"]/g;
10796
10762
  SIBLING_FILE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
10797
- TS_ESM_EXTENSION_REWRITES = [
10798
- [".js", [".ts", ".tsx", ".js", ".jsx"]],
10799
- [".jsx", [".tsx", ".jsx"]],
10800
- [".mjs", [".mts", ".mjs"]],
10801
- [".cjs", [".cts", ".cjs"]]
10802
- ];
10803
- IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
10804
10763
  _cliCacheVersionCache = null;
10805
10764
  }
10806
10765
  });
@@ -32517,8 +32476,7 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
32517
32476
  const sourceRef = await loadComponentSourceRef(
32518
32477
  component.name,
32519
32478
  component.sourcePath ?? component.source,
32520
- component.props.map((p) => p.name),
32521
- component.props.map((p) => p.type)
32479
+ component.props.map((p) => p.name)
32522
32480
  );
32523
32481
  const prompt2 = await buildPrompt({
32524
32482
  skill: "components",
@@ -32759,8 +32717,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
32759
32717
  const sampleSourceRef = sampleComponent ? await loadComponentSourceRef(
32760
32718
  sampleComponent.name,
32761
32719
  sampleComponent.sourcePath ?? sampleComponent.source,
32762
- sampleComponent.props.map((p) => p.name),
32763
- sampleComponent.props.map((p) => p.type)
32720
+ sampleComponent.props.map((p) => p.name)
32764
32721
  ) : void 0;
32765
32722
  const prompt2 = await buildPrompt({
32766
32723
  skill,
@@ -36126,7 +36083,8 @@ async function setupBuild(repoRoot) {
36126
36083
  var AGENT_DEFS = [
36127
36084
  { name: "Claude Code", binary: "claude", installHint: "npm install -g @anthropic-ai/claude-code && claude login" },
36128
36085
  { name: "OpenAI Codex", binary: "codex", installHint: "npm install -g @openai/codex (requires OPENAI_API_KEY)" },
36129
- { name: "OpenCode", binary: "opencode", installHint: "npm install -g opencode-ai && opencode auth" }
36086
+ { name: "OpenCode", binary: "opencode", installHint: "npm install -g opencode-ai && opencode auth" },
36087
+ { name: "GitHub Copilot", binary: "copilot", installHint: "npm install -g @github/copilot && copilot" }
36130
36088
  ];
36131
36089
  function pick(items, defaultIdx = 0) {
36132
36090
  items.forEach((item, i) => {
@@ -36161,7 +36119,7 @@ async function promptCodexModel() {
36161
36119
  return void 0;
36162
36120
  }
36163
36121
  async function setupAgent() {
36164
- section("Step 4: Coding agent (claude, codex, or opencode)", "[required]");
36122
+ section("Step 4: Coding agent (claude, codex, opencode, or copilot)", "[required]");
36165
36123
  info("experiences import uses a coding agent to generate component definitions.");
36166
36124
  info("");
36167
36125
  const found = (await Promise.all(AGENT_DEFS.map(async (a) => await binaryExists(a.binary) ? a : null))).filter(
@@ -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.30.1-dev-build-87c8cf1.0",
3
+ "version": "2.30.1-dev-build-6f93a80.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,8 +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-cli-v2": "2.30.1-dev-build-87c8cf1.0",
43
- "@contentful/experience-design-system-types": "2.30.1-dev-build-87c8cf1.0"
42
+ "@contentful/experience-design-system-cli-v2": "2.30.1-dev-build-6f93a80.0",
43
+ "@contentful/experience-design-system-types": "2.30.1-dev-build-6f93a80.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@tsconfig/node24": "^24.0.4",
@@ -52,9 +52,9 @@
52
52
  "ink-testing-library": "^4.0.0",
53
53
  "typescript-eslint": "^8.67.0",
54
54
  "vitest": "^4.0.16",
55
- "@contentful/experience-design-system-client": "2.30.1-dev-build-87c8cf1.0",
56
- "@contentful/experience-design-system-extraction": "2.30.1-dev-build-87c8cf1.0",
57
- "@contentful/experience-design-system-generation": "2.30.1-dev-build-87c8cf1.0"
55
+ "@contentful/experience-design-system-extraction": "2.30.1-dev-build-6f93a80.0",
56
+ "@contentful/experience-design-system-generation": "2.30.1-dev-build-6f93a80.0",
57
+ "@contentful/experience-design-system-client": "2.30.1-dev-build-6f93a80.0"
58
58
  },
59
59
  "repository": {
60
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.
@@ -208,14 +208,12 @@ the default "could be overridden" (every default can), or because the tie-break
208
208
  below exists. Nothing downstream re-derives, corrects, or second-guesses this —
209
209
  what you emit is what ships.
210
210
 
211
- **Ambiguity resolves to `enum` — but only when Q1–Q3 cannot be answered, and only
212
- when you can still cite its values.** The tie-break applies solely when the source
213
- shown does not let you answer the three questions: the property is interpolated
214
- in one place and looked up in another, the use sits in a file you cannot see, or
215
- the source is truncated at the point of use (the "uses not shown" line). In those
216
- cases emit `enum` if a value set is citable from the source shown, else `cdf_type:
217
- "string"` per "no legal `enum` with a guessed list" — either way, record the
218
- ambiguity in `reason`. When the three
211
+ **Ambiguity resolves to `enum` — but only when Q1–Q3 cannot be answered.** The
212
+ tie-break applies solely when the source shown does not let you answer the three
213
+ questions: the property is interpolated in one place and looked up in another,
214
+ the use sits in a file you cannot see, or the source is truncated at the point
215
+ of use (the "uses not shown" line). In those cases emit `enum` (or `string` when
216
+ it has no value set) and record the ambiguity in `reason`. When the three
219
217
  questions *can* be answered from the source shown, they are answered and the
220
218
  tie-break does not apply: a clear Q1-yes is `enum`, a clear Q2-yes + Q3-yes is
221
219
  `token`, and a type annotation does not create ambiguity. An `enum` is delivered
@@ -299,7 +297,7 @@ A prop with a complex TypeScript type is **not automatically excluded**. Many pr
299
297
  | Raw type pattern | How to resolve |
300
298
  |---|---|
301
299
  | `'primary' \| 'secondary' \| 'ghost'` (union of literals) | → `enum`, extract `values` |
302
- | `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum` if you can cite its values from the source shown (the type's own declaration, a lookup table, a default) — copy them, never invent them. If the source shown does not enumerate the values, this is not the Q1–Q3 ambiguity tie-break — it is the "no legal `enum` with a guessed list" rule: emit `cdf_type: "string"` instead, with the missing evidence noted in `reason`. Do not default to a generic guess like `['sm', 'md', 'lg']` or `['primary', 'secondary']` — those are one design system's vocabulary, not a fallback for missing evidence. |
300
+ | `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum`. **Never invent a value list.** Use only values you can cite from the source shown (the type's own declaration, a lookup table, a default). If the source shown does not enumerate the values, this is the "cannot be answered" case — apply "Ambiguity resolves to `enum`": emit `enum` (or `string` if it has no discoverable value set) and record the missing evidence in `description` (not `reason` — `description` ships to the CDF, `reason` does not), e.g. `"WARNING: values not visible in source — enum values unconfirmed"`. Do not default to a generic guess like `['sm', 'md', 'lg']` or `['primary', 'secondary']` — those are one design system's vocabulary, not a fallback for missing evidence. |
303
301
  | `Variant` / `variant` prop | Usually a visual design variant. → `enum`, `cdf_category: "design"`. Same evidence rule as above — cite values from the source shown; do not invent them. |
304
302
  | `Section[]` / array of custom items where the structure is unclear | → `exclude_prop` only if the array elements are complex objects with no obvious flat representation. If items are simple (title, label, id), consider representing as `string` (a comma-separated IDs or keys) or note in `description` why. |
305
303
  | `ExperienceConfiguration<Variant>` / deep generic | Personalization config — → `exclude_prop`, reason: `"personalization configuration — framework internal"` |
@@ -464,30 +462,14 @@ footer is supplementary and optional
464
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."}
465
463
  ```
466
464
 
467
- ### Named type (HeadingSize, ButtonVariant, etc.) — do NOT infer the values
465
+ ### Named type (HeadingSize, ButtonVariant, etc.)
468
466
 
469
- When a prop has a named TypeScript type that is not inlined as a union literal,
470
- the value set is whatever that type's declaration says. A type *name* is not
471
- evidence of its members: `HeadingSize` could be `h1`–`h6`, `sm|md|lg`, or
472
- `s|m|l|xl`. Guessing picks one design system's vocabulary and is wrong more
473
- often than it is right, and a wrong name matches no style rule — the author's
474
- setting silently does nothing.
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.
475
468
 
476
- So: look for the declaration in the source shown. If it is there, copy it. If it
477
- is not, **emit `string`** — `string` is an unrestricted enum, so the author stays
478
- unrestricted but correct.
479
-
480
- Members visible in the source shown — copy them, do not paraphrase:
481
- ```
482
- titleSize has type HeadingSize; HeadingSize is declared in the excerpt as 'sm' | 'md' | 'lg'
483
- {"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."}
484
- ```
485
-
486
- Members NOT visible in the source shown — degrade to `string`:
487
469
  ```
488
- titleSize has type HeadingSize; HeadingSize is not declared anywhere in the source shown
489
- no evidence for its members, so no values list — string, not a guess
490
- {"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"}
491
473
  ```
492
474
 
493
475
  ### href prop
@@ -513,7 +495,7 @@ Before emitting any tool calls, verify:
513
495
  1. Every prop in the input has exactly one `classify_prop` or `exclude_prop` call
514
496
  2. Every slot has exactly one `classify_slot` call
515
497
  3. `classify_component` is emitted exactly once
516
- 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
517
499
  5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed) and does **not** have `values` set
518
500
  6. No `cdf_type: "link"` — all href/url props use `string`
519
501
  7. `required` values are JSON booleans, not strings
@@ -525,7 +507,7 @@ Before emitting any tool calls, verify:
525
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.).
526
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`.
527
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.
528
- 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.
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`.
529
511
 
530
512
  After the run completes, the developer can validate the pipeline output with:
531
513
 
@@ -546,6 +528,4 @@ properties — this is never acceptable.
546
528
  If you are genuinely uncertain about every prop, classify each as:
547
529
  {"tool":"classify_prop","prop":"<name>","cdf_type":"string","cdf_category":"content","required":false,"description":"Uncertain classification — review recommended"}
548
530
 
549
- An imperfect classification is infinitely better than no classification. That is
550
- about **classifying a prop rather than dropping it** — it is not a licence to
551
- invent a `values` list. The uncertain answer is `cdf_type: "string"`, every time.
531
+ An imperfect classification is infinitely better than no classification.