/** * `gjc customize doctor` — provenance-aware customization inspection. * * Answers, for one project: what did GJC discover, which source convention won, * what is disabled/shadowed/rejected/quarantined, and why is a tool, skill, * hook, extension, command, MCP server, or plugin bundle absent. * * The command is read-only and reuses the canonical discovery registry * (`loadCapability`) and the exact session-startup consumers (`loadSkills`, * `loadSlashCommands`, `loadAllMCPConfigs`, plugin-bundle registry). It never * re-implements discovery, never executes hooks, never connects MCP servers, * and never prints credentials, endpoint tokens, auth headers, env values, or * raw config dumps. */ import { type Settings as SettingsInstance } from "../config/settings"; export type CustomizeSurfaceKind = "mcp" | "skill" | "hook" | "tool" | "extension" | "command" | "plugin-bundle"; /** * Provenance class of a discovered item — the reusable read model for the * `/extensions` surface (#4291) and CI/setup tooling. * * - `canonical`: project/global `.gjc` entries and GJC bundled defaults — the * primary load path and the persisted authority for sessions. * - `convention`: items from registered non-native conventions that are part of * the discovery load path (claude-plugins, claude/codex hooks, agents, * cursor, gemini, opencode, windsurf, cline, github, mcp-json, ssh). * - `import-candidate`: Claude Code / Codex project (+ global) files on * surfaces GJC deliberately never loads. Reported for provenance only; never * active runtime authority. Candidates for a future import flow (#4291). * - `imported`: items carrying explicit import provenance (reserved; no import * command exists yet, so nothing emits this today). * - `plugin`: plugin bundles (npm plugin packages + GJC plugin bundles). */ export type CustomizeSourceClass = "canonical" | "convention" | "import-candidate" | "imported" | "plugin"; export type CustomizeItemStatus = "loaded" | "disabled" | "shadowed" | "rejected" | "quarantined" | "stored-only" | "ignored"; /** * Bounded, documented reason codes. One reason per item; distinct examples * (malformed, disabled, shadowed, quarantined, policy-blocked) are always * distinguishable by `reason` alone. */ export type CustomizeReasonCode = "loaded" | "managed" | "storage-only" | "disabled-extension" | "disabled-provider" | "disabled-server" | "disabled-bundle" | "shadowed-by-precedence" | "invalid-config" | "load-error" | "quarantined" | "policy-blocked" | "source-ignored"; export interface CustomizePrecedenceEntry { provider: string; displayName: string; priority: number; enabled: boolean; } export interface McpSafeSummary { transport?: "stdio" | "http" | "sse"; /** Executable path for stdio servers (safe: no credential values). */ command?: string; /** Arguments with secret-looking values replaced by "". */ args?: string[]; /** Endpoint with userinfo/path/query redacted via redactMCPEndpoint. */ url?: string; /** Environment variable names only — values are never emitted. */ envKeys?: string[]; hasHeaders: boolean; hasAuth: boolean; hasOauth: boolean; enabled?: boolean; autoload?: boolean; /** True when session startup would include this server in its connect set. */ connectable: boolean; } export interface CustomizeDoctorItem { name: string; kind: CustomizeSurfaceKind; /** Provenance class (see CustomizeSourceClass). */ sourceClass: CustomizeSourceClass; convention: string; provider: string; providerName: string; scope: "user" | "project" | "native"; path: string; status: CustomizeItemStatus; reason: CustomizeReasonCode; detail: string; remediation: string[]; trust: string; restartRequired: boolean; precedence: { priority: number; shadowedBy?: { provider: string; scope: string; }; }; /** Present only for MCP servers. */ mcp?: McpSafeSummary; /** Present only for quarantined plugin-bundle surfaces. */ quarantineCode?: string; } export interface CustomizeDoctorSurface { kind: CustomizeSurfaceKind; displayName: string; description: string; precedence: CustomizePrecedenceEntry[]; items: CustomizeDoctorItem[]; /** Skill-surface policy notes (why discovery is off or scoped). */ skillScopeNotes?: string[]; /** Surface-level warnings (for example a failed startup projection). */ warnings?: string[]; } export interface CustomizeDoctorReport { schemaVersion: 1; command: "customize doctor"; cwd: string; generatedAt: string; policy: { skillsEnabled: boolean; skillScopeNotes: string[]; disabledProviders: string[]; mcpNote: string; /** Conventions present on disk but deliberately not in the load path. */ conventionsNotLoaded: string[]; /** User-home convention directories present (global import candidates). */ globalImportCandidateDirs: string[]; /** Stable descriptor table for the sourceClass taxonomy (CI contract). */ sourceClasses: Array<{ sourceClass: CustomizeSourceClass; description: string; }>; }; surfaces: CustomizeDoctorSurface[]; summary: Record; warnings: string[]; } export interface CustomizeDoctorCommandOptions { json: boolean; cwd?: string; } /** * Build the full doctor report for a project directory. * * @param cwd Project directory; defaults to getProjectDir(). * @param activeSettings Session-equivalent settings; when omitted, * Settings.loadReadonly is called for the given cwd (CLI path). The * read-only load never opens the DB, runs migrations, or writes files. * Tests pass an in-memory instance. */ export declare function runCustomizeDoctor(cwd?: string, activeSettings?: SettingsInstance): Promise; export declare function renderCustomizeDoctorText(report: CustomizeDoctorReport): string; export declare function renderCustomizeDoctorJson(report: CustomizeDoctorReport): string; /** CLI entry: run the doctor and write text or JSON to stdout. */ export declare function runCustomizeDoctorCommand(options: CustomizeDoctorCommandOptions): Promise;