@agent-plan/mcp 0.2.19-next.1 → 0.2.19-next.10
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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +128 -82
- package/package.json +3 -3
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
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,9 +4,9 @@ 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
|
-
import { createChecklistItemId, createFeatureId, createPhaseId, createShortId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef } from "@agent-plan/core/naming";
|
|
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"];
|
|
11
11
|
const PHASE_STATUS_VALUES = ["draft", "discovery", ...STATUS_VALUES];
|
|
12
12
|
function text(textValue, structuredContent) {
|
|
@@ -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
|
}
|
|
@@ -189,7 +162,7 @@ server.registerTool("planner-init", {
|
|
|
189
162
|
if (goal !== undefined)
|
|
190
163
|
project.goal = goal.trim();
|
|
191
164
|
await st.saveProject(project);
|
|
192
|
-
return writeAndSummarize(st, `.planner/ initialized for "${project.name}"
|
|
165
|
+
return writeAndSummarize(st, `.planner/ initialized for "${project.name}"`);
|
|
193
166
|
});
|
|
194
167
|
server.registerTool("planner-show", {
|
|
195
168
|
description: "Show the current planner overview.",
|
|
@@ -207,11 +180,11 @@ server.registerTool("planner-show", {
|
|
|
207
180
|
].join("\n"), { plan });
|
|
208
181
|
});
|
|
209
182
|
server.registerTool("planner-repair", {
|
|
210
|
-
description: "Repair dangling feature→phase references and report integrity.",
|
|
183
|
+
description: "Repair dangling feature→phase references, rebuild phase containment from each task's own phaseId (heals the migrateToGlobalSequence task-shuffle bug where tasks land in the wrong phase file), and report integrity.",
|
|
211
184
|
}, async () => {
|
|
212
185
|
const st = await requireStore();
|
|
213
186
|
const report = await st.repair();
|
|
214
|
-
return text(`Repair done: renamed ${report.migrated.renamed}, repaired ${report.migrated.repaired} refs, inferred ${report.migrated.inferred}. Integrity: ${report.integrity.duplicatePhaseIds.length} duplicate, ${report.integrity.danglingPhaseIds.length} dangling.`, { report });
|
|
187
|
+
return text(`Repair done: renamed ${report.migrated.renamed}, repaired ${report.migrated.repaired} refs, inferred ${report.migrated.inferred}. Containment: ${report.containment.changed} phase files rewritten (${report.containment.tasks} tasks scanned, ${report.containment.orphan} orphan). Integrity: ${report.integrity.duplicatePhaseIds.length} duplicate, ${report.integrity.danglingPhaseIds.length} dangling.`, { report });
|
|
215
188
|
});
|
|
216
189
|
server.registerTool("planner-project-language", {
|
|
217
190
|
description: "Persist preferred languages for plan content and chat.",
|
|
@@ -227,7 +200,7 @@ server.registerTool("planner-project-language", {
|
|
|
227
200
|
if (chatLanguage !== undefined)
|
|
228
201
|
project.chatLanguage = chatLanguage.trim();
|
|
229
202
|
await st.saveProject(project);
|
|
230
|
-
return writeAndSummarize(st, `Saved language preferences: content=${project.contentLanguage || "(unset)"}, chat=${project.chatLanguage || "(unset)"}
|
|
203
|
+
return writeAndSummarize(st, `Saved language preferences: content=${project.contentLanguage || "(unset)"}, chat=${project.chatLanguage || "(unset)"}`);
|
|
231
204
|
});
|
|
232
205
|
server.registerTool("planner-project-discuss", {
|
|
233
206
|
description: "Record/update project-level discovery fields. This is the MCP equivalent of grouped project discuss without Pi UI prompts.",
|
|
@@ -258,20 +231,73 @@ server.registerTool("planner-project-discuss", {
|
|
|
258
231
|
if (params.decisions !== undefined)
|
|
259
232
|
project.decisions = params.decisions.map((entry) => entry.trim()).filter(Boolean);
|
|
260
233
|
await st.saveProject(project);
|
|
261
|
-
return writeAndSummarize(st, `Project discussed/updated: ${project.name}
|
|
234
|
+
return writeAndSummarize(st, `Project discussed/updated: ${project.name}`);
|
|
262
235
|
});
|
|
263
236
|
server.registerTool("planner-feature-list", {
|
|
264
|
-
description: "List features
|
|
265
|
-
|
|
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
|
|
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.
|
|
247
|
+
return `- ${formatFeatureRef(feature.number)}${feature.shortId ? ` · ${feature.shortId}` : ""} — ${feature.name} (${feature.status}; ${featurePhases.length} phases, ${taskCount} tasks)`;
|
|
273
248
|
});
|
|
274
|
-
return text(lines.join("\n") || "No features"
|
|
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}`;
|
|
270
|
+
});
|
|
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:
|
|
318
|
+
number: await st.allocFeatureNumber(),
|
|
293
319
|
shortId,
|
|
294
320
|
priority,
|
|
295
321
|
name: name.trim(),
|
|
@@ -312,24 +338,25 @@ server.registerTool("planner-feature-add", {
|
|
|
312
338
|
doc.features.push(feature);
|
|
313
339
|
return doc;
|
|
314
340
|
});
|
|
315
|
-
return writeAndSummarize(st,
|
|
341
|
+
return writeAndSummarize(st, `✅ Feature created: ${formatFeatureRef(feature.number)} — ${feature.name}${feature.shortId ? ` · ${feature.shortId}` : ""}`);
|
|
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
|
|
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
|
-
|
|
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
|
|
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(),
|
|
@@ -369,12 +396,12 @@ server.registerTool("planner-feature-update", {
|
|
|
369
396
|
return doc;
|
|
370
397
|
});
|
|
371
398
|
const result = updated.features.find((entry) => entry.id === feature.id);
|
|
372
|
-
return writeAndSummarize(st,
|
|
399
|
+
return writeAndSummarize(st, `✅ Feature updated: ${formatFeatureRef(result.number)} — ${result.name}${result.shortId ? ` · ${result.shortId}` : ""}`);
|
|
373
400
|
});
|
|
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
|
|
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
|
|
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:
|
|
454
|
+
number: await st.allocPhaseNumber(),
|
|
428
455
|
shortId,
|
|
429
456
|
priority,
|
|
430
457
|
slug: normalizeSlug(title),
|
|
@@ -452,6 +479,8 @@ server.registerTool("planner-phase-add", {
|
|
|
452
479
|
updatedAt: timestamp,
|
|
453
480
|
handoff: "",
|
|
454
481
|
handoffUpdatedAt: "",
|
|
482
|
+
handoffReadAt: "",
|
|
483
|
+
handoffHistory: [],
|
|
455
484
|
};
|
|
456
485
|
await st.savePhase(phase);
|
|
457
486
|
if (feature) {
|
|
@@ -466,22 +495,24 @@ server.registerTool("planner-phase-add", {
|
|
|
466
495
|
});
|
|
467
496
|
if (!phase)
|
|
468
497
|
return text("Phase creation failed.");
|
|
469
|
-
return writeAndSummarize(st,
|
|
498
|
+
return writeAndSummarize(st, `✅ Phase created: ${formatPhaseRef(phase.number, feature?.number)} — ${phase.title}${phase.shortId ? ` · ${phase.shortId}` : ""}`);
|
|
470
499
|
});
|
|
471
500
|
server.registerTool("planner-phase-show", {
|
|
472
501
|
description: "Show a phase by id or name.",
|
|
473
|
-
inputSchema: { phase: z.string().min(1).describe("Phase
|
|
474
|
-
}, 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 }) => {
|
|
475
504
|
const st = await requireStore();
|
|
476
|
-
const
|
|
505
|
+
const features = (await st.loadFeatures()).features;
|
|
506
|
+
const phase = findPhaseByRef(await st.loadAllPhases(), features, ref);
|
|
477
507
|
if (!phase)
|
|
478
508
|
return text(`Phase not found: ${ref}`);
|
|
479
|
-
|
|
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);
|
|
480
511
|
});
|
|
481
512
|
server.registerTool("planner-phase-discuss", {
|
|
482
513
|
description: "Persist phase discovery fields and mark phase planned.",
|
|
483
514
|
inputSchema: {
|
|
484
|
-
phase: z.string().min(1).describe("Phase
|
|
515
|
+
phase: z.string().min(1).describe("Phase ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."),
|
|
485
516
|
goal: z.string().optional(),
|
|
486
517
|
summary: z.string().optional(),
|
|
487
518
|
scope: z.string().optional(),
|
|
@@ -492,7 +523,8 @@ server.registerTool("planner-phase-discuss", {
|
|
|
492
523
|
},
|
|
493
524
|
}, async ({ phase: ref, ...updates }) => {
|
|
494
525
|
const st = await requireStore();
|
|
495
|
-
const
|
|
526
|
+
const features = (await st.loadFeatures()).features;
|
|
527
|
+
const found = findPhaseByRef(await st.loadAllPhases(), features, ref);
|
|
496
528
|
if (!found)
|
|
497
529
|
return text(`Phase not found: ${ref}`);
|
|
498
530
|
const phase = await st.updatePhase(found.id, (entry) => {
|
|
@@ -517,7 +549,7 @@ server.registerTool("planner-phase-discuss", {
|
|
|
517
549
|
entry.updatedAt = nowISO();
|
|
518
550
|
return entry;
|
|
519
551
|
});
|
|
520
|
-
return writeAndSummarize(st,
|
|
552
|
+
return writeAndSummarize(st, `✅ Phase discussed/planned: ${formatPhaseRef(found.number, featureNumberOfPhase(found, features))} — ${phase.title}${phase.shortId ? ` · ${phase.shortId}` : ""}`);
|
|
521
553
|
});
|
|
522
554
|
server.registerTool("planner-phase-update", {
|
|
523
555
|
description: "Update phase fields.",
|
|
@@ -531,7 +563,8 @@ server.registerTool("planner-phase-update", {
|
|
|
531
563
|
},
|
|
532
564
|
}, async ({ phase: ref, ...updates }) => {
|
|
533
565
|
const st = await requireStore();
|
|
534
|
-
const
|
|
566
|
+
const features = (await st.loadFeatures()).features;
|
|
567
|
+
const found = findPhaseByRef(await st.loadAllPhases(), features, ref);
|
|
535
568
|
if (!found)
|
|
536
569
|
return text(`Phase not found: ${ref}`);
|
|
537
570
|
const phase = await st.updatePhase(found.id, (entry) => {
|
|
@@ -548,7 +581,7 @@ server.registerTool("planner-phase-update", {
|
|
|
548
581
|
entry.updatedAt = nowISO();
|
|
549
582
|
return entry;
|
|
550
583
|
});
|
|
551
|
-
return writeAndSummarize(st,
|
|
584
|
+
return writeAndSummarize(st, `✅ Phase updated: ${formatPhaseRef(found.number, featureNumberOfPhase(found, features))} — ${phase.title}${phase.shortId ? ` · ${phase.shortId}` : ""}`);
|
|
552
585
|
});
|
|
553
586
|
server.registerTool("planner-phase-delete", {
|
|
554
587
|
description: "Delete a phase and unlink it from features.",
|
|
@@ -564,12 +597,13 @@ server.registerTool("planner-phase-delete", {
|
|
|
564
597
|
feature.phaseIds = feature.phaseIds.filter((id) => id !== phase.id);
|
|
565
598
|
return doc;
|
|
566
599
|
});
|
|
567
|
-
|
|
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 });
|
|
568
602
|
});
|
|
569
603
|
server.registerTool("planner-task-add", {
|
|
570
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.",
|
|
571
605
|
inputSchema: {
|
|
572
|
-
phase: z.string().min(1).describe("Phase
|
|
606
|
+
phase: z.string().min(1).describe("Phase ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."),
|
|
573
607
|
title: z.string().min(1),
|
|
574
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."),
|
|
575
609
|
checklist: z.array(z.string()).optional(),
|
|
@@ -586,7 +620,7 @@ server.registerTool("planner-task-add", {
|
|
|
586
620
|
const task = {
|
|
587
621
|
id: taskId,
|
|
588
622
|
phaseId: found.id,
|
|
589
|
-
number:
|
|
623
|
+
number: await st.allocTaskNumber(),
|
|
590
624
|
shortId,
|
|
591
625
|
priority,
|
|
592
626
|
shortName: clampSlug(title, 30, `task-${Date.now().toString(36)}`),
|
|
@@ -616,13 +650,18 @@ server.registerTool("planner-task-add", {
|
|
|
616
650
|
});
|
|
617
651
|
server.registerTool("planner-task-show", {
|
|
618
652
|
description: "Show a task by id or name.",
|
|
619
|
-
inputSchema: { task: z.string().min(1).describe("Task
|
|
620
|
-
}, 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 }) => {
|
|
621
655
|
const st = await requireStore();
|
|
622
|
-
const found = findTaskByRef(await st.loadAllPhases(), ref);
|
|
656
|
+
const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
|
|
623
657
|
if (!found)
|
|
624
658
|
return text(`Task not found: ${ref}`);
|
|
625
|
-
|
|
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}` : ""}`);
|
|
626
665
|
});
|
|
627
666
|
server.registerTool("planner-task-discuss", {
|
|
628
667
|
description: "Persist task execution notes/checklist.",
|
|
@@ -633,7 +672,7 @@ server.registerTool("planner-task-discuss", {
|
|
|
633
672
|
},
|
|
634
673
|
}, async ({ task: ref, description, checklist }) => {
|
|
635
674
|
const st = await requireStore();
|
|
636
|
-
const found = findTaskByRef(await st.loadAllPhases(), ref);
|
|
675
|
+
const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
|
|
637
676
|
if (!found)
|
|
638
677
|
return text(`Task not found: ${ref}`);
|
|
639
678
|
let updatedTask;
|
|
@@ -666,7 +705,7 @@ server.registerTool("planner-task-update", {
|
|
|
666
705
|
},
|
|
667
706
|
}, async ({ task: ref, title, status, description, motivation, priority }) => {
|
|
668
707
|
const st = await requireStore();
|
|
669
|
-
const found = findTaskByRef(await st.loadAllPhases(), ref);
|
|
708
|
+
const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
|
|
670
709
|
if (!found)
|
|
671
710
|
return text(`Task not found: ${ref}`);
|
|
672
711
|
// Validate motivation requirement for status transitions.
|
|
@@ -716,7 +755,7 @@ server.registerTool("planner-task-delete", {
|
|
|
716
755
|
inputSchema: { task: z.string().min(1) },
|
|
717
756
|
}, async ({ task: ref }) => {
|
|
718
757
|
const st = await requireStore();
|
|
719
|
-
const found = findTaskByRef(await st.loadAllPhases(), ref);
|
|
758
|
+
const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
|
|
720
759
|
if (!found)
|
|
721
760
|
return text(`Task not found: ${ref}`);
|
|
722
761
|
await st.updatePhase(found.phase.id, (phase) => {
|
|
@@ -726,23 +765,30 @@ server.registerTool("planner-task-delete", {
|
|
|
726
765
|
return phase;
|
|
727
766
|
});
|
|
728
767
|
await st.syncTaskStatusRollup(found.phase.id);
|
|
729
|
-
|
|
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 });
|
|
730
770
|
});
|
|
731
771
|
server.registerTool("planner-task-start", {
|
|
732
772
|
description: "Set a task to in-progress.",
|
|
733
773
|
inputSchema: { task: z.string().min(1) },
|
|
734
774
|
}, async ({ task: ref }) => {
|
|
735
775
|
const st = await requireStore();
|
|
736
|
-
|
|
737
|
-
// task_start — the handoff is a captured-context artifact, not a lock (e.g. a
|
|
738
|
-
// small modification after writing a handoff should start without forcing
|
|
739
|
-
// deletion, which would lose context).
|
|
740
|
-
const handoffNotice = (await st.listHandoffs()).length > 0
|
|
741
|
-
? "ℹ️ One or more phases have a pending handoff — if relevant to the task you're starting, read it with planner-handoff-show <ref>, then clear with planner-handoff-clear. Proceeding with task start.\n"
|
|
742
|
-
: "";
|
|
743
|
-
const found = findTaskByRef(await st.loadAllPhases(), ref);
|
|
776
|
+
const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
|
|
744
777
|
if (!found)
|
|
745
778
|
return text(`Task not found: ${ref}`);
|
|
779
|
+
// Auto-archive the handoff on THIS task's phase (reason: task-started) — the
|
|
780
|
+
// agent is now actively working, so the captured context is consumed. Other
|
|
781
|
+
// phases' handoffs are left untouched (informational, non-blocking).
|
|
782
|
+
const allHandoffs = await st.listHandoffs();
|
|
783
|
+
let handoffNotice = "";
|
|
784
|
+
if ((found.phase.handoff ?? "") !== "") {
|
|
785
|
+
await st.clearPhaseHandoff(found.phase.id, "task-started").catch(() => { });
|
|
786
|
+
handoffNotice = "📦 Archived handoff for this phase (task started) — recover via planner-handoff-list + .planner/handoff-archive/.\n";
|
|
787
|
+
}
|
|
788
|
+
const _otherHandoffs = allHandoffs.filter((h) => h.phaseId !== found.phase.id);
|
|
789
|
+
if (_otherHandoffs.length > 0) {
|
|
790
|
+
handoffNotice += `ℹ️ ${_otherHandoffs.length} other phase handoff(s) pending — review with planner-handoff-list if relevant.\n`;
|
|
791
|
+
}
|
|
746
792
|
const timestamp = nowISO();
|
|
747
793
|
let updatedTask;
|
|
748
794
|
await st.updatePhase(found.phase.id, (phase) => {
|
|
@@ -780,7 +826,7 @@ server.registerTool("planner-task-complete", {
|
|
|
780
826
|
},
|
|
781
827
|
}, async ({ task: ref, force, description_update }) => {
|
|
782
828
|
const st = await requireStore();
|
|
783
|
-
const found = findTaskByRef(await st.loadAllPhases(), ref);
|
|
829
|
+
const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
|
|
784
830
|
if (!found)
|
|
785
831
|
return text(`Task not found: ${ref}`);
|
|
786
832
|
const unchecked = found.task.checklist.filter((item) => !item.checked);
|
|
@@ -870,8 +916,8 @@ server.registerTool("planner-handoff-clear", {
|
|
|
870
916
|
const r = await resolvePhaseForHandoff(st, phaseRef);
|
|
871
917
|
if (!r.ok)
|
|
872
918
|
return text(`❌ ${r.error}`);
|
|
873
|
-
await st.clearPhaseHandoff(r.phase.id);
|
|
874
|
-
return text(`✅ Cleared handoff on ${r.compositeRef}
|
|
919
|
+
await st.clearPhaseHandoff(r.phase.id, "manual");
|
|
920
|
+
return text(`✅ Cleared handoff on ${r.compositeRef} (handoffUpdatedAt preserved as audit; archived to .planner/handoff-archive/).`, { phaseRef: r.compositeRef, phaseId: r.phase.id });
|
|
875
921
|
});
|
|
876
922
|
server.registerTool("planner-web", {
|
|
877
923
|
description: "Manage the planner web dashboard (start/status/stop) from MCP stdio. Runs an in-process HTTP+WS server bound to LAN; it lives as long as this MCP process. plan-root comes from AGENT_PLAN_ROOT or cwd()/.planner.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-plan/mcp",
|
|
3
|
-
"version": "0.2.19-next.
|
|
3
|
+
"version": "0.2.19-next.10",
|
|
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.
|
|
45
|
-
"@agent-plan/server": "0.2.19-next.
|
|
44
|
+
"@agent-plan/core": "0.2.19-next.10",
|
|
45
|
+
"@agent-plan/server": "0.2.19-next.10"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -p tsconfig.json",
|