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
@@ -1,11 +1,44 @@
1
1
  import * as fs from 'fs'
2
2
  import * as path from 'path'
3
- import type { KanbanColumn, CardDisplaySettings, Priority, FeatureStatus } from './types'
3
+ import type { KanbanColumn, CardDisplaySettings, Priority } from './types'
4
+ import { DEFAULT_COLUMNS } from './types'
4
5
 
6
+ // Per-board configuration
7
+ export interface BoardConfig {
8
+ name: string
9
+ description?: string
10
+ columns: KanbanColumn[]
11
+ nextCardId: number
12
+ defaultStatus: string
13
+ defaultPriority: Priority
14
+ }
15
+
16
+ // V2 config with multi-board support
5
17
  export interface KanbanConfig {
18
+ version: 2
19
+ boards: Record<string, BoardConfig>
20
+ defaultBoard: string
6
21
  featuresDirectory: string
22
+ aiAgent: string
23
+ // Global display settings (fallback defaults for defaultPriority/defaultStatus)
7
24
  defaultPriority: Priority
8
- defaultStatus: FeatureStatus
25
+ defaultStatus: string
26
+ showPriorityBadges: boolean
27
+ showAssignee: boolean
28
+ showDueDate: boolean
29
+ showLabels: boolean
30
+ showBuildWithAI: boolean
31
+ showFileName: boolean
32
+ compactMode: boolean
33
+ markdownEditorMode: boolean
34
+ }
35
+
36
+ // Legacy v1 config (for migration)
37
+ interface KanbanConfigV1 {
38
+ version?: 1
39
+ featuresDirectory: string
40
+ defaultPriority: Priority
41
+ defaultStatus: string
9
42
  columns: KanbanColumn[]
10
43
  aiAgent: string
11
44
  nextCardId: number
@@ -19,19 +52,24 @@ export interface KanbanConfig {
19
52
  markdownEditorMode: boolean
20
53
  }
21
54
 
55
+ const DEFAULT_BOARD_CONFIG: BoardConfig = {
56
+ name: 'Default',
57
+ columns: [...DEFAULT_COLUMNS],
58
+ nextCardId: 1,
59
+ defaultStatus: 'backlog',
60
+ defaultPriority: 'medium'
61
+ }
62
+
22
63
  export const DEFAULT_CONFIG: KanbanConfig = {
64
+ version: 2,
65
+ boards: {
66
+ default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] }
67
+ },
68
+ defaultBoard: 'default',
23
69
  featuresDirectory: '.kanban',
70
+ aiAgent: 'claude',
24
71
  defaultPriority: 'medium',
25
72
  defaultStatus: 'backlog',
26
- columns: [
27
- { id: 'backlog', name: 'Backlog', color: '#6b7280' },
28
- { id: 'todo', name: 'To Do', color: '#3b82f6' },
29
- { id: 'in-progress', name: 'In Progress', color: '#f59e0b' },
30
- { id: 'review', name: 'Review', color: '#8b5cf6' },
31
- { id: 'done', name: 'Done', color: '#22c55e' }
32
- ],
33
- aiAgent: 'claude',
34
- nextCardId: 1,
35
73
  showPriorityBadges: true,
36
74
  showAssignee: true,
37
75
  showDueDate: true,
@@ -48,12 +86,69 @@ export function configPath(workspaceRoot: string): string {
48
86
  return path.join(workspaceRoot, CONFIG_FILENAME)
49
87
  }
50
88
 
