@agent-plan/mcp 0.2.19-next.0 → 0.2.19-next.1

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":";AA+5BA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAm5BA,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 } from "@agent-plan/core";
7
+ import { PlanStore, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, buildRecap } from "@agent-plan/core";
8
8
  import { serve } from "@agent-plan/server";
9
9
  import { createChecklistItemId, createFeatureId, createPhaseId, createShortId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef } from "@agent-plan/core/naming";
10
10
  const STATUS_VALUES = ["planned", "in-progress", "done", "blocked", "canceled", "rejected", "deferred", "waiting"];
@@ -55,49 +55,6 @@ async function ensureWebStarted() {
55
55
  return { localUrl: "" };
56
56
  }
57
57
  }
58
- /** Build a consolidated planner recap (project state + active work + pending
59
- * handoff + web URL). Harness-agnostic equivalent of Pi's buildStartupResumeSummary. */
60
- async function buildRecapText(st, web) {
61
- const [plan, resume, handoffs] = await Promise.all([st.loadAll(), st.loadResume(), st.listHandoffs()]);
62
- const allTasks = plan.phases.flatMap((p) => p.tasks.map((t) => ({ phase: p, task: t })));
63
- const totalF = plan.features.features.length;
64
- const doneF = plan.features.features.filter((f) => f.status === "done").length;
65
- const activeF = plan.features.features.filter((f) => f.status === "in-progress").length;
66
- const totalP = plan.phases.length;
67
- const doneP = plan.phases.filter((p) => p.status === "done").length;
68
- const activeP = plan.phases.filter((p) => p.status === "in-progress" || p.status === "discovery").length;
69
- const totalT = allTasks.length;
70
- const doneT = allTasks.filter(({ task }) => task.status === "done").length;
71
- const activeT = allTasks.filter(({ task }) => task.status === "in-progress").length;
72
- const inProgress = allTasks.filter(({ task }) => task.status === "in-progress");
73
- const focus = inProgress[0];
74
- const focusFeature = focus ? plan.features.features.find((f) => f.id === focus.phase.featureId) : undefined;
75
- const lines = [];
76
- lines.push("## Planner recap");
77
- const name = plan.project.name || "(unnamed project)";
78
- lines.push(`Project: ${name}${plan.project.goal ? " — " + plan.project.goal : ""}`);
79
- lines.push(`Progress: Features ${doneF}/${totalF} done (${activeF} active) · Phases ${doneP}/${totalP} done (${activeP} active) · Tasks ${doneT}/${totalT} done (${activeT} active)`);
80
- if (focus) {
81
- lines.push(`Current focus: ${focusFeature?.name ?? "?"} / ${focus.phase.title} / ${focus.task.title} (in-progress)`);
82
- }
83
- else {
84
- lines.push("Current focus: no active task — review the plan and pick the next concrete task");
85
- }
86
- if (resume?.nextSteps?.length)
87
- lines.push(`Next step: ${resume.nextSteps[0]}`);
88
- if (handoffs.length > 0) {
89
- lines.push("", `## Pending phase handoffs (${handoffs.length})`);
90
- handoffs.forEach((h, i) => lines.push(`[${i + 1}] ${h.compositeRef} — ${h.updatedAt} — "${h.firstLine}"`));
91
- lines.push("", "→ Read the relevant one with planner-handoff-show <ref> (validate against current state), then call planner-handoff-clear <ref> once consumed (delete-on-resume).");
92
- }
93
- else if (activeT === 0) {
94
- lines.push("", "No phase handoff pending and no task in-progress. Use planner-task-add / planner-task-start to begin work.");
95
- }
96
- if (web.localUrl) {
97
- lines.push("", "## Web UI", `🌐 ${web.localUrl}${web.lanUrl ? " (LAN: " + web.lanUrl + ")" : ""}`);
98
- }
99
- return lines.join("\n");
100
- }
101
58
  function findFeatureByRef(features, ref) {
102
59
  const normalized = ref.trim().toLowerCase();
103
60
  return features.find((feature) => feature.id.toLowerCase() === normalized)
@@ -106,8 +63,25 @@ function findFeatureByRef(features, ref) {
106
63
  }
107
64
  function findTaskByRef(phases, ref) {
108
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
+ }
109
82
  for (const phase of phases) {
110
83
  const task = phase.tasks.find((entry) => entry.id.toLowerCase() === normalized
84
+ || (entry.shortId && entry.shortId.toLowerCase() === normalized)
111
85
  || entry.title.toLowerCase() === normalized
112
86
  || entry.title.toLowerCase().includes(normalized));
113
87
  if (task)
@@ -115,6 +89,12 @@ function findTaskByRef(phases, ref) {
115
89
  }
116
90
  return undefined;
117
91
  }
92
+ function featureNumberOfPhase(phase, features) {
93
+ return phase.featureId ? features.find((f) => f.id === phase.featureId)?.number : undefined;
94
+ }
95
+ function taskCompositeRef(task, phase, features) {
96
+ return `${formatPhaseRef(phase.number, featureNumberOfPhase(phase, features))}/T${String(task.number).padStart(3, "0")}`;
97
+ }
118
98
  function applyTaskLifecycleDates(task, nextStatus, now) {
119
99
  const previousStatus = task.status;
120
100
  if (nextStatus === "in-progress" && !task.startedAt)
@@ -625,13 +605,14 @@ server.registerTool("planner-task-add", {
625
605
  createdAt: timestamp,
626
606
  updatedAt: timestamp,
627
607
  };
628
- const phase = await st.updatePhase(found.id, (entry) => {
608
+ await st.updatePhase(found.id, (entry) => {
629
609
  entry.tasks.push(task);
630
610
  entry.taskIds.push(task.id);
631
611
  entry.updatedAt = timestamp;
632
612
  return entry;
633
613
  });
634
- return writeAndSummarize(st, `Task created: ${task.id} — ${task.title}`, { task, phase });
614
+ const features = (await st.loadFeatures()).features;
615
+ return writeAndSummarize(st, `✅ Task created: ${taskCompositeRef(task, found, features)} — ${task.title} (planned)${task.shortId ? ` · ${task.shortId}` : ""}`);
635
616
  });
636
617
  server.registerTool("planner-task-show", {
637
618
  description: "Show a task by id or name.",
@@ -669,7 +650,9 @@ server.registerTool("planner-task-discuss", {
669
650
  updatedTask = task;
670
651
  return phase;
671
652
  });
672
- return writeAndSummarize(st, `Task discussed/updated: ${found.task.id}`, { task: updatedTask ?? found.task });
653
+ const features = (await st.loadFeatures()).features;
654
+ const t = updatedTask ?? found.task;
655
+ return writeAndSummarize(st, `✅ Task discussed/updated: ${taskCompositeRef(t, found.phase, features)} — ${t.title} (${t.status})${t.shortId ? ` · ${t.shortId}` : ""}`);
673
656
  });
674
657
  server.registerTool("planner-task-update", {
675
658
  description: "Update task fields. A motivation is REQUIRED when changing to blocked, canceled, deferred, rejected, waiting, or back to planned from another status.",
@@ -724,7 +707,9 @@ server.registerTool("planner-task-update", {
724
707
  return phase;
725
708
  });
726
709
  await st.syncTaskStatusRollup(found.phase.id);
727
- return writeAndSummarize(st, `Task updated: ${found.task.id}`, { task: updatedTask ?? found.task });
710
+ const features = (await st.loadFeatures()).features;
711
+ const t = updatedTask ?? found.task;
712
+ return writeAndSummarize(st, `✅ Task updated: ${taskCompositeRef(t, found.phase, features)} — ${t.title} (${t.status})${t.shortId ? ` · ${t.shortId}` : ""}`);
728
713
  });
729
714
  server.registerTool("planner-task-delete", {
730
715
  description: "Delete a task.",
@@ -782,7 +767,9 @@ server.registerTool("planner-task-start", {
782
767
  return phase;
783
768
  });
784
769
  await st.syncTaskStatusRollup(found.phase.id);
785
- return writeAndSummarize(st, `${handoffNotice}✅ Task started: ${found.task.id}`, { task: updatedTask ?? found.task });
770
+ const features = (await st.loadFeatures()).features;
771
+ const t = updatedTask ?? found.task;
772
+ return writeAndSummarize(st, `${handoffNotice}✅ Task started: ${taskCompositeRef(t, found.phase, features)} — ${t.title} (in-progress)${t.shortId ? ` · ${t.shortId}` : ""}`);
786
773
  });
787
774
  server.registerTool("planner-task-complete", {
788
775
  description: "Set a task to done. Fails if checklist is incomplete unless force=true.",
@@ -827,7 +814,9 @@ server.registerTool("planner-task-complete", {
827
814
  return phase;
828
815
  });
829
816
  const clearedRef = await st.syncTaskStatusRollup(found.phase.id);
830
- return writeAndSummarize(st, `✅ Task completed: ${found.task.id}${clearedRef ? ` — phase handoff auto-cleared (${clearedRef})` : ""}`, { task: updatedTask ?? found.task });
817
+ const features = (await st.loadFeatures()).features;
818
+ const t = updatedTask ?? found.task;
819
+ return writeAndSummarize(st, `✅ Task completed: ${taskCompositeRef(t, found.phase, features)} — ${t.title} (done)${t.shortId ? ` · ${t.shortId}` : ""}${clearedRef ? ` — phase handoff auto-cleared (${clearedRef})` : ""}`);
831
820
  });
832
821
  server.registerTool("planner-handoff-list", {
833
822
  description: "List all phases with a non-empty entity-scoped handoff (phase.handoff field). Returns composite refs (P00x / P00x(F00x)) with the first line and last-updated time.",
@@ -931,7 +920,7 @@ server.registerTool("planner-load", {
931
920
  if (!(await st.exists()))
932
921
  return text("No .planner/ found at " + planRoot() + ". Run planner-init first.");
933
922
  const web = await ensureWebStarted();
934
- const recap = await buildRecapText(st, web);
923
+ const recap = await buildRecap(st, web, { harness: "mcp" });
935
924
  return text(recap);
936
925
  });
937
926
  server.registerTool("planner-disable", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-plan/mcp",
3
- "version": "0.2.19-next.0",
3
+ "version": "0.2.19-next.1",
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.0",
45
- "@agent-plan/server": "0.2.19-next.0"
44
+ "@agent-plan/core": "0.2.19-next.1",
45
+ "@agent-plan/server": "0.2.19-next.1"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsc -p tsconfig.json",