@agent-plan/mcp 0.2.19-next.4 → 0.2.19-next.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA45BA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAg8BA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import * as z from "zod/v4";
5
5
  import { join } from "node:path";
6
6
  import { pathToFileURL } from "node:url";
7
- import { PlanStore, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, buildRecap } from "@agent-plan/core";
7
+ import { PlanStore, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap, migrateToGlobalSequence } from "@agent-plan/core";
8
8
  import { serve } from "@agent-plan/server";
9
9
  import { createChecklistItemId, createFeatureId, createPhaseId, createShortId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef } from "@agent-plan/core/naming";
10
10
  const STATUS_VALUES = ["planned", "in-progress", "done", "blocked", "canceled", "rejected", "deferred", "waiting"];
@@ -31,6 +31,7 @@ async function requireStore() {
31
31
  if (!planBackfillDone) {
32
32
  planBackfillDone = true;
33
33
  await st.ensureShortIdsAndPriority().catch(() => { });
34
+ await migrateToGlobalSequence(st).catch(() => { });
34
35
  }
35
36
  return st;
36
37
  }
@@ -61,34 +62,6 @@ function findFeatureByRef(features, ref) {
61
62
  ?? features.find((feature) => feature.name.toLowerCase() === normalized)
62
63
  ?? features.find((feature) => feature.name.toLowerCase().includes(normalized));
63
64
  }
64
- function findTaskByRef(phases, ref) {
65
- const normalized = ref.trim().toLowerCase();
66
- if (!normalized)
67
- return undefined;
68
- // Composite ref like "P00x(F00x)/T00x" or "T00x(P00x/F00x)": match by phase + task number.
69
- const tMatch = normalized.match(/t0*(\d+)/);
70
- const pMatch = normalized.match(/p0*(\d+)/);
71
- if (tMatch && pMatch) {
72
- const tNum = parseInt(tMatch[1], 10);
73
- const pNum = parseInt(pMatch[1], 10);
74
- for (const phase of phases) {
75
- if (phase.number !== pNum)
76
- continue;
77
- const task = phase.tasks.find((t) => t.number === tNum);
78
- if (task)
79
- return { phase, task };
80
- }
81
- }
82
- for (const phase of phases) {
83
- const task = phase.tasks.find((entry) => entry.id.toLowerCase() === normalized
84
- || (entry.shortId && entry.shortId.toLowerCase() === normalized)
85
- || entry.title.toLowerCase() === normalized
86
- || entry.title.toLowerCase().includes(normalized));
87
- if (task)
88
- return { phase, task };
89
- }
90
- return undefined;
91
- }
92
65
  function featureNumberOfPhase(phase, features) {
93
66
  return phase.featureId ? features.find((f) => f.id === phase.featureId)?.number : undefined;
94
67
  }
@@ -261,17 +234,70 @@ server.registerTool("planner-project-discuss", {
261
234
  return writeAndSummarize(st, `Project discussed/updated: ${project.name}`);
262
235
  });
