@contentful/experience-design-system-cli 2.12.1-dev-build-589b615.0 → 2.12.1-dev-build-0b7de73.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/select-agent/command.js +1 -0
- package/dist/src/import/command.js +24 -37
- package/dist/src/import/picker-dispatch.d.ts +18 -0
- package/dist/src/import/picker-dispatch.js +29 -0
- package/dist/src/import/tui/WizardApp.d.ts +1 -1
- package/dist/src/import/tui/WizardApp.js +7 -1
- package/dist/src/import/tui/merge-ai-decisions.d.ts +5 -0
- package/dist/src/import/tui/merge-ai-decisions.js +22 -0
- package/dist/src/import/tui/steps/ScopeGateStep.d.ts +1 -1
- package/dist/src/import/tui/steps/ScopeGateStep.js +10 -3
- package/package.json +2 -2
package/dist/package.json
CHANGED
|
@@ -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,
|
|
@@ -7,6 +7,7 @@ import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../host-utils.js';
|
|
|
7
7
|
import { replayRun, modifyRun } from '../runs/replay-helpers.js';
|
|
8
8
|
import { resolvePromptFlags } from './print-prompt.js';
|
|
9
9
|
import { shouldShowRunPicker } from '../runs/run-picker-mount.js';
|
|
10
|
+
import { dispatchPickerSelection } from './picker-dispatch.js';
|
|
10
11
|
export function registerImportCommand(program) {
|
|
11
12
|
program
|
|
12
13
|
.command('import')
|
|
@@ -220,15 +221,20 @@ export function registerImportCommand(program) {
|
|
|
220
221
|
isTTY: !!process.stdin.isTTY,
|
|
221
222
|
});
|
|
222
223
|
let pickerSelection = null;
|
|
224
|
+
// Ink `unmount` handle captured after `render(...)` below so the
|
|
225
|
+
// picker callback can tear down the wizard cleanly. Prior to this
|
|
226
|
+
// fix the callback did `setImmediate(() => process.exit(0))`, which
|
|
227
|
+
// killed the process before `dispatchPickerSelection` could run.
|
|
228
|
+
let unmountInk = null;
|
|
223
229
|
const pickerProps = {};
|
|
224
230
|
if (pickerDecision.shouldShow) {
|
|
225
231
|
pickerProps.initialRuns = pickerDecision.runs;
|
|
226
232
|
pickerProps.onRunPicked = (selection) => {
|
|
227
233
|
pickerSelection = selection;
|
|
228
|
-
|
|
234
|
+
unmountInk?.();
|
|
229
235
|
};
|
|
230
236
|
}
|
|
231
|
-
const { waitUntilExit } = render(createElement(WizardApp, {
|
|
237
|
+
const { waitUntilExit, unmount } = render(createElement(WizardApp, {
|
|
232
238
|
initialSpaceId: creds.spaceId,
|
|
233
239
|
initialEnvironmentId: creds.environmentId || 'master',
|
|
234
240
|
initialCmaToken: creds.cmaToken,
|
|
@@ -250,43 +256,24 @@ export function registerImportCommand(program) {
|
|
|
250
256
|
...(opts.rawTokens ? { initialRawTokensPath: resolve(opts.rawTokens) } : {}),
|
|
251
257
|
...pickerProps,
|
|
252
258
|
}));
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
/* Ink throws on process.exit; swallow so picker dispatch can run. */
|
|
258
|
-
}
|
|
259
|
+
unmountInk = unmount;
|
|
260
|
+
await waitUntilExit();
|
|
259
261
|
// ── Picker dispatch ─────────────────────────────────────────────
|
|
260
|
-
// If the operator picked a run, route into
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
// resolveRunTarget and threads its session IDs through
|
|
264
|
-
// launchModifyWizard (see runs/replay-helpers.ts).
|
|
262
|
+
// If the operator picked a run, route into replayRun / modifyRun.
|
|
263
|
+
// dispatchPickerSelection lives in ./picker-dispatch.ts so this
|
|
264
|
+
// decision is testable without an Ink runtime.
|
|
265
265
|
if (pickerSelection) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (sel.action === 'modify' && sel.runId) {
|
|
280
|
-
await modifyRun({
|
|
281
|
-
runIdOrPath: sel.runId,
|
|
282
|
-
...(opts.outDir ? { outDir: opts.outDir } : {}),
|
|
283
|
-
...(opts.overwrite ? { overwrite: true } : {}),
|
|
284
|
-
...(opts.saveAsNew ? { saveAsNew: true } : {}),
|
|
285
|
-
...(opts.force ? { force: true } : {}),
|
|
286
|
-
});
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
// action === 'new' falls through — the wizard already advanced.
|
|
266
|
+
await dispatchPickerSelection(pickerSelection, {
|
|
267
|
+
...(opts.spaceId ? { spaceId: opts.spaceId } : {}),
|
|
268
|
+
...(opts.environmentId ? { environmentId: opts.environmentId } : {}),
|
|
269
|
+
...(opts.cmaToken ? { cmaToken: opts.cmaToken } : {}),
|
|
270
|
+
...(opts.host ? { host: opts.host } : {}),
|
|
271
|
+
interactive: !!process.stdout.isTTY,
|
|
272
|
+
...(opts.outDir ? { outDir: opts.outDir } : {}),
|
|
273
|
+
...(opts.overwrite ? { overwrite: true } : {}),
|
|
274
|
+
...(opts.saveAsNew ? { saveAsNew: true } : {}),
|
|
275
|
+
...(opts.force ? { force: true } : {}),
|
|
276
|
+
}, { replayRun, modifyRun });
|
|
290
277
|
}
|
|
291
278
|
return;
|
|
292
279
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RunPickerSelection } from '../runs/run-picker.js';
|
|
2
|
+
import type { replayRun as replayRunFn, modifyRun as modifyRunFn } from '../runs/replay-helpers.js';
|
|
3
|
+
export type PickerDispatchOptions = {
|
|
4
|
+
spaceId?: string;
|
|
5
|
+
environmentId?: string;
|
|
6
|
+
cmaToken?: string;
|
|
7
|
+
host?: string;
|
|
8
|
+
interactive?: boolean;
|
|
9
|
+
outDir?: string;
|
|
10
|
+
overwrite?: boolean;
|
|
11
|
+
saveAsNew?: boolean;
|
|
12
|
+
force?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type PickerDispatchDeps = {
|
|
15
|
+
replayRun: typeof replayRunFn;
|
|
16
|
+
modifyRun: typeof modifyRunFn;
|
|
17
|
+
};
|
|
18
|
+
export declare function dispatchPickerSelection(selection: RunPickerSelection, opts: PickerDispatchOptions, deps: PickerDispatchDeps): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Route a resolved RunPickerSelection into replayRun / modifyRun. Extracted
|
|
2
|
+
// from command.ts so the dispatch decision is testable without an Ink runtime
|
|
3
|
+
// and so the picker callback in command.ts is a simple state-capture (no
|
|
4
|
+
// process.exit shenanigans that would kill the process before dispatch runs).
|
|
5
|
+
export async function dispatchPickerSelection(selection, opts, deps) {
|
|
6
|
+
if (selection.action === 'push' && selection.runId) {
|
|
7
|
+
await deps.replayRun({
|
|
8
|
+
runIdOrPath: selection.runId,
|
|
9
|
+
...(opts.spaceId ? { spaceId: opts.spaceId } : {}),
|
|
10
|
+
...(opts.environmentId ? { environmentId: opts.environmentId } : {}),
|
|
11
|
+
...(opts.cmaToken ? { cmaToken: opts.cmaToken } : {}),
|
|
12
|
+
...(opts.host ? { host: opts.host } : {}),
|
|
13
|
+
...(opts.interactive !== undefined ? { interactive: opts.interactive } : {}),
|
|
14
|
+
...(opts.force ? { force: true } : {}),
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (selection.action === 'modify' && selection.runId) {
|
|
19
|
+
await deps.modifyRun({
|
|
20
|
+
runIdOrPath: selection.runId,
|
|
21
|
+
...(opts.outDir ? { outDir: opts.outDir } : {}),
|
|
22
|
+
...(opts.overwrite ? { overwrite: true } : {}),
|
|
23
|
+
...(opts.saveAsNew ? { saveAsNew: true } : {}),
|
|
24
|
+
...(opts.force ? { force: true } : {}),
|
|
25
|
+
});
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// action === 'new' → no-op; the wizard already advanced past the picker.
|
|
29
|
+
}
|
|
@@ -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,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
|
-
|
|
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
|
-
|
|
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]
|
|
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-
|
|
3
|
+
"version": "2.12.1-dev-build-0b7de73.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-
|
|
40
|
+
"@contentful/experience-design-system-types": "2.12.1-dev-build-0b7de73.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@tsconfig/node24": "^24.0.3",
|