@agent-plan/mcp 0.2.22-next.2 → 0.2.22-next.4

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":";AAmjDA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAwjDA,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, findTaskByRef, buildRecap, addChecklistItem, removeChecklistItem, toggleChecklistItem, buildPhaseContextBlock, checkExplicitTaskStart, recommendNextTask, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, markFeatureRead, markPhaseRead, markTaskRead, parentReadAdvisory, requirementReadAdvisory, markRequirementRead, invalidateReads } from "@agent-plan/core";
7
+ import { PlanStore, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap, addChecklistItem, removeChecklistItem, toggleChecklistItem, buildPhaseContextBlock, checkExplicitTaskStart, recommendNextTask, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, markFeatureRead, markPhaseRead, markTaskRead, contextReadEligibility, hasReadRequirements, markRequirementRead, invalidateReads } from "@agent-plan/core";
8
8
  import { serve } from "@agent-plan/server";
9
9
  import { createChecklistItemId, createFeatureId, createPhaseId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef, validateResolvedTarget } from "@agent-plan/core/naming";
10
10
  const STATUS_VALUES = ["planned", "in-progress", "done", "blocked", "canceled", "rejected", "deferred", "waiting"];
@@ -450,7 +450,8 @@ server.registerTool("planner-feature-show", {
450
450
  if (!resolvedFeature.ok)
451
451
  return text(resolvedFeature.error);
452
452
  const feature = resolvedFeature.feature;
453
- markFeatureRead(feature.id);
453
+ if (full)
454
+ markFeatureRead(feature.id);
454
455
  const phases = (await st.loadAllPhases()).filter((phase) => phase.featureId === feature.id);
455
456
  const summary = `${feature.name} — ${formatFeatureRef(feature.number)}${feature.shortId ? ` · ${feature.shortId}` : ""} (${feature.status}; ${phases.length} phases)`;
456
457
  return text(full ? `${summary}\n\n${feature.description || ""}` : summary);
@@ -658,7 +659,8 @@ server.registerTool("planner-phase-show", {
658
659
  const phase = findPhaseByRef(await st.loadAllPhases(), features, ref);
659
660
  if (!phase)
660
661
  return text(`Phase not found: ${ref}`);
661
- markPhaseRead(phase.id, phase.featureId);
662
+ if (full)
663
+ markPhaseRead(phase.id, phase.featureId);
662
664
  const linkedRequirements = await st.linkedRequirementsForPhase(phase.id);
663
665
  const reqCount = linkedRequirements.length;
664
666
  const summary = `${phase.title} — ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, features))}${phase.shortId ? ` · ${phase.shortId}` : ""} (${phase.status}; ${phase.tasks.length} tasks${reqCount ? `; ${reqCount} linked requirement${reqCount === 1 ? "" : "s"}` : ""})`;
@@ -849,7 +851,8 @@ server.registerTool("planner-task-show", {
849
851
  const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
850
852
  if (!found)
851
853
  return text(`Task not found: ${ref}`);
852
- markTaskRead(found.task.id, found.phase.id, found.phase.featureId);
854
+ if (full)
855
+ markTaskRead(found.task.id, found.phase.id, found.phase.featureId);
853
856
  const features = (await st.loadFeatures()).features;
854
857
  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))})`;
855
858
  if (!full)
@@ -929,8 +932,8 @@ server.registerTool("planner-task-update", {
929
932
  const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
930
933
  if (!found)
931
934
  return text(`Task not found: ${ref}`);
932
- if (found.task.pauseSnapshot && status === "in-progress" && status !== found.task.status) {
933
- return text("Resume checkpoint lifecycle transitions require planner-task-start so the checkpoint and return stack remain consistent.");
935
+ if (status === "in-progress" && status !== found.task.status) {
936
+ return text("Task start/resume transitions require planner-task-start so lifecycle state, checkpoints, and return stacks remain consistent.");
934
937
  }
935
938
  // Validate motivation requirement for status transitions.
936
939
  if (status !== undefined && needsMotivation(found.task.status, status)) {
@@ -1109,12 +1112,12 @@ server.registerTool("planner-task-delete", {
1109
1112
  return writeAndSummarize(st, `Task deleted: ${taskCompositeRef(found.task, found.phase, tdFeatures)}${found.task.shortId ? ` · ${found.task.shortId}` : ""}`, { deleted: found.task.id });
1110
1113
  });
1111
1114
  server.registerTool("planner-task-recommend", {
1112
- description: "Return the harness-agnostic recommended task: continue one active task, otherwise choose ready work by feature → phase → task priority. Reports dependency, availability, and approved deviation/resume context without starting or blocking work.",
1115
+ description: "Return the harness-agnostic recommended task: continue one active task or the current phase, otherwise choose ready work by feature → phase → task priority. Reports dependency, availability, and approved deviation/resume context without starting or blocking work.",
1113
1116
  inputSchema: {},
1114
1117
  }, async () => {
1115
1118
  const st = await requireStore();
1116
- const [features, phases, project] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject()]);
1117
- const result = recommendNextTask(features.features, phases, project.workDeviations);
1119
+ const [features, phases, project, resume] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject(), st.loadResume()]);
1120
+ const result = recommendNextTask(features.features, phases, project.workDeviations, resume?.currentPhaseId);
1118
1121
  if (!result.candidate)
1119
1122
  return text(`No task recommendation: ${result.reason}`, { kind: result.kind, reason: result.reason, activeTaskIds: result.activeCandidates?.map((candidate) => candidate.task.id) ?? [] });
1120
1123
  const { candidate } = result;
@@ -1128,11 +1131,11 @@ server.registerTool("planner-task-deviation", {
1128
1131
  inputSchema: { temporary_task: z.string().min(1), resume_task: z.string().min(1).optional(), reason: z.string().min(1) },
1129
1132
  }, async ({ temporary_task, resume_task, reason }) => {
1130
1133
  const st = await requireStore();
1131
- const [features, phases, project] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject()]);
1134
+ const [features, phases, project, focus] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject(), st.loadResume()]);
1132
1135
  const temporary = findTaskByRef(phases, features.features, temporary_task);
1133
1136
  if (!temporary)
1134
1137
  return text(`Task not found: ${temporary_task}`);
1135
- const selected = recommendNextTask(features.features, phases, project.workDeviations);
1138
+ const selected = recommendNextTask(features.features, phases, project.workDeviations, focus?.currentPhaseId);
1136
1139
  const resume = resume_task ? findTaskByRef(phases, features.features, resume_task) : selected.candidate;
1137
1140
  if (!resume)
1138
1141
  return text(`No resume task is available. Provide resume_task explicitly. ${selected.reason}`);
@@ -1209,7 +1212,7 @@ server.registerTool("planner-task-switch", {
1209
1212
  },
1210
1213
  }, async ({ from_task, to_task, reason, what_was_being_done, resume_location, how_to_resume, switched_by }) => {
1211
1214
  const st = await requireStore();
1212
- const [featuresDoc, phases, project] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject()]);
1215
+ const [featuresDoc, phases, project, focus] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject(), st.loadResume()]);
1213
1216
  const features = featuresDoc.features;
1214
1217
  const source = findTaskByRef(phases, features, from_task);
1215
1218
  const target = findTaskByRef(phases, features, to_task);
@@ -1222,6 +1225,16 @@ server.registerTool("planner-task-switch", {
1222
1225
  if (source.task.status !== "in-progress" && !source.task.pauseSnapshot) {
1223
1226
  return text(`Task switch denied: source is ${source.task.status}; only in-progress or checkpointed work can be switched.`);
1224
1227
  }
1228
+ const contextEligibility = contextReadEligibility(target.task.id, target.phase.id, target.phase.featureId);
1229
+ if (!contextEligibility.eligible)
1230
+ return text(`Task switch denied: context reads required. ${contextEligibility.reason}`, contextEligibility);
1231
+ const linkedRequirementIds = [
1232
+ ...(await st.linkedRequirementsForPhase(target.phase.id)).map((requirement) => requirement.id),
1233
+ ...(target.phase.featureId ? (await st.linkedRequirementsForFeature(target.phase.featureId)).map((requirement) => requirement.id) : []),
1234
+ ];
1235
+ if (!hasReadRequirements(linkedRequirementIds)) {
1236
+ return text("Task switch denied: read the requirements linked to the target phase and feature before switching.", { requirementIds: linkedRequirementIds });
1237
+ }
1225
1238
  const eligibility = checkExplicitTaskStart(features, phases, target.task.id, project.workDeviations);
1226
1239
  if (!eligibility.eligible)
1227
1240
  return text(`Task switch denied: ${eligibility.reason}`);
@@ -1231,7 +1244,7 @@ server.registerTool("planner-task-switch", {
1231
1244
  resumeLocation: resume_location, howToResume: how_to_resume,
1232
1245
  relatedTaskId: target.task.id, pausedAt: timestamp, pausedBy: switched_by?.trim() ?? "",
1233
1246
  };
1234
- const selection = recommendNextTask(features, phases, project.workDeviations);
1247
+ const selection = recommendNextTask(features, phases, project.workDeviations, focus?.currentPhaseId);
1235
1248
  const record = {
1236
1249
  id: crypto.randomUUID(), recommendedTaskId: selection.candidate?.task.id ?? source.task.id,
1237
1250
  temporaryTaskId: target.task.id, resumeTaskId: source.task.id, reason, snapshot,
@@ -1310,22 +1323,16 @@ server.registerTool("planner-task-switch", {
1310
1323
  await st.syncTaskStatusRollup(source.phase.id);
1311
1324
  if (target.phase.id !== source.phase.id)
1312
1325
  await st.syncTaskStatusRollup(target.phase.id);
1313
- const targetReadAdvisory = parentReadAdvisory(target.phase.featureId, target.phase.id);
1314
- const targetRequirementAdvisory = requirementReadAdvisory([
1315
- ...(await st.linkedRequirementsForPhase(target.phase.id)).map((r) => r.id),
1316
- ...(target.phase.featureId ? (await st.linkedRequirementsForFeature(target.phase.featureId)).map((r) => r.id) : []),
1317
- ]);
1326
+ invalidateReads();
1318
1327
  return writeAndSummarize(st, [
1319
1328
  `🔀 Task switched: ${taskCompositeRef(source.task, source.phase, features)} → ${taskCompositeRef(target.task, target.phase, features)}`,
1320
1329
  `Resume checkpoint: ${snapshot.whatWasBeingDone}`,
1321
1330
  `Return target: ${taskCompositeRef(source.task, source.phase, features)} from ${snapshot.resumeLocation}`,
1322
1331
  `Normal priority was deliberately overridden; completing the temporary task will emit RESUME REQUIRED.`,
1323
- ...(targetReadAdvisory ? [targetReadAdvisory.trimStart()] : []),
1324
- ...(targetRequirementAdvisory ? [targetRequirementAdvisory.trimStart()] : []),
1325
- ].join("\n"), { deviation: record, snapshot, resumeTaskId: source.task.id, temporaryTaskId: target.task.id, ...(targetReadAdvisory || targetRequirementAdvisory ? { readRequired: true } : {}) });
1332
+ ].join("\n"), { deviation: record, snapshot, resumeTaskId: source.task.id, temporaryTaskId: target.task.id });
1326
1333
  });
1327
1334
  server.registerTool("planner-task-start", {
1328
- description: "Set a task to in-progress or resume checkpointed work. BEFORE calling, use planner-task-show with full=true and planner-phase-show with full=true for the parent phase, including linked requirements. A different active task must be suspended through planner-task-switch first. Otherwise follow active/pending-resume/feature → phase → task priority guidance.",
1335
+ description: "Set a task to in-progress or resume checkpointed work. BEFORE calling, use planner-task-show(full=true), then planner-phase-show(full=true), then planner-feature-show(full=true) for the same lineage, in that exact order. Read linked requirements with planner-requirement-list when present. A different active task must first be suspended through planner-task-switch.",
1329
1336
  inputSchema: { task: z.string().min(1) },
1330
1337
  }, async ({ task: ref }) => {
1331
1338
  const st = await requireStore();
@@ -1337,12 +1344,21 @@ server.registerTool("planner-task-start", {
1337
1344
  return text(`Task ${found.task.id} is already in-progress.`);
1338
1345
  if (found.task.status === "done")
1339
1346
  return text(`Task ${found.task.id} is done. Use planner-task-update to reopen.`);
1340
- const project = await st.loadProject();
1341
- const phases = await st.loadAllPhases();
1347
+ const contextEligibility = contextReadEligibility(found.task.id, found.phase.id, found.phase.featureId);
1348
+ if (!contextEligibility.eligible)
1349
+ return text(`Task start denied: context reads required. ${contextEligibility.reason}`, contextEligibility);
1350
+ const linkedRequirementIds = [
1351
+ ...(await st.linkedRequirementsForPhase(found.phase.id)).map((requirement) => requirement.id),
1352
+ ...(found.phase.featureId ? (await st.linkedRequirementsForFeature(found.phase.featureId)).map((requirement) => requirement.id) : []),
1353
+ ];
1354
+ if (!hasReadRequirements(linkedRequirementIds)) {
1355
+ return text("Task start denied: read the requirements linked to the task's phase and feature first.", { requirementIds: linkedRequirementIds });
1356
+ }
1357
+ const [project, phases, focus] = await Promise.all([st.loadProject(), st.loadAllPhases(), st.loadResume()]);
1342
1358
  const eligibility = checkExplicitTaskStart(features, phases, found.task.id, project.workDeviations);
1343
1359
  if (!eligibility.eligible)
1344
1360
  return text(`Task start denied: ${eligibility.reason}`, { reason: eligibility.reason });
1345
- const selection = recommendNextTask(features, phases, project.workDeviations);
1361
+ const selection = recommendNextTask(features, phases, project.workDeviations, focus?.currentPhaseId);
1346
1362
  if (selection.kind === "conflict") {
1347
1363
  return text(`${selection.reason} Pause/reconcile active tasks before starting another task.`, {
1348
1364
  activeTaskIds: selection.activeCandidates?.map((candidate) => candidate.task.id) ?? [],
@@ -1429,14 +1445,8 @@ server.registerTool("planner-task-start", {
1429
1445
  if (resumedDeviation)
1430
1446
  await st.setWorkDeviationState(resumedDeviation.id, "resumed", timestamp);
1431
1447
  const t = updatedTask ?? found.task;
1432
- const readAdvisory = parentReadAdvisory(parentFeature?.id, found.phase.id);
1433
- const requirementAdvisory = requirementReadAdvisory([
1434
- ...(phaseWithRequirements.linkedRequirements ?? []).map((r) => r.id),
1435
- ...featureRequirements.map((r) => r.id),
1436
- ]);
1437
- return writeAndSummarize(st, `✅ Task started: ${taskCompositeRef(t, found.phase, features)} — ${t.title} (in-progress)${t.shortId ? ` · ${t.shortId}` : ""}${phaseContext}${advisory}${readAdvisory}${requirementAdvisory}`, {
1448
+ return writeAndSummarize(st, `✅ Task started: ${taskCompositeRef(t, found.phase, features)} — ${t.title} (in-progress)${t.shortId ? ` · ${t.shortId}` : ""}${phaseContext}${advisory}`, {
1438
1449
  ...(resumeProposal ? { resumeRequired: resumeProposal.structured } : {}),
1439
- ...(readAdvisory || requirementAdvisory ? { readRequired: true } : {}),
1440
1450
  });
1441
1451
  });
1442
1452
  server.registerTool("planner-task-complete", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-plan/mcp",
3
- "version": "0.2.22-next.2",
3
+ "version": "0.2.22-next.4",
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.22-next.2",
47
- "@agent-plan/server": "0.2.22-next.2"
46
+ "@agent-plan/core": "0.2.22-next.4",
47
+ "@agent-plan/server": "0.2.22-next.4"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsc -p tsconfig.json",