@contentful/experience-design-system-cli 2.14.2 → 2.15.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/README.md +64 -2
- package/dist/package.json +3 -2
- package/dist/src/analyze/command.d.ts +11 -0
- package/dist/src/analyze/command.js +298 -3
- package/dist/src/analyze/composite-closure.d.ts +25 -0
- package/dist/src/analyze/composite-closure.js +0 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.d.ts +4 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.js +22 -0
- package/dist/src/analyze/composition/agent-parser/extract-parser.d.ts +8 -0
- package/dist/src/analyze/composition/agent-parser/extract-parser.js +44 -0
- package/dist/src/analyze/composition/agent-parser/load-prompt.d.ts +7 -0
- package/dist/src/analyze/composition/agent-parser/load-prompt.js +31 -0
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.d.ts +46 -0
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +88 -0
- package/dist/src/analyze/composition/agent-parser/sandbox.d.ts +45 -0
- package/dist/src/analyze/composition/agent-parser/sandbox.js +187 -0
- package/dist/src/analyze/composition/apply-mapping.d.ts +22 -0
- package/dist/src/analyze/composition/apply-mapping.js +67 -0
- package/dist/src/analyze/composition/candidate-critic-agent.d.ts +7 -0
- package/dist/src/analyze/composition/candidate-critic-agent.js +39 -0
- package/dist/src/analyze/composition/candidate-critic.d.ts +32 -0
- package/dist/src/analyze/composition/candidate-critic.js +63 -0
- package/dist/src/analyze/composition/candidate-files.d.ts +34 -0
- package/dist/src/analyze/composition/candidate-files.js +131 -0
- package/dist/src/analyze/composition/composition-cache-key.d.ts +19 -0
- package/dist/src/analyze/composition/composition-cache-key.js +19 -0
- package/dist/src/analyze/composition/interchange-schema.d.ts +37 -0
- package/dist/src/analyze/composition/interchange-schema.js +61 -0
- package/dist/src/analyze/composition/merge-edges.d.ts +19 -0
- package/dist/src/analyze/composition/merge-edges.js +50 -0
- package/dist/src/analyze/composition/parse-map-edges.d.ts +16 -0
- package/dist/src/analyze/composition/parse-map-edges.js +55 -0
- package/dist/src/analyze/composition/resolve-mapping-cli.d.ts +23 -0
- package/dist/src/analyze/composition/resolve-mapping-cli.js +29 -0
- package/dist/src/analyze/composition/resolve-mapping.d.ts +59 -0
- package/dist/src/analyze/composition/resolve-mapping.js +94 -0
- package/dist/src/analyze/cycle-detection.d.ts +27 -0
- package/dist/src/analyze/cycle-detection.js +234 -0
- package/dist/src/analyze/cycle-view.d.ts +7 -0
- package/dist/src/analyze/cycle-view.js +28 -0
- package/dist/src/analyze/fuzzy-search.d.ts +3 -0
- package/dist/src/analyze/fuzzy-search.js +55 -0
- package/dist/src/analyze/issue-inheritance.d.ts +12 -0
- package/dist/src/analyze/issue-inheritance.js +108 -0
- package/dist/src/analyze/lineage.d.ts +29 -0
- package/dist/src/analyze/lineage.js +0 -0
- package/dist/src/analyze/scope-gate-cascade.d.ts +11 -0
- package/dist/src/analyze/scope-gate-cascade.js +170 -0
- package/dist/src/analyze/search-neighborhood.d.ts +3 -0
- package/dist/src/analyze/search-neighborhood.js +56 -0
- package/dist/src/analyze/select/tui/App.js +5 -16
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.d.ts +0 -4
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +3 -15
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +29 -38
- package/dist/src/analyze/select/tui/components/FieldEditor.js +309 -185
- package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +23 -2
- package/dist/src/analyze/select/tui/components/GotoBanner.d.ts +31 -0
- package/dist/src/analyze/select/tui/components/GotoBanner.js +30 -0
- package/dist/src/analyze/select/tui/components/GroupedSidebar.d.ts +91 -0
- package/dist/src/analyze/select/tui/components/GroupedSidebar.js +497 -0
- package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +9 -3
- package/dist/src/analyze/select/tui/components/JsonEditor.js +3 -3
- package/dist/src/analyze/select/tui/components/LineagePanel.d.ts +14 -0
- package/dist/src/analyze/select/tui/components/LineagePanel.js +22 -0
- package/dist/src/analyze/select/tui/components/RationalePanel.d.ts +0 -7
- package/dist/src/analyze/select/tui/components/RationalePanel.js +3 -15
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +0 -6
- package/dist/src/analyze/select/tui/components/Sidebar.js +11 -27
- package/dist/src/analyze/select/tui/components/StatusBar.js +2 -1
- package/dist/src/analyze/select/tui/components/removed-components-text.d.ts +7 -0
- package/dist/src/analyze/select/tui/components/removed-components-text.js +11 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +1 -5
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +8 -10
- package/dist/src/analyze/select/tui/theme.d.ts +15 -0
- package/dist/src/analyze/select/tui/theme.js +28 -0
- package/dist/src/analyze/selection-cascade.d.ts +3 -0
- package/dist/src/analyze/selection-cascade.js +19 -0
- package/dist/src/analyze/slot-graph.d.ts +11 -0
- package/dist/src/analyze/slot-graph.js +17 -0
- package/dist/src/apply/api-client.d.ts +2 -1
- package/dist/src/apply/api-client.js +40 -6
- package/dist/src/apply/command.d.ts +17 -1
- package/dist/src/apply/command.js +69 -11
- package/dist/src/apply/error-parser.d.ts +16 -0
- package/dist/src/apply/error-parser.js +117 -0
- package/dist/src/apply/tui/ServerPreviewView.js +3 -1
- package/dist/src/credentials-store.d.ts +6 -0
- package/dist/src/credentials-store.js +5 -1
- package/dist/src/generate/agent-runner.d.ts +6 -0
- package/dist/src/generate/agent-runner.js +24 -8
- package/dist/src/import/command.js +69 -31
- package/dist/src/import/cycle-auto-reject.d.ts +4 -0
- package/dist/src/import/cycle-auto-reject.js +14 -0
- package/dist/src/import/orchestrator.d.ts +20 -7
- package/dist/src/import/orchestrator.js +139 -35
- package/dist/src/import/strip-allowed-components.d.ts +18 -0
- package/dist/src/import/strip-allowed-components.js +22 -0
- package/dist/src/import/tui/CustomPromptBanner.d.ts +0 -5
- package/dist/src/import/tui/CustomPromptBanner.js +2 -1
- package/dist/src/import/tui/WizardApp.d.ts +10 -74
- package/dist/src/import/tui/WizardApp.js +234 -246
- package/dist/src/import/tui/ai-flag.d.ts +5 -0
- package/dist/src/import/tui/ai-flag.js +6 -0
- package/dist/src/import/tui/autocomplete.d.ts +5 -0
- package/dist/src/import/tui/autocomplete.js +32 -0
- package/dist/src/import/tui/components/AutoFilterBanner.d.ts +10 -0
- package/dist/src/import/tui/components/AutoFilterBanner.js +19 -0
- package/dist/src/import/tui/components/CounterStrip.d.ts +12 -0
- package/dist/src/import/tui/components/CounterStrip.js +12 -0
- package/dist/src/import/tui/components/LegendEntry.d.ts +2 -0
- package/dist/src/import/tui/components/LegendEntry.js +6 -0
- package/dist/src/import/tui/cycle-panel-scroll.d.ts +8 -0
- package/dist/src/import/tui/cycle-panel-scroll.js +33 -0
- package/dist/src/import/tui/final-review-host.d.ts +3 -7
- package/dist/src/import/tui/final-review-host.js +9 -4
- package/dist/src/import/tui/group-collapse.d.ts +2 -0
- package/dist/src/import/tui/group-collapse.js +11 -0
- package/dist/src/import/tui/history.d.ts +22 -0
- package/dist/src/import/tui/history.js +53 -0
- package/dist/src/import/tui/hooks/useLineage.d.ts +32 -0
- package/dist/src/import/tui/hooks/useLineage.js +50 -0
- package/dist/src/import/tui/hooks/useOverlayPanel.d.ts +13 -0
- package/dist/src/import/tui/hooks/useOverlayPanel.js +27 -0
- package/dist/src/import/tui/lineage-layout.d.ts +75 -0
- package/dist/src/import/tui/lineage-layout.js +82 -0
- package/dist/src/import/tui/runLivePreview.d.ts +11 -0
- package/dist/src/import/tui/runLivePreview.js +7 -1
- package/dist/src/import/tui/scope-gate-columns.d.ts +31 -0
- package/dist/src/import/tui/scope-gate-columns.js +94 -0
- package/dist/src/import/tui/scope-gate-host.d.ts +3 -1
- package/dist/src/import/tui/scope-gate-host.js +11 -3
- package/dist/src/import/tui/sidebar-width.d.ts +1 -0
- package/dist/src/import/tui/sidebar-width.js +3 -0
- package/dist/src/import/tui/step-filters.d.ts +19 -0
- package/dist/src/import/tui/step-filters.js +42 -0
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.d.ts +42 -0
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +726 -0
- package/dist/src/import/tui/steps/AtomicScopeGateStep.d.ts +3 -0
- package/dist/src/import/tui/steps/AtomicScopeGateStep.js +211 -0
- package/dist/src/import/tui/steps/CredentialsStep.js +5 -18
- package/dist/src/import/tui/steps/DoneStep.d.ts +0 -2
- package/dist/src/import/tui/steps/DoneStep.js +3 -2
- package/dist/src/import/tui/steps/ErrorStep.js +2 -1
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +19 -25
- package/dist/src/import/tui/steps/GenerateReviewStep.js +1258 -229
- package/dist/src/import/tui/steps/PathValidationStep.js +3 -2
- package/dist/src/import/tui/steps/PreviewStep.js +2 -1
- package/dist/src/import/tui/steps/PushDecisionGateStep.d.ts +0 -7
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +3 -6
- package/dist/src/import/tui/steps/PushingStep.js +2 -1
- package/dist/src/import/tui/steps/RunningStep.d.ts +4 -1
- package/dist/src/import/tui/steps/RunningStep.js +3 -2
- package/dist/src/import/tui/steps/ScopeGateStep.d.ts +24 -0
- package/dist/src/import/tui/steps/ScopeGateStep.js +850 -193
- package/dist/src/import/tui/steps/TokenInputStep.js +2 -1
- package/dist/src/import/tui/steps/WelcomeStep.js +2 -1
- package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +6 -0
- package/dist/src/import/tui/steps/WizardPreviewStep.js +95 -56
- package/dist/src/import/tui/steps/auto-reject-decision.d.ts +7 -0
- package/dist/src/import/tui/steps/auto-reject-decision.js +9 -0
- package/dist/src/import/tui/steps/breaking-change-format.d.ts +2 -0
- package/dist/src/import/tui/steps/breaking-change-format.js +24 -0
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.d.ts +14 -0
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
- package/dist/src/import/tui/steps/preview-diff.js +48 -5
- package/dist/src/import/tui/useFinalizePreview.d.ts +34 -0
- package/dist/src/import/tui/useFinalizePreview.js +0 -0
- package/dist/src/import/tui/useLivePreview.d.ts +3 -0
- package/dist/src/import/tui/useLivePreview.js +1 -0
- package/dist/src/import/tui/wizard-state-transitions.d.ts +5 -59
- package/dist/src/import/tui/wizard-state-transitions.js +5 -60
- package/dist/src/lib/composition-mode.d.ts +28 -0
- package/dist/src/lib/composition-mode.js +37 -0
- package/dist/src/lib/prompt-overrides.d.ts +38 -0
- package/dist/src/lib/prompt-overrides.js +69 -0
- package/dist/src/print/command.js +19 -1
- package/dist/src/runs/modify-launcher.d.ts +3 -0
- package/dist/src/runs/modify-launcher.js +2 -0
- package/dist/src/runs/replay-helpers.js +1 -0
- package/dist/src/runs/run-picker.js +2 -18
- package/dist/src/runs/store.d.ts +4 -0
- package/dist/src/session/db.d.ts +23 -40
- package/dist/src/session/db.js +110 -138
- package/dist/src/setup/composition-mode-prompt.d.ts +14 -0
- package/dist/src/setup/composition-mode-prompt.js +37 -0
- package/package.json +5 -4
- package/prompts/composition-dir-critic.md +7 -0
- package/prompts/composition-edges.md +8 -0
- package/prompts/composition-parser-repair-empty.md +1 -0
- package/prompts/composition-parser-repair-error.md +2 -0
- package/prompts/composition-parser.md +42 -0
package/dist/src/session/db.js
CHANGED
|
@@ -138,6 +138,15 @@ CREATE TABLE IF NOT EXISTS scanned_files (
|
|
|
138
138
|
PRIMARY KEY (session_id, path)
|
|
139
139
|
);
|
|
140
140
|
|
|
141
|
+
CREATE TABLE IF NOT EXISTS slot_cycles (
|
|
142
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
143
|
+
cycle_index INTEGER NOT NULL,
|
|
144
|
+
path_json TEXT NOT NULL,
|
|
145
|
+
edges_json TEXT NOT NULL,
|
|
146
|
+
suggested_break_json TEXT,
|
|
147
|
+
PRIMARY KEY (session_id, cycle_index)
|
|
148
|
+
);
|
|
149
|
+
|
|
141
150
|
CREATE INDEX IF NOT EXISTS idx_steps_session ON steps(session_id);
|
|
142
151
|
CREATE INDEX IF NOT EXISTS idx_steps_command ON steps(session_id, command);
|
|
143
152
|
CREATE INDEX IF NOT EXISTS idx_raw_components_session ON raw_components(session_id);
|
|
@@ -160,18 +169,6 @@ export function openPipelineDb(dbPath) {
|
|
|
160
169
|
mkdirSync(dirname(path), { recursive: true });
|
|
161
170
|
try {
|
|
162
171
|
const db = new DatabaseSync(path);
|
|
163
|
-
// Configure connection-level pragmas BEFORE running schema/migrations.
|
|
164
|
-
// - journal_mode=WAL: persistent on the DB file; readers don't block writers
|
|
165
|
-
// and vice versa. (Also set in SCHEMA, but harmless to assert per-open.)
|
|
166
|
-
// - busy_timeout: per-connection; SQLite waits for a lock instead of
|
|
167
|
-
// immediately throwing 'database is locked'. Critical when the wizard
|
|
168
|
-
// main process and select-agent subprocesses contend on writes.
|
|
169
|
-
// - synchronous=NORMAL: safe under WAL, faster than FULL.
|
|
170
|
-
// busy_timeout MUST come first. PRAGMA journal_mode = WAL needs a
|
|
171
|
-
// RESERVED lock briefly even when the DB is already WAL; if the
|
|
172
|
-
// select-agent child is mid-write at that moment, the wizard's open
|
|
173
|
-
// fails instantly without retry. Setting busy_timeout first makes
|
|
174
|
-
// SQLite wait up to 5s for the lock.
|
|
175
172
|
db.exec('PRAGMA busy_timeout = 5000');
|
|
176
173
|
db.exec('PRAGMA journal_mode = WAL');
|
|
177
174
|
db.exec('PRAGMA synchronous = NORMAL');
|
|
@@ -182,23 +179,16 @@ export function openPipelineDb(dbPath) {
|
|
|
182
179
|
catch (e) {
|
|
183
180
|
const msg = e instanceof Error ? e.message : String(e);
|
|
184
181
|
if (msg.includes('database is locked')) {
|
|
185
|
-
// Throw a typed-ish error rather than process.exit(1). Calling exit
|
|
186
|
-
// from inside Ink's render reconciler crashes the TUI mid-frame and
|
|
187
|
-
// emits a noisy 'setState during render' warning. The CLI entry
|
|
188
|
-
// point (src/index.ts) catches and exits cleanly for non-TUI
|
|
189
|
-
// contexts; the wizard catches and transitions to its error step.
|
|
190
182
|
throw new Error('database is locked: another CLI process may be running. Retry once it exits.');
|
|
191
183
|
}
|
|
192
184
|
throw e;
|
|
193
185
|
}
|
|
194
186
|
}
|
|
195
187
|
function applyDbMigrations(db) {
|
|
196
|
-
// Add raw_slots.required column if it doesn't exist yet (added in v0.5.14).
|
|
197
188
|
const cols = db.prepare('PRAGMA table_info(raw_slots)').all();
|
|
198
189
|
if (!cols.some((c) => c.name === 'required')) {
|
|
199
190
|
db.exec('ALTER TABLE raw_slots ADD COLUMN required INTEGER NOT NULL DEFAULT 1 CHECK (required IN (0, 1))');
|
|
200
191
|
}
|
|
201
|
-
// Add extraction confidence scoring columns if they don't exist yet (added in v0.6.0).
|
|
202
192
|
const rawCompCols = db.prepare('PRAGMA table_info(raw_components)').all();
|
|
203
193
|
const rawCompColNames = new Set(rawCompCols.map((c) => c.name));
|
|
204
194
|
if (!rawCompColNames.has('extraction_confidence')) {
|
|
@@ -210,8 +200,6 @@ function applyDbMigrations(db) {
|
|
|
210
200
|
if (!rawCompColNames.has('needs_review')) {
|
|
211
201
|
db.exec('ALTER TABLE raw_components ADD COLUMN needs_review INTEGER NOT NULL DEFAULT 0');
|
|
212
202
|
}
|
|
213
|
-
// Feature 1: per-component source path on raw_components, rationale + per-prop
|
|
214
|
-
// source location on raw_props. All nullable, no DEFAULT — existing rows survive.
|
|
215
203
|
if (!rawCompColNames.has('source_path')) {
|
|
216
204
|
db.exec('ALTER TABLE raw_components ADD COLUMN source_path TEXT');
|
|
217
205
|
}
|
|
@@ -226,15 +214,9 @@ function applyDbMigrations(db) {
|
|
|
226
214
|
if (!rawPropColNames.has('source_end_line')) {
|
|
227
215
|
db.exec('ALTER TABLE raw_props ADD COLUMN source_end_line INTEGER');
|
|
228
216
|
}
|
|
229
|
-
// Feature 3: persist select-agent's reject reason on raw_components. Nullable,
|
|
230
|
-
// no DEFAULT — accepted components keep NULL, rejected ones store the LLM's
|
|
231
|
-
// reason (or `validation error: <codes>` for validation auto-rejections).
|
|
232
217
|
if (!rawCompColNames.has('reject_reason')) {
|
|
233
218
|
db.exec('ALTER TABLE raw_components ADD COLUMN reject_reason TEXT');
|
|
234
219
|
}
|
|
235
|
-
// Component rationale: WHY a component was classified, why these props,
|
|
236
|
-
// why these slots — surfaced via the `I` panel. All nullable, no DEFAULT
|
|
237
|
-
// so existing rows survive. Plus per-slot rationale on raw_slots.
|
|
238
220
|
if (!rawCompColNames.has('component_description_rationale')) {
|
|
239
221
|
db.exec('ALTER TABLE raw_components ADD COLUMN component_description_rationale TEXT');
|
|
240
222
|
}
|
|
@@ -248,9 +230,6 @@ function applyDbMigrations(db) {
|
|
|
248
230
|
if (!rawSlotColsForRationale.some((c) => c.name === 'rationale')) {
|
|
249
231
|
db.exec('ALTER TABLE raw_slots ADD COLUMN rationale TEXT');
|
|
250
232
|
}
|
|
251
|
-
// Fine-grained cache: extract_cache holds per-file extracted components keyed
|
|
252
|
-
// on (file_hash, cli_version). cli_version is a content hash of bundled skill
|
|
253
|
-
// files so cache busts on a prompt change (not on every npm patch bump).
|
|
254
233
|
const hasExtractCache = db
|
|
255
234
|
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='extract_cache'`)
|
|
256
235
|
.all();
|
|
@@ -268,9 +247,6 @@ function applyDbMigrations(db) {
|
|
|
268
247
|
CREATE INDEX idx_extract_cache_file ON extract_cache(file_path);
|
|
269
248
|
`);
|
|
270
249
|
}
|
|
271
|
-
// Fine-grained cache: select_cache stores accept/reject decisions per
|
|
272
|
-
// (component_hash, prompt_hash, cli_version). On hit, the wizard replays the
|
|
273
|
-
// decision without an LLM call.
|
|
274
250
|
const hasSelectCache = db
|
|
275
251
|
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='select_cache'`)
|
|
276
252
|
.all();
|
|
@@ -288,7 +264,6 @@ function applyDbMigrations(db) {
|
|
|
288
264
|
);
|
|
289
265
|
`);
|
|
290
266
|
}
|
|
291
|
-
// Add generation_cache table if it doesn't exist yet.
|
|
292
267
|
const tables = db
|
|
293
268
|
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='generation_cache'`)
|
|
294
269
|
.all();
|
|
@@ -308,16 +283,6 @@ function applyDbMigrations(db) {
|
|
|
308
283
|
CREATE INDEX idx_generation_cache_session ON generation_cache(source_session_id);
|
|
309
284
|
`);
|
|
310
285
|
}
|
|
311
|
-
// Fine-grained cache: add prompt_hash to generation_cache so cache busts when
|
|
312
|
-
// the operator switches prompt files. Existing rows get '' (matches the
|
|
313
|
-
// historical "default bundled prompt" — note that lookups using a non-empty
|
|
314
|
-
// hash for the bundled prompt won't match, so first run after upgrade busts
|
|
315
|
-
// the cache. Documented as acceptable.
|
|
316
|
-
//
|
|
317
|
-
// The primary key also needs to expand to include prompt_hash so multiple
|
|
318
|
-
// prompt variants can coexist per (input_hash, entity_type, entity_id).
|
|
319
|
-
// SQLite doesn't allow ALTERing a PK, so when promoting the schema we rebuild
|
|
320
|
-
// the table in-place.
|
|
321
286
|
const genCacheCols = db.prepare(`PRAGMA table_info(generation_cache)`).all();
|
|
322
287
|
const hasPromptHash = genCacheCols.some((c) => c.name === 'prompt_hash');
|
|
323
288
|
const oldPkCols = genCacheCols.filter((c) => c.pk > 0).map((c) => c.name);
|
|
@@ -356,6 +321,21 @@ function applyDbMigrations(db) {
|
|
|
356
321
|
throw e;
|
|
357
322
|
}
|
|
358
323
|
}
|
|
324
|
+
const hasCompositionCache = db
|
|
325
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='composition_cache'`)
|
|
326
|
+
.all();
|
|
327
|
+
if (hasCompositionCache.length === 0) {
|
|
328
|
+
db.exec(`
|
|
329
|
+
CREATE TABLE composition_cache (
|
|
330
|
+
input_hash TEXT NOT NULL,
|
|
331
|
+
cli_version TEXT NOT NULL,
|
|
332
|
+
agent_output TEXT NOT NULL,
|
|
333
|
+
created_at TEXT NOT NULL,
|
|
334
|
+
updated_at TEXT NOT NULL,
|
|
335
|
+
PRIMARY KEY (input_hash, cli_version)
|
|
336
|
+
);
|
|
337
|
+
`);
|
|
338
|
+
}
|
|
359
339
|
}
|
|
360
340
|
export function loadComponentReviewMetadata(db, sessionId, componentName) {
|
|
361
341
|
const compRow = db
|
|
@@ -393,13 +373,6 @@ export function loadComponentReviewMetadata(db, sessionId, componentName) {
|
|
|
393
373
|
props,
|
|
394
374
|
};
|
|
395
375
|
}
|
|
396
|
-
/**
|
|
397
|
-
* Component-rationale loader: data contract for the `I` ComponentRationalePanel.
|
|
398
|
-
* Returns the component's description + the three component-level rationale
|
|
399
|
-
* strings (why-description / why-props / why-slots), plus per-prop and per-slot
|
|
400
|
-
* rationale rows for the operator-facing panel. Returns null when no component
|
|
401
|
-
* matches.
|
|
402
|
-
*/
|
|
403
376
|
export function loadComponentRationale(db, sessionId, componentName) {
|
|
404
377
|
const compRow = db
|
|
405
378
|
.prepare(`SELECT component_id, name, description, component_description_rationale, props_rationale, slots_rationale
|
|
@@ -458,8 +431,6 @@ export function applyToolCalls(db, sessionId, componentId, componentName, calls,
|
|
|
458
431
|
if (call.description !== undefined) {
|
|
459
432
|
db.prepare('UPDATE raw_components SET description = ? WHERE session_id = ? AND component_id = ?').run(call.description, sessionId, componentId);
|
|
460
433
|
}
|
|
461
|
-
// Component-level rationale: sparse-update each field only when present
|
|
462
|
-
// so missing keys don't overwrite previously-stored values.
|
|
463
434
|
if (call.rationale) {
|
|
464
435
|
if (call.rationale.description !== undefined) {
|
|
465
436
|
db.prepare('UPDATE raw_components SET component_description_rationale = ? WHERE session_id = ? AND component_id = ?').run(call.rationale.description, sessionId, componentId);
|
|
@@ -499,8 +470,6 @@ export function applyToolCalls(db, sessionId, componentId, componentName, calls,
|
|
|
499
470
|
warnings.push(`${componentName}: classify_slot '${call.slot}' — slot not found, skipped`);
|
|
500
471
|
continue;
|
|
501
472
|
}
|
|
502
|
-
// Per-slot rationale: sparse-update only when present so omitting it
|
|
503
|
-
// doesn't blank existing values.
|
|
504
473
|
if (call.rationale !== undefined) {
|
|
505
474
|
db.prepare('UPDATE raw_slots SET rationale = ? WHERE session_id = ? AND component_id = ? AND name = ?').run(call.rationale, sessionId, componentId, call.slot);
|
|
506
475
|
}
|
|
@@ -521,11 +490,7 @@ export function applyToolCalls(db, sessionId, componentId, componentName, calls,
|
|
|
521
490
|
}
|
|
522
491
|
return { classified, excluded, slots, warnings };
|
|
523
492
|
}
|
|
524
|
-
export function getOrCreateSession(db, sessionFlag, sessionName,
|
|
525
|
-
// `_hints` is retained for call-site compatibility but no longer drives an
|
|
526
|
-
// implicit auto-match by inputPath/outDir — operators who want to resume must
|
|
527
|
-
// pass an explicit --session <id>.
|
|
528
|
-
_hints) {
|
|
493
|
+
export function getOrCreateSession(db, sessionFlag, sessionName, _hints) {
|
|
529
494
|
const now = new Date().toISOString();
|
|
530
495
|
if (sessionFlag === 'new' || sessionFlag === undefined) {
|
|
531
496
|
const id = generateSessionId();
|
|
@@ -626,7 +591,6 @@ export function storeRawComponents(db, sessionId, components, options) {
|
|
|
626
591
|
}
|
|
627
592
|
}
|
|
628
593
|
}
|
|
629
|
-
// Restore CDF classification data for props that still exist
|
|
630
594
|
if (options?.preserveCDF && cdfSnapshot.length > 0) {
|
|
631
595
|
const updateCDFByName = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?
|
|
632
596
|
WHERE session_id = ? AND component_id = ? AND name = ?`);
|
|
@@ -634,7 +598,6 @@ export function storeRawComponents(db, sessionId, components, options) {
|
|
|
634
598
|
WHERE session_id = ? AND component_id = ? AND position = ? AND cdf_type IS NULL`);
|
|
635
599
|
const restoredPropKeys = new Set();
|
|
636
600
|
const unmatchedSnaps = [];
|
|
637
|
-
// First pass: match by name
|
|
638
601
|
for (const snap of cdfSnapshot) {
|
|
639
602
|
const result = updateCDFByName.run(snap.cdf_type, snap.cdf_category, snap.cdf_token_kind, sessionId, snap.component_id, snap.name);
|
|
640
603
|
if (Number(result.changes) > 0) {
|
|
@@ -644,7 +607,6 @@ export function storeRawComponents(db, sessionId, components, options) {
|
|
|
644
607
|
unmatchedSnaps.push(snap);
|
|
645
608
|
}
|
|
646
609
|
}
|
|
647
|
-
// Second pass: for renamed props, fall back to position-based matching
|
|
648
610
|
for (const snap of unmatchedSnaps) {
|
|
649
611
|
const result = updateCDFByPosition.run(snap.cdf_type, snap.cdf_category, snap.cdf_token_kind, sessionId, snap.component_id, snap.position);
|
|
650
612
|
if (Number(result.changes) > 0) {
|
|
@@ -660,7 +622,6 @@ export function storeRawComponents(db, sessionId, components, options) {
|
|
|
660
622
|
for (const snap of descSnapshot) {
|
|
661
623
|
updateDesc.run(snap.description, sessionId, snap.component_id);
|
|
662
624
|
}
|
|
663
|
-
// Restore allowed values only for props that still exist (to avoid FK violations)
|
|
664
625
|
const relevantAV = avSnapshot.filter((av) => restoredPropKeys.has(`${av.component_id}::${av.prop_name}`));
|
|
665
626
|
if (relevantAV.length > 0) {
|
|
666
627
|
const deleteAV = db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ? AND prop_name = ?`);
|
|
@@ -712,7 +673,6 @@ export function loadRawComponents(db, sessionId, allowedNames) {
|
|
|
712
673
|
.prepare(`SELECT component_id, slot_name, position, allowed_component
|
|
713
674
|
FROM raw_slot_allowed_components WHERE session_id = ? ORDER BY component_id, slot_name, position`)
|
|
714
675
|
.all(sessionId);
|
|
715
|
-
// Group children by component_id
|
|
716
676
|
const propsByComponent = groupBy(props, (p) => p.component_id);
|
|
717
677
|
const allowedValuesByProp = groupBy(allowedValues, (av) => `${av.component_id}::${av.prop_name}`);
|
|
718
678
|
const slotsByComponent = groupBy(slots, (s) => s.component_id);
|
|
@@ -770,12 +730,6 @@ export function loadRawComponents(db, sessionId, allowedNames) {
|
|
|
770
730
|
}),
|
|
771
731
|
}));
|
|
772
732
|
}
|
|
773
|
-
/**
|
|
774
|
-
* Rename empty-named slots in the DB to heuristic names before generation.
|
|
775
|
-
* The LLM classify_slot tool call is matched back to the DB row by name, so a
|
|
776
|
-
* row with name="" can never be updated by the LLM — renaming it here makes it
|
|
777
|
-
* classifiable. Returns one warning string per renamed slot.
|
|
778
|
-
*/
|
|
779
733
|
export function renameEmptySlots(db, sessionId, componentId, componentName, slotCount) {
|
|
780
734
|
const emptySlots = db
|
|
781
735
|
.prepare(`SELECT name, position FROM raw_slots
|
|
@@ -787,15 +741,9 @@ export function renameEmptySlots(db, sessionId, componentId, componentName, slot
|
|
|
787
741
|
const renames = [];
|
|
788
742
|
const warnings = [];
|
|
789
743
|
const rename = db.prepare(`UPDATE raw_slots SET name = ? WHERE session_id = ? AND component_id = ? AND name = ? AND position = ?`);
|
|
790
|
-
// Multi-statement write — wrap in a transaction so a SIGINT or driver error
|
|
791
|
-
// mid-loop leaves raw_slots either fully renamed or fully untouched, never
|
|
792
|
-
// half-renamed (which would corrupt the prompt-vs-DB invariant the LLM
|
|
793
|
-
// relies on for classify_slot). Matches the existing BEGIN/COMMIT pattern
|
|
794
|
-
// throughout this file (see storeRawComponents, createStep, etc.).
|
|
795
744
|
db.exec('BEGIN');
|
|
796
745
|
try {
|
|
797
746
|
for (const slot of emptySlots) {
|
|
798
|
-
// Single unnamed slot → "children". Multiple → positional names.
|
|
799
747
|
const newName = slotCount === 1 ? 'children' : `slot_${slot.position}`;
|
|
800
748
|
rename.run(newName, sessionId, componentId, slot.name, slot.position);
|
|
801
749
|
renames.push({ oldName: slot.name, newName });
|
|
@@ -834,6 +782,11 @@ export function storeCDFComponents(db, sessionId, components) {
|
|
|
834
782
|
VALUES (?, ?, ?, ?, ?)`);
|
|
835
783
|
const insertAllowedComponent = db.prepare(`INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position)
|
|
836
784
|
VALUES (?, ?, ?, ?, ?)`);
|
|
785
|
+
const deleteSlots = db.prepare(`DELETE FROM raw_slots WHERE session_id = ? AND component_id = ?`);
|
|
786
|
+
const deleteSlotAllowedComponents = db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`);
|
|
787
|
+
const readExistingSlotDefaults = db.prepare(`SELECT name, is_default FROM raw_slots WHERE session_id = ? AND component_id = ?`);
|
|
788
|
+
const insertSlotOnUpdate = db.prepare(`INSERT INTO raw_slots (session_id, component_id, name, is_default, required, description, position)
|
|
789
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
837
790
|
db.exec('BEGIN');
|
|
838
791
|
try {
|
|
839
792
|
for (const { key, entry } of components) {
|
|
@@ -850,9 +803,19 @@ export function storeCDFComponents(db, sessionId, components) {
|
|
|
850
803
|
prop.$values.forEach((v, i) => insertAllowedValue.run(sessionId, componentId, propName, v, i));
|
|
851
804
|
}
|
|
852
805
|
}
|
|
806
|
+
const existingDefaults = new Map(readExistingSlotDefaults.all(sessionId, componentId).map((r) => [r.name, r.is_default]));
|
|
807
|
+
deleteSlotAllowedComponents.run(sessionId, componentId);
|
|
808
|
+
deleteSlots.run(sessionId, componentId);
|
|
809
|
+
let slotPos = 0;
|
|
810
|
+
for (const [slotName, slot] of Object.entries(entry.$slots ?? {})) {
|
|
811
|
+
const isDefault = existingDefaults.get(slotName) ?? 0;
|
|
812
|
+
insertSlotOnUpdate.run(sessionId, componentId, slotName, isDefault, slot.$required ? 1 : 0, slot.$description ?? null, slotPos++);
|
|
813
|
+
if (slot.$allowedComponents && slot.$allowedComponents.length > 0) {
|
|
814
|
+
slot.$allowedComponents.forEach((ac, i) => insertAllowedComponent.run(sessionId, componentId, slotName, ac, i));
|
|
815
|
+
}
|
|
816
|
+
}
|
|
853
817
|
}
|
|
854
818
|
else {
|
|
855
|
-
// Component not in raw_components (e.g. agent added a new one) — insert a minimal record
|
|
856
819
|
const componentId = createHash('sha256').update(`${key}:generated`).digest('hex').slice(0, 12);
|
|
857
820
|
db.prepare(`INSERT INTO raw_components (session_id, component_id, name, source, framework, extracted_at, status, description)
|
|
858
821
|
VALUES (?, ?, ?, '', 'react', ?, 'generated', ?)`).run(sessionId, componentId, key, now, entry.$description ?? null);
|
|
@@ -915,20 +878,8 @@ export function loadCDFComponents(db, sessionId) {
|
|
|
915
878
|
const allowedComponentsBySlot = groupBy(allowedComponents, (ac) => `${ac.component_id}::${ac.slot_name}`);
|
|
916
879
|
return components.map(({ component_id, name, description }) => {
|
|
917
880
|
const compProps = propsByComponent.get(component_id) ?? [];
|
|
918
|
-
// NOTE: Components with zero CDF-classified props are intentionally
|
|
919
|
-
// returned with an empty `$properties` object. Filtering them out here
|
|
920
|
-
// silently dropped them from the wizard's final-review (INTEG-4257),
|
|
921
|
-
// so the user couldn't see that the LLM had failed to classify anything
|
|
922
|
-
// and couldn't reject the empty component. Surface them instead — the
|
|
923
|
-
// wizard tags them with a ⚠ badge and an in-panel banner so the user
|
|
924
|
-
// can manually add props in FieldEditor or reject the component.
|
|
925
|
-
// Downstream consumers (buildManifest, push) tolerate empty $properties.
|
|
926
881
|
const $properties = {};
|
|
927
882
|
for (const p of compProps) {
|
|
928
|
-
// Hallucination insurance: drop any prop whose name didn't survive trim.
|
|
929
|
-
// The pre-generate rename guard catches empty-named slots, but if the LLM
|
|
930
|
-
// hallucinated a classify_prop with an empty name into the DB, surface
|
|
931
|
-
// nothing to the manifest builder rather than emitting "$properties: { '': ... }".
|
|
932
883
|
if (!p.name.trim())
|
|
933
884
|
continue;
|
|
934
885
|
const av = allowedValuesByProp.get(`${component_id}::${p.name}`);
|
|
@@ -957,9 +908,6 @@ export function loadCDFComponents(db, sessionId) {
|
|
|
957
908
|
const compSlots = slotsByComponent.get(component_id) ?? [];
|
|
958
909
|
const $slots = {};
|
|
959
910
|
for (const s of compSlots) {
|
|
960
|
-
// Hallucination insurance: same as $properties — drop empty-named slots
|
|
961
|
-
// before they reach buildManifest, which faithfully passes empty keys
|
|
962
|
-
// through and triggers a 422 from the preview API.
|
|
963
911
|
if (!s.name.trim())
|
|
964
912
|
continue;
|
|
965
913
|
const ac = allowedComponentsBySlot.get(`${component_id}::${s.name}`);
|
|
@@ -981,30 +929,39 @@ export function loadCDFComponents(db, sessionId) {
|
|
|
981
929
|
});
|
|
982
930
|
}
|
|
983
931
|
export function loadScopeComponents(db, sessionId) {
|
|
984
|
-
// Loads extract-stage components: those still pending operator review (`extracted`)
|
|
985
|
-
// plus those the Feature 3 auto-filter has already classified (`accepted` /
|
|
986
|
-
// `rejected`). Excludes `generated` (already past scope-gate) so re-runs of the
|
|
987
|
-
// wizard don't show already-confirmed components.
|
|
988
932
|
const rows = db
|
|
989
933
|
.prepare(`SELECT name, component_id, status, reject_reason FROM raw_components
|
|
990
934
|
WHERE session_id = ? AND status IN ('extracted', 'accepted', 'rejected')
|
|
991
935
|
ORDER BY name`)
|
|
992
936
|
.all(sessionId);
|
|
937
|
+
if (rows.length === 0)
|
|
938
|
+
return [];
|
|
939
|
+
const slotRows = db
|
|
940
|
+
.prepare(`SELECT component_id, name, position
|
|
941
|
+
FROM raw_slots WHERE session_id = ? ORDER BY component_id, position`)
|
|
942
|
+
.all(sessionId);
|
|
943
|
+
const allowedRows = db
|
|
944
|
+
.prepare(`SELECT component_id, slot_name, position, allowed_component
|
|
945
|
+
FROM raw_slot_allowed_components WHERE session_id = ? ORDER BY component_id, slot_name, position`)
|
|
946
|
+
.all(sessionId);
|
|
947
|
+
const slotsByComponent = groupBy(slotRows, (s) => s.component_id);
|
|
948
|
+
const allowedBySlot = groupBy(allowedRows, (a) => `${a.component_id}::${a.slot_name}`);
|
|
993
949
|
return rows.map((r) => ({
|
|
994
950
|
name: r.name,
|
|
995
951
|
componentId: r.component_id,
|
|
996
952
|
aiDecision: r.status === 'accepted' ? 'accepted' : r.status === 'rejected' ? 'rejected' : null,
|
|
997
953
|
aiReason: r.reject_reason,
|
|
954
|
+
slots: (slotsByComponent.get(r.component_id) ?? []).map((s) => ({
|
|
955
|
+
name: s.name,
|
|
956
|
+
allowedComponents: (allowedBySlot.get(`${r.component_id}::${s.name}`) ?? []).map((a) => a.allowed_component),
|
|
957
|
+
})),
|
|
998
958
|
}));
|
|
999
959
|
}
|
|
1000
960
|
export function applyScopeDecisions(db, sessionId, decisions) {
|
|
1001
961
|
const now = new Date().toISOString();
|
|
1002
962
|
const acceptedSet = new Set(decisions.accepted);
|
|
1003
963
|
const accepted = [...acceptedSet];
|
|
1004
|
-
// Conflict precedence: accepted wins over rejected.
|
|
1005
964
|
const rejected = [...new Set(decisions.rejected)].filter((n) => !acceptedSet.has(n));
|
|
1006
|
-
// Rejected first so accepted writes can flip back to 'generated' afterwards
|
|
1007
|
-
// if a name appears in both lists.
|
|
1008
965
|
if (rejected.length > 0) {
|
|
1009
966
|
const placeholders = rejected.map(() => '?').join(',');
|
|
1010
967
|
db.prepare(`UPDATE raw_components SET status = 'rejected' WHERE session_id = ? AND name IN (${placeholders})`).run(sessionId, ...rejected);
|
|
@@ -1107,8 +1064,6 @@ export function findLatestSessionForCommand(db, command) {
|
|
|
1107
1064
|
return row?.id ?? null;
|
|
1108
1065
|
}
|
|
1109
1066
|
export function seedCDFFromPriorSession(db, targetSessionId) {
|
|
1110
|
-
// Find the most recent session (other than target) that has CDF data for any of the
|
|
1111
|
-
// target's component_ids. This works regardless of which command produced the CDF.
|
|
1112
1067
|
const targetComponentIds = db
|
|
1113
1068
|
.prepare(`SELECT component_id FROM raw_components WHERE session_id = ?`)
|
|
1114
1069
|
.all(targetSessionId);
|
|
@@ -1124,7 +1079,6 @@ export function seedCDFFromPriorSession(db, targetSessionId) {
|
|
|
1124
1079
|
if (!priorRow)
|
|
1125
1080
|
return 0;
|
|
1126
1081
|
const priorSessionId = priorRow.session_id;
|
|
1127
|
-
// Copy CDF columns from prior session's props into matching props in the target session.
|
|
1128
1082
|
const result = db
|
|
1129
1083
|
.prepare(`UPDATE raw_props SET
|
|
1130
1084
|
cdf_type = (SELECT p2.cdf_type FROM raw_props p2
|
|
@@ -1141,7 +1095,6 @@ export function seedCDFFromPriorSession(db, targetSessionId) {
|
|
|
1141
1095
|
WHERE p2.session_id = ? AND p2.component_id = raw_props.component_id
|
|
1142
1096
|
AND p2.name = raw_props.name AND p2.cdf_type IS NOT NULL)`)
|
|
1143
1097
|
.run(priorSessionId, priorSessionId, priorSessionId, targetSessionId, priorSessionId);
|
|
1144
|
-
// Also copy descriptions from prior session's components
|
|
1145
1098
|
db.prepare(`UPDATE raw_components SET description = (
|
|
1146
1099
|
SELECT c2.description FROM raw_components c2
|
|
1147
1100
|
WHERE c2.session_id = ? AND c2.component_id = raw_components.component_id
|
|
@@ -1151,7 +1104,6 @@ export function seedCDFFromPriorSession(db, targetSessionId) {
|
|
|
1151
1104
|
AND EXISTS (SELECT 1 FROM raw_components c2
|
|
1152
1105
|
WHERE c2.session_id = ? AND c2.component_id = raw_components.component_id
|
|
1153
1106
|
AND c2.description IS NOT NULL)`).run(priorSessionId, targetSessionId, priorSessionId);
|
|
1154
|
-
// Copy allowed values for seeded props from prior session
|
|
1155
1107
|
if (result.changes > 0) {
|
|
1156
1108
|
db.prepare(`INSERT OR IGNORE INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value)
|
|
1157
1109
|
SELECT ?, av.component_id, av.prop_name, av.position, av.value
|
|
@@ -1199,12 +1151,6 @@ export function seedCDFFromPreviewResponse(db, sessionId, removedItems) {
|
|
|
1199
1151
|
}
|
|
1200
1152
|
return totalSeeded;
|
|
1201
1153
|
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Seeds server-side metadata for changed components:
|
|
1204
|
-
* 1. Fills in defaults the server has but we don't (prevents accidental removal)
|
|
1205
|
-
* 2. Seeds cdf_type/cdf_category for props that exist on server but lack CDF locally
|
|
1206
|
-
* (e.g. props with non-standard source types the generation step couldn't classify)
|
|
1207
|
-
*/
|
|
1208
1154
|
export function seedDefaultsFromChangedItems(db, sessionId, changedItems) {
|
|
1209
1155
|
if (changedItems.length === 0)
|
|
1210
1156
|
return 0;
|
|
@@ -1224,13 +1170,11 @@ export function seedDefaultsFromChangedItems(db, sessionId, changedItems) {
|
|
|
1224
1170
|
const contentProps = new Set(current.contentProperties);
|
|
1225
1171
|
const designProps = new Set(current.designProperties);
|
|
1226
1172
|
for (const [propName, propSummary] of Object.entries(current.fullProperties)) {
|
|
1227
|
-
// Seed defaults
|
|
1228
1173
|
if (propSummary.default !== undefined && propSummary.default !== null) {
|
|
1229
1174
|
const defaultStr = typeof propSummary.default === 'string' ? propSummary.default : JSON.stringify(propSummary.default);
|
|
1230
1175
|
const result = updateDefaultStmt.run(defaultStr, sessionId, localComponent.component_id, propName);
|
|
1231
1176
|
totalSeeded += Number(result.changes);
|
|
1232
1177
|
}
|
|
1233
|
-
// Seed CDF type/category for props that lack it
|
|
1234
1178
|
const cdfType = mapServerTypeToCDFType(propSummary.type);
|
|
1235
1179
|
if (!cdfType)
|
|
1236
1180
|
continue;
|
|
@@ -1249,14 +1193,7 @@ export function seedDefaultsFromChangedItems(db, sessionId, changedItems) {
|
|
|
1249
1193
|
}
|
|
1250
1194
|
return totalSeeded;
|
|
1251
1195
|
}
|
|
1252
|
-
/**
|
|
1253
|
-
* Ensures all props on generated components have a cdf_type.
|
|
1254
|
-
* Uses the pre-classification `category` column when available.
|
|
1255
|
-
* Falls back to cdf_type: 'string', cdf_category: 'content' for props
|
|
1256
|
-
* with no pre-classification (safe default — content is the most common category).
|
|
1257
|
-
*/
|
|
1258
1196
|
export function backfillUnclassifiedProps(db, sessionId) {
|
|
1259
|
-
// First: backfill props that HAVE a pre-classification category
|
|
1260
1197
|
const withCategory = db
|
|
1261
1198
|
.prepare(`UPDATE raw_props SET cdf_type = 'string', cdf_category = category
|
|
1262
1199
|
WHERE session_id = ? AND cdf_type IS NULL AND category IS NOT NULL
|
|
@@ -1264,7 +1201,6 @@ export function backfillUnclassifiedProps(db, sessionId) {
|
|
|
1264
1201
|
SELECT component_id FROM raw_components WHERE session_id = ? AND status = 'generated'
|
|
1265
1202
|
)`)
|
|
1266
1203
|
.run(sessionId, sessionId);
|
|
1267
|
-
// Second: backfill props with NO category (complex types the rules couldn't classify)
|
|
1268
1204
|
const withoutCategory = db
|
|
1269
1205
|
.prepare(`UPDATE raw_props SET cdf_type = 'string', cdf_category = 'content'
|
|
1270
1206
|
WHERE session_id = ? AND cdf_type IS NULL AND category IS NULL
|
|
@@ -1297,12 +1233,6 @@ function mapServerTypeToCDFType(serverType) {
|
|
|
1297
1233
|
}
|
|
1298
1234
|
}
|
|
1299
1235
|
export function computeComponentInputHash(component) {
|
|
1300
|
-
// Narrow the hashed payload to extractor-only fields. Any field that
|
|
1301
|
-
// `applyToolCalls` may overwrite (prop description/required/default/allowed
|
|
1302
|
-
// values, slot description/allowed components) is excluded so that the cache
|
|
1303
|
-
// key does not depend on its own output — otherwise re-reading raw props
|
|
1304
|
-
// after a generation pass would drift the hash even when the underlying
|
|
1305
|
-
// source has not changed.
|
|
1306
1236
|
const payload = {
|
|
1307
1237
|
framework: component.framework,
|
|
1308
1238
|
name: component.name,
|
|
@@ -1314,6 +1244,7 @@ export function computeComponentInputHash(component) {
|
|
|
1314
1244
|
slots: component.slots.map((s) => ({
|
|
1315
1245
|
name: s.name,
|
|
1316
1246
|
isDefault: s.isDefault,
|
|
1247
|
+
allowedComponents: [...(s.allowedComponents ?? [])].sort(),
|
|
1317
1248
|
})),
|
|
1318
1249
|
};
|
|
1319
1250
|
return createHash('sha256').update(JSON.stringify(payload)).digest('hex');
|
|
@@ -1388,6 +1319,36 @@ export function loadScannedFiles(db, sessionId) {
|
|
|
1388
1319
|
const rows = db.prepare('SELECT path FROM scanned_files WHERE session_id = ? ORDER BY path').all(sessionId);
|
|
1389
1320
|
return rows.map((r) => r.path);
|
|
1390
1321
|
}
|
|
1322
|
+
export function storeSlotCycles(db, sessionId, cycles) {
|
|
1323
|
+
db.exec('BEGIN');
|
|
1324
|
+
try {
|
|
1325
|
+
db.prepare('DELETE FROM slot_cycles WHERE session_id = ?').run(sessionId);
|
|
1326
|
+
const insert = db.prepare(`INSERT INTO slot_cycles (session_id, cycle_index, path_json, edges_json, suggested_break_json)
|
|
1327
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
1328
|
+
for (let i = 0; i < cycles.length; i += 1) {
|
|
1329
|
+
const cycle = cycles[i];
|
|
1330
|
+
insert.run(sessionId, i, JSON.stringify(cycle.path), JSON.stringify(cycle.edges), cycle.suggestedBreak ? JSON.stringify(cycle.suggestedBreak) : null);
|
|
1331
|
+
}
|
|
1332
|
+
db.exec('COMMIT');
|
|
1333
|
+
}
|
|
1334
|
+
catch (e) {
|
|
1335
|
+
db.exec('ROLLBACK');
|
|
1336
|
+
throw e;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
export function loadSlotCycles(db, sessionId) {
|
|
1340
|
+
const rows = db
|
|
1341
|
+
.prepare('SELECT cycle_index, path_json, edges_json, suggested_break_json FROM slot_cycles WHERE session_id = ? ORDER BY cycle_index')
|
|
1342
|
+
.all(sessionId);
|
|
1343
|
+
return rows.map((r) => ({
|
|
1344
|
+
path: JSON.parse(r.path_json),
|
|
1345
|
+
edges: JSON.parse(r.edges_json),
|
|
1346
|
+
suggestedBreak: r.suggested_break_json ? JSON.parse(r.suggested_break_json) : null,
|
|
1347
|
+
}));
|
|
1348
|
+
}
|
|
1349
|
+
export function clearSlotCycles(db, sessionId) {
|
|
1350
|
+
db.prepare('DELETE FROM slot_cycles WHERE session_id = ?').run(sessionId);
|
|
1351
|
+
}
|
|
1391
1352
|
export function markCacheHumanEdited(db, entityType, entityId) {
|
|
1392
1353
|
const now = new Date().toISOString();
|
|
1393
1354
|
db.prepare(`UPDATE generation_cache SET human_edited = 1, updated_at = ? WHERE entity_type = ? AND entity_id = ?`).run(now, entityType, entityId);
|
|
@@ -1458,18 +1419,7 @@ export function copyTokensFromCache(db, sourceSessionId, targetSessionId) {
|
|
|
1458
1419
|
throw e;
|
|
1459
1420
|
}
|
|
1460
1421
|
}
|
|
1461
|
-
// ---------------------------------------------------------------------------
|
|
1462
|
-
// Fine-grained cache: extract_cache helpers
|
|
1463
|
-
// ---------------------------------------------------------------------------
|
|
1464
1422
|
let _cliCacheVersionCache = null;
|
|
1465
|
-
/**
|
|
1466
|
-
* Returns the CLI cache version used to namespace cache rows. To avoid busting
|
|
1467
|
-
* the cache on every npm patch bump, this hashes the content of the bundled
|
|
1468
|
-
* skill prompts. When the prompts change, cache invalidates; when only library
|
|
1469
|
-
* code or test fixtures change, the cache stays warm.
|
|
1470
|
-
*
|
|
1471
|
-
* Memoized per-process. Falls back to package version on any I/O error.
|
|
1472
|
-
*/
|
|
1473
1423
|
export async function getCliCacheVersion() {
|
|
1474
1424
|
if (_cliCacheVersionCache)
|
|
1475
1425
|
return _cliCacheVersionCache;
|
|
@@ -1529,6 +1479,28 @@ export function lookupExtractCache(db, fileHash, cliVersion) {
|
|
|
1529
1479
|
components,
|
|
1530
1480
|
};
|
|
1531
1481
|
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Content-addressed cache for the composition resolver's raw agent output
|
|
1484
|
+
* (authored parser source or emitted-edge JSONL). Keyed by a hash of the
|
|
1485
|
+
* candidate files + producer identity (`composition-cache-key.ts`), scoped by
|
|
1486
|
+
* `cli_version` so a prompt/skill change invalidates stale entries. Replaces
|
|
1487
|
+
* the former on-disk `~/.contentful/.../composition-cache` store so all CLI
|
|
1488
|
+
* caches live in the one pipeline DB.
|
|
1489
|
+
*/
|
|
1490
|
+
export function storeCompositionCache(db, inputHash, cliVersion, agentOutput) {
|
|
1491
|
+
const now = new Date().toISOString();
|
|
1492
|
+
db.prepare(`INSERT INTO composition_cache (input_hash, cli_version, agent_output, created_at, updated_at)
|
|
1493
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1494
|
+
ON CONFLICT(input_hash, cli_version) DO UPDATE SET
|
|
1495
|
+
agent_output = excluded.agent_output,
|
|
1496
|
+
updated_at = excluded.updated_at`).run(inputHash, cliVersion, agentOutput, now, now);
|
|
1497
|
+
}
|
|
1498
|
+
export function lookupCompositionCache(db, inputHash, cliVersion) {
|
|
1499
|
+
const row = db
|
|
1500
|
+
.prepare(`SELECT agent_output FROM composition_cache WHERE input_hash = ? AND cli_version = ?`)
|
|
1501
|
+
.get(inputHash, cliVersion);
|
|
1502
|
+
return row ? row.agent_output : null;
|
|
1503
|
+
}
|
|
1532
1504
|
export function storeSelectCache(db, componentHash, promptHash, cliVersion, decision, reason) {
|
|
1533
1505
|
const now = new Date().toISOString();
|
|
1534
1506
|
db.prepare(`INSERT INTO select_cache (component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CompositionMode } from '../lib/composition-mode.js';
|
|
2
|
+
/**
|
|
3
|
+
* Setup prompts for composition (spec T10). Parallels `promptAutoFilterPreference`
|
|
4
|
+
* — injectable `ask` for TTY-free unit testing.
|
|
5
|
+
*
|
|
6
|
+
* (a) Composite-mode default. Default N → atomic (composition OFF); the
|
|
7
|
+
* low-surprise default. `--composite` opts in per invocation.
|
|
8
|
+
*/
|
|
9
|
+
export declare function promptCompositeModePreference(ask: (q: string) => Promise<string>, current?: CompositionMode): Promise<CompositionMode>;
|
|
10
|
+
/**
|
|
11
|
+
* (b) Agentic mapping resolution. Default N — opt-in (it costs tokens and the
|
|
12
|
+
* output needs review), and only meaningful in composite mode.
|
|
13
|
+
*/
|
|
14
|
+
export declare function promptAgenticResolutionPreference(ask: (q: string) => Promise<string>, current?: boolean): Promise<boolean>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup prompts for composition (spec T10). Parallels `promptAutoFilterPreference`
|
|
3
|
+
* — injectable `ask` for TTY-free unit testing.
|
|
4
|
+
*
|
|
5
|
+
* (a) Composite-mode default. Default N → atomic (composition OFF); the
|
|
6
|
+
* low-surprise default. `--composite` opts in per invocation.
|
|
7
|
+
*/
|
|
8
|
+
export async function promptCompositeModePreference(ask, current) {
|
|
9
|
+
const defaultValue = current ?? 'atomic';
|
|
10
|
+
const hint = defaultValue === 'composite' ? '[Y/n]' : '[y/N]';
|
|
11
|
+
const answer = (await ask(` Enable composite (embedded-component) mode by default? ${hint} `)).trim().toLowerCase();
|
|
12
|
+
if (answer === '')
|
|
13
|
+
return defaultValue;
|
|
14
|
+
if (answer.startsWith('y'))
|
|
15
|
+
return 'composite';
|
|
16
|
+
if (answer.startsWith('n'))
|
|
17
|
+
return 'atomic';
|
|
18
|
+
return defaultValue;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* (b) Agentic mapping resolution. Default N — opt-in (it costs tokens and the
|
|
22
|
+
* output needs review), and only meaningful in composite mode.
|
|
23
|
+
*/
|
|
24
|
+
export async function promptAgenticResolutionPreference(ask, current) {
|
|
25
|
+
const defaultValue = current ?? false;
|
|
26
|
+
const hint = defaultValue ? '[Y/n]' : '[y/N]';
|
|
27
|
+
const answer = (await ask(` Enable agentic mapping resolution when no groups are found? ${hint} `))
|
|
28
|
+
.trim()
|
|
29
|
+
.toLowerCase();
|
|
30
|
+
if (answer === '')
|
|
31
|
+
return defaultValue;
|
|
32
|
+
if (answer.startsWith('y'))
|
|
33
|
+
return true;
|
|
34
|
+
if (answer.startsWith('n'))
|
|
35
|
+
return false;
|
|
36
|
+
return defaultValue;
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"bin/",
|
|
27
27
|
"dist/",
|
|
28
|
-
"skills/"
|
|
28
|
+
"skills/",
|
|
29
|
+
"prompts/"
|
|
29
30
|
],
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"commander": "^13.1.0",
|
|
@@ -33,8 +34,8 @@
|
|
|
33
34
|
"react": "^18.3.1",
|
|
34
35
|
"react-devtools-core": "^4.19.1",
|
|
35
36
|
"react-dom": "^18.3.1",
|
|
36
|
-
"@contentful/experience-design-system-
|
|
37
|
-
"@contentful/experience-design-system-
|
|
37
|
+
"@contentful/experience-design-system-types": "2.15.0",
|
|
38
|
+
"@contentful/experience-design-system-extraction": "2.15.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@tsconfig/node24": "^24.0.3",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
These directories were NOT selected as composition candidates by a keyword filter.
|
|
2
|
+
Judging by the PATH/NAME alone, which (if any) might contain component composition,
|
|
3
|
+
mapping, or parent→child relationship declarations worth inspecting? Be selective —
|
|
4
|
+
skip obvious utilities, tests, styles, and unrelated feature code.
|
|
5
|
+
|
|
6
|
+
Reply with ONLY a JSON array of the directory strings to include, e.g. ["src/registry"].
|
|
7
|
+
Use [] if none look relevant. Copy the strings exactly as listed.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
You are extracting parent→child component composition from a design system by reading the files below.
|
|
2
|
+
|
|
3
|
+
STRICT RULES — follow exactly, they keep the output deterministic:
|
|
4
|
+
1. Emit an edge ONLY when the candidate files contain explicit evidence that the parent renders/accepts the child (e.g. a mapping declaration, a slot/`allowedComponents` list, a `withParentType`/`requiredParent`/`allowedTagNames` entry). Direct textual evidence only.
|
|
5
|
+
2. Do NOT infer, guess, or generalize from naming, category, or what "usually" nests. If the files do not state the relationship, do not emit it.
|
|
6
|
+
3. Every edge MUST include a `reason` that quotes or cites the exact file + declaration that justifies it. If you cannot cite evidence, omit the edge.
|
|
7
|
+
4. Emit each parent→child pair at most once. Do not repeat edges.
|
|
8
|
+
5. Prefer completeness of EVIDENCED edges over quantity — a smaller, fully-justified set is correct; padding with plausible-but-unstated edges is wrong.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Your previous parser ran without error but returned 0 edges, even though the candidate files clearly contain composition relationships. Re-examine the patterns (parent attribution is the usual culprit) and return only the corrected function in a single fenced code block.
|