@agent-plan/core 0.2.19-next.2 → 0.2.19-next.21

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.
@@ -1 +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"}
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,CAgJhH"}
package/dist/recap.js CHANGED
@@ -49,7 +49,6 @@ export async function buildRecap(st, web = {}, opts = {}) {
49
49
  const featureAddCmd = cmd("/planner feature add", "planner-feature-add");
50
50
  const phaseAddCmd = cmd("/planner phase add", "planner-phase-add");
51
51
  const handoffShowCmd = cmd("/planner handoff show", "planner-handoff-show");
52
- const handoffClearCmd = cmd("/planner handoff clear", "planner-handoff-clear");
53
52
  const lines = [];
54
53
  lines.push(italian ? "## Ripresa planner" : "## Planner recap");
55
54
  const name = plan.project.name || "(unnamed project)";
@@ -63,6 +62,10 @@ export async function buildRecap(st, web = {}, opts = {}) {
63
62
  const tr = tref(focusTask.task.number);
64
63
  lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${fr} — ${focusFeature?.name ?? "?"} / ${pr} — ${focusPhase.title} / ${tr} — ${focusTask.task.title} (in-progress)`);
65
64
  }
65
+ else if (handoffs.length > 0) {
66
+ const top = handoffs[0];
67
+ lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${italian ? "nessun task attivo" : "no active task"} · ${italian ? "handoff pendente più recente" : "most recent pending handoff"}: ${top.compositeRef} — "${top.firstLine}"`);
68
+ }
66
69
  else if (planComplete) {
67
70
  lines.push(italian
68
71
  ? "Focus corrente: piano completo — tutte le feature/fasi/task sono concluse."
@@ -71,18 +74,41 @@ export async function buildRecap(st, web = {}, opts = {}) {
71
74
  else {
72
75
  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
76
  }
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) {
77
+ // Next step: the phase handoff (phase.handoff) is the authoritative,
78
+ // actively-managed resume context. When a handoff is pending, point to it
79
+ // instead of surfacing potentially-stale resume.json nextSteps. When no
80
+ // handoff exists, fall back to resume.nextSteps but mark them as possibly
81
+ // stale (free-text that refreshResume never touches, so they can drift).
82
+ if (handoffs.length > 0) {
83
+ lines.push(italian
84
+ ? `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.`
85
+ : `Next step: read the pending phase handoff below (authoritative, actively maintained). Legacy resume.json nextSteps are suppressed because they may be stale.`);
86
+ }
87
+ else if (!planComplete && resume?.nextSteps?.length) {
78
88
  lines.push(`${italian ? "Prossimo step" : "Next step"}: ${resume.nextSteps[0]}`);
89
+ const staleNote = resume.nextStepsUpdatedAt
90
+ ? (italian
91
+ ? `⚠️ nextSteps free-text da resume.json, ultimo aggiornamento ${resume.nextStepsUpdatedAt} — può essere stale; verifica contro lo stato attuale prima di agire.`
92
+ : `⚠️ nextSteps are free-text from resume.json, last updated ${resume.nextStepsUpdatedAt} — may be stale; verify against current state before acting.`)
93
+ : (italian
94
+ ? `⚠️ nextSteps free-text da resume.json — può essere stale; verifica contro lo stato attuale prima di agire.`
95
+ : `⚠️ nextSteps are free-text from resume.json — may be stale; verify against current state before acting.`);
96
+ lines.push(staleNote);
79
97
  }
80
98
  if (handoffs.length > 0) {
99
+ const top = handoffs[0];
81
100
  lines.push("", italian ? `## Handoff di fase pendenti (${handoffs.length})` : `## Pending phase handoffs (${handoffs.length})`);
82
101
  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).`);
102
+ if (handoffs.length === 1) {
103
+ lines.push("", italian
104
+ ? `→ Vuoi riprendere da ${top.compositeRef}? Leggi l'handoff con ${handoffShowCmd} ${top.compositeRef} e avvia il primo task pertinente. L'handoff è MANTENUTO finché un task della fase non parte (auto-archiviato) o la fase non si conclude — non serve cancellarlo a mano.`
105
+ : `→ Do you want to resume from ${top.compositeRef}? Read the handoff with ${handoffShowCmd} ${top.compositeRef} and start the first relevant task. It is KEPT until a task in that phase starts (then auto-archived) or the phase completes — no need to clear it manually.`);
106
+ }
107
+ else {
108
+ lines.push("", italian
109
+ ? `→ Scegli da quale fase ripartire. Il più recente è [1] ${top.compositeRef} — leggilo con ${handoffShowCmd} ${top.compositeRef} e avvia il primo task pertinente. Ogni handoff è MANTENUTO finché un task della fase non parte (auto-archiviato) o la fase non si conclude — non serve cancellarlo a mano.`
110
+ : `→ Choose which phase to resume from. The most recent is [1] ${top.compositeRef} — read it with ${handoffShowCmd} ${top.compositeRef} and start the first relevant task. Each handoff is KEPT until a task in that phase starts (then auto-archived) or the phase completes — no need to clear it manually.`);
111
+ }
86
112
  }
87
113
  else if (planComplete) {
88
114
  lines.push("", italian
@@ -100,6 +126,6 @@ export async function buildRecap(st, web = {}, opts = {}) {
100
126
  else {
101
127
  lines.push("", italian ? "## Web UI" : "## Web UI", `🌐 Web UI: ${italian ? "non attiva — avvia con /planner load" : "not running — start with /planner load"}`);
102
128
  }
103
- lines.push("", italian ? "Vuoi che riprendiamo da qui?" : "Do you want to resume from here?");
129
+ lines.push("", italian ? "Pronto a riprendere?" : "Ready to resume?");
104
130
  return lines.join("\n");
105
131
  }
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
@@ -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,CAgCnB"}
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;AAiBxC,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,CA2D1C"}
package/dist/refs.js CHANGED
@@ -40,3 +40,84 @@ export function findPhaseByRef(phases, features, ref) {
40
40
  return (phases.find((p) => p.title.toLowerCase() === normalized) ??
41
41
  phases.find((p) => p.title.toLowerCase().includes(normalized)));
42
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
+ // Anchored patterns only: composite refs (F00x/P00x/T00x, P00x(F00x)/T00x,
47
+ // P00x/T00x) and bare T00x. A 5-char shortId (e.g. "HT23X") or a title never
48
+ // matches these because they are anchored to the WHOLE string.
49
+ const composite = ref.match(/^f0*(\d+)\/p0*(\d+)\/t0*(\d+)$/);
50
+ if (composite)
51
+ return { featureNum: parseInt(composite[1], 10), phaseNum: parseInt(composite[2], 10), taskNum: parseInt(composite[3], 10) };
52
+ const phaseTask = ref.match(/^p0*(\d+)(?:\(f0*(\d+)\))?\/t0*(\d+)$/);
53
+ if (phaseTask)
54
+ return { phaseNum: parseInt(phaseTask[1], 10), featureNum: phaseTask[2] ? parseInt(phaseTask[2], 10) : undefined, taskNum: parseInt(phaseTask[3], 10) };
55
+ const bare = ref.match(/^t0*(\d+)$/);
56
+ if (bare)
57
+ return { taskNum: parseInt(bare[1], 10) };
58
+ return {};
59
+ }
60
+ export function findTaskByRef(phases, features, ref) {
61
+ const normalized = ref.trim().toLowerCase();
62
+ if (!normalized)
63
+ return undefined;
64
+ // 1. UUID (exact)
65
+ for (const phase of phases) {
66
+ const task = phase.tasks.find((t) => t.id.toLowerCase() === normalized);
67
+ if (task)
68
+ return { phase, task };
69
+ }
70
+ // 1b. ShortId (5-char) — check BEFORE composite so a shortId like "HT23X"
71
+ // or "T1234" is never misread as a numeric task ref (regexes are anchored now,
72
+ // but a 5-char ref can still look numeric). SHORT_ID_LENGTH is always 5.
73
+ if (normalized.length === 5) {
74
+ for (const phase of phases) {
75
+ const task = phase.tasks.find((t) => t.shortId && t.shortId.toLowerCase() === normalized);
76
+ if (task)
77
+ return { phase, task };
78
+ }
79
+ }
80
+ // 2. Composite / short ref with a T segment
81
+ const { featureNum, phaseNum, taskNum } = parseTaskComposite(normalized);
82
+ if (taskNum !== undefined) {
83
+ if (phaseNum !== undefined) {
84
+ // Composite: validate phase (and feature if present), then resolve task by number.
85
+ const phase = phases.find((p) => p.number === phaseNum);
86
+ if (!phase)
87
+ return undefined;
88
+ if (featureNum !== undefined) {
89
+ const feat = features.find((f) => f.number === featureNum);
90
+ if (!feat || phase.featureId !== feat.id)
91
+ return undefined;
92
+ }
93
+ const task = phase.tasks.find((t) => t.number === taskNum);
94
+ if (task)
95
+ return { phase, task };
96
+ return undefined;
97
+ }
98
+ // Bare T00x — globally unique under the new numbering. Find the one task.
99
+ for (const phase of phases) {
100
+ const task = phase.tasks.find((t) => t.number === taskNum);
101
+ if (task)
102
+ return { phase, task };
103
+ }
104
+ }
105
+ // 3. ShortId (5-char)
106
+ for (const phase of phases) {
107
+ const task = phase.tasks.find((t) => t.shortId && t.shortId.toLowerCase() === normalized);
108
+ if (task)
109
+ return { phase, task };
110
+ }
111
+ // 4. Title fallback (backward compatibility)
112
+ for (const phase of phases) {
113
+ const exact = phase.tasks.find((t) => t.title.toLowerCase() === normalized);
114
+ if (exact)
115
+ return { phase, task: exact };
116
+ }
117
+ for (const phase of phases) {
118
+ const incl = phase.tasks.find((t) => t.title.toLowerCase().includes(normalized));
119
+ if (incl)
120
+ return { phase, task: incl };
121
+ }
122
+ return undefined;
123
+ }