89
+ function migrateConfigV1ToV2(raw: Record<string, unknown>): KanbanConfig {
90
+ const v1Defaults: KanbanConfigV1 = {
91
+ featuresDirectory: '.kanban',
92
+ defaultPriority: 'medium',
93
+ defaultStatus: 'backlog',
94
+ columns: [...DEFAULT_COLUMNS],
95
+ aiAgent: 'claude',
96
+ nextCardId: 1,
97
+ showPriorityBadges: true,
98
+ showAssignee: true,
99
+ showDueDate: true,
100
+ showLabels: true,
101
+ showBuildWithAI: true,
102
+ showFileName: false,
103
+ compactMode: false,
104
+ markdownEditorMode: false
105
+ }
106
+ const v1 = { ...v1Defaults, ...raw } as KanbanConfigV1
107
+ return {
108
+ version: 2,
109
+ boards: {
110
+ default: {
111
+ name: 'Default',
112
+ columns: v1.columns,
113
+ nextCardId: v1.nextCardId,
114
+ defaultStatus: v1.defaultStatus,
115
+ defaultPriority: v1.defaultPriority
116
+ }
117
+ },
118
+ defaultBoard: 'default',
119
+ featuresDirectory: v1.featuresDirectory,
120
+ aiAgent: v1.aiAgent,
121
+ defaultPriority: v1.defaultPriority,
122
+ defaultStatus: v1.defaultStatus,
123
+ showPriorityBadges: v1.showPriorityBadges,
124
+ showAssignee: v1.showAssignee,
125
+ showDueDate: v1.showDueDate,
126
+ showLabels: v1.showLabels,
127
+ showBuildWithAI: v1.showBuildWithAI,
128
+ showFileName: v1.showFileName,
129
+ compactMode: v1.compactMode,
130
+ markdownEditorMode: v1.markdownEditorMode
131
+ }
132
+ }
133
+
51
134
  export function readConfig(workspaceRoot: string): KanbanConfig {
52
135
  const filePath = configPath(workspaceRoot)
53
- const defaults = { ...DEFAULT_CONFIG, columns: [...DEFAULT_CONFIG.columns] }
136
+ const defaults = { ...DEFAULT_CONFIG, boards: { default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] } } }
54
137
  try {
55
138
  const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
56
- return { ...defaults, ...raw }
139
+ if (!raw.version || raw.version === 1) {
140
+ // Migrate v1 to v2 and persist
141
+ const v2 = migrateConfigV1ToV2(raw)
142
+ writeConfig(workspaceRoot, v2)
143
+ return v2
144
+ }
145
+ // Merge with defaults for any missing fields
146
+ const config = { ...defaults, ...raw }
147
+ // Ensure boards object exists with at least default board
148
+ if (!config.boards || Object.keys(config.boards).length === 0) {
149
+ config.boards = defaults.boards
150
+ }
151
+ return config
57
152
  } catch {
58
153
  return defaults
59
154
  }
@@ -64,25 +159,52 @@ export function writeConfig(workspaceRoot: string, config: KanbanConfig): void {
64
159
  fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8')
65
160
  }
66
161
 
