@andypai/agent-kanban 0.3.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.
Files changed (58) hide show
  1. package/README.md +34 -5
  2. package/package.json +1 -1
  3. package/src/__tests__/activity.test.ts +2 -2
  4. package/src/__tests__/api.test.ts +3 -3
  5. package/src/__tests__/commands/board.test.ts +3 -3
  6. package/src/__tests__/commands/bulk.test.ts +3 -3
  7. package/src/__tests__/commands/column.test.ts +4 -4
  8. package/src/__tests__/conflict.test.ts +3 -3
  9. package/src/__tests__/db.test.ts +2 -2
  10. package/src/__tests__/id.test.ts +1 -1
  11. package/src/__tests__/index.test.ts +3 -3
  12. package/src/__tests__/jira-adf.test.ts +13 -1
  13. package/src/__tests__/jira-cache.test.ts +1 -1
  14. package/src/__tests__/jira-client.test.ts +2 -2
  15. package/src/__tests__/jira-provider-comment.test.ts +3 -3
  16. package/src/__tests__/jira-provider-mutations.test.ts +4 -4
  17. package/src/__tests__/jira-provider-read.test.ts +5 -5
  18. package/src/__tests__/jira-wiring.test.ts +3 -3
  19. package/src/__tests__/linear-cache-description-activity.test.ts +1 -1
  20. package/src/__tests__/linear-provider-comment.test.ts +2 -2
  21. package/src/__tests__/linear-provider-sync.test.ts +4 -9
  22. package/src/__tests__/local-provider-comment.test.ts +2 -2
  23. package/src/__tests__/mcp-core.test.ts +4 -4
  24. package/src/__tests__/mcp-server.test.ts +3 -3
  25. package/src/__tests__/metrics.test.ts +2 -2
  26. package/src/__tests__/output.test.ts +1 -1
  27. package/src/__tests__/provider-capabilities.test.ts +40 -0
  28. package/src/__tests__/server.test.ts +3 -10
  29. package/src/__tests__/webhooks.test.ts +6 -6
  30. package/src/activity.ts +2 -2
  31. package/src/api.ts +3 -3
  32. package/src/commands/board.ts +4 -4
  33. package/src/commands/bulk.ts +4 -4
  34. package/src/commands/column.ts +4 -4
  35. package/src/commands/mcp.ts +3 -3
  36. package/src/config.ts +1 -1
  37. package/src/db.ts +4 -4
  38. package/src/index.ts +13 -19
  39. package/src/mcp/core.ts +4 -4
  40. package/src/mcp/errors.ts +1 -1
  41. package/src/mcp/index.ts +6 -6
  42. package/src/mcp/server.ts +3 -3
  43. package/src/mcp/types.ts +2 -2
  44. package/src/metrics.ts +1 -1
  45. package/src/output.ts +1 -1
  46. package/src/providers/capabilities.ts +21 -31
  47. package/src/providers/errors.ts +1 -1
  48. package/src/providers/index.ts +6 -6
  49. package/src/providers/jira-cache.ts +1 -1
  50. package/src/providers/jira-client.ts +2 -2
  51. package/src/providers/jira.ts +9 -14
  52. package/src/providers/linear-cache.ts +1 -1
  53. package/src/providers/linear-client.ts +3 -6
  54. package/src/providers/linear.ts +8 -13
  55. package/src/providers/local.ts +8 -8
  56. package/src/providers/types.ts +2 -2
  57. package/src/server.ts +2 -2
  58. package/src/types.ts +1 -0
package/README.md CHANGED
@@ -2,11 +2,21 @@
2
2
 
