/** * Type envelope for the `peaks workspace reconcile` CLI command. * * The reconcile command scans `.peaks/2026-MM-DD-session-<6hex>/` directories * under the project root, identifies a canonical session via a 4-tier * heuristic (active-skill binding -> most-recent session.json mtime -> * most-recent any-file mtime -> dir-name sort), re-points * `.peaks/.session.json`, and (optionally) deletes empty / abandoned * session dirs older than a configurable age threshold. * * Types are hand-rolled, no new top-level dependencies. */ export type SessionEntry = { /** Directory name (e.g. "2026-06-04-session-89f7cb"). */ sessionId: string; /** Absolute path to the session directory. */ path: string; /** mtime of `session.json` inside the dir in ms since epoch; null if missing. */ lastActivity: number | null; /** * Count of files under the session dir, EXCLUDING `session.json` * (which is auto-generated by `peaks workspace init`). A count of * 0 means the dir is empty / abandoned. */ artifactCount: number; }; export type DeletionCandidateReason = 'empty-or-auto-only' | 'older-than-threshold'; export type ReconcileResult = { /** Absolute project root the command operated on. */ projectRoot: string; /** All discovered `.peaks/2026-MM-DD-session-<6hex>/` directories, sorted by name. */ sessions: SessionEntry[]; /** The session id selected by the 4-tier canonical heuristic. */ canonicalSessionId: string | null; /** * The tier that decided the canonical pick (1..4), where tier 1 is * active-skill and tier 4 is lexicographic last. Null when there are * no sessions at all. */ canonicalSource: 'active-skill' | 'latest-session-json-mtime' | 'latest-any-file-mtime' | 'dir-name-sort' | null; /** The session id the binding pointed at before reconcile. Null if no prior binding. */ repointedFrom: string | null; /** The session id the binding now points at. Null if there were no sessions. */ repointedTo: string | null; /** Sessions that match the age-threshold + empty-or-auto-only deletion rule. */ deletionCandidates: SessionEntry[]; /** Actual deletions performed (only populated when `apply === true`). */ deleted: string[]; /** Sessions that WOULD be deleted if `--apply` were set (only populated when `apply === false`). */ wouldDelete: string[]; /** Age threshold in ms used to compute deletion candidates. */ ageThresholdMs: number; /** Whether `--apply` was set. */ apply: boolean; /** Whether the canonical session id differs from the prior binding. */ repointed: boolean; /** * Old-path runtime files that `migrateOldRuntimeState` moved into * `.peaks/_runtime/` during this reconcile run. Each entry is the * legacy path relative to the project root (e.g. ".peaks/.session.json", * ".peaks/.active-skill.json", ".peaks/sop-state"). Empty when the * tree is already on the new layout (idempotent re-runs return []). * * consumers can ignore this field. */ migratedFiles: string[]; /** * Count of legacy per-session sub-agent state files moved from * `.peaks/_runtime//system/{subagent-progress,progress-spawn}.json` into * `.peaks/_sub_agents//` during this reconcile run. * * detailed list of moved files is not surfaced here (the count is * what the CLI summary and QA test assert on); the underlying * `migrateSubAgentState` helper returns the full path list for * forensics. Additive — older consumers can ignore this field. */ subAgentStateMigrated: number; /** * Errors encountered during the migration step. Each entry has a * `kind: 'migrate'` discriminator so consumers can tell migration * errors apart from deletion errors. The shape is additive. */ errors: Array<{ sessionId: string; message: string; } | { kind: 'migrate'; path: string; message: string; }>; /** * result of syncing the single `change//` live * marker under `.peaks/_runtime/change/`. The marker is an empty * directory; every other entry under `change/` is removed. The * `removed` array lists entry names that were deleted (e.g. * `['/']`); `created` is the name of the new marker (or * `null` when the canonical marker already existed — no-op); * `error` is the first error message (if any) encountered during * the sync. Additive — older consumers can ignore this field. * Replaces the F3 `changeLinks` field. */ changeMarker: { removed: string[]; created: string | null; error: string | null; }; /** * subdirs that were removed by this reconcile run. The F3 * deletes it during reconcile. Empty when the canonical session * had no `system/` subdir. Additive. */ systemCleaned: string[]; }; export type ReconcileOptions = { projectRoot: string; /** When true, actually `rm -rf` the deletion candidates. */ apply: boolean; /** * Age threshold in milliseconds; sessions whose mtime-based * `lastActivity` is older than this AND whose `artifactCount === 0` * (or 1 if the only file is `session.json`) are deletion candidates. * Default: 7 days in the CLI layer. */ olderThanMs: number; };