@contentful/experience-design-system-cli 2.26.5-dev-build-505215b.0 → 2.26.5

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.26.5-dev-build-505215b.0",
3
+ "version": "2.26.5",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -46,7 +46,7 @@
46
46
  "react": "^18.3.1",
47
47
  "react-devtools-core": "^4.19.1",
48
48
  "react-dom": "^18.3.1",
49
- "svelte": "^5.57.0",
49
+ "svelte": "^5.56.4",
50
50
  "ts-morph": "^27.0.2",
51
51
  "typescript": "^5.9.3"
52
52
  },
package/dist/src/index.js CHANGED
@@ -118,16 +118,6 @@ function parseToolCallLines(stdout) {
118
118
  if (typeof rec.reason === "string")
119
119
  call.reason = rec.reason;
120
120
  calls.push(call);
121
- } else if (tool === "exclude_prop") {
122
- if (typeof rec.prop !== "string" || !rec.prop) {
123
- warnings.push("exclude_prop missing prop name \u2014 skipped");
124
- continue;
125
- }
126
- calls.push({
127
- tool: "exclude_prop",
128
- prop: rec.prop,
129
- reason: typeof rec.reason === "string" ? rec.reason : ""
130
- });
131
121
  } else if (tool === "classify_component") {
132
122
  const call = { tool: "classify_component" };
133
123
  if (typeof rec.description === "string")
@@ -383,7 +373,7 @@ var init_agent_runner = __esm({
383
373
  "use strict";
384
374
  init_agent_names();
385
375
  VALID_SELECT_TOOL_NAMES = /* @__PURE__ */ new Set(["select_component", "reject_component"]);
386
- VALID_TOOL_NAMES = /* @__PURE__ */ new Set(["classify_prop", "exclude_prop", "classify_component", "classify_slot"]);
376
+ VALID_TOOL_NAMES = /* @__PURE__ */ new Set(["classify_prop", "classify_component", "classify_slot"]);
387
377
  VALID_TOKEN_TOOL_NAMES = /* @__PURE__ */ new Set(["set_token", "set_group"]);
388
378
  VALID_CDF_TYPES = /* @__PURE__ */ new Set(["string", "richtext", "media", "enum", "token", "boolean"]);
389
379
  VALID_CATEGORIES = /* @__PURE__ */ new Set(["content", "design", "state"]);
@@ -552,28 +542,26 @@ All input data is provided inline below \u2014 do not read any additional files.
552
542
 
553
543
  Do NOT write any files or emit any JSON blobs. Instead, emit one JSON object per line to stdout for each classification decision. The CLI reads your stdout line by line and writes each decision directly to the pipeline database.
554
544
 
555
- The four tool calls you may emit are:
545
+ The three tool calls you may emit are:
556
546
 
557
547
  \`\`\`
558
548
  {"tool":"classify_component","description":"<optional component-level description>","rationale":{"description":"<why this component is classified the way it is>","props":"<why these props were chosen>","slots":"<why these slots were chosen>"}}
559
549
 
560
550
  {"tool":"classify_prop","prop":"<propName>","cdf_type":"<type>","cdf_category":"<category>","required":<bool>,"description":"<short customer-facing description>","reason":"<full internal rationale; not customer-facing>","values":["a","b"],"token_kind":"color","default":"<value>"}
561
551
 
562
- {"tool":"exclude_prop","prop":"<propName>","reason":"<why excluded>"}
563
-
564
552
  {"tool":"classify_slot","slot":"<slotName>","required":<bool>,"allowed_components":["ComponentName"],"description":"<reason>","rationale":"<why this slot was kept in the catalog>"}
565
553
  \`\`\`
566
554
 
567
555
  Rules:
568
556
  - Emit exactly one JSON object per line. No multi-line JSON. No markdown fences around the lines.
569
- - Every prop in the input must have exactly one call: either classify_prop or exclude_prop.
557
+ - Every prop in the input must have exactly one classify_prop call.
570
558
  - Every slot in the input must have exactly one classify_slot call.
571
559
  - Valid cdf_type values: string, richtext, media, enum, token, boolean
572
560
  - Valid cdf_category values: content, design, state
573
561
  - For enum type, always include "values" (non-empty string array).
574
562
  - For token type, always include "token_kind" (DTCG $type, e.g. "color").
575
563
  - href and URL props \u2192 cdf_type "string", cdf_category "content". Do NOT use cdf_type "link" \u2014 it is not valid.
576
- - Framework internals (ref, event handlers, test IDs) \u2192 exclude_prop.
564
+ - Framework internals (ref, event handlers, test IDs) \u2192 classify_prop, cdf_type "string" (or "boolean" if the raw type is boolean), cdf_category "state".
577
565
  - CSS design props (className, style, styles, positional/geometric props: top, bottom, left, right, rotation, offset, etc.) \u2192 classify_prop, cdf_type: "string", cdf_category: "design".
578
566
  - On classify_component, "rationale" fields are operator-facing (read-only) but may surface in customer-facing exports. The "rationale.description" field is subject to the description content rules in the skill prompt (no internal initiative names). "rationale.props" and "rationale.slots" describe your reasoning about scope; "classify_slot.rationale" explains why each slot was kept.
579
567
  - On classify_prop, "reason" is REQUIRED and is the LLM's internal rationale \u2014 shown to the developer reviewing the import, never to end-users. "description" is the customer-facing copy and is subject to the description content rules in the skill prompt. Keep them distinct: "description" is short and customer-facing; "reason" explains your reasoning in detail.
@@ -8732,16 +8720,11 @@ function applyToolCalls(db, sessionId2, componentId, componentName, calls, incom
8732
8720
  const now = (/* @__PURE__ */ new Date()).toISOString();
8733
8721
  const warnings = [...incomingWarnings];
8734
8722
  let classified = 0;
8735
- let excluded = 0;
8736
8723
  let slots = 0;
8737
8724
  const updateProp = db.prepare(
8738
8725
  `UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, rationale = ?
8739
8726
  WHERE session_id = ? AND component_id = ? AND name = ?`
8740
8727
  );
8741
- const clearProp = db.prepare(
8742
- `UPDATE raw_props SET cdf_type = 'excluded', cdf_category = NULL, cdf_token_kind = NULL, rationale = ?
8743
- WHERE session_id = ? AND component_id = ? AND name = ?`
8744
- );
8745
8728
  const deleteAllowedValues = db.prepare(
8746
8729
  `DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ? AND prop_name = ?`
8747
8730
  );
@@ -8818,9 +8801,6 @@ function applyToolCalls(db, sessionId2, componentId, componentName, calls, incom
8818
8801
  ).run(storedDefault, sessionId2, componentId, call.prop);
8819
8802
  }
8820
8803
  classified++;
8821
- } else if (call.tool === "exclude_prop") {
8822
- clearProp.run(call.reason || null, sessionId2, componentId, call.prop);
8823
- excluded++;
8824
8804
  } else if (call.tool === "classify_slot") {
8825
8805
  const slotRequired = call.required !== void 0 ? call.required ? 1 : 0 : 1;
8826
8806
  const slotChanges = updateSlot.run(
@@ -8860,7 +8840,7 @@ function applyToolCalls(db, sessionId2, componentId, componentName, calls, incom
8860
8840
  db.exec("ROLLBACK");
8861
8841
  throw e;
8862
8842
  }
8863
- return { classified, excluded, slots, warnings };
8843
+ return { classified, slots, warnings };
8864
8844
  }
8865
8845
  function getOrCreateSession(db, sessionFlag, sessionName, _hints) {
8866
8846
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -9335,6 +9315,8 @@ function loadCDFComponents(db, sessionId2) {
9335
9315
  ).all(sessionId2);
9336
9316
  if (components.length === 0) return [];
9337
9317
  const props = db.prepare(
9318
+ // Guards against stale rows in session DBs persisted before props
9319
+ // were guaranteed to always be classified into content/design/state.
9338
9320
  `SELECT component_id, name, required, default_value, description,
9339
9321
  cdf_type, cdf_category, cdf_token_kind, position
9340
9322
  FROM raw_props
@@ -28486,11 +28468,6 @@ function formatToolCall(obj) {
28486
28468
  const cat = String(obj["cdf_category"] ?? "");
28487
28469
  return ` ${c.green("+")} ${prop} ${c.dim(`${type} ${cat}`)}`;
28488
28470
  }
28489
- case "exclude_prop": {
28490
- const prop = String(obj["prop"] ?? "");
28491
- const reason = typeof obj["reason"] === "string" ? obj["reason"] : "";
28492
- return ` ${c.dim("\u2013")} ${prop} ${c.dim(reason)}`;
28493
- }
28494
28471
  case "classify_slot": {
28495
28472
  const slot = String(obj["slot"] ?? "");
28496
28473
  const desc = typeof obj["description"] === "string" ? obj["description"] : "";
@@ -30996,7 +30973,6 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
30996
30973
  return {
30997
30974
  componentName: component.name,
30998
30975
  classified: 0,
30999
- excluded: 0,
31000
30976
  slots: 0,
31001
30977
  warnings: [],
31002
30978
  failed: false,
@@ -31012,7 +30988,6 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
31012
30988
  return {
31013
30989
  componentName: component.name,
31014
30990
  classified: 0,
31015
- excluded: 0,
31016
30991
  slots: 0,
31017
30992
  warnings: [`${component.name}: source changed but human edits preserved`],
31018
30993
  failed: false,
@@ -31081,7 +31056,6 @@ ${outputBuf}`);
31081
31056
  return {
31082
31057
  componentName: component.name,
31083
31058
  classified: 0,
31084
- excluded: 0,
31085
31059
  slots: 0,
31086
31060
  warnings: [],
31087
31061
  failed: true,
@@ -31106,7 +31080,6 @@ ${outputBuf}`);
31106
31080
  return {
31107
31081
  componentName: component.name,
31108
31082
  classified: applied.classified,
31109
- excluded: applied.excluded,
31110
31083
  slots: applied.slots,
31111
31084
  warnings: applied.warnings,
31112
31085
  failed: false,
@@ -31116,7 +31089,6 @@ ${outputBuf}`);
31116
31089
  return {
31117
31090
  componentName: component.name,
31118
31091
  classified: 0,
31119
- excluded: 0,
31120
31092
  slots: 0,
31121
31093
  warnings: [],
31122
31094
  failed: true,
@@ -31342,12 +31314,11 @@ async function runGenerateSkill(skill, opts, verbose = false) {
31342
31314
  }
31343
31315
  }
31344
31316
  const totalClassified = generated.reduce((s, r) => s + r.classified, 0);
31345
- const totalExcluded = generated.reduce((s, r) => s + r.excluded, 0);
31346
31317
  const totalRenamedSlots = componentResults.reduce((s, r) => s + r.renamedSlotsCount, 0);
31347
31318
  const allOk = failed.length === 0;
31348
31319
  const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : "";
31349
31320
  process.stderr.write(
31350
- (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified, ${totalExcluded} excluded`) + "\n"
31321
+ (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified`) + "\n"
31351
31322
  );
31352
31323
  process.stdout.write(`renamed-slots: ${totalRenamedSlots}
31353
31324
  `);
@@ -28,7 +28,6 @@ export declare function getPipelineDbPath(): string;
28
28
  export declare function openPipelineDb(dbPath?: string): DatabaseSync;
29
29
  export interface ApplyToolCallsResult {
30
30
  classified: number;
31
- excluded: number;
32
31
  slots: number;
33
32
  warnings: string[];
34
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.26.5-dev-build-505215b.0",
3
+ "version": "2.26.5",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,10 +36,10 @@
36
36
  "react": "^18.3.1",
37
37
  "react-devtools-core": "^4.19.1",
38
38
  "react-dom": "^18.3.1",
39
- "svelte": "^5.57.0",
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.26.5-dev-build-505215b.0"
42
+ "@contentful/experience-design-system-types": "2.26.5"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@tsconfig/node24": "^24.0.4",
@@ -51,9 +51,9 @@
51
51
  "ink-testing-library": "^4.0.0",
52
52
  "typescript-eslint": "^8.67.0",
53
53
  "vitest": "^4.0.16",
54
- "@contentful/experience-design-system-client": "2.26.5-dev-build-505215b.0",
55
- "@contentful/experience-design-system-extraction": "2.26.5-dev-build-505215b.0",
56
- "@contentful/experience-design-system-generation": "2.26.5-dev-build-505215b.0"
54
+ "@contentful/experience-design-system-extraction": "2.26.5",
55
+ "@contentful/experience-design-system-generation": "2.26.5",
56
+ "@contentful/experience-design-system-client": "2.26.5"
57
57
  },
58
58
  "repository": {
59
59
  "type": "git",
@@ -72,21 +72,21 @@ The CLI assembles your output into CDF (Component Definition Format), a JSON sch
72
72
 
73
73
  Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not starting with `{` are treated as prose and ignored by the parser — use them freely for reasoning.
74
74
 
75
- **Four tool calls:**
75
+ **Three tool calls:**
76
76
 
77
77
  ```
78
- {"tool":"classify_component","description":"<required: one-sentence description of the component>","rationale":{"description":"<why this component is classified this way>","props":"<why these props were chosen / excluded>","slots":"<why these slots were chosen / excluded>"}}
78
+ {"tool":"classify_component","description":"<required: one-sentence description of the component>","rationale":{"description":"<why this component is classified this way>","props":"<why these props were classified the way they were, including which ones were made unattached and why>","slots":"<why these slots were chosen / excluded>"}}
79
79
 
80
80
  {"tool":"classify_prop","prop":"<propName>","cdf_type":"<type>","cdf_category":"<category>","required":<bool>,"description":"<short customer-facing description>","reason":"<full internal rationale; not customer-facing>","values":["a","b"],"token_kind":"color","default":"<value>"}
81
81
 
82
- {"tool":"exclude_prop","prop":"<propName>","reason":"<why excluded>"}
83
-
84
82
  {"tool":"classify_slot","slot":"<slotName>","required":<bool>,"allowed_components":["ComponentName"],"description":"<short customer-facing description>","rationale":"<why this slot was kept / its role>"}
85
83
  ```
86
84
 
85
+ Every prop — including framework internals, DOM/accessibility pass-through, and props with no clean flat representation — must produce a `classify_prop` call. Props that are not marketer-configurable are classified with `cdf_category: "state"`: they still ship in the CDF (visible in review, editable if someone wants to override the category), but stay unattached from the content/design editing surfaces by default. See "Valid cdf_category values" below for exactly what falls into `state`.
86
+
87
87
  **Rules:**
88
88
  - Emit exactly one JSON object per line. No multi-line JSON.
89
- - Every prop in the input must produce exactly one call: `classify_prop` OR `exclude_prop`.
89
+ - Every prop in the input must produce exactly one `classify_prop` call. There is no drop path — a prop you can't meaningfully type still gets `cdf_type: "string"`, `cdf_category: "state"`, with the reasoning in `reason`.
90
90
  - Every slot must produce exactly one `classify_slot` call.
91
91
  - Emit `classify_component` once at the start (required). The `description` field is **required** — always provide a brief description of the component's purpose.
92
92
  - `values` is required for `cdf_type: "enum"` — must be a non-empty string array.
@@ -135,7 +135,7 @@ Exactly **6** valid types:
135
135
  |---|---|
136
136
  | `content` | Data the component *displays* — what a copywriter or editor fills in: text, labels, headings, body copy, rich text, images, media, URLs, link targets, counts, locale |
137
137
  | `design` | Values that control *how the component looks* — what a designer sets: color, size (sm/md/lg), variant (primary/secondary/ghost), layout orientation, alignment, background, visual toggles (imageOnLeft, enableEffect), design tokens |
138
- | `state` | Runtime behavioral or interactive flags not visible in the editor's design or content panel: disabled, loading, expanded, isOpen, isSearchVisible, preview, identifiers used for analytics/tracking (componentId, sectionKey, componentName) |
138
+ | `state` | Two groups, both unattached from the content/design editing surfaces by default: (1) runtime behavioral or interactive flags disabled, loading, expanded, isOpen, isSearchVisible, preview, identifiers used for analytics/tracking (componentId, sectionKey, componentName); (2) props that aren't marketer-configurable at all — framework internals, DOM/accessibility pass-through, callbacks, refs, and any type with no clean flat representation. |
139
139
 
140
140
  The pre-classified `category` in the raw input is a starting point — correct it when it is wrong. Contentful uses this category to decide where the property appears in the editor UI, so accuracy matters.
141
141
 
@@ -145,21 +145,21 @@ The pre-classified `category` in the raw input is a starting point — correct i
145
145
 
146
146
  For each `RawPropDefinition`, apply in order:
147
147
 
148
- 1. **Framework / DOM / accessibility pass-through?** → `exclude_prop`. These are escape hatches for developers, not configurable surfaces for marketers. Exposing them in the ExO editor adds noise that obscures the props that actually carry intent. Always exclude:
148
+ 1. **Framework / DOM / accessibility pass-through?** → `classify_prop`, `cdf_type: "string"` (or `"boolean"` if the raw type is boolean), `cdf_category: "state"`. These are escape hatches for developers, not configurable surfaces for marketers unattaching them keeps the ExO editor's content/design panels free of noise, without dropping them from the CDF. Always route here:
149
149
  - Framework internals: `ref`, `innerRef`, event handlers (any `onSomething`), `testId`, `data-testid`, `key`
150
150
  - DOM pass-through: `className`, `class`, `classes`, `classNames`, `rootClassName`, `prefixCls`, `style`, `styles`, `id`, `role`, `tabIndex`, `htmlFor`, `for`, `slot`, `is`, `lang`, `dir`, `hidden`, `draggable`, `spellCheck`, `contentEditable`, `inputMode`, `autoComplete`, `autoFocus`, `translate`, `part`, `exportparts`
151
151
  - Accessibility pass-through: any `aria-*` or `ariaSomething` prop (including bare `aria` as an aria-attributes object), `aria-label`, `aria-hidden`, `aria-describedby`, `aria-controls`
152
152
  - Data attributes: any `data-*` prop
153
153
  - **Polymorphic component props**: `as`, `element`, `component` (when typed as an HTML tag string or component reference) — these change rendered HTML, not marketer-visible behavior
154
154
  - **Framework theming / pass-through escape hatches**: PrimeVue's `dt` / `pt` / `ptOptions` / `unstyled`, MUI/Chakra-style `sx`, anything explicitly typed as a developer "override" / "passthrough" object
155
- - **Important caveat**: only exclude when the prop is one of these *as the bare HTML attribute or framework-internal pass-through*. Compound names like `fileName`, `displayName`, `dataset`, `dataSource`, `roleDescription`, `idLabel` are not pass-through — classify them normally.
156
- 2. **Common semantic props — DO classify, do not exclude.** The LLM has been over-excluding these because they sound like framework internals; they are not. Classify each per the rest of this tree:
155
+ - **Important caveat**: only route here when the prop is one of these *as the bare HTML attribute or framework-internal pass-through*. Compound names like `fileName`, `displayName`, `dataset`, `dataSource`, `roleDescription`, `idLabel` are not pass-through — classify them normally.
156
+ 2. **Common semantic props — classify per their real nature, not as unattached.** The LLM has been over-routing these into `state` because they sound like framework internals; they are not. Classify each per the rest of this tree:
157
157
  - `icon` / `leftIcon` / `rightIcon` / `prefixIcon` / `suffixIcon` — slot or `string` (icon name); see slot guidance below
158
- - `items` / `options` / `actions` / `links` — usually array content; if the element shape is simple, classify as `string` (comma-separated names/IDs) and note in `description`. Only exclude when elements are deep nested objects with no flat representation.
159
- - `value` (the bare prop, not `modelValue`) — content prop, usually `string` (or `enum` if from a fixed set). Note: Vue's `modelValue` / `modelModifiers` are excluded by pre-classify because they're v-model framework wiring.
158
+ - `items` / `options` / `actions` / `links` — usually array content; if the element shape is simple, classify as `string` (comma-separated names/IDs) and note in `description`. Only fall back to `cdf_category: "state"` when elements are deep nested objects with no flat representation.
159
+ - `value` (the bare prop, not `modelValue`) — content prop, usually `string` (or `enum` if from a fixed set). Note: Vue's `modelValue` / `modelModifiers` are pre-classified as pass-through because they're v-model framework wiring — keep them in `state`.
160
160
  - `name` — content prop, usually `string`. Treat it as semantic component data, not as a DOM pass-through.
161
161
  - `form` (when not the literal `<form>` HTML attribute) — typically content; classify as `string` unless it's a complex form-config object
162
- - `inputId` / `componentId` — these CAN be content (anchor IDs, marketer-set tracking refs). Classify as `string`, `cdf_category: "content"` when the type is a plain string. Only exclude if the prop is clearly internal (e.g. typed as a generated React ID).
162
+ - `inputId` / `componentId` — these CAN be content (anchor IDs, marketer-set tracking refs). Classify as `string`, `cdf_category: "content"` when the type is a plain string. Only fall back to `state` if the prop is clearly internal (e.g. typed as a generated React ID).
163
163
  - `accessibleNameRef` / `accessibleDescriptionRef` (web components) — these are ID references for a11y wiring; classify as `string`, `cdf_category: "state"` (behavioral wiring, not design or content).
164
164
  - `eventDetails` / similar telemetry props — `cdf_category: "state"`.
165
165
  3. **Positional/geometric design prop?** (`top`, `bottom`, `left`, `right`, `rotation`, `offset`, `zIndex`) → `classify_prop`, `cdf_type: "string"`, `cdf_category: "design"`.
@@ -169,13 +169,13 @@ For each `RawPropDefinition`, apply in order:
169
169
  7. **Raw type is `string` / `number` / `boolean`?** → For `boolean`, use `cdf_type: "boolean"` with `default: true` or `false` (native boolean). For `number`, use `cdf_type: "string"` with `default` as the numeric value as a string (e.g. `"0"`). For `string`, use `cdf_type: "string"`.
170
170
  8. **Media/image type** (`ImageProps`, `MediaSource`, asset types)? → `cdf_type: "media"`.
171
171
  9. **Rich text / markup** (`ReactNode` used as content, HTML string)? → `cdf_type: "richtext"`.
172
- 10. **Complex type — resolve before excluding** (see below).
172
+ 10. **Complex type — resolve before falling back to unattached** (see below).
173
173
 
174
174
  ---
175
175
 
176
- ## Resolving complex types — do not exclude without reasoning
176
+ ## Resolving complex types — do not fall back to unattached without reasoning
177
177
 
178
- A prop with a complex TypeScript type is **not automatically excluded**. Many props that appear complex carry real marketer-configurable information. Before excluding, ask: *"Could a marketer set this value in Contentful?"* If yes, classify it.
178
+ A prop with a complex TypeScript type is **not automatically unattached**. Many props that appear complex carry real marketer-configurable information. Before falling back to `cdf_category: "state"`, ask: *"Could a marketer set this value in Contentful?"* If yes, classify it as `content` or `design`.
179
179
 
180
180
  **Common resolvable patterns:**
181
181
 
@@ -184,25 +184,25 @@ A prop with a complex TypeScript type is **not automatically excluded**. Many pr
184
184
  | `'primary' \| 'secondary' \| 'ghost'` (union of literals) | → `enum`, extract `values` |
185
185
  | `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum`, infer likely values from the prop name and context (e.g. `['sm', 'md', 'lg']` for size, `['primary', 'secondary']` for variant). Document your inference in `description`. |
186
186
  | `Variant` / `variant` prop | Usually a visual design variant. → `enum`, `cdf_category: "design"`. Infer values from context. |
187
- | `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. |
188
- | `ExperienceConfiguration<Variant>` / deep generic | Personalization config — → `exclude_prop`, reason: `"personalization configuration — framework internal"` |
189
- | `React.Dispatch<...>` / setter | State setter — → `exclude_prop`, reason: `"React state setter — framework internal"` |
190
- | `React.RefObject<...>` / `ref` | → `exclude_prop`, reason: `"ref — framework internal"` |
191
- | `() => void` / callback | → `exclude_prop`, reason: `"callback function — framework internal"` |
187
+ | `Section[]` / array of custom items where the structure is unclear | → `string`, `cdf_category: "state"` 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. |
188
+ | `ExperienceConfiguration<Variant>` / deep generic | Personalization config — → `string`, `cdf_category: "state"`, reason: `"personalization configuration — framework internal"` |
189
+ | `React.Dispatch<...>` / setter | State setter — → `string`, `cdf_category: "state"`, reason: `"React state setter — framework internal"` |
190
+ | `React.RefObject<...>` / `ref` | → `string`, `cdf_category: "state"`, reason: `"ref — framework internal"` |
191
+ | `() => void` / callback | → `string`, `cdf_category: "state"`, reason: `"callback function — framework internal"` |
192
192
  | `ReactNode` used as a slot-like prop (children, `icon`, `footer`) | → classify as a `slot` if it represents an injectable area, or `richtext` if it is inline markup content |
193
193
  | `boolean` with a name like `hideChevron`, `imageOnLeft`, `enableBackgroundColorEffect` | → `boolean`, `cdf_category: "design"`, `default: true` or `false` — these control visual appearance |
194
194
  | `boolean` with a name like `preview`, `hideContentForPersonalization` | → `boolean`, `cdf_category: "state"`, `default: false` — these control behavior |
195
195
  | `string` used as a `componentId`, `sectionKey`, `componentName` | → `string`, `cdf_category: "state"` — these are identifiers for tracking/lookup |
196
196
  | `string` locale (e.g. `locale: string`) | → `string`, `cdf_category: "state"` — locale is a behavioral/routing value |
197
197
 
198
- **When to finally exclude:**
198
+ **When to fall back to `cdf_category: "state"` on a complex type (never drop the prop):**
199
199
  - The type is a callback signature or event handler
200
200
  - The type is a React ref
201
201
  - The type is a React state setter (`Dispatch`)
202
202
  - The type is a deep generic used for personalization/A-B testing platform config (e.g. `ExperienceConfiguration<T>`)
203
203
  - The type is an array of rich objects where no flat representation makes sense for a marketer
204
204
 
205
- If you exclude a prop that could have been classified, the marketer loses the ability to configure it in Contentful. Prefer classifying with a reasonable inference over excluding.
205
+ In every one of these cases you still emit `classify_prop` — pick `cdf_type: "boolean"` when the raw type is boolean, otherwise `cdf_type: "string"` — with `cdf_category: "state"` and the reasoning in `reason`. If you classify a prop as `state` when it could have carried real content or design meaning, the marketer loses the ability to configure it in Contentful. Prefer classifying with a reasonable inference over falling back to `state`.
206
206
 
207
207
  ---
208
208
 
@@ -228,8 +228,8 @@ Rules for nested objects:
228
228
  - Flatten to max depth 2 (e.g., `item_nested_deep` is acceptable, deeper is not)
229
229
  - Each leaf field gets its own classify_prop call with underscore-joined name
230
230
  - Apply the same classification rules as top-level props
231
- - If the object has > 10 fields, classify the most important 10 and exclude the rest
232
- - If the object type cannot be resolved (opaque generic, imported interface without visible fields), exclude the parent prop with reason "opaque nested type"
231
+ - If the object has > 10 fields, classify the most important 10 normally and classify the rest `cdf_type: "string"`, `cdf_category: "state"` (still one call per field — never fewer calls than fields)
232
+ - If the object type cannot be resolved (opaque generic, imported interface without visible fields), classify the parent prop `cdf_type: "string"`, `cdf_category: "state"`, reason: "opaque nested type"
233
233
 
234
234
  ---
235
235
 
@@ -307,17 +307,17 @@ Input:
307
307
  Output:
308
308
  ```
309
309
  Starting Button classification — 5 props, 1 slot
310
- {"tool":"classify_component","description":"Primary action button with variant and state support","rationale":{"description":"Button is an atom — a single interactive control that triggers an action. It carries a label, a small set of visual variants, and a disabled flag, which is the minimal surface a marketer needs to configure a call-to-action.","props":"Kept label (content), variant (enum, design), disabled (boolean, state), and className (string, design escape hatch). Excluded onClick because it is an event handler — framework-internal and not configurable in Contentful.","slots":"Kept the icon slot as optional because the button renders correctly without it and the icon is purely decorative."}}
310
+ {"tool":"classify_component","description":"Primary action button with variant and state support","rationale":{"description":"Button is an atom — a single interactive control that triggers an action. It carries a label, a small set of visual variants, and a disabled flag, which is the minimal surface a marketer needs to configure a call-to-action.","props":"Kept label (content), variant (enum, design), disabled (boolean, state), and className (string, design escape hatch). Classified onClick as unattached (state) because it is an event handler — framework-internal and not configurable in Contentful.","slots":"Kept the icon slot as optional because the button renders correctly without it and the icon is purely decorative."}}
311
311
  label is a required string content prop
312
312
  {"tool":"classify_prop","prop":"label","cdf_type":"string","cdf_category":"content","required":true,"description":"Button label text"}
313
313
  variant is a string union — enum type, category design
314
314
  {"tool":"classify_prop","prop":"variant","cdf_type":"enum","cdf_category":"design","required":false,"values":["primary","secondary","ghost"],"default":"primary","description":"Visual variant"}
315
315
  disabled is a boolean state prop — raw category says design, correcting to state
316
316
  {"tool":"classify_prop","prop":"disabled","cdf_type":"boolean","cdf_category":"state","required":false,"default":false,"description":"Disables the button"}
317
- onClick is an event handler — framework internal
318
- {"tool":"exclude_prop","prop":"onClick","reason":"event handler — framework internal"}
317
+ onClick is an event handler — framework internal, unattached rather than dropped
318
+ {"tool":"classify_prop","prop":"onClick","cdf_type":"string","cdf_category":"state","required":false,"description":"Not exposed for content or design editing.","reason":"event handler — framework internal"}
319
319
  className is a DOM pass-through — developers wire CSS, marketers never set this
320
- {"tool":"exclude_prop","prop":"className","reason":"DOM pass-through — not a marketer-configurable surface"}
320
+ {"tool":"classify_prop","prop":"className","cdf_type":"string","cdf_category":"state","required":false,"description":"Not exposed for content or design editing.","reason":"DOM pass-through — not a marketer-configurable surface"}
321
321
  icon slot is clearly optional (decorative leading icon)
322
322
  {"tool":"classify_slot","slot":"icon","required":false,"description":"Optional leading icon","rationale":"Icon is a decorative leading glyph — optional because the button reads cleanly without it, but kept as a slot so marketers can inject a brand-specific icon component when desired."}
323
323
  ```
@@ -381,8 +381,8 @@ href is a URL string — cdf_type string (not link), category content
381
381
 
382
382
  ## Edge cases
383
383
 
384
- - **Prop with unresolvable type** (generics, intersection, callback) → `exclude_prop` with reason `"complex type — not representable in CDF"`.
385
- - **Component with zero classified props after exclusions** → still emit `classify_component`. The DB entry will have an empty `$properties` object.
384
+ - **Prop with unresolvable type** (generics, intersection, callback) → `classify_prop`, `cdf_type: "string"`, `cdf_category: "state"`, reason `"complex type — not representable in CDF"`.
385
+ - **Component with zero content/design props** → still emit `classify_component`, and still emit one `classify_prop` per prop even if every single one lands in `state`. The `$properties` object is never empty as long as the component has props.
386
386
  - **tokenReference present but not in sidecar** → `cdf_type: "token"`, omit `token_kind`, add `description` warning.
387
387
  - **Slot not in DB** → skipped with a warning; does not abort the run.
388
388
  - **Prop not in DB** → skipped with a warning; does not abort the run.
@@ -391,14 +391,14 @@ href is a URL string — cdf_type string (not link), category content
391
391
 
392
392
  Before emitting any tool calls, verify:
393
393
 
394
- 1. Every prop in the input has exactly one `classify_prop` or `exclude_prop` call
394
+ 1. Every prop in the input has exactly one `classify_prop` call
395
395
  2. Every slot has exactly one `classify_slot` call
396
396
  3. `classify_component` is emitted exactly once
397
397
  4. Every `cdf_type: "enum"` has a non-empty `values` array
398
398
  5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed)
399
399
  6. No `cdf_type: "link"` — all href/url props use `string`
400
400
  7. `required` values are JSON booleans, not strings
401
- 8. Framework, DOM, accessibility, and data-* pass-through props are excluded — `className`/`classes`/`classNames`/`rootClassName`/`prefixCls`, `style`, `id`, `role`, `tabIndex`, `aria-*` (and bare `aria`), `data-*`, polymorphic `as`/`element`/`component`, framework theming `dt`/`pt`/`ptOptions`/`unstyled`/`sx`. Discrete positional/geometric props (`top`, `bottom`, `left`, `right`, `rotation`, etc.) ARE classified as `string` design props. Common semantic props (`icon`, `items`, `actions`, `options`, `value`, `name`, `form`, `inputId`, `componentId`) are NOT excluded — classify them per their content/design/state nature.
401
+ 8. Framework, DOM, accessibility, and data-* pass-through props are classified `cdf_category: "state"` (unattached), not dropped — `className`/`classes`/`classNames`/`rootClassName`/`prefixCls`, `style`, `id`, `role`, `tabIndex`, `aria-*` (and bare `aria`), `data-*`, polymorphic `as`/`element`/`component`, framework theming `dt`/`pt`/`ptOptions`/`unstyled`/`sx`. Discrete positional/geometric props (`top`, `bottom`, `left`, `right`, `rotation`, etc.) ARE classified as `string` design props. Common semantic props (`icon`, `items`, `actions`, `options`, `value`, `name`, `form`, `inputId`, `componentId`) are NOT routed to `state` by default — classify them per their content/design/state nature.
402
402
  9. No `cdf_type: "link"` used — `link` is reserved and rejected by the CLI parser
403
403
  10. No `cdf_type: "number"` used — this is not a supported type; use `"string"` with numeric defaults. `cdf_type: "boolean"` IS valid — use it for boolean toggle props.
404
404
  11. `classify_component` includes a `rationale` object with all three sub-fields (`rationale.description`, `rationale.props`, `rationale.slots`) populated as non-empty strings.
@@ -417,9 +417,9 @@ Re-run or re-iterate on any components flagged by warnings until the output pass
417
417
 
418
418
  ## CRITICAL: Zero-output is a failure
419
419
 
420
- You MUST produce at least one classify_prop call for this component. A response with zero
421
- classify_prop/exclude_prop calls means the component will be pushed with no configurable
422
- properties — this is never acceptable.
420
+ You MUST produce exactly one classify_prop call per input prop for this component. A response
421
+ with zero classify_prop calls means the component will be pushed with no properties at all —
422
+ this is never acceptable.
423
423
 
424
424
  If you are genuinely uncertain about every prop, classify each as:
425
425
  {"tool":"classify_prop","prop":"<name>","cdf_type":"string","cdf_category":"content","required":false,"description":"Uncertain classification — review recommended"}