kanban-lite 1.0.9 → 1.0.13

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 (38) hide show
  1. package/dist/cli.js +1015 -407
  2. package/dist/extension.js +897 -347
  3. package/dist/mcp-server.js +550 -189
  4. package/dist/sdk/index.cjs +1223 -0
  5. package/dist/sdk/index.mjs +1168 -0
  6. package/dist/sdk/sdk/KanbanSDK.d.ts +39 -21
  7. package/dist/sdk/sdk/index.d.ts +4 -3
  8. package/dist/sdk/sdk/migration.d.ts +6 -0
  9. package/dist/sdk/sdk/types.d.ts +3 -5
  10. package/dist/sdk/shared/config.d.ts +23 -10
  11. package/dist/sdk/shared/types.d.ts +18 -4
  12. package/dist/standalone-webview/index.js +27 -27
  13. package/dist/standalone-webview/index.js.map +1 -1
  14. package/dist/standalone-webview/style.css +1 -1
  15. package/dist/standalone.js +831 -328
  16. package/dist/webview/index.js +45 -45
  17. package/dist/webview/index.js.map +1 -1
  18. package/dist/webview/style.css +1 -1
  19. package/package.json +4 -3
  20. package/src/cli/index.ts +217 -95
  21. package/src/extension/KanbanPanel.ts +49 -22
  22. package/src/mcp-server/index.ts +138 -62
  23. package/src/sdk/KanbanSDK.ts +283 -77
  24. package/src/sdk/__tests__/KanbanSDK.test.ts +5 -5
  25. package/src/sdk/__tests__/migration.test.ts +269 -0
  26. package/src/sdk/__tests__/multi-board.test.ts +449 -0
  27. package/src/sdk/index.ts +4 -3
  28. package/src/sdk/migration.ts +52 -0
  29. package/src/sdk/types.ts +3 -6
  30. package/src/shared/config.ts +144 -22
  31. package/src/shared/types.ts +14 -5
  32. package/src/standalone/__tests__/server.integration.test.ts +38 -37
  33. package/src/standalone/server.ts +239 -21
  34. package/src/webview/App.tsx +17 -7
  35. package/src/webview/components/Toolbar.tsx +99 -3
  36. package/src/webview/store/index.ts +11 -3
  37. package/.kanban/backlog/1-test1.md +0 -30
  38. package/.kanban.json +0 -42
