@agent-plan/pi-adapter 0.2.19-next.4 → 0.2.19-next.6
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 +119 -88
- 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":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAgvBtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAs9F9D"}
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - inject context into prompts
|
|
10
10
|
*/
|
|
11
11
|
import { Type } from "typebox";
|
|
12
|
-
import { ExportService, PlanStore, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, withFeatureLock, needsMotivation, findPhaseByRef, buildRecap } from "@agent-plan/core";
|
|
12
|
+
import { ExportService, PlanStore, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap } from "@agent-plan/core";
|
|
13
13
|
import { createChecklistItemId, createFeatureId, createPhaseId, createShortId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef, featureNumberOfPhase } from "@agent-plan/core/naming";
|
|
14
14
|
import { join, dirname } from "node:path";
|
|
15
15
|
import { homedir } from "node:os";
|
|
@@ -512,6 +512,9 @@ function statusIcon(status) {
|
|
|
512
512
|
};
|
|
513
513
|
return icons[status] ?? "❓";
|
|
514
514
|
}
|
|
515
|
+
function pad(n) {
|
|
516
|
+
return String(n).padStart(3, "0");
|
|
517
|
+
}
|
|
515
518
|
function applyTaskLifecycleDates(task, nextStatus, now) {
|
|
516
519
|
const previousStatus = task.status;
|
|
517
520
|
if (nextStatus === "in-progress" && !task.startedAt) {
|
|
@@ -1146,19 +1149,6 @@ export default function planPiExtension(pi) {
|
|
|
1146
1149
|
?? phases.find((phase) => phase.title.toLowerCase().includes(normalized))
|
|
1147
1150
|
?? null;
|
|
1148
1151
|
}
|
|
1149
|
-
function findTaskByRef(phases, ref) {
|
|
1150
|
-
const normalized = ref.trim().toLowerCase();
|
|
1151
|
-
if (!normalized)
|
|
1152
|
-
return null;
|
|
1153
|
-
for (const phase of phases) {
|
|
1154
|
-
const task = phase.tasks.find((entryTask) => entryTask.id.toLowerCase() === normalized
|
|
1155
|
-
|| entryTask.title.toLowerCase() === normalized
|
|
1156
|
-
|| entryTask.title.toLowerCase().includes(normalized));
|
|
1157
|
-
if (task)
|
|
1158
|
-
return { phase, task };
|
|
1159
|
-
}
|
|
1160
|
-
return null;
|
|
1161
|
-
}
|
|
1162
1152
|
function profile_packageManager(pkg, lockfile) {
|
|
1163
1153
|
if (pkg?.packageManager)
|
|
1164
1154
|
return pkg.packageManager;
|
|
@@ -1363,7 +1353,7 @@ export default function planPiExtension(pi) {
|
|
|
1363
1353
|
return;
|
|
1364
1354
|
}
|
|
1365
1355
|
const now = nowISO();
|
|
1366
|
-
const featureNumber =
|
|
1356
|
+
const featureNumber = await st.allocFeatureNumber();
|
|
1367
1357
|
const shortId = createShortId(await st.assignedShortIds());
|
|
1368
1358
|
const priority = await st.nextPriority("feature");
|
|
1369
1359
|
const feature = {
|
|
@@ -1392,7 +1382,7 @@ export default function planPiExtension(pi) {
|
|
|
1392
1382
|
return doc;
|
|
1393
1383
|
});
|
|
1394
1384
|
await st.writeGenerated();
|
|
1395
|
-
ctx.ui.notify(`Feature created: ${feature.
|
|
1385
|
+
ctx.ui.notify(`Feature created: ${formatFeatureRef(feature.number)}${feature.shortId ? ` +MI+ ${feature.shortId}` : ""} +EM+ ${feature.name}`, "info");
|
|
1396
1386
|
return;
|
|
1397
1387
|
}
|
|
1398
1388
|
if (b === "show") {
|
|
@@ -1473,7 +1463,7 @@ export default function planPiExtension(pi) {
|
|
|
1473
1463
|
});
|
|
1474
1464
|
feature = updatedDoc.features.find((entry) => entry.id === featureId) ?? feature;
|
|
1475
1465
|
await st.writeGenerated();
|
|
1476
|
-
ctx.ui.notify(`Feature updated: ${feature.
|
|
1466
|
+
ctx.ui.notify(`Feature updated: ${formatFeatureRef(feature.number)}${feature.shortId ? ` +MI+ ${feature.shortId}` : ""} +EM+ ${feature.name} (${feature.status})`, "info");
|
|
1477
1467
|
return;
|
|
1478
1468
|
}
|
|
1479
1469
|
if (b === "delete") {
|
|
@@ -1550,7 +1540,7 @@ export default function planPiExtension(pi) {
|
|
|
1550
1540
|
await withFeatureLock(feature.id, async () => {
|
|
1551
1541
|
const phases = await st.loadAllPhases();
|
|
1552
1542
|
const featurePhases = phases.filter((p) => p.featureId === feature.id);
|
|
1553
|
-
const number =
|
|
1543
|
+
const number = await st.allocPhaseNumber();
|
|
1554
1544
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1555
1545
|
const id = createPhaseId();
|
|
1556
1546
|
const shortId = createShortId(await st.assignedShortIds());
|
|
@@ -1577,7 +1567,7 @@ export default function planPiExtension(pi) {
|
|
|
1577
1567
|
});
|
|
1578
1568
|
if (!phase)
|
|
1579
1569
|
return;
|
|
1580
|
-
ctx.ui.notify(`Phase created: ${phase.
|
|
1570
|
+
ctx.ui.notify(`Phase created: ${formatPhaseRef(phase.number, feature.number)} — ${title}${phase.shortId ? ` · ${phase.shortId}` : ""} (feature ${feature.name})`, "info");
|
|
1581
1571
|
const startDiscuss = await ctx.ui.input("Start phase discuss now? Type yes to continue");
|
|
1582
1572
|
if (isYes(startDiscuss)) {
|
|
1583
1573
|
await handlePlanner(`phase discuss ${phase.id}`, ctx);
|
|
@@ -1602,7 +1592,7 @@ export default function planPiExtension(pi) {
|
|
|
1602
1592
|
phase.status = "discovery";
|
|
1603
1593
|
phase.updatedAt = nowISO();
|
|
1604
1594
|
await st.savePhase(phase);
|
|
1605
|
-
ctx.ui.notify(`Discovery for ${phase.
|
|
1595
|
+
ctx.ui.notify(`Discovery for ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))}…`, "info");
|
|
1606
1596
|
// ── Recap: give full context before planning tasks ──
|
|
1607
1597
|
const recapPlan = await st.loadAll();
|
|
1608
1598
|
const recapProject = recapPlan.project;
|
|
@@ -1667,7 +1657,7 @@ export default function planPiExtension(pi) {
|
|
|
1667
1657
|
phase.updatedAt = nowISO();
|
|
1668
1658
|
await st.savePhase(phase);
|
|
1669
1659
|
await st.writeGenerated();
|
|
1670
|
-
ctx.ui.notify(`Phase ${phase.
|
|
1660
|
+
ctx.ui.notify(`Phase ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))} is now **planned**.${phase.shortId ? ` · ${phase.shortId}` : ""}`, "info");
|
|
1671
1661
|
return;
|
|
1672
1662
|
}
|
|
1673
1663
|
if (b === "show") {
|
|
@@ -1715,7 +1705,7 @@ export default function planPiExtension(pi) {
|
|
|
1715
1705
|
}
|
|
1716
1706
|
await st.deletePhase(phase.id);
|
|
1717
1707
|
await st.writeGenerated();
|
|
1718
|
-
ctx.ui.notify(`Phase deleted: ${phase.
|
|
1708
|
+
ctx.ui.notify(`Phase deleted: ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))}${phase.shortId ? ` · ${phase.shortId}` : ""}`, "info");
|
|
1719
1709
|
return;
|
|
1720
1710
|
}
|
|
1721
1711
|
if (b === "update") {
|
|
@@ -1767,7 +1757,7 @@ export default function planPiExtension(pi) {
|
|
|
1767
1757
|
phase.updatedAt = nowISO();
|
|
1768
1758
|
await st.savePhase(phase);
|
|
1769
1759
|
await st.writeGenerated();
|
|
1770
|
-
ctx.ui.notify(`Phase updated: ${phase.
|
|
1760
|
+
ctx.ui.notify(`Phase updated: ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))} — ${phase.title} (${phase.status})${phase.shortId ? ` · ${phase.shortId}` : ""}`, "info");
|
|
1771
1761
|
return;
|
|
1772
1762
|
}
|
|
1773
1763
|
ctx.ui.notify(`Unknown phase action "${b}". Try: add, show, discuss, delete, update`, "warning");
|
|
@@ -1793,7 +1783,7 @@ export default function planPiExtension(pi) {
|
|
|
1793
1783
|
return;
|
|
1794
1784
|
}
|
|
1795
1785
|
const shortName = clampSlug(title, 30, `task-${Date.now().toString(36)}`);
|
|
1796
|
-
const taskNum =
|
|
1786
|
+
const taskNum = await st.allocTaskNumber();
|
|
1797
1787
|
const taskId = createTaskId();
|
|
1798
1788
|
const shortId = createShortId(await st.assignedShortIds());
|
|
1799
1789
|
const priority = await st.nextPriority("task", phase.id);
|
|
@@ -1825,7 +1815,7 @@ export default function planPiExtension(pi) {
|
|
|
1825
1815
|
}
|
|
1826
1816
|
if (b === "discuss") {
|
|
1827
1817
|
const phases = await st.loadAllPhases();
|
|
1828
|
-
let resolved = subArgs.trim() ? findTaskByRef(phases, subArgs.trim()) : null;
|
|
1818
|
+
let resolved = subArgs.trim() ? findTaskByRef(phases, (await st.loadFeatures()).features, subArgs.trim()) : null;
|
|
1829
1819
|
if (!resolved) {
|
|
1830
1820
|
const phase = await pickPhase();
|
|
1831
1821
|
if (!phase) {
|
|
@@ -1859,14 +1849,14 @@ export default function planPiExtension(pi) {
|
|
|
1859
1849
|
phase.updatedAt = nowISO();
|
|
1860
1850
|
await st.savePhase(phase);
|
|
1861
1851
|
await st.writeGenerated();
|
|
1862
|
-
ctx.ui.notify(`Task ${task.
|
|
1852
|
+
ctx.ui.notify(`Task ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))}/T${String(task.number).padStart(3, "0")} discussed and updated.`, "info");
|
|
1863
1853
|
return;
|
|
1864
1854
|
}
|
|
1865
1855
|
if (b === "show") {
|
|
1866
1856
|
let phase = null;
|
|
1867
1857
|
let task = null;
|
|
1868
1858
|
if (subArgs.trim()) {
|
|
1869
|
-
const resolved = findTaskByRef(await st.loadAllPhases(), subArgs.trim());
|
|
1859
|
+
const resolved = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, subArgs.trim());
|
|
1870
1860
|
phase = resolved?.phase ?? null;
|
|
1871
1861
|
task = resolved?.task ?? null;
|
|
1872
1862
|
}
|
|
@@ -1928,7 +1918,7 @@ export default function planPiExtension(pi) {
|
|
|
1928
1918
|
phase.updatedAt = nowISO();
|
|
1929
1919
|
await st.savePhase(phase);
|
|
1930
1920
|
await st.writeGenerated();
|
|
1931
|
-
ctx.ui.notify(`Task deleted: ${task.
|
|
1921
|
+
ctx.ui.notify(`Task deleted: ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))}/T${String(task.number).padStart(3, "0")}${task.shortId ? ` · ${task.shortId}` : ""}`, "info");
|
|
1932
1922
|
return;
|
|
1933
1923
|
}
|
|
1934
1924
|
if (b === "update") {
|
|
@@ -1977,14 +1967,14 @@ export default function planPiExtension(pi) {
|
|
|
1977
1967
|
phase.updatedAt = now;
|
|
1978
1968
|
await st.savePhase(phase);
|
|
1979
1969
|
await st.writeGenerated();
|
|
1980
|
-
ctx.ui.notify(`Task updated: ${task.
|
|
1970
|
+
ctx.ui.notify(`Task updated: ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))}/T${String(task.number).padStart(3, "0")} — ${task.title} (${task.status})${task.shortId ? ` · ${task.shortId}` : ""}`, "info");
|
|
1981
1971
|
return;
|
|
1982
1972
|
}
|
|
1983
1973
|
if (b === "start") {
|
|
1984
1974
|
let phase = null;
|
|
1985
1975
|
let task = null;
|
|
1986
1976
|
if (subArgs.trim()) {
|
|
1987
|
-
const resolved = findTaskByRef(await st.loadAllPhases(), subArgs.trim());
|
|
1977
|
+
const resolved = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, subArgs.trim());
|
|
1988
1978
|
phase = resolved?.phase ?? null;
|
|
1989
1979
|
task = resolved?.task ?? null;
|
|
1990
1980
|
}
|
|
@@ -2036,14 +2026,14 @@ export default function planPiExtension(pi) {
|
|
|
2036
2026
|
phase.updatedAt = now;
|
|
2037
2027
|
await st.savePhase(phase);
|
|
2038
2028
|
await st.writeGenerated();
|
|
2039
|
-
ctx.ui.notify(`✅ Task started: ${task.
|
|
2029
|
+
ctx.ui.notify(`✅ Task started: ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))}/T${String(task.number).padStart(3, "0")} — ${task.title} (in-progress)${task.shortId ? ` · ${task.shortId}` : ""}`, "info");
|
|
2040
2030
|
return;
|
|
2041
2031
|
}
|
|
2042
2032
|
if (b === "complete") {
|
|
2043
2033
|
let phase = null;
|
|
2044
2034
|
let task = null;
|
|
2045
2035
|
if (subArgs.trim()) {
|
|
2046
|
-
const resolved = findTaskByRef(await st.loadAllPhases(), subArgs.trim());
|
|
2036
|
+
const resolved = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, subArgs.trim());
|
|
2047
2037
|
phase = resolved?.phase ?? null;
|
|
2048
2038
|
task = resolved?.task ?? null;
|
|
2049
2039
|
}
|
|
@@ -2091,7 +2081,7 @@ export default function planPiExtension(pi) {
|
|
|
2091
2081
|
phase.updatedAt = now;
|
|
2092
2082
|
await st.savePhase(phase);
|
|
2093
2083
|
await st.writeGenerated();
|
|
2094
|
-
ctx.ui.notify(`✅ Task completed: ${task.
|
|
2084
|
+
ctx.ui.notify(`✅ Task completed: ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))}/T${String(task.number).padStart(3, "0")} — ${task.title} (done)${task.shortId ? ` · ${task.shortId}` : ""}`, "info");
|
|
2095
2085
|
return;
|
|
2096
2086
|
}
|
|
2097
2087
|
ctx.ui.notify(`Unknown task action "${b}". Try: add, show, discuss, delete, update, start, complete`, "warning");
|
|
@@ -2836,38 +2826,44 @@ export default function planPiExtension(pi) {
|
|
|
2836
2826
|
pi.registerTool({
|
|
2837
2827
|
name: "feature_list",
|
|
2838
2828
|
label: "Feature List",
|
|
2839
|
-
description: "List
|
|
2840
|
-
parameters: Type.Object({}),
|
|
2841
|
-
async execute(_id,
|
|
2829
|
+
description: "List features (compact: F00x · shortId — name (status; N phases, M tasks)). Use this to discover refs cheaply — do NOT read .planner/ files or plan_get full=true to find entities.",
|
|
2830
|
+
parameters: Type.Object({ featureRef: Type.Optional(Type.String({ description: "Optional: filter to one feature (F00x/shortId/UUID/name)" })) }),
|
|
2831
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
2842
2832
|
const st = await requirePlan(ctx);
|
|
2843
2833
|
if (!st)
|
|
2844
2834
|
return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
|
|
2845
|
-
const
|
|
2835
|
+
const all = (await st.loadFeatures()).features;
|
|
2836
|
+
const ref = params.featureRef?.trim();
|
|
2837
|
+
const matchF = (f) => !ref || formatFeatureRef(f.number) === ref || f.shortId === ref || f.id === ref || f.name.toLowerCase() === ref.toLowerCase();
|
|
2838
|
+
const features = all.filter(matchF);
|
|
2846
2839
|
const phases = await st.loadAllPhases();
|
|
2847
2840
|
const summary = features.map((f) => {
|
|
2848
2841
|
const fp = phases.filter((p) => p.featureId === f.id);
|
|
2849
|
-
return `- ${
|
|
2842
|
+
return `- ${formatFeatureRef(f.number)}${f.shortId ? ` · ${f.shortId}` : ""} — ${f.name} (${f.status}; ${fp.length} phases, ${fp.reduce((t, p) => t + p.tasks.length, 0)} tasks)`;
|
|
2850
2843
|
}).join("\n");
|
|
2851
|
-
return { content: [{ type: "text", text: summary || "No features" }], details: {
|
|
2844
|
+
return { content: [{ type: "text", text: summary || "No features" }], details: {} };
|
|
2852
2845
|
},
|
|
2853
2846
|
});
|
|
2854
2847
|
pi.registerTool({
|
|
2855
2848
|
name: "feature_get",
|
|
2856
2849
|
label: "Feature Get",
|
|
2857
|
-
description: "
|
|
2850
|
+
description: "Show a feature. Compact identity by default (saves tokens); pass full=true to include the description.",
|
|
2858
2851
|
parameters: Type.Object({
|
|
2859
|
-
featureId: Type.String({ description: "Feature
|
|
2852
|
+
featureId: Type.String({ description: "Feature ref: F00x, shortId, UUID, or name" }),
|
|
2853
|
+
full: Type.Optional(Type.Boolean({ description: "If true, include the feature description. Default: compact identity only." })),
|
|
2860
2854
|
}),
|
|
2861
2855
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
2862
2856
|
const st = await requirePlan(ctx);
|
|
2863
2857
|
if (!st)
|
|
2864
2858
|
return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
|
|
2865
2859
|
const features = (await st.loadFeatures()).features;
|
|
2866
|
-
const
|
|
2860
|
+
const ref = params.featureId.trim();
|
|
2861
|
+
const feature = features.find((f) => formatFeatureRef(f.number) === ref || f.shortId === ref || f.id === ref || f.name.toLowerCase() === ref.toLowerCase());
|
|
2867
2862
|
if (!feature)
|
|
2868
|
-
return { content: [{ type: "text", text: `Feature not found: ${
|
|
2863
|
+
return { content: [{ type: "text", text: `Feature not found: ${ref}` }], details: {} };
|
|
2869
2864
|
const phases = (await st.loadAllPhases()).filter((p) => p.featureId === feature.id);
|
|
2870
|
-
|
|
2865
|
+
const summary = `${feature.name} — ${formatFeatureRef(feature.number)}${feature.shortId ? ` · ${feature.shortId}` : ""} (${feature.status}; ${phases.length} phases)`;
|
|
2866
|
+
return { content: [{ type: "text", text: params.full ? `${summary}\n\n${feature.description || ""}` : summary }], details: {} };
|
|
2871
2867
|
},
|
|
2872
2868
|
});
|
|
2873
2869
|
pi.registerTool({
|
|
@@ -2890,7 +2886,7 @@ export default function planPiExtension(pi) {
|
|
|
2890
2886
|
const priority = await st.nextPriority("feature");
|
|
2891
2887
|
const feature = {
|
|
2892
2888
|
id: createFeatureId(),
|
|
2893
|
-
number:
|
|
2889
|
+
number: await st.allocFeatureNumber(),
|
|
2894
2890
|
shortId,
|
|
2895
2891
|
priority,
|
|
2896
2892
|
name: params.name,
|
|
@@ -2924,7 +2920,7 @@ export default function planPiExtension(pi) {
|
|
|
2924
2920
|
label: "Feature Update",
|
|
2925
2921
|
description: "Update one or more fields of a feature. Only provided fields are changed. IMPORTANT: feature status is derived from child phases/tasks, so do not update feature.status directly unless truly necessary.",
|
|
2926
2922
|
parameters: Type.Object({
|
|
2927
|
-
featureId: Type.String({ description: "Feature
|
|
2923
|
+
featureId: Type.String({ description: "Feature ref: F00x, shortId, UUID, or name" }),
|
|
2928
2924
|
name: Type.Optional(Type.String({ description: "New name" })),
|
|
2929
2925
|
description: Type.Optional(Type.String({ description: "New description" })),
|
|
2930
2926
|
status: Type.Optional(Type.String({ description: "New status: planned|in-progress|done|blocked|canceled. Avoid setting this directly unless you truly need an override: feature status is derived from child phases/tasks." })),
|
|
@@ -2999,7 +2995,7 @@ export default function planPiExtension(pi) {
|
|
|
2999
2995
|
label: "Feature Delete",
|
|
3000
2996
|
description: "Delete a feature. Its phases are unlinked (featureId cleared) but NOT deleted unless cascade=true.",
|
|
3001
2997
|
parameters: Type.Object({
|
|
3002
|
-
featureId: Type.String({ description: "Feature
|
|
2998
|
+
featureId: Type.String({ description: "Feature ref: F00x, shortId, UUID, or name to delete" }),
|
|
3003
2999
|
cascade: Type.Optional(Type.Boolean({ description: "If true, also delete all phases belonging to this feature. Default: false" })),
|
|
3004
3000
|
}),
|
|
3005
3001
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
@@ -3033,40 +3029,52 @@ export default function planPiExtension(pi) {
|
|
|
3033
3029
|
pi.registerTool({
|
|
3034
3030
|
name: "phase_list",
|
|
3035
3031
|
label: "Phase List",
|
|
3036
|
-
description: "List
|
|
3032
|
+
description: "List phases (compact: F00x/P00x · shortId — title (status; N tasks) [F00x]). Filters: featureRef, status. Cheap discovery — do NOT read .planner/ files or plan_get full=true.",
|
|
3037
3033
|
parameters: Type.Object({
|
|
3038
|
-
|
|
3034
|
+
featureRef: Type.Optional(Type.String({ description: "Optional: filter to one feature (F00x/shortId/UUID/name)" })),
|
|
3035
|
+
status: Type.Optional(Type.String({ description: "Optional: filter by status name" })),
|
|
3039
3036
|
}),
|
|
3040
3037
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
3041
3038
|
const st = await requirePlan(ctx);
|
|
3042
3039
|
if (!st)
|
|
3043
3040
|
return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
|
|
3041
|
+
const features = (await st.loadFeatures()).features;
|
|
3044
3042
|
let phases = await st.loadAllPhases();
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3043
|
+
const fref = params.featureRef?.trim();
|
|
3044
|
+
if (fref) {
|
|
3045
|
+
const f = features.find((x) => formatFeatureRef(x.number) === fref || x.shortId === fref || x.id === fref || x.name.toLowerCase() === fref.toLowerCase());
|
|
3046
|
+
if (!f)
|
|
3047
|
+
return { content: [{ type: "text", text: `Feature not found: ${fref}` }], details: {} };
|
|
3048
|
+
phases = phases.filter((p) => p.featureId === f.id);
|
|
3049
|
+
}
|
|
3050
|
+
if (params.status)
|
|
3051
|
+
phases = phases.filter((p) => p.status === params.status);
|
|
3052
|
+
const summary = phases.map((p) => {
|
|
3053
|
+
const fNum = featureNumberOfPhase(p, features);
|
|
3054
|
+
const fTag = fNum !== undefined ? ` [F${pad(fNum)}]` : "";
|
|
3055
|
+
return `- ${formatPhaseRef(p.number, fNum)}${p.shortId ? ` · ${p.shortId}` : ""} — ${p.title} (${p.status}; ${p.tasks.length} tasks)${fTag}`;
|
|
3056
|
+
}).join("\n");
|
|
3057
|
+
return { content: [{ type: "text", text: summary || "No phases" }], details: {} };
|
|
3049
3058
|
},
|
|
3050
3059
|
});
|
|
3051
3060
|
pi.registerTool({
|
|
3052
3061
|
name: "phase_get",
|
|
3053
3062
|
label: "Phase Get",
|
|
3054
|
-
description: "
|
|
3063
|
+
description: "Show a phase. Compact identity by default (saves tokens); pass full=true to include the description.",
|
|
3055
3064
|
parameters: Type.Object({
|
|
3056
|
-
phaseId: Type.String({ description: "Phase
|
|
3065
|
+
phaseId: Type.String({ description: "Phase ref: F00x/P00x, bare P00x (global), shortId, UUID, or title" }),
|
|
3066
|
+
full: Type.Optional(Type.Boolean({ description: "If true, include the phase description. Default: compact identity only." })),
|
|
3057
3067
|
}),
|
|
3058
3068
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
3059
3069
|
const st = await requirePlan(ctx);
|
|
3060
3070
|
if (!st)
|
|
3061
3071
|
return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
}
|
|
3066
|
-
catch {
|
|
3072
|
+
const features = (await st.loadFeatures()).features;
|
|
3073
|
+
const phase = findPhaseByRef(await st.loadAllPhases(), features, params.phaseId.trim());
|
|
3074
|
+
if (!phase)
|
|
3067
3075
|
return { content: [{ type: "text", text: `Phase not found: ${params.phaseId}` }], details: {} };
|
|
3068
|
-
}
|
|
3069
|
-
return { content: [{ type: "text", text:
|
|
3076
|
+
const summary = `${phase.title} — ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, features))}${phase.shortId ? ` · ${phase.shortId}` : ""} (${phase.status}; ${phase.tasks.length} tasks)`;
|
|
3077
|
+
return { content: [{ type: "text", text: params.full ? `${summary}\n\n${phase.description || ""}` : summary }], details: {} };
|
|
3070
3078
|
},
|
|
3071
3079
|
});
|
|
3072
3080
|
pi.registerTool({
|
|
@@ -3090,7 +3098,7 @@ export default function planPiExtension(pi) {
|
|
|
3090
3098
|
await withFeatureLock(params.featureId, async () => {
|
|
3091
3099
|
const phases = await st.loadAllPhases();
|
|
3092
3100
|
const featurePhases = phases.filter((p) => p.featureId === params.featureId);
|
|
3093
|
-
const number =
|
|
3101
|
+
const number = await st.allocPhaseNumber();
|
|
3094
3102
|
const id = createPhaseId();
|
|
3095
3103
|
const shortId = createShortId(await st.assignedShortIds());
|
|
3096
3104
|
const priority = await st.nextPriority("phase", params.featureId);
|
|
@@ -3133,7 +3141,7 @@ export default function planPiExtension(pi) {
|
|
|
3133
3141
|
label: "Phase Update",
|
|
3134
3142
|
description: "Update one or more fields of a phase. Only provided fields are changed. Supports re-linking to a feature via featureId. IMPORTANT: phase status is derived from task statuses, so do not update phase.status directly unless truly necessary.",
|
|
3135
3143
|
parameters: Type.Object({
|
|
3136
|
-
phaseId: Type.String({ description: "Phase
|
|
3144
|
+
phaseId: Type.String({ description: "Phase ref: F00x/P00x, bare P00x (global), shortId, UUID, or title" }),
|
|
3137
3145
|
title: Type.Optional(Type.String({ description: "New title" })),
|
|
3138
3146
|
status: Type.Optional(Type.String({ description: "New status: draft|discovery|planned|in-progress|done|blocked|canceled. Avoid setting this directly unless you truly need an override: phase status is derived from task statuses." })),
|
|
3139
3147
|
summary: Type.Optional(Type.String({ description: "New summary" })),
|
|
@@ -3223,7 +3231,7 @@ export default function planPiExtension(pi) {
|
|
|
3223
3231
|
label: "Phase Delete",
|
|
3224
3232
|
description: "Delete a phase. Its tasks are deleted with it (cascade). Unlinks the phase from its feature.",
|
|
3225
3233
|
parameters: Type.Object({
|
|
3226
|
-
phaseId: Type.String({ description: "Phase
|
|
3234
|
+
phaseId: Type.String({ description: "Phase ref: F00x/P00x, bare P00x (global), shortId, UUID, or title to delete" }),
|
|
3227
3235
|
}),
|
|
3228
3236
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
3229
3237
|
const st = await requirePlan(ctx);
|
|
@@ -3255,42 +3263,64 @@ export default function planPiExtension(pi) {
|
|
|
3255
3263
|
pi.registerTool({
|
|
3256
3264
|
name: "task_list",
|
|
3257
3265
|
label: "Task List",
|
|
3258
|
-
description: "List tasks
|
|
3266
|
+
description: "List tasks (compact: F00x/P00x/T00x · shortId — title (status)). Filters: featureRef, phaseRef, status. Omit all to list every task. Cheap discovery — do NOT read .planner/ files or plan_get full=true.",
|
|
3259
3267
|
parameters: Type.Object({
|
|
3260
|
-
|
|
3268
|
+
featureRef: Type.Optional(Type.String({})),
|
|
3269
|
+
phaseRef: Type.Optional(Type.String({})),
|
|
3270
|
+
status: Type.Optional(Type.String({ description: "Optional: filter by status name" })),
|
|
3261
3271
|
}),
|
|
3262
3272
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
3263
3273
|
const st = await requirePlan(ctx);
|
|
3264
3274
|
if (!st)
|
|
3265
3275
|
return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3276
|
+
const features = (await st.loadFeatures()).features;
|
|
3277
|
+
let phases = await st.loadAllPhases();
|
|
3278
|
+
const fref = params.featureRef?.trim();
|
|
3279
|
+
if (fref) {
|
|
3280
|
+
const f = features.find((x) => formatFeatureRef(x.number) === fref || x.shortId === fref || x.id === fref || x.name.toLowerCase() === fref.toLowerCase());
|
|
3281
|
+
if (!f)
|
|
3282
|
+
return { content: [{ type: "text", text: `Feature not found: ${fref}` }], details: {} };
|
|
3283
|
+
phases = phases.filter((p) => p.featureId === f.id);
|
|
3284
|
+
}
|
|
3285
|
+
const pref = params.phaseRef?.trim();
|
|
3286
|
+
if (pref) {
|
|
3287
|
+
const p = findPhaseByRef(phases, features, pref);
|
|
3288
|
+
if (!p)
|
|
3289
|
+
return { content: [{ type: "text", text: `Phase not found: ${pref}` }], details: {} };
|
|
3290
|
+
phases = [p];
|
|
3291
|
+
}
|
|
3292
|
+
const out = [];
|
|
3293
|
+
for (const phase of phases) {
|
|
3294
|
+
for (const task of phase.tasks) {
|
|
3295
|
+
if (params.status && task.status !== params.status)
|
|
3296
|
+
continue;
|
|
3297
|
+
out.push(`- ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, features))}/T${pad(task.number)}${task.shortId ? ` · ${task.shortId}` : ""} — ${task.title} (${task.status})`);
|
|
3298
|
+
}
|
|
3272
3299
|
}
|
|
3273
|
-
|
|
3274
|
-
return { content: [{ type: "text", text: summary || "No tasks" }], details: { tasks: phase.tasks } };
|
|
3300
|
+
return { content: [{ type: "text", text: out.join("\n") || "No tasks" }], details: {} };
|
|
3275
3301
|
},
|
|
3276
3302
|
});
|
|
3277
3303
|
pi.registerTool({
|
|
3278
3304
|
name: "task_get",
|
|
3279
3305
|
label: "Task Get",
|
|
3280
|
-
description: "
|
|
3306
|
+
description: "Show a task. Compact identity by default (saves tokens); pass full=true to include the description + statusLog.",
|
|
3281
3307
|
parameters: Type.Object({
|
|
3282
|
-
taskId: Type.String({ description: "Task
|
|
3308
|
+
taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title" }),
|
|
3309
|
+
full: Type.Optional(Type.Boolean({ description: "If true, include the task description + statusLog. Default: compact identity only." })),
|
|
3283
3310
|
}),
|
|
3284
3311
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
3285
3312
|
const st = await requirePlan(ctx);
|
|
3286
3313
|
if (!st)
|
|
3287
3314
|
return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
|
|
3288
|
-
const
|
|
3289
|
-
const
|
|
3290
|
-
|
|
3291
|
-
if (!task || !phase)
|
|
3315
|
+
const features = (await st.loadFeatures()).features;
|
|
3316
|
+
const found = findTaskByRef(await st.loadAllPhases(), features, params.taskId.trim());
|
|
3317
|
+
if (!found)
|
|
3292
3318
|
return { content: [{ type: "text", text: `Task not found: ${params.taskId}` }], details: {} };
|
|
3293
|
-
|
|
3319
|
+
const summary = `${found.task.title} — ${formatPhaseRef(found.phase.number, featureNumberOfPhase(found.phase, features))}/T${pad(found.task.number)}${found.task.shortId ? ` · ${found.task.shortId}` : ""} (${found.task.status})`;
|
|
3320
|
+
if (!params.full)
|
|
3321
|
+
return { content: [{ type: "text", text: summary }], details: {} };
|
|
3322
|
+
const log = (found.task.statusLog ?? []).map((e) => ` - ${e.date.slice(0, 10)} ${e.title}`).join("\n");
|
|
3323
|
+
return { content: [{ type: "text", text: `${summary}\n\n${found.task.description || ""}${log ? `\n\nStatus log:\n${log}` : ""}` }], details: {} };
|
|
3294
3324
|
},
|
|
3295
3325
|
});
|
|
3296
3326
|
pi.registerTool({
|
|
@@ -3323,7 +3353,7 @@ export default function planPiExtension(pi) {
|
|
|
3323
3353
|
const initialStatus = params.status ?? "planned";
|
|
3324
3354
|
const existingPhase = await st.loadPhase(params.phaseId);
|
|
3325
3355
|
const task = {
|
|
3326
|
-
id: taskId, phaseId: params.phaseId, number:
|
|
3356
|
+
id: taskId, phaseId: params.phaseId, number: await st.allocTaskNumber(), shortId, priority, shortName,
|
|
3327
3357
|
title: params.title,
|
|
3328
3358
|
status: initialStatus,
|
|
3329
3359
|
description: params.description ?? "",
|
|
@@ -3354,7 +3384,7 @@ export default function planPiExtension(pi) {
|
|
|
3354
3384
|
label: "Task Update",
|
|
3355
3385
|
description: "Update one or more fields of a task. Only provided fields are changed. Do NOT use this tool to start or complete work; use task_start and task_complete for lifecycle transitions so startedAt/completedAt stay correct.",
|
|
3356
3386
|
parameters: Type.Object({
|
|
3357
|
-
taskId: Type.String({ description: "Task
|
|
3387
|
+
taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title" }),
|
|
3358
3388
|
title: Type.Optional(Type.String({ description: "New title" })),
|
|
3359
3389
|
status: Type.Optional(Type.String({ description: "New status: planned|in-progress|done|blocked|canceled|rejected|deferred|waiting" })),
|
|
3360
3390
|
description: Type.Optional(Type.String({ description: "New description" })),
|
|
@@ -3442,7 +3472,7 @@ export default function planPiExtension(pi) {
|
|
|
3442
3472
|
label: "Task Delete",
|
|
3443
3473
|
description: "Delete a task from its phase.",
|
|
3444
3474
|
parameters: Type.Object({
|
|
3445
|
-
taskId: Type.String({ description: "Task
|
|
3475
|
+
taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title to delete" }),
|
|
3446
3476
|
}),
|
|
3447
3477
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
3448
3478
|
const st = await requirePlan(ctx);
|
|
@@ -3468,7 +3498,7 @@ export default function planPiExtension(pi) {
|
|
|
3468
3498
|
label: "Task Start",
|
|
3469
3499
|
description: "Set a task to in-progress. Use this BEFORE starting implementation work on any task. Sets startedAt timestamp automatically.",
|
|
3470
3500
|
parameters: Type.Object({
|
|
3471
|
-
taskId: Type.String({ description: "Task
|
|
3501
|
+
taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title to start" }),
|
|
3472
3502
|
}),
|
|
3473
3503
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
3474
3504
|
const st = await requirePlan(ctx);
|
|
@@ -3506,7 +3536,7 @@ export default function planPiExtension(pi) {
|
|
|
3506
3536
|
label: "Task Complete",
|
|
3507
3537
|
description: "Mark a task as done. Sets completedAt and startedAt (if missing) automatically. Checks for unchecked checklist items and warns unless force=true.",
|
|
3508
3538
|
parameters: Type.Object({
|
|
3509
|
-
taskId: Type.String({ description: "Task
|
|
3539
|
+
taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title to complete" }),
|
|
3510
3540
|
force: Type.Optional(Type.Boolean({ description: "Skip checklist completion check. Default: false" })),
|
|
3511
3541
|
description_update: Type.Optional(Type.String({ description: "Post-hoc summary: commit hash(s), files touched, decisions made, updated code references with new line numbers. Keeps the planner alive and traceable." })),
|
|
3512
3542
|
}),
|
|
@@ -3646,6 +3676,7 @@ export default function planPiExtension(pi) {
|
|
|
3646
3676
|
// add seconds of latency on every turn if run unconditionally.
|
|
3647
3677
|
if (!plannerHeavyInitDone) {
|
|
3648
3678
|
await migrateToUuids(st);
|
|
3679
|
+
await migrateToGlobalSequence(st).catch(() => null);
|
|
3649
3680
|
await st.ensureShortIdsAndPriority().catch(() => null);
|
|
3650
3681
|
await ensureProjectLanguagePreferences(st).catch(() => null);
|
|
3651
3682
|
await maybeHealStatuses(st, ctx);
|
|
@@ -3788,7 +3819,7 @@ export default function planPiExtension(pi) {
|
|
|
3788
3819
|
"3. IMMEDIATE SYNC: Update task status AT THE EXACT MOMENT of transition. Start = task_start NOW. Done = task_complete NOW. Blocked = task_update with motivation NOW. Never batch status updates.",
|
|
3789
3820
|
"4. BLOCKED MOTIVATION: Transitions to blocked/canceled/rejected/deferred/waiting/planned(from non-planned) MUST include a detailed 'motivation' parameter. Write it as if the next person has zero context.",
|
|
3790
3821
|
"5. NO SHORTCUTS: If a tool blocks you, follow the protocol. Bypasses are for emergencies only, not for convenience.",
|
|
3791
|
-
"6. ENTITY REFERENCES
|
|
3822
|
+
"6. ENTITY REFERENCES & FAST LOOKUP: Feature/Phase/Task numbers are GLOBALLY unique (one P003, one T007 in the whole plan) — bare P00x/T00x is a valid unambiguous ref. PREFER the 5-char shortId (e.g. UUXD1) or composite F00x/P00x/T00x in chat; NEVER a raw UUID. To FIND an entity: call feature_list / phase_list / task_list (compact, ~2K tokens for the whole plan) — do NOT read .planner/*.json files or plan_get full=true (~32K tokens) to locate an entity. To ACT: pass the ref directly to the action tool (task_start/complete/update/phase_update accept F00x/P00x/T00x, bare P00x/T00x, shortId, UUID, title). To read a description: call task_get / phase_get / feature_get with full=true for that ONE entity. Tool responses already show shortId + compositeRef; reuse them.",
|
|
3792
3823
|
"═══════════════════════════════════════════════════════════════",
|
|
3793
3824
|
"",
|
|
3794
3825
|
// ── Project details ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-plan/pi-adapter",
|
|
3
|
-
"version": "0.2.19-next.
|
|
3
|
+
"version": "0.2.19-next.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Pi agent extension for Agent Plan — structured project planning in .planner/",
|
|
6
6
|
"keywords": [
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"typebox": "^1.1.24",
|
|
30
30
|
"zod": "^3.24.0",
|
|
31
|
-
"@agent-plan/core": "0.2.19-next.
|
|
32
|
-
"@agent-plan/server": "0.2.19-next.
|
|
31
|
+
"@agent-plan/core": "0.2.19-next.6",
|
|
32
|
+
"@agent-plan/server": "0.2.19-next.6"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@earendil-works/pi-coding-agent": "^0.74.0",
|