@contentful/experience-design-system-cli 2.34.5-dev-build-a95aeeba.0 → 2.34.5-dev-build-f8f0b1fa.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/index.js +237 -165
- package/dist/src/session/db.d.ts +19 -3
- package/package.json +6 -6
- package/skills/generate-components.md +99 -65
package/dist/package.json
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -781,7 +781,7 @@ function renderTokenCandidateSection(kind, entries) {
|
|
|
781
781
|
${formatTokenCandidateLines(entries)}`;
|
|
782
782
|
}
|
|
783
783
|
function buildPreamble(options) {
|
|
784
|
-
const { skill, rawComponentsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline } = options;
|
|
784
|
+
const { skill, rawComponentsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, generatedCdf, tokenTree, componentSourceRefs, existingComponentsInline, existingTokensInline, componentAllowlistInline } = options;
|
|
785
785
|
const sections = [];
|
|
786
786
|
if (existingComponentsInline) {
|
|
787
787
|
sections.push(`Existing components in the target Contentful space (JSON) \u2014 use for alignment, not as a hard filter:
|
|
@@ -793,6 +793,12 @@ ${existingComponentsInline}
|
|
|
793
793
|
sections.push(`Existing design tokens in the target Contentful space (JSON) \u2014 prefer binding to these paths over inventing new ones:
|
|
794
794
|
\`\`\`json
|
|
795
795
|
${existingTokensInline}
|
|
796
|
+
\`\`\``);
|
|
797
|
+
}
|
|
798
|
+
if (componentAllowlistInline) {
|
|
799
|
+
sections.push(`Known component names for slot allowed_components (hard allowlist; never use any other name):
|
|
800
|
+
\`\`\`json
|
|
801
|
+
${componentAllowlistInline}
|
|
796
802
|
\`\`\``);
|
|
797
803
|
}
|
|
798
804
|
if (rawComponentsInline) {
|
|
@@ -935,6 +941,7 @@ Rules:
|
|
|
935
941
|
- Q2: is the value written straight into a style or attribute (\`rx={radius}\`, \`padding: \${padding}\`)? no \u2192 not \`token\`, apply the type rules.
|
|
936
942
|
- Q3: is there a design-token reference at that use \u2014 a \`tokens.*\` parameter default, a \`tokenReference\`, an inline \`tokens.*\` / \`var(--*)\`? yes \u2192 \`token\` (cite both lines in "reason"); no \u2192 \`string\`. "Ambiguity resolves to \`enum\`" applies only when Q1\u2013Q3 cannot be answered from the source shown.
|
|
937
943
|
- CSS design props (className, style, styles, positional/geometric props: top, bottom, left, right, rotation, offset, etc.) \u2192 classify_prop, cdf_type: "string", cdf_category: "design".
|
|
944
|
+
- 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.
|
|
938
945
|
- 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.
|
|
939
946
|
- On classify_prop, "reason" is REQUIRED and is the LLM's internal rationale \u2014 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.
|
|
940
947
|
- You may emit prose lines (not starting with {) anywhere \u2014 they are ignored by the parser and serve as your reasoning log.`;
|
|
@@ -2514,7 +2521,7 @@ function loadComponentRationale(db, sessionId2, componentName) {
|
|
|
2514
2521
|
}))
|
|
2515
2522
|
};
|
|
2516
2523
|
}
|
|
2517
|
-
function applyToolCalls(db, sessionId2, componentId, componentName, calls, incomingWarnings) {
|
|
2524
|
+
function applyToolCalls(db, sessionId2, componentId, componentName, calls, incomingWarnings, options = {}) {
|
|
2518
2525
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2519
2526
|
const warnings = [...incomingWarnings];
|
|
2520
2527
|
let classified = 0;
|
|
@@ -2649,9 +2656,18 @@ function applyToolCalls(db, sessionId2, componentId, componentName, calls, incom
|
|
|
2649
2656
|
}
|
|
2650
2657
|
if (call.allowed_components !== void 0) {
|
|
2651
2658
|
deleteAllowedComponents.run(sessionId2, componentId, call.slot);
|
|
2652
|
-
call.allowed_components.
|
|
2653
|
-
|
|
2654
|
-
|
|
2659
|
+
const allowedComponents = options.allowedComponentNames ? call.allowed_components.filter((allowedComponent) => options.allowedComponentNames.has(allowedComponent)) : call.allowed_components;
|
|
2660
|
+
if (options.allowedComponentNames) {
|
|
2661
|
+
const dropped = [
|
|
2662
|
+
...new Set(call.allowed_components.filter((ac) => !options.allowedComponentNames.has(ac)))
|
|
2663
|
+
];
|
|
2664
|
+
for (const allowedComponent of dropped) {
|
|
2665
|
+
warnings.push(
|
|
2666
|
+
`${componentName}: classify_slot '${call.slot}' \u2014 dropped unknown allowed component '${allowedComponent}'`
|
|
2667
|
+
);
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
allowedComponents.forEach((ac, i) => insertAllowedComponent.run(sessionId2, componentId, call.slot, ac, i));
|
|
2655
2671
|
}
|
|
2656
2672
|
slots++;
|
|
2657
2673
|
}
|
|
@@ -3201,9 +3217,7 @@ function storeCDFComponents(db, sessionId2, components) {
|
|
|
3201
3217
|
}
|
|
3202
3218
|
}
|
|
3203
3219
|
const existingDefaults = new Map(
|
|
3204
|
-
readExistingSlotDefaults.all(sessionId2, componentId).map(
|
|
3205
|
-
(r) => [r.name, r.is_default]
|
|
3206
|
-
)
|
|
3220
|
+
readExistingSlotDefaults.all(sessionId2, componentId).map((r) => [r.name, r.is_default])
|
|
3207
3221
|
);
|
|
3208
3222
|
deleteSlotAllowedComponents.run(sessionId2, componentId);
|
|
3209
3223
|
deleteSlots.run(sessionId2, componentId);
|
|
@@ -3640,7 +3654,41 @@ function loadSlotCycles(db, sessionId2) {
|
|
|
3640
3654
|
suggestedBreak: r.suggested_break_json ? JSON.parse(r.suggested_break_json) : null
|
|
3641
3655
|
}));
|
|
3642
3656
|
}
|
|
3643
|
-
function
|
|
3657
|
+
function findUnknownSlotAllowedComponents(db, sessionId2, allowedComponentNames2, componentId) {
|
|
3658
|
+
const rows = db.prepare(
|
|
3659
|
+
`SELECT a.component_id, c.name AS component_name, a.slot_name, a.allowed_component
|
|
3660
|
+
FROM raw_slot_allowed_components a
|
|
3661
|
+
JOIN raw_components c ON c.session_id = a.session_id AND c.component_id = a.component_id
|
|
3662
|
+
WHERE a.session_id = ? AND (? IS NULL OR a.component_id = ?)
|
|
3663
|
+
ORDER BY a.component_id, a.slot_name, a.position`
|
|
3664
|
+
).all(sessionId2, componentId ?? null, componentId ?? null);
|
|
3665
|
+
return rows.filter((row) => !allowedComponentNames2.has(row.allowed_component)).map((row) => ({
|
|
3666
|
+
componentId: row.component_id,
|
|
3667
|
+
componentName: row.component_name,
|
|
3668
|
+
slotName: row.slot_name,
|
|
3669
|
+
allowedComponent: row.allowed_component
|
|
3670
|
+
}));
|
|
3671
|
+
}
|
|
3672
|
+
function filterUnknownSlotAllowedComponents(db, sessionId2, allowedComponentNames2) {
|
|
3673
|
+
const unknown = findUnknownSlotAllowedComponents(db, sessionId2, allowedComponentNames2);
|
|
3674
|
+
if (unknown.length === 0) return unknown;
|
|
3675
|
+
db.exec("BEGIN");
|
|
3676
|
+
try {
|
|
3677
|
+
const remove = db.prepare(
|
|
3678
|
+
`DELETE FROM raw_slot_allowed_components
|
|
3679
|
+
WHERE session_id = ? AND component_id = ? AND slot_name = ? AND allowed_component = ?`
|
|
3680
|
+
);
|
|
3681
|
+
for (const row of unknown) {
|
|
3682
|
+
remove.run(sessionId2, row.componentId, row.slotName, row.allowedComponent);
|
|
3683
|
+
}
|
|
3684
|
+
db.exec("COMMIT");
|
|
3685
|
+
} catch (e) {
|
|
3686
|
+
db.exec("ROLLBACK");
|
|
3687
|
+
throw e;
|
|
3688
|
+
}
|
|
3689
|
+
return unknown;
|
|
3690
|
+
}
|
|
3691
|
+
function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId, manageTransaction = true, options = {}) {
|
|
3644
3692
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3645
3693
|
if (manageTransaction) db.exec("BEGIN");
|
|
3646
3694
|
try {
|
|
@@ -3714,7 +3762,8 @@ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentI
|
|
|
3714
3762
|
const insertSAC = db.prepare(
|
|
3715
3763
|
`INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`
|
|
3716
3764
|
);
|
|
3717
|
-
|
|
3765
|
+
const allowedSAC = options.allowedComponentNames ? srcSAC.filter((sac) => options.allowedComponentNames.has(sac.allowed_component)) : srcSAC;
|
|
3766
|
+
for (const sac of allowedSAC) {
|
|
3718
3767
|
insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
|
|
3719
3768
|
}
|
|
3720
3769
|
db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
|
|
@@ -3724,12 +3773,12 @@ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentI
|
|
|
3724
3773
|
throw e;
|
|
3725
3774
|
}
|
|
3726
3775
|
}
|
|
3727
|
-
function copyComponentsFromCache(db, entries) {
|
|
3776
|
+
function copyComponentsFromCache(db, entries, options = {}) {
|
|
3728
3777
|
if (entries.length === 0) return;
|
|
3729
3778
|
db.exec("BEGIN");
|
|
3730
3779
|
try {
|
|
3731
3780
|
for (const entry of entries) {
|
|
3732
|
-
copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false);
|
|
3781
|
+
copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false, options);
|
|
3733
3782
|
}
|
|
3734
3783
|
db.exec("COMMIT");
|
|
3735
3784
|
} catch (e) {
|
|
@@ -4873,7 +4922,7 @@ function printFallbackInstructions(options) {
|
|
|
4873
4922
|
lines.push(` Re-run the generate command with the agent available, or use --dry-run to inspect the prompt.`);
|
|
4874
4923
|
process.stderr.write(lines.join("\n") + "\n");
|
|
4875
4924
|
}
|
|
4876
|
-
function
|
|
4925
|
+
function createCachedComponentResult(componentName, warnings = []) {
|
|
4877
4926
|
return {
|
|
4878
4927
|
componentName,
|
|
4879
4928
|
classified: 0,
|
|
@@ -4885,6 +4934,31 @@ function cachedComponentResult(componentName, warnings = []) {
|
|
|
4885
4934
|
renamedSlotsCount: 0
|
|
4886
4935
|
};
|
|
4887
4936
|
}
|
|
4937
|
+
async function showGenerateView(result) {
|
|
4938
|
+
if (process.stdout.isTTY) {
|
|
4939
|
+
const { waitUntilExit } = renderWithGoodbye(
|
|
4940
|
+
createElement(GenerateView, {
|
|
4941
|
+
result,
|
|
4942
|
+
onExit: () => void exitWithAnalytics(0)
|
|
4943
|
+
})
|
|
4944
|
+
);
|
|
4945
|
+
await waitUntilExit();
|
|
4946
|
+
return;
|
|
4947
|
+
}
|
|
4948
|
+
process.stdout.write(
|
|
4949
|
+
`generate complete
|
|
4950
|
+
skill: ${result.skill}
|
|
4951
|
+
agent: ${result.agent}
|
|
4952
|
+
session=${result.sessionId}
|
|
4953
|
+
`
|
|
4954
|
+
);
|
|
4955
|
+
await exitWithAnalytics(0);
|
|
4956
|
+
}
|
|
4957
|
+
function writeCachedComponentStatus(position, componentName, pinned) {
|
|
4958
|
+
const status = pinned ? c.cyan("pinned (human-edited)") : c.green("cached");
|
|
4959
|
+
process.stderr.write(` ${position} ${c.bold(componentName)} ${status}
|
|
4960
|
+
`);
|
|
4961
|
+
}
|
|
4888
4962
|
function normalizeComponentForCache(component) {
|
|
4889
4963
|
const slots = component.slots.map((slot, index, allSlots) => ({
|
|
4890
4964
|
...slot,
|
|
@@ -4900,13 +4974,32 @@ function lookupComponentCache(db, component, promptHash) {
|
|
|
4900
4974
|
const legacyInputHash = hashComponentShape(component);
|
|
4901
4975
|
return legacyInputHash === inputHash ? null : lookupCache(db, legacyInputHash, "component", component.component_id, promptHash);
|
|
4902
4976
|
}
|
|
4903
|
-
function resolveComponentCache(db, component, promptHash) {
|
|
4977
|
+
function resolveComponentCache(db, component, promptHash, allowedComponentNames2) {
|
|
4904
4978
|
const cached = lookupComponentCache(db, component, promptHash);
|
|
4905
|
-
if (cached
|
|
4979
|
+
if (cached && findUnknownSlotAllowedComponents(db, cached.sourceSessionId, allowedComponentNames2, component.component_id).length === 0) {
|
|
4980
|
+
return { entry: cached, humanEdited: cached.humanEdited };
|
|
4981
|
+
}
|
|
4906
4982
|
const pinned = lookupCacheByEntity(db, "component", component.component_id);
|
|
4907
|
-
return pinned?.humanEdited ? { entry: pinned, humanEdited: true } : null;
|
|
4983
|
+
return pinned?.humanEdited && findUnknownSlotAllowedComponents(db, pinned.sourceSessionId, allowedComponentNames2, component.component_id).length === 0 ? { entry: pinned, humanEdited: true } : null;
|
|
4908
4984
|
}
|
|
4909
|
-
async function runOneComponent(
|
|
4985
|
+
async function runOneComponent(options, component, index, total) {
|
|
4986
|
+
const {
|
|
4987
|
+
agent,
|
|
4988
|
+
model,
|
|
4989
|
+
db,
|
|
4990
|
+
sessionId: sessionId2,
|
|
4991
|
+
tokensInline,
|
|
4992
|
+
tokenMapInline,
|
|
4993
|
+
verbose,
|
|
4994
|
+
noCache,
|
|
4995
|
+
skillPathOverride,
|
|
4996
|
+
skillContentOverride,
|
|
4997
|
+
promptHash,
|
|
4998
|
+
existingContentfulEntities,
|
|
4999
|
+
existingTokensInline,
|
|
5000
|
+
precomputedCachedNames,
|
|
5001
|
+
allowedComponentNames: allowedComponentNames2
|
|
5002
|
+
} = options;
|
|
4910
5003
|
const pos = c.dim(`[${index + 1}/${total}]`);
|
|
4911
5004
|
const { renames, warnings: renameWarnings } = renameEmptySlots(
|
|
4912
5005
|
db,
|
|
@@ -4922,17 +5015,25 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
|
|
|
4922
5015
|
for (const w of renameWarnings) process.stderr.write(` ${c.yellow("\u26A0")} ${w}
|
|
4923
5016
|
`);
|
|
4924
5017
|
}
|
|
5018
|
+
effectiveSlots = effectiveSlots.map((slot) => {
|
|
5019
|
+
if (!slot.allowedComponents) return slot;
|
|
5020
|
+
return {
|
|
5021
|
+
...slot,
|
|
5022
|
+
allowedComponents: slot.allowedComponents.filter((name) => allowedComponentNames2.has(name))
|
|
5023
|
+
};
|
|
5024
|
+
});
|
|
4925
5025
|
const cacheComponent = normalizeComponentForCache(component);
|
|
4926
5026
|
if (!noCache && precomputedCachedNames.has(component.name)) {
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
return cachedComponentResult(component.name);
|
|
5027
|
+
writeCachedComponentStatus(pos, component.name, false);
|
|
5028
|
+
return createCachedComponentResult(component.name);
|
|
4930
5029
|
}
|
|
4931
5030
|
if (!noCache) {
|
|
4932
5031
|
const inputHash = hashComponentShape(cacheComponent);
|
|
4933
|
-
const resolution = resolveComponentCache(db, component, promptHash);
|
|
5032
|
+
const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
|
|
4934
5033
|
if (resolution && !resolution.humanEdited) {
|
|
4935
|
-
copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id
|
|
5034
|
+
copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
|
|
5035
|
+
allowedComponentNames: allowedComponentNames2
|
|
5036
|
+
});
|
|
4936
5037
|
storeCache(
|
|
4937
5038
|
db,
|
|
4938
5039
|
inputHash,
|
|
@@ -4942,15 +5043,17 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
|
|
|
4942
5043
|
resolution.entry.humanEdited,
|
|
4943
5044
|
promptHash
|
|
4944
5045
|
);
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
return cachedComponentResult(component.name);
|
|
5046
|
+
writeCachedComponentStatus(pos, component.name, false);
|
|
5047
|
+
return createCachedComponentResult(component.name);
|
|
4948
5048
|
}
|
|
4949
5049
|
if (resolution?.humanEdited) {
|
|
4950
|
-
copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
5050
|
+
copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id, true, {
|
|
5051
|
+
allowedComponentNames: allowedComponentNames2
|
|
5052
|
+
});
|
|
5053
|
+
writeCachedComponentStatus(pos, component.name, true);
|
|
5054
|
+
return createCachedComponentResult(component.name, [
|
|
5055
|
+
`${component.name}: source changed but human edits preserved`
|
|
5056
|
+
]);
|
|
4954
5057
|
}
|
|
4955
5058
|
}
|
|
4956
5059
|
const rawComponentsInline = JSON.stringify(
|
|
@@ -4985,7 +5088,8 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
|
|
|
4985
5088
|
skillPathOverride,
|
|
4986
5089
|
skillContentOverride,
|
|
4987
5090
|
existingComponentsInline,
|
|
4988
|
-
existingTokensInline
|
|
5091
|
+
existingTokensInline,
|
|
5092
|
+
componentAllowlistInline: JSON.stringify([...allowedComponentNames2].sort())
|
|
4989
5093
|
});
|
|
4990
5094
|
const maxAttempts = 2;
|
|
4991
5095
|
let lastError = "";
|
|
@@ -5020,7 +5124,9 @@ ${outputBuf}`);
|
|
|
5020
5124
|
lastError = describeAgentFailure(result);
|
|
5021
5125
|
continue;
|
|
5022
5126
|
}
|
|
5023
|
-
const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings
|
|
5127
|
+
const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings, {
|
|
5128
|
+
allowedComponentNames: allowedComponentNames2
|
|
5129
|
+
});
|
|
5024
5130
|
if (!noCache) {
|
|
5025
5131
|
const inputHash = hashComponentShape(cacheComponent);
|
|
5026
5132
|
storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
|
|
@@ -5046,7 +5152,7 @@ ${outputBuf}`);
|
|
|
5046
5152
|
renamedSlotsCount: renames.length
|
|
5047
5153
|
};
|
|
5048
5154
|
}
|
|
5049
|
-
async function runAllComponents(
|
|
5155
|
+
async function runAllComponents(options, components) {
|
|
5050
5156
|
const concurrency = Number(process.env.EDS_GENERATE_CONCURRENCY ?? DEFAULT_COMPONENT_CONCURRENCY);
|
|
5051
5157
|
process.stderr.write(
|
|
5052
5158
|
`Categorizing ${c.bold(String(components.length))} component${components.length === 1 ? "" : "s"}` + c.dim(` (concurrency: ${concurrency})`) + "\n"
|
|
@@ -5057,25 +5163,7 @@ async function runAllComponents(agent, model, db, sessionId2, components, tokens
|
|
|
5057
5163
|
async function worker() {
|
|
5058
5164
|
while (next < components.length) {
|
|
5059
5165
|
const i = next++;
|
|
5060
|
-
results[i] = await runOneComponent(
|
|
5061
|
-
agent,
|
|
5062
|
-
model,
|
|
5063
|
-
db,
|
|
5064
|
-
sessionId2,
|
|
5065
|
-
components[i],
|
|
5066
|
-
tokensInline,
|
|
5067
|
-
tokenMapInline,
|
|
5068
|
-
i,
|
|
5069
|
-
components.length,
|
|
5070
|
-
verbose,
|
|
5071
|
-
noCache,
|
|
5072
|
-
skillPathOverride,
|
|
5073
|
-
skillContentOverride,
|
|
5074
|
-
promptHash,
|
|
5075
|
-
existingContentfulEntities,
|
|
5076
|
-
existingTokensInline,
|
|
5077
|
-
precomputedCachedNames
|
|
5078
|
-
);
|
|
5166
|
+
results[i] = await runOneComponent(options, components[i], i, components.length);
|
|
5079
5167
|
completed += 1;
|
|
5080
5168
|
process.stderr.write(`${formatGenerateProgressLine(completed, components.length, results[i].componentName)}
|
|
5081
5169
|
`);
|
|
@@ -5177,6 +5265,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
|
|
|
5177
5265
|
}
|
|
5178
5266
|
let sessionId2;
|
|
5179
5267
|
let allComponents;
|
|
5268
|
+
let allowedComponentNames2;
|
|
5180
5269
|
if (skill === "components") {
|
|
5181
5270
|
sessionId2 = await resolveSessionId(opts.session);
|
|
5182
5271
|
await bindAnalyticsSessionId(sessionId2);
|
|
@@ -5190,6 +5279,27 @@ async function runGenerateSkill(skill, opts, verbose = false) {
|
|
|
5190
5279
|
if (allComponents.length === 0) {
|
|
5191
5280
|
die(`Error: session '${sessionId2}' has no raw components. Run analyze extract first.`);
|
|
5192
5281
|
}
|
|
5282
|
+
allowedComponentNames2 = /* @__PURE__ */ new Set([
|
|
5283
|
+
...allComponents.map((component) => component.name),
|
|
5284
|
+
...(existingContentfulEntities?.components ?? []).map((component) => component.name)
|
|
5285
|
+
]);
|
|
5286
|
+
if (!opts.dryRun) {
|
|
5287
|
+
const dbForReferences = openPipelineDb();
|
|
5288
|
+
try {
|
|
5289
|
+
const dropped = filterUnknownSlotAllowedComponents(dbForReferences, sessionId2, allowedComponentNames2);
|
|
5290
|
+
for (const reference of dropped) {
|
|
5291
|
+
process.stderr.write(
|
|
5292
|
+
`Warning: ${reference.componentName}: slot '${reference.slotName}' \u2014 dropped unknown allowed component '${reference.allowedComponent}'
|
|
5293
|
+
`
|
|
5294
|
+
);
|
|
5295
|
+
}
|
|
5296
|
+
if (dropped.length > 0) {
|
|
5297
|
+
allComponents = loadRawComponents(dbForReferences, sessionId2, acceptedNames ?? void 0);
|
|
5298
|
+
}
|
|
5299
|
+
} finally {
|
|
5300
|
+
dbForReferences.close();
|
|
5301
|
+
}
|
|
5302
|
+
}
|
|
5193
5303
|
if (acceptedNames) {
|
|
5194
5304
|
process.stderr.write(`Scope: ${allComponents.length} accepted component(s) from analyze select
|
|
5195
5305
|
`);
|
|
@@ -5249,7 +5359,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
|
|
|
5249
5359
|
skillPathOverride: generatePromptPath,
|
|
5250
5360
|
skillContentOverride: generatePrompt,
|
|
5251
5361
|
existingComponentsInline: dryRunExistingComponentsInline,
|
|
5252
|
-
existingTokensInline: skill === "components" ? existingTokensInline : void 0
|
|
5362
|
+
existingTokensInline: skill === "components" ? existingTokensInline : void 0,
|
|
5363
|
+
componentAllowlistInline: skill === "components" && allowedComponentNames2 ? JSON.stringify([...allowedComponentNames2].sort()) : void 0
|
|
5253
5364
|
});
|
|
5254
5365
|
process.stdout.write(prompt + "\n");
|
|
5255
5366
|
await exitWithAnalytics(0);
|
|
@@ -5281,7 +5392,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
|
|
|
5281
5392
|
if (opts.cacheStatus) {
|
|
5282
5393
|
const cacheEnabled = opts.cache !== false && process.env.EDS_NO_CACHE !== "1";
|
|
5283
5394
|
const cachedComponents = cacheEnabled ? allComponents.flatMap((component) => {
|
|
5284
|
-
const resolution = resolveComponentCache(db, component, promptHash);
|
|
5395
|
+
const resolution = resolveComponentCache(db, component, promptHash, allowedComponentNames2);
|
|
5285
5396
|
return resolution ? [{ component, resolution }] : [];
|
|
5286
5397
|
}) : [];
|
|
5287
5398
|
if (cacheEnabled && opts.restoreCache) {
|
|
@@ -5301,7 +5412,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
|
|
|
5301
5412
|
});
|
|
5302
5413
|
rekeyed.push({ component, cached: resolution.entry });
|
|
5303
5414
|
}
|
|
5304
|
-
copyComponentsFromCache(db, restorations);
|
|
5415
|
+
copyComponentsFromCache(db, restorations, { allowedComponentNames: allowedComponentNames2 });
|
|
5305
5416
|
storeCaches(
|
|
5306
5417
|
db,
|
|
5307
5418
|
rekeyed.map(({ component, cached }) => ({
|
|
@@ -5325,21 +5436,24 @@ async function runGenerateSkill(skill, opts, verbose = false) {
|
|
|
5325
5436
|
return;
|
|
5326
5437
|
}
|
|
5327
5438
|
componentResults = await runAllComponents(
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5439
|
+
{
|
|
5440
|
+
agent,
|
|
5441
|
+
model,
|
|
5442
|
+
db,
|
|
5443
|
+
sessionId: sessionId2,
|
|
5444
|
+
tokensInline,
|
|
5445
|
+
tokenMapInline,
|
|
5446
|
+
verbose,
|
|
5447
|
+
noCache: opts.cache === false || process.env.EDS_NO_CACHE === "1",
|
|
5448
|
+
skillPathOverride: generatePromptPath,
|
|
5449
|
+
skillContentOverride: generatePrompt,
|
|
5450
|
+
promptHash,
|
|
5451
|
+
existingContentfulEntities,
|
|
5452
|
+
existingTokensInline,
|
|
5453
|
+
precomputedCachedNames,
|
|
5454
|
+
allowedComponentNames: allowedComponentNames2
|
|
5455
|
+
},
|
|
5456
|
+
allComponents
|
|
5343
5457
|
);
|
|
5344
5458
|
} finally {
|
|
5345
5459
|
db.close();
|
|
@@ -5414,20 +5528,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
|
|
|
5414
5528
|
`
|
|
5415
5529
|
);
|
|
5416
5530
|
db.close();
|
|
5417
|
-
|
|
5418
|
-
if (process.stdout.isTTY) {
|
|
5419
|
-
const { waitUntilExit } = renderWithGoodbye(
|
|
5420
|
-
createElement(GenerateView, { result: viewResult2, onExit: () => void exitWithAnalytics(0) })
|
|
5421
|
-
);
|
|
5422
|
-
await waitUntilExit();
|
|
5423
|
-
} else {
|
|
5424
|
-
process.stdout.write(`generate complete
|
|
5425
|
-
skill: ${skill}
|
|
5426
|
-
agent: ${agent}
|
|
5427
|
-
session=${sessionId2}
|
|
5428
|
-
`);
|
|
5429
|
-
await exitWithAnalytics(0);
|
|
5430
|
-
}
|
|
5531
|
+
await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
|
|
5431
5532
|
return;
|
|
5432
5533
|
}
|
|
5433
5534
|
}
|
|
@@ -5483,27 +5584,7 @@ ${tokenWarnings.map((w) => ` ${w}`).join("\n")}
|
|
|
5483
5584
|
db.close();
|
|
5484
5585
|
}
|
|
5485
5586
|
}
|
|
5486
|
-
|
|
5487
|
-
skill,
|
|
5488
|
-
agent,
|
|
5489
|
-
sessionId: sessionId2 ?? ""
|
|
5490
|
-
};
|
|
5491
|
-
if (process.stdout.isTTY) {
|
|
5492
|
-
const { waitUntilExit } = renderWithGoodbye(
|
|
5493
|
-
createElement(GenerateView, {
|
|
5494
|
-
result: viewResult,
|
|
5495
|
-
onExit: () => void exitWithAnalytics(0)
|
|
5496
|
-
})
|
|
5497
|
-
);
|
|
5498
|
-
await waitUntilExit();
|
|
5499
|
-
} else {
|
|
5500
|
-
process.stdout.write(`generate complete
|
|
5501
|
-
skill: ${skill}
|
|
5502
|
-
agent: ${agent}
|
|
5503
|
-
session=${sessionId2 ?? ""}
|
|
5504
|
-
`);
|
|
5505
|
-
await exitWithAnalytics(0);
|
|
5506
|
-
}
|
|
5587
|
+
await showGenerateView({ skill, agent, sessionId: sessionId2 ?? "" });
|
|
5507
5588
|
}
|
|
5508
5589
|
function addAgentFlags(cmd) {
|
|
5509
5590
|
return addAgentModelOptions(cmd).option("--verbose", "Show full agent output including reasoning text").option("--dry-run", "Print the prompt without invoking the agent").option(
|
|
@@ -11253,30 +11334,26 @@ function getTsxExtractionContext(filePaths, componentFilePattern) {
|
|
|
11253
11334
|
project: createTsxProject(getTsxProjectFiles(filePaths))
|
|
11254
11335
|
};
|
|
11255
11336
|
}
|
|
11256
|
-
function extractTsxComponents(filePaths, componentFilePattern,
|
|
11337
|
+
function extractTsxComponents(filePaths, componentFilePattern, extract) {
|
|
11257
11338
|
const extractionContext = getTsxExtractionContext(filePaths, componentFilePattern);
|
|
11258
|
-
if (!extractionContext)
|
|
11259
|
-
return
|
|
11260
|
-
|
|
11339
|
+
if (!extractionContext) {
|
|
11340
|
+
return { components: [], warnings: [], exclusions: [] };
|
|
11341
|
+
}
|
|
11342
|
+
const { componentFiles, project } = extractionContext;
|
|
11261
11343
|
const components = [];
|
|
11262
|
-
|
|
11344
|
+
const warnings = [];
|
|
11345
|
+
const exclusions = [];
|
|
11346
|
+
for (const filePath of componentFiles) {
|
|
11263
11347
|
try {
|
|
11264
|
-
const sourceFile =
|
|
11348
|
+
const sourceFile = project.getSourceFile(filePath);
|
|
11265
11349
|
if (!sourceFile)
|
|
11266
11350
|
continue;
|
|
11267
|
-
components.push(...
|
|
11351
|
+
components.push(...extract(sourceFile, exclusions));
|
|
11268
11352
|
} catch (e) {
|
|
11269
11353
|
warnings.push(`Failed to extract from ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
11270
11354
|
}
|
|
11271
11355
|
}
|
|
11272
|
-
return {
|
|
11273
|
-
}
|
|
11274
|
-
function resolveComponentExportName(exportKey, declarations, exported, allowVariableDeclaration = false) {
|
|
11275
|
-
const name = exportKey === "default" ? resolveDefaultExportName(declarations, exported, allowVariableDeclaration) : exportKey;
|
|
11276
|
-
return name && /^[A-Z]/.test(name) ? name : void 0;
|
|
11277
|
-
}
|
|
11278
|
-
function isCompositionHookName(name) {
|
|
11279
|
-
return name.startsWith("use");
|
|
11356
|
+
return { components, warnings, exclusions, project };
|
|
11280
11357
|
}
|
|
11281
11358
|
function resolveDefaultExportName(declarations, exported, allowVariableDeclaration = false) {
|
|
11282
11359
|
const declaration = declarations[0];
|
|
@@ -11285,6 +11362,27 @@ function resolveDefaultExportName(declarations, exported, allowVariableDeclarati
|
|
|
11285
11362
|
return void 0;
|
|
11286
11363
|
return name;
|
|
11287
11364
|
}
|
|
11365
|
+
function getRenderableExports(sourceFile, exclusions, options) {
|
|
11366
|
+
const renderable = [];
|
|
11367
|
+
const exported = sourceFile.getExportedDeclarations();
|
|
11368
|
+
for (const [exportKey, declarations] of exported) {
|
|
11369
|
+
const name = exportKey === "default" ? resolveDefaultExportName(declarations, exported, options.allowVariableDeclaration) : exportKey;
|
|
11370
|
+
if (!name || !/^[A-Z]/.test(name))
|
|
11371
|
+
continue;
|
|
11372
|
+
if (name.startsWith("use")) {
|
|
11373
|
+
exclusions.push({
|
|
11374
|
+
itemType: "component",
|
|
11375
|
+
name,
|
|
11376
|
+
source: sourceFile.getFilePath(),
|
|
11377
|
+
reason: options.hookReason,
|
|
11378
|
+
stage: "component-filter"
|
|
11379
|
+
});
|
|
11380
|
+
continue;
|
|
11381
|
+
}
|
|
11382
|
+
renderable.push({ name, declarations });
|
|
11383
|
+
}
|
|
11384
|
+
return renderable;
|
|
11385
|
+
}
|
|
11288
11386
|
function kebabToPascal(input) {
|
|
11289
11387
|
return input.split("-").filter(Boolean).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
11290
11388
|
}
|
|
@@ -13568,18 +13666,13 @@ function hasImplementationChildrenHint(funcNode, param) {
|
|
|
13568
13666
|
return body.getDescendantsOfKind(SyntaxKind4.JsxSpreadAttribute).some((attr) => isComponentSpreadAttribute(attr, /* @__PURE__ */ new Set([paramName])));
|
|
13569
13667
|
}
|
|
13570
13668
|
async function extractReactComponents(filePaths) {
|
|
13571
|
-
const exclusions = []
|
|
13572
|
-
const extractionContext = extractTsxComponents(filePaths, /\.[jt]sx$/, (sourceFile) => {
|
|
13669
|
+
const { components, warnings, exclusions, project } = extractTsxComponents(filePaths, /\.[jt]sx$/, (sourceFile, exclusions2) => {
|
|
13573
13670
|
if (isStencilFile2(sourceFile))
|
|
13574
13671
|
return [];
|
|
13575
13672
|
const fileExports = [...sourceFile.getExportedDeclarations().keys()];
|
|
13576
13673
|
const isNext = isNextJsComponent(sourceFile.getFilePath(), fileExports);
|
|
13577
|
-
return extractFromSourceFile2(sourceFile, isNext,
|
|
13674
|
+
return extractFromSourceFile2(sourceFile, isNext, exclusions2);
|
|
13578
13675
|
});
|
|
13579
|
-
if (!extractionContext) {
|
|
13580
|
-
return { components: [], warnings: [] };
|
|
13581
|
-
}
|
|
13582
|
-
const { project, components, warnings } = extractionContext;
|
|
13583
13676
|
const propsToComponent = /* @__PURE__ */ new Map();
|
|
13584
13677
|
const componentNames = /* @__PURE__ */ new Set();
|
|
13585
13678
|
for (const c2 of components) {
|
|
@@ -13615,7 +13708,7 @@ async function extractReactComponents(filePaths) {
|
|
|
13615
13708
|
const cached = structuralByFile.get(filePath);
|
|
13616
13709
|
if (cached)
|
|
13617
13710
|
return cached;
|
|
13618
|
-
const sourceFile = project
|
|
13711
|
+
const sourceFile = project?.getSourceFile(filePath);
|
|
13619
13712
|
if (!sourceFile)
|
|
13620
13713
|
return [];
|
|
13621
13714
|
const ctx = { propsToComponent, componentNames };
|
|
@@ -13677,22 +13770,11 @@ async function extractReactComponents(filePaths) {
|
|
|
13677
13770
|
}
|
|
13678
13771
|
function extractFromSourceFile2(sourceFile, isNext, exclusions) {
|
|
13679
13772
|
const components = [];
|
|
13680
|
-
const exported = sourceFile.getExportedDeclarations();
|
|
13681
13773
|
const usesCreateContext = sourceFileUsesCreateContext(sourceFile);
|
|
13682
|
-
for (const
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
if (isCompositionHookName(name)) {
|
|
13687
|
-
exclusions.push({
|
|
13688
|
-
itemType: "component",
|
|
13689
|
-
name,
|
|
13690
|
-
source: sourceFile.getFilePath(),
|
|
13691
|
-
reason: "React hook names are not renderable components",
|
|
13692
|
-
stage: "component-filter"
|
|
13693
|
-
});
|
|
13694
|
-
continue;
|
|
13695
|
-
}
|
|
13774
|
+
for (const { name, declarations } of getRenderableExports(sourceFile, exclusions, {
|
|
13775
|
+
allowVariableDeclaration: true,
|
|
13776
|
+
hookReason: "React hook names are not renderable components"
|
|
13777
|
+
})) {
|
|
13696
13778
|
const funcNode = resolveBestFunctionNode(declarations);
|
|
13697
13779
|
if (!funcNode)
|
|
13698
13780
|
continue;
|
|
@@ -14170,12 +14252,7 @@ var init_react = __esm({
|
|
|
14170
14252
|
// packages/experience-design-system-extraction/dist/src/extract/vue-tsx.js
|
|
14171
14253
|
import { Node as Node5, SyntaxKind as SyntaxKind5 } from "ts-morph";
|
|
14172
14254
|
async function extractVueTsxComponents(filePaths) {
|
|
14173
|
-
const exclusions =
|
|
14174
|
-
const extractionContext = extractTsxComponents(filePaths, /\.tsx$/, (sourceFile) => extractFromSourceFile3(sourceFile, exclusions));
|
|
14175
|
-
if (!extractionContext) {
|
|
14176
|
-
return { components: [], warnings: [] };
|
|
14177
|
-
}
|
|
14178
|
-
const { components, warnings } = extractionContext;
|
|
14255
|
+
const { components, warnings, exclusions } = extractTsxComponents(filePaths, /\.tsx$/, extractFromSourceFile3);
|
|
14179
14256
|
return {
|
|
14180
14257
|
components: components.sort((a, b) => a.name.localeCompare(b.name)),
|
|
14181
14258
|
warnings,
|
|
@@ -14184,21 +14261,9 @@ async function extractVueTsxComponents(filePaths) {
|
|
|
14184
14261
|
}
|
|
14185
14262
|
function extractFromSourceFile3(sourceFile, exclusions) {
|
|
14186
14263
|
const components = [];
|
|
14187
|
-
const
|
|
14188
|
-
|
|
14189
|
-
|
|
14190
|
-
if (!name)
|
|
14191
|
-
continue;
|
|
14192
|
-
if (isCompositionHookName(name)) {
|
|
14193
|
-
exclusions.push({
|
|
14194
|
-
itemType: "component",
|
|
14195
|
-
name,
|
|
14196
|
-
source: sourceFile.getFilePath(),
|
|
14197
|
-
reason: "Vue composition hook names are not renderable components",
|
|
14198
|
-
stage: "component-filter"
|
|
14199
|
-
});
|
|
14200
|
-
continue;
|
|
14201
|
-
}
|
|
14264
|
+
for (const { name, declarations } of getRenderableExports(sourceFile, exclusions, {
|
|
14265
|
+
hookReason: "Vue composition hook names are not renderable components"
|
|
14266
|
+
})) {
|
|
14202
14267
|
const component = extractVueTsxComponent(declarations, name, sourceFile);
|
|
14203
14268
|
if (component) {
|
|
14204
14269
|
components.push(component);
|
|
@@ -27169,6 +27234,7 @@ function WizardApp({
|
|
|
27169
27234
|
const credentialsReadyRef = useRef10(false);
|
|
27170
27235
|
const agentAuthVerifiedRef = useRef10(false);
|
|
27171
27236
|
const scopeGateCompletedRef = useRef10(false);
|
|
27237
|
+
const scopeComponentsRef = useRef10(null);
|
|
27172
27238
|
const existingEntitiesPromiseRef = useRef10(null);
|
|
27173
27239
|
const generatePromiseRef = useRef10(null);
|
|
27174
27240
|
const rawTokensEntryReady = !!initialRawTokensPath;
|
|
@@ -28809,12 +28875,18 @@ If using a custom host configuration, make sure the space exists on that host.`
|
|
|
28809
28875
|
}
|
|
28810
28876
|
const sessionId2 = state.extractSessionId;
|
|
28811
28877
|
const db = openPipelineDb();
|
|
28812
|
-
let
|
|
28878
|
+
let loadedComponents;
|
|
28813
28879
|
try {
|
|
28814
|
-
|
|
28880
|
+
loadedComponents = loadScopeComponents(db, sessionId2);
|
|
28815
28881
|
} finally {
|
|
28816
28882
|
db.close();
|
|
28817
28883
|
}
|
|
28884
|
+
const remembered = scopeComponentsRef.current;
|
|
28885
|
+
if (loadedComponents.length > 0 && remembered?.sessionId !== sessionId2) {
|
|
28886
|
+
scopeComponentsRef.current = { sessionId: sessionId2, components: loadedComponents };
|
|
28887
|
+
}
|
|
28888
|
+
const rememberedForSession = scopeComponentsRef.current?.sessionId === sessionId2 ? scopeComponentsRef.current.components : null;
|
|
28889
|
+
const components = scopeGateCompletedRef.current && rememberedForSession ? rememberedForSession : loadedComponents.length > 0 ? loadedComponents : rememberedForSession ?? [];
|
|
28818
28890
|
return /* @__PURE__ */ jsx50(
|
|
28819
28891
|
ScopeGateStep,
|
|
28820
28892
|
{
|
package/dist/src/session/db.d.ts
CHANGED
|
@@ -22,6 +22,10 @@ export interface ApplyToolCallsResult {
|
|
|
22
22
|
slots: number;
|
|
23
23
|
warnings: string[];
|
|
24
24
|
}
|
|
25
|
+
export interface ApplyToolCallsOptions {
|
|
26
|
+
/** Component names that may appear in generated slot allowed-components lists. */
|
|
27
|
+
allowedComponentNames?: ReadonlySet<string>;
|
|
28
|
+
}
|
|
25
29
|
export interface ComponentReviewMetadata {
|
|
26
30
|
sourcePath: string | null;
|
|
27
31
|
componentSource: string | null;
|
|
@@ -51,7 +55,7 @@ export interface ComponentRationale {
|
|
|
51
55
|
}>;
|
|
52
56
|
}
|
|
53
57
|
export declare function loadComponentRationale(db: DatabaseSync, sessionId: string, componentName: string): ComponentRationale | null;
|
|
54
|
-
export declare function applyToolCalls(db: DatabaseSync, sessionId: string, componentId: string, componentName: string, calls: ToolCall[], incomingWarnings: string[]): ApplyToolCallsResult;
|
|
58
|
+
export declare function applyToolCalls(db: DatabaseSync, sessionId: string, componentId: string, componentName: string, calls: ToolCall[], incomingWarnings: string[], options?: ApplyToolCallsOptions): ApplyToolCallsResult;
|
|
55
59
|
export type MatchHints = SessionMatchHints;
|
|
56
60
|
export type { SessionResolution };
|
|
57
61
|
export declare function getOrCreateSession(db: DatabaseSync, sessionFlag: string | undefined, sessionName: string | undefined, hints: MatchHints): SessionResolution;
|
|
@@ -141,12 +145,24 @@ export interface StoredSlotCycle extends SlotCycle {
|
|
|
141
145
|
export declare function loadSlotCycles(db: DatabaseSync, sessionId: string): StoredSlotCycle[];
|
|
142
146
|
export declare function clearSlotCycles(db: DatabaseSync, sessionId: string): void;
|
|
143
147
|
export declare function markCacheHumanEdited(db: DatabaseSync, entityType: 'component' | 'token_set', entityId: string): void;
|
|
144
|
-
export
|
|
148
|
+
export interface UnknownSlotAllowedComponent {
|
|
149
|
+
componentId: string;
|
|
150
|
+
componentName: string;
|
|
151
|
+
slotName: string;
|
|
152
|
+
allowedComponent: string;
|
|
153
|
+
}
|
|
154
|
+
export declare function findUnknownSlotAllowedComponents(db: DatabaseSync, sessionId: string, allowedComponentNames: ReadonlySet<string>, componentId?: string): UnknownSlotAllowedComponent[];
|
|
155
|
+
/** Remove slot references that cannot resolve in the current manifest or target space. */
|
|
156
|
+
export declare function filterUnknownSlotAllowedComponents(db: DatabaseSync, sessionId: string, allowedComponentNames: ReadonlySet<string>): UnknownSlotAllowedComponent[];
|
|
157
|
+
export interface CopyComponentFromCacheOptions {
|
|
158
|
+
allowedComponentNames?: ReadonlySet<string>;
|
|
159
|
+
}
|
|
160
|
+
export declare function copyComponentFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string, componentId: string, manageTransaction?: boolean, options?: CopyComponentFromCacheOptions): void;
|
|
145
161
|
export declare function copyComponentsFromCache(db: DatabaseSync, entries: Array<{
|
|
146
162
|
sourceSessionId: string;
|
|
147
163
|
targetSessionId: string;
|
|
148
164
|
componentId: string;
|
|
149
|
-
}
|
|
165
|
+
}>, options?: CopyComponentFromCacheOptions): void;
|
|
150
166
|
export declare function copyTokensFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string): void;
|
|
151
167
|
export declare function copyMapTokensFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string): number;
|
|
152
168
|
export declare function getCliCacheVersion(): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.34.5-dev-build-
|
|
3
|
+
"version": "2.34.5-dev-build-f8f0b1fa.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"svelte": "^5.56.4",
|
|
42
42
|
"ts-morph": "^27.0.2",
|
|
43
43
|
"typescript": "^5.9.3",
|
|
44
|
-
"@contentful/experience-design-system-
|
|
45
|
-
"@contentful/experience-design-system-
|
|
44
|
+
"@contentful/experience-design-system-cli-v2": "2.34.5-dev-build-f8f0b1fa.0",
|
|
45
|
+
"@contentful/experience-design-system-types": "2.34.5-dev-build-f8f0b1fa.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@tsconfig/node24": "^24.0.4",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"ink-testing-library": "^4.0.0",
|
|
55
55
|
"typescript-eslint": "^8.67.0",
|
|
56
56
|
"vitest": "^4.0.16",
|
|
57
|
-
"@contentful/experience-design-system-extraction": "2.34.5-dev-build-
|
|
58
|
-
"@contentful/experience-design-system-client": "2.34.5-dev-build-
|
|
59
|
-
"@contentful/experience-design-system-generation": "2.34.5-dev-build-
|
|
57
|
+
"@contentful/experience-design-system-extraction": "2.34.5-dev-build-f8f0b1fa.0",
|
|
58
|
+
"@contentful/experience-design-system-client": "2.34.5-dev-build-f8f0b1fa.0",
|
|
59
|
+
"@contentful/experience-design-system-generation": "2.34.5-dev-build-f8f0b1fa.0"
|
|
60
60
|
},
|
|
61
61
|
"repository": {
|
|
62
62
|
"type": "git",
|
|
@@ -12,11 +12,12 @@ Contentful Experience Orchestration is a Contentful product that enables **marke
|
|
|
12
12
|
|
|
13
13
|
A Component Type has two kinds of configurable properties:
|
|
14
14
|
|
|
15
|
-
- **Design Properties** — values that control
|
|
16
|
-
- **Content Properties** — values that are
|
|
15
|
+
- **Design Properties** — values that control _how the component looks_: color scheme, visual variant (primary/secondary/ghost), size (sm/md/lg), spacing, layout orientation, background color, font style, border style, any visual toggle that changes appearance. These are the values a designer sets once and a marketer may override. Think: "what would a designer put in a design token or a style guide?"
|
|
16
|
+
- **Content Properties** — values that are _data the component displays_: labels, headings, body text, rich text, images, media, URLs, counts, IDs used for CMS lookups, locales. These are filled by editors with real CMS content. Think: "what does a copywriter or content editor fill in?"
|
|
17
17
|
|
|
18
18
|
The third category:
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
- **State Properties** — values that control _interactive or behavioral state_: disabled, loading, expanded, isOpen, isSearchVisible. These are runtime behavioral flags, not visual design nor content.
|
|
20
21
|
|
|
21
22
|
Getting this right matters: Contentful uses the category to decide where a property appears in the editor UI. Design properties appear in the design panel, content properties appear in the content panel.
|
|
22
23
|
|
|
@@ -35,13 +36,13 @@ All input is embedded inline in the prompt before this file:
|
|
|
35
36
|
```typescript
|
|
36
37
|
interface RawPropDefinition {
|
|
37
38
|
name: string;
|
|
38
|
-
type: string;
|
|
39
|
+
type: string; // raw TypeScript type, e.g. "'primary' | 'secondary'"
|
|
39
40
|
required: boolean;
|
|
40
|
-
category?: 'content' | 'design' | 'state';
|
|
41
|
+
category?: 'content' | 'design' | 'state'; // pre-classified — verify, do not blindly trust
|
|
41
42
|
allowedValues?: string[];
|
|
42
43
|
defaultValue?: string;
|
|
43
44
|
description?: string;
|
|
44
|
-
tokenReference?: string;
|
|
45
|
+
tokenReference?: string; // raw token name — shape varies by design system, e.g. "--brand-primary" (CSS custom property) or "tokens.blue500" (flat/dotted JS reference)
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
interface RawSlotDefinition {
|
|
@@ -60,6 +61,7 @@ interface RawComponentDefinition {
|
|
|
60
61
|
|
|
61
62
|
The `category` field on each prop is a pre-classification hint from static analysis heuristics.
|
|
62
63
|
It is correct approximately 80% of the time for simple props. You should:
|
|
64
|
+
|
|
63
65
|
- Trust it for obvious cases (event handlers excluded, text labels as content)
|
|
64
66
|
- Override it when your domain knowledge indicates otherwise
|
|
65
67
|
- NEVER produce zero output — if you disagree with all hints, explain why in descriptions
|
|
@@ -87,12 +89,13 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
|
|
|
87
89
|
```
|
|
88
90
|
|
|
89
91
|
**Rules:**
|
|
92
|
+
|
|
90
93
|
- Emit exactly one JSON object per line. No multi-line JSON.
|
|
91
94
|
- Every prop in the input must produce exactly one call: `classify_prop` OR `exclude_prop`.
|
|
92
95
|
- Every slot must produce exactly one `classify_slot` call.
|
|
93
96
|
- Emit `classify_component` once at the start (required). The `description` field is **required** — always provide a brief description of the component's purpose.
|
|
94
97
|
- `values` is required for `cdf_type: "enum"` — must be a non-empty string array of the variant names the prop accepts. **There is no legal `enum` with a guessed list.** If the value set is shown to you anywhere in the material below — the type's own declaration, an `allowedValues` list on the raw prop, a variant/lookup map, a default, a sibling excerpt — copy it and emit `enum`. If it is shown nowhere, emit `cdf_type: "string"` and leave `values` off. `string` is an unrestricted enum: the author can still pass the real value. A guessed list silently rejects it.
|
|
95
|
-
- **Do NOT include `values` for `cdf_type: "token"`.** The two property types carry different kinds of list. An `enum` prop's list holds
|
|
98
|
+
- **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.
|
|
96
99
|
- `token_kind` is required for `cdf_type: "token"` — must be a DTCG `$type` string, e.g. `"color"`.
|
|
97
100
|
- `required` must be a JSON boolean (`true`/`false`), not a string.
|
|
98
101
|
- `description` on `classify_prop` is customer-facing — keep it short and subject to the description content rules below.
|
|
@@ -117,14 +120,14 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
|
|
|
117
120
|
|
|
118
121
|
Exactly **6** valid types:
|
|
119
122
|
|
|
120
|
-
| cdf_type
|
|
121
|
-
|
|
122
|
-
| `string`
|
|
123
|
-
| `richtext` | Formatted text, HTML, ReactNode used as markup
|
|
124
|
-
| `media`
|
|
125
|
-
| `enum`
|
|
126
|
-
| `token`
|
|
127
|
-
| `boolean`
|
|
123
|
+
| cdf_type | Use case |
|
|
124
|
+
| ---------- | -------------------------------------------------------------------------- |
|
|
125
|
+
| `string` | Plain text, URLs, href props, numbers (as string), any string-shaped value |
|
|
126
|
+
| `richtext` | Formatted text, HTML, ReactNode used as markup |
|
|
127
|
+
| `media` | Images, videos, media assets |
|
|
128
|
+
| `enum` | Fixed set of string choices — requires `values` |
|
|
129
|
+
| `token` | Design-token-linked prop — requires `token_kind` |
|
|
130
|
+
| `boolean` | Boolean toggle props (visible, disabled, enabled, etc.) |
|
|
128
131
|
|
|
129
132
|
> **IMPORTANT: No `number` type.** The design-systems API only supports the `String` design property variant for numeric values. All numeric props must use `cdf_type: "string"` with the number as a string default (e.g. `"0"`, `"100"`). Boolean props can now use `cdf_type: "boolean"` directly.
|
|
130
133
|
|
|
@@ -134,11 +137,11 @@ Exactly **6** valid types:
|
|
|
134
137
|
|
|
135
138
|
### `enum` versus `token`
|
|
136
139
|
|
|
137
|
-
Both describe a closed set of choices, but the
|
|
140
|
+
Both describe a closed set of choices, but the _contents_ differ, and that is the whole distinction:
|
|
138
141
|
|
|
139
|
-
|
|
|
140
|
-
|
|
141
|
-
| `enum`
|
|
142
|
+
| | What the list holds | Example |
|
|
143
|
+
| ------- | --------------------------------------- | ------------------------------------------------ |
|
|
144
|
+
| `enum` | Variant names the component accepts | `["primary", "secondary", "ghost"]` |
|
|
142
145
|
| `token` | Design token paths the prop may bind to | `["color.brand.primary", "color.brand.neutral"]` |
|
|
143
146
|
|
|
144
147
|
The distinction is **what the component does with the value it receives**. That
|
|
@@ -147,7 +150,7 @@ component's role, or from the property's TypeScript type.
|
|
|
147
150
|
|
|
148
151
|
An `enum` design property is delivered as a plain string, so the component
|
|
149
152
|
receives `variant="primary"` — the value its own code branches on. A `token`
|
|
150
|
-
property is resolved to the token's value
|
|
153
|
+
property is resolved to the token's value _before_ it reaches the component, so
|
|
151
154
|
that same component would receive `variant="#0059c8"` and match no branch.
|
|
152
155
|
|
|
153
156
|
#### The three-question procedure (closed, ordered)
|
|
@@ -160,7 +163,7 @@ that decides. There is no fourth question.
|
|
|
160
163
|
An indexed access (`tokens[fontColor]`, `SPACING[padding]`, a `Record<Variant, Token>`),
|
|
161
164
|
a `switch (variant)` / `if (variant === 'x')` chain, a `styles[variant]` class lookup,
|
|
162
165
|
or a vocabulary of its own (`allowedValues`, a literal union) — any of these means
|
|
163
|
-
the component needs the
|
|
166
|
+
the component needs the _name_. A presence check (`if (width)`) or arithmetic on
|
|
164
167
|
the value is not a lookup.
|
|
165
168
|
**Yes → `enum`, names in `values`, stop.** No → Q2.
|
|
166
169
|
- **Q2 — Is the value written straight into a style or attribute?**
|
|
@@ -170,7 +173,7 @@ that decides. There is no fourth question.
|
|
|
170
173
|
- **Q3 — Is there a design-token reference at that use?** Any one of:
|
|
171
174
|
the parameter default is a token (`radius = tokens.borderRadiusSmall`), the raw prop
|
|
172
175
|
carries a `tokenReference`, or an inline `tokens.*` / `var(--*)` sits in the same
|
|
173
|
-
expression. The reference must belong to
|
|
176
|
+
expression. The reference must belong to _this_ prop — a `tokens.*` on some other
|
|
174
177
|
line of the file, or in a sibling's style object, is not one.
|
|
175
178
|
**Yes → `token`, stop; resolve `token_kind` from that reference. No → `string`, stop.**
|
|
176
179
|
|
|
@@ -178,7 +181,7 @@ that decides. There is no fourth question.
|
|
|
178
181
|
`string | number`, `stringOrNumber` — none of these answers any question above;
|
|
179
182
|
they say what the compiler accepts, not what the component does with the value.
|
|
180
183
|
"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
|
|
184
|
+
a Q3-yes described as a no: a token parameter default _is_ the design-token
|
|
182
185
|
reference Q3 asks for, and the prop is `token`. Equally, a prop named `color` with
|
|
183
186
|
no lookup and no token reference is `string`, however token-like the name.
|
|
184
187
|
|
|
@@ -216,7 +219,7 @@ the source is truncated at the point of use (the "uses not shown" line). In thos
|
|
|
216
219
|
cases emit `enum` if a value set is citable from the source shown, else `cdf_type:
|
|
217
220
|
"string"` per "no legal `enum` with a guessed list" — either way, record the
|
|
218
221
|
ambiguity in `reason`. When the three
|
|
219
|
-
questions
|
|
222
|
+
questions _can_ be answered from the source shown, they are answered and the
|
|
220
223
|
tie-break does not apply: a clear Q1-yes is `enum`, a clear Q2-yes + Q3-yes is
|
|
221
224
|
`token`, and a type annotation does not create ambiguity. An `enum` is delivered
|
|
222
225
|
as the plain string the component was written to receive and remains reachable
|
|
@@ -229,11 +232,11 @@ reason to call a decided case a tie.
|
|
|
229
232
|
|
|
230
233
|
## Valid cdf_category values
|
|
231
234
|
|
|
232
|
-
| cdf_category | Use case
|
|
233
|
-
|
|
234
|
-
| `content`
|
|
235
|
-
| `design`
|
|
236
|
-
| `state`
|
|
235
|
+
| cdf_category | Use case |
|
|
236
|
+
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
237
|
+
| `content` | Data the component _displays_ — what a copywriter or editor fills in: text, labels, headings, body copy, rich text, images, media, URLs, link targets, counts, locale |
|
|
238
|
+
| `design` | Values that control _how the component looks_ — what a designer sets: color, size (sm/md/lg), variant (primary/secondary/ghost), layout orientation, alignment, background, visual toggles (imageOnLeft, enableEffect), design tokens |
|
|
239
|
+
| `state` | Runtime behavioral or interactive flags — not visible in the editor's design or content panel: disabled, loading, expanded, isOpen, isSearchVisible, preview, identifiers used for analytics/tracking (componentId, sectionKey, componentName) |
|
|
237
240
|
|
|
238
241
|
The pre-classified `category` in the raw input is a starting point — correct it when it is wrong. Contentful uses this category to decide where the property appears in the editor UI, so accuracy matters.
|
|
239
242
|
|
|
@@ -254,7 +257,8 @@ For each `RawPropDefinition`, apply in order:
|
|
|
254
257
|
- Data attributes: any `data-*` prop
|
|
255
258
|
- **Polymorphic component props**: `as`, `element`, `component` (when typed as an HTML tag string or component reference) — these change rendered HTML, not marketer-visible behavior
|
|
256
259
|
- **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
|
|
257
|
-
- **Important caveat**: only exclude when the prop is inherited/universal
|
|
260
|
+
- **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.
|
|
261
|
+
|
|
258
262
|
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:
|
|
259
263
|
- `icon` / `leftIcon` / `rightIcon` / `prefixIcon` / `suffixIcon` — slot or `string` (icon name); see slot guidance below
|
|
260
264
|
- `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.
|
|
@@ -266,21 +270,21 @@ For each `RawPropDefinition`, apply in order:
|
|
|
266
270
|
- `eventDetails` / similar telemetry props — `cdf_category: "state"`.
|
|
267
271
|
3. **Positional/geometric design prop?** (`top`, `bottom`, `left`, `right`, `rotation`, `offset`, `zIndex`) → `classify_prop`, `cdf_type: "string"`, `cdf_category: "design"`.
|
|
268
272
|
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:
|
|
273
|
+
- **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.
|
|
274
|
+
- **Q2** not written straight into a style or attribute → not `token`; continue to rules 5–7.
|
|
275
|
+
- **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"`.
|
|
276
|
+
- 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.
|
|
269
277
|
|
|
270
|
-
|
|
271
|
-
* **Q2** not written straight into a style or attribute → not `token`; continue to rules 5–7.
|
|
272
|
-
* **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"`.
|
|
273
|
-
* 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.
|
|
274
|
-
|
|
275
|
-
**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:
|
|
278
|
+
**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:
|
|
276
279
|
|
|
277
280
|
```ts
|
|
278
281
|
// Box.tsx — padding is an enum, despite Box being a layout primitive
|
|
279
|
-
const { padding } = props;
|
|
280
|
-
getSpacingStyles({ padding });
|
|
282
|
+
const { padding } = props; // "spacingXs"
|
|
283
|
+
getSpacingStyles({ padding }); // → SpacingTable["spacingXs"] → "0.5rem"
|
|
281
284
|
```
|
|
282
285
|
|
|
283
286
|
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.
|
|
287
|
+
|
|
284
288
|
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.
|
|
285
289
|
6. **Raw type is `string`** and prop name is `href`, `url`, or clearly a URL? → `cdf_type: "string"`, `cdf_category: "content"`.
|
|
286
290
|
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.
|
|
@@ -292,27 +296,28 @@ For each `RawPropDefinition`, apply in order:
|
|
|
292
296
|
|
|
293
297
|
## Resolving complex types — do not exclude without reasoning
|
|
294
298
|
|
|
295
|
-
A prop with a complex TypeScript type is **not automatically excluded**. Many props that appear complex carry real marketer-configurable information. Before excluding, ask:
|
|
299
|
+
A prop with a complex TypeScript type is **not automatically excluded**. Many props that appear complex carry real marketer-configurable information. Before excluding, ask: _"Could a marketer set this value in Contentful?"_ If yes, classify it.
|
|
296
300
|
|
|
297
301
|
**Common resolvable patterns:**
|
|
298
302
|
|
|
299
|
-
| Raw type pattern
|
|
300
|
-
|
|
301
|
-
| `'primary' \| 'secondary' \| 'ghost'` (union of literals)
|
|
303
|
+
| Raw type pattern | How to resolve |
|
|
304
|
+
| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
305
|
+
| `'primary' \| 'secondary' \| 'ghost'` (union of literals) | → `enum`, extract `values` |
|
|
302
306
|
| `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum` if you can cite its values from the source shown (the type's own declaration, a lookup table, a default) — copy them, never invent them. If the source shown does not enumerate the values, this is not the Q1–Q3 ambiguity tie-break — it is the "no legal `enum` with a guessed list" rule: emit `cdf_type: "string"` instead, with the missing evidence noted in `reason`. 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. |
|
|
303
|
-
| `Variant` / `variant` prop
|
|
304
|
-
| `Section[]` / array of custom items where the structure is unclear
|
|
305
|
-
| `ExperienceConfiguration<Variant>` / deep generic
|
|
306
|
-
| `React.Dispatch<...>` / setter
|
|
307
|
-
| `React.RefObject<...>` / `ref`
|
|
308
|
-
| `() => void` / callback
|
|
309
|
-
| `ReactNode` used as a slot-like prop (children, `icon`, `footer`)
|
|
310
|
-
| `boolean` with a name like `hideChevron`, `imageOnLeft`, `enableBackgroundColorEffect`
|
|
311
|
-
| `boolean` with a name like `preview`, `hideContentForPersonalization`
|
|
312
|
-
| `string` used as a `componentId`, `sectionKey`, `componentName`
|
|
313
|
-
| `string` locale (e.g. `locale: string`)
|
|
307
|
+
| `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. |
|
|
308
|
+
| `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. |
|
|
309
|
+
| `ExperienceConfiguration<Variant>` / deep generic | Personalization config — → `exclude_prop`, reason: `"personalization configuration — framework internal"` |
|
|
310
|
+
| `React.Dispatch<...>` / setter | State setter — → `exclude_prop`, reason: `"React state setter — framework internal"` |
|
|
311
|
+
| `React.RefObject<...>` / `ref` | → `exclude_prop`, reason: `"ref — framework internal"` |
|
|
312
|
+
| `() => void` / callback | → `exclude_prop`, reason: `"callback function — framework internal"` |
|
|
313
|
+
| `ReactNode` used as a slot-like prop (children, `icon`, `footer`) | → classify as a `slot` if it represents an injectable area, or `richtext` if it is inline markup content |
|
|
314
|
+
| `boolean` with a name like `hideChevron`, `imageOnLeft`, `enableBackgroundColorEffect` | → `boolean`, `cdf_category: "design"`, `default: true` or `false` — these control visual appearance |
|
|
315
|
+
| `boolean` with a name like `preview`, `hideContentForPersonalization` | → `boolean`, `cdf_category: "state"`, `default: false` — these control behavior |
|
|
316
|
+
| `string` used as a `componentId`, `sectionKey`, `componentName` | → `string`, `cdf_category: "state"` — these are identifiers for tracking/lookup |
|
|
317
|
+
| `string` locale (e.g. `locale: string`) | → `string`, `cdf_category: "state"` — locale is a behavioral/routing value |
|
|
314
318
|
|
|
315
319
|
**When to finally exclude:**
|
|
320
|
+
|
|
316
321
|
- The type is a callback signature or event handler
|
|
317
322
|
- The type is a React ref
|
|
318
323
|
- The type is a React state setter (`Dispatch`)
|
|
@@ -342,6 +347,7 @@ function splits on `.` and takes only the last segment, producing poor display n
|
|
|
342
347
|
produces display names like "Item Url", "Item Alt" which are more readable in the ExO editor.
|
|
343
348
|
|
|
344
349
|
Rules for nested objects:
|
|
350
|
+
|
|
345
351
|
- Flatten to max depth 2 (e.g., `item_nested_deep` is acceptable, deeper is not)
|
|
346
352
|
- Each leaf field gets its own classify_prop call with underscore-joined name
|
|
347
353
|
- Apply the same classification rules as top-level props
|
|
@@ -384,9 +390,11 @@ The pre-classified `category` is wrong in predictable ways. Correct silently (do
|
|
|
384
390
|
The `classify_slot` tool call maps to CDF's `$slots` object. Each slot you classify becomes a `$slots` entry; the `allowed_components` field maps to `$allowedComponents` in the output CDF.
|
|
385
391
|
|
|
386
392
|
For each `RawSlotDefinition`:
|
|
393
|
+
|
|
387
394
|
- `name` → `slot` field
|
|
388
395
|
- `description` → `description` field
|
|
389
396
|
- `allowedComponents` → `allowed_components` (pass through)
|
|
397
|
+
- `allowed_components` must contain only exact names from the hard allowlist supplied in the prompt; never invent a child from an import, JSX element, type name, icon name, or implementation helper, and omit any child that is not listed.
|
|
390
398
|
- `required`:
|
|
391
399
|
- `true` if the component does not render correctly without content in this slot (primary content area of a Card, Dialog body, etc.)
|
|
392
400
|
- `false` if clearly optional (icon slot, footer slot with a default, decorative slot)
|
|
@@ -401,21 +409,33 @@ For each `RawSlotDefinition`:
|
|
|
401
409
|
### Simple component
|
|
402
410
|
|
|
403
411
|
Input:
|
|
412
|
+
|
|
404
413
|
```json
|
|
405
414
|
{
|
|
406
415
|
"name": "Button",
|
|
407
416
|
"props": [
|
|
408
|
-
{
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
417
|
+
{
|
|
418
|
+
"name": "label",
|
|
419
|
+
"type": "string",
|
|
420
|
+
"category": "content",
|
|
421
|
+
"required": true
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"name": "variant",
|
|
425
|
+
"type": "'primary'|'secondary'|'ghost'",
|
|
426
|
+
"category": "design",
|
|
427
|
+
"defaultValue": "'primary'"
|
|
428
|
+
},
|
|
429
|
+
{ "name": "disabled", "type": "boolean", "category": "design" },
|
|
430
|
+
{ "name": "onClick", "type": "()=>void", "category": "state" },
|
|
431
|
+
{ "name": "className", "type": "string" }
|
|
413
432
|
],
|
|
414
|
-
"slots": [{"name":"icon","description":"Optional leading icon"}]
|
|
433
|
+
"slots": [{ "name": "icon", "description": "Optional leading icon" }]
|
|
415
434
|
}
|
|
416
435
|
```
|
|
417
436
|
|
|
418
437
|
Output:
|
|
438
|
+
|
|
419
439
|
```
|
|
420
440
|
Starting Button classification — 5 props, 1 slot
|
|
421
441
|
{"tool":"classify_component","description":"Primary action button with variant and state support","rationale":{"description":"Button is an atom — a single interactive control that triggers an action. It carries a label, a small set of visual variants, and a disabled flag, which is the minimal surface a marketer needs to configure a call-to-action.","props":"Kept label (content), variant (enum, design), disabled (boolean, state), and className (string, design escape hatch). Excluded onClick because it is an event handler — framework-internal and not configurable in Contentful.","slots":"Kept the icon slot as optional because the button renders correctly without it and the icon is purely decorative."}}
|
|
@@ -436,21 +456,33 @@ icon slot is clearly optional (decorative leading icon)
|
|
|
436
456
|
### Component with multiple slots (Card)
|
|
437
457
|
|
|
438
458
|
Input:
|
|
459
|
+
|
|
439
460
|
```json
|
|
440
461
|
{
|
|
441
462
|
"name": "Card",
|
|
442
463
|
"props": [
|
|
443
|
-
{
|
|
444
|
-
|
|
464
|
+
{
|
|
465
|
+
"name": "title",
|
|
466
|
+
"type": "string",
|
|
467
|
+
"category": "content",
|
|
468
|
+
"required": true
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
"name": "elevation",
|
|
472
|
+
"type": "'flat'|'raised'|'floating'",
|
|
473
|
+
"category": "design",
|
|
474
|
+
"defaultValue": "'raised'"
|
|
475
|
+
}
|
|
445
476
|
],
|
|
446
477
|
"slots": [
|
|
447
|
-
{"name":"body","description":"Main card body"},
|
|
448
|
-
{"name":"footer","description":"Optional footer area"}
|
|
478
|
+
{ "name": "body", "description": "Main card body" },
|
|
479
|
+
{ "name": "footer", "description": "Optional footer area" }
|
|
449
480
|
]
|
|
450
481
|
}
|
|
451
482
|
```
|
|
452
483
|
|
|
453
484
|
Output:
|
|
485
|
+
|
|
454
486
|
```
|
|
455
487
|
Starting Card classification — 2 props, 2 slots
|
|
456
488
|
{"tool":"classify_component","description":"Container that groups related content with a title, body, and optional footer","rationale":{"description":"Card is a molecule — it composes a title with body and footer slots into a single visual container. Useful as a building block for lists and grids of related content.","props":"Kept title (string, content) as the customer-facing label and elevation (enum, design) for the three visual depth variants. Nothing was excluded — both raw props map cleanly to CDF.","slots":"Kept both body (required, primary content area) and footer (optional, supplementary area). Body is required because a card with no body renders empty; footer is optional because many cards do not need one."}}
|
|
@@ -467,7 +499,7 @@ footer is supplementary and optional
|
|
|
467
499
|
### Named type (HeadingSize, ButtonVariant, etc.) — do NOT infer the values
|
|
468
500
|
|
|
469
501
|
When a prop has a named TypeScript type that is not inlined as a union literal,
|
|
470
|
-
the value set is whatever that type's declaration says. A type
|
|
502
|
+
the value set is whatever that type's declaration says. A type _name_ is not
|
|
471
503
|
evidence of its members: `HeadingSize` could be `h1`–`h6`, `sm|md|lg`, or
|
|
472
504
|
`s|m|l|xl`. Guessing picks one design system's vocabulary and is wrong more
|
|
473
505
|
often than it is right, and a wrong name matches no style rule — the author's
|
|
@@ -478,12 +510,14 @@ is not, **emit `string`** — `string` is an unrestricted enum, so the author st
|
|
|
478
510
|
unrestricted but correct.
|
|
479
511
|
|
|
480
512
|
Members visible in the source shown — copy them, do not paraphrase:
|
|
513
|
+
|
|
481
514
|
```
|
|
482
515
|
titleSize has type HeadingSize; HeadingSize is declared in the excerpt as 'sm' | 'md' | 'lg'
|
|
483
516
|
{"tool":"classify_prop","prop":"titleSize","cdf_type":"enum","cdf_category":"design","required":false,"values":["sm","md","lg"],"description":"Heading size","reason":"Copied from the HeadingSize declaration shown in the excerpt."}
|
|
484
517
|
```
|
|
485
518
|
|
|
486
519
|
Members NOT visible in the source shown — degrade to `string`:
|
|
520
|
+
|
|
487
521
|
```
|
|
488
522
|
titleSize has type HeadingSize; HeadingSize is not declared anywhere in the source shown
|
|
489
523
|
no evidence for its members, so no values list — string, not a guess
|
|
@@ -517,7 +551,7 @@ Before emitting any tool calls, verify:
|
|
|
517
551
|
5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed) and does **not** have `values` set
|
|
518
552
|
6. No `cdf_type: "link"` — all href/url props use `string`
|
|
519
553
|
7. `required` values are JSON booleans, not strings
|
|
520
|
-
8. Framework, DOM, accessibility, and data
|
|
554
|
+
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.
|
|
521
555
|
9. No `cdf_type: "link"` used — `link` is reserved and rejected by the CLI parser
|
|
522
556
|
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.
|
|
523
557
|
11. `classify_component` includes a `rationale` object with all three sub-fields (`rationale.description`, `rationale.props`, `rationale.slots`) populated as non-empty strings.
|