kanbango 3.0.2 → 3.2.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/.ai/lessons.jsonl +1 -0
- package/.ai/retro/last-run.json +1 -1
- package/AGENTS.md +6 -1
- package/CHANGELOG.md +36 -0
- package/LLM_AGENTS.md +1 -1
- package/README.md +26 -4
- package/agent-playbook.js +22 -6
- package/bin/kanban.js +287 -23
- package/gui-registry.js +14 -1
- package/index.html +687 -51
- package/kanban.js +677 -7
- package/mcp-server.js +239 -32
- package/package.json +1 -1
- package/tests/run.js +2 -0
package/mcp-server.js
CHANGED
|
@@ -146,30 +146,42 @@ function formatTaskResult(task, returnShape) {
|
|
|
146
146
|
return kanban.shapeTask(task, { view: returnShape === 'full' ? 'full' : 'summary' });
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
function guiIdentity(extra = {}) {
|
|
150
|
+
const cwd = extra.cwd || process.cwd();
|
|
151
|
+
return {
|
|
152
|
+
...extra,
|
|
153
|
+
cwd,
|
|
154
|
+
project: extra.project || guiRegistry.projectLabel(cwd)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
149
158
|
async function startGuiServer(port) {
|
|
150
159
|
if (port !== undefined && port !== null && port !== '' && !normalizePort(port)) {
|
|
151
160
|
throw invalidRequest('Invalid port', 'Use an integer between 1 and 65535', { port });
|
|
152
161
|
}
|
|
153
162
|
|
|
154
163
|
if (ownsGuiProcess() && guiPort) {
|
|
155
|
-
return {
|
|
164
|
+
return guiIdentity({
|
|
156
165
|
status: 'already_running',
|
|
157
166
|
owned: true,
|
|
158
167
|
port: guiPort,
|
|
159
168
|
pid: guiProcess.pid,
|
|
160
169
|
url: `http://localhost:${guiPort}`
|
|
161
|
-
};
|
|
170
|
+
});
|
|
162
171
|
}
|
|
163
172
|
|
|
164
173
|
const existing = await guiRegistry.discoverRunningGui();
|
|
165
174
|
if (existing) {
|
|
166
|
-
return {
|
|
175
|
+
return guiIdentity({
|
|
167
176
|
status: 'already_running',
|
|
168
177
|
owned: false,
|
|
169
178
|
port: existing.port,
|
|
170
179
|
pid: existing.pid,
|
|
171
|
-
url: existing.url
|
|
172
|
-
|
|
180
|
+
url: existing.url,
|
|
181
|
+
cwd: existing.cwd,
|
|
182
|
+
project: existing.project,
|
|
183
|
+
started_at: existing.started_at
|
|
184
|
+
});
|
|
173
185
|
}
|
|
174
186
|
|
|
175
187
|
const desiredPort = guiRegistry.resolvePreferredGuiPort(port);
|
|
@@ -194,19 +206,24 @@ async function startGuiServer(port) {
|
|
|
194
206
|
const ready = await waitForGuiReady(childPid);
|
|
195
207
|
guiPort = ready.port;
|
|
196
208
|
|
|
197
|
-
return {
|
|
209
|
+
return guiIdentity({
|
|
198
210
|
status: 'started',
|
|
199
211
|
owned: true,
|
|
200
212
|
port: ready.port,
|
|
201
213
|
pid: ready.pid,
|
|
202
|
-
url: ready.url
|
|
203
|
-
|
|
214
|
+
url: ready.url,
|
|
215
|
+
cwd: ready.cwd,
|
|
216
|
+
project: ready.project,
|
|
217
|
+
started_at: ready.started_at
|
|
218
|
+
});
|
|
204
219
|
}
|
|
205
220
|
|
|
206
221
|
async function stopGuiServer() {
|
|
207
222
|
if (ownsGuiProcess()) {
|
|
208
223
|
const port = guiPort;
|
|
209
224
|
const pid = guiProcess.pid;
|
|
225
|
+
const project = guiRegistry.projectLabel();
|
|
226
|
+
const cwd = process.cwd();
|
|
210
227
|
guiProcess.kill();
|
|
211
228
|
|
|
212
229
|
const deadline = Date.now() + 2000;
|
|
@@ -219,7 +236,7 @@ async function stopGuiServer() {
|
|
|
219
236
|
await guiRegistry.clearGuiPortFile({ force: true });
|
|
220
237
|
guiProcess = null;
|
|
221
238
|
guiPort = null;
|
|
222
|
-
return { status: 'stopping', owned: true, port, pid };
|
|
239
|
+
return { status: 'stopping', owned: true, port, pid, project, cwd };
|
|
223
240
|
}
|
|
224
241
|
|
|
225
242
|
guiProcess = null;
|
|
@@ -230,25 +247,28 @@ async function stopGuiServer() {
|
|
|
230
247
|
return { status: 'not_running' };
|
|
231
248
|
}
|
|
232
249
|
|
|
233
|
-
return {
|
|
250
|
+
return guiIdentity({
|
|
234
251
|
status: 'external_running',
|
|
235
252
|
owned: false,
|
|
236
253
|
port: discovered.port,
|
|
237
254
|
pid: discovered.pid,
|
|
238
255
|
url: discovered.url,
|
|
256
|
+
cwd: discovered.cwd,
|
|
257
|
+
project: discovered.project,
|
|
258
|
+
started_at: discovered.started_at,
|
|
239
259
|
hint: 'GUI was not started by this MCP process; stop refused. Stop it from the owning terminal or kill that PID manually.'
|
|
240
|
-
};
|
|
260
|
+
});
|
|
241
261
|
}
|
|
242
262
|
|
|
243
263
|
async function guiStatus() {
|
|
244
264
|
if (ownsGuiProcess() && guiPort) {
|
|
245
|
-
return {
|
|
265
|
+
return guiIdentity({
|
|
246
266
|
status: 'running',
|
|
247
267
|
owned: true,
|
|
248
268
|
port: guiPort,
|
|
249
269
|
pid: guiProcess.pid,
|
|
250
270
|
url: `http://localhost:${guiPort}`
|
|
251
|
-
};
|
|
271
|
+
});
|
|
252
272
|
}
|
|
253
273
|
|
|
254
274
|
guiProcess = null;
|
|
@@ -259,15 +279,16 @@ async function guiStatus() {
|
|
|
259
279
|
return { status: 'not_running' };
|
|
260
280
|
}
|
|
261
281
|
|
|
262
|
-
return {
|
|
282
|
+
return guiIdentity({
|
|
263
283
|
status: 'external_running',
|
|
264
284
|
owned: false,
|
|
265
285
|
port: discovered.port,
|
|
266
286
|
pid: discovered.pid,
|
|
267
287
|
url: discovered.url,
|
|
268
288
|
cwd: discovered.cwd,
|
|
289
|
+
project: discovered.project,
|
|
269
290
|
started_at: discovered.started_at
|
|
270
|
-
};
|
|
291
|
+
});
|
|
271
292
|
}
|
|
272
293
|
|
|
273
294
|
const server = new Server(
|
|
@@ -293,14 +314,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
293
314
|
properties: {
|
|
294
315
|
operation: {
|
|
295
316
|
type: 'string',
|
|
296
|
-
enum: ['list', 'show', 'help'],
|
|
297
|
-
description: 'list=
|
|
317
|
+
enum: ['list', 'show', 'list_epics', 'show_epic', 'help'],
|
|
318
|
+
description: 'list/show=tasks; list_epics/show_epic=initiative containers; help=token playbook',
|
|
298
319
|
default: 'list'
|
|
299
320
|
},
|
|
300
321
|
task_id: {
|
|
301
322
|
type: 'string',
|
|
302
323
|
description: "Required for show. Numeric id: '014' or '14'."
|
|
303
324
|
},
|
|
325
|
+
epic_id: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
description: "Required for show_epic. Epic id like 'E001'."
|
|
328
|
+
},
|
|
304
329
|
col: {
|
|
305
330
|
type: 'string',
|
|
306
331
|
enum: COLS,
|
|
@@ -308,7 +333,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
308
333
|
},
|
|
309
334
|
epic: {
|
|
310
335
|
type: 'string',
|
|
311
|
-
description: 'Filter list by epic
|
|
336
|
+
description: 'Filter list by epic id or title'
|
|
337
|
+
},
|
|
338
|
+
include_archived: {
|
|
339
|
+
type: 'boolean',
|
|
340
|
+
description: 'list/list_epics: include archived epics and their tasks (default false)'
|
|
341
|
+
},
|
|
342
|
+
include_done: {
|
|
343
|
+
type: 'boolean',
|
|
344
|
+
description: 'list_epics: include status=done epics without archived (default false)'
|
|
345
|
+
},
|
|
346
|
+
status: {
|
|
347
|
+
type: 'string',
|
|
348
|
+
enum: ['empty', 'planned', 'active', 'done', 'archived'],
|
|
349
|
+
description: 'list_epics: exact status filter (overrides live-only default)'
|
|
312
350
|
},
|
|
313
351
|
view: {
|
|
314
352
|
type: 'string',
|
|
@@ -332,12 +370,27 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
332
370
|
properties: {
|
|
333
371
|
action: {
|
|
334
372
|
type: 'string',
|
|
335
|
-
enum: [
|
|
336
|
-
|
|
373
|
+
enum: [
|
|
374
|
+
'create',
|
|
375
|
+
'move',
|
|
376
|
+
'update',
|
|
377
|
+
'delete',
|
|
378
|
+
'epic_create',
|
|
379
|
+
'epic_update',
|
|
380
|
+
'epic_archive',
|
|
381
|
+
'epic_unarchive',
|
|
382
|
+
'epic_delete',
|
|
383
|
+
'plan_create',
|
|
384
|
+
'plan_advance',
|
|
385
|
+
'plan_evidence',
|
|
386
|
+
'plan_done',
|
|
387
|
+
'plan_status'
|
|
388
|
+
],
|
|
389
|
+
description: 'create|move|update|delete daily; epic_create|epic_update|epic_archive|epic_unarchive|epic_delete; plan_* multi-step'
|
|
337
390
|
},
|
|
338
391
|
title: {
|
|
339
392
|
type: 'string',
|
|
340
|
-
description: 'Required for create
|
|
393
|
+
description: 'Required for create, plan_create, epic_create'
|
|
341
394
|
},
|
|
342
395
|
col: {
|
|
343
396
|
type: 'string',
|
|
@@ -348,11 +401,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
348
401
|
epic: {
|
|
349
402
|
type: 'string',
|
|
350
403
|
default: '—',
|
|
351
|
-
description: 'Epic
|
|
404
|
+
description: 'Epic id or title (create/update/plan_create). Prefer E001.'
|
|
405
|
+
},
|
|
406
|
+
epic_id: {
|
|
407
|
+
type: 'string',
|
|
408
|
+
description: 'Required for epic_update|epic_archive|epic_unarchive|epic_delete; optional link id'
|
|
352
409
|
},
|
|
353
410
|
description: {
|
|
354
411
|
type: 'string',
|
|
355
|
-
description: 'Why/context (recommended on create)'
|
|
412
|
+
description: 'Why/context (recommended on create / epic_create)'
|
|
413
|
+
},
|
|
414
|
+
goals: {
|
|
415
|
+
type: 'string',
|
|
416
|
+
description: 'Epic outcome one-liner (recommended on epic_create)'
|
|
356
417
|
},
|
|
357
418
|
specs: {
|
|
358
419
|
type: 'string',
|
|
@@ -397,7 +458,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
397
458
|
},
|
|
398
459
|
task_id: {
|
|
399
460
|
type: 'string',
|
|
400
|
-
description: "Required for move/update/plan_* except plan_create. '014' or '14'."
|
|
461
|
+
description: "Required for move/update/delete/plan_* except plan_create. '014' or '14'."
|
|
401
462
|
},
|
|
402
463
|
column: {
|
|
403
464
|
type: 'string',
|
|
@@ -411,7 +472,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
411
472
|
return: {
|
|
412
473
|
type: 'string',
|
|
413
474
|
enum: ['none', 'summary', 'full'],
|
|
414
|
-
description: 'move/update response size. Prefer none. Default summary. create
|
|
475
|
+
description: 'move/update/delete/epic_* response size. Prefer none. Default summary. create/epic_create return full once.'
|
|
415
476
|
},
|
|
416
477
|
index: {
|
|
417
478
|
type: 'integer',
|
|
@@ -493,12 +554,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
493
554
|
const readOptions = normalizeReadOptions(args, 'summary');
|
|
494
555
|
|
|
495
556
|
if (operation === 'list') {
|
|
496
|
-
|
|
557
|
+
await kanban.migrateEpicGroups();
|
|
558
|
+
const epics = await kanban.listEpicEntities();
|
|
559
|
+
let tasks = await kanban.allTasks();
|
|
497
560
|
if (args.col) {
|
|
498
561
|
tasks = tasks.filter((task) => task.column === args.col);
|
|
499
562
|
}
|
|
500
|
-
|
|
501
|
-
|
|
563
|
+
// Explicit epic filter bypasses live-only hide (agent asked for that initiative)
|
|
564
|
+
if (args.epic || args.epic_id) {
|
|
565
|
+
const filter = args.epic_id || args.epic;
|
|
566
|
+
tasks = tasks.filter((task) => kanban.taskMatchesEpicFilter(task, filter));
|
|
567
|
+
} else {
|
|
568
|
+
tasks = kanban.filterTasksForList(tasks, epics, {
|
|
569
|
+
include_archived: args.include_archived,
|
|
570
|
+
include_done: args.include_done
|
|
571
|
+
});
|
|
502
572
|
}
|
|
503
573
|
|
|
504
574
|
result = tasks.map((task) => kanban.shapeTask(task, readOptions));
|
|
@@ -512,10 +582,43 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
512
582
|
}
|
|
513
583
|
|
|
514
584
|
result = kanban.shapeTask(await kanban.getTask(args.task_id), readOptions);
|
|
585
|
+
} else if (operation === 'list_epics') {
|
|
586
|
+
await kanban.migrateEpicGroups();
|
|
587
|
+
const tasks = await kanban.allTasks();
|
|
588
|
+
const epics = await kanban.listEpicEntities();
|
|
589
|
+
const epicView = args.view === 'full' || args.view === 'planning' || args.view === 'execution'
|
|
590
|
+
? (args.view === 'execution' ? 'planning' : args.view)
|
|
591
|
+
: 'summary';
|
|
592
|
+
const shaped = epics.map((epic) => kanban.shapeEpic(epic, tasks, {
|
|
593
|
+
view: Array.isArray(args.fields) && args.fields.length > 0 ? undefined : epicView,
|
|
594
|
+
fields: args.fields
|
|
595
|
+
}));
|
|
596
|
+
result = kanban.filterShapedEpics(shaped, {
|
|
597
|
+
include_archived: args.include_archived,
|
|
598
|
+
include_done: args.include_done,
|
|
599
|
+
status: args.status
|
|
600
|
+
});
|
|
601
|
+
} else if (operation === 'show_epic') {
|
|
602
|
+
const epicRef = args.epic_id || args.epic;
|
|
603
|
+
if (!epicRef) {
|
|
604
|
+
throw invalidRequest(
|
|
605
|
+
"epic_id is required for 'show_epic' operation",
|
|
606
|
+
'Provide an epic id like E001',
|
|
607
|
+
{ operation }
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
await kanban.migrateEpicGroups();
|
|
611
|
+
const link = await kanban.resolveEpicRef(epicRef, { createIfMissing: false });
|
|
612
|
+
const epic = await kanban.getEpicEntity(link.epic_id);
|
|
613
|
+
const tasks = await kanban.allTasks();
|
|
614
|
+
result = kanban.shapeEpic(epic, tasks, {
|
|
615
|
+
view: args.view || 'full',
|
|
616
|
+
fields: args.fields
|
|
617
|
+
});
|
|
515
618
|
} else {
|
|
516
619
|
throw invalidRequest(
|
|
517
620
|
`Unknown operation: ${operation}`,
|
|
518
|
-
'Use one of: list, show, help',
|
|
621
|
+
'Use one of: list, show, list_epics, show_epic, help',
|
|
519
622
|
{ operation }
|
|
520
623
|
);
|
|
521
624
|
}
|
|
@@ -538,7 +641,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
538
641
|
subtasks: args.subtasks,
|
|
539
642
|
notes: args.notes
|
|
540
643
|
};
|
|
541
|
-
const
|
|
644
|
+
const epicRef = args.epic_id || args.epic || '—';
|
|
645
|
+
const created = await kanban.doCreate(args.title, args.col || 'planned', epicRef, createPayload);
|
|
542
646
|
const shaped = kanban.shapeTask(created, { view: 'full' });
|
|
543
647
|
const warnings = kanban.createFieldWarnings(createPayload);
|
|
544
648
|
result = warnings.length > 0
|
|
@@ -547,6 +651,108 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
547
651
|
break;
|
|
548
652
|
}
|
|
549
653
|
|
|
654
|
+
case 'epic_create': {
|
|
655
|
+
const epicPayload = {
|
|
656
|
+
description: args.description,
|
|
657
|
+
goals: args.goals,
|
|
658
|
+
in_scope: args.in_scope,
|
|
659
|
+
out_of_scope: args.out_of_scope,
|
|
660
|
+
notes: args.notes
|
|
661
|
+
};
|
|
662
|
+
const createdEpic = await kanban.doCreateEpic(args.title, epicPayload);
|
|
663
|
+
const shapedEpic = kanban.shapeEpic(createdEpic, [], { view: 'full' });
|
|
664
|
+
const epicWarnings = kanban.createEpicFieldWarnings(epicPayload);
|
|
665
|
+
result = epicWarnings.length > 0
|
|
666
|
+
? {
|
|
667
|
+
...shapedEpic,
|
|
668
|
+
warnings: epicWarnings,
|
|
669
|
+
missing_recommended: kanban.missingRecommendedEpicCreateFields(epicPayload)
|
|
670
|
+
}
|
|
671
|
+
: shapedEpic;
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
case 'epic_update': {
|
|
676
|
+
const epicId = args.epic_id || args.epic;
|
|
677
|
+
if (!epicId) {
|
|
678
|
+
throw invalidRequest(
|
|
679
|
+
"epic_id is required for 'epic_update'",
|
|
680
|
+
'Provide an epic id like E001',
|
|
681
|
+
{ action }
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
const epicPatch = args.patch ? { ...args.patch } : {};
|
|
685
|
+
if (args.title !== undefined) epicPatch.title = args.title;
|
|
686
|
+
if (args.description !== undefined) epicPatch.description = args.description;
|
|
687
|
+
if (args.goals !== undefined) epicPatch.goals = args.goals;
|
|
688
|
+
if (args.in_scope !== undefined) epicPatch.in_scope = args.in_scope;
|
|
689
|
+
if (args.out_of_scope !== undefined) epicPatch.out_of_scope = args.out_of_scope;
|
|
690
|
+
if (args.notes !== undefined) epicPatch.notes = args.notes;
|
|
691
|
+
if (args.patch && args.patch.archived !== undefined) {
|
|
692
|
+
epicPatch.archived = args.patch.archived;
|
|
693
|
+
}
|
|
694
|
+
const updatedEpic = await kanban.updateEpicEntity(epicId, epicPatch);
|
|
695
|
+
if (returnShape === 'none') {
|
|
696
|
+
result = { ok: true, epic_id: updatedEpic.id };
|
|
697
|
+
} else if (returnShape === 'summary') {
|
|
698
|
+
result = kanban.shapeEpic(updatedEpic, await kanban.allTasks(), { view: 'summary' });
|
|
699
|
+
} else {
|
|
700
|
+
result = kanban.shapeEpic(updatedEpic, await kanban.allTasks(), { view: 'full' });
|
|
701
|
+
}
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
case 'epic_archive':
|
|
706
|
+
case 'epic_unarchive': {
|
|
707
|
+
const epicId = args.epic_id || args.epic;
|
|
708
|
+
if (!epicId) {
|
|
709
|
+
throw invalidRequest(
|
|
710
|
+
`epic_id is required for '${action}'`,
|
|
711
|
+
'Provide an epic id like E001',
|
|
712
|
+
{ action }
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
const toggled = action === 'epic_archive'
|
|
716
|
+
? await kanban.archiveEpic(epicId)
|
|
717
|
+
: await kanban.unarchiveEpic(epicId);
|
|
718
|
+
if (returnShape === 'none') {
|
|
719
|
+
result = { ok: true, epic_id: toggled.id, archived: toggled.archived };
|
|
720
|
+
} else if (returnShape === 'summary') {
|
|
721
|
+
result = kanban.shapeEpic(toggled, await kanban.allTasks(), { view: 'summary' });
|
|
722
|
+
} else {
|
|
723
|
+
result = kanban.shapeEpic(toggled, await kanban.allTasks(), { view: 'full' });
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
case 'epic_delete': {
|
|
729
|
+
const epicId = args.epic_id || args.epic;
|
|
730
|
+
if (!epicId) {
|
|
731
|
+
throw invalidRequest(
|
|
732
|
+
"epic_id is required for 'epic_delete'",
|
|
733
|
+
'Provide an epic id like E001',
|
|
734
|
+
{ action }
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
result = await kanban.deleteEpic(epicId);
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
case 'delete': {
|
|
742
|
+
if (!args.task_id) {
|
|
743
|
+
throw invalidRequest(
|
|
744
|
+
"task_id is required for 'delete'",
|
|
745
|
+
'Provide a task ID',
|
|
746
|
+
{ action }
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
const deleted = await kanban.deleteTask(args.task_id);
|
|
750
|
+
result = returnShape === 'none'
|
|
751
|
+
? { ok: true, task_id: deleted.task_id }
|
|
752
|
+
: deleted;
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
|
|
550
756
|
case 'move': {
|
|
551
757
|
if (!args.task_id) {
|
|
552
758
|
throw invalidRequest(
|
|
@@ -585,7 +791,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
585
791
|
if (args.test_cases !== undefined) patch.test_cases = args.test_cases;
|
|
586
792
|
if (args.subtasks !== undefined) patch.subtasks = args.subtasks;
|
|
587
793
|
if (args.notes !== undefined) patch.notes = args.notes;
|
|
588
|
-
if (args.
|
|
794
|
+
if (args.epic_id !== undefined) patch.epic_id = args.epic_id;
|
|
795
|
+
else if (args.epic !== undefined) patch.epic = args.epic;
|
|
589
796
|
if (args.col !== undefined) patch.column = args.col;
|
|
590
797
|
const updated = await kanban.updateTask(args.task_id, patch);
|
|
591
798
|
result = formatTaskResult(updated, returnShape);
|
|
@@ -620,7 +827,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
620
827
|
default:
|
|
621
828
|
throw invalidRequest(
|
|
622
829
|
`Unknown action: ${action}`,
|
|
623
|
-
'Use create, move, update, plan_create, plan_advance, plan_evidence, plan_done, or plan_status',
|
|
830
|
+
'Use create, move, update, delete, epic_create, epic_update, epic_archive, epic_unarchive, epic_delete, plan_create, plan_advance, plan_evidence, plan_done, or plan_status',
|
|
624
831
|
{ action }
|
|
625
832
|
);
|
|
626
833
|
}
|
package/package.json
CHANGED
package/tests/run.js
CHANGED
|
@@ -20,3 +20,5 @@ 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');
|
|
24
|
+
runNode(path.join('tests', 'delete-archive.test.js'), [], 'Delete/archive test');
|