@contentful/experience-design-system-generation 2.27.5-dev-build-67ce1cd.0 → 2.27.5-dev-build-56846c8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +1 -1
- package/dist/src/agent-runner.d.ts +0 -12
- package/dist/src/agent-runner.js +0 -45
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +1 -1
- package/dist/src/prompt-builder.d.ts +2 -43
- package/dist/src/prompt-builder.js +7 -137
- package/package.json +2 -2
- package/skills/generate-components.md +35 -139
- package/skills/map-tokens.md +0 -90
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-generation",
|
|
3
|
-
"version": "2.27.5-dev-build-
|
|
3
|
+
"version": "2.27.5-dev-build-56846c8.0",
|
|
4
4
|
"description": "Agent-invocation and skill-prompt engine for the Contentful Experience Design System SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -89,18 +89,6 @@ export interface ParsedToolCalls {
|
|
|
89
89
|
}
|
|
90
90
|
export declare function parseToolCallLines(stdout: string): ParsedToolCalls;
|
|
91
91
|
export declare function parseTokenToolCallLines(stdout: string): ParsedTokenToolCalls;
|
|
92
|
-
export interface MapTokenPropCall {
|
|
93
|
-
tool: 'map_token_prop';
|
|
94
|
-
component: string;
|
|
95
|
-
prop: string;
|
|
96
|
-
/** Narrowed subset of tokens the prop may draw from. Required and non-empty. */
|
|
97
|
-
token_allowed: string[];
|
|
98
|
-
}
|
|
99
|
-
export interface ParsedMapTokenPropToolCalls {
|
|
100
|
-
calls: MapTokenPropCall[];
|
|
101
|
-
warnings: string[];
|
|
102
|
-
}
|
|
103
|
-
export declare function parseMapTokenPropToolCallLines(stdout: string): ParsedMapTokenPropToolCalls;
|
|
104
92
|
export declare function resolveBinary(agent: AgentName): string;
|
|
105
93
|
/**
|
|
106
94
|
* Resolve the model for an agent. Explicit flag/creds value wins, then a
|
package/dist/src/agent-runner.js
CHANGED
|
@@ -220,51 +220,6 @@ export function parseTokenToolCallLines(stdout) {
|
|
|
220
220
|
}
|
|
221
221
|
return { calls, warnings };
|
|
222
222
|
}
|
|
223
|
-
function isStringArray(value) {
|
|
224
|
-
return Array.isArray(value) && value.every((v) => typeof v === 'string');
|
|
225
|
-
}
|
|
226
|
-
export function parseMapTokenPropToolCallLines(stdout) {
|
|
227
|
-
const calls = [];
|
|
228
|
-
const warnings = [];
|
|
229
|
-
for (const raw of stdout.split('\n')) {
|
|
230
|
-
const line = raw.trim();
|
|
231
|
-
if (!line.startsWith('{'))
|
|
232
|
-
continue;
|
|
233
|
-
let obj;
|
|
234
|
-
try {
|
|
235
|
-
obj = JSON.parse(line);
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
warnings.push(`unparseable line: ${line.slice(0, 120)}`);
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
if (typeof obj !== 'object' || obj === null || !('tool' in obj))
|
|
242
|
-
continue;
|
|
243
|
-
const rec = obj;
|
|
244
|
-
if (rec.tool !== 'map_token_prop')
|
|
245
|
-
continue; // not a map-tokens call — skip silently
|
|
246
|
-
if (typeof rec.component !== 'string' || !rec.component) {
|
|
247
|
-
warnings.push('map_token_prop missing component — skipped');
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
if (typeof rec.prop !== 'string' || !rec.prop) {
|
|
251
|
-
warnings.push(`map_token_prop '${rec.component}': missing prop — skipped`);
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
if (!isStringArray(rec.token_allowed) || rec.token_allowed.length === 0) {
|
|
255
|
-
warnings.push(`map_token_prop '${rec.component}.${String(rec.prop)}': missing or empty token_allowed — skipped`);
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
const call = {
|
|
259
|
-
tool: 'map_token_prop',
|
|
260
|
-
component: rec.component,
|
|
261
|
-
prop: rec.prop,
|
|
262
|
-
token_allowed: rec.token_allowed,
|
|
263
|
-
};
|
|
264
|
-
calls.push(call);
|
|
265
|
-
}
|
|
266
|
-
return { calls, warnings };
|
|
267
|
-
}
|
|
268
223
|
// --- Agent invocation ---
|
|
269
224
|
const AGENT_BINARIES = {
|
|
270
225
|
claude: 'claude',
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { AGENT_NAMES, DEFAULT_AGENT_NAME, isAgentName } from './agent-names.js';
|
|
2
2
|
export type { AgentName } from './agent-names.js';
|
|
3
|
-
export { buildArgs, checkAgentAuth, describeAgentFailure, extractSentinelOutput,
|
|
4
|
-
export type { AgentAuthStatus, AgentDebugEvent, AgentRunResult, ClassifyComponentCall, ClassifyPropCall, ClassifySlotCall, ExcludePropCall,
|
|
3
|
+
export { buildArgs, checkAgentAuth, describeAgentFailure, extractSentinelOutput, parseSelectToolCallLines, parseTokenToolCallLines, parseToolCallLines, resolveAgentModel, resolveBinary, runAgent, } from './agent-runner.js';
|
|
4
|
+
export type { AgentAuthStatus, AgentDebugEvent, AgentRunResult, ClassifyComponentCall, ClassifyPropCall, ClassifySlotCall, ExcludePropCall, ParsedSelectToolCalls, ParsedTokenToolCalls, ParsedToolCalls, RejectComponentCall, SelectComponentCall, SelectToolCall, SetGroupCall, SetTokenCall, ToolCall, TokenToolCall, } from './agent-runner.js';
|
|
5
5
|
export { createLocalCliAgentInvoker } from './agent-invoker.js';
|
|
6
6
|
export type { AgentInvoker, CreateLocalCliAgentInvokerOptions, InvokeAgentOptions } from './agent-invoker.js';
|
|
7
7
|
export { buildPrompt, formatCustomPromptBanner, resolveSkillPath } from './prompt-builder.js';
|
|
8
|
-
export type {
|
|
8
|
+
export type { Mode, PromptOptions, Skill } from './prompt-builder.js';
|
|
9
9
|
export { formatGenerateProgressLine } from './progress.js';
|
package/dist/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Agent identity
|
|
2
2
|
export { AGENT_NAMES, DEFAULT_AGENT_NAME, isAgentName } from './agent-names.js';
|
|
3
3
|
// Agent invocation (low-level)
|
|
4
|
-
export { buildArgs, checkAgentAuth, describeAgentFailure, extractSentinelOutput,
|
|
4
|
+
export { buildArgs, checkAgentAuth, describeAgentFailure, extractSentinelOutput, parseSelectToolCallLines, parseTokenToolCallLines, parseToolCallLines, resolveAgentModel, resolveBinary, runAgent, } from './agent-runner.js';
|
|
5
5
|
// Agent invocation (interface)
|
|
6
6
|
export { createLocalCliAgentInvoker } from './agent-invoker.js';
|
|
7
7
|
// Prompt building
|
|
@@ -1,41 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export type Skill = 'components' | 'tokens' | 'select' | 'map-tokens';
|
|
1
|
+
/** `components` — classify component props; `tokens` — classify design tokens; `select` — decide whether a component belongs in Contentful Experience Orchestration */
|
|
2
|
+
export type Skill = 'components' | 'tokens' | 'select';
|
|
4
3
|
export type Mode = 'autonomous';
|
|
5
|
-
/**
|
|
6
|
-
* A component name paired with the source file it was extracted from, for
|
|
7
|
-
* token-mapping evidence. `content` is the real file text (bounded, see
|
|
8
|
-
* MAX_COMPONENT_SOURCE_CHARS) — this pipeline is agent-fs-free by design (see
|
|
9
|
-
* generate-components.md's "you do not write any files"), so the caller must
|
|
10
|
-
* read the file itself and inline the text here rather than handing the
|
|
11
|
-
* agent a path and expecting it to open the file. `null` when the file
|
|
12
|
-
* couldn't be read (moved/deleted since extraction) — callers fall back to
|
|
13
|
-
* inferring from the prop name and $token.kind alone in that case.
|
|
14
|
-
* `siblingFiles` carries the content of files the source file relatively
|
|
15
|
-
* imports (e.g. a co-located `.styles.ts`) — token-resolution logic often
|
|
16
|
-
* lives one hop away from the component file itself. `truncatedSiblingCount`
|
|
17
|
-
* is set when the caller found more resolvable sibling files than it inlines
|
|
18
|
-
* (see MAX_SIBLING_FILES) — surfaced in the prompt so the classifier knows
|
|
19
|
-
* evidence was dropped rather than that the search came up empty.
|
|
20
|
-
*/
|
|
21
|
-
export interface ComponentSourceRef {
|
|
22
|
-
component: string;
|
|
23
|
-
sourcePath: string;
|
|
24
|
-
content: string | null;
|
|
25
|
-
siblingFiles?: Array<{
|
|
26
|
-
path: string;
|
|
27
|
-
content: string;
|
|
28
|
-
}>;
|
|
29
|
-
truncatedSiblingCount?: number;
|
|
30
|
-
/**
|
|
31
|
-
* Properties with at least one use that fell outside the excerpt budget of
|
|
32
|
-
* the file it sits in. The reader cannot see that use, so its absence is a
|
|
33
|
-
* gap in the evidence, not evidence of absence.
|
|
34
|
-
*/
|
|
35
|
-
usesNotShown?: string[];
|
|
36
|
-
}
|
|
37
|
-
/** Plain-data shape of the CDF generated so far — component name -> component entry. */
|
|
38
|
-
export type GeneratedCdf = Record<string, CDFComponentEntry>;
|
|
39
4
|
/**
|
|
40
5
|
* Render the warning banner shown when a custom skill prompt is active.
|
|
41
6
|
* Always cites the bundled invariants that the override bypasses so the
|
|
@@ -54,12 +19,6 @@ export interface PromptOptions {
|
|
|
54
19
|
outDir: string;
|
|
55
20
|
/** For components skill only: the single component's name (used in error messages). */
|
|
56
21
|
componentName?: string;
|
|
57
|
-
/** For map-tokens skill: the CDF generated so far. Filtered internally to design-category token-typed props only. */
|
|
58
|
-
generatedCdf?: GeneratedCdf;
|
|
59
|
-
/** For map-tokens skill: the full DTCG token tree. Flattened internally to a path+`$type` index, with `$value` stripped. */
|
|
60
|
-
tokenTree?: Record<string, unknown>;
|
|
61
|
-
/** Component source file references — consumption evidence for the components skill, explicit-restriction evidence (comments, allowlists) for map-tokens. */
|
|
62
|
-
componentSourceRefs?: ComponentSourceRef[];
|
|
63
22
|
/**
|
|
64
23
|
* Feature 8: custom prompt path override. When set, this absolute or relative
|
|
65
24
|
* `.md` path is read in place of the bundled skill file. The bundled-prompt
|
|
@@ -2,7 +2,6 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { flattenDTCG } from '@contentful/experience-design-system-types';
|
|
6
5
|
/**
|
|
7
6
|
* Render the warning banner shown when a custom skill prompt is active.
|
|
8
7
|
* Always cites the bundled invariants that the override bypasses so the
|
|
@@ -17,7 +16,6 @@ const SKILL_FILES = {
|
|
|
17
16
|
components: 'generate-components.md',
|
|
18
17
|
tokens: 'generate-tokens.md',
|
|
19
18
|
select: 'select-components.md',
|
|
20
|
-
'map-tokens': 'map-tokens.md',
|
|
21
19
|
};
|
|
22
20
|
export async function buildPrompt(options) {
|
|
23
21
|
const skillContent = await readSkillFile(options.skill, options.skillPathOverride);
|
|
@@ -70,11 +68,6 @@ function inferFenceLang(filename) {
|
|
|
70
68
|
ts: 'ts',
|
|
71
69
|
mts: 'ts',
|
|
72
70
|
cts: 'ts',
|
|
73
|
-
tsx: 'tsx',
|
|
74
|
-
jsx: 'jsx',
|
|
75
|
-
vue: 'vue',
|
|
76
|
-
svelte: 'svelte',
|
|
77
|
-
astro: 'astro',
|
|
78
71
|
scss: 'scss',
|
|
79
72
|
sass: 'scss',
|
|
80
73
|
css: 'css',
|
|
@@ -83,53 +76,8 @@ function inferFenceLang(filename) {
|
|
|
83
76
|
};
|
|
84
77
|
return map[ext] ?? 'text';
|
|
85
78
|
}
|
|
86
|
-
/**
|
|
87
|
-
* Keeps only design-category, token-typed props per component (dropping
|
|
88
|
-
* components left with none), alongside the distinct `$token.kind` values
|
|
89
|
-
* seen among them and whether any such prop has no `$token.kind` at all —
|
|
90
|
-
* collected in the same pass rather than a second walk of the raw CDF.
|
|
91
|
-
*/
|
|
92
|
-
function filterDesignTokenProps(cdf) {
|
|
93
|
-
const result = {};
|
|
94
|
-
const kinds = new Set();
|
|
95
|
-
let hasUnscoped = false;
|
|
96
|
-
for (const [componentName, component] of Object.entries(cdf)) {
|
|
97
|
-
const properties = component.$properties;
|
|
98
|
-
if (!properties)
|
|
99
|
-
continue;
|
|
100
|
-
const filteredProps = {};
|
|
101
|
-
for (const [propName, prop] of Object.entries(properties)) {
|
|
102
|
-
if (prop.$type === 'token' && prop.$category === 'design') {
|
|
103
|
-
const { '$token.allowed': _tokenAllowed, ...rest } = prop;
|
|
104
|
-
filteredProps[propName] = rest;
|
|
105
|
-
const kind = prop['$token.kind'];
|
|
106
|
-
if (typeof kind === 'string' && kind.length > 0) {
|
|
107
|
-
kinds.add(kind);
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
hasUnscoped = true;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (Object.keys(filteredProps).length > 0) {
|
|
115
|
-
result[componentName] = { ...component, $properties: filteredProps };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return { filtered: result, kinds: [...kinds].sort(), hasUnscoped };
|
|
119
|
-
}
|
|
120
|
-
/** One line per candidate, e.g. `colors.brand.primary · color` — legible and countable, unlike nested JSON. */
|
|
121
|
-
function formatTokenCandidateLines(entries) {
|
|
122
|
-
return entries.map((entry) => `${entry.path} · ${entry.$type}`).join('\n');
|
|
123
|
-
}
|
|
124
|
-
/** Renders one kind-scoped (or, for `kind: null`, full-tree) candidate section. Sections are cumulative, never merged, so each stays independently legible. */
|
|
125
|
-
function renderTokenCandidateSection(kind, entries) {
|
|
126
|
-
const label = kind
|
|
127
|
-
? `Token path index — ${kind} candidates only`
|
|
128
|
-
: 'Token path index — full tree (no $token.kind to scope by)';
|
|
129
|
-
return `${label}, one leaf token per line as \`path · type\`, no \`$value\`:\n${formatTokenCandidateLines(entries)}`;
|
|
130
|
-
}
|
|
131
79
|
function buildPreamble(options) {
|
|
132
|
-
const { skill, rawComponentsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline
|
|
80
|
+
const { skill, rawComponentsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline } = options;
|
|
133
81
|
const sections = [];
|
|
134
82
|
if (rawComponentsInline) {
|
|
135
83
|
sections.push(`Raw component data (JSON):\n\`\`\`json\n${rawComponentsInline}\n\`\`\``);
|
|
@@ -145,56 +93,6 @@ function buildPreamble(options) {
|
|
|
145
93
|
if (tokenMapInline) {
|
|
146
94
|
sections.push(`Token-name sidecar (raw name → DTCG path):\n\`\`\`json\n${tokenMapInline}\n\`\`\``);
|
|
147
95
|
}
|
|
148
|
-
const filteredTokenProps = generatedCdf ? filterDesignTokenProps(generatedCdf) : undefined;
|
|
149
|
-
if (filteredTokenProps && Object.keys(filteredTokenProps.filtered).length > 0) {
|
|
150
|
-
sections.push(`Generated CDF so far — design-category token props only (JSON):\n\`\`\`json\n${JSON.stringify(filteredTokenProps.filtered)}\n\`\`\``);
|
|
151
|
-
}
|
|
152
|
-
if (tokenTree) {
|
|
153
|
-
const index = flattenDTCG(tokenTree, '').sort((a, b) => a.path.localeCompare(b.path));
|
|
154
|
-
if (index.length > 0) {
|
|
155
|
-
if (filteredTokenProps) {
|
|
156
|
-
const { kinds, hasUnscoped } = filteredTokenProps;
|
|
157
|
-
for (const kind of kinds) {
|
|
158
|
-
const scoped = index.filter((entry) => entry.$type === kind);
|
|
159
|
-
if (scoped.length > 0)
|
|
160
|
-
sections.push(renderTokenCandidateSection(kind, scoped));
|
|
161
|
-
}
|
|
162
|
-
if (hasUnscoped)
|
|
163
|
-
sections.push(renderTokenCandidateSection(null, index));
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
sections.push(renderTokenCandidateSection(null, index));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (componentSourceRefs && componentSourceRefs.length > 0) {
|
|
171
|
-
const withContent = componentSourceRefs.filter((ref) => ref.content != null);
|
|
172
|
-
const withoutContent = componentSourceRefs.filter((ref) => ref.content == null);
|
|
173
|
-
if (withContent.length > 0) {
|
|
174
|
-
const blocks = withContent.map((ref) => {
|
|
175
|
-
const mainBlock = `#### ${ref.component} (\`${ref.sourcePath}\`)\n\`\`\`${inferFenceLang(ref.sourcePath)}\n${ref.content}\n\`\`\``;
|
|
176
|
-
const siblingBlocks = (ref.siblingFiles ?? []).map((sibling) => `##### ${ref.component} — imported file \`${sibling.path}\`\n\`\`\`${inferFenceLang(sibling.path)}\n${sibling.content}\n\`\`\``);
|
|
177
|
-
const truncationNote = ref.truncatedSiblingCount && ref.truncatedSiblingCount > 0
|
|
178
|
-
? [
|
|
179
|
-
`##### ${ref.component} — +${ref.truncatedSiblingCount} more imported file${ref.truncatedSiblingCount === 1 ? '' : 's'} not shown (evidence may be incomplete)`,
|
|
180
|
-
]
|
|
181
|
-
: [];
|
|
182
|
-
const notShownNote = ref.usesNotShown && ref.usesNotShown.length > 0
|
|
183
|
-
? [
|
|
184
|
-
`##### ${ref.component} — uses not shown: ${ref.usesNotShown.join(', ')}\n\nThe files above are excerpts, and each of these properties has at least one use that fell outside the excerpt budget. Treat their consumption as unknown, not absent: do not classify them \`token\` on what is shown, and do not conclude they are unread.`,
|
|
185
|
-
]
|
|
186
|
-
: [];
|
|
187
|
-
return [mainBlock, ...siblingBlocks, ...truncationNote, ...notShownNote].join('\n\n');
|
|
188
|
-
});
|
|
189
|
-
sections.push(`### Component source references\n\n${blocks.join('\n\n')}`);
|
|
190
|
-
}
|
|
191
|
-
if (withoutContent.length > 0) {
|
|
192
|
-
const guidance = skill === 'components'
|
|
193
|
-
? 'there is no consumption evidence for these, so `token` cannot be earned for any of their props — classify from the prop signature alone, and any prop that would otherwise be enum-versus-token is `enum` (or `string`)'
|
|
194
|
-
: 'there is no source evidence to narrow from — emit nothing for their props';
|
|
195
|
-
sections.push(`Component source unavailable for (JSON) — ${guidance}:\n\`\`\`json\n${JSON.stringify(withoutContent.map((ref) => ({ component: ref.component, sourcePath: ref.sourcePath })))}\n\`\`\``);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
96
|
const inputBlock = sections.length > 0 ? `\n\n${sections.join('\n\n')}` : '';
|
|
199
97
|
if (skill === 'components') {
|
|
200
98
|
return buildComponentsAutonomousPreamble(inputBlock);
|
|
@@ -202,9 +100,6 @@ function buildPreamble(options) {
|
|
|
202
100
|
if (skill === 'select') {
|
|
203
101
|
return buildSelectAutonomousPreamble(inputBlock);
|
|
204
102
|
}
|
|
205
|
-
if (skill === 'map-tokens') {
|
|
206
|
-
return buildMapTokensAutonomousPreamble(inputBlock);
|
|
207
|
-
}
|
|
208
103
|
return buildTokensAutonomousPreamble(inputBlock);
|
|
209
104
|
}
|
|
210
105
|
function buildComponentsAutonomousPreamble(inputBlock) {
|
|
@@ -215,6 +110,8 @@ Context: You are classifying a React component for **Contentful Experience Orche
|
|
|
215
110
|
- **content**: the data a content editor fills in (text, images, URLs, rich text)
|
|
216
111
|
- **state**: runtime behavioral flags (disabled, loading, expanded, identifiers)
|
|
217
112
|
|
|
113
|
+
For props with complex TypeScript types (named types, enums): reason from the prop name and type name to classify them. Do not automatically exclude a prop just because its type is a named reference — infer the likely values and classify it as enum if it controls appearance.
|
|
114
|
+
|
|
218
115
|
Your task: classify every prop and slot in the component below. Apply all judgment calls yourself — do not pause to ask for confirmation. Include a "description" field on each tool call to document your reasoning so the developer can review it afterward.
|
|
219
116
|
|
|
220
117
|
All input data is provided inline below — do not read any additional files.${inputBlock}
|
|
@@ -241,12 +138,10 @@ Rules:
|
|
|
241
138
|
- Every slot in the input must have exactly one classify_slot call.
|
|
242
139
|
- Valid cdf_type values: string, richtext, media, enum, token, boolean
|
|
243
140
|
- Valid cdf_category values: content, design, state
|
|
244
|
-
- For enum type, always include
|
|
245
|
-
- For token type, always include
|
|
246
|
-
-
|
|
247
|
-
-
|
|
248
|
-
- Q2: is the value written straight into a style or attribute (\`rx={radius}\`, \`padding: \${padding}\`)? no → not \`token\`, apply the type rules.
|
|
249
|
-
- Q3: is there a design-token reference at that use — a \`tokens.*\` parameter default, a \`tokenReference\`, an inline \`tokens.*\` / \`var(--*)\`? yes → \`token\` (cite both lines in "reason"); no → \`string\`. "Ambiguity resolves to \`enum\`" applies only when Q1–Q3 cannot be answered from the source shown.
|
|
141
|
+
- For enum type, always include "values" (non-empty string array).
|
|
142
|
+
- For token type, always include "token_kind" (DTCG \$type, e.g. "color").
|
|
143
|
+
- href and URL props → cdf_type "string", cdf_category "content". Do NOT use cdf_type "link" — it is not valid.
|
|
144
|
+
- Framework internals (ref, event handlers, test IDs) → exclude_prop.
|
|
250
145
|
- CSS design props (className, style, styles, positional/geometric props: top, bottom, left, right, rotation, offset, etc.) → classify_prop, cdf_type: "string", cdf_category: "design".
|
|
251
146
|
- 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.
|
|
252
147
|
- On classify_prop, "reason" is REQUIRED and is the LLM's internal rationale — 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.
|
|
@@ -278,31 +173,6 @@ Rules:
|
|
|
278
173
|
- Emit exactly one tool call per input component. The "name" field must match a component name from the input array exactly. Tool calls may appear in any order.
|
|
279
174
|
- You may emit prose lines (not starting with {) to reason before each tool call — they are ignored by the parser.`;
|
|
280
175
|
}
|
|
281
|
-
function buildMapTokensAutonomousPreamble(inputBlock) {
|
|
282
|
-
return `You are running as part of the experience-design-system-cli generate pipeline in AUTONOMOUS mode. The developer is not present to answer questions.
|
|
283
|
-
|
|
284
|
-
Context: The components below already have design-category, token-typed props (\`$token.kind\` set). Your task is to decide, from source evidence, whether to narrow each one to a restricted subset (\`token_allowed\`) of the tokens matching its \`$token.kind\`. Apply all judgment calls yourself — do not pause to ask for confirmation.
|
|
285
|
-
|
|
286
|
-
All input data is provided inline below — do not read any additional files.${inputBlock}
|
|
287
|
-
|
|
288
|
-
## Output protocol
|
|
289
|
-
|
|
290
|
-
Do NOT write any files or emit any JSON blobs. Instead, emit one JSON object per line to stdout for each prop you narrow. The CLI reads your stdout line by line and writes each decision directly to the pipeline database.
|
|
291
|
-
|
|
292
|
-
The one tool call you may emit:
|
|
293
|
-
|
|
294
|
-
\`\`\`
|
|
295
|
-
{"tool":"map_token_prop","component":"<ComponentName>","prop":"<propName>","token_allowed":["colors.brand.primary","colors.brand.secondary"]}
|
|
296
|
-
\`\`\`
|
|
297
|
-
|
|
298
|
-
Rules:
|
|
299
|
-
- Emit exactly one JSON object per line. No multi-line JSON. No markdown fences around the lines.
|
|
300
|
-
- Only emit a call for a prop that appears in the "Generated CDF so far" section.
|
|
301
|
-
- Each "Token path index" section below is already scoped to one \`$token.kind\` — a prop only draws candidates from the section matching its own \`$token.kind\` (or the "full tree" section, for a prop with no \`$token.kind\`). Never cross sections, never emit a group/prefix path, and never invent a path — every entry in \`token_allowed\` must exist verbatim in the matching section.
|
|
302
|
-
- \`token_allowed\` is required and must be non-empty when the call is emitted.
|
|
303
|
-
- For the judgment call of whether a prop should be narrowed at all, follow the decision tree in the map-tokens skill file: it explains defaults vs. restrictions, misclassified variant-name props, and when to emit nothing.
|
|
304
|
-
- You may emit prose lines (not starting with \`{\`) anywhere — they are ignored by the parser and serve as your reasoning log.`;
|
|
305
|
-
}
|
|
306
176
|
function buildTokensAutonomousPreamble(inputBlock) {
|
|
307
177
|
return `You are running as part of the experience-design-system-cli generate pipeline in AUTONOMOUS mode. The developer is not present to answer questions.
|
|
308
178
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-generation",
|
|
3
|
-
"version": "2.27.5-dev-build-
|
|
3
|
+
"version": "2.27.5-dev-build-56846c8.0",
|
|
4
4
|
"description": "Agent-invocation and skill-prompt engine for the Contentful Experience Design System SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"skills/"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@contentful/experience-design-system-types": "2.27.5-dev-build-
|
|
25
|
+
"@contentful/experience-design-system-types": "2.27.5-dev-build-56846c8.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@tsconfig/node24": "^24.0.4",
|
|
@@ -29,8 +29,6 @@ All input is embedded inline in the prompt before this file:
|
|
|
29
29
|
- **Raw component data** — `RawComponentDefinition[]` (one component for this run)
|
|
30
30
|
- **DTCG token data** — full token tree, if provided
|
|
31
31
|
- **Token-name sidecar** — raw CSS custom property name → DTCG dot-notation path, if provided
|
|
32
|
-
- **Component source references** — the real file text for the component's own source, plus up to 5 sibling files (e.g. a co-located `.styles.ts` or `utils.ts`, or a file reached transitively through another sibling — such as a re-exported component's own styles module), if provided. **You have no filesystem access and no tools — `sourcePath` is a citation label only, never something to open.**
|
|
33
|
-
- A **"uses not shown"** line may accompany these files. The files are excerpts windowed around the property names, and this line names the properties with at least one use that fell outside the excerpt budget. Treat those properties' consumption as unknown: do not classify them `token` on what is shown, and do not conclude they are unread. Unknown resolves to `enum` (or `string`) per "Ambiguity resolves to `enum`".
|
|
34
32
|
|
|
35
33
|
```typescript
|
|
36
34
|
interface RawPropDefinition {
|
|
@@ -41,7 +39,7 @@ interface RawPropDefinition {
|
|
|
41
39
|
allowedValues?: string[];
|
|
42
40
|
defaultValue?: string;
|
|
43
41
|
description?: string;
|
|
44
|
-
tokenReference?: string; // raw token name
|
|
42
|
+
tokenReference?: string; // raw token name, e.g. "--brand-primary"
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
interface RawSlotDefinition {
|
|
@@ -91,8 +89,7 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
|
|
|
91
89
|
- Every prop in the input must produce exactly one call: `classify_prop` OR `exclude_prop`.
|
|
92
90
|
- Every slot must produce exactly one `classify_slot` call.
|
|
93
91
|
- 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
|
|
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.
|
|
92
|
+
- `values` is required for `cdf_type: "enum"` — must be a non-empty string array.
|
|
96
93
|
- `token_kind` is required for `cdf_type: "token"` — must be a DTCG `$type` string, e.g. `"color"`.
|
|
97
94
|
- `required` must be a JSON boolean (`true`/`false`), not a string.
|
|
98
95
|
- `description` on `classify_prop` is customer-facing — keep it short and subject to the description content rules below.
|
|
@@ -132,99 +129,6 @@ Exactly **6** valid types:
|
|
|
132
129
|
|
|
133
130
|
---
|
|
134
131
|
|
|
135
|
-
### `enum` versus `token`
|
|
136
|
-
|
|
137
|
-
Both describe a closed set of choices, but the *contents* differ, and that is the whole distinction:
|
|
138
|
-
|
|
139
|
-
| | What the list holds | Example |
|
|
140
|
-
|---|---|---|
|
|
141
|
-
| `enum` | Variant names the component accepts | `["primary", "secondary", "ghost"]` |
|
|
142
|
-
| `token` | Design token paths the prop may bind to | `["color.brand.primary", "color.brand.neutral"]` |
|
|
143
|
-
|
|
144
|
-
The distinction is **what the component does with the value it receives**. That
|
|
145
|
-
is decidable from source; it is not inferred from the property's name, from the
|
|
146
|
-
component's role, or from the property's TypeScript type.
|
|
147
|
-
|
|
148
|
-
An `enum` design property is delivered as a plain string, so the component
|
|
149
|
-
receives `variant="primary"` — the value its own code branches on. A `token`
|
|
150
|
-
property is resolved to the token's value *before* it reaches the component, so
|
|
151
|
-
that same component would receive `variant="#0059c8"` and match no branch.
|
|
152
|
-
|
|
153
|
-
#### The three-question procedure (closed, ordered)
|
|
154
|
-
|
|
155
|
-
For every design prop that carries a value (not a boolean), answer these three
|
|
156
|
-
questions **in this order**, from the source shown, and stop at the first answer
|
|
157
|
-
that decides. There is no fourth question.
|
|
158
|
-
|
|
159
|
-
- **Q1 — Does the component look the value up, switch on it, or compare it against names?**
|
|
160
|
-
An indexed access (`tokens[fontColor]`, `SPACING[padding]`, a `Record<Variant, Token>`),
|
|
161
|
-
a `switch (variant)` / `if (variant === 'x')` chain, a `styles[variant]` class lookup,
|
|
162
|
-
or a vocabulary of its own (`allowedValues`, a literal union) — any of these means
|
|
163
|
-
the component needs the *name*. A presence check (`if (width)`) or arithmetic on
|
|
164
|
-
the value is not a lookup.
|
|
165
|
-
**Yes → `enum`, names in `values`, stop.** No → Q2.
|
|
166
|
-
- **Q2 — Is the value written straight into a style or attribute?**
|
|
167
|
-
`rx={radius}`, `padding: ${padding}`, `style={{ color }}`, `background: var(${bg})` —
|
|
168
|
-
the value reaches the renderer without the component resolving it.
|
|
169
|
-
**No → not `token`; leave this procedure and continue with the type rules.** Yes → Q3.
|
|
170
|
-
- **Q3 — Is there a design-token reference at that use?** Any one of:
|
|
171
|
-
the parameter default is a token (`radius = tokens.borderRadiusSmall`), the raw prop
|
|
172
|
-
carries a `tokenReference`, or an inline `tokens.*` / `var(--*)` sits in the same
|
|
173
|
-
expression. The reference must belong to *this* prop — a `tokens.*` on some other
|
|
174
|
-
line of the file, or in a sibling's style object, is not one.
|
|
175
|
-
**Yes → `token`, stop; resolve `token_kind` from that reference. No → `string`, stop.**
|
|
176
|
-
|
|
177
|
-
**The raw TypeScript type is not an input to Q1–Q3.** `string`, `number`,
|
|
178
|
-
`string | number`, `stringOrNumber` — none of these answers any question above;
|
|
179
|
-
they say what the compiler accepts, not what the component does with the value.
|
|
180
|
-
"The type accepts any value, the default just happens to be a token reference" is
|
|
181
|
-
a Q3-yes described as a no: a token parameter default *is* the design-token
|
|
182
|
-
reference Q3 asks for, and the prop is `token`. Equally, a prop named `color` with
|
|
183
|
-
no lookup and no token reference is `string`, however token-like the name.
|
|
184
|
-
|
|
185
|
-
**Two worked examples — same shape, opposite answers:**
|
|
186
|
-
|
|
187
|
-
```tsx
|
|
188
|
-
// Divider.tsx
|
|
189
|
-
export const Divider = ({ thickness = tokens.borderWidthDefault, inset = 0 }: DividerProps) => (
|
|
190
|
-
<hr style={{ borderTopWidth: thickness, marginLeft: inset }} />
|
|
191
|
-
);
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
- `thickness` — Q1: no lookup. Q2: yes, `borderTopWidth: thickness`. Q3: yes, the
|
|
195
|
-
parameter default is `tokens.borderWidthDefault`. → **`token`**, `token_kind` from
|
|
196
|
-
`borderWidthDefault` via the sidecar. Its `string | number` annotation changes nothing.
|
|
197
|
-
- `inset` — Q1: no lookup. Q2: yes, `marginLeft: inset`. Q3: no, the default is the
|
|
198
|
-
bare number `0` and nothing at this use is a token. → **`string`**, `default: "0"`.
|
|
199
|
-
|
|
200
|
-
A lookup map from variant names to tokens is evidence **for** `enum`, not against
|
|
201
|
-
it — that is Q1 answering yes. The map exists precisely because the component was
|
|
202
|
-
handed a name it had to resolve itself.
|
|
203
|
-
|
|
204
|
-
**`token` is earned by Q2 and Q3 together, and only that way.** Cite both lines
|
|
205
|
-
in `reason`: the interpolation and the token reference. Once both are cited, the
|
|
206
|
-
prop is `token` — do not then downgrade it because of its type annotation, because
|
|
207
|
-
the default "could be overridden" (every default can), or because the tie-break
|
|
208
|
-
below exists. Nothing downstream re-derives, corrects, or second-guesses this —
|
|
209
|
-
what you emit is what ships.
|
|
210
|
-
|
|
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
|
|
217
|
-
questions *can* be answered from the source shown, they are answered and the
|
|
218
|
-
tie-break does not apply: a clear Q1-yes is `enum`, a clear Q2-yes + Q3-yes is
|
|
219
|
-
`token`, and a type annotation does not create ambiguity. An `enum` is delivered
|
|
220
|
-
as the plain string the component was written to receive and remains reachable
|
|
221
|
-
from a content-type field; a wrong `token` delivers a resolved value the
|
|
222
|
-
component cannot branch on and is permanently excluded from content-type field
|
|
223
|
-
mapping. That asymmetric cost is why a genuine tie goes to `enum` — it is not a
|
|
224
|
-
reason to call a decided case a tie.
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
132
|
## Valid cdf_category values
|
|
229
133
|
|
|
230
134
|
| cdf_category | Use case |
|
|
@@ -241,18 +145,14 @@ The pre-classified `category` in the raw input is a starting point — correct i
|
|
|
241
145
|
|
|
242
146
|
For each `RawPropDefinition`, apply in order:
|
|
243
147
|
|
|
244
|
-
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.
|
|
245
|
-
|
|
246
|
-
**The governing principle:** a prop is pass-through if it is inherited from a base interface the component extends (`React.HTMLAttributes<...>`, `AllHTMLAttributes`, a native element's attribute interface, an ARIA-attributes interface, etc.) or is one of a small set of universal framework hooks, **and the component does not redeclare or give it component-specific meaning**. Test this from the type declaration, not from name-matching against a list — a component that `extends React.HTMLAttributes<HTMLDivElement>` inherits `title`, `lang`, `translate`, `slot`, and dozens more that a fixed list will never enumerate; a component that redeclares one of these with its own type or JSDoc (e.g. its own `title: string` documented as "card heading") has given it new meaning and it is no longer pass-through.
|
|
247
|
-
|
|
248
|
-
Always exclude under this principle:
|
|
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:
|
|
249
149
|
- Framework internals: `ref`, `innerRef`, event handlers (any `onSomething`), `testId`, `data-testid`, `key`
|
|
250
|
-
-
|
|
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`
|
|
251
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`
|
|
252
152
|
- Data attributes: any `data-*` prop
|
|
253
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
|
|
254
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
|
|
255
|
-
- **Important caveat**: only exclude when the prop is
|
|
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.
|
|
256
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:
|
|
257
157
|
- `icon` / `leftIcon` / `rightIcon` / `prefixIcon` / `suffixIcon` — slot or `string` (icon name); see slot guidance below
|
|
258
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.
|
|
@@ -263,25 +163,10 @@ For each `RawPropDefinition`, apply in order:
|
|
|
263
163
|
- `accessibleNameRef` / `accessibleDescriptionRef` (web components) — these are ID references for a11y wiring; classify as `string`, `cdf_category: "state"` (behavioral wiring, not design or content).
|
|
264
164
|
- `eventDetails` / similar telemetry props — `cdf_category: "state"`.
|
|
265
165
|
3. **Positional/geometric design prop?** (`top`, `bottom`, `left`, `right`, `rotation`, `offset`, `zIndex`) → `classify_prop`, `cdf_type: "string"`, `cdf_category: "design"`.
|
|
266
|
-
4.
|
|
267
|
-
|
|
268
|
-
* **Q1** looked up / switched on / compared / has its own vocabulary → `cdf_type: "enum"`, names in `values`, no `token_kind`. This holds even when every name maps 1:1 to a design token; the map is the component resolving a name it was given.
|
|
269
|
-
* **Q2** not written straight into a style or attribute → not `token`; continue to rules 5–7.
|
|
270
|
-
* **Q3** written straight into a style or attribute **and** a token reference at that use (a `tokens.*` parameter default, a `tokenReference`, an inline `tokens.*` / `var(--*)`) → `cdf_type: "token"`, `token_kind` from that reference via the sidecar, no `values`. Interpolated with no token reference at that use → `cdf_type: "string"`.
|
|
271
|
-
* Q1–Q3 **cannot be answered** from the source shown (unread, truncated, in a file you cannot see, evidence pointing both ways) → `cdf_type: "enum"` (or `string`). See "Ambiguity resolves to `enum`" above.
|
|
272
|
-
|
|
273
|
-
**Do not classify on the component's role.** A layout primitive is not automatically a token consumer. Design systems routinely key their primitives on token *names*, and those props are `enum` — the component needs the name to perform its own lookup:
|
|
274
|
-
|
|
275
|
-
```ts
|
|
276
|
-
// Box.tsx — padding is an enum, despite Box being a layout primitive
|
|
277
|
-
const { padding } = props; // "spacingXs"
|
|
278
|
-
getSpacingStyles({ padding }); // → SpacingTable["spacingXs"] → "0.5rem"
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Classify that `padding` as `token` and the platform delivers `"0.5rem"`, so `SpacingTable["0.5rem"]` is `undefined` and the spacing silently disappears. A value that is spelled like a token name is still a lookup key (Q1 yes). Curated components with named variant APIs (`Button`, `Tag`, `Badge`, `Avatar`, `Notification`) are `enum` for the same reason. A design system may legitimately produce **zero** token-typed props.
|
|
282
|
-
5. **Union of string literals** (e.g. `'a' | 'b' | 'c'`)? → `cdf_type: "enum"`, extract literals into `values`. (Rule 4's Q1 will normally have decided this already — a literal union is the prop's own vocabulary.) This applies even when Component source references show each literal resolving to a design token internally — a resolved token map is evidence about the component's internals, not its interface.
|
|
166
|
+
4. **Has `tokenReference`?** → `cdf_type: "token"`, resolve `token_kind` via sidecar lookup (see below). This overrides all other heuristics.
|
|
167
|
+
5. **Union of string literals** (e.g. `'a' | 'b' | 'c'`)? → `cdf_type: "enum"`, extract literals into `values`.
|
|
283
168
|
6. **Raw type is `string`** and prop name is `href`, `url`, or clearly a URL? → `cdf_type: "string"`, `cdf_category: "content"`.
|
|
284
|
-
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"`.
|
|
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"`.
|
|
285
170
|
8. **Media/image type** (`ImageProps`, `MediaSource`, asset types)? → `cdf_type: "media"`.
|
|
286
171
|
9. **Rich text / markup** (`ReactNode` used as content, HTML string)? → `cdf_type: "richtext"`.
|
|
287
172
|
10. **Complex type — resolve before excluding** (see below).
|
|
@@ -297,8 +182,8 @@ A prop with a complex TypeScript type is **not automatically excluded**. Many pr
|
|
|
297
182
|
| Raw type pattern | How to resolve |
|
|
298
183
|
|---|---|
|
|
299
184
|
| `'primary' \| 'secondary' \| 'ghost'` (union of literals) | → `enum`, extract `values` |
|
|
300
|
-
| `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum
|
|
301
|
-
| `Variant` / `variant` prop | Usually a visual design variant. → `enum`, `cdf_category: "design"`.
|
|
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
|
+
| `Variant` / `variant` prop | Usually a visual design variant. → `enum`, `cdf_category: "design"`. Infer values from context. |
|
|
302
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. |
|
|
303
188
|
| `ExperienceConfiguration<Variant>` / deep generic | Personalization config — → `exclude_prop`, reason: `"personalization configuration — framework internal"` |
|
|
304
189
|
| `React.Dispatch<...>` / setter | State setter — → `exclude_prop`, reason: `"React state setter — framework internal"` |
|
|
@@ -348,20 +233,26 @@ Rules for nested objects:
|
|
|
348
233
|
|
|
349
234
|
---
|
|
350
235
|
|
|
351
|
-
##
|
|
236
|
+
## Token-aware mapping
|
|
352
237
|
|
|
353
|
-
|
|
238
|
+
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"`).
|
|
354
239
|
|
|
355
|
-
|
|
240
|
+
1. Look up `tokenReference` in the inline token-name sidecar → get the DTCG dot-notation path
|
|
241
|
+
2. Traverse that path in the inline DTCG token data to reach the leaf token
|
|
242
|
+
3. Use the leaf's `$type` (e.g. `"color"`) as `token_kind`
|
|
356
243
|
|
|
244
|
+
Example:
|
|
357
245
|
```
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
{"tool":"classify_prop","prop":"variant","cdf_type":"enum","cdf_category":"design","required":false,"values":["primary","secondary","warning","negative"],"default":"primary","description":"Visual variant","reason":"Fully token-backed internally, but the component consumes the variant name. Token classification would deliver a resolved color the component cannot branch on."}
|
|
246
|
+
tokenReference: "--brand-primary"
|
|
247
|
+
→ sidecar["--brand-primary"] → "colors.brand.primary"
|
|
248
|
+
→ token data: colors.brand.primary.$type → "color"
|
|
249
|
+
→ tool call: {"tool":"classify_prop","prop":"bgColor","cdf_type":"token","cdf_category":"design","token_kind":"color","description":"..."}
|
|
363
250
|
```
|
|
364
251
|
|
|
252
|
+
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"`.
|
|
253
|
+
|
|
254
|
+
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"`.
|
|
255
|
+
|
|
365
256
|
---
|
|
366
257
|
|
|
367
258
|
## Category correction rules
|
|
@@ -370,7 +261,7 @@ The pre-classified `category` is wrong in predictable ways. Correct silently (do
|
|
|
370
261
|
|
|
371
262
|
- Visual style props (`color`, `size`, `padding`, `spacing`, `variant`, `theme`, `bgColor`, `imageOnLeft`, `enableXxx`) classified as `content` or `state` → `design`
|
|
372
263
|
- Interactive/behavioral state props (`disabled`, `loading`, `expanded`, `selected`, `checked`, `active`, `isOpen`, `isSearchVisible`, `showXxx`, `hideXxx`, `preview`, `componentId`, `sectionKey`, `variantIndex`) classified as `design` or `content` → `state`
|
|
373
|
-
- Text/label/data props (`label`, `description`, `caption`, `text`, `boldText`, `labelText`, `richText`, `backgroundImage`, `link`, `placeholder`, `searchValue`, `total`, `slug`) classified as `design` → `content
|
|
264
|
+
- Text/label/data props (`title`, `label`, `description`, `caption`, `text`, `boldText`, `labelText`, `richText`, `backgroundImage`, `link`, `placeholder`, `searchValue`, `total`, `slug`) classified as `design` → `content`
|
|
374
265
|
- Locale classified as `content` → `state` (it is a behavioral routing value, not editor-filled text)
|
|
375
266
|
|
|
376
267
|
> **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.
|
|
@@ -472,6 +363,13 @@ inferring likely values: ["h1", "h2", "h3", "h4", "h5", "h6"] — documenting in
|
|
|
472
363
|
{"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"}
|
|
473
364
|
```
|
|
474
365
|
|
|
366
|
+
### Token-linked prop
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
bgColor has tokenReference "--bg-primary" — looking up sidecar
|
|
370
|
+
{"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"}
|
|
371
|
+
```
|
|
372
|
+
|
|
475
373
|
### href prop
|
|
476
374
|
|
|
477
375
|
```
|
|
@@ -485,6 +383,7 @@ href is a URL string — cdf_type string (not link), category content
|
|
|
485
383
|
|
|
486
384
|
- **Prop with unresolvable type** (generics, intersection, callback) → `exclude_prop` with reason `"complex type — not representable in CDF"`.
|
|
487
385
|
- **Component with zero classified props after exclusions** → still emit `classify_component`. The DB entry will have an empty `$properties` object.
|
|
386
|
+
- **tokenReference present but not in sidecar** → `cdf_type: "token"`, omit `token_kind`, add `description` warning.
|
|
488
387
|
- **Slot not in DB** → skipped with a warning; does not abort the run.
|
|
489
388
|
- **Prop not in DB** → skipped with a warning; does not abort the run.
|
|
490
389
|
|
|
@@ -496,18 +395,15 @@ Before emitting any tool calls, verify:
|
|
|
496
395
|
2. Every slot has exactly one `classify_slot` call
|
|
497
396
|
3. `classify_component` is emitted exactly once
|
|
498
397
|
4. Every `cdf_type: "enum"` has a non-empty `values` array
|
|
499
|
-
5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed)
|
|
398
|
+
5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed)
|
|
500
399
|
6. No `cdf_type: "link"` — all href/url props use `string`
|
|
501
400
|
7. `required` values are JSON booleans, not strings
|
|
502
|
-
8. Framework, DOM, accessibility, and data-* pass-through props are excluded
|
|
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.
|
|
503
402
|
9. No `cdf_type: "link"` used — `link` is reserved and rejected by the CLI parser
|
|
504
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.
|
|
505
404
|
11. `classify_component` includes a `rationale` object with all three sub-fields (`rationale.description`, `rationale.props`, `rationale.slots`) populated as non-empty strings.
|
|
506
405
|
12. Every `classify_slot` includes a non-empty `rationale` string.
|
|
507
406
|
13. `rationale.description` follows the same "Description content rules" as `description` — no internal initiative names (`INTEG-*`, `EDSI`, `DSI`, `M1`, `M2`, wave/phase references, etc.).
|
|
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`.
|
|
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.
|
|
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`.
|
|
511
407
|
|
|
512
408
|
After the run completes, the developer can validate the pipeline output with:
|
|
513
409
|
|
package/skills/map-tokens.md
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# Map Tokens — Token Restriction Narrowing Skill
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
For every design-category, token-typed prop already present in the generated CDF, decide whether there is concrete evidence to narrow it to a restricted subset (`$token.allowed`). Output one JSON tool call per line to stdout — the CLI reads it and writes each decision directly to the pipeline database. You do not write any files.
|
|
6
|
-
|
|
7
|
-
This is a narrowing fallback, not a classification step: it does not re-classify props or touch anything that isn't already a design-category `token` prop. It also does not name a candidate universe — the universe is implicit, every token in the library whose type matches the prop's `$token.kind`. Your only job is to decide, from source evidence, whether that universe should be narrowed further.
|
|
8
|
-
|
|
9
|
-
Default-path resolution is deterministic and happens in the CLI before this skill runs. A resolved `$default` is not model input for a mapping decision and never asks you to emit a mapping — a default alone does not restrict the allowed list.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Input
|
|
14
|
-
|
|
15
|
-
All input is embedded inline in the prompt before this file:
|
|
16
|
-
|
|
17
|
-
- **Generated CDF so far** — design-category, token-typed props only, grouped by component. Every prop shown here already has `$type: "token"` and `$category: "design"`; you do not need to re-verify either.
|
|
18
|
-
- **Token path index** — one or more sections, each a flat list of `path · type` lines (one leaf token per line, `$value` omitted). Every section is pre-scoped: a section titled with a `$token.kind` contains every leaf token of that type and no others; a prop with that `$token.kind` draws its candidates only from the matching section. The "full tree" section — present only when at least one prop has no `$token.kind` — lists every leaf token, unscoped. Never mix candidates across sections.
|
|
19
|
-
- **Component source references** — the real file text for each component (bounded/truncated), rendered inline as a fenced code block, so you can look for an explicit restriction: a comment naming the valid tokens, or code that validates the prop against a fixed list of token paths. Content from files the component's own file imports (siblings) is inlined the same way, one hop deep — token-resolution logic often lives one file away from the component itself. When more sibling files were found than could be inlined, or a property has a use that fell outside the excerpt shown, the prompt says so explicitly; treat that as unknown evidence, not absence. **You have no filesystem access and no tools — a citation path is a label only, never something to open.** When a component's source couldn't be read (moved/deleted since extraction), there is no source evidence to narrow from — skip the prop.
|
|
20
|
-
|
|
21
|
-
If no token path index is provided, there is nothing to narrow against — do not emit any tool calls.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Decision tree
|
|
26
|
-
|
|
27
|
-
For each design-category, token-typed prop shown in the "Generated CDF so far" section:
|
|
28
|
-
|
|
29
|
-
1. **Prop type is a union of variant names** (e.g. `'primary' | 'secondary'`)? That prop should not be a `token` at all — the component receives a name and resolves it, which is the `enum` case in the classification step. Do not narrow it; narrowing cannot repair a wrong type. Emit nothing, and note in a prose line that it looks misclassified.
|
|
30
|
-
2. **A default value or `tokenReference` is the prop's default, not a restriction.** `padding = tokens.spacingM` or a structured `tokenReference` tells you which token the prop *starts* on — it does not say the author may choose no other. Narrowing to that single path would leave the marketer one option, which is worse than no list. On its own, this evidence yields no tool call. If you narrow on other grounds (step 3), the default's path must be in the list.
|
|
31
|
-
3. **Look for an explicit restriction** in the component source or its siblings: a comment naming the valid tokens, or code that validates/maps the prop against a fixed list of token paths (an allowlist array, a `satisfies` over specific token keys). If found, emit `$token.allowed` as the evidenced subset, scoped to tokens of the prop's `$token.kind`.
|
|
32
|
-
4. **No explicit restriction?** Emit nothing. Omitting the list means "any token of this kind," which is correct and live — a guessed list is worse than none because it freezes the author's choices.
|
|
33
|
-
5. **No plausible candidates at all** (no index entries of the prop's `$token.kind`)? Skip the prop.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## Output protocol
|
|
38
|
-
|
|
39
|
-
Emit one JSON object per line. The CLI parses lines starting with `{`; anything else is prose, ignored by the parser — use it freely for reasoning.
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
{"tool":"map_token_prop","component":"<ComponentName>","prop":"<propName>","token_allowed":["colors.brand.primary","colors.brand.secondary"]}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Rules:
|
|
46
|
-
|
|
47
|
-
- Emit exactly one JSON object per line. No multi-line JSON.
|
|
48
|
-
- Only emit a call for a prop that appears in the "Generated CDF so far" section.
|
|
49
|
-
- `token_allowed` is required and must be non-empty. If there's nothing to narrow to, emit no call at all.
|
|
50
|
-
- Every path in `token_allowed` must be an individual **leaf** path that exists verbatim in the token path index section matching the prop's `$token.kind` (or the "full tree" section, for a prop with no `$token.kind`). Never emit a group/prefix path (e.g. `colors.brand`) — the index has no entry for groups, only leaves. Never invent a path, and never substitute a variant/enum name for a real token path; if you can't find a matching path, omit that entry.
|
|
51
|
-
- No `$value` is provided in this step — reason from paths and `$type` only.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Examples
|
|
56
|
-
|
|
57
|
-
### Explicit restriction — narrow
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
interface ButtonProps {
|
|
61
|
-
/** Accepts only the brand colour tokens: colors.brand.primary or colors.brand.secondary. */
|
|
62
|
-
accentColor?: string;
|
|
63
|
-
}
|
|
64
|
-
const ACCENT_TOKENS = ['colors.brand.primary', 'colors.brand.secondary'] as const;
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
accentColor is restricted to two named tokens by the doc comment and the ACCENT_TOKENS allowlist
|
|
69
|
-
{"tool":"map_token_prop","component":"Button","prop":"accentColor","token_allowed":["colors.brand.primary","colors.brand.secondary"]}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### No restriction evidence — emit nothing
|
|
73
|
-
|
|
74
|
-
`bgColor` is interpolated generically, with no comment or allowlist naming specific tokens, and no default. Any color token remains valid:
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
bgColor has no restriction evidence in source — any color token remains valid, so no tool call
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Checklist
|
|
83
|
-
|
|
84
|
-
Before emitting any tool calls, verify:
|
|
85
|
-
|
|
86
|
-
1. Every `map_token_prop` call targets a prop that appears in the "Generated CDF so far" section.
|
|
87
|
-
2. Every path in `token_allowed` is a leaf path that exists verbatim in the token path index, and matches the prop's `$token.kind` — no group/prefix paths, no variant names, nothing invented.
|
|
88
|
-
3. `token_allowed` is never empty.
|
|
89
|
-
4. Every emitted list includes the prop's own default token when it has one.
|
|
90
|
-
5. No list was emitted on the strength of a default alone, and none targets a prop whose type is a union of variant names.
|