/** * Project lifecycle engine — move, reroot, rename, and re-register CLEO projects. * * Provides the core operations for relocating a CLEO project on disk, * renaming it, and reconciling the nexus registry after manual moves. * * All functions accept explicit absolute paths — no CWD-walk-up (AC7). * * @task T11010 — T10298-1 * @task T12552 — dry runs are pure * @task T12556 — relocations RENAME; nothing is copied, so no stale copy exists * @task T12558 — `rerootProject`, child/ancestor targets refused, tombstone */ import type { MoveProjectResult, ProjectRelocationPlan, RerootProjectResult } from '@cleocode/contracts'; import { type EngineResult } from './engine-result.js'; export type { MoveProjectResult } from '@cleocode/contracts'; /** Result of a successful project rename. */ export interface RenameProjectResult { /** Stable project UUID — preserved across renames. */ projectId: string; /** The project root path. */ projectRoot: string; /** The old project name. */ oldName: string; /** The new project name. */ newName: string; /** * The project's write-once `projectHash`, unchanged by a rename (T12557). * The field name is kept for API compatibility. */ newProjectHash: string; /** * Where the name was recorded (T12716): `project.json` (committed — commit * the change), or `project-info.json` for a legacy project that has not * been migrated by `cleo doctor project-identity --resolve` yet. */ recordedIn?: 'project.json' | 'project-info.json'; /** Global registry label: `renamed`, `not-registered`, or why it failed (T12716). */ registry?: string; /** * Cleo Nexus label (T12716): `not-linked`, or `relink-required` when * `.cleo/nexus-link.json` binds this project — re-run `cleo project link` * to push the new label. */ nexusLabel?: 'not-linked' | 'relink-required'; /** Follow-up the operator should run, when any. */ hint?: string; } /** Result of a successful project re-registration. */ export interface ReregisterProjectResult { /** Stable project UUID. */ projectId: string; /** The project root path. */ projectRoot: string; /** Current project hash. */ projectHash: string; /** Whether the project had drifted (path changed since last register). */ drifted: boolean; /** Nexus reconcile status. */ reconcileStatus: 'ok' | 'path_updated' | 'auto_registered' | 'candidate'; /** Previous path if drift was detected. */ oldPath?: string; } /** Options shared by {@link moveProject} and {@link rerootProject}. */ export interface RelocateProjectOptions { /** * Return a {@link ProjectRelocationPlan} instead of acting. A dry run writes * nothing to disk and opens no database (T12552). */ dryRun?: boolean; } /** * Move a CLEO project to a new path on the SAME device by RENAMING its root. * * Nothing is copied: `.git`, the live database and its WAL sidecars move in * one atomic rename, so there is never a second copy to diverge (T12556) and * no torn database snapshot. In order: * * 1. Refuses a target inside or containing the project (`E_INVALID_TARGET`), * an existing file or non-empty directory, or a target on another device * (`E_CROSS_DEVICE` — use a plain `mv`, then any cleo command). * 2. Refuses while any session, CLEO worktree or git worktree is bound * (`E_MOVE_BLOCKED`). * 3. Takes the required checkpoint, copied outside the tree. * 4. Closes every database handle and renames the root. * 5. Rebinds the registry (the nonce travelled with `.cleo/`), old path `missing`. * * `project-info.json` is not rewritten: `projectHash` stays byte-identical and * no absolute `projectRoot` is written (T12557). A crash after step 4 leaves a * state the next command in the new root repairs on its own — the old path is * gone and the carried nonce proves the move (T12470). * * @param newPath - Absolute path to the new project root location. * @param projectRoot - Absolute path to the current project root. * @param opts - `dryRun: true` returns the plan and changes nothing. * @returns The plan (dry run) or the {@link MoveProjectResult}. * * @example * ```typescript * const plan = await moveProject('/new/location/project', '/old/project', { dryRun: true }); * const result = await moveProject('/new/location/project', '/old/project'); * ``` */ export declare function moveProject(newPath: string, projectRoot: string, opts: RelocateProjectOptions & { dryRun: true; }): Promise>; export declare function moveProject(newPath: string, projectRoot: string, opts?: RelocateProjectOptions & { dryRun?: false; }): Promise>; export declare function moveProject(newPath: string, projectRoot: string, opts?: RelocateProjectOptions): Promise>; /** * Make a child directory of the current project root the new project root. * * RENAMES CLEO's own top-level entries into `childDir` — `.cleo/`, * `.worktreeinclude`, and `.github/` when it holds only CLEO's init templates. * Nothing is copied, so tasks, BRAIN memory, sessions and the checkout nonce * travel together. Everything else at the old root stays. In order: * * 1. Refuses while any session or CLEO worktree is bound (`E_REROOT_BLOCKED`). * 2. Takes the required checkpoint, copied outside the tree. * 3. Closes every database handle, then renames. * 4. Confirms `.cleo/project-id`, writing the SAME id when absent. On failure * the renames are undone. * 5. Leaves `.cleo-moved.json` at the old root, so commands there refuse with * `E_PROJECT_MOVED` instead of creating an empty store. * 6. Rebinds the registry to `childDir`; the old root becomes `missing`. * * `project-info.json` is not rewritten: `projectHash` stays byte-identical and * no absolute `projectRoot` is written (T12557). * * Resumable: run from the child (`cleo project reroot .`) after an interrupted * run — `.cleo/` already in the child, registry still naming the old root — * it finishes steps 4-6. * * @param childDir - Absolute path of a directory strictly inside `projectRoot`, * or `projectRoot` itself to resume an interrupted reroot. * @param projectRoot - Absolute path to the current project root. * @param opts - `dryRun: true` returns the plan and touches no disk or registry. * @returns The plan (dry run) or the {@link RerootProjectResult}. * * @example * ```typescript * const plan = await rerootProject('/work/mono/app', '/work/mono', { dryRun: true }); * const done = await rerootProject('/work/mono/app', '/work/mono'); * ``` */ export declare function rerootProject(childDir: string, projectRoot: string, opts: RelocateProjectOptions & { dryRun: true; }): Promise>; export declare function rerootProject(childDir: string, projectRoot: string, opts?: RelocateProjectOptions & { dryRun?: false; }): Promise>; export declare function rerootProject(childDir: string, projectRoot: string, opts?: RelocateProjectOptions): Promise>; /** * Rename a CLEO project: the committed `.cleo/project.json` name, the global * registry label, and the Cleo Nexus label (T12716). * * This is a lightweight metadata operation — no files are moved and no id or * `projectHash` changes. * * - A project with `.cleo/project.json` gets its `name` rewritten there (the * id is carried over byte-identical); commit the change. * - A legacy project (only `.cleo/project-id` or none) keeps the pre-T12716 * behaviour — `project-info.json` `name` — and is told to migrate with * `cleo doctor project-identity --resolve`. Renaming never migrates. * - The registry row is relabelled best-effort; the Nexus label is reported as * `relink-required` when the project is linked (the link verb re-sends it). * * @param newName - The new project name. * @param projectRoot - Absolute path to the project root. * @returns EngineResult with {@link RenameProjectResult} on success. * * @example * ```typescript * const result = await renameProject('my-new-name', '/path/to/project'); * if (result.success) { * console.log(`Renamed to ${result.data.newName}`); * } * ``` */ export declare function renameProject(newName: string, projectRoot: string): Promise>; /** * Re-register a CLEO project with the nexus registry. * * Detects when a project has been moved on the filesystem without using * `moveProject`, and reconciles the nexus registry accordingly. * * @param projectRoot - Absolute path to the project root. * @returns EngineResult with {@link ReregisterProjectResult} on success. * * @remarks AC5: Reads project-info.json, calls nexusReconcile, and * returns drift status when the filesystem location has changed. * * @example * ```typescript * const result = await reregisterProject('/path/to/moved-project'); * if (result.success) { * console.log(`Drifted: ${result.data.drifted}, status: ${result.data.reconcileStatus}`); * } * ``` */ export declare function reregisterProject(projectRoot: string): Promise>; //# sourceMappingURL=project-lifecycle.d.ts.map