@contentful/experience-design-system-generation 2.36.2-dev-build-d983d04c.0 → 2.36.2-dev-build-dc6fb2aa.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +1 -1
- package/dist/src/agent-names.d.ts +1 -0
- package/dist/src/agent-names.js +7 -0
- package/dist/src/controller/create-generation-endpoint.d.ts +3 -0
- package/dist/src/controller/create-generation-endpoint.js +15 -0
- package/dist/src/controller/helpers/validate-generation-request.d.ts +2 -0
- package/dist/src/controller/helpers/validate-generation-request.js +18 -0
- package/dist/src/index.d.ts +19 -7
- package/dist/src/index.js +18 -6
- package/dist/src/orchestrator/execute-generation-orchestrator.d.ts +6 -0
- package/dist/src/orchestrator/execute-generation-orchestrator.js +12 -0
- package/dist/src/services/agent/helpers/build-agent-args.d.ts +6 -0
- package/dist/src/services/agent/helpers/build-agent-args.js +58 -0
- package/dist/src/services/agent/helpers/check-agent-auth.d.ts +3 -0
- package/dist/src/services/agent/helpers/check-agent-auth.js +19 -0
- package/dist/src/services/agent/helpers/check-claude-auth.d.ts +2 -0
- package/dist/src/services/agent/helpers/check-claude-auth.js +37 -0
- package/dist/src/services/agent/helpers/describe-agent-failure.d.ts +2 -0
- package/dist/src/services/agent/helpers/describe-agent-failure.js +5 -0
- package/dist/src/services/agent/helpers/extract-sentinel-output.d.ts +1 -0
- package/dist/src/services/agent/helpers/extract-sentinel-output.js +16 -0
- package/dist/src/services/agent/helpers/resolve-agent-binary.d.ts +2 -0
- package/dist/src/services/agent/helpers/resolve-agent-binary.js +8 -0
- package/dist/src/services/agent/local-cli-agent-invoker.d.ts +3 -0
- package/dist/src/{agent-invoker.js → services/agent/local-cli-agent-invoker.js} +2 -2
- package/dist/src/services/agent/run-agent.d.ts +15 -0
- package/dist/src/services/agent/run-agent.js +70 -0
- package/dist/src/services/parsing/helpers/read-tool-call-objects.d.ts +4 -0
- package/dist/src/services/parsing/helpers/read-tool-call-objects.js +54 -0
- package/dist/src/services/parsing/parse-map-token-tool-calls.d.ts +2 -0
- package/dist/src/services/parsing/parse-map-token-tool-calls.js +39 -0
- package/dist/src/services/parsing/parse-prop-tool-calls.d.ts +2 -0
- package/dist/src/services/parsing/parse-prop-tool-calls.js +91 -0
- package/dist/src/services/parsing/parse-select-tool-calls.d.ts +2 -0
- package/dist/src/services/parsing/parse-select-tool-calls.js +27 -0
- package/dist/src/services/parsing/parse-token-tool-calls.d.ts +2 -0
- package/dist/src/services/parsing/parse-token-tool-calls.js +39 -0
- package/dist/src/services/prompts/build-prompt.d.ts +4 -0
- package/dist/src/services/prompts/build-prompt.js +8 -0
- package/dist/src/services/prompts/helpers/build-preamble.d.ts +2 -0
- package/dist/src/services/prompts/helpers/build-preamble.js +48 -0
- package/dist/src/services/prompts/helpers/build-skill-preambles.d.ts +4 -0
- package/dist/src/services/prompts/helpers/build-skill-preambles.js +126 -0
- package/dist/src/services/prompts/helpers/format-source-refs.d.ts +2 -0
- package/dist/src/services/prompts/helpers/format-source-refs.js +31 -0
- package/dist/src/services/prompts/helpers/format-token-candidates.d.ts +8 -0
- package/dist/src/services/prompts/helpers/format-token-candidates.js +80 -0
- package/dist/src/services/prompts/helpers/resolve-skill-file.d.ts +4 -0
- package/dist/src/services/prompts/helpers/resolve-skill-file.js +49 -0
- package/dist/src/types/agent.d.ts +10 -0
- package/dist/src/types/agent.js +1 -0
- package/dist/src/types/contract.d.ts +24 -0
- package/dist/src/types/contract.js +1 -0
- package/dist/src/types/invoker.d.ts +17 -0
- package/dist/src/types/invoker.js +1 -0
- package/dist/src/types/prompt.d.ts +53 -0
- package/dist/src/types/prompt.js +1 -0
- package/dist/src/types/tool-calls.d.ts +91 -0
- package/dist/src/types/tool-calls.js +1 -0
- package/package.json +2 -2
- package/dist/src/agent-invoker.d.ts +0 -25
- package/dist/src/agent-runner.d.ts +0 -144
- package/dist/src/agent-runner.js +0 -564
- package/dist/src/prompt-builder.d.ts +0 -81
- package/dist/src/prompt-builder.js +0 -344
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readToolCallObjects } from './helpers/read-tool-call-objects.js';
|
|
2
|
+
const VALID_TOOL_NAMES = new Set(['classify_prop', 'exclude_prop', 'classify_component', 'classify_slot']);
|
|
3
|
+
const VALID_CDF_TYPES = new Set(['string', 'richtext', 'media', 'enum', 'token', 'boolean']);
|
|
4
|
+
const VALID_CATEGORIES = new Set(['content', 'design', 'state']);
|
|
5
|
+
export function parsePropToolCallLines(stdout) {
|
|
6
|
+
const calls = [];
|
|
7
|
+
const { objects, warnings } = readToolCallObjects(stdout);
|
|
8
|
+
for (const rec of objects) {
|
|
9
|
+
if (!VALID_TOOL_NAMES.has(rec.tool)) {
|
|
10
|
+
warnings.push(`unknown tool: ${String(rec.tool)}`);
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const tool = rec.tool;
|
|
14
|
+
if (tool === 'classify_prop') {
|
|
15
|
+
if (typeof rec.prop !== 'string' || !rec.prop) {
|
|
16
|
+
warnings.push('classify_prop missing prop name — skipped');
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (typeof rec.cdf_type !== 'string' || !VALID_CDF_TYPES.has(rec.cdf_type)) {
|
|
20
|
+
warnings.push(`classify_prop '${rec.prop}': invalid cdf_type '${String(rec.cdf_type)}' — skipped`);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (typeof rec.cdf_category !== 'string' || !VALID_CATEGORIES.has(rec.cdf_category)) {
|
|
24
|
+
warnings.push(`classify_prop '${rec.prop}': invalid cdf_category '${String(rec.cdf_category)}' — skipped`);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const call = {
|
|
28
|
+
tool: 'classify_prop',
|
|
29
|
+
prop: rec.prop,
|
|
30
|
+
cdf_type: rec.cdf_type,
|
|
31
|
+
cdf_category: rec.cdf_category,
|
|
32
|
+
};
|
|
33
|
+
if (Array.isArray(rec.values) && rec.values.every((v) => typeof v === 'string'))
|
|
34
|
+
call.values = rec.values;
|
|
35
|
+
if (typeof rec.token_kind === 'string')
|
|
36
|
+
call.token_kind = rec.token_kind;
|
|
37
|
+
if (typeof rec.required === 'boolean')
|
|
38
|
+
call.required = rec.required;
|
|
39
|
+
if (typeof rec.description === 'string')
|
|
40
|
+
call.description = rec.description;
|
|
41
|
+
if (typeof rec.default === 'string' || typeof rec.default === 'boolean')
|
|
42
|
+
call.default = rec.default;
|
|
43
|
+
if (typeof rec.reason === 'string')
|
|
44
|
+
call.reason = rec.reason;
|
|
45
|
+
calls.push(call);
|
|
46
|
+
}
|
|
47
|
+
else if (tool === 'exclude_prop') {
|
|
48
|
+
if (typeof rec.prop !== 'string' || !rec.prop) {
|
|
49
|
+
warnings.push('exclude_prop missing prop name — skipped');
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
calls.push({ tool: 'exclude_prop', prop: rec.prop, reason: typeof rec.reason === 'string' ? rec.reason : '' });
|
|
53
|
+
}
|
|
54
|
+
else if (tool === 'classify_component') {
|
|
55
|
+
const call = { tool: 'classify_component' };
|
|
56
|
+
if (typeof rec.description === 'string')
|
|
57
|
+
call.description = rec.description;
|
|
58
|
+
if (typeof rec.rationale === 'object' && rec.rationale !== null) {
|
|
59
|
+
const r = rec.rationale;
|
|
60
|
+
const rationale = {};
|
|
61
|
+
if (typeof r.description === 'string')
|
|
62
|
+
rationale.description = r.description;
|
|
63
|
+
if (typeof r.props === 'string')
|
|
64
|
+
rationale.props = r.props;
|
|
65
|
+
if (typeof r.slots === 'string')
|
|
66
|
+
rationale.slots = r.slots;
|
|
67
|
+
if (Object.keys(rationale).length > 0)
|
|
68
|
+
call.rationale = rationale;
|
|
69
|
+
}
|
|
70
|
+
calls.push(call);
|
|
71
|
+
}
|
|
72
|
+
else if (tool === 'classify_slot') {
|
|
73
|
+
if (typeof rec.slot !== 'string' || !rec.slot) {
|
|
74
|
+
warnings.push('classify_slot missing slot name — skipped');
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const call = { tool: 'classify_slot', slot: rec.slot };
|
|
78
|
+
if (typeof rec.required === 'boolean')
|
|
79
|
+
call.required = rec.required;
|
|
80
|
+
if (Array.isArray(rec.allowed_components) && rec.allowed_components.every((v) => typeof v === 'string')) {
|
|
81
|
+
call.allowed_components = rec.allowed_components;
|
|
82
|
+
}
|
|
83
|
+
if (typeof rec.description === 'string')
|
|
84
|
+
call.description = rec.description;
|
|
85
|
+
if (typeof rec.rationale === 'string')
|
|
86
|
+
call.rationale = rec.rationale;
|
|
87
|
+
calls.push(call);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { calls, warnings };
|
|
91
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readToolCallObjects } from './helpers/read-tool-call-objects.js';
|
|
2
|
+
const VALID_SELECT_TOOL_NAMES = new Set(['select_component', 'reject_component']);
|
|
3
|
+
export function parseSelectToolCallLines(stdout) {
|
|
4
|
+
const calls = [];
|
|
5
|
+
const { objects, warnings } = readToolCallObjects(stdout);
|
|
6
|
+
for (const rec of objects) {
|
|
7
|
+
if (!VALID_SELECT_TOOL_NAMES.has(rec.tool))
|
|
8
|
+
continue;
|
|
9
|
+
if (typeof rec.name !== 'string' || !rec.name) {
|
|
10
|
+
warnings.push(`${String(rec.tool)} missing name — skipped`);
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const call = { tool: rec.tool, name: rec.name };
|
|
14
|
+
if (typeof rec.reason === 'string')
|
|
15
|
+
call.reason = rec.reason;
|
|
16
|
+
if (typeof rec.confidence === 'number' && rec.confidence >= 1 && rec.confidence <= 5) {
|
|
17
|
+
if (call.tool === 'select_component') {
|
|
18
|
+
call.confidence = rec.confidence;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
call.confidence = rec.confidence;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
calls.push(call);
|
|
25
|
+
}
|
|
26
|
+
return { calls, warnings };
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readToolCallObjects } from './helpers/read-tool-call-objects.js';
|
|
2
|
+
const VALID_TOKEN_TOOL_NAMES = new Set(['set_token', 'set_group']);
|
|
3
|
+
export function parseTokenToolCallLines(stdout) {
|
|
4
|
+
const calls = [];
|
|
5
|
+
const { objects, warnings } = readToolCallObjects(stdout);
|
|
6
|
+
for (const rec of objects) {
|
|
7
|
+
if (!VALID_TOKEN_TOOL_NAMES.has(rec.tool))
|
|
8
|
+
continue;
|
|
9
|
+
if (rec.tool === 'set_token') {
|
|
10
|
+
if (typeof rec.path !== 'string' || !rec.path) {
|
|
11
|
+
warnings.push('set_token missing path — skipped');
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (typeof rec.type !== 'string' || !rec.type) {
|
|
15
|
+
warnings.push(`set_token '${rec.path}': missing type — skipped`);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (!('value' in rec)) {
|
|
19
|
+
warnings.push(`set_token '${rec.path}': missing value — skipped`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const call = { tool: 'set_token', path: rec.path, type: rec.type, value: rec.value };
|
|
23
|
+
if (typeof rec.description === 'string')
|
|
24
|
+
call.description = rec.description;
|
|
25
|
+
calls.push(call);
|
|
26
|
+
}
|
|
27
|
+
else if (rec.tool === 'set_group') {
|
|
28
|
+
if (typeof rec.path !== 'string' || !rec.path) {
|
|
29
|
+
warnings.push('set_group missing path — skipped');
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const call = { tool: 'set_group', path: rec.path };
|
|
33
|
+
if (typeof rec.description === 'string')
|
|
34
|
+
call.description = rec.description;
|
|
35
|
+
calls.push(call);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { calls, warnings };
|
|
39
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { readSkillFile } from './helpers/resolve-skill-file.js';
|
|
2
|
+
import { buildPreamble } from './helpers/build-preamble.js';
|
|
3
|
+
export { resolveSkillPath, formatCustomPromptBanner } from './helpers/resolve-skill-file.js';
|
|
4
|
+
export async function buildPrompt(options) {
|
|
5
|
+
const skillContent = options.skillContentOverride ?? (await readSkillFile(options.skill, options.skillPathOverride));
|
|
6
|
+
const preamble = buildPreamble(options);
|
|
7
|
+
return `${preamble}\n\nSkill instructions follow:\n---\n${skillContent}`;
|
|
8
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { inferFenceLang, filterDesignTokenProps, buildTokenCandidateSections } from './format-token-candidates.js';
|
|
2
|
+
import { formatSourceRefsSection } from './format-source-refs.js';
|
|
3
|
+
import { buildComponentsAutonomousPreamble, buildSelectAutonomousPreamble, buildMapTokensAutonomousPreamble, buildTokensAutonomousPreamble, } from './build-skill-preambles.js';
|
|
4
|
+
export function buildPreamble(options) {
|
|
5
|
+
const { skill, rawComponentsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline, componentAllowlistInline, } = options;
|
|
6
|
+
const sections = [];
|
|
7
|
+
if (existingComponentsInline) {
|
|
8
|
+
sections.push(`Existing components in the target Contentful space (JSON) — use for alignment, not as a hard filter:\n\`\`\`json\n${existingComponentsInline}\n\`\`\``);
|
|
9
|
+
}
|
|
10
|
+
if (existingTokensInline) {
|
|
11
|
+
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\`\`\``);
|
|
12
|
+
}
|
|
13
|
+
if (componentAllowlistInline) {
|
|
14
|
+
sections.push(`Known component names for slot allowed_components (hard allowlist; never use any other name):\n\`\`\`json\n${componentAllowlistInline}\n\`\`\``);
|
|
15
|
+
}
|
|
16
|
+
if (rawComponentsInline) {
|
|
17
|
+
sections.push(`Raw component data (JSON):\n\`\`\`json\n${rawComponentsInline}\n\`\`\``);
|
|
18
|
+
}
|
|
19
|
+
if (rawTokensInline) {
|
|
20
|
+
const lang = inferFenceLang(rawTokensFilename);
|
|
21
|
+
const label = rawTokensFilename ? `Raw token source (${rawTokensFilename})` : 'Raw token source';
|
|
22
|
+
sections.push(`${label}:\n\`\`\`${lang}\n${rawTokensInline}\n\`\`\``);
|
|
23
|
+
}
|
|
24
|
+
if (tokensInline) {
|
|
25
|
+
sections.push(`DTCG token data (for token kind lookups):\n\`\`\`json\n${tokensInline}\n\`\`\``);
|
|
26
|
+
}
|
|
27
|
+
if (tokenMapInline) {
|
|
28
|
+
sections.push(`Token-name sidecar (raw name → DTCG path):\n\`\`\`json\n${tokenMapInline}\n\`\`\``);
|
|
29
|
+
}
|
|
30
|
+
const filteredTokenProps = generatedCdf ? filterDesignTokenProps(generatedCdf) : undefined;
|
|
31
|
+
if (filteredTokenProps && Object.keys(filteredTokenProps.filtered).length > 0) {
|
|
32
|
+
sections.push(`Generated CDF so far — design-category token props only (JSON):\n\`\`\`json\n${JSON.stringify(filteredTokenProps.filtered)}\n\`\`\``);
|
|
33
|
+
}
|
|
34
|
+
if (tokenTree) {
|
|
35
|
+
sections.push(...buildTokenCandidateSections(tokenTree, filteredTokenProps));
|
|
36
|
+
}
|
|
37
|
+
if (componentSourceRefs && componentSourceRefs.length > 0) {
|
|
38
|
+
sections.push(...formatSourceRefsSection(componentSourceRefs, skill));
|
|
39
|
+
}
|
|
40
|
+
const inputBlock = sections.length > 0 ? `\n\n${sections.join('\n\n')}` : '';
|
|
41
|
+
if (skill === 'components')
|
|
42
|
+
return buildComponentsAutonomousPreamble(inputBlock);
|
|
43
|
+
if (skill === 'select')
|
|
44
|
+
return buildSelectAutonomousPreamble(inputBlock);
|
|
45
|
+
if (skill === 'map-tokens')
|
|
46
|
+
return buildMapTokensAutonomousPreamble(inputBlock);
|
|
47
|
+
return buildTokensAutonomousPreamble(inputBlock);
|
|
48
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function buildComponentsAutonomousPreamble(inputBlock: string): string;
|
|
2
|
+
export declare function buildSelectAutonomousPreamble(inputBlock: string): string;
|
|
3
|
+
export declare function buildMapTokensAutonomousPreamble(inputBlock: string): string;
|
|
4
|
+
export declare function buildTokensAutonomousPreamble(inputBlock: string): string;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export function buildComponentsAutonomousPreamble(inputBlock) {
|
|
2
|
+
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.
|
|
3
|
+
|
|
4
|
+
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:
|
|
5
|
+
- **design**: controls how the component looks (variant, size, color, layout toggles)
|
|
6
|
+
- **content**: the data a content editor fills in (text, images, URLs, rich text)
|
|
7
|
+
- **state**: runtime behavioral flags (disabled, loading, expanded, identifiers)
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
All input data is provided inline below — do not read any additional files.${inputBlock}
|
|
12
|
+
|
|
13
|
+
## Output protocol
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
The four tool calls you may emit are:
|
|
18
|
+
|
|
19
|
+
\`\`\`
|
|
20
|
+
{"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>"}}
|
|
21
|
+
|
|
22
|
+
{"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>"}
|
|
23
|
+
|
|
24
|
+
{"tool":"exclude_prop","prop":"<propName>","reason":"<why excluded>"}
|
|
25
|
+
|
|
26
|
+
{"tool":"classify_slot","slot":"<slotName>","required":<bool>,"allowed_components":["ComponentName"],"description":"<reason>","rationale":"<why this slot was kept in the catalog>"}
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
Rules:
|
|
30
|
+
- Emit exactly one JSON object per line. No multi-line JSON. No markdown fences around the lines.
|
|
31
|
+
- Every prop in the input must have exactly one call: either classify_prop or exclude_prop.
|
|
32
|
+
- Every slot in the input must have exactly one classify_slot call.
|
|
33
|
+
- Valid cdf_type values: string, richtext, media, enum, token, boolean
|
|
34
|
+
- Valid cdf_category values: content, design, state
|
|
35
|
+
- For enum type, always include \`values\` (non-empty string array of the variant names the prop accepts).
|
|
36
|
+
- 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.
|
|
37
|
+
- Never emit both "values" and "token_kind" on one prop.
|
|
38
|
+
- \`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.
|
|
39
|
+
- Q2: is the value written straight into a style or attribute (\`rx={radius}\`, \`padding: \${padding}\`)? no → not \`token\`, apply the type rules.
|
|
40
|
+
- 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.
|
|
41
|
+
- CSS design props (className, style, styles, positional/geometric props: top, bottom, left, right, rotation, offset, etc.) → classify_prop, cdf_type: "string", cdf_category: "design".
|
|
42
|
+
- 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.
|
|
43
|
+
- 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.
|
|
44
|
+
- 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.
|
|
45
|
+
- You may emit prose lines (not starting with {) anywhere — they are ignored by the parser and serve as your reasoning log.`;
|
|
46
|
+
}
|
|
47
|
+
export function buildSelectAutonomousPreamble(inputBlock) {
|
|
48
|
+
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.
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
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.
|
|
53
|
+
|
|
54
|
+
All input data is provided inline below — do not read any additional files.${inputBlock}
|
|
55
|
+
|
|
56
|
+
## Output protocol
|
|
57
|
+
|
|
58
|
+
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.
|
|
59
|
+
|
|
60
|
+
The two tool calls — emit exactly one per input component:
|
|
61
|
+
|
|
62
|
+
\`\`\`
|
|
63
|
+
{"tool":"select_component","name":"<ComponentName>","reason":"<brief reason>"}
|
|
64
|
+
|
|
65
|
+
{"tool":"reject_component","name":"<ComponentName>","reason":"<brief reason>"}
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
Rules:
|
|
69
|
+
- Emit exactly one JSON object per line. No multi-line JSON. No markdown fences.
|
|
70
|
+
- 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.
|
|
71
|
+
- You may emit prose lines (not starting with {) to reason before each tool call — they are ignored by the parser.`;
|
|
72
|
+
}
|
|
73
|
+
export function buildMapTokensAutonomousPreamble(inputBlock) {
|
|
74
|
+
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.
|
|
75
|
+
|
|
76
|
+
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.
|
|
77
|
+
|
|
78
|
+
All input data is provided inline below — do not read any additional files.${inputBlock}
|
|
79
|
+
|
|
80
|
+
## Output protocol
|
|
81
|
+
|
|
82
|
+
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.
|
|
83
|
+
|
|
84
|
+
The one tool call you may emit:
|
|
85
|
+
|
|
86
|
+
\`\`\`
|
|
87
|
+
{"tool":"map_token_prop","component":"<ComponentName>","prop":"<propName>","token_allowed":["colors.brand.primary","colors.brand.secondary"]}
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
Rules:
|
|
91
|
+
- Emit exactly one JSON object per line. No multi-line JSON. No markdown fences around the lines.
|
|
92
|
+
- Only emit a call for a prop that appears in the "Generated CDF so far" section.
|
|
93
|
+
- 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.
|
|
94
|
+
- \`token_allowed\` is required and must be non-empty when the call is emitted.
|
|
95
|
+
- 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.
|
|
96
|
+
- You may emit prose lines (not starting with \`{\`) anywhere — they are ignored by the parser and serve as your reasoning log.`;
|
|
97
|
+
}
|
|
98
|
+
export function buildTokensAutonomousPreamble(inputBlock) {
|
|
99
|
+
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.
|
|
100
|
+
|
|
101
|
+
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.
|
|
102
|
+
|
|
103
|
+
All input data is provided inline below — do not read any additional files.${inputBlock}
|
|
104
|
+
|
|
105
|
+
## Output protocol
|
|
106
|
+
|
|
107
|
+
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.
|
|
108
|
+
|
|
109
|
+
The two tool calls you may emit are:
|
|
110
|
+
|
|
111
|
+
\`\`\`
|
|
112
|
+
{"tool":"set_group","path":"<dot.notation.path>","description":"<optional group description>"}
|
|
113
|
+
|
|
114
|
+
{"tool":"set_token","path":"<dot.notation.path>","type":"<DTCG type>","value":<value>,"description":"<reason>"}
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
Rules:
|
|
118
|
+
- Emit exactly one JSON object per line. No multi-line JSON. No markdown fences.
|
|
119
|
+
- Emit a set_group call for every intermediate group node in the tree.
|
|
120
|
+
- Emit a set_token call for every leaf token.
|
|
121
|
+
- "path" is dot-notation, e.g. "colors.brand.primary" — no leading dots or slashes.
|
|
122
|
+
- "type" must be one of the 13 valid DTCG types: color, dimension, fontFamily, fontWeight, duration, cubicBezier, number, strokeStyle, border, transition, shadow, gradient, typography.
|
|
123
|
+
- "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.
|
|
124
|
+
- Emit set_group calls before the set_token calls that fall under them.
|
|
125
|
+
- You may emit prose lines (not starting with {) anywhere — they are ignored by the parser and serve as your reasoning log.`;
|
|
126
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { inferFenceLang } from './format-token-candidates.js';
|
|
2
|
+
function formatSourceRefBlock(ref) {
|
|
3
|
+
const mainBlock = `#### ${ref.component} (\`${ref.sourcePath}\`)\n\`\`\`${inferFenceLang(ref.sourcePath)}\n${ref.content}\n\`\`\``;
|
|
4
|
+
const siblingBlocks = (ref.siblingFiles ?? []).map((sibling) => `##### ${ref.component} — imported file \`${sibling.path}\`\n\`\`\`${inferFenceLang(sibling.path)}\n${sibling.content}\n\`\`\``);
|
|
5
|
+
const truncationNote = ref.truncatedSiblingCount && ref.truncatedSiblingCount > 0
|
|
6
|
+
? [
|
|
7
|
+
`##### ${ref.component} — +${ref.truncatedSiblingCount} more imported file${ref.truncatedSiblingCount === 1 ? '' : 's'} not shown (evidence may be incomplete)`,
|
|
8
|
+
]
|
|
9
|
+
: [];
|
|
10
|
+
const notShownNote = ref.usesNotShown && ref.usesNotShown.length > 0
|
|
11
|
+
? [
|
|
12
|
+
`##### ${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.`,
|
|
13
|
+
]
|
|
14
|
+
: [];
|
|
15
|
+
return [mainBlock, ...siblingBlocks, ...truncationNote, ...notShownNote].join('\n\n');
|
|
16
|
+
}
|
|
17
|
+
export function formatSourceRefsSection(refs, skill) {
|
|
18
|
+
const sections = [];
|
|
19
|
+
const withContent = refs.filter((ref) => ref.content != null);
|
|
20
|
+
const withoutContent = refs.filter((ref) => ref.content == null);
|
|
21
|
+
if (withContent.length > 0) {
|
|
22
|
+
sections.push(`### Component source references\n\n${withContent.map(formatSourceRefBlock).join('\n\n')}`);
|
|
23
|
+
}
|
|
24
|
+
if (withoutContent.length > 0) {
|
|
25
|
+
const guidance = skill === 'components'
|
|
26
|
+
? '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`)'
|
|
27
|
+
: 'there is no source evidence to narrow from — emit nothing for their props';
|
|
28
|
+
sections.push(`Component source unavailable for (JSON) — ${guidance}:\n\`\`\`json\n${JSON.stringify(withoutContent.map((ref) => ({ component: ref.component, sourcePath: ref.sourcePath })))}\n\`\`\``);
|
|
29
|
+
}
|
|
30
|
+
return sections;
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { GeneratedCdf } from '../../../types/prompt.js';
|
|
2
|
+
export declare function inferFenceLang(filename: string | undefined): string;
|
|
3
|
+
export declare function filterDesignTokenProps(cdf: GeneratedCdf): {
|
|
4
|
+
filtered: GeneratedCdf;
|
|
5
|
+
kinds: string[];
|
|
6
|
+
hasUnscoped: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function buildTokenCandidateSections(tokenTree: Record<string, unknown>, filteredTokenProps?: ReturnType<typeof filterDesignTokenProps>): string[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { flattenDTCG } from '@contentful/experience-design-system-types';
|
|
2
|
+
export function inferFenceLang(filename) {
|
|
3
|
+
if (!filename)
|
|
4
|
+
return 'json';
|
|
5
|
+
const ext = filename.split('.').pop()?.toLowerCase() ?? '';
|
|
6
|
+
const map = {
|
|
7
|
+
js: 'js',
|
|
8
|
+
mjs: 'js',
|
|
9
|
+
cjs: 'js',
|
|
10
|
+
ts: 'ts',
|
|
11
|
+
mts: 'ts',
|
|
12
|
+
cts: 'ts',
|
|
13
|
+
tsx: 'tsx',
|
|
14
|
+
jsx: 'jsx',
|
|
15
|
+
vue: 'vue',
|
|
16
|
+
svelte: 'svelte',
|
|
17
|
+
astro: 'astro',
|
|
18
|
+
scss: 'scss',
|
|
19
|
+
sass: 'scss',
|
|
20
|
+
css: 'css',
|
|
21
|
+
json: 'json',
|
|
22
|
+
json5: 'json',
|
|
23
|
+
};
|
|
24
|
+
return map[ext] ?? 'text';
|
|
25
|
+
}
|
|
26
|
+
export function filterDesignTokenProps(cdf) {
|
|
27
|
+
const result = {};
|
|
28
|
+
const kinds = new Set();
|
|
29
|
+
let hasUnscoped = false;
|
|
30
|
+
for (const [componentName, component] of Object.entries(cdf)) {
|
|
31
|
+
const properties = component.$properties;
|
|
32
|
+
if (!properties)
|
|
33
|
+
continue;
|
|
34
|
+
const filteredProps = {};
|
|
35
|
+
for (const [propName, prop] of Object.entries(properties)) {
|
|
36
|
+
if (prop.$type === 'token' && prop.$category === 'design') {
|
|
37
|
+
const { '$token.allowed': _tokenAllowed, ...rest } = prop;
|
|
38
|
+
filteredProps[propName] = rest;
|
|
39
|
+
const kind = prop['$token.kind'];
|
|
40
|
+
if (typeof kind === 'string' && kind.length > 0) {
|
|
41
|
+
kinds.add(kind);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
hasUnscoped = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (Object.keys(filteredProps).length > 0) {
|
|
49
|
+
result[componentName] = { ...component, $properties: filteredProps };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { filtered: result, kinds: [...kinds].sort(), hasUnscoped };
|
|
53
|
+
}
|
|
54
|
+
export function buildTokenCandidateSections(tokenTree, filteredTokenProps) {
|
|
55
|
+
const index = flattenDTCG(tokenTree, '').sort((a, b) => a.path.localeCompare(b.path));
|
|
56
|
+
if (index.length === 0)
|
|
57
|
+
return [];
|
|
58
|
+
const sections = [];
|
|
59
|
+
const renderSection = (kind, entries) => {
|
|
60
|
+
const label = kind
|
|
61
|
+
? `Token path index — ${kind} candidates only`
|
|
62
|
+
: 'Token path index — full tree (no $token.kind to scope by)';
|
|
63
|
+
const lines = entries.map((e) => `${e.path} · ${e.$type}`).join('\n');
|
|
64
|
+
return `${label}, one leaf token per line as \`path · type\`, no \`$value\`:\n${lines}`;
|
|
65
|
+
};
|
|
66
|
+
if (filteredTokenProps) {
|
|
67
|
+
const { kinds, hasUnscoped } = filteredTokenProps;
|
|
68
|
+
for (const kind of kinds) {
|
|
69
|
+
const scoped = index.filter((e) => e.$type === kind);
|
|
70
|
+
if (scoped.length > 0)
|
|
71
|
+
sections.push(renderSection(kind, scoped));
|
|
72
|
+
}
|
|
73
|
+
if (hasUnscoped)
|
|
74
|
+
sections.push(renderSection(null, index));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
sections.push(renderSection(null, index));
|
|
78
|
+
}
|
|
79
|
+
return sections;
|
|
80
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Skill } from '../../../types/prompt.js';
|
|
2
|
+
export declare function resolveSkillPath(skill: Skill): string;
|
|
3
|
+
export declare function formatCustomPromptBanner(skill: 'components' | 'select', path: string): string;
|
|
4
|
+
export declare function readSkillFile(skill: Skill, override?: string): Promise<string>;
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
const SKILL_FILES = {
|
|
6
|
+
components: 'generate-components.md',
|
|
7
|
+
tokens: 'generate-tokens.md',
|
|
8
|
+
select: 'select-components.md',
|
|
9
|
+
'map-tokens': 'map-tokens.md',
|
|
10
|
+
};
|
|
11
|
+
export function resolveSkillPath(skill) {
|
|
12
|
+
if (!(skill in SKILL_FILES))
|
|
13
|
+
throw new Error(`Invalid skill: ${skill}`);
|
|
14
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
let dir = thisDir;
|
|
16
|
+
for (;;) {
|
|
17
|
+
const candidate = join(dir, 'skills');
|
|
18
|
+
if (existsSync(candidate))
|
|
19
|
+
return join(candidate, SKILL_FILES[skill]);
|
|
20
|
+
const parent = resolve(dir, '..');
|
|
21
|
+
if (parent === dir) {
|
|
22
|
+
throw new Error(`skill file missing from CLI installation (could not locate skills/ directory from: ${thisDir})`);
|
|
23
|
+
}
|
|
24
|
+
dir = parent;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function formatCustomPromptBanner(skill, path) {
|
|
28
|
+
return (`WARNING: Custom prompt active for ${skill}: ${path}\n` +
|
|
29
|
+
` Bundled invariants (utility-wrapper rejection, description content rules) do NOT apply.\n` +
|
|
30
|
+
` You are responsible for the prompt's correctness.\n`);
|
|
31
|
+
}
|
|
32
|
+
export async function readSkillFile(skill, override) {
|
|
33
|
+
if (override) {
|
|
34
|
+
const skillPath = resolve(override);
|
|
35
|
+
try {
|
|
36
|
+
return await readFile(skillPath, 'utf8');
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new Error(`custom prompt file not found (skill: ${skill}, path: ${skillPath})`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const skillPath = resolveSkillPath(skill);
|
|
43
|
+
try {
|
|
44
|
+
return await readFile(skillPath, 'utf8');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error(`skill file missing from CLI installation — try reinstalling the CLI (looked for: ${skillPath})`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AgentName } from '../agent-names.js';
|
|
2
|
+
export type { AgentName };
|
|
3
|
+
export interface AgentRunResult {
|
|
4
|
+
exitCode: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
timedOut: boolean;
|
|
8
|
+
}
|
|
9
|
+
export type AgentAuthStatus = 'ok' | 'unauthenticated' | 'not-found';
|
|
10
|
+
export type AgentDebugEvent = (name: string, payload?: Record<string, unknown>) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AgentName } from '../agent-names.js';
|
|
2
|
+
import type { AgentAuthStatus, AgentDebugEvent } from './agent.js';
|
|
3
|
+
import type { PromptOptions } from './prompt.js';
|
|
4
|
+
export interface GenerationEndpointRequest {
|
|
5
|
+
readonly agent: AgentName;
|
|
6
|
+
readonly model?: string;
|
|
7
|
+
readonly bedrock?: boolean;
|
|
8
|
+
readonly promptOptions: PromptOptions;
|
|
9
|
+
readonly timeoutMs: number;
|
|
10
|
+
readonly onOutput?: (chunk: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface GenerationEndpointResponse {
|
|
13
|
+
readonly exitCode: number;
|
|
14
|
+
readonly stdout: string;
|
|
15
|
+
readonly stderr: string;
|
|
16
|
+
readonly timedOut: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface GenerationEndpointOptions {
|
|
19
|
+
onDebugEvent?: AgentDebugEvent;
|
|
20
|
+
}
|
|
21
|
+
export interface GenerationEndpoint {
|
|
22
|
+
run(request: GenerationEndpointRequest): Promise<GenerationEndpointResponse>;
|
|
23
|
+
checkAuth(agent: AgentName): Promise<AgentAuthStatus>;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentName } from '../agent-names.js';
|
|
2
|
+
import type { AgentAuthStatus, AgentDebugEvent, AgentRunResult } from './agent.js';
|
|
3
|
+
export interface InvokeAgentOptions {
|
|
4
|
+
agent: AgentName;
|
|
5
|
+
model?: string;
|
|
6
|
+
bedrock?: boolean;
|
|
7
|
+
prompt: string;
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
onOutput?: (chunk: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface AgentInvoker {
|
|
12
|
+
invoke(options: InvokeAgentOptions): Promise<AgentRunResult>;
|
|
13
|
+
checkAuth(agent: AgentName): Promise<AgentAuthStatus>;
|
|
14
|
+
}
|
|
15
|
+
export interface LocalCliAgentInvokerOptions {
|
|
16
|
+
onDebugEvent?: AgentDebugEvent;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|