@@ -0,0 +1,269 @@
1
+ import * as fs from 'node:fs'
2
+ import * as os from 'node:os'
3
+ import * as path from 'node:path'
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
5
+ import { migrateFileSystemToMultiBoard } from '../migration'
6
+ import { readConfig } from '../../shared/config'
7
+ import { DEFAULT_COLUMNS } from '../../shared/types'
8
+
9
+ function createTempDir(): string {
10
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'kanban-migration-test-'))
11
+ }
12
+
13
+ describe('migrateFileSystemToMultiBoard', () => {
14
+ let workspaceDir: string
15
+ let featuresDir: string
16
+
17
+ beforeEach(() => {
18
+ workspaceDir = createTempDir()
19
+ featuresDir = path.join(workspaceDir, '.kanban')
20
+ fs.mkdirSync(featuresDir, { recursive: true })
21
+ })
22
+
23
+ afterEach(() => {
24
+ fs.rmSync(workspaceDir, { recursive: true, force: true })
25
+ })
26
+
27
+ it('should create boards/default/ and move status subdirs into it', async () => {
28
+ // Create v1-style status directories with card files
29
+ const backlogDir = path.join(featuresDir, 'backlog')
30
+ const todoDir = path.join(featuresDir, 'todo')
31
+ const doneDir = path.join(featuresDir, 'done')
32
+ fs.mkdirSync(backlogDir, { recursive: true })
33
+ fs.mkdirSync(todoDir, { recursive: true })
34
+ fs.mkdirSync(doneDir, { recursive: true })
35
+ fs.writeFileSync(path.join(backlogDir, 'card1.md'), '# Card 1', 'utf-8')
36
+ fs.writeFileSync(path.join(todoDir, 'card2.md'), '# Card 2', 'utf-8')
37
+ fs.writeFileSync(path.join(doneDir, 'card3.md'), '# Card 3', 'utf-8')
38
+
39
+ await migrateFileSystemToMultiBoard(featuresDir)
40
+
41
+ // Verify boards/default/ was created
42
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default'))).toBe(true)
43
+
44
+ // Verify status dirs were moved into boards/default/
45
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'card1.md'))).toBe(true)
46
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'todo', 'card2.md'))).toBe(true)
47
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'done', 'card3.md'))).toBe(true)
48
+
49
+ // Verify old dirs no longer exist at root level
50
+ expect(fs.existsSync(path.join(featuresDir, 'backlog'))).toBe(false)
51
+ expect(fs.existsSync(path.join(featuresDir, 'todo'))).toBe(false)
52
+ expect(fs.existsSync(path.join(featuresDir, 'done'))).toBe(false)
53
+ })
54
+
55
+ it('should move root .md files into boards/default/backlog/', async () => {
56
+ // Create root-level markdown files (orphaned cards)
57
+ fs.writeFileSync(path.join(featuresDir, 'orphan1.md'), '# Orphan 1', 'utf-8')
58
+ fs.writeFileSync(path.join(featuresDir, 'orphan2.md'), '# Orphan 2', 'utf-8')
59
+ // Also a non-md file that should not be moved
60
+ fs.writeFileSync(path.join(featuresDir, 'board.json'), '{}', 'utf-8')
61
+
62
+ await migrateFileSystemToMultiBoard(featuresDir)
63
+
64
+ // .md files should be moved to boards/default/backlog/
65
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'orphan1.md'))).toBe(true)
66
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'orphan2.md'))).toBe(true)
67
+
68
+ // Non-md files remain at root
69
+ expect(fs.existsSync(path.join(featuresDir, 'board.json'))).toBe(true)
70
+
71
+ // Root .md files should no longer exist
72
+ expect(fs.existsSync(path.join(featuresDir, 'orphan1.md'))).toBe(false)
73
+ expect(fs.existsSync(path.join(featuresDir, 'orphan2.md'))).toBe(false)
74
+ })
75
+
76
+ it('should be idempotent - second call is a no-op', async () => {
77
+ // Create a status dir and a root .md file
78
+ const backlogDir = path.join(featuresDir, 'backlog')
79
+ fs.mkdirSync(backlogDir, { recursive: true })
80
+ fs.writeFileSync(path.join(backlogDir, 'card.md'), '# Card', 'utf-8')
81
+ fs.writeFileSync(path.join(featuresDir, 'orphan.md'), '# Orphan', 'utf-8')
82
+
83
+ // First migration
84
+ await migrateFileSystemToMultiBoard(featuresDir)
85
+
86
+ const migratedCardPath = path.join(featuresDir, 'boards', 'default', 'backlog', 'card.md')
87
+ const migratedOrphanPath = path.join(featuresDir, 'boards', 'default', 'backlog', 'orphan.md')
88
+ expect(fs.existsSync(migratedCardPath)).toBe(true)
89
+ expect(fs.existsSync(migratedOrphanPath)).toBe(true)
90
+
91
+ // Record content to verify it doesn't change
92
+ const cardContent = fs.readFileSync(migratedCardPath, 'utf-8')
93
+ const orphanContent = fs.readFileSync(migratedOrphanPath, 'utf-8')
94
+
95
+ // Second migration should be a no-op
96
+ await migrateFileSystemToMultiBoard(featuresDir)
97
+
98
+ expect(fs.existsSync(migratedCardPath)).toBe(true)
99
+ expect(fs.existsSync(migratedOrphanPath)).toBe(true)
100
+ expect(fs.readFileSync(migratedCardPath, 'utf-8')).toBe(cardContent)
101
+ expect(fs.readFileSync(migratedOrphanPath, 'utf-8')).toBe(orphanContent)
102
+ })
103
+
104
+ it('should handle non-existent featuresDir gracefully', async () => {
105
+ const nonExistentDir = path.join(workspaceDir, 'nonexistent', '.kanban')
106
+
107
+ // Should not throw
108
+ await expect(migrateFileSystemToMultiBoard(nonExistentDir)).resolves.toBeUndefined()
109
+
110
+ // boards/default/ gets created by mkdir({ recursive: true }) but no status dirs are moved
111
+ // since there were none to begin with, so the default board dir should be empty
112
+ const defaultBoardDir = path.join(nonExistentDir, 'boards', 'default')
113
+ expect(fs.existsSync(defaultBoardDir)).toBe(true)
114
+ const contents = fs.readdirSync(defaultBoardDir)
115
+ expect(contents).toEqual([])
116
+ })
117
+
118
+ it('should skip dot-directories (e.g. .git)', async () => {
119
+ // Create a dot-directory and a normal status directory
120
+ const gitDir = path.join(featuresDir, '.git')
121
+ const backlogDir = path.join(featuresDir, 'backlog')
122
+ fs.mkdirSync(gitDir, { recursive: true })
123
+ fs.mkdirSync(backlogDir, { recursive: true })
124
+ fs.writeFileSync(path.join(gitDir, 'config'), 'git config content', 'utf-8')
125
+ fs.writeFileSync(path.join(backlogDir, 'card.md'), '# Card', 'utf-8')
126
+
127
+ await migrateFileSystemToMultiBoard(featuresDir)
128
+
129
+ // .git directory should remain at root, not moved into boards/default/
130
+ expect(fs.existsSync(path.join(featuresDir, '.git', 'config'))).toBe(true)
131
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', '.git'))).toBe(false)
132
+
133
+ // backlog should be moved
134
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'card.md'))).toBe(true)
135
+ })
136
+
137
+ it('should handle mixed directories and root .md files together', async () => {
138
+ // Create status dirs, a dot-dir, and root .md files
139
+ const todoDir = path.join(featuresDir, 'todo')
140
+ const reviewDir = path.join(featuresDir, 'review')
141
+ fs.mkdirSync(todoDir, { recursive: true })
142
+ fs.mkdirSync(reviewDir, { recursive: true })
143
+ fs.writeFileSync(path.join(todoDir, 'task.md'), '# Task', 'utf-8')
144
+ fs.writeFileSync(path.join(reviewDir, 'pr.md'), '# PR Review', 'utf-8')
145
+ fs.writeFileSync(path.join(featuresDir, 'loose-card.md'), '# Loose', 'utf-8')
146
+
147
+ await migrateFileSystemToMultiBoard(featuresDir)
148
+
149
+ // Status dirs moved
150
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'todo', 'task.md'))).toBe(true)
151
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'review', 'pr.md'))).toBe(true)
152
+
153
+ // Root .md moved to backlog
154
+ expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'loose-card.md'))).toBe(true)
155
+ })
156
+ })
157
+
158
+ describe('config migration (v1 to v2)', () => {
159
+ let workspaceDir: string
160
+
161
+ beforeEach(() => {
162
+ workspaceDir = createTempDir()
163
+ })
164
+
165
+ afterEach(() => {
166
+ fs.rmSync(workspaceDir, { recursive: true, force: true })
167
+ })
168
+
169
+ it('should migrate a v1 config (no version field) to v2 format with boards.default', () => {
170
+ const v1Config = {
171
+ featuresDirectory: '.kanban',
172
+ defaultPriority: 'high',
173
+ defaultStatus: 'todo',
174
+ columns: [
175
+ { id: 'todo', name: 'To Do', color: '#3b82f6' },
176
+ { id: 'done', name: 'Done', color: '#22c55e' }
177
+ ],
178
+ aiAgent: 'claude',
179
+ nextCardId: 5,
180
+ showPriorityBadges: true,
181
+ showAssignee: false,
182
+ showDueDate: true,
183
+ showLabels: false,
184
+ showBuildWithAI: true,
185
+ showFileName: false,
186
+ compactMode: true,
187
+ markdownEditorMode: false
188
+ }
189
+ fs.writeFileSync(path.join(workspaceDir, '.kanban.json'), JSON.stringify(v1Config), 'utf-8')
190
+
191
+ const config = readConfig(workspaceDir)
192
+
193
+ // Should be v2 format
194
+ expect(config.version).toBe(2)
195
+ expect(config.defaultBoard).toBe('default')
196
+ expect(config.boards).toBeDefined()
197
+ expect(config.boards.default).toBeDefined()
198
+
199
+ // Board config should carry over v1 values
200
+ const board = config.boards.default
201
+ expect(board.name).toBe('Default')
202
+ expect(board.columns).toEqual(v1Config.columns)
203
+ expect(board.nextCardId).toBe(5)
204
+ expect(board.defaultStatus).toBe('todo')
205
+ expect(board.defaultPriority).toBe('high')
206
+
207
+ // Global settings should carry over
208
+ expect(config.showAssignee).toBe(false)
209
+ expect(config.compactMode).toBe(true)
210
+ expect(config.showLabels).toBe(false)
211
+ expect(config.featuresDirectory).toBe('.kanban')
212
+ })
213
+
214
+ it('should migrate a v1 config with explicit version: 1 to v2 format', () => {
215
+ const v1Config = {
216
+ version: 1,
217
+ featuresDirectory: '.kanban',
218
+ defaultPriority: 'medium',
219
+ defaultStatus: 'backlog',
220
+ columns: DEFAULT_COLUMNS,
221
+ aiAgent: 'claude',
222
+ nextCardId: 1,
223
+ showPriorityBadges: true,
224
+ showAssignee: true,
225
+ showDueDate: true,
226
+ showLabels: true,
227
+ showBuildWithAI: true,
228
+ showFileName: false,
229
+ compactMode: false,
230
+ markdownEditorMode: false
231
+ }
232
+ fs.writeFileSync(path.join(workspaceDir, '.kanban.json'), JSON.stringify(v1Config), 'utf-8')
233
+
234
+ const config = readConfig(workspaceDir)
235
+
236
+ expect(config.version).toBe(2)
237
+ expect(config.boards.default).toBeDefined()
238
+ expect(config.boards.default.columns).toEqual(DEFAULT_COLUMNS)
239
+ expect(config.boards.default.nextCardId).toBe(1)
240
+ })
241
+
242
+ it('should persist the migrated v2 config to disk', () => {
243
+ const v1Config = {
244
+ featuresDirectory: '.kanban',
245
+ columns: [{ id: 'backlog', name: 'Backlog', color: '#6b7280' }],
246
+ nextCardId: 3
247
+ }
248
+ fs.writeFileSync(path.join(workspaceDir, '.kanban.json'), JSON.stringify(v1Config), 'utf-8')
249
+
250
+ // First read triggers migration
251
+ readConfig(workspaceDir)
252
+
253
+ // Second read should load the persisted v2 config directly
254
+ const raw = JSON.parse(fs.readFileSync(path.join(workspaceDir, '.kanban.json'), 'utf-8'))
255
+ expect(raw.version).toBe(2)
256
+ expect(raw.boards).toBeDefined()
257
+ expect(raw.boards.default).toBeDefined()
258
+ expect(raw.boards.default.columns).toEqual([{ id: 'backlog', name: 'Backlog', color: '#6b7280' }])
259
+ })
260
+
261
+ it('should return default config when no .kanban.json exists', () => {
262
+ const config = readConfig(workspaceDir)
263
+
264
+ expect(config.version).toBe(2)
265
+ expect(config.boards.default).toBeDefined()
266
+ expect(config.boards.default.columns).toEqual(DEFAULT_COLUMNS)
267
+ expect(config.defaultBoard).toBe('default')
268
+ })
269
+ })