import { type Cli } from "../internals/clis.js"; import type { PlanContext } from "../internals/plan.js"; import { type KiroMcpProjectionResidue } from "../mcp/kiro-managed-projection.js"; import { type ManagedMcpProjectionResidue } from "../mcp/managed-projection.js"; /** * STALE PER-CLI ARTIFACTS — the read-only detection behind `aih prune`. When a repo * was bootstrapped for CLIs [A, B, C] but is now targeted at only [A], the per-CLI * files aih wrote for B and C are stale. This finds exactly those, safely, so the * preview (and, later, the removal) never touches a still-targeted tool or a file * aih doesn't own. * * Two things make it trustworthy: * * 1. It diffs against COMMITTED INTENT only — the `.aih-config.json` marker targets * (or an `aih init` orchestrator's threaded {@link PlanContext.targets}). The * report's other target-inference arms (`--cli`, `--detect`, wired-adapters, * default-claude) are heuristics; using a GUESS to decide what to DELETE could * remove a real on-disk CLI's canon. No committed target set → nothing is stale * (prune's own explicit selection is the escape hatch, not inference). * * 2. It classifies each artifact by DISPOSITION — set by whether aih can prove * ownership of what it would touch (how `bootstrap-ai`/`mcp` write it): * - `file` — aih writes the WHOLE file and overwrites it every run (the * per-CLI adapter note, Kiro's steering/hook extras). aih owns * it outright → `aih prune --apply` MOVES it to `.aih/legacy/`. * - `block` — aih merges a marker-FENCED block into a co-owned file (a * bootloader = preamble + `` * block). The fence proves ownership → `--apply` SUBTRACTS aih's * block in place and leaves the rest; the file is never deleted. * - `advisory` — a co-owned file where aih's part CANNOT be identified on disk * (repo MCP JSON + `.claude/settings.json` hooks: aih's servers / * hooks are plain sibling keys, indistinguishable from the user's, * names may collide). Auto-editing risks user data → `--apply` * only PRINTS what to remove by hand; it never modifies the file. * * Shared artifacts (AGENTS.md is one bootloader for five CLIs) are pruned only when * EVERY declaring CLI is dropped — the diff subtracts the kept set's paths first. * Global `~/…` MCP configs are structurally excluded (never repo-scoped). Pure fs * reads (existsSync / readdirSync) — no spawn, no network — so it is identical * dry-run vs `--verify`. */ /** How aih would remove an artifact — see the module header. */ export type PruneDisposition = "file" | "block" | "advisory"; /** Which kind of per-CLI artifact this is (drives the preview grouping + copy). */ export type PruneArtifactKind = "adapter" | "bootloader" | "mcp" | "settings" | "managed-settings" | "kiro-managed-mcp" | "kiro-steering" | "kiro-hook"; export interface PruneArtifact { kind: PruneArtifactKind; /** Repo-relative path (POSIX separators) prune would act on. */ path: string; disposition: PruneDisposition; /** The dropped CLI(s) this artifact belongs to (a shared bootloader lists all). */ clis: Cli[]; } /** Where the authoritative "kept" set came from — committed intent, or none. */ export type KeptSource = "marker" | "ctx" | "none"; export interface StalePruneSet { /** The committed target set that is KEPT (empty when there is no committed intent). */ targeted: Cli[]; source: KeptSource; /** CLIs with a per-CLI adapter on disk but no longer in the committed targets. */ dropped: Cli[]; /** The subset of `dropped` that is only there because of `--unrunnable` (still in * the committed targets, but no binary on PATH). Empty on every default run. */ unrunnable: Cli[]; /** Marker target strings aih does not recognize (after case-normalization). Non-empty * FAILS CLOSED: no artifact is treated as stale until the marker is fixed, because a * typo'd kept target would otherwise be pruned as if it were dropped. */ unknownTargets: string[]; /** The concrete stale artifacts, in canonical (kind, then declared) order. */ artifacts: PruneArtifact[]; /** * The org-policy-PROJECTED Claude managed-settings ownership state, when claude is * no longer a kept target. This file is not a registered per-CLI artifact (the * registry's Claude settings path is `.claude/settings.json`), so nothing else here * would ever see it — yet it is the one dropped-target artifact aih records * per-key provenance for, and therefore the one it can safely subtract (issue #566). * `undefined` when claude is kept, or when the marker records no active claim. */ managedMcp?: ManagedMcpProjectionResidue; /** Receipt-proven Kiro workspace-MCP distribution for a dropped Kiro target. */ kiroMcp?: KiroMcpProjectionResidue; } /** * The TARGETED CLIs (committed intent) that are wired into this repo but whose binary * is not on PATH — `aih prune --unrunnable`'s opt-in input. A CLI is unrunnable only * when NONE of its registry binaries resolve (`which`/`where`, the same read-only * PATH probe the readiness gate uses — allowlisted by the plan-purity guardrail). * Only targeted∩on-disk CLIs are probed: anything else has no artifacts to prune. * NEVER called on a default/`--stale` run or by the report ([prune-spec] locked: * a PATH problem — new shell, VDI — looks identical to a truly dropped CLI). */ export declare function unrunnableTargets(ctx: PlanContext): Promise; /** * Compute the stale prune set for a repo — read-only. Returns an empty `dropped` * (and `artifacts`) whenever there is no committed target set to diff against, so a * pre-marker repo is never told it has "stale" files it in fact still uses. * `treatAsDropped` (prune `--unrunnable` only) removes those CLIs from the kept set * before the diff — which also lets a shared bootloader fall out once its LAST kept * sharer is treated as dropped. The committed marker itself is never rewritten. */ export declare function stalePruneSet(ctx: PlanContext, opts?: { treatAsDropped?: readonly Cli[]; }): StalePruneSet; /** * A one-shot advisory line for the report's CLI-wiring digest, or undefined when * nothing is stale. Non-blocking (informational) and mirrors `aih adopt`'s * "aih leaves these untouched" voice — prune never removes anything without an * explicit run. */ export declare function staleAdvisory(set: StalePruneSet): string | undefined;