/** * Reconcile service for `.peaks/2026-MM-DD-session-<6hex>/` directories. * * Reconcile scans the project root's `.peaks/` directory, identifies a * canonical session via a 4-tier heuristic, re-points * `.peaks/_runtime/session.json` (the canonical new home of the * binding; legacy `.peaks/.session.json` is read-only back-compat), * and (optionally, with apply === true) deletes empty / abandoned * session dirs older than olderThanMs. * * also runs `migrateOldRuntimeState` at the start so pre-migration * trees have their `.peaks/.session.json` / `.peaks/.active-skill.json` * / `.peaks/sop-state/` files moved into `.peaks/_runtime/`. * * Pure hand-rolled; uses only node:fs, node:path, and the existing * session-manager helper for writing the binding. No new dependencies. */ import type { ReconcileOptions, ReconcileResult, SessionEntry } from './reconcile-types.js'; /** * Walk the project root's `.peaks/` directory and return an entry per * session dir matching the standard naming pattern, sorted by name * ascending (the most recent is last by sort order, since the date * prefix dominates the lexicographic order). * * Each entry's `lastActivity` is the mtime of the inner `session.json` * file, or null if that file is missing. `artifactCount` is the count * of files under the dir excluding `session.json` itself. */ export declare function discoverSessions(projectRoot: string): SessionEntry[]; /** * 4-tier canonical selection. Tiers evaluated in order; first one that * yields a session id wins. * * 1. active-skill sessionId, if it matches a real entry * 2. entry with the most recent `session.json` mtime * 3. entry with the most recent mtime of any file inside it * 4. entry whose dir name sorts last lexicographically */ export declare function pickCanonicalSession(entries: SessionEntry[], activeSkillSessionId: string | null): { sessionId: string; source: ReconcileResult['canonicalSource']; } | null; /** * Write `.peaks/.session.json` to bind the project to `canonicalSessionId`. * Preserves the projectRoot. The previous binding is returned so the CLI * can surface the re-point delta. */ export declare function repointSessionJson(projectRoot: string, canonicalSessionId: string, repointedFrom: string | null): { repointedFrom: string | null; repointedTo: string; }; /** * Identify deletion candidates. A session is a candidate when: * - the resolved `lastActivity` is older than `ageThresholdMs`, AND * - the dir is "empty or auto-only" (artifactCount === 0, OR the * only file is `session.json` which is auto-generated). * * If `lastActivity` is null (no `session.json` inside), the session's * own dir mtime is used as a fallback so empty dirs without inner * metadata are still fair-game. */ export declare function findDeletionCandidates(entries: SessionEntry[], ageThresholdMs: number): SessionEntry[]; /** * Apply or report deletion of the given candidates. When `apply` is * false, just return `wouldDelete` and do not touch disk. When `apply` * is true, actually `rm -rf` each dir and accumulate any per-dir * errors in the result. */ export declare function applyDeletions(candidates: SessionEntry[], apply: boolean): { deleted: string[]; wouldDelete: string[]; errors: Array<{ sessionId: string; message: string; }>; }; /** * Sync the single `change//` live marker under * `.peaks/_runtime/change/`. The marker is an EMPTY directory (no * per-change-id symlink layer to a single live marker so the * `change/` layer is a single sentinel — easy for the LLM to * navigate, easy for tests to assert on. * * Steps: * 1. Ensure `.peaks/_runtime/change/` exists. * 2. List its entries. * 3. Remove every entry whose name is NOT `/` * (no-op for those whose name matches). * 4. If `/` is missing, create it (empty). * 5. Return the diff for telemetry. * * Path-traversal guards: the canonical session id is validated by * the caller (`SESSION_ID_PATTERN` in the session-manager helper). * The function only ever operates under the project-root-resolved * `.peaks/_runtime/change/` dir, so the resolved paths cannot * escape the project tree. * * @returns `{ removed, created, error }`: * - `removed`: list of entry names that were deleted (e.g. `['/']`). * - `created`: name of the new marker (e.g. `'/'`) or null * when the canonical marker already existed (no-op). * - `error`: error message string or null. */ export declare function syncChangeMarker(projectRoot: string, canonicalSessionId: string): { removed: string[]; created: string | null; error: string | null; }; /** * Top-level orchestrator. Wires migration (added in slice * deletion-candidate selection, and deletion into a single result. */ export declare function reconcileWorkspace(options: ReconcileOptions): ReconcileResult; interface ReconcilePresenceInput { projectRoot: string; sessionId: string; callerId?: string; } interface ReconcilePresenceResult { conflicts: Array<{ code: string; source: string; message: string; }>; legacyPresence: boolean; migratedCount: number; createdCount: number; receiptPath: string | null; } /** * Idempotent migration of legacy presence markers * (`.peaks/_runtime/active-skill.json` and * `.peaks/_runtime//active-skill-*.json`) into the canonical * 4.0.8 lease + index + graph layout. Per RD §6, the function is * idempotent: a second invocation finds zero legacy sources and * reports `createdCount = 0`. * * ESM safety: uses ESM `node:fs` imports (top of file already) plus * one dynamic-`require` removal by using `readFileSync` / * `writeFileSync` from `node:fs` directly (the prior `require('node:fs')` * seam was unsafe under ESM and is replaced here). */ export declare function reconcilePresenceLeases(input: ReconcilePresenceInput): ReconcilePresenceResult; export {};