/** * Cross-project hygiene engine — T1637 (cleo-os system-wide daemon). * * Runs nightly (default 02:00 local time) from the sentient daemon. * Five sequential steps, each failing gracefully so one broken project * cannot abort the others: * * Step 1 – NEXUS integrity check: every registered project's tasks.db, * brain.db, and project-info.json are accessible and valid. * Step 2 – Temp-project GC: projects with no recent activity (> 30 days), * no .git directory, and no recent file changes are flagged in * ~/.local/share/cleo/audit/temp-gc.jsonl. Auto-delete is * intentionally withheld; owner must run * `cleo daemon hygiene apply ` to confirm. * Step 3 – Duplicate-epic detection: epics with similar normalised titles * across multiple registered projects suggest a shared-library * candidate; findings are emitted as Tier-2 proposals. * Step 4 – Stale agent-worktree pruning: delegates to the existing * {@link pruneOrphanedWorktrees} call per project root. * Step 5 – Aggregate digest: summary counts written to * {@link CrossProjectHygieneDigest} and persisted to sentient * state so `cleo daemon status` can surface them. * * Design constraints: * - NEVER throws — every public function returns a result object. * - No new third-party dependencies (uses existing nexus, project-health, spawn). * - All file writes go through node:fs/promises with atomic tmp-then-rename. * - Audit JSONL path: `~/.local/share/cleo/audit/temp-gc.jsonl`. * * @see ADR-047 — Autonomous GC and Disk Safety * @see ADR-054 — Sentient Loop Tier-1 * @task T1637 * @epic T1627 */ /** Days of inactivity before a temp project is flagged for GC. */ export declare const TEMP_GC_INACTIVITY_DAYS: 30; /** Cosine-similarity floor for epic-title duplicate detection (0–1). */ export declare const DUPLICATE_EPIC_SIMILARITY_THRESHOLD: 0.8; /** Result of Step 1 — NEXUS integrity check. */ export interface NexusIntegrityResult { /** Total registered projects examined. */ total: number; /** Projects whose tasks.db, brain.db, and project-info.json are all healthy. */ healthy: number; /** Projects with at least one accessibility failure. */ degraded: number; /** Projects whose directory is no longer reachable on disk. */ unreachable: number; /** Per-project issue strings (populated only for degraded/unreachable). */ issues: Array<{ projectHash: string; projectPath: string; problems: string[]; }>; } /** A single temp-project GC candidate. */ export interface TempGcCandidate { /** Project hash from the NEXUS registry. */ projectHash: string; /** Absolute path to the project root. */ projectPath: string; /** Project name from the registry. */ projectName: string; /** ISO-8601 timestamp of the last recorded activity (lastSeen). */ lastSeen: string; /** Reason the project was flagged. */ reason: string; } /** Result of Step 2 — temp-project GC audit. */ export interface TempGcResult { /** Unique identifier for this GC batch (used with `cleo daemon hygiene apply`). */ batchId: string; /** Candidates flagged for deletion. Owner must explicitly approve each batch. */ candidates: TempGcCandidate[]; /** Absolute path to the audit JSONL file where the batch was appended. */ auditPath: string; } /** A detected cross-project duplicate-epic group. */ export interface DuplicateEpicGroup { /** Normalised title all duplicates share. */ normalisedTitle: string; /** Array of {projectHash, taskId, title} for each duplicate. */ instances: Array<{ projectHash: string; projectPath: string; taskId: string; title: string; }>; } /** Result of Step 3 — duplicate-epic detection. */ export interface DuplicateEpicResult { /** Total projects scanned. */ projectsScanned: number; /** Duplicate groups found. */ groups: DuplicateEpicGroup[]; } /** Result of Step 4 — stale worktree pruning. */ export interface WorktreePruneResult { /** Number of projects scanned. */ projectsScanned: number; /** Total worktree entries pruned across all projects. */ totalPruned: number; /** Per-project errors (project is skipped, others continue). */ errors: Array<{ projectPath: string; reason: string; }>; } /** * Top-level output of one nightly hygiene run. * * Persisted into sentient state so `cleo daemon status` can surface counts * without re-running the full scan. */ export interface CrossProjectHygieneDigest { /** ISO-8601 timestamp of when this run started. */ startedAt: string; /** ISO-8601 timestamp of when this run finished. */ completedAt: string; /** Step 1 result. */ nexusIntegrity: NexusIntegrityResult; /** Step 2 result. */ tempGc: TempGcResult; /** Step 3 result. */ duplicateEpics: DuplicateEpicResult; /** Step 4 result. */ worktreePrune: WorktreePruneResult; /** Human-readable one-line summary for `cleo daemon status`. */ summary: string; } /** * Return the absolute path to the cross-project GC audit JSONL file. * Stored in ~/.local/share/cleo/audit/temp-gc.jsonl (XDG-compliant). */ export declare function getTempGcAuditPath(): string; /** * Check accessibility of every project registered in the global nexus registry. * * Tests: * • Project directory exists on disk. * • `.cleo/tasks.db` is readable. * • `.cleo/brain.db` is readable (optional — warn if missing). * • `.cleo/project-info.json` is parseable JSON. * * Never throws. * * @returns Summary counts + per-project issue list. */ export declare function runNexusIntegrityCheck(): Promise; /** * Detect temp projects (no .git, no recent activity) and flag them for deletion. * * A project is a GC candidate if ALL of: * (a) No `.git` directory at the project root. * (b) `lastSeen` in the nexus registry is older than {@link TEMP_GC_INACTIVITY_DAYS} days. * * The `lastSeen` field in the NEXUS registry is the authoritative activity timestamp — * it is bumped on every `cleo nexus sync`, health check, and task completion. Projects * with no `.git` AND inactive for > 30 days are temp scratch trees safe to remove. * * Candidates are audited to {@link getTempGcAuditPath()} in JSONL format. * Auto-delete is NOT performed — owner must call `cleo daemon hygiene apply `. * * Never throws. */ export declare function runTempProjectGc(): Promise; /** * Detect epics with similar titles across registered projects. * * Algorithm: * 1. For each registered project, load epic titles from tasks.db via getTaskAccessor. * 2. Build n-gram sets for each normalised title. * 3. Group by Jaccard similarity ≥ {@link DUPLICATE_EPIC_SIMILARITY_THRESHOLD}. * 4. Groups spanning ≥ 2 distinct projects are returned as duplicates. * * Never throws. */ export declare function runDuplicateEpicDetection(): Promise; /** * Prune stale agent worktrees for every registered project. * * Delegates to the existing {@link pruneOrphanedWorktrees} from * `packages/core/src/spawn/branch-lock.ts` so there is zero duplication * of the worktree-management logic (DRY). * * Preserve set (T11996 Amendment 1 — PREDICATE BLOCKER fix): * Any task whose status is NOT terminal (i.e. not `done`, `cancelled`, or * `archived`) must be preserved. Querying only `status: 'active'` was the * original bug — it silently removed worktrees for `pending`, `blocked`, and * `proposed` tasks. We now query all non-terminal statuses and build the * preserve set from their union. * * Fail-closed (T11996 Amendment 2): * If the task store is unreadable for a project, skip pruning for that * project and write a structured audit warning. If ALL projects fail to * load tasks, return with zero pruning. * * Never throws. */ export declare function runWorktreePrune(): Promise; /** * Run the full nightly cross-project hygiene loop. * * Executes Steps 1–4 sequentially (step 5 is assembly of the digest). * Each step is independently guarded — failure in one step does not * abort subsequent steps. * * @returns The assembled {@link CrossProjectHygieneDigest} for this run. */ export declare function runCrossProjectHygiene(): Promise; /** * Safe wrapper around {@link runCrossProjectHygiene} — swallows unexpected * errors so the daemon cron never crashes. * * @returns The digest on success, or a minimal digest with a summary error * string on failure. */ export declare function safeRunCrossProjectHygiene(): Promise; /** Result of applying a GC batch. */ export interface ApplyGcBatchResult { /** Batch ID that was applied. */ batchId: string; /** Number of projects unregistered from nexus. */ unregistered: number; /** Projects that could not be unregistered. */ errors: Array<{ projectPath: string; reason: string; }>; } /** * Apply a pending temp-project GC batch. * * Reads the audit JSONL, finds the batch with the given ID, marks it as * `applied`, and calls `nexusUnregister` for each candidate. * * Intentionally does NOT delete project files — the caller is responsible for * rm -rf. This is auditable: each unregister action is logged by nexus. * * Never throws — errors are captured in the result. */ export declare function applyGcBatch(batchId: string): Promise; //# sourceMappingURL=cross-project-hygiene.d.ts.map