kanbango 3.0.2 → 3.1.0
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/AGENTS.md +3 -0
- package/CHANGELOG.md +18 -0
- package/README.md +18 -3
- package/agent-playbook.js +13 -5
- package/bin/kanban.js +163 -20
- package/index.html +61 -31
- package/kanban.js +525 -7
- package/mcp-server.js +119 -16
- package/package.json +1 -1
- package/tests/run.js +1 -0
package/mcp-server.js
CHANGED
|
@@ -293,14 +293,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
293
293
|
properties: {
|
|
294
294
|
operation: {
|
|
295
295
|
type: 'string',
|
|
296
|
-
enum: ['list', 'show', 'help'],
|
|
297
|
-
description: 'list=
|
|
296
|
+
enum: ['list', 'show', 'list_epics', 'show_epic', 'help'],
|
|
297
|
+
description: 'list/show=tasks; list_epics/show_epic=initiative containers; help=token playbook',
|
|
298
298
|
default: 'list'
|
|
299
299
|
},
|
|
300
300
|
task_id: {
|
|
301
301
|
type: 'string',
|
|
302
302
|
description: "Required for show. Numeric id: '014' or '14'."
|
|
303
303
|
},
|
|
304
|
+
epic_id: {
|
|
305
|
+
type: 'string',
|
|
306
|
+
description: "Required for show_epic. Epic id like 'E001'."
|
|
307
|
+
},
|
|
304
308
|
col: {
|
|
305
309
|
type: 'string',
|
|
306
310
|
enum: COLS,
|
|
@@ -308,7 +312,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
308
312
|
},
|
|
309
313
|
epic: {
|
|
310
314
|
type: 'string',
|
|
311
|
-
description: 'Filter list by epic
|
|
315
|
+
description: 'Filter list by epic id or title'
|
|
312
316
|
},
|
|
313
317
|
view: {
|
|
314
318
|
type: 'string',
|
|
@@ -332,12 +336,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
332
336
|
properties: {
|
|
333
337
|
action: {
|
|
334
338
|
type: 'string',
|
|
335
|
-
enum: [
|
|
336
|
-
|
|
339
|
+
enum: [
|
|
340
|
+
'create',
|
|
341
|
+
'move',
|
|
342
|
+
'update',
|
|
343
|
+
'epic_create',
|
|
344
|
+
'epic_update',
|
|
345
|
+
'plan_create',
|
|
346
|
+
'plan_advance',
|
|
347
|
+
'plan_evidence',
|
|
348
|
+
'plan_done',
|
|
349
|
+
'plan_status'
|
|
350
|
+
],
|
|
351
|
+
description: 'create|move|update daily; epic_create|epic_update for containers; plan_* for multi-step work'
|
|
337
352
|
},
|
|
338
353
|
title: {
|
|
339
354
|
type: 'string',
|
|
340
|
-
description: 'Required for create
|
|
355
|
+
description: 'Required for create, plan_create, epic_create'
|
|
341
356
|
},
|
|
342
357
|
col: {
|
|
343
358
|
type: 'string',
|
|
@@ -348,11 +363,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
348
363
|
epic: {
|
|
349
364
|
type: 'string',
|
|
350
365
|
default: '—',
|
|
351
|
-
description: 'Epic
|
|
366
|
+
description: 'Epic id or title (create/update/plan_create). Prefer E001.'
|
|
367
|
+
},
|
|
368
|
+
epic_id: {
|
|
369
|
+
type: 'string',
|
|
370
|
+
description: 'Required for epic_update; optional filter/link id'
|
|
352
371
|
},
|
|
353
372
|
description: {
|
|
354
373
|
type: 'string',
|
|
355
|
-
description: 'Why/context (recommended on create)'
|
|
374
|
+
description: 'Why/context (recommended on create / epic_create)'
|
|
375
|
+
},
|
|
376
|
+
goals: {
|
|
377
|
+
type: 'string',
|
|
378
|
+
description: 'Epic outcome one-liner (recommended on epic_create)'
|
|
356
379
|
},
|
|
357
380
|
specs: {
|
|
358
381
|
type: 'string',
|
|
@@ -411,7 +434,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
411
434
|
return: {
|
|
412
435
|
type: 'string',
|
|
413
436
|
enum: ['none', 'summary', 'full'],
|
|
414
|
-
description: 'move/update response size. Prefer none. Default summary. create
|
|
437
|
+
description: 'move/update/epic_update response size. Prefer none. Default summary. create/epic_create return full once.'
|
|
415
438
|
},
|
|
416
439
|
index: {
|
|
417
440
|
type: 'integer',
|
|
@@ -493,12 +516,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
493
516
|
const readOptions = normalizeReadOptions(args, 'summary');
|
|
494
517
|
|
|
495
518
|
if (operation === 'list') {
|
|
496
|
-
|
|
519
|
+
await kanban.migrateEpicGroups();
|
|
520
|
+
let tasks = await kanban.allTasks();
|
|
497
521
|
if (args.col) {
|
|
498
522
|
tasks = tasks.filter((task) => task.column === args.col);
|
|
499
523
|
}
|
|
500
|
-
if (args.epic) {
|
|
501
|
-
|
|
524
|
+
if (args.epic || args.epic_id) {
|
|
525
|
+
const filter = args.epic_id || args.epic;
|
|
526
|
+
tasks = tasks.filter((task) => kanban.taskMatchesEpicFilter(task, filter));
|
|
502
527
|
}
|
|
503
528
|
|
|
504
529
|
result = tasks.map((task) => kanban.shapeTask(task, readOptions));
|
|
@@ -512,10 +537,38 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
512
537
|
}
|
|
513
538
|
|
|
514
539
|
result = kanban.shapeTask(await kanban.getTask(args.task_id), readOptions);
|
|
540
|
+
} else if (operation === 'list_epics') {
|
|
541
|
+
await kanban.migrateEpicGroups();
|
|
542
|
+
const tasks = await kanban.allTasks();
|
|
543
|
+
const epics = await kanban.listEpicEntities();
|
|
544
|
+
const epicView = args.view === 'full' || args.view === 'planning' || args.view === 'execution'
|
|
545
|
+
? (args.view === 'execution' ? 'planning' : args.view)
|
|
546
|
+
: 'summary';
|
|
547
|
+
result = epics.map((epic) => kanban.shapeEpic(epic, tasks, {
|
|
548
|
+
view: Array.isArray(args.fields) && args.fields.length > 0 ? undefined : epicView,
|
|
549
|
+
fields: args.fields
|
|
550
|
+
}));
|
|
551
|
+
} else if (operation === 'show_epic') {
|
|
552
|
+
const epicRef = args.epic_id || args.epic;
|
|
553
|
+
if (!epicRef) {
|
|
554
|
+
throw invalidRequest(
|
|
555
|
+
"epic_id is required for 'show_epic' operation",
|
|
556
|
+
'Provide an epic id like E001',
|
|
557
|
+
{ operation }
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
await kanban.migrateEpicGroups();
|
|
561
|
+
const link = await kanban.resolveEpicRef(epicRef, { createIfMissing: false });
|
|
562
|
+
const epic = await kanban.getEpicEntity(link.epic_id);
|
|
563
|
+
const tasks = await kanban.allTasks();
|
|
564
|
+
result = kanban.shapeEpic(epic, tasks, {
|
|
565
|
+
view: args.view || 'full',
|
|
566
|
+
fields: args.fields
|
|
567
|
+
});
|
|
515
568
|
} else {
|
|
516
569
|
throw invalidRequest(
|
|
517
570
|
`Unknown operation: ${operation}`,
|
|
518
|
-
'Use one of: list, show, help',
|
|
571
|
+
'Use one of: list, show, list_epics, show_epic, help',
|
|
519
572
|
{ operation }
|
|
520
573
|
);
|
|
521
574
|
}
|
|
@@ -538,7 +591,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
538
591
|
subtasks: args.subtasks,
|
|
539
592
|
notes: args.notes
|
|
540
593
|
};
|
|
541
|
-
const
|
|
594
|
+
const epicRef = args.epic_id || args.epic || '—';
|
|
595
|
+
const created = await kanban.doCreate(args.title, args.col || 'planned', epicRef, createPayload);
|
|
542
596
|
const shaped = kanban.shapeTask(created, { view: 'full' });
|
|
543
597
|
const warnings = kanban.createFieldWarnings(createPayload);
|
|
544
598
|
result = warnings.length > 0
|
|
@@ -547,6 +601,54 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
547
601
|
break;
|
|
548
602
|
}
|
|
549
603
|
|
|
604
|
+
case 'epic_create': {
|
|
605
|
+
const epicPayload = {
|
|
606
|
+
description: args.description,
|
|
607
|
+
goals: args.goals,
|
|
608
|
+
in_scope: args.in_scope,
|
|
609
|
+
out_of_scope: args.out_of_scope,
|
|
610
|
+
notes: args.notes
|
|
611
|
+
};
|
|
612
|
+
const createdEpic = await kanban.doCreateEpic(args.title, epicPayload);
|
|
613
|
+
const shapedEpic = kanban.shapeEpic(createdEpic, [], { view: 'full' });
|
|
614
|
+
const epicWarnings = kanban.createEpicFieldWarnings(epicPayload);
|
|
615
|
+
result = epicWarnings.length > 0
|
|
616
|
+
? {
|
|
617
|
+
...shapedEpic,
|
|
618
|
+
warnings: epicWarnings,
|
|
619
|
+
missing_recommended: kanban.missingRecommendedEpicCreateFields(epicPayload)
|
|
620
|
+
}
|
|
621
|
+
: shapedEpic;
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
case 'epic_update': {
|
|
626
|
+
const epicId = args.epic_id || args.epic;
|
|
627
|
+
if (!epicId) {
|
|
628
|
+
throw invalidRequest(
|
|
629
|
+
"epic_id is required for 'epic_update'",
|
|
630
|
+
'Provide an epic id like E001',
|
|
631
|
+
{ action }
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
const epicPatch = args.patch ? { ...args.patch } : {};
|
|
635
|
+
if (args.title !== undefined) epicPatch.title = args.title;
|
|
636
|
+
if (args.description !== undefined) epicPatch.description = args.description;
|
|
637
|
+
if (args.goals !== undefined) epicPatch.goals = args.goals;
|
|
638
|
+
if (args.in_scope !== undefined) epicPatch.in_scope = args.in_scope;
|
|
639
|
+
if (args.out_of_scope !== undefined) epicPatch.out_of_scope = args.out_of_scope;
|
|
640
|
+
if (args.notes !== undefined) epicPatch.notes = args.notes;
|
|
641
|
+
const updatedEpic = await kanban.updateEpicEntity(epicId, epicPatch);
|
|
642
|
+
if (returnShape === 'none') {
|
|
643
|
+
result = { ok: true, epic_id: updatedEpic.id };
|
|
644
|
+
} else if (returnShape === 'summary') {
|
|
645
|
+
result = kanban.shapeEpic(updatedEpic, await kanban.allTasks(), { view: 'summary' });
|
|
646
|
+
} else {
|
|
647
|
+
result = kanban.shapeEpic(updatedEpic, await kanban.allTasks(), { view: 'full' });
|
|
648
|
+
}
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
|
|
550
652
|
case 'move': {
|
|
551
653
|
if (!args.task_id) {
|
|
552
654
|
throw invalidRequest(
|
|
@@ -585,7 +687,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
585
687
|
if (args.test_cases !== undefined) patch.test_cases = args.test_cases;
|
|
586
688
|
if (args.subtasks !== undefined) patch.subtasks = args.subtasks;
|
|
587
689
|
if (args.notes !== undefined) patch.notes = args.notes;
|
|
588
|
-
if (args.
|
|
690
|
+
if (args.epic_id !== undefined) patch.epic_id = args.epic_id;
|
|
691
|
+
else if (args.epic !== undefined) patch.epic = args.epic;
|
|
589
692
|
if (args.col !== undefined) patch.column = args.col;
|
|
590
693
|
const updated = await kanban.updateTask(args.task_id, patch);
|
|
591
694
|
result = formatTaskResult(updated, returnShape);
|
|
@@ -620,7 +723,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
620
723
|
default:
|
|
621
724
|
throw invalidRequest(
|
|
622
725
|
`Unknown action: ${action}`,
|
|
623
|
-
'Use create, move, update, plan_create, plan_advance, plan_evidence, plan_done, or plan_status',
|
|
726
|
+
'Use create, move, update, epic_create, epic_update, plan_create, plan_advance, plan_evidence, plan_done, or plan_status',
|
|
624
727
|
{ action }
|
|
625
728
|
);
|
|
626
729
|
}
|
package/package.json
CHANGED
package/tests/run.js
CHANGED
|
@@ -20,3 +20,4 @@ runNode(path.join('tests', 'mcp-server.test.js'), [], 'MCP server test');
|
|
|
20
20
|
runNode(path.join('tests', 'gui-port.test.js'), [], 'GUI port test');
|
|
21
21
|
runNode(path.join('tests', 'plan-workflow.test.js'), [], 'Plan workflow test');
|
|
22
22
|
runNode(path.join('tests', 'agent-playbook.test.js'), [], 'Agent playbook test');
|
|
23
|
+
runNode(path.join('tests', 'epics.test.js'), [], 'Epics test');
|