@omnicoreos/planka-mcp 0.2.0 → 0.3.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/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +26 -0
- package/.claude-plugin/plugin.json +45 -0
- package/.codex-plugin/mcp.json +16 -0
- package/.codex-plugin/plugin.json +27 -0
- package/.mcp.json +17 -0
- package/CHANGELOG.md +510 -0
- package/README.es.md +294 -55
- package/README.md +293 -55
- package/dist/cli/init.d.ts +101 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +481 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/client.d.ts +32 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +106 -32
- package/dist/client.js.map +1 -1
- package/dist/config/policy.d.ts +82 -0
- package/dist/config/policy.d.ts.map +1 -0
- package/dist/config/policy.js +226 -0
- package/dist/config/policy.js.map +1 -0
- package/dist/errors.d.ts +5 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +62 -5
- package/dist/errors.js.map +1 -1
- package/dist/identity.generated.d.ts +2 -1
- package/dist/identity.generated.d.ts.map +1 -1
- package/dist/identity.generated.js +2 -1
- package/dist/identity.generated.js.map +1 -1
- package/dist/index.js +85 -10
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +21 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +37 -0
- package/dist/instructions.js.map +1 -0
- package/dist/operations/actions.d.ts +654 -0
- package/dist/operations/actions.d.ts.map +1 -0
- package/dist/operations/actions.js +154 -0
- package/dist/operations/actions.js.map +1 -0
- package/dist/operations/archive.d.ts +28 -0
- package/dist/operations/archive.d.ts.map +1 -0
- package/dist/operations/archive.js +74 -0
- package/dist/operations/archive.js.map +1 -0
- package/dist/operations/attachments.d.ts +1 -1
- package/dist/operations/attachments.d.ts.map +1 -1
- package/dist/operations/attachments.js +3 -1
- package/dist/operations/attachments.js.map +1 -1
- package/dist/operations/board-id.d.ts +1 -1
- package/dist/operations/board-id.d.ts.map +1 -1
- package/dist/operations/board-id.js +13 -7
- package/dist/operations/board-id.js.map +1 -1
- package/dist/operations/boards.d.ts +96 -19
- package/dist/operations/boards.d.ts.map +1 -1
- package/dist/operations/boards.js +377 -93
- package/dist/operations/boards.js.map +1 -1
- package/dist/operations/card-brief.d.ts +91 -0
- package/dist/operations/card-brief.d.ts.map +1 -0
- package/dist/operations/card-brief.js +79 -0
- package/dist/operations/card-brief.js.map +1 -0
- package/dist/operations/cards.d.ts +34 -9
- package/dist/operations/cards.d.ts.map +1 -1
- package/dist/operations/cards.js +60 -14
- package/dist/operations/cards.js.map +1 -1
- package/dist/operations/comments.d.ts +61 -4
- package/dist/operations/comments.d.ts.map +1 -1
- package/dist/operations/comments.js +91 -8
- package/dist/operations/comments.js.map +1 -1
- package/dist/operations/duplicate.d.ts +16 -0
- package/dist/operations/duplicate.d.ts.map +1 -0
- package/dist/operations/duplicate.js +43 -0
- package/dist/operations/duplicate.js.map +1 -0
- package/dist/operations/labels.d.ts +1 -1
- package/dist/operations/labels.d.ts.map +1 -1
- package/dist/operations/labels.js +7 -4
- package/dist/operations/labels.js.map +1 -1
- package/dist/operations/lists.d.ts +63 -1
- package/dist/operations/lists.d.ts.map +1 -1
- package/dist/operations/lists.js +97 -2
- package/dist/operations/lists.js.map +1 -1
- package/dist/operations/members.d.ts +39 -0
- package/dist/operations/members.d.ts.map +1 -0
- package/dist/operations/members.js +107 -0
- package/dist/operations/members.js.map +1 -0
- package/dist/operations/projects.d.ts +16 -0
- package/dist/operations/projects.d.ts.map +1 -1
- package/dist/operations/projects.js +54 -9
- package/dist/operations/projects.js.map +1 -1
- package/dist/operations/tasks.d.ts +1 -1
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js +5 -3
- package/dist/operations/tasks.js.map +1 -1
- package/dist/operations/users.d.ts +123 -0
- package/dist/operations/users.d.ts.map +1 -0
- package/dist/operations/users.js +180 -0
- package/dist/operations/users.js.map +1 -0
- package/dist/operations/verify.d.ts +84 -0
- package/dist/operations/verify.d.ts.map +1 -0
- package/dist/operations/verify.js +124 -0
- package/dist/operations/verify.js.map +1 -0
- package/dist/prompts.d.ts +48 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +155 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +38 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +127 -0
- package/dist/resources.js.map +1 -0
- package/dist/schemas/entities.d.ts +115 -24
- package/dist/schemas/entities.d.ts.map +1 -1
- package/dist/schemas/entities.js +48 -0
- package/dist/schemas/entities.js.map +1 -1
- package/dist/schemas/requests.d.ts +121 -46
- package/dist/schemas/requests.d.ts.map +1 -1
- package/dist/schemas/requests.js +57 -12
- package/dist/schemas/requests.js.map +1 -1
- package/dist/schemas/responses.d.ts +541 -186
- package/dist/schemas/responses.d.ts.map +1 -1
- package/dist/schemas/responses.js +13 -2
- package/dist/schemas/responses.js.map +1 -1
- package/dist/tools/activity.d.ts +150 -0
- package/dist/tools/activity.d.ts.map +1 -0
- package/dist/tools/activity.js +198 -0
- package/dist/tools/activity.js.map +1 -0
- package/dist/tools/annotations.d.ts +52 -0
- package/dist/tools/annotations.d.ts.map +1 -0
- package/dist/tools/annotations.js +214 -0
- package/dist/tools/annotations.js.map +1 -0
- package/dist/tools/attachments.d.ts +28 -4
- package/dist/tools/attachments.d.ts.map +1 -1
- package/dist/tools/attachments.js +53 -34
- package/dist/tools/attachments.js.map +1 -1
- package/dist/tools/card-ops.d.ts +232 -0
- package/dist/tools/card-ops.d.ts.map +1 -0
- package/dist/tools/card-ops.js +333 -0
- package/dist/tools/card-ops.js.map +1 -0
- package/dist/tools/cards.d.ts +90 -8
- package/dist/tools/cards.d.ts.map +1 -1
- package/dist/tools/cards.js +411 -128
- package/dist/tools/cards.js.map +1 -1
- package/dist/tools/comments.d.ts +226 -22
- package/dist/tools/comments.d.ts.map +1 -1
- package/dist/tools/comments.js +163 -134
- package/dist/tools/comments.js.map +1 -1
- package/dist/tools/dispatch.d.ts +47 -0
- package/dist/tools/dispatch.d.ts.map +1 -0
- package/dist/tools/dispatch.js +63 -0
- package/dist/tools/dispatch.js.map +1 -0
- package/dist/tools/guard.d.ts +9 -0
- package/dist/tools/guard.d.ts.map +1 -0
- package/dist/tools/guard.js +20 -0
- package/dist/tools/guard.js.map +1 -0
- package/dist/tools/index.d.ts +748 -450
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +136 -17
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/labels.d.ts +213 -18
- package/dist/tools/labels.d.ts.map +1 -1
- package/dist/tools/labels.js +218 -203
- package/dist/tools/labels.js.map +1 -1
- package/dist/tools/lists.d.ts +222 -15
- package/dist/tools/lists.d.ts.map +1 -1
- package/dist/tools/lists.js +175 -156
- package/dist/tools/lists.js.map +1 -1
- package/dist/tools/members.d.ts +128 -0
- package/dist/tools/members.d.ts.map +1 -0
- package/dist/tools/members.js +150 -0
- package/dist/tools/members.js.map +1 -0
- package/dist/tools/navigation.d.ts +22 -2
- package/dist/tools/navigation.d.ts.map +1 -1
- package/dist/tools/navigation.js +60 -15
- package/dist/tools/navigation.js.map +1 -1
- package/dist/tools/queries.d.ts +196 -166
- package/dist/tools/queries.d.ts.map +1 -1
- package/dist/tools/queries.js +125 -155
- package/dist/tools/queries.js.map +1 -1
- package/dist/tools/tasks.d.ts +26 -6
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +110 -55
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/users.d.ts +130 -0
- package/dist/tools/users.d.ts.map +1 -0
- package/dist/tools/users.js +165 -0
- package/dist/tools/users.js.map +1 -0
- package/docs/planka-2x-gotchas.md +121 -5
- package/docs/tools.md +771 -187
- package/docs/troubleshooting.md +137 -5
- package/hooks/hooks.json +15 -0
- package/hooks/preflight.mjs +100 -0
- package/package.json +6 -1
- package/scripts/setup.sh +8 -26
- package/scripts/sync-identity.mjs +55 -1
- package/server.json +87 -6
- package/tests/smoke/planka-smoke.mjs +512 -72
- package/workflow/skills/planka-close-card/SKILL.md +18 -5
- package/workflow/skills/planka-orchestrator/SKILL.md +36 -7
|
@@ -37,7 +37,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
37
37
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
38
38
|
import { PlankaApi, boardEntities } from "../../scripts/lib/planka-api.mjs";
|
|
39
39
|
|
|
40
|
-
const EXPECTED_TOOL_COUNT =
|
|
40
|
+
const EXPECTED_TOOL_COUNT = 41;
|
|
41
41
|
|
|
42
42
|
/** Prefix on every artifact this test creates; the initial sweep looks for it. */
|
|
43
43
|
const PREFIX = "[smoke planka-mcp]";
|
|
@@ -287,15 +287,19 @@ async function run() {
|
|
|
287
287
|
const boardText = JSON.stringify(mcpBoard);
|
|
288
288
|
|
|
289
289
|
// --- labels the scratch card will carry -----------------------------------
|
|
290
|
-
const labelA = await json("
|
|
291
|
-
action: "create",
|
|
290
|
+
const labelA = await json("planka_create_label", {
|
|
292
291
|
boardId,
|
|
293
292
|
name: `${PREFIX} A`,
|
|
294
293
|
color: "berry-red",
|
|
295
294
|
});
|
|
296
295
|
if (labelA?.label?.id) artifacts.labels.push(labelA.label.id);
|
|
297
|
-
check(
|
|
296
|
+
check(
|
|
297
|
+
"planka_create_label (A) reports verified: true",
|
|
298
|
+
Boolean(labelA?.label?.id) && labelA.verified === true,
|
|
299
|
+
labelA
|
|
300
|
+
);
|
|
298
301
|
|
|
302
|
+
// The one deprecated-alias check for labels: the old name must still route.
|
|
299
303
|
const labelB = await json("planka_manage_labels", {
|
|
300
304
|
action: "create",
|
|
301
305
|
boardId,
|
|
@@ -303,21 +307,24 @@ async function run() {
|
|
|
303
307
|
color: "lagoon-blue",
|
|
304
308
|
});
|
|
305
309
|
if (labelB?.label?.id) artifacts.labels.push(labelB.label.id);
|
|
306
|
-
check(
|
|
310
|
+
check(
|
|
311
|
+
"planka_manage_labels (DEPRECATED alias) still creates, verified: true",
|
|
312
|
+
Boolean(labelB?.label?.id) && labelB.verified === true,
|
|
313
|
+
labelB
|
|
314
|
+
);
|
|
307
315
|
|
|
308
316
|
if (!labelA?.label?.id || !labelB?.label?.id) {
|
|
309
317
|
throw new Error("Without labels there is no smoke — stopping here");
|
|
310
318
|
}
|
|
311
319
|
|
|
312
|
-
const renamed = await json("
|
|
313
|
-
action: "update",
|
|
320
|
+
const renamed = await json("planka_update_label", {
|
|
314
321
|
labelId: labelA.label.id,
|
|
315
322
|
name: `${PREFIX} A2`,
|
|
316
323
|
color: "pumpkin-orange",
|
|
317
324
|
});
|
|
318
325
|
check(
|
|
319
|
-
"
|
|
320
|
-
renamed?.label?.name === `${PREFIX} A2
|
|
326
|
+
"planka_update_label renames and recolors, verified: true",
|
|
327
|
+
renamed?.label?.name === `${PREFIX} A2` && renamed.verified === true,
|
|
321
328
|
renamed
|
|
322
329
|
);
|
|
323
330
|
|
|
@@ -332,8 +339,11 @@ async function run() {
|
|
|
332
339
|
const cardId = created?.card?.id;
|
|
333
340
|
if (cardId) artifacts.cards.push(cardId);
|
|
334
341
|
check(
|
|
335
|
-
"planka_create_card creates a card with 2 tasks and 1 label",
|
|
336
|
-
Boolean(cardId) &&
|
|
342
|
+
"planka_create_card creates a card with 2 tasks and 1 label, verified: true",
|
|
343
|
+
Boolean(cardId) &&
|
|
344
|
+
created.verified === true &&
|
|
345
|
+
created.tasksCreated === 2 &&
|
|
346
|
+
created.labelsAttached === 1,
|
|
337
347
|
created
|
|
338
348
|
);
|
|
339
349
|
if (!cardId) throw new Error("Without a card there is no smoke — stopping here");
|
|
@@ -345,44 +355,96 @@ async function run() {
|
|
|
345
355
|
cardId
|
|
346
356
|
);
|
|
347
357
|
|
|
348
|
-
|
|
358
|
+
// detail:"full" is what carries the task items and the whole description;
|
|
359
|
+
// the default digest returns counts and a 200-char preview.
|
|
360
|
+
const details = await json("planka_get_card", { cardId, detail: "full" });
|
|
349
361
|
check(
|
|
350
362
|
"planka_get_card returns the name, both tasks and the label",
|
|
351
363
|
details?.card?.name === CARD_NAME &&
|
|
352
|
-
details?.tasks?.
|
|
364
|
+
details?.tasks?.total === 2 &&
|
|
365
|
+
details?.tasks?.lists?.[0]?.items?.length === 2 &&
|
|
353
366
|
details?.labels?.length === 1,
|
|
354
367
|
details
|
|
355
368
|
);
|
|
356
|
-
const taskId = details?.tasks?.[0]?.id;
|
|
369
|
+
const taskId = details?.tasks?.lists?.[0]?.items?.[0]?.id;
|
|
357
370
|
|
|
358
|
-
const
|
|
371
|
+
const digest = await json("planka_get_card", { cardId });
|
|
372
|
+
check(
|
|
373
|
+
"the digest counts the tasks without listing them, and reads no comments",
|
|
374
|
+
digest?.detail === "digest" &&
|
|
375
|
+
digest?.tasks?.total === 2 &&
|
|
376
|
+
digest?.tasks?.lists?.[0]?.items === undefined &&
|
|
377
|
+
!Array.isArray(digest?.comments),
|
|
378
|
+
digest
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
const updated = await json("planka_update_card", {
|
|
359
382
|
cardId,
|
|
360
383
|
description: "description updated by the smoke test",
|
|
361
384
|
});
|
|
362
|
-
check(
|
|
385
|
+
check(
|
|
386
|
+
"planka_update_card updates the description, verified: true",
|
|
387
|
+
updated?.verified === true,
|
|
388
|
+
updated
|
|
389
|
+
);
|
|
363
390
|
|
|
364
|
-
const reread = await json("planka_get_card", { cardId });
|
|
391
|
+
const reread = await json("planka_get_card", { cardId, detail: "full" });
|
|
365
392
|
check(
|
|
366
393
|
"the update persisted (verified by re-reading)",
|
|
367
394
|
reread?.card?.description === "description updated by the smoke test",
|
|
368
395
|
reread?.card?.description
|
|
369
396
|
);
|
|
370
397
|
|
|
371
|
-
const taskUpdated = await
|
|
398
|
+
const taskUpdated = await json("planka_update_task", {
|
|
372
399
|
taskId,
|
|
400
|
+
cardId,
|
|
373
401
|
isCompleted: true,
|
|
374
402
|
name: "first (done)",
|
|
375
403
|
});
|
|
376
|
-
check(
|
|
404
|
+
check(
|
|
405
|
+
"planka_update_task completes and renames, verified against the card",
|
|
406
|
+
taskUpdated?.verified === true && taskUpdated.task?.isCompleted === true,
|
|
407
|
+
taskUpdated
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// A write that cannot be verified is NOT a success: no cardId means the task
|
|
411
|
+
// cannot be re-read, and the payload has to say so instead of claiming it.
|
|
412
|
+
const unverifiable = await callTool("planka_update_task", {
|
|
413
|
+
taskId,
|
|
414
|
+
name: "first (done)",
|
|
415
|
+
});
|
|
416
|
+
const unverifiableBody = (() => {
|
|
417
|
+
try {
|
|
418
|
+
return JSON.parse(unverifiable.text);
|
|
419
|
+
} catch {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
})();
|
|
423
|
+
check(
|
|
424
|
+
"planka_update_task without cardId answers success:false, not a false green",
|
|
425
|
+
unverifiable.isError === true &&
|
|
426
|
+
unverifiableBody?.success === false &&
|
|
427
|
+
unverifiableBody?.verified === false &&
|
|
428
|
+
String(unverifiableBody?.verificationUnavailable ?? "").includes("cardId"),
|
|
429
|
+
unverifiableBody ?? unverifiable.text
|
|
430
|
+
);
|
|
377
431
|
|
|
378
|
-
const taskAdded = await
|
|
432
|
+
const taskAdded = await json("planka_create_tasks", {
|
|
379
433
|
cardId,
|
|
380
434
|
tasks: ["third"],
|
|
381
435
|
});
|
|
382
|
-
check(
|
|
436
|
+
check(
|
|
437
|
+
"planka_create_tasks appends a task, verified: true",
|
|
438
|
+
taskAdded?.verified === true && taskAdded.tasksCreated === 1,
|
|
439
|
+
taskAdded
|
|
440
|
+
);
|
|
383
441
|
|
|
384
|
-
const taskDeleted = await
|
|
385
|
-
check(
|
|
442
|
+
const taskDeleted = await json("planka_delete_task", { taskId, cardId });
|
|
443
|
+
check(
|
|
444
|
+
"planka_delete_task removes the task, verified against the card",
|
|
445
|
+
taskDeleted?.verified === true,
|
|
446
|
+
taskDeleted
|
|
447
|
+
);
|
|
386
448
|
|
|
387
449
|
// --- labels on the card ---------------------------------------------------
|
|
388
450
|
const swapped = await json("planka_set_card_labels", {
|
|
@@ -418,7 +480,7 @@ async function run() {
|
|
|
418
480
|
});
|
|
419
481
|
check(
|
|
420
482
|
"planka_list_cards with labelFilter finds the card",
|
|
421
|
-
filtered?.
|
|
483
|
+
filtered?.matches?.some((card) => card.id === cardId),
|
|
422
484
|
filtered
|
|
423
485
|
);
|
|
424
486
|
|
|
@@ -436,6 +498,16 @@ async function run() {
|
|
|
436
498
|
allCards
|
|
437
499
|
);
|
|
438
500
|
|
|
501
|
+
// The list show answers the whole column in one request: `truncated` is
|
|
502
|
+
// false by construction and `total` is the real size of the column.
|
|
503
|
+
check(
|
|
504
|
+
"planka_find_cards with listId reads the column whole, not a page of it",
|
|
505
|
+
allCards?.source === "list" &&
|
|
506
|
+
allCards?.truncated === false &&
|
|
507
|
+
allCards?.list?.id === list.id,
|
|
508
|
+
allCards
|
|
509
|
+
);
|
|
510
|
+
|
|
439
511
|
const rawListCards = await api.get(`/api/lists/${list.id}/cards`);
|
|
440
512
|
check(
|
|
441
513
|
"the count matches the raw API (or exceeds one page, which is the point)",
|
|
@@ -443,10 +515,18 @@ async function run() {
|
|
|
443
515
|
`mcp total=${totalInList} · raw first page=${rawListCards.items?.length}`
|
|
444
516
|
);
|
|
445
517
|
|
|
518
|
+
// Cross-check against the list show, which carries every card of the column.
|
|
519
|
+
const rawListShow = await api.get(`/api/lists/${list.id}`);
|
|
520
|
+
check(
|
|
521
|
+
"planka_find_cards total equals the real size of the column (raw list show)",
|
|
522
|
+
totalInList === (rawListShow.included?.cards?.length ?? -1),
|
|
523
|
+
`mcp total=${totalInList} · raw list show=${rawListShow.included?.cards?.length}`
|
|
524
|
+
);
|
|
525
|
+
|
|
446
526
|
check(
|
|
447
527
|
"planka_list_cards omits descriptions by default",
|
|
448
|
-
allCards?.
|
|
449
|
-
allCards?.
|
|
528
|
+
allCards?.matches?.every((card) => card.description === undefined),
|
|
529
|
+
allCards?.matches?.[0]
|
|
450
530
|
);
|
|
451
531
|
|
|
452
532
|
const page = await json("planka_list_cards", {
|
|
@@ -474,7 +554,7 @@ async function run() {
|
|
|
474
554
|
"planka_list_cards with an offset returns the next card, not the same one",
|
|
475
555
|
next?.returned === 1 &&
|
|
476
556
|
next.offset === 1 &&
|
|
477
|
-
next.
|
|
557
|
+
next.matches[0].id !== page.matches[0].id,
|
|
478
558
|
next
|
|
479
559
|
);
|
|
480
560
|
} else {
|
|
@@ -495,6 +575,23 @@ async function run() {
|
|
|
495
575
|
lists
|
|
496
576
|
);
|
|
497
577
|
|
|
578
|
+
check(
|
|
579
|
+
"planka_board_summary warns instead of answering an empty cardsFrom",
|
|
580
|
+
await (async () => {
|
|
581
|
+
const typo = await json("planka_board_summary", {
|
|
582
|
+
boardId,
|
|
583
|
+
cardsFrom: [`${PREFIX} no such column`],
|
|
584
|
+
});
|
|
585
|
+
return (
|
|
586
|
+
Array.isArray(typo?.warnings) &&
|
|
587
|
+
typo.warnings.join(" ").includes(`${PREFIX} no such column`) &&
|
|
588
|
+
typo.warnings.join(" ").includes(list.name) &&
|
|
589
|
+
(typo.cards === undefined || typo.cards.length === 0)
|
|
590
|
+
);
|
|
591
|
+
})(),
|
|
592
|
+
"cardsFrom typo must come back as a warning, never as an empty column"
|
|
593
|
+
);
|
|
594
|
+
|
|
498
595
|
check(
|
|
499
596
|
"planka_list_lists is far cheaper than planka_get_board",
|
|
500
597
|
JSON.stringify(lists).length * 4 < boardText.length,
|
|
@@ -554,10 +651,20 @@ async function run() {
|
|
|
554
651
|
"planka_find_cards by label returns exactly the labelled card",
|
|
555
652
|
byLabel?.total === 1 &&
|
|
556
653
|
byLabel.matches[0].id === cardId &&
|
|
557
|
-
byLabel.matches[0].
|
|
654
|
+
byLabel.matches[0].listId === list.id &&
|
|
655
|
+
byLabel.matches[0].listName === list.name,
|
|
558
656
|
byLabel
|
|
559
657
|
);
|
|
560
658
|
|
|
659
|
+
// The digest carries the label IDs, not only the names: set_card_labels takes
|
|
660
|
+
// ids, and a digest without them sent the caller back to board_summary.
|
|
661
|
+
check(
|
|
662
|
+
"planka_find_cards digest carries labelIds, so set_card_labels can run on it",
|
|
663
|
+
Array.isArray(byLabel?.matches?.[0]?.labelIds) &&
|
|
664
|
+
byLabel.matches[0].labelIds.includes(labelB.label.id),
|
|
665
|
+
byLabel?.matches?.[0]
|
|
666
|
+
);
|
|
667
|
+
|
|
561
668
|
// The description was written by the update above and is NOT in the name:
|
|
562
669
|
// if this matches, the text search really is reading descriptions.
|
|
563
670
|
const byText = await json("planka_find_cards", {
|
|
@@ -578,7 +685,8 @@ async function run() {
|
|
|
578
685
|
check(
|
|
579
686
|
"planka_find_cards with listId stays inside that column",
|
|
580
687
|
scoped?.matches?.length > 0 &&
|
|
581
|
-
scoped.
|
|
688
|
+
scoped.list?.id === list.id &&
|
|
689
|
+
scoped.source === "list",
|
|
582
690
|
scoped
|
|
583
691
|
);
|
|
584
692
|
|
|
@@ -589,9 +697,9 @@ async function run() {
|
|
|
589
697
|
check(
|
|
590
698
|
"planka_find_cards warns when the label does not exist and lists the ones that do",
|
|
591
699
|
noLabel?.total === 0 &&
|
|
592
|
-
|
|
593
|
-
noLabel.
|
|
594
|
-
noLabel?.
|
|
700
|
+
Array.isArray(noLabel.warnings) &&
|
|
701
|
+
noLabel.warnings.join(" ").includes(`${PREFIX} B`),
|
|
702
|
+
noLabel?.warnings
|
|
595
703
|
);
|
|
596
704
|
|
|
597
705
|
const noFilter = await callTool("planka_find_cards", { boardId });
|
|
@@ -601,12 +709,78 @@ async function run() {
|
|
|
601
709
|
noFilter.text
|
|
602
710
|
);
|
|
603
711
|
|
|
604
|
-
const moved = await
|
|
712
|
+
const moved = await json("planka_move_card", {
|
|
605
713
|
cardId,
|
|
606
714
|
listId: list.id,
|
|
607
715
|
position: 1,
|
|
608
716
|
});
|
|
609
|
-
check(
|
|
717
|
+
check(
|
|
718
|
+
"planka_move_card repositions inside the list, verified: true",
|
|
719
|
+
moved?.verified === true,
|
|
720
|
+
moved
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
// A move BETWEEN columns is the real use of the tool: a temporary column is
|
|
724
|
+
// created for it, the card goes there and comes back, and the raw API is the
|
|
725
|
+
// judge in both directions.
|
|
726
|
+
const moveTarget = await json("planka_create_list", {
|
|
727
|
+
boardId,
|
|
728
|
+
name: `${PREFIX} move target`,
|
|
729
|
+
});
|
|
730
|
+
if (moveTarget?.list?.id) artifacts.lists.push(moveTarget.list.id);
|
|
731
|
+
check(
|
|
732
|
+
"planka_create_list (move target) reports verified: true",
|
|
733
|
+
Boolean(moveTarget?.list?.id) && moveTarget.verified === true,
|
|
734
|
+
moveTarget
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
if (moveTarget?.list?.id) {
|
|
738
|
+
const movedAcross = await json("planka_move_card", {
|
|
739
|
+
cardId,
|
|
740
|
+
listId: moveTarget.list.id,
|
|
741
|
+
});
|
|
742
|
+
check(
|
|
743
|
+
"planka_move_card moves the card to ANOTHER column, verified: true",
|
|
744
|
+
movedAcross?.verified === true &&
|
|
745
|
+
movedAcross.card?.listId === moveTarget.list.id,
|
|
746
|
+
movedAcross
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
const rawAfterMove = await rawBoard();
|
|
750
|
+
check(
|
|
751
|
+
"the raw API confirms the card changed column",
|
|
752
|
+
rawAfterMove.cards.find((item) => item.id === cardId)?.listId ===
|
|
753
|
+
moveTarget.list.id,
|
|
754
|
+
rawAfterMove.cards.find((item) => item.id === cardId)?.listId
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
const movedBack = await json("planka_move_card", {
|
|
758
|
+
cardId,
|
|
759
|
+
listId: list.id,
|
|
760
|
+
});
|
|
761
|
+
check(
|
|
762
|
+
"planka_move_card brings it back to the original column, verified: true",
|
|
763
|
+
movedBack?.verified === true && movedBack.card?.listId === list.id,
|
|
764
|
+
movedBack
|
|
765
|
+
);
|
|
766
|
+
|
|
767
|
+
const rawAfterBack = await rawBoard();
|
|
768
|
+
check(
|
|
769
|
+
"the raw API confirms the card is back (and was NOT deleted with the column)",
|
|
770
|
+
rawAfterBack.cards.find((item) => item.id === cardId)?.listId === list.id,
|
|
771
|
+
rawAfterBack.cards.find((item) => item.id === cardId)?.listId
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
const droppedTarget = await json("planka_delete_list", {
|
|
775
|
+
listId: moveTarget.list.id,
|
|
776
|
+
});
|
|
777
|
+
if (droppedTarget?.verified === true) forget("lists", moveTarget.list.id);
|
|
778
|
+
check(
|
|
779
|
+
"planka_delete_list removes the temporary column, verified: true",
|
|
780
|
+
droppedTarget?.verified === true,
|
|
781
|
+
droppedTarget
|
|
782
|
+
);
|
|
783
|
+
}
|
|
610
784
|
|
|
611
785
|
// --- comments: a 0 has to be a REAL 0 -------------------------------------
|
|
612
786
|
const emptyMcp = await json("planka_get_comments", { cardId });
|
|
@@ -617,12 +791,16 @@ async function run() {
|
|
|
617
791
|
`mcp=${emptyMcp?.commentCount} · raw=${emptyRaw.items?.length}`
|
|
618
792
|
);
|
|
619
793
|
|
|
620
|
-
const addedComment = await json("
|
|
794
|
+
const addedComment = await json("planka_create_comment", {
|
|
621
795
|
cardId,
|
|
622
796
|
text: "smoke: original comment",
|
|
623
797
|
});
|
|
624
798
|
const commentId = addedComment?.comment?.id;
|
|
625
|
-
check(
|
|
799
|
+
check(
|
|
800
|
+
"planka_create_comment creates the comment, verified: true",
|
|
801
|
+
Boolean(commentId) && addedComment.verified === true,
|
|
802
|
+
addedComment
|
|
803
|
+
);
|
|
626
804
|
|
|
627
805
|
const readMcp = await json("planka_get_comments", { cardId });
|
|
628
806
|
check(
|
|
@@ -647,12 +825,15 @@ async function run() {
|
|
|
647
825
|
readMcp?.comments?.[0]
|
|
648
826
|
);
|
|
649
827
|
|
|
650
|
-
const editedComment = await
|
|
651
|
-
action: "update",
|
|
828
|
+
const editedComment = await json("planka_update_comment", {
|
|
652
829
|
commentId,
|
|
653
830
|
text: "smoke: edited comment",
|
|
654
831
|
});
|
|
655
|
-
check(
|
|
832
|
+
check(
|
|
833
|
+
"planka_update_comment, verified: true",
|
|
834
|
+
editedComment?.verified === true,
|
|
835
|
+
editedComment
|
|
836
|
+
);
|
|
656
837
|
|
|
657
838
|
const afterEditMcp = await json("planka_get_comments", { cardId });
|
|
658
839
|
const afterEditRaw = await api.getComments(cardId);
|
|
@@ -663,11 +844,12 @@ async function run() {
|
|
|
663
844
|
`mcp=${afterEditMcp?.comments[0].text} · raw=${afterEditRaw.items[0]?.text}`
|
|
664
845
|
);
|
|
665
846
|
|
|
666
|
-
const deletedComment = await
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
847
|
+
const deletedComment = await json("planka_delete_comment", { commentId });
|
|
848
|
+
check(
|
|
849
|
+
"planka_delete_comment, verified: true",
|
|
850
|
+
deletedComment?.verified === true,
|
|
851
|
+
deletedComment
|
|
852
|
+
);
|
|
671
853
|
|
|
672
854
|
const afterDeleteMcp = await json("planka_get_comments", { cardId });
|
|
673
855
|
const afterDeleteRaw = await api.getComments(cardId);
|
|
@@ -677,6 +859,28 @@ async function run() {
|
|
|
677
859
|
`mcp=${afterDeleteMcp?.commentCount} · raw=${afterDeleteRaw.items?.length}`
|
|
678
860
|
);
|
|
679
861
|
|
|
862
|
+
// The two remaining deprecated aliases, on a throwaway comment: the old
|
|
863
|
+
// names must keep writing exactly like the tools that replaced them.
|
|
864
|
+
const aliasComment = await json("planka_add_comment", {
|
|
865
|
+
cardId,
|
|
866
|
+
text: "smoke: alias comment",
|
|
867
|
+
});
|
|
868
|
+
check(
|
|
869
|
+
"planka_add_comment (DEPRECATED alias) still creates, verified: true",
|
|
870
|
+
Boolean(aliasComment?.comment?.id) && aliasComment.verified === true,
|
|
871
|
+
aliasComment
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
const aliasDeleted = await json("planka_manage_comment", {
|
|
875
|
+
action: "delete",
|
|
876
|
+
commentId: aliasComment?.comment?.id,
|
|
877
|
+
});
|
|
878
|
+
check(
|
|
879
|
+
"planka_manage_comment (DEPRECATED alias) still deletes, verified: true",
|
|
880
|
+
aliasDeleted?.verified === true,
|
|
881
|
+
aliasDeleted
|
|
882
|
+
);
|
|
883
|
+
|
|
680
884
|
// --- attachments: byte-for-byte roundtrip ---------------------------------
|
|
681
885
|
workDir = mkdtempSync(join(tmpdir(), "planka-mcp-smoke-"));
|
|
682
886
|
const filePath = join(workDir, "smoke-payload.txt");
|
|
@@ -726,14 +930,15 @@ async function run() {
|
|
|
726
930
|
viewed.text
|
|
727
931
|
);
|
|
728
932
|
|
|
729
|
-
const deletedAttachment = await
|
|
933
|
+
const deletedAttachment = await json("planka_delete_attachment", {
|
|
730
934
|
attachmentId,
|
|
935
|
+
cardId,
|
|
731
936
|
});
|
|
732
|
-
if (
|
|
937
|
+
if (deletedAttachment?.verified === true) forget("attachments", attachmentId);
|
|
733
938
|
check(
|
|
734
|
-
"planka_delete_attachment removes the attachment",
|
|
735
|
-
|
|
736
|
-
deletedAttachment
|
|
939
|
+
"planka_delete_attachment removes the attachment, verified: true",
|
|
940
|
+
deletedAttachment?.verified === true,
|
|
941
|
+
deletedAttachment
|
|
737
942
|
);
|
|
738
943
|
|
|
739
944
|
const afterAttachmentDelete = await json("planka_get_attachments", { cardId });
|
|
@@ -744,42 +949,261 @@ async function run() {
|
|
|
744
949
|
);
|
|
745
950
|
|
|
746
951
|
// --- board lists ----------------------------------------------------------
|
|
747
|
-
const tempList = await json("
|
|
748
|
-
action: "create",
|
|
952
|
+
const tempList = await json("planka_create_list", {
|
|
749
953
|
boardId,
|
|
750
954
|
name: `${PREFIX} temporary list`,
|
|
751
955
|
});
|
|
752
956
|
if (tempList?.list?.id) artifacts.lists.push(tempList.list.id);
|
|
753
|
-
check(
|
|
957
|
+
check(
|
|
958
|
+
"planka_create_list, verified: true",
|
|
959
|
+
Boolean(tempList?.list?.id) && tempList.verified === true,
|
|
960
|
+
tempList
|
|
961
|
+
);
|
|
754
962
|
|
|
755
|
-
const renamedList = await json("
|
|
756
|
-
action: "update",
|
|
963
|
+
const renamedList = await json("planka_update_list", {
|
|
757
964
|
listId: tempList.list.id,
|
|
758
965
|
name: `${PREFIX} renamed list`,
|
|
759
966
|
});
|
|
760
967
|
check(
|
|
761
|
-
"
|
|
762
|
-
renamedList?.list?.name === `${PREFIX} renamed list
|
|
968
|
+
"planka_update_list renames, verified: true",
|
|
969
|
+
renamedList?.list?.name === `${PREFIX} renamed list` &&
|
|
970
|
+
renamedList.verified === true,
|
|
763
971
|
renamedList
|
|
764
972
|
);
|
|
765
973
|
|
|
766
|
-
|
|
974
|
+
// A bulk move needs a column of its own, which is exactly what tempList is.
|
|
975
|
+
const strayCard = await json("planka_create_card", {
|
|
976
|
+
listId: tempList.list.id,
|
|
977
|
+
name: `${PREFIX} bulk move passenger`,
|
|
978
|
+
});
|
|
979
|
+
if (strayCard?.card?.id) artifacts.cards.push(strayCard.card.id);
|
|
980
|
+
check(
|
|
981
|
+
"a card was created in the temporary column for the bulk move",
|
|
982
|
+
Boolean(strayCard?.card?.id) && strayCard.verified === true,
|
|
983
|
+
strayCard
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
const bulkMoved = await json("planka_move_list_cards", {
|
|
987
|
+
fromListId: tempList.list.id,
|
|
988
|
+
toListId: list.id,
|
|
989
|
+
boardId,
|
|
990
|
+
});
|
|
991
|
+
check(
|
|
992
|
+
"planka_move_list_cards empties the column and the counts add up",
|
|
993
|
+
bulkMoved?.verified === true &&
|
|
994
|
+
bulkMoved?.moved === 1 &&
|
|
995
|
+
bulkMoved?.fromRemaining === 0,
|
|
996
|
+
bulkMoved
|
|
997
|
+
);
|
|
998
|
+
|
|
999
|
+
const rawAfterBulk = await api.get(`/api/lists/${tempList.list.id}`);
|
|
1000
|
+
check(
|
|
1001
|
+
"the raw API confirms the temporary column is empty",
|
|
1002
|
+
(rawAfterBulk?.included?.cards ?? []).length === 0,
|
|
1003
|
+
rawAfterBulk?.included?.cards?.length
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
const bulkNoop = await json("planka_move_list_cards", {
|
|
1007
|
+
fromListId: tempList.list.id,
|
|
1008
|
+
toListId: list.id,
|
|
1009
|
+
});
|
|
1010
|
+
check(
|
|
1011
|
+
"planka_move_list_cards on an empty column moves nothing and says so",
|
|
1012
|
+
bulkNoop?.verified === true && bulkNoop?.moved === 0,
|
|
1013
|
+
bulkNoop
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
// The one deprecated-alias check for lists.
|
|
1017
|
+
const deletedList = await json("planka_manage_lists", {
|
|
767
1018
|
action: "delete",
|
|
768
1019
|
listId: tempList.list.id,
|
|
769
1020
|
});
|
|
770
|
-
if (
|
|
771
|
-
check(
|
|
1021
|
+
if (deletedList?.verified === true) forget("lists", tempList.list.id);
|
|
1022
|
+
check(
|
|
1023
|
+
"planka_manage_lists (DEPRECATED alias) still deletes, verified: true",
|
|
1024
|
+
deletedList?.verified === true,
|
|
1025
|
+
deletedList
|
|
1026
|
+
);
|
|
1027
|
+
|
|
1028
|
+
// --- identity -------------------------------------------------------------
|
|
1029
|
+
const who = await json("planka_whoami", { boardId });
|
|
1030
|
+
check(
|
|
1031
|
+
"planka_whoami names the account and the Planka version",
|
|
1032
|
+
Boolean(who?.user?.id) && typeof who?.plankaVersion === "string",
|
|
1033
|
+
who
|
|
1034
|
+
);
|
|
1035
|
+
check(
|
|
1036
|
+
"planka_whoami reports this session's access policy",
|
|
1037
|
+
who?.policy !== undefined && who?.authMode !== undefined,
|
|
1038
|
+
who?.policy
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
const roster = await json("planka_list_users", { boardId });
|
|
1042
|
+
const agentUser = roster?.users?.find((user) => user.id === who?.user?.id);
|
|
1043
|
+
check(
|
|
1044
|
+
"planka_list_users lists the agent account itself",
|
|
1045
|
+
Boolean(agentUser) && ["instance", "board"].includes(roster?.source),
|
|
1046
|
+
roster
|
|
1047
|
+
);
|
|
1048
|
+
check(
|
|
1049
|
+
"planka_list_users hides emails unless asked",
|
|
1050
|
+
(roster?.users ?? []).every((user) => user.email === undefined),
|
|
1051
|
+
roster?.users?.[0]
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
// --- card members ---------------------------------------------------------
|
|
1055
|
+
const assigned = await json("planka_set_card_members", {
|
|
1056
|
+
cardId,
|
|
1057
|
+
add: [who.user.id],
|
|
1058
|
+
});
|
|
1059
|
+
check(
|
|
1060
|
+
"planka_set_card_members assigns and re-reads the membership",
|
|
1061
|
+
assigned?.verified === true &&
|
|
1062
|
+
assigned?.finalMemberIds?.includes(who.user.id),
|
|
1063
|
+
assigned
|
|
1064
|
+
);
|
|
1065
|
+
|
|
1066
|
+
const rawAssigned = await api.getCard(cardId);
|
|
1067
|
+
check(
|
|
1068
|
+
"the raw API confirms the card membership",
|
|
1069
|
+
(rawAssigned?.included?.cardMemberships ?? []).some(
|
|
1070
|
+
(membership) => membership.userId === who.user.id
|
|
1071
|
+
),
|
|
1072
|
+
rawAssigned?.included?.cardMemberships
|
|
1073
|
+
);
|
|
1074
|
+
|
|
1075
|
+
const unassigned = await json("planka_set_card_members", {
|
|
1076
|
+
cardId,
|
|
1077
|
+
remove: [who.user.id],
|
|
1078
|
+
});
|
|
1079
|
+
check(
|
|
1080
|
+
"planka_set_card_members unassigns and reports an empty membership",
|
|
1081
|
+
unassigned?.verified === true &&
|
|
1082
|
+
!unassigned?.finalMemberIds?.includes(who.user.id),
|
|
1083
|
+
unassigned
|
|
1084
|
+
);
|
|
1085
|
+
|
|
1086
|
+
const rawUnassigned = await api.getCard(cardId);
|
|
1087
|
+
check(
|
|
1088
|
+
"the raw API confirms the membership is gone",
|
|
1089
|
+
!(rawUnassigned?.included?.cardMemberships ?? []).some(
|
|
1090
|
+
(membership) => membership.userId === who.user.id
|
|
1091
|
+
),
|
|
1092
|
+
rawUnassigned?.included?.cardMemberships
|
|
1093
|
+
);
|
|
1094
|
+
|
|
1095
|
+
// --- history: the card has been created, moved and assigned by now ---------
|
|
1096
|
+
const history = await json("planka_card_history", { cardId, limit: 10 });
|
|
1097
|
+
check(
|
|
1098
|
+
"planka_card_history returns the events this run generated",
|
|
1099
|
+
(history?.events ?? []).some((event) => event.type === "createCard") &&
|
|
1100
|
+
(history?.events ?? []).some((event) => event.type === "moveCard") &&
|
|
1101
|
+
(history?.events ?? []).some((event) => event.type === "addMemberToCard"),
|
|
1102
|
+
history
|
|
1103
|
+
);
|
|
1104
|
+
check(
|
|
1105
|
+
"planka_card_history summarises each event in one human line",
|
|
1106
|
+
(history?.events ?? []).every(
|
|
1107
|
+
(event) => typeof event.summary === "string" && event.summary.length > 0 && event.at
|
|
1108
|
+
),
|
|
1109
|
+
history?.events?.[0]
|
|
1110
|
+
);
|
|
1111
|
+
|
|
1112
|
+
const activity = await json("planka_board_activity", { boardId, limit: 20 });
|
|
1113
|
+
const inActivity = (activity?.cards ?? []).find((entry) => entry.cardId === cardId);
|
|
1114
|
+
check(
|
|
1115
|
+
"planka_board_activity groups the scratch card's events under it",
|
|
1116
|
+
Boolean(inActivity) && (inActivity?.events ?? []).length > 0,
|
|
1117
|
+
inActivity
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
// --- duplicate ------------------------------------------------------------
|
|
1121
|
+
const duplicate = await json("planka_duplicate_card", {
|
|
1122
|
+
cardId,
|
|
1123
|
+
name: `${PREFIX} duplicate`,
|
|
1124
|
+
});
|
|
1125
|
+
const duplicateId = duplicate?.card?.id ?? null;
|
|
1126
|
+
if (duplicateId) artifacts.cards.push(duplicateId);
|
|
1127
|
+
check(
|
|
1128
|
+
"planka_duplicate_card creates a verified copy",
|
|
1129
|
+
duplicate?.verified === true &&
|
|
1130
|
+
Boolean(duplicateId) &&
|
|
1131
|
+
duplicate?.card?.name === `${PREFIX} duplicate`,
|
|
1132
|
+
duplicate
|
|
1133
|
+
);
|
|
1134
|
+
|
|
1135
|
+
const rawDuplicate = duplicateId ? await api.getCard(duplicateId) : null;
|
|
1136
|
+
check(
|
|
1137
|
+
"the raw API confirms the copy exists in the same column",
|
|
1138
|
+
rawDuplicate?.item?.listId === duplicate?.card?.listId,
|
|
1139
|
+
rawDuplicate?.item
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
// --- archive and back -----------------------------------------------------
|
|
1143
|
+
const beforeArchive = (await api.getCard(cardId))?.item?.listId;
|
|
1144
|
+
const archived = await json("planka_archive_card", { cardId });
|
|
1145
|
+
check(
|
|
1146
|
+
"planka_archive_card moves the card into the board's archive column",
|
|
1147
|
+
archived?.verified === true && archived?.archived === true,
|
|
1148
|
+
archived
|
|
1149
|
+
);
|
|
1150
|
+
|
|
1151
|
+
const rawArchived = await api.getCard(cardId);
|
|
1152
|
+
check(
|
|
1153
|
+
"the raw API confirms the card left its column",
|
|
1154
|
+
rawArchived?.item?.listId === archived?.archiveListId &&
|
|
1155
|
+
rawArchived?.item?.listId !== beforeArchive,
|
|
1156
|
+
rawArchived?.item
|
|
1157
|
+
);
|
|
1158
|
+
|
|
1159
|
+
const archivedAgain = await json("planka_archive_card", { cardId });
|
|
1160
|
+
check(
|
|
1161
|
+
"archiving an archived card says so instead of moving it again",
|
|
1162
|
+
archivedAgain?.alreadyArchived === true,
|
|
1163
|
+
archivedAgain
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
const restored = await json("planka_archive_card", {
|
|
1167
|
+
cardId,
|
|
1168
|
+
restoreToListId: beforeArchive,
|
|
1169
|
+
});
|
|
1170
|
+
check(
|
|
1171
|
+
"planka_archive_card restores the card to the column it came from",
|
|
1172
|
+
restored?.verified === true && restored?.card?.listId === beforeArchive,
|
|
1173
|
+
restored
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
const rawRestored = await api.getCard(cardId);
|
|
1177
|
+
check(
|
|
1178
|
+
"the raw API confirms the card is back in its column",
|
|
1179
|
+
rawRestored?.item?.listId === beforeArchive,
|
|
1180
|
+
rawRestored?.item
|
|
1181
|
+
);
|
|
772
1182
|
|
|
773
1183
|
// --- actionable error paths ----------------------------------------------
|
|
774
|
-
const errMissing = await callTool("
|
|
1184
|
+
const errMissing = await callTool("planka_create_comment", { cardId });
|
|
775
1185
|
check(
|
|
776
1186
|
"ERROR: a missing required field names the tool and the field",
|
|
777
1187
|
errMissing.isError &&
|
|
778
1188
|
errMissing.text.includes("text") &&
|
|
779
|
-
errMissing.text.includes("
|
|
1189
|
+
errMissing.text.includes("planka_create_comment"),
|
|
780
1190
|
errMissing.text
|
|
781
1191
|
);
|
|
782
1192
|
|
|
1193
|
+
// The alias cannot declare per-action required fields, so it borrows the
|
|
1194
|
+
// target's. This is the check that the borrowing works.
|
|
1195
|
+
const errAliasMissing = await callTool("planka_manage_comment", {
|
|
1196
|
+
action: "update",
|
|
1197
|
+
commentId: "1",
|
|
1198
|
+
});
|
|
1199
|
+
check(
|
|
1200
|
+
"ERROR: manage_comment update without text points at planka_update_comment",
|
|
1201
|
+
errAliasMissing.isError &&
|
|
1202
|
+
errAliasMissing.text.includes("text") &&
|
|
1203
|
+
errAliasMissing.text.includes("planka_update_comment"),
|
|
1204
|
+
errAliasMissing.text
|
|
1205
|
+
);
|
|
1206
|
+
|
|
783
1207
|
const errEmptyId = await callTool("planka_get_card", { cardId: "" });
|
|
784
1208
|
check(
|
|
785
1209
|
"ERROR: an empty id is rejected with an actionable message",
|
|
@@ -801,8 +1225,7 @@ async function run() {
|
|
|
801
1225
|
errNoop.text
|
|
802
1226
|
);
|
|
803
1227
|
|
|
804
|
-
const errBadType = await callTool("
|
|
805
|
-
action: "create",
|
|
1228
|
+
const errBadType = await callTool("planka_create_list", {
|
|
806
1229
|
boardId,
|
|
807
1230
|
name: `${PREFIX} should not exist`,
|
|
808
1231
|
position: "not-a-number",
|
|
@@ -856,9 +1279,25 @@ async function run() {
|
|
|
856
1279
|
);
|
|
857
1280
|
|
|
858
1281
|
// --- teardown, verified ---------------------------------------------------
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1282
|
+
// The copy and the bulk-move passenger are cards like any other: they have to
|
|
1283
|
+
// be gone before the "no artifacts left" check below can mean anything.
|
|
1284
|
+
for (const extraId of [duplicateId, strayCard?.card?.id].filter(Boolean)) {
|
|
1285
|
+
const deletedExtra = await json("planka_delete_card", { cardId: extraId });
|
|
1286
|
+
if (deletedExtra?.verified === true) forget("cards", extraId);
|
|
1287
|
+
check(
|
|
1288
|
+
`planka_delete_card removes the extra card ${extraId}, verified: true`,
|
|
1289
|
+
deletedExtra?.verified === true,
|
|
1290
|
+
deletedExtra
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const deletedCard = await json("planka_delete_card", { cardId });
|
|
1295
|
+
if (deletedCard?.verified === true) forget("cards", cardId);
|
|
1296
|
+
check(
|
|
1297
|
+
"planka_delete_card deletes the card, verified: true",
|
|
1298
|
+
deletedCard?.verified === true,
|
|
1299
|
+
deletedCard
|
|
1300
|
+
);
|
|
862
1301
|
|
|
863
1302
|
state = await rawBoard();
|
|
864
1303
|
check(
|
|
@@ -868,12 +1307,13 @@ async function run() {
|
|
|
868
1307
|
);
|
|
869
1308
|
|
|
870
1309
|
for (const labelId of [labelA.label.id, labelB.label.id]) {
|
|
871
|
-
const deletedLabel = await
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1310
|
+
const deletedLabel = await json("planka_delete_label", { labelId });
|
|
1311
|
+
if (deletedLabel?.verified === true) forget("labels", labelId);
|
|
1312
|
+
check(
|
|
1313
|
+
`planka_delete_label (${labelId}), verified: true`,
|
|
1314
|
+
deletedLabel?.verified === true,
|
|
1315
|
+
deletedLabel
|
|
1316
|
+
);
|
|
877
1317
|
}
|
|
878
1318
|
|
|
879
1319
|
state = await rawBoard();
|