@contentful/experience-design-system-cli 2.27.5-dev-build-975d62f.0 → 2.27.5-dev-build-67ce1cd.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 +3 -3
- package/dist/src/analytics/types.d.ts +1 -1
- package/dist/src/index.js +2137 -1098
- package/dist/src/lib/cli-errors.d.ts +2 -0
- package/dist/src/map-tokens/apply.d.ts +12 -0
- package/dist/src/map-tokens/command.d.ts +2 -0
- package/dist/src/map-tokens/resolve-defaults.d.ts +31 -0
- package/dist/src/map-tokens/tui/MapTokensView.d.ts +13 -0
- package/dist/src/print/command.d.ts +9 -0
- package/dist/src/session/db.d.ts +35 -6
- package/dist/src/session/source-excerpt.d.ts +25 -0
- package/package.json +7 -7
- package/skills/generate-components.md +139 -35
- package/skills/map-tokens.md +90 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { MapTokenPropCall } from '@contentful/experience-design-system-generation';
|
|
3
|
+
export interface ApplyMapTokenPropCallsResult {
|
|
4
|
+
applied: number;
|
|
5
|
+
warnings: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Applies parsed `map_token_prop` calls to the session database. Each call is
|
|
9
|
+
* validated against session state — unlike parseMapTokenPropToolCallLines,
|
|
10
|
+
* which only checks the call's own shape — before any write happens.
|
|
11
|
+
*/
|
|
12
|
+
export declare function applyMapTokenPropCalls(db: DatabaseSync, sessionId: string, calls: MapTokenPropCall[], incomingWarnings: string[]): ApplyMapTokenPropCallsResult;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** A token default extracted from a design-category token property. */
|
|
2
|
+
export interface RawDesignTokenDefault {
|
|
3
|
+
/** The source default, retained verbatim for persistence and review. */
|
|
4
|
+
rawDefault: string;
|
|
5
|
+
/** The property's expected DTCG token type. */
|
|
6
|
+
tokenKind: string | null;
|
|
7
|
+
}
|
|
8
|
+
/** The path and type of one DTCG token leaf. Values are intentionally absent. */
|
|
9
|
+
export interface DTCGTokenLeaf {
|
|
10
|
+
path: string;
|
|
11
|
+
type: string;
|
|
12
|
+
}
|
|
13
|
+
export type UnresolvedTokenDefaultReason = 'no_match' | 'ambiguous' | 'type_mismatch';
|
|
14
|
+
export interface UnresolvedTokenDefault {
|
|
15
|
+
rawDefault: string;
|
|
16
|
+
tokenKind: string | null;
|
|
17
|
+
reason: UnresolvedTokenDefaultReason;
|
|
18
|
+
candidatePaths: string[];
|
|
19
|
+
message: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ResolveTokenDefaultsResult {
|
|
22
|
+
/** Exact source defaults mapped to their one compatible DTCG path. */
|
|
23
|
+
mappings: Record<string, string>;
|
|
24
|
+
diagnostics: UnresolvedTokenDefault[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves extracted source defaults without inspecting token values or changing
|
|
28
|
+
* the source defaults. A normalised leaf-key match is accepted only when it
|
|
29
|
+
* identifies exactly one leaf compatible with the property's token kind.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveTokenDefaults(defaults: readonly RawDesignTokenDefault[], leaves: readonly DTCGTokenLeaf[]): ResolveTokenDefaultsResult;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface MapTokensViewResult {
|
|
3
|
+
agent: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
applied: number;
|
|
6
|
+
cached: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface MapTokensViewProps {
|
|
9
|
+
result: MapTokensViewResult;
|
|
10
|
+
onExit: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function MapTokensView({ result, onExit }: MapTokensViewProps): React.ReactElement;
|
|
13
|
+
export {};
|
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
+
export declare function rebuildDTCGTree(groups: Array<{
|
|
3
|
+
path: string;
|
|
4
|
+
$description?: string;
|
|
5
|
+
}>, tokens: Array<{
|
|
6
|
+
path: string;
|
|
7
|
+
$type: string;
|
|
8
|
+
$value: unknown;
|
|
9
|
+
$description?: string;
|
|
10
|
+
}>): Record<string, unknown>;
|
|
2
11
|
export declare function registerPrintCommand(program: Command): void;
|
package/dist/src/session/db.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import type { RawComponentDefinition } from '../types.js';
|
|
3
3
|
import type { CDFComponentEntry, DTCGTokenEntry, DTCGTokenGroup } from '@contentful/experience-design-system-types';
|
|
4
|
-
import type { ToolCall, TokenToolCall } from '@contentful/experience-design-system-generation';
|
|
4
|
+
import type { ToolCall, TokenToolCall, ComponentSourceRef } from '@contentful/experience-design-system-generation';
|
|
5
5
|
import type { ComponentTypeSummary } from '@contentful/experience-design-system-types';
|
|
6
6
|
import type { SlotCycle, SlotEdge } from '../analyze/cycle-detection.js';
|
|
7
7
|
export type StepStatus = 'pending' | 'complete' | 'failed' | 'interrupted';
|
|
8
|
-
export type CommandName = 'analyze extract' | 'analyze select' | 'generate components' | 'generate tokens' | 'generate edit' | 'apply preview' | 'apply select' | 'apply push' | 'print components' | 'print tokens' | 'import';
|
|
8
|
+
export type CommandName = 'analyze extract' | 'analyze select' | 'generate components' | 'generate tokens' | 'generate edit' | 'apply preview' | 'apply select' | 'apply push' | 'print components' | 'print tokens' | 'map tokens' | 'import';
|
|
9
9
|
export interface SessionRow {
|
|
10
10
|
id: string;
|
|
11
11
|
name: string | null;
|
|
@@ -26,6 +26,30 @@ export interface StepRow {
|
|
|
26
26
|
}
|
|
27
27
|
export declare function getPipelineDbPath(): string;
|
|
28
28
|
export declare function openPipelineDb(dbPath?: string): DatabaseSync;
|
|
29
|
+
/** An exact source token reference paired with its canonical DTCG path. */
|
|
30
|
+
export type RawTokenNamePaths = Record<string, string>;
|
|
31
|
+
export type RawTokenNamePathSource = 'automatic' | 'manual';
|
|
32
|
+
export interface RawTokenNamePath {
|
|
33
|
+
rawName: string;
|
|
34
|
+
path: string;
|
|
35
|
+
source: RawTokenNamePathSource;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Replaces the session's exact source-reference-to-DTCG-path sidecar.
|
|
39
|
+
* Source references are retained verbatim; normalisation belongs to the
|
|
40
|
+
* deterministic resolver, not to persistence or CDF emission.
|
|
41
|
+
*/
|
|
42
|
+
export declare function replaceRawTokenNamePaths(db: DatabaseSync, sessionId: string, tokenNamePaths: RawTokenNamePaths, source?: RawTokenNamePathSource): void;
|
|
43
|
+
/** Loads the exact source-reference-to-DTCG-path sidecar for a session. */
|
|
44
|
+
export declare function loadRawTokenNamePaths(db: DatabaseSync, sessionId: string): RawTokenNamePaths;
|
|
45
|
+
export declare function loadRawTokenNamePathRows(db: DatabaseSync, sessionId: string): RawTokenNamePath[];
|
|
46
|
+
/**
|
|
47
|
+
* Where a prop's token-path list came from. 'agent' is a map-tokens suggestion,
|
|
48
|
+
* which a later run of that step may revise. 'review' is a person's decision in
|
|
49
|
+
* the review editor, which a re-run must leave alone.
|
|
50
|
+
*/
|
|
51
|
+
export type RawPropTokenPathSource = 'agent' | 'review';
|
|
52
|
+
export declare function replaceRawPropTokenPaths(db: DatabaseSync, sessionId: string, componentId: string, propName: string, paths: string[], source: RawPropTokenPathSource): void;
|
|
29
53
|
export interface ApplyToolCallsResult {
|
|
30
54
|
classified: number;
|
|
31
55
|
excluded: number;
|
|
@@ -83,6 +107,8 @@ export type RawComponentWithId = RawComponentDefinition & {
|
|
|
83
107
|
component_id: string;
|
|
84
108
|
};
|
|
85
109
|
export declare function loadRawComponents(db: DatabaseSync, sessionId: string, allowedNames?: Set<string>): RawComponentWithId[];
|
|
110
|
+
export declare function loadComponentSourceRef(name: string, sourcePath: string, propNames?: string[]): Promise<ComponentSourceRef>;
|
|
111
|
+
export declare function loadComponentSourceRefs(db: DatabaseSync, sessionId: string): Promise<ComponentSourceRef[]>;
|
|
86
112
|
export declare function renameEmptySlots(db: DatabaseSync, sessionId: string, componentId: string, componentName: string, slotCount: number): {
|
|
87
113
|
renames: Array<{
|
|
88
114
|
oldName: string;
|
|
@@ -133,9 +159,10 @@ export declare function seedDefaultsFromChangedItems(db: DatabaseSync, sessionId
|
|
|
133
159
|
proposed: object;
|
|
134
160
|
}>): number;
|
|
135
161
|
export declare function backfillUnclassifiedProps(db: DatabaseSync, sessionId: string): number;
|
|
162
|
+
export type CacheEntityType = 'component' | 'token_set' | 'token_mapping';
|
|
136
163
|
export interface CacheEntry {
|
|
137
164
|
inputHash: string;
|
|
138
|
-
entityType:
|
|
165
|
+
entityType: CacheEntityType;
|
|
139
166
|
entityId: string;
|
|
140
167
|
sourceSessionId: string;
|
|
141
168
|
humanEdited: boolean;
|
|
@@ -145,9 +172,10 @@ export interface CacheEntry {
|
|
|
145
172
|
}
|
|
146
173
|
export declare function computeComponentInputHash(component: RawComponentWithId): string;
|
|
147
174
|
export declare function computeTokenInputHash(rawTokenContent: string): string;
|
|
148
|
-
export declare function
|
|
149
|
-
export declare function
|
|
150
|
-
export declare function
|
|
175
|
+
export declare function computeMapTokensInputHash(db: DatabaseSync, sessionId: string, componentSourceRefs?: readonly ComponentSourceRef[]): string;
|
|
176
|
+
export declare function lookupCache(db: DatabaseSync, inputHash: string, entityType: CacheEntityType, entityId: string, promptHash?: string): CacheEntry | null;
|
|
177
|
+
export declare function lookupCacheByEntity(db: DatabaseSync, entityType: CacheEntityType, entityId: string): CacheEntry | null;
|
|
178
|
+
export declare function storeCache(db: DatabaseSync, inputHash: string, entityType: CacheEntityType, entityId: string, sourceSessionId: string, humanEdited: boolean, promptHash?: string): void;
|
|
151
179
|
export declare function storeScannedFiles(db: DatabaseSync, sessionId: string, filePaths: string[]): void;
|
|
152
180
|
export declare function loadScannedFiles(db: DatabaseSync, sessionId: string): string[];
|
|
153
181
|
export declare function storeSlotCycles(db: DatabaseSync, sessionId: string, cycles: Array<SlotCycle & {
|
|
@@ -161,6 +189,7 @@ export declare function clearSlotCycles(db: DatabaseSync, sessionId: string): vo
|
|
|
161
189
|
export declare function markCacheHumanEdited(db: DatabaseSync, entityType: 'component' | 'token_set', entityId: string): void;
|
|
162
190
|
export declare function copyComponentFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string, componentId: string): void;
|
|
163
191
|
export declare function copyTokensFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string): void;
|
|
192
|
+
export declare function copyMapTokensFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string): number;
|
|
164
193
|
export declare function getCliCacheVersion(): Promise<string>;
|
|
165
194
|
export interface ExtractCacheEntry {
|
|
166
195
|
filePath: string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded source excerpts that keep the lines that matter.
|
|
3
|
+
*
|
|
4
|
+
* Head truncation of a styles module keeps its imports and top-level
|
|
5
|
+
* constants and drops the one line that decides a property's classification —
|
|
6
|
+
* the place the value is interpolated into a style. An excerpt built from
|
|
7
|
+
* windows around each property name's occurrences spends the same byte budget
|
|
8
|
+
* on that line instead.
|
|
9
|
+
*/
|
|
10
|
+
export interface SourceExcerpt {
|
|
11
|
+
content: string;
|
|
12
|
+
/**
|
|
13
|
+
* Names with at least one occurrence that did not fit in `content`. The
|
|
14
|
+
* reader cannot see that use, so its absence from the excerpt is a gap, not
|
|
15
|
+
* evidence. Empty when every occurrence is shown.
|
|
16
|
+
*/
|
|
17
|
+
usesNotShown: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns `text` whole when it fits `maxChars`. Otherwise returns windows of
|
|
21
|
+
* `contextLines` lines around every line that mentions one of `names`, merged
|
|
22
|
+
* where they overlap, in file order, until the budget is spent. With no
|
|
23
|
+
* occurrences at all it falls back to the head of the file.
|
|
24
|
+
*/
|
|
25
|
+
export declare function excerptAroundNames(text: string, names: string[], maxChars: number, contextLines?: number): SourceExcerpt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.27.5-dev-build-
|
|
3
|
+
"version": "2.27.5-dev-build-67ce1cd.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,26 +34,26 @@
|
|
|
34
34
|
"commander": "^13.1.0",
|
|
35
35
|
"ink": "^4.4.1",
|
|
36
36
|
"react": "^18.3.1",
|
|
37
|
-
"react-devtools-core": "^4.
|
|
37
|
+
"react-devtools-core": "^4.19.1",
|
|
38
38
|
"react-dom": "^18.3.1",
|
|
39
39
|
"svelte": "^5.56.4",
|
|
40
40
|
"ts-morph": "^27.0.2",
|
|
41
41
|
"typescript": "^5.9.3",
|
|
42
|
-
"@contentful/experience-design-system-types": "2.27.5-dev-build-
|
|
42
|
+
"@contentful/experience-design-system-types": "2.27.5-dev-build-67ce1cd.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@tsconfig/node24": "^24.0.4",
|
|
46
46
|
"@types/node": "^24.0.3",
|
|
47
|
-
"@types/react": "^18.3.
|
|
47
|
+
"@types/react": "^18.3.24",
|
|
48
48
|
"eslint": "^9.39.5",
|
|
49
49
|
"eslint-config-prettier": "^10.1.8",
|
|
50
50
|
"eslint-plugin-prettier": "^5.5.6",
|
|
51
51
|
"ink-testing-library": "^4.0.0",
|
|
52
52
|
"typescript-eslint": "^8.67.0",
|
|
53
53
|
"vitest": "^4.0.16",
|
|
54
|
-
"@contentful/experience-design-system-extraction": "2.27.5-dev-build-
|
|
55
|
-
"@contentful/experience-design-system-
|
|
56
|
-
"@contentful/experience-design-system-
|
|
54
|
+
"@contentful/experience-design-system-extraction": "2.27.5-dev-build-67ce1cd.0",
|
|
55
|
+
"@contentful/experience-design-system-client": "2.27.5-dev-build-67ce1cd.0",
|
|
56
|
+
"@contentful/experience-design-system-generation": "2.27.5-dev-build-67ce1cd.0"
|
|
57
57
|
},
|
|
58
58
|
"repository": {
|
|
59
59
|
"type": "git",
|
|
@@ -29,6 +29,8 @@ 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`".
|
|
32
34
|
|
|
33
35
|
```typescript
|
|
34
36
|
interface RawPropDefinition {
|
|
@@ -39,7 +41,7 @@ interface RawPropDefinition {
|
|
|
39
41
|
allowedValues?: string[];
|
|
40
42
|
defaultValue?: string;
|
|
41
43
|
description?: string;
|
|
42
|
-
tokenReference?: string; // raw token name, e.g. "--brand-primary"
|
|
44
|
+
tokenReference?: string; // raw token name — shape varies by design system, e.g. "--brand-primary" (CSS custom property) or "tokens.blue500" (flat/dotted JS reference)
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
interface RawSlotDefinition {
|
|
@@ -89,7 +91,8 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
|
|
|
89
91
|
- Every prop in the input must produce exactly one call: `classify_prop` OR `exclude_prop`.
|
|
90
92
|
- Every slot must produce exactly one `classify_slot` call.
|
|
91
93
|
- 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.
|
|
94
|
+
- `values` is required for `cdf_type: "enum"` — must be a non-empty string array of the variant names the prop accepts.
|
|
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.
|
|
93
96
|
- `token_kind` is required for `cdf_type: "token"` — must be a DTCG `$type` string, e.g. `"color"`.
|
|
94
97
|
- `required` must be a JSON boolean (`true`/`false`), not a string.
|
|
95
98
|
- `description` on `classify_prop` is customer-facing — keep it short and subject to the description content rules below.
|
|
@@ -129,6 +132,99 @@ Exactly **6** valid types:
|
|
|
129
132
|
|
|
130
133
|
---
|
|
131
134
|
|
|
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
|
+
|
|
132
228
|
## Valid cdf_category values
|
|
133
229
|
|
|
134
230
|
| cdf_category | Use case |
|
|
@@ -145,14 +241,18 @@ The pre-classified `category` in the raw input is a starting point — correct i
|
|
|
145
241
|
|
|
146
242
|
For each `RawPropDefinition`, apply in order:
|
|
147
243
|
|
|
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.
|
|
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:
|
|
149
249
|
- Framework internals: `ref`, `innerRef`, event handlers (any `onSomething`), `testId`, `data-testid`, `key`
|
|
150
|
-
-
|
|
250
|
+
- Any prop inherited from an HTML-attributes base interface and not redeclared by the component — this replaces any fixed name list. Common examples include `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` — but these are examples of the principle, not an exhaustive list; apply the inheritance test to any prop the examples don't cover.
|
|
151
251
|
- Accessibility pass-through: any `aria-*` or `ariaSomething` prop (including bare `aria` as an aria-attributes object), `aria-label`, `aria-hidden`, `aria-describedby`, `aria-controls`
|
|
152
252
|
- Data attributes: any `data-*` prop
|
|
153
253
|
- **Polymorphic component props**: `as`, `element`, `component` (when typed as an HTML tag string or component reference) — these change rendered HTML, not marketer-visible behavior
|
|
154
254
|
- **Framework theming / pass-through escape hatches**: PrimeVue's `dt` / `pt` / `ptOptions` / `unstyled`, MUI/Chakra-style `sx`, anything explicitly typed as a developer "override" / "passthrough" object
|
|
155
|
-
- **Important caveat**: only exclude when the prop is
|
|
255
|
+
- **Important caveat**: only exclude when the prop is inherited/universal *and unredeclared*. Compound names like `fileName`, `displayName`, `dataset`, `dataSource`, `roleDescription`, `idLabel` are not pass-through — classify them normally. Likewise, a `title` (or any other inherited-attribute name) that the component redeclares with its own type or doc comment is component-specific content, not pass-through — classify it per rule 2 onward, do not exclude it and do not route it through the `title` → `content` correction below.
|
|
156
256
|
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:
|
|
157
257
|
- `icon` / `leftIcon` / `rightIcon` / `prefixIcon` / `suffixIcon` — slot or `string` (icon name); see slot guidance below
|
|
158
258
|
- `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.
|
|
@@ -163,10 +263,25 @@ For each `RawPropDefinition`, apply in order:
|
|
|
163
263
|
- `accessibleNameRef` / `accessibleDescriptionRef` (web components) — these are ID references for a11y wiring; classify as `string`, `cdf_category: "state"` (behavioral wiring, not design or content).
|
|
164
264
|
- `eventDetails` / similar telemetry props — `cdf_category: "state"`.
|
|
165
265
|
3. **Positional/geometric design prop?** (`top`, `bottom`, `left`, `right`, `rotation`, `offset`, `zIndex`) → `classify_prop`, `cdf_type: "string"`, `cdf_category: "design"`.
|
|
166
|
-
4.
|
|
167
|
-
|
|
266
|
+
4. **`enum` or `token`? — run the three-question procedure first.** Before any type-based rule, for every design prop that carries a value, answer Q1–Q3 from "`enum` versus `token`" above, in order, from the source shown:
|
|
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.
|
|
168
283
|
6. **Raw type is `string`** and prop name is `href`, `url`, or clearly a URL? → `cdf_type: "string"`, `cdf_category: "content"`.
|
|
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"`.
|
|
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"`. **A design prop reaches this rule only after leaving rule 4 at Q2 (not written into a style) or Q3 (written into a style with no token reference at that use).** A prop that answered Q3 yes is already `token` and never arrives here — a `string`, `number`, or `string | number` annotation on it does not bring it back to this rule.
|
|
170
285
|
8. **Media/image type** (`ImageProps`, `MediaSource`, asset types)? → `cdf_type: "media"`.
|
|
171
286
|
9. **Rich text / markup** (`ReactNode` used as content, HTML string)? → `cdf_type: "richtext"`.
|
|
172
287
|
10. **Complex type — resolve before excluding** (see below).
|
|
@@ -182,8 +297,8 @@ A prop with a complex TypeScript type is **not automatically excluded**. Many pr
|
|
|
182
297
|
| Raw type pattern | How to resolve |
|
|
183
298
|
|---|---|
|
|
184
299
|
| `'primary' \| 'secondary' \| 'ghost'` (union of literals) | → `enum`, extract `values` |
|
|
185
|
-
| `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum
|
|
186
|
-
| `Variant` / `variant` prop | Usually a visual design variant. → `enum`, `cdf_category: "design"`.
|
|
300
|
+
| `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum`. **Never invent a value list.** Use only values you can cite from the source shown (the type's own declaration, a lookup table, a default). If the source shown does not enumerate the values, this is the "cannot be answered" case — apply "Ambiguity resolves to `enum`": emit `enum` (or `string` if it has no discoverable value set) and record the missing evidence in `description` (not `reason` — `description` ships to the CDF, `reason` does not), e.g. `"WARNING: values not visible in source — enum values unconfirmed"`. Do not default to a generic guess like `['sm', 'md', 'lg']` or `['primary', 'secondary']` — those are one design system's vocabulary, not a fallback for missing evidence. |
|
|
301
|
+
| `Variant` / `variant` prop | Usually a visual design variant. → `enum`, `cdf_category: "design"`. Same evidence rule as above — cite values from the source shown; do not invent them. |
|
|
187
302
|
| `Section[]` / array of custom items where the structure is unclear | → `exclude_prop` only if the array elements are complex objects with no obvious flat representation. If items are simple (title, label, id), consider representing as `string` (a comma-separated IDs or keys) or note in `description` why. |
|
|
188
303
|
| `ExperienceConfiguration<Variant>` / deep generic | Personalization config — → `exclude_prop`, reason: `"personalization configuration — framework internal"` |
|
|
189
304
|
| `React.Dispatch<...>` / setter | State setter — → `exclude_prop`, reason: `"React state setter — framework internal"` |
|
|
@@ -233,26 +348,20 @@ Rules for nested objects:
|
|
|
233
348
|
|
|
234
349
|
---
|
|
235
350
|
|
|
236
|
-
##
|
|
351
|
+
## Resolving `token_kind`
|
|
237
352
|
|
|
238
|
-
|
|
353
|
+
`token_kind` becomes `$token.kind` in the CDF output — the DTCG `$type` of the token the prop binds to. For a prop that earned `cdf_type: "token"` (Q2 yes, Q3 yes), take the token name cited at Q3 (`tokens.borderRadiusSmall`, `var(--brand-primary)`, a raw `tokenReference`) and resolve it against the inline token data: through the token-name sidecar (raw name → DTCG dot-notation path) when one is provided, otherwise by matching the name to the DTCG tree by spelling (`tokens.borderRadiusSmall` → `border-radius.border-radius-small`); the leaf's `$type` (`"dimension"`) is the `token_kind`. If the name cannot be resolved to a leaf, keep `cdf_type: "token"`, omit `token_kind`, and say so in `description` (`"WARNING: token_kind unknown — <name> not found in token data"`).
|
|
239
354
|
|
|
240
|
-
|
|
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`
|
|
355
|
+
**Canonical example — a named variant backed by tokens is still `enum`:**
|
|
243
356
|
|
|
244
|
-
Example:
|
|
245
357
|
```
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
358
|
+
Tag.variant has allowedValues ["primary","secondary","warning","negative"] and PillNext.styles.ts
|
|
359
|
+
maps each one to a token (primary→tokens.blue100, warning→tokens.orange100, ...). The mapping is
|
|
360
|
+
internal to the design system — Tag's prop signature accepts the name, not the token. Classifying
|
|
361
|
+
as enum so the author picks "primary" and the component receives "primary".
|
|
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."}
|
|
250
363
|
```
|
|
251
364
|
|
|
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
|
-
|
|
256
365
|
---
|
|
257
366
|
|
|
258
367
|
## Category correction rules
|
|
@@ -261,7 +370,7 @@ The pre-classified `category` is wrong in predictable ways. Correct silently (do
|
|
|
261
370
|
|
|
262
371
|
- Visual style props (`color`, `size`, `padding`, `spacing`, `variant`, `theme`, `bgColor`, `imageOnLeft`, `enableXxx`) classified as `content` or `state` → `design`
|
|
263
372
|
- Interactive/behavioral state props (`disabled`, `loading`, `expanded`, `selected`, `checked`, `active`, `isOpen`, `isSearchVisible`, `showXxx`, `hideXxx`, `preview`, `componentId`, `sectionKey`, `variantIndex`) classified as `design` or `content` → `state`
|
|
264
|
-
- Text/label/data props (`
|
|
373
|
+
- Text/label/data props (`label`, `description`, `caption`, `text`, `boldText`, `labelText`, `richText`, `backgroundImage`, `link`, `placeholder`, `searchValue`, `total`, `slug`) classified as `design` → `content`. `title` is excluded from this auto-correction — it is one of the most common HTML-inherited attribute names (see the pass-through principle above). Only apply this correction to a `title` prop the component redeclares with its own type or documented, component-specific meaning; a `title` that is bare inherited pass-through was already excluded in rule 1 and never reaches this step.
|
|
265
374
|
- Locale classified as `content` → `state` (it is a behavioral routing value, not editor-filled text)
|
|
266
375
|
|
|
267
376
|
> **Key question for category**: "Who fills this in?" — A content editor fills in `content`. A designer configures `design`. Neither fills in `state` — it comes from routing, runtime behavior, or component infrastructure.
|
|
@@ -363,13 +472,6 @@ inferring likely values: ["h1", "h2", "h3", "h4", "h5", "h6"] — documenting in
|
|
|
363
472
|
{"tool":"classify_prop","prop":"titleSize","cdf_type":"enum","cdf_category":"design","required":false,"values":["h1","h2","h3","h4","h5","h6"],"description":"Heading level — inferred from HeadingSize type name; actual values may be h1–h6 or sm/md/lg"}
|
|
364
473
|
```
|
|
365
474
|
|
|
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
|
-
|
|
373
475
|
### href prop
|
|
374
476
|
|
|
375
477
|
```
|
|
@@ -383,7 +485,6 @@ href is a URL string — cdf_type string (not link), category content
|
|
|
383
485
|
|
|
384
486
|
- **Prop with unresolvable type** (generics, intersection, callback) → `exclude_prop` with reason `"complex type — not representable in CDF"`.
|
|
385
487
|
- **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.
|
|
387
488
|
- **Slot not in DB** → skipped with a warning; does not abort the run.
|
|
388
489
|
- **Prop not in DB** → skipped with a warning; does not abort the run.
|
|
389
490
|
|
|
@@ -395,15 +496,18 @@ Before emitting any tool calls, verify:
|
|
|
395
496
|
2. Every slot has exactly one `classify_slot` call
|
|
396
497
|
3. `classify_component` is emitted exactly once
|
|
397
498
|
4. Every `cdf_type: "enum"` has a non-empty `values` array
|
|
398
|
-
5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed)
|
|
499
|
+
5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed) and does **not** have `values` set
|
|
399
500
|
6. No `cdf_type: "link"` — all href/url props use `string`
|
|
400
501
|
7. `required` values are JSON booleans, not strings
|
|
401
|
-
8. Framework, DOM, accessibility, and data-* pass-through props are excluded —
|
|
502
|
+
8. Framework, DOM, accessibility, and data-* pass-through props are excluded per the inheritance principle (rule 1) — any prop inherited from an HTML-attributes base interface and not redeclared by the component, plus `aria-*` (and bare `aria`), `data-*`, polymorphic `as`/`element`/`component`, framework theming `dt`/`pt`/`ptOptions`/`unstyled`/`sx`. A prop matching an inherited-attribute name (including `title`) that the component redeclares with its own type or meaning is NOT excluded. 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.
|
|
402
503
|
9. No `cdf_type: "link"` used — `link` is reserved and rejected by the CLI parser
|
|
403
504
|
10. No `cdf_type: "number"` used — this is not a supported type; use `"string"` with numeric defaults. `cdf_type: "boolean"` IS valid — use it for boolean toggle props.
|
|
404
505
|
11. `classify_component` includes a `rationale` object with all three sub-fields (`rationale.description`, `rationale.props`, `rationale.slots`) populated as non-empty strings.
|
|
405
506
|
12. Every `classify_slot` includes a non-empty `rationale` string.
|
|
406
507
|
13. `rationale.description` follows the same "Description content rules" as `description` — no internal initiative names (`INTEG-*`, `EDSI`, `DSI`, `M1`, `M2`, wave/phase references, etc.).
|
|
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`.
|
|
407
511
|
|
|
408
512
|
After the run completes, the developer can validate the pipeline output with:
|
|
409
513
|
|
|
@@ -0,0 +1,90 @@
|
|
|
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.
|