@andypai/agent-kanban 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -24
- package/package.json +4 -2
- package/src/__tests__/activity.test.ts +16 -10
- package/src/__tests__/api.test.ts +99 -3
- package/src/__tests__/board-utils.test.ts +100 -0
- package/src/__tests__/commands/board.test.ts +7 -14
- package/src/__tests__/commands/bulk.test.ts +3 -3
- package/src/__tests__/commands/column.test.ts +4 -4
- package/src/__tests__/conflict.test.ts +64 -0
- package/src/__tests__/db.test.ts +2 -2
- package/src/__tests__/id.test.ts +1 -1
- package/src/__tests__/index.test.ts +233 -56
- package/src/__tests__/jira-adf.test.ts +180 -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 +488 -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__/metrics.test.ts +2 -2
- package/src/__tests__/output.test.ts +1 -1
- package/src/__tests__/provider-capabilities.test.ts +40 -0
- package/src/__tests__/server.test.ts +291 -0
- package/src/__tests__/webhooks.test.ts +604 -0
- package/src/activity.ts +2 -12
- package/src/api.ts +156 -21
- package/src/commands/board.ts +4 -14
- package/src/commands/bulk.ts +4 -4
- package/src/commands/column.ts +4 -4
- package/src/commands/mcp.ts +87 -0
- package/src/config.ts +1 -1
- package/src/db.ts +118 -6
- package/src/errors.ts +2 -0
- package/src/id.ts +1 -1
- package/src/index.ts +83 -35
- 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/metrics.ts +1 -1
- package/src/output.ts +1 -1
- package/src/providers/capabilities.ts +22 -17
- package/src/providers/errors.ts +1 -1
- package/src/providers/index.ts +36 -6
- 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 +773 -0
- package/src/providers/linear-cache.ts +250 -71
- package/src/providers/linear-client.ts +255 -15
- package/src/providers/linear.ts +338 -20
- package/src/providers/local.ts +74 -23
- package/src/providers/types.ts +19 -3
- package/src/server.ts +141 -13
- package/src/tunnel.ts +79 -0
- package/src/types.ts +19 -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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ErrorCode } from '../errors
|
|
2
|
-
import { providerUpstreamError } from './errors
|
|
1
|
+
import { ErrorCode } from '../errors'
|
|
2
|
+
import { providerUpstreamError } from './errors'
|
|
3
3
|
|
|
4
4
|
interface GraphQLResponse<T> {
|
|
5
5
|
data?: T
|
|
@@ -31,6 +31,16 @@ export interface LinearIssue {
|
|
|
31
31
|
assignee?: { id: string; name?: string | null; displayName?: string | null } | null
|
|
32
32
|
project?: { id: string; name: string; url?: string | null; state?: string | null } | null
|
|
33
33
|
state: { id: string; name: string; position: number }
|
|
34
|
+
labels?: string[]
|
|
35
|
+
commentCount?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface LinearComment {
|
|
39
|
+
id: string
|
|
40
|
+
body: string
|
|
41
|
+
createdAt: string
|
|
42
|
+
updatedAt: string
|
|
43
|
+
user?: { id: string; name?: string | null; displayName?: string | null } | null
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
interface LinearIssueNode {
|
|
@@ -45,8 +55,43 @@ interface LinearIssueNode {
|
|
|
45
55
|
assignee?: { id: string; name?: string | null; displayName?: string | null } | null
|
|
46
56
|
project?: { id: string; name: string; url?: string | null; state?: string | null } | null
|
|
47
57
|
state: { id: string; name: string; position: number }
|
|
58
|
+
labels?: { nodes: Array<{ id: string; name: string }> }
|
|
59
|
+
comments?: {
|
|
60
|
+
nodes: Array<{ id: string }>
|
|
61
|
+
pageInfo?: { hasNextPage: boolean; endCursor: string | null }
|
|
62
|
+
} | null
|
|
48
63
|
}
|
|
49
64
|
|
|
65
|
+
interface LinearCommentNode {
|
|
66
|
+
id: string
|
|
67
|
+
body: string
|
|
68
|
+
createdAt: string
|
|
69
|
+
updatedAt: string
|
|
70
|
+
user?: { id: string; name?: string | null; displayName?: string | null } | null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toLinearIssue(node: LinearIssueNode): LinearIssue {
|
|
74
|
+
return {
|
|
75
|
+
...node,
|
|
76
|
+
assignee: node.assignee
|
|
77
|
+
? {
|
|
78
|
+
id: node.assignee.id,
|
|
79
|
+
name: node.assignee.displayName || node.assignee.name,
|
|
80
|
+
}
|
|
81
|
+
: null,
|
|
82
|
+
labels: node.labels?.nodes.map((label) => label.name) ?? [],
|
|
83
|
+
commentCount: node.comments?.nodes?.length ?? undefined,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const COMMENT_FIELDS = `
|
|
88
|
+
id
|
|
89
|
+
body
|
|
90
|
+
createdAt
|
|
91
|
+
updatedAt
|
|
92
|
+
user { id name displayName }
|
|
93
|
+
`
|
|
94
|
+
|
|
50
95
|
export class LinearClient {
|
|
51
96
|
private readonly endpoint = 'https://api.linear.app/graphql'
|
|
52
97
|
|
|
@@ -234,6 +279,13 @@ export class LinearClient {
|
|
|
234
279
|
name
|
|
235
280
|
position
|
|
236
281
|
}
|
|
282
|
+
labels {
|
|
283
|
+
nodes { id name }
|
|
284
|
+
}
|
|
285
|
+
comments(first: 250) {
|
|
286
|
+
nodes { id }
|
|
287
|
+
pageInfo { hasNextPage endCursor }
|
|
288
|
+
}
|
|
237
289
|
}
|
|
238
290
|
pageInfo {
|
|
239
291
|
hasNextPage
|
|
@@ -244,17 +296,7 @@ export class LinearClient {
|
|
|
244
296
|
`,
|
|
245
297
|
{ teamId, after, updatedAfter: updatedAfter ?? '1970-01-01T00:00:00.000Z' },
|
|
246
298
|
)
|
|
247
|
-
issues.push(
|
|
248
|
-
...data.issues.nodes.map((issue: LinearIssueNode) => ({
|
|
249
|
-
...issue,
|
|
250
|
-
assignee: issue.assignee
|
|
251
|
-
? {
|
|
252
|
-
id: issue.assignee.id,
|
|
253
|
-
name: issue.assignee.displayName || issue.assignee.name,
|
|
254
|
-
}
|
|
255
|
-
: null,
|
|
256
|
-
})),
|
|
257
|
-
)
|
|
299
|
+
issues.push(...data.issues.nodes.map(toLinearIssue))
|
|
258
300
|
after = data.issues.pageInfo.hasNextPage ? data.issues.pageInfo.endCursor : null
|
|
259
301
|
} while (after)
|
|
260
302
|
|
|
@@ -271,7 +313,7 @@ export class LinearClient {
|
|
|
271
313
|
projectId?: string
|
|
272
314
|
}): Promise<{ success: boolean; issue: LinearIssue | null }> {
|
|
273
315
|
const data = await this.query<{
|
|
274
|
-
issueCreate: { success: boolean; issue:
|
|
316
|
+
issueCreate: { success: boolean; issue: LinearIssueNode | null }
|
|
275
317
|
}>(
|
|
276
318
|
`
|
|
277
319
|
mutation CreateIssue($input: IssueCreateInput!) {
|
|
@@ -289,6 +331,11 @@ export class LinearClient {
|
|
|
289
331
|
assignee { id name displayName }
|
|
290
332
|
project { id name url state }
|
|
291
333
|
state { id name position }
|
|
334
|
+
labels { nodes { id name } }
|
|
335
|
+
comments(first: 250) {
|
|
336
|
+
nodes { id }
|
|
337
|
+
pageInfo { hasNextPage endCursor }
|
|
338
|
+
}
|
|
292
339
|
}
|
|
293
340
|
}
|
|
294
341
|
}
|
|
@@ -305,7 +352,11 @@ export class LinearClient {
|
|
|
305
352
|
},
|
|
306
353
|
},
|
|
307
354
|
)
|
|
308
|
-
|
|
355
|
+
const node = data.issueCreate.issue
|
|
356
|
+
return {
|
|
357
|
+
success: data.issueCreate.success,
|
|
358
|
+
issue: node ? toLinearIssue(node) : null,
|
|
359
|
+
}
|
|
309
360
|
}
|
|
310
361
|
|
|
311
362
|
async updateIssue(
|
|
@@ -326,4 +377,193 @@ export class LinearClient {
|
|
|
326
377
|
)
|
|
327
378
|
return data.issueUpdate
|
|
328
379
|
}
|
|
380
|
+
|
|
381
|
+
async listIssueHistory(params: {
|
|
382
|
+
issueId: string
|
|
383
|
+
first?: number
|
|
384
|
+
after?: string | null
|
|
385
|
+
}): Promise<{
|
|
386
|
+
nodes: Array<{
|
|
387
|
+
id: string
|
|
388
|
+
createdAt: string
|
|
389
|
+
fromState?: { id: string } | null
|
|
390
|
+
toState?: { id: string } | null
|
|
391
|
+
}>
|
|
392
|
+
pageInfo: { hasNextPage: boolean; endCursor: string | null }
|
|
393
|
+
}> {
|
|
394
|
+
const data = await this.query<{
|
|
395
|
+
issue: {
|
|
396
|
+
history: {
|
|
397
|
+
nodes: Array<{
|
|
398
|
+
id: string
|
|
399
|
+
createdAt: string
|
|
400
|
+
fromState?: { id: string } | null
|
|
401
|
+
toState?: { id: string } | null
|
|
402
|
+
}>
|
|
403
|
+
pageInfo: { hasNextPage: boolean; endCursor: string | null }
|
|
404
|
+
}
|
|
405
|
+
} | null
|
|
406
|
+
}>(
|
|
407
|
+
`
|
|
408
|
+
query IssueHistory($issueId: String!, $first: Int, $after: String) {
|
|
409
|
+
issue(id: $issueId) {
|
|
410
|
+
history(first: $first, after: $after) {
|
|
411
|
+
nodes {
|
|
412
|
+
id
|
|
413
|
+
createdAt
|
|
414
|
+
fromState { id }
|
|
415
|
+
toState { id }
|
|
416
|
+
}
|
|
417
|
+
pageInfo { hasNextPage endCursor }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
`,
|
|
422
|
+
{
|
|
423
|
+
issueId: params.issueId,
|
|
424
|
+
first: params.first ?? 50,
|
|
425
|
+
after: params.after ?? null,
|
|
426
|
+
},
|
|
427
|
+
)
|
|
428
|
+
if (!data.issue) {
|
|
429
|
+
return { nodes: [], pageInfo: { hasNextPage: false, endCursor: null } }
|
|
430
|
+
}
|
|
431
|
+
return data.issue.history
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async getIssueTeam(issueId: string): Promise<{ id: string; key: string } | null> {
|
|
435
|
+
const data = await this.query<{
|
|
436
|
+
issue: {
|
|
437
|
+
team: {
|
|
438
|
+
id: string
|
|
439
|
+
key: string
|
|
440
|
+
} | null
|
|
441
|
+
} | null
|
|
442
|
+
}>(
|
|
443
|
+
`
|
|
444
|
+
query IssueTeam($issueId: String!) {
|
|
445
|
+
issue(id: $issueId) {
|
|
446
|
+
team {
|
|
447
|
+
id
|
|
448
|
+
key
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
`,
|
|
453
|
+
{ issueId },
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
return data.issue?.team ?? null
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async listComments(issueId: string): Promise<LinearComment[]> {
|
|
460
|
+
let after: string | null = null
|
|
461
|
+
const comments: LinearComment[] = []
|
|
462
|
+
|
|
463
|
+
do {
|
|
464
|
+
const data: {
|
|
465
|
+
issue: {
|
|
466
|
+
comments: {
|
|
467
|
+
nodes: LinearCommentNode[]
|
|
468
|
+
pageInfo: PageInfo
|
|
469
|
+
}
|
|
470
|
+
} | null
|
|
471
|
+
} = await this.query(
|
|
472
|
+
`
|
|
473
|
+
query IssueComments($issueId: String!, $after: String) {
|
|
474
|
+
issue(id: $issueId) {
|
|
475
|
+
comments(first: 100, after: $after) {
|
|
476
|
+
nodes { ${COMMENT_FIELDS} }
|
|
477
|
+
pageInfo {
|
|
478
|
+
hasNextPage
|
|
479
|
+
endCursor
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
`,
|
|
485
|
+
{ issueId, after },
|
|
486
|
+
)
|
|
487
|
+
if (!data.issue) {
|
|
488
|
+
providerUpstreamError(`Linear issue '${issueId}' was not found`)
|
|
489
|
+
}
|
|
490
|
+
comments.push(...data.issue.comments.nodes)
|
|
491
|
+
after = data.issue.comments.pageInfo.hasNextPage
|
|
492
|
+
? data.issue.comments.pageInfo.endCursor
|
|
493
|
+
: null
|
|
494
|
+
} while (after)
|
|
495
|
+
|
|
496
|
+
return comments
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async getComment(commentId: string): Promise<LinearComment> {
|
|
500
|
+
const data = await this.query<{ comment: LinearCommentNode | null }>(
|
|
501
|
+
`
|
|
502
|
+
query Comment($id: String!) {
|
|
503
|
+
comment(id: $id) { ${COMMENT_FIELDS} }
|
|
504
|
+
}
|
|
505
|
+
`,
|
|
506
|
+
{ id: commentId },
|
|
507
|
+
)
|
|
508
|
+
if (!data.comment) {
|
|
509
|
+
providerUpstreamError(`Linear comment '${commentId}' was not found`)
|
|
510
|
+
}
|
|
511
|
+
return data.comment
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async commentCreate(
|
|
515
|
+
issueId: string,
|
|
516
|
+
body: string,
|
|
517
|
+
): Promise<{ success: boolean; comment: LinearComment | null }> {
|
|
518
|
+
const data = await this.query<{
|
|
519
|
+
commentCreate: { success: boolean; comment: LinearCommentNode | null }
|
|
520
|
+
}>(
|
|
521
|
+
`
|
|
522
|
+
mutation CommentCreate($input: CommentCreateInput!) {
|
|
523
|
+
commentCreate(input: $input) {
|
|
524
|
+
success
|
|
525
|
+
comment { ${COMMENT_FIELDS} }
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
`,
|
|
529
|
+
{
|
|
530
|
+
input: {
|
|
531
|
+
issueId,
|
|
532
|
+
body,
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
)
|
|
536
|
+
return {
|
|
537
|
+
success: data.commentCreate.success,
|
|
538
|
+
comment: data.commentCreate.comment,
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async commentUpdate(
|
|
543
|
+
commentId: string,
|
|
544
|
+
body: string,
|
|
545
|
+
): Promise<{ success: boolean; comment: LinearComment | null }> {
|
|
546
|
+
const data = await this.query<{
|
|
547
|
+
commentUpdate: { success: boolean; comment: LinearCommentNode | null }
|
|
548
|
+
}>(
|
|
549
|
+
`
|
|
550
|
+
mutation CommentUpdate($id: String!, $input: CommentUpdateInput!) {
|
|
551
|
+
commentUpdate(id: $id, input: $input) {
|
|
552
|
+
success
|
|
553
|
+
comment { ${COMMENT_FIELDS} }
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
`,
|
|
557
|
+
{
|
|
558
|
+
id: commentId,
|
|
559
|
+
input: {
|
|
560
|
+
body,
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
)
|
|
564
|
+
return {
|
|
565
|
+
success: data.commentUpdate.success,
|
|
566
|
+
comment: data.commentUpdate.comment,
|
|
567
|
+
}
|
|
568
|
+
}
|
|
329
569
|
}
|