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,1168 @@
1
+ // src/sdk/KanbanSDK.ts
2
+ import * as fs4 from "fs/promises";
3
+ import * as path5 from "path";
4
+
5
+ // node_modules/.pnpm/fractional-indexing@3.2.0/node_modules/fractional-indexing/src/index.js
6
+ var BASE_62_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
7
+ function midpoint(a, b, digits) {
8
+ const zero = digits[0];
9
+ if (b != null && a >= b) {
10
+ throw new Error(a + " >= " + b);
11
+ }
12
+ if (a.slice(-1) === zero || b && b.slice(-1) === zero) {
13
+ throw new Error("trailing zero");
14
+ }
15
+ if (b) {
16
+ let n = 0;
17
+ while ((a[n] || zero) === b[n]) {
18
+ n++;
19
+ }
20
+ if (n > 0) {
21
+ return b.slice(0, n) + midpoint(a.slice(n), b.slice(n), digits);
22
+ }
23
+ }
24
+ const digitA = a ? digits.indexOf(a[0]) : 0;
25
+ const digitB = b != null ? digits.indexOf(b[0]) : digits.length;
26
+ if (digitB - digitA > 1) {
27
+ const midDigit = Math.round(0.5 * (digitA + digitB));
28
+ return digits[midDigit];
29
+ } else {
30
+ if (b && b.length > 1) {
31
+ return b.slice(0, 1);
32
+ } else {
33
+ return digits[digitA] + midpoint(a.slice(1), null, digits);
34
+ }
35
+ }
36
+ }
37
+ function validateInteger(int) {
38
+ if (int.length !== getIntegerLength(int[0])) {
39
+ throw new Error("invalid integer part of order key: " + int);
40
+ }
41
+ }
42
+ function getIntegerLength(head) {
43
+ if (head >= "a" && head <= "z") {
44
+ return head.charCodeAt(0) - "a".charCodeAt(0) + 2;
45
+ } else if (head >= "A" && head <= "Z") {
46
+ return "Z".charCodeAt(0) - head.charCodeAt(0) + 2;
47
+ } else {
48
+ throw new Error("invalid order key head: " + head);
49
+ }
50
+ }
51
+ function getIntegerPart(key) {
52
+ const integerPartLength = getIntegerLength(key[0]);
53
+ if (integerPartLength > key.length) {
54
+ throw new Error("invalid order key: " + key);
55
+ }
56
+ return key.slice(0, integerPartLength);
57
+ }
58
+ function validateOrderKey(key, digits) {
59
+ if (key === "A" + digits[0].repeat(26)) {
60
+ throw new Error("invalid order key: " + key);
61
+ }
62
+ const i = getIntegerPart(key);
63
+ const f = key.slice(i.length);
64
+ if (f.slice(-1) === digits[0]) {
65
+ throw new Error("invalid order key: " + key);
66
+ }
67
+ }
68
+ function incrementInteger(x, digits) {
69
+ validateInteger(x);
70
+ const [head, ...digs] = x.split("");
71
+ let carry = true;
72
+ for (let i = digs.length - 1; carry && i >= 0; i--) {
73
+ const d = digits.indexOf(digs[i]) + 1;
74
+ if (d === digits.length) {
75
+ digs[i] = digits[0];
76
+ } else {
77
+ digs[i] = digits[d];
78
+ carry = false;
79
+ }
80
+ }
81
+ if (carry) {
82
+ if (head === "Z") {
83
+ return "a" + digits[0];
84
+ }
85
+ if (head === "z") {
86
+ return null;
87
+ }
88
+ const h = String.fromCharCode(head.charCodeAt(0) + 1);
89
+ if (h > "a") {
90
+ digs.push(digits[0]);
91
+ } else {
92
+ digs.pop();
93
+ }
94
+ return h + digs.join("");
95
+ } else {
96
+ return head + digs.join("");
97
+ }
98
+ }
99
+ function decrementInteger(x, digits) {
100
+ validateInteger(x);
101
+ const [head, ...digs] = x.split("");
102
+ let borrow = true;
103
+ for (let i = digs.length - 1; borrow && i >= 0; i--) {
104
+ const d = digits.indexOf(digs[i]) - 1;
105
+ if (d === -1) {
106
+ digs[i] = digits.slice(-1);
107
+ } else {
108
+ digs[i] = digits[d];
109
+ borrow = false;
110
+ }
111
+ }
112
+ if (borrow) {
113
+ if (head === "a") {
114
+ return "Z" + digits.slice(-1);
115
+ }
116
+ if (head === "A") {
117
+ return null;
118
+ }
119
+ const h = String.fromCharCode(head.charCodeAt(0) - 1);
120
+ if (h < "Z") {
121
+ digs.push(digits.slice(-1));
122
+ } else {
123
+ digs.pop();
124
+ }
125
+ return h + digs.join("");
126
+ } else {
127
+ return head + digs.join("");
128
+ }
129
+ }
130
+ function generateKeyBetween(a, b, digits = BASE_62_DIGITS) {
131
+ if (a != null) {
132
+ validateOrderKey(a, digits);
133
+ }
134
+ if (b != null) {
135
+ validateOrderKey(b, digits);
136
+ }
137
+ if (a != null && b != null && a >= b) {
138
+ throw new Error(a + " >= " + b);
139
+ }
140
+ if (a == null) {
141
+ if (b == null) {
142
+ return "a" + digits[0];
143
+ }
144
+ const ib2 = getIntegerPart(b);
145
+ const fb2 = b.slice(ib2.length);
146
+ if (ib2 === "A" + digits[0].repeat(26)) {
147
+ return ib2 + midpoint("", fb2, digits);
148
+ }
149
+ if (ib2 < b) {
150
+ return ib2;
151
+ }
152
+ const res = decrementInteger(ib2, digits);
153
+ if (res == null) {
154
+ throw new Error("cannot decrement any more");
155
+ }
156
+ return res;
157
+ }
158
+ if (b == null) {
159
+ const ia2 = getIntegerPart(a);
160
+ const fa2 = a.slice(ia2.length);
161
+ const i2 = incrementInteger(ia2, digits);
162
+ return i2 == null ? ia2 + midpoint(fa2, null, digits) : i2;
163
+ }
164
+ const ia = getIntegerPart(a);
165
+ const fa = a.slice(ia.length);
166
+ const ib = getIntegerPart(b);
167
+ const fb = b.slice(ib.length);
168
+ if (ia === ib) {
169
+ return ia + midpoint(fa, fb, digits);
170
+ }
171
+ const i = incrementInteger(ia, digits);
172
+ if (i == null) {
173
+ throw new Error("cannot increment any more");
174
+ }
175
+ if (i < b) {
176
+ return i;
177
+ }
178
+ return ia + midpoint(fa, null, digits);
179
+ }
180
+ function generateNKeysBetween(a, b, n, digits = BASE_62_DIGITS) {
181
+ if (n === 0) {
182
+ return [];
183
+ }
184
+ if (n === 1) {
185
+ return [generateKeyBetween(a, b, digits)];
186
+ }
187
+ if (b == null) {
188
+ let c2 = generateKeyBetween(a, b, digits);
189
+ const result = [c2];
190
+ for (let i = 0; i < n - 1; i++) {
191
+ c2 = generateKeyBetween(c2, b, digits);
192
+ result.push(c2);
193
+ }
194
+ return result;
195
+ }
196
+ if (a == null) {
197
+ let c2 = generateKeyBetween(a, b, digits);
198
+ const result = [c2];
199
+ for (let i = 0; i < n - 1; i++) {
200
+ c2 = generateKeyBetween(a, c2, digits);
201
+ result.push(c2);
202
+ }
203
+ result.reverse();
204
+ return result;
205
+ }
206
+ const mid = Math.floor(n / 2);
207
+ const c = generateKeyBetween(a, b, digits);
208
+ return [
209
+ ...generateNKeysBetween(a, c, mid, digits),
210
+ c,
211
+ ...generateNKeysBetween(c, b, n - mid - 1, digits)
212
+ ];
213
+ }
214
+
215
+ // src/shared/types.ts
216
+ function getTitleFromContent(content) {
217
+ const match = content.match(/^#\s+(.+)$/m);
218
+ if (match)
219
+ return match[1].trim();
220
+ const firstLine = content.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
221
+ return firstLine || "Untitled";
222
+ }
223
+ function generateSlug(title) {
224
+ return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50) || "feature";
225
+ }
226
+ function generateFeatureFilename(id, title) {
227
+ const slug = generateSlug(title);
228
+ return `${id}-${slug}`;
229
+ }
230
+ function extractNumericId(filenameOrId) {
231
+ const match = filenameOrId.match(/^(\d+)(?:-|$)/);
232
+ return match ? parseInt(match[1], 10) : null;
233
+ }
234
+ var DEFAULT_COLUMNS = [
235
+ { id: "backlog", name: "Backlog", color: "#6b7280" },
236
+ { id: "todo", name: "To Do", color: "#3b82f6" },
237
+ { id: "in-progress", name: "In Progress", color: "#f59e0b" },
238
+ { id: "review", name: "Review", color: "#8b5cf6" },
239
+ { id: "done", name: "Done", color: "#22c55e" }
240
+ ];
241
+
242
+ // src/shared/config.ts
243
+ import * as fs from "fs";
244
+ import * as path from "path";
245
+ var DEFAULT_BOARD_CONFIG = {
246
+ name: "Default",
247
+ columns: [...DEFAULT_COLUMNS],
248
+ nextCardId: 1,
249
+ defaultStatus: "backlog",
250
+ defaultPriority: "medium"
251
+ };
252
+ var DEFAULT_CONFIG = {
253
+ version: 2,
254
+ boards: {
255
+ default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] }
256
+ },
257
+ defaultBoard: "default",
258
+ featuresDirectory: ".kanban",
259
+ aiAgent: "claude",
260
+ defaultPriority: "medium",
261
+ defaultStatus: "backlog",
262
+ showPriorityBadges: true,
263
+ showAssignee: true,
264
+ showDueDate: true,
265
+ showLabels: true,
266
+ showBuildWithAI: true,
267
+ showFileName: false,
268
+ compactMode: false,
269
+ markdownEditorMode: false
270
+ };
271
+ var CONFIG_FILENAME = ".kanban.json";
272
+ function configPath(workspaceRoot) {
273
+ return path.join(workspaceRoot, CONFIG_FILENAME);
274
+ }
275
+ function migrateConfigV1ToV2(raw) {
276
+ const v1Defaults = {
277
+ featuresDirectory: ".kanban",
278
+ defaultPriority: "medium",
279
+ defaultStatus: "backlog",
280
+ columns: [...DEFAULT_COLUMNS],
281
+ aiAgent: "claude",
282
+ nextCardId: 1,
283
+ showPriorityBadges: true,
284
+ showAssignee: true,
285
+ showDueDate: true,
286
+ showLabels: true,
287
+ showBuildWithAI: true,
288
+ showFileName: false,
289
+ compactMode: false,
290
+ markdownEditorMode: false
291
+ };
292
+ const v1 = { ...v1Defaults, ...raw };
293
+ return {
294
+ version: 2,
295
+ boards: {
296
+ default: {
297
+ name: "Default",
298
+ columns: v1.columns,
299
+ nextCardId: v1.nextCardId,
300
+ defaultStatus: v1.defaultStatus,
301
+ defaultPriority: v1.defaultPriority
302
+ }
303
+ },
304
+ defaultBoard: "default",
305
+ featuresDirectory: v1.featuresDirectory,
306
+ aiAgent: v1.aiAgent,
307
+ defaultPriority: v1.defaultPriority,
308
+ defaultStatus: v1.defaultStatus,
309
+ showPriorityBadges: v1.showPriorityBadges,
310
+ showAssignee: v1.showAssignee,
311
+ showDueDate: v1.showDueDate,
312
+ showLabels: v1.showLabels,
313
+ showBuildWithAI: v1.showBuildWithAI,
314
+ showFileName: v1.showFileName,
315
+ compactMode: v1.compactMode,
316
+ markdownEditorMode: v1.markdownEditorMode
317
+ };
318
+ }
319
+ function readConfig(workspaceRoot) {
320
+ const filePath = configPath(workspaceRoot);
321
+ const defaults = { ...DEFAULT_CONFIG, boards: { default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] } } };
322
+ try {
323
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
324
+ if (!raw.version || raw.version === 1) {
325
+ const v2 = migrateConfigV1ToV2(raw);
326
+ writeConfig(workspaceRoot, v2);
327
+ return v2;
328
+ }
329
+ const config = { ...defaults, ...raw };
330
+ if (!config.boards || Object.keys(config.boards).length === 0) {
331
+ config.boards = defaults.boards;
332
+ }
333
+ return config;
334
+ } catch {
335
+ return defaults;
336
+ }
337
+ }
338
+ function writeConfig(workspaceRoot, config) {
339
+ const filePath = configPath(workspaceRoot);
340
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
341
+ }
342
+ function getDefaultBoardId(workspaceRoot) {
343
+ const config = readConfig(workspaceRoot);
344
+ return config.defaultBoard;
345
+ }
346
+ function getBoardConfig(workspaceRoot, boardId) {
347
+ const config = readConfig(workspaceRoot);
348
+ const resolvedId = boardId || config.defaultBoard;
349
+ const board = config.boards[resolvedId];
350
+ if (!board) {
351
+ throw new Error(`Board '${resolvedId}' not found`);
352
+ }
353
+ return board;
354
+ }
355
+ function allocateCardId(workspaceRoot, boardId) {
356
+ const config = readConfig(workspaceRoot);
357
+ const resolvedId = boardId || config.defaultBoard;
358
+ const board = config.boards[resolvedId];
359
+ if (!board) {
360
+ throw new Error(`Board '${resolvedId}' not found`);
361
+ }
362
+ const id = board.nextCardId;
363
+ board.nextCardId = id + 1;
364
+ writeConfig(workspaceRoot, config);
365
+ return id;
366
+ }
367
+ function syncCardIdCounter(workspaceRoot, boardId, existingIds) {
368
+ if (existingIds.length === 0)
369
+ return;
370
+ const maxId = Math.max(...existingIds);
371
+ const config = readConfig(workspaceRoot);
372
+ const resolvedId = boardId || config.defaultBoard;
373
+ const board = config.boards[resolvedId];
374
+ if (!board)
375
+ return;
376
+ if (board.nextCardId <= maxId) {
377
+ board.nextCardId = maxId + 1;
378
+ writeConfig(workspaceRoot, config);
379
+ }
380
+ }
381
+ function configToSettings(config) {
382
+ return {
383
+ showPriorityBadges: config.showPriorityBadges,
384
+ showAssignee: config.showAssignee,
385
+ showDueDate: config.showDueDate,
386
+ showLabels: config.showLabels,
387
+ showBuildWithAI: config.showBuildWithAI,
388
+ showFileName: config.showFileName,
389
+ compactMode: config.compactMode,
390
+ markdownEditorMode: config.markdownEditorMode,
391
+ defaultPriority: config.defaultPriority,
392
+ defaultStatus: config.defaultStatus
393
+ };
394
+ }
395
+ function settingsToConfig(config, settings) {
396
+ return {
397
+ ...config,
398
+ showPriorityBadges: settings.showPriorityBadges,
399
+ showAssignee: settings.showAssignee,
400
+ showDueDate: settings.showDueDate,
401
+ showLabels: settings.showLabels,
402
+ showFileName: settings.showFileName,
403
+ compactMode: settings.compactMode,
404
+ defaultPriority: settings.defaultPriority,
405
+ defaultStatus: settings.defaultStatus
406
+ };
407
+ }
408
+
409
+ // src/sdk/parser.ts
410
+ import * as path2 from "path";
411
+ function extractIdFromFilename(filePath) {
412
+ const basename4 = path2.basename(filePath, ".md");
413
+ const numericMatch = basename4.match(/^(\d+)-/);
414
+ if (numericMatch)
415
+ return numericMatch[1];
416
+ return basename4;
417
+ }
418
+ function parseCommentBlock(header, body) {
419
+ const getValue = (key) => {
420
+ const match = header.match(new RegExp(`^${key}:\\s*(.*)$`, "m"));
421
+ if (!match)
422
+ return "";
423
+ return match[1].trim().replace(/^["']|["']$/g, "");
424
+ };
425
+ if (getValue("comment") !== "true")
426
+ return null;
427
+ const id = getValue("id");
428
+ const author = getValue("author");
429
+ const created = getValue("created");
430
+ if (!id)
431
+ return null;
432
+ return { id, author, created, content: body.trim() };
433
+ }
434
+ function parseFeatureFile(content, filePath) {
435
+ content = content.replace(/\r\n/g, "\n");
436
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
437
+ if (!frontmatterMatch)
438
+ return null;
439
+ const frontmatter = frontmatterMatch[1];
440
+ const rest = frontmatterMatch[2] || "";
441
+ const getValue = (key) => {
442
+ const match = frontmatter.match(new RegExp(`^${key}:\\s*(.*)$`, "m"));
443
+ if (!match)
444
+ return "";
445
+ const value = match[1].trim().replace(/^["']|["']$/g, "");
446
+ return value === "null" ? "" : value;
447
+ };
448
+ const getArrayValue = (key) => {
449
+ const match = frontmatter.match(new RegExp(`^${key}:\\s*\\[([^\\]]*)\\]`, "m"));
450
+ if (!match)
451
+ return [];
452
+ return match[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
453
+ };
454
+ const sections = rest.split(/\n---\n/);
455
+ let body = sections[0] || "";
456
+ const comments = [];
457
+ for (let i = 1; i < sections.length; i += 2) {
458
+ const header = sections[i];
459
+ const commentBody = sections[i + 1] || "";
460
+ if (header?.includes("comment:")) {
461
+ const comment = parseCommentBlock(header, commentBody);
462
+ if (comment)
463
+ comments.push(comment);
464
+ } else {
465
+ body += `
466
+ ---
467
+ ${header}`;
468
+ if (sections[i + 1] !== void 0) {
469
+ body += `
470
+ ---
471
+ ${sections[i + 1]}`;
472
+ i++;
473
+ }
474
+ }
475
+ }
476
+ return {
477
+ id: getValue("id") || extractIdFromFilename(filePath),
478
+ status: getValue("status") || "backlog",
479
+ priority: getValue("priority") || "medium",
480
+ assignee: getValue("assignee") || null,
481
+ dueDate: getValue("dueDate") || null,
482
+ created: getValue("created") || (/* @__PURE__ */ new Date()).toISOString(),
483
+ modified: getValue("modified") || (/* @__PURE__ */ new Date()).toISOString(),
484
+ completedAt: getValue("completedAt") || null,
485
+ labels: getArrayValue("labels"),
486
+ attachments: getArrayValue("attachments"),
487
+ comments,
488
+ order: getValue("order") || "a0",
489
+ content: body.trim(),
490
+ filePath
491
+ };
492
+ }
493
+ function serializeFeature(feature) {
494
+ const frontmatter = [
495
+ "---",
496
+ `id: "${feature.id}"`,
497
+ `status: "${feature.status}"`,
498
+ `priority: "${feature.priority}"`,
499
+ `assignee: ${feature.assignee ? `"${feature.assignee}"` : "null"}`,
500
+ `dueDate: ${feature.dueDate ? `"${feature.dueDate}"` : "null"}`,
501
+ `created: "${feature.created}"`,
502
+ `modified: "${feature.modified}"`,
503
+ `completedAt: ${feature.completedAt ? `"${feature.completedAt}"` : "null"}`,
504
+ `labels: [${feature.labels.map((l) => `"${l}"`).join(", ")}]`,
505
+ `attachments: [${(feature.attachments || []).map((a) => `"${a}"`).join(", ")}]`,
506
+ `order: "${feature.order}"`,
507
+ "---",
508
+ ""
509
+ ].join("\n");
510
+ let result = frontmatter + feature.content;
511
+ const comments = feature.comments || [];
512
+ for (const comment of comments) {
513
+ result += "\n\n---\n";
514
+ result += `comment: true
515
+ `;
516
+ result += `id: "${comment.id}"
517
+ `;
518
+ result += `author: "${comment.author}"
519
+ `;
520
+ result += `created: "${comment.created}"
521
+ `;
522
+ result += "---\n";
523
+ result += comment.content;
524
+ }
525
+ return result;
526
+ }
527
+
528
+ // src/sdk/fileUtils.ts
529
+ import * as path3 from "path";
530
+ import * as fs2 from "fs/promises";
531
+ function getFeatureFilePath(featuresDir, status, filename) {
532
+ return path3.join(featuresDir, status, `${filename}.md`);
533
+ }
534
+ async function ensureDirectories(featuresDir) {
535
+ await fs2.mkdir(featuresDir, { recursive: true });
536
+ }
537
+ async function ensureStatusSubfolders(featuresDir, statuses) {
538
+ for (const status of statuses) {
539
+ await fs2.mkdir(path3.join(featuresDir, status), { recursive: true });
540
+ }
541
+ }
542
+ async function moveFeatureFile(currentPath, featuresDir, newStatus, attachments) {
543
+ const filename = path3.basename(currentPath);
544
+ const targetDir = path3.join(featuresDir, newStatus);
545
+ let targetPath = path3.join(targetDir, filename);
546
+ if (currentPath === targetPath)
547
+ return currentPath;
548
+ const ext = path3.extname(filename);
549
+ const base = path3.basename(filename, ext);
550
+ let counter = 1;
551
+ while (await fileExists(targetPath)) {
552
+ targetPath = path3.join(targetDir, `${base}-${counter}${ext}`);
553
+ counter++;
554
+ }
555
+ await fs2.mkdir(targetDir, { recursive: true });
556
+ await fs2.rename(currentPath, targetPath);
557
+ if (attachments && attachments.length > 0) {
558
+ const sourceDir = path3.dirname(currentPath);
559
+ for (const attachment of attachments) {
560
+ const srcAttach = path3.join(sourceDir, attachment);
561
+ const destAttach = path3.join(targetDir, attachment);
562
+ try {
563
+ await fs2.access(srcAttach);
564
+ await fs2.rename(srcAttach, destAttach);
565
+ } catch {
566
+ }
567
+ }
568
+ }
569
+ return targetPath;
570
+ }
571
+ async function renameFeatureFile(currentPath, newFilename) {
572
+ const dir = path3.dirname(currentPath);
573
+ const newPath = path3.join(dir, `${newFilename}.md`);
574
+ if (currentPath === newPath)
575
+ return currentPath;
576
+ await fs2.rename(currentPath, newPath);
577
+ return newPath;
578
+ }
579
+ function getStatusFromPath(filePath, featuresDir) {
580
+ const relative2 = path3.relative(featuresDir, filePath);
581
+ const parts = relative2.split(path3.sep);
582
+ if (parts.length === 2) {
583
+ return parts[0];
584
+ }
585
+ return null;
586
+ }
587
+ async function fileExists(filePath) {
588
+ try {
589
+ await fs2.access(filePath);
590
+ return true;
591
+ } catch {
592
+ return false;
593
+ }
594
+ }
595
+
596
+ // src/sdk/migration.ts
597
+ import * as fs3 from "fs/promises";
598
+ import * as path4 from "path";
599
+ async function migrateFileSystemToMultiBoard(featuresDir) {
600
+ const boardsDir = path4.join(featuresDir, "boards");
601
+ const defaultBoardDir = path4.join(boardsDir, "default");
602
+ try {
603
+ await fs3.access(boardsDir);
604
+ return;
605
+ } catch {
606
+ }
607
+ await fs3.mkdir(defaultBoardDir, { recursive: true });
608
+ let entries;
609
+ try {
610
+ entries = await fs3.readdir(featuresDir, { withFileTypes: true });
611
+ } catch {
612
+ return;
613
+ }
614
+ for (const entry of entries) {
615
+ if (!entry.isDirectory())
616
+ continue;
617
+ if (entry.name === "boards" || entry.name.startsWith("."))
618
+ continue;
619
+ const src = path4.join(featuresDir, entry.name);
620
+ const dest = path4.join(defaultBoardDir, entry.name);
621
+ await fs3.rename(src, dest);
622
+ }
623
+ const rootMdFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".md"));
624
+ if (rootMdFiles.length > 0) {
625
+ const backlogDir = path4.join(defaultBoardDir, "backlog");
626
+ await fs3.mkdir(backlogDir, { recursive: true });
627
+ for (const file of rootMdFiles) {
628
+ await fs3.rename(
629
+ path4.join(featuresDir, file.name),
630
+ path4.join(backlogDir, file.name)
631
+ );
632
+ }
633
+ }
634
+ }
635
+
636
+ // src/sdk/KanbanSDK.ts
637
+ var KanbanSDK = class {
638
+ constructor(featuresDir) {
639
+ this.featuresDir = featuresDir;
640
+ this._migrated = false;
641
+ }
642
+ get workspaceRoot() {
643
+ return path5.dirname(this.featuresDir);
644
+ }
645
+ // --- Board resolution helpers ---
646
+ _resolveBoardId(boardId) {
647
+ const config = readConfig(this.workspaceRoot);
648
+ return boardId || config.defaultBoard;
649
+ }
650
+ _boardDir(boardId) {
651
+ const resolvedId = this._resolveBoardId(boardId);
652
+ return path5.join(this.featuresDir, "boards", resolvedId);
653
+ }
654
+ _isCompletedStatus(status, boardId) {
655
+ const config = readConfig(this.workspaceRoot);
656
+ const resolvedId = boardId || config.defaultBoard;
657
+ const board = config.boards[resolvedId];
658
+ if (!board || board.columns.length === 0)
659
+ return status === "done";
660
+ return board.columns[board.columns.length - 1].id === status;
661
+ }
662
+ async _ensureMigrated() {
663
+ if (this._migrated)
664
+ return;
665
+ await migrateFileSystemToMultiBoard(this.featuresDir);
666
+ this._migrated = true;
667
+ }
668
+ async init() {
669
+ await this._ensureMigrated();
670
+ const boardDir = this._boardDir();
671
+ await ensureDirectories(boardDir);
672
+ }
673
+ // --- Board management ---
674
+ listBoards() {
675
+ const config = readConfig(this.workspaceRoot);
676
+ return Object.entries(config.boards).map(([id, board]) => ({
677
+ id,
678
+ name: board.name,
679
+ description: board.description
680
+ }));
681
+ }
682
+ createBoard(id, name, options) {
683
+ const config = readConfig(this.workspaceRoot);
684
+ if (config.boards[id]) {
685
+ throw new Error(`Board already exists: ${id}`);
686
+ }
687
+ const columns = options?.columns || [...config.boards[config.defaultBoard]?.columns || [
688
+ { id: "backlog", name: "Backlog", color: "#6b7280" },
689
+ { id: "todo", name: "To Do", color: "#3b82f6" },
690
+ { id: "in-progress", name: "In Progress", color: "#f59e0b" },
691
+ { id: "review", name: "Review", color: "#8b5cf6" },
692
+ { id: "done", name: "Done", color: "#22c55e" }
693
+ ]];
694
+ config.boards[id] = {
695
+ name,
696
+ description: options?.description,
697
+ columns,
698
+ nextCardId: 1,
699
+ defaultStatus: options?.defaultStatus || columns[0]?.id || "backlog",
700
+ defaultPriority: options?.defaultPriority || config.defaultPriority
701
+ };
702
+ writeConfig(this.workspaceRoot, config);
703
+ return { id, name, description: options?.description };
704
+ }
705
+ async deleteBoard(boardId) {
706
+ const config = readConfig(this.workspaceRoot);
707
+ if (!config.boards[boardId]) {
708
+ throw new Error(`Board not found: ${boardId}`);
709
+ }
710
+ if (config.defaultBoard === boardId) {
711
+ throw new Error(`Cannot delete the default board: ${boardId}`);
712
+ }
713
+ const cards = await this.listCards(void 0, boardId);
714
+ if (cards.length > 0) {
715
+ throw new Error(`Cannot delete board "${boardId}": ${cards.length} card(s) still exist`);
716
+ }
717
+ const boardDir = this._boardDir(boardId);
718
+ try {
719
+ await fs4.rm(boardDir, { recursive: true });
720
+ } catch {
721
+ }
722
+ delete config.boards[boardId];
723
+ writeConfig(this.workspaceRoot, config);
724
+ }
725
+ getBoard(boardId) {
726
+ return getBoardConfig(this.workspaceRoot, boardId);
727
+ }
728
+ updateBoard(boardId, updates) {
729
+ const config = readConfig(this.workspaceRoot);
730
+ const board = config.boards[boardId];
731
+ if (!board) {
732
+ throw new Error(`Board not found: ${boardId}`);
733
+ }
734
+ if (updates.name !== void 0)
735
+ board.name = updates.name;
736
+ if (updates.description !== void 0)
737
+ board.description = updates.description;
738
+ if (updates.columns !== void 0)
739
+ board.columns = updates.columns;
740
+ if (updates.defaultStatus !== void 0)
741
+ board.defaultStatus = updates.defaultStatus;
742
+ if (updates.defaultPriority !== void 0)
743
+ board.defaultPriority = updates.defaultPriority;
744
+ writeConfig(this.workspaceRoot, config);
745
+ return board;
746
+ }
747
+ async transferCard(cardId, fromBoardId, toBoardId, targetStatus) {
748
+ const toBoardDir = this._boardDir(toBoardId);
749
+ const config = readConfig(this.workspaceRoot);
750
+ if (!config.boards[fromBoardId])
751
+ throw new Error(`Board not found: ${fromBoardId}`);
752
+ if (!config.boards[toBoardId])
753
+ throw new Error(`Board not found: ${toBoardId}`);
754
+ const card = await this.getCard(cardId, fromBoardId);
755
+ if (!card)
756
+ throw new Error(`Card not found: ${cardId} in board ${fromBoardId}`);
757
+ const toBoard = config.boards[toBoardId];
758
+ const newStatus = targetStatus || toBoard.defaultStatus || toBoard.columns[0]?.id || "backlog";
759
+ const targetDir = path5.join(toBoardDir, newStatus);
760
+ await fs4.mkdir(targetDir, { recursive: true });
761
+ const oldPath = card.filePath;
762
+ const filename = path5.basename(oldPath);
763
+ const newPath = path5.join(targetDir, filename);
764
+ await fs4.rename(oldPath, newPath);
765
+ card.status = newStatus;
766
+ card.boardId = toBoardId;
767
+ card.filePath = newPath;
768
+ card.modified = (/* @__PURE__ */ new Date()).toISOString();
769
+ card.completedAt = this._isCompletedStatus(newStatus, toBoardId) ? (/* @__PURE__ */ new Date()).toISOString() : null;
770
+ const targetCards = await this.listCards(void 0, toBoardId);
771
+ const cardsInStatus = targetCards.filter((c) => c.status === newStatus && c.id !== cardId).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
772
+ const lastOrder = cardsInStatus.length > 0 ? cardsInStatus[cardsInStatus.length - 1].order : null;
773
+ card.order = generateKeyBetween(lastOrder, null);
774
+ await fs4.writeFile(card.filePath, serializeFeature(card), "utf-8");
775
+ return card;
776
+ }
777
+ // --- Card CRUD ---
778
+ async listCards(columns, boardId) {
779
+ await this._ensureMigrated();
780
+ const boardDir = this._boardDir(boardId);
781
+ const resolvedBoardId = this._resolveBoardId(boardId);
782
+ await ensureDirectories(boardDir);
783
+ if (columns) {
784
+ await ensureStatusSubfolders(boardDir, columns);
785
+ }
786
+ try {
787
+ const rootFiles = await this._readMdFiles(boardDir);
788
+ for (const filePath of rootFiles) {
789
+ try {
790
+ const card = await this._loadCard(filePath);
791
+ if (card) {
792
+ await moveFeatureFile(filePath, boardDir, card.status, card.attachments);
793
+ }
794
+ } catch {
795
+ }
796
+ }
797
+ } catch {
798
+ }
799
+ const cards = [];
800
+ let entries;
801
+ try {
802
+ entries = await fs4.readdir(boardDir, { withFileTypes: true });
803
+ } catch {
804
+ return [];
805
+ }
806
+ for (const entry of entries) {
807
+ if (!entry.isDirectory() || entry.name.startsWith("."))
808
+ continue;
809
+ const subdir = path5.join(boardDir, entry.name);
810
+ try {
811
+ const mdFiles = await this._readMdFiles(subdir);
812
+ for (const filePath of mdFiles) {
813
+ const card = await this._loadCard(filePath);
814
+ if (card) {
815
+ card.boardId = resolvedBoardId;
816
+ cards.push(card);
817
+ }
818
+ }
819
+ } catch {
820
+ }
821
+ }
822
+ for (const card of cards) {
823
+ const pathStatus = getStatusFromPath(card.filePath, boardDir);
824
+ if (pathStatus !== null && pathStatus !== card.status) {
825
+ try {
826
+ card.filePath = await moveFeatureFile(card.filePath, boardDir, card.status, card.attachments);
827
+ } catch {
828
+ }
829
+ }
830
+ }
831
+ const hasLegacyOrder = cards.some((c) => /^\d+$/.test(c.order));
832
+ if (hasLegacyOrder) {
833
+ const byStatus = /* @__PURE__ */ new Map();
834
+ for (const c of cards) {
835
+ const list = byStatus.get(c.status) || [];
836
+ list.push(c);
837
+ byStatus.set(c.status, list);
838
+ }
839
+ for (const columnCards of byStatus.values()) {
840
+ columnCards.sort((a, b) => parseInt(a.order) - parseInt(b.order));
841
+ const keys = generateNKeysBetween(null, null, columnCards.length);
842
+ for (let i = 0; i < columnCards.length; i++) {
843
+ columnCards[i].order = keys[i];
844
+ await fs4.writeFile(columnCards[i].filePath, serializeFeature(columnCards[i]), "utf-8");
845
+ }
846
+ }
847
+ }
848
+ const numericIds = cards.map((c) => parseInt(c.id, 10)).filter((n) => !Number.isNaN(n));
849
+ if (numericIds.length > 0) {
850
+ syncCardIdCounter(this.workspaceRoot, resolvedBoardId, numericIds);
851
+ }
852
+ return cards.sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
853
+ }
854
+ async getCard(cardId, boardId) {
855
+ const cards = await this.listCards(void 0, boardId);
856
+ return cards.find((c) => c.id === cardId) || null;
857
+ }
858
+ async createCard(data) {
859
+ await this._ensureMigrated();
860
+ const resolvedBoardId = this._resolveBoardId(data.boardId);
861
+ const boardDir = this._boardDir(resolvedBoardId);
862
+ await ensureDirectories(boardDir);
863
+ const config = readConfig(this.workspaceRoot);
864
+ const board = config.boards[resolvedBoardId];
865
+ const status = data.status || board?.defaultStatus || config.defaultStatus || "backlog";
866
+ const priority = data.priority || board?.defaultPriority || config.defaultPriority || "medium";
867
+ const title = getTitleFromContent(data.content);
868
+ const numericId = allocateCardId(this.workspaceRoot, resolvedBoardId);
869
+ const filename = generateFeatureFilename(numericId, title);
870
+ const now = (/* @__PURE__ */ new Date()).toISOString();
871
+ const cards = await this.listCards(void 0, resolvedBoardId);
872
+ const cardsInStatus = cards.filter((c) => c.status === status).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
873
+ const lastOrder = cardsInStatus.length > 0 ? cardsInStatus[cardsInStatus.length - 1].order : null;
874
+ const card = {
875
+ id: String(numericId),
876
+ boardId: resolvedBoardId,
877
+ status,
878
+ priority,
879
+ assignee: data.assignee ?? null,
880
+ dueDate: data.dueDate ?? null,
881
+ created: now,
882
+ modified: now,
883
+ completedAt: this._isCompletedStatus(status, resolvedBoardId) ? now : null,
884
+ labels: data.labels || [],
885
+ attachments: data.attachments || [],
886
+ comments: [],
887
+ order: generateKeyBetween(lastOrder, null),
888
+ content: data.content,
889
+ filePath: getFeatureFilePath(boardDir, status, filename)
890
+ };
891
+ await fs4.mkdir(path5.dirname(card.filePath), { recursive: true });
892
+ await fs4.writeFile(card.filePath, serializeFeature(card), "utf-8");
893
+ return card;
894
+ }
895
+ async updateCard(cardId, updates, boardId) {
896
+ const card = await this.getCard(cardId, boardId);
897
+ if (!card)
898
+ throw new Error(`Card not found: ${cardId}`);
899
+ const resolvedBoardId = card.boardId || this._resolveBoardId(boardId);
900
+ const boardDir = this._boardDir(resolvedBoardId);
901
+ const oldStatus = card.status;
902
+ const oldTitle = getTitleFromContent(card.content);
903
+ const { filePath: _fp, id: _id, boardId: _bid, ...safeUpdates } = updates;
904
+ Object.assign(card, safeUpdates);
905
+ card.modified = (/* @__PURE__ */ new Date()).toISOString();
906
+ if (oldStatus !== card.status) {
907
+ card.completedAt = this._isCompletedStatus(card.status, resolvedBoardId) ? (/* @__PURE__ */ new Date()).toISOString() : null;
908
+ }
909
+ await fs4.writeFile(card.filePath, serializeFeature(card), "utf-8");
910
+ const newTitle = getTitleFromContent(card.content);
911
+ const numericId = extractNumericId(card.id);
912
+ if (numericId !== null && newTitle !== oldTitle) {
913
+ const newFilename = generateFeatureFilename(numericId, newTitle);
914
+ card.filePath = await renameFeatureFile(card.filePath, newFilename);
915
+ }
916
+ if (oldStatus !== card.status) {
917
+ const newPath = await moveFeatureFile(card.filePath, boardDir, card.status, card.attachments);
918
+ card.filePath = newPath;
919
+ }
920
+ return card;
921
+ }
922
+ async moveCard(cardId, newStatus, position, boardId) {
923
+ const cards = await this.listCards(void 0, boardId);
924
+ const card = cards.find((c) => c.id === cardId);
925
+ if (!card)
926
+ throw new Error(`Card not found: ${cardId}`);
927
+ const resolvedBoardId = card.boardId || this._resolveBoardId(boardId);
928
+ const boardDir = this._boardDir(resolvedBoardId);
929
+ const oldStatus = card.status;
930
+ card.status = newStatus;
931
+ card.modified = (/* @__PURE__ */ new Date()).toISOString();
932
+ if (oldStatus !== newStatus) {
933
+ card.completedAt = this._isCompletedStatus(newStatus, resolvedBoardId) ? (/* @__PURE__ */ new Date()).toISOString() : null;
934
+ }
935
+ const targetColumnCards = cards.filter((c) => c.status === newStatus && c.id !== cardId).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
936
+ const pos = position !== void 0 ? Math.max(0, Math.min(position, targetColumnCards.length)) : targetColumnCards.length;
937
+ const before = pos > 0 ? targetColumnCards[pos - 1].order : null;
938
+ const after = pos < targetColumnCards.length ? targetColumnCards[pos].order : null;
939
+ card.order = generateKeyBetween(before, after);
940
+ await fs4.writeFile(card.filePath, serializeFeature(card), "utf-8");
941
+ if (oldStatus !== newStatus) {
942
+ const newPath = await moveFeatureFile(card.filePath, boardDir, newStatus, card.attachments);
943
+ card.filePath = newPath;
944
+ }
945
+ return card;
946
+ }
947
+ async deleteCard(cardId, boardId) {
948
+ const card = await this.getCard(cardId, boardId);
949
+ if (!card)
950
+ throw new Error(`Card not found: ${cardId}`);
951
+ await fs4.unlink(card.filePath);
952
+ }
953
+ async getCardsByStatus(status, boardId) {
954
+ const cards = await this.listCards(void 0, boardId);
955
+ return cards.filter((c) => c.status === status);
956
+ }
957
+ async getUniqueAssignees(boardId) {
958
+ const cards = await this.listCards(void 0, boardId);
959
+ const assignees = /* @__PURE__ */ new Set();
960
+ for (const c of cards) {
961
+ if (c.assignee)
962
+ assignees.add(c.assignee);
963
+ }
964
+ return [...assignees].sort();
965
+ }
966
+ async getUniqueLabels(boardId) {
967
+ const cards = await this.listCards(void 0, boardId);
968
+ const labels = /* @__PURE__ */ new Set();
969
+ for (const c of cards) {
970
+ for (const l of c.labels)
971
+ labels.add(l);
972
+ }
973
+ return [...labels].sort();
974
+ }
975
+ // --- Attachment management ---
976
+ async addAttachment(cardId, sourcePath, boardId) {
977
+ const card = await this.getCard(cardId, boardId);
978
+ if (!card)
979
+ throw new Error(`Card not found: ${cardId}`);
980
+ const fileName = path5.basename(sourcePath);
981
+ const cardDir = path5.dirname(card.filePath);
982
+ const destPath = path5.join(cardDir, fileName);
983
+ const sourceDir = path5.dirname(path5.resolve(sourcePath));
984
+ if (sourceDir !== cardDir) {
985
+ await fs4.copyFile(path5.resolve(sourcePath), destPath);
986
+ }
987
+ if (!card.attachments.includes(fileName)) {
988
+ card.attachments.push(fileName);
989
+ }
990
+ card.modified = (/* @__PURE__ */ new Date()).toISOString();
991
+ await fs4.writeFile(card.filePath, serializeFeature(card), "utf-8");
992
+ return card;
993
+ }
994
+ async removeAttachment(cardId, attachment, boardId) {
995
+ const card = await this.getCard(cardId, boardId);
996
+ if (!card)
997
+ throw new Error(`Card not found: ${cardId}`);
998
+ card.attachments = card.attachments.filter((a) => a !== attachment);
999
+ card.modified = (/* @__PURE__ */ new Date()).toISOString();
1000
+ await fs4.writeFile(card.filePath, serializeFeature(card), "utf-8");
1001
+ return card;
1002
+ }
1003
+ async listAttachments(cardId, boardId) {
1004
+ const card = await this.getCard(cardId, boardId);
1005
+ if (!card)
1006
+ throw new Error(`Card not found: ${cardId}`);
1007
+ return card.attachments;
1008
+ }
1009
+ // --- Comment management ---
1010
+ async listComments(cardId, boardId) {
1011
+ const card = await this.getCard(cardId, boardId);
1012
+ if (!card)
1013
+ throw new Error(`Card not found: ${cardId}`);
1014
+ return card.comments || [];
1015
+ }
1016
+ async addComment(cardId, author, content, boardId) {
1017
+ const card = await this.getCard(cardId, boardId);
1018
+ if (!card)
1019
+ throw new Error(`Card not found: ${cardId}`);
1020
+ if (!card.comments)
1021
+ card.comments = [];
1022
+ const maxId = card.comments.reduce((max, c) => {
1023
+ const num = parseInt(c.id.replace("c", ""), 10);
1024
+ return Number.isNaN(num) ? max : Math.max(max, num);
1025
+ }, 0);
1026
+ const comment = {
1027
+ id: `c${maxId + 1}`,
1028
+ author,
1029
+ created: (/* @__PURE__ */ new Date()).toISOString(),
1030
+ content
1031
+ };
1032
+ card.comments.push(comment);
1033
+ card.modified = (/* @__PURE__ */ new Date()).toISOString();
1034
+ await fs4.writeFile(card.filePath, serializeFeature(card), "utf-8");
1035
+ return card;
1036
+ }
1037
+ async updateComment(cardId, commentId, content, boardId) {
1038
+ const card = await this.getCard(cardId, boardId);
1039
+ if (!card)
1040
+ throw new Error(`Card not found: ${cardId}`);
1041
+ const comment = (card.comments || []).find((c) => c.id === commentId);
1042
+ if (!comment)
1043
+ throw new Error(`Comment not found: ${commentId}`);
1044
+ comment.content = content;
1045
+ card.modified = (/* @__PURE__ */ new Date()).toISOString();
1046
+ await fs4.writeFile(card.filePath, serializeFeature(card), "utf-8");
1047
+ return card;
1048
+ }
1049
+ async deleteComment(cardId, commentId, boardId) {
1050
+ const card = await this.getCard(cardId, boardId);
1051
+ if (!card)
1052
+ throw new Error(`Card not found: ${cardId}`);
1053
+ card.comments = (card.comments || []).filter((c) => c.id !== commentId);
1054
+ card.modified = (/* @__PURE__ */ new Date()).toISOString();
1055
+ await fs4.writeFile(card.filePath, serializeFeature(card), "utf-8");
1056
+ return card;
1057
+ }
1058
+ // --- Column management (board-scoped) ---
1059
+ listColumns(boardId) {
1060
+ const config = readConfig(this.workspaceRoot);
1061
+ const resolvedId = boardId || config.defaultBoard;
1062
+ const board = config.boards[resolvedId];
1063
+ return board?.columns || [];
1064
+ }
1065
+ addColumn(column, boardId) {
1066
+ const config = readConfig(this.workspaceRoot);
1067
+ const resolvedId = boardId || config.defaultBoard;
1068
+ const board = config.boards[resolvedId];
1069
+ if (!board)
1070
+ throw new Error(`Board not found: ${resolvedId}`);
1071
+ if (board.columns.some((c) => c.id === column.id)) {
1072
+ throw new Error(`Column already exists: ${column.id}`);
1073
+ }
1074
+ board.columns.push(column);
1075
+ writeConfig(this.workspaceRoot, config);
1076
+ return board.columns;
1077
+ }
1078
+ updateColumn(columnId, updates, boardId) {
1079
+ const config = readConfig(this.workspaceRoot);
1080
+ const resolvedId = boardId || config.defaultBoard;
1081
+ const board = config.boards[resolvedId];
1082
+ if (!board)
1083
+ throw new Error(`Board not found: ${resolvedId}`);
1084
+ const col = board.columns.find((c) => c.id === columnId);
1085
+ if (!col)
1086
+ throw new Error(`Column not found: ${columnId}`);
1087
+ if (updates.name !== void 0)
1088
+ col.name = updates.name;
1089
+ if (updates.color !== void 0)
1090
+ col.color = updates.color;
1091
+ writeConfig(this.workspaceRoot, config);
1092
+ return board.columns;
1093
+ }
1094
+ async removeColumn(columnId, boardId) {
1095
+ const config = readConfig(this.workspaceRoot);
1096
+ const resolvedId = boardId || config.defaultBoard;
1097
+ const board = config.boards[resolvedId];
1098
+ if (!board)
1099
+ throw new Error(`Board not found: ${resolvedId}`);
1100
+ const idx = board.columns.findIndex((c) => c.id === columnId);
1101
+ if (idx === -1)
1102
+ throw new Error(`Column not found: ${columnId}`);
1103
+ const cards = await this.listCards(void 0, resolvedId);
1104
+ const cardsInColumn = cards.filter((c) => c.status === columnId);
1105
+ if (cardsInColumn.length > 0) {
1106
+ throw new Error(`Cannot remove column "${columnId}": ${cardsInColumn.length} card(s) still in this column`);
1107
+ }
1108
+ board.columns.splice(idx, 1);
1109
+ writeConfig(this.workspaceRoot, config);
1110
+ return board.columns;
1111
+ }
1112
+ reorderColumns(columnIds, boardId) {
1113
+ const config = readConfig(this.workspaceRoot);
1114
+ const resolvedId = boardId || config.defaultBoard;
1115
+ const board = config.boards[resolvedId];
1116
+ if (!board)
1117
+ throw new Error(`Board not found: ${resolvedId}`);
1118
+ const colMap = new Map(board.columns.map((c) => [c.id, c]));
1119
+ for (const id of columnIds) {
1120
+ if (!colMap.has(id))
1121
+ throw new Error(`Column not found: ${id}`);
1122
+ }
1123
+ if (columnIds.length !== board.columns.length) {
1124
+ throw new Error("Must include all column IDs when reordering");
1125
+ }
1126
+ board.columns = columnIds.map((id) => colMap.get(id));
1127
+ writeConfig(this.workspaceRoot, config);
1128
+ return board.columns;
1129
+ }
1130
+ // --- Settings management (global) ---
1131
+ getSettings() {
1132
+ return configToSettings(readConfig(this.workspaceRoot));
1133
+ }
1134
+ updateSettings(settings) {
1135
+ const config = readConfig(this.workspaceRoot);
1136
+ writeConfig(this.workspaceRoot, settingsToConfig(config, settings));
1137
+ }
1138
+ // --- Private helpers ---
1139
+ async _readMdFiles(dir) {
1140
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
1141
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => path5.join(dir, e.name));
1142
+ }
1143
+ async _loadCard(filePath) {
1144
+ const content = await fs4.readFile(filePath, "utf-8");
1145
+ return parseFeatureFile(content, filePath);
1146
+ }
1147
+ };
1148
+ export {
1149
+ DEFAULT_COLUMNS,
1150
+ KanbanSDK,
1151
+ configToSettings,
1152
+ ensureDirectories,
1153
+ ensureStatusSubfolders,
1154
+ generateFeatureFilename,
1155
+ getBoardConfig,
1156
+ getDefaultBoardId,
1157
+ getFeatureFilePath,
1158
+ getStatusFromPath,
1159
+ getTitleFromContent,
1160
+ migrateFileSystemToMultiBoard,
1161
+ moveFeatureFile,
1162
+ parseFeatureFile,
1163
+ readConfig,
1164
+ renameFeatureFile,
1165
+ serializeFeature,
1166
+ settingsToConfig,
1167
+ writeConfig
1168
+ };