/** * The persisted install manifest (read/write/build) and the manifest-driven uninstall-exactness * policy (spec 05). This module locates the hidden parent-sibling manifest, reads/validates and * atomically writes it, builds an {@link InstallManifest} from an apply result, and owns the * uninstall removal POLICY (`planUninstall`). The safe EXECUTION of that plan is `apply()` in * spec 04 — there is no `applyUninstall` here. * * Zero runtime dependencies; only `node:` built-ins. Named exports only. Core functions return * `Result` and never throw for expected errors; `JSON.parse` is wrapped in `try/catch`. */ import { type AgentId, type InstallManifest, type ManifestFile, type Mode, type Placement, type PlannedAction, type ResolveOpts, type Result, type Scope } from "./types.js"; /** * Inputs to {@link buildManifest}. The caller (apply.ts, spec 04) assembles this from the resolved * detection target, the chosen scope/mode, and the apply result's per-file inventory. */ export interface BuildManifestArgs { readonly agent: AgentId; readonly scope: Scope; readonly mode: Mode; /** Absolute path of the `feature-forge/` namespace dir this manifest governs. */ readonly destination: string; /** * Per-file inventory of what was written, paths relative to `destination`. In `"symlink"` mode * this is `[]` (no per-file copy exists). In `"copy"` mode each entry carries its `sha256`. */ readonly files: readonly ManifestFile[]; /** Installed skill ids (the bundle's `skills/*` dir names). */ readonly skills: readonly string[]; /** SHA-256 over the source bundle's canonical (sorted-path) file set — drift anchor (spec 03). */ readonly sourceHash: string; /** Recorded pinned rauf coordinate (e.g. "@garygentry/rauf@0.14.0"); `null` when `--skip-rauf` (spec 06). */ readonly raufPin: string | null; /** Symlink mode only: the source bundle the namespace dir links to (REQ-SAFE-02). */ readonly link?: { readonly target: string; }; /** Secondary placement inventory written this run (A4b); omit/empty when the agent has none. */ readonly placements?: readonly Placement[]; /** Prior manifest, if any. When present, its `installedAt` is preserved (this is an update). */ readonly previous?: InstallManifest | null; /** Injectable clock for deterministic tests. Default: `() => new Date()`. */ readonly now?: () => Date; } /** * Assemble an {@link InstallManifest} from an apply result (REQ-SAFE-01/03). Pure — no I/O. * * Timestamp policy: `updatedAt` is always "now"; `installedAt` is `previous.installedAt` when * reconciling an existing install, else "now". `featureForgeVersion` is always `null` today * (OQ-A/IR-1; C-3 forbids synthesizing one). */ export declare function buildManifest(args: BuildManifestArgs): InstallManifest; /** * Absolute path of the hidden parent-sibling manifest for an agent + scope (D6/D8): * `///.feature-forge..json` * e.g. `~/.claude/skills/.feature-forge.global.json`. Identical for copy and symlink mode. */ export declare function manifestPath(agent: AgentId, scope: Scope, opts?: Omit): string; /** * Read and validate the manifest at `p`. Absent (`ENOENT`) → `ok(null)`; present + valid → * `ok(manifest)`; unreadable / invalid JSON / failed shape validation → `err(MANIFEST_CORRUPT)`. */ export declare function readManifest(p: string): Result; /** * Atomically write the manifest to `p` (write `

.tmp` → `rename`). Creates the parent dir if * missing. Returns `err(WRITE_DENIED)` on a permission failure, cleaning up the temp file. */ export declare function writeManifest(p: string, m: InstallManifest): Result; /** * Compute the uninstall plan from a manifest (REQ-OPS-03, REQ-SAFE-01/02). PURE — no I/O, * manifest only. Returns an all-`"remove"` {@link PlannedAction}: copy mode one * `{ relpath, action: "remove" }` per `manifest.files[].path` in recorded order; symlink mode the * single `{ relpath: ".", action: "remove" }`. The safe EXECUTION is `apply()` in spec 04. */ export declare function planUninstall(manifest: InstallManifest): Result;