@agent-plan/core 0.2.18 → 0.2.19-next.1
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 +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/naming.d.ts +22 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +36 -1
- package/dist/plan-store.d.ts +70 -5
- package/dist/plan-store.d.ts.map +1 -1
- package/dist/plan-store.js +406 -107
- package/dist/recap.d.ts +32 -0
- package/dist/recap.d.ts.map +1 -0
- package/dist/recap.js +105 -0
- package/dist/refs.d.ts +24 -0
- package/dist/refs.d.ts.map +1 -0
- package/dist/refs.js +42 -0
- package/dist/schema.d.ts +117 -27
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +13 -2
- package/package.json +3 -2
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,CA+GhH"}
|
package/dist/recap.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
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 handoffClearCmd = cmd("/planner handoff clear", "planner-handoff-clear");
|
|
53
|
+
const lines = [];
|
|
54
|
+
lines.push(italian ? "## Ripresa planner" : "## Planner recap");
|
|
55
|
+
const name = plan.project.name || "(unnamed project)";
|
|
56
|
+
lines.push(`${italian ? "Progetto" : "Project"}: ${name}${plan.project.goal ? " — " + plan.project.goal : ""}`);
|
|
57
|
+
lines.push(italian
|
|
58
|
+
? `Avanzamento: feature ${doneF}/${totalF} completate (${activeF} attive) · fasi ${doneP}/${totalP} completate (${activeP} attive) · task ${doneT}/${totalT} completati (${activeT} attivi)`
|
|
59
|
+
: `Progress: Features ${doneF}/${totalF} done (${activeF} active) · Phases ${doneP}/${totalP} done (${activeP} active) · Tasks ${doneT}/${totalT} done (${activeT} active)`);
|
|
60
|
+
if (focusTask && focusPhase) {
|
|
61
|
+
const fr = focusFeature ? fref(focusFeature.number) : "?";
|
|
62
|
+
const pr = formatPhaseRef(focusPhase.number, focusFeature?.number);
|
|
63
|
+
const tr = tref(focusTask.task.number);
|
|
64
|
+
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${fr} — ${focusFeature?.name ?? "?"} / ${pr} — ${focusPhase.title} / ${tr} — ${focusTask.task.title} (in-progress)`);
|
|
65
|
+
}
|
|
66
|
+
else if (planComplete) {
|
|
67
|
+
lines.push(italian
|
|
68
|
+
? "Focus corrente: piano completo — tutte le feature/fasi/task sono concluse."
|
|
69
|
+
: "Current focus: plan complete — all features/phases/tasks are done.");
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
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"}`);
|
|
73
|
+
}
|
|
74
|
+
// Next step: only surface resume.nextSteps when the plan is NOT complete —
|
|
75
|
+
// otherwise stale init-time steps (e.g. "bootstrap discovery") leak through
|
|
76
|
+
// and contradict an all-done plan.
|
|
77
|
+
if (!planComplete && resume?.nextSteps?.length) {
|
|
78
|
+
lines.push(`${italian ? "Prossimo step" : "Next step"}: ${resume.nextSteps[0]}`);
|
|
79
|
+
}
|
|
80
|
+
if (handoffs.length > 0) {
|
|
81
|
+
lines.push("", italian ? `## Handoff di fase pendenti (${handoffs.length})` : `## Pending phase handoffs (${handoffs.length})`);
|
|
82
|
+
handoffs.forEach((h, i) => lines.push(`[${i + 1}] ${h.compositeRef} — ${h.updatedAt} — "${h.firstLine}"`));
|
|
83
|
+
lines.push("", italian
|
|
84
|
+
? `→ Leggi quello pertinente con ${handoffShowCmd} <ref> (valida contro lo stato attuale), poi ${handoffClearCmd} <ref> una volta consumato.`
|
|
85
|
+
: `→ Read the relevant one with ${handoffShowCmd} <ref> (validate against current state), then call ${handoffClearCmd} <ref> once consumed (delete-on-resume).`);
|
|
86
|
+
}
|
|
87
|
+
else if (planComplete) {
|
|
88
|
+
lines.push("", italian
|
|
89
|
+
? `Piano completo — aggiungi una nuova feature (${featureAddCmd}) o fase (${phaseAddCmd}) per continuare.`
|
|
90
|
+
: `Plan complete — add a new feature (${featureAddCmd}) or phase (${phaseAddCmd}) to continue.`);
|
|
91
|
+
}
|
|
92
|
+
else if (activeT === 0) {
|
|
93
|
+
lines.push("", italian
|
|
94
|
+
? `Nessun handoff pendente e nessun task in-progress. Usa ${taskAddCmd} / ${taskStartCmd} per iniziare.`
|
|
95
|
+
: `No phase handoff pending and no task in-progress. Use ${taskAddCmd} / ${taskStartCmd} to begin work.`);
|
|
96
|
+
}
|
|
97
|
+
if (web.localUrl) {
|
|
98
|
+
lines.push("", italian ? "## Web UI" : "## Web UI", `🌐 Web UI: ${web.localUrl}${web.lanUrl ? " (LAN: " + web.lanUrl + ")" : ""}${web.port ? " (port " + web.port + ")" : ""}`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
lines.push("", italian ? "## Web UI" : "## Web UI", `🌐 Web UI: ${italian ? "non attiva — avvia con /planner load" : "not running — start with /planner load"}`);
|
|
102
|
+
}
|
|
103
|
+
lines.push("", italian ? "Vuoi che riprendiamo da qui?" : "Do you want to resume from here?");
|
|
104
|
+
return lines.join("\n");
|
|
105
|
+
}
|
package/dist/refs.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness-agnostic reference resolution for planner entities.
|
|
3
|
+
*
|
|
4
|
+
* Used by adapters (pi-adapter, plan-mcp) and any future harness to resolve a
|
|
5
|
+
* human-facing ref to a concrete entity. Kept in @agent-plan/core so every
|
|
6
|
+
* adapter shares the exact same resolution semantics.
|
|
7
|
+
*
|
|
8
|
+
* Supported phase refs (case-insensitive):
|
|
9
|
+
* - UUID: "bd6ed366-..." -> phase.id
|
|
10
|
+
* - Short: "P001" | "p1" -> phase.number (globally unique)
|
|
11
|
+
* - Compos: "P002(F001)" -> phase.number with parent feature validation
|
|
12
|
+
* - Title: exact match, then includes (backward-compat fallback)
|
|
13
|
+
*/
|
|
14
|
+
import type { Phase, Feature } from "./schema.js";
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a phase reference to a Phase. Returns `undefined` when not found or
|
|
17
|
+
* when a composite (F00x) parent does not match the phase's featureId.
|
|
18
|
+
*
|
|
19
|
+
* @param phases all phases (st.loadAllPhases())
|
|
20
|
+
* @param features all features (st.loadAllFeatures() / loadFeatures())
|
|
21
|
+
* @param ref the human-facing ref (P00x / P00x(F00x) / UUID / title)
|
|
22
|
+
*/
|
|
23
|
+
export declare function findPhaseByRef(phases: Phase[], features: Feature[], ref: string): Phase | undefined;
|
|
24
|
+
//# sourceMappingURL=refs.d.ts.map
|
|
@@ -0,0 +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,CAgCnB"}
|
package/dist/refs.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// P00x or P00x(F00x) — accept 1+ digits so "p1" == "p001".
|
|
2
|
+
const PHASE_REF_RE = /^p(\d+)(?:\(f(\d+)\))?$/;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a phase reference to a Phase. Returns `undefined` when not found or
|
|
5
|
+
* when a composite (F00x) parent does not match the phase's featureId.
|
|
6
|
+
*
|
|
7
|
+
* @param phases all phases (st.loadAllPhases())
|
|
8
|
+
* @param features all features (st.loadAllFeatures() / loadFeatures())
|
|
9
|
+
* @param ref the human-facing ref (P00x / P00x(F00x) / UUID / title)
|
|
10
|
+
*/
|
|
11
|
+
export function findPhaseByRef(phases, features, ref) {
|
|
12
|
+
const normalized = ref.trim().toLowerCase();
|
|
13
|
+
if (!normalized)
|
|
14
|
+
return undefined;
|
|
15
|
+
// 1. UUID (exact)
|
|
16
|
+
let found = phases.find((p) => p.id.toLowerCase() === normalized);
|
|
17
|
+
if (found)
|
|
18
|
+
return found;
|
|
19
|
+
// 2. Composite / short ref: P00x or P00x(F00x)
|
|
20
|
+
const m = normalized.match(PHASE_REF_RE);
|
|
21
|
+
if (m) {
|
|
22
|
+
const phaseNum = parseInt(m[1], 10);
|
|
23
|
+
found = phases.find((p) => p.number === phaseNum);
|
|
24
|
+
if (found && m[2]) {
|
|
25
|
+
// validate parent feature when the (F00x) disambiguator is present
|
|
26
|
+
const featureNum = parseInt(m[2], 10);
|
|
27
|
+
const feat = features.find((f) => f.number === featureNum);
|
|
28
|
+
if (!feat || found.featureId !== feat.id)
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
if (found)
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
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)
|
|
40
|
+
return (phases.find((p) => p.title.toLowerCase() === normalized) ??
|
|
41
|
+
phases.find((p) => p.title.toLowerCase().includes(normalized)));
|
|
42
|
+
}
|