@andypai/agent-kanban 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -22
- package/package.json +4 -2
- package/src/__tests__/activity.test.ts +15 -9
- package/src/__tests__/api.test.ts +96 -0
- package/src/__tests__/board-utils.test.ts +100 -0
- package/src/__tests__/commands/board.test.ts +6 -13
- package/src/__tests__/conflict.test.ts +64 -0
- package/src/__tests__/index.test.ts +233 -56
- package/src/__tests__/jira-adf.test.ts +168 -0
- package/src/__tests__/jira-cache.test.ts +304 -0
- package/src/__tests__/jira-client.test.ts +169 -0
- package/src/__tests__/jira-provider-comment.test.ts +281 -0
- package/src/__tests__/jira-provider-mutations.test.ts +771 -0
- package/src/__tests__/jira-provider-read.test.ts +594 -0
- package/src/__tests__/jira-wiring.test.ts +187 -0
- package/src/__tests__/linear-cache-description-activity.test.ts +142 -0
- package/src/__tests__/linear-provider-comment.test.ts +243 -0
- package/src/__tests__/linear-provider-sync.test.ts +493 -0
- package/src/__tests__/local-provider-comment.test.ts +60 -0
- package/src/__tests__/mcp-core.test.ts +164 -0
- package/src/__tests__/mcp-server.test.ts +252 -0
- package/src/__tests__/server.test.ts +298 -0
- package/src/__tests__/webhooks.test.ts +604 -0
- package/src/activity.ts +1 -11
- package/src/api.ts +154 -19
- package/src/commands/board.ts +1 -11
- package/src/commands/mcp.ts +87 -0
- package/src/db.ts +115 -3
- package/src/errors.ts +2 -0
- package/src/id.ts +1 -1
- package/src/index.ts +72 -18
- package/src/mcp/core.ts +193 -0
- package/src/mcp/errors.ts +109 -0
- package/src/mcp/index.ts +13 -0
- package/src/mcp/server.ts +512 -0
- package/src/mcp/types.ts +72 -0
- package/src/providers/capabilities.ts +15 -0
- package/src/providers/index.ts +31 -1
- package/src/providers/jira-adf.ts +275 -0
- package/src/providers/jira-cache.ts +625 -0
- package/src/providers/jira-client.ts +390 -0
- package/src/providers/jira.ts +778 -0
- package/src/providers/linear-cache.ts +249 -70
- package/src/providers/linear-client.ts +256 -13
- package/src/providers/linear.ts +337 -14
- package/src/providers/local.ts +68 -17
- package/src/providers/types.ts +18 -2
- package/src/server.ts +139 -11
- package/src/tunnel.ts +79 -0
- package/src/types.ts +18 -2
- package/src/webhooks.ts +36 -0
- package/ui/dist/assets/index-DBnoKL_k.css +1 -0
- package/ui/dist/assets/index-qNVJ6clH.js +40 -0
- package/ui/dist/index.html +2 -2
- package/src/__tests__/commands/task.test.ts +0 -144
- package/src/commands/task.ts +0 -117
- package/src/fixtures.ts +0 -128
- package/ui/dist/assets/index-B8f9NB4z.css +0 -1
- package/ui/dist/assets/index-zWp-rB7b.js +0 -40
|
@@ -14,7 +14,9 @@ export interface LinearStateRow {
|
|
|
14
14
|
export interface LinearSyncMeta {
|
|
15
15
|
team: ProviderTeamInfo | null
|
|
16
16
|
lastSyncAt: string | null
|
|
17
|
+
lastFullSyncAt: string | null
|
|
17
18
|
lastIssueUpdatedAt: string | null
|
|
19
|
+
lastWebhookAt: string | null
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export function initLinearCacheSchema(db: Database): void {
|
|
@@ -66,6 +68,8 @@ export function initLinearCacheSchema(db: Database): void {
|
|
|
66
68
|
state_id TEXT NOT NULL,
|
|
67
69
|
state_name TEXT NOT NULL,
|
|
68
70
|
state_position INTEGER NOT NULL DEFAULT 0,
|
|
71
|
+
labels TEXT NOT NULL DEFAULT '[]',
|
|
72
|
+
comment_count INTEGER NOT NULL DEFAULT 0,
|
|
69
73
|
url TEXT,
|
|
70
74
|
created_at TEXT NOT NULL,
|
|
71
75
|
updated_at TEXT NOT NULL
|
|
@@ -73,6 +77,81 @@ export function initLinearCacheSchema(db: Database): void {
|
|
|
73
77
|
`)
|
|
74
78
|
db.run('CREATE INDEX IF NOT EXISTS idx_linear_issues_state_id ON linear_issues(state_id)')
|
|
75
79
|
db.run('CREATE INDEX IF NOT EXISTS idx_linear_issues_updated_at ON linear_issues(updated_at)')
|
|
80
|
+
db.run(`
|
|
81
|
+
CREATE TABLE IF NOT EXISTS linear_activity (
|
|
82
|
+
issue_id TEXT NOT NULL,
|
|
83
|
+
history_id TEXT NOT NULL,
|
|
84
|
+
item_field TEXT NOT NULL,
|
|
85
|
+
from_value TEXT,
|
|
86
|
+
to_value TEXT,
|
|
87
|
+
created_at TEXT NOT NULL,
|
|
88
|
+
PRIMARY KEY (issue_id, history_id, item_field)
|
|
89
|
+
)
|
|
90
|
+
`)
|
|
91
|
+
db.run(
|
|
92
|
+
'CREATE INDEX IF NOT EXISTS linear_activity_created_at_idx ON linear_activity(created_at DESC)',
|
|
93
|
+
)
|
|
94
|
+
migrateLinearCacheSchema(db)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface LinearActivityRow {
|
|
98
|
+
issue_id: string
|
|
99
|
+
history_id: string
|
|
100
|
+
item_field: string
|
|
101
|
+
from_value: string | null
|
|
102
|
+
to_value: string | null
|
|
103
|
+
created_at: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function saveLinearActivity(db: Database, rows: LinearActivityRow[]): void {
|
|
107
|
+
if (rows.length === 0) return
|
|
108
|
+
const stmt = db.prepare(
|
|
109
|
+
`INSERT OR IGNORE INTO linear_activity
|
|
110
|
+
(issue_id, history_id, item_field, from_value, to_value, created_at)
|
|
111
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
112
|
+
)
|
|
113
|
+
const tx = db.transaction((items: LinearActivityRow[]) => {
|
|
114
|
+
for (const r of items) {
|
|
115
|
+
stmt.run(r.issue_id, r.history_id, r.item_field, r.from_value, r.to_value, r.created_at)
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
tx(rows)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getCachedLinearActivity(
|
|
122
|
+
db: Database,
|
|
123
|
+
params: { issueId?: string; limit?: number } = {},
|
|
124
|
+
): LinearActivityRow[] {
|
|
125
|
+
const limit = params.limit ?? 100
|
|
126
|
+
if (params.issueId) {
|
|
127
|
+
return db
|
|
128
|
+
.query(
|
|
129
|
+
`SELECT issue_id, history_id, item_field, from_value, to_value, created_at
|
|
130
|
+
FROM linear_activity
|
|
131
|
+
WHERE issue_id = $issueId
|
|
132
|
+
ORDER BY created_at DESC
|
|
133
|
+
LIMIT $limit`,
|
|
134
|
+
)
|
|
135
|
+
.all({ $issueId: params.issueId, $limit: limit }) as LinearActivityRow[]
|
|
136
|
+
}
|
|
137
|
+
return db
|
|
138
|
+
.query(
|
|
139
|
+
`SELECT issue_id, history_id, item_field, from_value, to_value, created_at
|
|
140
|
+
FROM linear_activity
|
|
141
|
+
ORDER BY created_at DESC
|
|
142
|
+
LIMIT $limit`,
|
|
143
|
+
)
|
|
144
|
+
.all({ $limit: limit }) as LinearActivityRow[]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function migrateLinearCacheSchema(db: Database): void {
|
|
148
|
+
const cols = db.query('PRAGMA table_info(linear_issues)').all() as { name: string }[]
|
|
149
|
+
if (!cols.some((c) => c.name === 'labels')) {
|
|
150
|
+
db.run("ALTER TABLE linear_issues ADD COLUMN labels TEXT NOT NULL DEFAULT '[]'")
|
|
151
|
+
}
|
|
152
|
+
if (!cols.some((c) => c.name === 'comment_count')) {
|
|
153
|
+
db.run('ALTER TABLE linear_issues ADD COLUMN comment_count INTEGER NOT NULL DEFAULT 0')
|
|
154
|
+
}
|
|
76
155
|
}
|
|
77
156
|
|
|
78
157
|
function setMeta(db: Database, key: string, value: string): void {
|
|
@@ -82,6 +161,10 @@ function setMeta(db: Database, key: string, value: string): void {
|
|
|
82
161
|
).run({ $key: key, $value: value })
|
|
83
162
|
}
|
|
84
163
|
|
|
164
|
+
function deleteMeta(db: Database, key: string): void {
|
|
165
|
+
db.query('DELETE FROM linear_sync_meta WHERE key = $key').run({ $key: key })
|
|
166
|
+
}
|
|
167
|
+
|
|
85
168
|
function getMeta(db: Database, key: string): string | null {
|
|
86
169
|
const row = db.query('SELECT value FROM linear_sync_meta WHERE key = $key').get({
|
|
87
170
|
$key: key,
|
|
@@ -89,10 +172,31 @@ function getMeta(db: Database, key: string): string | null {
|
|
|
89
172
|
return row?.value ?? null
|
|
90
173
|
}
|
|
91
174
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
175
|
+
const META_KEYS = [
|
|
176
|
+
'team',
|
|
177
|
+
'lastSyncAt',
|
|
178
|
+
'lastFullSyncAt',
|
|
179
|
+
'lastIssueUpdatedAt',
|
|
180
|
+
'lastWebhookAt',
|
|
181
|
+
] as const
|
|
182
|
+
type MetaKey = (typeof META_KEYS)[number]
|
|
183
|
+
|
|
184
|
+
export function saveSyncMeta(db: Database, meta: Partial<LinearSyncMeta>): void {
|
|
185
|
+
for (const key of META_KEYS) {
|
|
186
|
+
if (!Object.prototype.hasOwnProperty.call(meta, key)) continue
|
|
187
|
+
const value = (meta as Record<MetaKey, unknown>)[key]
|
|
188
|
+
if (value === null) {
|
|
189
|
+
deleteMeta(db, key)
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
if (key === 'team') {
|
|
193
|
+
setMeta(db, key, JSON.stringify(value))
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
if (typeof value === 'string') {
|
|
197
|
+
setMeta(db, key, value)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
96
200
|
}
|
|
97
201
|
|
|
98
202
|
export function loadSyncMeta(db: Database): LinearSyncMeta {
|
|
@@ -100,7 +204,9 @@ export function loadSyncMeta(db: Database): LinearSyncMeta {
|
|
|
100
204
|
return {
|
|
101
205
|
team: teamRaw ? (JSON.parse(teamRaw) as ProviderTeamInfo) : null,
|
|
102
206
|
lastSyncAt: getMeta(db, 'lastSyncAt'),
|
|
207
|
+
lastFullSyncAt: getMeta(db, 'lastFullSyncAt'),
|
|
103
208
|
lastIssueUpdatedAt: getMeta(db, 'lastIssueUpdatedAt'),
|
|
209
|
+
lastWebhookAt: getMeta(db, 'lastWebhookAt'),
|
|
104
210
|
}
|
|
105
211
|
}
|
|
106
212
|
|
|
@@ -173,6 +279,16 @@ export function upsertProjects(
|
|
|
173
279
|
}
|
|
174
280
|
}
|
|
175
281
|
|
|
282
|
+
// Bound per-value storage so repeated edits to a long description can't balloon the cache.
|
|
283
|
+
const ACTIVITY_VALUE_MAX_CHARS = 4096
|
|
284
|
+
const ACTIVITY_TRUNCATION_SUFFIX = '…[truncated]'
|
|
285
|
+
const ACTIVITY_VALUE_BUDGET = ACTIVITY_VALUE_MAX_CHARS - ACTIVITY_TRUNCATION_SUFFIX.length
|
|
286
|
+
|
|
287
|
+
function clampActivityValue(value: string): string {
|
|
288
|
+
if (value.length <= ACTIVITY_VALUE_MAX_CHARS) return value
|
|
289
|
+
return value.slice(0, ACTIVITY_VALUE_BUDGET) + ACTIVITY_TRUNCATION_SUFFIX
|
|
290
|
+
}
|
|
291
|
+
|
|
176
292
|
export function upsertIssues(
|
|
177
293
|
db: Database,
|
|
178
294
|
issues: Array<{
|
|
@@ -188,18 +304,24 @@ export function upsertIssues(
|
|
|
188
304
|
stateId: string
|
|
189
305
|
stateName: string
|
|
190
306
|
statePosition: number
|
|
307
|
+
labels?: string[] | null
|
|
308
|
+
commentCount?: number | null
|
|
191
309
|
url?: string | null
|
|
192
310
|
createdAt: string
|
|
193
311
|
updatedAt: string
|
|
194
312
|
}>,
|
|
195
313
|
): void {
|
|
196
|
-
|
|
314
|
+
if (issues.length === 0) return
|
|
315
|
+
const upsertStmt = db.prepare(
|
|
197
316
|
`INSERT INTO linear_issues (
|
|
198
317
|
id, identifier, title, description, priority, assignee_id, assignee_name,
|
|
199
|
-
project_id, project_name, state_id, state_name, state_position,
|
|
318
|
+
project_id, project_name, state_id, state_name, state_position, labels, comment_count,
|
|
319
|
+
url, created_at, updated_at
|
|
200
320
|
) VALUES (
|
|
201
321
|
$id, $identifier, $title, $description, $priority, $assignee_id, $assignee_name,
|
|
202
|
-
$project_id, $project_name, $state_id, $state_name, $state_position, $
|
|
322
|
+
$project_id, $project_name, $state_id, $state_name, $state_position, $labels,
|
|
323
|
+
CASE WHEN $comment_count_provided = 1 THEN $comment_count ELSE 0 END,
|
|
324
|
+
$url, $created_at, $updated_at
|
|
203
325
|
)
|
|
204
326
|
ON CONFLICT(id) DO UPDATE SET
|
|
205
327
|
identifier = excluded.identifier,
|
|
@@ -213,29 +335,105 @@ export function upsertIssues(
|
|
|
213
335
|
state_id = excluded.state_id,
|
|
214
336
|
state_name = excluded.state_name,
|
|
215
337
|
state_position = excluded.state_position,
|
|
338
|
+
labels = excluded.labels,
|
|
339
|
+
comment_count = CASE
|
|
340
|
+
WHEN $comment_count_provided = 1 THEN excluded.comment_count
|
|
341
|
+
ELSE linear_issues.comment_count
|
|
342
|
+
END,
|
|
216
343
|
url = excluded.url,
|
|
217
344
|
created_at = excluded.created_at,
|
|
218
345
|
updated_at = excluded.updated_at`,
|
|
219
346
|
)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
$
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
347
|
+
const existingDescStmt = db.prepare(
|
|
348
|
+
'SELECT description FROM linear_issues WHERE id = $id LIMIT 1',
|
|
349
|
+
)
|
|
350
|
+
const activityStmt = db.prepare(
|
|
351
|
+
`INSERT OR IGNORE INTO linear_activity
|
|
352
|
+
(issue_id, history_id, item_field, from_value, to_value, created_at)
|
|
353
|
+
VALUES ($issue_id, $history_id, $item_field, $from_value, $to_value, $created_at)`,
|
|
354
|
+
)
|
|
355
|
+
const run = db.transaction(() => {
|
|
356
|
+
for (const issue of issues) {
|
|
357
|
+
const nextDescription = issue.description ?? ''
|
|
358
|
+
const hasCommentCount = issue.commentCount !== undefined && issue.commentCount !== null
|
|
359
|
+
const prior = existingDescStmt.get({ $id: issue.id }) as { description: string } | null
|
|
360
|
+
if (prior && prior.description !== nextDescription) {
|
|
361
|
+
activityStmt.run({
|
|
362
|
+
$issue_id: issue.id,
|
|
363
|
+
$history_id: `desc:${issue.updatedAt}`,
|
|
364
|
+
$item_field: 'description',
|
|
365
|
+
$from_value: clampActivityValue(prior.description),
|
|
366
|
+
$to_value: clampActivityValue(nextDescription),
|
|
367
|
+
$created_at: issue.updatedAt,
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
upsertStmt.run({
|
|
371
|
+
$id: issue.id,
|
|
372
|
+
$identifier: issue.identifier,
|
|
373
|
+
$title: issue.title,
|
|
374
|
+
$description: nextDescription,
|
|
375
|
+
$priority: issue.priority ?? 0,
|
|
376
|
+
$assignee_id: issue.assigneeId ?? null,
|
|
377
|
+
$assignee_name: issue.assigneeName ?? '',
|
|
378
|
+
$project_id: issue.projectId ?? null,
|
|
379
|
+
$project_name: issue.projectName ?? '',
|
|
380
|
+
$state_id: issue.stateId,
|
|
381
|
+
$state_name: issue.stateName,
|
|
382
|
+
$state_position: issue.statePosition,
|
|
383
|
+
$labels: JSON.stringify(issue.labels ?? []),
|
|
384
|
+
$comment_count: issue.commentCount ?? 0,
|
|
385
|
+
$comment_count_provided: hasCommentCount ? 1 : 0,
|
|
386
|
+
$url: issue.url ?? null,
|
|
387
|
+
$created_at: issue.createdAt,
|
|
388
|
+
$updated_at: issue.updatedAt,
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
run()
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function deleteLinearIssue(db: Database, idOrIdentifier: string): void {
|
|
396
|
+
db.query(
|
|
397
|
+
`DELETE FROM linear_activity
|
|
398
|
+
WHERE issue_id = $value
|
|
399
|
+
OR issue_id IN (SELECT id FROM linear_issues WHERE identifier = $value)`,
|
|
400
|
+
).run({
|
|
401
|
+
$value: idOrIdentifier,
|
|
402
|
+
})
|
|
403
|
+
db.query('DELETE FROM linear_issues WHERE id = $v OR identifier = $v').run({
|
|
404
|
+
$v: idOrIdentifier,
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function pruneLinearIssues(db: Database, liveIssueIds: string[]): void {
|
|
409
|
+
const keep = new Set(liveIssueIds)
|
|
410
|
+
const staleIssueIds = (db.query('SELECT id FROM linear_issues').all() as { id: string }[])
|
|
411
|
+
.map((row) => row.id)
|
|
412
|
+
.filter((issueId) => !keep.has(issueId))
|
|
413
|
+
if (staleIssueIds.length === 0) return
|
|
414
|
+
|
|
415
|
+
const run = db.transaction((issueIds: string[]) => {
|
|
416
|
+
for (const issueId of issueIds) {
|
|
417
|
+
deleteLinearIssue(db, issueId)
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
run(staleIssueIds)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function adjustLinearIssueCommentCount(
|
|
425
|
+
db: Database,
|
|
426
|
+
idOrIdentifier: string,
|
|
427
|
+
delta: number,
|
|
428
|
+
): void {
|
|
429
|
+
db.query(
|
|
430
|
+
`UPDATE linear_issues
|
|
431
|
+
SET comment_count = MAX(0, comment_count + $delta)
|
|
432
|
+
WHERE id = $value OR identifier = $value`,
|
|
433
|
+
).run({
|
|
434
|
+
$delta: delta,
|
|
435
|
+
$value: idOrIdentifier,
|
|
436
|
+
})
|
|
239
437
|
}
|
|
240
438
|
|
|
241
439
|
export function getCachedColumns(db: Database): LinearStateRow[] {
|
|
@@ -257,7 +455,7 @@ function mapPriority(priority: number): Task['priority'] {
|
|
|
257
455
|
}
|
|
258
456
|
}
|
|
259
457
|
|
|
260
|
-
|
|
458
|
+
interface LinearIssueRow {
|
|
261
459
|
id: string
|
|
262
460
|
identifier: string
|
|
263
461
|
title: string
|
|
@@ -267,10 +465,23 @@ function taskFromRow(row: {
|
|
|
267
465
|
priority: number
|
|
268
466
|
assignee_name: string
|
|
269
467
|
project_name: string
|
|
468
|
+
labels: string
|
|
469
|
+
comment_count: number
|
|
270
470
|
url: string | null
|
|
271
471
|
created_at: string
|
|
272
472
|
updated_at: string
|
|
273
|
-
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function parseLabels(raw: string): string[] {
|
|
476
|
+
try {
|
|
477
|
+
const parsed: unknown = JSON.parse(raw)
|
|
478
|
+
return Array.isArray(parsed) ? parsed.filter((v): v is string => typeof v === 'string') : []
|
|
479
|
+
} catch {
|
|
480
|
+
return []
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function taskFromRow(row: LinearIssueRow): Task {
|
|
274
485
|
return {
|
|
275
486
|
id: `linear:${row.id}`,
|
|
276
487
|
providerId: row.id,
|
|
@@ -282,10 +493,15 @@ function taskFromRow(row: {
|
|
|
282
493
|
position: row.state_position,
|
|
283
494
|
priority: mapPriority(row.priority),
|
|
284
495
|
assignee: row.assignee_name,
|
|
496
|
+
assignees: row.assignee_name ? [row.assignee_name] : [],
|
|
497
|
+
labels: parseLabels(row.labels),
|
|
498
|
+
comment_count: row.comment_count,
|
|
285
499
|
project: row.project_name,
|
|
286
500
|
metadata: '{}',
|
|
287
501
|
created_at: row.created_at,
|
|
288
502
|
updated_at: row.updated_at,
|
|
503
|
+
version: row.updated_at,
|
|
504
|
+
source_updated_at: row.updated_at,
|
|
289
505
|
}
|
|
290
506
|
}
|
|
291
507
|
|
|
@@ -301,20 +517,7 @@ export function getCachedBoard(db: Database): BoardView {
|
|
|
301
517
|
WHERE state_id = $state_id
|
|
302
518
|
ORDER BY updated_at DESC, title ASC`,
|
|
303
519
|
)
|
|
304
|
-
.all({ $state_id: column.id }) as
|
|
305
|
-
id: string
|
|
306
|
-
identifier: string
|
|
307
|
-
title: string
|
|
308
|
-
description: string
|
|
309
|
-
state_id: string
|
|
310
|
-
state_position: number
|
|
311
|
-
priority: number
|
|
312
|
-
assignee_name: string
|
|
313
|
-
project_name: string
|
|
314
|
-
url: string | null
|
|
315
|
-
created_at: string
|
|
316
|
-
updated_at: string
|
|
317
|
-
}>
|
|
520
|
+
.all({ $state_id: column.id }) as LinearIssueRow[]
|
|
318
521
|
).map(taskFromRow),
|
|
319
522
|
})),
|
|
320
523
|
}
|
|
@@ -328,39 +531,15 @@ export function getCachedTask(db: Database, lookup: string): Task | null {
|
|
|
328
531
|
WHERE id = $lookup OR identifier = $lookup
|
|
329
532
|
LIMIT 1`,
|
|
330
533
|
)
|
|
331
|
-
.get({ $lookup: normalized }) as
|
|
332
|
-
id: string
|
|
333
|
-
identifier: string
|
|
334
|
-
title: string
|
|
335
|
-
description: string
|
|
336
|
-
state_id: string
|
|
337
|
-
state_position: number
|
|
338
|
-
priority: number
|
|
339
|
-
assignee_name: string
|
|
340
|
-
project_name: string
|
|
341
|
-
url: string | null
|
|
342
|
-
created_at: string
|
|
343
|
-
updated_at: string
|
|
344
|
-
} | null
|
|
534
|
+
.get({ $lookup: normalized }) as LinearIssueRow | null
|
|
345
535
|
return row ? taskFromRow(row) : null
|
|
346
536
|
}
|
|
347
537
|
|
|
348
538
|
export function getCachedTasks(db: Database): Task[] {
|
|
349
539
|
return (
|
|
350
|
-
db
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
title: string
|
|
354
|
-
description: string
|
|
355
|
-
state_id: string
|
|
356
|
-
state_position: number
|
|
357
|
-
priority: number
|
|
358
|
-
assignee_name: string
|
|
359
|
-
project_name: string
|
|
360
|
-
url: string | null
|
|
361
|
-
created_at: string
|
|
362
|
-
updated_at: string
|
|
363
|
-
}>
|
|
540
|
+
db
|
|
541
|
+
.query('SELECT * FROM linear_issues ORDER BY updated_at DESC, title ASC')
|
|
542
|
+
.all() as LinearIssueRow[]
|
|
364
543
|
).map(taskFromRow)
|
|
365
544
|
}
|
|
366
545
|
|