/** * Age TTL for TERMINAL (done/cancelled) items. Once a completed or cancelled * item has been untouched this long it is reclaimed by the next read. Chosen to * comfortably outlive "I finished that last month and want to see it" while * still bounding a plan that is never manually cleared. */ export declare const WORK_PLAN_TERMINAL_ITEM_TTL_MS: number; /** * Count cap for TERMINAL (done/cancelled) items: the most recently completed * this many are kept, older ones are reclaimed oldest-first. Live items are * neither counted against this cap nor removed by it. */ export declare const WORK_PLAN_TERMINAL_ITEM_CAP = 200; /** Age TTL for quarantined (unreadable) plan files, the recovery copy's own bound. */ export declare const WORK_PLAN_QUARANTINE_TTL_MS: number; /** Count cap for quarantined plan files kept alongside one plan; oldest go first. */ export declare const WORK_PLAN_QUARANTINE_CAP = 5; /** * Minimum gap between quarantine-directory scans for one store instance. The * item bounds are evaluated on every read (they are a pure pass over in-memory * items); the directory scan is throttled so a redrawing modal cannot turn it * into a per-frame readdir. This is what makes the sweep periodic rather than * startup-only: a long-lived session keeps sweeping on this cadence. */ export declare const WORK_PLAN_QUARANTINE_SWEEP_INTERVAL_MS: number; export declare const WORK_PLAN_STATUSES: readonly ["pending", "in_progress", "blocked", "done", "failed", "cancelled"]; export type WorkPlanItemStatus = typeof WORK_PLAN_STATUSES[number]; export interface WorkPlanLinkTargets { readonly agentId?: string | undefined; readonly wrfcId?: string | undefined; readonly taskId?: string | undefined; readonly sessionId?: string | undefined; } export interface WorkPlanItem { readonly id: string; readonly title: string; readonly status: WorkPlanItemStatus; readonly owner?: string | undefined; readonly source?: string | undefined; readonly notes?: string | undefined; readonly linked?: WorkPlanLinkTargets | undefined; readonly createdAt: number; readonly updatedAt: number; readonly completedAt?: number | undefined; } /** * What the last recovery/bounding sweep reclaimed. Persisted with the plan and * rendered by `toMarkdown()` so a removal is always disclosed, counts and * paths only, never item text. */ export interface WorkPlanHousekeeping { /** Unix ms of the sweep that produced these counts. */ readonly at: number; /** Terminal items reclaimed by the age TTL. */ readonly expiredItems: number; /** Terminal items reclaimed by the count cap. */ readonly cappedItems: number; /** True when the plan file on disk could not be parsed and the plan was reset. */ readonly resetFromUnreadableFile?: boolean | undefined; /** Where the unreadable file was preserved, when there was content worth keeping. */ readonly quarantinePath?: string | undefined; /** Quarantined files removed by their own TTL / count cap. */ readonly quarantinesRemoved?: number | undefined; } export interface WorkPlan { readonly id: string; readonly projectId: string; readonly projectRoot: string; readonly title: string; readonly items: readonly WorkPlanItem[]; readonly activeItemId?: string | undefined; readonly source?: string | undefined; readonly createdAt: number; readonly updatedAt: number; /** Result of the most recent sweep that actually reclaimed something. */ readonly housekeeping?: WorkPlanHousekeeping | undefined; } export interface WorkPlanStoreOptions { readonly homeDirectory: string; /** * The owning product's storage scope, the `` segment in * `/.goodvibes//work-plans/`. Supplied rather than spelled * here so the plan lands under the same scope as the rest of that product's * state. */ readonly surfaceRoot: string; readonly projectId: string; readonly projectRoot: string; /** * Recorded on a newly-created plan as its `source`, naming which product * opened it. Omitted ⇒ the plan carries no source, which is what `readString` * already tolerates on read. */ readonly source?: string | undefined; } export interface AddWorkPlanItemOptions { readonly status?: WorkPlanItemStatus | undefined; readonly owner?: string | undefined; readonly source?: string | undefined; readonly notes?: string | undefined; readonly linked?: WorkPlanLinkTargets | undefined; } export interface UpdateWorkPlanItemPatch { readonly title?: string | undefined; readonly status?: WorkPlanItemStatus | undefined; readonly owner?: string | null | undefined; readonly source?: string | null | undefined; readonly notes?: string | null | undefined; readonly linked?: WorkPlanLinkTargets | null | undefined; } export declare function nextWorkPlanStatus(status: WorkPlanItemStatus): WorkPlanItemStatus; export declare class WorkPlanStore { private readonly options; readonly filePath: string; /** * Unix ms of this instance's last quarantine-directory scan. Zero means the * first read of the session sweeps, that read IS the recovery point. */ private lastQuarantineSweepAt; constructor(options: WorkPlanStoreOptions); getActivePlan(): WorkPlan; listItems(): readonly WorkPlanItem[]; addItem(title: string, options?: AddWorkPlanItemOptions): WorkPlanItem; updateItem(idOrPrefix: string, patch: UpdateWorkPlanItemPatch): WorkPlanItem; setItemStatus(idOrPrefix: string, status: WorkPlanItemStatus): WorkPlanItem; cycleItemStatus(idOrPrefix: string): WorkPlanItem; removeItem(idOrPrefix: string): WorkPlanItem; clearCompleted(): number; /** * Writes the current plan's `toMarkdown()` output to a sibling `.md` file * next to the JSON store file, so the checklist can be opened outside the * product that wrote it. Returns the written path alongside the markdown that * was written. */ exportMarkdown(): { readonly path: string; readonly markdown: string; }; toMarkdown(plan?: WorkPlan): string; /** * The plan as callers see it: loaded from disk (content-validated), then put * through the recovery/bounding sweep. When the sweep reclaimed anything the * bounded plan, carrying its housekeeping disclosure, is written back * immediately, so the disclosure survives even if the caller only reads. */ private readPlan; /** * Read and validate the plan file's CONTENT. A missing file is the ordinary * empty case. A file that exists but does not parse into a plan object is * moved aside (never overwritten in place) and reported as a recovery. */ private loadFromDisk; /** * Apply both bounds (age TTL, then count cap) over TERMINAL items only, plus * the throttled quarantine-file sweep. Pure with respect to disk except for * quarantine deletions; the caller persists the result when `changed`. * * Idempotent: re-running it on the returned plan reclaims nothing further, * which is what makes it safe to run concurrently from several processes. */ private sweep; /** * Move an unparseable plan file aside so the user can still recover it. * Returns the quarantine path, or undefined if the rename did not happen * (already moved by a concurrent process, or the directory is not writable). */ private quarantineUnreadableFile; /** * Bound the quarantine copies: delete any past WORK_PLAN_QUARANTINE_TTL_MS * and any beyond WORK_PLAN_QUARANTINE_CAP (oldest first). Throttled to * WORK_PLAN_QUARANTINE_SWEEP_INTERVAL_MS per store instance so a long-lived * session keeps sweeping without scanning the directory on every read. */ private sweepQuarantines; private createEmptyPlan; private writePlan; private resolveItem; private pruneItem; } //# sourceMappingURL=work-plan-store.d.ts.map