@contentful/experience-design-system-cli 2.2.1
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 +532 -0
- package/bin/cli.js +58 -0
- package/dist/package.json +56 -0
- package/dist/src/analyze/command.d.ts +3 -0
- package/dist/src/analyze/command.js +175 -0
- package/dist/src/analyze/extract/astro.d.ts +5 -0
- package/dist/src/analyze/extract/astro.js +280 -0
- package/dist/src/analyze/extract/pipeline.d.ts +6 -0
- package/dist/src/analyze/extract/pipeline.js +298 -0
- package/dist/src/analyze/extract/react.d.ts +2 -0
- package/dist/src/analyze/extract/react.js +1949 -0
- package/dist/src/analyze/extract/slot-detection.d.ts +35 -0
- package/dist/src/analyze/extract/slot-detection.js +101 -0
- package/dist/src/analyze/extract/stencil.d.ts +2 -0
- package/dist/src/analyze/extract/stencil.js +293 -0
- package/dist/src/analyze/extract/tsx-shared.d.ts +8 -0
- package/dist/src/analyze/extract/tsx-shared.js +263 -0
- package/dist/src/analyze/extract/vue-tsx.d.ts +2 -0
- package/dist/src/analyze/extract/vue-tsx.js +498 -0
- package/dist/src/analyze/extract/vue.d.ts +5 -0
- package/dist/src/analyze/extract/vue.js +647 -0
- package/dist/src/analyze/extract/web-components.d.ts +2 -0
- package/dist/src/analyze/extract/web-components.js +866 -0
- package/dist/src/analyze/pre-classify.d.ts +17 -0
- package/dist/src/analyze/pre-classify.js +144 -0
- package/dist/src/analyze/select/command.d.ts +2 -0
- package/dist/src/analyze/select/command.js +256 -0
- package/dist/src/analyze/select/index.d.ts +6 -0
- package/dist/src/analyze/select/index.js +5 -0
- package/dist/src/analyze/select/parser.d.ts +6 -0
- package/dist/src/analyze/select/parser.js +53 -0
- package/dist/src/analyze/select/persistence.d.ts +9 -0
- package/dist/src/analyze/select/persistence.js +42 -0
- package/dist/src/analyze/select/stdout.d.ts +7 -0
- package/dist/src/analyze/select/stdout.js +3 -0
- package/dist/src/analyze/select/tui/App.d.ts +8 -0
- package/dist/src/analyze/select/tui/App.js +491 -0
- package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +20 -0
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +43 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.js +531 -0
- package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +10 -0
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +15 -0
- package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +7 -0
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +11 -0
- package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/JsonEditor.js +154 -0
- package/dist/src/analyze/select/tui/components/JsonPanel.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/JsonPanel.js +62 -0
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.d.ts +8 -0
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +29 -0
- package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +8 -0
- package/dist/src/analyze/select/tui/components/QuitDialog.js +14 -0
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +15 -0
- package/dist/src/analyze/select/tui/components/Sidebar.js +48 -0
- package/dist/src/analyze/select/tui/components/SourcePanel.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/SourcePanel.js +52 -0
- package/dist/src/analyze/select/tui/components/StatusBar.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/StatusBar.js +6 -0
- package/dist/src/analyze/select/tui/components/TopBar.d.ts +10 -0
- package/dist/src/analyze/select/tui/components/TopBar.js +5 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +24 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +68 -0
- package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +24 -0
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -0
- package/dist/src/analyze/select/tui/hooks/useSession.d.ts +19 -0
- package/dist/src/analyze/select/tui/hooks/useSession.js +52 -0
- package/dist/src/analyze/select/tui/hooks/useUndo.d.ts +8 -0
- package/dist/src/analyze/select/tui/hooks/useUndo.js +26 -0
- package/dist/src/analyze/select/types.d.ts +46 -0
- package/dist/src/analyze/select/types.js +20 -0
- package/dist/src/analyze/select-agent/command.d.ts +2 -0
- package/dist/src/analyze/select-agent/command.js +208 -0
- package/dist/src/analyze/tui/AnalyzeView.d.ts +24 -0
- package/dist/src/analyze/tui/AnalyzeView.js +38 -0
- package/dist/src/apply/api-client.d.ts +35 -0
- package/dist/src/apply/api-client.js +143 -0
- package/dist/src/apply/command.d.ts +6 -0
- package/dist/src/apply/command.js +787 -0
- package/dist/src/apply/manifest.d.ts +1 -0
- package/dist/src/apply/manifest.js +1 -0
- package/dist/src/apply/tui/SelectView.d.ts +18 -0
- package/dist/src/apply/tui/SelectView.js +34 -0
- package/dist/src/apply/tui/ServerApplyView.d.ts +32 -0
- package/dist/src/apply/tui/ServerApplyView.js +42 -0
- package/dist/src/apply/tui/ServerPreviewView.d.ts +9 -0
- package/dist/src/apply/tui/ServerPreviewView.js +21 -0
- package/dist/src/credentials-store.d.ts +8 -0
- package/dist/src/credentials-store.js +30 -0
- package/dist/src/generate/agent-runner.d.ts +86 -0
- package/dist/src/generate/agent-runner.js +314 -0
- package/dist/src/generate/command.d.ts +2 -0
- package/dist/src/generate/command.js +545 -0
- package/dist/src/generate/edit/command.d.ts +2 -0
- package/dist/src/generate/edit/command.js +126 -0
- package/dist/src/generate/prompt-builder.d.ts +18 -0
- package/dist/src/generate/prompt-builder.js +202 -0
- package/dist/src/generate/tui/GenerateView.d.ts +12 -0
- package/dist/src/generate/tui/GenerateView.js +10 -0
- package/dist/src/import/command.d.ts +2 -0
- package/dist/src/import/command.js +96 -0
- package/dist/src/import/orchestrator.d.ts +37 -0
- package/dist/src/import/orchestrator.js +374 -0
- package/dist/src/import/path-utils.d.ts +15 -0
- package/dist/src/import/path-utils.js +30 -0
- package/dist/src/import/tui/WizardApp.d.ts +10 -0
- package/dist/src/import/tui/WizardApp.js +906 -0
- package/dist/src/import/tui/steps/CredentialsStep.d.ts +15 -0
- package/dist/src/import/tui/steps/CredentialsStep.js +79 -0
- package/dist/src/import/tui/steps/DoneStep.d.ts +20 -0
- package/dist/src/import/tui/steps/DoneStep.js +17 -0
- package/dist/src/import/tui/steps/ErrorStep.d.ts +8 -0
- package/dist/src/import/tui/steps/ErrorStep.js +11 -0
- package/dist/src/import/tui/steps/GateStep.d.ts +14 -0
- package/dist/src/import/tui/steps/GateStep.js +20 -0
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +8 -0
- package/dist/src/import/tui/steps/GenerateReviewStep.js +208 -0
- package/dist/src/import/tui/steps/PathValidationStep.d.ts +10 -0
- package/dist/src/import/tui/steps/PathValidationStep.js +151 -0
- package/dist/src/import/tui/steps/PreviewStep.d.ts +21 -0
- package/dist/src/import/tui/steps/PreviewStep.js +36 -0
- package/dist/src/import/tui/steps/RunningStep.d.ts +10 -0
- package/dist/src/import/tui/steps/RunningStep.js +20 -0
- package/dist/src/import/tui/steps/TokenInputStep.d.ts +8 -0
- package/dist/src/import/tui/steps/TokenInputStep.js +70 -0
- package/dist/src/import/tui/steps/WelcomeStep.d.ts +7 -0
- package/dist/src/import/tui/steps/WelcomeStep.js +33 -0
- package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +15 -0
- package/dist/src/import/tui/steps/WizardPreviewStep.js +121 -0
- package/dist/src/import/tui/steps/preview-diff.d.ts +10 -0
- package/dist/src/import/tui/steps/preview-diff.js +132 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/output/format.d.ts +23 -0
- package/dist/src/output/format.js +110 -0
- package/dist/src/print/command.d.ts +2 -0
- package/dist/src/print/command.js +199 -0
- package/dist/src/print/validate/tui/ValidateView.d.ts +15 -0
- package/dist/src/print/validate/tui/ValidateView.js +37 -0
- package/dist/src/print/validate/validators/cdf-validator.d.ts +2 -0
- package/dist/src/print/validate/validators/cdf-validator.js +104 -0
- package/dist/src/print/validate/validators/dtcg-validator.d.ts +2 -0
- package/dist/src/print/validate/validators/dtcg-validator.js +110 -0
- package/dist/src/print/validate/validators/format-errors.d.ts +12 -0
- package/dist/src/print/validate/validators/format-errors.js +18 -0
- package/dist/src/program.d.ts +2 -0
- package/dist/src/program.js +25 -0
- package/dist/src/session/command.d.ts +2 -0
- package/dist/src/session/command.js +261 -0
- package/dist/src/session/db.d.ts +111 -0
- package/dist/src/session/db.js +1114 -0
- package/dist/src/session/migration.d.ts +4 -0
- package/dist/src/session/migration.js +117 -0
- package/dist/src/session/session-id.d.ts +1 -0
- package/dist/src/session/session-id.js +212 -0
- package/dist/src/session/stats.d.ts +27 -0
- package/dist/src/session/stats.js +89 -0
- package/dist/src/setup/command.d.ts +2 -0
- package/dist/src/setup/command.js +765 -0
- package/dist/src/types.d.ts +48 -0
- package/dist/src/types.js +1 -0
- package/package.json +55 -0
- package/skills/generate-components.md +361 -0
- package/skills/generate-tokens.md +194 -0
- package/skills/select-components.md +180 -0
|
@@ -0,0 +1,1114 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { generateSessionId } from './session-id.js';
|
|
7
|
+
const SCHEMA = `
|
|
8
|
+
PRAGMA journal_mode = WAL;
|
|
9
|
+
PRAGMA foreign_keys = ON;
|
|
10
|
+
|
|
11
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
12
|
+
name TEXT NOT NULL PRIMARY KEY,
|
|
13
|
+
applied_at TEXT NOT NULL
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
17
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
18
|
+
name TEXT,
|
|
19
|
+
created_at TEXT NOT NULL,
|
|
20
|
+
updated_at TEXT NOT NULL
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS steps (
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
26
|
+
command TEXT NOT NULL,
|
|
27
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
28
|
+
started_at TEXT NOT NULL,
|
|
29
|
+
completed_at TEXT,
|
|
30
|
+
inputs TEXT NOT NULL DEFAULT '{}',
|
|
31
|
+
outputs TEXT NOT NULL DEFAULT '{}',
|
|
32
|
+
error TEXT,
|
|
33
|
+
updated_at TEXT NOT NULL
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS raw_components (
|
|
37
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
38
|
+
component_id TEXT NOT NULL,
|
|
39
|
+
name TEXT NOT NULL,
|
|
40
|
+
source TEXT NOT NULL,
|
|
41
|
+
framework TEXT NOT NULL,
|
|
42
|
+
extracted_at TEXT NOT NULL,
|
|
43
|
+
status TEXT NOT NULL DEFAULT 'extracted',
|
|
44
|
+
cdf_schema TEXT,
|
|
45
|
+
description TEXT,
|
|
46
|
+
PRIMARY KEY (session_id, component_id)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS raw_props (
|
|
50
|
+
session_id TEXT NOT NULL,
|
|
51
|
+
component_id TEXT NOT NULL,
|
|
52
|
+
name TEXT NOT NULL,
|
|
53
|
+
type TEXT NOT NULL,
|
|
54
|
+
required INTEGER NOT NULL CHECK (required IN (0, 1)),
|
|
55
|
+
category TEXT CHECK (category IN ('content', 'design', 'state')),
|
|
56
|
+
default_value TEXT,
|
|
57
|
+
description TEXT,
|
|
58
|
+
token_reference TEXT,
|
|
59
|
+
position INTEGER NOT NULL,
|
|
60
|
+
cdf_type TEXT,
|
|
61
|
+
cdf_category TEXT CHECK (cdf_category IN ('content', 'design', 'state')),
|
|
62
|
+
cdf_token_kind TEXT,
|
|
63
|
+
PRIMARY KEY (session_id, component_id, name),
|
|
64
|
+
FOREIGN KEY (session_id, component_id) REFERENCES raw_components(session_id, component_id) ON DELETE CASCADE
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE TABLE IF NOT EXISTS raw_prop_allowed_values (
|
|
68
|
+
session_id TEXT NOT NULL,
|
|
69
|
+
component_id TEXT NOT NULL,
|
|
70
|
+
prop_name TEXT NOT NULL,
|
|
71
|
+
position INTEGER NOT NULL,
|
|
72
|
+
value TEXT NOT NULL,
|
|
73
|
+
PRIMARY KEY (session_id, component_id, prop_name, position),
|
|
74
|
+
FOREIGN KEY (session_id, component_id, prop_name) REFERENCES raw_props(session_id, component_id, name) ON DELETE CASCADE
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
CREATE TABLE IF NOT EXISTS raw_slots (
|
|
78
|
+
session_id TEXT NOT NULL,
|
|
79
|
+
component_id TEXT NOT NULL,
|
|
80
|
+
name TEXT NOT NULL,
|
|
81
|
+
is_default INTEGER NOT NULL CHECK (is_default IN (0, 1)),
|
|
82
|
+
description TEXT,
|
|
83
|
+
position INTEGER NOT NULL,
|
|
84
|
+
required INTEGER NOT NULL DEFAULT 1 CHECK (required IN (0, 1)),
|
|
85
|
+
PRIMARY KEY (session_id, component_id, name),
|
|
86
|
+
FOREIGN KEY (session_id, component_id) REFERENCES raw_components(session_id, component_id) ON DELETE CASCADE
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
CREATE TABLE IF NOT EXISTS raw_slot_allowed_components (
|
|
90
|
+
session_id TEXT NOT NULL,
|
|
91
|
+
component_id TEXT NOT NULL,
|
|
92
|
+
slot_name TEXT NOT NULL,
|
|
93
|
+
position INTEGER NOT NULL,
|
|
94
|
+
allowed_component TEXT NOT NULL,
|
|
95
|
+
PRIMARY KEY (session_id, component_id, slot_name, position),
|
|
96
|
+
FOREIGN KEY (session_id, component_id, slot_name) REFERENCES raw_slots(session_id, component_id, name) ON DELETE CASCADE
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
CREATE TABLE IF NOT EXISTS raw_token_groups (
|
|
100
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
101
|
+
path TEXT NOT NULL,
|
|
102
|
+
description TEXT,
|
|
103
|
+
PRIMARY KEY (session_id, path)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CREATE TABLE IF NOT EXISTS raw_tokens (
|
|
107
|
+
session_id TEXT NOT NULL,
|
|
108
|
+
path TEXT NOT NULL,
|
|
109
|
+
type TEXT NOT NULL,
|
|
110
|
+
value TEXT NOT NULL,
|
|
111
|
+
description TEXT,
|
|
112
|
+
PRIMARY KEY (session_id, path),
|
|
113
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
CREATE TABLE IF NOT EXISTS generation_cache (
|
|
117
|
+
input_hash TEXT NOT NULL,
|
|
118
|
+
entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set')),
|
|
119
|
+
entity_id TEXT NOT NULL,
|
|
120
|
+
source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
121
|
+
human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
|
|
122
|
+
created_at TEXT NOT NULL,
|
|
123
|
+
updated_at TEXT NOT NULL,
|
|
124
|
+
PRIMARY KEY (input_hash, entity_type, entity_id)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_steps_session ON steps(session_id);
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_steps_command ON steps(session_id, command);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_raw_components_session ON raw_components(session_id);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id, component_id);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_raw_slots_session ON raw_slots(session_id, component_id);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_raw_tokens_session ON raw_tokens(session_id);
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_raw_token_groups_session ON raw_token_groups(session_id);
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
|
|
136
|
+
`;
|
|
137
|
+
export function getPipelineDbPath() {
|
|
138
|
+
if (process.env.EDS_PIPELINE_DB_PATH) {
|
|
139
|
+
return resolve(process.env.EDS_PIPELINE_DB_PATH);
|
|
140
|
+
}
|
|
141
|
+
return resolve(homedir(), '.contentful', 'experience-design-system-cli', 'pipeline.db');
|
|
142
|
+
}
|
|
143
|
+
export function openPipelineDb(dbPath) {
|
|
144
|
+
const path = dbPath ?? getPipelineDbPath();
|
|
145
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
146
|
+
try {
|
|
147
|
+
const db = new DatabaseSync(path);
|
|
148
|
+
db.exec(SCHEMA);
|
|
149
|
+
applyDbMigrations(db);
|
|
150
|
+
return db;
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
154
|
+
if (msg.includes('database is locked')) {
|
|
155
|
+
process.stderr.write('Error: another CLI process is already running. Wait for it to finish and retry.\n');
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
throw e;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function applyDbMigrations(db) {
|
|
162
|
+
// Add raw_slots.required column if it doesn't exist yet (added in v0.5.14).
|
|
163
|
+
const cols = db.prepare('PRAGMA table_info(raw_slots)').all();
|
|
164
|
+
if (!cols.some((c) => c.name === 'required')) {
|
|
165
|
+
db.exec('ALTER TABLE raw_slots ADD COLUMN required INTEGER NOT NULL DEFAULT 1 CHECK (required IN (0, 1))');
|
|
166
|
+
}
|
|
167
|
+
// Add generation_cache table if it doesn't exist yet.
|
|
168
|
+
const tables = db
|
|
169
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='generation_cache'`)
|
|
170
|
+
.all();
|
|
171
|
+
if (tables.length === 0) {
|
|
172
|
+
db.exec(`
|
|
173
|
+
CREATE TABLE generation_cache (
|
|
174
|
+
input_hash TEXT NOT NULL,
|
|
175
|
+
entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set')),
|
|
176
|
+
entity_id TEXT NOT NULL,
|
|
177
|
+
source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
178
|
+
human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
|
|
179
|
+
created_at TEXT NOT NULL,
|
|
180
|
+
updated_at TEXT NOT NULL,
|
|
181
|
+
PRIMARY KEY (input_hash, entity_type, entity_id)
|
|
182
|
+
);
|
|
183
|
+
CREATE INDEX idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
|
|
184
|
+
CREATE INDEX idx_generation_cache_session ON generation_cache(source_session_id);
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export function applyToolCalls(db, sessionId, componentId, componentName, calls, incomingWarnings) {
|
|
189
|
+
const now = new Date().toISOString();
|
|
190
|
+
const warnings = [...incomingWarnings];
|
|
191
|
+
let classified = 0;
|
|
192
|
+
let excluded = 0;
|
|
193
|
+
let slots = 0;
|
|
194
|
+
const updateProp = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?
|
|
195
|
+
WHERE session_id = ? AND component_id = ? AND name = ?`);
|
|
196
|
+
const clearProp = db.prepare(`UPDATE raw_props SET cdf_type = 'excluded', cdf_category = NULL, cdf_token_kind = NULL
|
|
197
|
+
WHERE session_id = ? AND component_id = ? AND name = ?`);
|
|
198
|
+
const deleteAllowedValues = db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ? AND prop_name = ?`);
|
|
199
|
+
const insertAllowedValue = db.prepare(`INSERT OR IGNORE INTO raw_prop_allowed_values (session_id, component_id, prop_name, value, position)
|
|
200
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
201
|
+
const deleteAllowedComponents = db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ? AND slot_name = ?`);
|
|
202
|
+
const insertAllowedComponent = db.prepare(`INSERT OR IGNORE INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position)
|
|
203
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
204
|
+
const updateSlot = db.prepare(`UPDATE raw_slots SET required = ?, description = ? WHERE session_id = ? AND component_id = ? AND name = ?`);
|
|
205
|
+
db.exec('BEGIN');
|
|
206
|
+
try {
|
|
207
|
+
for (const call of calls) {
|
|
208
|
+
if (call.tool === 'classify_component') {
|
|
209
|
+
if (call.description !== undefined) {
|
|
210
|
+
db.prepare('UPDATE raw_components SET description = ? WHERE session_id = ? AND component_id = ?').run(call.description, sessionId, componentId);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (call.tool === 'classify_prop') {
|
|
214
|
+
const changes = updateProp.run(call.cdf_type, call.cdf_category, call.token_kind ?? null, call.required !== undefined ? (call.required ? 1 : 0) : 0, call.description ?? null, sessionId, componentId, call.prop);
|
|
215
|
+
if (changes.changes === 0) {
|
|
216
|
+
warnings.push(`${componentName}: classify_prop '${call.prop}' — prop not found, skipped`);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (call.values && call.values.length > 0) {
|
|
220
|
+
deleteAllowedValues.run(sessionId, componentId, call.prop);
|
|
221
|
+
call.values.forEach((v, i) => insertAllowedValue.run(sessionId, componentId, call.prop, v, i));
|
|
222
|
+
}
|
|
223
|
+
if (call.default !== undefined) {
|
|
224
|
+
const storedDefault = typeof call.default === 'boolean' ? String(call.default) : call.default;
|
|
225
|
+
db.prepare(`UPDATE raw_props SET default_value = ? WHERE session_id = ? AND component_id = ? AND name = ?`).run(storedDefault, sessionId, componentId, call.prop);
|
|
226
|
+
}
|
|
227
|
+
classified++;
|
|
228
|
+
}
|
|
229
|
+
else if (call.tool === 'exclude_prop') {
|
|
230
|
+
clearProp.run(sessionId, componentId, call.prop);
|
|
231
|
+
excluded++;
|
|
232
|
+
}
|
|
233
|
+
else if (call.tool === 'classify_slot') {
|
|
234
|
+
const slotRequired = call.required !== undefined ? (call.required ? 1 : 0) : 1;
|
|
235
|
+
const slotChanges = updateSlot.run(slotRequired, call.description ?? null, sessionId, componentId, call.slot);
|
|
236
|
+
if (slotChanges.changes === 0) {
|
|
237
|
+
warnings.push(`${componentName}: classify_slot '${call.slot}' — slot not found, skipped`);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (call.allowed_components !== undefined) {
|
|
241
|
+
deleteAllowedComponents.run(sessionId, componentId, call.slot);
|
|
242
|
+
call.allowed_components.forEach((ac, i) => insertAllowedComponent.run(sessionId, componentId, call.slot, ac, i));
|
|
243
|
+
}
|
|
244
|
+
slots++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
db.prepare(`UPDATE raw_components SET status = 'generated', extracted_at = ? WHERE session_id = ? AND component_id = ?`).run(now, sessionId, componentId);
|
|
248
|
+
db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
|
|
249
|
+
db.exec('COMMIT');
|
|
250
|
+
}
|
|
251
|
+
catch (e) {
|
|
252
|
+
db.exec('ROLLBACK');
|
|
253
|
+
throw e;
|
|
254
|
+
}
|
|
255
|
+
return { classified, excluded, slots, warnings };
|
|
256
|
+
}
|
|
257
|
+
export function getOrCreateSession(db, sessionFlag, sessionName, hints) {
|
|
258
|
+
const now = new Date().toISOString();
|
|
259
|
+
if (sessionFlag === 'new' || sessionFlag === undefined) {
|
|
260
|
+
if (sessionFlag !== 'new') {
|
|
261
|
+
const match = findMatchingSession(db, hints);
|
|
262
|
+
if (match) {
|
|
263
|
+
return { sessionId: match, isNew: false, isResumed: true };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const id = generateSessionId();
|
|
267
|
+
db.prepare('INSERT INTO sessions (id, name, created_at, updated_at) VALUES (?, ?, ?, ?)').run(id, sessionName ?? null, now, now);
|
|
268
|
+
return { sessionId: id, isNew: true, isResumed: false };
|
|
269
|
+
}
|
|
270
|
+
const existing = db.prepare('SELECT id FROM sessions WHERE id = ?').get(sessionFlag);
|
|
271
|
+
if (!existing) {
|
|
272
|
+
throw new Error(`session '${sessionFlag}' not found. Run 'session list' to see active sessions.`);
|
|
273
|
+
}
|
|
274
|
+
return { sessionId: sessionFlag, isNew: false, isResumed: false };
|
|
275
|
+
}
|
|
276
|
+
function findMatchingSession(db, hints) {
|
|
277
|
+
if (!hints.inputPath && !hints.outDir)
|
|
278
|
+
return null;
|
|
279
|
+
const rows = db
|
|
280
|
+
.prepare(`SELECT s.id, st.inputs, st.outputs, st.status
|
|
281
|
+
FROM sessions s
|
|
282
|
+
JOIN steps st ON st.session_id = s.id
|
|
283
|
+
WHERE st.command = ?
|
|
284
|
+
AND st.status IN ('pending', 'interrupted')
|
|
285
|
+
ORDER BY st.started_at DESC
|
|
286
|
+
LIMIT 20`)
|
|
287
|
+
.all(hints.command);
|
|
288
|
+
for (const row of rows) {
|
|
289
|
+
let inputs = {};
|
|
290
|
+
try {
|
|
291
|
+
inputs = JSON.parse(row.inputs);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (hints.inputPath) {
|
|
297
|
+
const inputValues = Object.values(inputs);
|
|
298
|
+
if (inputValues.some((v) => v === hints.inputPath)) {
|
|
299
|
+
return row.id;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (hints.outDir) {
|
|
303
|
+
let outputs = {};
|
|
304
|
+
try {
|
|
305
|
+
outputs = JSON.parse(row.outputs);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// ignore
|
|
309
|
+
}
|
|
310
|
+
const allValues = [...Object.values(inputs), ...Object.values(outputs)];
|
|
311
|
+
if (allValues.some((v) => v === hints.outDir)) {
|
|
312
|
+
return row.id;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
export function createStep(db, sessionId, command, inputs) {
|
|
319
|
+
const now = new Date().toISOString();
|
|
320
|
+
db.exec('BEGIN');
|
|
321
|
+
try {
|
|
322
|
+
db.prepare(`UPDATE steps SET status = 'interrupted', completed_at = ?, updated_at = ?
|
|
323
|
+
WHERE session_id = ? AND command = ? AND status = 'pending'`).run(now, now, sessionId, command);
|
|
324
|
+
const result = db
|
|
325
|
+
.prepare(`INSERT INTO steps (session_id, command, status, started_at, inputs, outputs, updated_at)
|
|
326
|
+
VALUES (?, ?, 'pending', ?, ?, '{}', ?)`)
|
|
327
|
+
.run(sessionId, command, now, JSON.stringify(inputs), now);
|
|
328
|
+
db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
|
|
329
|
+
db.exec('COMMIT');
|
|
330
|
+
return Number(result.lastInsertRowid);
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
db.exec('ROLLBACK');
|
|
334
|
+
throw e;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
export function updateStep(db, stepId, status, outputs, error) {
|
|
338
|
+
const now = new Date().toISOString();
|
|
339
|
+
db.exec('BEGIN');
|
|
340
|
+
try {
|
|
341
|
+
const step = db.prepare('SELECT session_id FROM steps WHERE id = ?').get(stepId);
|
|
342
|
+
db.prepare(`UPDATE steps SET status = ?, completed_at = ?, outputs = ?, error = ?, updated_at = ? WHERE id = ?`).run(status, now, JSON.stringify(outputs), error ?? null, now, stepId);
|
|
343
|
+
if (step) {
|
|
344
|
+
db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, step.session_id);
|
|
345
|
+
}
|
|
346
|
+
db.exec('COMMIT');
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
db.exec('ROLLBACK');
|
|
350
|
+
throw e;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
export function storeRawComponents(db, sessionId, components, options) {
|
|
354
|
+
const now = new Date().toISOString();
|
|
355
|
+
const insertComp = db.prepare(`INSERT INTO raw_components (session_id, component_id, name, source, framework, extracted_at)
|
|
356
|
+
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
357
|
+
const insertProp = db.prepare(`INSERT INTO raw_props
|
|
358
|
+
(session_id, component_id, name, type, required, category, default_value, description, token_reference, position)
|
|
359
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
360
|
+
const insertAllowedValue = db.prepare(`INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value)
|
|
361
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
362
|
+
const insertSlot = db.prepare(`INSERT INTO raw_slots (session_id, component_id, name, is_default, description, position)
|
|
363
|
+
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
364
|
+
const insertAllowedComponent = db.prepare(`INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, position, allowed_component)
|
|
365
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
366
|
+
db.exec('BEGIN');
|
|
367
|
+
try {
|
|
368
|
+
let cdfSnapshot = [];
|
|
369
|
+
let descSnapshot = [];
|
|
370
|
+
let avSnapshot = [];
|
|
371
|
+
if (options?.preserveCDF) {
|
|
372
|
+
cdfSnapshot = db
|
|
373
|
+
.prepare(`SELECT component_id, name, position, cdf_type, cdf_category, cdf_token_kind
|
|
374
|
+
FROM raw_props WHERE session_id = ? AND cdf_type IS NOT NULL`)
|
|
375
|
+
.all(sessionId);
|
|
376
|
+
descSnapshot = db
|
|
377
|
+
.prepare(`SELECT component_id, description
|
|
378
|
+
FROM raw_components WHERE session_id = ? AND description IS NOT NULL`)
|
|
379
|
+
.all(sessionId);
|
|
380
|
+
if (cdfSnapshot.length > 0) {
|
|
381
|
+
avSnapshot = db
|
|
382
|
+
.prepare(`SELECT component_id, prop_name, position, value
|
|
383
|
+
FROM raw_prop_allowed_values WHERE session_id = ?`)
|
|
384
|
+
.all(sessionId);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
db.prepare('DELETE FROM raw_components WHERE session_id = ?').run(sessionId);
|
|
388
|
+
for (const comp of components) {
|
|
389
|
+
const componentId = deriveComponentId(comp.name, comp.source);
|
|
390
|
+
insertComp.run(sessionId, componentId, comp.name, comp.source, comp.framework, now);
|
|
391
|
+
for (let i = 0; i < comp.props.length; i++) {
|
|
392
|
+
const prop = comp.props[i];
|
|
393
|
+
insertProp.run(sessionId, componentId, prop.name, prop.type, prop.required ? 1 : 0, prop.category ?? null, prop.defaultValue ?? null, prop.description ?? null, prop.tokenReference ?? null, i);
|
|
394
|
+
if (prop.allowedValues) {
|
|
395
|
+
prop.allowedValues.forEach((v, j) => insertAllowedValue.run(sessionId, componentId, prop.name, j, v));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
for (let i = 0; i < comp.slots.length; i++) {
|
|
399
|
+
const slot = comp.slots[i];
|
|
400
|
+
insertSlot.run(sessionId, componentId, slot.name, slot.isDefault ? 1 : 0, slot.description ?? null, i);
|
|
401
|
+
if (slot.allowedComponents) {
|
|
402
|
+
slot.allowedComponents.forEach((ac, j) => insertAllowedComponent.run(sessionId, componentId, slot.name, j, ac));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// Restore CDF classification data for props that still exist
|
|
407
|
+
if (options?.preserveCDF && cdfSnapshot.length > 0) {
|
|
408
|
+
const updateCDFByName = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?
|
|
409
|
+
WHERE session_id = ? AND component_id = ? AND name = ?`);
|
|
410
|
+
const updateCDFByPosition = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?
|
|
411
|
+
WHERE session_id = ? AND component_id = ? AND position = ? AND cdf_type IS NULL`);
|
|
412
|
+
const restoredPropKeys = new Set();
|
|
413
|
+
const unmatchedSnaps = [];
|
|
414
|
+
// First pass: match by name
|
|
415
|
+
for (const snap of cdfSnapshot) {
|
|
416
|
+
const result = updateCDFByName.run(snap.cdf_type, snap.cdf_category, snap.cdf_token_kind, sessionId, snap.component_id, snap.name);
|
|
417
|
+
if (Number(result.changes) > 0) {
|
|
418
|
+
restoredPropKeys.add(`${snap.component_id}::${snap.name}`);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
unmatchedSnaps.push(snap);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Second pass: for renamed props, fall back to position-based matching
|
|
425
|
+
for (const snap of unmatchedSnaps) {
|
|
426
|
+
const result = updateCDFByPosition.run(snap.cdf_type, snap.cdf_category, snap.cdf_token_kind, sessionId, snap.component_id, snap.position);
|
|
427
|
+
if (Number(result.changes) > 0) {
|
|
428
|
+
const matched = db
|
|
429
|
+
.prepare(`SELECT name FROM raw_props WHERE session_id = ? AND component_id = ? AND position = ?`)
|
|
430
|
+
.get(sessionId, snap.component_id, snap.position);
|
|
431
|
+
if (matched) {
|
|
432
|
+
restoredPropKeys.add(`${snap.component_id}::${matched.name}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const updateDesc = db.prepare(`UPDATE raw_components SET description = ? WHERE session_id = ? AND component_id = ?`);
|
|
437
|
+
for (const snap of descSnapshot) {
|
|
438
|
+
updateDesc.run(snap.description, sessionId, snap.component_id);
|
|
439
|
+
}
|
|
440
|
+
// Restore allowed values only for props that still exist (to avoid FK violations)
|
|
441
|
+
const relevantAV = avSnapshot.filter((av) => restoredPropKeys.has(`${av.component_id}::${av.prop_name}`));
|
|
442
|
+
if (relevantAV.length > 0) {
|
|
443
|
+
const deleteAV = db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ? AND prop_name = ?`);
|
|
444
|
+
const insertAV = db.prepare(`INSERT OR IGNORE INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value)
|
|
445
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
446
|
+
const deletedKeys = new Set();
|
|
447
|
+
for (const av of relevantAV) {
|
|
448
|
+
const key = `${av.component_id}::${av.prop_name}`;
|
|
449
|
+
if (!deletedKeys.has(key)) {
|
|
450
|
+
deleteAV.run(sessionId, av.component_id, av.prop_name);
|
|
451
|
+
deletedKeys.add(key);
|
|
452
|
+
}
|
|
453
|
+
insertAV.run(sessionId, av.component_id, av.prop_name, av.position, av.value);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (options?.status) {
|
|
458
|
+
db.prepare('UPDATE raw_components SET status = ? WHERE session_id = ?').run(options.status, sessionId);
|
|
459
|
+
}
|
|
460
|
+
db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
|
|
461
|
+
db.exec('COMMIT');
|
|
462
|
+
}
|
|
463
|
+
catch (e) {
|
|
464
|
+
db.exec('ROLLBACK');
|
|
465
|
+
throw e;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
export function loadRawComponents(db, sessionId, allowedNames) {
|
|
469
|
+
const all = db
|
|
470
|
+
.prepare('SELECT component_id, name, source, framework FROM raw_components WHERE session_id = ? ORDER BY rowid')
|
|
471
|
+
.all(sessionId);
|
|
472
|
+
const components = allowedNames ? all.filter((c) => allowedNames.has(c.name)) : all;
|
|
473
|
+
if (components.length === 0)
|
|
474
|
+
return [];
|
|
475
|
+
const props = db
|
|
476
|
+
.prepare(`SELECT component_id, name, type, required, category, default_value, description, token_reference, position
|
|
477
|
+
FROM raw_props WHERE session_id = ? ORDER BY component_id, position`)
|
|
478
|
+
.all(sessionId);
|
|
479
|
+
const allowedValues = db
|
|
480
|
+
.prepare(`SELECT component_id, prop_name, position, value
|
|
481
|
+
FROM raw_prop_allowed_values WHERE session_id = ? ORDER BY component_id, prop_name, position`)
|
|
482
|
+
.all(sessionId);
|
|
483
|
+
const slots = db
|
|
484
|
+
.prepare(`SELECT component_id, name, is_default, description, position
|
|
485
|
+
FROM raw_slots WHERE session_id = ? ORDER BY component_id, position`)
|
|
486
|
+
.all(sessionId);
|
|
487
|
+
const allowedComponents = db
|
|
488
|
+
.prepare(`SELECT component_id, slot_name, position, allowed_component
|
|
489
|
+
FROM raw_slot_allowed_components WHERE session_id = ? ORDER BY component_id, slot_name, position`)
|
|
490
|
+
.all(sessionId);
|
|
491
|
+
// Group children by component_id
|
|
492
|
+
const propsByComponent = groupBy(props, (p) => p.component_id);
|
|
493
|
+
const allowedValuesByProp = groupBy(allowedValues, (av) => `${av.component_id}::${av.prop_name}`);
|
|
494
|
+
const slotsByComponent = groupBy(slots, (s) => s.component_id);
|
|
495
|
+
const allowedComponentsBySlot = groupBy(allowedComponents, (ac) => `${ac.component_id}::${ac.slot_name}`);
|
|
496
|
+
return components.map((c) => ({
|
|
497
|
+
component_id: c.component_id,
|
|
498
|
+
name: c.name,
|
|
499
|
+
source: c.source,
|
|
500
|
+
framework: c.framework,
|
|
501
|
+
props: (propsByComponent.get(c.component_id) ?? []).map((p) => {
|
|
502
|
+
const av = allowedValuesByProp.get(`${c.component_id}::${p.name}`);
|
|
503
|
+
const prop = {
|
|
504
|
+
name: p.name,
|
|
505
|
+
type: p.type,
|
|
506
|
+
required: Boolean(p.required),
|
|
507
|
+
};
|
|
508
|
+
if (p.category !== null)
|
|
509
|
+
prop.category = p.category;
|
|
510
|
+
if (p.default_value !== null)
|
|
511
|
+
prop.defaultValue = p.default_value;
|
|
512
|
+
if (p.description !== null)
|
|
513
|
+
prop.description = p.description;
|
|
514
|
+
if (p.token_reference !== null)
|
|
515
|
+
prop.tokenReference = p.token_reference;
|
|
516
|
+
if (av && av.length > 0)
|
|
517
|
+
prop.allowedValues = av.map((v) => v.value);
|
|
518
|
+
return prop;
|
|
519
|
+
}),
|
|
520
|
+
slots: (slotsByComponent.get(c.component_id) ?? []).map((s) => {
|
|
521
|
+
const ac = allowedComponentsBySlot.get(`${c.component_id}::${s.name}`);
|
|
522
|
+
const slot = {
|
|
523
|
+
name: s.name,
|
|
524
|
+
isDefault: Boolean(s.is_default),
|
|
525
|
+
};
|
|
526
|
+
if (s.description !== null)
|
|
527
|
+
slot.description = s.description;
|
|
528
|
+
if (ac && ac.length > 0)
|
|
529
|
+
slot.allowedComponents = ac.map((v) => v.allowed_component);
|
|
530
|
+
return slot;
|
|
531
|
+
}),
|
|
532
|
+
}));
|
|
533
|
+
}
|
|
534
|
+
function groupBy(items, key) {
|
|
535
|
+
const map = new Map();
|
|
536
|
+
for (const item of items) {
|
|
537
|
+
const k = key(item);
|
|
538
|
+
let arr = map.get(k);
|
|
539
|
+
if (!arr) {
|
|
540
|
+
arr = [];
|
|
541
|
+
map.set(k, arr);
|
|
542
|
+
}
|
|
543
|
+
arr.push(item);
|
|
544
|
+
}
|
|
545
|
+
return map;
|
|
546
|
+
}
|
|
547
|
+
function deriveComponentId(name, source) {
|
|
548
|
+
return createHash('sha256').update(`${name}:${source}`).digest('hex').slice(0, 12);
|
|
549
|
+
}
|
|
550
|
+
export function storeCDFComponents(db, sessionId, components) {
|
|
551
|
+
const now = new Date().toISOString();
|
|
552
|
+
const updateComp = db.prepare(`UPDATE raw_components SET status = 'generated', description = ? WHERE session_id = ? AND name = ?`);
|
|
553
|
+
const updateProp = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ? WHERE session_id = ? AND component_id = ? AND name = ?`);
|
|
554
|
+
const deleteAllowedValues = db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ? AND prop_name = ?`);
|
|
555
|
+
const insertAllowedValue = db.prepare(`INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, value, position)
|
|
556
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
557
|
+
const insertAllowedComponent = db.prepare(`INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position)
|
|
558
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
559
|
+
db.exec('BEGIN');
|
|
560
|
+
try {
|
|
561
|
+
for (const { key, entry } of components) {
|
|
562
|
+
const row = db
|
|
563
|
+
.prepare('SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?')
|
|
564
|
+
.get(sessionId, key);
|
|
565
|
+
if (row) {
|
|
566
|
+
const { component_id: componentId } = row;
|
|
567
|
+
updateComp.run(entry.$description ?? null, sessionId, key);
|
|
568
|
+
for (const [propName, prop] of Object.entries(entry.$properties)) {
|
|
569
|
+
updateProp.run(prop.$type, prop.$category, prop['$token.kind'] ?? null, prop.$required ? 1 : 0, sessionId, componentId, propName);
|
|
570
|
+
if (prop.$values && prop.$values.length > 0) {
|
|
571
|
+
deleteAllowedValues.run(sessionId, componentId, propName);
|
|
572
|
+
prop.$values.forEach((v, i) => insertAllowedValue.run(sessionId, componentId, propName, v, i));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
// Component not in raw_components (e.g. agent added a new one) — insert a minimal record
|
|
578
|
+
const componentId = createHash('sha256').update(`${key}:generated`).digest('hex').slice(0, 12);
|
|
579
|
+
db.prepare(`INSERT INTO raw_components (session_id, component_id, name, source, framework, extracted_at, status, description)
|
|
580
|
+
VALUES (?, ?, ?, '', 'react', ?, 'generated', ?)`).run(sessionId, componentId, key, now, entry.$description ?? null);
|
|
581
|
+
let position = 0;
|
|
582
|
+
for (const [propName, prop] of Object.entries(entry.$properties)) {
|
|
583
|
+
db.prepare(`INSERT OR REPLACE INTO raw_props
|
|
584
|
+
(session_id, component_id, name, type, required, category, default_value, description, token_reference, position, cdf_type, cdf_category, cdf_token_kind)
|
|
585
|
+
VALUES (?, ?, ?, '', ?, NULL, NULL, NULL, NULL, ?, ?, ?, ?)`).run(sessionId, componentId, propName, prop.$required ? 1 : 0, position++, prop.$type, prop.$category, prop['$token.kind'] ?? null);
|
|
586
|
+
if (prop.$values && prop.$values.length > 0) {
|
|
587
|
+
prop.$values.forEach((v, i) => insertAllowedValue.run(sessionId, componentId, propName, v, i));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
let slotPos = 0;
|
|
591
|
+
for (const [slotName, slot] of Object.entries(entry.$slots ?? {})) {
|
|
592
|
+
db.prepare(`INSERT OR REPLACE INTO raw_slots
|
|
593
|
+
(session_id, component_id, name, is_default, required, description, position)
|
|
594
|
+
VALUES (?, ?, ?, 0, ?, ?, ?)`).run(sessionId, componentId, slotName, slot.$required ? 1 : 0, slot.$description ?? null, slotPos++);
|
|
595
|
+
if (slot.$allowedComponents && slot.$allowedComponents.length > 0) {
|
|
596
|
+
slot.$allowedComponents.forEach((ac, i) => insertAllowedComponent.run(sessionId, componentId, slotName, ac, i));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
|
|
602
|
+
db.exec('COMMIT');
|
|
603
|
+
}
|
|
604
|
+
catch (e) {
|
|
605
|
+
db.exec('ROLLBACK');
|
|
606
|
+
throw e;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
export function loadCDFComponents(db, sessionId) {
|
|
610
|
+
const components = db
|
|
611
|
+
.prepare(`SELECT component_id, name, description FROM raw_components
|
|
612
|
+
WHERE session_id = ? AND status = 'generated' ORDER BY rowid`)
|
|
613
|
+
.all(sessionId);
|
|
614
|
+
if (components.length === 0)
|
|
615
|
+
return [];
|
|
616
|
+
const props = db
|
|
617
|
+
.prepare(`SELECT component_id, name, required, default_value, description,
|
|
618
|
+
cdf_type, cdf_category, cdf_token_kind, position
|
|
619
|
+
FROM raw_props
|
|
620
|
+
WHERE session_id = ? AND cdf_type IS NOT NULL AND cdf_type != 'excluded'
|
|
621
|
+
ORDER BY component_id, position`)
|
|
622
|
+
.all(sessionId);
|
|
623
|
+
const allowedValues = db
|
|
624
|
+
.prepare(`SELECT component_id, prop_name, value, position
|
|
625
|
+
FROM raw_prop_allowed_values WHERE session_id = ? ORDER BY component_id, prop_name, position`)
|
|
626
|
+
.all(sessionId);
|
|
627
|
+
const slots = db
|
|
628
|
+
.prepare(`SELECT component_id, name, description, required FROM raw_slots WHERE session_id = ? ORDER BY component_id, position`)
|
|
629
|
+
.all(sessionId);
|
|
630
|
+
const allowedComponents = db
|
|
631
|
+
.prepare(`SELECT component_id, slot_name, allowed_component
|
|
632
|
+
FROM raw_slot_allowed_components WHERE session_id = ? ORDER BY component_id, slot_name, position`)
|
|
633
|
+
.all(sessionId);
|
|
634
|
+
const propsByComponent = groupBy(props, (p) => p.component_id);
|
|
635
|
+
const allowedValuesByProp = groupBy(allowedValues, (av) => `${av.component_id}::${av.prop_name}`);
|
|
636
|
+
const slotsByComponent = groupBy(slots, (s) => s.component_id);
|
|
637
|
+
const allowedComponentsBySlot = groupBy(allowedComponents, (ac) => `${ac.component_id}::${ac.slot_name}`);
|
|
638
|
+
return components
|
|
639
|
+
.map(({ component_id, name, description }) => {
|
|
640
|
+
const compProps = propsByComponent.get(component_id) ?? [];
|
|
641
|
+
// Skip components with no CDF-classified props — sending them with empty
|
|
642
|
+
// $properties causes the server to flag all stored props as "removed" (breaking).
|
|
643
|
+
if (compProps.length === 0)
|
|
644
|
+
return null;
|
|
645
|
+
const $properties = {};
|
|
646
|
+
for (const p of compProps) {
|
|
647
|
+
const av = allowedValuesByProp.get(`${component_id}::${p.name}`);
|
|
648
|
+
const propDef = {
|
|
649
|
+
$type: p.cdf_type,
|
|
650
|
+
$category: p.cdf_category,
|
|
651
|
+
};
|
|
652
|
+
if (p.required)
|
|
653
|
+
propDef.$required = true;
|
|
654
|
+
if (p.default_value !== null) {
|
|
655
|
+
if (p.cdf_type === 'boolean') {
|
|
656
|
+
propDef.$default = p.default_value === 'true';
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
propDef.$default = p.default_value;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (p.description !== null)
|
|
663
|
+
propDef.$description = p.description;
|
|
664
|
+
if (av && av.length > 0)
|
|
665
|
+
propDef.$values = av.map((v) => v.value);
|
|
666
|
+
if (p.cdf_token_kind !== null)
|
|
667
|
+
propDef['$token.kind'] = p.cdf_token_kind;
|
|
668
|
+
$properties[p.name] = propDef;
|
|
669
|
+
}
|
|
670
|
+
const compSlots = slotsByComponent.get(component_id) ?? [];
|
|
671
|
+
const $slots = {};
|
|
672
|
+
for (const s of compSlots) {
|
|
673
|
+
const ac = allowedComponentsBySlot.get(`${component_id}::${s.name}`);
|
|
674
|
+
const slotDef = {};
|
|
675
|
+
if (s.description !== null)
|
|
676
|
+
slotDef.$description = s.description;
|
|
677
|
+
if (s.required)
|
|
678
|
+
slotDef.$required = true;
|
|
679
|
+
if (ac && ac.length > 0)
|
|
680
|
+
slotDef.$allowedComponents = ac.map((v) => v.allowed_component);
|
|
681
|
+
$slots[s.name] = slotDef;
|
|
682
|
+
}
|
|
683
|
+
const entry = { $type: 'component', $properties };
|
|
684
|
+
if (description !== null)
|
|
685
|
+
entry.$description = description;
|
|
686
|
+
if (Object.keys($slots).length > 0)
|
|
687
|
+
entry.$slots = $slots;
|
|
688
|
+
return { key: name, entry };
|
|
689
|
+
})
|
|
690
|
+
.filter((c) => c !== null);
|
|
691
|
+
}
|
|
692
|
+
export function storeDTCGTokens(db, sessionId, groups, tokens) {
|
|
693
|
+
const now = new Date().toISOString();
|
|
694
|
+
db.exec('BEGIN');
|
|
695
|
+
try {
|
|
696
|
+
db.prepare('DELETE FROM raw_token_groups WHERE session_id = ?').run(sessionId);
|
|
697
|
+
db.prepare('DELETE FROM raw_tokens WHERE session_id = ?').run(sessionId);
|
|
698
|
+
const insertGroup = db.prepare(`INSERT INTO raw_token_groups (session_id, path, description) VALUES (?, ?, ?)`);
|
|
699
|
+
for (const group of groups) {
|
|
700
|
+
insertGroup.run(sessionId, group.path, group.$description ?? null);
|
|
701
|
+
}
|
|
702
|
+
const insertToken = db.prepare(`INSERT INTO raw_tokens (session_id, path, type, value, description) VALUES (?, ?, ?, ?, ?)`);
|
|
703
|
+
for (const token of tokens) {
|
|
704
|
+
insertToken.run(sessionId, token.path, token.$type, JSON.stringify(token.$value), token.$description ?? null);
|
|
705
|
+
}
|
|
706
|
+
db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
|
|
707
|
+
db.exec('COMMIT');
|
|
708
|
+
}
|
|
709
|
+
catch (e) {
|
|
710
|
+
db.exec('ROLLBACK');
|
|
711
|
+
throw e;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
export function applyTokenToolCalls(db, sessionId, calls, incomingWarnings) {
|
|
715
|
+
const now = new Date().toISOString();
|
|
716
|
+
const warnings = [...incomingWarnings];
|
|
717
|
+
let tokens = 0;
|
|
718
|
+
let groups = 0;
|
|
719
|
+
const upsertToken = db.prepare(`INSERT INTO raw_tokens (session_id, path, type, value, description) VALUES (?, ?, ?, ?, ?)
|
|
720
|
+
ON CONFLICT(session_id, path) DO UPDATE SET type = excluded.type, value = excluded.value, description = excluded.description`);
|
|
721
|
+
const upsertGroup = db.prepare(`INSERT INTO raw_token_groups (session_id, path, description) VALUES (?, ?, ?)
|
|
722
|
+
ON CONFLICT(session_id, path) DO UPDATE SET description = excluded.description`);
|
|
723
|
+
db.exec('BEGIN');
|
|
724
|
+
try {
|
|
725
|
+
for (const call of calls) {
|
|
726
|
+
if (call.tool === 'set_token') {
|
|
727
|
+
upsertToken.run(sessionId, call.path, call.type, JSON.stringify(call.value), call.description ?? null);
|
|
728
|
+
tokens++;
|
|
729
|
+
}
|
|
730
|
+
else if (call.tool === 'set_group') {
|
|
731
|
+
upsertGroup.run(sessionId, call.path, call.description ?? null);
|
|
732
|
+
groups++;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
|
|
736
|
+
db.exec('COMMIT');
|
|
737
|
+
}
|
|
738
|
+
catch (e) {
|
|
739
|
+
db.exec('ROLLBACK');
|
|
740
|
+
throw e;
|
|
741
|
+
}
|
|
742
|
+
return { tokens, groups, warnings };
|
|
743
|
+
}
|
|
744
|
+
export function loadDTCGTokens(db, sessionId) {
|
|
745
|
+
const groupRows = db
|
|
746
|
+
.prepare('SELECT path, description FROM raw_token_groups WHERE session_id = ? ORDER BY path')
|
|
747
|
+
.all(sessionId);
|
|
748
|
+
const tokenRows = db
|
|
749
|
+
.prepare('SELECT path, type, value, description FROM raw_tokens WHERE session_id = ? ORDER BY path')
|
|
750
|
+
.all(sessionId);
|
|
751
|
+
const groups = groupRows.map((r) => {
|
|
752
|
+
const prefix = `${r.path}.`;
|
|
753
|
+
const tokenIds = tokenRows
|
|
754
|
+
.filter((t) => t.path.startsWith(prefix) && !t.path.slice(prefix.length).includes('.'))
|
|
755
|
+
.map((t) => t.path);
|
|
756
|
+
const g = { path: r.path, tokenIds };
|
|
757
|
+
if (r.description !== null)
|
|
758
|
+
g.$description = r.description;
|
|
759
|
+
return g;
|
|
760
|
+
});
|
|
761
|
+
const tokens = tokenRows.map((r) => {
|
|
762
|
+
const t = {
|
|
763
|
+
path: r.path,
|
|
764
|
+
$type: r.type,
|
|
765
|
+
$value: JSON.parse(r.value),
|
|
766
|
+
};
|
|
767
|
+
if (r.description !== null)
|
|
768
|
+
t.$description = r.description;
|
|
769
|
+
return t;
|
|
770
|
+
});
|
|
771
|
+
return { groups, tokens };
|
|
772
|
+
}
|
|
773
|
+
export function findLatestSessionForCommand(db, command) {
|
|
774
|
+
const row = db
|
|
775
|
+
.prepare(`SELECT s.id FROM sessions s
|
|
776
|
+
JOIN steps st ON st.session_id = s.id
|
|
777
|
+
WHERE st.command = ? AND st.status = 'complete'
|
|
778
|
+
ORDER BY st.started_at DESC, st.id DESC
|
|
779
|
+
LIMIT 1`)
|
|
780
|
+
.get(command);
|
|
781
|
+
return row?.id ?? null;
|
|
782
|
+
}
|
|
783
|
+
export function seedCDFFromPriorSession(db, targetSessionId) {
|
|
784
|
+
// Find the most recent session (other than target) that has CDF data for any of the
|
|
785
|
+
// target's component_ids. This works regardless of which command produced the CDF.
|
|
786
|
+
const targetComponentIds = db
|
|
787
|
+
.prepare(`SELECT component_id FROM raw_components WHERE session_id = ?`)
|
|
788
|
+
.all(targetSessionId);
|
|
789
|
+
if (targetComponentIds.length === 0)
|
|
790
|
+
return 0;
|
|
791
|
+
const placeholders = targetComponentIds.map(() => '?').join(',');
|
|
792
|
+
const priorRow = db
|
|
793
|
+
.prepare(`SELECT session_id FROM raw_props
|
|
794
|
+
WHERE cdf_type IS NOT NULL AND session_id != ?
|
|
795
|
+
AND component_id IN (${placeholders})
|
|
796
|
+
ORDER BY rowid DESC LIMIT 1`)
|
|
797
|
+
.get(targetSessionId, ...targetComponentIds.map((r) => r.component_id));
|
|
798
|
+
if (!priorRow)
|
|
799
|
+
return 0;
|
|
800
|
+
const priorSessionId = priorRow.session_id;
|
|
801
|
+
// Copy CDF columns from prior session's props into matching props in the target session.
|
|
802
|
+
const result = db
|
|
803
|
+
.prepare(`UPDATE raw_props SET
|
|
804
|
+
cdf_type = (SELECT p2.cdf_type FROM raw_props p2
|
|
805
|
+
WHERE p2.session_id = ? AND p2.component_id = raw_props.component_id
|
|
806
|
+
AND p2.name = raw_props.name AND p2.cdf_type IS NOT NULL),
|
|
807
|
+
cdf_category = (SELECT p2.cdf_category FROM raw_props p2
|
|
808
|
+
WHERE p2.session_id = ? AND p2.component_id = raw_props.component_id
|
|
809
|
+
AND p2.name = raw_props.name AND p2.cdf_type IS NOT NULL),
|
|
810
|
+
cdf_token_kind = (SELECT p2.cdf_token_kind FROM raw_props p2
|
|
811
|
+
WHERE p2.session_id = ? AND p2.component_id = raw_props.component_id
|
|
812
|
+
AND p2.name = raw_props.name AND p2.cdf_type IS NOT NULL)
|
|
813
|
+
WHERE session_id = ? AND cdf_type IS NULL
|
|
814
|
+
AND EXISTS (SELECT 1 FROM raw_props p2
|
|
815
|
+
WHERE p2.session_id = ? AND p2.component_id = raw_props.component_id
|
|
816
|
+
AND p2.name = raw_props.name AND p2.cdf_type IS NOT NULL)`)
|
|
817
|
+
.run(priorSessionId, priorSessionId, priorSessionId, targetSessionId, priorSessionId);
|
|
818
|
+
// Also copy descriptions from prior session's components
|
|
819
|
+
db.prepare(`UPDATE raw_components SET description = (
|
|
820
|
+
SELECT c2.description FROM raw_components c2
|
|
821
|
+
WHERE c2.session_id = ? AND c2.component_id = raw_components.component_id
|
|
822
|
+
AND c2.description IS NOT NULL
|
|
823
|
+
)
|
|
824
|
+
WHERE session_id = ? AND description IS NULL
|
|
825
|
+
AND EXISTS (SELECT 1 FROM raw_components c2
|
|
826
|
+
WHERE c2.session_id = ? AND c2.component_id = raw_components.component_id
|
|
827
|
+
AND c2.description IS NOT NULL)`).run(priorSessionId, targetSessionId, priorSessionId);
|
|
828
|
+
// Copy allowed values for seeded props from prior session
|
|
829
|
+
if (result.changes > 0) {
|
|
830
|
+
db.prepare(`INSERT OR IGNORE INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value)
|
|
831
|
+
SELECT ?, av.component_id, av.prop_name, av.position, av.value
|
|
832
|
+
FROM raw_prop_allowed_values av
|
|
833
|
+
WHERE av.session_id = ?
|
|
834
|
+
AND EXISTS (SELECT 1 FROM raw_props p
|
|
835
|
+
WHERE p.session_id = ? AND p.component_id = av.component_id
|
|
836
|
+
AND p.name = av.prop_name AND p.cdf_type IS NOT NULL)`).run(targetSessionId, priorSessionId, targetSessionId);
|
|
837
|
+
}
|
|
838
|
+
return Number(result.changes);
|
|
839
|
+
}
|
|
840
|
+
export function seedCDFFromPreviewResponse(db, sessionId, removedItems) {
|
|
841
|
+
if (removedItems.length === 0)
|
|
842
|
+
return 0;
|
|
843
|
+
let totalSeeded = 0;
|
|
844
|
+
const updateStmt = db.prepare(`UPDATE raw_props
|
|
845
|
+
SET cdf_type = ?, cdf_category = ?
|
|
846
|
+
WHERE session_id = ? AND component_id = ? AND name = ? AND cdf_type IS NULL`);
|
|
847
|
+
for (const item of removedItems) {
|
|
848
|
+
if (!item.fullProperties)
|
|
849
|
+
continue;
|
|
850
|
+
const localComponent = db
|
|
851
|
+
.prepare(`SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?`)
|
|
852
|
+
.get(sessionId, item.name);
|
|
853
|
+
if (!localComponent)
|
|
854
|
+
continue;
|
|
855
|
+
const contentProps = new Set(item.contentProperties);
|
|
856
|
+
const designProps = new Set(item.designProperties);
|
|
857
|
+
for (const [propName, propSummary] of Object.entries(item.fullProperties)) {
|
|
858
|
+
const cdfType = mapServerTypeToCDFType(propSummary.type);
|
|
859
|
+
if (!cdfType)
|
|
860
|
+
continue;
|
|
861
|
+
let cdfCategory = propSummary.category || null;
|
|
862
|
+
if (!cdfCategory || !['content', 'design', 'state'].includes(cdfCategory)) {
|
|
863
|
+
if (contentProps.has(propName))
|
|
864
|
+
cdfCategory = 'content';
|
|
865
|
+
else if (designProps.has(propName))
|
|
866
|
+
cdfCategory = 'design';
|
|
867
|
+
else
|
|
868
|
+
cdfCategory = 'state';
|
|
869
|
+
}
|
|
870
|
+
const result = updateStmt.run(cdfType, cdfCategory, sessionId, localComponent.component_id, propName);
|
|
871
|
+
totalSeeded += Number(result.changes);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return totalSeeded;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Seeds server-side metadata for changed components:
|
|
878
|
+
* 1. Fills in defaults the server has but we don't (prevents accidental removal)
|
|
879
|
+
* 2. Seeds cdf_type/cdf_category for props that exist on server but lack CDF locally
|
|
880
|
+
* (e.g. props with non-standard source types the generation step couldn't classify)
|
|
881
|
+
*/
|
|
882
|
+
export function seedDefaultsFromChangedItems(db, sessionId, changedItems) {
|
|
883
|
+
if (changedItems.length === 0)
|
|
884
|
+
return 0;
|
|
885
|
+
let totalSeeded = 0;
|
|
886
|
+
const updateDefaultStmt = db.prepare(`UPDATE raw_props SET default_value = ?
|
|
887
|
+
WHERE session_id = ? AND component_id = ? AND name = ? AND default_value IS NULL`);
|
|
888
|
+
const updateCDFStmt = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?
|
|
889
|
+
WHERE session_id = ? AND component_id = ? AND name = ? AND cdf_type IS NULL`);
|
|
890
|
+
for (const { current } of changedItems) {
|
|
891
|
+
if (!current.fullProperties)
|
|
892
|
+
continue;
|
|
893
|
+
const localComponent = db
|
|
894
|
+
.prepare(`SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?`)
|
|
895
|
+
.get(sessionId, current.name);
|
|
896
|
+
if (!localComponent)
|
|
897
|
+
continue;
|
|
898
|
+
const contentProps = new Set(current.contentProperties);
|
|
899
|
+
const designProps = new Set(current.designProperties);
|
|
900
|
+
for (const [propName, propSummary] of Object.entries(current.fullProperties)) {
|
|
901
|
+
// Seed defaults
|
|
902
|
+
if (propSummary.default !== undefined && propSummary.default !== null) {
|
|
903
|
+
const defaultStr = typeof propSummary.default === 'string' ? propSummary.default : JSON.stringify(propSummary.default);
|
|
904
|
+
const result = updateDefaultStmt.run(defaultStr, sessionId, localComponent.component_id, propName);
|
|
905
|
+
totalSeeded += Number(result.changes);
|
|
906
|
+
}
|
|
907
|
+
// Seed CDF type/category for props that lack it
|
|
908
|
+
const cdfType = mapServerTypeToCDFType(propSummary.type);
|
|
909
|
+
if (!cdfType)
|
|
910
|
+
continue;
|
|
911
|
+
let cdfCategory = propSummary.category || null;
|
|
912
|
+
if (!cdfCategory || !['content', 'design', 'state'].includes(cdfCategory)) {
|
|
913
|
+
if (contentProps.has(propName))
|
|
914
|
+
cdfCategory = 'content';
|
|
915
|
+
else if (designProps.has(propName))
|
|
916
|
+
cdfCategory = 'design';
|
|
917
|
+
else
|
|
918
|
+
cdfCategory = 'state';
|
|
919
|
+
}
|
|
920
|
+
const result = updateCDFStmt.run(cdfType, cdfCategory, sessionId, localComponent.component_id, propName);
|
|
921
|
+
totalSeeded += Number(result.changes);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return totalSeeded;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Ensures all props on generated components have a cdf_type.
|
|
928
|
+
* Uses the pre-classification `category` column when available.
|
|
929
|
+
* Falls back to cdf_type: 'string', cdf_category: 'content' for props
|
|
930
|
+
* with no pre-classification (safe default — content is the most common category).
|
|
931
|
+
*/
|
|
932
|
+
export function backfillUnclassifiedProps(db, sessionId) {
|
|
933
|
+
// First: backfill props that HAVE a pre-classification category
|
|
934
|
+
const withCategory = db
|
|
935
|
+
.prepare(`UPDATE raw_props SET cdf_type = 'string', cdf_category = category
|
|
936
|
+
WHERE session_id = ? AND cdf_type IS NULL AND category IS NOT NULL
|
|
937
|
+
AND component_id IN (
|
|
938
|
+
SELECT component_id FROM raw_components WHERE session_id = ? AND status = 'generated'
|
|
939
|
+
)`)
|
|
940
|
+
.run(sessionId, sessionId);
|
|
941
|
+
// Second: backfill props with NO category (complex types the rules couldn't classify)
|
|
942
|
+
const withoutCategory = db
|
|
943
|
+
.prepare(`UPDATE raw_props SET cdf_type = 'string', cdf_category = 'content'
|
|
944
|
+
WHERE session_id = ? AND cdf_type IS NULL AND category IS NULL
|
|
945
|
+
AND component_id IN (
|
|
946
|
+
SELECT component_id FROM raw_components WHERE session_id = ? AND status = 'generated'
|
|
947
|
+
)`)
|
|
948
|
+
.run(sessionId, sessionId);
|
|
949
|
+
return Number(withCategory.changes) + Number(withoutCategory.changes);
|
|
950
|
+
}
|
|
951
|
+
function mapServerTypeToCDFType(serverType) {
|
|
952
|
+
switch (serverType.toLowerCase()) {
|
|
953
|
+
case 'string':
|
|
954
|
+
case 'text':
|
|
955
|
+
return 'string';
|
|
956
|
+
case 'richtext':
|
|
957
|
+
return 'richtext';
|
|
958
|
+
case 'media':
|
|
959
|
+
return 'media';
|
|
960
|
+
case 'link':
|
|
961
|
+
return 'link';
|
|
962
|
+
case 'enum':
|
|
963
|
+
case 'symbol':
|
|
964
|
+
return 'enum';
|
|
965
|
+
case 'token':
|
|
966
|
+
return 'token';
|
|
967
|
+
case 'boolean':
|
|
968
|
+
return 'boolean';
|
|
969
|
+
default:
|
|
970
|
+
return 'string';
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
export function computeComponentInputHash(component) {
|
|
974
|
+
const payload = {
|
|
975
|
+
framework: component.framework,
|
|
976
|
+
name: component.name,
|
|
977
|
+
props: component.props.map((p) => ({
|
|
978
|
+
allowedValues: p.allowedValues ?? [],
|
|
979
|
+
defaultValue: p.defaultValue ?? null,
|
|
980
|
+
description: p.description ?? null,
|
|
981
|
+
name: p.name,
|
|
982
|
+
required: p.required,
|
|
983
|
+
tokenReference: p.tokenReference ?? null,
|
|
984
|
+
type: p.type,
|
|
985
|
+
})),
|
|
986
|
+
slots: component.slots.map((s) => ({
|
|
987
|
+
allowedComponents: s.allowedComponents ?? [],
|
|
988
|
+
description: s.description ?? null,
|
|
989
|
+
isDefault: s.isDefault,
|
|
990
|
+
name: s.name,
|
|
991
|
+
})),
|
|
992
|
+
source: component.source,
|
|
993
|
+
};
|
|
994
|
+
return createHash('sha256').update(JSON.stringify(payload)).digest('hex');
|
|
995
|
+
}
|
|
996
|
+
export function computeTokenInputHash(rawTokenContent) {
|
|
997
|
+
return createHash('sha256').update(rawTokenContent.trim()).digest('hex');
|
|
998
|
+
}
|
|
999
|
+
export function lookupCache(db, inputHash, entityType, entityId) {
|
|
1000
|
+
const row = db
|
|
1001
|
+
.prepare(`SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at
|
|
1002
|
+
FROM generation_cache
|
|
1003
|
+
WHERE input_hash = ? AND entity_type = ? AND entity_id = ?`)
|
|
1004
|
+
.get(inputHash, entityType, entityId);
|
|
1005
|
+
if (!row)
|
|
1006
|
+
return null;
|
|
1007
|
+
return {
|
|
1008
|
+
inputHash: row.input_hash,
|
|
1009
|
+
entityType: row.entity_type,
|
|
1010
|
+
entityId: row.entity_id,
|
|
1011
|
+
sourceSessionId: row.source_session_id,
|
|
1012
|
+
humanEdited: row.human_edited === 1,
|
|
1013
|
+
createdAt: row.created_at,
|
|
1014
|
+
updatedAt: row.updated_at,
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
export function lookupCacheByEntity(db, entityType, entityId) {
|
|
1018
|
+
const row = db
|
|
1019
|
+
.prepare(`SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at
|
|
1020
|
+
FROM generation_cache
|
|
1021
|
+
WHERE entity_type = ? AND entity_id = ?
|
|
1022
|
+
ORDER BY updated_at DESC LIMIT 1`)
|
|
1023
|
+
.get(entityType, entityId);
|
|
1024
|
+
if (!row)
|
|
1025
|
+
return null;
|
|
1026
|
+
return {
|
|
1027
|
+
inputHash: row.input_hash,
|
|
1028
|
+
entityType: row.entity_type,
|
|
1029
|
+
entityId: row.entity_id,
|
|
1030
|
+
sourceSessionId: row.source_session_id,
|
|
1031
|
+
humanEdited: row.human_edited === 1,
|
|
1032
|
+
createdAt: row.created_at,
|
|
1033
|
+
updatedAt: row.updated_at,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
export function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited) {
|
|
1037
|
+
const now = new Date().toISOString();
|
|
1038
|
+
db.prepare(`INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at)
|
|
1039
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1040
|
+
ON CONFLICT(input_hash, entity_type, entity_id) DO UPDATE SET
|
|
1041
|
+
source_session_id = excluded.source_session_id,
|
|
1042
|
+
human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
|
|
1043
|
+
updated_at = excluded.updated_at`).run(inputHash, entityType, entityId, sourceSessionId, humanEdited ? 1 : 0, now, now);
|
|
1044
|
+
}
|
|
1045
|
+
export function markCacheHumanEdited(db, entityType, entityId) {
|
|
1046
|
+
const now = new Date().toISOString();
|
|
1047
|
+
db.prepare(`UPDATE generation_cache SET human_edited = 1, updated_at = ? WHERE entity_type = ? AND entity_id = ?`).run(now, entityType, entityId);
|
|
1048
|
+
}
|
|
1049
|
+
export function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId) {
|
|
1050
|
+
const now = new Date().toISOString();
|
|
1051
|
+
db.exec('BEGIN');
|
|
1052
|
+
try {
|
|
1053
|
+
const srcComp = db
|
|
1054
|
+
.prepare(`SELECT description, status FROM raw_components WHERE session_id = ? AND component_id = ?`)
|
|
1055
|
+
.get(sourceSessionId, componentId);
|
|
1056
|
+
if (srcComp) {
|
|
1057
|
+
db.prepare(`UPDATE raw_components SET description = ?, status = ?, extracted_at = ? WHERE session_id = ? AND component_id = ?`).run(srcComp.description, srcComp.status, now, targetSessionId, componentId);
|
|
1058
|
+
}
|
|
1059
|
+
const srcProps = db
|
|
1060
|
+
.prepare(`SELECT name, cdf_type, cdf_category, cdf_token_kind, required, description, default_value
|
|
1061
|
+
FROM raw_props WHERE session_id = ? AND component_id = ?`)
|
|
1062
|
+
.all(sourceSessionId, componentId);
|
|
1063
|
+
for (const p of srcProps) {
|
|
1064
|
+
db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, default_value = ?
|
|
1065
|
+
WHERE session_id = ? AND component_id = ? AND name = ?`).run(p.cdf_type, p.cdf_category, p.cdf_token_kind, p.required, p.description, p.default_value, targetSessionId, componentId, p.name);
|
|
1066
|
+
}
|
|
1067
|
+
db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`).run(targetSessionId, componentId);
|
|
1068
|
+
const srcAV = db
|
|
1069
|
+
.prepare(`SELECT prop_name, position, value FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`)
|
|
1070
|
+
.all(sourceSessionId, componentId);
|
|
1071
|
+
const insertAV = db.prepare(`INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value) VALUES (?, ?, ?, ?, ?)`);
|
|
1072
|
+
for (const av of srcAV) {
|
|
1073
|
+
insertAV.run(targetSessionId, componentId, av.prop_name, av.position, av.value);
|
|
1074
|
+
}
|
|
1075
|
+
const srcSlots = db
|
|
1076
|
+
.prepare(`SELECT name, required, description FROM raw_slots WHERE session_id = ? AND component_id = ?`)
|
|
1077
|
+
.all(sourceSessionId, componentId);
|
|
1078
|
+
for (const s of srcSlots) {
|
|
1079
|
+
db.prepare(`UPDATE raw_slots SET required = ?, description = ? WHERE session_id = ? AND component_id = ? AND name = ?`).run(s.required, s.description, targetSessionId, componentId, s.name);
|
|
1080
|
+
}
|
|
1081
|
+
db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`).run(targetSessionId, componentId);
|
|
1082
|
+
const srcSAC = db
|
|
1083
|
+
.prepare(`SELECT slot_name, position, allowed_component FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`)
|
|
1084
|
+
.all(sourceSessionId, componentId);
|
|
1085
|
+
const insertSAC = db.prepare(`INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`);
|
|
1086
|
+
for (const sac of srcSAC) {
|
|
1087
|
+
insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
|
|
1088
|
+
}
|
|
1089
|
+
db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, targetSessionId);
|
|
1090
|
+
db.exec('COMMIT');
|
|
1091
|
+
}
|
|
1092
|
+
catch (e) {
|
|
1093
|
+
db.exec('ROLLBACK');
|
|
1094
|
+
throw e;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
export function copyTokensFromCache(db, sourceSessionId, targetSessionId) {
|
|
1098
|
+
const now = new Date().toISOString();
|
|
1099
|
+
db.exec('BEGIN');
|
|
1100
|
+
try {
|
|
1101
|
+
db.prepare('DELETE FROM raw_tokens WHERE session_id = ?').run(targetSessionId);
|
|
1102
|
+
db.prepare('DELETE FROM raw_token_groups WHERE session_id = ?').run(targetSessionId);
|
|
1103
|
+
db.prepare(`INSERT INTO raw_token_groups (session_id, path, description)
|
|
1104
|
+
SELECT ?, path, description FROM raw_token_groups WHERE session_id = ?`).run(targetSessionId, sourceSessionId);
|
|
1105
|
+
db.prepare(`INSERT INTO raw_tokens (session_id, path, type, value, description)
|
|
1106
|
+
SELECT ?, path, type, value, description FROM raw_tokens WHERE session_id = ?`).run(targetSessionId, sourceSessionId);
|
|
1107
|
+
db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, targetSessionId);
|
|
1108
|
+
db.exec('COMMIT');
|
|
1109
|
+
}
|
|
1110
|
+
catch (e) {
|
|
1111
|
+
db.exec('ROLLBACK');
|
|
1112
|
+
throw e;
|
|
1113
|
+
}
|
|
1114
|
+
}
|