3
3
  [![CI](https://github.com/abpai/agent-kanban/actions/workflows/ci.yml/badge.svg)](https://github.com/abpai/agent-kanban/actions/workflows/ci.yml)
4
4
 
5
- Agent-friendly kanban board CLI. Manage tasks via bash commands, parse structured JSON output.
5
+ `agent-kanban` exists because browser-first project tools are a bad control
6
+ plane for agents, shell scripts, and CI jobs. If automation has to click
7
+ through a web app, scrape HTML, or learn a different integration for every
8
+ tracker, the setup gets brittle fast.
6
9
 
7
- ## Why
10
+ This repo gives you one small contract across three modes: a local SQLite board,
11
+ Linear, and Jira Cloud. The CLI stays the same. The JSON envelope stays the
12
+ same. Humans still get an optional dashboard when they want a visual pass.
8
13
 
9
- Most project-management tools are built for humans clicking through UIs. `agent-kanban` is built for **CLI-first workflows** — AI agents and scripts get deterministic JSON they can parse, humans get a pretty-printed view and a web dashboard. Runs against a local SQLite file, a Linear backend, or a Jira Cloud project.
14
+ That buys you a few things that are easy to miss at first:
15
+
16
+ - You can prototype an agent against a local board, then point the same workflow at Linear or Jira later.
17
+ - Remote modes use webhooks plus polling fallback, so missed events are less painful than with one-shot scripts or browser automation.
18
+ - Local mode needs no external database or service, which makes scratch boards, demos, and CI setups much easier to spin up.
19
+ - The repo also includes a reusable MCP layer, so sibling tools can reuse the same tracker semantics instead of growing their own tracker adapter.
10
20
 
11
21
  ## Documentation
12
22
 
@@ -107,6 +117,13 @@ kanban board view
107
117
  | webhooks | no | yes | yes |
108
118
 
109
119
  Linear tasks carry an `externalRef` (e.g. `TEAM-123`) and a `url`. Commands accept either the internal ID or the external ref.
120
+ Jira tasks can also be addressed by issue key (for example `ENG-123`).
121
+
122
+ Local mode is still the only mode with built-in metrics, config mutation, bulk
123
+ cleanup, and the dashboard/bootstrap activity feed. Linear and Jira do keep
124
+ remote issue history and comment counts in their cache tables for sync and
125
+ provider-backed flows, but those modes do not expose the same local analytics
126
+ surface.
110
127
 
111
128
  Unsupported operations return error code `UNSUPPORTED_OPERATION` with exit code 1.
112
129
 
@@ -223,6 +240,16 @@ kanban serve --port 8080
223
240
  kanban serve --tunnel # optional public URL for webhook testing
224
241
  ```
225
242
 
243
+ ### mcp
244
+
245
+ ```bash
246
+ kanban mcp
247
+ kanban mcp --db /path/to/board.db
248
+ ```
249
+
250
+ Runs the bundled MCP server over stdio for local MCP clients such as Claude
251
+ Desktop. See [`docs/mcp.md`](docs/mcp.md) for the tool surface and caveats.
252
+
226
253
  ## Global flags
227
254
 
228
255
  | Flag | Description |
@@ -278,8 +305,10 @@ Comment routes:
278
305
  ## Reusable MCP core
279
306
 
280
307
  The repo also includes a reusable tracker MCP implementation under `src/mcp/`.
281
- It is intended for sibling workspaces and in-repo consumers rather than the
282
- `kanban` CLI itself.
308
+ There are two ways to use it today:
309
+
310
+ - run `kanban mcp` for a bundled stdio MCP server
311
+ - import the helpers in `src/mcp/` from a sibling workspace or in-repo consumer
283
312
 
284
313
  See [`docs/mcp.md`](docs/mcp.md) for the current default tool set, the auth and
285
314
  policy model, and the caveats around source-level imports and `kanban serve`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andypai/agent-kanban",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
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": {
@@ -9,8 +9,8 @@ import {
9
9
  moveTask,
10
10
  bulkMoveAll,
11
11
  bulkClearDone,
12
- } from '../db.ts'
13
- import { listActivity } from '../activity.ts'
12
+ } from '../db'
13
+ import { listActivity } from '../activity'
14
14
 
15
15
  let db: Database
16
16
 
@@ -1,8 +1,8 @@
1
1
  import { beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { initSchema, seedDefaultColumns, addTask } from '../db.ts'
4
- import { handleRequest } from '../api.ts'
5
- import { createProvider } from '../providers/index.ts'
3
+ import { initSchema, seedDefaultColumns, addTask } from '../db'
4
+ import { handleRequest } from '../api'
5
+ import { createProvider } from '../providers/index'
6
6
 
7
7
  let db: Database
8
8
  let provider: ReturnType<typeof createProvider>
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, test, beforeEach } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { getBoardView, initSchema, seedDefaultColumns } from '../../db.ts'
4
- import { boardInit, boardReset } from '../../commands/board.ts'
5
- import { KanbanError } from '../../errors.ts'
3
+ import { getBoardView, initSchema, seedDefaultColumns } from '../../db'
4
+ import { boardInit, boardReset } from '../../commands/board'
5
+ import { KanbanError } from '../../errors'
6
6
 
7
7
  let db: Database
8
8
 
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, test, beforeEach } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { initSchema, seedDefaultColumns, addTask, listTasks } from '../../db.ts'
4
- import { bulkMoveAllCmd, bulkClearDoneCmd } from '../../commands/bulk.ts'
5
- import { KanbanError } from '../../errors.ts'
3
+ import { initSchema, seedDefaultColumns, addTask, listTasks } from '../../db'
4
+ import { bulkMoveAllCmd, bulkClearDoneCmd } from '../../commands/bulk'
5
+ import { KanbanError } from '../../errors'
6
6
 
7
7
  let db: Database
8
8
 
@@ -1,15 +1,15 @@
1
1
  import { describe, expect, test, beforeEach } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { initSchema, seedDefaultColumns, addTask } from '../../db.ts'
3
+ import { initSchema, seedDefaultColumns, addTask } from '../../db'
4
4
  import {
5
5
  columnAdd,
6
6
  columnList,
7
7
  columnRename,
8
8
  columnReorder,
9
9
  columnDelete,
10
- } from '../../commands/column.ts'
11
- import { KanbanError } from '../../errors.ts'
12
- import type { Column } from '../../types.ts'
10
+ } from '../../commands/column'
11
+ import { KanbanError } from '../../errors'
12
+ import type { Column } from '../../types'
13
13
 
14
14
  let db: Database
15
15
 
@@ -1,8 +1,8 @@
1
1
  import { beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { initSchema, seedDefaultColumns } from '../db.ts'
4
- import { LocalProvider } from '../providers/local.ts'
5
- import { ErrorCode, KanbanError } from '../errors.ts'
3
+ import { initSchema, seedDefaultColumns } from '../db'
4
+ import { LocalProvider } from '../providers/local'
5
+ import { ErrorCode, KanbanError } from '../errors'
6
6
 
7
7
  let db: Database
8
8
  let provider: LocalProvider
@@ -25,8 +25,8 @@ import {
25
25
  bulkClearDone,
26
26
  resetBoard,
27
27
  migrateSchema,
28
- } from '../db.ts'
29
- import { KanbanError } from '../errors.ts'
28
+ } from '../db'
29
+ import { KanbanError } from '../errors'
30
30
 
31
31
  let db: Database
32
32
  let originalCwd: string
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { generateId } from '../id.ts'
2
+ import { generateId } from '../id'
3
3
 
4
4
  describe('generateId', () => {
5
5
  test('generates task IDs with t_ prefix', () => {
@@ -3,9 +3,9 @@ import { Database } from 'bun:sqlite'
3
3
  import { mkdtempSync, rmSync } from 'node:fs'
4
4
  import { join } from 'node:path'
5
5
  import { tmpdir } from 'node:os'
6
- import { ErrorCode, KanbanError, type ErrorCodeValue } from '../errors.ts'
7
- import type { Task } from '../types.ts'
8
- import { parseServeArgs, run } from '../index.ts'
6
+ import { ErrorCode, KanbanError, type ErrorCodeValue } from '../errors'
7
+ import type { Task } from '../types'
8
+ import { parseServeArgs, run } from '../index'
9
9
 
10
10
  async function withTempDb(runTest: (dbPath: string) => Promise<void>): Promise<void> {
11
11
  const dir = mkdtempSync(join(tmpdir(), 'kanban-run-'))
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { adfToPlainText, plainTextToAdf, type AdfDocument } from '../providers/jira-adf.ts'
2
+ import { adfToPlainText, plainTextToAdf, type AdfDocument } from '../providers/jira-adf'
3
3
 
4
4
  describe('plainTextToAdf / adfToPlainText', () => {
5
5
  test('empty doc round-trip', () => {
@@ -91,6 +91,18 @@ describe('plainTextToAdf / adfToPlainText', () => {
91
91
  expect(adfToPlainText(doc)).toBe(input)
92
92
  })
93
93
 
94
+ test('garage-baton fenced comment round-trips byte-for-byte', () => {
95
+ const input =
96
+ 'garage-triage: ✅ Accepted — abpai/garage-band\n\nIncrement SMOKE_TEST_TASK.md from current_count=1 to 2.\n\n```garage-baton\n{"v":1,"accepted":true,"repo":{"owner":"abpai","name":"garage-band"},"questions":[],"summary":"Increment smoke counter."}\n```'
97
+ const doc = plainTextToAdf(input)
98
+ const code = doc.content.find((node) => node.type === 'codeBlock') as
99
+ | { type: string; attrs?: { language?: string } }
100
+ | undefined
101
+
102
+ expect(code?.attrs?.language).toBe('garage-baton')
103
+ expect(adfToPlainText(doc)).toBe(input)
104
+ })
105
+
94
106
  test('mixed content (paragraph + list + code block) round-trip', () => {
95
107
  const input = 'intro paragraph\n\n- a\n- b\n\n```js\nconsole.log(1)\n```\n\nouttro'
96
108
  const doc = plainTextToAdf(input)
@@ -15,7 +15,7 @@ import {
15
15
  saveJiraSyncMeta,
16
16
  upsertJiraIssues,
17
17
  upsertJiraUsers,
18
- } from '../providers/jira-cache.ts'
18
+ } from '../providers/jira-cache'
19
19
 
20
20
  let db: Database
21
21
 
@@ -1,7 +1,7 @@
1
1
  import { afterEach, describe, expect, test } from 'bun:test'
2
2
  import { Buffer } from 'node:buffer'
3
- import { ErrorCode, KanbanError } from '../errors.ts'
4
- import { JiraClient } from '../providers/jira-client.ts'
3
+ import { ErrorCode, KanbanError } from '../errors'
4
+ import { JiraClient } from '../providers/jira-client'
5
5
 
6
6
  const origFetch = globalThis.fetch
7
7
  let lastRequest: { url: string; init?: RequestInit } | null = null
@@ -1,13 +1,13 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { JiraClient } from '../providers/jira-client.ts'
4
- import { JiraProvider, type JiraProviderConfig } from '../providers/jira.ts'
3
+ import { JiraClient } from '../providers/jira-client'
4
+ import { JiraProvider, type JiraProviderConfig } from '../providers/jira'
5
5
  import {
6
6
  initJiraCacheSchema,
7
7
  saveJiraSyncMeta,
8
8
  saveTeamInfo,
9
9
  upsertJiraIssues,
10
- } from '../providers/jira-cache.ts'
10
+ } from '../providers/jira-cache'
11
11
 
12
12
  const baseConfig: JiraProviderConfig = {
13
13
  baseUrl: 'https://example.atlassian.net',
@@ -1,8 +1,8 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { ErrorCode, KanbanError } from '../errors.ts'
4
- import { JiraClient } from '../providers/jira-client.ts'
5
- import { JiraProvider, type JiraProviderConfig } from '../providers/jira.ts'
3
+ import { ErrorCode, KanbanError } from '../errors'
4
+ import { JiraClient } from '../providers/jira-client'
5
+ import { JiraProvider, type JiraProviderConfig } from '../providers/jira'
6
6
  import {
7
7
  initJiraCacheSchema,
8
8
  replaceJiraColumns,
@@ -12,7 +12,7 @@ import {
12
12
  saveTeamInfo,
13
13
  upsertJiraIssues,
14
14
  upsertJiraUsers,
15
- } from '../providers/jira-cache.ts'
15
+ } from '../providers/jira-cache'
16
16
 
17
17
  type FetchInit = RequestInit | undefined
18
18
  type StubCall = { url: string; method: string; body: string | null }
@@ -1,8 +1,8 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { ErrorCode, KanbanError } from '../errors.ts'
4
- import { JiraClient } from '../providers/jira-client.ts'
5
- import { JiraProvider, type JiraProviderConfig } from '../providers/jira.ts'
3
+ import { ErrorCode, KanbanError } from '../errors'
4
+ import { JiraClient } from '../providers/jira-client'
5
+ import { JiraProvider, type JiraProviderConfig } from '../providers/jira'
6
6
  import {
7
7
  getCachedColumns,
8
8
  getCachedTasks,
@@ -11,7 +11,7 @@ import {
11
11
  saveJiraSyncMeta,
12
12
  saveTeamInfo,
13
13
  initJiraCacheSchema,
14
- } from '../providers/jira-cache.ts'
14
+ } from '../providers/jira-cache'
15
15
 
16
16
  type FetchInit = RequestInit | undefined
17
17
  type StubCall = { url: string; init?: FetchInit }
@@ -353,7 +353,7 @@ describe('JiraProvider read path', () => {
353
353
  // Sync first (populates statuses-based columns)
354
354
  await provider.getBoard()
355
355
  // Overwrite with the test-scenario columns + issues directly.
356
- const { replaceJiraColumns, upsertJiraIssues } = await import('../providers/jira-cache.ts')
356
+ const { replaceJiraColumns, upsertJiraIssues } = await import('../providers/jira-cache')
357
357
  replaceJiraColumns(db, [
358
358
  {
359
359
  id: 'board:3:Done',
@@ -3,9 +3,9 @@ import { Database } from 'bun:sqlite'
3
3
  import { mkdtempSync, rmSync } from 'node:fs'
4
4
  import { tmpdir } from 'node:os'
5
5
  import { join } from 'node:path'
6
- import { ErrorCode, KanbanError } from '../errors.ts'
7
- import { run } from '../index.ts'
8
- import { createProvider } from '../providers/index.ts'
6
+ import { ErrorCode, KanbanError } from '../errors'
7
+ import { run } from '../index'
8
+ import { createProvider } from '../providers/index'
9
9
 
10
10
  const ENV_KEYS = [
11
11
  'KANBAN_PROVIDER',
@@ -6,7 +6,7 @@ import {
6
6
  getCachedLinearActivity,
7
7
  initLinearCacheSchema,
8
8
  upsertIssues,
9
- } from '../providers/linear-cache.ts'
9
+ } from '../providers/linear-cache'
10
10
 
11
11
  function mkIssue(overrides: Partial<Parameters<typeof upsertIssues>[1][0]> = {}) {
12
12
  return {
@@ -1,12 +1,12 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { LinearProvider } from '../providers/linear.ts'
3
+ import { LinearProvider } from '../providers/linear'
4
4
  import {
5
5
  initLinearCacheSchema,
6
6
  replaceStates,
7
7
  saveSyncMeta,
8
8
  upsertIssues,
9
- } from '../providers/linear-cache.ts'
9
+ } from '../providers/linear-cache'
10
10
 
11
11
  let db: Database
12
12
  let originalFetch: typeof fetch
@@ -1,6 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { LinearProvider } from '../providers/linear.ts'
3
+ import { LinearProvider } from '../providers/linear'
4
4
  import {
5
5
  getCachedTasks,
6
6
  initLinearCacheSchema,
@@ -8,7 +8,7 @@ import {
8
8
  replaceStates,
9
9
  saveSyncMeta,
10
10
  upsertIssues,
11
- } from '../providers/linear-cache.ts'
11
+ } from '../providers/linear-cache'
12
12
 
13
13
  let db: Database
14
14
  let originalFetch: typeof fetch
@@ -38,7 +38,6 @@ function linearIssue(
38
38
  state: { id: string; name: string; position: number }
39
39
  labels: { nodes: Array<{ id: string; name: string }> }
40
40
  comments: {
41
- totalCount?: number
42
41
  nodes: Array<{ id: string }>
43
42
  pageInfo?: { hasNextPage: boolean; endCursor: string | null }
44
43
  }
@@ -57,7 +56,7 @@ function linearIssue(
57
56
  project: null,
58
57
  state: { id: 'state-1', name: 'Todo', position: 0 },
59
58
  labels: { nodes: [] },
60
- comments: { totalCount: 0, nodes: [], pageInfo: { hasNextPage: false, endCursor: null } },
59
+ comments: { nodes: [], pageInfo: { hasNextPage: false, endCursor: null } },
61
60
  ...overrides,
62
61
  }
63
62
  }
@@ -162,7 +161,6 @@ describe('LinearProvider sync', () => {
162
161
  state: { id: 'state-1', name: 'Todo', position: 0 },
163
162
  labels: { nodes: [] },
164
163
  comments: {
165
- totalCount: 0,
166
164
  nodes: [],
167
165
  pageInfo: { hasNextPage: false, endCursor: null },
168
166
  },
@@ -258,9 +256,7 @@ describe('LinearProvider sync', () => {
258
256
  JSON.stringify({
259
257
  data: {
260
258
  issues: {
261
- nodes: [
262
- linearIssue({ comments: { totalCount: 2, nodes: [{ id: 'c1' }, { id: 'c2' }] } }),
263
- ],
259
+ nodes: [linearIssue({ comments: { nodes: [{ id: 'c1' }, { id: 'c2' }] } })],
264
260
  pageInfo: { hasNextPage: false, endCursor: null },
265
261
  },
266
262
  },
@@ -368,7 +364,6 @@ describe('LinearProvider sync', () => {
368
364
  nodes: [
369
365
  linearIssue({
370
366
  comments: {
371
- totalCount: 7,
372
367
  nodes: Array.from({ length: 7 }, (_, index) => ({ id: `c${index}` })),
373
368
  },
374
369
  }),
@@ -1,7 +1,7 @@
1
1
  import { beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { initSchema, seedDefaultColumns, addTask } from '../db.ts'
4
- import { LocalProvider } from '../providers/local.ts'
3
+ import { initSchema, seedDefaultColumns, addTask } from '../db'
4
+ import { LocalProvider } from '../providers/local'
5
5
 
6
6
  let db: Database
7
7
  let provider: LocalProvider
@@ -1,9 +1,9 @@
1
1
  import { beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { addTask, initSchema, seedDefaultColumns } from '../db.ts'
4
- import { createTrackerCore } from '../mcp/core.ts'
5
- import { TrackerMcpError } from '../mcp/errors.ts'
6
- import { LocalProvider } from '../providers/local.ts'
3
+ import { addTask, initSchema, seedDefaultColumns } from '../db'
4
+ import { createTrackerCore } from '../mcp/core'
5
+ import { TrackerMcpError } from '../mcp/errors'
6
+ import { LocalProvider } from '../providers/local'
7
7
 
8
8
  interface TestScope {
9
9
  actor: string
@@ -2,9 +2,9 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
3
  import { Client } from '@modelcontextprotocol/sdk/client'
4
4
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
5
- import { addTask, initSchema, seedDefaultColumns } from '../db.ts'
6
- import { createTrackerCore, createTrackerMcpServer, TrackerMcpError } from '../mcp/index.ts'
7
- import { LocalProvider } from '../providers/local.ts'
5
+ import { addTask, initSchema, seedDefaultColumns } from '../db'
6
+ import { createTrackerCore, createTrackerMcpServer, TrackerMcpError } from '../mcp/index'
7
+ import { LocalProvider } from '../providers/local'
8
8
 
9
9
  interface TestScope {
10
10
  actor: string
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, test, beforeEach } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
- import { initSchema, seedDefaultColumns, addTask } from '../db.ts'
4
- import { getBoardMetrics } from '../metrics.ts'
3
+ import { initSchema, seedDefaultColumns, addTask } from '../db'
4
+ import { getBoardMetrics } from '../metrics'
5
5
 
6
6
  let db: Database
7
7
 
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { success, error, formatOutput } from '../output.ts'
2
+ import { success, error, formatOutput } from '../output'
3
3
 
4
4
  describe('success', () => {
5
5
  test('wraps data in ok envelope', () => {
@@ -0,0 +1,40 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import {
4
+ JIRA_CAPABILITIES,
5
+ LINEAR_CAPABILITIES,
6
+ LOCAL_CAPABILITIES,
7
+ } from '../providers/capabilities'
8
+
9
+ describe('provider capabilities', () => {
10
+ test('remote providers share the same read/write baseline', () => {
11
+ expect(LINEAR_CAPABILITIES).toEqual(JIRA_CAPABILITIES)
12
+ expect(LINEAR_CAPABILITIES).toEqual({
13
+ taskCreate: true,
14
+ taskUpdate: true,
15
+ taskMove: true,
16
+ taskDelete: false,
17
+ comment: true,
18
+ activity: false,
19
+ metrics: false,
20
+ columnCrud: false,
21
+ bulk: false,
22
+ configEdit: false,
23
+ })
24
+ })
25
+
26
+ test('local provider exposes the full local board surface', () => {
27
+ expect(LOCAL_CAPABILITIES).toEqual({
28
+ taskCreate: true,
29
+ taskUpdate: true,
30
+ taskMove: true,
31
+ taskDelete: true,
32
+ comment: true,
33
+ activity: true,
34
+ metrics: true,
35
+ columnCrud: true,
36
+ bulk: true,
37
+ configEdit: true,
38
+ })
39
+ })
40
+ })
@@ -1,13 +1,6 @@
1
1
  import { afterEach, describe, expect, test } from 'bun:test'
2
- import type {
3
- BoardBootstrap,
4
- BoardConfig,
5
- BoardMetrics,
6
- BoardView,
7
- Column,
8
- Task,
9
- } from '../types.ts'
10
- import { startServer, type StartedServer } from '../server.ts'
2
+ import type { BoardBootstrap, BoardConfig, BoardMetrics, BoardView, Column, Task } from '../types'
3
+ import { startServer, type StartedServer } from '../server'
11
4
  import type {
12
5
  CreateTaskInput,
13
6
  KanbanProvider,
@@ -15,7 +8,7 @@ import type {
15
8
  ProviderSyncStatus,
16
9
  TaskListFilters,
17
10
  UpdateTaskInput,
18
- } from '../providers/types.ts'
11
+ } from '../providers/types'
19
12
 
20
13
  const emptyBoard: BoardView = { columns: [] }
21
14
  const emptyConfig: BoardConfig = { members: [], projects: [], provider: 'local' }
@@ -1,10 +1,10 @@
1
1
  import { beforeEach, afterEach, describe, expect, test } from 'bun:test'
2
2
  import { Database } from 'bun:sqlite'
3
3
  import { createHmac } from 'node:crypto'
4
- import { verifyHmacSha256 } from '../webhooks.ts'
5
- import { JiraProvider, type JiraProviderConfig } from '../providers/jira.ts'
6
- import { JiraClient } from '../providers/jira-client.ts'
7
- import { LinearProvider } from '../providers/linear.ts'
4
+ import { verifyHmacSha256 } from '../webhooks'
5
+ import { JiraProvider, type JiraProviderConfig } from '../providers/jira'
6
+ import { JiraClient } from '../providers/jira-client'
7
+ import { LinearProvider } from '../providers/linear'
8
8
  import {
9
9
  getCachedActivity,
10
10
  getCachedTasks as getCachedJiraTasks,
@@ -13,7 +13,7 @@ import {
13
13
  saveTeamInfo,
14
14
  replaceJiraColumns,
15
15
  upsertJiraIssues,
16
- } from '../providers/jira-cache.ts'
16
+ } from '../providers/jira-cache'
17
17
  import {
18
18
  getCachedTasks as getCachedLinearTasks,
19
19
  initLinearCacheSchema,
@@ -21,7 +21,7 @@ import {
21
21
  replaceStates,
22
22
  saveSyncMeta,
23
23
  upsertIssues,
24
- } from '../providers/linear-cache.ts'
24
+ } from '../providers/linear-cache'
25
25
 
26
26
  function hmac(secret: string, body: string): string {
27
27
  return createHmac('sha256', secret).update(body).digest('hex')
package/src/activity.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Database } from 'bun:sqlite'
2
- import { generateId } from './id.ts'
3
- import type { ActivityEntry, ActivityAction } from './types.ts'
2
+ import { generateId } from './id'
3
+ import type { ActivityEntry, ActivityAction } from './types'
4
4
 
5
5
  export function logActivity(
6
6
  db: Database,
package/src/api.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { KanbanError, ErrorCode } from './errors.ts'
2
- import type { BoardConfig, CliOutput, Task } from './types.ts'
3
- import type { CreateTaskInput, UpdateTaskInput, KanbanProvider } from './providers/types.ts'
1
+ import { KanbanError, ErrorCode } from './errors'
2
+ import type { BoardConfig, CliOutput, Task } from './types'
3
+ import type { CreateTaskInput, UpdateTaskInput, KanbanProvider } from './providers/types'
4
4
 
5
5
  export type WsEvent =
6
6
  | { type: 'task:upsert'; task: Task; columnName: string }
@@ -1,8 +1,8 @@
1
1
  import type { Database } from 'bun:sqlite'
2
- import { initSchema, seedDefaultColumns, isInitialized, resetBoard } from '../db.ts'
3
- import { ErrorCode, KanbanError } from '../errors.ts'
4
- import { success } from '../output.ts'
5
- import type { CliOutput } from '../types.ts'
2
+ import { initSchema, seedDefaultColumns, isInitialized, resetBoard } from '../db'
3
+ import { ErrorCode, KanbanError } from '../errors'
4
+ import { success } from '../output'
5
+ import type { CliOutput } from '../types'
6
6
 
7
7
  export function boardInit(db: Database): CliOutput {
8
8
  if (isInitialized(db)) {
@@ -1,8 +1,8 @@
1
1
  import type { Database } from 'bun:sqlite'
2
- import { bulkMoveAll, bulkClearDone } from '../db.ts'
3
- import { ErrorCode, KanbanError } from '../errors.ts'
4
- import { success } from '../output.ts'
5
- import type { CliOutput } from '../types.ts'
2
+ import { bulkMoveAll, bulkClearDone } from '../db'
3
+ import { ErrorCode, KanbanError } from '../errors'
4
+ import { success } from '../output'
5
+ import type { CliOutput } from '../types'
6
6
 
7
7
  export function bulkMoveAllCmd(db: Database, args: { from?: string; to?: string }): CliOutput {
8
8
  if (!args.from || !args.to) {
@@ -1,8 +1,8 @@
1
1
  import type { Database } from 'bun:sqlite'
2
- import { addColumn, listColumns, renameColumn, reorderColumn, deleteColumn } from '../db.ts'
3
- import { ErrorCode, KanbanError } from '../errors.ts'
4
- import { success } from '../output.ts'
5
- import type { CliOutput } from '../types.ts'
2
+ import { addColumn, listColumns, renameColumn, reorderColumn, deleteColumn } from '../db'
3
+ import { ErrorCode, KanbanError } from '../errors'
4
+ import { success } from '../output'
5
+ import type { CliOutput } from '../types'
6
6
 
7
7
  export function columnAdd(
8
8
  db: Database,
@@ -6,9 +6,9 @@ import {
6
6
  type CallToolResult,
7
7
  } from '@modelcontextprotocol/sdk/types.js'
8
8
  import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv'
9
- import { createTrackerCore, defaultTools, TrackerMcpError } from '../mcp/index.ts'
10
- import type { TrackerMcpPolicy, TrackerMcpTool } from '../mcp/index.ts'
11
- import type { KanbanProvider } from '../providers/types.ts'
9
+ import { createTrackerCore, defaultTools, TrackerMcpError } from '../mcp/index'
10
+ import type { TrackerMcpPolicy, TrackerMcpTool } from '../mcp/index'
11
+ import type { KanbanProvider } from '../providers/types'
12
12
 
13
13
  type LocalScope = Record<string, never>
14
14
 
package/src/config.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, writeFileSync, renameSync } from 'node:fs'
2
2
  import { dirname, join } from 'node:path'
3
- import type { BoardConfig } from './types.ts'
3
+ import type { BoardConfig } from './types'
4
4
 
5
5
  const DEFAULT_CONFIG: BoardConfig = { members: [], projects: [] }
6
6
 
package/src/db.ts CHANGED
@@ -2,10 +2,10 @@ import { Database } from 'bun:sqlite'
2
2
  import { existsSync, mkdirSync } from 'node:fs'
3
3
  import { homedir } from 'node:os'
4
4
  import { join } from 'node:path'
5
- import { generateId } from './id.ts'
6
- import { ErrorCode, KanbanError } from './errors.ts'
7
- import type { BoardView, Column, Priority, Task, TaskComment, TaskWithColumn } from './types.ts'
8
- import { logActivity, enterColumn, exitColumn } from './activity.ts'
5
+ import { generateId } from './id'
6
+ import { ErrorCode, KanbanError } from './errors'
7
+ import type { BoardView, Column, Priority, Task, TaskComment, TaskWithColumn } from './types'
8
+ import { logActivity, enterColumn, exitColumn } from './activity'
9
9
 
10
10
  const DEFAULT_COLUMNS = [
11
11
  { name: 'recurring', position: 0 },
package/src/index.ts CHANGED
@@ -2,22 +2,16 @@
2
2
 
3
3
  import { parseArgs } from 'node:util'
4
4
  import { Database } from 'bun:sqlite'
5
- import { KanbanError, ErrorCode } from './errors.ts'
6
- import { formatOutput, error, success } from './output.ts'
7
- import { openDb, getDbPath, initSchema, migrateSchema, seedDefaultColumns } from './db.ts'
8
- import { boardInit, boardReset } from './commands/board.ts'
9
- import {
10
- columnAdd,
11
- columnDelete,
12
- columnList,
13
- columnRename,
14
- columnReorder,
15
- } from './commands/column.ts'
16
- import { bulkClearDoneCmd, bulkMoveAllCmd } from './commands/bulk.ts'
17
- import { getConfigPath, loadConfig, saveConfig } from './config.ts'
18
- import type { CliOutput, Priority } from './types.ts'
19
- import { createProvider } from './providers/index.ts'
20
- import { unsupportedOperation } from './providers/errors.ts'
5
+ import { KanbanError, ErrorCode } from './errors'
6
+ import { formatOutput, error, success } from './output'
7
+ import { openDb, getDbPath, initSchema, migrateSchema, seedDefaultColumns } from './db'
8
+ import { boardInit, boardReset } from './commands/board'
9
+ import { columnAdd, columnDelete, columnList, columnRename, columnReorder } from './commands/column'
10
+ import { bulkClearDoneCmd, bulkMoveAllCmd } from './commands/bulk'
11
+ import { getConfigPath, loadConfig, saveConfig } from './config'
12
+ import type { CliOutput, Priority } from './types'
13
+ import { createProvider } from './providers/index'
14
+ import { unsupportedOperation } from './providers/errors'
21
15
 
22
16
  interface ParsedArgs {
23
17
  values: Record<string, unknown>
@@ -415,7 +409,7 @@ if (import.meta.main) {
415
409
  const db = openDb(dbPath)
416
410
  migrateSchema(db)
417
411
  const provider = createProvider(db, dbPath)
418
- const { startStdioMcpServer } = await import('./commands/mcp.ts')
412
+ const { startStdioMcpServer } = await import('./commands/mcp')
419
413
  await startStdioMcpServer(provider)
420
414
  } else if (argv[0] === 'serve') {
421
415
  const opts = parseServeArgs(argv)
@@ -424,11 +418,11 @@ if (import.meta.main) {
424
418
  const db = openDb(dbPath)
425
419
  migrateSchema(db)
426
420
  const provider = createProvider(db, dbPath)
427
- const { startServer } = await import('./server.ts')
421
+ const { startServer } = await import('./server')
428
422
  startServer(provider, opts.port)
429
423
 
430
424
  if (opts.tunnel) {
431
- const { startCloudflareTunnel } = await import('./tunnel.ts')
425
+ const { startCloudflareTunnel } = await import('./tunnel')
432
426
  try {
433
427
  const handle = startCloudflareTunnel(opts.port)
434
428
  const shutdown = (): void => {
package/src/mcp/core.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { KanbanProvider } from '../providers/types.ts'
2
- import type { Task, TaskComment } from '../types.ts'
3
- import { TrackerMcpError, toTrackerMcpError } from './errors.ts'
4
- import type { TrackerMcpHooks, TrackerMcpPolicy } from './types.ts'
1
+ import type { KanbanProvider } from '../providers/types'
2
+ import type { Task, TaskComment } from '../types'
3
+ import { TrackerMcpError, toTrackerMcpError } from './errors'
4
+ import type { TrackerMcpHooks, TrackerMcpPolicy } from './types'
5
5
 
6
6
  export interface TrackerCore<TScope> {
7
7
  notifyAuthFailure(input: {
package/src/mcp/errors.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { McpError, ErrorCode as JsonRpcErrorCode } from '@modelcontextprotocol/sdk/types.js'
2
- import { ErrorCode, type ErrorCodeValue, KanbanError } from '../errors.ts'
2
+ import { ErrorCode, type ErrorCodeValue, KanbanError } from '../errors'
3
3
 
4
4
  export type TrackerMcpErrorCode =
5
5
  | 'auth_failed'
package/src/mcp/index.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { createTrackerCore } from './core.ts'
2
- export { createTrackerMcpServer } from './server.ts'
3
- export { TrackerMcpError, type TrackerMcpErrorCode } from './errors.ts'
4
- export type { TrackerCore } from './core.ts'
1
+ export { createTrackerCore } from './core'
2
+ export { createTrackerMcpServer } from './server'
3
+ export { TrackerMcpError, type TrackerMcpErrorCode } from './errors'
4
+ export type { TrackerCore } from './core'
5
5
  export type {
6
6
  TrackerMcpAuthResolver,
7
7
  TrackerMcpHooks,
@@ -9,5 +9,5 @@ export type {
9
9
  TrackerMcpServer,
10
10
  TrackerMcpTool,
11
11
  TrackerMcpToolHandlerContext,
12
- } from './types.ts'
13
- export { defaultTools } from './server.ts'
12
+ } from './types'
13
+ export { defaultTools } from './server'
package/src/mcp/server.ts CHANGED
@@ -13,9 +13,9 @@ import type {
13
13
  JsonSchemaValidatorResult,
14
14
  JsonSchemaType,
15
15
  } from '@modelcontextprotocol/sdk/validation'
16
- import type { TrackerCore } from './core.ts'
17
- import { TrackerMcpError, toMcpError, toTrackerMcpError, trackerMcpJsonRpcCode } from './errors.ts'
18
- import type { TrackerMcpAuthResolver, TrackerMcpServer, TrackerMcpTool } from './types.ts'
16
+ import type { TrackerCore } from './core'
17
+ import { TrackerMcpError, toMcpError, toTrackerMcpError, trackerMcpJsonRpcCode } from './errors'
18
+ import type { TrackerMcpAuthResolver, TrackerMcpServer, TrackerMcpTool } from './types'
19
19
 
20
20
  const EMPTY_OBJECT_SCHEMA = {
21
21
  type: 'object',
package/src/mcp/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'
2
2
  import type { JsonSchemaType } from '@modelcontextprotocol/sdk/validation'
3
- import type { TaskComment } from '../types.ts'
4
- import type { TrackerMcpError, TrackerMcpErrorCode } from './errors.ts'
3
+ import type { TaskComment } from '../types'
4
+ import type { TrackerMcpError, TrackerMcpErrorCode } from './errors'
5
5
 
6
6
  export type TrackerMcpAuthResolver<TScope> = (ctx: {
7
7
  request: Request
package/src/metrics.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Database } from 'bun:sqlite'
2
- import type { ActivityEntry, BoardMetrics } from './types.ts'
2
+ import type { ActivityEntry, BoardMetrics } from './types'
3
3
 
4
4
  function getDistinctTaskFieldValues(db: Database, field: 'assignee' | 'project'): string[] {
5
5
  return (
package/src/output.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CliOutput, BoardView, Task, TaskWithColumn, Column } from './types.ts'
1
+ import type { CliOutput, BoardView, Task, TaskWithColumn, Column } from './types'
2
2
 
3
3
  export function success<T>(data: T): CliOutput<T> {
4
4
  return { ok: true, data }
@@ -1,40 +1,30 @@
1
- import type { ProviderCapabilities } from '../types.ts'
1
+ import type { ProviderCapabilities } from '../types'
2
2
 
3
- export const LOCAL_CAPABILITIES: ProviderCapabilities = {
4
- taskCreate: true,
5
- taskUpdate: true,
6
- taskMove: true,
3
+ function capabilities(overrides: Partial<ProviderCapabilities> = {}): ProviderCapabilities {
4
+ return {
5
+ taskCreate: true,
6
+ taskUpdate: true,
7
+ taskMove: true,
8
+ taskDelete: false,
9
+ comment: true,
10
+ activity: false,
11
+ metrics: false,
12
+ columnCrud: false,
13
+ bulk: false,
14
+ configEdit: false,
15
+ ...overrides,
16
+ }
17
+ }
18
+
19
+ export const LOCAL_CAPABILITIES: ProviderCapabilities = capabilities({
7
20
  taskDelete: true,
8
- comment: true,
9
21
  activity: true,
10
22
  metrics: true,
11
23
  columnCrud: true,
12
24
  bulk: true,
13
25
  configEdit: true,
14
- }
26
+ })
15
27
 
16
- export const LINEAR_CAPABILITIES: ProviderCapabilities = {
17
- taskCreate: true,
18
- taskUpdate: true,
19
- taskMove: true,
20
- taskDelete: false,
21
- comment: true,
22
- activity: false,
23
- metrics: false,
24
- columnCrud: false,
25
- bulk: false,
26
- configEdit: false,
27
- }
28
+ export const LINEAR_CAPABILITIES: ProviderCapabilities = capabilities()
28
29
 
29
- export const JIRA_CAPABILITIES: ProviderCapabilities = {
30
- taskCreate: true,
31
- taskUpdate: true,
32
- taskMove: true,
33
- taskDelete: false,
34
- comment: true,
35
- activity: false,
36
- metrics: false,
37
- columnCrud: false,
38
- bulk: false,
39
- configEdit: false,
40
- }
30
+ export const JIRA_CAPABILITIES: ProviderCapabilities = capabilities()
@@ -1,4 +1,4 @@
1
- import { ErrorCode, type ErrorCodeValue, KanbanError } from '../errors.ts'
1
+ import { ErrorCode, type ErrorCodeValue, KanbanError } from '../errors'
2
2
 
3
3
  export function unsupportedOperation(message: string): never {
4
4
  throw new KanbanError(ErrorCode.UNSUPPORTED_OPERATION, message)
@@ -1,10 +1,10 @@
1
1
  import type { Database } from 'bun:sqlite'
2
- import { getDbPath, initSchema, seedDefaultColumns } from '../db.ts'
3
- import { providerNotConfigured } from './errors.ts'
4
- import { JiraProvider } from './jira.ts'
5
- import { LinearProvider } from './linear.ts'
6
- import { LocalProvider } from './local.ts'
7
- import type { KanbanProvider } from './types.ts'
2
+ import { getDbPath, initSchema, seedDefaultColumns } from '../db'
3
+ import { providerNotConfigured } from './errors'
4
+ import { JiraProvider } from './jira'
5
+ import { LinearProvider } from './linear'
6
+ import { LocalProvider } from './local'
7
+ import type { KanbanProvider } from './types'
8
8
 
9
9
  export function createProvider(db: Database, dbPath = getDbPath()): KanbanProvider {
10
10
  const providerType = (process.env['KANBAN_PROVIDER'] ?? 'local') as 'local' | 'linear' | 'jira'
@@ -1,5 +1,5 @@
1
1
  import type { Database } from 'bun:sqlite'
2
- import type { BoardView, ProviderTeamInfo, Task } from '../types.ts'
2
+ import type { BoardView, ProviderTeamInfo, Task } from '../types'
3
3
 
4
4
  // Column ids are prefixed to avoid collisions across sources:
5
5
  // - board-sourced columns: 'board:<boardId>:<columnName>'
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer'
2
- import { ErrorCode } from '../errors.ts'
3
- import { providerUpstreamError } from './errors.ts'
2
+ import { ErrorCode } from '../errors'
3
+ import { providerUpstreamError } from './errors'
4
4
 
5
5
  export interface JiraProject {
6
6
  id: string
@@ -1,5 +1,5 @@
1
1
  import type { Database } from 'bun:sqlite'
2
- import { ErrorCode, KanbanError } from '../errors.ts'
2
+ import { ErrorCode, KanbanError } from '../errors'
3
3
  import type {
4
4
  ActivityEntry,
5
5
  BoardBootstrap,
@@ -10,17 +10,12 @@ import type {
10
10
  Priority,
11
11
  TaskComment,
12
12
  Task,
13
- } from '../types.ts'
14
- import {
15
- headerLower,
16
- verifyHmacSha256,
17
- type WebhookRequest,
18
- type WebhookResult,
19
- } from '../webhooks.ts'
20
- import { adfToPlainText, plainTextToAdf, type AdfDocument } from './jira-adf.ts'
21
- import { JIRA_CAPABILITIES } from './capabilities.ts'
22
- import { providerUpstreamError, unsupportedOperation } from './errors.ts'
23
- import { JiraClient, type JiraComment, type JiraIssue } from './jira-client.ts'
13
+ } from '../types'
14
+ import { headerLower, verifyHmacSha256, type WebhookRequest, type WebhookResult } from '../webhooks'
15
+ import { adfToPlainText, plainTextToAdf, type AdfDocument } from './jira-adf'
16
+ import { JIRA_CAPABILITIES } from './capabilities'
17
+ import { providerUpstreamError, unsupportedOperation } from './errors'
18
+ import { JiraClient, type JiraComment, type JiraIssue } from './jira-client'
24
19
  import {
25
20
  adjustJiraIssueCommentCount,
26
21
  decodeColumnStatusIds,
@@ -45,7 +40,7 @@ import {
45
40
  upsertJiraUsers,
46
41
  type JiraActivityRow,
47
42
  type JiraSyncMeta,
48
- } from './jira-cache.ts'
43
+ } from './jira-cache'
49
44
  import type {
50
45
  CreateTaskInput,
51
46
  KanbanProvider,
@@ -53,7 +48,7 @@ import type {
53
48
  ProviderSyncStatus,
54
49
  TaskListFilters,
55
50
  UpdateTaskInput,
56
- } from './types.ts'
51
+ } from './types'
57
52
 
58
53
  const SYNC_INTERVAL_MS = 30_000
59
54
  const FULL_RECONCILE_INTERVAL_MS = 5 * 60_000
@@ -1,5 +1,5 @@
1
1
  import type { Database } from 'bun:sqlite'
2
- import type { BoardConfig, BoardView, ProviderTeamInfo, Task } from '../types.ts'
2
+ import type { BoardConfig, BoardView, ProviderTeamInfo, Task } from '../types'
3
3
 
4
4
  export interface LinearStateRow {
5
5
  id: string
@@ -1,5 +1,5 @@
1
- import { ErrorCode } from '../errors.ts'
2
- import { providerUpstreamError } from './errors.ts'
1
+ import { ErrorCode } from '../errors'
2
+ import { providerUpstreamError } from './errors'
3
3
 
4
4
  interface GraphQLResponse<T> {
5
5
  data?: T
@@ -57,7 +57,6 @@ interface LinearIssueNode {
57
57
  state: { id: string; name: string; position: number }
58
58
  labels?: { nodes: Array<{ id: string; name: string }> }
59
59
  comments?: {
60
- totalCount?: number
61
60
  nodes: Array<{ id: string }>
62
61
  pageInfo?: { hasNextPage: boolean; endCursor: string | null }
63
62
  } | null
@@ -81,7 +80,7 @@ function toLinearIssue(node: LinearIssueNode): LinearIssue {
81
80
  }
82
81
  : null,
83
82
  labels: node.labels?.nodes.map((label) => label.name) ?? [],
84
- commentCount: node.comments?.totalCount ?? node.comments?.nodes?.length ?? undefined,
83
+ commentCount: node.comments?.nodes?.length ?? undefined,
85
84
  }
86
85
  }
87
86
 
@@ -284,7 +283,6 @@ export class LinearClient {
284
283
  nodes { id name }
285
284
  }
286
285
  comments(first: 250) {
287
- totalCount
288
286
  nodes { id }
289
287
  pageInfo { hasNextPage endCursor }
290
288
  }
@@ -335,7 +333,6 @@ export class LinearClient {
335
333
  state { id name position }
336
334
  labels { nodes { id name } }
337
335
  comments(first: 250) {
338
- totalCount
339
336
  nodes { id }
340
337
  pageInfo { hasNextPage endCursor }
341
338
  }
@@ -1,5 +1,5 @@
1
1
  import type { Database } from 'bun:sqlite'
2
- import { ErrorCode, KanbanError } from '../errors.ts'
2
+ import { ErrorCode, KanbanError } from '../errors'
3
3
  import type {
4
4
  ActivityEntry,
5
5
  BoardBootstrap,
@@ -8,14 +8,9 @@ import type {
8
8
  Column,
9
9
  TaskComment,
10
10
  Task,
11
- } from '../types.ts'
12
- import {
13
- headerLower,
14
- verifyHmacSha256,
15
- type WebhookRequest,
16
- type WebhookResult,
17
- } from '../webhooks.ts'
18
- import { LINEAR_CAPABILITIES } from './capabilities.ts'
11
+ } from '../types'
12
+ import { headerLower, verifyHmacSha256, type WebhookRequest, type WebhookResult } from '../webhooks'
13
+ import { LINEAR_CAPABILITIES } from './capabilities'
19
14
  import {
20
15
  adjustLinearIssueCommentCount,
21
16
  deleteLinearIssue,
@@ -35,9 +30,9 @@ import {
35
30
  upsertProjects,
36
31
  upsertUsers,
37
32
  type LinearActivityRow,
38
- } from './linear-cache.ts'
39
- import { LinearClient, type LinearComment } from './linear-client.ts'
40
- import { unsupportedOperation } from './errors.ts'
33
+ } from './linear-cache'
34
+ import { LinearClient, type LinearComment } from './linear-client'
35
+ import { unsupportedOperation } from './errors'
41
36
  import type {
42
37
  CreateTaskInput,
43
38
  KanbanProvider,
@@ -45,7 +40,7 @@ import type {
45
40
  ProviderSyncStatus,
46
41
  TaskListFilters,
47
42
  UpdateTaskInput,
48
- } from './types.ts'
43
+ } from './types'
49
44
 
50
45
  const SYNC_INTERVAL_MS = 30_000
51
46
  const FULL_RECONCILIATION_INTERVAL_MS = 5 * 60_000
@@ -1,6 +1,6 @@
1
1
  import type { Database } from 'bun:sqlite'
2
- import { listActivity } from '../activity.ts'
3
- import { getConfigPath, loadConfig, saveConfig } from '../config.ts'
2
+ import { listActivity } from '../activity'
3
+ import { getConfigPath, loadConfig, saveConfig } from '../config'
4
4
  import {
5
5
  addComment,
6
6
  countComments,
@@ -17,11 +17,11 @@ import {
17
17
  moveTask,
18
18
  updateComment as updateTaskComment,
19
19
  updateTask,
20
- } from '../db.ts'
21
- import { getBoardMetrics, getDiscoveredAssignees, getDiscoveredProjects } from '../metrics.ts'
22
- import type { BoardBootstrap, BoardConfig, Task, TaskComment } from '../types.ts'
23
- import { ErrorCode, KanbanError } from '../errors.ts'
24
- import { LOCAL_CAPABILITIES } from './capabilities.ts'
20
+ } from '../db'
21
+ import { getBoardMetrics, getDiscoveredAssignees, getDiscoveredProjects } from '../metrics'
22
+ import type { BoardBootstrap, BoardConfig, Task, TaskComment } from '../types'
23
+ import { ErrorCode, KanbanError } from '../errors'
24
+ import { LOCAL_CAPABILITIES } from './capabilities'
25
25
  import type {
26
26
  CreateTaskInput,
27
27
  KanbanProvider,
@@ -29,7 +29,7 @@ import type {
29
29
  ProviderSyncStatus,
30
30
  TaskListFilters,
31
31
  UpdateTaskInput,
32
- } from './types.ts'
32
+ } from './types'
33
33
 
34
34
  function buildLocalConfig(
35
35
  db: Database,
@@ -1,4 +1,4 @@
1
- import type { WebhookRequest, WebhookResult } from '../webhooks.ts'
1
+ import type { WebhookRequest, WebhookResult } from '../webhooks'
2
2
  import type {
3
3
  ActivityEntry,
4
4
  BoardBootstrap,
@@ -11,7 +11,7 @@ import type {
11
11
  ProviderTeamInfo,
12
12
  TaskComment,
13
13
  Task,
14
- } from '../types.ts'
14
+ } from '../types'
15
15
 
16
16
  export interface TaskListFilters {
17
17
  column?: string
package/src/server.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
- import { handleRequest } from './api.ts'
3
+ import { handleRequest } from './api'
4
4
  import type { ServerWebSocket } from 'bun'
5
- import type { KanbanProvider } from './providers/types.ts'
5
+ import type { KanbanProvider } from './providers/types'
6
6
 
7
7
  const wsClients = new Set<ServerWebSocket<unknown>>()
8
8
  const CORS_HEADERS = {
package/src/types.ts CHANGED
@@ -115,6 +115,7 @@ export interface ProviderCapabilities {
115
115
  taskMove: boolean
116
116
  taskDelete: boolean
117
117
  comment: boolean
118
+ /** True when provider-backed bootstrap/dashboard activity is exposed, not merely cached internally. */
118
119
  activity: boolean
119
120
  metrics: boolean
120
121
  columnCrud: boolean