From 331239cc967f5ccc209f3036210ab7d67d4b83a5 Mon Sep 17 00:00:00 2001 From: Juha Litola Date: Wed, 26 Aug 2026 23:33:24 +0300 Subject: [PATCH] fix: scope uninstall guidance cleanup Respect interactive agent selection when removing guidance.\n\nPreserve shared targets used by detected hosts and retain guidance when MCP removal fails. --- ...pe-interactive-uninstall-guidance.fixed.md | 6 + docs/implementation/cli-commands.md | 4 +- ...nit-guidance-and-expanded-agent-support.md | 17 +- docs/implementation/init-setup-output.md | 21 +- src/commands/init/init.test.ts | 295 +++++++++++++++++- src/commands/init/init.ts | 73 ++++- 6 files changed, 386 insertions(+), 30 deletions(-) create mode 100644 changes/scope-interactive-uninstall-guidance.fixed.md diff --git a/changes/scope-interactive-uninstall-guidance.fixed.md b/changes/scope-interactive-uninstall-guidance.fixed.md new file mode 100644 index 00000000..1242fb27 --- /dev/null +++ b/changes/scope-interactive-uninstall-guidance.fixed.md @@ -0,0 +1,6 @@ +--- +"githits": patch +"@githits/mcp": none +--- + +- **Respect uninstall selection for guidance** - Interactive user uninstall now removes guidance only for selected tools whose MCP is absent after cleanup and preserves shared guidance usable by any detected tool that was kept. diff --git a/docs/implementation/cli-commands.md b/docs/implementation/cli-commands.md index 97feb267..120951c0 100644 --- a/docs/implementation/cli-commands.md +++ b/docs/implementation/cli-commands.md @@ -140,9 +140,9 @@ Global setup supports Claude Code, Cursor, Windsurf, VS Code / Copilot, Cline, C The command uses `createContainer()` lazily for the login step. Tool detection and configuration use lightweight dependencies that don't require auth. -The top-level `githits uninstall` command reverses the tool configuration performed by init; `githits init uninstall` remains a compatibility alias with the same flags and behavior. In interactive mode it first asks whether to remove user-level config or project-level config. User-level uninstall scans the same supported agents, removes only GitHits MCP/plugin entries, and leaves authentication credentials untouched. Unless `--keep-guidance` is passed, it also removes all four canonical GitHits skill files and the exact historical Cline/Junie `githits-mcp/SKILL.md` files, while preserving unrelated skills and directories. Use `githits logout` separately to remove stored credentials. For Claude Code, user-level inspection reads `$CLAUDE_CONFIG_DIR/.claude.json` when `CLAUDE_CONFIG_DIR` is non-empty, otherwise `~/.claude.json`; it inspects only `mcpServers.githits`, skips an already-absent removal, and rereads the structure after mutation. Malformed or unreadable Claude state blocks MCP mutation. Config-file uninstall removes `GitHits`/case-variant server entries while preserving other MCP servers and user settings; MCP host-config cleanup preserves the host config files and directories. Project uninstall uses the verified project config paths above, removes only GitHits entries from project-local files, preserves unrelated servers/settings, and best-effort removes legacy `.githits/init/project-setup.json` markers from earlier versions. Project uninstall does not remove global tools or Pi's global `pi-mcp-adapter` package because they can be shared by other projects; legacy, pre-existing, and GitHits-installed global Pi adapters are left installed. User-level Pi uninstall removes the `GitHits` entry from Pi's MCP config and runs `pi remove npm:pi-mcp-adapter` when the Pi CLI is available; if Pi is no longer installed, stale Pi MCP config entries are still removable without attempting adapter cleanup. Project uninstall is best-effort across supported config paths: missing files and files without GitHits are skipped, malformed/read-only/write-failing files are reported in the final summary, and the command exits 1 only after all possible removals have been attempted. JSONC-style files are accepted on read and rewritten as canonical JSON only when changed, matching setup behavior. Codex project setup rewrites `.codex/config.toml` as TOML and does not preserve existing TOML comments or formatting. +The top-level `githits uninstall` command reverses the tool configuration performed by init; `githits init uninstall` remains a compatibility alias with the same flags and behavior. In interactive mode it first asks whether to remove user-level config or project-level config. User-level uninstall scans the same supported agents, removes only GitHits MCP/plugin entries, and leaves authentication credentials untouched. Unless `--keep-guidance` is passed, interactive user uninstall also removes the four canonical GitHits skill files and exact historical Cline/Junie `githits-mcp/SKILL.md` files owned by the selected tools. Guidance usable by an unselected detected tool is retained, including shared targets, and a selected tool retains its guidance when its MCP removal fails. Non-interactive `--yes` and user uninstall with no configured MCP target clean every verified user guidance target; project uninstall cleans every verified project guidance target. Unrelated skills and directories are preserved. Use `githits logout` separately to remove stored credentials. For Claude Code, user-level inspection reads `$CLAUDE_CONFIG_DIR/.claude.json` when `CLAUDE_CONFIG_DIR` is non-empty, otherwise `~/.claude.json`; it inspects only `mcpServers.githits`, skips an already-absent removal, and rereads the structure after mutation. Malformed or unreadable Claude state blocks MCP mutation. Config-file uninstall removes `GitHits`/case-variant server entries while preserving other MCP servers and user settings; MCP host-config cleanup preserves the host config files and directories. Project uninstall uses the verified project config paths above, removes only GitHits entries from project-local files, preserves unrelated servers/settings, and best-effort removes legacy `.githits/init/project-setup.json` markers from earlier versions. Project uninstall does not remove global tools or Pi's global `pi-mcp-adapter` package because they can be shared by other projects; legacy, pre-existing, and GitHits-installed global Pi adapters are left installed. User-level Pi uninstall removes the `GitHits` entry from Pi's MCP config and runs `pi remove npm:pi-mcp-adapter` when the Pi CLI is available; if Pi is no longer installed, stale Pi MCP config entries are still removable without attempting adapter cleanup. Project uninstall is best-effort across supported config paths: missing files and files without GitHits are skipped, malformed/read-only/write-failing files are reported in the final summary, and the command exits 1 only after all possible removals have been attempted. JSONC-style files are accepted on read and rewritten as canonical JSON only when changed, matching setup behavior. Codex project setup rewrites `.codex/config.toml` as TOML and does not preserve existing TOML comments or formatting. -Unless `--keep-guidance` is passed, global guidance cleanup attempts every verified skill and managed-block target independently. Already-absent targets are quiet, supported skill-directory symlinks remain in place after `SKILL.md` removal, and hard failures keep their target path while later targets continue. Guidance is rendered separately from MCP agents: changed or failed paths are shown, all-absent guidance collapses to one unchanged row, guidance affects the headline and exit status, and agent counts exclude it. +Guidance cleanup attempts each in-scope skill and managed-block target independently. Already-absent targets are quiet, supported skill-directory symlinks remain in place after `SKILL.md` removal, and hard failures keep their target path while later targets continue. Guidance is rendered separately from MCP agents: changed or failed paths are shown, protected targets collapse to one unchanged row stating how many targets tools may still use, all-absent guidance collapses to one unchanged row, guidance affects the headline and exit status, and agent counts exclude it. For automation, `githits uninstall --yes` is user-level only and never touches project files. Use `githits uninstall --project --yes` for non-interactive project-level removal. The nested `githits init uninstall` form remains supported for compatibility. diff --git a/docs/implementation/init-guidance-and-expanded-agent-support.md b/docs/implementation/init-guidance-and-expanded-agent-support.md index 3989f3be..ee57d002 100644 --- a/docs/implementation/init-guidance-and-expanded-agent-support.md +++ b/docs/implementation/init-guidance-and-expanded-agent-support.md @@ -106,12 +106,17 @@ After successful cleanup, another guided run is a no-op for that migration. The canonical command is `githits uninstall`; `githits init uninstall` remains a compatibility alias with identical `--yes`, `--project`, and -`--keep-guidance` behavior. Without `--keep-guidance`, uninstall independently -and best-effort removes all four active skill files and the exact historical -Cline/Junie files for the chosen scope, while preserving unrelated files and -directories. Removing a shared root can affect every compatible agent that -reads that directory; the result reports the shared guidance target. The -`--keep-guidance` option preserves active and historical guidance. +`--keep-guidance` behavior. Without `--keep-guidance`, interactive user +uninstall best-effort removes active and historical guidance only for selected +tools. It retains a shared skill or managed-block target when any unselected +detected tool could use it, and retains a selected tool's guidance when its MCP +removal fails. Non-interactive `--yes`, project uninstall, and user uninstall +with no configured MCP targets clean every verified guidance target in the +chosen scope. Cleanup removes all four active skill files and the exact +historical Cline/Junie files while preserving unrelated files and directories. +The `--keep-guidance` option preserves active and historical guidance. +When cleanup removes a shared root, the result warns that every compatible +agent reading that root is affected. Human output lists created, updated, unchanged, removed, and failed skill files accurately. Uninstall failure reasons are sanitized while failed target paths diff --git a/docs/implementation/init-setup-output.md b/docs/implementation/init-setup-output.md index 4c9c7565..bfac8b65 100644 --- a/docs/implementation/init-setup-output.md +++ b/docs/implementation/init-setup-output.md @@ -64,13 +64,20 @@ preselected, the user deselects tools to retain, and the selection is the consent. `--yes` removes all configured user-level MCP targets. Project uninstall deduplicates project config paths and uses one confirmation. -Without `--keep-guidance`, uninstall independently and best-effort removes all -four active skill files at verified roots plus only the historical CLI-owned -files `/.cline/skills/githits-mcp/SKILL.md` and -`/.junie/skills/githits-mcp/SKILL.md`. It preserves unrelated skills, -directories, plugin payloads, and credentials. Removing a shared root can -affect every compatible agent that reads that directory. `--keep-guidance` -preserves both active and historical guidance. +Without `--keep-guidance`, interactive user uninstall best-effort removes the +active and historical guidance targets owned by the selected tools. Guidance +usable by any unselected detected tool is retained, including a shared skill or +managed-block target also used by a selected tool. A selected tool also retains +its guidance when its MCP removal fails. Non-interactive `--yes`, +project uninstall, and user uninstall with no configured MCP targets continue +to clean every verified guidance target in their chosen scope. Cleanup removes +all four active skill files plus only the historical CLI-owned files +`/.cline/skills/githits-mcp/SKILL.md` and +`/.junie/skills/githits-mcp/SKILL.md`; it preserves unrelated skills, +directories, plugin payloads, and credentials. `--keep-guidance` preserves both +active and historical guidance. When cleanup removes a shared skill +root, the output warns that every compatible agent reading that root is +affected. Missing files are successful no-ops. A historical Cline or Junie file is removed only after a complete active shared skill set has been written and diff --git a/src/commands/init/init.test.ts b/src/commands/init/init.test.ts index a3627b55..42cf09cd 100644 --- a/src/commands/init/init.test.ts +++ b/src/commands/init/init.test.ts @@ -196,6 +196,20 @@ function createFsWithDetection( }); } +function createAgentSelectionPromptService(agentId: string): PromptService { + return createMockPromptService({ + checkbox: mock((_message: string, choices: { value: T }[]) => + Promise.resolve( + choices + .map((choice) => choice.value) + .filter( + (value): value is T => (value as { id?: string }).id === agentId, + ), + ), + ) as PromptService["checkbox"], + }); +} + /** Helper to extract log output as string array */ function getLogOutput(): string[] { return (logSpy.mock.calls as unknown[][]).map((c) => String(c[0] ?? "")); @@ -7396,15 +7410,7 @@ describe("initUninstallAction", () => { }, ); // Keep Windsurf by leaving only Cursor checked. - const promptService = createMockPromptService({ - checkbox: mock((_m: string, choices: { value: T }[]) => - Promise.resolve( - choices - .map((c) => c.value) - .filter((v): v is T => (v as { id?: string }).id === "cursor"), - ), - ) as PromptService["checkbox"], - }); + const promptService = createAgentSelectionPromptService("cursor"); await initUninstallAction( {}, @@ -7427,6 +7433,277 @@ describe("initUninstallAction", () => { ).toBe(true); }); + it("does not remove unrelated guidance when only Claude Desktop is selected", async () => { + Object.defineProperty(process, "platform", { + configurable: true, + value: "darwin", + }); + const desktopConfigPath = + "/home/test/Library/Application Support/Claude/claude_desktop_config.json"; + const guidanceFiles = { + ...readCanonicalSkillFiles("/home/test/.claude/skills"), + ...readCanonicalSkillFiles("/home/test/.agents/skills"), + ...readCanonicalSkillFiles("/home/test/.hermes/skills"), + }; + const configFiles: Record = { + [desktopConfigPath]: JSON.stringify({ + mcpServers: { + GitHits: { + command: "npx", + args: ["-y", "githits@latest", "mcp", "start"], + }, + }, + }), + ...guidanceFiles, + }; + const fs = createFsWithDetection( + ["/home/test/Library/Application Support/Claude"], + configFiles, + ); + const deleteCalls: string[] = []; + fs.deleteFile = mock(async (path: string) => { + deleteCalls.push(path); + delete configFiles[path]; + }) as typeof fs.deleteFile; + const promptService = createAgentSelectionPromptService("claude-desktop"); + + await initUninstallAction( + {}, + { + fileSystemService: fs, + promptService, + execService: createMockExecService(), + }, + ); + + expect(JSON.parse(configFiles[desktopConfigPath]!).mcpServers.GitHits).toBe( + undefined, + ); + expect(deleteCalls).toEqual([]); + for (const [path, content] of Object.entries(guidanceFiles)) { + expect(configFiles[path]).toBe(content); + } + expect( + getLogOutput().some((line) => line.includes("GitHits guidance")), + ).toBe(false); + }); + + it("preserves shared guidance usable by an unselected detected tool", async () => { + const githitsConfig = JSON.stringify({ + mcpServers: { + GitHits: { + command: "npx", + args: ["-y", "githits@latest", "mcp", "start"], + }, + }, + }); + const guidanceFiles = readCanonicalSkillFiles("/home/test/.agents/skills"); + const configFiles: Record = { + "/home/test/.cursor/mcp.json": githitsConfig, + ...guidanceFiles, + }; + const fs = createFsWithDetection( + ["/home/test/.cursor", "/home/test/.codeium/windsurf"], + configFiles, + ); + const deleteCalls: string[] = []; + fs.deleteFile = mock(async (path: string) => { + deleteCalls.push(path); + delete configFiles[path]; + }) as typeof fs.deleteFile; + const promptService = createAgentSelectionPromptService("cursor"); + + await initUninstallAction( + {}, + { + fileSystemService: fs, + promptService, + execService: createMockExecService(), + }, + ); + + expect( + JSON.parse(configFiles["/home/test/.cursor/mcp.json"]!).mcpServers, + ).toEqual({}); + expect( + configFiles["/home/test/.codeium/windsurf/mcp_config.json"], + ).toBeUndefined(); + expect(deleteCalls).toEqual([]); + for (const [path, content] of Object.entries(guidanceFiles)) { + expect(configFiles[path]).toBe(content); + } + expect(normalizeHumanOutput()).toContain( + "GitHits guidance unchanged 4 guidance targets skipped because tools may still use them", + ); + }); + + it("preserves shared guidance usable by an unselected configured tool", async () => { + const githitsConfig = JSON.stringify({ + mcpServers: { + GitHits: { + command: "npx", + args: ["-y", "githits@latest", "mcp", "start"], + }, + }, + }); + const guidanceFiles = readCanonicalSkillFiles("/home/test/.agents/skills"); + const configFiles: Record = { + "/home/test/.cursor/mcp.json": githitsConfig, + "/home/test/.codeium/windsurf/mcp_config.json": githitsConfig, + ...guidanceFiles, + }; + const fs = createFsWithDetection( + ["/home/test/.cursor", "/home/test/.codeium/windsurf"], + configFiles, + ); + const deleteCalls: string[] = []; + fs.deleteFile = mock(async (path: string) => { + deleteCalls.push(path); + delete configFiles[path]; + }) as typeof fs.deleteFile; + + await initUninstallAction( + {}, + { + fileSystemService: fs, + promptService: createAgentSelectionPromptService("cursor"), + execService: createMockExecService(), + }, + ); + + expect( + JSON.parse(configFiles["/home/test/.cursor/mcp.json"]!).mcpServers, + ).toEqual({}); + expect( + JSON.parse(configFiles["/home/test/.codeium/windsurf/mcp_config.json"]!) + .mcpServers.GitHits, + ).toBeDefined(); + expect(deleteCalls).toEqual([]); + for (const [path, content] of Object.entries(guidanceFiles)) { + expect(configFiles[path]).toBe(content); + } + expect(normalizeHumanOutput()).toContain( + "GitHits guidance unchanged 4 guidance targets skipped because tools may still use them", + ); + }); + + it("cleans all guidance with --yes when configured agents exist", async () => { + const cursorConfigPath = "/home/test/.cursor/mcp.json"; + const guidanceFiles = readCanonicalSkillFiles("/home/test/.claude/skills"); + const configFiles: Record = { + [cursorConfigPath]: JSON.stringify({ + mcpServers: { + GitHits: { + url: "https://mcp.githits.com", + }, + }, + }), + ...guidanceFiles, + }; + const fs = createFsWithDetection(["/home/test/.cursor"], configFiles); + const deleteCalls: string[] = []; + fs.deleteFile = mock(async (path: string) => { + deleteCalls.push(path); + delete configFiles[path]; + }) as typeof fs.deleteFile; + + await initUninstallAction( + { yes: true }, + { + fileSystemService: fs, + promptService: createMockPromptService(), + execService: createMockExecService(), + }, + ); + + expect(JSON.parse(configFiles[cursorConfigPath]!).mcpServers).toEqual({}); + expect(deleteCalls).toEqual(Object.keys(guidanceFiles)); + for (const path of Object.keys(guidanceFiles)) { + expect(configFiles[path]).toBeUndefined(); + } + }); + + it("removes guidance owned by the selected tool", async () => { + const kiroConfigPath = "/home/test/.kiro/settings/mcp.json"; + const guidanceFiles = readCanonicalSkillFiles("/home/test/.kiro/skills"); + const configFiles: Record = { + [kiroConfigPath]: JSON.stringify({ + mcpServers: { + GitHits: { + command: "npx", + args: ["-y", "githits@latest", "mcp", "start"], + }, + }, + }), + ...guidanceFiles, + }; + const fs = createFsWithDetection(["/home/test/.kiro"], configFiles); + const deleteCalls: string[] = []; + fs.deleteFile = mock(async (path: string) => { + deleteCalls.push(path); + delete configFiles[path]; + }) as typeof fs.deleteFile; + const promptService = createAgentSelectionPromptService("kiro"); + + await initUninstallAction( + {}, + { + fileSystemService: fs, + promptService, + execService: createMockExecService(), + }, + ); + + expect(JSON.parse(configFiles[kiroConfigPath]!).mcpServers).toEqual({}); + expect(deleteCalls).toEqual(Object.keys(guidanceFiles)); + for (const path of Object.keys(guidanceFiles)) { + expect(configFiles[path]).toBeUndefined(); + } + }); + + it("preserves selected tool guidance when its MCP uninstall fails", async () => { + const kiroConfigPath = "/home/test/.kiro/settings/mcp.json"; + const guidanceFiles = readCanonicalSkillFiles("/home/test/.kiro/skills"); + const configFiles: Record = { + [kiroConfigPath]: JSON.stringify({ + mcpServers: { + GitHits: { + command: "npx", + args: ["-y", "githits@latest", "mcp", "start"], + }, + }, + }), + ...guidanceFiles, + }; + const fs = createFsWithDetection(["/home/test/.kiro"], configFiles); + fs.atomicWriteFile = mock(async () => { + throw Object.assign(new Error("read only"), { code: "EACCES" }); + }) as typeof fs.atomicWriteFile; + const deleteCalls: string[] = []; + fs.deleteFile = mock(async (path: string) => { + deleteCalls.push(path); + delete configFiles[path]; + }) as typeof fs.deleteFile; + + await initUninstallAction( + {}, + { + fileSystemService: fs, + promptService: createAgentSelectionPromptService("kiro"), + execService: createMockExecService(), + }, + ); + + expect( + JSON.parse(configFiles[kiroConfigPath]!).mcpServers.GitHits, + ).toBeDefined(); + expect(deleteCalls).toEqual([]); + for (const [path, content] of Object.entries(guidanceFiles)) { + expect(configFiles[path]).toBe(content); + } + expect(process.exitCode).toBe(1); + }); + it("removes configured CLI agents with verified commands", async () => { const lookupCmd = lookupCommandFor(); const fs = createFsWithDetection([]); diff --git a/src/commands/init/init.ts b/src/commands/init/init.ts index 132c892e..bc6a58df 100644 --- a/src/commands/init/init.ts +++ b/src/commands/init/init.ts @@ -188,6 +188,8 @@ interface GuidanceUninstallOutcome { message?: string; changes?: UninstallChange[]; failures?: GuidanceUninstallFailure[]; + /** Selected target paths skipped because tools may still need them. */ + protectedCount?: number; } interface GuidanceUninstallFailure { @@ -3819,9 +3821,33 @@ async function runUserMcpUninstall( labelWidth, )), ]; + // Interactive checkbox consent scopes guidance cleanup too. Scope-wide + // modes still clean orphaned guidance, while retained/failed agents protect + // any guidance path they share with a selected agent. + const cleanupAllGuidance = + Boolean(options.yes) || scan.configured.length === 0; + const selectedIds = new Set(toRemove.map((agent) => agent.id)); + const protectedGuidanceIds = new Set([ + ...scan.configured + .filter((agent) => !selectedIds.has(agent.id)) + .map((agent) => agent.id), + ...scan.notConfigured.map((agent) => agent.id), + ...outcomes + .filter((outcome) => outcome.status === "failed") + .map((outcome) => outcome.id), + ]); const guidanceOutcome = options.keepGuidance ? null - : await uninstallGuidance(agentDefinitions, fileSystemService, "user"); + : await uninstallGuidance( + cleanupAllGuidance ? agentDefinitions : toRemove, + fileSystemService, + "user", + cleanupAllGuidance + ? [] + : agentDefinitions.filter((agent) => + protectedGuidanceIds.has(agent.id), + ), + ); if (guidanceOutcome) { printGuidanceUninstallOutcome( guidanceOutcome, @@ -4408,7 +4434,7 @@ function printGuidanceUninstallOutcome( fileSystemService: FileSystemService, useColors: boolean, ): void { - if (outcome.status === "skipped") return; + if (outcome.status === "skipped" && !outcome.protectedCount) return; const rows: ChangeRow[] = []; const changes = outcome.changes ?? []; const hasRemovedChange = changes.some( @@ -4431,6 +4457,14 @@ function printGuidanceUninstallOutcome( detail: `${formatConfigPath(failure.path, fileSystemService)}: ${failure.reason}`, }); } + if (outcome.protectedCount) { + rows.push({ + tone: "ok", + label: "GitHits guidance", + verb: "unchanged", + detail: `${outcome.protectedCount} guidance target${outcome.protectedCount === 1 ? "" : "s"} skipped because tools may still use them`, + }); + } if (rows.length === 0) { rows.push({ tone: outcome.status === "failed" ? "error" : "ok", @@ -4536,16 +4570,38 @@ async function removeHistoricalGuidanceSkills( return { changes, failures }; } +/** + * Remove guidance for the requested agents while retaining any target path + * also owned by a protected agent. Protection is path-based because multiple + * agents can share one skill root or managed instruction file. + */ async function uninstallGuidance( agents: AgentDefinition[], fileSystemService: FileSystemService, scope: InitSetupScope, + protectedAgents: AgentDefinition[] = [], ): Promise { - const steps = getGuidanceUninstallSteps(agents, fileSystemService, scope); + const protectedPaths = new Set( + getGuidanceUninstallSteps(protectedAgents, fileSystemService, scope).map( + (step) => step.targetPath, + ), + ); + const selectedSteps = getGuidanceUninstallSteps( + agents, + fileSystemService, + scope, + ); + const protectedCount = selectedSteps.filter((step) => + protectedPaths.has(step.targetPath), + ).length; + const steps = selectedSteps.filter( + (step) => !protectedPaths.has(step.targetPath), + ); if (steps.length === 0) { return { status: "skipped", message: "no guidance targets", + ...(protectedCount > 0 ? { protectedCount } : {}), }; } @@ -4581,6 +4637,7 @@ async function uninstallGuidance( message: "One or more GitHits guidance targets failed to uninstall.", changes, failures, + ...(protectedCount > 0 ? { protectedCount } : {}), }; } @@ -4589,12 +4646,14 @@ async function uninstallGuidance( status: "removed", message: "Guidance removed successfully", changes, + ...(protectedCount > 0 ? { protectedCount } : {}), }; } return { status: anyNotConfigured ? "not_configured" : "skipped", message: "GitHits guidance not configured", changes, + ...(protectedCount > 0 ? { protectedCount } : {}), }; } @@ -5293,9 +5352,11 @@ const INIT_UNINSTALL_DESCRIPTION = `Remove GitHits MCP server configuration from In interactive mode, asks whether to remove user-level coding-agent config or project-level MCP config. Removes only GitHits MCP/plugin entries with your -confirmation. By default it also removes GitHits-owned guidance files; pass -\`--keep-guidance\` to leave them in place. Authentication tokens are not -removed; use \`githits logout\` to remove stored credentials.`; +confirmation. Interactive user guidance cleanup follows the tool selection and +retains shared guidance usable by detected tools you keep; \`--yes\` and project +cleanup are scope-wide. Pass \`--keep-guidance\` to leave all guidance in place. +Authentication tokens are not removed; use \`githits logout\` to remove stored +credentials.`; function registerUninstallCommand(parent: Command): Command { return parent