@contentful/experience-design-system-generation 2.35.1-dev-build-45d6c918.0 → 2.36.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.
Files changed (65) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/agent-invoker.d.ts +25 -0
  3. package/dist/src/{services/agent/local-cli-agent-invoker.js → agent-invoker.js} +2 -2
  4. package/dist/src/agent-names.d.ts +0 -1
  5. package/dist/src/agent-names.js +0 -7
  6. package/dist/src/agent-runner.d.ts +144 -0
  7. package/dist/src/agent-runner.js +564 -0
  8. package/dist/src/index.d.ts +7 -19
  9. package/dist/src/index.js +6 -18
  10. package/dist/src/prompt-builder.d.ts +81 -0
  11. package/dist/src/prompt-builder.js +344 -0
  12. package/package.json +2 -2
  13. package/dist/src/controller/create-generation-endpoint.d.ts +0 -3
  14. package/dist/src/controller/create-generation-endpoint.js +0 -15
  15. package/dist/src/controller/helpers/validate-generation-request.d.ts +0 -2
  16. package/dist/src/controller/helpers/validate-generation-request.js +0 -18
  17. package/dist/src/orchestrator/execute-generation-orchestrator.d.ts +0 -6
  18. package/dist/src/orchestrator/execute-generation-orchestrator.js +0 -12
  19. package/dist/src/services/agent/helpers/build-agent-args.d.ts +0 -6
  20. package/dist/src/services/agent/helpers/build-agent-args.js +0 -58
  21. package/dist/src/services/agent/helpers/check-agent-auth.d.ts +0 -3
  22. package/dist/src/services/agent/helpers/check-agent-auth.js +0 -19
  23. package/dist/src/services/agent/helpers/check-claude-auth.d.ts +0 -2
  24. package/dist/src/services/agent/helpers/check-claude-auth.js +0 -37
  25. package/dist/src/services/agent/helpers/describe-agent-failure.d.ts +0 -2
  26. package/dist/src/services/agent/helpers/describe-agent-failure.js +0 -5
  27. package/dist/src/services/agent/helpers/extract-sentinel-output.d.ts +0 -1
  28. package/dist/src/services/agent/helpers/extract-sentinel-output.js +0 -16
  29. package/dist/src/services/agent/helpers/resolve-agent-binary.d.ts +0 -2
  30. package/dist/src/services/agent/helpers/resolve-agent-binary.js +0 -8
  31. package/dist/src/services/agent/local-cli-agent-invoker.d.ts +0 -3
  32. package/dist/src/services/agent/run-agent.d.ts +0 -15
  33. package/dist/src/services/agent/run-agent.js +0 -70
  34. package/dist/src/services/parsing/helpers/read-tool-call-objects.d.ts +0 -4
  35. package/dist/src/services/parsing/helpers/read-tool-call-objects.js +0 -54
  36. package/dist/src/services/parsing/parse-map-token-tool-calls.d.ts +0 -2
  37. package/dist/src/services/parsing/parse-map-token-tool-calls.js +0 -39
  38. package/dist/src/services/parsing/parse-prop-tool-calls.d.ts +0 -2
  39. package/dist/src/services/parsing/parse-prop-tool-calls.js +0 -91
  40. package/dist/src/services/parsing/parse-select-tool-calls.d.ts +0 -2
  41. package/dist/src/services/parsing/parse-select-tool-calls.js +0 -27
  42. package/dist/src/services/parsing/parse-token-tool-calls.d.ts +0 -2
  43. package/dist/src/services/parsing/parse-token-tool-calls.js +0 -39
  44. package/dist/src/services/prompts/build-prompt.d.ts +0 -4
  45. package/dist/src/services/prompts/build-prompt.js +0 -8
  46. package/dist/src/services/prompts/helpers/build-preamble.d.ts +0 -2
  47. package/dist/src/services/prompts/helpers/build-preamble.js +0 -48
  48. package/dist/src/services/prompts/helpers/build-skill-preambles.d.ts +0 -4
  49. package/dist/src/services/prompts/helpers/build-skill-preambles.js +0 -126
  50. package/dist/src/services/prompts/helpers/format-source-refs.d.ts +0 -2
  51. package/dist/src/services/prompts/helpers/format-source-refs.js +0 -31
  52. package/dist/src/services/prompts/helpers/format-token-candidates.d.ts +0 -8
  53. package/dist/src/services/prompts/helpers/format-token-candidates.js +0 -80
  54. package/dist/src/services/prompts/helpers/resolve-skill-file.d.ts +0 -4
  55. package/dist/src/services/prompts/helpers/resolve-skill-file.js +0 -49
  56. package/dist/src/types/agent.d.ts +0 -10
  57. package/dist/src/types/agent.js +0 -1
  58. package/dist/src/types/contract.d.ts +0 -24
  59. package/dist/src/types/contract.js +0 -1
  60. package/dist/src/types/invoker.d.ts +0 -17
  61. package/dist/src/types/invoker.js +0 -1
  62. package/dist/src/types/prompt.d.ts +0 -53
  63. package/dist/src/types/prompt.js +0 -1
  64. package/dist/src/types/tool-calls.d.ts +0 -91
  65. package/dist/src/types/tool-calls.js +0 -1
