@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,599 +0,0 @@
|
|
|
1
|
-
import { createElement } from 'react';
|
|
2
|
-
import { render } from 'ink';
|
|
3
|
-
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
5
|
-
import { addAgentModelOptions } from '../lib/agent-model-options.js';
|
|
6
|
-
import { extractComponents, preClassifyComponent, isNonAuthorableComponent, computeExtractionScore, deriveNeedsReview, describeReviewReasons, inspectComponentSource, validateExtractedComponents, } from '@contentful/experience-design-system-extraction';
|
|
7
|
-
import { AnalyzeView } from './tui/AnalyzeView.js';
|
|
8
|
-
import { registerAnalyzeEditCommand } from './select/command.js';
|
|
9
|
-
import { registerAnalyzeSelectAgentCommand } from './select-agent/command.js';
|
|
10
|
-
import { openPipelineDb, getOrCreateSession, createStep, updateStep, storeRawComponents, storeScannedFiles, storeSlotCycles, getCliCacheVersion, lookupCompositionCache, storeCompositionCache, } from '../session/db.js';
|
|
11
|
-
import { findSlotCycles, suggestCycleBreakEdge } from './cycle-detection.js';
|
|
12
|
-
import { resolveCompositionMode } from '../lib/composition-mode.js';
|
|
13
|
-
import { resolveMapping } from './composition/resolve-mapping.js';
|
|
14
|
-
import { loadUserMap, resolveCompositionSources } from './composition/resolve-mapping-cli.js';
|
|
15
|
-
import { selectCandidateFiles, capCandidatesToPromptBudget } from './composition/candidate-files.js';
|
|
16
|
-
import { critiqueCandidates } from './composition/candidate-critic.js';
|
|
17
|
-
import { buildDirCriticPrompt, parseDirCriticReply } from './composition/candidate-critic-agent.js';
|
|
18
|
-
import { buildCompositionInputHash } from './composition/composition-cache-key.js';
|
|
19
|
-
import { collectManifestDocEdges } from './composition/manifest-doc-evidence.js';
|
|
20
|
-
import { runParserInSandbox } from './composition/agent-parser/sandbox.js';
|
|
21
|
-
import { resolveViaAgentParser } from './composition/agent-parser/resolve-via-parser.js';
|
|
22
|
-
import { parsePromptOverrides, resolvePromptOverride } from '../lib/prompt-overrides.js';
|
|
23
|
-
import { DEFAULT_AGENT_NAME, isAgentName, runAgent, } from '@contentful/experience-design-system-generation';
|
|
24
|
-
import { readExperiencesCredentials } from '../credentials-store.js';
|
|
25
|
-
import { buildAnalyzeViewRows, partitionGlobalWarnings } from './build-analyze-view-rows.js';
|
|
26
|
-
import { getInteractiveTerminalSupport } from '../lib/terminal-capabilities.js';
|
|
27
|
-
import { bindAnalyticsSessionId, emitSessionStarted, enrichCommandResult, exitWithAnalytics, isPipelineAnalyticsChild, } from '../analytics/index.js';
|
|
28
|
-
import { getDebugLogger } from '../lib/debug-logger.js';
|
|
29
|
-
const SCANNED_FILE_EXTENSIONS = new Set(['.astro', '.js', '.jsx', '.svelte', '.ts', '.tsx', '.vue']);
|
|
30
|
-
/**
|
|
31
|
-
* `.json`/`.md` are scanned too (Figma `manifest.json`, `AGENTS.md`-style
|
|
32
|
-
* docs, and other design/composition-adjacent files we don't yet have a name
|
|
33
|
-
* for) — gated by a denylist rather than an allowlist, so coverage isn't
|
|
34
|
-
* capped at a couple of exact filenames. Their content is never inlined into
|
|
35
|
-
* an LLM prompt by virtue of being scanned here; that's a separate gate (see
|
|
36
|
-
* `selectCandidateFiles` in candidate-files.ts) which still only admits files
|
|
37
|
-
* matching its own name/content-marker heuristics. Deterministic parsing
|
|
38
|
-
* (manifest-doc-evidence.ts) reads this full set directly, with no LLM
|
|
39
|
-
* involved, which is the actual prompt-injection safeguard for that signal.
|
|
40
|
-
*/
|
|
41
|
-
const DENYLIST_GATED_EXTENSIONS = new Set(['.json', '.md']);
|
|
42
|
-
const DENYLISTED_EXACT_FILE_NAMES = new Set([
|
|
43
|
-
'package.json',
|
|
44
|
-
'package-lock.json',
|
|
45
|
-
'npm-shrinkwrap.json',
|
|
46
|
-
'nx.json',
|
|
47
|
-
'project.json',
|
|
48
|
-
'turbo.json',
|
|
49
|
-
'lerna.json',
|
|
50
|
-
'jsconfig.json',
|
|
51
|
-
]);
|
|
52
|
-
/** Config-file families that vary by suffix (`tsconfig.build.json`, `.eslintrc.cjs.json`, ...) plus common repo docs. */
|
|
53
|
-
const DENYLISTED_FILE_NAME_PATTERNS = [
|
|
54
|
-
/^tsconfig(\..+)?\.json$/,
|
|
55
|
-
/^\.?eslintrc(\..+)?\.json$/,
|
|
56
|
-
/^\.?prettierrc(\..+)?\.json$/,
|
|
57
|
-
/^(readme|changelog|contributing|code_of_conduct|license|security)(\..+)?\.md$/i,
|
|
58
|
-
];
|
|
59
|
-
function isDenylistedNoiseFile(name) {
|
|
60
|
-
return DENYLISTED_EXACT_FILE_NAMES.has(name) || DENYLISTED_FILE_NAME_PATTERNS.some((pattern) => pattern.test(name));
|
|
61
|
-
}
|
|
62
|
-
const IGNORED_DIRECTORY_NAMES = new Set([
|
|
63
|
-
'.changeset',
|
|
64
|
-
'.git',
|
|
65
|
-
'.github',
|
|
66
|
-
'.idea',
|
|
67
|
-
'.next',
|
|
68
|
-
'.nuxt',
|
|
69
|
-
'.vscode',
|
|
70
|
-
'build',
|
|
71
|
-
'coverage',
|
|
72
|
-
'demo',
|
|
73
|
-
'demos',
|
|
74
|
-
'dist',
|
|
75
|
-
'example',
|
|
76
|
-
'examples',
|
|
77
|
-
'node_modules',
|
|
78
|
-
'out',
|
|
79
|
-
'storybook-static',
|
|
80
|
-
]);
|
|
81
|
-
const IGNORED_FILE_SUFFIXES = new Set([
|
|
82
|
-
'.stories.ts',
|
|
83
|
-
'.stories.tsx',
|
|
84
|
-
'.stories.js',
|
|
85
|
-
'.stories.jsx',
|
|
86
|
-
'.story.ts',
|
|
87
|
-
'.story.tsx',
|
|
88
|
-
'.story.js',
|
|
89
|
-
'.story.jsx',
|
|
90
|
-
'.spec.ts',
|
|
91
|
-
'.spec.tsx',
|
|
92
|
-
'.test.ts',
|
|
93
|
-
'.test.tsx',
|
|
94
|
-
]);
|
|
95
|
-
function pluralize(count, singular, plural = `${singular}s`) {
|
|
96
|
-
return `${count} ${count === 1 ? singular : plural}`;
|
|
97
|
-
}
|
|
98
|
-
function resolveFromProjectRoot(projectRoot, inputPath) {
|
|
99
|
-
return isAbsolute(inputPath) ? inputPath : resolve(projectRoot, inputPath);
|
|
100
|
-
}
|
|
101
|
-
function wrapperConfidenceToIssueCount(confidence) {
|
|
102
|
-
if (confidence >= 4)
|
|
103
|
-
return 2;
|
|
104
|
-
if (confidence === 3)
|
|
105
|
-
return 1;
|
|
106
|
-
return 0;
|
|
107
|
-
}
|
|
108
|
-
async function pathExists(path) {
|
|
109
|
-
return Boolean(await stat(path).catch(() => null));
|
|
110
|
-
}
|
|
111
|
-
export async function collectSourceFiles(directory, onProgress) {
|
|
112
|
-
const files = [];
|
|
113
|
-
async function visit(currentDirectory) {
|
|
114
|
-
const entries = await readdir(currentDirectory, { withFileTypes: true });
|
|
115
|
-
const subdirs = [];
|
|
116
|
-
for (const entry of entries) {
|
|
117
|
-
const fullPath = join(currentDirectory, entry.name);
|
|
118
|
-
if (entry.isDirectory()) {
|
|
119
|
-
if (!IGNORED_DIRECTORY_NAMES.has(entry.name)) {
|
|
120
|
-
subdirs.push(fullPath);
|
|
121
|
-
}
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (!entry.isFile()) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
const extension = entry.name.slice(entry.name.lastIndexOf('.'));
|
|
128
|
-
const isCodeFile = SCANNED_FILE_EXTENSIONS.has(extension) && !entry.name.endsWith('.d.ts');
|
|
129
|
-
const isNoiseGatedFile = DENYLIST_GATED_EXTENSIONS.has(extension) && !isDenylistedNoiseFile(entry.name);
|
|
130
|
-
if (!isCodeFile && !isNoiseGatedFile) {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
if ([...IGNORED_FILE_SUFFIXES].some((suffix) => entry.name.endsWith(suffix))) {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
files.push(fullPath);
|
|
137
|
-
onProgress?.(files.length);
|
|
138
|
-
}
|
|
139
|
-
await Promise.all(subdirs.map((subdir) => visit(subdir)));
|
|
140
|
-
}
|
|
141
|
-
await visit(directory);
|
|
142
|
-
return files.sort();
|
|
143
|
-
}
|
|
144
|
-
/** Read the persisted default composition mode; missing config is fine. */
|
|
145
|
-
async function safeReadCompositionMode() {
|
|
146
|
-
try {
|
|
147
|
-
return (await readExperiencesCredentials()).compositionMode;
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
return undefined;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Build a { version, groups } interchange skeleton (spec T1) from the resolved
|
|
155
|
-
* components' slot allowedComponents — reflecting BOTH typed-slot edges the
|
|
156
|
-
* extractor found and anything the mapping resolver added.
|
|
157
|
-
*/
|
|
158
|
-
export function componentsToInterchangeMap(components) {
|
|
159
|
-
const groups = {};
|
|
160
|
-
for (const c of components) {
|
|
161
|
-
const children = new Set();
|
|
162
|
-
for (const slot of c.slots) {
|
|
163
|
-
for (const child of slot.allowedComponents ?? [])
|
|
164
|
-
children.add(child);
|
|
165
|
-
}
|
|
166
|
-
if (children.size > 0)
|
|
167
|
-
groups[c.name] = [...children].sort();
|
|
168
|
-
}
|
|
169
|
-
const sorted = {};
|
|
170
|
-
for (const parent of Object.keys(groups).sort())
|
|
171
|
-
sorted[parent] = groups[parent];
|
|
172
|
-
return { version: 1, groups: sorted };
|
|
173
|
-
}
|
|
174
|
-
/** Resolve which coding-agent runs mapping resolution: `--agent` flag > env > default. */
|
|
175
|
-
function resolveCompositionAgentName(flagValue) {
|
|
176
|
-
if (flagValue && isAgentName(flagValue))
|
|
177
|
-
return flagValue;
|
|
178
|
-
const env = process.env['EDS_COMPOSITION_AGENT'];
|
|
179
|
-
if (env && isAgentName(env))
|
|
180
|
-
return env;
|
|
181
|
-
return DEFAULT_AGENT_NAME;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Read the source files of the extracted components plus nearby mapping/meta
|
|
185
|
-
* files, so the candidate pre-filter (T3) can pick the relevant ones. Reads
|
|
186
|
-
* each unique `sourcePath` once; missing files are skipped.
|
|
187
|
-
*/
|
|
188
|
-
async function readCandidateFiles(components, extraFiles = []) {
|
|
189
|
-
const paths = new Set(extraFiles);
|
|
190
|
-
for (const c of components) {
|
|
191
|
-
if (c.sourcePath)
|
|
192
|
-
paths.add(c.sourcePath);
|
|
193
|
-
}
|
|
194
|
-
const out = [];
|
|
195
|
-
await Promise.all([...paths].map(async (p) => {
|
|
196
|
-
try {
|
|
197
|
-
const content = await readFile(p, 'utf8');
|
|
198
|
-
out.push({ path: p, content });
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
void 0;
|
|
202
|
-
}
|
|
203
|
-
}));
|
|
204
|
-
return out;
|
|
205
|
-
}
|
|
206
|
-
export function registerAnalyzeCommand(program) {
|
|
207
|
-
const analyze = program
|
|
208
|
-
.command('analyze')
|
|
209
|
-
.description('Extract component definitions from a project, or correct analysis output');
|
|
210
|
-
const extractCmd = analyze
|
|
211
|
-
.command('extract')
|
|
212
|
-
.description('Extract component definitions from a project')
|
|
213
|
-
.requiredOption('--project <path>', 'Path to the project root')
|
|
214
|
-
.option('--dir <path>', 'Path to the component source directory relative to the project root')
|
|
215
|
-
.option('--resolve-unreachable <mode>', "Retry pass for unresolved Svelte Props types: 'auto' (default), 'always', or 'never'", 'auto')
|
|
216
|
-
.option('--composite', 'Resolve embedded-component composition (opt in; default is atomic)')
|
|
217
|
-
.option('--atomic', 'Skip composition resolution — flat components only (default)')
|
|
218
|
-
.option('--composition-map <path>', 'Consume a hand-authored parent→children interchange map (implies --composite)')
|
|
219
|
-
.option('--composition-agent', 'Opt into agentic mapping resolution when deterministic sources find no groups (implies --composite)')
|
|
220
|
-
.option('--composition-refresh', 'Force the mapping agent to run even where deterministic sources answered (implies --composite)')
|
|
221
|
-
.option('--generate-map <path>', 'Write a skeleton interchange map from resolved composition (implies --composite)')
|
|
222
|
-
.option('--prompt <stage=value>', 'Override a stage prompt (repeatable). value is a file path or literal text, e.g. --prompt composition=./p.md', (v, acc) => [...acc, v], []);
|
|
223
|
-
addAgentModelOptions(extractCmd, {
|
|
224
|
-
includeModel: false,
|
|
225
|
-
agentDescription: 'Coding agent for composition mapping resolution (claude|codex|opencode|cursor)',
|
|
226
|
-
})
|
|
227
|
-
.option('--composition-agent-mode <mode>', "Agent resolution mode: 'parser' (agent writes a sandboxed parser — deterministic, default) or 'edges' (agent lists edges directly)", 'parser')
|
|
228
|
-
.action(async (opts) => {
|
|
229
|
-
const resolveUnreachable = (() => {
|
|
230
|
-
const v = opts.resolveUnreachable ?? 'auto';
|
|
231
|
-
if (v !== 'auto' && v !== 'always' && v !== 'never') {
|
|
232
|
-
process.stderr.write(`Error: --resolve-unreachable must be one of 'auto', 'always', 'never' (got '${v}')\n`);
|
|
233
|
-
process.exit(1);
|
|
234
|
-
}
|
|
235
|
-
return v;
|
|
236
|
-
})();
|
|
237
|
-
const projectRoot = resolve(opts.project);
|
|
238
|
-
const outDir = join(projectRoot, '.contentful');
|
|
239
|
-
let sourceDirectory;
|
|
240
|
-
if (opts.dir !== undefined) {
|
|
241
|
-
sourceDirectory = resolveFromProjectRoot(projectRoot, opts.dir);
|
|
242
|
-
if (!(await pathExists(sourceDirectory))) {
|
|
243
|
-
process.stderr.write(`Error: source directory does not exist: ${sourceDirectory}\n`);
|
|
244
|
-
process.exit(1);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
const srcPath = resolveFromProjectRoot(projectRoot, 'src');
|
|
249
|
-
sourceDirectory = (await pathExists(srcPath)) ? srcPath : projectRoot;
|
|
250
|
-
}
|
|
251
|
-
const sourceFiles = await collectSourceFiles(sourceDirectory, (count) => {
|
|
252
|
-
if (!process.stdout.isTTY) {
|
|
253
|
-
process.stderr.write(`progress=scan:${count}\n`);
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
const extraction = await extractComponents(sourceFiles, ({ filesProcessed, componentsFound }) => {
|
|
257
|
-
if (!process.stdout.isTTY) {
|
|
258
|
-
process.stderr.write(`progress=extract:${filesProcessed}/${sourceFiles.length}:${componentsFound}\n`);
|
|
259
|
-
}
|
|
260
|
-
}, { resolveUnreachable, projectRoot });
|
|
261
|
-
await mkdir(outDir, { recursive: true });
|
|
262
|
-
const db = openPipelineDb();
|
|
263
|
-
const { sessionId } = getOrCreateSession(db, undefined, undefined, {
|
|
264
|
-
command: 'analyze extract',
|
|
265
|
-
inputPath: projectRoot,
|
|
266
|
-
outDir,
|
|
267
|
-
});
|
|
268
|
-
await bindAnalyticsSessionId(sessionId);
|
|
269
|
-
if (!isPipelineAnalyticsChild()) {
|
|
270
|
-
await emitSessionStarted('analyze_extract');
|
|
271
|
-
}
|
|
272
|
-
const stepId = createStep(db, sessionId, 'analyze extract', {
|
|
273
|
-
project: projectRoot,
|
|
274
|
-
});
|
|
275
|
-
const classifiedComponents = extraction.components.map(preClassifyComponent);
|
|
276
|
-
const inspectedComponents = await Promise.all(classifiedComponents.map(async (component) => ({
|
|
277
|
-
component,
|
|
278
|
-
inspection: await inspectComponentSource(component),
|
|
279
|
-
})));
|
|
280
|
-
const filteredComponents = [];
|
|
281
|
-
const filterWarnings = [];
|
|
282
|
-
for (const { component, inspection } of inspectedComponents) {
|
|
283
|
-
const verdict = isNonAuthorableComponent(component);
|
|
284
|
-
const keepDespiteZeroSurface = verdict.skip && verdict.reason === 'component has no props and no slots' && inspection.keepDespiteZeroSurface;
|
|
285
|
-
const retainedForReview = verdict.skip && !keepDespiteZeroSurface;
|
|
286
|
-
if (retainedForReview) {
|
|
287
|
-
filterWarnings.push(`${component.name}: requires operator review (${verdict.reason})`);
|
|
288
|
-
}
|
|
289
|
-
if (keepDespiteZeroSurface) {
|
|
290
|
-
filterWarnings.push(`${component.name}: retained despite 0 props/slots because the source renders visible or compositional UI`);
|
|
291
|
-
}
|
|
292
|
-
if (inspection.reviewReasons.length > 0) {
|
|
293
|
-
const reviewNotes = describeReviewReasons(inspection.reviewReasons)
|
|
294
|
-
.filter((note) => note !== 'high-confidence data-fetch wrapper')
|
|
295
|
-
.join('; ');
|
|
296
|
-
if (reviewNotes) {
|
|
297
|
-
filterWarnings.push(`${component.name}: ${reviewNotes}`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
// Preserve any extractor-level review reasons (e.g. `props-type-unresolved`
|
|
301
|
-
// from the Svelte parser) by merging them into the post-processing recompute.
|
|
302
|
-
// Without this, recomputing here clobbers the per-extractor signal.
|
|
303
|
-
const extractorReasons = component.reviewReasons ?? [];
|
|
304
|
-
const nonAuthorableReason = retainedForReview ? [`non-authorable:${verdict.reason}`] : [];
|
|
305
|
-
const { confidence, reasons } = computeExtractionScore(component, {
|
|
306
|
-
additionalIssueCount: wrapperConfidenceToIssueCount(inspection.wrapperConfidence) +
|
|
307
|
-
extractorReasons.length +
|
|
308
|
-
nonAuthorableReason.length,
|
|
309
|
-
additionalReasons: [...extractorReasons, ...inspection.reviewReasons, ...nonAuthorableReason],
|
|
310
|
-
});
|
|
311
|
-
filteredComponents.push({
|
|
312
|
-
...component,
|
|
313
|
-
extractionConfidence: confidence,
|
|
314
|
-
reviewReasons: reasons,
|
|
315
|
-
needsReview: deriveNeedsReview(confidence) ||
|
|
316
|
-
inspection.wrapperConfidence >= 4 ||
|
|
317
|
-
inspection.keepDespiteZeroSurface ||
|
|
318
|
-
retainedForReview ||
|
|
319
|
-
// An extractor-level type-resolution failure is a strong signal regardless
|
|
320
|
-
// of the otherwise-derived confidence threshold; force review.
|
|
321
|
-
extractorReasons.includes('props-type-unresolved') ||
|
|
322
|
-
(component.needsReview ?? false),
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
let validatedComponents = validateExtractedComponents(filteredComponents);
|
|
326
|
-
// Composition mapping resolution (spec U2). Only in composite mode and
|
|
327
|
-
// only when a source is provided (user map / agent opt-in).
|
|
328
|
-
// Atomic (default) never resolves — it would only be stripped later.
|
|
329
|
-
const compositionMode = resolveCompositionMode(opts, (await safeReadCompositionMode()) ?? undefined);
|
|
330
|
-
if (compositionMode === 'composite') {
|
|
331
|
-
const sources = resolveCompositionSources(opts);
|
|
332
|
-
const { overrides: promptOverrides, errors: promptErrors } = parsePromptOverrides(opts.prompt ?? []);
|
|
333
|
-
for (const err of promptErrors) {
|
|
334
|
-
process.stderr.write(`Error: ${err}\n`);
|
|
335
|
-
process.exit(1);
|
|
336
|
-
}
|
|
337
|
-
let compositionPrompt;
|
|
338
|
-
const compositionOverride = promptOverrides.get('composition');
|
|
339
|
-
if (compositionOverride) {
|
|
340
|
-
try {
|
|
341
|
-
compositionPrompt = await resolvePromptOverride(compositionOverride);
|
|
342
|
-
}
|
|
343
|
-
catch (e) {
|
|
344
|
-
process.stderr.write(`Error: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
345
|
-
process.exit(1);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
let userMap;
|
|
349
|
-
if (opts.compositionMap) {
|
|
350
|
-
const loaded = await loadUserMap(opts.compositionMap);
|
|
351
|
-
if (!loaded.ok) {
|
|
352
|
-
process.stderr.write(`Error: ${loaded.error}\n`);
|
|
353
|
-
process.exit(1);
|
|
354
|
-
}
|
|
355
|
-
userMap = loaded.map;
|
|
356
|
-
}
|
|
357
|
-
const hasSource = !!userMap || sources.useAgent || sources.forceAgent;
|
|
358
|
-
if (hasSource || opts.generateMap) {
|
|
359
|
-
// Composition progress mirrors the scan/extract progress convention:
|
|
360
|
-
// emit `progress=composition:<phase>` on stderr so the wizard can
|
|
361
|
-
// render a second progress line during the (potentially slow, agent-
|
|
362
|
-
// backed) resolution instead of appearing frozen.
|
|
363
|
-
const emitCompositionProgress = (phase) => {
|
|
364
|
-
if (!process.stdout.isTTY)
|
|
365
|
-
process.stderr.write(`progress=composition:${phase}\n`);
|
|
366
|
-
};
|
|
367
|
-
emitCompositionProgress('resolving');
|
|
368
|
-
const allFiles = await readCandidateFiles(validatedComponents, sourceFiles);
|
|
369
|
-
// Decouple the two file sets (design: candidate-heuristic fragility):
|
|
370
|
-
// - `promptFiles`: a bounded candidate SAMPLE inlined into the agent
|
|
371
|
-
// prompt so it sees the convention without ingesting the whole repo.
|
|
372
|
-
// - `allFiles`: EVERY scanned file, handed to the authored parser at
|
|
373
|
-
// runtime. The parser is deterministic code — give it everything so
|
|
374
|
-
// a candidate-filter miss can never starve it of a definition file.
|
|
375
|
-
const selectedCandidates = selectCandidateFiles(allFiles).map((c) => ({ path: c.path, content: c.content }));
|
|
376
|
-
// Cap the inlined set so a large design system can't overflow the
|
|
377
|
-
// agent's context window and fail resolution outright (see the budget
|
|
378
|
-
// constant). The runtime parser still sees EVERY file.
|
|
379
|
-
const capped = capCandidatesToPromptBudget(selectedCandidates);
|
|
380
|
-
let promptFiles = capped.kept;
|
|
381
|
-
if (capped.dropped.length > 0) {
|
|
382
|
-
process.stderr.write(`Warning: composition — ${capped.dropped.length} candidate file(s) omitted from the agent prompt to fit the context budget; resolution runs on the ${promptFiles.length} highest-value files.\n`);
|
|
383
|
-
}
|
|
384
|
-
const runtimeFiles = allFiles.map((c) => ({ path: c.path, content: c.content }));
|
|
385
|
-
const resolverAgent = resolveCompositionAgentName(opts.agent);
|
|
386
|
-
const parserMode = (opts.compositionAgentMode ?? 'parser') !== 'edges';
|
|
387
|
-
const componentNameSet = new Set(validatedComponents.map((c) => c.name));
|
|
388
|
-
const cacheVersion = await getCliCacheVersion();
|
|
389
|
-
// Tracks the exit code of the most recent spawnAgent call so the
|
|
390
|
-
// caching sites can refuse to persist a failed run (a non-zero exit —
|
|
391
|
-
// e.g. a context-window overflow — otherwise poisons the cache and
|
|
392
|
-
// replays the error on every subsequent run).
|
|
393
|
-
let lastAgentExitCode = 0;
|
|
394
|
-
const spawnAgent = async (prompt) => {
|
|
395
|
-
const res = await runAgent({
|
|
396
|
-
agent: resolverAgent,
|
|
397
|
-
prompt,
|
|
398
|
-
timeoutMs: 120_000,
|
|
399
|
-
promptViaStdin: true,
|
|
400
|
-
onDebugEvent: (name, payload) => getDebugLogger().event('agent', name, payload),
|
|
401
|
-
});
|
|
402
|
-
lastAgentExitCode = res.exitCode;
|
|
403
|
-
if (res.exitCode !== 0 && res.stderr.trim()) {
|
|
404
|
-
process.stderr.write(`Warning: composition — agent exited ${res.exitCode}: ${res.stderr.trim()}\n`);
|
|
405
|
-
}
|
|
406
|
-
return res.stdout;
|
|
407
|
-
};
|
|
408
|
-
// Completeness critic: let the agent flag composition-relevant dirs
|
|
409
|
-
// the keyword filter missed (by path/name alone — cheap). It can only
|
|
410
|
-
// ADD to the prompt sample. Runs ONLY on a genuine authoring pass (see
|
|
411
|
-
// the cache-miss branch below) so a parser cache hit pays nothing.
|
|
412
|
-
const runCandidateCritic = async () => {
|
|
413
|
-
const critic = await critiqueCandidates(runtimeFiles, promptFiles, async (dirs) => parseDirCriticReply(await spawnAgent(buildDirCriticPrompt(dirs)), dirs));
|
|
414
|
-
if (critic.addedDirs.length > 0) {
|
|
415
|
-
promptFiles = critic.files;
|
|
416
|
-
if (process.env['EDS_DEBUG']) {
|
|
417
|
-
process.stderr.write(`[composition-debug] critic added dirs: ${critic.addedDirs.join(', ')}\n`);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
// Agent-authored parser path (default): the agent writes a sandboxed
|
|
422
|
-
// (ctx) => Edge[] parser we run deterministically, cached by parser
|
|
423
|
-
// SOURCE. Falls back to direct edge-emission if authoring fails.
|
|
424
|
-
let parserEdges;
|
|
425
|
-
if (sources.useAgent && parserMode) {
|
|
426
|
-
const parserCacheKey = buildCompositionInputHash({
|
|
427
|
-
files: runtimeFiles,
|
|
428
|
-
agent: resolverAgent,
|
|
429
|
-
kind: 'parser',
|
|
430
|
-
});
|
|
431
|
-
const cachedSource = opts.compositionRefresh
|
|
432
|
-
? null
|
|
433
|
-
: lookupCompositionCache(db, parserCacheKey, cacheVersion);
|
|
434
|
-
if (cachedSource !== null) {
|
|
435
|
-
emitCompositionProgress('cache-hit');
|
|
436
|
-
const ran = await runParserInSandbox(cachedSource, {
|
|
437
|
-
files: runtimeFiles,
|
|
438
|
-
componentNames: [...componentNameSet],
|
|
439
|
-
});
|
|
440
|
-
if (!ran.error) {
|
|
441
|
-
parserEdges = ran.edges.filter((e) => e.parent !== e.child && componentNameSet.has(e.parent) && componentNameSet.has(e.child));
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (parserEdges === undefined) {
|
|
445
|
-
await runCandidateCritic();
|
|
446
|
-
const pr = await resolveViaAgentParser({
|
|
447
|
-
files: promptFiles,
|
|
448
|
-
runtimeFiles,
|
|
449
|
-
componentNames: componentNameSet,
|
|
450
|
-
runAgentFn: ({ prompt }) => spawnAgent(prompt),
|
|
451
|
-
...(compositionPrompt ? { instructionOverride: compositionPrompt } : {}),
|
|
452
|
-
onPhase: (phase) => emitCompositionProgress(phase),
|
|
453
|
-
// The candidate filter selected these files because they carry
|
|
454
|
-
// composition markers, so a clean 0-edge parse is suspicious —
|
|
455
|
-
// let the resolver spend its one repair round on it.
|
|
456
|
-
retryOnEmpty: promptFiles.length > 0,
|
|
457
|
-
});
|
|
458
|
-
for (const w of pr.warnings)
|
|
459
|
-
process.stderr.write(`Warning: composition — ${w}\n`);
|
|
460
|
-
if (!pr.usedFallback) {
|
|
461
|
-
parserEdges = pr.edges;
|
|
462
|
-
if (pr.parserSource && lastAgentExitCode === 0) {
|
|
463
|
-
storeCompositionCache(db, parserCacheKey, cacheVersion, pr.parserSource);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
else {
|
|
467
|
-
process.stderr.write('Warning: composition — parser mode failed; falling back to edge emission\n');
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
if (process.env['EDS_DEBUG'] && parserEdges) {
|
|
472
|
-
process.stderr.write(`[composition-debug] prompt files: ${promptFiles.length}; runtime files: ${runtimeFiles.length}; componentNames: ${componentNameSet.size}; parser edges: ${parserEdges.length}\n`);
|
|
473
|
-
for (const e of parserEdges)
|
|
474
|
-
process.stderr.write(`[composition-debug] edge ${e.parent} -> ${e.child}\n`);
|
|
475
|
-
}
|
|
476
|
-
// Manifest (Figma `manifest.json`)/doc (`AGENTS.md`) evidence — rank
|
|
477
|
-
// 4/5, deterministic (no LLM), runs over the FULL file set
|
|
478
|
-
// regardless of composition mode/agent settings since it's cheap
|
|
479
|
-
// and code/design-adjacent rather than agent-derived.
|
|
480
|
-
const manifestDocEdges = collectManifestDocEdges(runtimeFiles, validatedComponents, componentNameSet);
|
|
481
|
-
const extraEdges = [...(parserEdges ?? []), ...manifestDocEdges];
|
|
482
|
-
// Edge-emission cache (used for both explicit edges-mode and the
|
|
483
|
-
// parser-mode fallback). Keyed on prompt files + agent identity — the
|
|
484
|
-
// agent emits edges directly from what it reads in the prompt.
|
|
485
|
-
const agentCacheKey = buildCompositionInputHash({
|
|
486
|
-
files: promptFiles,
|
|
487
|
-
agent: resolverAgent,
|
|
488
|
-
kind: 'edges',
|
|
489
|
-
});
|
|
490
|
-
const useEdgeEmission = sources.useAgent && parserEdges === undefined;
|
|
491
|
-
const result = await resolveMapping({
|
|
492
|
-
components: validatedComponents,
|
|
493
|
-
...(userMap ? { userMap } : {}),
|
|
494
|
-
...(extraEdges.length > 0 ? { extraEdges } : {}),
|
|
495
|
-
useAgent: useEdgeEmission,
|
|
496
|
-
forceAgent: sources.forceAgent && useEdgeEmission,
|
|
497
|
-
files: promptFiles,
|
|
498
|
-
...(compositionPrompt ? { promptOverride: compositionPrompt } : {}),
|
|
499
|
-
runAgentFn: async ({ prompt }) => {
|
|
500
|
-
if (!opts.compositionRefresh) {
|
|
501
|
-
const cached = lookupCompositionCache(db, agentCacheKey, cacheVersion);
|
|
502
|
-
if (cached !== null) {
|
|
503
|
-
emitCompositionProgress('cache-hit');
|
|
504
|
-
return cached;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
emitCompositionProgress(`agent:${resolverAgent}`);
|
|
508
|
-
const stdout = await spawnAgent(prompt);
|
|
509
|
-
if (lastAgentExitCode === 0) {
|
|
510
|
-
storeCompositionCache(db, agentCacheKey, cacheVersion, stdout);
|
|
511
|
-
}
|
|
512
|
-
return stdout;
|
|
513
|
-
},
|
|
514
|
-
});
|
|
515
|
-
emitCompositionProgress('done');
|
|
516
|
-
for (const w of result.warnings)
|
|
517
|
-
process.stderr.write(`Warning: composition — ${w}\n`);
|
|
518
|
-
for (const c of result.conflicts) {
|
|
519
|
-
process.stderr.write(`Warning: composition conflict on ${c.parent}→${c.child}: kept ${c.winner}, dropped ${c.loser}\n`);
|
|
520
|
-
}
|
|
521
|
-
validatedComponents = result.components;
|
|
522
|
-
if (opts.generateMap) {
|
|
523
|
-
// Reflect the FULL resolved composition — typed-slot edges already
|
|
524
|
-
// on the extracted components PLUS anything the resolver added — not
|
|
525
|
-
// just the resolver's own contributed edges.
|
|
526
|
-
const skeleton = componentsToInterchangeMap(validatedComponents);
|
|
527
|
-
await writeFile(opts.generateMap, JSON.stringify(skeleton, null, 2) + '\n');
|
|
528
|
-
process.stderr.write(`Wrote composition map skeleton to ${opts.generateMap}\n`);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
storeRawComponents(db, sessionId, validatedComponents);
|
|
533
|
-
const cycleInput = validatedComponents.map((c) => ({
|
|
534
|
-
name: c.name,
|
|
535
|
-
slots: c.slots.map((s) => ({ name: s.name, allowedComponents: s.allowedComponents })),
|
|
536
|
-
}));
|
|
537
|
-
const cycles = findSlotCycles(cycleInput);
|
|
538
|
-
const withBreaks = cycles.map((cycle) => ({
|
|
539
|
-
...cycle,
|
|
540
|
-
suggestedBreak: suggestCycleBreakEdge(cycle, cycles),
|
|
541
|
-
}));
|
|
542
|
-
storeSlotCycles(db, sessionId, withBreaks);
|
|
543
|
-
storeScannedFiles(db, sessionId, sourceFiles.map((f) => relative(projectRoot, f)));
|
|
544
|
-
updateStep(db, stepId, 'complete', { sessionId });
|
|
545
|
-
enrichCommandResult({ extracted_component_count: validatedComponents.length });
|
|
546
|
-
db.close();
|
|
547
|
-
const allWarnings = [...extraction.warnings, ...filterWarnings];
|
|
548
|
-
const { rows: componentRows, totalErrors } = buildAnalyzeViewRows(filteredComponents, validatedComponents, allWarnings);
|
|
549
|
-
// Split warnings: per-component (those whose prefix matches a surviving component name)
|
|
550
|
-
// are rendered under that component in the TUI; global ones (retry summaries,
|
|
551
|
-
// non-authorable skips, anything else) are rendered at the top of the warnings panel
|
|
552
|
-
// so they don't disappear into the count. `partitionGlobalWarnings` shares its
|
|
553
|
-
// matching rule with `buildAnalyzeViewRows` to keep the two halves symmetric.
|
|
554
|
-
const globalWarnings = partitionGlobalWarnings(allWarnings, componentRows.map((r) => r.name));
|
|
555
|
-
const analyzeResult = {
|
|
556
|
-
sourceDirectory,
|
|
557
|
-
sessionId,
|
|
558
|
-
fileCount: sourceFiles.length,
|
|
559
|
-
components: componentRows,
|
|
560
|
-
totalWarnings: allWarnings.length,
|
|
561
|
-
totalErrors,
|
|
562
|
-
globalWarnings,
|
|
563
|
-
};
|
|
564
|
-
if (getInteractiveTerminalSupport().supported) {
|
|
565
|
-
const { waitUntilExit } = render(createElement(AnalyzeView, {
|
|
566
|
-
result: analyzeResult,
|
|
567
|
-
onExit: () => void exitWithAnalytics(0),
|
|
568
|
-
}));
|
|
569
|
-
await waitUntilExit();
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
const sessionLine = `session=${sessionId}\n`;
|
|
573
|
-
process.stdout.write(sessionLine);
|
|
574
|
-
const summaryLines = [
|
|
575
|
-
`Scanned ${pluralize(sourceFiles.length, 'source file')} in ${sourceDirectory}`,
|
|
576
|
-
`Extracted ${pluralize(extraction.components.length, 'component')}`,
|
|
577
|
-
];
|
|
578
|
-
if (totalErrors > 0) {
|
|
579
|
-
summaryLines.push(`Errors (${totalErrors}):`);
|
|
580
|
-
for (const c of componentRows) {
|
|
581
|
-
for (const e of c.errors) {
|
|
582
|
-
summaryLines.push(`- ${c.name}: ${e}`);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
if (allWarnings.length > 0) {
|
|
587
|
-
summaryLines.push(`Warnings (${allWarnings.length}):`);
|
|
588
|
-
summaryLines.push(...allWarnings.map((w) => `- ${w}`));
|
|
589
|
-
}
|
|
590
|
-
else if (totalErrors === 0) {
|
|
591
|
-
summaryLines.push('Warnings: none');
|
|
592
|
-
}
|
|
593
|
-
process.stderr.write(summaryLines.join('\n') + '\n');
|
|
594
|
-
await exitWithAnalytics(0);
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
registerAnalyzeEditCommand(analyze);
|
|
598
|
-
registerAnalyzeSelectAgentCommand(analyze);
|
|
599
|
-
}
|
|
Binary file
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { loadPrompt } from './load-prompt.js';
|
|
2
|
-
/**
|
|
3
|
-
* Build the prompt that asks the agent to WRITE a pure parser function (spec:
|
|
4
|
-
* agent-authored-parser, Phase 2) rather than list edges. The static
|
|
5
|
-
* instruction + contract live in `prompts/composition-parser.md`; this
|
|
6
|
-
* appends the dynamic candidate files + component names. The authored parser
|
|
7
|
-
* runs in the sandbox (sandbox.ts), so the contract forbids all I/O.
|
|
8
|
-
*/
|
|
9
|
-
const PROMPT_FILE = 'composition-parser.md';
|
|
10
|
-
export function buildAuthorPrompt(files, componentNames, instructionOverride) {
|
|
11
|
-
const fileBlocks = files.map((f) => `--- ${f.path} ---\n${f.content}`).join('\n\n');
|
|
12
|
-
const instruction = instructionOverride?.trim() ? instructionOverride.trim() : loadPrompt(PROMPT_FILE).trim();
|
|
13
|
-
return [
|
|
14
|
-
instruction,
|
|
15
|
-
'',
|
|
16
|
-
'Component names (use ONLY these):',
|
|
17
|
-
componentNames.join(', '),
|
|
18
|
-
'',
|
|
19
|
-
'Candidate files:',
|
|
20
|
-
fileBlocks,
|
|
21
|
-
].join('\n');
|
|
22
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pull the parser function source out of the agent's stdout (spec:
|
|
3
|
-
* agent-authored-parser, Phase 2). Agents typically wrap code in a fenced
|
|
4
|
-
* block, but often emit a non-parser block first (a JSON sample, prose). We
|
|
5
|
-
* return the first block that is actually SHAPED like the contract — a
|
|
6
|
-
* default-exported function taking one param — so a stray sample never reaches
|
|
7
|
-
* the sandbox. Lenient by design: no parser-shaped source → null → caller
|
|
8
|
-
* falls back.
|
|
9
|
-
*/
|
|
10
|
-
const FENCE = /```(?:[a-zA-Z]+)?\n([\s\S]*?)```/g;
|
|
11
|
-
/**
|
|
12
|
-
* Cheap structural check that source matches the parser contract:
|
|
13
|
-
* `export default` of a function (declaration or arrow) that takes at least
|
|
14
|
-
* one parameter. Not a full parse — just enough to reject JSON samples, prose,
|
|
15
|
-
* and no-arg functions before we spend a sandbox spawn on them.
|
|
16
|
-
*/
|
|
17
|
-
export function looksLikeParser(source) {
|
|
18
|
-
const s = source.trim();
|
|
19
|
-
if (!/export\s+default\b/.test(s))
|
|
20
|
-
return false;
|
|
21
|
-
// export default function [name] (param...) { ... }
|
|
22
|
-
const fnDecl = /export\s+default\s+(?:async\s+)?function\b[^(]*\(\s*[A-Za-z_$][\w$]*/;
|
|
23
|
-
// export default (param...) => ... OR export default param => ...
|
|
24
|
-
const arrow = /export\s+default\s+(?:async\s+)?(?:\(\s*[A-Za-z_$][\w$]*|[A-Za-z_$][\w$]*\s*=>)/;
|
|
25
|
-
return fnDecl.test(s) || arrow.test(s);
|
|
26
|
-
}
|
|
27
|
-
export function extractParserSource(stdout) {
|
|
28
|
-
FENCE.lastIndex = 0;
|
|
29
|
-
let m;
|
|
30
|
-
while ((m = FENCE.exec(stdout)) !== null) {
|
|
31
|
-
const candidate = m[1].trim();
|
|
32
|
-
if (candidate !== '' && looksLikeParser(candidate))
|
|
33
|
-
return candidate;
|
|
34
|
-
}
|
|
35
|
-
// No parser-shaped fenced block — accept bare source only if it, too, is
|
|
36
|
-
// shaped like the contract.
|
|
37
|
-
const idx = stdout.indexOf('export default');
|
|
38
|
-
if (idx !== -1) {
|
|
39
|
-
const bare = stdout.slice(idx).trim();
|
|
40
|
-
if (looksLikeParser(bare))
|
|
41
|
-
return bare;
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
}
|