@contentful/experience-design-system-cli 2.26.1-dev-build-a852362.0 → 2.26.1-dev-build-f03f014.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/dist/package.json +14 -10
  2. package/dist/src/index.js +34937 -9
  3. package/dist/src/lib/cli-path.d.ts +4 -0
  4. package/dist/src/runs/export-helpers.d.ts +1 -12
  5. package/dist/src/runs/push-helpers.d.ts +1 -6
  6. package/package.json +12 -7
  7. package/skills/generate-components.md +427 -0
  8. package/skills/generate-tokens.md +194 -0
  9. package/skills/select-components.md +224 -0
  10. package/dist/src/analytics/apply.js +0 -30
  11. package/dist/src/analytics/client.js +0 -81
  12. package/dist/src/analytics/constants.js +0 -4
  13. package/dist/src/analytics/env.js +0 -14
  14. package/dist/src/analytics/exit.js +0 -24
  15. package/dist/src/analytics/index.js +0 -9
  16. package/dist/src/analytics/normalize.js +0 -18
  17. package/dist/src/analytics/os.js +0 -13
  18. package/dist/src/analytics/session.js +0 -15
  19. package/dist/src/analytics/tracker.js +0 -126
  20. package/dist/src/analytics/types.js +0 -1
  21. package/dist/src/analyze/build-analyze-view-rows.js +0 -63
  22. package/dist/src/analyze/command.js +0 -599
  23. package/dist/src/analyze/composite-closure.js +0 -0
  24. package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
  25. package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
  26. package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
  27. package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
  28. package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
  29. package/dist/src/analyze/composition/apply-mapping.js +0 -67
  30. package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
  31. package/dist/src/analyze/composition/candidate-critic.js +0 -63
  32. package/dist/src/analyze/composition/candidate-files.js +0 -131
  33. package/dist/src/analyze/composition/composition-cache-key.js +0 -19
  34. package/dist/src/analyze/composition/interchange-schema.js +0 -61
  35. package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
  36. package/dist/src/analyze/composition/merge-edges.js +0 -70
  37. package/dist/src/analyze/composition/parse-map-edges.js +0 -55
  38. package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
  39. package/dist/src/analyze/composition/resolve-mapping.js +0 -111
  40. package/dist/src/analyze/cycle-detection.js +0 -234
  41. package/dist/src/analyze/cycle-view.js +0 -28
  42. package/dist/src/analyze/fuzzy-search.js +0 -55
  43. package/dist/src/analyze/issue-inheritance.js +0 -108
  44. package/dist/src/analyze/lineage.js +0 -0
  45. package/dist/src/analyze/scope-gate-cascade.js +0 -170
  46. package/dist/src/analyze/search-neighborhood.js +0 -56
  47. package/dist/src/analyze/select/command.js +0 -484
  48. package/dist/src/analyze/select/index.js +0 -5
  49. package/dist/src/analyze/select/parser.js +0 -53
  50. package/dist/src/analyze/select/persistence.js +0 -80
  51. package/dist/src/analyze/select/preview-annotations.js +0 -62
  52. package/dist/src/analyze/select/stdout.js +0 -3
  53. package/dist/src/analyze/select/tui/App.js +0 -483
  54. package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
  55. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
  56. package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
  57. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
  58. package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
  59. package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
  60. package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
  61. package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
  62. package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
  63. package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
  64. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
  65. package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
  66. package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
  67. package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
  68. package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
  69. package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
  70. package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
  71. package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
  72. package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
  73. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
  74. package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
  75. package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
  76. package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
  77. package/dist/src/analyze/select/tui/theme.js +0 -28
  78. package/dist/src/analyze/select/types.js +0 -39
  79. package/dist/src/analyze/select-agent/command.js +0 -558
  80. package/dist/src/analyze/select-agent/context-builder.js +0 -197
  81. package/dist/src/analyze/select-agent/show-rationale.js +0 -92
  82. package/dist/src/analyze/selection-cascade.js +0 -19
  83. package/dist/src/analyze/slot-graph.js +0 -17
  84. package/dist/src/analyze/tui/AnalyzeView.js +0 -64
  85. package/dist/src/apply/api-client.js +0 -440
  86. package/dist/src/apply/command.js +0 -845
  87. package/dist/src/apply/manifest.js +0 -1
  88. package/dist/src/apply/preview-utils.js +0 -26
  89. package/dist/src/apply/tui/SelectView.js +0 -34
  90. package/dist/src/apply/tui/ServerApplyView.js +0 -49
  91. package/dist/src/apply/tui/ServerPreviewView.js +0 -23
  92. package/dist/src/credentials-store.js +0 -74
  93. package/dist/src/generate/command.js +0 -597
  94. package/dist/src/generate/edit/command.js +0 -127
  95. package/dist/src/generate/tui/GenerateView.js +0 -10
  96. package/dist/src/host-utils.js +0 -26
  97. package/dist/src/import/agent-model-resolve.js +0 -35
  98. package/dist/src/import/auto-filter-resolve.js +0 -16
  99. package/dist/src/import/command.js +0 -380
  100. package/dist/src/import/cycle-auto-reject.js +0 -14
  101. package/dist/src/import/orchestrator.js +0 -603
  102. package/dist/src/import/path-utils.js +0 -30
  103. package/dist/src/import/picker-dispatch.js +0 -44
  104. package/dist/src/import/print-prompt.js +0 -31
  105. package/dist/src/import/strip-allowed-components.js +0 -22
  106. package/dist/src/import/tui/CustomPromptBanner.js +0 -8
  107. package/dist/src/import/tui/WizardApp.js +0 -1670
  108. package/dist/src/import/tui/ai-flag.js +0 -6
  109. package/dist/src/import/tui/auto-filter-error.js +0 -29
  110. package/dist/src/import/tui/autocomplete.js +0 -32
  111. package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
  112. package/dist/src/import/tui/components/CounterStrip.js +0 -12
  113. package/dist/src/import/tui/components/LegendEntry.js +0 -6
  114. package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
  115. package/dist/src/import/tui/final-review-host.js +0 -25
  116. package/dist/src/import/tui/group-collapse.js +0 -11
  117. package/dist/src/import/tui/history.js +0 -53
  118. package/dist/src/import/tui/hooks/useLineage.js +0 -50
  119. package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
  120. package/dist/src/import/tui/lineage-layout.js +0 -82
  121. package/dist/src/import/tui/merge-ai-decisions.js +0 -22
  122. package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
  123. package/dist/src/import/tui/push-progress.js +0 -1
  124. package/dist/src/import/tui/run-print-files-helpers.js +0 -8
  125. package/dist/src/import/tui/run-teaser.js +0 -9
  126. package/dist/src/import/tui/runLivePreview.js +0 -82
  127. package/dist/src/import/tui/runScopeGate.js +0 -28
  128. package/dist/src/import/tui/scope-gate-columns.js +0 -94
  129. package/dist/src/import/tui/scope-gate-host.js +0 -28
  130. package/dist/src/import/tui/sidebar-width.js +0 -3
  131. package/dist/src/import/tui/spawn-generate.js +0 -53
  132. package/dist/src/import/tui/step-filters.js +0 -42
  133. package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
  134. package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
  135. package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
  136. package/dist/src/import/tui/steps/DoneStep.js +0 -24
  137. package/dist/src/import/tui/steps/ErrorStep.js +0 -16
  138. package/dist/src/import/tui/steps/GateStep.js +0 -22
  139. package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
  140. package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
  141. package/dist/src/import/tui/steps/PreviewStep.js +0 -37
  142. package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
  143. package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
  144. package/dist/src/import/tui/steps/PushingStep.js +0 -24
  145. package/dist/src/import/tui/steps/RunningStep.js +0 -21
  146. package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
  147. package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
  148. package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
  149. package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
  150. package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
  151. package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
  152. package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
  153. package/dist/src/import/tui/steps/preview-diff.js +0 -175
  154. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  155. package/dist/src/import/tui/useLivePreview.js +0 -122
  156. package/dist/src/import/tui/wizard-422-helpers.js +0 -60
  157. package/dist/src/import/tui/wizard-generate-progress.js +0 -25
  158. package/dist/src/import/tui/wizard-save-flow.js +0 -20
  159. package/dist/src/import/tui/wizard-state-transitions.js +0 -32
  160. package/dist/src/lib/agent-model-options.js +0 -15
  161. package/dist/src/lib/command-options.js +0 -30
  162. package/dist/src/lib/composition-mode.js +0 -39
  163. package/dist/src/lib/contentful-urls.js +0 -27
  164. package/dist/src/lib/debug-logger.js +0 -229
  165. package/dist/src/lib/debug-preamble.js +0 -33
  166. package/dist/src/lib/error-parser.js +0 -269
  167. package/dist/src/lib/prompt-overrides.js +0 -69
  168. package/dist/src/lib/terminal-capabilities.js +0 -51
  169. package/dist/src/lib/user-agent.js +0 -29
  170. package/dist/src/output/format.js +0 -110
  171. package/dist/src/print/command.js +0 -221
  172. package/dist/src/print/validate/tui/ValidateView.js +0 -37
  173. package/dist/src/print/validate/validators/cdf-validator.js +0 -104
  174. package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
  175. package/dist/src/print/validate/validators/format-errors.js +0 -18
  176. package/dist/src/program.js +0 -105
  177. package/dist/src/runs/export-helpers.js +0 -59
  178. package/dist/src/runs/fingerprint.js +0 -75
  179. package/dist/src/runs/ls-command.js +0 -137
  180. package/dist/src/runs/modify-launcher.js +0 -42
  181. package/dist/src/runs/path-prompt.js +0 -72
  182. package/dist/src/runs/push-creds-prompt.js +0 -39
  183. package/dist/src/runs/push-helpers.js +0 -72
  184. package/dist/src/runs/push-launcher.js +0 -77
  185. package/dist/src/runs/replay-helpers.js +0 -164
  186. package/dist/src/runs/resolve-run-target.js +0 -45
  187. package/dist/src/runs/run-picker-mount.js +0 -72
  188. package/dist/src/runs/run-picker.js +0 -129
  189. package/dist/src/runs/save-conflict.js +0 -56
  190. package/dist/src/runs/save-path-resolver.js +0 -75
  191. package/dist/src/runs/staleness.js +0 -140
  192. package/dist/src/runs/store.js +0 -136
  193. package/dist/src/session/cache-keys.js +0 -44
  194. package/dist/src/session/command.js +0 -261
  195. package/dist/src/session/db.js +0 -1538
  196. package/dist/src/session/migration.js +0 -117
  197. package/dist/src/session/session-id.js +0 -212
  198. package/dist/src/session/stats.js +0 -89
  199. package/dist/src/setup/analytics-prompt.js +0 -24
  200. package/dist/src/setup/auto-filter-prompt.js +0 -24
  201. package/dist/src/setup/command.js +0 -966
  202. package/dist/src/setup/composition-mode-prompt.js +0 -37
  203. package/dist/src/setup/debug-mode-prompt.js +0 -22
  204. package/dist/src/types.js +0 -1