@@ -0,0 +1,81 @@
1
+ import { type CDFComponentEntry } from '@contentful/experience-design-system-types';
2
+ /** `components` — classify component props; `tokens` — classify design tokens; `select` — decide whether a component belongs in Contentful Experience Orchestration; `map-tokens` — suggest `$token.allowed` restrictions for design-category token props */
3
+ export type Skill = 'components' | 'tokens' | 'select' | 'map-tokens';
4
+ 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
+ /**
40
+ * Render the warning banner shown when a custom skill prompt is active.
41
+ * Always cites the bundled invariants that the override bypasses so the
42
+ * operator cannot miss it.
43
+ */
44
+ export declare function formatCustomPromptBanner(skill: 'components' | 'select', path: string): string;
45
+ export interface PromptOptions {
46
+ skill: Skill;
47
+ mode: Mode;
48
+ rawComponentsInline?: string;
49
+ rawTokensInline?: string;
50
+ /** Original filename for raw tokens — used to set the correct code fence language. */
51
+ rawTokensFilename?: string;
52
+ tokensInline?: string;
53
+ tokenMapInline?: string;
54
+ outDir: string;
55
+ /** For components skill only: the single component's name (used in error messages). */
56
+ 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
+ /**
64
+ * Feature 8: custom prompt path override. When set, this absolute or relative
65
+ * `.md` path is read in place of the bundled skill file. The bundled-prompt
66
+ * invariants (utility-wrapper rejection, description content rules, etc.) do
67
+ * NOT apply under an override — callers are responsible for showing the
68
+ * appropriate warning banner.
69
+ */
70
+ skillPathOverride?: string;
71
+ /** Inline prompt instructions, taking precedence over the bundled skill or path override. */
72
+ skillContentOverride?: string;
73
+ /** JSON-serialized summary of existing space Components. Callers pre-project the shape per skill. */
74
+ existingComponentsInline?: string;
75
+ /** JSON-serialized summary of existing space DesignTokens. Callers pre-project the shape per skill. */
76
+ existingTokensInline?: string;
77
+ /** JSON-serialized hard allowlist for generated slot allowed-components references. */
78
+ componentAllowlistInline?: string;
79
+ }
80
+ export declare function buildPrompt(options: PromptOptions): Promise<string>;
81
+ export declare function resolveSkillPath(skill: Skill): string;
@@ -0,0 +1,344 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { flattenDTCG } from '@contentful/experience-design-system-types';
6
+ /**
7
+ * Render the warning banner shown when a custom skill prompt is active.
8
+ * Always cites the bundled invariants that the override bypasses so the
9
+ * operator cannot miss it.
10
+ */
11
+ export function formatCustomPromptBanner(skill, path) {
12
+ return (`WARNING: Custom prompt active for ${skill}: ${path}\n` +
13
+ ` Bundled invariants (utility-wrapper rejection, description content rules) do NOT apply.\n` +
14
+ ` You are responsible for the prompt's correctness.\n`);
15
+ }
16
+ const SKILL_FILES = {
17
+ components: 'generate-components.md',
18
+ tokens: 'generate-tokens.md',
19
+ select: 'select-components.md',
20
+ 'map-tokens': 'map-tokens.md',
21
+ };
22
+ export async function buildPrompt(options) {
23
+ const skillContent = options.skillContentOverride ?? (await readSkillFile(options.skill, options.skillPathOverride));
24
+ const preamble = buildPreamble(options);
25
+ return `${preamble}\n\nSkill instructions follow:\n---\n${skillContent}`;
26
+ }
27
+ export function resolveSkillPath(skill) {
28
+ if (!(skill in SKILL_FILES))
29
+ throw new Error(`Invalid skill: ${skill}`);
30
+ // Walk up until we find the skills/ directory (works from both src/ and dist/src/ contexts)
31
+ const thisDir = dirname(fileURLToPath(import.meta.url));
32
+ let dir = thisDir;
33
+ for (;;) {
34
+ const candidate = join(dir, 'skills');
35
+ if (existsSync(candidate))
36
+ return join(candidate, SKILL_FILES[skill]);
37
+ const parent = resolve(dir, '..');
38
+ if (parent === dir) {
39
+ throw new Error(`skill file missing from CLI installation (could not locate skills/ directory from: ${thisDir})`);
40
+ }
41
+ dir = parent;
42
+ }
43
+ }
44
+ async function readSkillFile(skill, override) {
45
+ if (override) {
46
+ const skillPath = resolve(override);
47
+ try {
48
+ return await readFile(skillPath, 'utf8');
49
+ }
50
+ catch {
51
+ throw new Error(`custom prompt file not found (skill: ${skill}, path: ${skillPath})`);
52
+ }
53
+ }
54
+ const skillPath = resolveSkillPath(skill);
55
+ try {
56
+ return await readFile(skillPath, 'utf8');
57
+ }
58
+ catch {
59
+ throw new Error(`skill file missing from CLI installation — try reinstalling the CLI (looked for: ${skillPath})`);
60
+ }
61
+ }
62
+ function inferFenceLang(filename) {
63
+ if (!filename)
64
+ return 'json';
65
+ const ext = filename.split('.').pop()?.toLowerCase() ?? '';
66
+ const map = {
67
+ js: 'js',
68
+ mjs: 'js',
69
+ cjs: 'js',
70
+ ts: 'ts',
71
+ mts: 'ts',
72
+ cts: 'ts',
73
+ tsx: 'tsx',
74
+ jsx: 'jsx',
75
+ vue: 'vue',
76
+ svelte: 'svelte',
77
+ astro: 'astro',
78
+ scss: 'scss',
79
+ sass: 'scss',
80
+ css: 'css',
81
+ json: 'json',
82
+ json5: 'json',
83
+ };
84
+ return map[ext] ?? 'text';
85
+ }
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
+ function buildPreamble(options) {
132
+ const { skill, rawComponentsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline, componentAllowlistInline, } = options;
133
+ const sections = [];
134
+ if (existingComponentsInline) {
135
+ sections.push(`Existing components in the target Contentful space (JSON) — use for alignment, not as a hard filter:\n\`\`\`json\n${existingComponentsInline}\n\`\`\``);
136
+ }
137
+ if (existingTokensInline) {
138
+ sections.push(`Existing design tokens in the target Contentful space (JSON) — prefer binding to these paths over inventing new ones:\n\`\`\`json\n${existingTokensInline}\n\`\`\``);
139
+ }
140
+ if (componentAllowlistInline) {
141
+ sections.push(`Known component names for slot allowed_components (hard allowlist; never use any other name):\n\`\`\`json\n${componentAllowlistInline}\n\`\`\``);
142
+ }
143
+ if (rawComponentsInline) {
144
+ sections.push(`Raw component data (JSON):\n\`\`\`json\n${rawComponentsInline}\n\`\`\``);
145
+ }
146
+ if (rawTokensInline) {
147
+ const lang = inferFenceLang(rawTokensFilename);
148
+ const label = rawTokensFilename ? `Raw token source (${rawTokensFilename})` : 'Raw token source';
149
+ sections.push(`${label}:\n\`\`\`${lang}\n${rawTokensInline}\n\`\`\``);
150
+ }
151
+ if (tokensInline) {
152
+ sections.push(`DTCG token data (for token kind lookups):\n\`\`\`json\n${tokensInline}\n\`\`\``);
153
+ }
154
+ if (tokenMapInline) {
155
+ sections.push(`Token-name sidecar (raw name → DTCG path):\n\`\`\`json\n${tokenMapInline}\n\`\`\``);
156
+ }
157
+ const filteredTokenProps = generatedCdf ? filterDesignTokenProps(generatedCdf) : undefined;
158
+ if (filteredTokenProps && Object.keys(filteredTokenProps.filtered).length > 0) {
159
+ sections.push(`Generated CDF so far — design-category token props only (JSON):\n\`\`\`json\n${JSON.stringify(filteredTokenProps.filtered)}\n\`\`\``);
160
+ }
161
+ if (tokenTree) {
162
+ const index = flattenDTCG(tokenTree, '').sort((a, b) => a.path.localeCompare(b.path));
163
+ if (index.length > 0) {
164
+ if (filteredTokenProps) {
165
+ const { kinds, hasUnscoped } = filteredTokenProps;
166
+ for (const kind of kinds) {
167
+ const scoped = index.filter((entry) => entry.$type === kind);
168
+ if (scoped.length > 0)
169
+ sections.push(renderTokenCandidateSection(kind, scoped));
170
+ }
171
+ if (hasUnscoped)
172
+ sections.push(renderTokenCandidateSection(null, index));
173
+ }
174
+ else {
175
+ sections.push(renderTokenCandidateSection(null, index));
176
+ }
177
+ }
178
+ }
179
+ if (componentSourceRefs && componentSourceRefs.length > 0) {
180
+ const withContent = componentSourceRefs.filter((ref) => ref.content != null);
181
+ const withoutContent = componentSourceRefs.filter((ref) => ref.content == null);
182
+ if (withContent.length > 0) {
183
+ const blocks = withContent.map((ref) => {
184
+ const mainBlock = `#### ${ref.component} (\`${ref.sourcePath}\`)\n\`\`\`${inferFenceLang(ref.sourcePath)}\n${ref.content}\n\`\`\``;
185
+ const siblingBlocks = (ref.siblingFiles ?? []).map((sibling) => `##### ${ref.component} — imported file \`${sibling.path}\`\n\`\`\`${inferFenceLang(sibling.path)}\n${sibling.content}\n\`\`\``);
186
+ const truncationNote = ref.truncatedSiblingCount && ref.truncatedSiblingCount > 0
187
+ ? [
188
+ `##### ${ref.component} — +${ref.truncatedSiblingCount} more imported file${ref.truncatedSiblingCount === 1 ? '' : 's'} not shown (evidence may be incomplete)`,
189
+ ]
190
+ : [];
191
+ const notShownNote = ref.usesNotShown && ref.usesNotShown.length > 0
192
+ ? [
193
+ `##### ${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.`,
194
+ ]
195
+ : [];
196
+ return [mainBlock, ...siblingBlocks, ...truncationNote, ...notShownNote].join('\n\n');
197
+ });
198
+ sections.push(`### Component source references\n\n${blocks.join('\n\n')}`);
199
+ }
200
+ if (withoutContent.length > 0) {
201
+ const guidance = skill === 'components'
202
+ ? '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`)'
203
+ : 'there is no source evidence to narrow from — emit nothing for their props';
204
+ sections.push(`Component source unavailable for (JSON) — ${guidance}:\n\`\`\`json\n${JSON.stringify(withoutContent.map((ref) => ({ component: ref.component, sourcePath: ref.sourcePath })))}\n\`\`\``);
205
+ }
206
+ }
207
+ const inputBlock = sections.length > 0 ? `\n\n${sections.join('\n\n')}` : '';
208
+ if (skill === 'components') {
209
+ return buildComponentsAutonomousPreamble(inputBlock);
210
+ }
211
+ if (skill === 'select') {
212
+ return buildSelectAutonomousPreamble(inputBlock);
213
+ }
214
+ if (skill === 'map-tokens') {
215
+ return buildMapTokensAutonomousPreamble(inputBlock);
216
+ }
217
+ return buildTokensAutonomousPreamble(inputBlock);
218
+ }
219
+ function buildComponentsAutonomousPreamble(inputBlock) {
220
+ 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.
221
+
222
+ Context: You are classifying a React component for **Contentful Experience Orchestration**. The result is a Component Type — a schema that tells Contentful what a marketer can configure. Properties fall into three categories:
223
+ - **design**: controls how the component looks (variant, size, color, layout toggles)
224
+ - **content**: the data a content editor fills in (text, images, URLs, rich text)
225
+ - **state**: runtime behavioral flags (disabled, loading, expanded, identifiers)
226
+
227
+ 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.
228
+
229
+ All input data is provided inline below — do not read any additional files.${inputBlock}
230
+
231
+ ## Output protocol
232
+
233
+ Do NOT write any files or emit any JSON blobs. Instead, emit one JSON object per line to stdout for each classification decision. The CLI reads your stdout line by line and writes each decision directly to the pipeline database.
234
+
235
+ The four tool calls you may emit are:
236
+
237
+ \`\`\`
238
+ {"tool":"classify_component","description":"<optional component-level description>","rationale":{"description":"<why this component is classified the way it is>","props":"<why these props were chosen>","slots":"<why these slots were chosen>"}}
239
+
240
+ {"tool":"classify_prop","prop":"<propName>","cdf_type":"<type>","cdf_category":"<category>","required":<bool>,"description":"<short customer-facing description>","reason":"<full internal rationale; not customer-facing>","values":["a","b"],"token_kind":"color","default":"<value>"}
241
+
242
+ {"tool":"exclude_prop","prop":"<propName>","reason":"<why excluded>"}
243
+
244
+ {"tool":"classify_slot","slot":"<slotName>","required":<bool>,"allowed_components":["ComponentName"],"description":"<reason>","rationale":"<why this slot was kept in the catalog>"}
245
+ \`\`\`
246
+
247
+ Rules:
248
+ - Emit exactly one JSON object per line. No multi-line JSON. No markdown fences around the lines.
249
+ - Every prop in the input must have exactly one call: either classify_prop or exclude_prop.
250
+ - Every slot in the input must have exactly one classify_slot call.
251
+ - Valid cdf_type values: string, richtext, media, enum, token, boolean
252
+ - Valid cdf_category values: content, design, state
253
+ - For enum type, always include \`values\` (non-empty string array of the variant names the prop accepts).
254
+ - For token type, always include \`token_kind\` (DTCG \$type, e.g. "color"). **Never emit \`values\` for \`cdf_type: "token"\` props** — an enum prop's list holds variant names; a token prop's list holds design token paths, produced separately and never by you. Emitting \`values\` on a token prop makes the definition invalid.
255
+ - Never emit both "values" and "token_kind" on one prop.
256
+ - \`enum\` vs \`token\` is decided by three questions answered in order from the source shown — never from the TypeScript type. Q1: does the component look the value up, switch on it, or have a vocabulary for it (\`tokens[x]\`, \`switch (variant)\`, \`allowedValues\`)? yes → \`enum\`, stop.
257
+ - Q2: is the value written straight into a style or attribute (\`rx={radius}\`, \`padding: \${padding}\`)? no → not \`token\`, apply the type rules.
258
+ - 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.
259
+ - CSS design props (className, style, styles, positional/geometric props: top, bottom, left, right, rotation, offset, etc.) → classify_prop, cdf_type: "string", cdf_category: "design".
260
+ - For \`classify_slot.allowed_components\`, use only exact names from the hard allowlist provided below. Never invent a child from an import, JSX element, type name, icon name, or implementation helper; if a child is not listed, omit it.
261
+ - 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.
262
+ - 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.
263
+ - You may emit prose lines (not starting with {) anywhere — they are ignored by the parser and serve as your reasoning log.`;
264
+ }
265
+ function buildSelectAutonomousPreamble(inputBlock) {
266
+ return `You are running as part of the experience-design-system-cli import pipeline in AUTONOMOUS mode. The developer is not present to answer questions.
267
+
268
+ Your task: review the components provided below and decide whether each belongs in Contentful Experience Orchestration as a Component Type. The input is a JSON array — you may receive 1–N components in a single message. Emit one tool call per input component, named after the component. Apply all judgment calls yourself — do not pause to ask for confirmation. Include a brief "reason" to document your reasoning for each decision.
269
+
270
+ Key rule: accept any component that renders visible UI — atoms, molecules, and organisms are all valid Component Types in Contentful Experience Orchestration. Reject only components that produce zero visual output: React hooks, pure context providers, A/B testing or variant-routing wrappers, analytics trackers, and security utilities. Do NOT reject a component because it has few props, is low-level, or has some A/B testing or personalization-related props mixed in — those props are handled in the generate step.
271
+
272
+ All input data is provided inline below — do not read any additional files.${inputBlock}
273
+
274
+ ## Output protocol
275
+
276
+ Do NOT write any files or emit any JSON blobs. Instead, emit JSON tool calls one per line to stdout. The CLI reads your stdout line by line.
277
+
278
+ The two tool calls — emit exactly one per input component:
279
+
280
+ \`\`\`
281
+ {"tool":"select_component","name":"<ComponentName>","reason":"<brief reason>"}
282
+
283
+ {"tool":"reject_component","name":"<ComponentName>","reason":"<brief reason>"}
284
+ \`\`\`
285
+
286
+ Rules:
287
+ - Emit exactly one JSON object per line. No multi-line JSON. No markdown fences.
288
+ - 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.
289
+ - You may emit prose lines (not starting with {) to reason before each tool call — they are ignored by the parser.`;
290
+ }
291
+ function buildMapTokensAutonomousPreamble(inputBlock) {
292
+ 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.
293
+
294
+ 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.
295
+
296
+ All input data is provided inline below — do not read any additional files.${inputBlock}
297
+
298
+ ## Output protocol
299
+
300
+ 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.
301
+
302
+ The one tool call you may emit:
303
+
304
+ \`\`\`
305
+ {"tool":"map_token_prop","component":"<ComponentName>","prop":"<propName>","token_allowed":["colors.brand.primary","colors.brand.secondary"]}
306
+ \`\`\`
307
+
308
+ Rules:
309
+ - Emit exactly one JSON object per line. No multi-line JSON. No markdown fences around the lines.
310
+ - Only emit a call for a prop that appears in the "Generated CDF so far" section.
311
+ - 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.
312
+ - \`token_allowed\` is required and must be non-empty when the call is emitted.
313
+ - 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.
314
+ - You may emit prose lines (not starting with \`{\`) anywhere — they are ignored by the parser and serve as your reasoning log.`;
315
+ }
316
+ function buildTokensAutonomousPreamble(inputBlock) {
317
+ 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.
318
+
319
+ Your task: classify every raw token from the input below into a DTCG token tree. Apply all judgment calls yourself — do not pause to ask for confirmation. Include a "description" field on each set_token call to document your reasoning.
320
+
321
+ All input data is provided inline below — do not read any additional files.${inputBlock}
322
+
323
+ ## Output protocol
324
+
325
+ Do NOT write any files or emit any JSON blobs. Instead, emit one JSON object per line to stdout for each token or group. The CLI reads your stdout line by line and writes each entry directly to the pipeline database.
326
+
327
+ The two tool calls you may emit are:
328
+
329
+ \`\`\`
330
+ {"tool":"set_group","path":"<dot.notation.path>","description":"<optional group description>"}
331
+
332
+ {"tool":"set_token","path":"<dot.notation.path>","type":"<DTCG type>","value":<value>,"description":"<reason>"}
333
+ \`\`\`
334
+
335
+ Rules:
336
+ - Emit exactly one JSON object per line. No multi-line JSON. No markdown fences.
337
+ - Emit a set_group call for every intermediate group node in the tree.
338
+ - Emit a set_token call for every leaf token.
339
+ - "path" is dot-notation, e.g. "colors.brand.primary" — no leading dots or slashes.
340
+ - "type" must be one of the 13 valid DTCG types: color, dimension, fontFamily, fontWeight, duration, cubicBezier, number, strokeStyle, border, transition, shadow, gradient, typography.
341
+ - "value" must be valid JSON (string, number, array, or object depending on the type). Do NOT wrap it in quotes if it is a complex type.
342
+ - Emit set_group calls before the set_token calls that fall under them.
343
+ - You may emit prose lines (not starting with {) anywhere — they are ignored by the parser and serve as your reasoning log.`;
344
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-generation",
3
- "version": "2.35.1-dev-build-45d6c918.0",
3
+ "version": "2.36.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.35.1-dev-build-45d6c918.0"
25
+ "@contentful/experience-design-system-types": "2.36.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@tsconfig/node24": "^24.0.4",
@@ -1,3 +0,0 @@
1
- import type { GenerationEndpoint, GenerationEndpointOptions, GenerationEndpointRequest, GenerationEndpointResponse } from '../types/contract.js';
2
- export type { GenerationEndpoint, GenerationEndpointOptions, GenerationEndpointRequest, GenerationEndpointResponse };
3
- export declare function createGenerationEndpoint(options?: GenerationEndpointOptions): GenerationEndpoint;
@@ -1,15 +0,0 @@
1
- import { validateGenerationRequest } from './helpers/validate-generation-request.js';
2
- import { executeGenerationOrchestrator } from '../orchestrator/execute-generation-orchestrator.js';
3
- import { createLocalCliAgentInvoker } from '../services/agent/local-cli-agent-invoker.js';
4
- export function createGenerationEndpoint(options = {}) {
5
- const invoker = createLocalCliAgentInvoker({ onDebugEvent: options.onDebugEvent });
6
- return {
7
- async run(request) {
8
- validateGenerationRequest(request);
9
- return executeGenerationOrchestrator({ ...request, invoker });
10
- },
11
- checkAuth(agent) {
12
- return invoker.checkAuth(agent);
13
- },
14
- };
15
- }
@@ -1,2 +0,0 @@
1
- import type { GenerationEndpointRequest } from '../../types/contract.js';
2
- export declare function validateGenerationRequest(request: GenerationEndpointRequest): void;
@@ -1,18 +0,0 @@
1
- import { isAgentName } from '../../agent-names.js';
2
- export function validateGenerationRequest(request) {
3
- if (request === null || typeof request !== 'object') {
4
- throw new TypeError('createGenerationEndpoint requires a request object');
5
- }
6
- if (!isAgentName(request.agent)) {
7
- throw new TypeError(`createGenerationEndpoint requires agent to be a valid agent name, got: ${String(request.agent)}`);
8
- }
9
- if (typeof request.timeoutMs !== 'number' || request.timeoutMs <= 0) {
10
- throw new TypeError('createGenerationEndpoint requires timeoutMs to be a positive number');
11
- }
12
- if (request.promptOptions === null || typeof request.promptOptions !== 'object') {
13
- throw new TypeError('createGenerationEndpoint requires promptOptions to be an object');
14
- }
15
- if (request.onOutput !== undefined && typeof request.onOutput !== 'function') {
16
- throw new TypeError('createGenerationEndpoint requires onOutput to be a function when provided');
17
- }
18
- }
@@ -1,6 +0,0 @@
1
- import type { GenerationEndpointRequest, GenerationEndpointResponse } from '../types/contract.js';
2
- import type { AgentInvoker } from '../services/agent/local-cli-agent-invoker.js';
3
- export interface GenerationOrchestratorRequest extends GenerationEndpointRequest {
4
- readonly invoker: AgentInvoker;
5
- }
6
- export declare function executeGenerationOrchestrator(request: GenerationOrchestratorRequest): Promise<GenerationEndpointResponse>;
@@ -1,12 +0,0 @@
1
- import { buildPrompt } from '../services/prompts/build-prompt.js';
2
- export async function executeGenerationOrchestrator(request) {
3
- const prompt = await buildPrompt(request.promptOptions);
4
- return request.invoker.invoke({
5
- agent: request.agent,
6
- model: request.model,
7
- bedrock: request.bedrock,
8
- prompt,
9
- timeoutMs: request.timeoutMs,
10
- onOutput: request.onOutput,
11
- });
12
- }
@@ -1,6 +0,0 @@
1
- import type { AgentName } from '../../../agent-names.js';
2
- /** Per-agent env vars that switch model calls to AWS Bedrock. */
3
- export declare const BEDROCK_ENV_BY_AGENT: Partial<Record<AgentName, Record<string, string>>>;
4
- export declare function agentSupportsBedrock(agent: AgentName): boolean;
5
- export declare function resolveAgentModel(agent: AgentName, explicit?: string, bedrock?: boolean): string | undefined;
6
- export declare function buildAgentArgs(agent: AgentName, prompt: string, model?: string, promptViaStdin?: boolean, bedrock?: boolean): string[];
@@ -1,58 +0,0 @@
1
- /** Per-agent env vars that switch model calls to AWS Bedrock. */
2
- export const BEDROCK_ENV_BY_AGENT = {
3
- claude: { CLAUDE_CODE_USE_BEDROCK: '1' },
4
- };
5
- const BEDROCK_CAPABLE_AGENTS = new Set(['claude', 'codex', 'opencode']);
6
- export function agentSupportsBedrock(agent) {
7
- return BEDROCK_CAPABLE_AGENTS.has(agent);
8
- }
9
- const DEFAULT_CODEX_BEDROCK_REGION = 'us-east-1';
10
- const DEFAULT_OPENCODE_MODEL = 'claude-haiku-4-5';
11
- const DEFAULT_CODEX_BEDROCK_MODEL = 'openai.gpt-5.6-luna';
12
- const DEFAULT_MODELS = {
13
- claude: 'haiku',
14
- opencode: DEFAULT_OPENCODE_MODEL,
15
- cursor: 'gpt-mini',
16
- copilot: 'Auto',
17
- };
18
- function withBedrockProviderPrefix(model) {
19
- return model.includes('/') ? model : `amazon-bedrock/${model}`;
20
- }
21
- export function resolveAgentModel(agent, explicit, bedrock = false) {
22
- if (explicit && explicit.trim())
23
- return explicit.trim();
24
- const override = process.env[`EDS_AGENT_MODEL_${agent.toUpperCase()}`];
25
- if (override && override.trim())
26
- return override.trim();
27
- if (bedrock && agent === 'codex')
28
- return DEFAULT_CODEX_BEDROCK_MODEL;
29
- if (bedrock && agent === 'opencode')
30
- return withBedrockProviderPrefix(DEFAULT_OPENCODE_MODEL);
31
- return DEFAULT_MODELS[agent];
32
- }
33
- function codexBedrockConfigArgs() {
34
- const region = process.env.AWS_REGION?.trim() || process.env.AWS_DEFAULT_REGION?.trim() || DEFAULT_CODEX_BEDROCK_REGION;
35
- return ['-c', 'model_provider=amazon-bedrock', '-c', `model_providers.amazon-bedrock.region=${region}`];
36
- }
37
- export function buildAgentArgs(agent, prompt, model, promptViaStdin = false, bedrock = false) {
38
- const resolvedModel = resolveAgentModel(agent, model, bedrock);
39
- const modelArg = resolvedModel ? ['--model', resolvedModel] : [];
40
- const promptArg = promptViaStdin ? [] : [prompt];
41
- switch (agent) {
42
- case 'claude':
43
- return ['--print', ...modelArg, ...promptArg];
44
- case 'codex': {
45
- const bedrockArgs = bedrock ? codexBedrockConfigArgs() : [];
46
- return ['exec', ...bedrockArgs, ...modelArg, '--dangerously-bypass-approvals-and-sandbox', ...promptArg];
47
- }
48
- case 'opencode':
49
- return ['run', ...modelArg, ...promptArg];
50
- case 'cursor':
51
- return ['--print', ...modelArg, ...promptArg];
52
- case 'copilot': {
53
- const copilotModel = resolveAgentModel('copilot', model);
54
- const copilotModelArg = !copilotModel || copilotModel === 'Auto' ? [] : ['--model', copilotModel];
55
- return ['-p', ...promptArg, ...copilotModelArg, '--allow-all-tools'];
56
- }
57
- }
58
- }
@@ -1,3 +0,0 @@
1
- import type { AgentName } from '../../../agent-names.js';
2
- import type { AgentAuthStatus } from '../../../types/agent.js';
3
- export declare function checkAgentAuth(agent: AgentName): Promise<AgentAuthStatus>;
@@ -1,19 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { resolveAgentBinary } from './resolve-agent-binary.js';
3
- import { checkClaudeAuth } from './check-claude-auth.js';
4
- export async function checkAgentAuth(agent) {
5
- const binary = resolveAgentBinary(agent);
6
- const binaryExists = await new Promise((resolve) => {
7
- if (binary.startsWith('/')) {
8
- import('node:fs/promises').then((fs) => fs.access(binary).then(() => resolve(true), () => resolve(false)));
9
- return;
10
- }
11
- const child = spawn(process.platform === 'win32' ? 'where' : 'which', [binary], { stdio: 'ignore' });
12
- child.on('close', (code) => resolve(code === 0));
13
- });
14
- if (!binaryExists)
15
- return 'not-found';
16
- if (agent !== 'claude')
17
- return 'ok';
18
- return checkClaudeAuth(binary);
19
- }
@@ -1,2 +0,0 @@
1
- import type { AgentAuthStatus } from '../../../types/agent.js';
2
- export declare function checkClaudeAuth(binary: string): Promise<AgentAuthStatus>;
@@ -1,37 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- export function checkClaudeAuth(binary) {
3
- return new Promise((resolve) => {
4
- const child = spawn(binary, ['auth', 'status', '--json'], {
5
- stdio: ['ignore', 'pipe', 'pipe'],
6
- });
7
- let stdout = '';
8
- let done = false;
9
- const timer = setTimeout(() => {
10
- if (!done) {
11
- done = true;
12
- child.kill('SIGTERM');
13
- resolve('unauthenticated');
14
- }
15
- }, 5000);
16
- child.stdout?.on('data', (chunk) => {
17
- stdout += chunk.toString();
18
- });
19
- child.on('close', (code) => {
20
- if (done)
21
- return;
22
- done = true;
23
- clearTimeout(timer);
24
- if (code !== 0) {
25
- resolve('unauthenticated');
26
- return;
27
- }
28
- try {
29
- const status = JSON.parse(stdout);
30
- resolve(status.loggedIn ? 'ok' : 'unauthenticated');
31
- }
32
- catch {
33
- resolve('unauthenticated');
34
- }
35
- });
36
- });
37
- }
@@ -1,2 +0,0 @@
1
- import type { AgentRunResult } from '../../../types/agent.js';
2
- export declare function describeAgentFailure(result: AgentRunResult, maxDetail?: number): string;