@contentful/experience-design-system-cli 2.6.2-dev-build-864e323.0 → 2.7.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/analyze/command.js +1 -1
- package/dist/src/analyze/extract/scoring.d.ts +3 -2
- package/dist/src/analyze/extract/scoring.js +39 -22
- package/dist/src/analyze/select/tui/App.js +7 -6
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +6 -7
- package/dist/src/analyze/select/types.d.ts +1 -1
- package/dist/src/analyze/select/types.js +1 -1
- package/dist/src/analyze/tui/AnalyzeView.d.ts +1 -1
- package/dist/src/analyze/tui/AnalyzeView.js +3 -3
- package/dist/src/generate/agent-runner.js +7 -2
- package/dist/src/import/tui/steps/GenerateReviewStep.js +1 -1
- package/dist/src/session/db.js +4 -4
- package/dist/src/types.d.ts +3 -1
- package/dist/src/types.js +4 -1
- package/package.json +2 -2
- package/skills/select-components.md +8 -3
package/dist/package.json
CHANGED
|
@@ -153,7 +153,7 @@ export function registerAnalyzeCommand(program) {
|
|
|
153
153
|
propCount: c.props.length,
|
|
154
154
|
slotCount: c.slots.length,
|
|
155
155
|
warnings: allWarnings.filter((w) => w.startsWith(c.name + ':')),
|
|
156
|
-
extractionConfidence: c.extractionConfidence ??
|
|
156
|
+
extractionConfidence: c.extractionConfidence ?? null,
|
|
157
157
|
needsReview: c.needsReview ?? false,
|
|
158
158
|
})),
|
|
159
159
|
totalWarnings: allWarnings.length,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { RawComponentDefinition } from '../../types.js';
|
|
2
|
+
export type ExtractionConfidence = 1 | 2 | 3 | 4 | 5;
|
|
2
3
|
export type ExtractionScore = {
|
|
3
|
-
confidence:
|
|
4
|
+
confidence: ExtractionConfidence;
|
|
4
5
|
reasons: string[];
|
|
5
6
|
};
|
|
6
7
|
export declare function computeExtractionScore(component: RawComponentDefinition): ExtractionScore;
|
|
7
|
-
export declare function deriveNeedsReview(confidence:
|
|
8
|
+
export declare function deriveNeedsReview(confidence: ExtractionConfidence): boolean;
|
|
@@ -41,47 +41,64 @@ function isWidePrimitiveUnion(type) {
|
|
|
41
41
|
const nullability = new Set(['null', 'undefined']);
|
|
42
42
|
const baseCount = parts.filter((p) => basePrimitives.has(p)).length;
|
|
43
43
|
const nullabilityCount = parts.filter((p) => nullability.has(p)).length;
|
|
44
|
-
// Wide only when there are 3+ base primitives, or 2+ base primitives combined with nullability modifiers
|
|
45
44
|
return baseCount >= 3 || (baseCount >= 2 && nullabilityCount > 0 && baseCount + nullabilityCount >= 3);
|
|
46
45
|
}
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Count the number of issues found for scoring
|
|
47
|
+
function countIssues(component) {
|
|
48
|
+
let count = 0;
|
|
49
49
|
const reasons = [];
|
|
50
|
-
// No props and no slots — extractor likely missed something or component is a wrapper
|
|
51
50
|
if (component.props.length === 0 && component.slots.length === 0) {
|
|
52
|
-
|
|
51
|
+
count++;
|
|
53
52
|
reasons.push('no-props-or-slots');
|
|
54
53
|
}
|
|
54
|
+
if (component.props.length > 50) {
|
|
55
|
+
count++;
|
|
56
|
+
reasons.push(`high-prop-count:${component.props.length}`);
|
|
57
|
+
}
|
|
55
58
|
for (const prop of component.props) {
|
|
56
|
-
// Opaque types — extractor couldn't resolve the real type
|
|
57
59
|
if (OPAQUE_TYPES.has(prop.type.trim())) {
|
|
58
|
-
|
|
60
|
+
count++;
|
|
59
61
|
reasons.push(`opaque-type:${prop.name}`);
|
|
60
|
-
break;
|
|
62
|
+
break;
|
|
61
63
|
}
|
|
62
|
-
// Wide primitive union — hard for the AI agent to classify meaningfully
|
|
63
64
|
if (isWidePrimitiveUnion(prop.type)) {
|
|
64
|
-
|
|
65
|
+
count++;
|
|
65
66
|
reasons.push(`wide-union:${prop.name}`);
|
|
66
|
-
break;
|
|
67
|
+
break;
|
|
67
68
|
}
|
|
68
|
-
// Non-obvious prop name with no description
|
|
69
69
|
if (!prop.description && !OBVIOUS_PROP_NAMES.has(prop.name)) {
|
|
70
|
-
|
|
70
|
+
count++;
|
|
71
71
|
reasons.push('props-missing-description');
|
|
72
|
-
break;
|
|
72
|
+
break;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
return { count, reasons: [...new Set(reasons)] };
|
|
76
|
+
}
|
|
77
|
+
// Maps issue count to a 1–5 confidence scale:
|
|
78
|
+
// 0 issues → 5 (clean)
|
|
79
|
+
// 1 issue → 4 (minor concern)
|
|
80
|
+
// 2 issues → 3 (moderate concern)
|
|
81
|
+
// 3 issues → 2 (significant concern)
|
|
82
|
+
// 4+ issues → 1 (likely wrong)
|
|
83
|
+
function issueCountToConfidence(count) {
|
|
84
|
+
if (count === 0)
|
|
85
|
+
return 5;
|
|
86
|
+
if (count === 1)
|
|
87
|
+
return 4;
|
|
88
|
+
if (count === 2)
|
|
89
|
+
return 3;
|
|
90
|
+
if (count === 3)
|
|
91
|
+
return 2;
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
export function computeExtractionScore(component) {
|
|
95
|
+
const { count, reasons } = countIssues(component);
|
|
80
96
|
return {
|
|
81
|
-
confidence:
|
|
82
|
-
reasons
|
|
97
|
+
confidence: issueCountToConfidence(count),
|
|
98
|
+
reasons,
|
|
83
99
|
};
|
|
84
100
|
}
|
|
101
|
+
// Flag components scoring 2 or below for human review
|
|
85
102
|
export function deriveNeedsReview(confidence) {
|
|
86
|
-
return confidence
|
|
103
|
+
return confidence <= 2;
|
|
87
104
|
}
|
|
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
3
3
|
import { Box, Text, useStdout } from 'ink';
|
|
4
4
|
import { readFile } from 'node:fs/promises';
|
|
5
5
|
import { createReviewSessionDetail } from '../types.js';
|
|
6
|
+
import { stripScoringFields } from '../../../types.js';
|
|
6
7
|
import { TopBar } from './components/TopBar.js';
|
|
7
8
|
import { Sidebar } from './components/Sidebar.js';
|
|
8
9
|
import { ComponentDetail } from './components/ComponentDetail.js';
|
|
@@ -285,10 +286,7 @@ export function App({ sessionId, artifactsRoot, reviewRoot }) {
|
|
|
285
286
|
setEditMode(true);
|
|
286
287
|
setDraftsByComponentId((prev) => ({
|
|
287
288
|
...prev,
|
|
288
|
-
[selectedId]: (()
|
|
289
|
-
const { extractionConfidence: _c, reviewReasons: _r, needsReview: _n, ...rest } = component.editedProposal;
|
|
290
|
-
return prev[selectedId] ?? JSON.stringify(rest, null, 2);
|
|
291
|
-
})(),
|
|
289
|
+
[selectedId]: prev[selectedId] ?? JSON.stringify(stripScoringFields(component.editedProposal), null, 2),
|
|
292
290
|
}));
|
|
293
291
|
},
|
|
294
292
|
onToggleSource: () => {
|
|
@@ -339,7 +337,7 @@ export function App({ sessionId, artifactsRoot, reviewRoot }) {
|
|
|
339
337
|
name: c.name,
|
|
340
338
|
status: c.status,
|
|
341
339
|
previewAnnotation: previewAnnotations[c.name],
|
|
342
|
-
extractionConfidence: c.originalProposal.extractionConfidence ??
|
|
340
|
+
extractionConfidence: c.originalProposal.extractionConfidence ?? null,
|
|
343
341
|
needsReview: c.originalProposal.needsReview ?? false,
|
|
344
342
|
}))
|
|
345
343
|
.sort((a, b) => {
|
|
@@ -347,7 +345,10 @@ export function App({ sessionId, artifactsRoot, reviewRoot }) {
|
|
|
347
345
|
const bFlagged = b.needsReview && b.status === 'needs-review' ? 0 : 1;
|
|
348
346
|
if (aFlagged !== bFlagged)
|
|
349
347
|
return aFlagged - bFlagged;
|
|
350
|
-
|
|
348
|
+
// null (unscored) sorts last; lower numeric score sorts first (most concerning)
|
|
349
|
+
const aConf = a.extractionConfidence ?? 6;
|
|
350
|
+
const bConf = b.extractionConfidence ?? 6;
|
|
351
|
+
return aConf - bConf;
|
|
351
352
|
}), [session?.components, previewAnnotations]);
|
|
352
353
|
// Stable ID order — kept in a ref for use inside keymap handlers
|
|
353
354
|
const sortedIds = useMemo(() => sessionSummary.map((c) => c.id), [sessionSummary]);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { stripScoringFields } from '../../../../types.js';
|
|
3
4
|
import { JsonPanel } from './JsonPanel.js';
|
|
4
5
|
import { FieldEditor } from './FieldEditor.js';
|
|
5
6
|
import { SourcePanel } from './SourcePanel.js';
|
|
@@ -34,14 +35,12 @@ export function ComponentDetail({ component, sourceCode, draftValue, editMode, s
|
|
|
34
35
|
editWidth = availableWidth - originalWidth - 3;
|
|
35
36
|
sourceWidth = 0;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const editedJson = JSON.stringify(stripScoring(component.editedProposal), null, 2);
|
|
41
|
-
const conf = component.originalProposal.extractionConfidence ?? 100;
|
|
38
|
+
const originalJson = JSON.stringify(stripScoringFields(component.originalProposal), null, 2);
|
|
39
|
+
const editedJson = JSON.stringify(stripScoringFields(component.editedProposal), null, 2);
|
|
40
|
+
const conf = component.originalProposal.extractionConfidence ?? null;
|
|
42
41
|
const nr = component.originalProposal.needsReview ?? false;
|
|
43
|
-
const confColor = nr ? 'red' : conf >=
|
|
44
|
-
const confLabel = (nr ? '⚑ ' : '') + 'confidence: ' + String(conf);
|
|
42
|
+
const confColor = conf === null ? 'gray' : nr ? 'red' : conf >= 4 ? 'white' : conf >= 3 ? 'yellow' : 'red';
|
|
43
|
+
const confLabel = conf === null ? 'confidence: —' : (nr ? '⚑ ' : '') + 'confidence: ' + String(conf) + '/5';
|
|
45
44
|
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: component.name }), (() => {
|
|
46
45
|
const ann = annotationLabel(previewAnnotation);
|
|
47
46
|
return ann ? _jsx(Text, { color: ann.color, children: ann.text }) : null;
|
|
@@ -22,7 +22,7 @@ export type ReviewComponentSummary = {
|
|
|
22
22
|
name: string;
|
|
23
23
|
status: ReviewComponentStatus;
|
|
24
24
|
previewAnnotation?: PreviewAnnotation;
|
|
25
|
-
extractionConfidence: number;
|
|
25
|
+
extractionConfidence: number | null;
|
|
26
26
|
needsReview: boolean;
|
|
27
27
|
};
|
|
28
28
|
export type ReviewSessionSnapshot = {
|
|
@@ -4,7 +4,7 @@ export function createReviewSessionSummary(session) {
|
|
|
4
4
|
id: component.id,
|
|
5
5
|
name: component.name,
|
|
6
6
|
status: component.status,
|
|
7
|
-
extractionConfidence: component.originalProposal.extractionConfidence ??
|
|
7
|
+
extractionConfidence: component.originalProposal.extractionConfidence ?? null,
|
|
8
8
|
needsReview: component.originalProposal.needsReview ?? false,
|
|
9
9
|
})),
|
|
10
10
|
};
|
|
@@ -50,9 +50,9 @@ export function AnalyzeView({ result, onExit }) {
|
|
|
50
50
|
result.components.length +
|
|
51
51
|
')'
|
|
52
52
|
: '') }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { children: " " }), showScrollUp && _jsx(Text, { dimColor: true, children: " \u25B2 scroll up" }), visible.map((component) => {
|
|
53
|
-
const conf = component.extractionConfidence
|
|
54
|
-
const confColor = component.needsReview ? 'red' : conf >=
|
|
55
|
-
const confLabel = (component.needsReview ? '⚑ ' : '') + String(conf);
|
|
53
|
+
const conf = component.extractionConfidence;
|
|
54
|
+
const confColor = conf === null ? 'gray' : component.needsReview ? 'red' : conf >= 4 ? 'white' : conf >= 3 ? 'yellow' : 'red';
|
|
55
|
+
const confLabel = conf === null ? '—' : (component.needsReview ? '⚑ ' : '') + String(conf);
|
|
56
56
|
return (_jsxs(Box, { children: [component.warnings.length > 0 && _jsx(Text, { color: "yellow", children: "\u26A0 " }), component.warnings.length === 0 && _jsx(Text, { children: " " }), _jsx(Text, { children: truncateName(component.name).padEnd(20) }), _jsx(Text, { dimColor: true, children: component.framework.padEnd(10) }), _jsx(Text, { children: (component.propCount + ' props').padEnd(10) }), _jsx(Text, { children: (component.slotCount + ' ' + (component.slotCount === 1 ? 'slot' : 'slots')).padEnd(8) }), _jsx(Text, { color: confColor, children: confLabel }), component.warnings.length > 0 && (_jsx(Text, { color: "yellow", children: ' ⚠ ' + component.warnings.length + ' warning' + (component.warnings.length === 1 ? '' : 's') }))] }, component.name));
|
|
57
57
|
}), showScrollDown && _jsx(Text, { dimColor: true, children: " \u25BC scroll down" }), result.totalWarnings > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { bold: true, color: "yellow", children: 'Warnings (' + result.totalWarnings + ')' }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { children: " " }), result.components
|
|
58
58
|
.filter((c) => c.warnings.length > 0)
|
|
@@ -30,8 +30,13 @@ export function parseSelectToolCallLines(stdout) {
|
|
|
30
30
|
};
|
|
31
31
|
if (typeof rec.reason === 'string')
|
|
32
32
|
call.reason = rec.reason;
|
|
33
|
-
if (typeof rec.confidence === 'number' && rec.confidence >=
|
|
34
|
-
call.
|
|
33
|
+
if (typeof rec.confidence === 'number' && rec.confidence >= 1 && rec.confidence <= 5) {
|
|
34
|
+
if (call.tool === 'select_component') {
|
|
35
|
+
call.confidence = rec.confidence;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
call.confidence = rec.confidence;
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
calls.push(call);
|
|
37
42
|
}
|
|
@@ -185,7 +185,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }) {
|
|
|
185
185
|
id: c.key,
|
|
186
186
|
name: c.key,
|
|
187
187
|
status: c.status,
|
|
188
|
-
extractionConfidence:
|
|
188
|
+
extractionConfidence: null,
|
|
189
189
|
needsReview: false,
|
|
190
190
|
}));
|
|
191
191
|
const longestName = components.reduce((m, c) => Math.max(m, c.key.length), 0);
|
package/dist/src/session/db.js
CHANGED
|
@@ -43,7 +43,7 @@ CREATE TABLE IF NOT EXISTS raw_components (
|
|
|
43
43
|
status TEXT NOT NULL DEFAULT 'extracted',
|
|
44
44
|
cdf_schema TEXT,
|
|
45
45
|
description TEXT,
|
|
46
|
-
extraction_confidence INTEGER
|
|
46
|
+
extraction_confidence INTEGER,
|
|
47
47
|
review_reasons TEXT NOT NULL DEFAULT '[]',
|
|
48
48
|
needs_review INTEGER NOT NULL DEFAULT 0,
|
|
49
49
|
PRIMARY KEY (session_id, component_id)
|
|
@@ -171,7 +171,7 @@ function applyDbMigrations(db) {
|
|
|
171
171
|
const rawCompCols = db.prepare('PRAGMA table_info(raw_components)').all();
|
|
172
172
|
const rawCompColNames = new Set(rawCompCols.map((c) => c.name));
|
|
173
173
|
if (!rawCompColNames.has('extraction_confidence')) {
|
|
174
|
-
db.exec('ALTER TABLE raw_components ADD COLUMN extraction_confidence INTEGER
|
|
174
|
+
db.exec('ALTER TABLE raw_components ADD COLUMN extraction_confidence INTEGER');
|
|
175
175
|
}
|
|
176
176
|
if (!rawCompColNames.has('review_reasons')) {
|
|
177
177
|
db.exec("ALTER TABLE raw_components ADD COLUMN review_reasons TEXT NOT NULL DEFAULT '[]'");
|
|
@@ -402,7 +402,7 @@ export function storeRawComponents(db, sessionId, components, options) {
|
|
|
402
402
|
db.prepare('DELETE FROM raw_components WHERE session_id = ?').run(sessionId);
|
|
403
403
|
for (const comp of components) {
|
|
404
404
|
const componentId = deriveComponentId(comp.name, comp.source);
|
|
405
|
-
insertComp.run(sessionId, componentId, comp.name, comp.source, comp.framework, now, comp.extractionConfidence ??
|
|
405
|
+
insertComp.run(sessionId, componentId, comp.name, comp.source, comp.framework, now, comp.extractionConfidence ?? null, JSON.stringify(comp.reviewReasons ?? []), comp.needsReview ? 1 : 0);
|
|
406
406
|
for (let i = 0; i < comp.props.length; i++) {
|
|
407
407
|
const prop = comp.props[i];
|
|
408
408
|
insertProp.run(sessionId, componentId, prop.name, prop.type, prop.required ? 1 : 0, prop.category ?? null, prop.defaultValue ?? null, prop.description ?? null, prop.tokenReference ?? null, i);
|
|
@@ -513,7 +513,7 @@ export function loadRawComponents(db, sessionId, allowedNames) {
|
|
|
513
513
|
name: c.name,
|
|
514
514
|
source: c.source,
|
|
515
515
|
framework: c.framework,
|
|
516
|
-
extractionConfidence: c.extraction_confidence ??
|
|
516
|
+
extractionConfidence: c.extraction_confidence ?? null,
|
|
517
517
|
reviewReasons: (() => {
|
|
518
518
|
try {
|
|
519
519
|
return JSON.parse(c.review_reasons ?? '[]');
|
package/dist/src/types.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface RawComponentDefinition {
|
|
|
34
34
|
* non-authorable context-provider components.
|
|
35
35
|
*/
|
|
36
36
|
usesCreateContext?: boolean;
|
|
37
|
-
extractionConfidence?: number;
|
|
37
|
+
extractionConfidence?: number | null;
|
|
38
38
|
reviewReasons?: string[];
|
|
39
39
|
needsReview?: boolean;
|
|
40
40
|
}
|
|
@@ -55,3 +55,5 @@ export interface TokenExtractor {
|
|
|
55
55
|
name: string;
|
|
56
56
|
extract(projectRoot: string): Promise<RawTokenDefinition[]>;
|
|
57
57
|
}
|
|
58
|
+
/** Strip internal scoring fields before serialising a RawComponentDefinition for display or editing. */
|
|
59
|
+
export declare function stripScoringFields({ extractionConfidence: _c, reviewReasons: _r, needsReview: _n, ...rest }: RawComponentDefinition): Omit<RawComponentDefinition, 'extractionConfidence' | 'reviewReasons' | 'needsReview'>;
|
package/dist/src/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"react-dom": "^18.3.1",
|
|
37
37
|
"ts-morph": "^27.0.2",
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
|
-
"@contentful/experience-design-system-types": "2.
|
|
39
|
+
"@contentful/experience-design-system-types": "2.7.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@tsconfig/node24": "^24.0.3",
|
|
@@ -70,16 +70,21 @@ Emit one JSON object on a single line. Lines not starting with `{` are ignored b
|
|
|
70
70
|
**Two tool calls — emit exactly one:**
|
|
71
71
|
|
|
72
72
|
```
|
|
73
|
-
{"tool":"select_component","name":"<ComponentName>","reason":"<brief reason>","confidence":<
|
|
73
|
+
{"tool":"select_component","name":"<ComponentName>","reason":"<brief reason>","confidence":<1-5>}
|
|
74
74
|
|
|
75
|
-
{"tool":"reject_component","name":"<ComponentName>","reason":"<brief reason>","confidence":<
|
|
75
|
+
{"tool":"reject_component","name":"<ComponentName>","reason":"<brief reason>","confidence":<1-5>}
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
**Rules:**
|
|
79
79
|
- Emit exactly one JSON object, on one line. No multi-line JSON. No markdown fences.
|
|
80
80
|
- The `name` must match the component name in the input.
|
|
81
81
|
- `reason` is a brief phrase documenting your decision.
|
|
82
|
-
- `confidence` is your certainty (
|
|
82
|
+
- `confidence` is your certainty (1–5) that the decision is correct:
|
|
83
|
+
- **5** — obvious case, no doubt (clear UI atom, or clear infrastructure with no visual output)
|
|
84
|
+
- **4** — likely correct, minor ambiguity
|
|
85
|
+
- **3** — uncertain, borderline component (few props, ambiguous purpose, could go either way)
|
|
86
|
+
- **2** — low confidence, guessing
|
|
87
|
+
- **1** — very unsure, human review strongly recommended
|
|
83
88
|
- Emit prose lines (not starting with `{`) to log your reasoning before the final tool call.
|
|
84
89
|
|
|
85
90
|
---
|