/** * Low-disk guard — pure helpers for the disk janitor's free-space check. * * The janitor checks free space on the volume holding CREWLY_HOME every * 10 minutes. Below the low mark — max(15 GB, 8 % of the disk), because a * ~1 TB Mac already misbehaves at 5–8 % free — it cleans up at once with * shorter idle thresholds; if space is still short it tells the owner (at most * once per 24 h, or once per 6 h and marked urgent below max(5 GB, 3 %)) what * it freed, what it could not remove and why, and that it never touches the * owner's own files. This file holds the decisions and the wording; the * service does the I/O. * * @module services/worktree/low-disk-guard */ /** Subset of `fs.StatFs` the guard needs (numbers or bigints). */ export interface StatFsLike { /** Free blocks available to unprivileged users */ bavail: number | bigint; /** Block size in bytes */ bsize: number | bigint; /** Total blocks on the volume (optional: an older probe may not report it) */ blocks?: number | bigint; } /** Free and total bytes of a volume. */ export interface DiskSpace { /** Free bytes for unprivileged users */ freeBytes: number; /** Total bytes, or null when the probe did not report the size */ totalBytes: number | null; } /** `fs.promises.statfs`-compatible probe. */ export type StatFsFn = (p: string) => Promise; /** Disk level relative to the thresholds. */ export type DiskLevel = 'ok' | 'low' | 'critical'; /** Janitor state persisted under CREWLY_HOME (notice cadence, sweep cadence, arming). */ export interface LowDiskNoticeState { /** Last notice of any kind (ms epoch) */ lastNoticeAt?: number; /** Last urgent notice (ms epoch) */ lastUrgentAt?: number; /** * First real pass that ran with the crewly#1354 retention code (ms epoch). * Its deletes stay dry-run until RETENTION_ARM_DELAY_MS after this. */ retentionArmedAt?: number; /** Last transcript sweep (ms epoch) */ lastTranscriptSweepAt?: number; /** Last tmp / logs sweep (ms epoch) */ lastTmpSweepAt?: number; } /** Something the janitor looked at and left on disk. */ export interface KeptItem { /** Absolute path */ path: string; /** Size in bytes */ bytes: number; /** Janitor reason code (`dirty`, `not-merged`, `unpushed`, …) */ reason: string; } /** * Free bytes on the volume holding `p`. * * @param p - Any path on the volume * @param statfs - Probe (default `fs.promises.statfs`) * @returns Free bytes for unprivileged users, or null when the probe failed */ export declare function readFreeBytes(p: string, statfs?: StatFsFn): Promise; /** * Free and total bytes of the volume holding `p`. * * @param p - Any path on the volume * @param statfs - Probe (default `fs.promises.statfs`) * @returns Free and total bytes, or null when the probe failed */ export declare function readDiskSpace(p: string, statfs?: StatFsFn): Promise; /** * The low-disk mark for a volume: the absolute floor or a fraction of the * disk, whichever is larger. A 926 GB disk gets ~74 GB, a 100 GB disk 15 GB. * * @param totalBytes - Disk size, or null when unknown (the floor is used) * @returns Bytes below which free space counts as low */ export declare function lowDiskThresholdBytes(totalBytes: number | null): number; /** * The critical mark for a volume: max(5 GB, 3 % of the disk). * * @param totalBytes - Disk size, or null when unknown (the floor is used) * @returns Bytes below which free space counts as critical */ export declare function criticalDiskThresholdBytes(totalBytes: number | null): number; /** * Classify free space against the volume's marks. * * @param freeBytes - Free bytes * @param totalBytes - Disk size (null / omitted: the absolute floors apply) * @returns `critical` below the critical mark, `low` below the low mark, else `ok` * * @example * ```typescript * diskLevel(50 * GB, 926 * GB); // 'low' (mark is 8 % = 74 GB) * diskLevel(50 * GB); // 'ok' (no size: 15 GB floor) * ``` */ export declare function diskLevel(freeBytes: number, totalBytes?: number | null): DiskLevel; /** * Whether the crewly#1354 retention deletes are armed: the first real pass * records the time and only logs; deletes start RETENTION_ARM_DELAY_MS later. * * @param state - Janitor state * @param now - Current time (ms) * @returns True when deletes may run */ export declare function retentionArmed(state: LowDiskNoticeState, now: number): boolean; /** * Idle threshold to use, shortened in low-disk mode. * * @param normalMs - Threshold in normal mode * @param lowDisk - Whether free space is below LOW_DISK_BYTES * @returns `normalMs`, or `normalMs / LOW_DISK_IDLE_DIVISOR` floored at LOW_DISK_MIN_IDLE_FLOOR_MS * * @example * ```typescript * idleThreshold(24 * HOUR, true); // 12 h * idleThreshold(2 * HOUR, true); // 2 h (floor) * ``` */ export declare function idleThreshold(normalMs: number, lowDisk: boolean): number; /** * Whether to tell the owner now, and how. * * @param level - Current disk level (after cleanup) * @param state - When the owner was last told * @param now - Current time (ms) * @returns `urgent`, `normal`, or null (nothing to send) */ export declare function noticeToSend(level: DiskLevel, state: LowDiskNoticeState, now: number): 'urgent' | 'normal' | null; /** * The state after a notice went out. * * @param state - Previous state * @param kind - What was sent * @param now - Send time (ms) * @returns New state */ export declare function recordNotice(state: LowDiskNoticeState, kind: 'urgent' | 'normal', now: number): LowDiskNoticeState; /** What one pass freed, by kind (bytes), for the owner line. */ export interface FreedBreakdown { /** Finished worktrees and scratch dirs removed */ worktrees: number; /** node_modules / build output stripped from kept worktrees */ stripped: number; /** Worktrees archived as a patch and removed */ archived: number; /** Agent transcripts pruned */ transcripts: number; /** CREWLY_HOME/tmp and logs */ tmp: number; } /** * One short line of what a pass freed, by kind; empty when nothing was. * * @param freed - Bytes freed per kind * @returns `"freed 3.2 GB (worktrees 2.1 GB, node_modules 1.1 GB)"` or '' */ export declare function describeFreed(freed: FreedBreakdown): string; /** * Plain-English reason for a kept item. * * @param reason - Janitor reason code * @returns Short phrase ("has changes that were never committed") */ export declare function describeReason(reason: string): string; /** * Owner notice for low disk: free space, what was freed, and the biggest * items left with the reason, short enough for a phone. * * @param input - Free bytes, urgency, bytes freed this pass, kept items * @returns Title and message */ export declare function buildLowDiskNotice(input: { freeBytes: number; urgent: boolean; freedBytes: number; items: KeptItem[]; /** Optional breakdown of what was freed (replaces the plain "cleaned up X" line) */ freed?: FreedBreakdown; /** Optional: total disk size, so the owner sees the mark that was crossed */ totalBytes?: number | null; }): { title: string; message: string; }; //# sourceMappingURL=low-disk-guard.d.ts.map