67
- /** Read and increment the nextCardId counter, returning the allocated ID */
68
- export function allocateCardId(workspaceRoot: string): number {
162
+ /** Get the default board ID from config */
163
+ export function getDefaultBoardId(workspaceRoot: string): string {
69
164
  const config = readConfig(workspaceRoot)
70
- const id = config.nextCardId
71
- writeConfig(workspaceRoot, { ...config, nextCardId: id + 1 })
165
+ return config.defaultBoard
166
+ }
167
+
168
+ /** Get board config, using default board if boardId is omitted */
169
+ export function getBoardConfig(workspaceRoot: string, boardId?: string): BoardConfig {
170
+ const config = readConfig(workspaceRoot)
171
+ const resolvedId = boardId || config.defaultBoard
172
+ const board = config.boards[resolvedId]
173
+ if (!board) {
174
+ throw new Error(`Board '${resolvedId}' not found`)
175
+ }
176
+ return board
177
+ }
178
+
179
+ /** Read and increment the nextCardId counter for a board, returning the allocated ID */
180
+ export function allocateCardId(workspaceRoot: string, boardId?: string): number {
181
+ const config = readConfig(workspaceRoot)
182
+ const resolvedId = boardId || config.defaultBoard
183
+ const board = config.boards[resolvedId]
184
+ if (!board) {
185
+ throw new Error(`Board '${resolvedId}' not found`)
186
+ }
187
+ const id = board.nextCardId
188
+ board.nextCardId = id + 1
189
+ writeConfig(workspaceRoot, config)
72
190
  return id
73
191
  }
74
192
 
75
- /** Ensure nextCardId is ahead of all existing numeric IDs */
76
- export function syncCardIdCounter(workspaceRoot: string, existingIds: number[]): void {
193
+ /** Ensure nextCardId is ahead of all existing numeric IDs for a board */
194
+ export function syncCardIdCounter(workspaceRoot: string, boardId: string, existingIds: number[]): void {
77
195
  if (existingIds.length === 0) return
78
196
  const maxId = Math.max(...existingIds)
79
197
  const config = readConfig(workspaceRoot)
80
- if (config.nextCardId <= maxId) {
81
- writeConfig(workspaceRoot, { ...config, nextCardId: maxId + 1 })
198
+ const resolvedId = boardId || config.defaultBoard
199
+ const board = config.boards[resolvedId]
200
+ if (!board) return
201
+ if (board.nextCardId <= maxId) {
202
+ board.nextCardId = maxId + 1
203
+ writeConfig(workspaceRoot, config)
82
204
  }
83
205
  }
84
206
 
85
- /** Extract CardDisplaySettings from a KanbanConfig */
207
+ /** Extract CardDisplaySettings from a KanbanConfig (global settings + fallback defaults) */
86
208
  export function configToSettings(config: KanbanConfig): CardDisplaySettings {
87
209
  return {
88
210
  showPriorityBadges: config.showPriorityBadges,
@@ -1,7 +1,7 @@
1
1
  // Kanban types
2
2
 
3
3
  export type Priority = 'critical' | 'high' | 'medium' | 'low'
4
- export type FeatureStatus = 'backlog' | 'todo' | 'in-progress' | 'review' | 'done'
4
+ export type FeatureStatus = string
5
5
 
6
6
  export interface Comment {
7
7
  id: string
@@ -12,6 +12,7 @@ export interface Comment {
12
12
 
13
13
  export interface Feature {
14
14
  id: string
15
+ boardId?: string
15
16
  status: FeatureStatus
16
17
  priority: Priority
17
18
  assignee: string | null
@@ -27,6 +28,12 @@ export interface Feature {
27
28
  filePath: string
28
29
  }
29
30
 
31
+ export interface BoardInfo {
32
+ id: string
33
+ name: string
34
+ description?: string
35
+ }
36
+
30
37
  // Parse title from the first # heading in markdown content, falling back to the first line
31
38
  export function getTitleFromContent(content: string): string {
32
39
  const match = content.match(/^#\s+(.+)$/m)
@@ -82,12 +89,12 @@ export interface CardDisplaySettings {
82
89
  compactMode: boolean
83
90
  markdownEditorMode: boolean
84
91
  defaultPriority: Priority
85
- defaultStatus: FeatureStatus
92
+ defaultStatus: string
86
93
  }
87
94
 
88
95
  // Messages between extension and webview
89
96
  export type ExtensionMessage =
90
- | { type: 'init'; features: Feature[]; columns: KanbanColumn[]; settings: CardDisplaySettings }
97
+ | { type: 'init'; features: Feature[]; columns: KanbanColumn[]; settings: CardDisplaySettings; boards?: BoardInfo[]; currentBoard?: string }
91
98
  | { type: 'featuresUpdated'; features: Feature[] }
92
99
  | { type: 'triggerCreateDialog' }
93
100
  | { type: 'featureContent'; featureId: string; content: string; frontmatter: FeatureFrontmatter; comments: Comment[] }
@@ -96,7 +103,7 @@ export type ExtensionMessage =
96
103
  // Frontmatter for editing
97
104
  export interface FeatureFrontmatter {
98
105
  id: string
99
- status: FeatureStatus
106
+ status: string
100
107
  priority: Priority
101
108
  assignee: string | null
102
109
  dueDate: string | null
@@ -110,7 +117,7 @@ export interface FeatureFrontmatter {
110
117
 
111
118
  export type WebviewMessage =
112
119
  | { type: 'ready' }
113
- | { type: 'createFeature'; data: { status: FeatureStatus; priority: Priority; content: string; assignee: string | null; dueDate: string | null; labels: string[] } }
120
+ | { type: 'createFeature'; data: { status: string; priority: Priority; content: string; assignee: string | null; dueDate: string | null; labels: string[] } }
114
121
  | { type: 'moveFeature'; featureId: string; newStatus: string; newOrder: number }
115
122
  | { type: 'deleteFeature'; featureId: string }
116
123
  | { type: 'updateFeature'; featureId: string; updates: Partial<Feature> }
@@ -129,3 +136,5 @@ export type WebviewMessage =
129
136
  | { type: 'addComment'; featureId: string; author: string; content: string }
130
137
  | { type: 'updateComment'; featureId: string; commentId: string; content: string }
131
138
  | { type: 'deleteComment'; featureId: string; commentId: string }
139
+ | { type: 'switchBoard'; boardId: string }
140
+ | { type: 'createBoard'; name: string }
@@ -12,8 +12,9 @@ function createTempDir(): string {
12
12
  }
13
13
 
14
14
  // Helper: write a feature markdown file
15
+ // Files are stored under boards/default/{status}/ in the multi-board layout
15
16
  function writeFeatureFile(dir: string, filename: string, content: string, subfolder?: string): string {
16
- const targetDir = subfolder ? path.join(dir, subfolder) : dir
17
+ const targetDir = subfolder ? path.join(dir, 'boards', 'default', subfolder) : path.join(dir, 'boards', 'default')
17
18
  fs.mkdirSync(targetDir, { recursive: true })
18
19
  const filePath = path.join(targetDir, filename)
19
20
  fs.writeFileSync(filePath, content, 'utf-8')
@@ -366,8 +367,8 @@ describe('Standalone Server Integration', () => {
366
367
  expect(features[0].content).toBe('# My New Feature\n\nSome description')
367
368
  expect(features[0].labels).toEqual(['frontend'])
368
369
 
369
- // Verify file exists on disk in todo/ subfolder
370
- const todoDir = path.join(tempDir, 'todo')
370
+ // Verify file exists on disk in boards/default/todo/ subfolder
371
+ const todoDir = path.join(tempDir, 'boards', 'default', 'todo')
371
372
  const files = fs.readdirSync(todoDir).filter(f => f.endsWith('.md'))
372
373
  expect(files.length).toBe(1)
373
374
 
@@ -402,8 +403,8 @@ describe('Standalone Server Integration', () => {
402
403
  expect(features[0].status).toBe('done')
403
404
  expect(features[0].completedAt).toBeTruthy()
404
405
 
405
- // File should be in done/ subfolder
406
- const doneFiles = fs.readdirSync(path.join(tempDir, 'done')).filter(f => f.endsWith('.md'))
406
+ // File should be in boards/default/done/ subfolder
407
+ const doneFiles = fs.readdirSync(path.join(tempDir, 'boards', 'default', 'done')).filter(f => f.endsWith('.md'))
407
408
  expect(doneFiles.length).toBe(1)
408
409
  })
409
410
 
@@ -436,7 +437,7 @@ describe('Standalone Server Integration', () => {
436
437
  const backlogFeatures = features.filter(f => f.status === 'backlog')
437
438
  expect(backlogFeatures.length).toBe(2)
438
439
  // New feature should come after existing (order > 'a0')
439
- expect(backlogFeatures[1].order > backlogFeatures[0].order).toBe(true)
440
+ expect((backlogFeatures[1].order as string) > (backlogFeatures[0].order as string)).toBe(true)
440
441
  })
441
442
 
442
443
  it('should preserve assignee and dueDate', async () => {
@@ -491,9 +492,9 @@ describe('Standalone Server Integration', () => {
491
492
  const features = response.features as Array<Record<string, unknown>>
492
493
  expect(features[0].status).toBe('in-progress')
493
494
 
494
- // Verify file was moved to in-progress/ subfolder
495
- expect(fs.existsSync(path.join(tempDir, 'backlog', 'move-me.md'))).toBe(false)
496
- const fileContent = fs.readFileSync(path.join(tempDir, 'in-progress', 'move-me.md'), 'utf-8')
495
+ // Verify file was moved to boards/default/in-progress/ subfolder
496
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'backlog', 'move-me.md'))).toBe(false)
497
+ const fileContent = fs.readFileSync(path.join(tempDir, 'boards', 'default', 'in-progress', 'move-me.md'), 'utf-8')
497
498
  expect(fileContent).toContain('status: "in-progress"')
498
499
  })
499
500
 
@@ -521,9 +522,9 @@ describe('Standalone Server Integration', () => {
521
522
  expect(features[0].status).toBe('done')
522
523
  expect(features[0].completedAt).toBeTruthy()
523
524
 
524
- // File should now be in done/ subfolder
525
- expect(fs.existsSync(path.join(tempDir, 'review', 'finish-me.md'))).toBe(false)
526
- expect(fs.existsSync(path.join(tempDir, 'done', 'finish-me.md'))).toBe(true)
525
+ // File should now be in boards/default/done/ subfolder
526
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'review', 'finish-me.md'))).toBe(false)
527
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'done', 'finish-me.md'))).toBe(true)
527
528
  })
528
529
 
529
530
  it('should move file from done/ to target status folder', async () => {
@@ -550,9 +551,9 @@ describe('Standalone Server Integration', () => {
550
551
  expect(features[0].status).toBe('todo')
551
552
  expect(features[0].completedAt).toBeNull()
552
553
 
553
- // File should be in todo/ subfolder
554
- expect(fs.existsSync(path.join(tempDir, 'done', 'reopen-me.md'))).toBe(false)
555
- expect(fs.existsSync(path.join(tempDir, 'todo', 'reopen-me.md'))).toBe(true)
554
+ // File should be in boards/default/todo/ subfolder
555
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'done', 'reopen-me.md'))).toBe(false)
556
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'todo', 'reopen-me.md'))).toBe(true)
556
557
  })
557
558
 
558
559
  it('should compute correct fractional order between neighbors', async () => {
@@ -596,8 +597,8 @@ describe('Standalone Server Integration', () => {
596
597
  expect(todoFeatures[1].id).toBe('feat-move')
597
598
  expect(todoFeatures[2].id).toBe('feat-c')
598
599
  // Verify order is between a0 and a2
599
- expect(todoFeatures[1].order > todoFeatures[0].order).toBe(true)
600
- expect(todoFeatures[1].order < todoFeatures[2].order).toBe(true)
600
+ expect((todoFeatures[1].order as string) > (todoFeatures[0].order as string)).toBe(true)
601
+ expect((todoFeatures[1].order as string) < (todoFeatures[2].order as string)).toBe(true)
601
602
  })
602
603
  })
603
604
 
@@ -625,7 +626,7 @@ describe('Standalone Server Integration', () => {
625
626
  expect(features.length).toBe(0)
626
627
 
627
628
  // File should be removed
628
- expect(fs.existsSync(path.join(tempDir, 'backlog', 'delete-me.md'))).toBe(false)
629
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'backlog', 'delete-me.md'))).toBe(false)
629
630
  })
630
631
 
631
632
  it('should only delete the targeted feature', async () => {
@@ -646,7 +647,7 @@ describe('Standalone Server Integration', () => {
646
647
  const features = response.features as Array<Record<string, unknown>>
647
648
  expect(features.length).toBe(1)
648
649
  expect(features[0].id).toBe('keep-me')
649
- expect(fs.existsSync(path.join(tempDir, 'backlog', 'keep-me.md'))).toBe(true)
650
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'backlog', 'keep-me.md'))).toBe(true)
650
651
  })
