/** * Secondary install placements (A4b) — the second-root generalization the single-`destination` * install model can't express. Two kinds (see {@link PlacementKind}): * - "mirror" — codex copies the bundle's `agents/*.toml` FLAT into `.codex/agents/`, where * Codex loads custom agents (it does NOT read them from `.agents/skills`). * - "managed-block" — copilot writes a sentinel-delimited pointer block into the (possibly * user-owned) `.github/copilot-instructions.md`, preserving the rest of it. * * This module is PURE: it resolves declarative {@link PlacementSpec}s to absolute roots, selects the * mirror source files, and provides the managed-block string transforms (render/upsert/remove/read). * The planner (plan.ts) decides actions and the apply engine (apply.ts) executes them; neither knows * the per-kind string mechanics — those live here. Zero runtime dependencies; only `node:` built-ins. */ import { type AgentTarget, type PlacementKind, type PlacementSpec, type ResolveOpts, type Scope } from "./types.js"; import type { LocatedSource } from "./source.js"; /** A {@link PlacementSpec} resolved to absolute paths under a scope. Pure derivation; nothing stored. */ export interface ResolvedPlacement { readonly kind: PlacementKind; /** Absolute containment boundary (REQ-SEC-02): `/`. */ readonly root: string; /** Absolute destination: a DIR ("mirror") or a FILE ("managed-block"): `/`. */ readonly destination: string; readonly spec: PlacementSpec; } /** * Resolve every secondary placement declared on `target` to absolute roots under `scope` (A4b). * Returns `[]` for agents with no placements (claude/cursor/gemini). Pure; the single derivation * point so a new rule stays one `AGENT_TARGETS` edit (REQ-SCALE-01). */ export declare function resolvePlacements(target: AgentTarget, scope: Scope, opts?: ResolveOpts): ResolvedPlacement[]; /** One selected mirror source: the bundle-relative source and its FLAT destination basename. */ export interface MirrorFile { readonly srcRelpath: string; readonly destRelpath: string; readonly srcHash: string; } /** * Select the bundle files a "mirror" placement copies (A4b): every `source.files` entry whose * POSIX relpath starts with `spec.sourcePrefix`, copied FLAT (basename only) into the destination. * Sorted by destination basename for deterministic plans. Pure. */ export declare function selectMirrorFiles(source: LocatedSource, spec: PlacementSpec): MirrorFile[]; /** * Render the managed-block BODY (without sentinels) for copilot (A4b). Points Copilot — which has no * skills loader — at the staged bundle under `.github/feature-forge/` and lists the available skills. * Deterministic given the bundle's skill ids. Pure. */ export declare function renderCopilotBlock(skills: readonly string[]): string; /** Wrap a rendered block body in the managed sentinels — the exact region written on disk. */ export declare function wrapBlock(body: string): string; /** * Extract the full managed region (sentinels INCLUDED) currently present in `content`, or `null` if * no well-formed `start…end` region exists. The region is what `wrapBlock` produces, so its hash is * directly comparable to a freshly rendered block. Pure. */ export declare function extractManagedRegion(content: string): string | null; /** * Insert or replace the managed block in `existing`, preserving all user content outside the * sentinels (A4b). If a region exists it is replaced in place; otherwise the block is appended after * the existing content (separated by a blank line). `existing` is `""` for a not-yet-created file. * The result always ends with a single trailing newline. Pure. */ export declare function upsertBlock(existing: string, body: string): string; /** * Remove the managed block from `existing`, preserving the rest (A4b uninstall). Returns the * remaining content (trailing whitespace trimmed to a single newline), or `""` if nothing but the * block (and whitespace) remains — the caller deletes the file in that case. Pure. */ export declare function removeBlock(existing: string): string;