@agent-plan/core 0.2.19-next.2 → 0.2.19-next.4
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/plan-store.d.ts +26 -12
- package/dist/plan-store.d.ts.map +1 -1
- package/dist/plan-store.js +93 -49
- package/dist/recap.d.ts.map +1 -1
- package/dist/recap.js +21 -7
- package/dist/schema.d.ts +105 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +21 -0
- package/package.json +1 -1
package/dist/plan-store.d.ts
CHANGED
|
@@ -63,7 +63,6 @@ export declare class PlanStore {
|
|
|
63
63
|
private codebasePath;
|
|
64
64
|
private resumePath;
|
|
65
65
|
private activityPath;
|
|
66
|
-
private handoffPath;
|
|
67
66
|
init(projectName: string): Promise<void>;
|
|
68
67
|
exists(): Promise<boolean>;
|
|
69
68
|
loadManifest(): Promise<Manifest>;
|
|
@@ -90,14 +89,6 @@ export declare class PlanStore {
|
|
|
90
89
|
/** True when a guard bypass is currently active (not expired). */
|
|
91
90
|
isGuardBypassed(): Promise<boolean>;
|
|
92
91
|
loadActivityLog(): Promise<ActivityLog>;
|
|
93
|
-
handoffExists(): Promise<boolean>;
|
|
94
|
-
loadHandoff(): Promise<{
|
|
95
|
-
content: string;
|
|
96
|
-
createdAt: string;
|
|
97
|
-
updatedAt: string;
|
|
98
|
-
} | null>;
|
|
99
|
-
saveHandoff(content: string): Promise<void>;
|
|
100
|
-
deleteHandoff(): Promise<void>;
|
|
101
92
|
appendActivity(type: string, ref: string, summary: string): Promise<ActivityEntry>;
|
|
102
93
|
/** Derive an up-to-date resume focus from the current workspace state. */
|
|
103
94
|
refreshResume(notes?: string, lastSessionSummary?: string): Promise<ResumeFocus>;
|
|
@@ -188,9 +179,32 @@ export declare class PlanStore {
|
|
|
188
179
|
/** Set the handoff text for a phase + stamp handoffUpdatedAt. Atomic per-file
|
|
189
180
|
* update via updatePhase (so the web UI refreshes via maybeAutoSync). */
|
|
190
181
|
setPhaseHandoff(phaseId: string, text: string): Promise<void>;
|
|
191
|
-
/**
|
|
192
|
-
*
|
|
193
|
-
|
|
182
|
+
/** Directory where cleared handoff content is archived as .md files
|
|
183
|
+
* (gitignored). Keeps the phase JSON lean while making past handoffs
|
|
184
|
+
* recoverable + human-readable. */
|
|
185
|
+
private handoffArchiveDir;
|
|
186
|
+
/** Mark the phase handoff as read/acknowledged on recap (sets handoffReadAt).
|
|
187
|
+
* Does NOT clear the handoff — content is kept until a task starts or the
|
|
188
|
+
* phase completes, so a restart between read and resume does not lose it. */
|
|
189
|
+
markHandoffRead(phaseId: string): Promise<void>;
|
|
190
|
+
/** One-time import of a legacy .planner/HANDOFF.md file (file-based handoff
|
|
191
|
+
* from before F004) into the entity-scoped phase.handoff. Idempotent: if the
|
|
192
|
+
* file is absent or empty, no-op. If it exists + non-empty + the target phase
|
|
193
|
+
* has no handoff, writes the content onto the current in-progress phase (or
|
|
194
|
+
* the first phase if none in-progress) with an "imported" handoffHistory entry,
|
|
195
|
+
* then renames the file to HANDOFF.md.bak so it won't re-import. If the target
|
|
196
|
+
* already has a handoff, the entity-scoped one wins and the file is just .bak'd. */
|
|
197
|
+
importLegacyHandoffFile(): Promise<{
|
|
198
|
+
imported: boolean;
|
|
199
|
+
phaseRef?: string;
|
|
200
|
+
}>;
|
|
201
|
+
/** Clear the handoff for a phase, archiving its content first. The handoff
|
|
202
|
+
* markdown is written to .planner/handoff-archive/<phaseId>-<ISO>.md and a
|
|
203
|
+
* metadata entry { file, clearedAt, reason } is prepended to handoffHistory
|
|
204
|
+
* (capped at 5; oldest file is deleted when trimmed). handoffUpdatedAt is
|
|
205
|
+
* left unchanged as an audit trail. If the handoff is empty, this is a no-op.
|
|
206
|
+
* reason: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
207
|
+
clearPhaseHandoff(phaseId: string, reason?: string): Promise<void>;
|
|
194
208
|
/** List all phases that have a non-empty handoff, newest first, with a
|
|
195
209
|
* human-readable composite ref (P00x or P00x(F00x)) and a first-line excerpt. */
|
|
196
210
|
listHandoffs(): Promise<PhaseHandoffSummary[]>;
|
package/dist/plan-store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-store.d.ts","sourceRoot":"","sources":["../src/plan-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,gBAAgB,EAGrB,KAAK,QAAQ,EAEb,KAAK,KAAK,EAEV,KAAK,aAAa,EAElB,KAAK,OAAO,EAEZ,KAAK,oBAAoB,EAIzB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAOrB,qBAAa,cAAe,SAAQ,KAAK;aAGrB,KAAK,CAAC,EAAE,OAAO;gBAD/B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,OAAO,YAAA;CAKlC;AAeD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAElF;AAKD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAEvE;AAaD,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAStF;AAmDD,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;CACnB;AASD,wBAAsB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEpE;AA4BD,qBAAa,SAAS;IACpB,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAO1B,OAAO,CAAC,eAAe,CAAS;gBAEpB,IAAI,EAAE,MAAM;IAIxB;;4EAEwE;IACxE,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAEpC;;;2BAGuB;YACT,UAAU;IAUxB,2EAA2E;IACrE,oBAAoB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAI/D;;oFAEgF;IAC1E,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;YAIrC,aAAa;IAK3B,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,yBAAyB;IAUjC,OAAO,CAAC,sBAAsB;IAc9B,OAAO,CAAC,0BAA0B;IAuE5B,uBAAuB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"plan-store.d.ts","sourceRoot":"","sources":["../src/plan-store.ts"],"names":[],"mappings":"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"}
|
package/dist/plan-store.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, copyFile, mkdir, readdir, readFile, rename,
|
|
1
|
+
import { access, copyFile, mkdir, readdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { CodebaseProfileSchema, FeatureSchema, FeaturesDocumentSchema, ManifestSchema, PhaseSchema, ProjectSchema, RequirementsDocumentSchema, ResumeFocusSchema, ActivityLogSchema, } from "./schema.js";
|
|
4
4
|
import { createFeatureId, createPhaseId, createRequirementId, createShortId, createTaskId, formatPhaseRef, isLegacyPhaseId } from "./naming.js";
|
|
@@ -338,7 +338,7 @@ export class PlanStore {
|
|
|
338
338
|
};
|
|
339
339
|
}
|
|
340
340
|
async ensureStructureOrdering() {
|
|
341
|
-
|
|
341
|
+
const result = await this.runAsBatch(async () => {
|
|
342
342
|
const featuresDoc = await this.loadFeatures();
|
|
343
343
|
const phases = await this.loadAllPhases();
|
|
344
344
|
const normalized = this.normalizeStructureSnapshot(featuresDoc, phases);
|
|
@@ -350,6 +350,10 @@ export class PlanStore {
|
|
|
350
350
|
}
|
|
351
351
|
return { changed: true };
|
|
352
352
|
});
|
|
353
|
+
// One-time import of a legacy file-based HANDOFF.md (pre-F004) into the
|
|
354
|
+
// entity-scoped phase.handoff. Idempotent — renames the file to .bak.
|
|
355
|
+
await this.importLegacyHandoffFile().catch(() => { });
|
|
356
|
+
return result;
|
|
353
357
|
}
|
|
354
358
|
// ── Path helpers ─────────────────────────────────────────────────────
|
|
355
359
|
manifestPath() {
|
|
@@ -411,9 +415,6 @@ export class PlanStore {
|
|
|
411
415
|
activityPath() {
|
|
412
416
|
return join(this.root, "activity.json");
|
|
413
417
|
}
|
|
414
|
-
handoffPath() {
|
|
415
|
-
return join(this.root, "HANDOFF.md");
|
|
416
|
-
}
|
|
417
418
|
// ── Init ─────────────────────────────────────────────────────────────
|
|
418
419
|
async init(projectName) {
|
|
419
420
|
if (await this.exists()) {
|
|
@@ -460,6 +461,7 @@ export class PlanStore {
|
|
|
460
461
|
currentPhaseId: "",
|
|
461
462
|
inProgressTaskIds: [],
|
|
462
463
|
nextSteps: ["Run /planner project discuss to bootstrap discovery"],
|
|
464
|
+
nextStepsUpdatedAt: nowISO(),
|
|
463
465
|
blockers: [],
|
|
464
466
|
notes: "Project initialized. Awaiting discovery.",
|
|
465
467
|
lastSessionSummary: "",
|
|
@@ -495,6 +497,7 @@ export class PlanStore {
|
|
|
495
497
|
"resume.json",
|
|
496
498
|
"resume.*.json",
|
|
497
499
|
"generated/",
|
|
500
|
+
"handoff-archive/",
|
|
498
501
|
"",
|
|
499
502
|
].join("\n"), "utf-8");
|
|
500
503
|
}
|
|
@@ -589,7 +592,18 @@ export class PlanStore {
|
|
|
589
592
|
}
|
|
590
593
|
}
|
|
591
594
|
async saveResume(resume) {
|
|
592
|
-
|
|
595
|
+
// Track when `nextSteps` actually change (free-text can go stale; the recap
|
|
596
|
+
// surfaces nextStepsUpdatedAt so staleness is visible). Preserved when
|
|
597
|
+
// refreshResume keeps existing nextSteps; bumped only on a real change.
|
|
598
|
+
const existing = await this.loadResume().catch(() => null);
|
|
599
|
+
const nextStepsChanged = JSON.stringify(existing?.nextSteps ?? []) !== JSON.stringify(resume.nextSteps ?? []);
|
|
600
|
+
const withTs = {
|
|
601
|
+
...resume,
|
|
602
|
+
nextStepsUpdatedAt: nextStepsChanged
|
|
603
|
+
? nowISO()
|
|
604
|
+
: (resume.nextStepsUpdatedAt || existing?.nextStepsUpdatedAt || nowISO()),
|
|
605
|
+
};
|
|
606
|
+
const parsed = ResumeFocusSchema.parse(withTs);
|
|
593
607
|
await atomicWriteJson(this.resumePath(), parsed);
|
|
594
608
|
await this.touchManifest();
|
|
595
609
|
}
|
|
@@ -605,6 +619,7 @@ export class PlanStore {
|
|
|
605
619
|
currentPhaseId: "",
|
|
606
620
|
inProgressTaskIds: [],
|
|
607
621
|
nextSteps: [],
|
|
622
|
+
nextStepsUpdatedAt: "",
|
|
608
623
|
blockers: [],
|
|
609
624
|
notes: "",
|
|
610
625
|
lastSessionSummary: "",
|
|
@@ -643,44 +658,6 @@ export class PlanStore {
|
|
|
643
658
|
return { entries: [] };
|
|
644
659
|
}
|
|
645
660
|
}
|
|
646
|
-
async handoffExists() {
|
|
647
|
-
try {
|
|
648
|
-
await access(this.handoffPath());
|
|
649
|
-
return true;
|
|
650
|
-
}
|
|
651
|
-
catch {
|
|
652
|
-
return false;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
async loadHandoff() {
|
|
656
|
-
try {
|
|
657
|
-
const [content, info] = await Promise.all([
|
|
658
|
-
readFile(this.handoffPath(), "utf-8"),
|
|
659
|
-
stat(this.handoffPath()),
|
|
660
|
-
]);
|
|
661
|
-
const createdAt = content.match(/^Created at:\s*(.+)$/m)?.[1]?.trim() ?? info.birthtime.toISOString();
|
|
662
|
-
const updatedAt = content.match(/^Updated at:\s*(.+)$/m)?.[1]?.trim() ?? info.mtime.toISOString();
|
|
663
|
-
return {
|
|
664
|
-
content,
|
|
665
|
-
createdAt,
|
|
666
|
-
updatedAt,
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
catch {
|
|
670
|
-
return null;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
async saveHandoff(content) {
|
|
674
|
-
await atomicWriteText(this.handoffPath(), content);
|
|
675
|
-
await this.touchManifest();
|
|
676
|
-
}
|
|
677
|
-
async deleteHandoff() {
|
|
678
|
-
try {
|
|
679
|
-
await unlink(this.handoffPath());
|
|
680
|
-
}
|
|
681
|
-
catch { }
|
|
682
|
-
await this.touchManifest();
|
|
683
|
-
}
|
|
684
661
|
async appendActivity(type, ref, summary) {
|
|
685
662
|
const log = await this.loadActivityLog();
|
|
686
663
|
const id = `act-${log.entries.length + 1}-${type}`;
|
|
@@ -705,6 +682,7 @@ export class PlanStore {
|
|
|
705
682
|
currentPhaseId: inProgressPhases[0]?.id ?? existing?.currentPhaseId ?? "",
|
|
706
683
|
inProgressTaskIds: inProgressTasks.map((t) => t.id),
|
|
707
684
|
nextSteps: existing?.nextSteps ?? [],
|
|
685
|
+
nextStepsUpdatedAt: existing?.nextStepsUpdatedAt ?? "",
|
|
708
686
|
blockers: blockedTasks.map((t) => `${t.id}: ${t.title}`),
|
|
709
687
|
notes: notes ?? existing?.notes ?? "",
|
|
710
688
|
lastSessionSummary: lastSessionSummary ?? existing?.lastSessionSummary ?? "",
|
|
@@ -1128,7 +1106,7 @@ export class PlanStore {
|
|
|
1128
1106
|
const phase = await this.loadPhase(phaseId);
|
|
1129
1107
|
let cleared = null;
|
|
1130
1108
|
if (phase.status === "done" && phase.handoff !== "") {
|
|
1131
|
-
await this.
|
|
1109
|
+
await this.clearPhaseHandoff(phaseId, "phase-done");
|
|
1132
1110
|
const features = await this.loadFeatures();
|
|
1133
1111
|
const feature = features.features.find((f) => f.id === phase.featureId);
|
|
1134
1112
|
cleared = formatPhaseRef(phase.number, feature?.number);
|
|
@@ -1246,10 +1224,76 @@ export class PlanStore {
|
|
|
1246
1224
|
const now = new Date().toISOString();
|
|
1247
1225
|
await this.updatePhase(phaseId, (phase) => ({ ...phase, handoff: text, handoffUpdatedAt: now }));
|
|
1248
1226
|
}
|
|
1249
|
-
/**
|
|
1250
|
-
*
|
|
1251
|
-
|
|
1252
|
-
|
|
1227
|
+
/** Directory where cleared handoff content is archived as .md files
|
|
1228
|
+
* (gitignored). Keeps the phase JSON lean while making past handoffs
|
|
1229
|
+
* recoverable + human-readable. */
|
|
1230
|
+
handoffArchiveDir() {
|
|
1231
|
+
return join(this.root, "handoff-archive");
|
|
1232
|
+
}
|
|
1233
|
+
/** Mark the phase handoff as read/acknowledged on recap (sets handoffReadAt).
|
|
1234
|
+
* Does NOT clear the handoff — content is kept until a task starts or the
|
|
1235
|
+
* phase completes, so a restart between read and resume does not lose it. */
|
|
1236
|
+
async markHandoffRead(phaseId) {
|
|
1237
|
+
await this.updatePhase(phaseId, (phase) => ({ ...phase, handoffReadAt: nowISO() }));
|
|
1238
|
+
}
|
|
1239
|
+
/** One-time import of a legacy .planner/HANDOFF.md file (file-based handoff
|
|
1240
|
+
* from before F004) into the entity-scoped phase.handoff. Idempotent: if the
|
|
1241
|
+
* file is absent or empty, no-op. If it exists + non-empty + the target phase
|
|
1242
|
+
* has no handoff, writes the content onto the current in-progress phase (or
|
|
1243
|
+
* the first phase if none in-progress) with an "imported" handoffHistory entry,
|
|
1244
|
+
* then renames the file to HANDOFF.md.bak so it won't re-import. If the target
|
|
1245
|
+
* already has a handoff, the entity-scoped one wins and the file is just .bak'd. */
|
|
1246
|
+
async importLegacyHandoffFile() {
|
|
1247
|
+
const filePath = join(this.root, "HANDOFF.md");
|
|
1248
|
+
const content = await readFile(filePath, "utf-8").catch(() => null);
|
|
1249
|
+
if (content === null)
|
|
1250
|
+
return { imported: false };
|
|
1251
|
+
if (content.trim() === "") {
|
|
1252
|
+
await rename(filePath, filePath + ".bak").catch(() => { });
|
|
1253
|
+
return { imported: false };
|
|
1254
|
+
}
|
|
1255
|
+
const phases = await this.loadAllPhases();
|
|
1256
|
+
const target = phases.find((p) => p.status === "in-progress") ?? phases[0] ?? null;
|
|
1257
|
+
if (!target)
|
|
1258
|
+
return { imported: false }; // no phases yet — leave file for a later run
|
|
1259
|
+
if ((target.handoff ?? "") === "") {
|
|
1260
|
+
await this.setPhaseHandoff(target.id, content + "\n\n<!-- imported from legacy .planner/HANDOFF.md -->\n");
|
|
1261
|
+
await this.updatePhase(target.id, (p) => ({
|
|
1262
|
+
...p,
|
|
1263
|
+
handoffHistory: [{ file: "(legacy HANDOFF.md)", clearedAt: nowISO(), reason: "imported" }, ...(p.handoffHistory ?? [])].slice(0, 5),
|
|
1264
|
+
}));
|
|
1265
|
+
}
|
|
1266
|
+
await rename(filePath, filePath + ".bak").catch(() => { });
|
|
1267
|
+
const features = await this.loadFeatures();
|
|
1268
|
+
const feat = features.features.find((f) => f.id === target.featureId);
|
|
1269
|
+
return { imported: true, phaseRef: formatPhaseRef(target.number, feat?.number) };
|
|
1270
|
+
}
|
|
1271
|
+
/** Clear the handoff for a phase, archiving its content first. The handoff
|
|
1272
|
+
* markdown is written to .planner/handoff-archive/<phaseId>-<ISO>.md and a
|
|
1273
|
+
* metadata entry { file, clearedAt, reason } is prepended to handoffHistory
|
|
1274
|
+
* (capped at 5; oldest file is deleted when trimmed). handoffUpdatedAt is
|
|
1275
|
+
* left unchanged as an audit trail. If the handoff is empty, this is a no-op.
|
|
1276
|
+
* reason: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
1277
|
+
async clearPhaseHandoff(phaseId, reason = "manual") {
|
|
1278
|
+
const phase = await this.loadPhase(phaseId).catch(() => null);
|
|
1279
|
+
if (!phase || phase.handoff === "")
|
|
1280
|
+
return; // nothing to archive
|
|
1281
|
+
const clearedAt = nowISO();
|
|
1282
|
+
const safeTs = clearedAt.replace(/[:.]/g, "-");
|
|
1283
|
+
const archiveDir = this.handoffArchiveDir();
|
|
1284
|
+
await mkdir(archiveDir, { recursive: true }).catch(() => { });
|
|
1285
|
+
const fileName = `${phaseId}-${safeTs}.md`;
|
|
1286
|
+
const filePath = join(archiveDir, fileName);
|
|
1287
|
+
await atomicWriteText(filePath, phase.handoff);
|
|
1288
|
+
const entry = { file: `handoff-archive/${fileName}`, clearedAt, reason };
|
|
1289
|
+
// Cap history at 5: prepend new entry, drop oldest (and delete its file).
|
|
1290
|
+
const trimmed = [entry, ...(phase.handoffHistory ?? [])].slice(0, 5);
|
|
1291
|
+
const dropped = (phase.handoffHistory ?? []).slice(4); // entries beyond index 4 after prepend
|
|
1292
|
+
for (const d of dropped) {
|
|
1293
|
+
if (d?.file)
|
|
1294
|
+
await unlink(join(this.root, d.file)).catch(() => { });
|
|
1295
|
+
}
|
|
1296
|
+
await this.updatePhase(phaseId, (p) => ({ ...p, handoff: "", handoffHistory: trimmed }));
|
|
1253
1297
|
}
|
|
1254
1298
|
/** List all phases that have a non-empty handoff, newest first, with a
|
|
1255
1299
|
* human-readable composite ref (P00x or P00x(F00x)) and a first-line excerpt. */
|
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,CA8HhH"}
|
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)";
|
|
@@ -71,18 +70,33 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
71
70
|
else {
|
|
72
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"}`);
|
|
73
72
|
}
|
|
74
|
-
// Next step:
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
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) {
|
|
78
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);
|
|
79
93
|
}
|
|
80
94
|
if (handoffs.length > 0) {
|
|
81
95
|
lines.push("", italian ? `## Handoff di fase pendenti (${handoffs.length})` : `## Pending phase handoffs (${handoffs.length})`);
|
|
82
96
|
handoffs.forEach((h, i) => lines.push(`[${i + 1}] ${h.compositeRef} — ${h.updatedAt} — "${h.firstLine}"`));
|
|
83
97
|
lines.push("", italian
|
|
84
|
-
? `→ Leggi quello pertinente con ${handoffShowCmd} <ref> (valida contro lo stato attuale)
|
|
85
|
-
: `→ Read the relevant one with ${handoffShowCmd} <ref> (validate against current state)
|
|
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.`);
|
|
86
100
|
}
|
|
87
101
|
else if (planComplete) {
|
|
88
102
|
lines.push("", italian
|
package/dist/schema.d.ts
CHANGED
|
@@ -133,6 +133,10 @@ export declare const ResumeFocusSchema: z.ZodObject<{
|
|
|
133
133
|
currentPhaseId: z.ZodDefault<z.ZodString>;
|
|
134
134
|
inProgressTaskIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
135
135
|
nextSteps: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
136
|
+
/** When `nextSteps` last changed (free-text can go stale; the recap surfaces
|
|
137
|
+
* this so staleness is visible). Preserved across refreshResume (which keeps
|
|
138
|
+
* existing nextSteps); bumped only when saveResume receives new nextSteps. */
|
|
139
|
+
nextStepsUpdatedAt: z.ZodDefault<z.ZodString>;
|
|
136
140
|
blockers: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
137
141
|
notes: z.ZodDefault<z.ZodString>;
|
|
138
142
|
lastSessionSummary: z.ZodDefault<z.ZodString>;
|
|
@@ -142,6 +146,7 @@ export declare const ResumeFocusSchema: z.ZodObject<{
|
|
|
142
146
|
currentPhaseId: string;
|
|
143
147
|
inProgressTaskIds: string[];
|
|
144
148
|
nextSteps: string[];
|
|
149
|
+
nextStepsUpdatedAt: string;
|
|
145
150
|
blockers: string[];
|
|
146
151
|
notes: string;
|
|
147
152
|
lastSessionSummary: string;
|
|
@@ -151,6 +156,7 @@ export declare const ResumeFocusSchema: z.ZodObject<{
|
|
|
151
156
|
currentPhaseId?: string | undefined;
|
|
152
157
|
inProgressTaskIds?: string[] | undefined;
|
|
153
158
|
nextSteps?: string[] | undefined;
|
|
159
|
+
nextStepsUpdatedAt?: string | undefined;
|
|
154
160
|
blockers?: string[] | undefined;
|
|
155
161
|
notes?: string | undefined;
|
|
156
162
|
lastSessionSummary?: string | undefined;
|
|
@@ -719,6 +725,21 @@ export declare const TaskSchema: z.ZodEffects<z.ZodObject<{
|
|
|
719
725
|
startedAt?: string | undefined;
|
|
720
726
|
completedAt?: string | undefined;
|
|
721
727
|
}>;
|
|
728
|
+
export declare const HandoffHistoryEntrySchema: z.ZodObject<{
|
|
729
|
+
/** Relative path under .planner/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
730
|
+
file: z.ZodDefault<z.ZodString>;
|
|
731
|
+
clearedAt: z.ZodString;
|
|
732
|
+
/** Why the handoff was cleared: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
733
|
+
reason: z.ZodDefault<z.ZodString>;
|
|
734
|
+
}, "strip", z.ZodTypeAny, {
|
|
735
|
+
file: string;
|
|
736
|
+
clearedAt: string;
|
|
737
|
+
reason: string;
|
|
738
|
+
}, {
|
|
739
|
+
clearedAt: string;
|
|
740
|
+
file?: string | undefined;
|
|
741
|
+
reason?: string | undefined;
|
|
742
|
+
}>;
|
|
722
743
|
export declare const PhaseSchema: z.ZodObject<{
|
|
723
744
|
id: z.ZodString;
|
|
724
745
|
featureId: z.ZodOptional<z.ZodString>;
|
|
@@ -1049,6 +1070,30 @@ export declare const PhaseSchema: z.ZodObject<{
|
|
|
1049
1070
|
updatedAt: z.ZodString;
|
|
1050
1071
|
handoff: z.ZodDefault<z.ZodString>;
|
|
1051
1072
|
handoffUpdatedAt: z.ZodDefault<z.ZodString>;
|
|
1073
|
+
/** When the handoff was last read/acknowledged on recap (ISO). Non-empty means
|
|
1074
|
+
* the agent has seen it; the recap won't re-prompt every turn. The handoff
|
|
1075
|
+
* content is KEPT until a task in the phase starts (auto-clear) or the phase
|
|
1076
|
+
* completes — so a restart between read and resume does not lose context. */
|
|
1077
|
+
handoffReadAt: z.ZodDefault<z.ZodString>;
|
|
1078
|
+
/** Metadata for recently-cleared handoffs (newest-first, capped at 5). The
|
|
1079
|
+
* full content lives as .md files in .planner/handoff-archive/ (gitignored);
|
|
1080
|
+
* this array only holds the pointer + clear reason + timestamp, so the phase
|
|
1081
|
+
* JSON stays lean and the archive is recoverable. */
|
|
1082
|
+
handoffHistory: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
1083
|
+
/** Relative path under .planner/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
1084
|
+
file: z.ZodDefault<z.ZodString>;
|
|
1085
|
+
clearedAt: z.ZodString;
|
|
1086
|
+
/** Why the handoff was cleared: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
1087
|
+
reason: z.ZodDefault<z.ZodString>;
|
|
1088
|
+
}, "strip", z.ZodTypeAny, {
|
|
1089
|
+
file: string;
|
|
1090
|
+
clearedAt: string;
|
|
1091
|
+
reason: string;
|
|
1092
|
+
}, {
|
|
1093
|
+
clearedAt: string;
|
|
1094
|
+
file?: string | undefined;
|
|
1095
|
+
reason?: string | undefined;
|
|
1096
|
+
}>, "many">>;
|
|
1052
1097
|
}, "strip", z.ZodTypeAny, {
|
|
1053
1098
|
number: number;
|
|
1054
1099
|
dependencies: string[];
|
|
@@ -1130,6 +1175,12 @@ export declare const PhaseSchema: z.ZodObject<{
|
|
|
1130
1175
|
}[];
|
|
1131
1176
|
handoff: string;
|
|
1132
1177
|
handoffUpdatedAt: string;
|
|
1178
|
+
handoffReadAt: string;
|
|
1179
|
+
handoffHistory: {
|
|
1180
|
+
file: string;
|
|
1181
|
+
clearedAt: string;
|
|
1182
|
+
reason: string;
|
|
1183
|
+
}[];
|
|
1133
1184
|
featureId?: string | undefined;
|
|
1134
1185
|
}, {
|
|
1135
1186
|
number: number;
|
|
@@ -1213,6 +1264,12 @@ export declare const PhaseSchema: z.ZodObject<{
|
|
|
1213
1264
|
}[] | undefined;
|
|
1214
1265
|
handoff?: string | undefined;
|
|
1215
1266
|
handoffUpdatedAt?: string | undefined;
|
|
1267
|
+
handoffReadAt?: string | undefined;
|
|
1268
|
+
handoffHistory?: {
|
|
1269
|
+
clearedAt: string;
|
|
1270
|
+
file?: string | undefined;
|
|
1271
|
+
reason?: string | undefined;
|
|
1272
|
+
}[] | undefined;
|
|
1216
1273
|
}>;
|
|
1217
1274
|
export declare const FeatureSchema: z.ZodObject<{
|
|
1218
1275
|
id: z.ZodString;
|
|
@@ -2347,6 +2404,30 @@ export declare const PlanWorkspaceSchema: z.ZodObject<{
|
|
|
2347
2404
|
updatedAt: z.ZodString;
|
|
2348
2405
|
handoff: z.ZodDefault<z.ZodString>;
|
|
2349
2406
|
handoffUpdatedAt: z.ZodDefault<z.ZodString>;
|
|
2407
|
+
/** When the handoff was last read/acknowledged on recap (ISO). Non-empty means
|
|
2408
|
+
* the agent has seen it; the recap won't re-prompt every turn. The handoff
|
|
2409
|
+
* content is KEPT until a task in the phase starts (auto-clear) or the phase
|
|
2410
|
+
* completes — so a restart between read and resume does not lose context. */
|
|
2411
|
+
handoffReadAt: z.ZodDefault<z.ZodString>;
|
|
2412
|
+
/** Metadata for recently-cleared handoffs (newest-first, capped at 5). The
|
|
2413
|
+
* full content lives as .md files in .planner/handoff-archive/ (gitignored);
|
|
2414
|
+
* this array only holds the pointer + clear reason + timestamp, so the phase
|
|
2415
|
+
* JSON stays lean and the archive is recoverable. */
|
|
2416
|
+
handoffHistory: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
2417
|
+
/** Relative path under .planner/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
2418
|
+
file: z.ZodDefault<z.ZodString>;
|
|
2419
|
+
clearedAt: z.ZodString;
|
|
2420
|
+
/** Why the handoff was cleared: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
2421
|
+
reason: z.ZodDefault<z.ZodString>;
|
|
2422
|
+
}, "strip", z.ZodTypeAny, {
|
|
2423
|
+
file: string;
|
|
2424
|
+
clearedAt: string;
|
|
2425
|
+
reason: string;
|
|
2426
|
+
}, {
|
|
2427
|
+
clearedAt: string;
|
|
2428
|
+
file?: string | undefined;
|
|
2429
|
+
reason?: string | undefined;
|
|
2430
|
+
}>, "many">>;
|
|
2350
2431
|
}, "strip", z.ZodTypeAny, {
|
|
2351
2432
|
number: number;
|
|
2352
2433
|
dependencies: string[];
|
|
@@ -2428,6 +2509,12 @@ export declare const PlanWorkspaceSchema: z.ZodObject<{
|
|
|
2428
2509
|
}[];
|
|
2429
2510
|
handoff: string;
|
|
2430
2511
|
handoffUpdatedAt: string;
|
|
2512
|
+
handoffReadAt: string;
|
|
2513
|
+
handoffHistory: {
|
|
2514
|
+
file: string;
|
|
2515
|
+
clearedAt: string;
|
|
2516
|
+
reason: string;
|
|
2517
|
+
}[];
|
|
2431
2518
|
featureId?: string | undefined;
|
|
2432
2519
|
}, {
|
|
2433
2520
|
number: number;
|
|
@@ -2511,6 +2598,12 @@ export declare const PlanWorkspaceSchema: z.ZodObject<{
|
|
|
2511
2598
|
}[] | undefined;
|
|
2512
2599
|
handoff?: string | undefined;
|
|
2513
2600
|
handoffUpdatedAt?: string | undefined;
|
|
2601
|
+
handoffReadAt?: string | undefined;
|
|
2602
|
+
handoffHistory?: {
|
|
2603
|
+
clearedAt: string;
|
|
2604
|
+
file?: string | undefined;
|
|
2605
|
+
reason?: string | undefined;
|
|
2606
|
+
}[] | undefined;
|
|
2514
2607
|
}>, "many">;
|
|
2515
2608
|
}, "strip", z.ZodTypeAny, {
|
|
2516
2609
|
features: {
|
|
@@ -2676,6 +2769,12 @@ export declare const PlanWorkspaceSchema: z.ZodObject<{
|
|
|
2676
2769
|
}[];
|
|
2677
2770
|
handoff: string;
|
|
2678
2771
|
handoffUpdatedAt: string;
|
|
2772
|
+
handoffReadAt: string;
|
|
2773
|
+
handoffHistory: {
|
|
2774
|
+
file: string;
|
|
2775
|
+
clearedAt: string;
|
|
2776
|
+
reason: string;
|
|
2777
|
+
}[];
|
|
2679
2778
|
featureId?: string | undefined;
|
|
2680
2779
|
}[];
|
|
2681
2780
|
}, {
|
|
@@ -2843,6 +2942,12 @@ export declare const PlanWorkspaceSchema: z.ZodObject<{
|
|
|
2843
2942
|
}[] | undefined;
|
|
2844
2943
|
handoff?: string | undefined;
|
|
2845
2944
|
handoffUpdatedAt?: string | undefined;
|
|
2945
|
+
handoffReadAt?: string | undefined;
|
|
2946
|
+
handoffHistory?: {
|
|
2947
|
+
clearedAt: string;
|
|
2948
|
+
file?: string | undefined;
|
|
2949
|
+
reason?: string | undefined;
|
|
2950
|
+
}[] | undefined;
|
|
2846
2951
|
}[];
|
|
2847
2952
|
}>;
|
|
2848
2953
|
export type Timestamp = z.infer<typeof TimestampSchema>;
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,eAAe,aAAwB,CAAC;AACrD,eAAO,MAAM,UAAU,aAAiD,CAAC;AAEzE,eAAO,MAAM,kBAAkB;;;;;;;;;EAG7B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;EAK7B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmBhC,CAAC;AAEH,eAAO,MAAM,iBAAiB
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.js
CHANGED
|
@@ -37,6 +37,10 @@ export const ResumeFocusSchema = z.object({
|
|
|
37
37
|
currentPhaseId: z.string().default(""),
|
|
38
38
|
inProgressTaskIds: z.array(z.string().min(1)).default([]),
|
|
39
39
|
nextSteps: z.array(z.string().min(1)).default([]),
|
|
40
|
+
/** When `nextSteps` last changed (free-text can go stale; the recap surfaces
|
|
41
|
+
* this so staleness is visible). Preserved across refreshResume (which keeps
|
|
42
|
+
* existing nextSteps); bumped only when saveResume receives new nextSteps. */
|
|
43
|
+
nextStepsUpdatedAt: z.string().default(""),
|
|
40
44
|
blockers: z.array(z.string().min(1)).default([]),
|
|
41
45
|
notes: z.string().default(""),
|
|
42
46
|
lastSessionSummary: z.string().default(""),
|
|
@@ -163,6 +167,13 @@ export const TaskSchema = z.object({
|
|
|
163
167
|
: { ...item, id: item.id || createChecklistItemId(task.id, index + 1, item.title), title: item.title.trim(), checked: item.checked ?? false })
|
|
164
168
|
.filter((item) => item.title.length > 0),
|
|
165
169
|
}));
|
|
170
|
+
export const HandoffHistoryEntrySchema = z.object({
|
|
171
|
+
/** Relative path under .planner/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
172
|
+
file: z.string().default(""),
|
|
173
|
+
clearedAt: TimestampSchema,
|
|
174
|
+
/** Why the handoff was cleared: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
175
|
+
reason: z.string().default(""),
|
|
176
|
+
});
|
|
166
177
|
export const PhaseSchema = z.object({
|
|
167
178
|
id: z.string(),
|
|
168
179
|
featureId: z.string().optional(),
|
|
@@ -195,6 +206,16 @@ export const PhaseSchema = z.object({
|
|
|
195
206
|
updatedAt: TimestampSchema,
|
|
196
207
|
handoff: z.string().default(""),
|
|
197
208
|
handoffUpdatedAt: z.string().default(""),
|
|
209
|
+
/** When the handoff was last read/acknowledged on recap (ISO). Non-empty means
|
|
210
|
+
* the agent has seen it; the recap won't re-prompt every turn. The handoff
|
|
211
|
+
* content is KEPT until a task in the phase starts (auto-clear) or the phase
|
|
212
|
+
* completes — so a restart between read and resume does not lose context. */
|
|
213
|
+
handoffReadAt: z.string().default(""),
|
|
214
|
+
/** Metadata for recently-cleared handoffs (newest-first, capped at 5). The
|
|
215
|
+
* full content lives as .md files in .planner/handoff-archive/ (gitignored);
|
|
216
|
+
* this array only holds the pointer + clear reason + timestamp, so the phase
|
|
217
|
+
* JSON stays lean and the archive is recoverable. */
|
|
218
|
+
handoffHistory: z.array(HandoffHistoryEntrySchema).default([]),
|
|
198
219
|
});
|
|
199
220
|
export const FeatureSchema = z.object({
|
|
200
221
|
id: z.string(),
|
package/package.json
CHANGED