@straiffi/archon 1.0.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 (124) hide show
  1. package/README.md +224 -0
  2. package/dist/cli.js +216 -0
  3. package/dist/client/assets/index-8_-boBBA.css +2 -0
  4. package/dist/client/assets/index-s_jjeqha.js +176 -0
  5. package/dist/client/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  6. package/dist/client/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  7. package/dist/client/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  8. package/dist/client/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  9. package/dist/client/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  10. package/dist/client/favicon.svg +62 -0
  11. package/dist/client/icons.svg +24 -0
  12. package/dist/client/index.html +14 -0
  13. package/dist/server/db.js +764 -0
  14. package/dist/server/db.js.map +1 -0
  15. package/dist/server/index.js +5134 -0
  16. package/dist/server/index.js.map +1 -0
  17. package/dist/server/lib/agent.js +1302 -0
  18. package/dist/server/lib/agent.js.map +1 -0
  19. package/dist/server/lib/buildChains.js +2 -0
  20. package/dist/server/lib/buildChains.js.map +1 -0
  21. package/dist/server/lib/buildFlow.js +59 -0
  22. package/dist/server/lib/buildFlow.js.map +1 -0
  23. package/dist/server/lib/buildSequences.js +599 -0
  24. package/dist/server/lib/buildSequences.js.map +1 -0
  25. package/dist/server/lib/bundleActivity.js +95 -0
  26. package/dist/server/lib/bundleActivity.js.map +1 -0
  27. package/dist/server/lib/bundlePullRequests.js +126 -0
  28. package/dist/server/lib/bundlePullRequests.js.map +1 -0
  29. package/dist/server/lib/chatMessages.js +60 -0
  30. package/dist/server/lib/chatMessages.js.map +1 -0
  31. package/dist/server/lib/chatTargets.js +123 -0
  32. package/dist/server/lib/chatTargets.js.map +1 -0
  33. package/dist/server/lib/chatTicketProposals.js +180 -0
  34. package/dist/server/lib/chatTicketProposals.js.map +1 -0
  35. package/dist/server/lib/chats.js +279 -0
  36. package/dist/server/lib/chats.js.map +1 -0
  37. package/dist/server/lib/config.js +3 -0
  38. package/dist/server/lib/config.js.map +1 -0
  39. package/dist/server/lib/cors.js +30 -0
  40. package/dist/server/lib/cors.js.map +1 -0
  41. package/dist/server/lib/directoryPicker.js +174 -0
  42. package/dist/server/lib/directoryPicker.js.map +1 -0
  43. package/dist/server/lib/git.js +1284 -0
  44. package/dist/server/lib/git.js.map +1 -0
  45. package/dist/server/lib/integrations/github.js +511 -0
  46. package/dist/server/lib/integrations/github.js.map +1 -0
  47. package/dist/server/lib/integrations/index.js +162 -0
  48. package/dist/server/lib/integrations/index.js.map +1 -0
  49. package/dist/server/lib/integrations/jira.js +283 -0
  50. package/dist/server/lib/integrations/jira.js.map +1 -0
  51. package/dist/server/lib/integrations/planning.js +27 -0
  52. package/dist/server/lib/integrations/planning.js.map +1 -0
  53. package/dist/server/lib/integrations/types.js +2 -0
  54. package/dist/server/lib/integrations/types.js.map +1 -0
  55. package/dist/server/lib/lightweightPrompt.js +88 -0
  56. package/dist/server/lib/lightweightPrompt.js.map +1 -0
  57. package/dist/server/lib/models.js +219 -0
  58. package/dist/server/lib/models.js.map +1 -0
  59. package/dist/server/lib/preview.js +377 -0
  60. package/dist/server/lib/preview.js.map +1 -0
  61. package/dist/server/lib/previewProxy.js +659 -0
  62. package/dist/server/lib/previewProxy.js.map +1 -0
  63. package/dist/server/lib/projectAutoConfig.js +682 -0
  64. package/dist/server/lib/projectAutoConfig.js.map +1 -0
  65. package/dist/server/lib/projectFileSuggestions.js +133 -0
  66. package/dist/server/lib/projectFileSuggestions.js.map +1 -0
  67. package/dist/server/lib/projectMemory.js +1519 -0
  68. package/dist/server/lib/projectMemory.js.map +1 -0
  69. package/dist/server/lib/projectMemoryPrompt.js +390 -0
  70. package/dist/server/lib/projectMemoryPrompt.js.map +1 -0
  71. package/dist/server/lib/projectMemoryScan.js +681 -0
  72. package/dist/server/lib/projectMemoryScan.js.map +1 -0
  73. package/dist/server/lib/projectMemorySuggestions.js +166 -0
  74. package/dist/server/lib/projectMemorySuggestions.js.map +1 -0
  75. package/dist/server/lib/projectMemoryTransfer.js +958 -0
  76. package/dist/server/lib/projectMemoryTransfer.js.map +1 -0
  77. package/dist/server/lib/projects.js +569 -0
  78. package/dist/server/lib/projects.js.map +1 -0
  79. package/dist/server/lib/promptSkills.js +28 -0
  80. package/dist/server/lib/promptSkills.js.map +1 -0
  81. package/dist/server/lib/queue.js +15 -0
  82. package/dist/server/lib/queue.js.map +1 -0
  83. package/dist/server/lib/reviewFindings.js +390 -0
  84. package/dist/server/lib/reviewFindings.js.map +1 -0
  85. package/dist/server/lib/run.js +416 -0
  86. package/dist/server/lib/run.js.map +1 -0
  87. package/dist/server/lib/runtimePaths.js +93 -0
  88. package/dist/server/lib/runtimePaths.js.map +1 -0
  89. package/dist/server/lib/shell.js +27 -0
  90. package/dist/server/lib/shell.js.map +1 -0
  91. package/dist/server/lib/skills.js +124 -0
  92. package/dist/server/lib/skills.js.map +1 -0
  93. package/dist/server/lib/startDev.js +18 -0
  94. package/dist/server/lib/startDev.js.map +1 -0
  95. package/dist/server/lib/staticClient.js +80 -0
  96. package/dist/server/lib/staticClient.js.map +1 -0
  97. package/dist/server/lib/terminal.js +366 -0
  98. package/dist/server/lib/terminal.js.map +1 -0
  99. package/dist/server/lib/ticketDependencies.js +174 -0
  100. package/dist/server/lib/ticketDependencies.js.map +1 -0
  101. package/dist/server/lib/ticketMessages.js +65 -0
  102. package/dist/server/lib/ticketMessages.js.map +1 -0
  103. package/dist/server/lib/ticketOpenQuestions.js +128 -0
  104. package/dist/server/lib/ticketOpenQuestions.js.map +1 -0
  105. package/dist/server/lib/ticketUndo.js +549 -0
  106. package/dist/server/lib/ticketUndo.js.map +1 -0
  107. package/dist/server/lib/tickets.js +981 -0
  108. package/dist/server/lib/tickets.js.map +1 -0
  109. package/dist/server/lib/types.js +2 -0
  110. package/dist/server/lib/types.js.map +1 -0
  111. package/dist/server/package.json +3 -0
  112. package/dist/server/workers/build.js +229 -0
  113. package/dist/server/workers/build.js.map +1 -0
  114. package/dist/server/workers/chat.js +190 -0
  115. package/dist/server/workers/chat.js.map +1 -0
  116. package/dist/server/workers/followUp.js +204 -0
  117. package/dist/server/workers/followUp.js.map +1 -0
  118. package/dist/server/workers/plan.js +1130 -0
  119. package/dist/server/workers/plan.js.map +1 -0
  120. package/dist/server/workers/planFollowUp.js +360 -0
  121. package/dist/server/workers/planFollowUp.js.map +1 -0
  122. package/dist/server/workers/review.js +167 -0
  123. package/dist/server/workers/review.js.map +1 -0
  124. package/package.json +40 -0
