@agent-plan/mcp 0.2.27 → 0.2.29

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":";AA0kGA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
package/dist/index.js CHANGED
@@ -1437,10 +1437,7 @@ server.registerTool("planner-phase-update", {
1437
1437
  const staleParentRefs = descriptionFreshness?.diagnostics
1438
1438
  .filter((entry) => entry.state === "stale" && entry.ownerId === persisted.featureId)
1439
1439
  .map((entry) => entry.ownerRef) ?? [];
1440
- const freshnessNotice = staleParentRefs.length > 0
1441
- ? `\n⚠️ Parent description review required: ${staleParentRefs.join(", ")}. Run planner-description-freshness for the non-mutating reconciliation preview.`
1442
- : "";
1443
- return text(`✅ Phase updated: ${formatPhaseRef(persisted.number, featureNumberOfPhase(persisted, refreshedFeatures))} — ${persisted.title}${persisted.shortId ? ` · ${persisted.shortId}` : ""}. Fields saved: ${receivedFields.join(", ")}.${freshnessNotice}`, { phase: persisted, updated: true, updatedFields: receivedFields, ...(descriptionFreshness ? { descriptionFreshness, staleParentRefs } : {}) });
1440
+ return text(`✅ Phase updated: ${formatPhaseRef(persisted.number, featureNumberOfPhase(persisted, refreshedFeatures))} — ${persisted.title}${persisted.shortId ? ` · ${persisted.shortId}` : ""}. Fields saved: ${receivedFields.join(", ")}.`, { phase: persisted, updated: true, updatedFields: receivedFields, ...(descriptionFreshness ? { descriptionFreshness, staleParentRefs } : {}) });
1444
1441
  });
1445
1442
  server.registerTool("planner-phase-delete", {
1446
1443
  description: "Delete a phase and unlink it from features.",
@@ -1748,60 +1745,62 @@ server.registerTool("planner-task-update", {
1748
1745
  }
1749
1746
  let updatedTask;
1750
1747
  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" });
1748
+ try {
1749
+ await st.updateTask(found.phase.id, found.task.id, (task) => {
1750
+ if (title !== undefined)
1751
+ task.title = title.trim();
1752
+ if (priority !== undefined)
1753
+ task.priority = priority;
1754
+ if (description !== undefined)
1755
+ task.description = description.trim();
1756
+ if (rawDescriptionRef !== undefined) {
1757
+ const descriptionRef = normalizeDescriptionRef(rawDescriptionRef);
1758
+ if (descriptionRef)
1759
+ task.descriptionRef = descriptionRef;
1760
+ else
1761
+ delete task.descriptionRef;
1784
1762
  }
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
- });
1763
+ if (notes !== undefined)
1764
+ task.notes = notes.trim();
1765
+ if (decisions !== undefined)
1766
+ task.decisions = decisions.map((item) => item.trim()).filter(Boolean);
1767
+ if (checklist !== undefined) {
1768
+ task.checklist = checklist.map((item, index) => ({
1769
+ id: createChecklistItemId(task.id, index + 1, item),
1770
+ number: index + 1,
1771
+ title: item.trim(),
1772
+ checked: false,
1773
+ }));
1774
+ }
1775
+ if (subtasks !== undefined) {
1776
+ const existingIds = new Set(task.subtasks.map((item) => item.id));
1777
+ if (subtasks.some((item) => item.id !== undefined && !existingIds.has(item.id))) {
1778
+ throw new PlanStoreError("Subtask IDs must belong to the target task.", undefined, { errorCode: "SUBTASK_ID_INVALID" });
1779
+ }
1780
+ 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 }));
1781
+ }
1782
+ if (status !== undefined && status !== task.status) {
1783
+ // Record status change in the incremental statusLog.
1784
+ const entry = {
1785
+ id: createChecklistItemId(task.id, (task.statusLog?.length ?? 0) + 1, `${task.status}-${status}`),
1786
+ date: timestamp,
1787
+ fromStatus: task.status,
1788
+ toStatus: status,
1789
+ title: motivation?.split("\n")[0]?.trim() || `${task.status} → ${status}`,
1790
+ description: motivation?.trim() || "",
1791
+ };
1792
+ task.statusLog = [...(task.statusLog ?? []), entry];
1793
+ applyTaskLifecycleDates(task, status, timestamp);
1794
+ }
1795
+ task.updatedAt = timestamp;
1796
+ updatedTask = task;
1797
+ return task;
1798
+ });
1799
+ }
1800
+ catch (error) {
1801
+ const details = error instanceof PlanStoreError ? error.details : undefined;
1802
+ return { ...text(`Task update failed: ${error instanceof Error ? error.message : "unknown error"}`, { updated: false, errorCode: details?.errorCode ?? "TASK_UPDATE_FAILED" }), isError: true };
1803
+ }
1805
1804
  await st.syncTaskStatusRollup(found.phase.id);
1806
1805
  const features = (await st.loadFeatures()).features;
1807
1806
  const t = updatedTask ?? found.task;
