@agent-plan/core 0.2.20 → 0.2.22-next.0
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/display-status.d.ts +2 -2
- package/dist/display-status.js +2 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/package-version.d.ts +9 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +35 -0
- package/dist/plan-store.d.ts +26 -10
- package/dist/plan-store.d.ts.map +1 -1
- package/dist/plan-store.js +289 -45
- package/dist/planner-rules.d.ts +21 -0
- package/dist/planner-rules.d.ts.map +1 -0
- package/dist/planner-rules.js +58 -0
- package/dist/read-tracking.d.ts +58 -0
- package/dist/read-tracking.d.ts.map +1 -0
- package/dist/read-tracking.js +87 -0
- package/dist/recap.d.ts.map +1 -1
- package/dist/recap.js +78 -5
- package/dist/schema.d.ts +880 -21
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +27 -1
- package/dist/task-selection.d.ts +37 -0
- package/dist/task-selection.d.ts.map +1 -1
- package/dist/task-selection.js +57 -21
- package/package.json +1 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T307 (P072/F005) — tracked, deduplicated parent read-enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Analogous to the existing NO ACTIVE TASK advisory: an in-memory, per-session
|
|
5
|
+
* record of which feature/phase refs the agent has actually read (via the
|
|
6
|
+
* feature/phase/task get/show tools). Starting, resuming, or switching a task
|
|
7
|
+
* checks that the target's parent feature + phase are in the read set; a missing
|
|
8
|
+
* read yields an unavoidable (non-blocking) advisory instead of a silent skip.
|
|
9
|
+
*
|
|
10
|
+
* Dedupe: reading a phase (or any task inside it) records both its phase and
|
|
11
|
+
* feature, so 10 sibling tasks need a single feature + phase read. Switching to
|
|
12
|
+
* a different phase/feature naturally fails (that phase is not recorded). A
|
|
13
|
+
* pause invalidates the set, so resuming forces a fresh read.
|
|
14
|
+
*
|
|
15
|
+
* T319 (P072/F005) extends the set with requirements: starting/resuming/switching
|
|
16
|
+
* also requires the requirements linked to the phase (and, when present, the
|
|
17
|
+
* feature) to have been explicitly read. Requirements are NOT auto-recorded by a
|
|
18
|
+
* phase/feature/task read (per project decision: explicit separate read); they
|
|
19
|
+
* are recorded only via the requirement list tool (markRequirementRead). An empty
|
|
20
|
+
* linked-requirement list means nothing is required.
|
|
21
|
+
*/
|
|
22
|
+
let state = { features: new Set(), phases: new Set(), requirements: new Set() };
|
|
23
|
+
/** Record that a feature was read. Does NOT imply any phase was read. */
|
|
24
|
+
export function markFeatureRead(featureId) {
|
|
25
|
+
state.features.add(featureId);
|
|
26
|
+
}
|
|
27
|
+
/** Record that a phase (and, when known, its feature) was read. */
|
|
28
|
+
export function markPhaseRead(phaseId, featureId) {
|
|
29
|
+
state.phases.add(phaseId);
|
|
30
|
+
if (featureId)
|
|
31
|
+
state.features.add(featureId);
|
|
32
|
+
}
|
|
33
|
+
/** Record that a task (and, by context, its parent phase + feature) was read. */
|
|
34
|
+
export function markTaskRead(_taskId, phaseId, featureId) {
|
|
35
|
+
state.phases.add(phaseId);
|
|
36
|
+
if (featureId)
|
|
37
|
+
state.features.add(featureId);
|
|
38
|
+
}
|
|
39
|
+
/** Record that a requirement was explicitly read (via the requirement list tool). */
|
|
40
|
+
export function markRequirementRead(requirementId) {
|
|
41
|
+
state.requirements.add(requirementId);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether the target task's parent feature + phase have both been read.
|
|
45
|
+
* `featureId` is optional: an orphan phase (no feature) only requires the phase.
|
|
46
|
+
*/
|
|
47
|
+
export function hasReadParents(featureId, phaseId) {
|
|
48
|
+
const phaseOk = state.phases.has(phaseId);
|
|
49
|
+
const featureOk = featureId ? state.features.has(featureId) : true;
|
|
50
|
+
return phaseOk && featureOk;
|
|
51
|
+
}
|
|
52
|
+
/** Whether every linked requirement (phase + feature) has been explicitly read. */
|
|
53
|
+
export function hasReadRequirements(requirementIds) {
|
|
54
|
+
return requirementIds.every((id) => state.requirements.has(id));
|
|
55
|
+
}
|
|
56
|
+
/** Clear the read set (e.g. on pause, so resuming forces a fresh read). */
|
|
57
|
+
export function invalidateReads() {
|
|
58
|
+
state = { features: new Set(), phases: new Set(), requirements: new Set() };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Non-blocking advisory (mirrors the NO ACTIVE TASK pattern): empty when the
|
|
62
|
+
* target's parent feature + phase have both been read, otherwise a loud
|
|
63
|
+
* READ REQUIRED notice the agent cannot silently skip.
|
|
64
|
+
*/
|
|
65
|
+
export function parentReadAdvisory(featureId, phaseId) {
|
|
66
|
+
if (hasReadParents(featureId, phaseId))
|
|
67
|
+
return "";
|
|
68
|
+
return "\n\n⚠️ READ REQUIRED before proceeding: read the parent feature and phase (full=true) for this task before starting or resuming it. One feature+phase read covers every task in that phase; a pause/resume invalidates the read and requires re-reading.";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Non-blocking advisory (mirrors parentReadAdvisory): empty when every linked
|
|
72
|
+
* requirement (phase + feature) has been explicitly read, otherwise a loud
|
|
73
|
+
* REQUIREMENTS READ REQUIRED notice. An empty list means the phase/feature has
|
|
74
|
+
* no linked requirements, so nothing is required.
|
|
75
|
+
*/
|
|
76
|
+
export function requirementReadAdvisory(requirementIds) {
|
|
77
|
+
if (requirementIds.length === 0)
|
|
78
|
+
return "";
|
|
79
|
+
const unread = requirementIds.filter((id) => !state.requirements.has(id));
|
|
80
|
+
if (unread.length === 0)
|
|
81
|
+
return "";
|
|
82
|
+
return "\n\n⚠️ REQUIREMENTS READ REQUIRED before proceeding: read the requirements linked to this phase (and feature) before starting or resuming the task. Use the requirement list tool to read them (one read covers every task in that phase; a pause/resume invalidates the read).";
|
|
83
|
+
}
|
|
84
|
+
/** Snapshot for diagnostics/tests. */
|
|
85
|
+
export function readTrackingSnapshot() {
|
|
86
|
+
return { features: [...state.features], phases: [...state.phases], requirements: [...state.requirements] };
|
|
87
|
+
}
|
package/dist/recap.d.ts.map
CHANGED
|
@@ -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,
|
|
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,CAyPhH"}
|
package/dist/recap.js
CHANGED
|
@@ -33,6 +33,20 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
33
33
|
const totalT = allTasks.length;
|
|
34
34
|
const doneT = allTasks.filter(({ task }) => task.status === "done").length;
|
|
35
35
|
const activeT = allTasks.filter(({ task }) => task.status === "in-progress").length;
|
|
36
|
+
const checkpointedTasks = allTasks.filter(({ task }) => !["done", "canceled", "rejected"].includes(task.status) && task.pauseSnapshot);
|
|
37
|
+
const checkpointedT = checkpointedTasks.length;
|
|
38
|
+
const pendingDeviation = [...plan.project.workDeviations]
|
|
39
|
+
.filter((deviation) => deviation.state === "resume-required" || deviation.state === "resolved")
|
|
40
|
+
.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
|
|
41
|
+
.find((deviation) => allTasks.some(({ task }) => task.id === deviation.resumeTaskId
|
|
42
|
+
&& (task.status === "planned" || task.status === "waiting" || task.status === "in-progress")));
|
|
43
|
+
const pendingResume = pendingDeviation
|
|
44
|
+
? allTasks.find(({ task }) => task.id === pendingDeviation.resumeTaskId)
|
|
45
|
+
: undefined;
|
|
46
|
+
const standaloneCheckpoints = checkpointedTasks
|
|
47
|
+
.filter(({ task }) => task.id !== pendingResume?.task.id)
|
|
48
|
+
.sort((left, right) => (right.task.pauseSnapshot?.pausedAt ?? "").localeCompare(left.task.pauseSnapshot?.pausedAt ?? ""));
|
|
49
|
+
const latestStandaloneCheckpoint = standaloneCheckpoints[0];
|
|
36
50
|
// Plan is fully complete: there is work and all of it is done, nothing active.
|
|
37
51
|
// (totalT > 0 guards the empty/unstarted case from looking "complete".)
|
|
38
52
|
const planComplete = totalT > 0 && doneT === totalT && doneP === totalP && doneF === totalF;
|
|
@@ -49,18 +63,34 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
49
63
|
const featureAddCmd = cmd("/planner feature add", "planner-feature-add");
|
|
50
64
|
const phaseAddCmd = cmd("/planner phase add", "planner-phase-add");
|
|
51
65
|
const handoffShowCmd = cmd("/planner handoff show", "planner-handoff-show");
|
|
66
|
+
const featureShowCmd = cmd("/planner feature show", "planner-feature-show");
|
|
67
|
+
const phaseShowCmd = cmd("/planner phase show", "planner-phase-show");
|
|
68
|
+
const taskShowCmd = cmd("/planner task show", "planner-task-show");
|
|
52
69
|
const lines = [];
|
|
53
70
|
lines.push(italian ? "## Ripresa planner" : "## Planner recap");
|
|
54
71
|
const name = plan.project.name || "(unnamed project)";
|
|
55
72
|
lines.push(`${italian ? "Progetto" : "Project"}: ${name}${plan.project.goal ? " — " + plan.project.goal : ""}`);
|
|
56
73
|
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)`);
|
|
74
|
+
? `Avanzamento: feature ${doneF}/${totalF} completate (${activeF} attive) · fasi ${doneP}/${totalP} completate (${activeP} attive) · task ${doneT}/${totalT} completati (${activeT} attivi, ${checkpointedT} con checkpoint)`
|
|
75
|
+
: `Progress: Features ${doneF}/${totalF} done (${activeF} active) · Phases ${doneP}/${totalP} done (${activeP} active) · Tasks ${doneT}/${totalT} done (${activeT} active, ${checkpointedT} with checkpoints)`);
|
|
59
76
|
if (focusTask && focusPhase) {
|
|
60
77
|
const fr = focusFeature ? fref(focusFeature.number) : "?";
|
|
61
78
|
const pr = formatPhaseRef(focusPhase.number, focusFeature?.number);
|
|
62
79
|
const tr = tref(focusTask.task.number);
|
|
63
80
|
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${fr} — ${focusFeature?.name ?? "?"} / ${pr} — ${focusPhase.title} / ${tr} — ${focusTask.task.title} (in-progress)`);
|
|
81
|
+
lines.push("", italian
|
|
82
|
+
? `Questo task è in-progress (lavoro iniziato in una sessione precedente). Prima di continuare, rileggi il contesto completo: ${featureShowCmd} ${fr}, ${phaseShowCmd} ${pr}, ${taskShowCmd} ${tr} (full).`
|
|
83
|
+
: `⚠️ This task is in-progress (work started in a previous session). Before continuing, re-read the full context: ${featureShowCmd} ${fr}, ${phaseShowCmd} ${pr}, ${taskShowCmd} ${tr} (full).`);
|
|
84
|
+
}
|
|
85
|
+
else if (pendingResume) {
|
|
86
|
+
const feature = feats.find((entry) => entry.id === pendingResume.phase.featureId);
|
|
87
|
+
const ref = `${formatPhaseRef(pendingResume.phase.number, feature?.number)}/${tref(pendingResume.task.number)}`;
|
|
88
|
+
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${italian ? "ripresa obbligatoria" : "resume required"} — ${ref} — ${pendingResume.task.title} (${pendingResume.task.status})`);
|
|
89
|
+
}
|
|
90
|
+
else if (latestStandaloneCheckpoint) {
|
|
91
|
+
const feature = feats.find((entry) => entry.id === latestStandaloneCheckpoint.phase.featureId);
|
|
92
|
+
const ref = `${formatPhaseRef(latestStandaloneCheckpoint.phase.number, feature?.number)}/${tref(latestStandaloneCheckpoint.task.number)}`;
|
|
93
|
+
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${italian ? "checkpoint da valutare" : "checkpoint to evaluate"} — ${ref} — ${latestStandaloneCheckpoint.task.title}`);
|
|
64
94
|
}
|
|
65
95
|
else if (handoffs.length > 0) {
|
|
66
96
|
const top = handoffs[0];
|
|
@@ -74,12 +104,35 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
74
104
|
else {
|
|
75
105
|
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"}`);
|
|
76
106
|
}
|
|
107
|
+
// Planner extension rules — the agent-behavior contract for every project
|
|
108
|
+
// using the extension. Static (no timestamps), loaded from .planner/rules.json
|
|
109
|
+
// or the canonical code set. Kept out of project.json so it never diverges
|
|
110
|
+
// across worktrees/branches.
|
|
111
|
+
const extensionRules = await st.extensionRules();
|
|
112
|
+
if (extensionRules.length > 0) {
|
|
113
|
+
lines.push("", italian ? "## Regole del planner (estensione)" : "## Planner rules (extension)");
|
|
114
|
+
extensionRules.forEach((rule, i) => lines.push(`${i + 1}. ${rule}`));
|
|
115
|
+
}
|
|
77
116
|
// Next step: the phase handoff (phase.handoff) is the authoritative,
|
|
78
117
|
// actively-managed resume context. When a handoff is pending, point to it
|
|
79
118
|
// instead of surfacing potentially-stale resume.json nextSteps. When no
|
|
80
119
|
// handoff exists, fall back to resume.nextSteps but mark them as possibly
|
|
81
120
|
// stale (free-text that refreshResume never touches, so they can drift).
|
|
82
|
-
if (
|
|
121
|
+
if (pendingResume) {
|
|
122
|
+
const feature = feats.find((entry) => entry.id === pendingResume.phase.featureId);
|
|
123
|
+
const ref = `${formatPhaseRef(pendingResume.phase.number, feature?.number)}/${tref(pendingResume.task.number)}`;
|
|
124
|
+
lines.push(italian
|
|
125
|
+
? `Avviso ripresa: valuta se riprendere ${ref} con ${taskStartCmd}. Il checkpoint del task va letto prima di decidere se tornare su questo lavoro o proseguire altrove.`
|
|
126
|
+
: `Resume advisory: evaluate whether to resume ${ref} with ${taskStartCmd}. Read its checkpoint before deciding whether to return to this work or continue elsewhere.`);
|
|
127
|
+
}
|
|
128
|
+
else if (latestStandaloneCheckpoint) {
|
|
129
|
+
const feature = feats.find((entry) => entry.id === latestStandaloneCheckpoint.phase.featureId);
|
|
130
|
+
const ref = `${formatPhaseRef(latestStandaloneCheckpoint.phase.number, feature?.number)}/${tref(latestStandaloneCheckpoint.task.number)}`;
|
|
131
|
+
lines.push(italian
|
|
132
|
+
? `Avviso ripresa: valuta il checkpoint più recente, ${ref}, con ${taskStartCmd}, prima di scegliere se tornare su quel lavoro o avviarne altro.`
|
|
133
|
+
: `Resume advisory: evaluate the newest checkpoint, ${ref}, with ${taskStartCmd} before deciding whether to return to it or start other work.`);
|
|
134
|
+
}
|
|
135
|
+
else if (handoffs.length > 0) {
|
|
83
136
|
lines.push(italian
|
|
84
137
|
? `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
138
|
: `Next step: read the pending phase handoff below (authoritative, actively maintained). Legacy resume.json nextSteps are suppressed because they may be stale.`);
|
|
@@ -95,11 +148,31 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
95
148
|
: `⚠️ nextSteps are free-text from resume.json — may be stale; verify against current state before acting.`);
|
|
96
149
|
lines.push(staleNote);
|
|
97
150
|
}
|
|
151
|
+
if (pendingResume) {
|
|
152
|
+
const feature = feats.find((entry) => entry.id === pendingResume.phase.featureId);
|
|
153
|
+
const ref = `${formatPhaseRef(pendingResume.phase.number, feature?.number)}/${tref(pendingResume.task.number)}`;
|
|
154
|
+
const snapshot = pendingResume.task.pauseSnapshot ?? pendingDeviation?.snapshot;
|
|
155
|
+
lines.push("", italian ? "## Avviso ripresa task" : "## Task resume advisory", `${ref} — ${pendingResume.task.title}`, snapshot ? `${italian ? "Perché è stato salvato il checkpoint" : "Checkpoint reason"}: ${snapshot.reason}` : "", snapshot ? `${italian ? "Stato del lavoro" : "Work checkpoint"}: ${snapshot.whatWasBeingDone}` : "", snapshot ? `${italian ? "Riprendi da" : "Resume from"}: ${snapshot.resumeLocation}` : "", snapshot ? `${italian ? "Come riprendere" : "How to resume"}: ${snapshot.howToResume}` : "", italian ? `Azione suggerita: valuta ${taskStartCmd} ${ref}` : `Suggested action: evaluate ${taskStartCmd} ${ref}`);
|
|
156
|
+
}
|
|
157
|
+
if (standaloneCheckpoints.length > 0) {
|
|
158
|
+
lines.push("", italian ? `## Checkpoint salvati (${standaloneCheckpoints.length})` : `## Saved checkpoints (${standaloneCheckpoints.length})`);
|
|
159
|
+
for (const entry of standaloneCheckpoints) {
|
|
160
|
+
const feature = feats.find((item) => item.id === entry.phase.featureId);
|
|
161
|
+
const ref = `${formatPhaseRef(entry.phase.number, feature?.number)}/${tref(entry.task.number)}`;
|
|
162
|
+
const snapshot = entry.task.pauseSnapshot;
|
|
163
|
+
lines.push(`${ref} — ${entry.task.title}`, snapshot ? ` ${italian ? "Perché" : "Why"}: ${snapshot.reason}` : "", snapshot ? ` ${italian ? "Stato" : "Checkpoint"}: ${snapshot.whatWasBeingDone}` : "", snapshot ? ` ${italian ? "Riprendi da" : "Resume from"}: ${snapshot.resumeLocation}` : "", snapshot ? ` ${italian ? "Come" : "How"}: ${snapshot.howToResume}` : "");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
98
166
|
if (handoffs.length > 0) {
|
|
99
167
|
const top = handoffs[0];
|
|
100
168
|
lines.push("", italian ? `## Handoff di fase pendenti (${handoffs.length})` : `## Pending phase handoffs (${handoffs.length})`);
|
|
101
169
|
handoffs.forEach((h, i) => lines.push(`[${i + 1}] ${h.compositeRef} — ${h.updatedAt} — "${h.firstLine}"`));
|
|
102
|
-
if (
|
|
170
|
+
if (pendingResume || latestStandaloneCheckpoint) {
|
|
171
|
+
lines.push("", italian
|
|
172
|
+
? "→ Questi handoff restano disponibili come contesto, ma prima risolvi il task da riprendere indicato sopra."
|
|
173
|
+
: "→ These handoffs remain available as context, but first resolve the task resume shown above.");
|
|
174
|
+
}
|
|
175
|
+
else if (handoffs.length === 1) {
|
|
103
176
|
lines.push("", italian
|
|
104
177
|
? `→ Vuoi riprendere da ${top.compositeRef}? Leggi l'handoff con ${handoffShowCmd} ${top.compositeRef} e avvia il primo task pertinente. L'handoff resta attivo finché tutti i task della fase non sono done/canceled, viene sostituito da un nuovo handoff, oppure viene cancellato esplicitamente.`
|
|
105
178
|
: `→ Do you want to resume from ${top.compositeRef}? Read the handoff with ${handoffShowCmd} ${top.compositeRef} and start the first relevant task. It stays active until every phase task is done/canceled, a new handoff replaces it, or it is explicitly cleared.`);
|
|
@@ -115,7 +188,7 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
115
188
|
? `Piano completo — aggiungi una nuova feature (${featureAddCmd}) o fase (${phaseAddCmd}) per continuare.`
|
|
116
189
|
: `Plan complete — add a new feature (${featureAddCmd}) or phase (${phaseAddCmd}) to continue.`);
|
|
117
190
|
}
|
|
118
|
-
else if (activeT === 0) {
|
|
191
|
+
else if (activeT === 0 && !pendingResume && standaloneCheckpoints.length === 0) {
|
|
119
192
|
lines.push("", italian
|
|
120
193
|
? `Nessun handoff pendente e nessun task in-progress. Usa ${taskAddCmd} / ${taskStartCmd} per iniziare.`
|
|
121
194
|
: `No phase handoff pending and no task in-progress. Use ${taskAddCmd} / ${taskStartCmd} to begin work.`);
|