@@ -0,0 +1,764 @@
1
+ import Database from 'better-sqlite3';
2
+ import { ensureParentDirectory, resolveArchonDbPath } from './lib/runtimePaths.js';
3
+ const dbPath = ensureParentDirectory(resolveArchonDbPath());
4
+ const db = new Database(dbPath);
5
+ db.pragma('foreign_keys = ON');
6
+ db.exec(`
7
+ CREATE TABLE IF NOT EXISTS tickets (
8
+ id TEXT PRIMARY KEY,
9
+ title TEXT NOT NULL,
10
+ description TEXT,
11
+ state TEXT NOT NULL DEFAULT 'plan',
12
+ agent_status TEXT,
13
+ parked_at DATETIME,
14
+ auto_park_dismissed_at DATETIME,
15
+ branch TEXT,
16
+ agent_log TEXT,
17
+ model TEXT,
18
+ lane_order REAL,
19
+ done_order REAL,
20
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
21
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
22
+ )
23
+ `);
24
+ db.exec(`
25
+ CREATE TABLE IF NOT EXISTS worktree_bundles (
26
+ id TEXT PRIMARY KEY,
27
+ name TEXT NOT NULL,
28
+ branch TEXT NOT NULL,
29
+ kind TEXT NOT NULL DEFAULT 'worktree',
30
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
31
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
32
+ )
33
+ `);
34
+ db.exec(`
35
+ CREATE TABLE IF NOT EXISTS worktree_bases (
36
+ repo_path TEXT NOT NULL,
37
+ branch TEXT NOT NULL,
38
+ base_branch TEXT,
39
+ base_commit TEXT,
40
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
41
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
42
+ PRIMARY KEY (repo_path, branch)
43
+ )
44
+ `);
45
+ db.exec(`
46
+ CREATE TABLE IF NOT EXISTS projects (
47
+ id TEXT PRIMARY KEY,
48
+ name TEXT NOT NULL,
49
+ repo_path TEXT NOT NULL,
50
+ active_target_kind TEXT NOT NULL DEFAULT 'repo_root',
51
+ active_target_bundle_id TEXT,
52
+ worktree_cmd TEXT,
53
+ run_setup TEXT,
54
+ run_services TEXT,
55
+ run_ide TEXT,
56
+ preview_service_name TEXT,
57
+ preview_path TEXT,
58
+ preview_capability_mode TEXT,
59
+ helper_model TEXT,
60
+ helper_variant TEXT,
61
+ commit_message_rules TEXT,
62
+ auto_park_stale_tickets INTEGER NOT NULL DEFAULT 0,
63
+ memory_enabled INTEGER NOT NULL DEFAULT 0,
64
+ worktree_sync TEXT,
65
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
66
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
67
+ )
68
+ `);
69
+ db.exec(`
70
+ CREATE TABLE IF NOT EXISTS project_links (
71
+ project_id TEXT NOT NULL,
72
+ linked_project_id TEXT NOT NULL,
73
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
74
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
75
+ PRIMARY KEY (project_id, linked_project_id),
76
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
77
+ FOREIGN KEY (linked_project_id) REFERENCES projects(id) ON DELETE RESTRICT
78
+ )
79
+ `);
80
+ const ensureColumn = (table, column, type) => {
81
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all();
82
+ if (!columns.some(existingColumn => existingColumn.name === column)) {
83
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
84
+ }
85
+ };
86
+ ensureColumn('tickets', 'model', 'TEXT');
87
+ ensureColumn('tickets', 'variant', 'TEXT');
88
+ ensureColumn('tickets', 'skills_json', 'TEXT');
89
+ ensureColumn('tickets', 'session_id', 'TEXT');
90
+ ensureColumn('tickets', 'planning_session_id', 'TEXT');
91
+ ensureColumn('tickets', 'project_id', 'TEXT');
92
+ ensureColumn('tickets', 'tool', 'TEXT');
93
+ ensureColumn('tickets', 'worktree_bundle_id', 'TEXT');
94
+ ensureColumn('tickets', 'planning_context_json', 'TEXT');
95
+ ensureColumn('tickets', 'build_completed_at', 'DATETIME');
96
+ ensureColumn('tickets', 'parked_at', 'DATETIME');
97
+ ensureColumn('tickets', 'auto_park_dismissed_at', 'DATETIME');
98
+ ensureColumn('tickets', 'streaming_response', 'TEXT');
99
+ ensureColumn('tickets', 'run_setup_status', 'TEXT');
100
+ ensureColumn('tickets', 'lane_order', 'REAL');
101
+ ensureColumn('tickets', 'done_order', 'REAL');
102
+ ensureColumn('tickets', 'external_provider', 'TEXT');
103
+ ensureColumn('tickets', 'external_id', 'TEXT');
104
+ ensureColumn('tickets', 'external_key', 'TEXT');
105
+ ensureColumn('tickets', 'external_url', 'TEXT');
106
+ ensureColumn('tickets', 'external_metadata_json', 'TEXT');
107
+ ensureColumn('worktree_bundles', 'project_id', 'TEXT');
108
+ ensureColumn('worktree_bundles', 'kind', "TEXT NOT NULL DEFAULT 'worktree'");
109
+ ensureColumn('projects', 'worktree_sync', 'TEXT');
110
+ ensureColumn('projects', 'active_target_kind', "TEXT NOT NULL DEFAULT 'repo_root'");
111
+ ensureColumn('projects', 'active_target_bundle_id', 'TEXT');
112
+ ensureColumn('projects', 'helper_model', 'TEXT');
113
+ ensureColumn('projects', 'helper_variant', 'TEXT');
114
+ ensureColumn('projects', 'commit_message_rules', 'TEXT');
115
+ ensureColumn('projects', 'preview_service_name', 'TEXT');
116
+ ensureColumn('projects', 'preview_path', 'TEXT');
117
+ ensureColumn('projects', 'preview_capability_mode', 'TEXT');
118
+ ensureColumn('projects', 'auto_park_stale_tickets', 'INTEGER NOT NULL DEFAULT 0');
119
+ ensureColumn('projects', 'memory_enabled', 'INTEGER NOT NULL DEFAULT 0');
120
+ ensureColumn('worktree_bases', 'repo_path', 'TEXT');
121
+ ensureColumn('worktree_bases', 'base_branch', 'TEXT');
122
+ ensureColumn('worktree_bases', 'base_commit', 'TEXT');
123
+ const ORDER_STEP = 1000;
124
+ const backfillTicketOrders = () => {
125
+ const activeRows = db.prepare(`
126
+ SELECT id, state
127
+ FROM tickets
128
+ WHERE lane_order IS NULL
129
+ ORDER BY state ASC, updated_at DESC, id DESC
130
+ `).all();
131
+ const nextLaneOrder = new Map([
132
+ ['plan', 0],
133
+ ['build', 0],
134
+ ['review', 0],
135
+ ]);
136
+ for (const row of activeRows) {
137
+ const nextOrder = (nextLaneOrder.get(row.state) ?? 0) + ORDER_STEP;
138
+ nextLaneOrder.set(row.state, nextOrder);
139
+ db.prepare('UPDATE tickets SET lane_order = ? WHERE id = ?').run(nextOrder, row.id);
140
+ }
141
+ const parkedRows = db.prepare(`
142
+ SELECT id
143
+ FROM tickets
144
+ WHERE parked_at IS NOT NULL AND done_order IS NULL
145
+ ORDER BY parked_at DESC, updated_at DESC, id DESC
146
+ `).all();
147
+ let nextDoneOrder = 0;
148
+ for (const row of parkedRows) {
149
+ nextDoneOrder += ORDER_STEP;
150
+ db.prepare('UPDATE tickets SET done_order = ? WHERE id = ?').run(nextDoneOrder, row.id);
151
+ }
152
+ };
153
+ backfillTicketOrders();
154
+ const ensureUniqueBundleNames = () => {
155
+ const duplicateGroups = db.prepare(`
156
+ SELECT project_id, name
157
+ FROM worktree_bundles
158
+ GROUP BY project_id, name
159
+ HAVING COUNT(*) > 1
160
+ `).all();
161
+ const listDuplicates = db.prepare(`
162
+ SELECT id, name
163
+ FROM worktree_bundles
164
+ WHERE project_id IS ? AND name = ?
165
+ ORDER BY created_at ASC, id ASC
166
+ `);
167
+ const hasName = db.prepare(`
168
+ SELECT 1
169
+ FROM worktree_bundles
170
+ WHERE project_id IS ? AND name = ? AND id != ?
171
+ LIMIT 1
172
+ `);
173
+ const updateName = db.prepare(`
174
+ UPDATE worktree_bundles
175
+ SET name = ?, updated_at = CURRENT_TIMESTAMP
176
+ WHERE id = ?
177
+ `);
178
+ for (const duplicateGroup of duplicateGroups) {
179
+ const bundles = listDuplicates.all(duplicateGroup.project_id, duplicateGroup.name);
180
+ for (let index = 1; index < bundles.length; index += 1) {
181
+ const bundle = bundles[index];
182
+ let suffix = index + 1;
183
+ let candidateName = `${duplicateGroup.name} ${suffix}`;
184
+ while (hasName.get(duplicateGroup.project_id, candidateName, bundle.id)) {
185
+ suffix += 1;
186
+ candidateName = `${duplicateGroup.name} ${suffix}`;
187
+ }
188
+ updateName.run(candidateName, bundle.id);
189
+ }
190
+ }
191
+ };
192
+ const ensureBundleIndexes = () => {
193
+ db.exec('CREATE INDEX IF NOT EXISTS idx_tickets_worktree_bundle_id ON tickets(worktree_bundle_id)');
194
+ db.exec('DROP INDEX IF EXISTS idx_worktree_bundles_branch');
195
+ db.exec('DROP INDEX IF EXISTS idx_worktree_bundles_project_name');
196
+ db.exec('DROP INDEX IF EXISTS idx_worktree_bundles_project_branch');
197
+ db.exec('CREATE INDEX IF NOT EXISTS idx_worktree_bundles_project_id ON worktree_bundles(project_id)');
198
+ ensureUniqueBundleNames();
199
+ db.exec("UPDATE worktree_bundles SET kind = 'worktree' WHERE kind IS NULL OR TRIM(kind) = ''");
200
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_worktree_bundles_project_name ON worktree_bundles(project_id, name, kind)");
201
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_worktree_bundles_project_branch ON worktree_bundles(project_id, branch, kind)");
202
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_worktree_bundles_project_root ON worktree_bundles(project_id, kind) WHERE kind = 'project_root'");
203
+ };
204
+ ensureBundleIndexes();
205
+ db.exec(`
206
+ CREATE TABLE IF NOT EXISTS integration_connections (
207
+ id TEXT PRIMARY KEY,
208
+ provider TEXT NOT NULL,
209
+ project_id TEXT,
210
+ status TEXT NOT NULL,
211
+ config_json TEXT,
212
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
213
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
214
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
215
+ )
216
+ `);
217
+ db.exec(`
218
+ CREATE TABLE IF NOT EXISTS project_conventions (
219
+ id TEXT PRIMARY KEY,
220
+ project_id TEXT NOT NULL,
221
+ title TEXT NOT NULL,
222
+ scope TEXT,
223
+ instruction TEXT NOT NULL,
224
+ rationale TEXT,
225
+ stages_json TEXT,
226
+ priority TEXT NOT NULL DEFAULT 'normal',
227
+ injection_mode TEXT NOT NULL DEFAULT 'relevant',
228
+ status TEXT NOT NULL DEFAULT 'active',
229
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
230
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
231
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
232
+ )
233
+ `);
234
+ ensureColumn('project_conventions', 'injection_mode', "TEXT NOT NULL DEFAULT 'relevant'");
235
+ db.exec(`
236
+ CREATE TABLE IF NOT EXISTS project_decisions (
237
+ id TEXT PRIMARY KEY,
238
+ project_id TEXT NOT NULL,
239
+ title TEXT NOT NULL,
240
+ scope TEXT,
241
+ decision TEXT NOT NULL,
242
+ rationale TEXT,
243
+ implications_json TEXT,
244
+ status TEXT NOT NULL DEFAULT 'accepted',
245
+ source_ticket_id TEXT,
246
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
247
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
248
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
249
+ )
250
+ `);
251
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_conventions_project_status ON project_conventions(project_id, status)');
252
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_decisions_project_status ON project_decisions(project_id, status)');
253
+ db.exec(`
254
+ CREATE TABLE IF NOT EXISTS project_memory_suggestions (
255
+ id TEXT PRIMARY KEY,
256
+ project_id TEXT NOT NULL,
257
+ ticket_id TEXT,
258
+ chat_session_id TEXT,
259
+ source_stage TEXT NOT NULL,
260
+ kind TEXT NOT NULL,
261
+ title TEXT NOT NULL,
262
+ scope TEXT,
263
+ content_text TEXT NOT NULL,
264
+ rationale TEXT,
265
+ details_json TEXT,
266
+ duplicate_kind TEXT NOT NULL DEFAULT 'none',
267
+ duplicate_target_id TEXT,
268
+ status TEXT NOT NULL DEFAULT 'pending',
269
+ resolved_memory_id TEXT,
270
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
271
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
272
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
273
+ FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
274
+ FOREIGN KEY (chat_session_id) REFERENCES chat_sessions(id) ON DELETE CASCADE,
275
+ CHECK (ticket_id IS NOT NULL OR chat_session_id IS NOT NULL)
276
+ )
277
+ `);
278
+ db.exec(`
279
+ CREATE TABLE IF NOT EXISTS project_context_scans (
280
+ id TEXT PRIMARY KEY,
281
+ project_id TEXT NOT NULL,
282
+ status TEXT NOT NULL,
283
+ repo_head TEXT,
284
+ repo_branch TEXT,
285
+ scanner_tool TEXT,
286
+ scanner_model TEXT,
287
+ scanner_variant TEXT,
288
+ summary_markdown TEXT,
289
+ error TEXT,
290
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
291
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
292
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
293
+ )
294
+ `);
295
+ db.exec(`
296
+ CREATE TABLE IF NOT EXISTS project_context_artifacts (
297
+ id TEXT PRIMARY KEY,
298
+ scan_id TEXT NOT NULL,
299
+ project_id TEXT NOT NULL,
300
+ kind TEXT NOT NULL,
301
+ title TEXT NOT NULL,
302
+ content_json TEXT,
303
+ content_markdown TEXT,
304
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
305
+ FOREIGN KEY (scan_id) REFERENCES project_context_scans(id) ON DELETE CASCADE,
306
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
307
+ )
308
+ `);
309
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_context_scans_project_status ON project_context_scans(project_id, status)');
310
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_context_scans_project_created_at ON project_context_scans(project_id, created_at DESC)');
311
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_context_artifacts_scan_id ON project_context_artifacts(scan_id)');
312
+ db.prepare(`
313
+ UPDATE project_context_scans
314
+ SET status = 'error',
315
+ error = CASE
316
+ WHEN error IS NULL OR TRIM(error) = '' THEN 'Server restarted before the scan completed.'
317
+ ELSE error
318
+ END,
319
+ updated_at = CURRENT_TIMESTAMP
320
+ WHERE status IN ('pending', 'running')
321
+ `).run();
322
+ db.exec(`
323
+ CREATE TABLE IF NOT EXISTS build_sequences (
324
+ id TEXT PRIMARY KEY,
325
+ project_id TEXT NOT NULL,
326
+ worktree_bundle_id TEXT,
327
+ kind TEXT NOT NULL,
328
+ root_ticket_id TEXT,
329
+ status TEXT NOT NULL DEFAULT 'active',
330
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
331
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
332
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
333
+ FOREIGN KEY (worktree_bundle_id) REFERENCES worktree_bundles(id) ON DELETE CASCADE,
334
+ FOREIGN KEY (root_ticket_id) REFERENCES tickets(id) ON DELETE SET NULL
335
+ )
336
+ `);
337
+ db.exec(`
338
+ CREATE TABLE IF NOT EXISTS build_sequence_tickets (
339
+ build_sequence_id TEXT NOT NULL,
340
+ ticket_id TEXT NOT NULL,
341
+ position INTEGER NOT NULL,
342
+ status TEXT NOT NULL,
343
+ entered_queue_at DATETIME DEFAULT CURRENT_TIMESTAMP,
344
+ started_at DATETIME,
345
+ finished_at DATETIME,
346
+ PRIMARY KEY (build_sequence_id, ticket_id),
347
+ FOREIGN KEY (build_sequence_id) REFERENCES build_sequences(id) ON DELETE CASCADE,
348
+ FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE
349
+ )
350
+ `);
351
+ db.exec(`
352
+ CREATE TABLE IF NOT EXISTS ticket_dependencies (
353
+ blocker_ticket_id TEXT NOT NULL,
354
+ dependent_ticket_id TEXT NOT NULL,
355
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
356
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
357
+ PRIMARY KEY (blocker_ticket_id, dependent_ticket_id),
358
+ FOREIGN KEY (blocker_ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
359
+ FOREIGN KEY (dependent_ticket_id) REFERENCES tickets(id) ON DELETE CASCADE
360
+ )
361
+ `);
362
+ db.exec(`
363
+ CREATE TABLE IF NOT EXISTS ticket_messages (
364
+ id TEXT PRIMARY KEY,
365
+ ticket_id TEXT NOT NULL,
366
+ role TEXT NOT NULL,
367
+ kind TEXT NOT NULL,
368
+ content TEXT NOT NULL,
369
+ bundle_visible_at DATETIME,
370
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
371
+ FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE
372
+ )
373
+ `);
374
+ ensureColumn('ticket_messages', 'bundle_visible_at', 'DATETIME');
375
+ db.prepare(`
376
+ UPDATE ticket_messages
377
+ SET bundle_visible_at = (
378
+ SELECT MIN(related.created_at)
379
+ FROM ticket_messages AS related
380
+ WHERE related.ticket_id = ticket_messages.ticket_id
381
+ AND related.kind != 'description'
382
+ )
383
+ WHERE ticket_messages.kind = 'description'
384
+ AND ticket_messages.bundle_visible_at IS NULL
385
+ AND EXISTS (
386
+ SELECT 1
387
+ FROM ticket_messages AS related
388
+ WHERE related.ticket_id = ticket_messages.ticket_id
389
+ AND related.kind != 'description'
390
+ )
391
+ `).run();
392
+ db.prepare(`
393
+ UPDATE ticket_messages
394
+ SET bundle_visible_at = (
395
+ SELECT tickets.updated_at
396
+ FROM tickets
397
+ WHERE tickets.id = ticket_messages.ticket_id
398
+ )
399
+ WHERE ticket_messages.kind = 'description'
400
+ AND ticket_messages.bundle_visible_at IS NULL
401
+ AND EXISTS (
402
+ SELECT 1
403
+ FROM tickets
404
+ WHERE tickets.id = ticket_messages.ticket_id
405
+ AND tickets.state != 'plan'
406
+ AND (tickets.agent_status IS NOT NULL OR tickets.session_id IS NOT NULL)
407
+ )
408
+ `).run();
409
+ db.exec(`
410
+ CREATE TABLE IF NOT EXISTS chat_sessions (
411
+ id TEXT PRIMARY KEY,
412
+ project_id TEXT NOT NULL,
413
+ title TEXT NOT NULL,
414
+ mode TEXT NOT NULL,
415
+ tool TEXT NOT NULL,
416
+ model TEXT,
417
+ variant TEXT,
418
+ skills_json TEXT,
419
+ agent_status TEXT,
420
+ agent_log TEXT,
421
+ streaming_response TEXT,
422
+ target_kind TEXT NOT NULL DEFAULT 'repo_root',
423
+ target_bundle_id TEXT,
424
+ target_branch_snapshot TEXT,
425
+ target_label_snapshot TEXT,
426
+ claude_session_id TEXT,
427
+ opencode_session_id TEXT,
428
+ usage_snapshot_context_window INTEGER,
429
+ usage_snapshot_max_output_tokens INTEGER,
430
+ usage_snapshot_total_tokens INTEGER,
431
+ usage_snapshot_input_tokens INTEGER,
432
+ usage_snapshot_output_tokens INTEGER,
433
+ usage_snapshot_cache_read_tokens INTEGER,
434
+ usage_snapshot_reasoning_tokens INTEGER,
435
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
436
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
437
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
438
+ )
439
+ `);
440
+ db.exec(`
441
+ CREATE TABLE IF NOT EXISTS chat_messages (
442
+ id TEXT PRIMARY KEY,
443
+ chat_session_id TEXT NOT NULL,
444
+ role TEXT NOT NULL,
445
+ kind TEXT NOT NULL,
446
+ content TEXT NOT NULL,
447
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
448
+ FOREIGN KEY (chat_session_id) REFERENCES chat_sessions(id) ON DELETE CASCADE
449
+ )
450
+ `);
451
+ ensureColumn('chat_sessions', 'usage_snapshot_context_window', 'INTEGER');
452
+ ensureColumn('chat_sessions', 'usage_snapshot_max_output_tokens', 'INTEGER');
453
+ ensureColumn('chat_sessions', 'usage_snapshot_total_tokens', 'INTEGER');
454
+ ensureColumn('chat_sessions', 'usage_snapshot_input_tokens', 'INTEGER');
455
+ ensureColumn('chat_sessions', 'usage_snapshot_output_tokens', 'INTEGER');
456
+ ensureColumn('chat_sessions', 'usage_snapshot_cache_read_tokens', 'INTEGER');
457
+ ensureColumn('chat_sessions', 'usage_snapshot_reasoning_tokens', 'INTEGER');
458
+ ensureColumn('chat_sessions', 'target_kind', "TEXT NOT NULL DEFAULT 'repo_root'");
459
+ ensureColumn('chat_sessions', 'target_bundle_id', 'TEXT');
460
+ ensureColumn('chat_sessions', 'target_branch_snapshot', 'TEXT');
461
+ ensureColumn('chat_sessions', 'target_label_snapshot', 'TEXT');
462
+ const createChatSessionsTableSql = `
463
+ CREATE TABLE chat_sessions (
464
+ id TEXT PRIMARY KEY,
465
+ project_id TEXT NOT NULL,
466
+ title TEXT NOT NULL,
467
+ mode TEXT NOT NULL,
468
+ tool TEXT NOT NULL,
469
+ model TEXT,
470
+ variant TEXT,
471
+ skills_json TEXT,
472
+ agent_status TEXT,
473
+ agent_log TEXT,
474
+ streaming_response TEXT,
475
+ target_kind TEXT NOT NULL DEFAULT 'repo_root',
476
+ target_bundle_id TEXT,
477
+ target_branch_snapshot TEXT,
478
+ target_label_snapshot TEXT,
479
+ claude_session_id TEXT,
480
+ opencode_session_id TEXT,
481
+ usage_snapshot_context_window INTEGER,
482
+ usage_snapshot_max_output_tokens INTEGER,
483
+ usage_snapshot_total_tokens INTEGER,
484
+ usage_snapshot_input_tokens INTEGER,
485
+ usage_snapshot_output_tokens INTEGER,
486
+ usage_snapshot_cache_read_tokens INTEGER,
487
+ usage_snapshot_reasoning_tokens INTEGER,
488
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
489
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
490
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
491
+ )
492
+ `;
493
+ const migrateChatSessionsUsageSnapshotTable = () => {
494
+ const columns = db.prepare('PRAGMA table_info(chat_sessions)').all();
495
+ if (columns.length === 0) {
496
+ return;
497
+ }
498
+ const hasLegacyUsageColumns = columns.some(column => column.name.startsWith('latest_turn_'));
499
+ if (!hasLegacyUsageColumns) {
500
+ return;
501
+ }
502
+ const resolveColumn = (preferred, legacy) => {
503
+ if (legacy && columns.some(column => column.name === legacy)) {
504
+ return legacy;
505
+ }
506
+ if (columns.some(column => column.name === preferred)) {
507
+ return preferred;
508
+ }
509
+ return 'NULL';
510
+ };
511
+ db.exec('PRAGMA foreign_keys = OFF');
512
+ try {
513
+ db.exec('BEGIN');
514
+ db.exec(createChatSessionsTableSql.replace('chat_sessions', 'chat_sessions_v2'));
515
+ db.exec(`
516
+ INSERT INTO chat_sessions_v2 (
517
+ id,
518
+ project_id,
519
+ title,
520
+ mode,
521
+ tool,
522
+ model,
523
+ variant,
524
+ skills_json,
525
+ agent_status,
526
+ agent_log,
527
+ streaming_response,
528
+ claude_session_id,
529
+ opencode_session_id,
530
+ usage_snapshot_context_window,
531
+ usage_snapshot_max_output_tokens,
532
+ usage_snapshot_total_tokens,
533
+ usage_snapshot_input_tokens,
534
+ usage_snapshot_output_tokens,
535
+ usage_snapshot_cache_read_tokens,
536
+ usage_snapshot_reasoning_tokens,
537
+ created_at,
538
+ updated_at
539
+ )
540
+ SELECT
541
+ id,
542
+ project_id,
543
+ title,
544
+ mode,
545
+ tool,
546
+ model,
547
+ variant,
548
+ skills_json,
549
+ agent_status,
550
+ agent_log,
551
+ streaming_response,
552
+ claude_session_id,
553
+ opencode_session_id,
554
+ ${resolveColumn('usage_snapshot_context_window', 'latest_turn_context_window')},
555
+ ${resolveColumn('usage_snapshot_max_output_tokens', 'latest_turn_max_output_tokens')},
556
+ ${resolveColumn('usage_snapshot_total_tokens', 'latest_turn_total_tokens')},
557
+ ${resolveColumn('usage_snapshot_input_tokens', 'latest_turn_input_tokens')},
558
+ ${resolveColumn('usage_snapshot_output_tokens', 'latest_turn_output_tokens')},
559
+ ${resolveColumn('usage_snapshot_cache_read_tokens', 'latest_turn_cache_read_tokens')},
560
+ ${resolveColumn('usage_snapshot_reasoning_tokens', 'latest_turn_reasoning_tokens')},
561
+ created_at,
562
+ updated_at
563
+ FROM chat_sessions
564
+ `);
565
+ db.exec('DROP TABLE chat_sessions');
566
+ db.exec('ALTER TABLE chat_sessions_v2 RENAME TO chat_sessions');
567
+ db.exec('COMMIT');
568
+ }
569
+ catch (error) {
570
+ db.exec('ROLLBACK');
571
+ throw error;
572
+ }
573
+ finally {
574
+ db.exec('PRAGMA foreign_keys = ON');
575
+ }
576
+ };
577
+ migrateChatSessionsUsageSnapshotTable();
578
+ const migrateProjectMemorySuggestionsTable = () => {
579
+ const columns = db.prepare('PRAGMA table_info(project_memory_suggestions)').all();
580
+ if (columns.length === 0) {
581
+ return;
582
+ }
583
+ const ticketIdColumn = columns.find(column => column.name === 'ticket_id');
584
+ const hasChatSessionId = columns.some(column => column.name === 'chat_session_id');
585
+ const needsMigration = !hasChatSessionId || ticketIdColumn?.notnull === 1;
586
+ if (!needsMigration) {
587
+ return;
588
+ }
589
+ db.exec(`
590
+ CREATE TABLE project_memory_suggestions_v2 (
591
+ id TEXT PRIMARY KEY,
592
+ project_id TEXT NOT NULL,
593
+ ticket_id TEXT,
594
+ chat_session_id TEXT,
595
+ source_stage TEXT NOT NULL,
596
+ kind TEXT NOT NULL,
597
+ title TEXT NOT NULL,
598
+ scope TEXT,
599
+ content_text TEXT NOT NULL,
600
+ rationale TEXT,
601
+ details_json TEXT,
602
+ duplicate_kind TEXT NOT NULL DEFAULT 'none',
603
+ duplicate_target_id TEXT,
604
+ status TEXT NOT NULL DEFAULT 'pending',
605
+ resolved_memory_id TEXT,
606
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
607
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
608
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
609
+ FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
610
+ FOREIGN KEY (chat_session_id) REFERENCES chat_sessions(id) ON DELETE CASCADE,
611
+ CHECK (ticket_id IS NOT NULL OR chat_session_id IS NOT NULL)
612
+ )
613
+ `);
614
+ db.exec(`
615
+ INSERT INTO project_memory_suggestions_v2 (
616
+ id,
617
+ project_id,
618
+ ticket_id,
619
+ chat_session_id,
620
+ source_stage,
621
+ kind,
622
+ title,
623
+ scope,
624
+ content_text,
625
+ rationale,
626
+ details_json,
627
+ duplicate_kind,
628
+ duplicate_target_id,
629
+ status,
630
+ resolved_memory_id,
631
+ created_at,
632
+ updated_at
633
+ )
634
+ SELECT
635
+ id,
636
+ project_id,
637
+ ticket_id,
638
+ NULL,
639
+ source_stage,
640
+ kind,
641
+ title,
642
+ scope,
643
+ content_text,
644
+ rationale,
645
+ details_json,
646
+ duplicate_kind,
647
+ duplicate_target_id,
648
+ status,
649
+ resolved_memory_id,
650
+ created_at,
651
+ updated_at
652
+ FROM project_memory_suggestions
653
+ `);
654
+ db.exec('DROP TABLE project_memory_suggestions');
655
+ db.exec('ALTER TABLE project_memory_suggestions_v2 RENAME TO project_memory_suggestions');
656
+ };
657
+ migrateProjectMemorySuggestionsTable();
658
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_memory_suggestions_project_status ON project_memory_suggestions(project_id, status)');
659
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_memory_suggestions_ticket_status ON project_memory_suggestions(ticket_id, status)');
660
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_memory_suggestions_chat_session_status ON project_memory_suggestions(chat_session_id, status)');
661
+ db.exec(`
662
+ CREATE TABLE IF NOT EXISTS ticket_stage_changes (
663
+ id TEXT PRIMARY KEY,
664
+ ticket_id TEXT NOT NULL,
665
+ project_id TEXT,
666
+ worktree_bundle_id TEXT,
667
+ branch TEXT NOT NULL,
668
+ stage TEXT NOT NULL,
669
+ baseline_snapshot_path TEXT,
670
+ baseline_build_completed_at DATETIME,
671
+ baseline_session_id TEXT,
672
+ diff_text TEXT,
673
+ changed_files_json TEXT,
674
+ diff_stats_json TEXT,
675
+ success_revision INTEGER DEFAULT 0,
676
+ last_success_at DATETIME,
677
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
678
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
679
+ FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE
680
+ )
681
+ `);
682
+ db.exec(`
683
+ CREATE TABLE IF NOT EXISTS review_runs (
684
+ id TEXT PRIMARY KEY,
685
+ ticket_id TEXT NOT NULL,
686
+ project_id TEXT,
687
+ worktree_bundle_id TEXT,
688
+ stage TEXT NOT NULL,
689
+ diff_signature TEXT NOT NULL,
690
+ workspace_signature TEXT,
691
+ message_id TEXT,
692
+ status TEXT NOT NULL,
693
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
694
+ FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
695
+ FOREIGN KEY (message_id) REFERENCES ticket_messages(id) ON DELETE SET NULL
696
+ )
697
+ `);
698
+ ensureColumn('review_runs', 'workspace_signature', 'TEXT');
699
+ db.exec(`
700
+ CREATE TABLE IF NOT EXISTS review_findings (
701
+ id TEXT PRIMARY KEY,
702
+ review_run_id TEXT NOT NULL,
703
+ ordinal INTEGER NOT NULL,
704
+ title TEXT NOT NULL,
705
+ severity TEXT NOT NULL,
706
+ file_path TEXT,
707
+ old_line_number INTEGER,
708
+ new_line_number INTEGER,
709
+ line_content TEXT,
710
+ hunk_header TEXT,
711
+ what_text TEXT NOT NULL,
712
+ why_text TEXT,
713
+ fix_text TEXT,
714
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
715
+ FOREIGN KEY (review_run_id) REFERENCES review_runs(id) ON DELETE CASCADE
716
+ )
717
+ `);
718
+ db.exec(`
719
+ CREATE TABLE IF NOT EXISTS bundle_pull_requests (
720
+ id TEXT PRIMARY KEY,
721
+ bundle_id TEXT NOT NULL,
722
+ project_id TEXT NOT NULL,
723
+ provider TEXT NOT NULL,
724
+ repo_host TEXT NOT NULL,
725
+ repo_owner TEXT NOT NULL,
726
+ repo_name TEXT NOT NULL,
727
+ pr_number INTEGER NOT NULL,
728
+ pr_url TEXT NOT NULL,
729
+ pr_title TEXT NOT NULL,
730
+ base_branch TEXT NOT NULL,
731
+ head_branch TEXT NOT NULL,
732
+ state TEXT NOT NULL,
733
+ is_draft INTEGER NOT NULL DEFAULT 0,
734
+ merged_at DATETIME,
735
+ closed_at DATETIME,
736
+ github_pr_node_id TEXT,
737
+ last_synced_at DATETIME,
738
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
739
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
740
+ FOREIGN KEY (bundle_id) REFERENCES worktree_bundles(id) ON DELETE CASCADE,
741
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
742
+ )
743
+ `);
744
+ db.exec('CREATE INDEX IF NOT EXISTS idx_ticket_dependencies_blocker_ticket_id ON ticket_dependencies(blocker_ticket_id)');
745
+ db.exec('CREATE INDEX IF NOT EXISTS idx_ticket_dependencies_dependent_ticket_id ON ticket_dependencies(dependent_ticket_id)');
746
+ db.exec('CREATE INDEX IF NOT EXISTS idx_ticket_messages_ticket_id_created_at ON ticket_messages(ticket_id, created_at)');
747
+ db.exec('CREATE INDEX IF NOT EXISTS idx_chat_sessions_project_updated_at ON chat_sessions(project_id, updated_at DESC)');
748
+ db.exec('CREATE INDEX IF NOT EXISTS idx_chat_messages_session_created_at ON chat_messages(chat_session_id, created_at)');
749
+ db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_ticket_stage_changes_ticket_stage ON ticket_stage_changes(ticket_id, stage)');
750
+ db.exec('CREATE INDEX IF NOT EXISTS idx_ticket_stage_changes_bundle_stage_success ON ticket_stage_changes(project_id, worktree_bundle_id, stage, success_revision)');
751
+ db.exec('CREATE INDEX IF NOT EXISTS idx_review_runs_ticket_created_at ON review_runs(ticket_id, created_at)');
752
+ db.exec('CREATE INDEX IF NOT EXISTS idx_review_runs_bundle_created_at ON review_runs(project_id, worktree_bundle_id, created_at)');
753
+ db.exec('CREATE INDEX IF NOT EXISTS idx_review_findings_review_run_ordinal ON review_findings(review_run_id, ordinal)');
754
+ db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_bundle_pull_requests_project_bundle_provider ON bundle_pull_requests(project_id, bundle_id, provider)');
755
+ db.exec('CREATE INDEX IF NOT EXISTS idx_bundle_pull_requests_project_state ON bundle_pull_requests(project_id, state)');
756
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_links_project_id ON project_links(project_id)');
757
+ db.exec('CREATE INDEX IF NOT EXISTS idx_project_links_linked_project_id ON project_links(linked_project_id)');
758
+ db.exec('CREATE INDEX IF NOT EXISTS idx_build_sequences_project_status ON build_sequences(project_id, status)');
759
+ db.exec('CREATE INDEX IF NOT EXISTS idx_build_sequences_bundle_status ON build_sequences(worktree_bundle_id, status)');
760
+ db.exec('CREATE INDEX IF NOT EXISTS idx_build_sequence_tickets_sequence_position ON build_sequence_tickets(build_sequence_id, position)');
761
+ db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_integration_connections_provider_project ON integration_connections(provider, project_id)');
762
+ db.exec('CREATE INDEX IF NOT EXISTS idx_tickets_external_key ON tickets(external_provider, external_key)');
763
+ export default db;
764
+ //# sourceMappingURL=db.js.map