@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.
Files changed (204) hide show
  1. package/dist/package.json +14 -10
  2. package/dist/src/index.js +34937 -9
  3. package/dist/src/lib/cli-path.d.ts +4 -0
  4. package/dist/src/runs/export-helpers.d.ts +1 -12
  5. package/dist/src/runs/push-helpers.d.ts +1 -6
  6. package/package.json +12 -7
  7. package/skills/generate-components.md +427 -0
  8. package/skills/generate-tokens.md +194 -0
  9. package/skills/select-components.md +224 -0
  10. package/dist/src/analytics/apply.js +0 -30
  11. package/dist/src/analytics/client.js +0 -81
  12. package/dist/src/analytics/constants.js +0 -4
  13. package/dist/src/analytics/env.js +0 -14
  14. package/dist/src/analytics/exit.js +0 -24
  15. package/dist/src/analytics/index.js +0 -9
  16. package/dist/src/analytics/normalize.js +0 -18
  17. package/dist/src/analytics/os.js +0 -13
  18. package/dist/src/analytics/session.js +0 -15
  19. package/dist/src/analytics/tracker.js +0 -126
  20. package/dist/src/analytics/types.js +0 -1
  21. package/dist/src/analyze/build-analyze-view-rows.js +0 -63
  22. package/dist/src/analyze/command.js +0 -599
  23. package/dist/src/analyze/composite-closure.js +0 -0
  24. package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
  25. package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
  26. package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
  27. package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
  28. package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
  29. package/dist/src/analyze/composition/apply-mapping.js +0 -67
  30. package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
  31. package/dist/src/analyze/composition/candidate-critic.js +0 -63
  32. package/dist/src/analyze/composition/candidate-files.js +0 -131
  33. package/dist/src/analyze/composition/composition-cache-key.js +0 -19
  34. package/dist/src/analyze/composition/interchange-schema.js +0 -61
  35. package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
  36. package/dist/src/analyze/composition/merge-edges.js +0 -70
  37. package/dist/src/analyze/composition/parse-map-edges.js +0 -55
  38. package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
  39. package/dist/src/analyze/composition/resolve-mapping.js +0 -111
  40. package/dist/src/analyze/cycle-detection.js +0 -234
  41. package/dist/src/analyze/cycle-view.js +0 -28
  42. package/dist/src/analyze/fuzzy-search.js +0 -55
  43. package/dist/src/analyze/issue-inheritance.js +0 -108
  44. package/dist/src/analyze/lineage.js +0 -0
  45. package/dist/src/analyze/scope-gate-cascade.js +0 -170
  46. package/dist/src/analyze/search-neighborhood.js +0 -56
  47. package/dist/src/analyze/select/command.js +0 -484
  48. package/dist/src/analyze/select/index.js +0 -5
  49. package/dist/src/analyze/select/parser.js +0 -53
  50. package/dist/src/analyze/select/persistence.js +0 -80
  51. package/dist/src/analyze/select/preview-annotations.js +0 -62
  52. package/dist/src/analyze/select/stdout.js +0 -3
  53. package/dist/src/analyze/select/tui/App.js +0 -483
  54. package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
  55. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
  56. package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
  57. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
  58. package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
  59. package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
  60. package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
  61. package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
  62. package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
  63. package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
  64. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
  65. package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
  66. package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
  67. package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
  68. package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
  69. package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
  70. package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
  71. package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
  72. package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
  73. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
  74. package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
  75. package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
  76. package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
  77. package/dist/src/analyze/select/tui/theme.js +0 -28
  78. package/dist/src/analyze/select/types.js +0 -39
  79. package/dist/src/analyze/select-agent/command.js +0 -558
  80. package/dist/src/analyze/select-agent/context-builder.js +0 -197
  81. package/dist/src/analyze/select-agent/show-rationale.js +0 -92
  82. package/dist/src/analyze/selection-cascade.js +0 -19
  83. package/dist/src/analyze/slot-graph.js +0 -17
  84. package/dist/src/analyze/tui/AnalyzeView.js +0 -64
  85. package/dist/src/apply/api-client.js +0 -440
  86. package/dist/src/apply/command.js +0 -845
  87. package/dist/src/apply/manifest.js +0 -1
  88. package/dist/src/apply/preview-utils.js +0 -26
  89. package/dist/src/apply/tui/SelectView.js +0 -34
  90. package/dist/src/apply/tui/ServerApplyView.js +0 -49
  91. package/dist/src/apply/tui/ServerPreviewView.js +0 -23
  92. package/dist/src/credentials-store.js +0 -74
  93. package/dist/src/generate/command.js +0 -597
  94. package/dist/src/generate/edit/command.js +0 -127
  95. package/dist/src/generate/tui/GenerateView.js +0 -10
  96. package/dist/src/host-utils.js +0 -26
  97. package/dist/src/import/agent-model-resolve.js +0 -35
  98. package/dist/src/import/auto-filter-resolve.js +0 -16
  99. package/dist/src/import/command.js +0 -380
  100. package/dist/src/import/cycle-auto-reject.js +0 -14
  101. package/dist/src/import/orchestrator.js +0 -603
  102. package/dist/src/import/path-utils.js +0 -30
  103. package/dist/src/import/picker-dispatch.js +0 -44
  104. package/dist/src/import/print-prompt.js +0 -31
  105. package/dist/src/import/strip-allowed-components.js +0 -22
  106. package/dist/src/import/tui/CustomPromptBanner.js +0 -8
  107. package/dist/src/import/tui/WizardApp.js +0 -1670
  108. package/dist/src/import/tui/ai-flag.js +0 -6
  109. package/dist/src/import/tui/auto-filter-error.js +0 -29
  110. package/dist/src/import/tui/autocomplete.js +0 -32
  111. package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
  112. package/dist/src/import/tui/components/CounterStrip.js +0 -12
  113. package/dist/src/import/tui/components/LegendEntry.js +0 -6
  114. package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
  115. package/dist/src/import/tui/final-review-host.js +0 -25
  116. package/dist/src/import/tui/group-collapse.js +0 -11
  117. package/dist/src/import/tui/history.js +0 -53
  118. package/dist/src/import/tui/hooks/useLineage.js +0 -50
  119. package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
  120. package/dist/src/import/tui/lineage-layout.js +0 -82
  121. package/dist/src/import/tui/merge-ai-decisions.js +0 -22
  122. package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
  123. package/dist/src/import/tui/push-progress.js +0 -1
  124. package/dist/src/import/tui/run-print-files-helpers.js +0 -8
  125. package/dist/src/import/tui/run-teaser.js +0 -9
  126. package/dist/src/import/tui/runLivePreview.js +0 -82
  127. package/dist/src/import/tui/runScopeGate.js +0 -28
  128. package/dist/src/import/tui/scope-gate-columns.js +0 -94
  129. package/dist/src/import/tui/scope-gate-host.js +0 -28
  130. package/dist/src/import/tui/sidebar-width.js +0 -3
  131. package/dist/src/import/tui/spawn-generate.js +0 -53
  132. package/dist/src/import/tui/step-filters.js +0 -42
  133. package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
  134. package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
  135. package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
  136. package/dist/src/import/tui/steps/DoneStep.js +0 -24
  137. package/dist/src/import/tui/steps/ErrorStep.js +0 -16
  138. package/dist/src/import/tui/steps/GateStep.js +0 -22
  139. package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
  140. package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
  141. package/dist/src/import/tui/steps/PreviewStep.js +0 -37
  142. package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
  143. package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
  144. package/dist/src/import/tui/steps/PushingStep.js +0 -24
  145. package/dist/src/import/tui/steps/RunningStep.js +0 -21
  146. package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
  147. package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
  148. package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
  149. package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
  150. package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
  151. package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
  152. package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
  153. package/dist/src/import/tui/steps/preview-diff.js +0 -175
  154. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  155. package/dist/src/import/tui/useLivePreview.js +0 -122
  156. package/dist/src/import/tui/wizard-422-helpers.js +0 -60
  157. package/dist/src/import/tui/wizard-generate-progress.js +0 -25
  158. package/dist/src/import/tui/wizard-save-flow.js +0 -20
  159. package/dist/src/import/tui/wizard-state-transitions.js +0 -32
  160. package/dist/src/lib/agent-model-options.js +0 -15
  161. package/dist/src/lib/command-options.js +0 -30
  162. package/dist/src/lib/composition-mode.js +0 -39
  163. package/dist/src/lib/contentful-urls.js +0 -27
  164. package/dist/src/lib/debug-logger.js +0 -229
  165. package/dist/src/lib/debug-preamble.js +0 -33
  166. package/dist/src/lib/error-parser.js +0 -269
  167. package/dist/src/lib/prompt-overrides.js +0 -69
  168. package/dist/src/lib/terminal-capabilities.js +0 -51
  169. package/dist/src/lib/user-agent.js +0 -29
  170. package/dist/src/output/format.js +0 -110
  171. package/dist/src/print/command.js +0 -221
  172. package/dist/src/print/validate/tui/ValidateView.js +0 -37
  173. package/dist/src/print/validate/validators/cdf-validator.js +0 -104
  174. package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
  175. package/dist/src/print/validate/validators/format-errors.js +0 -18
  176. package/dist/src/program.js +0 -105
  177. package/dist/src/runs/export-helpers.js +0 -59
  178. package/dist/src/runs/fingerprint.js +0 -75
  179. package/dist/src/runs/ls-command.js +0 -137
  180. package/dist/src/runs/modify-launcher.js +0 -42
  181. package/dist/src/runs/path-prompt.js +0 -72
  182. package/dist/src/runs/push-creds-prompt.js +0 -39
  183. package/dist/src/runs/push-helpers.js +0 -72
  184. package/dist/src/runs/push-launcher.js +0 -77
  185. package/dist/src/runs/replay-helpers.js +0 -164
  186. package/dist/src/runs/resolve-run-target.js +0 -45
  187. package/dist/src/runs/run-picker-mount.js +0 -72
  188. package/dist/src/runs/run-picker.js +0 -129
  189. package/dist/src/runs/save-conflict.js +0 -56
  190. package/dist/src/runs/save-path-resolver.js +0 -75
  191. package/dist/src/runs/staleness.js +0 -140
  192. package/dist/src/runs/store.js +0 -136
  193. package/dist/src/session/cache-keys.js +0 -44
  194. package/dist/src/session/command.js +0 -261
  195. package/dist/src/session/db.js +0 -1538
  196. package/dist/src/session/migration.js +0 -117
  197. package/dist/src/session/session-id.js +0 -212
  198. package/dist/src/session/stats.js +0 -89
  199. package/dist/src/setup/analytics-prompt.js +0 -24
  200. package/dist/src/setup/auto-filter-prompt.js +0 -24
  201. package/dist/src/setup/command.js +0 -966
  202. package/dist/src/setup/composition-mode-prompt.js +0 -37
  203. package/dist/src/setup/debug-mode-prompt.js +0 -22
  204. package/dist/src/types.js +0 -1
