@agent-plan/core 0.2.19-next.18 → 0.2.19-next.19
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/naming.d.ts +6 -3
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +24 -3
- package/dist/plan-store.d.ts +15 -7
- package/dist/plan-store.d.ts.map +1 -1
- package/dist/plan-store.js +184 -65
- package/dist/schema.d.ts +5 -5
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/task-context.d.ts +16 -0
- package/dist/task-context.d.ts.map +1 -0
- package/dist/task-context.js +48 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./schema.js";
|
|
|
4
4
|
export * from "./checklist.js";
|
|
5
5
|
export * from "./recap.js";
|
|
6
6
|
export * from "./display-status.js";
|
|
7
|
+
export * from "./task-context.js";
|
|
7
8
|
export { PlanStore, PlanStoreError, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, type PhaseHandoffSummary, type OrphanPhaseSummary } from "./plan-store.js";
|
|
8
9
|
export { PlanRenderer } from "./renderer.js";
|
|
9
10
|
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,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,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
|
@@ -4,6 +4,7 @@ export * from "./schema.js";
|
|
|
4
4
|
export * from "./checklist.js";
|
|
5
5
|
export * from "./recap.js";
|
|
6
6
|
export * from "./display-status.js";
|
|
7
|
+
export * from "./task-context.js";
|
|
7
8
|
export { PlanStore, PlanStoreError, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock } from "./plan-store.js";
|
|
8
9
|
export { PlanRenderer } from "./renderer.js";
|
|
9
10
|
export { ExportService } from "./export-service.js";
|
package/dist/naming.d.ts
CHANGED
|
@@ -18,9 +18,12 @@ export declare function validateResolvedTarget<T extends {
|
|
|
18
18
|
error: string;
|
|
19
19
|
}>;
|
|
20
20
|
/** Generate a globally-unique short id (5 chars, Crockford Base32, e.g. `UUXD1`-style
|
|
21
|
-
* but without 0/1/I/O).
|
|
22
|
-
*
|
|
23
|
-
|
|
21
|
+
* but without 0/1/I/O). If `seed` is provided, the id is derived deterministically
|
|
22
|
+
* from a stable hash of the seed so two worktrees starting from the same commit
|
|
23
|
+
* produce identical shortIds for the same entity. Retries until the id is not in
|
|
24
|
+
* `existing` (project-scoped collision guard). Throws only in the impossible
|
|
25
|
+
* saturation case (~50 retries). */
|
|
26
|
+
export declare function createShortId(existing?: Set<string>, seed?: string): string;
|
|
24
27
|
export declare function normalizeSlug(input: string): string;
|
|
25
28
|
/** Normalize, truncate to maxLen, and strip dangling dashes so the result
|
|
26
29
|
* always satisfies SlugSchema (/^[a-z0-9]+(?:-[a-z0-9]+)*$/). Returns fallback
|
package/dist/naming.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAKA;iFACiF;AACjF,eAAO,MAAM,kBAAkB,qCAAqC,CAAC;AAErE,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,gBAAgB,QAAkB,CAAC;AAEhD,4EAA4E;AAC5E,eAAO,MAAM,YAAY,QAAoE,CAAC;AAE9F,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEtD;AAED;;gEAEgE;AAChE,wBAAsB,sBAAsB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACnE,IAAI,EAAE,SAAS,GAAG,OAAO,EACzB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,GACnC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAStD;AAGD
|
|
1
|
+
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAKA;iFACiF;AACjF,eAAO,MAAM,kBAAkB,qCAAqC,CAAC;AAErE,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,gBAAgB,QAAkB,CAAC;AAEhD,4EAA4E;AAC5E,eAAO,MAAM,YAAY,QAAoE,CAAC;AAE9F,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEtD;AAED;;gEAEgE;AAChE,wBAAsB,sBAAsB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACnE,IAAI,EAAE,SAAS,GAAG,OAAO,EACzB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,GACnC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAStD;AAGD;;;;;qCAKqC;AACrC,wBAAgB,aAAa,CAAC,QAAQ,GAAE,GAAG,CAAC,MAAM,CAAa,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CA2BtF;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED;;;;8BAI8B;AAC9B,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAK,EAAE,QAAQ,SAAa,GAAG,MAAM,CAGnF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;iBAEiB;AACjB,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAGlF;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,oFAAoF;AACpF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,EAChD,QAAQ,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,GACzC,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3F;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C"}
|
package/dist/naming.js
CHANGED
|
@@ -25,9 +25,30 @@ export async function validateResolvedTarget(kind, resolvedId, loader) {
|
|
|
25
25
|
return { ok: true };
|
|
26
26
|
}
|
|
27
27
|
/** Generate a globally-unique short id (5 chars, Crockford Base32, e.g. `UUXD1`-style
|
|
28
|
-
* but without 0/1/I/O).
|
|
29
|
-
*
|
|
30
|
-
|
|
28
|
+
* but without 0/1/I/O). If `seed` is provided, the id is derived deterministically
|
|
29
|
+
* from a stable hash of the seed so two worktrees starting from the same commit
|
|
30
|
+
* produce identical shortIds for the same entity. Retries until the id is not in
|
|
31
|
+
* `existing` (project-scoped collision guard). Throws only in the impossible
|
|
32
|
+
* saturation case (~50 retries). */
|
|
33
|
+
export function createShortId(existing = new Set(), seed) {
|
|
34
|
+
if (seed) {
|
|
35
|
+
// Stable string hash -> Crockford encoding.
|
|
36
|
+
let hash = 0;
|
|
37
|
+
for (const c of seed) {
|
|
38
|
+
hash = ((hash << 5) - hash + c.charCodeAt(0)) | 0;
|
|
39
|
+
}
|
|
40
|
+
const base = Math.abs(hash);
|
|
41
|
+
for (let offset = 0; offset < 64; offset += 1) {
|
|
42
|
+
let id = "";
|
|
43
|
+
let value = base + offset;
|
|
44
|
+
for (let i = 0; i < SHORT_ID_LENGTH; i += 1) {
|
|
45
|
+
id = CROCKFORD_ALPHABET[value % CROCKFORD_ALPHABET.length] + id;
|
|
46
|
+
value = Math.floor(value / CROCKFORD_ALPHABET.length);
|
|
47
|
+
}
|
|
48
|
+
if (!existing.has(id))
|
|
49
|
+
return id;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
31
52
|
const max = CROCKFORD_ALPHABET.length;
|
|
32
53
|
for (let attempt = 0; attempt < 64; attempt += 1) {
|
|
33
54
|
let id = "";
|
package/dist/plan-store.d.ts
CHANGED
|
@@ -132,6 +132,17 @@ export declare class PlanStore {
|
|
|
132
132
|
private codebasePath;
|
|
133
133
|
private resumePath;
|
|
134
134
|
private activityPath;
|
|
135
|
+
private localRoot;
|
|
136
|
+
private timestampPath;
|
|
137
|
+
private backupsDir;
|
|
138
|
+
private tmpDir;
|
|
139
|
+
private handoffArchiveDir;
|
|
140
|
+
/** One-time migration for plans created before .planner/.local/ existed.
|
|
141
|
+
* Moves a legacy root-level file into .local/ if the legacy file exists and
|
|
142
|
+
* the .local/ counterpart does not. Safe to call on every load. */
|
|
143
|
+
private migrateLegacyLocalFile;
|
|
144
|
+
private migrateLegacyGeneratedDir;
|
|
145
|
+
private migrateLegacyHandoffArchive;
|
|
135
146
|
init(projectName: string): Promise<void>;
|
|
136
147
|
exists(): Promise<boolean>;
|
|
137
148
|
loadManifest(): Promise<Manifest>;
|
|
@@ -286,10 +297,6 @@ export declare class PlanStore {
|
|
|
286
297
|
/** Set the handoff text for a phase + stamp handoffUpdatedAt. Atomic per-file
|
|
287
298
|
* update via updatePhase (so the web UI refreshes via maybeAutoSync). */
|
|
288
299
|
setPhaseHandoff(phaseId: string, text: string): Promise<void>;
|
|
289
|
-
/** Directory where cleared handoff content is archived as .md files
|
|
290
|
-
* (gitignored). Keeps the phase JSON lean while making past handoffs
|
|
291
|
-
* recoverable + human-readable. */
|
|
292
|
-
private handoffArchiveDir;
|
|
293
300
|
/** Mark the phase handoff as read/acknowledged on recap (sets handoffReadAt).
|
|
294
301
|
* Does NOT clear the handoff — content is kept until a task starts or the
|
|
295
302
|
* phase completes, so a restart between read and resume does not lose it. */
|
|
@@ -323,9 +330,10 @@ export declare class PlanStore {
|
|
|
323
330
|
deletePhase(phaseId: string): Promise<void>;
|
|
324
331
|
/** Load the full workspace (manifest + phases + project + requirements + features) */
|
|
325
332
|
loadWorkspace(): Promise<PlanWorkspace>;
|
|
326
|
-
/** Load all data, render markdown, and write into generated/.
|
|
333
|
+
/** Load all data, render markdown, and write into generated/. Skips files
|
|
334
|
+
* whose content is unchanged to avoid unnecessary backup churn. */
|
|
327
335
|
writeGenerated(): Promise<string[]>;
|
|
328
|
-
/** Update
|
|
329
|
-
private
|
|
336
|
+
/** Update .local/timestamp.json to reflect a change. */
|
|
337
|
+
private touchTimestamp;
|
|
330
338
|
}
|
|
331
339
|
//# sourceMappingURL=plan-store.d.ts.map
|
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,EAGL,KAAK,OAAO,EACZ,KAAK,gBAAgB,EAGrB,KAAK,QAAQ,EAEb,KAAK,KAAK,EAEV,KAAK,aAAa,EAElB,KAAK,OAAO,EAEZ,KAAK,oBAAoB,EACzB,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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { access, copyFile, mkdir, readdir, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { ZodError } from "zod";
|
|
4
|
-
import { CodebaseProfileSchema, FeatureSchema, FeaturesDocumentSchema, ManifestSchema, PhaseSchema, ProjectSchema, RequirementsDocumentSchema, ResumeFocusSchema, ActivityLogSchema, } from "./schema.js";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { z, ZodError } from "zod";
|
|
4
|
+
import { CodebaseProfileSchema, FeatureSchema, FeaturesDocumentSchema, ManifestSchema, PhaseSchema, ProjectSchema, RequirementsDocumentSchema, ResumeFocusSchema, ActivityLogSchema, TimestampSchema, } from "./schema.js";
|
|
5
5
|
import { createFeatureId, createPhaseId, createRequirementId, createShortId, createStatusLogEntryId, createTaskId, formatPhaseRef, isLegacyPhaseId } from "./naming.js";
|
|
6
6
|
import { deriveParentDisplay, fromCanonicalStatus } from "./display-status.js";
|
|
7
7
|
function nowISO() {
|
|
@@ -118,14 +118,27 @@ export function withFeatureLock(featureId, fn) {
|
|
|
118
118
|
featureLocks.delete(featureId);
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
|
-
async function atomicWriteText(path, raw) {
|
|
121
|
+
async function atomicWriteText(path, raw, root) {
|
|
122
122
|
return withWriteLock(path, async () => {
|
|
123
123
|
writeBusyHook?.(true);
|
|
124
|
-
const
|
|
124
|
+
const localRoot = root ? join(root, ".local") : undefined;
|
|
125
|
+
const tmpDir = localRoot ? join(localRoot, "tmp") : dirname(path);
|
|
126
|
+
const backupsRoot = localRoot ? join(localRoot, "backups") : dirname(path);
|
|
127
|
+
const rel = localRoot && path.startsWith(localRoot)
|
|
128
|
+
? path.slice(localRoot.length).replace(/^\//, "")
|
|
129
|
+
: root && path.startsWith(root)
|
|
130
|
+
? path.slice(root.length).replace(/^\//, "")
|
|
131
|
+
: basename(path);
|
|
132
|
+
const backupRel = rel ? rel + ".bak" : basename(path) + ".bak";
|
|
133
|
+
const backupPath = join(backupsRoot, backupRel);
|
|
134
|
+
const tmpName = rel ? rel.replace(/[/\\]/g, "--") + `.tmp.${process.pid}.${Date.now()}` : `${basename(path)}.tmp.${process.pid}.${Date.now()}`;
|
|
135
|
+
const tmp = join(tmpDir, tmpName);
|
|
125
136
|
try {
|
|
137
|
+
await mkdir(tmpDir, { recursive: true });
|
|
126
138
|
await writeFile(tmp, raw, "utf-8");
|
|
127
139
|
try {
|
|
128
|
-
await
|
|
140
|
+
await mkdir(dirname(backupPath), { recursive: true });
|
|
141
|
+
await copyFile(path, backupPath);
|
|
129
142
|
}
|
|
130
143
|
catch { }
|
|
131
144
|
await rename(tmp, path);
|
|
@@ -140,10 +153,10 @@ async function atomicWriteText(path, raw) {
|
|
|
140
153
|
}
|
|
141
154
|
});
|
|
142
155
|
}
|
|
143
|
-
async function atomicWriteJson(path, data) {
|
|
144
|
-
return atomicWriteText(path, JSON.stringify(data, null, 2));
|
|
156
|
+
async function atomicWriteJson(path, data, root) {
|
|
157
|
+
return atomicWriteText(path, JSON.stringify(data, null, 2), root);
|
|
145
158
|
}
|
|
146
|
-
async function atomicUpdateJson(path, schema, updater) {
|
|
159
|
+
async function atomicUpdateJson(path, schema, updater, root) {
|
|
147
160
|
// NOTE: write the file INLINE here, do NOT call atomicWriteJson/atomicWriteText,
|
|
148
161
|
// because those re-acquire withWriteLock(path) — and we already hold it (below).
|
|
149
162
|
// Re-entrant locking is not supported, so calling them would deadlock.
|
|
@@ -152,11 +165,23 @@ async function atomicUpdateJson(path, schema, updater) {
|
|
|
152
165
|
const updated = updater(current);
|
|
153
166
|
const parsed = schema.parse(updated);
|
|
154
167
|
writeBusyHook?.(true);
|
|
155
|
-
const
|
|
168
|
+
const localRoot = root ? join(root, ".local") : undefined;
|
|
169
|
+
const tmpDir = localRoot ? join(localRoot, "tmp") : dirname(path);
|
|
170
|
+
const backupsRoot = localRoot ? join(localRoot, "backups") : dirname(path);
|
|
171
|
+
const rel = localRoot && path.startsWith(localRoot)
|
|
172
|
+
? path.slice(localRoot.length).replace(/^\//, "")
|
|
173
|
+
: root && path.startsWith(root)
|
|
174
|
+
? path.slice(root.length).replace(/^\//, "")
|
|
175
|
+
: basename(path);
|
|
176
|
+
const backupPath = join(backupsRoot, rel + ".bak");
|
|
177
|
+
const tmpName = rel ? rel.replace(/[/\\]/g, "--") + `.tmp.${process.pid}.${Date.now()}` : `${basename(path)}.tmp.${process.pid}.${Date.now()}`;
|
|
178
|
+
const tmp = join(tmpDir, tmpName);
|
|
156
179
|
try {
|
|
180
|
+
await mkdir(tmpDir, { recursive: true });
|
|
157
181
|
await writeFile(tmp, JSON.stringify(parsed, null, 2), "utf-8");
|
|
158
182
|
try {
|
|
159
|
-
await
|
|
183
|
+
await mkdir(dirname(backupPath), { recursive: true });
|
|
184
|
+
await copyFile(path, backupPath);
|
|
160
185
|
}
|
|
161
186
|
catch { }
|
|
162
187
|
await rename(tmp, path);
|
|
@@ -608,7 +633,7 @@ export class PlanStore {
|
|
|
608
633
|
}
|
|
609
634
|
await mkdir(this.featuresDir(), { recursive: true });
|
|
610
635
|
for (const feat of legacy.features) {
|
|
611
|
-
await atomicWriteJson(this.featurePath(feat.id), feat);
|
|
636
|
+
await atomicWriteJson(this.featurePath(feat.id), feat, this.root);
|
|
612
637
|
}
|
|
613
638
|
await unlink(this.featuresPath()).catch(() => { });
|
|
614
639
|
}
|
|
@@ -619,16 +644,81 @@ export class PlanStore {
|
|
|
619
644
|
return join(this.phasesDir(), `${phaseId}.json`);
|
|
620
645
|
}
|
|
621
646
|
generatedDir() {
|
|
622
|
-
return join(this.
|
|
647
|
+
return join(this.localRoot(), "generated");
|
|
623
648
|
}
|
|
624
649
|
codebasePath() {
|
|
625
650
|
return join(this.root, "codebase.json");
|
|
626
651
|
}
|
|
627
652
|
resumePath() {
|
|
628
|
-
return join(this.
|
|
653
|
+
return join(this.localRoot(), "resume.json");
|
|
629
654
|
}
|
|
630
655
|
activityPath() {
|
|
631
|
-
return join(this.
|
|
656
|
+
return join(this.localRoot(), "activity.json");
|
|
657
|
+
}
|
|
658
|
+
localRoot() {
|
|
659
|
+
return join(this.root, ".local");
|
|
660
|
+
}
|
|
661
|
+
timestampPath() {
|
|
662
|
+
return join(this.localRoot(), "timestamp.json");
|
|
663
|
+
}
|
|
664
|
+
backupsDir() {
|
|
665
|
+
return join(this.localRoot(), "backups");
|
|
666
|
+
}
|
|
667
|
+
tmpDir() {
|
|
668
|
+
return join(this.localRoot(), "tmp");
|
|
669
|
+
}
|
|
670
|
+
handoffArchiveDir() {
|
|
671
|
+
return join(this.localRoot(), "handoff-archive");
|
|
672
|
+
}
|
|
673
|
+
/** One-time migration for plans created before .planner/.local/ existed.
|
|
674
|
+
* Moves a legacy root-level file into .local/ if the legacy file exists and
|
|
675
|
+
* the .local/ counterpart does not. Safe to call on every load. */
|
|
676
|
+
async migrateLegacyLocalFile(oldPath, newPath) {
|
|
677
|
+
try {
|
|
678
|
+
await access(oldPath);
|
|
679
|
+
}
|
|
680
|
+
catch {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
await access(newPath);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
catch { }
|
|
688
|
+
await mkdir(dirname(newPath), { recursive: true });
|
|
689
|
+
await rename(oldPath, newPath);
|
|
690
|
+
}
|
|
691
|
+
async migrateLegacyGeneratedDir() {
|
|
692
|
+
const oldDir = join(this.root, "generated");
|
|
693
|
+
const newDir = this.generatedDir();
|
|
694
|
+
try {
|
|
695
|
+
await access(oldDir);
|
|
696
|
+
}
|
|
697
|
+
catch {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
try {
|
|
701
|
+
await access(newDir);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
catch { }
|
|
705
|
+
await rename(oldDir, newDir);
|
|
706
|
+
}
|
|
707
|
+
async migrateLegacyHandoffArchive() {
|
|
708
|
+
const oldDir = join(this.root, "handoff-archive");
|
|
709
|
+
const newDir = this.handoffArchiveDir();
|
|
710
|
+
try {
|
|
711
|
+
await access(oldDir);
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
try {
|
|
717
|
+
await access(newDir);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
catch { }
|
|
721
|
+
await rename(oldDir, newDir);
|
|
632
722
|
}
|
|
633
723
|
// ── Init ─────────────────────────────────────────────────────────────
|
|
634
724
|
async init(projectName) {
|
|
@@ -638,7 +728,11 @@ export class PlanStore {
|
|
|
638
728
|
await mkdir(this.root, { recursive: true });
|
|
639
729
|
await mkdir(this.phasesDir(), { recursive: true });
|
|
640
730
|
await mkdir(this.featuresDir(), { recursive: true });
|
|
731
|
+
await mkdir(this.localRoot(), { recursive: true });
|
|
732
|
+
await mkdir(this.backupsDir(), { recursive: true });
|
|
733
|
+
await mkdir(this.tmpDir(), { recursive: true });
|
|
641
734
|
await mkdir(join(this.generatedDir(), "phases"), { recursive: true });
|
|
735
|
+
await mkdir(this.handoffArchiveDir(), { recursive: true });
|
|
642
736
|
await mkdir(join(this.root, "schema"), { recursive: true });
|
|
643
737
|
await mkdir(join(this.root, "adapters"), { recursive: true });
|
|
644
738
|
const manifest = {
|
|
@@ -648,7 +742,8 @@ export class PlanStore {
|
|
|
648
742
|
createdAt: nowISO(),
|
|
649
743
|
updatedAt: nowISO(),
|
|
650
744
|
};
|
|
651
|
-
await atomicWriteJson(this.manifestPath(), manifest);
|
|
745
|
+
await atomicWriteJson(this.manifestPath(), manifest, this.root);
|
|
746
|
+
await atomicWriteJson(this.timestampPath(), { updatedAt: manifest.updatedAt }, this.root);
|
|
652
747
|
await this.saveProject({
|
|
653
748
|
name: projectName,
|
|
654
749
|
goal: "",
|
|
@@ -698,7 +793,7 @@ export class PlanStore {
|
|
|
698
793
|
"- `project.json` — scope, rules, stack, tools",
|
|
699
794
|
"- `requirements.json` — requirements and macro-tasks",
|
|
700
795
|
"- `phases/` — one JSON file per phase",
|
|
701
|
-
"- `generated/` — auto-generated markdown views",
|
|
796
|
+
"- `generated/` — auto-generated markdown views (under `.local/`)",
|
|
702
797
|
"- `schema/plan.schema.json` — JSON Schema for tooling",
|
|
703
798
|
].join("\n");
|
|
704
799
|
await writeFile(join(this.root, "README.md"), readme, "utf-8");
|
|
@@ -709,13 +804,8 @@ export class PlanStore {
|
|
|
709
804
|
// timestamp (guardBypassUntil must NOT leak into git/other clones)
|
|
710
805
|
// - generated/: auto-regenerated markdown views (derived from JSON; churn)
|
|
711
806
|
await writeFile(join(this.root, ".gitignore"), [
|
|
712
|
-
"# Agent Plan transient/derived files — do not track",
|
|
713
|
-
"
|
|
714
|
-
"*.tmp.*",
|
|
715
|
-
"resume.json",
|
|
716
|
-
"resume.*.json",
|
|
717
|
-
"generated/",
|
|
718
|
-
"handoff-archive/",
|
|
807
|
+
"# Agent Plan transient/derived/session-local files — do not track",
|
|
808
|
+
".local/",
|
|
719
809
|
"",
|
|
720
810
|
].join("\n"), "utf-8");
|
|
721
811
|
}
|
|
@@ -730,7 +820,23 @@ export class PlanStore {
|
|
|
730
820
|
}
|
|
731
821
|
// ── Loaders ──────────────────────────────────────────────────────────
|
|
732
822
|
async loadManifest() {
|
|
733
|
-
|
|
823
|
+
const manifest = await readJson(this.manifestPath(), ManifestSchema);
|
|
824
|
+
try {
|
|
825
|
+
const timestamp = await readJson(this.timestampPath(), z.object({ updatedAt: TimestampSchema }));
|
|
826
|
+
manifest.updatedAt = timestamp.updatedAt;
|
|
827
|
+
}
|
|
828
|
+
catch {
|
|
829
|
+
// Legacy plan without .local/timestamp.json: bootstrap from manifest.updatedAt
|
|
830
|
+
// and create the timestamp file so subsequent writes stay in .local/.
|
|
831
|
+
try {
|
|
832
|
+
await mkdir(this.localRoot(), { recursive: true });
|
|
833
|
+
await atomicWriteJson(this.timestampPath(), { updatedAt: manifest.updatedAt }, this.root);
|
|
834
|
+
}
|
|
835
|
+
catch {
|
|
836
|
+
// ignore bootstrap failure
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return manifest;
|
|
734
840
|
}
|
|
735
841
|
async loadProject() {
|
|
736
842
|
return readJson(this.projectPath(), ProjectSchema);
|
|
@@ -847,10 +953,11 @@ export class PlanStore {
|
|
|
847
953
|
}
|
|
848
954
|
async saveCodebaseProfile(profile) {
|
|
849
955
|
const parsed = CodebaseProfileSchema.parse(profile);
|
|
850
|
-
await atomicWriteJson(this.codebasePath(), parsed);
|
|
851
|
-
await this.
|
|
956
|
+
await atomicWriteJson(this.codebasePath(), parsed, this.root);
|
|
957
|
+
await this.touchTimestamp();
|
|
852
958
|
}
|
|
853
959
|
async loadResume() {
|
|
960
|
+
await this.migrateLegacyLocalFile(join(this.root, "resume.json"), this.resumePath());
|
|
854
961
|
try {
|
|
855
962
|
return await readJson(this.resumePath(), ResumeFocusSchema);
|
|
856
963
|
}
|
|
@@ -871,8 +978,8 @@ export class PlanStore {
|
|
|
871
978
|
: (resume.nextStepsUpdatedAt || existing?.nextStepsUpdatedAt || nowISO()),
|
|
872
979
|
};
|
|
873
980
|
const parsed = ResumeFocusSchema.parse(withTs);
|
|
874
|
-
await atomicWriteJson(this.resumePath(), parsed);
|
|
875
|
-
await this.
|
|
981
|
+
await atomicWriteJson(this.resumePath(), parsed, this.root);
|
|
982
|
+
await this.touchTimestamp();
|
|
876
983
|
}
|
|
877
984
|
/**
|
|
878
985
|
* Authorize a temporary guard bypass so edit/write tools may proceed even
|
|
@@ -918,6 +1025,7 @@ export class PlanStore {
|
|
|
918
1025
|
return until > Date.now();
|
|
919
1026
|
}
|
|
920
1027
|
async loadActivityLog() {
|
|
1028
|
+
await this.migrateLegacyLocalFile(join(this.root, "activity.json"), this.activityPath());
|
|
921
1029
|
try {
|
|
922
1030
|
return await readJson(this.activityPath(), ActivityLogSchema);
|
|
923
1031
|
}
|
|
@@ -933,8 +1041,8 @@ export class PlanStore {
|
|
|
933
1041
|
// Cap to last 200 entries
|
|
934
1042
|
if (log.entries.length > 200)
|
|
935
1043
|
log.entries = log.entries.slice(-200);
|
|
936
|
-
await atomicWriteJson(this.activityPath(), { entries: log.entries });
|
|
937
|
-
await this.
|
|
1044
|
+
await atomicWriteJson(this.activityPath(), { entries: log.entries }, this.root);
|
|
1045
|
+
await this.touchTimestamp();
|
|
938
1046
|
return entry;
|
|
939
1047
|
}
|
|
940
1048
|
/** Derive an up-to-date resume focus from the current workspace state. */
|
|
@@ -1228,7 +1336,7 @@ export class PlanStore {
|
|
|
1228
1336
|
const sortedFeatures = [...featuresDoc.features].sort((a, b) => a.number - b.number || a.createdAt.localeCompare(b.createdAt));
|
|
1229
1337
|
sortedFeatures.forEach((f, index) => {
|
|
1230
1338
|
if (!f.shortId) {
|
|
1231
|
-
f.shortId = createShortId(existing);
|
|
1339
|
+
f.shortId = createShortId(existing, `feature:${f.number}:${f.id}`);
|
|
1232
1340
|
existing.add(f.shortId);
|
|
1233
1341
|
shortIdsAssigned += 1;
|
|
1234
1342
|
featuresDirty = true;
|
|
@@ -1247,7 +1355,7 @@ export class PlanStore {
|
|
|
1247
1355
|
for (const phase of phases) {
|
|
1248
1356
|
let phaseDirty = false;
|
|
1249
1357
|
if (!phase.shortId) {
|
|
1250
|
-
phase.shortId = createShortId(existing);
|
|
1358
|
+
phase.shortId = createShortId(existing, `phase:${phase.number}:${phase.id}`);
|
|
1251
1359
|
existing.add(phase.shortId);
|
|
1252
1360
|
shortIdsAssigned += 1;
|
|
1253
1361
|
phaseDirty = true;
|
|
@@ -1261,7 +1369,7 @@ export class PlanStore {
|
|
|
1261
1369
|
const sortedTasks = [...phase.tasks].sort((a, b) => a.number - b.number || a.createdAt.localeCompare(b.createdAt));
|
|
1262
1370
|
sortedTasks.forEach((t, index) => {
|
|
1263
1371
|
if (!t.shortId) {
|
|
1264
|
-
t.shortId = createShortId(existing);
|
|
1372
|
+
t.shortId = createShortId(existing, `task:${t.number}:${t.id}`);
|
|
1265
1373
|
existing.add(t.shortId);
|
|
1266
1374
|
shortIdsAssigned += 1;
|
|
1267
1375
|
phaseDirty = true;
|
|
@@ -1509,7 +1617,7 @@ export class PlanStore {
|
|
|
1509
1617
|
}
|
|
1510
1618
|
// ── Savers ───────────────────────────────────────────────────────────
|
|
1511
1619
|
async updateProject(updater) {
|
|
1512
|
-
const updated = await atomicUpdateJson(this.projectPath(), ProjectSchema, updater);
|
|
1620
|
+
const updated = await atomicUpdateJson(this.projectPath(), ProjectSchema, updater, this.root);
|
|
1513
1621
|
await this.maybeAutoSync();
|
|
1514
1622
|
return updated;
|
|
1515
1623
|
}
|
|
@@ -1525,14 +1633,14 @@ export class PlanStore {
|
|
|
1525
1633
|
return updated;
|
|
1526
1634
|
}
|
|
1527
1635
|
async updateRequirements(updater) {
|
|
1528
|
-
const updated = await atomicUpdateJson(this.requirementsPath(), RequirementsDocumentSchema, updater);
|
|
1636
|
+
const updated = await atomicUpdateJson(this.requirementsPath(), RequirementsDocumentSchema, updater, this.root);
|
|
1529
1637
|
await this.maybeAutoSync();
|
|
1530
1638
|
return updated;
|
|
1531
1639
|
}
|
|
1532
1640
|
async saveProject(project) {
|
|
1533
1641
|
const parsed = ProjectSchema.parse(project);
|
|
1534
|
-
await atomicWriteJson(this.projectPath(), parsed);
|
|
1535
|
-
await this.
|
|
1642
|
+
await atomicWriteJson(this.projectPath(), parsed, this.root);
|
|
1643
|
+
await this.touchTimestamp();
|
|
1536
1644
|
await this.maybeAutoSync();
|
|
1537
1645
|
}
|
|
1538
1646
|
async saveFeatures(features) {
|
|
@@ -1540,7 +1648,7 @@ export class PlanStore {
|
|
|
1540
1648
|
await this.migrateLegacy();
|
|
1541
1649
|
await this.saveFeaturesRaw(features);
|
|
1542
1650
|
});
|
|
1543
|
-
await this.
|
|
1651
|
+
await this.touchTimestamp();
|
|
1544
1652
|
await this.maybeAutoSync();
|
|
1545
1653
|
}
|
|
1546
1654
|
/** Per-file write of all features + orphan reconcile. No lock (caller holds withFeaturesLock). */
|
|
@@ -1549,7 +1657,7 @@ export class PlanStore {
|
|
|
1549
1657
|
await mkdir(this.featuresDir(), { recursive: true });
|
|
1550
1658
|
const wantIds = new Set(parsed.features.map((f) => f.id));
|
|
1551
1659
|
for (const feat of parsed.features) {
|
|
1552
|
-
await atomicWriteJson(this.featurePath(feat.id), feat);
|
|
1660
|
+
await atomicWriteJson(this.featurePath(feat.id), feat, this.root);
|
|
1553
1661
|
}
|
|
1554
1662
|
// Orphan reconcile: remove feature files no longer in the document.
|
|
1555
1663
|
try {
|
|
@@ -1573,21 +1681,26 @@ export class PlanStore {
|
|
|
1573
1681
|
await this.migrateLegacy();
|
|
1574
1682
|
await mkdir(this.featuresDir(), { recursive: true });
|
|
1575
1683
|
const parsed = FeatureSchema.parse(feature);
|
|
1576
|
-
await atomicWriteJson(this.featurePath(parsed.id), parsed);
|
|
1684
|
+
await atomicWriteJson(this.featurePath(parsed.id), parsed, this.root);
|
|
1577
1685
|
});
|
|
1578
|
-
await this.
|
|
1686
|
+
await this.touchTimestamp();
|
|
1579
1687
|
await this.maybeAutoSync();
|
|
1580
1688
|
}
|
|
1581
1689
|
async saveRequirements(reqs) {
|
|
1582
1690
|
const parsed = RequirementsDocumentSchema.parse(reqs);
|
|
1583
|
-
await atomicWriteJson(this.requirementsPath(), parsed);
|
|
1584
|
-
await this.
|
|
1691
|
+
await atomicWriteJson(this.requirementsPath(), parsed, this.root);
|
|
1692
|
+
await this.touchTimestamp();
|
|
1585
1693
|
}
|
|
1586
1694
|
async savePhase(phase) {
|
|
1587
1695
|
const features = await this.loadRawFeatures();
|
|
1588
1696
|
const resolvedFeatureId = resolveStoredFeatureId(features, phase.featureId);
|
|
1589
1697
|
// Referential integrity: if a featureId is present but cannot be resolved
|
|
1590
|
-
// to a known feature, REJECT — never persist an orphan
|
|
1698
|
+
// to a known feature, REJECT — never persist an orphan featureId.
|
|
1699
|
+
// NOTE: a missing/empty featureId is intentionally ALLOWED here so that
|
|
1700
|
+
// legacy migrations, repair, and feature-delete (unlink) can persist phases
|
|
1701
|
+
// without a feature yet. The hard "featureId required" gate lives at the
|
|
1702
|
+
// adapter boundary (Pi phase_create/task_create and MCP planner-phase-add/
|
|
1703
|
+
// planner-task-add), which is where user-facing creation happens.
|
|
1591
1704
|
if (phase.featureId && phase.featureId.trim() && !resolvedFeatureId) {
|
|
1592
1705
|
throw new PlanStoreError(`Cannot save phase "${phase.title}": featureId "${phase.featureId}" does not match any existing feature. Use a valid feature UUID, F00x ref, or shortId.`);
|
|
1593
1706
|
}
|
|
@@ -1596,8 +1709,8 @@ export class PlanStore {
|
|
|
1596
1709
|
: phase;
|
|
1597
1710
|
const parsed = PhaseSchema.parse(this.normalizePhaseDocument(normalizedInput).phase);
|
|
1598
1711
|
await mkdir(this.phasesDir(), { recursive: true });
|
|
1599
|
-
await atomicWriteJson(this.phasePath(parsed.id), parsed);
|
|
1600
|
-
await this.
|
|
1712
|
+
await atomicWriteJson(this.phasePath(parsed.id), parsed, this.root);
|
|
1713
|
+
await this.touchTimestamp();
|
|
1601
1714
|
await this.maybeAutoSync();
|
|
1602
1715
|
}
|
|
1603
1716
|
/** Atomic read-modify-write on a single phase file. Serializes concurrent
|
|
@@ -1636,12 +1749,6 @@ export class PlanStore {
|
|
|
1636
1749
|
const now = new Date().toISOString();
|
|
1637
1750
|
await this.updatePhase(phaseId, (phase) => ({ ...phase, handoff: text, handoffUpdatedAt: now }));
|
|
1638
1751
|
}
|
|
1639
|
-
/** Directory where cleared handoff content is archived as .md files
|
|
1640
|
-
* (gitignored). Keeps the phase JSON lean while making past handoffs
|
|
1641
|
-
* recoverable + human-readable. */
|
|
1642
|
-
handoffArchiveDir() {
|
|
1643
|
-
return join(this.root, "handoff-archive");
|
|
1644
|
-
}
|
|
1645
1752
|
/** Mark the phase handoff as read/acknowledged on recap (sets handoffReadAt).
|
|
1646
1753
|
* Does NOT clear the handoff — content is kept until a task starts or the
|
|
1647
1754
|
* phase completes, so a restart between read and resume does not lose it. */
|
|
@@ -1687,6 +1794,7 @@ export class PlanStore {
|
|
|
1687
1794
|
* left unchanged as an audit trail. If the handoff is empty, this is a no-op.
|
|
1688
1795
|
* reason: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
1689
1796
|
async clearPhaseHandoff(phaseId, reason = "manual") {
|
|
1797
|
+
await this.migrateLegacyHandoffArchive();
|
|
1690
1798
|
const phase = await this.loadPhase(phaseId).catch(() => null);
|
|
1691
1799
|
if (!phase || phase.handoff === "")
|
|
1692
1800
|
return; // nothing to archive
|
|
@@ -1696,14 +1804,17 @@ export class PlanStore {
|
|
|
1696
1804
|
await mkdir(archiveDir, { recursive: true }).catch(() => { });
|
|
1697
1805
|
const fileName = `${phaseId}-${safeTs}.md`;
|
|
1698
1806
|
const filePath = join(archiveDir, fileName);
|
|
1699
|
-
await atomicWriteText(filePath, phase.handoff);
|
|
1807
|
+
await atomicWriteText(filePath, phase.handoff, this.root);
|
|
1700
1808
|
const entry = { file: `handoff-archive/${fileName}`, clearedAt, reason };
|
|
1701
1809
|
// Cap history at 5: prepend new entry, drop oldest (and delete its file).
|
|
1702
1810
|
const trimmed = [entry, ...(phase.handoffHistory ?? [])].slice(0, 5);
|
|
1703
1811
|
const dropped = (phase.handoffHistory ?? []).slice(4); // entries beyond index 4 after prepend
|
|
1704
1812
|
for (const d of dropped) {
|
|
1705
|
-
if (d?.file)
|
|
1706
|
-
|
|
1813
|
+
if (!d?.file)
|
|
1814
|
+
continue;
|
|
1815
|
+
// Legacy entries used `.planner/handoff-archive/...`; new entries use `.planner/.local/handoff-archive/...`.
|
|
1816
|
+
await unlink(join(this.handoffArchiveDir(), basename(d.file))).catch(() => { });
|
|
1817
|
+
await unlink(join(this.root, d.file)).catch(() => { });
|
|
1707
1818
|
}
|
|
1708
1819
|
await this.updatePhase(phaseId, (p) => ({ ...p, handoff: "", handoffHistory: trimmed }));
|
|
1709
1820
|
}
|
|
@@ -1775,7 +1886,7 @@ export class PlanStore {
|
|
|
1775
1886
|
}
|
|
1776
1887
|
return doc;
|
|
1777
1888
|
});
|
|
1778
|
-
await this.
|
|
1889
|
+
await this.touchTimestamp();
|
|
1779
1890
|
await this.writeGenerated();
|
|
1780
1891
|
return { found, removed: found };
|
|
1781
1892
|
});
|
|
@@ -1787,7 +1898,7 @@ export class PlanStore {
|
|
|
1787
1898
|
catch {
|
|
1788
1899
|
// already gone
|
|
1789
1900
|
}
|
|
1790
|
-
await this.
|
|
1901
|
+
await this.touchTimestamp();
|
|
1791
1902
|
}
|
|
1792
1903
|
// ── Workspace-level operations ─────────────────────────────────────
|
|
1793
1904
|
/** Load the full workspace (manifest + phases + project + requirements + features) */
|
|
@@ -1800,8 +1911,10 @@ export class PlanStore {
|
|
|
1800
1911
|
return { manifest, phases, project, features, requirements };
|
|
1801
1912
|
}
|
|
1802
1913
|
// ── Markdown generation ────────────────────────────────────────────
|
|
1803
|
-
/** Load all data, render markdown, and write into generated/.
|
|
1914
|
+
/** Load all data, render markdown, and write into generated/. Skips files
|
|
1915
|
+
* whose content is unchanged to avoid unnecessary backup churn. */
|
|
1804
1916
|
async writeGenerated() {
|
|
1917
|
+
await this.migrateLegacyGeneratedDir();
|
|
1805
1918
|
const { PlanRenderer } = await import("./renderer.js");
|
|
1806
1919
|
const plan = await this.loadAll();
|
|
1807
1920
|
const renderer = new PlanRenderer();
|
|
@@ -1817,21 +1930,27 @@ export class PlanStore {
|
|
|
1817
1930
|
if (dir !== genDir) {
|
|
1818
1931
|
await mkdir(dir, { recursive: true });
|
|
1819
1932
|
}
|
|
1933
|
+
try {
|
|
1934
|
+
const existing = await readFile(fullPath, "utf-8");
|
|
1935
|
+
if (existing === content)
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
catch {
|
|
1939
|
+
// file does not exist yet — write it
|
|
1940
|
+
}
|
|
1820
1941
|
await writeFile(fullPath, content, "utf-8");
|
|
1821
1942
|
written.push(relPath);
|
|
1822
1943
|
}
|
|
1823
1944
|
return written;
|
|
1824
1945
|
}
|
|
1825
1946
|
// ── Touch ────────────────────────────────────────────────────────────
|
|
1826
|
-
/** Update
|
|
1827
|
-
async
|
|
1947
|
+
/** Update .local/timestamp.json to reflect a change. */
|
|
1948
|
+
async touchTimestamp() {
|
|
1828
1949
|
try {
|
|
1829
|
-
|
|
1830
|
-
m.updatedAt = nowISO();
|
|
1831
|
-
await atomicWriteJson(this.manifestPath(), m);
|
|
1950
|
+
await atomicWriteJson(this.timestampPath(), { updatedAt: nowISO() }, this.root);
|
|
1832
1951
|
}
|
|
1833
1952
|
catch {
|
|
1834
|
-
// if
|
|
1953
|
+
// if .local/ doesn't exist yet, skip
|
|
1835
1954
|
}
|
|
1836
1955
|
}
|
|
1837
1956
|
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -779,7 +779,7 @@ export declare const TaskSchema: z.ZodEffects<z.ZodObject<{
|
|
|
779
779
|
completedAt?: string | undefined;
|
|
780
780
|
}>;
|
|
781
781
|
export declare const HandoffHistoryEntrySchema: z.ZodObject<{
|
|
782
|
-
/** Relative path under .planner/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
782
|
+
/** Relative path under .planner/.local/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
783
783
|
file: z.ZodDefault<z.ZodString>;
|
|
784
784
|
clearedAt: z.ZodString;
|
|
785
785
|
/** Why the handoff was cleared: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
@@ -1146,11 +1146,11 @@ export declare const PhaseSchema: z.ZodObject<{
|
|
|
1146
1146
|
* completes — so a restart between read and resume does not lose context. */
|
|
1147
1147
|
handoffReadAt: z.ZodDefault<z.ZodString>;
|
|
1148
1148
|
/** Metadata for recently-cleared handoffs (newest-first, capped at 5). The
|
|
1149
|
-
* full content lives as .md files in .planner/handoff-archive/ (gitignored);
|
|
1149
|
+
* full content lives as .md files in .planner/.local/handoff-archive/ (gitignored);
|
|
1150
1150
|
* this array only holds the pointer + clear reason + timestamp, so the phase
|
|
1151
1151
|
* JSON stays lean and the archive is recoverable. */
|
|
1152
1152
|
handoffHistory: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
1153
|
-
/** Relative path under .planner/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
1153
|
+
/** Relative path under .planner/.local/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
1154
1154
|
file: z.ZodDefault<z.ZodString>;
|
|
1155
1155
|
clearedAt: z.ZodString;
|
|
1156
1156
|
/** Why the handoff was cleared: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
@@ -2700,11 +2700,11 @@ export declare const PlanWorkspaceSchema: z.ZodObject<{
|
|
|
2700
2700
|
* completes — so a restart between read and resume does not lose context. */
|
|
2701
2701
|
handoffReadAt: z.ZodDefault<z.ZodString>;
|
|
2702
2702
|
/** Metadata for recently-cleared handoffs (newest-first, capped at 5). The
|
|
2703
|
-
* full content lives as .md files in .planner/handoff-archive/ (gitignored);
|
|
2703
|
+
* full content lives as .md files in .planner/.local/handoff-archive/ (gitignored);
|
|
2704
2704
|
* this array only holds the pointer + clear reason + timestamp, so the phase
|
|
2705
2705
|
* JSON stays lean and the archive is recoverable. */
|
|
2706
2706
|
handoffHistory: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
2707
|
-
/** Relative path under .planner/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
2707
|
+
/** Relative path under .planner/.local/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
2708
2708
|
file: z.ZodDefault<z.ZodString>;
|
|
2709
2709
|
clearedAt: z.ZodString;
|
|
2710
2710
|
/** Why the handoff was cleared: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
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;;;;;IAK5B;;mFAE+E;;;;;;;;;;;;;;;;;;;;;;;;;;EAM/E,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;EAM9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAE5B,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;EAMzB,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;EAI9B,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;EAOjC,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAexB,gGAAgG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIhG,CAAC;AAEH,eAAO,MAAM,mBAAmB,yGAAuG,CAAC;AACxI,eAAO,MAAM,gBAAgB,yGAAuG,CAAC;AACrI,eAAO,MAAM,iBAAiB,+HAA6H,CAAC;AAC5J,eAAO,MAAM,uBAAuB,yGAAuG,CAAC;AAC5I,eAAO,MAAM,mBAAmB,yGAAuG,CAAC;AAExI,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;EAOxB,CAAC;AAEH,eAAO,MAAM,mBAAmB;;IAE9B;;yDAEqD;;;;;;;;;;;;;;EAIrD,CAAC;AAIH,wEAAwE;AACxE,eAAO,MAAM,8BAA8B,aAEzC,CAAC;AAEH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK7E;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;EAO/B,CAAC;AAEH;2EAC2E;AAC3E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;EAOpC,CAAC;AAEH,eAAO,MAAM,UAAU;;IAErB;oEACgE;;IAEhE,mJAAmJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QArDnJ;;6DAEqD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6EpD,CAAC;AAEJ,eAAO,MAAM,yBAAyB;IACpC
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.js
CHANGED
|
@@ -189,7 +189,7 @@ export const TaskSchema = z.object({
|
|
|
189
189
|
.filter((item) => item.title.length > 0),
|
|
190
190
|
}));
|
|
191
191
|
export const HandoffHistoryEntrySchema = z.object({
|
|
192
|
-
/** Relative path under .planner/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
192
|
+
/** Relative path under .planner/.local/handoff-archive/ (e.g. <phaseId>-<ISO>.md). */
|
|
193
193
|
file: z.string().default(""),
|
|
194
194
|
clearedAt: TimestampSchema,
|
|
195
195
|
/** Why the handoff was cleared: "task-started" | "phase-done" | "manual" | "superseded" | "imported". */
|
|
@@ -237,7 +237,7 @@ export const PhaseSchema = z.object({
|
|
|
237
237
|
* completes — so a restart between read and resume does not lose context. */
|
|
238
238
|
handoffReadAt: z.string().default(""),
|
|
239
239
|
/** Metadata for recently-cleared handoffs (newest-first, capped at 5). The
|
|
240
|
-
* full content lives as .md files in .planner/handoff-archive/ (gitignored);
|
|
240
|
+
* full content lives as .md files in .planner/.local/handoff-archive/ (gitignored);
|
|
241
241
|
* this array only holds the pointer + clear reason + timestamp, so the phase
|
|
242
242
|
* JSON stays lean and the archive is recoverable. */
|
|
243
243
|
handoffHistory: z.array(HandoffHistoryEntrySchema).default([]),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Feature, Phase } from "./schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a compact, agent-facing context block that surfaces the PARENT PHASE
|
|
4
|
+
* (and grandparent FEATURE) description when a task is started. The phase
|
|
5
|
+
* description carries the shared design context (file:line refs, architecture,
|
|
6
|
+
* current state, behaviors to preserve) that EVERY task in the phase inherits.
|
|
7
|
+
*
|
|
8
|
+
* Injecting this into the `task_start` tool response guarantees the agent sees
|
|
9
|
+
* the phase-level context in the exact moment it begins work — instead of
|
|
10
|
+
* relying on the agent to voluntarily read it beforehand.
|
|
11
|
+
*
|
|
12
|
+
* Harness-agnostic: returns a plain markdown string. Both the Pi adapter and
|
|
13
|
+
* the MCP server append this to their `task_start` confirmation text.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildPhaseContextBlock(phase: Phase, feature: Feature | undefined): string;
|
|
16
|
+
//# sourceMappingURL=task-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-context.d.ts","sourceRoot":"","sources":["../src/task-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGlD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,GAAG,SAAS,GAC3B,MAAM,CAiCR"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { formatPhaseRef } from "./naming.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a compact, agent-facing context block that surfaces the PARENT PHASE
|
|
4
|
+
* (and grandparent FEATURE) description when a task is started. The phase
|
|
5
|
+
* description carries the shared design context (file:line refs, architecture,
|
|
6
|
+
* current state, behaviors to preserve) that EVERY task in the phase inherits.
|
|
7
|
+
*
|
|
8
|
+
* Injecting this into the `task_start` tool response guarantees the agent sees
|
|
9
|
+
* the phase-level context in the exact moment it begins work — instead of
|
|
10
|
+
* relying on the agent to voluntarily read it beforehand.
|
|
11
|
+
*
|
|
12
|
+
* Harness-agnostic: returns a plain markdown string. Both the Pi adapter and
|
|
13
|
+
* the MCP server append this to their `task_start` confirmation text.
|
|
14
|
+
*/
|
|
15
|
+
export function buildPhaseContextBlock(phase, feature) {
|
|
16
|
+
const lines = [];
|
|
17
|
+
const phaseRef = formatPhaseRef(phase.number, feature?.number);
|
|
18
|
+
lines.push(`\n📋 Phase context — read this BEFORE touching code:`);
|
|
19
|
+
lines.push(`Phase ${phaseRef} — ${phase.title}`);
|
|
20
|
+
if (phase.summary && phase.summary.trim()) {
|
|
21
|
+
lines.push(`Summary: ${phase.summary.trim()}`);
|
|
22
|
+
}
|
|
23
|
+
if (phase.description && phase.description.trim()) {
|
|
24
|
+
lines.push(`\nPhase description:\n${phase.description.trim()}`);
|
|
25
|
+
}
|
|
26
|
+
const bullet = (label, items) => {
|
|
27
|
+
if (items && items.length > 0) {
|
|
28
|
+
lines.push(`\n${label}:`);
|
|
29
|
+
for (const it of items)
|
|
30
|
+
lines.push(` - ${it}`);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
bullet("Goals", phase.goals);
|
|
34
|
+
bullet("Non-goals", phase.nonGoals);
|
|
35
|
+
bullet("Dependencies", phase.dependencies);
|
|
36
|
+
bullet("Risks", phase.risks);
|
|
37
|
+
bullet("Open questions", phase.openQuestions);
|
|
38
|
+
bullet("Decisions", phase.decisions);
|
|
39
|
+
bullet("Completion criteria", phase.completionCriteria);
|
|
40
|
+
if (feature) {
|
|
41
|
+
lines.push(`\nFeature F${String(feature.number).padStart(3, "0")} — ${feature.name}`);
|
|
42
|
+
if (feature.description && feature.description.trim()) {
|
|
43
|
+
lines.push(`\nFeature description:\n${feature.description.trim()}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
lines.push(`\n(End of phase context. Now proceed with the task.)`);
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
}
|
package/package.json
CHANGED