@@ -1,1538 +0,0 @@
1
- import { DatabaseSync } from 'node:sqlite';
2
- import { mkdirSync, readFileSync } 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
- extraction_confidence INTEGER,
47
- review_reasons TEXT NOT NULL DEFAULT '[]',
48
- needs_review INTEGER NOT NULL DEFAULT 0,
49
- source_path TEXT,
50
- reject_reason TEXT,
51
- PRIMARY KEY (session_id, component_id)
52
- );
53
-
54
- CREATE TABLE IF NOT EXISTS raw_props (
55
- session_id TEXT NOT NULL,
56
- component_id TEXT NOT NULL,
57
- name TEXT NOT NULL,
58
- type TEXT NOT NULL,
59
- required INTEGER NOT NULL CHECK (required IN (0, 1)),
60
- category TEXT CHECK (category IN ('content', 'design', 'state')),
61
- default_value TEXT,
62
- description TEXT,
63
- token_reference TEXT,
64
- position INTEGER NOT NULL,
65
- cdf_type TEXT,
66
- cdf_category TEXT CHECK (cdf_category IN ('content', 'design', 'state')),
67
- cdf_token_kind TEXT,
68
- rationale TEXT,
69
- source_start_line INTEGER,
70
- source_end_line INTEGER,
71
- PRIMARY KEY (session_id, component_id, name),
72
- FOREIGN KEY (session_id, component_id) REFERENCES raw_components(session_id, component_id) ON DELETE CASCADE
73
- );
74
-
75
- CREATE TABLE IF NOT EXISTS raw_prop_allowed_values (
76
- session_id TEXT NOT NULL,
77
- component_id TEXT NOT NULL,
78
- prop_name TEXT NOT NULL,
79
- position INTEGER NOT NULL,
80
- value TEXT NOT NULL,
81
- PRIMARY KEY (session_id, component_id, prop_name, position),
82
- FOREIGN KEY (session_id, component_id, prop_name) REFERENCES raw_props(session_id, component_id, name) ON DELETE CASCADE
83
- );
84
-
85
- CREATE TABLE IF NOT EXISTS raw_slots (
86
- session_id TEXT NOT NULL,
87
- component_id TEXT NOT NULL,
88
- name TEXT NOT NULL,
89
- is_default INTEGER NOT NULL CHECK (is_default IN (0, 1)),
90
- description TEXT,
91
- position INTEGER NOT NULL,
92
- required INTEGER NOT NULL DEFAULT 1 CHECK (required IN (0, 1)),
93
- PRIMARY KEY (session_id, component_id, name),
94
- FOREIGN KEY (session_id, component_id) REFERENCES raw_components(session_id, component_id) ON DELETE CASCADE
95
- );
96
-
97
- CREATE TABLE IF NOT EXISTS raw_slot_allowed_components (
98
- session_id TEXT NOT NULL,
99
- component_id TEXT NOT NULL,
100
- slot_name TEXT NOT NULL,
101
- position INTEGER NOT NULL,
102
- allowed_component TEXT NOT NULL,
103
- PRIMARY KEY (session_id, component_id, slot_name, position),
104
- FOREIGN KEY (session_id, component_id, slot_name) REFERENCES raw_slots(session_id, component_id, name) ON DELETE CASCADE
105
- );
106
-
107
- CREATE TABLE IF NOT EXISTS raw_token_groups (
108
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
109
- path TEXT NOT NULL,
110
- description TEXT,
111
- PRIMARY KEY (session_id, path)
112
- );
113
-
114
- CREATE TABLE IF NOT EXISTS raw_tokens (
115
- session_id TEXT NOT NULL,
116
- path TEXT NOT NULL,
117
- type TEXT NOT NULL,
118
- value TEXT NOT NULL,
119
- description TEXT,
120
- PRIMARY KEY (session_id, path),
121
- FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
122
- );
123
-
124
- CREATE TABLE IF NOT EXISTS generation_cache (
125
- input_hash TEXT NOT NULL,
126
- entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set')),
127
- entity_id TEXT NOT NULL,
128
- source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
129
- human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
130
- created_at TEXT NOT NULL,
131
- updated_at TEXT NOT NULL,
132
- PRIMARY KEY (input_hash, entity_type, entity_id)
133
- );
134
-
135
- CREATE TABLE IF NOT EXISTS scanned_files (
136
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
137
- path TEXT NOT NULL,
138
- PRIMARY KEY (session_id, path)
139
- );
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
-
150
- CREATE INDEX IF NOT EXISTS idx_steps_session ON steps(session_id);
151
- CREATE INDEX IF NOT EXISTS idx_steps_command ON steps(session_id, command);
152
- CREATE INDEX IF NOT EXISTS idx_raw_components_session ON raw_components(session_id);
153
- CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id, component_id);
154
- CREATE INDEX IF NOT EXISTS idx_raw_slots_session ON raw_slots(session_id, component_id);
155
- CREATE INDEX IF NOT EXISTS idx_raw_tokens_session ON raw_tokens(session_id);
156
- CREATE INDEX IF NOT EXISTS idx_raw_token_groups_session ON raw_token_groups(session_id);
157
- CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
158
- CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
159
- CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session_id);
160
- `;
161
- export function getPipelineDbPath() {
162
- if (process.env.EDS_PIPELINE_DB_PATH) {
163
- return resolve(process.env.EDS_PIPELINE_DB_PATH);
164
- }
165
- return resolve(homedir(), '.contentful', 'experience-design-system-cli', 'pipeline.db');
166
- }
167
- export function openPipelineDb(dbPath) {
168
- const path = dbPath ?? getPipelineDbPath();
169
- mkdirSync(dirname(path), { recursive: true });
170
- try {
171
- const db = new DatabaseSync(path);
172
- db.exec('PRAGMA busy_timeout = 5000');
173
- db.exec('PRAGMA journal_mode = WAL');
174
- db.exec('PRAGMA synchronous = NORMAL');
175
- db.exec(SCHEMA);
176
- applyDbMigrations(db);
177
- return db;
178
- }
179
- catch (e) {
180
- const msg = e instanceof Error ? e.message : String(e);
181
- if (msg.includes('database is locked')) {
182
- throw new Error('database is locked: another CLI process may be running. Retry once it exits.');
183
- }
184
- throw e;
185
- }
186
- }
187
- function applyDbMigrations(db) {
188
- const cols = db.prepare('PRAGMA table_info(raw_slots)').all();
189
- if (!cols.some((c) => c.name === 'required')) {
190
- db.exec('ALTER TABLE raw_slots ADD COLUMN required INTEGER NOT NULL DEFAULT 1 CHECK (required IN (0, 1))');
191
- }
192
- const rawCompCols = db.prepare('PRAGMA table_info(raw_components)').all();
193
- const rawCompColNames = new Set(rawCompCols.map((c) => c.name));
194
- if (!rawCompColNames.has('extraction_confidence')) {
195
- db.exec('ALTER TABLE raw_components ADD COLUMN extraction_confidence INTEGER');
196
- }
197
- if (!rawCompColNames.has('review_reasons')) {
198
- db.exec("ALTER TABLE raw_components ADD COLUMN review_reasons TEXT NOT NULL DEFAULT '[]'");
199
- }
200
- if (!rawCompColNames.has('needs_review')) {
201
- db.exec('ALTER TABLE raw_components ADD COLUMN needs_review INTEGER NOT NULL DEFAULT 0');
202
- }
203
- if (!rawCompColNames.has('source_path')) {
204
- db.exec('ALTER TABLE raw_components ADD COLUMN source_path TEXT');
205
- }
206
- const rawPropCols = db.prepare('PRAGMA table_info(raw_props)').all();
207
- const rawPropColNames = new Set(rawPropCols.map((c) => c.name));
208
- if (!rawPropColNames.has('rationale')) {
209
- db.exec('ALTER TABLE raw_props ADD COLUMN rationale TEXT');
210
- }
211
- if (!rawPropColNames.has('source_start_line')) {
212
- db.exec('ALTER TABLE raw_props ADD COLUMN source_start_line INTEGER');
213
- }
214
- if (!rawPropColNames.has('source_end_line')) {
215
- db.exec('ALTER TABLE raw_props ADD COLUMN source_end_line INTEGER');
216
- }
217
- if (!rawCompColNames.has('reject_reason')) {
218
- db.exec('ALTER TABLE raw_components ADD COLUMN reject_reason TEXT');
219
- }
220
- if (!rawCompColNames.has('component_description_rationale')) {
221
- db.exec('ALTER TABLE raw_components ADD COLUMN component_description_rationale TEXT');
222
- }
223
- if (!rawCompColNames.has('props_rationale')) {
224
- db.exec('ALTER TABLE raw_components ADD COLUMN props_rationale TEXT');
225
- }
226
- if (!rawCompColNames.has('slots_rationale')) {
227
- db.exec('ALTER TABLE raw_components ADD COLUMN slots_rationale TEXT');
228
- }
229
- const rawSlotColsForRationale = db.prepare('PRAGMA table_info(raw_slots)').all();
230
- if (!rawSlotColsForRationale.some((c) => c.name === 'rationale')) {
231
- db.exec('ALTER TABLE raw_slots ADD COLUMN rationale TEXT');
232
- }
233
- const hasExtractCache = db
234
- .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='extract_cache'`)
235
- .all();
236
- if (hasExtractCache.length === 0) {
237
- db.exec(`
238
- CREATE TABLE extract_cache (
239
- file_path TEXT NOT NULL,
240
- file_hash TEXT NOT NULL,
241
- cli_version TEXT NOT NULL,
242
- created_at TEXT NOT NULL,
243
- updated_at TEXT NOT NULL,
244
- components_json TEXT NOT NULL,
245
- PRIMARY KEY (file_hash, cli_version)
246
- );
247
- CREATE INDEX idx_extract_cache_file ON extract_cache(file_path);
248
- `);
249
- }
250
- const hasSelectCache = db
251
- .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='select_cache'`)
252
- .all();
253
- if (hasSelectCache.length === 0) {
254
- db.exec(`
255
- CREATE TABLE select_cache (
256
- component_hash TEXT NOT NULL,
257
- prompt_hash TEXT NOT NULL,
258
- cli_version TEXT NOT NULL,
259
- decision TEXT NOT NULL CHECK (decision IN ('accepted','rejected')),
260
- reason TEXT,
261
- created_at TEXT NOT NULL,
262
- updated_at TEXT NOT NULL,
263
- PRIMARY KEY (component_hash, prompt_hash, cli_version)
264
- );
265
- `);
266
- }
267
- const tables = db
268
- .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='generation_cache'`)
269
- .all();
270
- if (tables.length === 0) {
271
- db.exec(`
272
- CREATE TABLE generation_cache (
273
- input_hash TEXT NOT NULL,
274
- entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set')),
275
- entity_id TEXT NOT NULL,
276
- source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
277
- human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
278
- created_at TEXT NOT NULL,
279
- updated_at TEXT NOT NULL,
280
- PRIMARY KEY (input_hash, entity_type, entity_id)
281
- );
282
- CREATE INDEX idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
283
- CREATE INDEX idx_generation_cache_session ON generation_cache(source_session_id);
284
- `);
285
- }
286
- const genCacheCols = db.prepare(`PRAGMA table_info(generation_cache)`).all();
287
- const hasPromptHash = genCacheCols.some((c) => c.name === 'prompt_hash');
288
- const oldPkCols = genCacheCols.filter((c) => c.pk > 0).map((c) => c.name);
289
- const promptHashInPk = oldPkCols.includes('prompt_hash');
290
- if (!hasPromptHash) {
291
- db.exec(`ALTER TABLE generation_cache ADD COLUMN prompt_hash TEXT NOT NULL DEFAULT ''`);
292
- }
293
- if (!promptHashInPk) {
294
- db.exec('BEGIN');
295
- try {
296
- db.exec(`
297
- CREATE TABLE generation_cache__new (
298
- input_hash TEXT NOT NULL,
299
- entity_type TEXT NOT NULL CHECK (entity_type IN ('component', 'token_set')),
300
- entity_id TEXT NOT NULL,
301
- source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
302
- human_edited INTEGER NOT NULL DEFAULT 0 CHECK (human_edited IN (0, 1)),
303
- created_at TEXT NOT NULL,
304
- updated_at TEXT NOT NULL,
305
- prompt_hash TEXT NOT NULL DEFAULT '',
306
- PRIMARY KEY (input_hash, prompt_hash, entity_type, entity_id)
307
- );
308
- INSERT INTO generation_cache__new
309
- (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
310
- SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
311
- FROM generation_cache;
312
- DROP TABLE generation_cache;
313
- ALTER TABLE generation_cache__new RENAME TO generation_cache;
314
- CREATE INDEX IF NOT EXISTS idx_generation_cache_entity ON generation_cache(entity_type, entity_id);
315
- CREATE INDEX IF NOT EXISTS idx_generation_cache_session ON generation_cache(source_session_id);
316
- `);
317
- db.exec('COMMIT');
318
- }
319
- catch (e) {
320
- db.exec('ROLLBACK');
321
- throw e;
322
- }
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
- }
339
- }
340
- export function loadComponentReviewMetadata(db, sessionId, componentName) {
341
- const compRow = db
342
- .prepare(`SELECT component_id, source, source_path FROM raw_components WHERE session_id = ? AND name = ?`)
343
- .get(sessionId, componentName);
344
- if (!compRow)
345
- return null;
346
- const propRows = db
347
- .prepare(`SELECT name, rationale, source_start_line, source_end_line FROM raw_props WHERE session_id = ? AND component_id = ?`)
348
- .all(sessionId, compRow.component_id);
349
- const props = {};
350
- for (const r of propRows) {
351
- props[r.name] = {
352
- rationale: r.rationale,
353
- sourceStartLine: r.source_start_line,
354
- sourceEndLine: r.source_end_line,
355
- };
356
- }
357
- // raw_components.source historically stores the file path, not the file
358
- // text. For the source-view panel to render real lines, prefer reading
359
- // the file from disk via source_path. Fall back to whatever's in `source`
360
- // (handles in-memory fixtures and tests).
361
- let componentSource = compRow.source ?? null;
362
- if (compRow.source_path) {
363
- try {
364
- componentSource = readFileSync(compRow.source_path, 'utf8');
365
- }
366
- catch {
367
- // File no longer exists or unreadable — leave fallback.
368
- }
369
- }
370
- return {
371
- sourcePath: compRow.source_path,
372
- componentSource,
373
- props,
374
- };
375
- }
376
- export function loadComponentRationale(db, sessionId, componentName) {
377
- const compRow = db
378
- .prepare(`SELECT component_id, name, description, component_description_rationale, props_rationale, slots_rationale
379
- FROM raw_components WHERE session_id = ? AND name = ?`)
380
- .get(sessionId, componentName);
381
- if (!compRow)
382
- return null;
383
- const propRows = db
384
- .prepare(`SELECT name, cdf_category, category, description, rationale FROM raw_props
385
- WHERE session_id = ? AND component_id = ? ORDER BY position`)
386
- .all(sessionId, compRow.component_id);
387
- const slotRows = db
388
- .prepare(`SELECT name, description, rationale FROM raw_slots
389
- WHERE session_id = ? AND component_id = ? ORDER BY position`)
390
- .all(sessionId, compRow.component_id);
391
- return {
392
- name: compRow.name,
393
- description: compRow.description,
394
- descriptionRationale: compRow.component_description_rationale,
395
- propsRationale: compRow.props_rationale,
396
- slotsRationale: compRow.slots_rationale,
397
- props: propRows.map((p) => ({
398
- name: p.name,
399
- category: p.cdf_category ?? p.category ?? null,
400
- description: p.description,
401
- rationale: p.rationale,
402
- })),
403
- slots: slotRows.map((s) => ({
404
- name: s.name,
405
- description: s.description,
406
- rationale: s.rationale,
407
- })),
408
- };
409
- }
410
- export function applyToolCalls(db, sessionId, componentId, componentName, calls, incomingWarnings) {
411
- const now = new Date().toISOString();
412
- const warnings = [...incomingWarnings];
413
- let classified = 0;
414
- let excluded = 0;
415
- let slots = 0;
416
- const updateProp = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, rationale = ?
417
- WHERE session_id = ? AND component_id = ? AND name = ?`);
418
- const clearProp = db.prepare(`UPDATE raw_props SET cdf_type = 'excluded', cdf_category = NULL, cdf_token_kind = NULL, rationale = ?
419
- WHERE session_id = ? AND component_id = ? AND name = ?`);
420
- const deleteAllowedValues = db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ? AND prop_name = ?`);
421
- const insertAllowedValue = db.prepare(`INSERT OR IGNORE INTO raw_prop_allowed_values (session_id, component_id, prop_name, value, position)
422
- VALUES (?, ?, ?, ?, ?)`);
423
- const deleteAllowedComponents = db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ? AND slot_name = ?`);
424
- const insertAllowedComponent = db.prepare(`INSERT OR IGNORE INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position)
425
- VALUES (?, ?, ?, ?, ?)`);
426
- const updateSlot = db.prepare(`UPDATE raw_slots SET required = ?, description = ? WHERE session_id = ? AND component_id = ? AND name = ?`);
427
- db.exec('BEGIN');
428
- try {
429
- for (const call of calls) {
430
- if (call.tool === 'classify_component') {
431
- if (call.description !== undefined) {
432
- db.prepare('UPDATE raw_components SET description = ? WHERE session_id = ? AND component_id = ?').run(call.description, sessionId, componentId);
433
- }
434
- if (call.rationale) {
435
- if (call.rationale.description !== undefined) {
436
- db.prepare('UPDATE raw_components SET component_description_rationale = ? WHERE session_id = ? AND component_id = ?').run(call.rationale.description, sessionId, componentId);
437
- }
438
- if (call.rationale.props !== undefined) {
439
- db.prepare('UPDATE raw_components SET props_rationale = ? WHERE session_id = ? AND component_id = ?').run(call.rationale.props, sessionId, componentId);
440
- }
441
- if (call.rationale.slots !== undefined) {
442
- db.prepare('UPDATE raw_components SET slots_rationale = ? WHERE session_id = ? AND component_id = ?').run(call.rationale.slots, sessionId, componentId);
443
- }
444
- }
445
- }
446
- else if (call.tool === 'classify_prop') {
447
- 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, call.reason ?? null, sessionId, componentId, call.prop);
448
- if (changes.changes === 0) {
449
- warnings.push(`${componentName}: classify_prop '${call.prop}' — prop not found, skipped`);
450
- continue;
451
- }
452
- if (call.values && call.values.length > 0) {
453
- deleteAllowedValues.run(sessionId, componentId, call.prop);
454
- call.values.forEach((v, i) => insertAllowedValue.run(sessionId, componentId, call.prop, v, i));
455
- }
456
- if (call.default !== undefined) {
457
- const storedDefault = typeof call.default === 'boolean' ? String(call.default) : call.default;
458
- db.prepare(`UPDATE raw_props SET default_value = ? WHERE session_id = ? AND component_id = ? AND name = ?`).run(storedDefault, sessionId, componentId, call.prop);
459
- }
460
- classified++;
461
- }
462
- else if (call.tool === 'exclude_prop') {
463
- clearProp.run(call.reason || null, sessionId, componentId, call.prop);
464
- excluded++;
465
- }
466
- else if (call.tool === 'classify_slot') {
467
- const slotRequired = call.required !== undefined ? (call.required ? 1 : 0) : 1;
468
- const slotChanges = updateSlot.run(slotRequired, call.description ?? null, sessionId, componentId, call.slot);
469
- if (slotChanges.changes === 0) {
470
- warnings.push(`${componentName}: classify_slot '${call.slot}' — slot not found, skipped`);
471
- continue;
472
- }
473
- if (call.rationale !== undefined) {
474
- db.prepare('UPDATE raw_slots SET rationale = ? WHERE session_id = ? AND component_id = ? AND name = ?').run(call.rationale, sessionId, componentId, call.slot);
475
- }
476
- if (call.allowed_components !== undefined) {
477
- deleteAllowedComponents.run(sessionId, componentId, call.slot);
478
- call.allowed_components.forEach((ac, i) => insertAllowedComponent.run(sessionId, componentId, call.slot, ac, i));
479
- }
480
- slots++;
481
- }
482
- }
483
- db.prepare(`UPDATE raw_components SET status = 'generated', extracted_at = ? WHERE session_id = ? AND component_id = ?`).run(now, sessionId, componentId);
484
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
485
- db.exec('COMMIT');
486
- }
487
- catch (e) {
488
- db.exec('ROLLBACK');
489
- throw e;
490
- }
491
- return { classified, excluded, slots, warnings };
492
- }
493
- export function getOrCreateSession(db, sessionFlag, sessionName, _hints) {
494
- const now = new Date().toISOString();
495
- if (sessionFlag === 'new' || sessionFlag === undefined) {
496
- const id = generateSessionId();
497
- db.prepare('INSERT INTO sessions (id, name, created_at, updated_at) VALUES (?, ?, ?, ?)').run(id, sessionName ?? null, now, now);
498
- return { sessionId: id, isNew: true, isResumed: false };
499
- }
500
- const existing = db.prepare('SELECT id FROM sessions WHERE id = ?').get(sessionFlag);
501
- if (!existing) {
502
- throw new Error(`session '${sessionFlag}' not found. Run 'session list' to see active sessions.`);
503
- }
504
- return { sessionId: sessionFlag, isNew: false, isResumed: false };
505
- }
506
- export function createStep(db, sessionId, command, inputs) {
507
- const now = new Date().toISOString();
508
- db.exec('BEGIN');
509
- try {
510
- db.prepare(`UPDATE steps SET status = 'interrupted', completed_at = ?, updated_at = ?
511
- WHERE session_id = ? AND command = ? AND status = 'pending'`).run(now, now, sessionId, command);
512
- const result = db
513
- .prepare(`INSERT INTO steps (session_id, command, status, started_at, inputs, outputs, updated_at)
514
- VALUES (?, ?, 'pending', ?, ?, '{}', ?)`)
515
- .run(sessionId, command, now, JSON.stringify(inputs), now);
516
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
517
- db.exec('COMMIT');
518
- return Number(result.lastInsertRowid);
519
- }
520
- catch (e) {
521
- db.exec('ROLLBACK');
522
- throw e;
523
- }
524
- }
525
- export function updateStep(db, stepId, status, outputs, error) {
526
- const now = new Date().toISOString();
527
- db.exec('BEGIN');
528
- try {
529
- const step = db.prepare('SELECT session_id FROM steps WHERE id = ?').get(stepId);
530
- db.prepare(`UPDATE steps SET status = ?, completed_at = ?, outputs = ?, error = ?, updated_at = ? WHERE id = ?`).run(status, now, JSON.stringify(outputs), error ?? null, now, stepId);
531
- if (step) {
532
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, step.session_id);
533
- }
534
- db.exec('COMMIT');
535
- }
536
- catch (e) {
537
- db.exec('ROLLBACK');
538
- throw e;
539
- }
540
- }
541
- export function storeRawComponents(db, sessionId, components, options) {
542
- const now = new Date().toISOString();
543
- const insertComp = db.prepare(`INSERT INTO raw_components (session_id, component_id, name, source, framework, extracted_at, extraction_confidence, review_reasons, needs_review, source_path)
544
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
545
- const insertProp = db.prepare(`INSERT INTO raw_props
546
- (session_id, component_id, name, type, required, category, default_value, description, token_reference, position, source_start_line, source_end_line)
547
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
548
- const insertAllowedValue = db.prepare(`INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value)
549
- VALUES (?, ?, ?, ?, ?)`);
550
- const insertSlot = db.prepare(`INSERT INTO raw_slots (session_id, component_id, name, is_default, description, position)
551
- VALUES (?, ?, ?, ?, ?, ?)`);
552
- const insertAllowedComponent = db.prepare(`INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, position, allowed_component)
553
- VALUES (?, ?, ?, ?, ?)`);
554
- db.exec('BEGIN');
555
- try {
556
- let cdfSnapshot = [];
557
- let descSnapshot = [];
558
- let avSnapshot = [];
559
- if (options?.preserveCDF) {
560
- cdfSnapshot = db
561
- .prepare(`SELECT component_id, name, position, cdf_type, cdf_category, cdf_token_kind
562
- FROM raw_props WHERE session_id = ? AND cdf_type IS NOT NULL`)
563
- .all(sessionId);
564
- descSnapshot = db
565
- .prepare(`SELECT component_id, description
566
- FROM raw_components WHERE session_id = ? AND description IS NOT NULL`)
567
- .all(sessionId);
568
- if (cdfSnapshot.length > 0) {
569
- avSnapshot = db
570
- .prepare(`SELECT component_id, prop_name, position, value
571
- FROM raw_prop_allowed_values WHERE session_id = ?`)
572
- .all(sessionId);
573
- }
574
- }
575
- db.prepare('DELETE FROM raw_components WHERE session_id = ?').run(sessionId);
576
- for (const comp of components) {
577
- const componentId = deriveComponentId(comp.name, comp.source);
578
- insertComp.run(sessionId, componentId, comp.name, comp.source, comp.framework, now, comp.extractionConfidence ?? null, JSON.stringify(comp.reviewReasons ?? []), comp.needsReview ? 1 : 0, comp.sourcePath ?? null);
579
- for (let i = 0; i < comp.props.length; i++) {
580
- const prop = comp.props[i];
581
- 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, prop.sourceStartLine ?? null, prop.sourceEndLine ?? null);
582
- if (prop.allowedValues) {
583
- prop.allowedValues.forEach((v, j) => insertAllowedValue.run(sessionId, componentId, prop.name, j, v));
584
- }
585
- }
586
- for (let i = 0; i < comp.slots.length; i++) {
587
- const slot = comp.slots[i];
588
- insertSlot.run(sessionId, componentId, slot.name, slot.isDefault ? 1 : 0, slot.description ?? null, i);
589
- if (slot.allowedComponents) {
590
- slot.allowedComponents.forEach((ac, j) => insertAllowedComponent.run(sessionId, componentId, slot.name, j, ac));
591
- }
592
- }
593
- }
594
- if (options?.preserveCDF && cdfSnapshot.length > 0) {
595
- const updateCDFByName = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?
596
- WHERE session_id = ? AND component_id = ? AND name = ?`);
597
- const updateCDFByPosition = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?
598
- WHERE session_id = ? AND component_id = ? AND position = ? AND cdf_type IS NULL`);
599
- const restoredPropKeys = new Set();
600
- const unmatchedSnaps = [];
601
- for (const snap of cdfSnapshot) {
602
- const result = updateCDFByName.run(snap.cdf_type, snap.cdf_category, snap.cdf_token_kind, sessionId, snap.component_id, snap.name);
603
- if (Number(result.changes) > 0) {
604
- restoredPropKeys.add(`${snap.component_id}::${snap.name}`);
605
- }
606
- else {
607
- unmatchedSnaps.push(snap);
608
- }
609
- }
610
- for (const snap of unmatchedSnaps) {
611
- const result = updateCDFByPosition.run(snap.cdf_type, snap.cdf_category, snap.cdf_token_kind, sessionId, snap.component_id, snap.position);
612
- if (Number(result.changes) > 0) {
613
- const matched = db
614
- .prepare(`SELECT name FROM raw_props WHERE session_id = ? AND component_id = ? AND position = ?`)
615
- .get(sessionId, snap.component_id, snap.position);
616
- if (matched) {
617
- restoredPropKeys.add(`${snap.component_id}::${matched.name}`);
618
- }
619
- }
620
- }
621
- const updateDesc = db.prepare(`UPDATE raw_components SET description = ? WHERE session_id = ? AND component_id = ?`);
622
- for (const snap of descSnapshot) {
623
- updateDesc.run(snap.description, sessionId, snap.component_id);
624
- }
625
- const relevantAV = avSnapshot.filter((av) => restoredPropKeys.has(`${av.component_id}::${av.prop_name}`));
626
- if (relevantAV.length > 0) {
627
- const deleteAV = db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ? AND prop_name = ?`);
628
- const insertAV = db.prepare(`INSERT OR IGNORE INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value)
629
- VALUES (?, ?, ?, ?, ?)`);
630
- const deletedKeys = new Set();
631
- for (const av of relevantAV) {
632
- const key = `${av.component_id}::${av.prop_name}`;
633
- if (!deletedKeys.has(key)) {
634
- deleteAV.run(sessionId, av.component_id, av.prop_name);
635
- deletedKeys.add(key);
636
- }
637
- insertAV.run(sessionId, av.component_id, av.prop_name, av.position, av.value);
638
- }
639
- }
640
- }
641
- if (options?.status) {
642
- db.prepare('UPDATE raw_components SET status = ? WHERE session_id = ?').run(options.status, sessionId);
643
- }
644
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
645
- db.exec('COMMIT');
646
- }
647
- catch (e) {
648
- db.exec('ROLLBACK');
649
- throw e;
650
- }
651
- }
652
- export function loadRawComponents(db, sessionId, allowedNames) {
653
- const all = db
654
- .prepare('SELECT component_id, name, source, framework, extraction_confidence, review_reasons, needs_review, source_path FROM raw_components WHERE session_id = ? ORDER BY rowid')
655
- .all(sessionId);
656
- const components = allowedNames ? all.filter((c) => allowedNames.has(c.name)) : all;
657
- if (components.length === 0)
658
- return [];
659
- const props = db
660
- .prepare(`SELECT component_id, name, type, required, category, default_value, description, token_reference, position,
661
- rationale, source_start_line, source_end_line
662
- FROM raw_props WHERE session_id = ? ORDER BY component_id, position`)
663
- .all(sessionId);
664
- const allowedValues = db
665
- .prepare(`SELECT component_id, prop_name, position, value
666
- FROM raw_prop_allowed_values WHERE session_id = ? ORDER BY component_id, prop_name, position`)
667
- .all(sessionId);
668
- const slots = db
669
- .prepare(`SELECT component_id, name, is_default, description, position
670
- FROM raw_slots WHERE session_id = ? ORDER BY component_id, position`)
671
- .all(sessionId);
672
- const allowedComponents = db
673
- .prepare(`SELECT component_id, slot_name, position, allowed_component
674
- FROM raw_slot_allowed_components WHERE session_id = ? ORDER BY component_id, slot_name, position`)
675
- .all(sessionId);
676
- const propsByComponent = groupBy(props, (p) => p.component_id);
677
- const allowedValuesByProp = groupBy(allowedValues, (av) => `${av.component_id}::${av.prop_name}`);
678
- const slotsByComponent = groupBy(slots, (s) => s.component_id);
679
- const allowedComponentsBySlot = groupBy(allowedComponents, (ac) => `${ac.component_id}::${ac.slot_name}`);
680
- return components.map((c) => ({
681
- component_id: c.component_id,
682
- name: c.name,
683
- source: c.source,
684
- framework: c.framework,
685
- extractionConfidence: c.extraction_confidence ?? null,
686
- reviewReasons: parseReviewReasons(c.review_reasons),
687
- needsReview: Boolean(c.needs_review),
688
- sourcePath: c.source_path ?? undefined,
689
- props: (propsByComponent.get(c.component_id) ?? []).map((p) => {
690
- const av = allowedValuesByProp.get(`${c.component_id}::${p.name}`);
691
- const prop = {
692
- name: p.name,
693
- type: p.type,
694
- required: Boolean(p.required),
695
- };
696
- if (p.category !== null)
697
- prop.category = p.category;
698
- if (p.default_value !== null)
699
- prop.defaultValue = p.default_value;
700
- if (p.description !== null)
701
- prop.description = p.description;
702
- if (p.token_reference !== null)
703
- prop.tokenReference = p.token_reference;
704
- if (av && av.length > 0)
705
- prop.allowedValues = av.map((v) => v.value);
706
- if (p.source_start_line !== null)
707
- prop.sourceStartLine = p.source_start_line;
708
- if (p.source_end_line !== null)
709
- prop.sourceEndLine = p.source_end_line;
710
- return prop;
711
- }),
712
- slots: (slotsByComponent.get(c.component_id) ?? []).map((s) => {
713
- const ac = allowedComponentsBySlot.get(`${c.component_id}::${s.name}`);
714
- const slot = {
715
- name: s.name,
716
- isDefault: Boolean(s.is_default),
717
- };
718
- if (s.description !== null)
719
- slot.description = s.description;
720
- if (ac && ac.length > 0)
721
- slot.allowedComponents = ac.map((v) => v.allowed_component);
722
- return slot;
723
- }),
724
- }));
725
- }
726
- export function renameEmptySlots(db, sessionId, componentId, componentName, slotCount) {
727
- const emptySlots = db
728
- .prepare(`SELECT name, position FROM raw_slots
729
- WHERE session_id = ? AND component_id = ? AND trim(name) = ''
730
- ORDER BY position`)
731
- .all(sessionId, componentId);
732
- if (emptySlots.length === 0)
733
- return { renames: [], warnings: [] };
734
- const renames = [];
735
- const warnings = [];
736
- const rename = db.prepare(`UPDATE raw_slots SET name = ? WHERE session_id = ? AND component_id = ? AND name = ? AND position = ?`);
737
- db.exec('BEGIN');
738
- try {
739
- for (const slot of emptySlots) {
740
- const newName = slotCount === 1 ? 'children' : `slot_${slot.position}`;
741
- rename.run(newName, sessionId, componentId, slot.name, slot.position);
742
- renames.push({ oldName: slot.name, newName });
743
- warnings.push(`${componentName}: slot at position ${slot.position} had empty name — renamed to "${newName}" for classification`);
744
- }
745
- db.exec('COMMIT');
746
- }
747
- catch (e) {
748
- db.exec('ROLLBACK');
749
- throw e;
750
- }
751
- return { renames, warnings };
752
- }
753
- function parseReviewReasons(raw) {
754
- try {
755
- const parsed = JSON.parse(raw ?? '[]');
756
- return Array.isArray(parsed) ? parsed.filter((reason) => typeof reason === 'string') : [];
757
- }
758
- catch {
759
- // A malformed historical value should not prevent callers from loading.
760
- return [];
761
- }
762
- }
763
- function groupBy(items, key) {
764
- const map = new Map();
765
- for (const item of items) {
766
- const k = key(item);
767
- let arr = map.get(k);
768
- if (!arr) {
769
- arr = [];
770
- map.set(k, arr);
771
- }
772
- arr.push(item);
773
- }
774
- return map;
775
- }
776
- function deriveComponentId(name, source) {
777
- return createHash('sha256').update(`${name}:${source}`).digest('hex').slice(0, 12);
778
- }
779
- export function storeCDFComponents(db, sessionId, components) {
780
- const now = new Date().toISOString();
781
- const updateComp = db.prepare(`UPDATE raw_components SET status = 'generated', description = ? WHERE session_id = ? AND name = ?`);
782
- const updateProp = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ? WHERE session_id = ? AND component_id = ? AND name = ?`);
783
- const deleteAllowedValues = db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ? AND prop_name = ?`);
784
- const insertAllowedValue = db.prepare(`INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, value, position)
785
- VALUES (?, ?, ?, ?, ?)`);
786
- const insertAllowedComponent = db.prepare(`INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position)
787
- VALUES (?, ?, ?, ?, ?)`);
788
- const deleteSlots = db.prepare(`DELETE FROM raw_slots WHERE session_id = ? AND component_id = ?`);
789
- const deleteSlotAllowedComponents = db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`);
790
- const readExistingSlotDefaults = db.prepare(`SELECT name, is_default FROM raw_slots WHERE session_id = ? AND component_id = ?`);
791
- const insertSlotOnUpdate = db.prepare(`INSERT INTO raw_slots (session_id, component_id, name, is_default, required, description, position)
792
- VALUES (?, ?, ?, ?, ?, ?, ?)`);
793
- db.exec('BEGIN');
794
- try {
795
- for (const { key, entry } of components) {
796
- const row = db
797
- .prepare('SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?')
798
- .get(sessionId, key);
799
- if (row) {
800
- const { component_id: componentId } = row;
801
- updateComp.run(entry.$description ?? null, sessionId, key);
802
- for (const [propName, prop] of Object.entries(entry.$properties)) {
803
- updateProp.run(prop.$type, prop.$category, prop['$token.kind'] ?? null, prop.$required ? 1 : 0, sessionId, componentId, propName);
804
- if (prop.$values && prop.$values.length > 0) {
805
- deleteAllowedValues.run(sessionId, componentId, propName);
806
- prop.$values.forEach((v, i) => insertAllowedValue.run(sessionId, componentId, propName, v, i));
807
- }
808
- }
809
- const existingDefaults = new Map(readExistingSlotDefaults.all(sessionId, componentId).map((r) => [r.name, r.is_default]));
810
- deleteSlotAllowedComponents.run(sessionId, componentId);
811
- deleteSlots.run(sessionId, componentId);
812
- let slotPos = 0;
813
- for (const [slotName, slot] of Object.entries(entry.$slots ?? {})) {
814
- const isDefault = existingDefaults.get(slotName) ?? 0;
815
- insertSlotOnUpdate.run(sessionId, componentId, slotName, isDefault, slot.$required ? 1 : 0, slot.$description ?? null, slotPos++);
816
- if (slot.$allowedComponents && slot.$allowedComponents.length > 0) {
817
- slot.$allowedComponents.forEach((ac, i) => insertAllowedComponent.run(sessionId, componentId, slotName, ac, i));
818
- }
819
- }
820
- }
821
- else {
822
- const componentId = createHash('sha256').update(`${key}:generated`).digest('hex').slice(0, 12);
823
- db.prepare(`INSERT INTO raw_components (session_id, component_id, name, source, framework, extracted_at, status, description)
824
- VALUES (?, ?, ?, '', 'react', ?, 'generated', ?)`).run(sessionId, componentId, key, now, entry.$description ?? null);
825
- let position = 0;
826
- for (const [propName, prop] of Object.entries(entry.$properties)) {
827
- db.prepare(`INSERT OR REPLACE INTO raw_props
828
- (session_id, component_id, name, type, required, category, default_value, description, token_reference, position, cdf_type, cdf_category, cdf_token_kind)
829
- VALUES (?, ?, ?, '', ?, NULL, NULL, NULL, NULL, ?, ?, ?, ?)`).run(sessionId, componentId, propName, prop.$required ? 1 : 0, position++, prop.$type, prop.$category, prop['$token.kind'] ?? null);
830
- if (prop.$values && prop.$values.length > 0) {
831
- prop.$values.forEach((v, i) => insertAllowedValue.run(sessionId, componentId, propName, v, i));
832
- }
833
- }
834
- let slotPos = 0;
835
- for (const [slotName, slot] of Object.entries(entry.$slots ?? {})) {
836
- db.prepare(`INSERT OR REPLACE INTO raw_slots
837
- (session_id, component_id, name, is_default, required, description, position)
838
- VALUES (?, ?, ?, 0, ?, ?, ?)`).run(sessionId, componentId, slotName, slot.$required ? 1 : 0, slot.$description ?? null, slotPos++);
839
- if (slot.$allowedComponents && slot.$allowedComponents.length > 0) {
840
- slot.$allowedComponents.forEach((ac, i) => insertAllowedComponent.run(sessionId, componentId, slotName, ac, i));
841
- }
842
- }
843
- }
844
- }
845
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
846
- db.exec('COMMIT');
847
- }
848
- catch (e) {
849
- db.exec('ROLLBACK');
850
- throw e;
851
- }
852
- }
853
- export function loadCDFComponents(db, sessionId) {
854
- const components = db
855
- .prepare(`SELECT component_id, name, description FROM raw_components
856
- WHERE session_id = ? AND status = 'generated' ORDER BY rowid`)
857
- .all(sessionId);
858
- if (components.length === 0)
859
- return [];
860
- const props = db
861
- .prepare(`SELECT component_id, name, required, default_value, description,
862
- cdf_type, cdf_category, cdf_token_kind, position
863
- FROM raw_props
864
- WHERE session_id = ? AND cdf_type IS NOT NULL AND cdf_type != 'excluded'
865
- ORDER BY component_id, position`)
866
- .all(sessionId);
867
- const allowedValues = db
868
- .prepare(`SELECT component_id, prop_name, value, position
869
- FROM raw_prop_allowed_values WHERE session_id = ? ORDER BY component_id, prop_name, position`)
870
- .all(sessionId);
871
- const slots = db
872
- .prepare(`SELECT component_id, name, description, required FROM raw_slots WHERE session_id = ? ORDER BY component_id, position`)
873
- .all(sessionId);
874
- const allowedComponents = db
875
- .prepare(`SELECT component_id, slot_name, allowed_component
876
- FROM raw_slot_allowed_components WHERE session_id = ? ORDER BY component_id, slot_name, position`)
877
- .all(sessionId);
878
- const propsByComponent = groupBy(props, (p) => p.component_id);
879
- const allowedValuesByProp = groupBy(allowedValues, (av) => `${av.component_id}::${av.prop_name}`);
880
- const slotsByComponent = groupBy(slots, (s) => s.component_id);
881
- const allowedComponentsBySlot = groupBy(allowedComponents, (ac) => `${ac.component_id}::${ac.slot_name}`);
882
- return components.map(({ component_id, name, description }) => {
883
- const compProps = propsByComponent.get(component_id) ?? [];
884
- const $properties = {};
885
- for (const p of compProps) {
886
- if (!p.name.trim())
887
- continue;
888
- const av = allowedValuesByProp.get(`${component_id}::${p.name}`);
889
- const propDef = {
890
- $type: p.cdf_type,
891
- $category: p.cdf_category,
892
- };
893
- if (p.required)
894
- propDef.$required = true;
895
- if (p.default_value !== null) {
896
- if (p.cdf_type === 'boolean') {
897
- propDef.$default = p.default_value === 'true';
898
- }
899
- else {
900
- propDef.$default = p.default_value;
901
- }
902
- }
903
- if (p.description !== null)
904
- propDef.$description = p.description;
905
- if (av && av.length > 0)
906
- propDef.$values = av.map((v) => v.value);
907
- if (p.cdf_token_kind !== null)
908
- propDef['$token.kind'] = p.cdf_token_kind;
909
- $properties[p.name] = propDef;
910
- }
911
- const compSlots = slotsByComponent.get(component_id) ?? [];
912
- const $slots = {};
913
- for (const s of compSlots) {
914
- if (!s.name.trim())
915
- continue;
916
- const ac = allowedComponentsBySlot.get(`${component_id}::${s.name}`);
917
- const slotDef = {};
918
- if (s.description !== null)
919
- slotDef.$description = s.description;
920
- if (s.required)
921
- slotDef.$required = true;
922
- if (ac && ac.length > 0)
923
- slotDef.$allowedComponents = ac.map((v) => v.allowed_component);
924
- $slots[s.name] = slotDef;
925
- }
926
- const entry = { $type: 'component', $properties };
927
- if (description !== null)
928
- entry.$description = description;
929
- if (Object.keys($slots).length > 0)
930
- entry.$slots = $slots;
931
- return { key: name, entry };
932
- });
933
- }
934
- export function loadScopeComponents(db, sessionId) {
935
- const rows = db
936
- .prepare(`SELECT name, component_id, status, reject_reason, review_reasons, needs_review FROM raw_components
937
- WHERE session_id = ? AND status IN ('extracted', 'accepted', 'rejected')
938
- ORDER BY name`)
939
- .all(sessionId);
940
- if (rows.length === 0)
941
- return [];
942
- const slotRows = db
943
- .prepare(`SELECT component_id, name, position
944
- FROM raw_slots WHERE session_id = ? ORDER BY component_id, position`)
945
- .all(sessionId);
946
- const allowedRows = db
947
- .prepare(`SELECT component_id, slot_name, position, allowed_component
948
- FROM raw_slot_allowed_components WHERE session_id = ? ORDER BY component_id, slot_name, position`)
949
- .all(sessionId);
950
- const slotsByComponent = groupBy(slotRows, (s) => s.component_id);
951
- const allowedBySlot = groupBy(allowedRows, (a) => `${a.component_id}::${a.slot_name}`);
952
- return rows.map((r) => {
953
- const reviewReasons = parseReviewReasons(r.review_reasons);
954
- const nonAuthorableReason = reviewReasons.find((reason) => reason.startsWith('non-authorable:'));
955
- return {
956
- name: r.name,
957
- componentId: r.component_id,
958
- aiDecision: r.status === 'accepted' ? 'accepted' : r.status === 'rejected' ? 'rejected' : null,
959
- aiReason: r.reject_reason ?? nonAuthorableReason?.slice('non-authorable:'.length) ?? null,
960
- needsReview: Boolean(r.needs_review),
961
- slots: (slotsByComponent.get(r.component_id) ?? []).map((s) => ({
962
- name: s.name,
963
- allowedComponents: (allowedBySlot.get(`${r.component_id}::${s.name}`) ?? []).map((a) => a.allowed_component),
964
- })),
965
- };
966
- });
967
- }
968
- export function applyScopeDecisions(db, sessionId, decisions) {
969
- const now = new Date().toISOString();
970
- const acceptedSet = new Set(decisions.accepted);
971
- const accepted = [...acceptedSet];
972
- const rejected = [...new Set(decisions.rejected)].filter((n) => !acceptedSet.has(n));
973
- if (rejected.length > 0) {
974
- const placeholders = rejected.map(() => '?').join(',');
975
- db.prepare(`UPDATE raw_components SET status = 'rejected' WHERE session_id = ? AND name IN (${placeholders})`).run(sessionId, ...rejected);
976
- }
977
- if (accepted.length > 0) {
978
- const placeholders = accepted.map(() => '?').join(',');
979
- db.prepare(`UPDATE raw_components SET status = 'generated' WHERE session_id = ? AND name IN (${placeholders})`).run(sessionId, ...accepted);
980
- }
981
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
982
- }
983
- export function storeDTCGTokens(db, sessionId, groups, tokens) {
984
- const now = new Date().toISOString();
985
- db.exec('BEGIN');
986
- try {
987
- db.prepare('DELETE FROM raw_token_groups WHERE session_id = ?').run(sessionId);
988
- db.prepare('DELETE FROM raw_tokens WHERE session_id = ?').run(sessionId);
989
- const insertGroup = db.prepare(`INSERT INTO raw_token_groups (session_id, path, description) VALUES (?, ?, ?)`);
990
- for (const group of groups) {
991
- insertGroup.run(sessionId, group.path, group.$description ?? null);
992
- }
993
- const insertToken = db.prepare(`INSERT INTO raw_tokens (session_id, path, type, value, description) VALUES (?, ?, ?, ?, ?)`);
994
- for (const token of tokens) {
995
- insertToken.run(sessionId, token.path, token.$type, JSON.stringify(token.$value), token.$description ?? null);
996
- }
997
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
998
- db.exec('COMMIT');
999
- }
1000
- catch (e) {
1001
- db.exec('ROLLBACK');
1002
- throw e;
1003
- }
1004
- }
1005
- export function applyTokenToolCalls(db, sessionId, calls, incomingWarnings) {
1006
- const now = new Date().toISOString();
1007
- const warnings = [...incomingWarnings];
1008
- let tokens = 0;
1009
- let groups = 0;
1010
- const upsertToken = db.prepare(`INSERT INTO raw_tokens (session_id, path, type, value, description) VALUES (?, ?, ?, ?, ?)
1011
- ON CONFLICT(session_id, path) DO UPDATE SET type = excluded.type, value = excluded.value, description = excluded.description`);
1012
- const upsertGroup = db.prepare(`INSERT INTO raw_token_groups (session_id, path, description) VALUES (?, ?, ?)
1013
- ON CONFLICT(session_id, path) DO UPDATE SET description = excluded.description`);
1014
- db.exec('BEGIN');
1015
- try {
1016
- for (const call of calls) {
1017
- if (call.tool === 'set_token') {
1018
- upsertToken.run(sessionId, call.path, call.type, JSON.stringify(call.value), call.description ?? null);
1019
- tokens++;
1020
- }
1021
- else if (call.tool === 'set_group') {
1022
- upsertGroup.run(sessionId, call.path, call.description ?? null);
1023
- groups++;
1024
- }
1025
- }
1026
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, sessionId);
1027
- db.exec('COMMIT');
1028
- }
1029
- catch (e) {
1030
- db.exec('ROLLBACK');
1031
- throw e;
1032
- }
1033
- return { tokens, groups, warnings };
1034
- }
1035
- export function loadDTCGTokens(db, sessionId) {
1036
- const groupRows = db
1037
- .prepare('SELECT path, description FROM raw_token_groups WHERE session_id = ? ORDER BY path')
1038
- .all(sessionId);
1039
- const tokenRows = db
1040
- .prepare('SELECT path, type, value, description FROM raw_tokens WHERE session_id = ? ORDER BY path')
1041
- .all(sessionId);
1042
- const groups = groupRows.map((r) => {
1043
- const prefix = `${r.path}.`;
1044
- const tokenIds = tokenRows
1045
- .filter((t) => t.path.startsWith(prefix) && !t.path.slice(prefix.length).includes('.'))
1046
- .map((t) => t.path);
1047
- const g = { path: r.path, tokenIds };
1048
- if (r.description !== null)
1049
- g.$description = r.description;
1050
- return g;
1051
- });
1052
- const tokens = tokenRows.map((r) => {
1053
- const t = {
1054
- path: r.path,
1055
- $type: r.type,
1056
- $value: JSON.parse(r.value),
1057
- };
1058
- if (r.description !== null)
1059
- t.$description = r.description;
1060
- return t;
1061
- });
1062
- return { groups, tokens };
1063
- }
1064
- export function findLatestSessionForCommand(db, command) {
1065
- const row = db
1066
- .prepare(`SELECT s.id FROM sessions s
1067
- JOIN steps st ON st.session_id = s.id
1068
- WHERE st.command = ? AND st.status = 'complete'
1069
- ORDER BY st.started_at DESC, st.id DESC
1070
- LIMIT 1`)
1071
- .get(command);
1072
- return row?.id ?? null;
1073
- }
1074
- export function seedCDFFromPriorSession(db, targetSessionId) {
1075
- const targetComponentIds = db
1076
- .prepare(`SELECT component_id FROM raw_components WHERE session_id = ?`)
1077
- .all(targetSessionId);
1078
- if (targetComponentIds.length === 0)
1079
- return 0;
1080
- const placeholders = targetComponentIds.map(() => '?').join(',');
1081
- const priorRow = db
1082
- .prepare(`SELECT session_id FROM raw_props
1083
- WHERE cdf_type IS NOT NULL AND session_id != ?
1084
- AND component_id IN (${placeholders})
1085
- ORDER BY rowid DESC LIMIT 1`)
1086
- .get(targetSessionId, ...targetComponentIds.map((r) => r.component_id));
1087
- if (!priorRow)
1088
- return 0;
1089
- const priorSessionId = priorRow.session_id;
1090
- const result = db
1091
- .prepare(`UPDATE raw_props SET
1092
- cdf_type = (SELECT p2.cdf_type FROM raw_props p2
1093
- WHERE p2.session_id = ? AND p2.component_id = raw_props.component_id
1094
- AND p2.name = raw_props.name AND p2.cdf_type IS NOT NULL),
1095
- cdf_category = (SELECT p2.cdf_category FROM raw_props p2
1096
- WHERE p2.session_id = ? AND p2.component_id = raw_props.component_id
1097
- AND p2.name = raw_props.name AND p2.cdf_type IS NOT NULL),
1098
- cdf_token_kind = (SELECT p2.cdf_token_kind FROM raw_props p2
1099
- WHERE p2.session_id = ? AND p2.component_id = raw_props.component_id
1100
- AND p2.name = raw_props.name AND p2.cdf_type IS NOT NULL)
1101
- WHERE session_id = ? AND cdf_type IS NULL
1102
- AND EXISTS (SELECT 1 FROM raw_props p2
1103
- WHERE p2.session_id = ? AND p2.component_id = raw_props.component_id
1104
- AND p2.name = raw_props.name AND p2.cdf_type IS NOT NULL)`)
1105
- .run(priorSessionId, priorSessionId, priorSessionId, targetSessionId, priorSessionId);
1106
- db.prepare(`UPDATE raw_components SET description = (
1107
- SELECT c2.description FROM raw_components c2
1108
- WHERE c2.session_id = ? AND c2.component_id = raw_components.component_id
1109
- AND c2.description IS NOT NULL
1110
- )
1111
- WHERE session_id = ? AND description IS NULL
1112
- AND EXISTS (SELECT 1 FROM raw_components c2
1113
- WHERE c2.session_id = ? AND c2.component_id = raw_components.component_id
1114
- AND c2.description IS NOT NULL)`).run(priorSessionId, targetSessionId, priorSessionId);
1115
- if (result.changes > 0) {
1116
- db.prepare(`INSERT OR IGNORE INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value)
1117
- SELECT ?, av.component_id, av.prop_name, av.position, av.value
1118
- FROM raw_prop_allowed_values av
1119
- WHERE av.session_id = ?
1120
- AND EXISTS (SELECT 1 FROM raw_props p
1121
- WHERE p.session_id = ? AND p.component_id = av.component_id
1122
- AND p.name = av.prop_name AND p.cdf_type IS NOT NULL)`).run(targetSessionId, priorSessionId, targetSessionId);
1123
- }
1124
- return Number(result.changes);
1125
- }
1126
- export function seedCDFFromPreviewResponse(db, sessionId, removedItems) {
1127
- if (removedItems.length === 0)
1128
- return 0;
1129
- let totalSeeded = 0;
1130
- const updateStmt = db.prepare(`UPDATE raw_props
1131
- SET cdf_type = ?, cdf_category = ?
1132
- WHERE session_id = ? AND component_id = ? AND name = ? AND cdf_type IS NULL`);
1133
- for (const item of removedItems) {
1134
- if (!item.fullProperties)
1135
- continue;
1136
- const localComponent = db
1137
- .prepare(`SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?`)
1138
- .get(sessionId, item.name);
1139
- if (!localComponent)
1140
- continue;
1141
- const contentProps = new Set(item.contentProperties);
1142
- const designProps = new Set(item.designProperties);
1143
- for (const [propName, propSummary] of Object.entries(item.fullProperties)) {
1144
- const cdfType = mapServerTypeToCDFType(propSummary.type);
1145
- if (!cdfType)
1146
- continue;
1147
- let cdfCategory = propSummary.category || null;
1148
- if (!cdfCategory || !['content', 'design', 'state'].includes(cdfCategory)) {
1149
- if (contentProps.has(propName))
1150
- cdfCategory = 'content';
1151
- else if (designProps.has(propName))
1152
- cdfCategory = 'design';
1153
- else
1154
- cdfCategory = 'state';
1155
- }
1156
- const result = updateStmt.run(cdfType, cdfCategory, sessionId, localComponent.component_id, propName);
1157
- totalSeeded += Number(result.changes);
1158
- }
1159
- }
1160
- return totalSeeded;
1161
- }
1162
- export function seedDefaultsFromChangedItems(db, sessionId, changedItems) {
1163
- if (changedItems.length === 0)
1164
- return 0;
1165
- let totalSeeded = 0;
1166
- const updateDefaultStmt = db.prepare(`UPDATE raw_props SET default_value = ?
1167
- WHERE session_id = ? AND component_id = ? AND name = ? AND default_value IS NULL`);
1168
- const updateCDFStmt = db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?
1169
- WHERE session_id = ? AND component_id = ? AND name = ? AND cdf_type IS NULL`);
1170
- for (const { current } of changedItems) {
1171
- if (!current.fullProperties)
1172
- continue;
1173
- const localComponent = db
1174
- .prepare(`SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?`)
1175
- .get(sessionId, current.name);
1176
- if (!localComponent)
1177
- continue;
1178
- const contentProps = new Set(current.contentProperties);
1179
- const designProps = new Set(current.designProperties);
1180
- for (const [propName, propSummary] of Object.entries(current.fullProperties)) {
1181
- if (propSummary.default !== undefined && propSummary.default !== null) {
1182
- const defaultStr = typeof propSummary.default === 'string' ? propSummary.default : JSON.stringify(propSummary.default);
1183
- const result = updateDefaultStmt.run(defaultStr, sessionId, localComponent.component_id, propName);
1184
- totalSeeded += Number(result.changes);
1185
- }
1186
- const cdfType = mapServerTypeToCDFType(propSummary.type);
1187
- if (!cdfType)
1188
- continue;
1189
- let cdfCategory = propSummary.category || null;
1190
- if (!cdfCategory || !['content', 'design', 'state'].includes(cdfCategory)) {
1191
- if (contentProps.has(propName))
1192
- cdfCategory = 'content';
1193
- else if (designProps.has(propName))
1194
- cdfCategory = 'design';
1195
- else
1196
- cdfCategory = 'state';
1197
- }
1198
- const result = updateCDFStmt.run(cdfType, cdfCategory, sessionId, localComponent.component_id, propName);
1199
- totalSeeded += Number(result.changes);
1200
- }
1201
- }
1202
- return totalSeeded;
1203
- }
1204
- export function backfillUnclassifiedProps(db, sessionId) {
1205
- const withCategory = db
1206
- .prepare(`UPDATE raw_props SET cdf_type = 'string', cdf_category = category
1207
- WHERE session_id = ? AND cdf_type IS NULL AND category IS NOT NULL
1208
- AND component_id IN (
1209
- SELECT component_id FROM raw_components WHERE session_id = ? AND status = 'generated'
1210
- )`)
1211
- .run(sessionId, sessionId);
1212
- const withoutCategory = db
1213
- .prepare(`UPDATE raw_props SET cdf_type = 'string', cdf_category = 'content'
1214
- WHERE session_id = ? AND cdf_type IS NULL AND category IS NULL
1215
- AND component_id IN (
1216
- SELECT component_id FROM raw_components WHERE session_id = ? AND status = 'generated'
1217
- )`)
1218
- .run(sessionId, sessionId);
1219
- return Number(withCategory.changes) + Number(withoutCategory.changes);
1220
- }
1221
- function mapServerTypeToCDFType(serverType) {
1222
- switch (serverType.toLowerCase()) {
1223
- case 'string':
1224
- case 'text':
1225
- return 'string';
1226
- case 'richtext':
1227
- return 'richtext';
1228
- case 'media':
1229
- return 'media';
1230
- case 'link':
1231
- return 'link';
1232
- case 'enum':
1233
- case 'symbol':
1234
- return 'enum';
1235
- case 'token':
1236
- return 'token';
1237
- case 'boolean':
1238
- return 'boolean';
1239
- default:
1240
- return 'string';
1241
- }
1242
- }
1243
- export function computeComponentInputHash(component) {
1244
- const payload = {
1245
- framework: component.framework,
1246
- name: component.name,
1247
- source: component.source,
1248
- props: component.props.map((p) => ({
1249
- name: p.name,
1250
- type: p.type,
1251
- })),
1252
- slots: component.slots.map((s) => ({
1253
- name: s.name,
1254
- isDefault: s.isDefault,
1255
- allowedComponents: [...(s.allowedComponents ?? [])].sort(),
1256
- })),
1257
- };
1258
- return createHash('sha256').update(JSON.stringify(payload)).digest('hex');
1259
- }
1260
- export function computeTokenInputHash(rawTokenContent) {
1261
- return createHash('sha256').update(rawTokenContent.trim()).digest('hex');
1262
- }
1263
- export function lookupCache(db, inputHash, entityType, entityId, promptHash = '') {
1264
- const row = db
1265
- .prepare(`SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
1266
- FROM generation_cache
1267
- WHERE input_hash = ? AND entity_type = ? AND entity_id = ? AND prompt_hash = ?`)
1268
- .get(inputHash, entityType, entityId, promptHash);
1269
- if (!row)
1270
- return null;
1271
- return {
1272
- inputHash: row.input_hash,
1273
- entityType: row.entity_type,
1274
- entityId: row.entity_id,
1275
- sourceSessionId: row.source_session_id,
1276
- humanEdited: row.human_edited === 1,
1277
- promptHash: row.prompt_hash,
1278
- createdAt: row.created_at,
1279
- updatedAt: row.updated_at,
1280
- };
1281
- }
1282
- export function lookupCacheByEntity(db, entityType, entityId) {
1283
- const row = db
1284
- .prepare(`SELECT input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash
1285
- FROM generation_cache
1286
- WHERE entity_type = ? AND entity_id = ?
1287
- ORDER BY updated_at DESC LIMIT 1`)
1288
- .get(entityType, entityId);
1289
- if (!row)
1290
- return null;
1291
- return {
1292
- inputHash: row.input_hash,
1293
- entityType: row.entity_type,
1294
- entityId: row.entity_id,
1295
- sourceSessionId: row.source_session_id,
1296
- humanEdited: row.human_edited === 1,
1297
- promptHash: row.prompt_hash,
1298
- createdAt: row.created_at,
1299
- updatedAt: row.updated_at,
1300
- };
1301
- }
1302
- export function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash = '') {
1303
- const now = new Date().toISOString();
1304
- db.prepare(`INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
1305
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1306
- ON CONFLICT(input_hash, prompt_hash, entity_type, entity_id) DO UPDATE SET
1307
- source_session_id = excluded.source_session_id,
1308
- human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
1309
- updated_at = excluded.updated_at`).run(inputHash, entityType, entityId, sourceSessionId, humanEdited ? 1 : 0, now, now, promptHash);
1310
- }
1311
- export function storeScannedFiles(db, sessionId, filePaths) {
1312
- db.exec('BEGIN');
1313
- try {
1314
- db.prepare('DELETE FROM scanned_files WHERE session_id = ?').run(sessionId);
1315
- const insert = db.prepare('INSERT INTO scanned_files (session_id, path) VALUES (?, ?)');
1316
- for (const path of filePaths) {
1317
- insert.run(sessionId, path);
1318
- }
1319
- db.exec('COMMIT');
1320
- }
1321
- catch (e) {
1322
- db.exec('ROLLBACK');
1323
- throw e;
1324
- }
1325
- }
1326
- export function loadScannedFiles(db, sessionId) {
1327
- const rows = db.prepare('SELECT path FROM scanned_files WHERE session_id = ? ORDER BY path').all(sessionId);
1328
- return rows.map((r) => r.path);
1329
- }
1330
- export function storeSlotCycles(db, sessionId, cycles) {
1331
- db.exec('BEGIN');
1332
- try {
1333
- db.prepare('DELETE FROM slot_cycles WHERE session_id = ?').run(sessionId);
1334
- const insert = db.prepare(`INSERT INTO slot_cycles (session_id, cycle_index, path_json, edges_json, suggested_break_json)
1335
- VALUES (?, ?, ?, ?, ?)`);
1336
- for (let i = 0; i < cycles.length; i += 1) {
1337
- const cycle = cycles[i];
1338
- insert.run(sessionId, i, JSON.stringify(cycle.path), JSON.stringify(cycle.edges), cycle.suggestedBreak ? JSON.stringify(cycle.suggestedBreak) : null);
1339
- }
1340
- db.exec('COMMIT');
1341
- }
1342
- catch (e) {
1343
- db.exec('ROLLBACK');
1344
- throw e;
1345
- }
1346
- }
1347
- export function loadSlotCycles(db, sessionId) {
1348
- const rows = db
1349
- .prepare('SELECT cycle_index, path_json, edges_json, suggested_break_json FROM slot_cycles WHERE session_id = ? ORDER BY cycle_index')
1350
- .all(sessionId);
1351
- return rows.map((r) => ({
1352
- path: JSON.parse(r.path_json),
1353
- edges: JSON.parse(r.edges_json),
1354
- suggestedBreak: r.suggested_break_json ? JSON.parse(r.suggested_break_json) : null,
1355
- }));
1356
- }
1357
- export function clearSlotCycles(db, sessionId) {
1358
- db.prepare('DELETE FROM slot_cycles WHERE session_id = ?').run(sessionId);
1359
- }
1360
- export function markCacheHumanEdited(db, entityType, entityId) {
1361
- const now = new Date().toISOString();
1362
- db.prepare(`UPDATE generation_cache SET human_edited = 1, updated_at = ? WHERE entity_type = ? AND entity_id = ?`).run(now, entityType, entityId);
1363
- }
1364
- export function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId) {
1365
- const now = new Date().toISOString();
1366
- db.exec('BEGIN');
1367
- try {
1368
- const srcComp = db
1369
- .prepare(`SELECT description, status FROM raw_components WHERE session_id = ? AND component_id = ?`)
1370
- .get(sourceSessionId, componentId);
1371
- if (srcComp) {
1372
- db.prepare(`UPDATE raw_components SET description = ?, status = ?, extracted_at = ? WHERE session_id = ? AND component_id = ?`).run(srcComp.description, srcComp.status, now, targetSessionId, componentId);
1373
- }
1374
- const srcProps = db
1375
- .prepare(`SELECT name, cdf_type, cdf_category, cdf_token_kind, required, description, default_value
1376
- FROM raw_props WHERE session_id = ? AND component_id = ?`)
1377
- .all(sourceSessionId, componentId);
1378
- for (const p of srcProps) {
1379
- db.prepare(`UPDATE raw_props SET cdf_type = ?, cdf_category = ?, cdf_token_kind = ?, required = ?, description = ?, default_value = ?
1380
- 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);
1381
- }
1382
- db.prepare(`DELETE FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`).run(targetSessionId, componentId);
1383
- const srcAV = db
1384
- .prepare(`SELECT prop_name, position, value FROM raw_prop_allowed_values WHERE session_id = ? AND component_id = ?`)
1385
- .all(sourceSessionId, componentId);
1386
- const insertAV = db.prepare(`INSERT INTO raw_prop_allowed_values (session_id, component_id, prop_name, position, value) VALUES (?, ?, ?, ?, ?)`);
1387
- for (const av of srcAV) {
1388
- insertAV.run(targetSessionId, componentId, av.prop_name, av.position, av.value);
1389
- }
1390
- const srcSlots = db
1391
- .prepare(`SELECT name, required, description FROM raw_slots WHERE session_id = ? AND component_id = ?`)
1392
- .all(sourceSessionId, componentId);
1393
- for (const s of srcSlots) {
1394
- 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);
1395
- }
1396
- db.prepare(`DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`).run(targetSessionId, componentId);
1397
- const srcSAC = db
1398
- .prepare(`SELECT slot_name, position, allowed_component FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ?`)
1399
- .all(sourceSessionId, componentId);
1400
- const insertSAC = db.prepare(`INSERT INTO raw_slot_allowed_components (session_id, component_id, slot_name, allowed_component, position) VALUES (?, ?, ?, ?, ?)`);
1401
- for (const sac of srcSAC) {
1402
- insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
1403
- }
1404
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, targetSessionId);
1405
- db.exec('COMMIT');
1406
- }
1407
- catch (e) {
1408
- db.exec('ROLLBACK');
1409
- throw e;
1410
- }
1411
- }
1412
- export function copyTokensFromCache(db, sourceSessionId, targetSessionId) {
1413
- const now = new Date().toISOString();
1414
- db.exec('BEGIN');
1415
- try {
1416
- db.prepare('DELETE FROM raw_tokens WHERE session_id = ?').run(targetSessionId);
1417
- db.prepare('DELETE FROM raw_token_groups WHERE session_id = ?').run(targetSessionId);
1418
- db.prepare(`INSERT INTO raw_token_groups (session_id, path, description)
1419
- SELECT ?, path, description FROM raw_token_groups WHERE session_id = ?`).run(targetSessionId, sourceSessionId);
1420
- db.prepare(`INSERT INTO raw_tokens (session_id, path, type, value, description)
1421
- SELECT ?, path, type, value, description FROM raw_tokens WHERE session_id = ?`).run(targetSessionId, sourceSessionId);
1422
- db.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(now, targetSessionId);
1423
- db.exec('COMMIT');
1424
- }
1425
- catch (e) {
1426
- db.exec('ROLLBACK');
1427
- throw e;
1428
- }
1429
- }
1430
- let _cliCacheVersionCache = null;
1431
- export async function getCliCacheVersion() {
1432
- if (_cliCacheVersionCache)
1433
- return _cliCacheVersionCache;
1434
- try {
1435
- const { hashContent } = await import('./cache-keys.js');
1436
- const { resolveSkillPath } = await import('@contentful/experience-design-system-generation');
1437
- const skills = ['components', 'tokens', 'select'];
1438
- const parts = [];
1439
- for (const s of skills) {
1440
- try {
1441
- const p = resolveSkillPath(s);
1442
- parts.push(readFileSync(p, 'utf8'));
1443
- }
1444
- catch {
1445
- parts.push(`<missing:${s}>`);
1446
- }
1447
- }
1448
- _cliCacheVersionCache = hashContent(parts.join('\n---\n'));
1449
- return _cliCacheVersionCache;
1450
- }
1451
- catch {
1452
- _cliCacheVersionCache = 'fallback';
1453
- return _cliCacheVersionCache;
1454
- }
1455
- }
1456
- export function storeExtractCache(db, filePath, fileHash, cliVersion, components) {
1457
- const now = new Date().toISOString();
1458
- const componentsJson = JSON.stringify(components);
1459
- db.prepare(`INSERT INTO extract_cache (file_path, file_hash, cli_version, created_at, updated_at, components_json)
1460
- VALUES (?, ?, ?, ?, ?, ?)
1461
- ON CONFLICT(file_hash, cli_version) DO UPDATE SET
1462
- file_path = excluded.file_path,
1463
- updated_at = excluded.updated_at,
1464
- components_json = excluded.components_json`).run(filePath, fileHash, cliVersion, now, now, componentsJson);
1465
- }
1466
- export function lookupExtractCache(db, fileHash, cliVersion) {
1467
- const row = db
1468
- .prepare(`SELECT file_path, file_hash, cli_version, created_at, updated_at, components_json
1469
- FROM extract_cache
1470
- WHERE file_hash = ? AND cli_version = ?`)
1471
- .get(fileHash, cliVersion);
1472
- if (!row)
1473
- return null;
1474
- let components = [];
1475
- try {
1476
- components = JSON.parse(row.components_json);
1477
- }
1478
- catch {
1479
- return null;
1480
- }
1481
- return {
1482
- filePath: row.file_path,
1483
- fileHash: row.file_hash,
1484
- cliVersion: row.cli_version,
1485
- createdAt: row.created_at,
1486
- updatedAt: row.updated_at,
1487
- components,
1488
- };
1489
- }
1490
- /**
1491
- * Content-addressed cache for the composition resolver's raw agent output
1492
- * (authored parser source or emitted-edge JSONL). Keyed by a hash of the
1493
- * candidate files + producer identity (`composition-cache-key.ts`), scoped by
1494
- * `cli_version` so a prompt/skill change invalidates stale entries. Replaces
1495
- * the former on-disk `~/.contentful/.../composition-cache` store so all CLI
1496
- * caches live in the one pipeline DB.
1497
- */
1498
- export function storeCompositionCache(db, inputHash, cliVersion, agentOutput) {
1499
- const now = new Date().toISOString();
1500
- db.prepare(`INSERT INTO composition_cache (input_hash, cli_version, agent_output, created_at, updated_at)
1501
- VALUES (?, ?, ?, ?, ?)
1502
- ON CONFLICT(input_hash, cli_version) DO UPDATE SET
1503
- agent_output = excluded.agent_output,
1504
- updated_at = excluded.updated_at`).run(inputHash, cliVersion, agentOutput, now, now);
1505
- }
1506
- export function lookupCompositionCache(db, inputHash, cliVersion) {
1507
- const row = db
1508
- .prepare(`SELECT agent_output FROM composition_cache WHERE input_hash = ? AND cli_version = ?`)
1509
- .get(inputHash, cliVersion);
1510
- return row ? row.agent_output : null;
1511
- }
1512
- export function storeSelectCache(db, componentHash, promptHash, cliVersion, decision, reason) {
1513
- const now = new Date().toISOString();
1514
- db.prepare(`INSERT INTO select_cache (component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at)
1515
- VALUES (?, ?, ?, ?, ?, ?, ?)
1516
- ON CONFLICT(component_hash, prompt_hash, cli_version) DO UPDATE SET
1517
- decision = excluded.decision,
1518
- reason = excluded.reason,
1519
- updated_at = excluded.updated_at`).run(componentHash, promptHash, cliVersion, decision, reason, now, now);
1520
- }
1521
- export function lookupSelectCache(db, componentHash, promptHash, cliVersion) {
1522
- const row = db
1523
- .prepare(`SELECT component_hash, prompt_hash, cli_version, decision, reason, created_at, updated_at
1524
- FROM select_cache
1525
- WHERE component_hash = ? AND prompt_hash = ? AND cli_version = ?`)
1526
- .get(componentHash, promptHash, cliVersion);
1527
- if (!row)
1528
- return null;
1529
- return {
1530
- componentHash: row.component_hash,
1531
- promptHash: row.prompt_hash,
1532
- cliVersion: row.cli_version,
1533
- decision: row.decision,
1534
- reason: row.reason,
1535
- createdAt: row.created_at,
1536
- updatedAt: row.updated_at,
1537
- };
1538
- }