263
236
  server.registerTool("planner-feature-list", {
264
- description: "List features with phase/task counts.",
265
- }, async () => {
237
+ description: "List features (compact: F00x · shortId — name (status; N phases, M tasks)). Pass featureRef to filter. Use this to discover refs cheaply — do NOT read .planner/ files or planner-plan-get full=true to find entities.",
238
+ inputSchema: { featureRef: z.string().optional().describe("Optional: filter to one feature (F00x/shortId/UUID/name).") },
239
+ }, async ({ featureRef }) => {
266
240
  const st = await requireStore();
267
- const features = (await st.loadFeatures()).features;
241
+ const allFeatures = (await st.loadFeatures()).features;
242
+ const features = featureRef ? allFeatures.filter((f) => f.id === findFeatureByRef(allFeatures, featureRef)?.id) : allFeatures;
268
243
  const phases = await st.loadAllPhases();
269
244
  const lines = features.map((feature) => {
270
245
  const featurePhases = phases.filter((phase) => phase.featureId === feature.id);
271
246
  const taskCount = featurePhases.reduce((total, phase) => total + phase.tasks.length, 0);
272
- return `- ${feature.id} — ${feature.name} (${feature.status}; ${featurePhases.length} phases, ${taskCount} tasks)`;
247
+ return `- ${formatFeatureRef(feature.number)}${feature.shortId ? ` · ${feature.shortId}` : ""} — ${feature.name} (${feature.status}; ${featurePhases.length} phases, ${taskCount} tasks)`;
248
+ });
249
+ return text(lines.join("\n") || "No features");
250
+ });
251
+ server.registerTool("planner-phase-list", {
252
+ description: "List phases (compact: F00x/P00x · shortId — title (status; N tasks) [F00x]). Filters: featureRef, status. Cheap discovery — do NOT read .planner/ files or planner-plan-get full=true.",
253
+ inputSchema: { featureRef: z.string().optional().describe("Optional: filter to one feature (F00x/shortId/UUID/name)."), status: z.string().optional().describe("Optional: filter by status name.") },
254
+ }, async ({ featureRef, status }) => {
255
+ const st = await requireStore();
256
+ const features = (await st.loadFeatures()).features;
257
+ let phases = await st.loadAllPhases();
258
+ if (featureRef) {
259
+ const f = findFeatureByRef(features, featureRef);
260
+ if (!f)
261
+ return text(`Feature not found: ${featureRef}`);
262
+ phases = phases.filter((p) => p.featureId === f.id);
263
+ }
264
+ if (status)
265
+ phases = phases.filter((p) => p.status === status);
266
+ const lines = phases.map((phase) => {
267
+ const fNum = featureNumberOfPhase(phase, features);
268
+ const fTag = fNum !== undefined ? ` [F${String(fNum).padStart(3, "0")}]` : "";
269
+ return `- ${formatPhaseRef(phase.number, fNum)}${phase.shortId ? ` · ${phase.shortId}` : ""} — ${phase.title} (${phase.status}; ${phase.tasks.length} tasks)${fTag}`;
273
270
  });
274
- return text(lines.join("\n") || "No features", { features });
271
+ return text(lines.join("\n") || "No phases");
272
+ });
273
+ server.registerTool("planner-task-list", {
274
+ description: "List tasks (compact: F00x/P00x/T00x · shortId — title (status)). Filters: featureRef, phaseRef, status. Cheap discovery — do NOT read .planner/ files or planner-plan-get full=true.",
275
+ inputSchema: { featureRef: z.string().optional(), phaseRef: z.string().optional(), status: z.string().optional().describe("Optional: filter by status name.") },
276
+ }, async ({ featureRef, phaseRef, status }) => {
277
+ const st = await requireStore();
278
+ const features = (await st.loadFeatures()).features;
279
+ let phases = await st.loadAllPhases();
280
+ if (featureRef) {
281
+ const f = findFeatureByRef(features, featureRef);
282
+ if (!f)
283
+ return text(`Feature not found: ${featureRef}`);
284
+ phases = phases.filter((p) => p.featureId === f.id);
285
+ }
286
+ if (phaseRef) {
287
+ const p = findPhaseByRef(phases, features, phaseRef);
288
+ if (!p)
289
+ return text(`Phase not found: ${phaseRef}`);
290
+ phases = [p];
291
+ }
292
+ const out = [];
293
+ for (const phase of phases) {
294
+ for (const task of phase.tasks) {
295
+ if (status && task.status !== status)
296
+ continue;
297
+ out.push(`- ${taskCompositeRef(task, phase, features)}${task.shortId ? ` · ${task.shortId}` : ""} — ${task.title} (${task.status})`);
298
+ }
299
+ }
300
+ return text(out.join("\n") || "No tasks");
275
301
  });