651
652
 
652
653
  it('should handle deleting non-existent feature gracefully', async () => {
@@ -697,7 +698,7 @@ describe('Standalone Server Integration', () => {
697
698
  expect(features[0].labels).toEqual(['urgent'])
698
699
 
699
700
  // Verify persisted on disk
700
- const fileContent = fs.readFileSync(path.join(tempDir, 'backlog', 'update-me.md'), 'utf-8')
701
+ const fileContent = fs.readFileSync(path.join(tempDir, 'boards', 'default', 'backlog', 'update-me.md'), 'utf-8')
701
702
  expect(fileContent).toContain('priority: "critical"')
702
703
  expect(fileContent).toContain('assignee: "alice"')
703
704
  expect(fileContent).toContain('labels: ["urgent"]')
@@ -813,9 +814,9 @@ describe('Standalone Server Integration', () => {
813
814
  expect(saved.assignee).toBe('charlie')
814
815
  expect(saved.labels).toEqual(['updated'])
815
816
 
816
- // Verify on disk — file moved from backlog/ to in-progress/
817
- expect(fs.existsSync(path.join(tempDir, 'backlog', 'save-me.md'))).toBe(false)
818
- const fileContent = fs.readFileSync(path.join(tempDir, 'in-progress', 'save-me.md'), 'utf-8')
817
+ // Verify on disk — file moved from boards/default/backlog/ to boards/default/in-progress/
818
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'backlog', 'save-me.md'))).toBe(false)
819
+ const fileContent = fs.readFileSync(path.join(tempDir, 'boards', 'default', 'in-progress', 'save-me.md'), 'utf-8')
819
820
  expect(fileContent).toContain('status: "in-progress"')
820
821
  expect(fileContent).toContain('# Save Me Updated')
821
822
  expect(fileContent).toContain('assignee: "charlie"')
@@ -856,8 +857,8 @@ describe('Standalone Server Integration', () => {
856
857
  }
857
858
  }, 'init')
858
859
 
859
- expect(fs.existsSync(path.join(tempDir, 'review', 'save-done.md'))).toBe(false)
860
- expect(fs.existsSync(path.join(tempDir, 'done', 'save-done.md'))).toBe(true)
860
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'review', 'save-done.md'))).toBe(false)
861
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'done', 'save-done.md'))).toBe(true)
861
862
  })
