/** * Claude USER-scope contamination report (D13). Claude loads USER-scope surfaces * (`~/.claude/...`) into EVERY project, so a global framework install (a global * ECC install, a globally enabled plugin) contaminates all projects. This is the * pure, READ-ONLY scan that produces the conflict inventory a binding's doctor * consults: it turns the messy user scope into a countable leakage summary plus * per-surface detail rows. * * SCOPE. It reads ONLY under the injected `home` root (never the real `~`, never * the project root — a project-scope surface must never appear here). `projectRoot` * is part of the D13 report CONTEXT (W7 assembles the final label with both scopes), * but this user-scope scan deliberately never reads it. * * FAIL-OPEN, DELIBERATELY. A contamination report must still render on a broken * machine — that is its job. Malformed user-scope JSON (`settings.json`, * `~/.mcp.json`) is therefore NOT fatal: the unreadable file is recorded in * `warnings` and the scan counts every OTHER surface. This is a scoped exception * to fail-closed, justified because the module is read-only diagnostics; the * cleanup write paths that consume this report stay strictly fail-closed. */ /** The framework a surface is best-effort attributed to (never guessed beyond this vocabulary). */ export type FrameworkAttribution = "ecc" | "superpowers" | "gsd" | "unknown"; /** The countable user-scope surface kinds (one leakage counter each). */ export type ContaminationSurface = "skill" | "agent" | "hook" | "rule" | "plugin" | "mcpServer"; /** One detected user-scope surface row. */ export interface ContaminationEntry { surface: ContaminationSurface; /** The surface's identifying name (skill/rule dir, agent filename, plugin key, server id, hook event). */ name: string; /** Home-relative POSIX path of the surface (the file/dir, or the JSON file a field lives in). */ path: string; /** Best-effort framework tag; `unknown` when no token matches (never guessed further). */ attribution: FrameworkAttribution; /** * For `surface: "hook"` only — the hook command string, so cleanup can target the * exact command for removal. Absent for every other surface. */ command?: string; } /** The countable leakage summary ("N skills, N agents, N hooks, N rules, ..."). */ export interface ContaminationLeakage { skills: number; agents: number; hooks: number; rules: number; plugins: number; mcpServers: number; } export interface ClaudeContaminationReport { /** The countable summary — every field is 0 on a clean home. */ leakage: ContaminationLeakage; /** Per-surface detail rows (one per counted surface instance). */ entries: ContaminationEntry[]; /** Informational context that is NOT leakage: `skillOverrides` keys from settings. */ informational: { skillOverrides: string[]; }; /** Named unreadable user-scope files (malformed JSON) — the report still rendered. */ warnings: string[]; /** True only when every leakage count is 0. */ clean: boolean; /** The D13 label decision INPUT (final label assembly is W7). */ verdictInput: "clean" | "contaminated"; } export interface ClaudeContaminationParams { /** The user's home root to scan (tests inject a mkdtemp home; NEVER the real `~`). */ home: string; /** The project root — part of the D13 report context; the user-scope scan never reads it. */ projectRoot: string; } /** * The hook-scope a chain entry was read from — which settings LAYER it lives in. * `home` = user-scope (`~/.claude/settings.json`), `project` = * `/.claude/settings.json`, `local` = `/.claude/settings.local.json`. */ export type HookScope = "home" | "project" | "local"; /** * One resolved hook-chain entry (W7 §B.6). Richer than {@link collectHookCommands}: * it keeps the group `matcher` and records the settings `scope`. `command` is the * RAW command string (it may embed a machine-local path, so it is for structured * consumers — the card/evidence — never for a `Check.detail`); `origin` is a * PATH-FREE display label (the basename of the command's first token) safe to echo * into a deterministic, portable doctor detail. */ export interface HookChainEntry { event: string; matcher?: string; /** Raw command (may contain a machine-local path) — structured consumers only. */ command: string; /** Path-free display label: the basename of the command's first token. */ origin: string; scope: HookScope; } /** * Collect the per-event hook chain across ALL three Claude settings layers — home * (`~/.claude/settings.json`), project (`/.claude/settings.json`), and local * (`/.claude/settings.local.json`) — for the W7 §B.6 doctor probe. READ-ONLY * and tolerant: an absent or malformed settings file contributes nothing (never * throws). Entries are returned in layer order (home, project, local); the caller * SORTS before formatting for determinism. */ export declare function collectHookChain(params: { home: string; projectRoot: string; }): HookChainEntry[]; export declare function claudeContaminationReport(params: ClaudeContaminationParams): ClaudeContaminationReport;