@contentful/experience-design-system-cli 2.11.4-dev-build-d47ab65.0 → 2.12.1-dev-build-589b615.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/README.md +214 -357
- package/dist/package.json +1 -1
- package/dist/src/analyze/extract/astro.js +9 -0
- package/dist/src/analyze/extract/react.js +23 -0
- package/dist/src/analyze/extract/stencil.js +3 -0
- package/dist/src/analyze/extract/vue.js +6 -0
- package/dist/src/analyze/extract/web-components.js +10 -0
- package/dist/src/analyze/pre-classify.d.ts +1 -1
- package/dist/src/analyze/pre-classify.js +3 -3
- package/dist/src/analyze/select/persistence.d.ts +13 -0
- package/dist/src/analyze/select/persistence.js +38 -0
- package/dist/src/analyze/select/preview-annotations.d.ts +26 -0
- package/dist/src/analyze/select/preview-annotations.js +62 -0
- package/dist/src/analyze/select/tui/App.js +5 -2
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.d.ts +30 -0
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +137 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +58 -1
- package/dist/src/analyze/select/tui/components/FieldEditor.js +637 -161
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +1 -1
- package/dist/src/analyze/select/tui/components/JsonPanel.js +5 -1
- package/dist/src/analyze/select/tui/components/RationalePanel.d.ts +34 -0
- package/dist/src/analyze/select/tui/components/RationalePanel.js +92 -0
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/Sidebar.js +27 -3
- package/dist/src/analyze/select/tui/hooks/scroll-offset.d.ts +29 -0
- package/dist/src/analyze/select/tui/hooks/scroll-offset.js +26 -0
- package/dist/src/analyze/select-agent/command.d.ts +2 -0
- package/dist/src/analyze/select-agent/command.js +259 -45
- package/dist/src/analyze/select-agent/show-rationale.d.ts +35 -0
- package/dist/src/analyze/select-agent/show-rationale.js +92 -0
- package/dist/src/apply/command.js +7 -3
- package/dist/src/apply/tui/ServerApplyView.d.ts +3 -1
- package/dist/src/apply/tui/ServerApplyView.js +3 -2
- package/dist/src/credentials-store.d.ts +5 -0
- package/dist/src/credentials-store.js +7 -1
- package/dist/src/generate/agent-runner.d.ts +14 -0
- package/dist/src/generate/agent-runner.js +16 -0
- package/dist/src/generate/command.d.ts +6 -0
- package/dist/src/generate/command.js +45 -10
- package/dist/src/generate/progress.d.ts +9 -0
- package/dist/src/generate/progress.js +11 -0
- package/dist/src/generate/prompt-builder.d.ts +8 -0
- package/dist/src/generate/prompt-builder.js +22 -11
- package/dist/src/import/agent-model-resolve.d.ts +23 -0
- package/dist/src/import/agent-model-resolve.js +35 -0
- package/dist/src/import/auto-filter-resolve.d.ts +12 -0
- package/dist/src/import/auto-filter-resolve.js +16 -0
- package/dist/src/import/command.js +248 -8
- package/dist/src/import/orchestrator.d.ts +2 -0
- package/dist/src/import/orchestrator.js +11 -3
- package/dist/src/import/print-prompt.d.ts +35 -0
- package/dist/src/import/print-prompt.js +31 -0
- package/dist/src/import/tui/CustomPromptBanner.d.ts +11 -0
- package/dist/src/import/tui/CustomPromptBanner.js +7 -0
- package/dist/src/import/tui/WizardApp.d.ts +112 -8
- package/dist/src/import/tui/WizardApp.js +814 -333
- package/dist/src/import/tui/auto-filter-error.d.ts +2 -0
- package/dist/src/import/tui/auto-filter-error.js +29 -0
- package/dist/src/import/tui/final-review-host.d.ts +15 -0
- package/dist/src/import/tui/final-review-host.js +20 -0
- package/dist/src/import/tui/push-decision-gate-helpers.d.ts +8 -0
- package/dist/src/import/tui/push-decision-gate-helpers.js +8 -0
- package/dist/src/import/tui/push-progress.d.ts +23 -0
- package/dist/src/import/tui/push-progress.js +14 -0
- package/dist/src/import/tui/run-print-files-helpers.d.ts +26 -0
- package/dist/src/import/tui/run-print-files-helpers.js +8 -0
- package/dist/src/import/tui/run-teaser.d.ts +5 -0
- package/dist/src/import/tui/run-teaser.js +9 -0
- package/dist/src/import/tui/runLivePreview.d.ts +35 -0
- package/dist/src/import/tui/runLivePreview.js +73 -0
- package/dist/src/import/tui/runScopeGate.d.ts +22 -0
- package/dist/src/import/tui/runScopeGate.js +28 -0
- package/dist/src/import/tui/scope-gate-host.d.ts +21 -0
- package/dist/src/import/tui/scope-gate-host.js +20 -0
- package/dist/src/import/tui/spawn-generate.d.ts +33 -0
- package/dist/src/import/tui/spawn-generate.js +53 -0
- package/dist/src/import/tui/steps/CredentialsStep.d.ts +25 -1
- package/dist/src/import/tui/steps/CredentialsStep.js +31 -3
- package/dist/src/import/tui/steps/DoneStep.d.ts +6 -1
- package/dist/src/import/tui/steps/DoneStep.js +10 -4
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +29 -2
- package/dist/src/import/tui/steps/GenerateReviewStep.js +412 -48
- package/dist/src/import/tui/steps/PushDecisionGateStep.d.ts +19 -0
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +76 -0
- package/dist/src/import/tui/steps/PushingStep.d.ts +10 -0
- package/dist/src/import/tui/steps/PushingStep.js +40 -0
- package/dist/src/import/tui/steps/ScopeGateStep.d.ts +23 -0
- package/dist/src/import/tui/steps/ScopeGateStep.js +243 -0
- package/dist/src/import/tui/steps/TokenInputStep.js +20 -3
- package/dist/src/import/tui/useLivePreview.d.ts +35 -0
- package/dist/src/import/tui/useLivePreview.js +120 -0
- package/dist/src/import/tui/wizard-generate-progress.d.ts +21 -0
- package/dist/src/import/tui/wizard-generate-progress.js +25 -0
- package/dist/src/import/tui/wizard-save-flow.d.ts +37 -0
- package/dist/src/import/tui/wizard-save-flow.js +20 -0
- package/dist/src/import/tui/wizard-state-transitions.d.ts +80 -0
- package/dist/src/import/tui/wizard-state-transitions.js +79 -0
- package/dist/src/lib/contentful-urls.d.ts +34 -0
- package/dist/src/lib/contentful-urls.js +41 -0
- package/dist/src/program.js +2 -0
- package/dist/src/runs/export-helpers.d.ts +30 -0
- package/dist/src/runs/export-helpers.js +59 -0
- package/dist/src/runs/fingerprint.d.ts +47 -0
- package/dist/src/runs/fingerprint.js +75 -0
- package/dist/src/runs/ls-command.d.ts +12 -0
- package/dist/src/runs/ls-command.js +137 -0
- package/dist/src/runs/modify-launcher.d.ts +24 -0
- package/dist/src/runs/modify-launcher.js +36 -0
- package/dist/src/runs/path-prompt.d.ts +8 -0
- package/dist/src/runs/path-prompt.js +72 -0
- package/dist/src/runs/push-creds-prompt.d.ts +25 -0
- package/dist/src/runs/push-creds-prompt.js +39 -0
- package/dist/src/runs/push-helpers.d.ts +29 -0
- package/dist/src/runs/push-helpers.js +69 -0
- package/dist/src/runs/replay-helpers.d.ts +67 -0
- package/dist/src/runs/replay-helpers.js +148 -0
- package/dist/src/runs/resolve-run-target.d.ts +12 -0
- package/dist/src/runs/resolve-run-target.js +45 -0
- package/dist/src/runs/run-picker-mount.d.ts +46 -0
- package/dist/src/runs/run-picker-mount.js +72 -0
- package/dist/src/runs/run-picker.d.ts +17 -0
- package/dist/src/runs/run-picker.js +145 -0
- package/dist/src/runs/save-conflict.d.ts +8 -0
- package/dist/src/runs/save-conflict.js +56 -0
- package/dist/src/runs/save-path-resolver.d.ts +55 -0
- package/dist/src/runs/save-path-resolver.js +71 -0
- package/dist/src/runs/staleness.d.ts +38 -0
- package/dist/src/runs/staleness.js +140 -0
- package/dist/src/runs/store.d.ts +78 -0
- package/dist/src/runs/store.js +136 -0
- package/dist/src/session/cache-keys.d.ts +15 -0
- package/dist/src/session/cache-keys.js +38 -0
- package/dist/src/session/db.d.ts +89 -3
- package/dist/src/session/db.js +447 -104
- package/dist/src/setup/auto-filter-prompt.d.ts +13 -0
- package/dist/src/setup/auto-filter-prompt.js +24 -0
- package/dist/src/setup/command.d.ts +7 -0
- package/dist/src/setup/command.js +58 -2
- package/dist/src/types.d.ts +6 -0
- package/package.json +2 -2
- package/skills/generate-components.md +54 -6
- package/skills/select-components.md +13 -4
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask the operator whether to enable the AI auto-filter by default.
|
|
3
|
+
*
|
|
4
|
+
* The helper is injectable so it can be unit-tested without a TTY. The caller
|
|
5
|
+
* provides an `ask` function that yields one line of input per call.
|
|
6
|
+
*
|
|
7
|
+
* Behavior:
|
|
8
|
+
* - Empty input returns `current` if defined, else `true` (default ON).
|
|
9
|
+
* - Input starting with 'y' or 'Y' returns `true`.
|
|
10
|
+
* - Input starting with 'n' or 'N' returns `false`.
|
|
11
|
+
* - Any other input falls back to the same rule as empty input.
|
|
12
|
+
*/
|
|
13
|
+
export declare function promptAutoFilterPreference(ask: (q: string) => Promise<string>, current?: boolean): Promise<boolean>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask the operator whether to enable the AI auto-filter by default.
|
|
3
|
+
*
|
|
4
|
+
* The helper is injectable so it can be unit-tested without a TTY. The caller
|
|
5
|
+
* provides an `ask` function that yields one line of input per call.
|
|
6
|
+
*
|
|
7
|
+
* Behavior:
|
|
8
|
+
* - Empty input returns `current` if defined, else `true` (default ON).
|
|
9
|
+
* - Input starting with 'y' or 'Y' returns `true`.
|
|
10
|
+
* - Input starting with 'n' or 'N' returns `false`.
|
|
11
|
+
* - Any other input falls back to the same rule as empty input.
|
|
12
|
+
*/
|
|
13
|
+
export async function promptAutoFilterPreference(ask, current) {
|
|
14
|
+
const defaultValue = current ?? true;
|
|
15
|
+
const hint = defaultValue ? '[Y/n]' : '[y/N]';
|
|
16
|
+
const answer = (await ask(` Enable AI auto-filter by default? ${hint} `)).trim().toLowerCase();
|
|
17
|
+
if (answer === '')
|
|
18
|
+
return defaultValue;
|
|
19
|
+
if (answer.startsWith('y'))
|
|
20
|
+
return true;
|
|
21
|
+
if (answer.startsWith('n'))
|
|
22
|
+
return false;
|
|
23
|
+
return defaultValue;
|
|
24
|
+
}
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
+
export type SkillPromptKind = 'select' | 'generate';
|
|
3
|
+
/**
|
|
4
|
+
* Prompt the operator for a custom skill prompt path. Returns the resolved
|
|
5
|
+
* trimmed value, or `undefined` to leave the current value unchanged, or `null`
|
|
6
|
+
* to clear it. `ask` is injectable so tests can stub stdin.
|
|
7
|
+
*/
|
|
8
|
+
export declare function promptCustomSkillPath(kind: SkillPromptKind, current: string | undefined, ask?: (q: string) => Promise<string>): Promise<string | undefined | null>;
|
|
2
9
|
export declare function registerSetupCommand(program: Command): void;
|
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { createInterface } from 'node:readline';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import { readExperiencesCredentials, writeExperiencesCredentials, experiencesCredentialsPath, } from '../credentials-store.js';
|
|
9
|
+
import { promptAutoFilterPreference } from './auto-filter-prompt.js';
|
|
9
10
|
import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../host-utils.js';
|
|
10
11
|
const execFileAsync = promisify(execFile);
|
|
11
12
|
const REQUIRED_NODE_MAJOR = 24;
|
|
@@ -521,12 +522,43 @@ async function setupContentfulCredentials() {
|
|
|
521
522
|
info('Run experiences import — credentials will be pre-filled automatically.');
|
|
522
523
|
return true;
|
|
523
524
|
}
|
|
525
|
+
/**
|
|
526
|
+
* Prompt the operator for a custom skill prompt path. Returns the resolved
|
|
527
|
+
* trimmed value, or `undefined` to leave the current value unchanged, or `null`
|
|
528
|
+
* to clear it. `ask` is injectable so tests can stub stdin.
|
|
529
|
+
*/
|
|
530
|
+
export async function promptCustomSkillPath(kind, current, ask = prompt) {
|
|
531
|
+
const flagName = kind === 'select' ? '--select-prompt-path' : '--generate-prompt-path';
|
|
532
|
+
void flagName;
|
|
533
|
+
const label = kind === 'select' ? 'select (analyze select-agent)' : 'generate (generate components)';
|
|
534
|
+
const currentLabel = current ? ` [${current}]` : ' [none]';
|
|
535
|
+
const answer = await ask(` Custom ${label} prompt path${currentLabel} (empty=keep, "-"=clear): `);
|
|
536
|
+
const trimmed = answer.trim();
|
|
537
|
+
if (trimmed === '')
|
|
538
|
+
return undefined;
|
|
539
|
+
if (trimmed === '-')
|
|
540
|
+
return null;
|
|
541
|
+
return trimmed;
|
|
542
|
+
}
|
|
524
543
|
// ── Step 6: Optional quality-of-life ─────────────────────────────────────────
|
|
525
544
|
async function setupQoL(profilePath) {
|
|
526
545
|
section('Step 6: Optional extras', '[optional]');
|
|
527
546
|
info('These are not required for experiences import but improve the experience.');
|
|
528
547
|
info('');
|
|
529
|
-
// 6a:
|
|
548
|
+
// 6a: AI auto-filter default
|
|
549
|
+
const existingCreds = await readExperiencesCredentials();
|
|
550
|
+
info('AI auto-filter — runs an agent pass before the manual scope-gate to prefilter components.');
|
|
551
|
+
info('Operators who prefer to review every component can default this OFF and override per run with --auto-filter.');
|
|
552
|
+
const autoFilter = await promptAutoFilterPreference((q) => prompt(q), existingCreds.autoFilter);
|
|
553
|
+
if (autoFilter !== (existingCreds.autoFilter ?? true)) {
|
|
554
|
+
await writeExperiencesCredentials({ ...existingCreds, autoFilter });
|
|
555
|
+
ok(`AI auto-filter default set to ${autoFilter ? 'ON' : 'OFF'}`);
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
dim(' unchanged');
|
|
559
|
+
}
|
|
560
|
+
info('');
|
|
561
|
+
// 6b: EDS_EXTRACT_CONCURRENCY
|
|
530
562
|
const hasConcurrency = await profileContains(profilePath, 'EDS_EXTRACT_CONCURRENCY');
|
|
531
563
|
if (!hasConcurrency) {
|
|
532
564
|
info('EDS_EXTRACT_CONCURRENCY — controls how many components are analyzed in parallel.');
|
|
@@ -543,7 +575,31 @@ async function setupQoL(profilePath) {
|
|
|
543
575
|
else {
|
|
544
576
|
ok('EDS_EXTRACT_CONCURRENCY — already set');
|
|
545
577
|
}
|
|
546
|
-
//
|
|
578
|
+
// 6c (Feature 8): custom skill prompt paths
|
|
579
|
+
info('');
|
|
580
|
+
info('Custom skill prompt paths — point select-agent and/or generate components at your own .md prompts.');
|
|
581
|
+
info('When set, the bundled invariants (utility-wrapper rejection, description rules) do NOT apply.');
|
|
582
|
+
const offerCustomPrompts = await confirm('Configure custom skill prompt paths?', false);
|
|
583
|
+
if (offerCustomPrompts) {
|
|
584
|
+
const stored = await readExperiencesCredentials();
|
|
585
|
+
const selectAnswer = await promptCustomSkillPath('select', stored.selectPromptPath);
|
|
586
|
+
const generateAnswer = await promptCustomSkillPath('generate', stored.generatePromptPath);
|
|
587
|
+
const updated = { ...stored };
|
|
588
|
+
if (selectAnswer === null)
|
|
589
|
+
delete updated.selectPromptPath;
|
|
590
|
+
else if (selectAnswer !== undefined)
|
|
591
|
+
updated.selectPromptPath = selectAnswer;
|
|
592
|
+
if (generateAnswer === null)
|
|
593
|
+
delete updated.generatePromptPath;
|
|
594
|
+
else if (generateAnswer !== undefined)
|
|
595
|
+
updated.generatePromptPath = generateAnswer;
|
|
596
|
+
await writeExperiencesCredentials(updated);
|
|
597
|
+
ok(`Custom prompt paths saved to ${experiencesCredentialsPath()}`);
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
dim(' skipped');
|
|
601
|
+
}
|
|
602
|
+
// 6d: NO_COLOR
|
|
547
603
|
info('');
|
|
548
604
|
info('NO_COLOR — set to 1 to disable ANSI color output (useful in CI or plain terminals).');
|
|
549
605
|
const setNoColor = await confirm('Add NO_COLOR=1 (disable colors) to your profile?', false);
|
package/dist/src/types.d.ts
CHANGED
|
@@ -22,6 +22,10 @@ export interface RawPropDefinition {
|
|
|
22
22
|
allowedValues?: string[];
|
|
23
23
|
description?: string;
|
|
24
24
|
tokenReference?: string;
|
|
25
|
+
/** 1-indexed source line where this prop's declaration begins; relative to RawComponentDefinition.source. */
|
|
26
|
+
sourceStartLine?: number;
|
|
27
|
+
/** 1-indexed inclusive source line where this prop's declaration ends; relative to RawComponentDefinition.source. */
|
|
28
|
+
sourceEndLine?: number;
|
|
25
29
|
}
|
|
26
30
|
export interface RawSlotDefinition {
|
|
27
31
|
name: string;
|
|
@@ -45,6 +49,8 @@ export interface RawComponentDefinition {
|
|
|
45
49
|
reviewReasons?: string[];
|
|
46
50
|
needsReview?: boolean;
|
|
47
51
|
validationIssues?: ExtractionValidationIssue[];
|
|
52
|
+
/** Absolute path to the source file this component was extracted from. Null for synthesized / multi-file. */
|
|
53
|
+
sourcePath?: string;
|
|
48
54
|
}
|
|
49
55
|
export interface ComponentExtractionResult {
|
|
50
56
|
components: RawComponentDefinition[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.1-dev-build-589b615.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"svelte": "^5.56.4",
|
|
38
38
|
"ts-morph": "^27.0.2",
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
|
-
"@contentful/experience-design-system-types": "2.
|
|
40
|
+
"@contentful/experience-design-system-types": "2.12.1-dev-build-589b615.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@tsconfig/node24": "^24.0.3",
|
|
@@ -75,13 +75,13 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
|
|
|
75
75
|
**Four tool calls:**
|
|
76
76
|
|
|
77
77
|
```
|
|
78
|
-
{"tool":"classify_component","description":"<required: one-sentence description of the component>"}
|
|
78
|
+
{"tool":"classify_component","description":"<required: one-sentence description of the component>","rationale":{"description":"<why this component is classified this way>","props":"<why these props were chosen / excluded>","slots":"<why these slots were chosen / excluded>"}}
|
|
79
79
|
|
|
80
|
-
{"tool":"classify_prop","prop":"<propName>","cdf_type":"<type>","cdf_category":"<category>","required":<bool>,"description":"<reason>","values":["a","b"],"token_kind":"color","default":"<value>"}
|
|
80
|
+
{"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>"}
|
|
81
81
|
|
|
82
82
|
{"tool":"exclude_prop","prop":"<propName>","reason":"<why excluded>"}
|
|
83
83
|
|
|
84
|
-
{"tool":"classify_slot","slot":"<slotName>","required":<bool>,"allowed_components":["ComponentName"],"description":"<
|
|
84
|
+
{"tool":"classify_slot","slot":"<slotName>","required":<bool>,"allowed_components":["ComponentName"],"description":"<short customer-facing description>","rationale":"<why this slot was kept / its role>"}
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
**Rules:**
|
|
@@ -92,7 +92,21 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
|
|
|
92
92
|
- `values` is required for `cdf_type: "enum"` — must be a non-empty string array.
|
|
93
93
|
- `token_kind` is required for `cdf_type: "token"` — must be a DTCG `$type` string, e.g. `"color"`.
|
|
94
94
|
- `required` must be a JSON boolean (`true`/`false`), not a string.
|
|
95
|
-
- `description` on `classify_prop`
|
|
95
|
+
- `description` on `classify_prop` is customer-facing — keep it short and subject to the description content rules below.
|
|
96
|
+
- `reason` on `classify_prop` is **required** and is your internal rationale — shown to the developer reviewing the import, never to end-users. Use it to explain your reasoning in detail. The customer-facing description content rules below apply to `description` only, not to `reason`.
|
|
97
|
+
- `rationale` on `classify_component` is **REQUIRED**. It is an object with three REQUIRED string sub-fields:
|
|
98
|
+
- `rationale.description` — why this component is classified the way it is (its purpose, where it fits in the design system, atom/molecule/organism reasoning). **Subject to the same "Description content rules" as the `description` field — no internal initiative names, no `INTEG-*`, no `EDSI`/`DSI`/`M1`/`M2`/wave/phase references.**
|
|
99
|
+
- `rationale.props` — operator-facing explanation of which props you accepted vs excluded and why. Audience is the developer reviewing the import (not the customer), so you may discuss types, framework internals, and category corrections in technical terms. Do not include internal initiative names.
|
|
100
|
+
- `rationale.slots` — operator-facing explanation of which slots you kept vs collapsed and why. Same audience and rules as `rationale.props`.
|
|
101
|
+
- All three sub-fields are required strings, minimum one sentence each. Never emit an empty string. If the component has zero slots, `rationale.slots` should state that explicitly (e.g. `"No slots — the component renders no injectable regions."`).
|
|
102
|
+
- `rationale` on `classify_slot` is **REQUIRED**. It is a single string explaining why this slot exists and what role it plays in the component (operator-facing; minimum one sentence). Same audience and rules as `rationale.props` / `rationale.slots` above.
|
|
103
|
+
|
|
104
|
+
**Description content rules (CRITICAL — applies to every `description` field on `classify_component`, `classify_prop`, and `classify_slot`, AND to `rationale.description` on `classify_component`):**
|
|
105
|
+
|
|
106
|
+
- Write **customer-facing technical descriptions**. The audience is a third-party developer or content editor configuring this component in Contentful — not a Contentful engineer.
|
|
107
|
+
- **Never** reference internal Contentful initiatives, project code names, sprint or roadmap labels, product development phases, or implementation milestones. This includes (non-exhaustive) terms like `P1`, `P2`, `P3`, `M1`, `M2`, `EXT-*`, `INTEG-*`, `DSI`, `EDSI`, `CDF compliance`, `wave 1`, `wave 2`, `phase 1`, `phase 2`, "for compliance with…", "to support the … initiative", or any internal-sounding rationale.
|
|
108
|
+
- Do not invent rationale. If you do not know **why** a prop exists, describe **what** it does (its observable effect on the component) — never guess at organizational context.
|
|
109
|
+
- Descriptions should explain WHAT the prop/component does in terms a developer reading the public component catalog would understand. Stick to behavior, appearance, and configuration semantics.
|
|
96
110
|
|
|
97
111
|
---
|
|
98
112
|
|
|
@@ -292,7 +306,7 @@ Input:
|
|
|
292
306
|
Output:
|
|
293
307
|
```
|
|
294
308
|
Starting Button classification — 5 props, 1 slot
|
|
295
|
-
{"tool":"classify_component","description":"Primary action button with variant and state support"}
|
|
309
|
+
{"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."}}
|
|
296
310
|
label is a required string content prop
|
|
297
311
|
{"tool":"classify_prop","prop":"label","cdf_type":"string","cdf_category":"content","required":true,"description":"Button label text"}
|
|
298
312
|
variant is a string union — enum type, category design
|
|
@@ -304,7 +318,38 @@ onClick is an event handler — framework internal
|
|
|
304
318
|
className is a DOM pass-through — developers wire CSS, marketers never set this
|
|
305
319
|
{"tool":"exclude_prop","prop":"className","reason":"DOM pass-through — not a marketer-configurable surface"}
|
|
306
320
|
icon slot is clearly optional (decorative leading icon)
|
|
307
|
-
{"tool":"classify_slot","slot":"icon","required":false,"description":"Optional leading icon"}
|
|
321
|
+
{"tool":"classify_slot","slot":"icon","required":false,"description":"Optional leading icon","rationale":"Icon is a decorative leading glyph — optional because the button reads cleanly without it, but kept as a slot so marketers can inject a brand-specific icon component when desired."}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Component with multiple slots (Card)
|
|
325
|
+
|
|
326
|
+
Input:
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"name": "Card",
|
|
330
|
+
"props": [
|
|
331
|
+
{"name":"title","type":"string","category":"content","required":true},
|
|
332
|
+
{"name":"elevation","type":"'flat'|'raised'|'floating'","category":"design","defaultValue":"'raised'"}
|
|
333
|
+
],
|
|
334
|
+
"slots": [
|
|
335
|
+
{"name":"body","description":"Main card body"},
|
|
336
|
+
{"name":"footer","description":"Optional footer area"}
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Output:
|
|
342
|
+
```
|
|
343
|
+
Starting Card classification — 2 props, 2 slots
|
|
344
|
+
{"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."}}
|
|
345
|
+
title is a required content string
|
|
346
|
+
{"tool":"classify_prop","prop":"title","cdf_type":"string","cdf_category":"content","required":true,"description":"Card title text"}
|
|
347
|
+
elevation is a finite visual variant — enum, design
|
|
348
|
+
{"tool":"classify_prop","prop":"elevation","cdf_type":"enum","cdf_category":"design","required":false,"values":["flat","raised","floating"],"default":"raised","description":"Visual depth variant"}
|
|
349
|
+
body is the primary content region — required
|
|
350
|
+
{"tool":"classify_slot","slot":"body","required":true,"description":"Main card body content","rationale":"Body is the primary content region of the card. Required because a card with no body renders an empty container, which is never a useful editor state."}
|
|
351
|
+
footer is supplementary and optional
|
|
352
|
+
{"tool":"classify_slot","slot":"footer","required":false,"description":"Optional footer area for actions or metadata","rationale":"Footer is a supplementary region typically used for actions or metadata. Optional because most cards do not need one and the card renders correctly without it."}
|
|
308
353
|
```
|
|
309
354
|
|
|
310
355
|
### Named type (HeadingSize, ButtonVariant, etc.)
|
|
@@ -355,6 +400,9 @@ Before emitting any tool calls, verify:
|
|
|
355
400
|
8. Framework, DOM, accessibility, and data-* pass-through props are excluded — `className`/`classes`/`classNames`/`rootClassName`/`prefixCls`, `style`, `id`, `role`, `tabIndex`, `name` (bare HTML form attribute), `aria-*` (and bare `aria`), `data-*`, polymorphic `as`/`element`/`component`, framework theming `dt`/`pt`/`ptOptions`/`unstyled`/`sx`. Discrete positional/geometric props (`top`, `bottom`, `left`, `right`, `rotation`, etc.) ARE classified as `string` design props. Common semantic props (`icon`, `items`, `actions`, `options`, `value`, `form`, `inputId`, `componentId`) are NOT excluded — classify them per their content/design/state nature.
|
|
356
401
|
9. No `cdf_type: "link"` used — `link` is reserved and rejected by the CLI parser
|
|
357
402
|
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.
|
|
403
|
+
11. `classify_component` includes a `rationale` object with all three sub-fields (`rationale.description`, `rationale.props`, `rationale.slots`) populated as non-empty strings.
|
|
404
|
+
12. Every `classify_slot` includes a non-empty `rationale` string.
|
|
405
|
+
13. `rationale.description` follows the same "Description content rules" as `description` — no internal initiative names (`INTEG-*`, `EDSI`, `DSI`, `M1`, `M2`, wave/phase references, etc.).
|
|
358
406
|
|
|
359
407
|
After the run completes, the developer can validate the pipeline output with:
|
|
360
408
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Review the
|
|
5
|
+
Review the extracted React/Next.js component(s) 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. Output one JSON tool call per input component to stdout, named after the component. Tool calls may appear in any order.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -66,6 +66,14 @@ These are **not** valid reasons to reject a component:
|
|
|
66
66
|
|
|
67
67
|
> **Data-fetch wrapper rule**: Reject a component if it imports or calls a generated query hook, loads data, and then forwards that data into a sibling renderer. The sibling renderer is the Component Type; the data-loader wrapper is not.
|
|
68
68
|
|
|
69
|
+
> **Utility-wrapper rule**: Reject a component if **all three** of the following are true:
|
|
70
|
+
>
|
|
71
|
+
> 1. It has no props that meaningfully shape user-facing content (no text strings, headings, image URLs, links, body content, rich text, or media references).
|
|
72
|
+
> 2. The props it does have are purely structural or behavioral — e.g., `container`, `target`, `as`, `asChild`, render-prop callbacks, internal `ref` forwarding, focus/portal targets, debug toggles, or `children` only.
|
|
73
|
+
> 3. It is a utility wrapper rather than a composable content surface. Concrete examples to reject under this rule: `Portal`, `SrOnly` (screen-reader-only wrappers), `FocusTrap`, `ErrorBoundary`, `Suspense` fallbacks, debug-only wrappers, and provider-shaped components whose only job is to forward children.
|
|
74
|
+
>
|
|
75
|
+
> Use `reject_component` with a reason like `"Utility wrapper — no authorable content surface"` or `"Structural-only component — no user-shaping props"`. This rule is additive to the categories above; do **not** use it to reject a component that has even one author-shaping prop (e.g., a `label`, `title`, `text`, `href`, `src`, or `richText` prop) — those still belong as Component Types per the "one rule" above.
|
|
76
|
+
|
|
69
77
|
## Using `selectionContext`
|
|
70
78
|
|
|
71
79
|
If the input includes `selectionContext`, treat it as the only repo-level context you may use. It is already bounded to the customer-provided project files and may include:
|
|
@@ -89,7 +97,7 @@ Use that bounded context to distinguish the author-facing renderer from infrastr
|
|
|
89
97
|
|
|
90
98
|
Emit one JSON object on a single line. Lines not starting with `{` are ignored by the parser — use them freely for reasoning.
|
|
91
99
|
|
|
92
|
-
**Two tool calls — emit exactly one:**
|
|
100
|
+
**Two tool calls — emit exactly one per input component:**
|
|
93
101
|
|
|
94
102
|
```
|
|
95
103
|
{"tool":"select_component","name":"<ComponentName>","reason":"<brief reason>","confidence":<1-5>}
|
|
@@ -99,8 +107,9 @@ Emit one JSON object on a single line. Lines not starting with `{` are ignored b
|
|
|
99
107
|
|
|
100
108
|
**Rules:**
|
|
101
109
|
|
|
102
|
-
- Emit exactly one JSON object
|
|
103
|
-
-
|
|
110
|
+
- Emit exactly one JSON object per line. No multi-line JSON. No markdown fences.
|
|
111
|
+
- Emit exactly one tool call per input component. Tool calls may appear in any order.
|
|
112
|
+
- The `name` must match a component name from the input array exactly.
|
|
104
113
|
- `reason` is a brief phrase documenting your decision.
|
|
105
114
|
- `confidence` is your certainty (1–5) that the decision is correct:
|
|
106
115
|
- **5** — obvious case, no doubt (clear UI atom, or clear infrastructure with no visual output)
|