@agent-plan/core 0.2.23 → 0.2.24
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/handoff-context.d.ts +60 -0
- package/dist/handoff-context.d.ts.map +1 -0
- package/dist/handoff-context.js +150 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/plan-store.d.ts +28 -0
- package/dist/plan-store.d.ts.map +1 -1
- package/dist/plan-store.js +142 -0
- package/dist/planner-rules.js +2 -2
- package/dist/read-tracking.d.ts +60 -23
- package/dist/read-tracking.d.ts.map +1 -1
- package/dist/read-tracking.js +131 -45
- package/dist/schema.d.ts +324 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +12 -0
- package/dist/task-start-outcome.d.ts +23 -0
- package/dist/task-start-outcome.d.ts.map +1 -0
- package/dist/task-start-outcome.js +23 -0
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Feature, Phase, Task } from "./schema.js";
|
|
2
|
+
export declare const COMPLETION_SUMMARY_HEADING = "**Completion summary:**";
|
|
3
|
+
export interface HandoffTaskContextUpdate {
|
|
4
|
+
taskId: string;
|
|
5
|
+
completionSummary: string;
|
|
6
|
+
verification: string;
|
|
7
|
+
remainingWork: string;
|
|
8
|
+
filesTouched?: string[];
|
|
9
|
+
decisions?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface HandoffPhaseContextUpdate {
|
|
12
|
+
progressSummary: string;
|
|
13
|
+
remainingWork: string;
|
|
14
|
+
decisions?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface HandoffFeatureContextUpdate {
|
|
17
|
+
workDone: string;
|
|
18
|
+
workRemaining: string;
|
|
19
|
+
}
|
|
20
|
+
export interface PhaseHandoffContextSync {
|
|
21
|
+
taskUpdates: HandoffTaskContextUpdate[];
|
|
22
|
+
phaseUpdate?: HandoffPhaseContextUpdate;
|
|
23
|
+
phaseNoUpdateReason?: string;
|
|
24
|
+
featureUpdate?: HandoffFeatureContextUpdate;
|
|
25
|
+
featureNoUpdateReason?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface PhaseHandoffAudit {
|
|
28
|
+
phaseId: string;
|
|
29
|
+
featureId: string;
|
|
30
|
+
handoff: string;
|
|
31
|
+
handoffUpdatedAt: string;
|
|
32
|
+
missingCompletionTaskIds: string[];
|
|
33
|
+
missingCompletionTasks: Array<{
|
|
34
|
+
id: string;
|
|
35
|
+
number: number;
|
|
36
|
+
title: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
export interface RefreshPhaseHandoffInput {
|
|
40
|
+
content: string;
|
|
41
|
+
expectedHandoffUpdatedAt: string;
|
|
42
|
+
reconciledExistingHandoff: boolean;
|
|
43
|
+
contextSync: PhaseHandoffContextSync;
|
|
44
|
+
}
|
|
45
|
+
export interface RefreshPhaseHandoffResult {
|
|
46
|
+
phase: Phase;
|
|
47
|
+
feature: Feature;
|
|
48
|
+
updatedTaskIds: string[];
|
|
49
|
+
handoffUpdatedAt: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function hasTaskCompletionEvidence(task: Task): boolean;
|
|
52
|
+
export declare function auditPhaseHandoff(phase: Phase, feature: Feature): PhaseHandoffAudit;
|
|
53
|
+
export declare function validateCanonicalHandoffContent(content: string): void;
|
|
54
|
+
export declare function validateHandoffContextSync(phase: Phase, feature: Feature, input: RefreshPhaseHandoffInput): void;
|
|
55
|
+
export declare function applyHandoffContextSync(phase: Phase, feature: Feature, input: RefreshPhaseHandoffInput, timestamp: string): {
|
|
56
|
+
phase: Phase;
|
|
57
|
+
feature: Feature;
|
|
58
|
+
updatedTaskIds: string[];
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=handoff-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff-context.d.ts","sourceRoot":"","sources":["../src/handoff-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExD,eAAO,MAAM,0BAA0B,4BAA4B,CAAC;AAEpE,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,wBAAwB,EAAE,CAAC;IACxC,WAAW,CAAC,EAAE,yBAAyB,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,sBAAsB,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB,EAAE,MAAM,CAAC;IACjC,yBAAyB,EAAE,OAAO,CAAC;IACnC,WAAW,EAAE,uBAAuB,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAI7D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAG,iBAAiB,CAYnF;AAkBD,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAgBrE;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,wBAAwB,GAC9B,IAAI,CA4CN;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,wBAAwB,EAC/B,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAqD9D"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export const COMPLETION_SUMMARY_HEADING = "**Completion summary:**";
|
|
2
|
+
export function hasTaskCompletionEvidence(task) {
|
|
3
|
+
if (task.status !== "done")
|
|
4
|
+
return true;
|
|
5
|
+
if (task.description.includes(COMPLETION_SUMMARY_HEADING))
|
|
6
|
+
return true;
|
|
7
|
+
return task.statusLog.some((entry) => entry.toStatus === "done" && entry.description.trim().length > 0);
|
|
8
|
+
}
|
|
9
|
+
export function auditPhaseHandoff(phase, feature) {
|
|
10
|
+
const missingCompletionTasks = phase.tasks
|
|
11
|
+
.filter((task) => task.status === "done" && !hasTaskCompletionEvidence(task))
|
|
12
|
+
.map((task) => ({ id: task.id, number: task.number, title: task.title }));
|
|
13
|
+
return {
|
|
14
|
+
phaseId: phase.id,
|
|
15
|
+
featureId: feature.id,
|
|
16
|
+
handoff: phase.handoff,
|
|
17
|
+
handoffUpdatedAt: phase.handoffUpdatedAt,
|
|
18
|
+
missingCompletionTaskIds: missingCompletionTasks.map((task) => task.id),
|
|
19
|
+
missingCompletionTasks,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function nonEmpty(value, field) {
|
|
23
|
+
const normalized = value?.trim() ?? "";
|
|
24
|
+
if (!normalized)
|
|
25
|
+
throw new Error(`${field} is required.`);
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
function uniqueStrings(values) {
|
|
29
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
30
|
+
}
|
|
31
|
+
function appendSection(existing, section) {
|
|
32
|
+
const normalized = section.trim();
|
|
33
|
+
if (!normalized || existing.includes(normalized))
|
|
34
|
+
return existing;
|
|
35
|
+
return existing.trim() ? `${existing.trim()}\n\n---\n${normalized}` : normalized;
|
|
36
|
+
}
|
|
37
|
+
export function validateCanonicalHandoffContent(content) {
|
|
38
|
+
const body = nonEmpty(content, "Handoff content");
|
|
39
|
+
const required = [
|
|
40
|
+
{ label: "Created at", pattern: /^Created at:\s*\S+/im },
|
|
41
|
+
{ label: "Updated at", pattern: /^Updated at:\s*\S+/im },
|
|
42
|
+
{ label: "Reason", pattern: /^Reason:\s*\S+/im },
|
|
43
|
+
{ label: "Current focus", pattern: /^##\s+Current focus\s*$/im },
|
|
44
|
+
{ label: "What was being done", pattern: /^##\s+What was being done\s*$/im },
|
|
45
|
+
{ label: "How to resume", pattern: /^##\s+How to resume\s*$/im },
|
|
46
|
+
{ label: "Files touched", pattern: /^##\s+Files touched\s*$/im },
|
|
47
|
+
{ label: "Blockers", pattern: /^##\s+Blockers\s*$/im },
|
|
48
|
+
{ label: "Next steps", pattern: /^##\s+Next steps\s*$/im },
|
|
49
|
+
{ label: "Recent decisions", pattern: /^##\s+Recent decisions\s*$/im },
|
|
50
|
+
];
|
|
51
|
+
const missing = required.filter((entry) => !entry.pattern.test(body)).map((entry) => entry.label);
|
|
52
|
+
if (missing.length > 0)
|
|
53
|
+
throw new Error(`Canonical handoff is missing required sections: ${missing.join(", ")}.`);
|
|
54
|
+
}
|
|
55
|
+
export function validateHandoffContextSync(phase, feature, input) {
|
|
56
|
+
validateCanonicalHandoffContent(input.content);
|
|
57
|
+
if (phase.status === "done" || phase.status === "canceled") {
|
|
58
|
+
throw new Error(`Cannot write a handoff on ${phase.status} phase ${phase.id}; completed phases have no pending handoff.`);
|
|
59
|
+
}
|
|
60
|
+
if (phase.handoffUpdatedAt !== input.expectedHandoffUpdatedAt) {
|
|
61
|
+
throw new Error("Handoff changed after preparation. Run handoff_prepare again and reconcile the latest content before writing.");
|
|
62
|
+
}
|
|
63
|
+
if (phase.handoff.trim() && !input.reconciledExistingHandoff) {
|
|
64
|
+
throw new Error("An active handoff already exists. Reconcile its still-relevant content and set reconciledExistingHandoff=true.");
|
|
65
|
+
}
|
|
66
|
+
const sync = input.contextSync;
|
|
67
|
+
const taskIds = sync.taskUpdates.map((update) => nonEmpty(update.taskId, "Task update taskId"));
|
|
68
|
+
if (new Set(taskIds).size !== taskIds.length)
|
|
69
|
+
throw new Error("Each task may appear only once in handoff context updates.");
|
|
70
|
+
const phaseTaskIds = new Set(phase.tasks.map((task) => task.id));
|
|
71
|
+
for (const taskId of taskIds) {
|
|
72
|
+
if (!phaseTaskIds.has(taskId))
|
|
73
|
+
throw new Error(`Task ${taskId} does not belong to phase ${phase.id}.`);
|
|
74
|
+
}
|
|
75
|
+
for (const update of sync.taskUpdates) {
|
|
76
|
+
nonEmpty(update.completionSummary, `Task ${update.taskId} completionSummary`);
|
|
77
|
+
nonEmpty(update.verification, `Task ${update.taskId} verification`);
|
|
78
|
+
nonEmpty(update.remainingWork, `Task ${update.taskId} remainingWork`);
|
|
79
|
+
}
|
|
80
|
+
const audit = auditPhaseHandoff(phase, feature);
|
|
81
|
+
const covered = new Set(taskIds);
|
|
82
|
+
const uncovered = audit.missingCompletionTaskIds.filter((taskId) => !covered.has(taskId));
|
|
83
|
+
if (uncovered.length > 0) {
|
|
84
|
+
throw new Error(`Done tasks are missing durable completion evidence: ${uncovered.join(", ")}. Include context updates for every listed task.`);
|
|
85
|
+
}
|
|
86
|
+
if (sync.phaseUpdate) {
|
|
87
|
+
nonEmpty(sync.phaseUpdate.progressSummary, "Phase progressSummary");
|
|
88
|
+
nonEmpty(sync.phaseUpdate.remainingWork, "Phase remainingWork");
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
nonEmpty(sync.phaseNoUpdateReason, "phaseNoUpdateReason");
|
|
92
|
+
}
|
|
93
|
+
if (sync.featureUpdate) {
|
|
94
|
+
nonEmpty(sync.featureUpdate.workDone, "Feature workDone");
|
|
95
|
+
nonEmpty(sync.featureUpdate.workRemaining, "Feature workRemaining");
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
nonEmpty(sync.featureNoUpdateReason, "featureNoUpdateReason");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export function applyHandoffContextSync(phase, feature, input, timestamp) {
|
|
102
|
+
validateHandoffContextSync(phase, feature, input);
|
|
103
|
+
const nextPhase = structuredClone(phase);
|
|
104
|
+
const nextFeature = structuredClone(feature);
|
|
105
|
+
const updatedTaskIds = [];
|
|
106
|
+
for (const update of input.contextSync.taskUpdates) {
|
|
107
|
+
const task = nextPhase.tasks.find((candidate) => candidate.id === update.taskId);
|
|
108
|
+
const files = uniqueStrings(update.filesTouched ?? []);
|
|
109
|
+
const decisions = uniqueStrings(update.decisions ?? []);
|
|
110
|
+
const section = [
|
|
111
|
+
COMPLETION_SUMMARY_HEADING,
|
|
112
|
+
update.completionSummary.trim(),
|
|
113
|
+
"",
|
|
114
|
+
"**Verification:**",
|
|
115
|
+
update.verification.trim(),
|
|
116
|
+
"",
|
|
117
|
+
"**Remaining or unverified:**",
|
|
118
|
+
update.remainingWork.trim(),
|
|
119
|
+
...(files.length > 0 ? ["", "**Files touched:**", ...files.map((file) => `- ${file}`)] : []),
|
|
120
|
+
].join("\n");
|
|
121
|
+
task.description = appendSection(task.description, section);
|
|
122
|
+
task.descriptionUpdatedAt = timestamp;
|
|
123
|
+
task.decisions = uniqueStrings([...(task.decisions ?? []), ...decisions]);
|
|
124
|
+
task.updatedAt = timestamp;
|
|
125
|
+
updatedTaskIds.push(task.id);
|
|
126
|
+
}
|
|
127
|
+
const phaseUpdate = input.contextSync.phaseUpdate;
|
|
128
|
+
if (phaseUpdate) {
|
|
129
|
+
const section = [
|
|
130
|
+
"**Handoff context update:**",
|
|
131
|
+
phaseUpdate.progressSummary.trim(),
|
|
132
|
+
"",
|
|
133
|
+
"**Remaining work:**",
|
|
134
|
+
phaseUpdate.remainingWork.trim(),
|
|
135
|
+
].join("\n");
|
|
136
|
+
nextPhase.notes = appendSection(nextPhase.notes, section);
|
|
137
|
+
nextPhase.decisions = uniqueStrings([...(nextPhase.decisions ?? []), ...(phaseUpdate.decisions ?? [])]);
|
|
138
|
+
}
|
|
139
|
+
const featureUpdate = input.contextSync.featureUpdate;
|
|
140
|
+
if (featureUpdate) {
|
|
141
|
+
nextFeature.workDone = appendSection(nextFeature.workDone, featureUpdate.workDone);
|
|
142
|
+
nextFeature.workRemaining = appendSection(nextFeature.workRemaining, featureUpdate.workRemaining);
|
|
143
|
+
}
|
|
144
|
+
nextPhase.handoff = input.content.trim();
|
|
145
|
+
nextPhase.handoffUpdatedAt = timestamp;
|
|
146
|
+
nextPhase.handoffReadAt = "";
|
|
147
|
+
nextPhase.updatedAt = timestamp;
|
|
148
|
+
nextFeature.updatedAt = timestamp;
|
|
149
|
+
return { phase: nextPhase, feature: nextFeature, updatedTaskIds };
|
|
150
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export * from "./planner-rules.js";
|
|
|
7
7
|
export * from "./display-status.js";
|
|
8
8
|
export * from "./task-context.js";
|
|
9
9
|
export * from "./task-selection.js";
|
|
10
|
+
export * from "./task-start-outcome.js";
|
|
10
11
|
export * from "./read-tracking.js";
|
|
12
|
+
export * from "./handoff-context.js";
|
|
11
13
|
export * from "./package-version.js";
|
|
12
14
|
export { PlanStore, PlanStoreError, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, type PhaseHandoffSummary, type OrphanPhaseSummary } from "./plan-store.js";
|
|
13
15
|
export { PlanRenderer } from "./renderer.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,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,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,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,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
|
@@ -7,7 +7,9 @@ export * from "./planner-rules.js";
|
|
|
7
7
|
export * from "./display-status.js";
|
|
8
8
|
export * from "./task-context.js";
|
|
9
9
|
export * from "./task-selection.js";
|
|
10
|
+
export * from "./task-start-outcome.js";
|
|
10
11
|
export * from "./read-tracking.js";
|
|
12
|
+
export * from "./handoff-context.js";
|
|
11
13
|
export * from "./package-version.js";
|
|
12
14
|
export { PlanStore, PlanStoreError, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock } from "./plan-store.js";
|
|
13
15
|
export { PlanRenderer } from "./renderer.js";
|
package/dist/plan-store.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type Feature, type FeaturesDocument, type Manifest, type Phase, type Task, type TaskPauseSnapshot, type PlanWorkspace, type Project, type RequirementsDocument, type Requirement, type ActivityEntry, type ActivityLog, type CodebaseProfile, type ResumeFocus, type WorkDeviation } from "./schema.js";
|
|
3
3
|
import { type ParentDisplay } from "./display-status.js";
|
|
4
|
+
import { type PhaseHandoffAudit, type RefreshPhaseHandoffInput, type RefreshPhaseHandoffResult } from "./handoff-context.js";
|
|
5
|
+
export interface RecordContextReadInput {
|
|
6
|
+
sessionId: string;
|
|
7
|
+
phaseId: string;
|
|
8
|
+
taskId: string;
|
|
9
|
+
featureId?: string;
|
|
10
|
+
requirementIds?: string[];
|
|
11
|
+
createdAt?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface RecordContextReadResult {
|
|
14
|
+
phase: Phase;
|
|
15
|
+
feature?: Feature;
|
|
16
|
+
requirements: Requirement[];
|
|
17
|
+
createdAt: string;
|
|
18
|
+
}
|
|
4
19
|
export interface PlanStoreErrorDetails {
|
|
5
20
|
path?: string;
|
|
6
21
|
operation?: string;
|
|
@@ -338,6 +353,12 @@ export declare class PlanStore {
|
|
|
338
353
|
setWorkDeviationState(id: string, state: "active" | "resume-required" | "resolved" | "resumed" | "canceled", timestamp?: string): Promise<Project>;
|
|
339
354
|
updateFeatures(updater: (f: FeaturesDocument) => FeaturesDocument): Promise<FeaturesDocument>;
|
|
340
355
|
updateRequirements(updater: (r: RequirementsDocument) => RequirementsDocument): Promise<RequirementsDocument>;
|
|
356
|
+
/**
|
|
357
|
+
* Persist completion of one full task → phase → feature → requirements read
|
|
358
|
+
* sequence. Session metadata is deliberately the only changed entity field:
|
|
359
|
+
* semantic updatedAt/descriptionUpdatedAt values are preserved exactly.
|
|
360
|
+
*/
|
|
361
|
+
recordContextRead(input: RecordContextReadInput): Promise<RecordContextReadResult>;
|
|
341
362
|
saveProject(project: Project): Promise<void>;
|
|
342
363
|
saveFeatures(features: FeaturesDocument): Promise<void>;
|
|
343
364
|
/** Per-file write of all features + orphan reconcile. No lock (caller holds withFeaturesLock). */
|
|
@@ -356,6 +377,13 @@ export declare class PlanStore {
|
|
|
356
377
|
resumeTask(phaseId: string, taskId: string, timestamp?: string): Promise<Task>;
|
|
357
378
|
/** Get the handoff text for a phase ("" if none). Throws if phase missing. */
|
|
358
379
|
getPhaseHandoff(phaseId: string): Promise<string>;
|
|
380
|
+
/** Audit one exact phase before preparing a handoff refresh. */
|
|
381
|
+
preparePhaseHandoff(phaseId: string): Promise<PhaseHandoffAudit>;
|
|
382
|
+
/** Refresh the single active handoff and synchronize durable task/phase/feature
|
|
383
|
+
* context. Unlike setPhaseHandoff(), this deliberately does not archive the
|
|
384
|
+
* previous active body: callers must reconcile it using the optimistic
|
|
385
|
+
* handoffUpdatedAt token returned by preparePhaseHandoff(). */
|
|
386
|
+
refreshPhaseHandoff(phaseId: string, input: RefreshPhaseHandoffInput): Promise<RefreshPhaseHandoffResult>;
|
|
359
387
|
/** Set the handoff text for a phase + stamp handoffUpdatedAt. A completed or
|
|
360
388
|
* canceled phase cannot receive a new operational handoff. Replacing an
|
|
361
389
|
* existing handoff archives the previous content as `superseded` first. */
|
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":"AAGA,OAAO,EAAE,CAAC,EAAY,MAAM,KAAK,CAAC;AAclC,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,gBAAgB,EAGrB,KAAK,QAAQ,EAEb,KAAK,KAAK,EACV,KAAK,IAAI,EACT,KAAK,iBAAiB,EAEtB,KAAK,aAAa,EAElB,KAAK,OAAO,EAEZ,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAKhB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,aAAa,EAEnB,MAAM,aAAa,CAAC;AAErB,OAAO,EAA4C,KAAK,aAAa,EAAuB,MAAM,qBAAqB,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
|
@@ -18,9 +18,22 @@ import { CodebaseProfileSchema, FeatureSchema, FeaturesDocumentSchema, ManifestS
|
|
|
18
18
|
import { createFeatureId, createPhaseId, createRequirementId, createShortId, createStatusLogEntryId, createTaskId, formatPhaseRef, isLegacyPhaseId } from "./naming.js";
|
|
19
19
|
import { deriveParentDisplay, fromCanonicalStatus } from "./display-status.js";
|
|
20
20
|
import { loadExtensionRules, PLANNER_EXTENSION_RULES } from "./planner-rules.js";
|
|
21
|
+
import { applyHandoffContextSync, auditPhaseHandoff, } from "./handoff-context.js";
|
|
21
22
|
function nowISO() {
|
|
22
23
|
return new Date().toISOString();
|
|
23
24
|
}
|
|
25
|
+
const MAX_SESSION_INFO_ENTRIES = 16;
|
|
26
|
+
function upsertSessionInfo(entity, sessionId, createdAt) {
|
|
27
|
+
const nextInfo = [
|
|
28
|
+
...entity.sessionInfo.filter((entry) => entry.sessionId !== sessionId),
|
|
29
|
+
{ sessionId, createdAt },
|
|
30
|
+
]
|
|
31
|
+
.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
|
|
32
|
+
.slice(0, MAX_SESSION_INFO_ENTRIES);
|
|
33
|
+
const changed = nextInfo.length !== entity.sessionInfo.length
|
|
34
|
+
|| nextInfo.some((entry, index) => entry.sessionId !== entity.sessionInfo[index]?.sessionId || entry.createdAt !== entity.sessionInfo[index]?.createdAt);
|
|
35
|
+
return changed ? { entity: { ...entity, sessionInfo: nextInfo }, changed: true } : { entity, changed: false };
|
|
36
|
+
}
|
|
24
37
|
function resolveStoredFeatureId(features, ref) {
|
|
25
38
|
const raw = ref?.trim();
|
|
26
39
|
if (!raw)
|
|
@@ -1942,6 +1955,90 @@ export class PlanStore {
|
|
|
1942
1955
|
await this.maybeAutoSync();
|
|
1943
1956
|
return updated;
|
|
1944
1957
|
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Persist completion of one full task → phase → feature → requirements read
|
|
1960
|
+
* sequence. Session metadata is deliberately the only changed entity field:
|
|
1961
|
+
* semantic updatedAt/descriptionUpdatedAt values are preserved exactly.
|
|
1962
|
+
*/
|
|
1963
|
+
async recordContextRead(input) {
|
|
1964
|
+
const sessionId = input.sessionId.trim();
|
|
1965
|
+
if (!sessionId)
|
|
1966
|
+
throw new PlanStoreError("Context-read attestation requires a non-empty sessionId.");
|
|
1967
|
+
const createdAt = TimestampSchema.parse(input.createdAt ?? nowISO());
|
|
1968
|
+
const requirementIds = [...new Set((input.requirementIds ?? []).map((id) => id.trim()).filter(Boolean))];
|
|
1969
|
+
return this.runAsBatch(async () => {
|
|
1970
|
+
const originalPhase = await this.loadPhase(input.phaseId);
|
|
1971
|
+
const taskIndex = originalPhase.tasks.findIndex((task) => task.id === input.taskId);
|
|
1972
|
+
if (taskIndex < 0)
|
|
1973
|
+
throw new PlanStoreError(`Task ${input.taskId} does not belong to phase ${input.phaseId}.`);
|
|
1974
|
+
const originalFeatures = await this.loadFeatures();
|
|
1975
|
+
const featureId = input.featureId?.trim() || originalPhase.featureId;
|
|
1976
|
+
const featureIndex = featureId
|
|
1977
|
+
? originalFeatures.features.findIndex((feature) => feature.id === featureId)
|
|
1978
|
+
: -1;
|
|
1979
|
+
if (featureId && featureIndex < 0)
|
|
1980
|
+
throw new PlanStoreError(`Feature ${featureId} was not found for context-read attestation.`);
|
|
1981
|
+
const originalRequirements = await this.loadRequirements();
|
|
1982
|
+
const missingRequirement = requirementIds.find((id) => !originalRequirements.requirements.some((requirement) => requirement.id === id));
|
|
1983
|
+
if (missingRequirement)
|
|
1984
|
+
throw new PlanStoreError(`Requirement ${missingRequirement} was not found for context-read attestation.`);
|
|
1985
|
+
const nextPhase = structuredClone(originalPhase);
|
|
1986
|
+
const nextTask = upsertSessionInfo(nextPhase.tasks[taskIndex], sessionId, createdAt);
|
|
1987
|
+
nextPhase.tasks[taskIndex] = nextTask.entity;
|
|
1988
|
+
const nextPhaseInfo = upsertSessionInfo(nextPhase, sessionId, createdAt);
|
|
1989
|
+
const phaseChanged = nextTask.changed || nextPhaseInfo.changed;
|
|
1990
|
+
let nextFeature;
|
|
1991
|
+
let featureChanged = false;
|
|
1992
|
+
if (featureIndex >= 0) {
|
|
1993
|
+
const featureResult = upsertSessionInfo(structuredClone(originalFeatures.features[featureIndex]), sessionId, createdAt);
|
|
1994
|
+
nextFeature = featureResult.entity;
|
|
1995
|
+
featureChanged = featureResult.changed;
|
|
1996
|
+
}
|
|
1997
|
+
const nextRequirements = structuredClone(originalRequirements);
|
|
1998
|
+
let requirementsChanged = false;
|
|
1999
|
+
for (const requirement of nextRequirements.requirements) {
|
|
2000
|
+
if (!requirementIds.includes(requirement.id))
|
|
2001
|
+
continue;
|
|
2002
|
+
const result = upsertSessionInfo(requirement, sessionId, createdAt);
|
|
2003
|
+
if (result.changed) {
|
|
2004
|
+
Object.assign(requirement, result.entity);
|
|
2005
|
+
requirementsChanged = true;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
if (!phaseChanged && !featureChanged && !requirementsChanged) {
|
|
2009
|
+
return {
|
|
2010
|
+
phase: originalPhase,
|
|
2011
|
+
...(featureIndex >= 0 ? { feature: originalFeatures.features[featureIndex] } : {}),
|
|
2012
|
+
requirements: originalRequirements.requirements.filter((requirement) => requirementIds.includes(requirement.id)),
|
|
2013
|
+
createdAt,
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
try {
|
|
2017
|
+
if (featureChanged && nextFeature)
|
|
2018
|
+
await this.saveFeature(nextFeature);
|
|
2019
|
+
if (phaseChanged)
|
|
2020
|
+
await this.savePhase(nextPhaseInfo.entity);
|
|
2021
|
+
if (requirementsChanged)
|
|
2022
|
+
await this.saveRequirements(nextRequirements);
|
|
2023
|
+
}
|
|
2024
|
+
catch (error) {
|
|
2025
|
+
const rollbackErrors = [];
|
|
2026
|
+
if (featureChanged && nextFeature)
|
|
2027
|
+
await this.saveFeature(originalFeatures.features[featureIndex]).catch((rollbackError) => rollbackErrors.push(rollbackError));
|
|
2028
|
+
if (phaseChanged)
|
|
2029
|
+
await this.savePhase(originalPhase).catch((rollbackError) => rollbackErrors.push(rollbackError));
|
|
2030
|
+
if (requirementsChanged)
|
|
2031
|
+
await this.saveRequirements(originalRequirements).catch((rollbackError) => rollbackErrors.push(rollbackError));
|
|
2032
|
+
if (rollbackErrors.length > 0)
|
|
2033
|
+
throw new AggregateError([error, ...rollbackErrors], "Context-read attestation failed and rollback was incomplete.");
|
|
2034
|
+
throw error;
|
|
2035
|
+
}
|
|
2036
|
+
const phase = await this.loadPhase(input.phaseId);
|
|
2037
|
+
const feature = featureIndex >= 0 ? (await this.loadFeatures()).features.find((candidate) => candidate.id === featureId) : undefined;
|
|
2038
|
+
const requirements = (await this.loadRequirements()).requirements.filter((requirement) => requirementIds.includes(requirement.id));
|
|
2039
|
+
return { phase, ...(feature ? { feature } : {}), requirements, createdAt };
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
1945
2042
|
async saveProject(project) {
|
|
1946
2043
|
// Runtime workDeviations live in .local/deviations.json (T299); never
|
|
1947
2044
|
// persist them here so shared project.json stays stable across worktrees.
|
|
@@ -2138,6 +2235,51 @@ export class PlanStore {
|
|
|
2138
2235
|
async getPhaseHandoff(phaseId) {
|
|
2139
2236
|
return (await this.loadPhase(phaseId)).handoff;
|
|
2140
2237
|
}
|
|
2238
|
+
/** Audit one exact phase before preparing a handoff refresh. */
|
|
2239
|
+
async preparePhaseHandoff(phaseId) {
|
|
2240
|
+
const phase = await this.loadPhase(phaseId);
|
|
2241
|
+
if (!phase.featureId)
|
|
2242
|
+
throw new PlanStoreError(`Phase ${phaseId} has no parent feature; durable handoff context cannot be synchronized.`);
|
|
2243
|
+
const feature = (await this.loadFeatures()).features.find((candidate) => candidate.id === phase.featureId);
|
|
2244
|
+
if (!feature)
|
|
2245
|
+
throw new PlanStoreError(`Parent feature ${phase.featureId} not found for phase ${phaseId}.`);
|
|
2246
|
+
return auditPhaseHandoff(phase, feature);
|
|
2247
|
+
}
|
|
2248
|
+
/** Refresh the single active handoff and synchronize durable task/phase/feature
|
|
2249
|
+
* context. Unlike setPhaseHandoff(), this deliberately does not archive the
|
|
2250
|
+
* previous active body: callers must reconcile it using the optimistic
|
|
2251
|
+
* handoffUpdatedAt token returned by preparePhaseHandoff(). */
|
|
2252
|
+
async refreshPhaseHandoff(phaseId, input) {
|
|
2253
|
+
return this.runAsBatch(async () => {
|
|
2254
|
+
const originalPhase = await this.loadPhase(phaseId);
|
|
2255
|
+
if (!originalPhase.featureId)
|
|
2256
|
+
throw new PlanStoreError(`Phase ${phaseId} has no parent feature; durable handoff context cannot be synchronized.`);
|
|
2257
|
+
const originalFeatures = await this.loadFeatures();
|
|
2258
|
+
const featureIndex = originalFeatures.features.findIndex((candidate) => candidate.id === originalPhase.featureId);
|
|
2259
|
+
if (featureIndex < 0)
|
|
2260
|
+
throw new PlanStoreError(`Parent feature ${originalPhase.featureId} not found for phase ${phaseId}.`);
|
|
2261
|
+
const timestamp = nowISO();
|
|
2262
|
+
const applied = applyHandoffContextSync(originalPhase, originalFeatures.features[featureIndex], input, timestamp);
|
|
2263
|
+
const nextFeatures = structuredClone(originalFeatures);
|
|
2264
|
+
nextFeatures.features[featureIndex] = applied.feature;
|
|
2265
|
+
try {
|
|
2266
|
+
await this.saveFeatures(nextFeatures);
|
|
2267
|
+
await this.savePhase(applied.phase);
|
|
2268
|
+
}
|
|
2269
|
+
catch (error) {
|
|
2270
|
+
const rollbackErrors = [];
|
|
2271
|
+
await this.saveFeatures(originalFeatures).catch((rollbackError) => rollbackErrors.push(rollbackError));
|
|
2272
|
+
await this.savePhase(originalPhase).catch((rollbackError) => rollbackErrors.push(rollbackError));
|
|
2273
|
+
if (rollbackErrors.length > 0) {
|
|
2274
|
+
throw new AggregateError([error, ...rollbackErrors], "Handoff refresh failed and rollback was incomplete.");
|
|
2275
|
+
}
|
|
2276
|
+
throw error;
|
|
2277
|
+
}
|
|
2278
|
+
const phase = await this.loadPhase(phaseId);
|
|
2279
|
+
const feature = (await this.loadFeatures()).features.find((candidate) => candidate.id === originalPhase.featureId);
|
|
2280
|
+
return { phase, feature, updatedTaskIds: applied.updatedTaskIds, handoffUpdatedAt: phase.handoffUpdatedAt };
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2141
2283
|
/** Set the handoff text for a phase + stamp handoffUpdatedAt. A completed or
|
|
2142
2284
|
* canceled phase cannot receive a new operational handoff. Replacing an
|
|
2143
2285
|
* existing handoff archives the previous content as `superseded` first. */
|
package/dist/planner-rules.js
CHANGED
|
@@ -14,7 +14,7 @@ export const PLANNER_EXTENSION_RULES = [
|
|
|
14
14
|
// §1 — source of truth
|
|
15
15
|
"Keep the planner as the single operational source of truth while working: read the relevant planner state before starting; update it when an activity starts, changes state, blocks, or concludes; and record next steps, blockers, and decisions in the relevant planner entities. Never leave work only in the conversation.",
|
|
16
16
|
// §2 — task lifecycle
|
|
17
|
-
"Respect the task lifecycle strictly. Always call task_start before touching code, and task_complete when a deliverable is done. Sync state changes (start/complete/block) to the planner at the exact moment they happen — never batch updates at session end. If the
|
|
17
|
+
"Respect the task lifecycle strictly. Always call task_start before touching code, and task_complete with durable evidence of shipped work, verification (including partial verification), remaining/unverified work, files, and decisions when a deliverable is done. Never enter in-progress or done through task_update. Sync state changes (start/complete/block) to the planner at the exact moment they happen — never batch updates at session end. If task_start is denied, the task remains planned: satisfy the stated read prerequisites and retry, and never claim work started without an explicit successful start result. A task marked in-progress means you are actually working on it; if you stop, close or block it with a motivation in statusLog. Derived feature/phase status is computed from tasks, not stored in JSON.",
|
|
18
18
|
// §3 — markdown not source of truth
|
|
19
19
|
"Do not treat markdown as the source of truth for the plan. The plan's primary source is structured data in .planner/; markdown is a generated, human/agent-readable view.",
|
|
20
20
|
// §5 — plan location
|
|
@@ -28,7 +28,7 @@ export const PLANNER_EXTENSION_RULES = [
|
|
|
28
28
|
// §10 — references
|
|
29
29
|
"Reference entities with human, unique, composite IDs — Feature 'F001 - Name', Phase 'P001(F001) - Title', Task 'T003 - Number'. Short forms P003/T007 and the 5-char global shortId (e.g. UUXD1) are also valid. Never reference raw UUIDs. To locate an entity, use the compact list tools (feature_list/phase_list/task_list), not by reading .planner/*.json files.",
|
|
30
30
|
// §12 — handoff
|
|
31
|
-
"Handoff is per-phase (phase.handoff), not a file. Write it only on explicit user request
|
|
31
|
+
"Handoff is per-phase (phase.handoff), not a file. Write it only on explicit user request and only after the exact feature+phase target is confirmed. Run handoff_prepare for that phase, reconcile all still-relevant existing content into one active handoff, and synchronize durable task/phase/feature context in the same handoff_write operation. A pending handoff never blocks task_start and is archived only when the phase completes or the user explicitly clears it; refreshing it must not create superseded copies.",
|
|
32
32
|
// §12 — operational hygiene
|
|
33
33
|
"Operational hygiene: start the task (task_start) before thinking about implementation; complete it (task_complete) as part of delivering the deliverable, not after; motivate every block so a third party can understand the impediment.",
|
|
34
34
|
// Avvio del planner
|