@contentful/experience-design-system-cli 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +532 -0
  2. package/bin/cli.js +58 -0
  3. package/dist/package.json +56 -0
  4. package/dist/src/analyze/command.d.ts +3 -0
  5. package/dist/src/analyze/command.js +175 -0
  6. package/dist/src/analyze/extract/astro.d.ts +5 -0
  7. package/dist/src/analyze/extract/astro.js +280 -0
  8. package/dist/src/analyze/extract/pipeline.d.ts +6 -0
  9. package/dist/src/analyze/extract/pipeline.js +298 -0
  10. package/dist/src/analyze/extract/react.d.ts +2 -0
  11. package/dist/src/analyze/extract/react.js +1949 -0
  12. package/dist/src/analyze/extract/slot-detection.d.ts +35 -0
  13. package/dist/src/analyze/extract/slot-detection.js +101 -0
  14. package/dist/src/analyze/extract/stencil.d.ts +2 -0
  15. package/dist/src/analyze/extract/stencil.js +293 -0
  16. package/dist/src/analyze/extract/tsx-shared.d.ts +8 -0
  17. package/dist/src/analyze/extract/tsx-shared.js +263 -0
  18. package/dist/src/analyze/extract/vue-tsx.d.ts +2 -0
  19. package/dist/src/analyze/extract/vue-tsx.js +498 -0
  20. package/dist/src/analyze/extract/vue.d.ts +5 -0
  21. package/dist/src/analyze/extract/vue.js +647 -0
  22. package/dist/src/analyze/extract/web-components.d.ts +2 -0
  23. package/dist/src/analyze/extract/web-components.js +866 -0
  24. package/dist/src/analyze/pre-classify.d.ts +17 -0
  25. package/dist/src/analyze/pre-classify.js +144 -0
  26. package/dist/src/analyze/select/command.d.ts +2 -0
  27. package/dist/src/analyze/select/command.js +256 -0
  28. package/dist/src/analyze/select/index.d.ts +6 -0
  29. package/dist/src/analyze/select/index.js +5 -0
  30. package/dist/src/analyze/select/parser.d.ts +6 -0
  31. package/dist/src/analyze/select/parser.js +53 -0
  32. package/dist/src/analyze/select/persistence.d.ts +9 -0
  33. package/dist/src/analyze/select/persistence.js +42 -0
  34. package/dist/src/analyze/select/stdout.d.ts +7 -0
  35. package/dist/src/analyze/select/stdout.js +3 -0
  36. package/dist/src/analyze/select/tui/App.d.ts +8 -0
  37. package/dist/src/analyze/select/tui/App.js +491 -0
  38. package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +20 -0
  39. package/dist/src/analyze/select/tui/components/ComponentDetail.js +43 -0
  40. package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +11 -0
  41. package/dist/src/analyze/select/tui/components/FieldEditor.js +531 -0
  42. package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +10 -0
  43. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +15 -0
  44. package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +7 -0
  45. package/dist/src/analyze/select/tui/components/HelpOverlay.js +11 -0
  46. package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +11 -0
  47. package/dist/src/analyze/select/tui/components/JsonEditor.js +154 -0
  48. package/dist/src/analyze/select/tui/components/JsonPanel.d.ts +11 -0
  49. package/dist/src/analyze/select/tui/components/JsonPanel.js +62 -0
  50. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.d.ts +8 -0
  51. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +29 -0
  52. package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +8 -0
  53. package/dist/src/analyze/select/tui/components/QuitDialog.js +14 -0
  54. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +15 -0
  55. package/dist/src/analyze/select/tui/components/Sidebar.js +48 -0
  56. package/dist/src/analyze/select/tui/components/SourcePanel.d.ts +11 -0
  57. package/dist/src/analyze/select/tui/components/SourcePanel.js +52 -0
  58. package/dist/src/analyze/select/tui/components/StatusBar.d.ts +11 -0
  59. package/dist/src/analyze/select/tui/components/StatusBar.js +6 -0
  60. package/dist/src/analyze/select/tui/components/TopBar.d.ts +10 -0
  61. package/dist/src/analyze/select/tui/components/TopBar.js +5 -0
  62. package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +24 -0
  63. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +68 -0
  64. package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +24 -0
  65. package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -0
  66. package/dist/src/analyze/select/tui/hooks/useSession.d.ts +19 -0
  67. package/dist/src/analyze/select/tui/hooks/useSession.js +52 -0
  68. package/dist/src/analyze/select/tui/hooks/useUndo.d.ts +8 -0
  69. package/dist/src/analyze/select/tui/hooks/useUndo.js +26 -0
  70. package/dist/src/analyze/select/types.d.ts +46 -0
  71. package/dist/src/analyze/select/types.js +20 -0
  72. package/dist/src/analyze/select-agent/command.d.ts +2 -0
  73. package/dist/src/analyze/select-agent/command.js +208 -0
  74. package/dist/src/analyze/tui/AnalyzeView.d.ts +24 -0
  75. package/dist/src/analyze/tui/AnalyzeView.js +38 -0
  76. package/dist/src/apply/api-client.d.ts +35 -0
  77. package/dist/src/apply/api-client.js +143 -0
  78. package/dist/src/apply/command.d.ts +6 -0
  79. package/dist/src/apply/command.js +787 -0
  80. package/dist/src/apply/manifest.d.ts +1 -0
  81. package/dist/src/apply/manifest.js +1 -0
  82. package/dist/src/apply/tui/SelectView.d.ts +18 -0
  83. package/dist/src/apply/tui/SelectView.js +34 -0
  84. package/dist/src/apply/tui/ServerApplyView.d.ts +32 -0
  85. package/dist/src/apply/tui/ServerApplyView.js +42 -0
  86. package/dist/src/apply/tui/ServerPreviewView.d.ts +9 -0
  87. package/dist/src/apply/tui/ServerPreviewView.js +21 -0
  88. package/dist/src/credentials-store.d.ts +8 -0
  89. package/dist/src/credentials-store.js +30 -0
  90. package/dist/src/generate/agent-runner.d.ts +86 -0
  91. package/dist/src/generate/agent-runner.js +314 -0
  92. package/dist/src/generate/command.d.ts +2 -0
  93. package/dist/src/generate/command.js +545 -0
  94. package/dist/src/generate/edit/command.d.ts +2 -0
  95. package/dist/src/generate/edit/command.js +126 -0
  96. package/dist/src/generate/prompt-builder.d.ts +18 -0
  97. package/dist/src/generate/prompt-builder.js +202 -0
  98. package/dist/src/generate/tui/GenerateView.d.ts +12 -0
  99. package/dist/src/generate/tui/GenerateView.js +10 -0
  100. package/dist/src/import/command.d.ts +2 -0
  101. package/dist/src/import/command.js +96 -0
  102. package/dist/src/import/orchestrator.d.ts +37 -0
  103. package/dist/src/import/orchestrator.js +374 -0
  104. package/dist/src/import/path-utils.d.ts +15 -0
  105. package/dist/src/import/path-utils.js +30 -0
  106. package/dist/src/import/tui/WizardApp.d.ts +10 -0
  107. package/dist/src/import/tui/WizardApp.js +906 -0
  108. package/dist/src/import/tui/steps/CredentialsStep.d.ts +15 -0
  109. package/dist/src/import/tui/steps/CredentialsStep.js +79 -0
  110. package/dist/src/import/tui/steps/DoneStep.d.ts +20 -0
  111. package/dist/src/import/tui/steps/DoneStep.js +17 -0
  112. package/dist/src/import/tui/steps/ErrorStep.d.ts +8 -0
  113. package/dist/src/import/tui/steps/ErrorStep.js +11 -0
  114. package/dist/src/import/tui/steps/GateStep.d.ts +14 -0
  115. package/dist/src/import/tui/steps/GateStep.js +20 -0
  116. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +8 -0
  117. package/dist/src/import/tui/steps/GenerateReviewStep.js +208 -0
  118. package/dist/src/import/tui/steps/PathValidationStep.d.ts +10 -0
  119. package/dist/src/import/tui/steps/PathValidationStep.js +151 -0
  120. package/dist/src/import/tui/steps/PreviewStep.d.ts +21 -0
  121. package/dist/src/import/tui/steps/PreviewStep.js +36 -0
  122. package/dist/src/import/tui/steps/RunningStep.d.ts +10 -0
  123. package/dist/src/import/tui/steps/RunningStep.js +20 -0
  124. package/dist/src/import/tui/steps/TokenInputStep.d.ts +8 -0
  125. package/dist/src/import/tui/steps/TokenInputStep.js +70 -0
  126. package/dist/src/import/tui/steps/WelcomeStep.d.ts +7 -0
  127. package/dist/src/import/tui/steps/WelcomeStep.js +33 -0
  128. package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +15 -0
  129. package/dist/src/import/tui/steps/WizardPreviewStep.js +121 -0
  130. package/dist/src/import/tui/steps/preview-diff.d.ts +10 -0
  131. package/dist/src/import/tui/steps/preview-diff.js +132 -0
  132. package/dist/src/index.d.ts +1 -0
  133. package/dist/src/index.js +2 -0
  134. package/dist/src/output/format.d.ts +23 -0
  135. package/dist/src/output/format.js +110 -0
  136. package/dist/src/print/command.d.ts +2 -0
  137. package/dist/src/print/command.js +199 -0
  138. package/dist/src/print/validate/tui/ValidateView.d.ts +15 -0
  139. package/dist/src/print/validate/tui/ValidateView.js +37 -0
  140. package/dist/src/print/validate/validators/cdf-validator.d.ts +2 -0
  141. package/dist/src/print/validate/validators/cdf-validator.js +104 -0
  142. package/dist/src/print/validate/validators/dtcg-validator.d.ts +2 -0
  143. package/dist/src/print/validate/validators/dtcg-validator.js +110 -0
  144. package/dist/src/print/validate/validators/format-errors.d.ts +12 -0
  145. package/dist/src/print/validate/validators/format-errors.js +18 -0
  146. package/dist/src/program.d.ts +2 -0
  147. package/dist/src/program.js +25 -0
  148. package/dist/src/session/command.d.ts +2 -0
  149. package/dist/src/session/command.js +261 -0
  150. package/dist/src/session/db.d.ts +111 -0
  151. package/dist/src/session/db.js +1114 -0
  152. package/dist/src/session/migration.d.ts +4 -0
  153. package/dist/src/session/migration.js +117 -0
  154. package/dist/src/session/session-id.d.ts +1 -0
  155. package/dist/src/session/session-id.js +212 -0
  156. package/dist/src/session/stats.d.ts +27 -0
  157. package/dist/src/session/stats.js +89 -0
  158. package/dist/src/setup/command.d.ts +2 -0
  159. package/dist/src/setup/command.js +765 -0
  160. package/dist/src/types.d.ts +48 -0
  161. package/dist/src/types.js +1 -0
  162. package/package.json +55 -0
  163. package/skills/generate-components.md +361 -0
  164. package/skills/generate-tokens.md +194 -0
  165. package/skills/select-components.md +180 -0
