@agent-plan/mcp 0.2.19-next.16 → 0.2.19-next.18
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 +146 -16
- 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":";AA+pCA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { PlanStore, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap, migrateToGlobalSequence, addChecklistItem, removeChecklistItem, toggleChecklistItem } from "@agent-plan/core";
|
|
8
8
|
import { serve } from "@agent-plan/server";
|
|
9
|
-
import { createChecklistItemId, createFeatureId, createPhaseId, createShortId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef } from "@agent-plan/core/naming";
|
|
9
|
+
import { createChecklistItemId, createFeatureId, createPhaseId, createShortId, 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"];
|
|
11
11
|
const PHASE_STATUS_VALUES = ["draft", "discovery", ...STATUS_VALUES];
|
|
12
12
|
function text(textValue, structuredContent) {
|
|
@@ -58,10 +58,50 @@ async function ensureWebStarted() {
|
|
|
58
58
|
}
|
|
59
59
|
function findFeatureByRef(features, ref) {
|
|
60
60
|
const normalized = ref.trim().toLowerCase();
|
|
61
|
-
|
|
61
|
+
if (!normalized)
|
|
62
|
+
return undefined;
|
|
63
|
+
const fMatch = normalized.match(/^f(\d+)$/);
|
|
64
|
+
if (fMatch) {
|
|
65
|
+
const n = parseInt(fMatch[1], 10);
|
|
66
|
+
const byNum = features.find((feature) => feature.number === n);
|
|
67
|
+
if (byNum)
|
|
68
|
+
return byNum;
|
|
69
|
+
}
|
|
70
|
+
return features.find((feature) => feature.shortId?.toLowerCase() === normalized)
|
|
71
|
+
?? features.find((feature) => feature.id.toLowerCase() === normalized)
|
|
62
72
|
?? features.find((feature) => feature.name.toLowerCase() === normalized)
|
|
63
73
|
?? features.find((feature) => feature.name.toLowerCase().includes(normalized));
|
|
64
74
|
}
|
|
75
|
+
function resolveFeatureRefStrict(features, ref) {
|
|
76
|
+
const raw = ref.trim();
|
|
77
|
+
if (!raw)
|
|
78
|
+
return { ok: false, error: "Feature ref is required." };
|
|
79
|
+
const normalized = raw.toLowerCase();
|
|
80
|
+
const byNumber = normalized.match(/^f(\d+)$/)
|
|
81
|
+
? features.find((feature) => feature.number === parseInt(normalized.slice(1), 10))
|
|
82
|
+
: undefined;
|
|
83
|
+
if (byNumber)
|
|
84
|
+
return { ok: true, feature: byNumber };
|
|
85
|
+
const byShortId = features.find((feature) => feature.shortId?.toLowerCase() === normalized);
|
|
86
|
+
if (byShortId)
|
|
87
|
+
return { ok: true, feature: byShortId };
|
|
88
|
+
const byId = features.find((feature) => feature.id.toLowerCase() === normalized);
|
|
89
|
+
if (byId)
|
|
90
|
+
return { ok: true, feature: byId };
|
|
91
|
+
const exactName = features.filter((feature) => feature.name.toLowerCase() === normalized);
|
|
92
|
+
if (exactName.length === 1)
|
|
93
|
+
return { ok: true, feature: exactName[0] };
|
|
94
|
+
if (exactName.length > 1)
|
|
95
|
+
return { ok: false, error: `Ambiguous feature ref: ${raw}. Multiple features have that exact name; use F00x, shortId, or UUID.` };
|
|
96
|
+
const partialName = features.filter((feature) => feature.name.toLowerCase().includes(normalized));
|
|
97
|
+
if (partialName.length === 1)
|
|
98
|
+
return { ok: true, feature: partialName[0] };
|
|
99
|
+
if (partialName.length > 1)
|
|
100
|
+
return { ok: false, error: `Ambiguous feature ref: ${raw}. Matches: ${partialName.map((feature) => formatFeatureRef(feature.number)).join(", ")}. Use a specific F00x, shortId, or UUID.` };
|
|
101
|
+
return { ok: false, error: `Feature not found: ${raw}` };
|
|
102
|
+
}
|
|
103
|
+
/** Belt-and-suspenders validation: a resolved ref must be a real UUID and the target
|
|
104
|
+
* must still exist in the store before we allocate numbers or write. */
|
|
65
105
|
function featureNumberOfPhase(phase, features) {
|
|
66
106
|
return phase.featureId ? features.find((f) => f.id === phase.featureId)?.number : undefined;
|
|
67
107
|
}
|
|
@@ -210,6 +250,33 @@ server.registerTool("planner-repair", {
|
|
|
210
250
|
const report = await st.repair();
|
|
211
251
|
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 });
|
|
212
252
|
});
|
|
253
|
+
server.registerTool("planner-cleanup-orphan-phases", {
|
|
254
|
+
description: "Discover phase files that no longer resolve to a valid owning feature, and optionally delete them. Run once with confirm=false (default) to inspect, then rerun with confirm=true to remove them.",
|
|
255
|
+
inputSchema: {
|
|
256
|
+
confirm: z.boolean().optional().describe("Set true to actually delete the discovered orphan phase files. Default: false (dry-run/list only)."),
|
|
257
|
+
},
|
|
258
|
+
}, async ({ confirm }) => {
|
|
259
|
+
const st = await requireStore();
|
|
260
|
+
const found = await st.listOrphanPhases();
|
|
261
|
+
if (!confirm) {
|
|
262
|
+
if (found.length === 0)
|
|
263
|
+
return text("No orphan phases found.", { found: [] });
|
|
264
|
+
const lines = [
|
|
265
|
+
`Found ${found.length} orphan phase${found.length === 1 ? "" : "s"}.`,
|
|
266
|
+
...found.map((phase) => `- ${phase.compositeRef}${phase.shortId ? ` · ${phase.shortId}` : ""} — ${phase.title} (${phase.reason})`),
|
|
267
|
+
"Rerun with confirm=true to delete these orphan phase files.",
|
|
268
|
+
];
|
|
269
|
+
return text(lines.join("\n"), { found, confirmRequired: true });
|
|
270
|
+
}
|
|
271
|
+
const report = await st.cleanupOrphanPhases();
|
|
272
|
+
if (report.removed.length === 0)
|
|
273
|
+
return text("No orphan phases found.", report);
|
|
274
|
+
const lines = [
|
|
275
|
+
`Removed ${report.removed.length} orphan phase${report.removed.length === 1 ? "" : "s"}.`,
|
|
276
|
+
...report.removed.map((phase) => `- ${phase.compositeRef}${phase.shortId ? ` · ${phase.shortId}` : ""} — ${phase.title}`),
|
|
277
|
+
];
|
|
278
|
+
return text(lines.join("\n"), report);
|
|
279
|
+
});
|
|
213
280
|
server.registerTool("planner-project-language", {
|
|
214
281
|
description: "Persist preferred languages for plan content and chat.",
|
|
215
282
|
inputSchema: {
|
|
@@ -371,13 +438,51 @@ server.registerTool("planner-feature-show", {
|
|
|
371
438
|
}, async ({ feature: ref, full }) => {
|
|
372
439
|
const st = await requireStore();
|
|
373
440
|
const features = (await st.loadFeatures()).features;
|
|
374
|
-
const
|
|
375
|
-
if (!
|
|
376
|
-
return text(
|
|
441
|
+
const resolvedFeature = resolveFeatureRefStrict(features, ref);
|
|
442
|
+
if (!resolvedFeature.ok)
|
|
443
|
+
return text(resolvedFeature.error);
|
|
444
|
+
const feature = resolvedFeature.feature;
|
|
377
445
|
const phases = (await st.loadAllPhases()).filter((phase) => phase.featureId === feature.id);
|
|
378
446
|
const summary = `${feature.name} — ${formatFeatureRef(feature.number)}${feature.shortId ? ` · ${feature.shortId}` : ""} (${feature.status}; ${phases.length} phases)`;
|
|
379
447
|
return text(full ? `${summary}\n\n${feature.description || ""}` : summary);
|
|
380
448
|
});
|
|
449
|
+
server.registerTool("planner-feature-discuss", {
|
|
450
|
+
description: "Persist feature discovery/governance fields and mark the feature context as ready.",
|
|
451
|
+
inputSchema: {
|
|
452
|
+
feature: z.string().min(1).describe("Feature ref. Accepts F00x, shortId, UUID, or title."),
|
|
453
|
+
description: z.string().optional().describe("Current implementation state, scope, and goals for this feature."),
|
|
454
|
+
workDone: z.string().optional().describe("What is already implemented / decided."),
|
|
455
|
+
workRemaining: z.string().optional().describe("What still needs to be done."),
|
|
456
|
+
dependencies: z.array(z.string()).optional().describe("Cross-feature or external dependencies."),
|
|
457
|
+
},
|
|
458
|
+
}, async ({ feature: ref, description, workDone, workRemaining, dependencies }) => {
|
|
459
|
+
const st = await requireStore();
|
|
460
|
+
const features = (await st.loadFeatures()).features;
|
|
461
|
+
const resolvedFeature = resolveFeatureRefStrict(features, ref);
|
|
462
|
+
if (!resolvedFeature.ok)
|
|
463
|
+
return text(resolvedFeature.error);
|
|
464
|
+
const feature = resolvedFeature.feature;
|
|
465
|
+
const updated = await st.updateFeatures((doc) => {
|
|
466
|
+
const target = doc.features.find((entry) => entry.id === feature.id);
|
|
467
|
+
if (!target)
|
|
468
|
+
return doc;
|
|
469
|
+
if (description !== undefined)
|
|
470
|
+
target.description = description.trim();
|
|
471
|
+
if (workDone !== undefined)
|
|
472
|
+
target.workDone = workDone.trim();
|
|
473
|
+
if (workRemaining !== undefined)
|
|
474
|
+
target.workRemaining = workRemaining.trim();
|
|
475
|
+
if (dependencies !== undefined)
|
|
476
|
+
target.dependsOn = dependencies.map((item) => item.trim()).filter(Boolean);
|
|
477
|
+
target.discussedAt = nowISO();
|
|
478
|
+
target.contextReady = true;
|
|
479
|
+
target.contextReadyReason = "Updated through planner-feature-discuss MCP tool.";
|
|
480
|
+
target.updatedAt = nowISO();
|
|
481
|
+
return doc;
|
|
482
|
+
});
|
|
483
|
+
const result = updated.features.find((entry) => entry.id === feature.id);
|
|
484
|
+
return writeAndSummarize(st, `✅ Feature discussed/updated: ${formatFeatureRef(result.number)} — ${result.name}${result.shortId ? ` · ${result.shortId}` : ""}`);
|
|
485
|
+
});
|
|
381
486
|
server.registerTool("planner-feature-update", {
|
|
382
487
|
description: "Update a feature.",
|
|
383
488
|
inputSchema: {
|
|
@@ -394,9 +499,10 @@ server.registerTool("planner-feature-update", {
|
|
|
394
499
|
}, async ({ feature: ref, ...updates }) => {
|
|
395
500
|
const st = await requireStore();
|
|
396
501
|
const features = (await st.loadFeatures()).features;
|
|
397
|
-
const
|
|
398
|
-
if (!
|
|
399
|
-
return text(
|
|
502
|
+
const resolvedFeature = resolveFeatureRefStrict(features, ref);
|
|
503
|
+
if (!resolvedFeature.ok)
|
|
504
|
+
return text(resolvedFeature.error);
|
|
505
|
+
const feature = resolvedFeature.feature;
|
|
400
506
|
const updated = await st.updateFeatures((doc) => {
|
|
401
507
|
const target = doc.features.find((entry) => entry.id === feature.id);
|
|
402
508
|
if (!target)
|
|
@@ -432,9 +538,10 @@ server.registerTool("planner-feature-delete", {
|
|
|
432
538
|
}, async ({ feature: ref, cascade }) => {
|
|
433
539
|
const st = await requireStore();
|
|
434
540
|
const features = (await st.loadFeatures()).features;
|
|
435
|
-
const
|
|
436
|
-
if (!
|
|
437
|
-
return text(
|
|
541
|
+
const resolvedFeature = resolveFeatureRefStrict(features, ref);
|
|
542
|
+
if (!resolvedFeature.ok)
|
|
543
|
+
return text(resolvedFeature.error);
|
|
544
|
+
const feature = resolvedFeature.feature;
|
|
438
545
|
const phases = (await st.loadAllPhases()).filter((phase) => phase.featureId === feature.id);
|
|
439
546
|
await st.updateFeatures((doc) => {
|
|
440
547
|
doc.features = doc.features.filter((entry) => entry.id !== feature.id);
|
|
@@ -463,9 +570,21 @@ server.registerTool("planner-phase-add", {
|
|
|
463
570
|
}, async ({ title, feature: featureRef, summary, description }) => {
|
|
464
571
|
const st = await requireStore();
|
|
465
572
|
const featuresDoc = await st.loadFeatures();
|
|
466
|
-
|
|
573
|
+
let feature;
|
|
574
|
+
if (featureRef) {
|
|
575
|
+
const resolvedFeature = resolveFeatureRefStrict(featuresDoc.features, featureRef);
|
|
576
|
+
if (!resolvedFeature.ok)
|
|
577
|
+
return text(resolvedFeature.error);
|
|
578
|
+
const featureValidation = await validateResolvedTarget("feature", resolvedFeature.feature.id, () => st.loadFeatures().then((doc) => doc.features.find((f) => f.id === resolvedFeature.feature.id)).catch(() => undefined));
|
|
579
|
+
if (!featureValidation.ok)
|
|
580
|
+
return text(featureValidation.error);
|
|
581
|
+
feature = resolvedFeature.feature;
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
feature = featuresDoc.features[0];
|
|
585
|
+
}
|
|
467
586
|
if (featureRef && !feature)
|
|
468
|
-
return text(`
|
|
587
|
+
return text(`Resolved feature ${featureRef} no longer exists. Refusing to create phase.`);
|
|
469
588
|
const lockKey = feature?.id ?? "__unscoped__";
|
|
470
589
|
let phase;
|
|
471
590
|
await withFeatureLock(lockKey, async () => {
|
|
@@ -524,7 +643,7 @@ server.registerTool("planner-phase-add", {
|
|
|
524
643
|
return writeAndSummarize(st, `✅ Phase created: ${formatPhaseRef(phase.number, feature?.number)} — ${phase.title}${phase.shortId ? ` · ${phase.shortId}` : ""}`);
|
|
525
644
|
});
|
|
526
645
|
server.registerTool("planner-phase-show", {
|
|
527
|
-
description: "Show a phase by id or name.",
|
|
646
|
+
description: "Show a phase by id or name, including derived linked requirements in the full view.",
|
|
528
647
|
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).") },
|
|
529
648
|
}, async ({ phase: ref, full }) => {
|
|
530
649
|
const st = await requireStore();
|
|
@@ -532,8 +651,13 @@ server.registerTool("planner-phase-show", {
|
|
|
532
651
|
const phase = findPhaseByRef(await st.loadAllPhases(), features, ref);
|
|
533
652
|
if (!phase)
|
|
534
653
|
return text(`Phase not found: ${ref}`);
|
|
535
|
-
const
|
|
536
|
-
|
|
654
|
+
const linkedRequirements = await st.linkedRequirementsForPhase(phase.id);
|
|
655
|
+
const reqCount = linkedRequirements.length;
|
|
656
|
+
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"}` : ""})`;
|
|
657
|
+
const requirementsBlock = reqCount > 0
|
|
658
|
+
? `\n\nLinked requirements:\n${linkedRequirements.map((requirement) => `- ${requirement.title} (${requirement.status})`).join("\n")}`
|
|
659
|
+
: "";
|
|
660
|
+
return text(full ? `${summary}\n\n${phase.description || ""}${requirementsBlock}` : summary, { linkedRequirements });
|
|
537
661
|
});
|
|
538
662
|
server.registerTool("planner-phase-discuss", {
|
|
539
663
|
description: "Persist phase discovery fields and mark phase planned.",
|
|
@@ -639,6 +763,12 @@ server.registerTool("planner-task-add", {
|
|
|
639
763
|
const found = findPhaseByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, ref);
|
|
640
764
|
if (!found)
|
|
641
765
|
return text(`Phase not found: ${ref}`);
|
|
766
|
+
const phaseValidation = await validateResolvedTarget("phase", found.id, () => st.loadPhase(found.id).catch(() => undefined));
|
|
767
|
+
if (!phaseValidation.ok)
|
|
768
|
+
return text(phaseValidation.error);
|
|
769
|
+
const existingPhase = await st.loadPhase(found.id);
|
|
770
|
+
if (!existingPhase)
|
|
771
|
+
return text(`Resolved phase ${found.id} no longer exists. Refusing to create task.`);
|
|
642
772
|
const timestamp = nowISO();
|
|
643
773
|
const taskId = createTaskId();
|
|
644
774
|
const shortId = createShortId(await st.assignedShortIds());
|
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.18",
|
|
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.18",
|
|
45
|
+
"@agent-plan/server": "0.2.19-next.18"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -p tsconfig.json",
|