862
863
  })
863
864
 
@@ -1088,8 +1089,8 @@ describe('Standalone Server Integration', () => {
1088
1089
  }
1089
1090
 
1090
1091
  // Files on disk should be updated
1091
- const file1 = fs.readFileSync(path.join(tempDir, 'backlog', 'legacy-1.md'), 'utf-8')
1092
- const file2 = fs.readFileSync(path.join(tempDir, 'backlog', 'legacy-2.md'), 'utf-8')
1092
+ const file1 = fs.readFileSync(path.join(tempDir, 'boards', 'default', 'backlog', 'legacy-1.md'), 'utf-8')
1093
+ const file2 = fs.readFileSync(path.join(tempDir, 'boards', 'default', 'backlog', 'legacy-2.md'), 'utf-8')
1093
1094
  const orderMatch1 = file1.match(/order: "(.+)"/)
1094
1095
  const orderMatch2 = file2.match(/order: "(.+)"/)
1095
1096
  expect(orderMatch1).toBeTruthy()
@@ -1118,9 +1119,9 @@ describe('Standalone Server Integration', () => {
1118
1119
 
1119
1120
  await sendAndReceive(ws, { type: 'ready' }, 'init')
1120
1121
 
1121
- // After load, file should have been migrated to done/
1122
- expect(fs.existsSync(path.join(tempDir, 'misplaced-done.md'))).toBe(false)
1123
- expect(fs.existsSync(path.join(tempDir, 'done', 'misplaced-done.md'))).toBe(true)
1122
+ // After load, file should have been migrated to boards/default/done/
1123
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'misplaced-done.md'))).toBe(false)
1124
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'done', 'misplaced-done.md'))).toBe(true)
1124
1125
  })