@@ -1827,13 +1826,10 @@ server.registerTool("planner-task-update", {
1827
1826
  const staleParentRefs = descriptionFreshness?.diagnostics
1828
1827
  .filter((entry) => entry.state === "stale" && ownerIds.has(entry.ownerId))
1829
1828
  .map((entry) => entry.ownerRef) ?? [];
1830
- const freshnessNotice = staleParentRefs.length > 0
1831
- ? `\n⚠️ Parent description review required: ${staleParentRefs.join(", ")}. Run planner-description-freshness for the non-mutating reconciliation preview.`
1832
- : "";
1833
1829
  const details = resumeRequired
1834
1830
  ? { task: t, updated: true, updatedFields: receivedFields, resumeRequired, ...(descriptionFreshness ? { descriptionFreshness, staleParentRefs } : {}) }
1835
1831
  : { task: t, updated: true, updatedFields: receivedFields, ...(descriptionFreshness ? { descriptionFreshness, staleParentRefs } : {}) };
1836
- return writeAndSummarize(st, `✅ Task updated: ${taskCompositeRef(t, found.phase, features)} — ${t.title} (${t.status})${t.shortId ? ` · ${t.shortId}` : ""}. Fields saved: ${receivedFields.join(", ")}.${freshnessNotice}${resumeNotice}`, details);
1832
+ return writeAndSummarize(st, `✅ Task updated: ${taskCompositeRef(t, found.phase, features)} — ${t.title} (${t.status})${t.shortId ? ` · ${t.shortId}` : ""}. Fields saved: ${receivedFields.join(", ")}.${resumeNotice}`, details);
1837
1833
  });
1838
1834
  server.registerTool("planner-task-checklist-toggle", {
1839
1835
  description: "Tick/untick a task checklist item (a 'step') without rewriting the whole list. Accepts the item as C{n} (e.g. C2), the item id, or the title (case-insensitive, first match). Pass checked=true/false to set explicitly, or omit to toggle. Use this instead of writing DONE in step titles. Items are numbered C1/C2… per task.",
@@ -2564,8 +2560,8 @@ server.registerTool("planner-handoff-show", {
2564
2560
  && phase.handoffAudit.contentLength === phase.handoff.length);
2565
2561
  const resumeReady = persistenceVerified && Boolean(phase.handoffAudit?.resumeReadyAt);
2566
2562
  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.";
2563
+ ? "Resume-ready: persisted compact capsule read-back completed."
2564
+ : "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
2565
  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
2566
  phaseRef: r.compositeRef,
2571
2567
  phaseId: r.phase.id,
@@ -2579,15 +2575,15 @@ server.registerTool("planner-handoff-show", {
2579
2575
  });
2580
2576
  });
2581
2577
  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.",
2578
+ 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
2579
  inputSchema: {
2584
2580
  phaseRef: z.string().min(1),
2585
2581
  expectedContentHash: z.string().regex(/^[a-f0-9]{64}$/),
2586
2582
  sourceReviews: z.array(z.object({
2587
2583
  source: z.enum(["conversation", "planner-entities", "working-tree", "verification-runtime", "peer-agent-output"]),
2588
2584
  detail: z.string().min(12),
2589
- })),
2590
- omissionsFound: z.array(z.string().min(1)),
2585
+ })).optional().describe("Optional legacy evidence; omitted for compact handoffs."),
2586
+ omissionsFound: z.array(z.string().min(1)).optional().describe("Optional read-back gaps; omit when the persisted capsule is complete."),
2591
2587
  },
