@andypai/agent-kanban 0.1.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 +8 -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-DEnUD0fq.css +0 -1
- package/ui/dist/assets/index-DMRjw1nI.js +0 -40
|
@@ -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,44 @@ 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
|
+
totalCount?: number
|
|
61
|
+
nodes: Array<{ id: string }>
|
|
62
|
+
pageInfo?: { hasNextPage: boolean; endCursor: string | null }
|
|
63
|
+
} | null
|
|
48
64
|
}
|
|
49
65
|
|
|
66
|
+
interface LinearCommentNode {
|
|
67
|
+
id: string
|
|
68
|
+
body: string
|
|
69
|
+
createdAt: string
|
|
70
|
+
updatedAt: string
|
|
71
|
+
user?: { id: string; name?: string | null; displayName?: string | null } | null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function toLinearIssue(node: LinearIssueNode): LinearIssue {
|
|
75
|
+
return {
|
|
76
|
+
...node,
|
|
77
|
+
assignee: node.assignee
|
|
78
|
+
? {
|
|
79
|
+
id: node.assignee.id,
|
|
80
|
+
name: node.assignee.displayName || node.assignee.name,
|
|
81
|
+
}
|
|
82
|
+
: null,
|
|
83
|
+
labels: node.labels?.nodes.map((label) => label.name) ?? [],
|
|
84
|
+
commentCount: node.comments?.totalCount ?? node.comments?.nodes?.length ?? undefined,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const COMMENT_FIELDS = `
|
|
89
|
+
id
|
|
90
|
+
body
|
|
91
|
+
createdAt
|
|
92
|
+
updatedAt
|
|
93
|
+
user { id name displayName }
|
|
94
|
+
`
|
|
95
|
+
|
|
50
96
|
export class LinearClient {
|
|
51
97
|
private readonly endpoint = 'https://api.linear.app/graphql'
|
|
52
98
|
|
|
@@ -234,6 +280,14 @@ export class LinearClient {
|
|
|
234
280
|
name
|
|
235
281
|
position
|
|
236
282
|
}
|
|
283
|
+
labels {
|
|
284
|
+
nodes { id name }
|
|
285
|
+
}
|
|
286
|
+
comments(first: 250) {
|
|
287
|
+
totalCount
|
|
288
|
+
nodes { id }
|
|
289
|
+
pageInfo { hasNextPage endCursor }
|
|
290
|
+
}
|
|
237
291
|
}
|
|
238
292
|
pageInfo {
|
|
239
293
|
hasNextPage
|
|
@@ -244,17 +298,7 @@ export class LinearClient {
|
|
|
244
298
|
`,
|
|
245
299
|
{ teamId, after, updatedAfter: updatedAfter ?? '1970-01-01T00:00:00.000Z' },
|
|
246
300
|
)
|
|
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
|
-
)
|
|
301
|
+
issues.push(...data.issues.nodes.map(toLinearIssue))
|
|
258
302
|
after = data.issues.pageInfo.hasNextPage ? data.issues.pageInfo.endCursor : null
|
|
259
303
|
} while (after)
|
|
260
304
|
|
|
@@ -271,7 +315,7 @@ export class LinearClient {
|
|
|
271
315
|
projectId?: string
|
|
272
316
|
}): Promise<{ success: boolean; issue: LinearIssue | null }> {
|
|
273
317
|
const data = await this.query<{
|
|
274
|
-
issueCreate: { success: boolean; issue:
|
|
318
|
+
issueCreate: { success: boolean; issue: LinearIssueNode | null }
|
|
275
319
|
}>(
|
|
276
320
|
`
|
|
277
321
|
mutation CreateIssue($input: IssueCreateInput!) {
|
|
@@ -289,6 +333,12 @@ export class LinearClient {
|
|
|
289
333
|
assignee { id name displayName }
|
|
290
334
|
project { id name url state }
|
|
291
335
|
state { id name position }
|
|
336
|
+
labels { nodes { id name } }
|
|
337
|
+
comments(first: 250) {
|
|
338
|
+
totalCount
|
|
339
|
+
nodes { id }
|
|
340
|
+
pageInfo { hasNextPage endCursor }
|
|
341
|
+
}
|
|
292
342
|
}
|
|
293
343
|
}
|
|
294
344
|
}
|
|
@@ -305,7 +355,11 @@ export class LinearClient {
|
|
|
305
355
|
},
|
|
306
356
|
},
|
|
307
357
|
)
|
|
308
|
-
|
|
358
|
+
const node = data.issueCreate.issue
|
|
359
|
+
return {
|
|
360
|
+
success: data.issueCreate.success,
|
|
361
|
+
issue: node ? toLinearIssue(node) : null,
|
|
362
|
+
}
|
|
309
363
|
}
|
|
310
364
|
|
|
311
365
|
async updateIssue(
|
|
@@ -326,4 +380,193 @@ export class LinearClient {
|
|
|
326
380
|
)
|
|
327
381
|
return data.issueUpdate
|
|
328
382
|
}
|
|
383
|
+
|
|
384
|
+
async listIssueHistory(params: {
|
|
385
|
+
issueId: string
|
|
386
|
+
first?: number
|
|
387
|
+
after?: string | null
|
|
388
|
+
}): Promise<{
|
|
389
|
+
nodes: Array<{
|
|
390
|
+
id: string
|
|
391
|
+
createdAt: string
|
|
392
|
+
fromState?: { id: string } | null
|
|
393
|
+
toState?: { id: string } | null
|
|
394
|
+
}>
|
|
395
|
+
pageInfo: { hasNextPage: boolean; endCursor: string | null }
|
|
396
|
+
}> {
|
|
397
|
+
const data = await this.query<{
|
|
398
|
+
issue: {
|
|
399
|
+
history: {
|
|
400
|
+
nodes: Array<{
|
|
401
|
+
id: string
|
|
402
|
+
createdAt: string
|
|
403
|
+
fromState?: { id: string } | null
|
|
404
|
+
toState?: { id: string } | null
|
|
405
|
+
}>
|
|
406
|
+
pageInfo: { hasNextPage: boolean; endCursor: string | null }
|
|
407
|
+
}
|
|
408
|
+
} | null
|
|
409
|
+
}>(
|
|
410
|
+
`
|
|
411
|
+
query IssueHistory($issueId: String!, $first: Int, $after: String) {
|
|
412
|
+
issue(id: $issueId) {
|
|
413
|
+
history(first: $first, after: $after) {
|
|
414
|
+
nodes {
|
|
415
|
+
id
|
|
416
|
+
createdAt
|
|
417
|
+
fromState { id }
|
|
418
|
+
toState { id }
|
|
419
|
+
}
|
|
420
|
+
pageInfo { hasNextPage endCursor }
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
`,
|
|
425
|
+
{
|
|
426
|
+
issueId: params.issueId,
|
|
427
|
+
first: params.first ?? 50,
|
|
428
|
+
after: params.after ?? null,
|
|
429
|
+
},
|
|
430
|
+
)
|
|
431
|
+
if (!data.issue) {
|
|
432
|
+
return { nodes: [], pageInfo: { hasNextPage: false, endCursor: null } }
|
|
433
|
+
}
|
|
434
|
+
return data.issue.history
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async getIssueTeam(issueId: string): Promise<{ id: string; key: string } | null> {
|
|
438
|
+
const data = await this.query<{
|
|
439
|
+
issue: {
|
|
440
|
+
team: {
|
|
441
|
+
id: string
|
|
442
|
+
key: string
|
|
443
|
+
} | null
|
|
444
|
+
} | null
|
|
445
|
+
}>(
|
|
446
|
+
`
|
|
447
|
+
query IssueTeam($issueId: String!) {
|
|
448
|
+
issue(id: $issueId) {
|
|
449
|
+
team {
|
|
450
|
+
id
|
|
451
|
+
key
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
`,
|
|
456
|
+
{ issueId },
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
return data.issue?.team ?? null
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async listComments(issueId: string): Promise<LinearComment[]> {
|
|
463
|
+
let after: string | null = null
|
|
464
|
+
const comments: LinearComment[] = []
|
|
465
|
+
|
|
466
|
+
do {
|
|
467
|
+
const data: {
|
|
468
|
+
issue: {
|
|
469
|
+
comments: {
|
|
470
|
+
nodes: LinearCommentNode[]
|
|
471
|
+
pageInfo: PageInfo
|
|
472
|
+
}
|
|
473
|
+
} | null
|
|
474
|
+
} = await this.query(
|
|
475
|
+
`
|
|
476
|
+
query IssueComments($issueId: String!, $after: String) {
|
|
477
|
+
issue(id: $issueId) {
|
|
478
|
+
comments(first: 100, after: $after) {
|
|
479
|
+
nodes { ${COMMENT_FIELDS} }
|
|
480
|
+
pageInfo {
|
|
481
|
+
hasNextPage
|
|
482
|
+
endCursor
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
`,
|
|
488
|
+
{ issueId, after },
|
|
489
|
+
)
|
|
490
|
+
if (!data.issue) {
|
|
491
|
+
providerUpstreamError(`Linear issue '${issueId}' was not found`)
|
|
492
|
+
}
|
|
493
|
+
comments.push(...data.issue.comments.nodes)
|
|
494
|
+
after = data.issue.comments.pageInfo.hasNextPage
|
|
495
|
+
? data.issue.comments.pageInfo.endCursor
|
|
496
|
+
: null
|
|
497
|
+
} while (after)
|
|
498
|
+
|
|
499
|
+
return comments
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async getComment(commentId: string): Promise<LinearComment> {
|
|
503
|
+
const data = await this.query<{ comment: LinearCommentNode | null }>(
|
|
504
|
+
`
|
|
505
|
+
query Comment($id: String!) {
|
|
506
|
+
comment(id: $id) { ${COMMENT_FIELDS} }
|
|
507
|
+
}
|
|
508
|
+
`,
|
|
509
|
+
{ id: commentId },
|
|
510
|
+
)
|
|
511
|
+
if (!data.comment) {
|
|
512
|
+
providerUpstreamError(`Linear comment '${commentId}' was not found`)
|
|
513
|
+
}
|
|
514
|
+
return data.comment
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async commentCreate(
|
|
518
|
+
issueId: string,
|
|
519
|
+
body: string,
|
|
520
|
+
): Promise<{ success: boolean; comment: LinearComment | null }> {
|
|
521
|
+
const data = await this.query<{
|
|
522
|
+
commentCreate: { success: boolean; comment: LinearCommentNode | null }
|
|
523
|
+
}>(
|
|
524
|
+
`
|
|
525
|
+
mutation CommentCreate($input: CommentCreateInput!) {
|
|
526
|
+
commentCreate(input: $input) {
|
|
527
|
+
success
|
|
528
|
+
comment { ${COMMENT_FIELDS} }
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
`,
|
|
532
|
+
{
|
|
533
|
+
input: {
|
|
534
|
+
issueId,
|
|
535
|
+
body,
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
)
|
|
539
|
+
return {
|
|
540
|
+
success: data.commentCreate.success,
|
|
541
|
+
comment: data.commentCreate.comment,
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async commentUpdate(
|
|
546
|
+
commentId: string,
|
|
547
|
+
body: string,
|
|
548
|
+
): Promise<{ success: boolean; comment: LinearComment | null }> {
|
|
549
|
+
const data = await this.query<{
|
|
550
|
+
commentUpdate: { success: boolean; comment: LinearCommentNode | null }
|
|
551
|
+
}>(
|
|
552
|
+
`
|
|
553
|
+
mutation CommentUpdate($id: String!, $input: CommentUpdateInput!) {
|
|
554
|
+
commentUpdate(id: $id, input: $input) {
|
|
555
|
+
success
|
|
556
|
+
comment { ${COMMENT_FIELDS} }
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
`,
|
|
560
|
+
{
|
|
561
|
+
id: commentId,
|
|
562
|
+
input: {
|
|
563
|
+
body,
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
)
|
|
567
|
+
return {
|
|
568
|
+
success: data.commentUpdate.success,
|
|
569
|
+
comment: data.commentUpdate.comment,
|
|
570
|
+
}
|
|
571
|
+
}
|
|
329
572
|
}
|