@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.6.2-dev-build-864e323.0",
3
+ "version": "2.7.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 ?? 100,
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: number;
4
+ confidence: ExtractionConfidence;
4
5
  reasons: string[];
5
6
  };
6
7
  export declare function computeExtractionScore(component: RawComponentDefinition): ExtractionScore;
7
- export declare function deriveNeedsReview(confidence: number): boolean;
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
- export function computeExtractionScore(component) {
48
- let confidence = 100;
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
- confidence -= 15;
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
- confidence -= 20;
60
+ count++;
59
61
  reasons.push(`opaque-type:${prop.name}`);
60
- break; // only penalise once per component
62
+ break;
61
63
  }
62
- // Wide primitive union — hard for the AI agent to classify meaningfully
63
64
  if (isWidePrimitiveUnion(prop.type)) {
64
- confidence -= 10;
65
+ count++;
65
66
  reasons.push(`wide-union:${prop.name}`);
66
- break; // only penalise once
67
+ break;
67
68
  }
68
- // Non-obvious prop name with no description
69
69
  if (!prop.description && !OBVIOUS_PROP_NAMES.has(prop.name)) {
70
- confidence -= 10;
70
+ count++;
71
71
  reasons.push('props-missing-description');
72
- break; // only penalise once
72
+ break;
73
73
  }
74
74
  }
75
- // High prop count possible DOM inflation near-miss or overly broad extraction
76
- if (component.props.length > 50) {
77
- confidence -= 20;
78
- reasons.push(`high-prop-count:${component.props.length}`);
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: Math.max(0, Math.min(100, confidence)),
82
- reasons: [...new Set(reasons)], // deduplicate
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 < 70;
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 ?? 100,
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
- return a.extractionConfidence - b.extractionConfidence;
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
- // Strip internal scoring fields — they're metadata, not part of the component definition
38
- const stripScoring = ({ extractionConfidence: _c, reviewReasons: _r, needsReview: _n, ...rest }) => rest;
39
- const originalJson = JSON.stringify(stripScoring(component.originalProposal), null, 2);
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 >= 80 ? 'white' : conf >= 50 ? 'yellow' : 'red';
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 ?? 100,
7
+ extractionConfidence: component.originalProposal.extractionConfidence ?? null,
8
8
  needsReview: component.originalProposal.needsReview ?? false,
9
9
  })),
10
10
  };
@@ -9,7 +9,7 @@ export type AnalyzeViewResult = {
9
9
  propCount: number;
10
10
  slotCount: number;
11
11
  warnings: string[];
12
- extractionConfidence: number;
12
+ extractionConfidence: number | null;
13
13
  needsReview: boolean;
14
14
  }>;
15
15
  totalWarnings: number;
@@ -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 ?? 100;
54
- const confColor = component.needsReview ? 'red' : conf >= 80 ? 'white' : conf >= 50 ? 'yellow' : 'red';
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 >= 0 && rec.confidence <= 100) {
34
- call.confidence = rec.confidence;
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: 100,
188
+ extractionConfidence: null,
189
189
  needsReview: false,
190
190
  }));
191
191
  const longestName = components.reduce((m, c) => Math.max(m, c.key.length), 0);
@@ -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 NOT NULL DEFAULT 100,
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 NOT NULL DEFAULT 100');
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 ?? 100, JSON.stringify(comp.reviewReasons ?? []), comp.needsReview ? 1 : 0);
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 ?? 100,
516
+ extractionConfidence: c.extraction_confidence ?? null,
517
517
  reviewReasons: (() => {
518
518
  try {
519
519
  return JSON.parse(c.review_reasons ?? '[]');
@@ -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
@@ -1 +1,4 @@
1
- export {};
1
+ /** Strip internal scoring fields before serialising a RawComponentDefinition for display or editing. */
2
+ export function stripScoringFields({ extractionConfidence: _c, reviewReasons: _r, needsReview: _n, ...rest }) {
3
+ return rest;
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.6.2-dev-build-864e323.0",
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.6.2-dev-build-864e323.0"
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":<0-100>}
73
+ {"tool":"select_component","name":"<ComponentName>","reason":"<brief reason>","confidence":<1-5>}
74
74
 
75
- {"tool":"reject_component","name":"<ComponentName>","reason":"<brief reason>","confidence":<0-100>}
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 (0100) that the decision is correct. Use 90–100 for obvious cases (clear UI atom or obvious infrastructure), 60–80 for borderline cases (few props, ambiguous purpose), and below 60 when you're genuinely unsure.
82
+ - `confidence` is your certainty (15) 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
  ---