2592
2588
  }, async ({ phaseRef, expectedContentHash, sourceReviews, omissionsFound }) => {
2593
2589
  const st = await requireStore();
@@ -2595,7 +2591,11 @@ server.registerTool("planner-handoff-verify", {
2595
2591
  if (!r.ok)
2596
2592
  return text(`❌ ${r.error}`, { resumeReady: false });
2597
2593
  try {
2598
- const result = await st.verifyPhaseHandoffReadBack(r.phase.id, { expectedContentHash, sourceReviews, omissionsFound });
2594
+ const result = await st.verifyPhaseHandoffReadBack(r.phase.id, {
2595
+ expectedContentHash,
2596
+ ...(sourceReviews ? { sourceReviews } : {}),
2597
+ ...(omissionsFound ? { omissionsFound } : {}),
2598
+ });
2599
2599
  await st.writeGenerated();
2600
2600
  return text(`✅ Handoff on ${r.compositeRef} is resume-ready after persisted read-back and source reconciliation.`, {
2601
2601
  phaseRef: r.compositeRef,
@@ -2881,7 +2881,7 @@ server.registerTool("planner-idea-promotion-finalize", {
2881
2881
  return text(`Idea promoted: ${formatIdeaRef(promoted.number)} → ${promoted.promotion.targetRef}`, { idea: promoted, promoted: true, targetRef: promoted.promotion.targetRef });
2882
2882
  });
2883
2883
  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.",
2884
+ 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
2885
  inputSchema: {
2886
2886
  phaseRef: z.string().min(1).describe("Exact confirmed phase ref: P00x | P00x(F00x) | UUID | title."),
2887
2887
  title: z.string().min(3).optional().describe("Meaningful handoff title summarizing the work."),
@@ -2897,7 +2897,7 @@ server.registerTool("planner-handoff-write", {
2897
2897
  status: z.enum(["captured", "not-applicable"]),
2898
2898
  detail: z.string().min(1),
2899
2899
  })),
2900
- }).optional().describe("Mandatory for confirmed writes; every category from planner-handoff-prepare must be present."),
2900
+ }).optional().describe("Optional legacy completeness evidence. Compact handoffs omit this duplicate prose."),
2901
2901
  coldStartInventory: z.object({
2902
2902
  version: z.literal(HANDOFF_COLD_START_INVENTORY_VERSION),
2903
2903
  sourceReviews: z.array(z.object({ source: z.string().min(1), detail: z.string().min(1) })),
@@ -2906,7 +2906,7 @@ server.registerTool("planner-handoff-write", {
2906
2906
  items: z.array(z.string().min(1)),
2907
2907
  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
2908
  })),
2909
- }).optional().describe("Mandatory for confirmed writes. Every concrete item must appear verbatim in the handoff or a validated supporting document."),
2909
+ }).optional().describe("Optional legacy cold-start evidence. Compact handoffs omit this duplicate prose."),
2910
2910
  supportingDocuments: z.array(z.object({
2911
2911
  path: z.string().min(1).describe("Committed Markdown path under .planner/docs/."),
2912
2912
  description: z.string().min(1).describe("What the document contains and why the next agent needs it."),
@@ -2993,7 +2993,7 @@ server.registerTool("planner-handoff-write", {
2993
2993
  },
2994
2994
  });
2995
2995
  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.`, {
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 persisted compact capsule, then call planner-handoff-verify with the returned contentHash. Rewrite only if resume-critical context is missing.`, {
2997
2997
  phaseRef: r.compositeRef,
2998
2998
  phaseId: r.phase.id,
2999
2999
  updatedTaskIds: result.updatedTaskIds,
@@ -3004,8 +3004,7 @@ server.registerTool("planner-handoff-write", {
3004
3004
  verificationRequired: true,
3005
3005
  nextActions: [
3006
3006
  `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.",
3007
+ "Call planner-handoff-verify with the shown contentHash; legacy sourceReviews and omissionsFound are optional because the planner derives omitted evidence from persisted state.",
3009
3008
  ],
3010
3009
  });
3011
3010
  }
@@ -3017,7 +3016,7 @@ server.registerTool("planner-handoff-write", {
3017
3016
  }
3018
3017
  });
3019
3018
  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.",
3019
+ 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
3020
  inputSchema: { phaseRef: z.string().min(1).optional() },
3022
3021
  }, async ({ phaseRef }) => {
3023
3022
  if (!phaseRef)
@@ -3032,9 +3031,10 @@ server.registerTool("planner-handoff-prepare", {
3032
3031
  return text(`❌ ${r.error}`);
3033
3032
  try {
3034
3033
  const audit = await st.preparePhaseHandoff(r.phase.id);
3034
+ const { completenessCategories: _completenessCategories, coldStartSourceReviews: _coldStartSourceReviews, coldStartInventoryCategories: _coldStartInventoryCategories, ...compactAudit } = audit;
3035
3035
  const missing = audit.missingCompletionTasks.map((task) => `- T${String(task.number).padStart(3, "0")} — ${task.title}`).join("\n") || "- None";
3036
3036
  return text([
3037
- `Handoff preparation audit for ${r.compositeRef}`,
3037
+ `Handoff preparation for ${r.compositeRef}`,
3038
3038
  `Base handoffUpdatedAt: ${audit.handoffUpdatedAt || "(empty)"}`,
3039
3039
  "Required human inputs before drafting:",
3040
3040
  ...audit.requiredHumanInputs.map((input) => `- ${input.id} — ${input.description}`),
@@ -3045,18 +3045,18 @@ server.registerTool("planner-handoff-prepare", {
3045
3045
  "Done tasks missing durable completion/verification evidence:",
3046
3046
  missing,
3047
3047
  "",
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/.`,
3048
+ "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.",
3049
+ "No Markdown heading is mandatory: concise free-form resume prose is accepted. Use headings only when they make the capsule clearer.",
3050
+ "Completeness audit and cold-start evidence are planner-owned metadata; do not copy their categories or source reviews into Markdown.",
3051
+ `Inline target: ${audit.targetContentChars} characters; absolute compatibility ceiling: ${audit.maxContentChars}. Extended detail is externalized automatically when needed.`,
3056
3052
  "",
3057
3053
  "Existing active handoff (reconcile all still-relevant content):",
3058
3054
  audit.handoff.trim() || "(none)",
3059
- ].join("\n"), { phaseRef: r.compositeRef, ...audit });
3055
+ ].join("\n"), {
3056
+ phaseRef: r.compositeRef,
3057
+ ...compactAudit,
3058
+ evidenceContract: "Planner-owned completeness and cold-start evidence is derived from persisted state; no category inventory is required from the agent.",
3059
+ });
3060
3060
  }
3061
3061
  catch (error) {
3062
3062
  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.29",
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.29",
47
+ "@agent-plan/server": "0.2.29"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsc -p tsconfig.json",