276
302
  server.registerTool("planner-feature-add", {
277
303
  description: "Create a feature with a rich description. REQUIRED: description must include code references (file:line), current implementation state (what exists, what is unimplemented), systems/structs/traits involved, concrete goals, and behaviors to preserve. The description is the primary context for future agents resuming this feature; one-liners cause misalignment.",
@@ -289,7 +315,7 @@ server.registerTool("planner-feature-add", {
289
315
  const priority = await st.nextPriority("feature");
290
316
  const feature = {
291
317
  id: createFeatureId(),
292
- number: existingFeatures.length + 1,
318
+ number: await st.allocFeatureNumber(),
293
319
  shortId,
294
320
  priority,
295
321
  name: name.trim(),
@@ -316,20 +342,21 @@ server.registerTool("planner-feature-add", {
316
342
  });
317
343
  server.registerTool("planner-feature-show", {
318
344
  description: "Show a feature by id or name.",
319
- inputSchema: { feature: z.string().min(1).describe("Feature id or name") },
320
- }, async ({ feature: ref }) => {
345
+ inputSchema: { feature: z.string().min(1).describe("Feature ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."), full: z.boolean().optional().describe("If true, include the feature description. Default: compact identity only (saves tokens).") },
346
+ }, async ({ feature: ref, full }) => {
321
347
  const st = await requireStore();
322
348
  const features = (await st.loadFeatures()).features;
323
349
  const feature = findFeatureByRef(features, ref);
324
350
  if (!feature)
325
351
  return text(`Feature not found: ${ref}`);
326
352
  const phases = (await st.loadAllPhases()).filter((phase) => phase.featureId === feature.id);
327
- return text(`${feature.name} (${feature.id}) — ${feature.status}; ${phases.length} phases`, { feature, phases });
353
+ const summary = `${feature.name} — ${formatFeatureRef(feature.number)}${feature.shortId ? ` · ${feature.shortId}` : ""} (${feature.status}; ${phases.length} phases)`;
354
+ return text(full ? `${summary}\n\n${feature.description || ""}` : summary);
328
355
  });
329
356
  server.registerTool("planner-feature-update", {
330
357
  description: "Update a feature.",
331
358
  inputSchema: {
332
- feature: z.string().min(1).describe("Feature id or name"),
359
+ feature: z.string().min(1).describe("Feature ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."),
333
360
  name: z.string().optional(),
334
361
  description: z.string().optional(),
335
362
  status: z.enum(STATUS_VALUES).optional(),
@@ -374,7 +401,7 @@ server.registerTool("planner-feature-update", {
374
401
  server.registerTool("planner-feature-delete", {
375
402
  description: "Delete a feature. By default, phases are unlinked rather than deleted.",
376
403
  inputSchema: {
377
- feature: z.string().min(1).describe("Feature id or name"),
404
+ feature: z.string().min(1).describe("Feature ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."),
378
405
  cascade: z.boolean().optional().describe("Also delete phases belonging to this feature"),
379
406
  },
380
407
  }, async ({ feature: ref, cascade }) => {
@@ -404,7 +431,7 @@ server.registerTool("planner-phase-add", {
404
431
  description: "Create a phase with a rich description. REQUIRED: description must include code references (file:line), current implementation state, dependencies, specific files/systems to modify, and behaviors to preserve. The description is the primary context for future agents; one-liners cause misalignment.",
405
432
  inputSchema: {
406
433
  title: z.string().min(1),
407
- feature: z.string().optional().describe("Feature id or name"),
434
+ feature: z.string().optional().describe("Feature ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."),
408
435
  summary: z.string().optional().describe("One-line summary of the phase"),
409
436
  description: z.string().min(50, "Description must be at least 50 characters — include code references (file:line), current state, structs/traits involved, concrete work items, behaviors to preserve. Prefix with 'design-only' for pre-implementation design tasks.").describe("Required code references (file:line), current state, structs/traits involved, concrete work items, behaviors to preserve. Not a one-liner."),
410
437
  },
@@ -424,7 +451,7 @@ server.registerTool("planner-phase-add", {
424
451
  const priority = await st.nextPriority("phase", feature?.id);
425
452
  phase = {
426
453
  id: createPhaseId(),
427
- number: featurePhases.reduce((max, entry) => Math.max(max, entry.number), 0) + 1,
454
+ number: await st.allocPhaseNumber(),
428
455
  shortId,
429
456
  priority,
430
457
  slug: normalizeSlug(title),
@@ -472,18 +499,20 @@ server.registerTool("planner-phase-add", {
472
499
  });
473
500
  server.registerTool("planner-phase-show", {
474
501
  description: "Show a phase by id or name.",
475
- inputSchema: { phase: z.string().min(1).describe("Phase id or name") },
476
- }, async ({ phase: ref }) => {
502
+ inputSchema: { phase: z.string().min(1).describe("Phase ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."), full: z.boolean().optional().describe("If true, include the phase description. Default: compact identity only (saves tokens).") },
503
+ }, async ({ phase: ref, full }) => {
477
504
  const st = await requireStore();
478
- const phase = findPhaseByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
505
+ const features = (await st.loadFeatures()).features;
506
+ const phase = findPhaseByRef(await st.loadAllPhases(), features, ref);
479
507
  if (!phase)
480
508
  return text(`Phase not found: ${ref}`);
481
- return text(`${phase.title} (${phase.id}) — ${phase.status}; ${phase.tasks.length} tasks`, { phase });
509
+ const summary = `${phase.title} — ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, features))}${phase.shortId ? ` · ${phase.shortId}` : ""} (${phase.status}; ${phase.tasks.length} tasks)`;
510
+ return text(full ? `${summary}\n\n${phase.description || ""}` : summary);
482
511
  });
483
512
  server.registerTool("planner-phase-discuss", {
484
513
  description: "Persist phase discovery fields and mark phase planned.",
485
514
  inputSchema: {
486
- phase: z.string().min(1).describe("Phase id or name"),
515
+ phase: z.string().min(1).describe("Phase ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."),
487
516
  goal: z.string().optional(),
488
517
  summary: z.string().optional(),
489
518
  scope: z.string().optional(),
@@ -568,12 +597,13 @@ server.registerTool("planner-phase-delete", {
568
597
  feature.phaseIds = feature.phaseIds.filter((id) => id !== phase.id);
569
598
  return doc;
570
599
  });
571
- return writeAndSummarize(st, `Phase deleted: ${phase.id}`, { deleted: phase.id });
600
+ const dFeat = (await st.loadFeatures()).features.find((f) => f.id === phase.featureId);
601
+ return writeAndSummarize(st, `Phase deleted: ${formatPhaseRef(phase.number, dFeat?.number)}${phase.shortId ? ` · ${phase.shortId}` : ""}`, { deleted: phase.id });
572
602
  });
573
603
  server.registerTool("planner-task-add", {
574
604
  description: "Create a task with a rich description. REQUIRED: description must include code references (file:line), what already exists vs what needs to be built, specific structs/traits/systems to modify, concrete implementation steps, and edge cases to handle. The description is the execution context for agents; one-liners cause misalignment.",
575
605
  inputSchema: {
576
- phase: z.string().min(1).describe("Phase id or name"),
606
+ phase: z.string().min(1).describe("Phase ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."),
577
607
  title: z.string().min(1),
578
608
  description: z.string().min(50, "Description must be at least 50 characters — include code references (file:line), current state vs desired state, structs/traits to modify, concrete implementation steps, edge cases. Prefix with 'design-only' for pre-implementation design tasks.").describe("Required code references (file:line), current state vs desired state, structs/traits to modify, concrete implementation steps, edge cases. Not a one-liner."),
579
609
  checklist: z.array(z.string()).optional(),
@@ -590,7 +620,7 @@ server.registerTool("planner-task-add", {
590
620
  const task = {
591
621
  id: taskId,
592
622
  phaseId: found.id,
593
- number: found.tasks.length + 1,
623
+ number: await st.allocTaskNumber(),
594
624
  shortId,
595
625
  priority,
596
626
  shortName: clampSlug(title, 30, `task-${Date.now().toString(36)}`),
@@ -620,13 +650,18 @@ server.registerTool("planner-task-add", {
620
650
  });
621
651
  server.registerTool("planner-task-show", {
622
652
  description: "Show a task by id or name.",
623
- inputSchema: { task: z.string().min(1).describe("Task id or name") },
624
- }, async ({ task: ref }) => {
653
+ inputSchema: { task: z.string().min(1).describe("Task ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."), full: z.boolean().optional().describe("If true, include the task description + statusLog. Default: compact identity only (saves tokens).") },
654
+ }, async ({ task: ref, full }) => {
625
655
  const st = await requireStore();
626
- const found = findTaskByRef(await st.loadAllPhases(), ref);
656
+ const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
627
657
  if (!found)
628
658
  return text(`Task not found: ${ref}`);
629
- return text(`${found.task.title} (${found.task.id}) — ${found.task.status}; phase ${found.phase.id}`, { task: found.task, phase: found.phase });
659
+ const features = (await st.loadFeatures()).features;
660
+ const summary = `${found.task.title} — ${taskCompositeRef(found.task, found.phase, features)}${found.task.shortId ? ` · ${found.task.shortId}` : ""} (${found.task.status}; phase ${formatPhaseRef(found.phase.number, featureNumberOfPhase(found.phase, features))})`;
661
+ if (!full)
662
+ return text(summary);
663
+ const log = (found.task.statusLog ?? []).map((e) => ` - ${e.date.slice(0, 10)} ${e.title}`).join("\n");
664
+ return text(`${summary}\n\n${found.task.description || ""}${log ? `\n\nStatus log:\n${log}` : ""}`);
630
665
  });
631
666
  server.registerTool("planner-task-discuss", {
632
667
  description: "Persist task execution notes/checklist.",
@@ -637,7 +672,7 @@ server.registerTool("planner-task-discuss", {
637
672
  },
638
673
  }, async ({ task: ref, description, checklist }) => {
639
674
  const st = await requireStore();
640
- const found = findTaskByRef(await st.loadAllPhases(), ref);
675
+ const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
641
676
  if (!found)
642
677
  return text(`Task not found: ${ref}`);
643
678
  let updatedTask;
@@ -670,7 +705,7 @@ server.registerTool("planner-task-update", {
670
705
  },
671
706
  }, async ({ task: ref, title, status, description, motivation, priority }) => {
672
707
  const st = await requireStore();
673
- const found = findTaskByRef(await st.loadAllPhases(), ref);
708
+ const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
674
709
  if (!found)
675
710
  return text(`Task not found: ${ref}`);
676
711
  // Validate motivation requirement for status transitions.
@@ -720,7 +755,7 @@ server.registerTool("planner-task-delete", {
720
755
  inputSchema: { task: z.string().min(1) },
721
756
  }, async ({ task: ref }) => {
722
757
  const st = await requireStore();
723
- const found = findTaskByRef(await st.loadAllPhases(), ref);
758
+ const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
724
759
  if (!found)
725
760
  return text(`Task not found: ${ref}`);
726
761
  await st.updatePhase(found.phase.id, (phase) => {
@@ -730,14 +765,15 @@ server.registerTool("planner-task-delete", {
730
765
  return phase;
731
766
  });
732
767
  await st.syncTaskStatusRollup(found.phase.id);
733
- return writeAndSummarize(st, `Task deleted: ${found.task.id}`, { deleted: found.task.id });
768
+ const tdFeatures = (await st.loadFeatures()).features;
769
+ return writeAndSummarize(st, `Task deleted: ${taskCompositeRef(found.task, found.phase, tdFeatures)}${found.task.shortId ? ` · ${found.task.shortId}` : ""}`, { deleted: found.task.id });
734
770
  });
735
771
  server.registerTool("planner-task-start", {
736
772
  description: "Set a task to in-progress.",
737
773
  inputSchema: { task: z.string().min(1) },
738
774
  }, async ({ task: ref }) => {
739
775
  const st = await requireStore();
740
- const found = findTaskByRef(await st.loadAllPhases(), ref);
776
+ const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
741
777
  if (!found)
742
778
  return text(`Task not found: ${ref}`);
743
779
  // Auto-archive the handoff on THIS task's phase (reason: task-started) — the
@@ -790,7 +826,7 @@ server.registerTool("planner-task-complete", {
790
826
  },
791
827
  }, async ({ task: ref, force, description_update }) => {
792
828
  const st = await requireStore();
793
- const found = findTaskByRef(await st.loadAllPhases(), ref);
829
+ const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
794
830
  if (!found)
795
831
  return text(`Task not found: ${ref}`);
796
832
  const unchecked = found.task.checklist.filter((item) => !item.checked);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-plan/mcp",
3
- "version": "0.2.19-next.4",
3
+ "version": "0.2.19-next.6",
4
4
  "private": false,
5
5
  "description": "MCP server for Agent Plan, exposing planner-* tools over stdio.",
6
6
  "license": "MIT",
@@ -41,8 +41,8 @@
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "^1.17.0",
43
43
  "zod": "^3.24.0",
44
- "@agent-plan/core": "0.2.19-next.4",
45
- "@agent-plan/server": "0.2.19-next.4"
44
+ "@agent-plan/core": "0.2.19-next.6",
45
+ "@agent-plan/server": "0.2.19-next.6"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsc -p tsconfig.json",