/** * Session management service for Peaks artifact storage. * Manages session lifecycle: creation, retrieval, and directory initialization. * * Sessions are automatically created when any skill is invoked. * Each session gets a unique directory under .peaks/ with incrementing numbered files. */ export type SessionInfo = { sessionId: string; createdAt: string; projectRoot: string; }; export type SessionMeta = { sessionId: string; title?: string | undefined; skill?: string | undefined; mode?: string | undefined; gate?: string | undefined; createdAt: string; lastActivity?: string | undefined; projectRoot: string; /** * The outer (harness / IDE / plugin) session id that * `ensureSession` was called from. Sourced from * `PEAKS_OUTER_SESSION_ID` env var, with `CLAUDE_CODE_SESSION_ID` * as a Claude-Code fallback. Stamped once at session creation; * later presence writes can compare against this to detect an * outer-session swap and AskUserQuestion the user about rolling * a new peaks session. Sessions predating the field simply * have it undefined; presence-mismatch detection skips those * (no false positives on legacy data). */ outerSessionId?: string | undefined; }; /** * Drop the project-level session binding at the canonical * `.peaks/_runtime/session.json` so the next `ensureSession()` call * auto-generates a fresh session id. The on-disk session directory * is left intact — rotating does NOT delete the user's data, it * just unbinds the project from that session. Also drops the legacy * `.peaks/.session.json` if present so a stale read from another * tool cannot re-bind the project after rotation. Also drops the * ROTATING caller's per-caller binding when it points at the rotated-out * `clearRotatedCallerBinding`. * * Returns the id of the session that was unbound, or `null` if * no binding was present. The caller is expected to do something * with that — at minimum surface it in the CLI response so the * user can find the directory again if they need to. * * variant so a binding written with `projectRoot: "."` (relative * form, anchored from inside the project dir) is still found when * the caller passes the absolute realpath. Pre-F22 the * strict-equality read returned null in that case, and rotate * silently no-op'd (the CLI reported "no prior binding"). */ export declare function rotateSessionBinding(projectRoot: string): string | null; /** * Bind the project's current session to the given session id by writing * `.peaks/.session.json`. The single-session binding is the source of truth * for `ensureSession()` and any other path that needs to discover the * active session without an explicit --session-id flag. * * This does NOT touch the per-session `session.json` inside `.peaks/_runtime//`; * that file is owned by `setSessionMeta` and records session-scoped * metadata (title, skill, mode, gate, etc.). */ export declare function setCurrentSessionBinding(projectRoot: string, sessionId: string): SessionInfo; /** * Read metadata for a specific session directory. * Returns null if the session directory or its session.json does not exist. */ export declare function getSessionMeta(projectRoot: string, sessionId: string): SessionMeta | null; /** * Write or update metadata for a session. Fields besides sessionId and createdAt * are merged on top of the current meta (partial update). */ export declare function setSessionMeta(projectRoot: string, sessionId: string, partial: Partial>): SessionMeta; /** * Set the display title for a session directory. */ export declare function setSessionTitle(projectRoot: string, sessionId: string, title: string): SessionMeta; /** * List all session directories under .peaks with their metadata. * Returns sessions sorted by sessionId descending (most recent first). * * dirs live at the canonical runtime home `.peaks/_runtime//`. * The legacy top-level layout is read for back-compat (one minor * release) but is not authoritative. */ export declare function listSessionMetas(projectRoot: string): SessionMeta[]; export { ensureSession, ensureSessionWithRotation, type EnsureSessionOptions, type EnsureSessionResult } from './session-binding-bridge.js'; /** * Get the current session ID without creating a new one. * Returns null if no session exists. * * @param projectRoot - Root directory of the project * @returns Session ID or null */ export declare function getSessionId(projectRoot: string): string | null; /** * Resolve the current session id with canonicalize-on-read * semantics. This is the variant the progress subcommands * (step / watch / start / close) use, because the legacy * `getSessionId` returns null any time the stored * `projectRoot` form differs from the caller-passed form * (e.g. stored is "." from inside the project dir; caller * is the absolute realpath). When `getSessionId` returns * null, callers like `ensureSession` create a brand-new * session and overwrite the binding — which is what the * user observed as the "mid-dogfood rebind" bug. * * The fix is to canonicalize both sides of the compare * (realpath, then optionally resolve relative stored * against the caller's project root). The two forms of * the same physical directory now compare equal, and the * existing binding is found instead of being overwritten. * * Use this instead of `getSessionId` only when the * caller is operating on a user-supplied `--project` flag * and the binding may have been written by a CLI invocation * that was running from inside the project dir (the common * peaks-code / peaks-sop scenario). Other modules depend * on the strict-equality semantics of `getSessionId` (the * "no binding" fallback path is part of their contract), * so this variant is opt-in. */ export declare function getSessionIdCanonical(projectRoot: string): string | null; /** * Outcome of the caller-binding half of session resolution. * * `staleSessionId` is the id of a per-caller binding that was SKIPPED * ordinary "no binding" case. It exists so the fall-through is * observable: a caller can tell "this caller has no binding" apart from * "this caller's binding pointed at a session tree that is gone", and * report it instead of silently resolving a different session. */ export type CallerBoundSession = { sessionId: string | null; staleSessionId: string | null; }; /** * rid=caller-binding-staleness). * * The caller-first precedence is unchanged: this caller's binding still * outranks the project-global `session.json`. What changed is what counts * as a USABLE binding — a binding whose bound session directory is gone * is stale, is not trusted, and is reported through `staleSessionId` * instead of being returned as if it were live. * * Returns `{ sessionId: null, staleSessionId: null }` when caller-id * resolution fails (`PEAKS_CALLER_NOT_RESOLVED`), when no per-caller file * exists, or when the file is malformed. */ export declare function resolveCallerBoundSession(projectRoot: string): CallerBoundSession; /** * Get the absolute path to the current session directory. * Creates the session if it doesn't exist. * * home is `.peaks/_runtime//`. The legacy top-level layout * `.peaks/_runtime//` is the back-compat read fallback only. * * @param projectRoot - Root directory of the project */ export declare function getCurrentSessionDir(projectRoot: string): Promise; /** * List all session directories in the .peaks folder. * Returns session IDs (directory names) sorted by date. * * home is `.peaks/_runtime//`. The legacy top-level layout * `.peaks/_runtime//` is read for back-compat (one minor release) so * pre-migration trees keep working. * * @param projectRoot - Root directory of the project * @returns Array of session IDs */ export declare function listSessions(projectRoot: string): string[]; /** * Get the path to project-scan.md for the current session. * Creates the session if it doesn't exist. * * @param projectRoot - Root directory of the project * @returns Absolute path to project-scan.md */ export declare function getProjectScanPath(projectRoot: string): Promise; /** * Check if project-scan.md exists for the current session. * * @param projectRoot - Root directory of the project * @returns true if project-scan.md exists */ export declare function hasProjectScan(projectRoot: string): boolean;