@@ -0,0 +1,48 @@
1
+ import type { DesignTokenType } from '@contentful/experience-design-system-types';
2
+ export interface RawTokenDefinition {
3
+ name: string;
4
+ value: string;
5
+ source: 'css' | 'tailwind' | 'style-dictionary';
6
+ inferredKind: DesignTokenType | string;
7
+ ambiguous: boolean;
8
+ }
9
+ export interface RawPropDefinition {
10
+ name: string;
11
+ type: string;
12
+ required: boolean;
13
+ category?: 'content' | 'design' | 'state';
14
+ defaultValue?: string;
15
+ allowedValues?: string[];
16
+ description?: string;
17
+ tokenReference?: string;
18
+ }
19
+ export interface RawSlotDefinition {
20
+ name: string;
21
+ isDefault: boolean;
22
+ description?: string;
23
+ allowedComponents?: string[];
24
+ }
25
+ export interface RawComponentDefinition {
26
+ name: string;
27
+ source: string;
28
+ framework: 'react' | 'next' | 'vue' | 'astro' | 'web-component' | 'stencil';
29
+ props: RawPropDefinition[];
30
+ slots: RawSlotDefinition[];
31
+ }
32
+ export interface ComponentExtractionResult {
33
+ components: RawComponentDefinition[];
34
+ warnings: string[];
35
+ }
36
+ export type ExtractorProgress = {
37
+ filesProcessed: number;
38
+ componentsFound: number;
39
+ };
40
+ export interface ComponentExtractor {
41
+ name: string;
42
+ fileFilter: (filePath: string) => boolean;
43
+ extract(filePaths: string[], onProgress?: (p: ExtractorProgress) => void): Promise<ComponentExtractionResult>;
44
+ }
45
+ export interface TokenExtractor {
46
+ name: string;
47
+ extract(projectRoot: string): Promise<RawTokenDefinition[]>;
48
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@contentful/experience-design-system-cli",
3
+ "version": "2.2.1",
4
+ "description": "Contentful Experiences design system import CLI",
5
+ "type": "module",
6
+ "bin": {
7
+ "experience-design-system-cli": "./bin/cli.js",
8
+ "exo": "./bin/cli.js"
9
+ },
10
+ "main": "./dist/src/index.js",
11
+ "types": "./dist/src/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/src/index.d.ts",
15
+ "import": "./dist/src/index.js",
16
+ "node": "./dist/src/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "bin/",
21
+ "dist/",
22
+ "skills/"
23
+ ],
24
+ "dependencies": {
25
+ "@vue/compiler-sfc": "^3.5.31",
26
+ "commander": "^13.1.0",
27
+ "ink": "^4.4.1",
28
+ "react": "^18.3.1",
29
+ "react-devtools-core": "^4.19.1",
30
+ "react-dom": "^18.3.1",
31
+ "ts-morph": "^27.0.2",
32
+ "typescript": "^5.9.3",
33
+ "@contentful/experience-design-system-types": "2.2.1"
34
+ },
35
+ "devDependencies": {
36
+ "@tsconfig/node24": "^24.0.3",
37
+ "@types/node": "^24.0.3",
38
+ "@types/react": "^18.3.24",
39
+ "eslint": "^9.39.2",
40
+ "eslint-config-prettier": "^10.1.8",
41
+ "eslint-plugin-prettier": "^5.5.4",
42
+ "ink-testing-library": "^4.0.0",
43
+ "typescript-eslint": "^8.52.0",
44
+ "vitest": "^4.0.16"
45
+ },
46
+ "scripts": {
47
+ "build": "nx build experience-design-system-cli",
48
+ "typecheck": "nx typecheck experience-design-system-cli",
49
+ "clean": "nx clean experience-design-system-cli",
50
+ "test": "nx test experience-design-system-cli",
51
+ "test:watch": "nx test:watch experience-design-system-cli",
52
+ "lint": "nx lint experience-design-system-cli",
53
+ "lint:fix": "nx lint:fix experience-design-system-cli"
54
+ }
55
+ }
@@ -0,0 +1,361 @@
1
+ # Generate Components — Classification Skill
2
+
3
+ ## Purpose
4
+
5
+ Classify every prop and slot in the component definition provided inline for use in **Contentful Experience Orchestration**. Output one JSON tool call per line to stdout. The CLI reads your stdout and writes each decision directly to the pipeline database — you do not write any files.
6
+
7
+ ---
8
+
9
+ ## What is Contentful Experience Orchestration?
10
+
11
+ Contentful Experience Orchestration is a Contentful product that enables **marketers** to manage both the content and visual presentation of digital experiences — web pages, multi-channel — entirely within Contentful. The entity being defined here is a **Component Type**: the schema that tells Contentful what a marketer can configure for this UI component.
12
+
13
+ A Component Type has two kinds of configurable properties:
14
+
15
+ - **Design Properties** — values that control *how the component looks*: color scheme, visual variant (primary/secondary/ghost), size (sm/md/lg), spacing, layout orientation, background color, font style, border style, any visual toggle that changes appearance. These are the values a designer sets once and a marketer may override. Think: "what would a designer put in a design token or a style guide?"
16
+ - **Content Properties** — values that are *data the component displays*: labels, headings, body text, rich text, images, media, URLs, counts, IDs used for CMS lookups, locales. These are filled by editors with real CMS content. Think: "what does a copywriter or content editor fill in?"
17
+
18
+ The third category:
19
+ - **State Properties** — values that control *interactive or behavioral state*: disabled, loading, expanded, isOpen, isSearchVisible. These are runtime behavioral flags, not visual design nor content.
20
+
21
+ Getting this right matters: Contentful uses the category to decide where a property appears in the editor UI. Design properties appear in the design panel, content properties appear in the content panel.
22
+
23
+ ---
24
+
25
+ ## Prerequisites — Input
26
+
27
+ All input is embedded inline in the prompt before this file:
28
+
29
+ - **Raw component data** — `RawComponentDefinition[]` (one component for this run)
30
+ - **DTCG token data** — full token tree, if provided
31
+ - **Token-name sidecar** — raw CSS custom property name → DTCG dot-notation path, if provided
32
+
33
+ ```typescript
34
+ interface RawPropDefinition {
35
+ name: string;
36
+ type: string; // raw TypeScript type, e.g. "'primary' | 'secondary'"
37
+ required: boolean;
38
+ category?: 'content' | 'design' | 'state'; // pre-classified — verify, do not blindly trust
39
+ allowedValues?: string[];
40
+ defaultValue?: string;
41
+ description?: string;
42
+ tokenReference?: string; // raw token name, e.g. "--brand-primary"
43
+ }
44
+
45
+ interface RawSlotDefinition {
46
+ name: string;
47
+ description?: string;
48
+ allowedComponents?: string[];
49
+ }
50
+
51
+ interface RawComponentDefinition {
52
+ name: string;
53
+ source: string;
54
+ props: RawPropDefinition[];
55
+ slots: RawSlotDefinition[];
56
+ }
57
+ ```
58
+
59
+ The `category` field on each prop is a pre-classification hint from static analysis heuristics.
60
+ It is correct approximately 80% of the time for simple props. You should:
61
+ - Trust it for obvious cases (event handlers excluded, text labels as content)
62
+ - Override it when your domain knowledge indicates otherwise
63
+ - NEVER produce zero output — if you disagree with all hints, explain why in descriptions
64
+
65
+ ---
66
+
67
+ ## Target schema
68
+
69
+ The CLI assembles your output into CDF (Component Definition Format), a JSON schema with `$schema: "https://contentful.com/schemas/cdf/v1"`. Each component you classify produces a CDF component entry (`$type: "component"`) in the pipeline database. Properties carry `$category` (`content`, `design`, or `state`) and a `$type`. You do not produce this JSON directly — emit tool calls and the CLI writes the DB columns.
70
+
71
+ ## Output protocol
72
+
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
+
75
+ **Four tool calls:**
76
+
77
+ ```
78
+ {"tool":"classify_component","description":"<required: one-sentence description of the component>"}
79
+
80
+ {"tool":"classify_prop","prop":"<propName>","cdf_type":"<type>","cdf_category":"<category>","required":<bool>,"description":"<reason>","values":["a","b"],"token_kind":"color","default":"<value>"}
81
+
82
+ {"tool":"exclude_prop","prop":"<propName>","reason":"<why excluded>"}
83
+
84
+ {"tool":"classify_slot","slot":"<slotName>","required":<bool>,"allowed_components":["ComponentName"],"description":"<reason>"}
85
+ ```
86
+
87
+ **Rules:**
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`.
90
+ - Every slot must produce exactly one `classify_slot` call.
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
+ - `values` is required for `cdf_type: "enum"` — must be a non-empty string array.
93
+ - `token_kind` is required for `cdf_type: "token"` — must be a DTCG `$type` string, e.g. `"color"`.
94
+ - `required` must be a JSON boolean (`true`/`false`), not a string.
95
+ - `description` on `classify_prop` documents your reasoning — always include it.
96
+
97
+ ---
98
+
99
+ ## Valid cdf_type values
100
+
101
+ Exactly **6** valid types:
102
+
103
+ | cdf_type | Use case |
104
+ |---|---|
105
+ | `string` | Plain text, URLs, href props, numbers (as string), any string-shaped value |
106
+ | `richtext` | Formatted text, HTML, ReactNode used as markup |
107
+ | `media` | Images, videos, media assets |
108
+ | `enum` | Fixed set of string choices — requires `values` |
109
+ | `token` | Design-token-linked prop — requires `token_kind` |
110
+ | `boolean` | Boolean toggle props (visible, disabled, enabled, etc.) |
111
+
112
+ > **IMPORTANT: No `number` type.** The EMA Phase 2 API only supports the `String` design property variant for numeric values. All numeric props must use `cdf_type: "string"` with the number as a string default (e.g. `"0"`, `"100"`). Boolean props can now use `cdf_type: "boolean"` directly.
113
+
114
+ > **Avoid `link` type for simple URL props.** Props named `href`, `url`, or holding plain URL strings → `cdf_type: "string"`, `cdf_category: "content"`. Reserve `link` for props that hold a reference to another Contentful entry.
115
+
116
+ ---
117
+
118
+ ## Valid cdf_category values
119
+
120
+ | cdf_category | Use case |
121
+ |---|---|
122
+ | `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 |
123
+ | `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 |
124
+ | `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) |
125
+
126
+ 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.
127
+
128
+ ---
129
+
130
+ ## Mapping guidance — Classification decision tree
131
+
132
+ For each `RawPropDefinition`, apply in order:
133
+
134
+ 1. **Framework internal?** (`ref`, event handlers, `testId`, `data-testid`, `key`) → `exclude_prop`.
135
+ 2. **CSS design prop?** (`className`, `style`, `styles`, positional/geometric props like `top`, `bottom`, `left`, `right`, `rotation`, `offset`) → `classify_prop`, `cdf_type: "string"`, `cdf_category: "design"`.
136
+ 3. **Has `tokenReference`?** → `cdf_type: "token"`, resolve `token_kind` via sidecar lookup (see below). This overrides all other heuristics.
137
+ 4. **Union of string literals** (e.g. `'a' | 'b' | 'c'`)? → `cdf_type: "enum"`, extract literals into `values`.
138
+ 5. **Raw type is `string`** and prop name is `href`, `url`, or clearly a URL? → `cdf_type: "string"`, `cdf_category: "content"`.
139
+ 6. **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"`.
140
+ 7. **Media/image type** (`ImageProps`, `MediaSource`, asset types)? → `cdf_type: "media"`.
141
+ 8. **Rich text / markup** (`ReactNode` used as content, HTML string)? → `cdf_type: "richtext"`.
142
+ 9. **Complex type — resolve before excluding** (see below).
143
+
144
+ ---
145
+
146
+ ## Resolving complex types — do not exclude without reasoning
147
+
148
+ 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.
149
+
150
+ **Common resolvable patterns:**
151
+
152
+ | Raw type pattern | How to resolve |
153
+ |---|---|
154
+ | `'primary' \| 'secondary' \| 'ghost'` (union of literals) | → `enum`, extract `values` |
155
+ | `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`. |
156
+ | `Variant` / `variant` prop | Usually a visual design variant. → `enum`, `cdf_category: "design"`. Infer values from context. |
157
+ | `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. |
158
+ | `ExperienceConfiguration<Variant>` / deep generic | Personalization config — → `exclude_prop`, reason: `"personalization configuration — framework internal"` |
159
+ | `React.Dispatch<...>` / setter | State setter — → `exclude_prop`, reason: `"React state setter — framework internal"` |
160
+ | `React.RefObject<...>` / `ref` | → `exclude_prop`, reason: `"ref — framework internal"` |
161
+ | `() => void` / callback | → `exclude_prop`, reason: `"callback function — framework internal"` |
162
+ | `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 |
163
+ | `boolean` with a name like `hideChevron`, `imageOnLeft`, `enableBackgroundColorEffect` | → `boolean`, `cdf_category: "design"`, `default: true` or `false` — these control visual appearance |
164
+ | `boolean` with a name like `preview`, `hideContentForPersonalization` | → `boolean`, `cdf_category: "state"`, `default: false` — these control behavior |
165
+ | `string` used as a `componentId`, `sectionKey`, `componentName` | → `string`, `cdf_category: "state"` — these are identifiers for tracking/lookup |
166
+ | `string` locale (e.g. `locale: string`) | → `string`, `cdf_category: "state"` — locale is a behavioral/routing value |
167
+
168
+ **When to finally exclude:**
169
+ - The type is a callback signature or event handler
170
+ - The type is a React ref
171
+ - The type is a React state setter (`Dispatch`)
172
+ - The type is a deep generic used for personalization/A-B testing platform config (e.g. `ExperienceConfiguration<T>`)
173
+ - The type is an array of rich objects where no flat representation makes sense for a marketer
174
+
175
+ 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.
176
+
177
+ ---
178
+
179
+ ## Handling nested object props
180
+
181
+ When a prop has an inline object type (e.g., `item: { url?: string; alt?: string; target?: string }`),
182
+ classify EACH field as a separate prop using underscore notation (parentName_fieldName):
183
+
184
+ Input:
185
+ {"name":"item","type":"{ url?: string; alt?: string; linkUrl?: string; target?: string }","required":true}
186
+
187
+ Output:
188
+ {"tool":"classify_prop","prop":"item_url","cdf_type":"string","cdf_category":"content","required":false,"description":"Image source URL"}
189
+ {"tool":"classify_prop","prop":"item_alt","cdf_type":"string","cdf_category":"content","required":false,"description":"Image alt text"}
190
+ {"tool":"classify_prop","prop":"item_linkUrl","cdf_type":"string","cdf_category":"content","required":false,"description":"Navigation URL"}
191
+ {"tool":"classify_prop","prop":"item_target","cdf_type":"enum","cdf_category":"design","required":false,"values":["_blank","_self","_parent","_top"],"description":"Link open behavior"}
192
+
193
+ Note: Underscore notation is used (not dot-notation) because the backend's `toDisplayName()`
194
+ function splits on `.` and takes only the last segment, producing poor display names. Underscore
195
+ produces display names like "Item Url", "Item Alt" which are more readable in the ExO editor.
196
+
197
+ Rules for nested objects:
198
+ - Flatten to max depth 2 (e.g., `item_nested_deep` is acceptable, deeper is not)
199
+ - Each leaf field gets its own classify_prop call with underscore-joined name
200
+ - Apply the same classification rules as top-level props
201
+ - If the object has > 10 fields, classify the most important 10 and exclude the rest
202
+ - If the object type cannot be resolved (opaque generic, imported interface without visible fields), exclude the parent prop with reason "opaque nested type"
203
+
204
+ ---
205
+
206
+ ## Token-aware mapping
207
+
208
+ When `tokenReference` is present, classify with `cdf_type: "token"`. The `token_kind` field becomes `$token.kind` in the CDF output (a DTCG `$type` string, e.g. `"color"`).
209
+
210
+ 1. Look up `tokenReference` in the inline token-name sidecar → get the DTCG dot-notation path
211
+ 2. Traverse that path in the inline DTCG token data to reach the leaf token
212
+ 3. Use the leaf's `$type` (e.g. `"color"`) as `token_kind`
213
+
214
+ Example:
215
+ ```
216
+ tokenReference: "--brand-primary"
217
+ → sidecar["--brand-primary"] → "colors.brand.primary"
218
+ → token data: colors.brand.primary.$type → "color"
219
+ → tool call: {"tool":"classify_prop","prop":"bgColor","cdf_type":"token","cdf_category":"design","token_kind":"color","description":"..."}
220
+ ```
221
+
222
+ If `tokenReference` is not found in the sidecar → `cdf_type: "token"`, omit `token_kind`, add `description: "WARNING: tokenReference not found in sidecar — token_kind unknown"`.
223
+
224
+ If token data was not provided and `tokenReference` is present → `cdf_type: "token"`, omit `token_kind`, add `description: "WARNING: no token data supplied — token_kind unknown"`.
225
+
226
+ ---
227
+
228
+ ## Category correction rules
229
+
230
+ The pre-classified `category` is wrong in predictable ways. Correct silently (document in `description`):
231
+
232
+ - Visual style props (`color`, `size`, `padding`, `spacing`, `variant`, `theme`, `bgColor`, `imageOnLeft`, `enableXxx`) classified as `content` or `state` → `design`
233
+ - Interactive/behavioral state props (`disabled`, `loading`, `expanded`, `selected`, `checked`, `active`, `isOpen`, `isSearchVisible`, `showXxx`, `hideXxx`, `preview`, `componentId`, `sectionKey`, `variantIndex`) classified as `design` or `content` → `state`
234
+ - Text/label/data props (`title`, `label`, `description`, `caption`, `text`, `boldText`, `labelText`, `richText`, `backgroundImage`, `link`, `placeholder`, `searchValue`, `total`, `slug`) classified as `design` → `content`
235
+ - Locale classified as `content` → `state` (it is a behavioral routing value, not editor-filled text)
236
+
237
+ > **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.
238
+
239
+ ---
240
+
241
+ ## Slot classification
242
+
243
+ The `classify_slot` tool call maps to CDF's `$slots` object. Each slot you classify becomes a `$slots` entry; the `allowed_components` field maps to `$allowedComponents` in the output CDF.
244
+
245
+ For each `RawSlotDefinition`:
246
+ - `name` → `slot` field
247
+ - `description` → `description` field
248
+ - `allowedComponents` → `allowed_components` (pass through)
249
+ - `required`:
250
+ - `true` if the component does not render correctly without content in this slot (primary content area of a Card, Dialog body, etc.)
251
+ - `false` if clearly optional (icon slot, footer slot with a default, decorative slot)
252
+ - Default to `true` when the source gives no signal
253
+
254
+ ---
255
+
256
+ ## Examples
257
+
258
+ ### Simple component
259
+
260
+ Input:
261
+ ```json
262
+ {
263
+ "name": "Button",
264
+ "props": [
265
+ {"name":"label","type":"string","category":"content","required":true},
266
+ {"name":"variant","type":"'primary'|'secondary'|'ghost'","category":"design","defaultValue":"'primary'"},
267
+ {"name":"disabled","type":"boolean","category":"design"},
268
+ {"name":"onClick","type":"()=>void","category":"state"},
269
+ {"name":"className","type":"string","category":"design"}
270
+ ],
271
+ "slots": [{"name":"icon","description":"Optional leading icon"}]
272
+ }
273
+ ```
274
+
275
+ Output:
276
+ ```
277
+ Starting Button classification — 5 props, 1 slot
278
+ {"tool":"classify_component","description":"Primary action button with variant and state support"}
279
+ label is a required string content prop
280
+ {"tool":"classify_prop","prop":"label","cdf_type":"string","cdf_category":"content","required":true,"description":"Button label text"}
281
+ variant is a string union — enum type, category design
282
+ {"tool":"classify_prop","prop":"variant","cdf_type":"enum","cdf_category":"design","required":false,"values":["primary","secondary","ghost"],"default":"primary","description":"Visual variant"}
283
+ disabled is a boolean state prop — raw category says design, correcting to state
284
+ {"tool":"classify_prop","prop":"disabled","cdf_type":"boolean","cdf_category":"state","required":false,"default":false,"description":"Disables the button"}
285
+ onClick is an event handler — framework internal
286
+ {"tool":"exclude_prop","prop":"onClick","reason":"event handler — framework internal"}
287
+ className is a CSS design escape hatch — classify as string/design
288
+ {"tool":"classify_prop","prop":"className","cdf_type":"string","cdf_category":"design","required":false,"description":"CSS class override"}
289
+ icon slot is clearly optional (decorative leading icon)
290
+ {"tool":"classify_slot","slot":"icon","required":false,"description":"Optional leading icon"}
291
+ ```
292
+
293
+ ### Named type (HeadingSize, ButtonVariant, etc.)
294
+
295
+ 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.
296
+
297
+ ```
298
+ titleSize has type HeadingSize — this is a named enum controlling heading size
299
+ inferring likely values: ["h1", "h2", "h3", "h4", "h5", "h6"] — documenting inference
300
+ {"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"}
301
+ ```
302
+
303
+ ### Token-linked prop
304
+
305
+ ```
306
+ bgColor has tokenReference "--bg-primary" — looking up sidecar
307
+ {"tool":"classify_prop","prop":"bgColor","cdf_type":"token","cdf_category":"design","token_kind":"color","description":"Background color token linked via --bg-primary → colors.bg.primary"}
308
+ ```
309
+
310
+ ### href prop
311
+
312
+ ```
313
+ href is a URL string — cdf_type string (not link), category content
314
+ {"tool":"classify_prop","prop":"href","cdf_type":"string","cdf_category":"content","required":false,"description":"Navigation URL"}
315
+ ```
316
+
317
+ ---
318
+
319
+ ## Edge cases
320
+
321
+ - **Prop with unresolvable type** (generics, intersection, callback) → `exclude_prop` with reason `"complex type — not representable in CDF"`.
322
+ - **Component with zero classified props after exclusions** → still emit `classify_component`. The DB entry will have an empty `$properties` object.
323
+ - **tokenReference present but not in sidecar** → `cdf_type: "token"`, omit `token_kind`, add `description` warning.
324
+ - **Slot not in DB** → skipped with a warning; does not abort the run.
325
+ - **Prop not in DB** → skipped with a warning; does not abort the run.
326
+
327
+ ## Validation step — Pre-emit checklist
328
+
329
+ Before emitting any tool calls, verify:
330
+
331
+ 1. Every prop in the input has exactly one `classify_prop` or `exclude_prop` call
332
+ 2. Every slot has exactly one `classify_slot` call
333
+ 3. `classify_component` is emitted exactly once
334
+ 4. Every `cdf_type: "enum"` has a non-empty `values` array
335
+ 5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed)
336
+ 6. No `cdf_type: "link"` — all href/url props use `string`
337
+ 7. `required` values are JSON booleans, not strings
338
+ 8. Framework internals (`ref`, event handlers, test IDs) are excluded — `className`, `style`, and `styles` are classified as `string` design props; discrete positional/geometric props (`top`, `bottom`, `left`, `right`, `rotation`, etc.) are also classified
339
+ 9. No `cdf_type: "link"` used — `link` is reserved and rejected by the CLI parser
340
+ 10. No `cdf_type: "number"` used — this is not a valid P2 type; use `"string"` with numeric defaults. `cdf_type: "boolean"` IS valid — use it for boolean toggle props.
341
+
342
+ After the run completes, the developer can validate the pipeline output with:
343
+
344
+ ```
345
+ experience-design-system-cli print validate --components <out-path>
346
+ ```
347
+
348
+ Re-run or re-iterate on any components flagged by warnings until the output passes validation.
349
+
350
+ ---
351
+
352
+ ## CRITICAL: Zero-output is a failure
353
+
354
+ You MUST produce at least one classify_prop call for this component. A response with zero
355
+ classify_prop/exclude_prop calls means the component will be pushed with no configurable
356
+ properties — this is never acceptable.
357
+
358
+ If you are genuinely uncertain about every prop, classify each as:
359
+ {"tool":"classify_prop","prop":"<name>","cdf_type":"string","cdf_category":"content","required":false,"description":"Uncertain classification — review recommended"}
360
+
361
+ An imperfect classification is infinitely better than no classification.