@contentful/experience-design-system-cli 2.12.1-dev-build-589b615.0 → 2.12.1-dev-build-bae15c7.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.12.1-dev-build-589b615.0",
3
+ "version": "2.12.1-dev-build-bae15c7.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -167,6 +167,7 @@ async function selectBatch(agent, model, batch, total, verbose, skillPathOverrid
167
167
  const call = calls.find((toolCall) => toolCall.name === component.name);
168
168
  if (!call) {
169
169
  process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.yellow('no tool call')}\n`);
170
+ emitProgress(item, 'failed', 'no-tool-call-from-agent');
170
171
  results.push({
171
172
  componentKey: componentKey(component),
172
173
  componentName: component.name,
@@ -19,7 +19,7 @@ export declare function buildSelectAgentArgs(opts: {
19
19
  export type AutoFilterProgress = {
20
20
  n: number;
21
21
  total: number;
22
- decision: 'accepted' | 'rejected';
22
+ decision: 'accepted' | 'rejected' | 'failed';
23
23
  name: string;
24
24
  reason: string;
25
25
  };
@@ -40,6 +40,7 @@ import { readTokensFromPath, hasBreakingChangesWithImpact } from '../../apply/ma
40
40
  import { buildManifest } from '@contentful/experience-design-system-types';
41
41
  import { openPipelineDb, loadCDFComponents, loadScopeComponents, seedCDFFromPreviewResponse, seedDefaultsFromChangedItems, backfillUnclassifiedProps, } from '../../session/db.js';
42
42
  import { ScopeGateHost } from './scope-gate-host.js';
43
+ import { mergeAiDecisions } from './merge-ai-decisions.js';
43
44
  import { FinalReviewHost } from './final-review-host.js';
44
45
  import { runScopeGate } from './runScopeGate.js';
45
46
  import { buildAutoFilterErrorTail } from './auto-filter-error.js';
@@ -83,7 +84,7 @@ export function parseAutoFilterProgressLine(line) {
83
84
  const counterMatch = /^(\d+)\/(\d+)$/.exec(counter);
84
85
  if (!counterMatch)
85
86
  return null;
86
- if (decision !== 'accepted' && decision !== 'rejected')
87
+ if (decision !== 'accepted' && decision !== 'rejected' && decision !== 'failed')
87
88
  return null;
88
89
  if (!name)
89
90
  return null;
@@ -1399,6 +1400,11 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
1399
1400
  finally {
1400
1401
  db.close();
1401
1402
  }
1403
+ // INTEG-4318: overlay the streamed auto-filter decisions (from
1404
+ // stderr progress lines) onto DB-loaded rows so 'failed' components
1405
+ // (LLM omitted a tool call in a batch) surface in the scope-gate
1406
+ // instead of silently defaulting to included.
1407
+ components = mergeAiDecisions(components, state.aiDecisions);
1402
1408
  return (_jsx(ScopeGateHost, { components: components, autoAccept: autoAcceptScope, aiFilterStatus: state.aiFilterStatus, aiFilterProgress: state.aiFilterProgress, aiFilterError: state.aiFilterError, onCancelAutoFilter: cancelAutoFilter, onConfirm: (decisions) => {
1403
1409
  void runScopeGate({
1404
1410
  sessionId,
@@ -0,0 +1,5 @@
1
+ import type { ScopeComponent } from './steps/ScopeGateStep.js';
2
+ export declare function mergeAiDecisions(components: ReadonlyArray<ScopeComponent>, aiDecisions: Record<string, {
3
+ decision: 'accepted' | 'rejected' | 'failed';
4
+ reason: string;
5
+ }>): ScopeComponent[];
@@ -0,0 +1,22 @@
1
+ // INTEG-4318: overlay streamed auto-filter decisions (from the select-agent
2
+ // child's stderr progress lines) onto components loaded from raw_components.
3
+ // Only fills gaps where the DB has no decision — the DB row is authoritative
4
+ // once the child persists its status. This is what lets the scope-gate see
5
+ // a 'failed' status for components where the LLM omitted a tool call in a
6
+ // batch (the select-agent does not persist a 'failed' status to the DB).
7
+ export function mergeAiDecisions(components, aiDecisions) {
8
+ return components.map((component) => {
9
+ if (component.aiDecision !== null && component.aiDecision !== undefined) {
10
+ return component;
11
+ }
12
+ const streamed = aiDecisions[component.name];
13
+ if (!streamed) {
14
+ return component;
15
+ }
16
+ return {
17
+ ...component,
18
+ aiDecision: streamed.decision,
19
+ aiReason: streamed.reason,
20
+ };
21
+ });
22
+ }
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  export type ScopeComponent = {
3
3
  name: string;
4
4
  componentId: string;
5
- aiDecision?: 'accepted' | 'rejected' | null;
5
+ aiDecision?: 'accepted' | 'rejected' | 'failed' | null;
6
6
  aiReason?: string | null;
7
7
  };
8
8
  export type ScopeGateStepProps = {
@@ -20,7 +20,11 @@ function truncateReason(reason) {
20
20
  return reason.slice(0, REASON_DISPLAY_MAX - 1).trimEnd() + '…';
21
21
  }
22
22
  function isAiFlagged(row) {
23
- return row.aiDecision === 'rejected';
23
+ // INTEG-4318: `failed` means the LLM omitted a decision for this component
24
+ // (e.g. batch under-emit). Surface these in the AI-recommended-exclusions
25
+ // section so the operator sees them and can override — silent inclusion was
26
+ // the bug.
27
+ return row.aiDecision === 'rejected' || row.aiDecision === 'failed';
24
28
  }
25
29
  export function ScopeGateStep({ components, onConfirm, onQuit, aiFilterStatus = 'idle', aiFilterProgress = null, aiFilterError = null, onCancelAutoFilter, }) {
26
30
  // Pilot-2026-06-25: scope-gate UX overhaul — single unified list.
@@ -60,7 +64,10 @@ export function ScopeGateStep({ components, onConfirm, onQuit, aiFilterStatus =
60
64
  return false;
61
65
  if (userUnExcluded.has(row.name))
62
66
  return true;
63
- return row.aiDecision !== 'rejected';
67
+ // INTEG-4318: exclude on 'rejected' AND 'failed'. Missing/null aiDecision
68
+ // (auto-filter not run, or component never seen) still defaults to
69
+ // included so --no-auto-filter and skip-credentials flows are unchanged.
70
+ return row.aiDecision !== 'rejected' && row.aiDecision !== 'failed';
64
71
  };
65
72
  const partition = () => {
66
73
  const accepted = [];
@@ -216,7 +223,7 @@ export function ScopeGateStep({ components, onConfirm, onQuit, aiFilterStatus =
216
223
  const showCancelledBanner = aiFilterStatus === 'cancelled';
217
224
  const showFailedBanner = aiFilterStatus === 'failed';
218
225
  const allRejected = aiFilterStatus === 'complete' && total > 0 && flatList.every((c) => !isIncluded(c));
219
- return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsx(Text, { color: "green", children: "\u2713 Extraction complete" }), _jsxs(Text, { dimColor: true, children: ["Found ", total, " component", total === 1 ? '' : 's', ". Pick which ones to import. Generation runs only on the included set."] }), showRunningHeader && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsxs(Text, { color: "cyan", children: ["[AI filtering (", aiFilterProgress.done, "/", aiFilterProgress.total, ")\u2026] ", _jsx(Text, { dimColor: true, children: "[q] cancels" })] }) })), showCancelledBanner && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["AI auto-filter cancelled", aiFilterProgress ? ` at ${aiFilterProgress.done}/${aiFilterProgress.total}` : '', ". Review remaining manually."] }) })), showFailedBanner && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["AI auto-filter failed: ", aiFilterError ?? 'unknown error', ". Continuing without AI suggestions."] }) })), reasonPanelOpen && flatList[cursor]?.aiDecision === 'rejected' && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: `AI rejection reason: ${flatList[cursor].name}` }), _jsx(Text, { children: flatList[cursor].aiReason ?? '<no reason given>' }), _jsx(Text, { dimColor: true, children: "[s] close \u00B7 [Esc] close" })] })), allRejected ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "AI excluded all components \u2014 press [a] to override or [q] to quit" }) })) : (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [above > 0 && _jsxs(Text, { dimColor: true, children: ["\u2191 ", above, " above"] }), visible.map((c, vi) => {
226
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsx(Text, { color: "green", children: "\u2713 Extraction complete" }), _jsxs(Text, { dimColor: true, children: ["Found ", total, " component", total === 1 ? '' : 's', ". Pick which ones to import. Generation runs only on the included set."] }), showRunningHeader && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsxs(Text, { color: "cyan", children: ["[AI filtering (", aiFilterProgress.done, "/", aiFilterProgress.total, ")\u2026] ", _jsx(Text, { dimColor: true, children: "[q] cancels" })] }) })), showCancelledBanner && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["AI auto-filter cancelled", aiFilterProgress ? ` at ${aiFilterProgress.done}/${aiFilterProgress.total}` : '', ". Review remaining manually."] }) })), showFailedBanner && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["AI auto-filter failed: ", aiFilterError ?? 'unknown error', ". Continuing without AI suggestions."] }) })), reasonPanelOpen && flatList[cursor] !== undefined && isAiFlagged(flatList[cursor]) && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: `AI rejection reason: ${flatList[cursor].name}` }), _jsx(Text, { children: flatList[cursor].aiReason ?? '<no reason given>' }), _jsx(Text, { dimColor: true, children: "[s] close \u00B7 [Esc] close" })] })), allRejected ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "AI excluded all components \u2014 press [a] to override or [q] to quit" }) })) : (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [above > 0 && _jsxs(Text, { dimColor: true, children: ["\u2191 ", above, " above"] }), visible.map((c, vi) => {
220
227
  const i = vi + scrollOffset;
221
228
  const isCursor = i === cursor;
222
229
  const included = isIncluded(c);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.12.1-dev-build-589b615.0",
3
+ "version": "2.12.1-dev-build-bae15c7.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.12.1-dev-build-589b615.0"
40
+ "@contentful/experience-design-system-types": "2.12.1-dev-build-bae15c7.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@tsconfig/node24": "^24.0.3",