@contentful/experience-design-system-cli 2.26.1-dev-build-a852362.0 → 2.26.1-dev-build-f03f014.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 +14 -10
- package/dist/src/index.js +34937 -9
- package/dist/src/lib/cli-path.d.ts +4 -0
- package/dist/src/runs/export-helpers.d.ts +1 -12
- package/dist/src/runs/push-helpers.d.ts +1 -6
- package/package.json +12 -7
- package/skills/generate-components.md +427 -0
- package/skills/generate-tokens.md +194 -0
- package/skills/select-components.md +224 -0
- package/dist/src/analytics/apply.js +0 -30
- package/dist/src/analytics/client.js +0 -81
- package/dist/src/analytics/constants.js +0 -4
- package/dist/src/analytics/env.js +0 -14
- package/dist/src/analytics/exit.js +0 -24
- package/dist/src/analytics/index.js +0 -9
- package/dist/src/analytics/normalize.js +0 -18
- package/dist/src/analytics/os.js +0 -13
- package/dist/src/analytics/session.js +0 -15
- package/dist/src/analytics/tracker.js +0 -126
- package/dist/src/analytics/types.js +0 -1
- package/dist/src/analyze/build-analyze-view-rows.js +0 -63
- package/dist/src/analyze/command.js +0 -599
- package/dist/src/analyze/composite-closure.js +0 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
- package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
- package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
- package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
- package/dist/src/analyze/composition/apply-mapping.js +0 -67
- package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
- package/dist/src/analyze/composition/candidate-critic.js +0 -63
- package/dist/src/analyze/composition/candidate-files.js +0 -131
- package/dist/src/analyze/composition/composition-cache-key.js +0 -19
- package/dist/src/analyze/composition/interchange-schema.js +0 -61
- package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
- package/dist/src/analyze/composition/merge-edges.js +0 -70
- package/dist/src/analyze/composition/parse-map-edges.js +0 -55
- package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
- package/dist/src/analyze/composition/resolve-mapping.js +0 -111
- package/dist/src/analyze/cycle-detection.js +0 -234
- package/dist/src/analyze/cycle-view.js +0 -28
- package/dist/src/analyze/fuzzy-search.js +0 -55
- package/dist/src/analyze/issue-inheritance.js +0 -108
- package/dist/src/analyze/lineage.js +0 -0
- package/dist/src/analyze/scope-gate-cascade.js +0 -170
- package/dist/src/analyze/search-neighborhood.js +0 -56
- package/dist/src/analyze/select/command.js +0 -484
- package/dist/src/analyze/select/index.js +0 -5
- package/dist/src/analyze/select/parser.js +0 -53
- package/dist/src/analyze/select/persistence.js +0 -80
- package/dist/src/analyze/select/preview-annotations.js +0 -62
- package/dist/src/analyze/select/stdout.js +0 -3
- package/dist/src/analyze/select/tui/App.js +0 -483
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
- package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
- package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
- package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
- package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
- package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
- package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
- package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
- package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
- package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
- package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
- package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
- package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
- package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
- package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
- package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
- package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
- package/dist/src/analyze/select/tui/theme.js +0 -28
- package/dist/src/analyze/select/types.js +0 -39
- package/dist/src/analyze/select-agent/command.js +0 -558
- package/dist/src/analyze/select-agent/context-builder.js +0 -197
- package/dist/src/analyze/select-agent/show-rationale.js +0 -92
- package/dist/src/analyze/selection-cascade.js +0 -19
- package/dist/src/analyze/slot-graph.js +0 -17
- package/dist/src/analyze/tui/AnalyzeView.js +0 -64
- package/dist/src/apply/api-client.js +0 -440
- package/dist/src/apply/command.js +0 -845
- package/dist/src/apply/manifest.js +0 -1
- package/dist/src/apply/preview-utils.js +0 -26
- package/dist/src/apply/tui/SelectView.js +0 -34
- package/dist/src/apply/tui/ServerApplyView.js +0 -49
- package/dist/src/apply/tui/ServerPreviewView.js +0 -23
- package/dist/src/credentials-store.js +0 -74
- package/dist/src/generate/command.js +0 -597
- package/dist/src/generate/edit/command.js +0 -127
- package/dist/src/generate/tui/GenerateView.js +0 -10
- package/dist/src/host-utils.js +0 -26
- package/dist/src/import/agent-model-resolve.js +0 -35
- package/dist/src/import/auto-filter-resolve.js +0 -16
- package/dist/src/import/command.js +0 -380
- package/dist/src/import/cycle-auto-reject.js +0 -14
- package/dist/src/import/orchestrator.js +0 -603
- package/dist/src/import/path-utils.js +0 -30
- package/dist/src/import/picker-dispatch.js +0 -44
- package/dist/src/import/print-prompt.js +0 -31
- package/dist/src/import/strip-allowed-components.js +0 -22
- package/dist/src/import/tui/CustomPromptBanner.js +0 -8
- package/dist/src/import/tui/WizardApp.js +0 -1670
- package/dist/src/import/tui/ai-flag.js +0 -6
- package/dist/src/import/tui/auto-filter-error.js +0 -29
- package/dist/src/import/tui/autocomplete.js +0 -32
- package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
- package/dist/src/import/tui/components/CounterStrip.js +0 -12
- package/dist/src/import/tui/components/LegendEntry.js +0 -6
- package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
- package/dist/src/import/tui/final-review-host.js +0 -25
- package/dist/src/import/tui/group-collapse.js +0 -11
- package/dist/src/import/tui/history.js +0 -53
- package/dist/src/import/tui/hooks/useLineage.js +0 -50
- package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
- package/dist/src/import/tui/lineage-layout.js +0 -82
- package/dist/src/import/tui/merge-ai-decisions.js +0 -22
- package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
- package/dist/src/import/tui/push-progress.js +0 -1
- package/dist/src/import/tui/run-print-files-helpers.js +0 -8
- package/dist/src/import/tui/run-teaser.js +0 -9
- package/dist/src/import/tui/runLivePreview.js +0 -82
- package/dist/src/import/tui/runScopeGate.js +0 -28
- package/dist/src/import/tui/scope-gate-columns.js +0 -94
- package/dist/src/import/tui/scope-gate-host.js +0 -28
- package/dist/src/import/tui/sidebar-width.js +0 -3
- package/dist/src/import/tui/spawn-generate.js +0 -53
- package/dist/src/import/tui/step-filters.js +0 -42
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
- package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
- package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
- package/dist/src/import/tui/steps/DoneStep.js +0 -24
- package/dist/src/import/tui/steps/ErrorStep.js +0 -16
- package/dist/src/import/tui/steps/GateStep.js +0 -22
- package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
- package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
- package/dist/src/import/tui/steps/PreviewStep.js +0 -37
- package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
- package/dist/src/import/tui/steps/PushingStep.js +0 -24
- package/dist/src/import/tui/steps/RunningStep.js +0 -21
- package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
- package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
- package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
- package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
- package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
- package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
- package/dist/src/import/tui/steps/preview-diff.js +0 -175
- package/dist/src/import/tui/useFinalizePreview.js +0 -0
- package/dist/src/import/tui/useLivePreview.js +0 -122
- package/dist/src/import/tui/wizard-422-helpers.js +0 -60
- package/dist/src/import/tui/wizard-generate-progress.js +0 -25
- package/dist/src/import/tui/wizard-save-flow.js +0 -20
- package/dist/src/import/tui/wizard-state-transitions.js +0 -32
- package/dist/src/lib/agent-model-options.js +0 -15
- package/dist/src/lib/command-options.js +0 -30
- package/dist/src/lib/composition-mode.js +0 -39
- package/dist/src/lib/contentful-urls.js +0 -27
- package/dist/src/lib/debug-logger.js +0 -229
- package/dist/src/lib/debug-preamble.js +0 -33
- package/dist/src/lib/error-parser.js +0 -269
- package/dist/src/lib/prompt-overrides.js +0 -69
- package/dist/src/lib/terminal-capabilities.js +0 -51
- package/dist/src/lib/user-agent.js +0 -29
- package/dist/src/output/format.js +0 -110
- package/dist/src/print/command.js +0 -221
- package/dist/src/print/validate/tui/ValidateView.js +0 -37
- package/dist/src/print/validate/validators/cdf-validator.js +0 -104
- package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
- package/dist/src/print/validate/validators/format-errors.js +0 -18
- package/dist/src/program.js +0 -105
- package/dist/src/runs/export-helpers.js +0 -59
- package/dist/src/runs/fingerprint.js +0 -75
- package/dist/src/runs/ls-command.js +0 -137
- package/dist/src/runs/modify-launcher.js +0 -42
- package/dist/src/runs/path-prompt.js +0 -72
- package/dist/src/runs/push-creds-prompt.js +0 -39
- package/dist/src/runs/push-helpers.js +0 -72
- package/dist/src/runs/push-launcher.js +0 -77
- package/dist/src/runs/replay-helpers.js +0 -164
- package/dist/src/runs/resolve-run-target.js +0 -45
- package/dist/src/runs/run-picker-mount.js +0 -72
- package/dist/src/runs/run-picker.js +0 -129
- package/dist/src/runs/save-conflict.js +0 -56
- package/dist/src/runs/save-path-resolver.js +0 -75
- package/dist/src/runs/staleness.js +0 -140
- package/dist/src/runs/store.js +0 -136
- package/dist/src/session/cache-keys.js +0 -44
- package/dist/src/session/command.js +0 -261
- package/dist/src/session/db.js +0 -1538
- package/dist/src/session/migration.js +0 -117
- package/dist/src/session/session-id.js +0 -212
- package/dist/src/session/stats.js +0 -89
- package/dist/src/setup/analytics-prompt.js +0 -24
- package/dist/src/setup/auto-filter-prompt.js +0 -24
- package/dist/src/setup/command.js +0 -966
- package/dist/src/setup/composition-mode-prompt.js +0 -37
- package/dist/src/setup/debug-mode-prompt.js +0 -22
- package/dist/src/types.js +0 -1
|
@@ -1,558 +0,0 @@
|
|
|
1
|
-
import { addAgentModelOptions } from '../../lib/agent-model-options.js';
|
|
2
|
-
import { openPipelineDb, loadRawComponents, loadScannedFiles, createStep, updateStep, computeComponentInputHash, lookupSelectCache, storeSelectCache, getCliCacheVersion, } from '../../session/db.js';
|
|
3
|
-
import { hashPromptForSkill } from '../../session/cache-keys.js';
|
|
4
|
-
import { appendReviewEvent, getRefineArtifactsRoot, ensureRefineSession, getRefineSessionPaths, saveReviewState, } from '../select/persistence.js';
|
|
5
|
-
import { loadReviewInput } from '../select/parser.js';
|
|
6
|
-
import { AGENT_NAMES, buildPrompt, createLocalCliAgentInvoker, formatCustomPromptBanner, isAgentName, parseSelectToolCallLines, } from '@contentful/experience-design-system-generation';
|
|
7
|
-
import { access } from 'node:fs/promises';
|
|
8
|
-
import { readExperiencesCredentials } from '../../credentials-store.js';
|
|
9
|
-
import { OutputFormatter, c } from '../../output/format.js';
|
|
10
|
-
import { buildRepoContextIndex, buildSelectionContext } from './context-builder.js';
|
|
11
|
-
import { runShowRationale } from './show-rationale.js';
|
|
12
|
-
import { isAbsolute, resolve } from 'node:path';
|
|
13
|
-
import { getDebugLogger } from '../../lib/debug-logger.js';
|
|
14
|
-
import { bindAnalyticsSessionId, enrichCommandResult, exitWithAnalytics } from '../../analytics/index.js';
|
|
15
|
-
import { validateExtractedComponents, shouldExcludeDueToValidation, formatExclusionWarning, } from '@contentful/experience-design-system-extraction';
|
|
16
|
-
const DEFAULT_TIMEOUT_MS = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 3 * 60 * 1000);
|
|
17
|
-
export const DEFAULT_CONCURRENCY = 10;
|
|
18
|
-
export const DEFAULT_BATCH_SIZE = 5;
|
|
19
|
-
const invoker = createLocalCliAgentInvoker({
|
|
20
|
-
onDebugEvent: (name, payload) => getDebugLogger().event('agent', name, payload),
|
|
21
|
-
});
|
|
22
|
-
function resolveBatchSize() {
|
|
23
|
-
const raw = process.env.EDS_SELECT_BATCH_SIZE;
|
|
24
|
-
if (!raw)
|
|
25
|
-
return DEFAULT_BATCH_SIZE;
|
|
26
|
-
const n = Number(raw);
|
|
27
|
-
if (!Number.isFinite(n) || n < 1)
|
|
28
|
-
return DEFAULT_BATCH_SIZE;
|
|
29
|
-
return Math.floor(n);
|
|
30
|
-
}
|
|
31
|
-
async function resolveSessionId(sessionFlag) {
|
|
32
|
-
if (sessionFlag)
|
|
33
|
-
return sessionFlag;
|
|
34
|
-
const db = openPipelineDb();
|
|
35
|
-
try {
|
|
36
|
-
const row = db
|
|
37
|
-
.prepare(`SELECT s.id FROM sessions s
|
|
38
|
-
JOIN steps st ON st.session_id = s.id
|
|
39
|
-
WHERE st.command = 'analyze extract'
|
|
40
|
-
AND st.status = 'complete'
|
|
41
|
-
ORDER BY st.started_at DESC
|
|
42
|
-
LIMIT 1`)
|
|
43
|
-
.get();
|
|
44
|
-
if (!row) {
|
|
45
|
-
process.stderr.write('Error: no completed analyze extract session found. Run analyze extract first, or pass --session <id>.\n');
|
|
46
|
-
return await exitWithAnalytics(1);
|
|
47
|
-
}
|
|
48
|
-
return row.id;
|
|
49
|
-
}
|
|
50
|
-
finally {
|
|
51
|
-
db.close();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function componentKey(component) {
|
|
55
|
-
return `${component.name}::${component.source}`;
|
|
56
|
-
}
|
|
57
|
-
function buildComponentData(candidate) {
|
|
58
|
-
const { component, selectionContext } = candidate;
|
|
59
|
-
const payload = {
|
|
60
|
-
name: component.name,
|
|
61
|
-
source: component.source,
|
|
62
|
-
framework: component.framework,
|
|
63
|
-
propCount: component.props.length,
|
|
64
|
-
slotCount: component.slots.length,
|
|
65
|
-
propNames: component.props.slice(0, 8).map((p) => p.name),
|
|
66
|
-
props: component.props,
|
|
67
|
-
slots: component.slots,
|
|
68
|
-
extractionConfidence: component.extractionConfidence ?? null,
|
|
69
|
-
needsReview: component.needsReview ?? false,
|
|
70
|
-
};
|
|
71
|
-
if (component.reviewReasons && component.reviewReasons.length > 0) {
|
|
72
|
-
payload.reviewReasons = component.reviewReasons;
|
|
73
|
-
}
|
|
74
|
-
if (selectionContext) {
|
|
75
|
-
payload.selectionContext = selectionContext;
|
|
76
|
-
}
|
|
77
|
-
return payload;
|
|
78
|
-
}
|
|
79
|
-
function resolveProjectRoot(sessionId, projectRootFlag) {
|
|
80
|
-
if (projectRootFlag)
|
|
81
|
-
return resolve(projectRootFlag);
|
|
82
|
-
const db = openPipelineDb();
|
|
83
|
-
try {
|
|
84
|
-
const row = db
|
|
85
|
-
.prepare(`SELECT inputs FROM steps
|
|
86
|
-
WHERE session_id = ?
|
|
87
|
-
AND command = 'analyze extract'
|
|
88
|
-
ORDER BY started_at DESC
|
|
89
|
-
LIMIT 1`)
|
|
90
|
-
.get(sessionId);
|
|
91
|
-
if (!row?.inputs)
|
|
92
|
-
return null;
|
|
93
|
-
const parsed = JSON.parse(row.inputs);
|
|
94
|
-
return parsed.project ? resolve(parsed.project) : null;
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
finally {
|
|
100
|
-
db.close();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
async function selectBatch(agent, model, batch, total, verbose, skillPathOverride) {
|
|
104
|
-
const prompt = await buildPrompt({
|
|
105
|
-
skill: 'select',
|
|
106
|
-
mode: 'autonomous',
|
|
107
|
-
rawComponentsInline: JSON.stringify(batch.map((b) => buildComponentData(b.candidate)), null, 2),
|
|
108
|
-
outDir: process.cwd(),
|
|
109
|
-
skillPathOverride,
|
|
110
|
-
});
|
|
111
|
-
let outputBuf = '';
|
|
112
|
-
const formatter = new OutputFormatter(verbose, (s) => {
|
|
113
|
-
outputBuf += s;
|
|
114
|
-
});
|
|
115
|
-
const result = await invoker.invoke({
|
|
116
|
-
agent,
|
|
117
|
-
model,
|
|
118
|
-
prompt,
|
|
119
|
-
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
120
|
-
onOutput: (chunk) => formatter.push(chunk),
|
|
121
|
-
});
|
|
122
|
-
formatter.flush();
|
|
123
|
-
if (verbose) {
|
|
124
|
-
const names = batch.map((b) => b.candidate.component.name).join(', ');
|
|
125
|
-
process.stderr.write(` ${c.bold(`batch: ${names}`)}\n${outputBuf}`);
|
|
126
|
-
}
|
|
127
|
-
// Feature 3: emit one progress= line per component (in input order) regardless
|
|
128
|
-
// of batch outcome. The wizard's runAutoFilter parser depends on this contract.
|
|
129
|
-
const emitProgress = (item, decision, reason) => {
|
|
130
|
-
const reasonEncoded = reason ? encodeURIComponent(reason) : '';
|
|
131
|
-
process.stderr.write(`progress=select-agent:${item.index + 1}/${total}:${decision}:${item.candidate.component.name}:${reasonEncoded}\n`);
|
|
132
|
-
};
|
|
133
|
-
// Whole-batch failure: timeout or non-zero exit. Mark every item failed and
|
|
134
|
-
// do NOT emit progress lines (no decision was made).
|
|
135
|
-
if (result.timedOut) {
|
|
136
|
-
for (const item of batch) {
|
|
137
|
-
const pos = c.dim(`[${item.index + 1}/${total}]`);
|
|
138
|
-
process.stderr.write(` ${pos} ${c.bold(item.candidate.component.name)} ${c.yellow('timed out')}\n`);
|
|
139
|
-
}
|
|
140
|
-
return batch.map((item) => ({
|
|
141
|
-
componentKey: componentKey(item.candidate.component),
|
|
142
|
-
componentName: item.candidate.component.name,
|
|
143
|
-
decision: null,
|
|
144
|
-
failed: true,
|
|
145
|
-
}));
|
|
146
|
-
}
|
|
147
|
-
if (result.exitCode !== 0) {
|
|
148
|
-
for (const item of batch) {
|
|
149
|
-
const pos = c.dim(`[${item.index + 1}/${total}]`);
|
|
150
|
-
process.stderr.write(` ${pos} ${c.bold(item.candidate.component.name)} ${c.red(`agent exited with code ${result.exitCode}`)}\n`);
|
|
151
|
-
}
|
|
152
|
-
return batch.map((item) => ({
|
|
153
|
-
componentKey: componentKey(item.candidate.component),
|
|
154
|
-
componentName: item.candidate.component.name,
|
|
155
|
-
decision: null,
|
|
156
|
-
failed: true,
|
|
157
|
-
}));
|
|
158
|
-
}
|
|
159
|
-
const { calls, warnings } = parseSelectToolCallLines(result.stdout);
|
|
160
|
-
if (warnings.length > 0) {
|
|
161
|
-
for (const warning of warnings) {
|
|
162
|
-
process.stderr.write(` ${c.yellow('⚠')} batch: ${warning}\n`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
const results = [];
|
|
166
|
-
for (const item of batch) {
|
|
167
|
-
const { component } = item.candidate;
|
|
168
|
-
const pos = c.dim(`[${item.index + 1}/${total}]`);
|
|
169
|
-
const call = calls.find((toolCall) => toolCall.name === component.name);
|
|
170
|
-
if (!call) {
|
|
171
|
-
process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.yellow('no tool call')}\n`);
|
|
172
|
-
emitProgress(item, 'failed', 'no-tool-call-from-agent');
|
|
173
|
-
results.push({
|
|
174
|
-
componentKey: componentKey(component),
|
|
175
|
-
componentName: component.name,
|
|
176
|
-
decision: null,
|
|
177
|
-
failed: true,
|
|
178
|
-
});
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
const decision = call.tool === 'select_component' ? 'accepted' : 'rejected';
|
|
182
|
-
const finalColor = decision === 'accepted' ? c.green : c.red;
|
|
183
|
-
process.stderr.write(` ${pos} ${c.bold(component.name)} ${finalColor(decision)}${call.reason ? ` ${c.dim(call.reason)}` : ''}\n`);
|
|
184
|
-
emitProgress(item, decision, call.reason);
|
|
185
|
-
results.push({
|
|
186
|
-
componentKey: componentKey(component),
|
|
187
|
-
componentName: component.name,
|
|
188
|
-
decision,
|
|
189
|
-
reason: call.reason,
|
|
190
|
-
failed: false,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
return results;
|
|
194
|
-
}
|
|
195
|
-
async function selectAllComponents(agent, model, components, verbose, skillPathOverride, cacheConfig = { noCache: true }) {
|
|
196
|
-
const concurrency = Number(process.env.EDS_GENERATE_CONCURRENCY ?? DEFAULT_CONCURRENCY);
|
|
197
|
-
const batchSize = resolveBatchSize();
|
|
198
|
-
const total = components.length;
|
|
199
|
-
// ── Fine-grained select cache: replay decisions for components whose
|
|
200
|
-
// (component_hash, prompt_hash, cli_version) triple already has a row. Only
|
|
201
|
-
// uncached components are forwarded to the LLM batches.
|
|
202
|
-
const cachedResults = new Map();
|
|
203
|
-
let promptHash = '';
|
|
204
|
-
let cliVersion = '';
|
|
205
|
-
if (!cacheConfig.noCache) {
|
|
206
|
-
try {
|
|
207
|
-
promptHash = await hashPromptForSkill('select', agent, model, skillPathOverride);
|
|
208
|
-
cliVersion = await getCliCacheVersion();
|
|
209
|
-
const db = openPipelineDb(cacheConfig.dbPath);
|
|
210
|
-
try {
|
|
211
|
-
for (let i = 0; i < components.length; i++) {
|
|
212
|
-
const candidate = components[i];
|
|
213
|
-
const compHash = computeComponentInputHash(candidate.component);
|
|
214
|
-
const hit = lookupSelectCache(db, compHash, promptHash, cliVersion);
|
|
215
|
-
if (hit) {
|
|
216
|
-
const finalColor = hit.decision === 'accepted' ? c.green : c.red;
|
|
217
|
-
process.stderr.write(` ${c.dim(`[${i + 1}/${total}]`)} ${c.bold(candidate.component.name)} ${finalColor(hit.decision)} ${c.dim('(cached)')}` +
|
|
218
|
-
(hit.reason ? ` ${c.dim(hit.reason)}` : '') +
|
|
219
|
-
'\n');
|
|
220
|
-
// Mirror the progress= line that selectBatch emits for live LLM
|
|
221
|
-
// calls so the wizard's parser sees a uniform stream.
|
|
222
|
-
const reasonEncoded = hit.reason ? encodeURIComponent(hit.reason) : '';
|
|
223
|
-
process.stderr.write(`progress=select-agent:${i + 1}/${total}:${hit.decision}:${candidate.component.name}:${reasonEncoded}\n`);
|
|
224
|
-
cachedResults.set(i, {
|
|
225
|
-
componentKey: componentKey(candidate.component),
|
|
226
|
-
componentName: candidate.component.name,
|
|
227
|
-
decision: hit.decision,
|
|
228
|
-
reason: hit.reason ?? undefined,
|
|
229
|
-
failed: false,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
finally {
|
|
235
|
-
db.close();
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
catch {
|
|
239
|
-
// Cache prefill is best-effort. On any error, fall through and run the
|
|
240
|
-
// full LLM batch path.
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
// Build the work-set of components we still need to ask the LLM about.
|
|
244
|
-
const uncached = [];
|
|
245
|
-
const uncachedIndices = [];
|
|
246
|
-
for (let i = 0; i < components.length; i++) {
|
|
247
|
-
if (!cachedResults.has(i)) {
|
|
248
|
-
uncached.push(components[i]);
|
|
249
|
-
uncachedIndices.push(i);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// Chunk into batches of `batchSize`, preserving original indices for the
|
|
253
|
-
// per-component progress=select-agent: lines.
|
|
254
|
-
const batches = [];
|
|
255
|
-
for (let i = 0; i < uncached.length; i += batchSize) {
|
|
256
|
-
const slice = uncached.slice(i, i + batchSize);
|
|
257
|
-
batches.push(slice.map((candidate, j) => ({ candidate, index: uncachedIndices[i + j] })));
|
|
258
|
-
}
|
|
259
|
-
process.stderr.write(`Validating ${c.bold(String(uncached.length))} component${uncached.length === 1 ? '' : 's'}` +
|
|
260
|
-
(cachedResults.size > 0 ? c.dim(` (${cachedResults.size} cached)`) : '') +
|
|
261
|
-
c.dim(` (concurrency: ${concurrency}, batch: ${batchSize}, batches: ${batches.length})`) +
|
|
262
|
-
'\n');
|
|
263
|
-
const results = new Array(total);
|
|
264
|
-
for (const [i, r] of cachedResults)
|
|
265
|
-
results[i] = r;
|
|
266
|
-
let nextBatch = 0;
|
|
267
|
-
async function worker() {
|
|
268
|
-
while (nextBatch < batches.length) {
|
|
269
|
-
const b = nextBatch++;
|
|
270
|
-
const batch = batches[b];
|
|
271
|
-
const batchResults = await selectBatch(agent, model, batch, total, verbose, skillPathOverride);
|
|
272
|
-
for (let k = 0; k < batch.length; k++) {
|
|
273
|
-
results[batch[k].index] = batchResults[k];
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
await Promise.all(Array.from({ length: Math.min(concurrency, batches.length) }, worker));
|
|
278
|
-
// Persist fresh decisions (skip cached + failed ones).
|
|
279
|
-
if (!cacheConfig.noCache && promptHash) {
|
|
280
|
-
try {
|
|
281
|
-
const db = openPipelineDb(cacheConfig.dbPath);
|
|
282
|
-
try {
|
|
283
|
-
for (let i = 0; i < results.length; i++) {
|
|
284
|
-
if (cachedResults.has(i))
|
|
285
|
-
continue;
|
|
286
|
-
const r = results[i];
|
|
287
|
-
if (!r || r.failed || !r.decision)
|
|
288
|
-
continue;
|
|
289
|
-
const candidate = components[i];
|
|
290
|
-
const compHash = computeComponentInputHash(candidate.component);
|
|
291
|
-
storeSelectCache(db, compHash, promptHash, cliVersion, r.decision, r.reason ?? null);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
finally {
|
|
295
|
-
db.close();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
catch {
|
|
299
|
-
// Best-effort.
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
return results;
|
|
303
|
-
}
|
|
304
|
-
export function registerAnalyzeSelectAgentCommand(program) {
|
|
305
|
-
const cmd = program
|
|
306
|
-
.command('select-agent')
|
|
307
|
-
.description('Use an AI agent to select components for Contentful Experience Orchestration')
|
|
308
|
-
.option('--session <id>', 'Session ID from analyze extract (defaults to most recent)')
|
|
309
|
-
.option('--project-root <path>', 'Project root for resolving component source files');
|
|
310
|
-
addAgentModelOptions(cmd)
|
|
311
|
-
.option('--verbose', 'Show full agent output including reasoning text')
|
|
312
|
-
.option('--dry-run', 'Print the prompt for the first component without invoking the agent')
|
|
313
|
-
.option('--exclude-invalid', 'Auto-reject components with validation errors instead of failing loud (LLM cannot fix structural issues)')
|
|
314
|
-
.option('--select-prompt-path <path>', 'Path to a custom .md skill prompt for select-agent (bypasses bundled prompt invariants)')
|
|
315
|
-
.option('--no-select-cache', 'Skip the per-component select cache and re-LLM every component')
|
|
316
|
-
.option('--no-cache', 'Skip ALL fine-grained caches (extract, select, generate)')
|
|
317
|
-
.option('--show-rationale', 'Read-only: print the AI rejection rationale persisted by a prior select-agent run and exit. ' +
|
|
318
|
-
'No LLM call. Combine with --json for machine-readable output.')
|
|
319
|
-
.option('--json', 'When used with --show-rationale, emit a JSON array instead of a human-readable table')
|
|
320
|
-
.action(async (opts) => {
|
|
321
|
-
// --show-rationale is a read-only branch that short-circuits the normal
|
|
322
|
-
// select-agent flow. It re-reads the rationale columns persisted by a
|
|
323
|
-
// prior run (raw_components.status + reject_reason) and prints them.
|
|
324
|
-
// No LLM call, no agent resolution, no validation gates.
|
|
325
|
-
if (opts.showRationale) {
|
|
326
|
-
try {
|
|
327
|
-
runShowRationale({ session: opts.session, json: opts.json });
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
catch (err) {
|
|
331
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
332
|
-
process.stderr.write(`Error: ${message}\n`);
|
|
333
|
-
await exitWithAnalytics(1);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
const savedCreds = await readExperiencesCredentials();
|
|
338
|
-
const agentName = opts.agent ?? savedCreds.agent;
|
|
339
|
-
const model = opts.model ?? savedCreds.agentModel;
|
|
340
|
-
if (!agentName || !isAgentName(agentName)) {
|
|
341
|
-
process.stderr.write(`Error: no agent configured. Pass --agent <name> or run experiences setup. Accepted values: ${AGENT_NAMES.join(', ')}\n`);
|
|
342
|
-
await exitWithAnalytics(1);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
const agent = agentName;
|
|
346
|
-
// Feature 8: validate + announce custom prompt path before any heavy
|
|
347
|
-
// work. Flag wins over saved credentials.
|
|
348
|
-
const selectPromptPath = opts.selectPromptPath ?? savedCreds.selectPromptPath;
|
|
349
|
-
if (selectPromptPath) {
|
|
350
|
-
const resolvedPath = resolve(selectPromptPath);
|
|
351
|
-
const exists = await access(resolvedPath)
|
|
352
|
-
.then(() => true)
|
|
353
|
-
.catch(() => false);
|
|
354
|
-
if (!exists) {
|
|
355
|
-
process.stderr.write(`Error: custom prompt path not found: ${resolvedPath}\n`);
|
|
356
|
-
await exitWithAnalytics(1);
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
if (!selectPromptPath.toLowerCase().endsWith('.md')) {
|
|
360
|
-
process.stderr.write(`WARNING: custom prompt path does not end in .md (${selectPromptPath}) — proceeding anyway.\n`);
|
|
361
|
-
}
|
|
362
|
-
process.stderr.write(formatCustomPromptBanner('select', resolvedPath));
|
|
363
|
-
}
|
|
364
|
-
const sessionId = await resolveSessionId(opts.session);
|
|
365
|
-
await bindAnalyticsSessionId(sessionId);
|
|
366
|
-
const selectionRoot = resolveProjectRoot(sessionId, opts.projectRoot);
|
|
367
|
-
const db = openPipelineDb();
|
|
368
|
-
let rawComponents;
|
|
369
|
-
let scannedFiles = [];
|
|
370
|
-
try {
|
|
371
|
-
rawComponents = loadRawComponents(db, sessionId);
|
|
372
|
-
scannedFiles = loadScannedFiles(db, sessionId);
|
|
373
|
-
}
|
|
374
|
-
finally {
|
|
375
|
-
db.close();
|
|
376
|
-
}
|
|
377
|
-
if (rawComponents.length === 0) {
|
|
378
|
-
process.stderr.write(`Error: session '${sessionId}' has no raw components. Run analyze extract first.\n`);
|
|
379
|
-
await exitWithAnalytics(1);
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
// Re-run validation (not persisted to DB, so always recompute).
|
|
383
|
-
const validatedComponents = validateExtractedComponents(rawComponents);
|
|
384
|
-
// Fail-loud gate: if any component has validation errors, refuse to
|
|
385
|
-
// proceed unless --exclude-invalid is set. The LLM cannot fix
|
|
386
|
-
// structural issues (empty names, slot/prop collisions, etc.) — silent
|
|
387
|
-
// exclusion in this CI-style command would drop components without the
|
|
388
|
-
// caller noticing.
|
|
389
|
-
const invalidComponents = validatedComponents.filter(shouldExcludeDueToValidation);
|
|
390
|
-
if (invalidComponents.length > 0 && !opts.excludeInvalid) {
|
|
391
|
-
const lines = [
|
|
392
|
-
`Error: ${invalidComponents.length} component(s) failed validation; refusing select-agent without --exclude-invalid:`,
|
|
393
|
-
];
|
|
394
|
-
for (const comp of invalidComponents) {
|
|
395
|
-
const codes = (comp.validationIssues ?? [])
|
|
396
|
-
.filter((i) => i.severity === 'error')
|
|
397
|
-
.map((i) => i.code)
|
|
398
|
-
.join(', ');
|
|
399
|
-
lines.push(` ✗ ${comp.name} ${codes}`);
|
|
400
|
-
}
|
|
401
|
-
lines.push('');
|
|
402
|
-
lines.push('Re-run with --exclude-invalid to auto-reject these components, or fix them in source first.');
|
|
403
|
-
process.stderr.write(lines.join('\n') + '\n');
|
|
404
|
-
await exitWithAnalytics(1);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
const componentsForAgent = validatedComponents.filter((comp) => !shouldExcludeDueToValidation(comp));
|
|
408
|
-
if (invalidComponents.length > 0) {
|
|
409
|
-
process.stderr.write(c.yellow(formatExclusionWarning(invalidComponents)));
|
|
410
|
-
}
|
|
411
|
-
if (selectionRoot && scannedFiles.length > 0) {
|
|
412
|
-
scannedFiles = scannedFiles.map((f) => (isAbsolute(f) ? f : resolve(selectionRoot, f)));
|
|
413
|
-
}
|
|
414
|
-
if (selectionRoot && scannedFiles.length === 0 && rawComponents.length > 0) {
|
|
415
|
-
process.stderr.write('warn: session has no scanned-files index (likely extracted on an older CLI version). ' +
|
|
416
|
-
'Re-run `analyze extract` to enable data-fetch wrapper detection during selection.\n');
|
|
417
|
-
}
|
|
418
|
-
let selectionCandidates = componentsForAgent.map((component) => ({ component }));
|
|
419
|
-
if (selectionRoot && scannedFiles.length > 0) {
|
|
420
|
-
const repoIndex = await buildRepoContextIndex(selectionRoot, scannedFiles).catch(() => null);
|
|
421
|
-
if (repoIndex) {
|
|
422
|
-
selectionCandidates = componentsForAgent.map((component) => ({
|
|
423
|
-
component,
|
|
424
|
-
selectionContext: buildSelectionContext(repoIndex, component),
|
|
425
|
-
}));
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (opts.dryRun) {
|
|
429
|
-
if (selectionCandidates.length === 0) {
|
|
430
|
-
process.stderr.write('No valid components to preview — all components were excluded due to validation errors.\n');
|
|
431
|
-
await exitWithAnalytics(0);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
const first = selectionCandidates[0];
|
|
435
|
-
const prompt = await buildPrompt({
|
|
436
|
-
skill: 'select',
|
|
437
|
-
mode: 'autonomous',
|
|
438
|
-
rawComponentsInline: JSON.stringify([buildComponentData(first)], null, 2),
|
|
439
|
-
outDir: process.cwd(),
|
|
440
|
-
skillPathOverride: selectPromptPath ? resolve(selectPromptPath) : undefined,
|
|
441
|
-
});
|
|
442
|
-
process.stdout.write(prompt + '\n');
|
|
443
|
-
await exitWithAnalytics(0);
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
// --no-cache is the global kill-switch; --no-select-cache is the stage-
|
|
447
|
-
// specific opt-out. Both Commander negation flags arrive as `false` when
|
|
448
|
-
// the user passed --no-*; default (undefined) means cache stays on.
|
|
449
|
-
const noCache = opts.cache === false || opts.selectCache === false || process.env.EDS_NO_CACHE === '1';
|
|
450
|
-
const selectResults = await selectAllComponents(agent, model, selectionCandidates, opts.verbose ?? false, selectPromptPath ? resolve(selectPromptPath) : undefined, { noCache });
|
|
451
|
-
// Build decision map from results. Seed auto-rejections from validation exclusions first.
|
|
452
|
-
const decisions = new Map();
|
|
453
|
-
// Track per-component reasons (for raw_components.reject_reason persistence).
|
|
454
|
-
// Validation auto-rejections get a synthesized "validation error: <codes>" reason
|
|
455
|
-
// so the scope-gate UI can distinguish wizard- vs LLM-driven exclusions.
|
|
456
|
-
const reasons = new Map();
|
|
457
|
-
for (const comp of invalidComponents) {
|
|
458
|
-
decisions.set(componentKey(comp), 'rejected');
|
|
459
|
-
const codes = (comp.validationIssues ?? [])
|
|
460
|
-
.filter((i) => i.severity === 'error')
|
|
461
|
-
.map((i) => i.code)
|
|
462
|
-
.join(', ');
|
|
463
|
-
reasons.set(componentKey(comp), `validation error: ${codes}`);
|
|
464
|
-
}
|
|
465
|
-
for (const r of selectResults) {
|
|
466
|
-
decisions.set(r.componentKey, r.decision);
|
|
467
|
-
if (r.decision === 'rejected' && r.reason) {
|
|
468
|
-
reasons.set(r.componentKey, r.reason);
|
|
469
|
-
}
|
|
470
|
-
else if (r.decision === 'accepted') {
|
|
471
|
-
// Defensive: clear any previously-stored reason on re-runs that flip a
|
|
472
|
-
// rejected component to accepted.
|
|
473
|
-
reasons.set(r.componentKey, null);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// Feature 3: persist decisions to raw_components (status + reject_reason)
|
|
477
|
-
// so the wizard can render LLM exclusions in the scope-gate without re-reading
|
|
478
|
-
// the snapshot file. We open a fresh DB handle here (the earlier one was closed
|
|
479
|
-
// after loading raw components above) and reuse it for the step-recording below.
|
|
480
|
-
const persistDb = openPipelineDb();
|
|
481
|
-
try {
|
|
482
|
-
const updateStmt = persistDb.prepare(`UPDATE raw_components
|
|
483
|
-
SET status = ?, reject_reason = ?
|
|
484
|
-
WHERE session_id = ? AND component_id = ?`);
|
|
485
|
-
for (const comp of validatedComponents) {
|
|
486
|
-
const key = componentKey(comp);
|
|
487
|
-
const decision = decisions.get(key);
|
|
488
|
-
if (!decision)
|
|
489
|
-
continue;
|
|
490
|
-
if (!comp.component_id)
|
|
491
|
-
continue;
|
|
492
|
-
const reason = reasons.get(key) ?? null;
|
|
493
|
-
updateStmt.run(decision, decision === 'accepted' ? null : reason, sessionId, comp.component_id);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
finally {
|
|
497
|
-
persistDb.close();
|
|
498
|
-
}
|
|
499
|
-
const artifactsRoot = getRefineArtifactsRoot();
|
|
500
|
-
let snapshot;
|
|
501
|
-
try {
|
|
502
|
-
snapshot = await loadReviewInput(validatedComponents, {
|
|
503
|
-
reviewRoot: selectionRoot ?? undefined,
|
|
504
|
-
});
|
|
505
|
-
snapshot = await ensureRefineSession(sessionId, artifactsRoot, snapshot);
|
|
506
|
-
}
|
|
507
|
-
catch (error) {
|
|
508
|
-
process.stderr.write(`Error: unable to initialize select session.\n${error instanceof Error ? error.message : String(error)}\n`);
|
|
509
|
-
await exitWithAnalytics(1);
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
|
|
513
|
-
const updated = {
|
|
514
|
-
...snapshot,
|
|
515
|
-
components: snapshot.components.map((comp) => {
|
|
516
|
-
const key = componentKey(comp.originalProposal);
|
|
517
|
-
const decision = decisions.get(key);
|
|
518
|
-
if (!decision)
|
|
519
|
-
return comp;
|
|
520
|
-
return { ...comp, status: decision };
|
|
521
|
-
}),
|
|
522
|
-
};
|
|
523
|
-
await saveReviewState(paths.statePath, updated);
|
|
524
|
-
await Promise.all(updated.components
|
|
525
|
-
.filter((component) => component.status === 'accepted' || component.status === 'rejected')
|
|
526
|
-
.map((component) => appendReviewEvent(paths.eventsPath, {
|
|
527
|
-
type: 'select_agent_decision',
|
|
528
|
-
payload: {
|
|
529
|
-
component: component.name,
|
|
530
|
-
source: component.originalProposal.source,
|
|
531
|
-
status: component.status,
|
|
532
|
-
},
|
|
533
|
-
})));
|
|
534
|
-
const accepted = updated.components.filter((comp) => comp.status === 'accepted');
|
|
535
|
-
const rejected = updated.components.filter((comp) => comp.status === 'rejected');
|
|
536
|
-
const unresolved = updated.components.filter((comp) => comp.status === 'needs-review');
|
|
537
|
-
const failed = selectResults.filter((r) => r.failed);
|
|
538
|
-
if (failed.length > 0) {
|
|
539
|
-
process.stderr.write(c.red(`Failed (${failed.length}/${selectResults.length}):`) + '\n');
|
|
540
|
-
for (const f of failed) {
|
|
541
|
-
process.stderr.write(` ${c.red('✗')} ${f.componentName}\n`);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
const stepDb = openPipelineDb();
|
|
545
|
-
const stepId = createStep(stepDb, sessionId, 'analyze select', {
|
|
546
|
-
sessionId,
|
|
547
|
-
});
|
|
548
|
-
try {
|
|
549
|
-
const status = failed.length === selectResults.length ? 'failed' : 'complete';
|
|
550
|
-
updateStep(stepDb, stepId, status, { sessionId });
|
|
551
|
-
}
|
|
552
|
-
finally {
|
|
553
|
-
stepDb.close();
|
|
554
|
-
}
|
|
555
|
-
enrichCommandResult({ accepted_component_count: accepted.length });
|
|
556
|
-
process.stderr.write(`Accepted: ${accepted.length} Rejected: ${rejected.length} Needs review: ${unresolved.length}\n`);
|
|
557
|
-
});
|
|
558
|
-
}
|