@agent-plan/core 0.2.19-next.0 → 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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/naming.d.ts +9 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +8 -0
- package/dist/plan-store.d.ts +92 -16
- package/dist/plan-store.d.ts.map +1 -1
- package/dist/plan-store.js +376 -214
- package/dist/recap.d.ts +32 -0
- package/dist/recap.d.ts.map +1 -0
- package/dist/recap.js +119 -0
- package/dist/refs.d.ts +23 -0
- package/dist/refs.d.ts.map +1 -1
- package/dist/refs.js +72 -1
- package/dist/schema.d.ts +152 -27
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +33 -2
- package/package.json +1 -1
package/dist/recap.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PlanStore } from "./plan-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* Web UI info for the recap (harness-agnostic — no module globals).
|
|
4
|
+
* Pass { localUrl, lanUrl, port } from whichever harness runs the server.
|
|
5
|
+
* Omit/empty → the recap notes the dashboard is not running.
|
|
6
|
+
*/
|
|
7
|
+
export interface RecapWebInfo {
|
|
8
|
+
localUrl?: string | undefined;
|
|
9
|
+
lanUrl?: string | undefined;
|
|
10
|
+
port?: number | undefined;
|
|
11
|
+
}
|
|
12
|
+
/** Which harness is presenting the recap — drives command-name hints. */
|
|
13
|
+
export type RecapHarness = "pi" | "mcp";
|
|
14
|
+
export interface RecapOptions {
|
|
15
|
+
harness?: RecapHarness;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build a consolidated planner recap (project state + active focus + pending
|
|
19
|
+
* handoffs + web URL). SHARED across all harnesses (Pi command, Pi tool,
|
|
20
|
+
* Claude Code/Codex MCP) so every adapter presents identical content.
|
|
21
|
+
*
|
|
22
|
+
* English by default; Italian if project.chatLanguage is set to Italian.
|
|
23
|
+
* Uses human-readable names/titles plus composite IDs (F00x / P00x(F00x) / T00x)
|
|
24
|
+
* so the user/agent can reference entities unambiguously.
|
|
25
|
+
*
|
|
26
|
+
* Pass opts.harness to get harness-correct command hints in the actionable
|
|
27
|
+
* lines (e.g. "/planner task start" in Pi vs "planner-task-start" in MCP).
|
|
28
|
+
* When the plan is fully complete, stale resume.nextSteps are suppressed and a
|
|
29
|
+
* "plan complete — add a feature/phase to continue" hint is shown instead.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildRecap(st: PlanStore, web?: RecapWebInfo, opts?: RecapOptions): Promise<string>;
|
|
32
|
+
//# sourceMappingURL=recap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../src/recap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,KAAK,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,GAAE,YAAiB,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8HhH"}
|
package/dist/recap.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { formatPhaseRef, formatTwoDigitNumber } from "./naming.js";
|
|
2
|
+
const fref = (n) => `F${formatTwoDigitNumber(n)}`;
|
|
3
|
+
const tref = (n) => `T${formatTwoDigitNumber(n)}`;
|
|
4
|
+
/**
|
|
5
|
+
* Build a consolidated planner recap (project state + active focus + pending
|
|
6
|
+
* handoffs + web URL). SHARED across all harnesses (Pi command, Pi tool,
|
|
7
|
+
* Claude Code/Codex MCP) so every adapter presents identical content.
|
|
8
|
+
*
|
|
9
|
+
* English by default; Italian if project.chatLanguage is set to Italian.
|
|
10
|
+
* Uses human-readable names/titles plus composite IDs (F00x / P00x(F00x) / T00x)
|
|
11
|
+
* so the user/agent can reference entities unambiguously.
|
|
12
|
+
*
|
|
13
|
+
* Pass opts.harness to get harness-correct command hints in the actionable
|
|
14
|
+
* lines (e.g. "/planner task start" in Pi vs "planner-task-start" in MCP).
|
|
15
|
+
* When the plan is fully complete, stale resume.nextSteps are suppressed and a
|
|
16
|
+
* "plan complete — add a feature/phase to continue" hint is shown instead.
|
|
17
|
+
*/
|
|
18
|
+
export async function buildRecap(st, web = {}, opts = {}) {
|
|
19
|
+
const [plan, resume, handoffs] = await Promise.all([
|
|
20
|
+
st.loadAll(),
|
|
21
|
+
st.loadResume().catch(() => null),
|
|
22
|
+
st.listHandoffs(),
|
|
23
|
+
]);
|
|
24
|
+
const feats = plan.features.features;
|
|
25
|
+
const phases = plan.phases;
|
|
26
|
+
const allTasks = phases.flatMap((p) => p.tasks.map((task) => ({ phase: p, task })));
|
|
27
|
+
const totalF = feats.length;
|
|
28
|
+
const doneF = feats.filter((x) => x.status === "done").length;
|
|
29
|
+
const activeF = feats.filter((x) => x.status === "in-progress").length;
|
|
30
|
+
const totalP = phases.length;
|
|
31
|
+
const doneP = phases.filter((x) => x.status === "done").length;
|
|
32
|
+
const activeP = phases.filter((x) => x.status === "in-progress" || x.status === "discovery").length;
|
|
33
|
+
const totalT = allTasks.length;
|
|
34
|
+
const doneT = allTasks.filter(({ task }) => task.status === "done").length;
|
|
35
|
+
const activeT = allTasks.filter(({ task }) => task.status === "in-progress").length;
|
|
36
|
+
// Plan is fully complete: there is work and all of it is done, nothing active.
|
|
37
|
+
// (totalT > 0 guards the empty/unstarted case from looking "complete".)
|
|
38
|
+
const planComplete = totalT > 0 && doneT === totalT && doneP === totalP && doneF === totalF;
|
|
39
|
+
// Current focus = first in-progress task (and its phase/feature).
|
|
40
|
+
const focusTask = allTasks.find(({ task }) => task.status === "in-progress");
|
|
41
|
+
const focusPhase = focusTask?.phase;
|
|
42
|
+
const focusFeature = focusPhase ? feats.find((x) => x.id === focusPhase.featureId) : undefined;
|
|
43
|
+
const italian = (plan.project.chatLanguage || "").toLowerCase().includes("ital");
|
|
44
|
+
// Harness-aware command names for actionable hints.
|
|
45
|
+
const isPi = opts.harness === "pi";
|
|
46
|
+
const cmd = (piCmd, mcpCmd) => (isPi ? piCmd : mcpCmd);
|
|
47
|
+
const taskAddCmd = cmd("/planner task add", "planner-task-add");
|
|
48
|
+
const taskStartCmd = cmd("/planner task start", "planner-task-start");
|
|
49
|
+
const featureAddCmd = cmd("/planner feature add", "planner-feature-add");
|
|
50
|
+
const phaseAddCmd = cmd("/planner phase add", "planner-phase-add");
|
|
51
|
+
const handoffShowCmd = cmd("/planner handoff show", "planner-handoff-show");
|
|
52
|
+
const lines = [];
|
|
53
|
+
lines.push(italian ? "## Ripresa planner" : "## Planner recap");
|
|
54
|
+
const name = plan.project.name || "(unnamed project)";
|
|
55
|
+
lines.push(`${italian ? "Progetto" : "Project"}: ${name}${plan.project.goal ? " — " + plan.project.goal : ""}`);
|
|
56
|
+
lines.push(italian
|
|
57
|
+
? `Avanzamento: feature ${doneF}/${totalF} completate (${activeF} attive) · fasi ${doneP}/${totalP} completate (${activeP} attive) · task ${doneT}/${totalT} completati (${activeT} attivi)`
|
|
58
|
+
: `Progress: Features ${doneF}/${totalF} done (${activeF} active) · Phases ${doneP}/${totalP} done (${activeP} active) · Tasks ${doneT}/${totalT} done (${activeT} active)`);
|
|
59
|
+
if (focusTask && focusPhase) {
|
|
60
|
+
const fr = focusFeature ? fref(focusFeature.number) : "?";
|
|
61
|
+
const pr = formatPhaseRef(focusPhase.number, focusFeature?.number);
|
|
62
|
+
const tr = tref(focusTask.task.number);
|
|
63
|
+
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${fr} — ${focusFeature?.name ?? "?"} / ${pr} — ${focusPhase.title} / ${tr} — ${focusTask.task.title} (in-progress)`);
|
|
64
|
+
}
|
|
65
|
+
else if (planComplete) {
|
|
66
|
+
lines.push(italian
|
|
67
|
+
? "Focus corrente: piano completo — tutte le feature/fasi/task sono concluse."
|
|
68
|
+
: "Current focus: plan complete — all features/phases/tasks are done.");
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${italian ? "nessun task attivo — rivedi il piano e scegli il prossimo task concreto" : "no active task — review the plan and pick the next concrete task"}`);
|
|
72
|
+
}
|
|
73
|
+
// Next step: the phase handoff (phase.handoff) is the authoritative,
|
|
74
|
+
// actively-managed resume context. When a handoff is pending, point to it
|
|
75
|
+
// instead of surfacing potentially-stale resume.json nextSteps. When no
|
|
76
|
+
// handoff exists, fall back to resume.nextSteps but mark them as possibly
|
|
77
|
+
// stale (free-text that refreshResume never touches, so they can drift).
|
|
78
|
+
if (handoffs.length > 0) {
|
|
79
|
+
lines.push(italian
|
|
80
|
+
? `Prossimo step: leggi l'handoff di fase pendente sotto (fonte autorevole, gestita attivamente). I nextSteps legacy di resume.json sono soppressi perché possono essere stale.`
|
|
81
|
+
: `Next step: read the pending phase handoff below (authoritative, actively maintained). Legacy resume.json nextSteps are suppressed because they may be stale.`);
|
|
82
|
+
}
|
|
83
|
+
else if (!planComplete && resume?.nextSteps?.length) {
|
|
84
|
+
lines.push(`${italian ? "Prossimo step" : "Next step"}: ${resume.nextSteps[0]}`);
|
|
85
|
+
const staleNote = resume.nextStepsUpdatedAt
|
|
86
|
+
? (italian
|
|
87
|
+
? `⚠️ nextSteps free-text da resume.json, ultimo aggiornamento ${resume.nextStepsUpdatedAt} — può essere stale; verifica contro lo stato attuale prima di agire.`
|
|
88
|
+
: `⚠️ nextSteps are free-text from resume.json, last updated ${resume.nextStepsUpdatedAt} — may be stale; verify against current state before acting.`)
|
|
89
|
+
: (italian
|
|
90
|
+
? `⚠️ nextSteps free-text da resume.json — può essere stale; verifica contro lo stato attuale prima di agire.`
|
|
91
|
+
: `⚠️ nextSteps are free-text from resume.json — may be stale; verify against current state before acting.`);
|
|
92
|
+
lines.push(staleNote);
|
|
93
|
+
}
|
|
94
|
+
if (handoffs.length > 0) {
|
|
95
|
+
lines.push("", italian ? `## Handoff di fase pendenti (${handoffs.length})` : `## Pending phase handoffs (${handoffs.length})`);
|
|
96
|
+
handoffs.forEach((h, i) => lines.push(`[${i + 1}] ${h.compositeRef} — ${h.updatedAt} — "${h.firstLine}"`));
|
|
97
|
+
lines.push("", italian
|
|
98
|
+
? `→ Leggi quello pertinente con ${handoffShowCmd} <ref> (valida contro lo stato attuale). L'handoff è MANTENUTO finché un task della fase non parte (auto-archiviato) o la fase non si conclude — non serve cancellarlo a mano.`
|
|
99
|
+
: `→ Read the relevant one with ${handoffShowCmd} <ref> (validate against current state). It is KEPT until a task in that phase starts (then auto-archived) or the phase completes — no need to clear it manually.`);
|
|
100
|
+
}
|
|
101
|
+
else if (planComplete) {
|
|
102
|
+
lines.push("", italian
|
|
103
|
+
? `Piano completo — aggiungi una nuova feature (${featureAddCmd}) o fase (${phaseAddCmd}) per continuare.`
|
|
104
|
+
: `Plan complete — add a new feature (${featureAddCmd}) or phase (${phaseAddCmd}) to continue.`);
|
|
105
|
+
}
|
|
106
|
+
else if (activeT === 0) {
|
|
107
|
+
lines.push("", italian
|
|
108
|
+
? `Nessun handoff pendente e nessun task in-progress. Usa ${taskAddCmd} / ${taskStartCmd} per iniziare.`
|
|
109
|
+
: `No phase handoff pending and no task in-progress. Use ${taskAddCmd} / ${taskStartCmd} to begin work.`);
|
|
110
|
+
}
|
|
111
|
+
if (web.localUrl) {
|
|
112
|
+
lines.push("", italian ? "## Web UI" : "## Web UI", `🌐 Web UI: ${web.localUrl}${web.lanUrl ? " (LAN: " + web.lanUrl + ")" : ""}${web.port ? " (port " + web.port + ")" : ""}`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
lines.push("", italian ? "## Web UI" : "## Web UI", `🌐 Web UI: ${italian ? "non attiva — avvia con /planner load" : "not running — start with /planner load"}`);
|
|
116
|
+
}
|
|
117
|
+
lines.push("", italian ? "Vuoi che riprendiamo da qui?" : "Do you want to resume from here?");
|
|
118
|
+
return lines.join("\n");
|
|
119
|
+
}
|
package/dist/refs.d.ts
CHANGED
|
@@ -21,4 +21,27 @@ import type { Phase, Feature } from "./schema.js";
|
|
|
21
21
|
* @param ref the human-facing ref (P00x / P00x(F00x) / UUID / title)
|
|
22
22
|
*/
|
|
23
23
|
export declare function findPhaseByRef(phases: Phase[], features: Feature[], ref: string): Phase | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a task reference to { phase, task }. Harness-agnostic, shared by all
|
|
26
|
+
* adapters so resolution semantics are identical everywhere.
|
|
27
|
+
*
|
|
28
|
+
* Supported task refs (case-insensitive):
|
|
29
|
+
* - UUID: "bd6ed366-..." -> task.id
|
|
30
|
+
* - Short: "T003" | "t3" -> task.number (GLOBALLY unique under the
|
|
31
|
+
* new global-sequence numbering; bare T00x
|
|
32
|
+
* is unambiguous across all phases)
|
|
33
|
+
* - Compos: "F001/P002/T003" -> feature/phase/task numbers with parent
|
|
34
|
+
* "P002(F001)/T003" validation (feature + phase must match)
|
|
35
|
+
* "P002/T003"
|
|
36
|
+
* - ShortId: "UUXD1" -> task.shortId (5-char, globally unique)
|
|
37
|
+
* - Title: exact match, then includes (backward-compat fallback)
|
|
38
|
+
*
|
|
39
|
+
* Returns `undefined` when not found, or when a composite parent (phase/feature)
|
|
40
|
+
* does not match the resolved task's parent.
|
|
41
|
+
*/
|
|
42
|
+
import type { Task } from "./schema.js";
|
|
43
|
+
export declare function findTaskByRef(phases: Phase[], features: Feature[], ref: string): {
|
|
44
|
+
phase: Phase;
|
|
45
|
+
task: Task;
|
|
46
|
+
} | undefined;
|
|
24
47
|
//# sourceMappingURL=refs.d.ts.map
|
package/dist/refs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../src/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAKlD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV,KAAK,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../src/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAKlD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV,KAAK,GAAG,SAAS,CAgCnB;AACD;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAexC,wBAAgB,aAAa,CAC3B,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG,SAAS,CAiD1C"}
|
package/dist/refs.js
CHANGED
|
@@ -31,7 +31,78 @@ export function findPhaseByRef(phases, features, ref) {
|
|
|
31
31
|
if (found)
|
|
32
32
|
return found;
|
|
33
33
|
}
|
|
34
|
-
// 3.
|
|
34
|
+
// 3. ShortId
|
|
35
|
+
found = phases.find((p) => p.shortId && p.shortId.toLowerCase() === normalized);
|
|
36
|
+
if (found)
|
|
37
|
+
return found;
|
|
38
|
+
// 4. Title fallback (backward compatibility with agents using titles)
|
|
39
|
+
// 5. Title fallback (backward compatibility with agents using titles)
|
|
35
40
|
return (phases.find((p) => p.title.toLowerCase() === normalized) ??
|
|
36
41
|
phases.find((p) => p.title.toLowerCase().includes(normalized)));
|
|
37
42
|
}
|
|
43
|
+
// Extract optional feature/phase/task numbers from a composite ref.
|
|
44
|
+
// Accepts 1+ digits so "t3" == "t003". Numbers are GLOBAL (post-migration).
|
|
45
|
+
function parseTaskComposite(ref) {
|
|
46
|
+
const t = ref.match(/t0*(\d+)/);
|
|
47
|
+
const p = ref.match(/p0*(\d+)/);
|
|
48
|
+
const f = ref.match(/f0*(\d+)/);
|
|
49
|
+
return {
|
|
50
|
+
taskNum: t ? parseInt(t[1], 10) : undefined,
|
|
51
|
+
phaseNum: p ? parseInt(p[1], 10) : undefined,
|
|
52
|
+
featureNum: f ? parseInt(f[1], 10) : undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function findTaskByRef(phases, features, ref) {
|
|
56
|
+
const normalized = ref.trim().toLowerCase();
|
|
57
|
+
if (!normalized)
|
|
58
|
+
return undefined;
|
|
59
|
+
// 1. UUID (exact)
|
|
60
|
+
for (const phase of phases) {
|
|
61
|
+
const task = phase.tasks.find((t) => t.id.toLowerCase() === normalized);
|
|
62
|
+
if (task)
|
|
63
|
+
return { phase, task };
|
|
64
|
+
}
|
|
65
|
+
// 2. Composite / short ref with a T segment
|
|
66
|
+
const { featureNum, phaseNum, taskNum } = parseTaskComposite(normalized);
|
|
67
|
+
if (taskNum !== undefined) {
|
|
68
|
+
if (phaseNum !== undefined) {
|
|
69
|
+
// Composite: validate phase (and feature if present), then resolve task by number.
|
|
70
|
+
const phase = phases.find((p) => p.number === phaseNum);
|
|
71
|
+
if (!phase)
|
|
72
|
+
return undefined;
|
|
73
|
+
if (featureNum !== undefined) {
|
|
74
|
+
const feat = features.find((f) => f.number === featureNum);
|
|
75
|
+
if (!feat || phase.featureId !== feat.id)
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const task = phase.tasks.find((t) => t.number === taskNum);
|
|
79
|
+
if (task)
|
|
80
|
+
return { phase, task };
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
// Bare T00x — globally unique under the new numbering. Find the one task.
|
|
84
|
+
for (const phase of phases) {
|
|
85
|
+
const task = phase.tasks.find((t) => t.number === taskNum);
|
|
86
|
+
if (task)
|
|
87
|
+
return { phase, task };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// 3. ShortId (5-char)
|
|
91
|
+
for (const phase of phases) {
|
|
92
|
+
const task = phase.tasks.find((t) => t.shortId && t.shortId.toLowerCase() === normalized);
|
|
93
|
+
if (task)
|
|
94
|
+
return { phase, task };
|
|
95
|
+
}
|
|
96
|
+
// 4. Title fallback (backward compatibility)
|
|
97
|
+
for (const phase of phases) {
|
|
98
|
+
const exact = phase.tasks.find((t) => t.title.toLowerCase() === normalized);
|
|
99
|
+
if (exact)
|
|
100
|
+
return { phase, task: exact };
|
|
101
|
+
}
|
|
102
|
+
for (const phase of phases) {
|
|
103
|
+
const incl = phase.tasks.find((t) => t.title.toLowerCase().includes(normalized));
|
|
104
|
+
if (incl)
|
|
105
|
+
return { phase, task: incl };
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|