1125
1126
 
1126
1127
  it('should move mismatched file to correct status subfolder', async () => {
@@ -1137,9 +1138,9 @@ describe('Standalone Server Integration', () => {
1137
1138
 
1138
1139
  await sendAndReceive(ws, { type: 'ready' }, 'init')
1139
1140
 
1140
- // After load, file should have been moved to backlog/
1141
- expect(fs.existsSync(path.join(tempDir, 'done', 'misplaced-active.md'))).toBe(false)
1142
- expect(fs.existsSync(path.join(tempDir, 'backlog', 'misplaced-active.md'))).toBe(true)
1141
+ // After load, file should have been moved to boards/default/backlog/
1142
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'done', 'misplaced-active.md'))).toBe(false)
1143
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'backlog', 'misplaced-active.md'))).toBe(true)
1143
1144
  })
1144
1145
  })
1145
1146
 
@@ -1536,7 +1537,7 @@ describe('Standalone Server Integration', () => {
1536
1537
  expect(json.data.filePath).toBeUndefined()
1537
1538
 
1538
1539
  // Verify persisted on disk
1539
- const todoDir = path.join(tempDir, 'todo')
1540
+ const todoDir = path.join(tempDir, 'boards', 'default', 'todo')
1540
1541
  const files = fs.readdirSync(todoDir).filter(f => f.endsWith('.md'))
1541
1542
  expect(files.length).toBe(1)
1542
1543
  })
@@ -1597,8 +1598,8 @@ describe('Standalone Server Integration', () => {
1597
1598
  expect(json.data.status).toBe('in-progress')
1598
1599
 
1599
1600
  // File should be moved
1600
- expect(fs.existsSync(path.join(tempDir, 'backlog', 'move-api.md'))).toBe(false)
1601
- expect(fs.existsSync(path.join(tempDir, 'in-progress', 'move-api.md'))).toBe(true)
1601
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'backlog', 'move-api.md'))).toBe(false)
1602
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'in-progress', 'move-api.md'))).toBe(true)
1602
1603
  })
1603
1604
 
1604
1605
  it('DELETE /api/tasks/:id should delete a task', async () => {
@@ -1617,7 +1618,7 @@ describe('Standalone Server Integration', () => {
1617
1618
  expect(json.ok).toBe(true)
1618
1619
 
1619
1620
  // File should be gone
1620
- expect(fs.existsSync(path.join(tempDir, 'backlog', 'delete-api.md'))).toBe(false)
1621
+ expect(fs.existsSync(path.join(tempDir, 'boards', 'default', 'backlog', 'delete-api.md'))).toBe(false)
1621
1622
  })
1622
1623
 
1623
1624
  it('DELETE /api/tasks/:id should return 404 for non-existent task', async () => {