@andypai/agent-kanban 0.6.1 → 0.6.3
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/package.json +1 -1
- package/src/__tests__/api.test.ts +48 -0
- package/src/__tests__/conflict.test.ts +25 -1
- package/src/__tests__/linear-provider-sync.test.ts +517 -40
- package/src/__tests__/mcp-core.test.ts +54 -0
- package/src/__tests__/mcp-server.test.ts +17 -0
- package/src/__tests__/metrics-spec.test.ts +121 -0
- package/src/__tests__/postgres-linear-provider.test.ts +10 -2
- package/src/__tests__/postgres-local-provider.test.ts +140 -0
- package/src/__tests__/tracker-config-columns.test.ts +23 -0
- package/src/__tests__/transport-input.test.ts +35 -0
- package/src/__tests__/webhooks.test.ts +25 -9
- package/src/api.ts +35 -31
- package/src/commands/mcp.ts +2 -1
- package/src/db.ts +1 -1
- package/src/errors.ts +2 -0
- package/src/index.ts +6 -1
- package/src/mcp/core.ts +24 -2
- package/src/mcp/server.ts +2 -1
- package/src/mcp/types.ts +12 -1
- package/src/metrics-spec.ts +87 -0
- package/src/metrics.ts +29 -60
- package/src/providers/capabilities.ts +12 -0
- package/src/providers/jira-core.ts +5 -0
- package/src/providers/linear-client.ts +172 -95
- package/src/providers/linear-core.ts +94 -59
- package/src/providers/postgres-local.ts +136 -89
- package/src/tracker-config.ts +17 -1
- package/src/transport-input.ts +30 -0
- package/src/version.ts +8 -0
- package/src/webhooks.ts +4 -11
- package/ui/dist/assets/index-Cigv8a9S.js +40 -0
- package/ui/dist/index.html +1 -1
- package/ui/dist/assets/index-65LcV0R7.js +0 -40
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andypai/agent-kanban",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Agent-friendly kanban board CLI. Manage tasks via bash commands, parse structured JSON output.",
|
|
5
5
|
"homepage": "https://github.com/abpai/agent-kanban#readme",
|
|
6
6
|
"repository": {
|
|
@@ -37,6 +37,54 @@ describe('handleRequest', () => {
|
|
|
37
37
|
expect(result.mutated).toBe(false)
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
+
test('returns the error envelope for a malformed JSON body', async () => {
|
|
41
|
+
const req = new Request('http://localhost/api/tasks', {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: { 'Content-Type': 'application/json' },
|
|
44
|
+
body: '{ not valid json',
|
|
45
|
+
})
|
|
46
|
+
const result = await handleRequest(provider, req)
|
|
47
|
+
const body = (await result.response.json()) as {
|
|
48
|
+
ok: boolean
|
|
49
|
+
error: { code: string; message: string }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
expect(result.response.status).toBe(400)
|
|
53
|
+
expect(result.mutated).toBe(false)
|
|
54
|
+
expect(body.ok).toBe(false)
|
|
55
|
+
expect(body.error.code).toBe('INVALID_REQUEST_BODY')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('still returns MISSING_ARGUMENT when a valid body omits a required field', async () => {
|
|
59
|
+
const req = new Request('http://localhost/api/tasks', {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify({ description: 'no title' }),
|
|
63
|
+
})
|
|
64
|
+
const result = await handleRequest(provider, req)
|
|
65
|
+
const body = (await result.response.json()) as {
|
|
66
|
+
ok: boolean
|
|
67
|
+
error: { code: string }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
expect(result.response.status).toBe(400)
|
|
71
|
+
expect(result.mutated).toBe(false)
|
|
72
|
+
expect(body.error.code).toBe('MISSING_ARGUMENT')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('rejects an invalid limit query parameter through the envelope', async () => {
|
|
76
|
+
const req = new Request('http://localhost/api/tasks?limit=-5', { method: 'GET' })
|
|
77
|
+
const result = await handleRequest(provider, req)
|
|
78
|
+
const body = (await result.response.json()) as {
|
|
79
|
+
ok: boolean
|
|
80
|
+
error: { code: string }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
expect(result.response.status).toBe(400)
|
|
84
|
+
expect(body.ok).toBe(false)
|
|
85
|
+
expect(body.error.code).toBe('INVALID_ARGUMENT')
|
|
86
|
+
})
|
|
87
|
+
|
|
40
88
|
test('marks successful task creation as mutated', async () => {
|
|
41
89
|
const req = new Request('http://localhost/api/tasks', {
|
|
42
90
|
method: 'POST',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
|
-
import { initSchema, seedDefaultColumns, resolveColumn } from '../db'
|
|
3
|
+
import { initSchema, seedDefaultColumns, resolveColumn, bulkMoveAll } from '../db'
|
|
4
4
|
import { LocalProvider } from '../providers/local'
|
|
5
5
|
import { ErrorCode, KanbanError } from '../errors'
|
|
6
6
|
|
|
@@ -62,6 +62,30 @@ describe('local provider conflict detection', () => {
|
|
|
62
62
|
expect(v2.version).toBe('2')
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
+
test('bulkMoveAll bumps the task version so a stale expectedVersion conflicts', async () => {
|
|
66
|
+
const created = await provider.createTask({ title: 'T1', column: 'backlog' })
|
|
67
|
+
expect(created.version).toBe('0')
|
|
68
|
+
|
|
69
|
+
const { moved } = bulkMoveAll(db, 'backlog', 'in-progress')
|
|
70
|
+
expect(moved).toBe(1)
|
|
71
|
+
|
|
72
|
+
// The move is a real mutation, so the version must change.
|
|
73
|
+
const after = await provider.getTask(created.id)
|
|
74
|
+
expect(after.version).toBe('1')
|
|
75
|
+
|
|
76
|
+
let err: unknown
|
|
77
|
+
try {
|
|
78
|
+
await provider.updateTask(created.id, {
|
|
79
|
+
title: 'stale',
|
|
80
|
+
expectedVersion: created.version ?? undefined,
|
|
81
|
+
})
|
|
82
|
+
} catch (e) {
|
|
83
|
+
err = e
|
|
84
|
+
}
|
|
85
|
+
expect(err).toBeInstanceOf(KanbanError)
|
|
86
|
+
expect((err as KanbanError).code).toBe(ErrorCode.CONFLICT)
|
|
87
|
+
})
|
|
88
|
+
|
|
65
89
|
// moveTask(id, column) intentionally has no expectedVersion parameter, so moves
|
|
66
90
|
// are last-write-wins by design (drag-and-drop has no loaded version to check).
|
|
67
91
|
// This locks in that contract so a move is never rejected for a stale version.
|