patchrelay 0.36.7 → 0.36.9

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.
@@ -0,0 +1,559 @@
1
+ import { isoNow } from "./shared.js";
2
+ export class IssueStore {
3
+ connection;
4
+ syncIssueSessionFromIssue;
5
+ constructor(connection, syncIssueSessionFromIssue) {
6
+ this.connection = connection;
7
+ this.syncIssueSessionFromIssue = syncIssueSessionFromIssue;
8
+ }
9
+ upsertIssue(params) {
10
+ const now = isoNow();
11
+ const existing = this.getIssue(params.projectId, params.linearIssueId);
12
+ if (existing) {
13
+ const sets = ["updated_at = @now"];
14
+ const values = {
15
+ now,
16
+ projectId: params.projectId,
17
+ linearIssueId: params.linearIssueId,
18
+ };
19
+ if (params.issueKey !== undefined) {
20
+ sets.push("issue_key = COALESCE(@issueKey, issue_key)");
21
+ values.issueKey = params.issueKey;
22
+ }
23
+ if (params.title !== undefined) {
24
+ sets.push("title = COALESCE(@title, title)");
25
+ values.title = params.title;
26
+ }
27
+ if (params.description !== undefined) {
28
+ sets.push("description = COALESCE(@description, description)");
29
+ values.description = params.description;
30
+ }
31
+ if (params.url !== undefined) {
32
+ sets.push("url = COALESCE(@url, url)");
33
+ values.url = params.url;
34
+ }
35
+ if (params.priority !== undefined) {
36
+ sets.push("priority = @priority");
37
+ values.priority = params.priority;
38
+ }
39
+ if (params.estimate !== undefined) {
40
+ sets.push("estimate = @estimate");
41
+ values.estimate = params.estimate;
42
+ }
43
+ if (params.currentLinearState !== undefined) {
44
+ sets.push("current_linear_state = COALESCE(@currentLinearState, current_linear_state)");
45
+ values.currentLinearState = params.currentLinearState;
46
+ }
47
+ if (params.currentLinearStateType !== undefined) {
48
+ sets.push("current_linear_state_type = COALESCE(@currentLinearStateType, current_linear_state_type)");
49
+ values.currentLinearStateType = params.currentLinearStateType;
50
+ }
51
+ if (params.factoryState !== undefined) {
52
+ sets.push("factory_state = @factoryState");
53
+ values.factoryState = params.factoryState;
54
+ }
55
+ if (params.pendingRunType !== undefined) {
56
+ sets.push("pending_run_type = @pendingRunType");
57
+ values.pendingRunType = params.pendingRunType;
58
+ }
59
+ if (params.pendingRunContextJson !== undefined) {
60
+ sets.push("pending_run_context_json = @pendingRunContextJson");
61
+ values.pendingRunContextJson = params.pendingRunContextJson;
62
+ }
63
+ if (params.branchName !== undefined) {
64
+ sets.push("branch_name = COALESCE(@branchName, branch_name)");
65
+ values.branchName = params.branchName;
66
+ }
67
+ if (params.worktreePath !== undefined) {
68
+ sets.push("worktree_path = COALESCE(@worktreePath, worktree_path)");
69
+ values.worktreePath = params.worktreePath;
70
+ }
71
+ if (params.threadId !== undefined) {
72
+ sets.push("thread_id = @threadId");
73
+ values.threadId = params.threadId;
74
+ }
75
+ if (params.activeRunId !== undefined) {
76
+ sets.push("active_run_id = @activeRunId");
77
+ values.activeRunId = params.activeRunId;
78
+ }
79
+ if (params.statusCommentId !== undefined) {
80
+ sets.push("status_comment_id = @statusCommentId");
81
+ values.statusCommentId = params.statusCommentId;
82
+ }
83
+ if (params.agentSessionId !== undefined) {
84
+ sets.push("agent_session_id = @agentSessionId");
85
+ values.agentSessionId = params.agentSessionId;
86
+ }
87
+ if (params.prNumber !== undefined) {
88
+ sets.push("pr_number = @prNumber");
89
+ values.prNumber = params.prNumber;
90
+ }
91
+ if (params.prUrl !== undefined) {
92
+ sets.push("pr_url = @prUrl");
93
+ values.prUrl = params.prUrl;
94
+ }
95
+ if (params.prState !== undefined) {
96
+ sets.push("pr_state = @prState");
97
+ values.prState = params.prState;
98
+ }
99
+ if (params.prHeadSha !== undefined) {
100
+ sets.push("pr_head_sha = @prHeadSha");
101
+ values.prHeadSha = params.prHeadSha;
102
+ }
103
+ if (params.prAuthorLogin !== undefined) {
104
+ sets.push("pr_author_login = @prAuthorLogin");
105
+ values.prAuthorLogin = params.prAuthorLogin;
106
+ }
107
+ if (params.prReviewState !== undefined) {
108
+ sets.push("pr_review_state = @prReviewState");
109
+ values.prReviewState = params.prReviewState;
110
+ }
111
+ if (params.prCheckStatus !== undefined) {
112
+ sets.push("pr_check_status = @prCheckStatus");
113
+ values.prCheckStatus = params.prCheckStatus;
114
+ }
115
+ if (params.lastBlockingReviewHeadSha !== undefined) {
116
+ sets.push("last_blocking_review_head_sha = @lastBlockingReviewHeadSha");
117
+ values.lastBlockingReviewHeadSha = params.lastBlockingReviewHeadSha;
118
+ }
119
+ if (params.lastGitHubFailureSource !== undefined) {
120
+ sets.push("last_github_failure_source = @lastGitHubFailureSource");
121
+ values.lastGitHubFailureSource = params.lastGitHubFailureSource;
122
+ }
123
+ if (params.lastGitHubFailureHeadSha !== undefined) {
124
+ sets.push("last_github_failure_head_sha = @lastGitHubFailureHeadSha");
125
+ values.lastGitHubFailureHeadSha = params.lastGitHubFailureHeadSha;
126
+ }
127
+ if (params.lastGitHubFailureSignature !== undefined) {
128
+ sets.push("last_github_failure_signature = @lastGitHubFailureSignature");
129
+ values.lastGitHubFailureSignature = params.lastGitHubFailureSignature;
130
+ }
131
+ if (params.lastGitHubFailureCheckName !== undefined) {
132
+ sets.push("last_github_failure_check_name = @lastGitHubFailureCheckName");
133
+ values.lastGitHubFailureCheckName = params.lastGitHubFailureCheckName;
134
+ }
135
+ if (params.lastGitHubFailureCheckUrl !== undefined) {
136
+ sets.push("last_github_failure_check_url = @lastGitHubFailureCheckUrl");
137
+ values.lastGitHubFailureCheckUrl = params.lastGitHubFailureCheckUrl;
138
+ }
139
+ if (params.lastGitHubFailureContextJson !== undefined) {
140
+ sets.push("last_github_failure_context_json = @lastGitHubFailureContextJson");
141
+ values.lastGitHubFailureContextJson = params.lastGitHubFailureContextJson;
142
+ }
143
+ if (params.lastGitHubFailureAt !== undefined) {
144
+ sets.push("last_github_failure_at = @lastGitHubFailureAt");
145
+ values.lastGitHubFailureAt = params.lastGitHubFailureAt;
146
+ }
147
+ if (params.lastGitHubCiSnapshotHeadSha !== undefined) {
148
+ sets.push("last_github_ci_snapshot_head_sha = @lastGitHubCiSnapshotHeadSha");
149
+ values.lastGitHubCiSnapshotHeadSha = params.lastGitHubCiSnapshotHeadSha;
150
+ }
151
+ if (params.lastGitHubCiSnapshotGateCheckName !== undefined) {
152
+ sets.push("last_github_ci_snapshot_gate_check_name = @lastGitHubCiSnapshotGateCheckName");
153
+ values.lastGitHubCiSnapshotGateCheckName = params.lastGitHubCiSnapshotGateCheckName;
154
+ }
155
+ if (params.lastGitHubCiSnapshotGateCheckStatus !== undefined) {
156
+ sets.push("last_github_ci_snapshot_gate_check_status = @lastGitHubCiSnapshotGateCheckStatus");
157
+ values.lastGitHubCiSnapshotGateCheckStatus = params.lastGitHubCiSnapshotGateCheckStatus;
158
+ }
159
+ if (params.lastGitHubCiSnapshotJson !== undefined) {
160
+ sets.push("last_github_ci_snapshot_json = @lastGitHubCiSnapshotJson");
161
+ values.lastGitHubCiSnapshotJson = params.lastGitHubCiSnapshotJson;
162
+ }
163
+ if (params.lastGitHubCiSnapshotSettledAt !== undefined) {
164
+ sets.push("last_github_ci_snapshot_settled_at = @lastGitHubCiSnapshotSettledAt");
165
+ values.lastGitHubCiSnapshotSettledAt = params.lastGitHubCiSnapshotSettledAt;
166
+ }
167
+ if (params.lastQueueSignalAt !== undefined) {
168
+ sets.push("last_queue_signal_at = @lastQueueSignalAt");
169
+ values.lastQueueSignalAt = params.lastQueueSignalAt;
170
+ }
171
+ if (params.lastQueueIncidentJson !== undefined) {
172
+ sets.push("last_queue_incident_json = @lastQueueIncidentJson");
173
+ values.lastQueueIncidentJson = params.lastQueueIncidentJson;
174
+ }
175
+ if (params.lastAttemptedFailureHeadSha !== undefined) {
176
+ sets.push("last_attempted_failure_head_sha = @lastAttemptedFailureHeadSha");
177
+ values.lastAttemptedFailureHeadSha = params.lastAttemptedFailureHeadSha;
178
+ }
179
+ if (params.lastAttemptedFailureSignature !== undefined) {
180
+ sets.push("last_attempted_failure_signature = @lastAttemptedFailureSignature");
181
+ values.lastAttemptedFailureSignature = params.lastAttemptedFailureSignature;
182
+ }
183
+ if (params.ciRepairAttempts !== undefined) {
184
+ sets.push("ci_repair_attempts = @ciRepairAttempts");
185
+ values.ciRepairAttempts = params.ciRepairAttempts;
186
+ }
187
+ if (params.queueRepairAttempts !== undefined) {
188
+ sets.push("queue_repair_attempts = @queueRepairAttempts");
189
+ values.queueRepairAttempts = params.queueRepairAttempts;
190
+ }
191
+ if (params.reviewFixAttempts !== undefined) {
192
+ sets.push("review_fix_attempts = @reviewFixAttempts");
193
+ values.reviewFixAttempts = params.reviewFixAttempts;
194
+ }
195
+ if (params.zombieRecoveryAttempts !== undefined) {
196
+ sets.push("zombie_recovery_attempts = @zombieRecoveryAttempts");
197
+ values.zombieRecoveryAttempts = params.zombieRecoveryAttempts;
198
+ }
199
+ if (params.lastZombieRecoveryAt !== undefined) {
200
+ sets.push("last_zombie_recovery_at = @lastZombieRecoveryAt");
201
+ values.lastZombieRecoveryAt = params.lastZombieRecoveryAt;
202
+ }
203
+ this.connection.prepare(`UPDATE issues SET ${sets.join(", ")} WHERE project_id = @projectId AND linear_issue_id = @linearIssueId`).run(values);
204
+ }
205
+ else {
206
+ this.connection.prepare(`
207
+ INSERT INTO issues (
208
+ project_id, linear_issue_id, issue_key, title, description, url,
209
+ priority, estimate,
210
+ current_linear_state, current_linear_state_type, factory_state, pending_run_type, pending_run_context_json,
211
+ branch_name, worktree_path, thread_id, active_run_id, status_comment_id,
212
+ agent_session_id,
213
+ pr_number, pr_url, pr_state, pr_head_sha, pr_author_login, pr_review_state, pr_check_status, last_blocking_review_head_sha,
214
+ last_github_failure_source, last_github_failure_head_sha, last_github_failure_signature, last_github_failure_check_name, last_github_failure_check_url, last_github_failure_context_json, last_github_failure_at,
215
+ last_github_ci_snapshot_head_sha, last_github_ci_snapshot_gate_check_name, last_github_ci_snapshot_gate_check_status, last_github_ci_snapshot_json, last_github_ci_snapshot_settled_at,
216
+ last_queue_signal_at, last_queue_incident_json,
217
+ last_attempted_failure_head_sha, last_attempted_failure_signature,
218
+ ci_repair_attempts, queue_repair_attempts, review_fix_attempts, zombie_recovery_attempts, last_zombie_recovery_at,
219
+ updated_at
220
+ ) VALUES (
221
+ @projectId, @linearIssueId, @issueKey, @title, @description, @url,
222
+ @priority, @estimate,
223
+ @currentLinearState, @currentLinearStateType, @factoryState, @pendingRunType, @pendingRunContextJson,
224
+ @branchName, @worktreePath, @threadId, @activeRunId, @statusCommentId,
225
+ @agentSessionId,
226
+ @prNumber, @prUrl, @prState, @prHeadSha, @prAuthorLogin, @prReviewState, @prCheckStatus, @lastBlockingReviewHeadSha,
227
+ @lastGitHubFailureSource, @lastGitHubFailureHeadSha, @lastGitHubFailureSignature, @lastGitHubFailureCheckName, @lastGitHubFailureCheckUrl, @lastGitHubFailureContextJson, @lastGitHubFailureAt,
228
+ @lastGitHubCiSnapshotHeadSha, @lastGitHubCiSnapshotGateCheckName, @lastGitHubCiSnapshotGateCheckStatus, @lastGitHubCiSnapshotJson, @lastGitHubCiSnapshotSettledAt,
229
+ @lastQueueSignalAt, @lastQueueIncidentJson,
230
+ @lastAttemptedFailureHeadSha, @lastAttemptedFailureSignature,
231
+ @ciRepairAttempts, @queueRepairAttempts, @reviewFixAttempts, @zombieRecoveryAttempts, @lastZombieRecoveryAt,
232
+ @now
233
+ )
234
+ `).run({
235
+ projectId: params.projectId,
236
+ linearIssueId: params.linearIssueId,
237
+ issueKey: params.issueKey ?? null,
238
+ title: params.title ?? null,
239
+ description: params.description ?? null,
240
+ url: params.url ?? null,
241
+ priority: params.priority ?? null,
242
+ estimate: params.estimate ?? null,
243
+ currentLinearState: params.currentLinearState ?? null,
244
+ currentLinearStateType: params.currentLinearStateType ?? null,
245
+ factoryState: params.factoryState ?? "delegated",
246
+ pendingRunType: params.pendingRunType ?? null,
247
+ pendingRunContextJson: params.pendingRunContextJson ?? null,
248
+ branchName: params.branchName ?? null,
249
+ worktreePath: params.worktreePath ?? null,
250
+ threadId: params.threadId ?? null,
251
+ activeRunId: params.activeRunId ?? null,
252
+ statusCommentId: params.statusCommentId ?? null,
253
+ agentSessionId: params.agentSessionId ?? null,
254
+ prNumber: params.prNumber ?? null,
255
+ prUrl: params.prUrl ?? null,
256
+ prState: params.prState ?? null,
257
+ prHeadSha: params.prHeadSha ?? null,
258
+ prAuthorLogin: params.prAuthorLogin ?? null,
259
+ prReviewState: params.prReviewState ?? null,
260
+ prCheckStatus: params.prCheckStatus ?? null,
261
+ lastBlockingReviewHeadSha: params.lastBlockingReviewHeadSha ?? null,
262
+ lastGitHubFailureSource: params.lastGitHubFailureSource ?? null,
263
+ lastGitHubFailureHeadSha: params.lastGitHubFailureHeadSha ?? null,
264
+ lastGitHubFailureSignature: params.lastGitHubFailureSignature ?? null,
265
+ lastGitHubFailureCheckName: params.lastGitHubFailureCheckName ?? null,
266
+ lastGitHubFailureCheckUrl: params.lastGitHubFailureCheckUrl ?? null,
267
+ lastGitHubFailureContextJson: params.lastGitHubFailureContextJson ?? null,
268
+ lastGitHubFailureAt: params.lastGitHubFailureAt ?? null,
269
+ lastGitHubCiSnapshotHeadSha: params.lastGitHubCiSnapshotHeadSha ?? null,
270
+ lastGitHubCiSnapshotGateCheckName: params.lastGitHubCiSnapshotGateCheckName ?? null,
271
+ lastGitHubCiSnapshotGateCheckStatus: params.lastGitHubCiSnapshotGateCheckStatus ?? null,
272
+ lastGitHubCiSnapshotJson: params.lastGitHubCiSnapshotJson ?? null,
273
+ lastGitHubCiSnapshotSettledAt: params.lastGitHubCiSnapshotSettledAt ?? null,
274
+ lastQueueSignalAt: params.lastQueueSignalAt ?? null,
275
+ lastQueueIncidentJson: params.lastQueueIncidentJson ?? null,
276
+ lastAttemptedFailureHeadSha: params.lastAttemptedFailureHeadSha ?? null,
277
+ lastAttemptedFailureSignature: params.lastAttemptedFailureSignature ?? null,
278
+ ciRepairAttempts: params.ciRepairAttempts ?? 0,
279
+ queueRepairAttempts: params.queueRepairAttempts ?? 0,
280
+ reviewFixAttempts: params.reviewFixAttempts ?? 0,
281
+ zombieRecoveryAttempts: params.zombieRecoveryAttempts ?? 0,
282
+ lastZombieRecoveryAt: params.lastZombieRecoveryAt ?? null,
283
+ now,
284
+ });
285
+ }
286
+ const updated = this.getIssue(params.projectId, params.linearIssueId);
287
+ this.syncIssueSessionFromIssue(updated);
288
+ return updated;
289
+ }
290
+ getIssue(projectId, linearIssueId) {
291
+ const row = this.connection
292
+ .prepare("SELECT * FROM issues WHERE project_id = ? AND linear_issue_id = ?")
293
+ .get(projectId, linearIssueId);
294
+ return row ? mapIssueRow(row) : undefined;
295
+ }
296
+ getIssueById(id) {
297
+ const row = this.connection.prepare("SELECT * FROM issues WHERE id = ?").get(id);
298
+ return row ? mapIssueRow(row) : undefined;
299
+ }
300
+ getIssueByKey(issueKey) {
301
+ const row = this.connection.prepare("SELECT * FROM issues WHERE issue_key = ?").get(issueKey);
302
+ return row ? mapIssueRow(row) : undefined;
303
+ }
304
+ getIssueByBranch(branchName) {
305
+ const row = this.connection.prepare("SELECT * FROM issues WHERE branch_name = ?").get(branchName);
306
+ return row ? mapIssueRow(row) : undefined;
307
+ }
308
+ getIssueByPrNumber(prNumber) {
309
+ const row = this.connection.prepare("SELECT * FROM issues WHERE pr_number = ?").get(prNumber);
310
+ return row ? mapIssueRow(row) : undefined;
311
+ }
312
+ listIssues() {
313
+ const rows = this.connection
314
+ .prepare("SELECT * FROM issues ORDER BY updated_at DESC")
315
+ .all();
316
+ return rows.map(mapIssueRow);
317
+ }
318
+ listIssuesWithAgentSessions() {
319
+ const rows = this.connection
320
+ .prepare("SELECT * FROM issues WHERE agent_session_id IS NOT NULL ORDER BY updated_at DESC")
321
+ .all();
322
+ return rows.map(mapIssueRow);
323
+ }
324
+ listIssuesByState(projectId, state) {
325
+ const rows = this.connection
326
+ .prepare("SELECT * FROM issues WHERE project_id = ? AND factory_state = ? ORDER BY pr_number ASC")
327
+ .all(projectId, state);
328
+ return rows.map(mapIssueRow);
329
+ }
330
+ listIdleNonTerminalIssues() {
331
+ const rows = this.connection
332
+ .prepare(`SELECT * FROM issues
333
+ WHERE factory_state NOT IN ('done', 'escalated', 'failed', 'awaiting_input')
334
+ AND active_run_id IS NULL
335
+ AND pending_run_type IS NULL
336
+ AND pr_number IS NOT NULL`)
337
+ .all();
338
+ return rows.map(mapIssueRow);
339
+ }
340
+ listBlockedDelegatedIssues() {
341
+ const rows = this.connection
342
+ .prepare(`SELECT DISTINCT i.* FROM issues i
343
+ JOIN issue_dependencies d ON d.project_id = i.project_id AND d.linear_issue_id = i.linear_issue_id
344
+ WHERE i.factory_state = 'delegated'
345
+ AND i.active_run_id IS NULL
346
+ AND i.pending_run_type IS NULL`)
347
+ .all();
348
+ return rows.map(mapIssueRow);
349
+ }
350
+ listAwaitingQueueIssues() {
351
+ const rows = this.connection
352
+ .prepare(`SELECT * FROM issues
353
+ WHERE factory_state = 'awaiting_queue'
354
+ AND active_run_id IS NULL
355
+ AND pending_run_type IS NULL
356
+ AND pr_number IS NOT NULL`)
357
+ .all();
358
+ return rows.map(mapIssueRow);
359
+ }
360
+ setBranchOwner(projectId, linearIssueId, owner) {
361
+ const now = isoNow();
362
+ this.connection.prepare(`
363
+ UPDATE issues
364
+ SET branch_owner = ?, branch_ownership_changed_at = ?, updated_at = ?
365
+ WHERE project_id = ? AND linear_issue_id = ?
366
+ `).run(owner, now, now, projectId, linearIssueId);
367
+ }
368
+ replaceIssueDependencies(params) {
369
+ const now = isoNow();
370
+ this.connection
371
+ .prepare("DELETE FROM issue_dependencies WHERE project_id = ? AND linear_issue_id = ?")
372
+ .run(params.projectId, params.linearIssueId);
373
+ if (params.blockers.length === 0) {
374
+ return;
375
+ }
376
+ const insert = this.connection.prepare(`
377
+ INSERT INTO issue_dependencies (
378
+ project_id,
379
+ linear_issue_id,
380
+ blocker_linear_issue_id,
381
+ blocker_issue_key,
382
+ blocker_title,
383
+ blocker_current_linear_state,
384
+ blocker_current_linear_state_type,
385
+ updated_at
386
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
387
+ `);
388
+ for (const blocker of params.blockers) {
389
+ insert.run(params.projectId, params.linearIssueId, blocker.blockerLinearIssueId, blocker.blockerIssueKey ?? null, blocker.blockerTitle ?? null, blocker.blockerCurrentLinearState ?? null, blocker.blockerCurrentLinearStateType ?? null, now);
390
+ }
391
+ }
392
+ listIssueDependencies(projectId, linearIssueId) {
393
+ const rows = this.connection.prepare(`
394
+ SELECT
395
+ d.project_id,
396
+ d.linear_issue_id,
397
+ d.blocker_linear_issue_id,
398
+ COALESCE(blockers.issue_key, d.blocker_issue_key) AS blocker_issue_key,
399
+ COALESCE(blockers.title, d.blocker_title) AS blocker_title,
400
+ COALESCE(blockers.current_linear_state, d.blocker_current_linear_state) AS blocker_current_linear_state,
401
+ COALESCE(blockers.current_linear_state_type, d.blocker_current_linear_state_type) AS blocker_current_linear_state_type,
402
+ d.updated_at
403
+ FROM issue_dependencies d
404
+ LEFT JOIN issues blockers
405
+ ON blockers.project_id = d.project_id
406
+ AND blockers.linear_issue_id = d.blocker_linear_issue_id
407
+ WHERE d.project_id = ? AND d.linear_issue_id = ?
408
+ ORDER BY COALESCE(blockers.issue_key, d.blocker_issue_key, d.blocker_linear_issue_id) ASC
409
+ `).all(projectId, linearIssueId);
410
+ return rows.map((row) => ({
411
+ projectId: String(row.project_id),
412
+ linearIssueId: String(row.linear_issue_id),
413
+ blockerLinearIssueId: String(row.blocker_linear_issue_id),
414
+ ...(row.blocker_issue_key !== null && row.blocker_issue_key !== undefined ? { blockerIssueKey: String(row.blocker_issue_key) } : {}),
415
+ ...(row.blocker_title !== null && row.blocker_title !== undefined ? { blockerTitle: String(row.blocker_title) } : {}),
416
+ ...(row.blocker_current_linear_state !== null && row.blocker_current_linear_state !== undefined
417
+ ? { blockerCurrentLinearState: String(row.blocker_current_linear_state) }
418
+ : {}),
419
+ ...(row.blocker_current_linear_state_type !== null && row.blocker_current_linear_state_type !== undefined
420
+ ? { blockerCurrentLinearStateType: String(row.blocker_current_linear_state_type) }
421
+ : {}),
422
+ updatedAt: String(row.updated_at),
423
+ }));
424
+ }
425
+ listDependents(projectId, blockerLinearIssueId) {
426
+ const rows = this.connection.prepare(`
427
+ SELECT project_id, linear_issue_id
428
+ FROM issue_dependencies
429
+ WHERE project_id = ? AND blocker_linear_issue_id = ?
430
+ ORDER BY linear_issue_id ASC
431
+ `).all(projectId, blockerLinearIssueId);
432
+ return rows.map((row) => ({
433
+ projectId: String(row.project_id),
434
+ linearIssueId: String(row.linear_issue_id),
435
+ }));
436
+ }
437
+ countUnresolvedBlockers(projectId, linearIssueId) {
438
+ const row = this.connection.prepare(`
439
+ SELECT COUNT(*) AS count
440
+ FROM issue_dependencies d
441
+ LEFT JOIN issues blockers
442
+ ON blockers.project_id = d.project_id
443
+ AND blockers.linear_issue_id = d.blocker_linear_issue_id
444
+ WHERE d.project_id = ? AND d.linear_issue_id = ?
445
+ AND (
446
+ COALESCE(blockers.current_linear_state_type, d.blocker_current_linear_state_type, '') != 'completed'
447
+ AND LOWER(TRIM(COALESCE(blockers.current_linear_state, d.blocker_current_linear_state, ''))) != 'done'
448
+ )
449
+ `).get(projectId, linearIssueId);
450
+ return Number(row?.count ?? 0);
451
+ }
452
+ getLatestGitHubCiSnapshot(projectId, linearIssueId) {
453
+ const issue = this.getIssue(projectId, linearIssueId);
454
+ if (!issue?.lastGitHubCiSnapshotJson)
455
+ return undefined;
456
+ try {
457
+ return JSON.parse(issue.lastGitHubCiSnapshotJson);
458
+ }
459
+ catch {
460
+ return undefined;
461
+ }
462
+ }
463
+ }
464
+ export function mapIssueRow(row) {
465
+ return {
466
+ id: Number(row.id),
467
+ projectId: String(row.project_id),
468
+ linearIssueId: String(row.linear_issue_id),
469
+ ...(row.issue_key !== null ? { issueKey: String(row.issue_key) } : {}),
470
+ ...(row.title !== null ? { title: String(row.title) } : {}),
471
+ ...(row.description !== null && row.description !== undefined ? { description: String(row.description) } : {}),
472
+ ...(row.url !== null ? { url: String(row.url) } : {}),
473
+ ...(row.priority !== null && row.priority !== undefined ? { priority: Number(row.priority) } : {}),
474
+ ...(row.estimate !== null && row.estimate !== undefined ? { estimate: Number(row.estimate) } : {}),
475
+ ...(row.current_linear_state !== null ? { currentLinearState: String(row.current_linear_state) } : {}),
476
+ ...(row.current_linear_state_type !== null && row.current_linear_state_type !== undefined
477
+ ? { currentLinearStateType: String(row.current_linear_state_type) }
478
+ : {}),
479
+ factoryState: String(row.factory_state ?? "delegated"),
480
+ ...(row.pending_run_type !== null && row.pending_run_type !== undefined ? { pendingRunType: String(row.pending_run_type) } : {}),
481
+ ...(row.pending_run_context_json !== null && row.pending_run_context_json !== undefined ? { pendingRunContextJson: String(row.pending_run_context_json) } : {}),
482
+ ...(row.branch_name !== null ? { branchName: String(row.branch_name) } : {}),
483
+ ...(row.branch_owner !== null && row.branch_owner !== undefined && String(row.branch_owner) === "patchrelay"
484
+ ? { branchOwner: "patchrelay" }
485
+ : { branchOwner: "patchrelay" }),
486
+ ...(row.branch_ownership_changed_at !== null && row.branch_ownership_changed_at !== undefined
487
+ ? { branchOwnershipChangedAt: String(row.branch_ownership_changed_at) }
488
+ : {}),
489
+ ...(row.worktree_path !== null ? { worktreePath: String(row.worktree_path) } : {}),
490
+ ...(row.thread_id !== null ? { threadId: String(row.thread_id) } : {}),
491
+ ...(row.active_run_id !== null ? { activeRunId: Number(row.active_run_id) } : {}),
492
+ ...(row.status_comment_id !== null && row.status_comment_id !== undefined ? { statusCommentId: String(row.status_comment_id) } : {}),
493
+ ...(row.agent_session_id !== null ? { agentSessionId: String(row.agent_session_id) } : {}),
494
+ updatedAt: String(row.updated_at),
495
+ ...(row.pr_number !== null && row.pr_number !== undefined ? { prNumber: Number(row.pr_number) } : {}),
496
+ ...(row.pr_url !== null && row.pr_url !== undefined ? { prUrl: String(row.pr_url) } : {}),
497
+ ...(row.pr_state !== null && row.pr_state !== undefined ? { prState: String(row.pr_state) } : {}),
498
+ ...(row.pr_head_sha !== null && row.pr_head_sha !== undefined ? { prHeadSha: String(row.pr_head_sha) } : {}),
499
+ ...(row.pr_author_login !== null && row.pr_author_login !== undefined ? { prAuthorLogin: String(row.pr_author_login) } : {}),
500
+ ...(row.pr_review_state !== null && row.pr_review_state !== undefined ? { prReviewState: String(row.pr_review_state) } : {}),
501
+ ...(row.pr_check_status !== null && row.pr_check_status !== undefined ? { prCheckStatus: String(row.pr_check_status) } : {}),
502
+ ...(row.last_blocking_review_head_sha !== null && row.last_blocking_review_head_sha !== undefined
503
+ ? { lastBlockingReviewHeadSha: String(row.last_blocking_review_head_sha) }
504
+ : {}),
505
+ ...(row.last_github_failure_source !== null && row.last_github_failure_source !== undefined
506
+ ? { lastGitHubFailureSource: String(row.last_github_failure_source) }
507
+ : {}),
508
+ ...(row.last_github_failure_head_sha !== null && row.last_github_failure_head_sha !== undefined
509
+ ? { lastGitHubFailureHeadSha: String(row.last_github_failure_head_sha) }
510
+ : {}),
511
+ ...(row.last_github_failure_signature !== null && row.last_github_failure_signature !== undefined
512
+ ? { lastGitHubFailureSignature: String(row.last_github_failure_signature) }
513
+ : {}),
514
+ ...(row.last_github_failure_check_name !== null && row.last_github_failure_check_name !== undefined
515
+ ? { lastGitHubFailureCheckName: String(row.last_github_failure_check_name) }
516
+ : {}),
517
+ ...(row.last_github_failure_check_url !== null && row.last_github_failure_check_url !== undefined
518
+ ? { lastGitHubFailureCheckUrl: String(row.last_github_failure_check_url) }
519
+ : {}),
520
+ ...(row.last_github_failure_context_json !== null && row.last_github_failure_context_json !== undefined
521
+ ? { lastGitHubFailureContextJson: String(row.last_github_failure_context_json) }
522
+ : {}),
523
+ ...(row.last_github_failure_at !== null && row.last_github_failure_at !== undefined
524
+ ? { lastGitHubFailureAt: String(row.last_github_failure_at) }
525
+ : {}),
526
+ ...(row.last_github_ci_snapshot_head_sha !== null && row.last_github_ci_snapshot_head_sha !== undefined
527
+ ? { lastGitHubCiSnapshotHeadSha: String(row.last_github_ci_snapshot_head_sha) }
528
+ : {}),
529
+ ...(row.last_github_ci_snapshot_gate_check_name !== null && row.last_github_ci_snapshot_gate_check_name !== undefined
530
+ ? { lastGitHubCiSnapshotGateCheckName: String(row.last_github_ci_snapshot_gate_check_name) }
531
+ : {}),
532
+ ...(row.last_github_ci_snapshot_gate_check_status !== null && row.last_github_ci_snapshot_gate_check_status !== undefined
533
+ ? { lastGitHubCiSnapshotGateCheckStatus: String(row.last_github_ci_snapshot_gate_check_status) }
534
+ : {}),
535
+ ...(row.last_github_ci_snapshot_json !== null && row.last_github_ci_snapshot_json !== undefined
536
+ ? { lastGitHubCiSnapshotJson: String(row.last_github_ci_snapshot_json) }
537
+ : {}),
538
+ ...(row.last_github_ci_snapshot_settled_at !== null && row.last_github_ci_snapshot_settled_at !== undefined
539
+ ? { lastGitHubCiSnapshotSettledAt: String(row.last_github_ci_snapshot_settled_at) }
540
+ : {}),
541
+ ...(row.last_queue_signal_at !== null && row.last_queue_signal_at !== undefined
542
+ ? { lastQueueSignalAt: String(row.last_queue_signal_at) }
543
+ : {}),
544
+ ...(row.last_queue_incident_json !== null && row.last_queue_incident_json !== undefined
545
+ ? { lastQueueIncidentJson: String(row.last_queue_incident_json) }
546
+ : {}),
547
+ ...(row.last_attempted_failure_head_sha !== null && row.last_attempted_failure_head_sha !== undefined
548
+ ? { lastAttemptedFailureHeadSha: String(row.last_attempted_failure_head_sha) }
549
+ : {}),
550
+ ...(row.last_attempted_failure_signature !== null && row.last_attempted_failure_signature !== undefined
551
+ ? { lastAttemptedFailureSignature: String(row.last_attempted_failure_signature) }
552
+ : {}),
553
+ ciRepairAttempts: Number(row.ci_repair_attempts ?? 0),
554
+ queueRepairAttempts: Number(row.queue_repair_attempts ?? 0),
555
+ reviewFixAttempts: Number(row.review_fix_attempts ?? 0),
556
+ zombieRecoveryAttempts: Number(row.zombie_recovery_attempts ?? 0),
557
+ ...(row.last_zombie_recovery_at !== null && row.last_zombie_recovery_at !== undefined ? { lastZombieRecoveryAt: String(row.last_zombie_recovery_at) } : {}),
558
+ };
559
+ }