@@ -1,597 +0,0 @@
1
- import { createElement } from 'react';
2
- import { render } from 'ink';
3
- import { access, readFile, readdir, stat } from 'node:fs/promises';
4
- import { join, resolve } from 'node:path';
5
- import { execFile } from 'node:child_process';
6
- import { promisify } from 'node:util';
7
- import { AGENT_NAMES, createLocalCliAgentInvoker, describeAgentFailure, formatCustomPromptBanner, formatGenerateProgressLine, isAgentName, parseToolCallLines, parseTokenToolCallLines, resolveBinary, resolveSkillPath, buildPrompt, } from '@contentful/experience-design-system-generation';
8
- import { OutputFormatter, c } from '../output/format.js';
9
- import { getDebugLogger } from '../lib/debug-logger.js';
10
- import { GenerateView } from './tui/GenerateView.js';
11
- import { registerGenerateEditCommand } from './edit/command.js';
12
- import { openPipelineDb, loadRawComponents, applyToolCalls, applyTokenToolCalls, computeComponentInputHash, computeTokenInputHash, lookupCache, lookupCacheByEntity, storeCache, copyComponentFromCache, copyTokensFromCache, renameEmptySlots, } from '../session/db.js';
13
- import { hashPromptForSkill } from '../session/cache-keys.js';
14
- import { getRefineArtifactsRoot, getRefineSessionPaths } from '../analyze/select/persistence.js';
15
- import { readExperiencesCredentials } from '../credentials-store.js';
16
- import { addAgentModelOptions } from '../lib/agent-model-options.js';
17
- import { bindAnalyticsSessionId, exitWithAnalytics } from '../analytics/index.js';
18
- const execFileAsync = promisify(execFile);
19
- const DEFAULT_TIMEOUT_MS = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 3 * 60 * 1000);
20
- const DEFAULT_COMPONENT_CONCURRENCY = 10;
21
- const RETRY_BACKOFF_MS = Number(process.env.EDS_RETRY_BACKOFF_MS ?? 5_000);
22
- const invoker = createLocalCliAgentInvoker({
23
- onDebugEvent: (name, payload) => getDebugLogger().event('agent', name, payload),
24
- });
25
- function die(message) {
26
- process.stderr.write(`${message}\n`);
27
- void exitWithAnalytics(1);
28
- throw new Error('exit');
29
- }
30
- async function pathExists(p) {
31
- return access(p)
32
- .then(() => true)
33
- .catch(() => false);
34
- }
35
- async function assertFileExists(flag, p) {
36
- if (!(await pathExists(p)))
37
- die(`Error: file not found: ${p} (from ${flag})`);
38
- }
39
- async function readFileInline(path) {
40
- if (!path)
41
- return undefined;
42
- const resolved = resolve(path);
43
- let s;
44
- try {
45
- s = await stat(resolved);
46
- }
47
- catch {
48
- return undefined;
49
- }
50
- if (!s.isDirectory())
51
- return readFile(resolved, 'utf8');
52
- // Directory: collect and concatenate all JSON files
53
- const files = [];
54
- async function walk(dir) {
55
- let entries;
56
- try {
57
- entries = await readdir(dir);
58
- }
59
- catch {
60
- return;
61
- }
62
- for (const entry of entries.sort()) {
63
- const full = join(dir, entry);
64
- let es;
65
- try {
66
- es = await stat(full);
67
- }
68
- catch {
69
- continue;
70
- }
71
- if (es.isDirectory()) {
72
- await walk(full);
73
- }
74
- else if (entry.endsWith('.json')) {
75
- files.push(full);
76
- }
77
- }
78
- }
79
- await walk(resolved);
80
- if (files.length === 0)
81
- return undefined;
82
- const parts = await Promise.all(files.map((f) => readFile(f, 'utf8').catch(() => '')));
83
- return parts.filter(Boolean).join('\n\n');
84
- }
85
- async function assertBinaryInPath(binary) {
86
- try {
87
- await execFileAsync('which', [binary]);
88
- return true;
89
- }
90
- catch {
91
- return false;
92
- }
93
- }
94
- function printFallbackInstructions(options) {
95
- const binary = resolveBinary(options.agent);
96
- const skillPath = resolveSkillPath(options.skill);
97
- const lines = [
98
- `Error: agent '${options.agent}' not found in $PATH (looked for binary: ${binary}).`,
99
- `Install it or use one of: claude, codex, opencode, cursor`,
100
- ``,
101
- `To run the generation step manually:`,
102
- ``,
103
- ` 1. Open your coding agent`,
104
- ` 2. Run this skill (all input data will be embedded inline):`,
105
- ` ${skillPath}`,
106
- ``,
107
- ` Use --dry-run to print the full prompt including all inline data.`,
108
- ];
109
- lines.push(``, ` When done, the agent output must be stored in the session database.`);
110
- lines.push(` Re-run the generate command with the agent available, or use --dry-run to inspect the prompt.`);
111
- process.stderr.write(lines.join('\n') + '\n');
112
- }
113
- async function runOneComponent(agent, model, db, sessionId, component, tokensInline, tokenMapInline, index, total, verbose, noCache, skillPathOverride, promptHash) {
114
- const pos = c.dim(`[${index + 1}/${total}]`);
115
- if (!noCache) {
116
- const inputHash = computeComponentInputHash(component);
117
- const cached = lookupCache(db, inputHash, 'component', component.component_id, promptHash);
118
- if (cached) {
119
- copyComponentFromCache(db, cached.sourceSessionId, sessionId, component.component_id);
120
- process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.green('cached')}\n`);
121
- return {
122
- componentName: component.name,
123
- classified: 0,
124
- excluded: 0,
125
- slots: 0,
126
- warnings: [],
127
- failed: false,
128
- cached: true,
129
- renamedSlotsCount: 0,
130
- };
131
- }
132
- // Check for pinned (human-edited) entry with a different hash
133
- const pinned = lookupCacheByEntity(db, 'component', component.component_id);
134
- if (pinned?.humanEdited) {
135
- copyComponentFromCache(db, pinned.sourceSessionId, sessionId, component.component_id);
136
- process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.cyan('pinned (human-edited)')}\n`);
137
- return {
138
- componentName: component.name,
139
- classified: 0,
140
- excluded: 0,
141
- slots: 0,
142
- warnings: [`${component.name}: source changed but human edits preserved`],
143
- failed: false,
144
- cached: true,
145
- renamedSlotsCount: 0,
146
- };
147
- }
148
- }
149
- // Rename empty-named slots in the DB and patch the in-memory component so the
150
- // prompt sees the heuristic names. applyToolCalls matches by name — a row with
151
- // name="" would never be reachable by a classify_slot call otherwise.
152
- const { renames, warnings: renameWarnings } = renameEmptySlots(db, sessionId, component.component_id, component.name, component.slots.length);
153
- let effectiveSlots = component.slots;
154
- if (renames.length > 0) {
155
- const renameMap = new Map(renames.map((r) => [r.oldName, r.newName]));
156
- effectiveSlots = component.slots.map((s) => (renameMap.has(s.name) ? { ...s, name: renameMap.get(s.name) } : s));
157
- for (const w of renameWarnings)
158
- process.stderr.write(` ${c.yellow('⚠')} ${w}\n`);
159
- }
160
- const rawComponentsInline = JSON.stringify([
161
- {
162
- name: component.name,
163
- source: component.source,
164
- framework: component.framework,
165
- props: component.props,
166
- slots: effectiveSlots,
167
- },
168
- ], null, 2);
169
- const prompt = await buildPrompt({
170
- skill: 'components',
171
- mode: 'autonomous',
172
- rawComponentsInline,
173
- tokensInline,
174
- tokenMapInline,
175
- outDir: process.cwd(),
176
- componentName: component.name,
177
- skillPathOverride,
178
- });
179
- const maxAttempts = 2;
180
- let lastError = '';
181
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
182
- if (attempt > 1)
183
- await new Promise((res) => setTimeout(res, RETRY_BACKOFF_MS));
184
- let outputBuf = '';
185
- const formatter = new OutputFormatter(verbose, (s) => {
186
- outputBuf += s;
187
- });
188
- const result = await invoker.invoke({
189
- agent,
190
- model,
191
- prompt,
192
- timeoutMs: DEFAULT_TIMEOUT_MS,
193
- onOutput: (chunk) => formatter.push(chunk),
194
- });
195
- formatter.flush();
196
- // Write header + all tool-call output as one block so concurrent workers don't interleave.
197
- const retryNote = attempt > 1 ? ` ${c.yellow(`retrying (${attempt}/${maxAttempts})`)}` : '';
198
- process.stderr.write(` ${pos} ${c.bold(component.name)}${retryNote}\n${outputBuf}`);
199
- if (result.timedOut) {
200
- // Don't retry timeouts — same timeout will hit again
201
- return {
202
- componentName: component.name,
203
- classified: 0,
204
- excluded: 0,
205
- slots: 0,
206
- warnings: [],
207
- failed: true,
208
- error: `timed out after ${DEFAULT_TIMEOUT_MS / 60000} minutes`,
209
- renamedSlotsCount: renames.length,
210
- };
211
- }
212
- if (result.exitCode !== 0) {
213
- lastError = describeAgentFailure(result);
214
- continue;
215
- }
216
- const { calls, warnings } = parseToolCallLines(result.stdout);
217
- if (calls.length === 0) {
218
- lastError = describeAgentFailure(result);
219
- continue;
220
- }
221
- const applied = applyToolCalls(db, sessionId, component.component_id, component.name, calls, warnings);
222
- if (!noCache) {
223
- const inputHash = computeComponentInputHash(component);
224
- storeCache(db, inputHash, 'component', component.component_id, sessionId, false, promptHash);
225
- }
226
- return {
227
- componentName: component.name,
228
- classified: applied.classified,
229
- excluded: applied.excluded,
230
- slots: applied.slots,
231
- warnings: applied.warnings,
232
- failed: false,
233
- renamedSlotsCount: renames.length,
234
- };
235
- }
236
- return {
237
- componentName: component.name,
238
- classified: 0,
239
- excluded: 0,
240
- slots: 0,
241
- warnings: [],
242
- failed: true,
243
- error: lastError,
244
- renamedSlotsCount: renames.length,
245
- };
246
- }
247
- async function runAllComponents(agent, model, db, sessionId, components, tokensInline, tokenMapInline, verbose, noCache, skillPathOverride, promptHash) {
248
- const concurrency = Number(process.env.EDS_GENERATE_CONCURRENCY ?? DEFAULT_COMPONENT_CONCURRENCY);
249
- process.stderr.write(`Categorizing ${c.bold(String(components.length))} component${components.length === 1 ? '' : 's'}` +
250
- c.dim(` (concurrency: ${concurrency})`) +
251
- '\n');
252
- const results = new Array(components.length);
253
- let next = 0;
254
- let completed = 0;
255
- async function worker() {
256
- while (next < components.length) {
257
- const i = next++;
258
- results[i] = await runOneComponent(agent, model, db, sessionId, components[i], tokensInline, tokenMapInline, i, components.length, verbose, noCache, skillPathOverride, promptHash);
259
- completed += 1;
260
- process.stderr.write(`${formatGenerateProgressLine(completed, components.length, results[i].componentName)}\n`);
261
- }
262
- }
263
- await Promise.all(Array.from({ length: Math.min(concurrency, components.length) }, worker));
264
- return results;
265
- }
266
- function resolveSessionId(sessionFlag) {
267
- if (sessionFlag)
268
- return sessionFlag;
269
- const db = openPipelineDb();
270
- try {
271
- const row = db
272
- .prepare(`SELECT s.id FROM sessions s
273
- JOIN steps st ON st.session_id = s.id
274
- WHERE st.command = 'analyze extract'
275
- AND st.status = 'complete'
276
- ORDER BY st.started_at DESC
277
- LIMIT 1`)
278
- .get();
279
- if (!row) {
280
- process.stderr.write('Error: no completed analyze extract session found. Run analyze extract first, or pass --session <id>.\n');
281
- void exitWithAnalytics(1);
282
- throw new Error('exit');
283
- }
284
- return row.id;
285
- }
286
- finally {
287
- db.close();
288
- }
289
- }
290
- async function loadAcceptedNames(sessionId) {
291
- try {
292
- const artifactsRoot = getRefineArtifactsRoot();
293
- const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
294
- const raw = await readFile(paths.statePath, 'utf8');
295
- const snapshot = JSON.parse(raw);
296
- const accepted = snapshot.components.filter((c) => c.status === 'accepted').map((c) => c.name);
297
- if (accepted.length === 0)
298
- return null;
299
- return new Set(accepted);
300
- }
301
- catch {
302
- return null;
303
- }
304
- }
305
- async function runGenerateSkill(skill, opts, verbose = false) {
306
- const savedCreds = await readExperiencesCredentials();
307
- const agentName = opts.agent ?? savedCreds.agent;
308
- const model = opts.model ?? savedCreds.agentModel;
309
- if (!agentName || !isAgentName(agentName)) {
310
- die(`Error: no agent configured. Pass --agent <name> or run experiences setup. Accepted values: ${AGENT_NAMES.join(', ')}`);
311
- }
312
- const agent = agentName;
313
- // Feature 8: resolve custom-prompt path for `components` (flag wins over
314
- // saved credentials), validate, and emit the warning banner once at action
315
- // entry.
316
- const generatePromptPath = skill === 'components' ? (opts.generatePromptPath ?? savedCreds.generatePromptPath) : undefined;
317
- if (generatePromptPath) {
318
- if (!(await pathExists(resolve(generatePromptPath)))) {
319
- die(`Error: custom prompt path not found: ${resolve(generatePromptPath)}`);
320
- }
321
- if (!generatePromptPath.toLowerCase().endsWith('.md')) {
322
- process.stderr.write(`WARNING: custom prompt path does not end in .md (${generatePromptPath}) — proceeding anyway.\n`);
323
- }
324
- process.stderr.write(formatCustomPromptBanner('components', resolve(generatePromptPath)));
325
- }
326
- if (skill === 'tokens' && !opts.rawTokens) {
327
- die('Error: --raw-tokens is required when using generate tokens');
328
- }
329
- if (opts.rawTokens)
330
- await assertFileExists('--raw-tokens', opts.rawTokens);
331
- if (opts.tokens)
332
- await assertFileExists('--tokens', opts.tokens);
333
- if (opts.tokenMap)
334
- await assertFileExists('--token-map', opts.tokenMap);
335
- // Read all token files inline so the agent never needs to read files itself
336
- const [rawTokensInline, tokensInline, tokenMapInline] = await Promise.all([
337
- readFileInline(opts.rawTokens),
338
- readFileInline(opts.tokens),
339
- readFileInline(opts.tokenMap),
340
- ]);
341
- // Load raw components from DB for the components skill
342
- let sessionId;
343
- let allComponents;
344
- if (skill === 'components') {
345
- sessionId = resolveSessionId(opts.session);
346
- await bindAnalyticsSessionId(sessionId);
347
- const acceptedNames = await loadAcceptedNames(sessionId);
348
- const db = openPipelineDb();
349
- try {
350
- allComponents = loadRawComponents(db, sessionId, acceptedNames ?? undefined);
351
- }
352
- finally {
353
- db.close();
354
- }
355
- if (allComponents.length === 0) {
356
- die(`Error: session '${sessionId}' has no raw components. Run analyze extract first.`);
357
- }
358
- if (acceptedNames) {
359
- process.stderr.write(`Scope: ${allComponents.length} accepted component(s) from analyze select\n`);
360
- }
361
- // Warn about duplicate component names — later occurrences will overwrite earlier ones
362
- const nameCounts = new Map();
363
- for (const c of allComponents) {
364
- const sources = nameCounts.get(c.name) ?? [];
365
- sources.push(c.source);
366
- nameCounts.set(c.name, sources);
367
- }
368
- const dupes = [...nameCounts.entries()].filter(([, srcs]) => srcs.length > 1);
369
- if (dupes.length > 0) {
370
- process.stderr.write(`Warning: ${dupes.length} duplicate component name(s) detected — only the last occurrence will be generated:\n`);
371
- for (const [name, sources] of dupes) {
372
- process.stderr.write(` ${name}:\n`);
373
- for (const src of sources)
374
- process.stderr.write(` ${src}\n`);
375
- }
376
- }
377
- }
378
- if (opts.dryRun) {
379
- const sampleComponent = allComponents?.[0];
380
- const sampleInline = sampleComponent
381
- ? JSON.stringify([
382
- {
383
- name: sampleComponent.name,
384
- source: sampleComponent.source,
385
- framework: sampleComponent.framework,
386
- props: sampleComponent.props,
387
- slots: sampleComponent.slots,
388
- },
389
- ], null, 2)
390
- : undefined;
391
- const prompt = await buildPrompt({
392
- skill,
393
- mode: 'autonomous',
394
- rawComponentsInline: sampleInline ?? rawTokensInline,
395
- rawTokensInline: skill === 'tokens' ? rawTokensInline : undefined,
396
- rawTokensFilename: opts.rawTokens ? resolve(opts.rawTokens).split('/').pop() : undefined,
397
- tokensInline,
398
- tokenMapInline,
399
- outDir: process.cwd(),
400
- skillPathOverride: generatePromptPath,
401
- });
402
- process.stdout.write(prompt + '\n');
403
- await exitWithAnalytics(0);
404
- }
405
- const binary = resolveBinary(agent);
406
- if (!(await assertBinaryInPath(binary))) {
407
- printFallbackInstructions({
408
- agent,
409
- skill,
410
- sessionId: sessionId ?? '',
411
- });
412
- await exitWithAnalytics(1);
413
- }
414
- if (skill === 'components' && allComponents && sessionId) {
415
- const db = openPipelineDb();
416
- let componentResults;
417
- try {
418
- const promptHash = await hashPromptForSkill('components', agent, model, generatePromptPath);
419
- componentResults = await runAllComponents(agent, model, db, sessionId, allComponents, tokensInline, tokenMapInline, verbose, opts.cache === false || process.env.EDS_NO_CACHE === '1', generatePromptPath, promptHash);
420
- }
421
- finally {
422
- db.close();
423
- }
424
- const failed = componentResults.filter((r) => r.failed);
425
- const cachedResults = componentResults.filter((r) => r.cached);
426
- const generated = componentResults.filter((r) => !r.failed && !r.cached);
427
- const allWarnings = componentResults.flatMap((r) => r.warnings.map((w) => ` ${r.componentName}: ${w}`));
428
- if (allWarnings.length > 0) {
429
- process.stderr.write(c.yellow('Warnings:') + '\n' + allWarnings.join('\n') + '\n');
430
- }
431
- if (failed.length > 0) {
432
- process.stderr.write(c.red(`Failed (${failed.length}/${componentResults.length}):`) + '\n');
433
- for (const f of failed) {
434
- process.stderr.write(` ${c.red('✗')} ${f.componentName} ${c.dim(f.error ?? 'unknown error')}\n`);
435
- }
436
- }
437
- const totalClassified = generated.reduce((s, r) => s + r.classified, 0);
438
- const totalExcluded = generated.reduce((s, r) => s + r.excluded, 0);
439
- const totalRenamedSlots = componentResults.reduce((s, r) => s + r.renamedSlotsCount, 0);
440
- const allOk = failed.length === 0;
441
- const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : '';
442
- process.stderr.write((allOk ? c.green('✓') : c.yellow('⚠')) +
443
- ` ${generated.length + cachedResults.length}/${componentResults.length} components` +
444
- cachedNote +
445
- c.dim(` ${totalClassified} classified, ${totalExcluded} excluded`) +
446
- '\n');
447
- // Machine-parseable summary on stdout for the wizard / orchestrator.
448
- process.stdout.write(`renamed-slots: ${totalRenamedSlots}\n`);
449
- if (generated.length === 0 && cachedResults.length === 0) {
450
- die(`Error: all ${componentResults.length} component(s) failed to generate — see the per-component errors above.`);
451
- }
452
- }
453
- else if (skill === 'tokens') {
454
- const noCache = opts.cache === false || process.env.EDS_NO_CACHE === '1';
455
- const tokenInputContent = rawTokensInline ?? '';
456
- const tokenInputHash = computeTokenInputHash(tokenInputContent);
457
- const db = openPipelineDb();
458
- try {
459
- let resolvedSessionId = opts.session;
460
- if (!resolvedSessionId) {
461
- const s = db
462
- .prepare(`SELECT s.id FROM sessions s
463
- JOIN steps st ON st.session_id = s.id
464
- WHERE st.command = 'analyze extract' AND st.status = 'complete'
465
- ORDER BY st.started_at DESC LIMIT 1`)
466
- .get();
467
- if (s) {
468
- resolvedSessionId = s.id;
469
- }
470
- else {
471
- const { generateSessionId } = await import('../session/session-id.js');
472
- const newId = generateSessionId();
473
- const now = new Date().toISOString();
474
- db.prepare('INSERT INTO sessions (id, name, created_at, updated_at) VALUES (?, NULL, ?, ?)').run(newId, now, now);
475
- resolvedSessionId = newId;
476
- }
477
- }
478
- sessionId = resolvedSessionId;
479
- await bindAnalyticsSessionId(resolvedSessionId);
480
- const tokenPromptHash = await hashPromptForSkill('tokens', agent, model);
481
- // Check cache before invoking agent
482
- if (!noCache) {
483
- const tokenCached = lookupCache(db, tokenInputHash, 'token_set', '__tokens__', tokenPromptHash);
484
- if (tokenCached) {
485
- copyTokensFromCache(db, tokenCached.sourceSessionId, resolvedSessionId);
486
- sessionId = resolvedSessionId;
487
- process.stderr.write(`Done: tokens reused from cache ${c.dim(`(source: ${tokenCached.sourceSessionId.slice(0, 12)})`)}\n`);
488
- db.close();
489
- // Skip agent invocation — jump to view
490
- const viewResult = { skill, agent, sessionId: sessionId ?? '' };
491
- if (process.stdout.isTTY) {
492
- const { waitUntilExit } = render(createElement(GenerateView, { result: viewResult, onExit: () => void exitWithAnalytics(0) }));
493
- await waitUntilExit();
494
- }
495
- else {
496
- process.stdout.write(`generate complete\nskill: ${skill}\nagent: ${agent}\nsession=${sessionId ?? ''}\n`);
497
- await exitWithAnalytics(0);
498
- }
499
- return;
500
- }
501
- }
502
- // Cache miss — invoke agent
503
- const prompt = await buildPrompt({
504
- skill,
505
- mode: 'autonomous',
506
- rawTokensInline,
507
- rawTokensFilename: opts.rawTokens ? resolve(opts.rawTokens).split('/').pop() : undefined,
508
- tokensInline,
509
- tokenMapInline,
510
- outDir: process.cwd(),
511
- });
512
- const result = await invoker.invoke({
513
- agent,
514
- model,
515
- prompt,
516
- timeoutMs: DEFAULT_TIMEOUT_MS * 5,
517
- });
518
- if (result.timedOut) {
519
- die(`Error: agent did not complete within ${(DEFAULT_TIMEOUT_MS * 5) / 60000} minutes`);
520
- }
521
- if (result.exitCode !== 0) {
522
- if (result.stderr)
523
- process.stderr.write(result.stderr);
524
- die(`Error: agent exited with code ${result.exitCode}`);
525
- }
526
- const { calls: tokenCalls, warnings: tokenWarnings } = parseTokenToolCallLines(result.stdout);
527
- const tokenCount = tokenCalls.filter((tc) => tc.tool === 'set_token').length;
528
- if (tokenCount === 0) {
529
- process.stderr.write(`Error: agent produced no set_token calls.\n` +
530
- `Run with --dry-run to inspect the prompt.\n\n` +
531
- `Agent output:\n${result.stdout}\n`);
532
- await exitWithAnalytics(1);
533
- }
534
- if (tokenWarnings.length > 0) {
535
- process.stderr.write(`Warnings:\n${tokenWarnings.map((w) => ` ${w}`).join('\n')}\n`);
536
- }
537
- applyTokenToolCalls(db, resolvedSessionId, tokenCalls, []);
538
- if (!noCache) {
539
- storeCache(db, tokenInputHash, 'token_set', '__tokens__', resolvedSessionId, false, tokenPromptHash);
540
- }
541
- sessionId = resolvedSessionId;
542
- const groupCount = tokenCalls.filter((tc) => tc.tool === 'set_group').length;
543
- process.stderr.write(`Done: ${tokenCount} tokens, ${groupCount} groups stored\n`);
544
- }
545
- finally {
546
- db.close();
547
- }
548
- }
549
- const viewResult = {
550
- skill,
551
- agent,
552
- sessionId: sessionId ?? '',
553
- };
554
- if (process.stdout.isTTY) {
555
- const { waitUntilExit } = render(createElement(GenerateView, {
556
- result: viewResult,
557
- onExit: () => void exitWithAnalytics(0),
558
- }));
559
- await waitUntilExit();
560
- }
561
- else {
562
- process.stdout.write(`generate complete\nskill: ${skill}\nagent: ${agent}\nsession=${sessionId ?? ''}\n`);
563
- await exitWithAnalytics(0);
564
- }
565
- }
566
- function addAgentFlags(cmd) {
567
- return addAgentModelOptions(cmd)
568
- .option('--verbose', 'Show full agent output including reasoning text')
569
- .option('--dry-run', 'Print the prompt without invoking the agent')
570
- .option('--no-cache', 'Bypass ALL fine-grained caches (extract, select, generate) and force AI re-run. ' +
571
- 'Cache keys now factor in prompt content — changing the prompt file via --generate-prompt-path or ' +
572
- '--select-prompt-path will already bust the corresponding stage. Use --no-cache to force a full re-run.');
573
- }
574
- export function registerGenerateCommand(program) {
575
- const generate = program.command('generate').description('Generate CDF/DTCG artifacts or correct generation output');
576
- // generate components subcommand
577
- const componentsCmd = generate
578
- .command('components')
579
- .description('Invoke a coding agent to produce components.json from raw analysis output')
580
- .option('--session <id>', 'Session ID from analyze extract (defaults to most recent)')
581
- .option('--tokens <path>', 'Path to tokens.json for token-linked prop resolution')
582
- .option('--token-map <path>', 'Path to token-name-map.json sidecar')
583
- .option('--generate-prompt-path <path>', 'Path to a custom .md skill prompt for components generation (bypasses bundled prompt invariants)');
584
- addAgentFlags(componentsCmd).action(async (opts) => {
585
- await runGenerateSkill('components', opts, opts.verbose ?? false);
586
- });
587
- registerGenerateEditCommand(componentsCmd, 'components');
588
- // generate tokens subcommand
589
- const tokensCmd = generate
590
- .command('tokens')
591
- .description('Invoke a coding agent to produce tokens.json from raw token data')
592
- .option('--raw-tokens <path>', 'Path to raw token input file');
593
- addAgentFlags(tokensCmd).action(async (opts) => {
594
- await runGenerateSkill('tokens', opts, opts.verbose ?? false);
595
- });
596
- registerGenerateEditCommand(tokensCmd, 'tokens');
597
- }