@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.
Files changed (59) hide show
  1. package/README.md +89 -22
  2. package/package.json +4 -2
  3. package/src/__tests__/activity.test.ts +15 -9
  4. package/src/__tests__/api.test.ts +96 -0
  5. package/src/__tests__/board-utils.test.ts +100 -0
  6. package/src/__tests__/commands/board.test.ts +6 -13
  7. package/src/__tests__/conflict.test.ts +64 -0
  8. package/src/__tests__/index.test.ts +233 -56
  9. package/src/__tests__/jira-adf.test.ts +168 -0
  10. package/src/__tests__/jira-cache.test.ts +304 -0
  11. package/src/__tests__/jira-client.test.ts +169 -0
  12. package/src/__tests__/jira-provider-comment.test.ts +281 -0
  13. package/src/__tests__/jira-provider-mutations.test.ts +771 -0
  14. package/src/__tests__/jira-provider-read.test.ts +594 -0
  15. package/src/__tests__/jira-wiring.test.ts +187 -0
  16. package/src/__tests__/linear-cache-description-activity.test.ts +142 -0
  17. package/src/__tests__/linear-provider-comment.test.ts +243 -0
  18. package/src/__tests__/linear-provider-sync.test.ts +493 -0
  19. package/src/__tests__/local-provider-comment.test.ts +60 -0
  20. package/src/__tests__/mcp-core.test.ts +164 -0
  21. package/src/__tests__/mcp-server.test.ts +252 -0
  22. package/src/__tests__/server.test.ts +298 -0
  23. package/src/__tests__/webhooks.test.ts +604 -0
  24. package/src/activity.ts +1 -11
  25. package/src/api.ts +154 -19
  26. package/src/commands/board.ts +1 -11
  27. package/src/commands/mcp.ts +87 -0
  28. package/src/db.ts +115 -3
  29. package/src/errors.ts +2 -0
  30. package/src/id.ts +1 -1
  31. package/src/index.ts +72 -18
  32. package/src/mcp/core.ts +193 -0
  33. package/src/mcp/errors.ts +109 -0
  34. package/src/mcp/index.ts +13 -0
  35. package/src/mcp/server.ts +512 -0
  36. package/src/mcp/types.ts +72 -0
  37. package/src/providers/capabilities.ts +15 -0
  38. package/src/providers/index.ts +31 -1
  39. package/src/providers/jira-adf.ts +275 -0
  40. package/src/providers/jira-cache.ts +625 -0
  41. package/src/providers/jira-client.ts +390 -0
  42. package/src/providers/jira.ts +778 -0
  43. package/src/providers/linear-cache.ts +249 -70
  44. package/src/providers/linear-client.ts +256 -13
  45. package/src/providers/linear.ts +337 -14
  46. package/src/providers/local.ts +68 -17
  47. package/src/providers/types.ts +18 -2
  48. package/src/server.ts +139 -11
  49. package/src/tunnel.ts +79 -0
  50. package/src/types.ts +18 -2
  51. package/src/webhooks.ts +36 -0
  52. package/ui/dist/assets/index-DBnoKL_k.css +1 -0
  53. package/ui/dist/assets/index-qNVJ6clH.js +40 -0
  54. package/ui/dist/index.html +8 -2
  55. package/src/__tests__/commands/task.test.ts +0 -144
  56. package/src/commands/task.ts +0 -117
  57. package/src/fixtures.ts +0 -128
  58. package/ui/dist/assets/index-DEnUD0fq.css +0 -1
  59. 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: LinearIssue | null }
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
- return data.issueCreate
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
  }