kanban-lite 1.0.21 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{CLAUDE.md → AGENTS.md} +13 -0
- package/CHANGELOG.md +68 -0
- package/README.md +10 -0
- package/dist/cli.js +168 -102
- package/dist/extension.js +178 -104
- package/dist/mcp-server.js +145 -95
- package/dist/sdk/index.cjs +126 -93
- package/dist/sdk/index.mjs +126 -93
- package/dist/sdk/sdk/KanbanSDK.d.ts +39 -7
- package/dist/sdk/shared/config.d.ts +4 -0
- package/dist/sdk/shared/types.d.ts +4 -0
- package/dist/standalone-webview/index.js +58 -58
- package/dist/standalone-webview/index.js.map +1 -1
- package/dist/standalone-webview/style.css +1 -1
- package/dist/standalone.js +606 -364
- package/dist/webview/index.js +57 -57
- package/dist/webview/index.js.map +1 -1
- package/dist/webview/style.css +1 -1
- package/docs/plans/2026-02-26-settings-tabs-design.md +40 -0
- package/docs/plans/2026-02-26-settings-tabs.md +166 -0
- package/docs/plans/2026-02-27-zoom-settings-design.md +82 -0
- package/docs/plans/2026-02-27-zoom-settings.md +395 -0
- package/docs/sdk.md +3 -6
- package/package.json +1 -1
- package/src/cli/index.ts +12 -2
- package/src/extension/KanbanPanel.ts +25 -5
- package/src/mcp-server/index.ts +20 -2
- package/src/sdk/KanbanSDK.ts +64 -7
- package/src/sdk/__tests__/KanbanSDK.test.ts +17 -1
- package/src/sdk/__tests__/metadata.test.ts +3 -1
- package/src/sdk/__tests__/multi-board.test.ts +2 -0
- package/src/sdk/parser.ts +50 -83
- package/src/shared/config.ts +14 -2
- package/src/shared/types.ts +4 -0
- package/src/standalone/__tests__/server.integration.test.ts +2 -2
- package/src/standalone/index.ts +7 -4
- package/src/standalone/server.ts +31 -6
- package/src/webview/App.tsx +42 -3
- package/src/webview/assets/main.css +31 -2
- package/src/webview/components/KanbanBoard.tsx +35 -3
- package/src/webview/components/KanbanColumn.tsx +40 -4
- package/src/webview/components/SettingsPanel.tsx +179 -77
- package/src/webview/components/Toolbar.tsx +127 -32
- package/src/webview/store/index.ts +26 -28
package/dist/mcp-server.js
CHANGED
|
@@ -302,6 +302,8 @@ var DEFAULT_CONFIG = {
|
|
|
302
302
|
compactMode: false,
|
|
303
303
|
markdownEditorMode: false,
|
|
304
304
|
showDeletedColumn: false,
|
|
305
|
+
boardZoom: 100,
|
|
306
|
+
cardZoom: 100,
|
|
305
307
|
port: 3e3,
|
|
306
308
|
labels: {}
|
|
307
309
|
};
|
|
@@ -352,6 +354,8 @@ function migrateConfigV1ToV2(raw) {
|
|
|
352
354
|
compactMode: v1.compactMode,
|
|
353
355
|
markdownEditorMode: v1.markdownEditorMode,
|
|
354
356
|
showDeletedColumn: false,
|
|
357
|
+
boardZoom: 100,
|
|
358
|
+
cardZoom: 100,
|
|
355
359
|
port: 3e3
|
|
356
360
|
};
|
|
357
361
|
}
|
|
@@ -425,7 +429,9 @@ function configToSettings(config) {
|
|
|
425
429
|
markdownEditorMode: config.markdownEditorMode,
|
|
426
430
|
showDeletedColumn: config.showDeletedColumn,
|
|
427
431
|
defaultPriority: config.defaultPriority,
|
|
428
|
-
defaultStatus: config.defaultStatus
|
|
432
|
+
defaultStatus: config.defaultStatus,
|
|
433
|
+
boardZoom: config.boardZoom ?? 100,
|
|
434
|
+
cardZoom: config.cardZoom ?? 100
|
|
429
435
|
};
|
|
430
436
|
}
|
|
431
437
|
function settingsToConfig(config, settings) {
|
|
@@ -439,7 +445,9 @@ function settingsToConfig(config, settings) {
|
|
|
439
445
|
compactMode: settings.compactMode,
|
|
440
446
|
showDeletedColumn: settings.showDeletedColumn,
|
|
441
447
|
defaultPriority: settings.defaultPriority,
|
|
442
|
-
defaultStatus: settings.defaultStatus
|
|
448
|
+
defaultStatus: settings.defaultStatus,
|
|
449
|
+
boardZoom: settings.boardZoom,
|
|
450
|
+
cardZoom: settings.cardZoom
|
|
443
451
|
};
|
|
444
452
|
}
|
|
445
453
|
|
|
@@ -3099,6 +3107,7 @@ function renamed(from, to) {
|
|
|
3099
3107
|
throw new Error("Function yaml." + from + " is removed in js-yaml 4. Use yaml." + to + " instead, which is now safe by default.");
|
|
3100
3108
|
};
|
|
3101
3109
|
}
|
|
3110
|
+
var JSON_SCHEMA = json;
|
|
3102
3111
|
var load = loader.load;
|
|
3103
3112
|
var loadAll = loader.loadAll;
|
|
3104
3113
|
var dump = dumper.dump;
|
|
@@ -3137,48 +3146,26 @@ function parseFeatureFile(content, filePath) {
|
|
|
3137
3146
|
return null;
|
|
3138
3147
|
const frontmatter = frontmatterMatch[1];
|
|
3139
3148
|
const rest = frontmatterMatch[2] || "";
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3149
|
+
let parsed;
|
|
3150
|
+
try {
|
|
3151
|
+
const loaded = load(frontmatter, { schema: JSON_SCHEMA });
|
|
3152
|
+
if (!loaded || typeof loaded !== "object" || Array.isArray(loaded))
|
|
3153
|
+
return null;
|
|
3154
|
+
parsed = loaded;
|
|
3155
|
+
} catch {
|
|
3156
|
+
return null;
|
|
3157
|
+
}
|
|
3158
|
+
const str2 = (key) => {
|
|
3159
|
+
const val = parsed[key];
|
|
3160
|
+
if (val == null)
|
|
3143
3161
|
return "";
|
|
3144
|
-
|
|
3145
|
-
return value === "null" ? "" : value;
|
|
3162
|
+
return String(val);
|
|
3146
3163
|
};
|
|
3147
|
-
const
|
|
3148
|
-
const
|
|
3149
|
-
if (!
|
|
3164
|
+
const arr = (key) => {
|
|
3165
|
+
const val = parsed[key];
|
|
3166
|
+
if (!Array.isArray(val))
|
|
3150
3167
|
return [];
|
|
3151
|
-
return
|
|
3152
|
-
};
|
|
3153
|
-
const getMetadata = () => {
|
|
3154
|
-
const lines = frontmatter.split("\n");
|
|
3155
|
-
let metaStart = -1;
|
|
3156
|
-
for (let j = 0; j < lines.length; j++) {
|
|
3157
|
-
if (/^metadata:\s*$/.test(lines[j])) {
|
|
3158
|
-
metaStart = j + 1;
|
|
3159
|
-
break;
|
|
3160
|
-
}
|
|
3161
|
-
}
|
|
3162
|
-
if (metaStart === -1)
|
|
3163
|
-
return void 0;
|
|
3164
|
-
const indentedLines = [];
|
|
3165
|
-
for (let j = metaStart; j < lines.length; j++) {
|
|
3166
|
-
if (/^\s/.test(lines[j]) || lines[j].trim() === "") {
|
|
3167
|
-
indentedLines.push(lines[j]);
|
|
3168
|
-
} else {
|
|
3169
|
-
break;
|
|
3170
|
-
}
|
|
3171
|
-
}
|
|
3172
|
-
if (indentedLines.length === 0)
|
|
3173
|
-
return void 0;
|
|
3174
|
-
try {
|
|
3175
|
-
const parsed = load(indentedLines.join("\n"));
|
|
3176
|
-
if (parsed && typeof parsed === "object")
|
|
3177
|
-
return parsed;
|
|
3178
|
-
return void 0;
|
|
3179
|
-
} catch {
|
|
3180
|
-
return void 0;
|
|
3181
|
-
}
|
|
3168
|
+
return val.filter((v) => v != null).map(String);
|
|
3182
3169
|
};
|
|
3183
3170
|
const sections = rest.split(/\n---\n/);
|
|
3184
3171
|
let body = sections[0] || "";
|
|
@@ -3199,22 +3186,23 @@ ${section}`;
|
|
|
3199
3186
|
i += 1;
|
|
3200
3187
|
}
|
|
3201
3188
|
}
|
|
3202
|
-
const
|
|
3203
|
-
const
|
|
3189
|
+
const actions = arr("actions");
|
|
3190
|
+
const rawMeta = parsed.metadata;
|
|
3191
|
+
const meta = rawMeta != null && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : void 0;
|
|
3204
3192
|
return {
|
|
3205
|
-
version: parseInt(
|
|
3206
|
-
id:
|
|
3207
|
-
status:
|
|
3208
|
-
priority:
|
|
3209
|
-
assignee:
|
|
3210
|
-
dueDate:
|
|
3211
|
-
created:
|
|
3212
|
-
modified:
|
|
3213
|
-
completedAt:
|
|
3214
|
-
labels:
|
|
3215
|
-
attachments:
|
|
3193
|
+
version: typeof parsed.version === "number" ? parsed.version : parseInt(str2("version"), 10) || 0,
|
|
3194
|
+
id: str2("id") || extractIdFromFilename(filePath),
|
|
3195
|
+
status: str2("status") || "backlog",
|
|
3196
|
+
priority: str2("priority") || "medium",
|
|
3197
|
+
assignee: parsed.assignee != null ? String(parsed.assignee) : null,
|
|
3198
|
+
dueDate: parsed.dueDate != null ? String(parsed.dueDate) : null,
|
|
3199
|
+
created: str2("created") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3200
|
+
modified: str2("modified") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3201
|
+
completedAt: parsed.completedAt != null ? String(parsed.completedAt) : null,
|
|
3202
|
+
labels: arr("labels"),
|
|
3203
|
+
attachments: arr("attachments"),
|
|
3216
3204
|
comments,
|
|
3217
|
-
order:
|
|
3205
|
+
order: str2("order") || "a0",
|
|
3218
3206
|
content: body.trim(),
|
|
3219
3207
|
...meta ? { metadata: meta } : {},
|
|
3220
3208
|
...actions.length > 0 ? { actions } : {},
|
|
@@ -3222,37 +3210,28 @@ ${section}`;
|
|
|
3222
3210
|
};
|
|
3223
3211
|
}
|
|
3224
3212
|
function serializeFeature(feature) {
|
|
3225
|
-
const
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
lines.push(" " + line);
|
|
3248
|
-
}
|
|
3249
|
-
}
|
|
3250
|
-
lines.push("---");
|
|
3251
|
-
lines.push("");
|
|
3252
|
-
const frontmatter = lines.join("\n");
|
|
3253
|
-
let result = frontmatter + feature.content;
|
|
3254
|
-
const comments = feature.comments || [];
|
|
3255
|
-
for (const comment of comments) {
|
|
3213
|
+
const frontmatterObj = {
|
|
3214
|
+
version: feature.version ?? CARD_FORMAT_VERSION,
|
|
3215
|
+
id: feature.id,
|
|
3216
|
+
status: feature.status,
|
|
3217
|
+
priority: feature.priority,
|
|
3218
|
+
assignee: feature.assignee ?? null,
|
|
3219
|
+
dueDate: feature.dueDate ?? null,
|
|
3220
|
+
created: feature.created,
|
|
3221
|
+
modified: feature.modified,
|
|
3222
|
+
completedAt: feature.completedAt ?? null,
|
|
3223
|
+
labels: feature.labels,
|
|
3224
|
+
attachments: feature.attachments || [],
|
|
3225
|
+
order: feature.order,
|
|
3226
|
+
...feature.actions?.length ? { actions: feature.actions } : {},
|
|
3227
|
+
...feature.metadata && Object.keys(feature.metadata).length > 0 ? { metadata: feature.metadata } : {}
|
|
3228
|
+
};
|
|
3229
|
+
const yamlStr = dump(frontmatterObj, { lineWidth: -1, quotingType: '"', forceQuotes: true });
|
|
3230
|
+
let result = `---
|
|
3231
|
+
${yamlStr}---
|
|
3232
|
+
|
|
3233
|
+
${feature.content}`;
|
|
3234
|
+
for (const comment of feature.comments || []) {
|
|
3256
3235
|
result += "\n\n---\n";
|
|
3257
3236
|
result += `comment: true
|
|
3258
3237
|
`;
|
|
@@ -4245,25 +4224,28 @@ var KanbanSDK = class {
|
|
|
4245
4224
|
writeConfig(this.workspaceRoot, config);
|
|
4246
4225
|
}
|
|
4247
4226
|
/**
|
|
4248
|
-
* Removes a label definition from the workspace configuration
|
|
4249
|
-
*
|
|
4250
|
-
* This only removes the color/group definition — cards that use this
|
|
4251
|
-
* label keep their label strings. Those labels will render with default
|
|
4252
|
-
* gray styling in the UI.
|
|
4227
|
+
* Removes a label definition from the workspace configuration and cascades
|
|
4228
|
+
* the deletion to all cards by removing the label from their `labels` array.
|
|
4253
4229
|
*
|
|
4254
4230
|
* @param name - The label name to remove.
|
|
4255
4231
|
*
|
|
4256
4232
|
* @example
|
|
4257
4233
|
* ```ts
|
|
4258
|
-
* sdk.deleteLabel('bug')
|
|
4234
|
+
* await sdk.deleteLabel('bug')
|
|
4259
4235
|
* ```
|
|
4260
4236
|
*/
|
|
4261
|
-
deleteLabel(name) {
|
|
4237
|
+
async deleteLabel(name) {
|
|
4262
4238
|
const config = readConfig(this.workspaceRoot);
|
|
4263
4239
|
if (config.labels) {
|
|
4264
4240
|
delete config.labels[name];
|
|
4265
4241
|
writeConfig(this.workspaceRoot, config);
|
|
4266
4242
|
}
|
|
4243
|
+
const cards = await this.listCards();
|
|
4244
|
+
for (const card of cards) {
|
|
4245
|
+
if (card.labels.includes(name)) {
|
|
4246
|
+
await this.updateCard(card.id, { labels: card.labels.filter((l) => l !== name) });
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4267
4249
|
}
|
|
4268
4250
|
/**
|
|
4269
4251
|
* Renames a label in the configuration and cascades the change to all cards.
|
|
@@ -4688,6 +4670,57 @@ var KanbanSDK = class {
|
|
|
4688
4670
|
this.emitEvent("column.deleted", removed);
|
|
4689
4671
|
return board.columns;
|
|
4690
4672
|
}
|
|
4673
|
+
/**
|
|
4674
|
+
* Moves all cards in the specified column to the `deleted` (soft-delete) column.
|
|
4675
|
+
*
|
|
4676
|
+
* This is a non-destructive operation — cards are moved to the reserved
|
|
4677
|
+
* `deleted` status and can be restored or permanently deleted later.
|
|
4678
|
+
* The column itself is not removed.
|
|
4679
|
+
*
|
|
4680
|
+
* @param columnId - The ID of the column whose cards should be moved to `deleted`.
|
|
4681
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
4682
|
+
* @returns A promise resolving to the number of cards that were moved.
|
|
4683
|
+
* @throws {Error} If the column is `'deleted'` (no-op protection).
|
|
4684
|
+
*
|
|
4685
|
+
* @example
|
|
4686
|
+
* ```ts
|
|
4687
|
+
* const moved = await sdk.cleanupColumn('blocked')
|
|
4688
|
+
* console.log(`Moved ${moved} cards to deleted`)
|
|
4689
|
+
* ```
|
|
4690
|
+
*/
|
|
4691
|
+
async cleanupColumn(columnId, boardId) {
|
|
4692
|
+
if (columnId === DELETED_STATUS_ID)
|
|
4693
|
+
return 0;
|
|
4694
|
+
const cards = await this.listCards(void 0, boardId);
|
|
4695
|
+
const cardsToMove = cards.filter((c) => c.status === columnId);
|
|
4696
|
+
for (const card of cardsToMove) {
|
|
4697
|
+
await this.moveCard(card.id, DELETED_STATUS_ID, 0, boardId);
|
|
4698
|
+
}
|
|
4699
|
+
return cardsToMove.length;
|
|
4700
|
+
}
|
|
4701
|
+
/**
|
|
4702
|
+
* Permanently deletes all cards currently in the `deleted` column.
|
|
4703
|
+
*
|
|
4704
|
+
* This is equivalent to "empty trash". All soft-deleted cards are
|
|
4705
|
+
* removed from disk. This operation cannot be undone.
|
|
4706
|
+
*
|
|
4707
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
4708
|
+
* @returns A promise resolving to the number of cards that were permanently deleted.
|
|
4709
|
+
*
|
|
4710
|
+
* @example
|
|
4711
|
+
* ```ts
|
|
4712
|
+
* const count = await sdk.purgeDeletedCards()
|
|
4713
|
+
* console.log(`Permanently deleted ${count} cards`)
|
|
4714
|
+
* ```
|
|
4715
|
+
*/
|
|
4716
|
+
async purgeDeletedCards(boardId) {
|
|
4717
|
+
const cards = await this.listCards(void 0, boardId);
|
|
4718
|
+
const deleted = cards.filter((c) => c.status === DELETED_STATUS_ID);
|
|
4719
|
+
for (const card of deleted) {
|
|
4720
|
+
await this.permanentlyDeleteCard(card.id, boardId);
|
|
4721
|
+
}
|
|
4722
|
+
return deleted.length;
|
|
4723
|
+
}
|
|
4691
4724
|
/**
|
|
4692
4725
|
* Reorders the columns of a board.
|
|
4693
4726
|
*
|
|
@@ -5657,6 +5690,23 @@ async function main() {
|
|
|
5657
5690
|
};
|
|
5658
5691
|
}
|
|
5659
5692
|
);
|
|
5693
|
+
server.tool(
|
|
5694
|
+
"cleanup_column",
|
|
5695
|
+
"Move all cards in a column to the deleted (soft-delete) column. The column itself is kept.",
|
|
5696
|
+
{
|
|
5697
|
+
boardId: import_zod.z.string().optional().describe("Board ID (uses default board if omitted)"),
|
|
5698
|
+
columnId: import_zod.z.string().describe("Column ID to clean up")
|
|
5699
|
+
},
|
|
5700
|
+
async ({ boardId, columnId }) => {
|
|
5701
|
+
const moved = await sdk.cleanupColumn(columnId, boardId);
|
|
5702
|
+
return {
|
|
5703
|
+
content: [{
|
|
5704
|
+
type: "text",
|
|
5705
|
+
text: `Moved ${moved} card${moved === 1 ? "" : "s"} from "${columnId}" to deleted`
|
|
5706
|
+
}]
|
|
5707
|
+
};
|
|
5708
|
+
}
|
|
5709
|
+
);
|
|
5660
5710
|
server.tool("list_labels", "List all label definitions with colors and groups", {
|
|
5661
5711
|
boardId: import_zod.z.string().optional().describe("Board ID")
|
|
5662
5712
|
}, async () => {
|
|
@@ -5678,10 +5728,10 @@ async function main() {
|
|
|
5678
5728
|
await sdk.renameLabel(oldName, newName);
|
|
5679
5729
|
return { content: [{ type: "text", text: `Label "${oldName}" renamed to "${newName}"` }] };
|
|
5680
5730
|
});
|
|
5681
|
-
server.tool("delete_label", "Remove a label definition
|
|
5731
|
+
server.tool("delete_label", "Remove a label definition and remove it from all cards", {
|
|
5682
5732
|
name: import_zod.z.string().describe("Label name to remove")
|
|
5683
5733
|
}, async ({ name }) => {
|
|
5684
|
-
sdk.deleteLabel(name);
|
|
5734
|
+
await sdk.deleteLabel(name);
|
|
5685
5735
|
return { content: [{ type: "text", text: `Label "${name}" definition removed` }] };
|
|
5686
5736
|
});
|
|
5687
5737
|
server.tool(
|
package/dist/sdk/index.cjs
CHANGED
|
@@ -325,6 +325,8 @@ var DEFAULT_CONFIG = {
|
|
|
325
325
|
compactMode: false,
|
|
326
326
|
markdownEditorMode: false,
|
|
327
327
|
showDeletedColumn: false,
|
|
328
|
+
boardZoom: 100,
|
|
329
|
+
cardZoom: 100,
|
|
328
330
|
port: 3e3,
|
|
329
331
|
labels: {}
|
|
330
332
|
};
|
|
@@ -375,6 +377,8 @@ function migrateConfigV1ToV2(raw) {
|
|
|
375
377
|
compactMode: v1.compactMode,
|
|
376
378
|
markdownEditorMode: v1.markdownEditorMode,
|
|
377
379
|
showDeletedColumn: false,
|
|
380
|
+
boardZoom: 100,
|
|
381
|
+
cardZoom: 100,
|
|
378
382
|
port: 3e3
|
|
379
383
|
};
|
|
380
384
|
}
|
|
@@ -452,7 +456,9 @@ function configToSettings(config) {
|
|
|
452
456
|
markdownEditorMode: config.markdownEditorMode,
|
|
453
457
|
showDeletedColumn: config.showDeletedColumn,
|
|
454
458
|
defaultPriority: config.defaultPriority,
|
|
455
|
-
defaultStatus: config.defaultStatus
|
|
459
|
+
defaultStatus: config.defaultStatus,
|
|
460
|
+
boardZoom: config.boardZoom ?? 100,
|
|
461
|
+
cardZoom: config.cardZoom ?? 100
|
|
456
462
|
};
|
|
457
463
|
}
|
|
458
464
|
function settingsToConfig(config, settings) {
|
|
@@ -466,7 +472,9 @@ function settingsToConfig(config, settings) {
|
|
|
466
472
|
compactMode: settings.compactMode,
|
|
467
473
|
showDeletedColumn: settings.showDeletedColumn,
|
|
468
474
|
defaultPriority: settings.defaultPriority,
|
|
469
|
-
defaultStatus: settings.defaultStatus
|
|
475
|
+
defaultStatus: settings.defaultStatus,
|
|
476
|
+
boardZoom: settings.boardZoom,
|
|
477
|
+
cardZoom: settings.cardZoom
|
|
470
478
|
};
|
|
471
479
|
}
|
|
472
480
|
|
|
@@ -3126,6 +3134,7 @@ function renamed(from, to) {
|
|
|
3126
3134
|
throw new Error("Function yaml." + from + " is removed in js-yaml 4. Use yaml." + to + " instead, which is now safe by default.");
|
|
3127
3135
|
};
|
|
3128
3136
|
}
|
|
3137
|
+
var JSON_SCHEMA = json;
|
|
3129
3138
|
var load = loader.load;
|
|
3130
3139
|
var loadAll = loader.loadAll;
|
|
3131
3140
|
var dump = dumper.dump;
|
|
@@ -3164,48 +3173,26 @@ function parseFeatureFile(content, filePath) {
|
|
|
3164
3173
|
return null;
|
|
3165
3174
|
const frontmatter = frontmatterMatch[1];
|
|
3166
3175
|
const rest = frontmatterMatch[2] || "";
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3176
|
+
let parsed;
|
|
3177
|
+
try {
|
|
3178
|
+
const loaded = load(frontmatter, { schema: JSON_SCHEMA });
|
|
3179
|
+
if (!loaded || typeof loaded !== "object" || Array.isArray(loaded))
|
|
3180
|
+
return null;
|
|
3181
|
+
parsed = loaded;
|
|
3182
|
+
} catch {
|
|
3183
|
+
return null;
|
|
3184
|
+
}
|
|
3185
|
+
const str2 = (key) => {
|
|
3186
|
+
const val = parsed[key];
|
|
3187
|
+
if (val == null)
|
|
3170
3188
|
return "";
|
|
3171
|
-
|
|
3172
|
-
return value === "null" ? "" : value;
|
|
3189
|
+
return String(val);
|
|
3173
3190
|
};
|
|
3174
|
-
const
|
|
3175
|
-
const
|
|
3176
|
-
if (!
|
|
3191
|
+
const arr = (key) => {
|
|
3192
|
+
const val = parsed[key];
|
|
3193
|
+
if (!Array.isArray(val))
|
|
3177
3194
|
return [];
|
|
3178
|
-
return
|
|
3179
|
-
};
|
|
3180
|
-
const getMetadata = () => {
|
|
3181
|
-
const lines = frontmatter.split("\n");
|
|
3182
|
-
let metaStart = -1;
|
|
3183
|
-
for (let j = 0; j < lines.length; j++) {
|
|
3184
|
-
if (/^metadata:\s*$/.test(lines[j])) {
|
|
3185
|
-
metaStart = j + 1;
|
|
3186
|
-
break;
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3189
|
-
if (metaStart === -1)
|
|
3190
|
-
return void 0;
|
|
3191
|
-
const indentedLines = [];
|
|
3192
|
-
for (let j = metaStart; j < lines.length; j++) {
|
|
3193
|
-
if (/^\s/.test(lines[j]) || lines[j].trim() === "") {
|
|
3194
|
-
indentedLines.push(lines[j]);
|
|
3195
|
-
} else {
|
|
3196
|
-
break;
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3199
|
-
if (indentedLines.length === 0)
|
|
3200
|
-
return void 0;
|
|
3201
|
-
try {
|
|
3202
|
-
const parsed = load(indentedLines.join("\n"));
|
|
3203
|
-
if (parsed && typeof parsed === "object")
|
|
3204
|
-
return parsed;
|
|
3205
|
-
return void 0;
|
|
3206
|
-
} catch {
|
|
3207
|
-
return void 0;
|
|
3208
|
-
}
|
|
3195
|
+
return val.filter((v) => v != null).map(String);
|
|
3209
3196
|
};
|
|
3210
3197
|
const sections = rest.split(/\n---\n/);
|
|
3211
3198
|
let body = sections[0] || "";
|
|
@@ -3226,22 +3213,23 @@ ${section}`;
|
|
|
3226
3213
|
i += 1;
|
|
3227
3214
|
}
|
|
3228
3215
|
}
|
|
3229
|
-
const
|
|
3230
|
-
const
|
|
3216
|
+
const actions = arr("actions");
|
|
3217
|
+
const rawMeta = parsed.metadata;
|
|
3218
|
+
const meta = rawMeta != null && typeof rawMeta === "object" && !Array.isArray(rawMeta) ? rawMeta : void 0;
|
|
3231
3219
|
return {
|
|
3232
|
-
version: parseInt(
|
|
3233
|
-
id:
|
|
3234
|
-
status:
|
|
3235
|
-
priority:
|
|
3236
|
-
assignee:
|
|
3237
|
-
dueDate:
|
|
3238
|
-
created:
|
|
3239
|
-
modified:
|
|
3240
|
-
completedAt:
|
|
3241
|
-
labels:
|
|
3242
|
-
attachments:
|
|
3220
|
+
version: typeof parsed.version === "number" ? parsed.version : parseInt(str2("version"), 10) || 0,
|
|
3221
|
+
id: str2("id") || extractIdFromFilename(filePath),
|
|
3222
|
+
status: str2("status") || "backlog",
|
|
3223
|
+
priority: str2("priority") || "medium",
|
|
3224
|
+
assignee: parsed.assignee != null ? String(parsed.assignee) : null,
|
|
3225
|
+
dueDate: parsed.dueDate != null ? String(parsed.dueDate) : null,
|
|
3226
|
+
created: str2("created") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3227
|
+
modified: str2("modified") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3228
|
+
completedAt: parsed.completedAt != null ? String(parsed.completedAt) : null,
|
|
3229
|
+
labels: arr("labels"),
|
|
3230
|
+
attachments: arr("attachments"),
|
|
3243
3231
|
comments,
|
|
3244
|
-
order:
|
|
3232
|
+
order: str2("order") || "a0",
|
|
3245
3233
|
content: body.trim(),
|
|
3246
3234
|
...meta ? { metadata: meta } : {},
|
|
3247
3235
|
...actions.length > 0 ? { actions } : {},
|
|
@@ -3249,37 +3237,28 @@ ${section}`;
|
|
|
3249
3237
|
};
|
|
3250
3238
|
}
|
|
3251
3239
|
function serializeFeature(feature) {
|
|
3252
|
-
const
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
lines.push(" " + line);
|
|
3275
|
-
}
|
|
3276
|
-
}
|
|
3277
|
-
lines.push("---");
|
|
3278
|
-
lines.push("");
|
|
3279
|
-
const frontmatter = lines.join("\n");
|
|
3280
|
-
let result = frontmatter + feature.content;
|
|
3281
|
-
const comments = feature.comments || [];
|
|
3282
|
-
for (const comment of comments) {
|
|
3240
|
+
const frontmatterObj = {
|
|
3241
|
+
version: feature.version ?? CARD_FORMAT_VERSION,
|
|
3242
|
+
id: feature.id,
|
|
3243
|
+
status: feature.status,
|
|
3244
|
+
priority: feature.priority,
|
|
3245
|
+
assignee: feature.assignee ?? null,
|
|
3246
|
+
dueDate: feature.dueDate ?? null,
|
|
3247
|
+
created: feature.created,
|
|
3248
|
+
modified: feature.modified,
|
|
3249
|
+
completedAt: feature.completedAt ?? null,
|
|
3250
|
+
labels: feature.labels,
|
|
3251
|
+
attachments: feature.attachments || [],
|
|
3252
|
+
order: feature.order,
|
|
3253
|
+
...feature.actions?.length ? { actions: feature.actions } : {},
|
|
3254
|
+
...feature.metadata && Object.keys(feature.metadata).length > 0 ? { metadata: feature.metadata } : {}
|
|
3255
|
+
};
|
|
3256
|
+
const yamlStr = dump(frontmatterObj, { lineWidth: -1, quotingType: '"', forceQuotes: true });
|
|
3257
|
+
let result = `---
|
|
3258
|
+
${yamlStr}---
|
|
3259
|
+
|
|
3260
|
+
${feature.content}`;
|
|
3261
|
+
for (const comment of feature.comments || []) {
|
|
3283
3262
|
result += "\n\n---\n";
|
|
3284
3263
|
result += `comment: true
|
|
3285
3264
|
`;
|
|
@@ -4272,25 +4251,28 @@ var KanbanSDK = class {
|
|
|
4272
4251
|
writeConfig(this.workspaceRoot, config);
|
|
4273
4252
|
}
|
|
4274
4253
|
/**
|
|
4275
|
-
* Removes a label definition from the workspace configuration
|
|
4276
|
-
*
|
|
4277
|
-
* This only removes the color/group definition — cards that use this
|
|
4278
|
-
* label keep their label strings. Those labels will render with default
|
|
4279
|
-
* gray styling in the UI.
|
|
4254
|
+
* Removes a label definition from the workspace configuration and cascades
|
|
4255
|
+
* the deletion to all cards by removing the label from their `labels` array.
|
|
4280
4256
|
*
|
|
4281
4257
|
* @param name - The label name to remove.
|
|
4282
4258
|
*
|
|
4283
4259
|
* @example
|
|
4284
4260
|
* ```ts
|
|
4285
|
-
* sdk.deleteLabel('bug')
|
|
4261
|
+
* await sdk.deleteLabel('bug')
|
|
4286
4262
|
* ```
|
|
4287
4263
|
*/
|
|
4288
|
-
deleteLabel(name) {
|
|
4264
|
+
async deleteLabel(name) {
|
|
4289
4265
|
const config = readConfig(this.workspaceRoot);
|
|
4290
4266
|
if (config.labels) {
|
|
4291
4267
|
delete config.labels[name];
|
|
4292
4268
|
writeConfig(this.workspaceRoot, config);
|
|
4293
4269
|
}
|
|
4270
|
+
const cards = await this.listCards();
|
|
4271
|
+
for (const card of cards) {
|
|
4272
|
+
if (card.labels.includes(name)) {
|
|
4273
|
+
await this.updateCard(card.id, { labels: card.labels.filter((l) => l !== name) });
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4294
4276
|
}
|
|
4295
4277
|
/**
|
|
4296
4278
|
* Renames a label in the configuration and cascades the change to all cards.
|
|
@@ -4715,6 +4697,57 @@ var KanbanSDK = class {
|
|
|
4715
4697
|
this.emitEvent("column.deleted", removed);
|
|
4716
4698
|
return board.columns;
|
|
4717
4699
|
}
|
|
4700
|
+
/**
|
|
4701
|
+
* Moves all cards in the specified column to the `deleted` (soft-delete) column.
|
|
4702
|
+
*
|
|
4703
|
+
* This is a non-destructive operation — cards are moved to the reserved
|
|
4704
|
+
* `deleted` status and can be restored or permanently deleted later.
|
|
4705
|
+
* The column itself is not removed.
|
|
4706
|
+
*
|
|
4707
|
+
* @param columnId - The ID of the column whose cards should be moved to `deleted`.
|
|
4708
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
4709
|
+
* @returns A promise resolving to the number of cards that were moved.
|
|
4710
|
+
* @throws {Error} If the column is `'deleted'` (no-op protection).
|
|
4711
|
+
*
|
|
4712
|
+
* @example
|
|
4713
|
+
* ```ts
|
|
4714
|
+
* const moved = await sdk.cleanupColumn('blocked')
|
|
4715
|
+
* console.log(`Moved ${moved} cards to deleted`)
|
|
4716
|
+
* ```
|
|
4717
|
+
*/
|
|
4718
|
+
async cleanupColumn(columnId, boardId) {
|
|
4719
|
+
if (columnId === DELETED_STATUS_ID)
|
|
4720
|
+
return 0;
|
|
4721
|
+
const cards = await this.listCards(void 0, boardId);
|
|
4722
|
+
const cardsToMove = cards.filter((c) => c.status === columnId);
|
|
4723
|
+
for (const card of cardsToMove) {
|
|
4724
|
+
await this.moveCard(card.id, DELETED_STATUS_ID, 0, boardId);
|
|
4725
|
+
}
|
|
4726
|
+
return cardsToMove.length;
|
|
4727
|
+
}
|
|
4728
|
+
/**
|
|
4729
|
+
* Permanently deletes all cards currently in the `deleted` column.
|
|
4730
|
+
*
|
|
4731
|
+
* This is equivalent to "empty trash". All soft-deleted cards are
|
|
4732
|
+
* removed from disk. This operation cannot be undone.
|
|
4733
|
+
*
|
|
4734
|
+
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
4735
|
+
* @returns A promise resolving to the number of cards that were permanently deleted.
|
|
4736
|
+
*
|
|
4737
|
+
* @example
|
|
4738
|
+
* ```ts
|
|
4739
|
+
* const count = await sdk.purgeDeletedCards()
|
|
4740
|
+
* console.log(`Permanently deleted ${count} cards`)
|
|
4741
|
+
* ```
|
|
4742
|
+
*/
|
|
4743
|
+
async purgeDeletedCards(boardId) {
|
|
4744
|
+
const cards = await this.listCards(void 0, boardId);
|
|
4745
|
+
const deleted = cards.filter((c) => c.status === DELETED_STATUS_ID);
|
|
4746
|
+
for (const card of deleted) {
|
|
4747
|
+
await this.permanentlyDeleteCard(card.id, boardId);
|
|
4748
|
+
}
|
|
4749
|
+
return deleted.length;
|
|
4750
|
+
}
|
|
4718
4751
|
/**
|
|
4719
4752
|
* Reorders the columns of a board.
|
|
4720
4753
|
*
|