@agent-plan/core 0.2.19-next.20 → 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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/plan-store.d.ts +49 -6
- package/dist/plan-store.d.ts.map +1 -1
- package/dist/plan-store.js +117 -25
- package/dist/schema.d.ts +395 -194
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +18 -0
- package/dist/task-context.d.ts +2 -2
- package/dist/task-context.d.ts.map +1 -1
- package/dist/task-context.js +26 -9
- package/dist/task-selection.d.ts +23 -0
- package/dist/task-selection.d.ts.map +1 -0
- package/dist/task-selection.js +66 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./checklist.js";
|
|
|
5
5
|
export * from "./recap.js";
|
|
6
6
|
export * from "./display-status.js";
|
|
7
7
|
export * from "./task-context.js";
|
|
8
|
+
export * from "./task-selection.js";
|
|
8
9
|
export { PlanStore, PlanStoreError, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, type PhaseHandoffSummary, type OrphanPhaseSummary } from "./plan-store.js";
|
|
9
10
|
export { PlanRenderer } from "./renderer.js";
|
|
10
11
|
export { ExportService } from "./export-service.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,eAAe,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC/M,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,eAAe,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC/M,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./checklist.js";
|
|
|
5
5
|
export * from "./recap.js";
|
|
6
6
|
export * from "./display-status.js";
|
|
7
7
|
export * from "./task-context.js";
|
|
8
|
+
export * from "./task-selection.js";
|
|
8
9
|
export { PlanStore, PlanStoreError, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock } from "./plan-store.js";
|
|
9
10
|
export { PlanRenderer } from "./renderer.js";
|
|
10
11
|
export { ExportService } from "./export-service.js";
|
package/dist/plan-store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Feature, type FeaturesDocument, type Manifest, type Phase, type PlanWorkspace, type Project, type RequirementsDocument, type Requirement, type ActivityEntry, type ActivityLog, type CodebaseProfile, type ResumeFocus } from "./schema.js";
|
|
1
|
+
import { type Feature, type FeaturesDocument, type Manifest, type Phase, type PlanWorkspace, type Project, type RequirementsDocument, type Requirement, type ActivityEntry, type ActivityLog, type CodebaseProfile, type ResumeFocus, type WorkDeviation } from "./schema.js";
|
|
2
2
|
import { type ParentDisplay } from "./display-status.js";
|
|
3
3
|
export interface PlanStoreErrorDetails {
|
|
4
4
|
path?: string;
|
|
@@ -25,7 +25,8 @@ export declare class PlanStoreError extends Error {
|
|
|
25
25
|
export declare function setWriteBusyHook(hook: ((busy: boolean) => void) | undefined): void;
|
|
26
26
|
export declare function setWriteNotifyHook(hook: (() => void) | undefined): void;
|
|
27
27
|
export declare function withFeatureLock<T>(featureId: string, fn: () => Promise<T>): Promise<T>;
|
|
28
|
-
/** Summary of a phase that has a non-empty handoff, for the
|
|
28
|
+
/** Summary of a phase that has a non-empty pending handoff, for the
|
|
29
|
+
* listHandoffs() API. Completed/canceled phases are never returned here. */
|
|
29
30
|
export interface PhaseHandoffSummary {
|
|
30
31
|
phaseId: string;
|
|
31
32
|
/** Owning feature id (for deep-linking to the phase-detail route). */
|
|
@@ -40,6 +41,19 @@ export interface PhaseHandoffSummary {
|
|
|
40
41
|
* inline without a per-phase fetch. */
|
|
41
42
|
content: string;
|
|
42
43
|
}
|
|
44
|
+
/** Summary of an archived handoff. The content remains recoverable under
|
|
45
|
+
* `.planner/.local/handoff-archive/` but is not part of the active handoff
|
|
46
|
+
* list. */
|
|
47
|
+
export interface ArchivedPhaseHandoffSummary {
|
|
48
|
+
phaseId: string;
|
|
49
|
+
featureId?: string | undefined;
|
|
50
|
+
compositeRef: string;
|
|
51
|
+
file: string;
|
|
52
|
+
archivedAt: string;
|
|
53
|
+
reason: string;
|
|
54
|
+
firstLine: string;
|
|
55
|
+
content: string;
|
|
56
|
+
}
|
|
43
57
|
/** Summary of a phase file that exists on disk but no longer resolves to a
|
|
44
58
|
* valid owning feature. Missing back-links alone do NOT make a phase orphan:
|
|
45
59
|
* if `phase.featureId` still resolves to a known feature, repair can relink
|
|
@@ -198,6 +212,8 @@ export declare class PlanStore {
|
|
|
198
212
|
refreshResume(notes?: string, lastSessionSummary?: string): Promise<ResumeFocus>;
|
|
199
213
|
loadRequirements(): Promise<RequirementsDocument>;
|
|
200
214
|
linkedRequirementsForPhase(phaseId: string): Promise<Requirement[]>;
|
|
215
|
+
/** Requirements linked to any phase belonging to a feature, deduplicated by ID. */
|
|
216
|
+
linkedRequirementsForFeature(featureId: string): Promise<Requirement[]>;
|
|
201
217
|
loadPhaseWithRequirements(phaseId: string): Promise<Phase & {
|
|
202
218
|
linkedRequirements: Requirement[];
|
|
203
219
|
}>;
|
|
@@ -262,6 +278,9 @@ export declare class PlanStore {
|
|
|
262
278
|
tasks: number;
|
|
263
279
|
orphan: number;
|
|
264
280
|
};
|
|
281
|
+
handoffs: {
|
|
282
|
+
archived: number;
|
|
283
|
+
};
|
|
265
284
|
integrity: {
|
|
266
285
|
duplicatePhaseIds: string[];
|
|
267
286
|
danglingPhaseIds: string[];
|
|
@@ -284,6 +303,10 @@ export declare class PlanStore {
|
|
|
284
303
|
* Returns the composite ref of the phase if its handoff was cleared, else null. */
|
|
285
304
|
syncTaskStatusRollup(phaseId: string): Promise<string | null>;
|
|
286
305
|
updateProject(updater: (p: Project) => Project): Promise<Project>;
|
|
306
|
+
/** Persist an explicitly approved work deviation without coupling it to a harness. */
|
|
307
|
+
addWorkDeviation(deviation: WorkDeviation): Promise<Project>;
|
|
308
|
+
/** Mark an approved/active deviation as resolved or canceled while retaining its audit record. */
|
|
309
|
+
setWorkDeviationState(id: string, state: "active" | "resolved" | "canceled", timestamp?: string): Promise<Project>;
|
|
287
310
|
updateFeatures(updater: (f: FeaturesDocument) => FeaturesDocument): Promise<FeaturesDocument>;
|
|
288
311
|
updateRequirements(updater: (r: RequirementsDocument) => RequirementsDocument): Promise<RequirementsDocument>;
|
|
289
312
|
saveProject(project: Project): Promise<void>;
|
|
@@ -300,8 +323,9 @@ export declare class PlanStore {
|
|
|
300
323
|
updatePhase(phaseId: string, updater: (phase: Phase) => Phase): Promise<Phase>;
|
|
301
324
|
/** Get the handoff text for a phase ("" if none). Throws if phase missing. */
|
|
302
325
|
getPhaseHandoff(phaseId: string): Promise<string>;
|
|
303
|
-
/** Set the handoff text for a phase + stamp handoffUpdatedAt.
|
|
304
|
-
*
|
|
326
|
+
/** Set the handoff text for a phase + stamp handoffUpdatedAt. A completed or
|
|
327
|
+
* canceled phase cannot receive a new operational handoff. Replacing an
|
|
328
|
+
* existing handoff archives the previous content as `superseded` first. */
|
|
305
329
|
setPhaseHandoff(phaseId: string, text: string): Promise<void>;
|
|
306
330
|
/** Mark the phase handoff as read/acknowledged on recap (sets handoffReadAt).
|
|
307
331
|
* Does NOT clear the handoff — content is kept until a task starts or the
|
|
@@ -325,15 +349,34 @@ export declare class PlanStore {
|
|
|
325
349
|
* left unchanged as an audit trail. If the handoff is empty, this is a no-op.
|
|
326
350
|
* reason: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
327
351
|
clearPhaseHandoff(phaseId: string, reason?: string): Promise<void>;
|
|
328
|
-
/**
|
|
329
|
-
*
|
|
352
|
+
/** Archive stale handoffs left on phases that are already completed/canceled.
|
|
353
|
+
* Idempotent: only non-empty phase.handoff values are moved. */
|
|
354
|
+
private archiveStaleHandoffs;
|
|
355
|
+
/** Public maintenance operation for retroactively archiving stale handoffs. */
|
|
356
|
+
cleanupStaleHandoffs(): Promise<number>;
|
|
357
|
+
/** List only active/pending phase handoffs, newest first. Completed,
|
|
358
|
+
* canceled, and orphaned phases are intentionally excluded. Any legacy stale
|
|
359
|
+
* handoff found on a completed/canceled phase is archived before returning,
|
|
360
|
+
* so callers (including /handoff) also repair old plans automatically. */
|
|
330
361
|
listHandoffs(): Promise<PhaseHandoffSummary[]>;
|
|
362
|
+
/** List recoverable archived handoffs from phase.handoffHistory. Archived
|
|
363
|
+
* entries are never returned by listHandoffs() and are safe to show on a
|
|
364
|
+
* dedicated history page. */
|
|
365
|
+
listArchivedHandoffs(): Promise<ArchivedPhaseHandoffSummary[]>;
|
|
331
366
|
listOrphanPhases(): Promise<OrphanPhaseSummary[]>;
|
|
332
367
|
cleanupOrphanPhases(): Promise<{
|
|
333
368
|
found: OrphanPhaseSummary[];
|
|
334
369
|
removed: OrphanPhaseSummary[];
|
|
335
370
|
}>;
|
|
336
371
|
deletePhase(phaseId: string): Promise<void>;
|
|
372
|
+
/** Remove a phase file AND its inline .bak backup. atomicUpdateJson (used by
|
|
373
|
+
* updatePhase without root) writes the backup inline at phases/<id>.json.bak,
|
|
374
|
+
* and readJson falls back to `${path}.bak` on a missing main file — so a
|
|
375
|
+
* delete that leaves the .bak behind would RESURRECT the deleted phase on
|
|
376
|
+
* the next read. Feature backups (written with root) live under
|
|
377
|
+
* .local/backups/ and are never read by readJson, so only the inline .bak
|
|
378
|
+
* needs removing here. */
|
|
379
|
+
private unlinkPhaseFiles;
|
|
337
380
|
/** Load the full workspace (manifest + phases + project + requirements + features) */
|
|
338
381
|
loadWorkspace(): Promise<PlanWorkspace>;
|
|
339
382
|
/** Load all data, render markdown, and write into generated/. Skips files
|
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":"AAgBA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,gBAAgB,EAGrB,KAAK,QAAQ,EAEb,KAAK,KAAK,EAEV,KAAK,aAAa,EAElB,KAAK,OAAO,EAEZ,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAKhB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,
|
|
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
|
@@ -778,6 +778,7 @@ export class PlanStore {
|
|
|
778
778
|
nextFeatureNumber: 1,
|
|
779
779
|
nextPhaseNumber: 1,
|
|
780
780
|
nextTaskNumber: 1,
|
|
781
|
+
workDeviations: [],
|
|
781
782
|
});
|
|
782
783
|
await this.saveRequirements({ requirements: [] });
|
|
783
784
|
await this.saveFeatures({ features: [] });
|
|
@@ -1111,6 +1112,12 @@ export class PlanStore {
|
|
|
1111
1112
|
const requirements = await this.loadRequirements();
|
|
1112
1113
|
return requirements.requirements.filter((requirement) => requirement.linkedPhaseIds.includes(phaseId));
|
|
1113
1114
|
}
|
|
1115
|
+
/** Requirements linked to any phase belonging to a feature, deduplicated by ID. */
|
|
1116
|
+
async linkedRequirementsForFeature(featureId) {
|
|
1117
|
+
const [phases, requirements] = await Promise.all([this.loadAllPhases(), this.loadRequirements()]);
|
|
1118
|
+
const phaseIds = new Set(phases.filter((phase) => phase.featureId === featureId).map((phase) => phase.id));
|
|
1119
|
+
return requirements.requirements.filter((requirement) => requirement.linkedPhaseIds.some((phaseId) => phaseIds.has(phaseId)));
|
|
1120
|
+
}
|
|
1114
1121
|
async loadPhaseWithRequirements(phaseId) {
|
|
1115
1122
|
const [phase, linkedRequirements] = await Promise.all([
|
|
1116
1123
|
this.loadPhase(phaseId),
|
|
@@ -1224,10 +1231,7 @@ export class PlanStore {
|
|
|
1224
1231
|
task.phaseId = newId;
|
|
1225
1232
|
}
|
|
1226
1233
|
await this.savePhase(phase);
|
|
1227
|
-
|
|
1228
|
-
await unlink(this.phasePath(oldId));
|
|
1229
|
-
}
|
|
1230
|
-
catch { }
|
|
1234
|
+
await this.unlinkPhaseFiles(oldId);
|
|
1231
1235
|
renamed += 1;
|
|
1232
1236
|
}
|
|
1233
1237
|
// Repair feature.phaseIds: replace legacy refs with new ids, drop dangling ones.
|
|
@@ -1452,9 +1456,10 @@ export class PlanStore {
|
|
|
1452
1456
|
// corrupted by the migrateToGlobalSequence index-mismatch bug (core
|
|
1453
1457
|
// <0.2.19-next.7). Lossless + idempotent — safe to run every repair.
|
|
1454
1458
|
const containment = await this.rebuildContainment();
|
|
1459
|
+
const handoffs = { archived: await this.archiveStaleHandoffs() };
|
|
1455
1460
|
const integrity = await this.validateIntegrity();
|
|
1456
1461
|
await this.writeGenerated();
|
|
1457
|
-
return { migrated, backfill, containment, integrity };
|
|
1462
|
+
return { migrated, backfill, containment, handoffs, integrity };
|
|
1458
1463
|
});
|
|
1459
1464
|
}
|
|
1460
1465
|
async repairPhaseFeatureRefs() {
|
|
@@ -1657,6 +1662,25 @@ export class PlanStore {
|
|
|
1657
1662
|
await this.maybeAutoSync();
|
|
1658
1663
|
return updated;
|
|
1659
1664
|
}
|
|
1665
|
+
/** Persist an explicitly approved work deviation without coupling it to a harness. */
|
|
1666
|
+
async addWorkDeviation(deviation) {
|
|
1667
|
+
return this.updateProject((project) => ({
|
|
1668
|
+
...project,
|
|
1669
|
+
workDeviations: [...project.workDeviations, deviation],
|
|
1670
|
+
}));
|
|
1671
|
+
}
|
|
1672
|
+
/** Mark an approved/active deviation as resolved or canceled while retaining its audit record. */
|
|
1673
|
+
async setWorkDeviationState(id, state, timestamp = nowISO()) {
|
|
1674
|
+
return this.updateProject((project) => ({
|
|
1675
|
+
...project,
|
|
1676
|
+
workDeviations: project.workDeviations.map((deviation) => deviation.id !== id ? deviation : {
|
|
1677
|
+
...deviation,
|
|
1678
|
+
state,
|
|
1679
|
+
activatedAt: state === "active" ? timestamp : deviation.activatedAt,
|
|
1680
|
+
resolvedAt: state === "resolved" || state === "canceled" ? timestamp : deviation.resolvedAt,
|
|
1681
|
+
}),
|
|
1682
|
+
}));
|
|
1683
|
+
}
|
|
1660
1684
|
async updateFeatures(updater) {
|
|
1661
1685
|
const updated = await this.withFeaturesLock(async () => {
|
|
1662
1686
|
await this.migrateLegacy();
|
|
@@ -1779,11 +1803,20 @@ export class PlanStore {
|
|
|
1779
1803
|
async getPhaseHandoff(phaseId) {
|
|
1780
1804
|
return (await this.loadPhase(phaseId)).handoff;
|
|
1781
1805
|
}
|
|
1782
|
-
/** Set the handoff text for a phase + stamp handoffUpdatedAt.
|
|
1783
|
-
*
|
|
1806
|
+
/** Set the handoff text for a phase + stamp handoffUpdatedAt. A completed or
|
|
1807
|
+
* canceled phase cannot receive a new operational handoff. Replacing an
|
|
1808
|
+
* existing handoff archives the previous content as `superseded` first. */
|
|
1784
1809
|
async setPhaseHandoff(phaseId, text) {
|
|
1810
|
+
const phase = await this.loadPhase(phaseId);
|
|
1811
|
+
const normalized = text.trim();
|
|
1812
|
+
if (phase.status === "done" || phase.status === "canceled") {
|
|
1813
|
+
throw new PlanStoreError(`Cannot write a handoff on ${phase.status} phase ${phaseId}; completed phases have no pending handoff.`);
|
|
1814
|
+
}
|
|
1815
|
+
if (phase.handoff && normalized && phase.handoff !== normalized) {
|
|
1816
|
+
await this.clearPhaseHandoff(phaseId, "superseded");
|
|
1817
|
+
}
|
|
1785
1818
|
const now = new Date().toISOString();
|
|
1786
|
-
await this.updatePhase(phaseId, (
|
|
1819
|
+
await this.updatePhase(phaseId, (current) => ({ ...current, handoff: normalized, handoffUpdatedAt: now }));
|
|
1787
1820
|
}
|
|
1788
1821
|
/** Mark the phase handoff as read/acknowledged on recap (sets handoffReadAt).
|
|
1789
1822
|
* Does NOT clear the handoff — content is kept until a task starts or the
|
|
@@ -1808,9 +1841,11 @@ export class PlanStore {
|
|
|
1808
1841
|
return { imported: false };
|
|
1809
1842
|
}
|
|
1810
1843
|
const phases = await this.loadAllPhases();
|
|
1811
|
-
const target = phases.find((p) => p.status === "in-progress")
|
|
1844
|
+
const target = phases.find((p) => p.status === "in-progress")
|
|
1845
|
+
?? phases.find((p) => p.status !== "done" && p.status !== "canceled")
|
|
1846
|
+
?? null;
|
|
1812
1847
|
if (!target)
|
|
1813
|
-
return { imported: false }; // no
|
|
1848
|
+
return { imported: false }; // no non-completed phase — leave file for a later run
|
|
1814
1849
|
if ((target.handoff ?? "") === "") {
|
|
1815
1850
|
await this.setPhaseHandoff(target.id, content + "\n\n<!-- imported from legacy .planner/HANDOFF.md -->\n");
|
|
1816
1851
|
await this.updatePhase(target.id, (p) => ({
|
|
@@ -1854,17 +1889,40 @@ export class PlanStore {
|
|
|
1854
1889
|
}
|
|
1855
1890
|
await this.updatePhase(phaseId, (p) => ({ ...p, handoff: "", handoffHistory: trimmed }));
|
|
1856
1891
|
}
|
|
1857
|
-
/**
|
|
1858
|
-
*
|
|
1892
|
+
/** Archive stale handoffs left on phases that are already completed/canceled.
|
|
1893
|
+
* Idempotent: only non-empty phase.handoff values are moved. */
|
|
1894
|
+
async archiveStaleHandoffs() {
|
|
1895
|
+
const phases = await this.loadAllPhases();
|
|
1896
|
+
let archived = 0;
|
|
1897
|
+
for (const phase of phases) {
|
|
1898
|
+
if ((phase.status === "done" || phase.status === "canceled") && phase.handoff) {
|
|
1899
|
+
await this.clearPhaseHandoff(phase.id, "phase-done");
|
|
1900
|
+
archived += 1;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
return archived;
|
|
1904
|
+
}
|
|
1905
|
+
/** Public maintenance operation for retroactively archiving stale handoffs. */
|
|
1906
|
+
async cleanupStaleHandoffs() {
|
|
1907
|
+
return this.runAsBatch(() => this.archiveStaleHandoffs());
|
|
1908
|
+
}
|
|
1909
|
+
/** List only active/pending phase handoffs, newest first. Completed,
|
|
1910
|
+
* canceled, and orphaned phases are intentionally excluded. Any legacy stale
|
|
1911
|
+
* handoff found on a completed/canceled phase is archived before returning,
|
|
1912
|
+
* so callers (including /handoff) also repair old plans automatically. */
|
|
1859
1913
|
async listHandoffs() {
|
|
1914
|
+
await this.archiveStaleHandoffs();
|
|
1860
1915
|
const phases = await this.loadAllPhases();
|
|
1861
1916
|
const features = await this.loadFeatures();
|
|
1917
|
+
const featureIds = new Set(features.features.map((f) => f.id));
|
|
1862
1918
|
const featureNumber = new Map();
|
|
1863
1919
|
for (const f of features.features)
|
|
1864
1920
|
featureNumber.set(f.id, f.number);
|
|
1865
1921
|
const out = [];
|
|
1866
1922
|
for (const p of phases) {
|
|
1867
|
-
if (!p.handoff)
|
|
1923
|
+
if (!p.handoff || p.status === "done" || p.status === "canceled")
|
|
1924
|
+
continue;
|
|
1925
|
+
if (p.featureId && !featureIds.has(p.featureId))
|
|
1868
1926
|
continue;
|
|
1869
1927
|
const fnum = p.featureId ? featureNumber.get(p.featureId) : undefined;
|
|
1870
1928
|
out.push({
|
|
@@ -1879,6 +1937,39 @@ export class PlanStore {
|
|
|
1879
1937
|
out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1880
1938
|
return out;
|
|
1881
1939
|
}
|
|
1940
|
+
/** List recoverable archived handoffs from phase.handoffHistory. Archived
|
|
1941
|
+
* entries are never returned by listHandoffs() and are safe to show on a
|
|
1942
|
+
* dedicated history page. */
|
|
1943
|
+
async listArchivedHandoffs() {
|
|
1944
|
+
const phases = await this.loadAllPhases();
|
|
1945
|
+
const features = await this.loadFeatures();
|
|
1946
|
+
const featureNumber = new Map();
|
|
1947
|
+
for (const f of features.features)
|
|
1948
|
+
featureNumber.set(f.id, f.number);
|
|
1949
|
+
const out = [];
|
|
1950
|
+
for (const phase of phases) {
|
|
1951
|
+
const fnum = phase.featureId ? featureNumber.get(phase.featureId) : undefined;
|
|
1952
|
+
for (const entry of phase.handoffHistory ?? []) {
|
|
1953
|
+
if (!entry.file)
|
|
1954
|
+
continue;
|
|
1955
|
+
const localPath = join(this.localRoot(), entry.file);
|
|
1956
|
+
const legacyPath = join(this.root, entry.file);
|
|
1957
|
+
const content = await readFile(localPath, "utf-8").catch(() => readFile(legacyPath, "utf-8").catch(() => ""));
|
|
1958
|
+
out.push({
|
|
1959
|
+
phaseId: phase.id,
|
|
1960
|
+
featureId: phase.featureId,
|
|
1961
|
+
compositeRef: formatPhaseRef(phase.number, fnum),
|
|
1962
|
+
file: entry.file,
|
|
1963
|
+
archivedAt: entry.clearedAt,
|
|
1964
|
+
reason: entry.reason,
|
|
1965
|
+
firstLine: handoffFirstLine(content),
|
|
1966
|
+
content,
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
out.sort((a, b) => b.archivedAt.localeCompare(a.archivedAt));
|
|
1971
|
+
return out;
|
|
1972
|
+
}
|
|
1882
1973
|
async listOrphanPhases() {
|
|
1883
1974
|
const phases = await this.loadAllPhases();
|
|
1884
1975
|
const featuresDoc = await this.loadFeatures();
|
|
@@ -1909,12 +2000,7 @@ export class PlanStore {
|
|
|
1909
2000
|
return { found, removed: [] };
|
|
1910
2001
|
const orphanIds = new Set(found.map((phase) => phase.phaseId));
|
|
1911
2002
|
for (const orphan of found) {
|
|
1912
|
-
|
|
1913
|
-
await unlink(this.phasePath(orphan.phaseId));
|
|
1914
|
-
}
|
|
1915
|
-
catch {
|
|
1916
|
-
// already gone
|
|
1917
|
-
}
|
|
2003
|
+
await this.unlinkPhaseFiles(orphan.phaseId);
|
|
1918
2004
|
}
|
|
1919
2005
|
await this.updateFeatures((doc) => {
|
|
1920
2006
|
for (const feature of doc.features) {
|
|
@@ -1928,14 +2014,20 @@ export class PlanStore {
|
|
|
1928
2014
|
});
|
|
1929
2015
|
}
|
|
1930
2016
|
async deletePhase(phaseId) {
|
|
1931
|
-
|
|
1932
|
-
await unlink(this.phasePath(phaseId));
|
|
1933
|
-
}
|
|
1934
|
-
catch {
|
|
1935
|
-
// already gone
|
|
1936
|
-
}
|
|
2017
|
+
await this.unlinkPhaseFiles(phaseId);
|
|
1937
2018
|
await this.touchTimestamp();
|
|
1938
2019
|
}
|
|
2020
|
+
/** Remove a phase file AND its inline .bak backup. atomicUpdateJson (used by
|
|
2021
|
+
* updatePhase without root) writes the backup inline at phases/<id>.json.bak,
|
|
2022
|
+
* and readJson falls back to `${path}.bak` on a missing main file — so a
|
|
2023
|
+
* delete that leaves the .bak behind would RESURRECT the deleted phase on
|
|
2024
|
+
* the next read. Feature backups (written with root) live under
|
|
2025
|
+
* .local/backups/ and are never read by readJson, so only the inline .bak
|
|
2026
|
+
* needs removing here. */
|
|
2027
|
+
async unlinkPhaseFiles(phaseId) {
|
|
2028
|
+
await unlink(this.phasePath(phaseId)).catch(() => { });
|
|
2029
|
+
await unlink(`${this.phasePath(phaseId)}.bak`).catch(() => { });
|
|
2030
|
+
}
|
|
1939
2031
|
// ── Workspace-level operations ─────────────────────────────────────
|
|
1940
2032
|
/** Load the full workspace (manifest + phases + project + requirements + features) */
|
|
1941
2033
|
async loadWorkspace() {
|