@agent-plan/mcp 0.2.27 → 0.2.28

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":";AAqkGA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAglGA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
package/dist/index.js CHANGED
@@ -1748,60 +1748,62 @@ server.registerTool("planner-task-update", {
1748
1748
  }
1749
1749
  let updatedTask;
1750
1750
  const timestamp = nowISO();
1751
- await st.updatePhase(found.phase.id, (phase) => {
1752
- const task = phase.tasks.find((entry) => entry.id === found.task.id);
1753
- if (!task)
1754
- return phase;
1755
- if (title !== undefined)
1756
- task.title = title.trim();
1757
- if (priority !== undefined)
1758
- task.priority = priority;
1759
- if (description !== undefined)
1760
- task.description = description.trim();
1761
- if (rawDescriptionRef !== undefined) {
1762
- const descriptionRef = normalizeDescriptionRef(rawDescriptionRef);
1763
- if (descriptionRef)
1764
- task.descriptionRef = descriptionRef;
1765
- else
1766
- delete task.descriptionRef;
1767
- }
1768
- if (notes !== undefined)
1769
- task.notes = notes.trim();
1770
- if (decisions !== undefined)
1771
- task.decisions = decisions.map((item) => item.trim()).filter(Boolean);
1772
- if (checklist !== undefined) {
1773
- task.checklist = checklist.map((item, index) => ({
1774
- id: createChecklistItemId(task.id, index + 1, item),
1775
- number: index + 1,
1776
- title: item.trim(),
1777
- checked: false,
1778
- }));
1779
- }
1780
- if (subtasks !== undefined) {
1781
- const existingIds = new Set(task.subtasks.map((item) => item.id));
1782
- if (subtasks.some((item) => item.id !== undefined && !existingIds.has(item.id))) {
1783
- throw new PlanStoreError("Subtask IDs must belong to the target task.", undefined, { errorCode: "SUBTASK_ID_INVALID" });
1751
+ try {
1752
+ await st.updateTask(found.phase.id, found.task.id, (task) => {
1753
+ if (title !== undefined)
1754
+ task.title = title.trim();
1755
+ if (priority !== undefined)
1756
+ task.priority = priority;
1757
+ if (description !== undefined)
1758
+ task.description = description.trim();
1759
+ if (rawDescriptionRef !== undefined) {
1760
+ const descriptionRef = normalizeDescriptionRef(rawDescriptionRef);
1761
+ if (descriptionRef)
1762
+ task.descriptionRef = descriptionRef;
1763
+ else
1764
+ delete task.descriptionRef;
1784
1765
  }
1785
- task.subtasks = subtasks.map((item) => ({ id: item.id ?? randomUUID(), title: item.title.trim(), description: item.description?.trim() ?? "", status: item.status ?? "planned", createdAt: task.subtasks.find((candidate) => candidate.id === item.id)?.createdAt ?? timestamp, updatedAt: timestamp }));
1786
- }
1787
- if (status !== undefined && status !== task.status) {
1788
- // Record status change in the incremental statusLog.
1789
- const entry = {
1790
- id: createChecklistItemId(task.id, (task.statusLog?.length ?? 0) + 1, `${task.status}-${status}`),
1791
- date: timestamp,
1792
- fromStatus: task.status,
1793
- toStatus: status,
1794
- title: motivation?.split("\n")[0]?.trim() || `${task.status} → ${status}`,
1795
- description: motivation?.trim() || "",
1796
- };
1797
- task.statusLog = [...(task.statusLog ?? []), entry];
1798
- applyTaskLifecycleDates(task, status, timestamp);
1799
- }
1800
- task.updatedAt = timestamp;
1801
- phase.updatedAt = timestamp;
1802
- updatedTask = task;
1803
- return phase;
1804
- });
1766
+ if (notes !== undefined)
1767
+ task.notes = notes.trim();
1768
+ if (decisions !== undefined)
1769
+ task.decisions = decisions.map((item) => item.trim()).filter(Boolean);
1770
+ if (checklist !== undefined) {
1771
+ task.checklist = checklist.map((item, index) => ({
1772
+ id: createChecklistItemId(task.id, index + 1, item),
1773
+ number: index + 1,
1774
+ title: item.trim(),
1775
+ checked: false,
1776
+ }));
1777
+ }
1778
+ if (subtasks !== undefined) {
1779
+ const existingIds = new Set(task.subtasks.map((item) => item.id));
1780
+ if (subtasks.some((item) => item.id !== undefined && !existingIds.has(item.id))) {
1781
+ throw new PlanStoreError("Subtask IDs must belong to the target task.", undefined, { errorCode: "SUBTASK_ID_INVALID" });
1782
+ }
1783
+ task.subtasks = subtasks.map((item) => ({ id: item.id ?? randomUUID(), title: item.title.trim(), description: item.description?.trim() ?? "", status: item.status ?? "planned", createdAt: task.subtasks.find((candidate) => candidate.id === item.id)?.createdAt ?? timestamp, updatedAt: timestamp }));
1784
+ }
1785
+ if (status !== undefined && status !== task.status) {
1786
+ // Record status change in the incremental statusLog.
1787
+ const entry = {
1788
+ id: createChecklistItemId(task.id, (task.statusLog?.length ?? 0) + 1, `${task.status}-${status}`),
1789
+ date: timestamp,
1790
+ fromStatus: task.status,
1791
+ toStatus: status,
1792
+ title: motivation?.split("\n")[0]?.trim() || `${task.status} → ${status}`,
1793
+ description: motivation?.trim() || "",
1794
+ };
1795
+ task.statusLog = [...(task.statusLog ?? []), entry];
1796
+ applyTaskLifecycleDates(task, status, timestamp);
1797
+ }
1798
+ task.updatedAt = timestamp;
1799
+ updatedTask = task;
1800
+ return task;
1801
+ });
1802
+ }
1803
+ catch (error) {
1804
+ const details = error instanceof PlanStoreError ? error.details : undefined;
1805
+ return { ...text(`Task update failed: ${error instanceof Error ? error.message : "unknown error"}`, { updated: false, errorCode: details?.errorCode ?? "TASK_UPDATE_FAILED" }), isError: true };
1806
+ }
1805
1807
  await st.syncTaskStatusRollup(found.phase.id);
1806
1808
  const features = (await st.loadFeatures()).features;
1807
1809
  const t = updatedTask ?? found.task;
@@ -2564,8 +2566,8 @@ server.registerTool("planner-handoff-show", {
2564
2566
  && phase.handoffAudit.contentLength === phase.handoff.length);
2565
2567
  const resumeReady = persistenceVerified && Boolean(phase.handoffAudit?.resumeReadyAt);
2566
2568
  const status = resumeReady
2567
- ? "Resume-ready: persisted read-back and source reconciliation completed."
2568
- : "NOT resume-ready: after reading this entire persisted body, call planner-handoff-verify with its contentHash and a fresh comparison against every required source. If any gap exists, rewrite instead of verifying.";
2569
+ ? "Resume-ready: persisted compact capsule read-back completed."
2570
+ : "NOT resume-ready: after reading this persisted capsule, call planner-handoff-verify with its contentHash. The planner derives legacy evidence from persisted state; rewrite only if the capsule itself omits resume-critical context.";
2569
2571
  return text(`Handoff for ${r.compositeRef}\n${status}\nContent hash: ${contentHash}\n\n${phaseWorkMap.content}\n\nBefore proposing new work, reread the canonical phase and relevant sibling task full view; do not duplicate an already-owned capability.\n\n${bounded.content}`, {
2570
2572
  phaseRef: r.compositeRef,
2571
2573
  phaseId: r.phase.id,
@@ -2579,15 +2581,15 @@ server.registerTool("planner-handoff-show", {
2579
2581
  });
2580
2582
  });
2581
2583
  server.registerTool("planner-handoff-verify", {
2582
- description: "Finalize one persisted handoff as resume-ready only after planner-handoff-show has displayed the entire body and the agent has compared it again with conversation corrections, planner entities, working tree/diff, verification/runtime evidence, and peer output. Any discovered omission blocks verification and requires prepare+rewrite.",
2584
+ description: "Finalize one persisted handoff as resume-ready after planner-handoff-show confirms the persisted compact resume capsule. Legacy sourceReviews and omissionsFound evidence remains accepted but is optional; the planner derives omitted evidence from persisted state.",
2583
2585
  inputSchema: {
2584
2586
  phaseRef: z.string().min(1),
2585
2587
  expectedContentHash: z.string().regex(/^[a-f0-9]{64}$/),
2586
2588
  sourceReviews: z.array(z.object({
2587
2589
  source: z.enum(["conversation", "planner-entities", "working-tree", "verification-runtime", "peer-agent-output"]),
2588
2590
  detail: z.string().min(12),
2589
- })),
2590
- omissionsFound: z.array(z.string().min(1)),
2591
+ })).optional().describe("Optional legacy evidence; omitted for compact handoffs."),
2592
+ omissionsFound: z.array(z.string().min(1)).optional().describe("Optional read-back gaps; omit when the persisted capsule is complete."),
2591
2593
  },
2592
2594
  }, async ({ phaseRef, expectedContentHash, sourceReviews, omissionsFound }) => {
2593
2595
  const st = await requireStore();
@@ -2595,7 +2597,11 @@ server.registerTool("planner-handoff-verify", {
2595
2597
  if (!r.ok)
2596
2598
  return text(`❌ ${r.error}`, { resumeReady: false });
2597
2599
  try {
2598
- const result = await st.verifyPhaseHandoffReadBack(r.phase.id, { expectedContentHash, sourceReviews, omissionsFound });
2600
+ const result = await st.verifyPhaseHandoffReadBack(r.phase.id, {
2601
+ expectedContentHash,
2602
+ ...(sourceReviews ? { sourceReviews } : {}),
2603
+ ...(omissionsFound ? { omissionsFound } : {}),
2604
+ });
2599
2605
  await st.writeGenerated();
2600
2606
  return text(`✅ Handoff on ${r.compositeRef} is resume-ready after persisted read-back and source reconciliation.`, {
2601
2607
  phaseRef: r.compositeRef,
@@ -2881,7 +2887,7 @@ server.registerTool("planner-idea-promotion-finalize", {
2881
2887
  return text(`Idea promoted: ${formatIdeaRef(promoted.number)} → ${promoted.promotion.targetRef}`, { idea: promoted, promoted: true, targetRef: promoted.promotion.targetRef });
2882
2888
  });
2883
2889
  server.registerTool("planner-handoff-write", {
2884
- description: "Persist a reconciled handoff candidate with durable context synchronization and mandatory audits. The result always has resumeReady=false until a separate planner-handoff-show read-back and planner-handoff-verify source reconciliation succeeds. Run planner-handoff-prepare first; link extended detail from committed .planner/docs/ Markdown.",
2890
+ description: "Persist a compact reconciled handoff candidate with durable context synchronization. The result always has resumeReady=false until a separate planner-handoff-show read-back and planner-handoff-verify succeeds. Run planner-handoff-prepare first; provide only resume-critical context and link optional extended detail from .planner/docs/ Markdown.",
2885
2891
  inputSchema: {
2886
2892
  phaseRef: z.string().min(1).describe("Exact confirmed phase ref: P00x | P00x(F00x) | UUID | title."),
2887
2893
  title: z.string().min(3).optional().describe("Meaningful handoff title summarizing the work."),
@@ -2897,7 +2903,7 @@ server.registerTool("planner-handoff-write", {
2897
2903
  status: z.enum(["captured", "not-applicable"]),
2898
2904
  detail: z.string().min(1),
2899
2905
  })),
2900
- }).optional().describe("Mandatory for confirmed writes; every category from planner-handoff-prepare must be present."),
2906
+ }).optional().describe("Optional legacy completeness evidence. Compact handoffs omit this duplicate prose."),
2901
2907
  coldStartInventory: z.object({
2902
2908
  version: z.literal(HANDOFF_COLD_START_INVENTORY_VERSION),
2903
2909
  sourceReviews: z.array(z.object({ source: z.string().min(1), detail: z.string().min(1) })),
@@ -2906,7 +2912,7 @@ server.registerTool("planner-handoff-write", {
2906
2912
  items: z.array(z.string().min(1)),
2907
2913
  notApplicableReason: z.string().min(1).optional().describe("Optional explanation only; every category still requires a concrete item, using an explicit verified-negative statement when nothing exists."),
2908
2914
  })),
2909
- }).optional().describe("Mandatory for confirmed writes. Every concrete item must appear verbatim in the handoff or a validated supporting document."),
2915
+ }).optional().describe("Optional legacy cold-start evidence. Compact handoffs omit this duplicate prose."),
2910
2916
  supportingDocuments: z.array(z.object({
2911
2917
  path: z.string().min(1).describe("Committed Markdown path under .planner/docs/."),
2912
2918
  description: z.string().min(1).describe("What the document contains and why the next agent needs it."),
@@ -2993,7 +2999,7 @@ server.registerTool("planner-handoff-write", {
2993
2999
  },
2994
3000
  });
2995
3001
  await st.writeGenerated();
2996
- return text(`⚠️ Handoff candidate persisted on ${r.compositeRef}, but it is NOT resume-ready yet. Required next action: call planner-handoff-show ${r.compositeRef}, read the entire persisted body, compare it again with every required source, then call planner-handoff-verify with the returned contentHash. If that review finds any gap, run prepare+write again instead of verifying.`, {
3002
+ return text(`⚠️ Handoff candidate persisted on ${r.compositeRef}, but it is NOT resume-ready yet. Required next action: call planner-handoff-show ${r.compositeRef}, read the persisted compact capsule, then call planner-handoff-verify with the returned contentHash. Rewrite only if resume-critical context is missing.`, {
2997
3003
  phaseRef: r.compositeRef,
2998
3004
  phaseId: r.phase.id,
2999
3005
  updatedTaskIds: result.updatedTaskIds,
@@ -3004,8 +3010,7 @@ server.registerTool("planner-handoff-write", {
3004
3010
  verificationRequired: true,
3005
3011
  nextActions: [
3006
3012
  `Call planner-handoff-show ${r.compositeRef} and read the entire persisted body.`,
3007
- "Compare the persisted body against conversation corrections, planner entities, working tree/diff, verification/runtime evidence, and peer-agent output.",
3008
- "If any omission exists, run planner-handoff-prepare and planner-handoff-write again; otherwise call planner-handoff-verify with the shown contentHash.",
3013
+ "Call planner-handoff-verify with the shown contentHash; legacy sourceReviews and omissionsFound are optional because the planner derives omitted evidence from persisted state.",
3009
3014
  ],
3010
3015
  });
3011
3016
  }
@@ -3017,7 +3022,7 @@ server.registerTool("planner-handoff-write", {
3017
3022
  }
3018
3023
  });
3019
3024
  server.registerTool("planner-handoff-prepare", {
3020
- description: "Identify or audit the exact handoff target. With a confirmed phaseRef, returns the canonical scaffold, a bounded priority-ordered phase work map with sibling goals/dependencies/capability ownership, the current token, missing task evidence, mandatory audits, and the 24,000-character inline budget. Reread the canonical phase and relevant sibling tasks before proposing work.",
3025
+ description: "Identify or prepare the exact handoff target. With a confirmed phaseRef, returns a compact resume scaffold, bounded priority-ordered phase work map, current token, missing task evidence, and inline budget. Reread the canonical phase and relevant sibling tasks before proposing work.",
3021
3026
  inputSchema: { phaseRef: z.string().min(1).optional() },
3022
3027
  }, async ({ phaseRef }) => {
3023
3028
  if (!phaseRef)
@@ -3032,9 +3037,10 @@ server.registerTool("planner-handoff-prepare", {
3032
3037
  return text(`❌ ${r.error}`);
3033
3038
  try {
3034
3039
  const audit = await st.preparePhaseHandoff(r.phase.id);
3040
+ const { completenessCategories: _completenessCategories, coldStartSourceReviews: _coldStartSourceReviews, coldStartInventoryCategories: _coldStartInventoryCategories, ...compactAudit } = audit;
3035
3041
  const missing = audit.missingCompletionTasks.map((task) => `- T${String(task.number).padStart(3, "0")} — ${task.title}`).join("\n") || "- None";
3036
3042
  return text([
3037
- `Handoff preparation audit for ${r.compositeRef}`,
3043
+ `Handoff preparation for ${r.compositeRef}`,
3038
3044
  `Base handoffUpdatedAt: ${audit.handoffUpdatedAt || "(empty)"}`,
3039
3045
  "Required human inputs before drafting:",
3040
3046
  ...audit.requiredHumanInputs.map((input) => `- ${input.id} — ${input.description}`),
@@ -3045,18 +3051,18 @@ server.registerTool("planner-handoff-prepare", {
3045
3051
  "Done tasks missing durable completion/verification evidence:",
3046
3052
  missing,
3047
3053
  "",
3048
- `Required completeness audit v${audit.completenessVersion} — every category must be captured or marked not-applicable with a substantive reason:`,
3049
- ...audit.completenessCategories.map((entry) => `- ${entry.id} — ${entry.label}`),
3050
- "",
3051
- `Required cold-start source review v${audit.coldStartInventoryVersion} — inspect and substantively summarize every source before drafting:`,
3052
- ...audit.coldStartSourceReviews.map((entry) => `- ${entry.id} — ${entry.label}`),
3053
- "Required cold-start inventory — every category needs a concrete item (use an explicit verified-negative statement when nothing exists), and every item must appear verbatim in the handoff or a validated supporting document:",
3054
- ...audit.coldStartInventoryCategories.map((entry) => `- ${entry.id} — ${entry.label}`),
3055
- `Maximum canonical inline content: ${audit.maxContentChars} characters. Link extended material from .planner/docs/.`,
3054
+ "Compact handoff contract: include only the exact focus/resume point, current or partial state, constraints, blockers, decisions, verification, and ordered next actions needed by the next agent.",
3055
+ "No Markdown heading is mandatory: concise free-form resume prose is accepted. Use headings only when they make the capsule clearer.",
3056
+ "Completeness audit and cold-start evidence are planner-owned metadata; do not copy their categories or source reviews into Markdown.",
3057
+ `Inline target: ${audit.targetContentChars} characters; absolute compatibility ceiling: ${audit.maxContentChars}. Extended detail is externalized automatically when needed.`,
3056
3058
  "",
3057
3059
  "Existing active handoff (reconcile all still-relevant content):",
3058
3060
  audit.handoff.trim() || "(none)",
3059
- ].join("\n"), { phaseRef: r.compositeRef, ...audit });
3061
+ ].join("\n"), {
3062
+ phaseRef: r.compositeRef,
3063
+ ...compactAudit,
3064
+ evidenceContract: "Planner-owned completeness and cold-start evidence is derived from persisted state; no category inventory is required from the agent.",
3065
+ });
3060
3066
  }
3061
3067
  catch (error) {
3062
3068
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-plan/mcp",
3
- "version": "0.2.27",
3
+ "version": "0.2.28",
4
4
  "private": false,
5
5
  "description": "MCP server for Agent Plan, exposing planner-* tools over stdio.",
6
6
  "license": "MIT",
@@ -43,8 +43,8 @@
43
43
  "dependencies": {
44
44
  "@modelcontextprotocol/sdk": "^1.17.0",
45
45
  "zod": "^3.24.0",
46
- "@agent-plan/core": "0.2.27",
47
- "@agent-plan/server": "0.2.27"
46
+ "@agent-plan/core": "0.2.28",
47
+ "@agent-plan/server": "0.2.28"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsc -p tsconfig.json",