Skip to content

feat(cli): add gitnexus uninstall to reverse setup (#2060)#2062

Merged
magyargergo merged 15 commits into
abhigyanpatwari:mainfrom
NilotpalK:fix/issue-2060-uninstall-command
Jun 9, 2026
Merged

feat(cli): add gitnexus uninstall to reverse setup (#2060)#2062
magyargergo merged 15 commits into
abhigyanpatwari:mainfrom
NilotpalK:fix/issue-2060-uninstall-command

Conversation

@NilotpalK

@NilotpalK NilotpalK commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

gitnexus uninstall was documented in #168 but never implemented, so the CLI rejected it with "error: unknown command 'uninstall'" (#2060).

Add an uninstall command that reverses gitnexus setup target-by-target: removes the GitNexus MCP server entries (Cursor, Claude Code, Antigravity, OpenCode, Codex), the installed skill directories, and the Claude Code / Antigravity hook entries plus their bundled hook scripts. Edits are surgical and idempotent — only gitnexus-owned keys/entries/dirs are touched, and JSONC comments/indentation are preserved. Defaults to a dry-run preview; --force applies. Per-repo indexes and the global npm package are left alone with printed hints, since both are destructive in ways setup never caused.

Adds i18n entries (en + zh-CN), help wiring, README/CHANGELOG docs, and unit tests covering MCP/hook/skill/Codex-TOML removal, dry-run, corrupt-file safety, and the no-op case.

Testing & verification

  • cd gitnexus && npm test
  • cd gitnexus && npm run test:integration (if core/indexing/MCP paths changed)
  • cd gitnexus && npx tsc --noEmit
  • cd gitnexus-web && npm test (if web changed)
  • cd gitnexus-web && npx tsc -b --noEmit (if web changed)
  • Manual / Playwright E2E (note environment — see gitnexus-web/e2e/)

…#2060)

`gitnexus uninstall` was documented in abhigyanpatwari#168 but never implemented, so the
CLI rejected it with "error: unknown command 'uninstall'" (abhigyanpatwari#2060).

Add an `uninstall` command that reverses `gitnexus setup` target-by-target:
removes the GitNexus MCP server entries (Cursor, Claude Code, Antigravity,
OpenCode, Codex), the installed skill directories, and the Claude Code /
Antigravity hook entries plus their bundled hook scripts. Edits are surgical
and idempotent — only gitnexus-owned keys/entries/dirs are touched, and JSONC
comments/indentation are preserved. Defaults to a dry-run preview; `--force`
applies. Per-repo indexes and the global npm package are left alone with
printed hints, since both are destructive in ways setup never caused.

Adds i18n entries (en + zh-CN), help wiring, README/CHANGELOG docs, and unit
tests covering MCP/hook/skill/Codex-TOML removal, dry-run, corrupt-file
safety, and the no-op case.
@NilotpalK NilotpalK requested a review from magyargergo as a code owner June 6, 2026 11:32
@vercel

vercel Bot commented Jun 6, 2026

Copy link
Copy Markdown

@NilotpalK is attempting to deploy a commit to the NexusCore Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

CI Report

All checks passed

Pipeline Status

Stage Status Details
✅ Typecheck success tsc --noEmit
✅ Tests success unit tests, 3 platforms
✅ E2E success gitnexus-web changes only

Test Results

Tests Passed Failed Skipped Duration
10809 10793 0 16 400s

✅ All 10793 tests passed

16 test(s) skipped — expand for details
  • COBOL pipeline benchmark > scales with file count
  • C++ ADL emit benchmark > emit phase scales sub-quadratically with co-scaled files and sites
  • C++ pipeline benchmark > scales with file count
  • C# pipeline benchmark > scales with file count — namespaces spread across the solution
  • C# pipeline benchmark > scales with file count — all types in one (global) namespace bucket
  • C# pipeline benchmark > scales with file count — all types in one (named) namespace bucket
  • Go pipeline benchmark > scales with file count (workers enabled)
  • Go pipeline benchmark — worker pool (issue Worker idle timeout kills long Go scope extraction and surfaces as Napi::Error during analyze #1848) > does not quarantine the large generated Go file on sub-batch idle timeout
  • Go structural interface detection benchmark > scales linearly with interface × struct count
  • Go structural interface detection split-phase benchmark > separates index-build and detection time
  • PHP pipeline benchmark > scales with file count (workers enabled)
  • Ruby pipeline benchmark > scales with file count (workers enabled)
  • Rust pipeline benchmark > scales with file count (workers enabled)
  • Vue pipeline benchmark > scales with component count
  • run.cjs direct-exec entrypoint (fix(cli): steer docs, skills, and hooks through a CLI-neutral project-local runner (#1939) #1945) > resolves a .cmd shim via the Windows shell branch, passing args and exit code
  • buildTypeEnv > known limitations (documented skip tests) > Ruby block parameter: users.each { |user| } — closure param inference, different feature

Code Coverage

Tests

Metric Coverage Covered Base Delta Status
Statements 75.07% 35444/47214 N/A% 🟢 ███████████████░░░░░
Branches 62.81% 21902/34867 N/A% 🟢 ████████████░░░░░░░░
Functions 80.81% 3829/4738 N/A% 🟢 ████████████████░░░░
Lines 78.87% 32048/40632 N/A% 🟢 ███████████████░░░░░

📋 View full run · Generated by CI

@magyargergo

Copy link
Copy Markdown
Collaborator

Please revert the changes from the CHANGELOG.md files.

@magyargergo magyargergo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tri-review: PR #2062gitnexus uninstall

Reviewed three ways: 8 Claude reviewer lanes (gitnexus risk / test-CI / security-boundary; CE correctness / adversarial / reliability / maintainability / testing) + Codex (live, independent engine). 8 of 9 lanes are Claude under different persona prompts (correlated priors); Codex is the only independent engine, so Codex+Claude agreement is weighted strong and Claude-only agreement as "consistent across personas."

Credit — the core is sound

  • Reversal is correct. The setup→uninstall target map was checked target-by-target (correctness lane, independently confirmed by Codex): every MCP path & keyPath (['mcpServers','gitnexus'], OpenCode's distinct ['mcp','gitnexus'], Codex's [mcp_servers.gitnexus]), both hook needles/events, both hook-script dirs, and all 5 skill dirs match setup.ts exactly. No missed target.
  • Dry-run is fully side-effect-free across every branch (adversarial verified). Symlinks are safefs.rm removes the link, not its target. Command-injection clean — all execFile('codex',…) args are constant. No bidi/whitespace/secret hygiene issues.

Inline findings

  1. [P2 — latent, P1 blast radius] Empty skill name wipes the entire editor skills dir. path.basename('.md','.md') === ''path.join(targetDir,'') === targetDirfs.rm(targetDir,{recursive,force}) deletes the WHOLE skills dir (×5 editors), taking user skills with it. Unguarded. LATENT today (the shipped gitnexus/skills/ has no bare .md); reachable via a future stray file or the GITNEXUS_TEST_SKILLS_ROOT override. One-line fix: skip empty derived names. Lanes: adversarial (proven e2e) + security (P1) + my own repro. [reproduced]
  2. [P2] Corrupt settings.json → dangling hook. uninstall.ts:371 (& :388): removeDir(hookScriptDir) runs unconditionally, even when removeHookEntries returned 'corrupt'. The hook ENTRY is correctly left in place, but its script dir is deleted → the editor then runs a registered hook pointing at a missing script (ENOENT) on every matched tool call. Fix: gate the removeDir on status !== 'corrupt'. Lanes: reliability (conf 95) + my code-read. [code-read]
  3. [P2 — cross-engine] Whole-entry hook deletion destroys a co-located user hook. uninstall.ts:152 removes the entire matcher entry when ANY command in its hooks[] matches the needle, whereas setup INSERTS at element granularity. A user who adds their own command into gitnexus's entry's hooks[] loses it silently — contradicting this file's own docstring ("other hooks … preserved", :8–11). Fix: filter only the gitnexus command from entry.hooks; delete the entry only if hooks[] becomes empty. Lanes: Codex + adversarial (proven e2e) + correctness — the one cross-engine-corroborated inline finding. [reproduced]

Body findings

  1. [P2 — cross-engine] Deletion is provenance-blind. Nothing distinguishes a gitnexus-installed artifact from a user-owned one with the same name. Codex (independent) flagged this as its top issue: a user dir/skill named like a bundled skill, user files inside a gitnexus skill dir, or a user's own MCP server literally named gitnexus are all deleted purely by name match (Codex rated the skill-dir case P0; realistic trigger is narrower → P2). Adversarial + security noted the same absence of an ownership check. Suggested direction: have setup drop an ownership marker (e.g. .gitnexus-managed) that uninstall verifies before deleting. This and #1 share the fix surface (removeDir).
  2. [P2/P3 — cross-engine, fallback-path only] Hand-rolled TOML stripper is fragile (runs only when the codex binary is absent). Codex + adversarial + correctness agree: (a) a [mcp_servers.gitnexus.env] child sub-table survives the strip as leftover dead/stale config when the parent [mcp_servers.gitnexus] is removed (still valid TOML, but a stale reference to a now-unregistered server); (b) the whole-file blank-line reflow (\n{3,}\n\n, leading-newline strip) touches unrelated formatting, contradicting the "preserve formatting" claim; adversarial additionally showed a [mcp_servers.gitnexus] literal inside a multiline string truncates user data. Fix: a real TOML parser, or scope the section boundary to non-descendant headers + track multiline-string state.
  3. [P2 — maintainability] setup.ts ↔ uninstall.ts duplicate the target map with no shared source of truth — ~14 file paths, 2 keyPaths, 3 event names, 2 needles, and detectIndentation (copy-pasted). The next editor/path change to setup will silently stop being reversed, with no compile/test signal. Recommend extracting a shared editor-targets.ts both import. Not a bug today; a real drift hazard for a command whose whole job is to mirror setup.
  4. [P2 — testing] Coverage is Claude-path-only. Untested: OpenCode MCP (the distinct ['mcp','gitnexus'] keyPath), Antigravity MCP, Antigravity hooks (AfterTool / gitnexus-antigravity-hook), the codex mcp remove SUCCESS path (the mock forces failure), 4 of 5 skill targets, the directory-layout skill branch, multi-match-in-one-event hook removal, and dry-run for hooks/skills. Only Claude skills prove a user skill survives — the data-loss preservation guarantee is untested for the other 4 targets. For a destructive command, strongly worth filling before merge. (uninstall.test.ts runs only in the ubuntu/coverage job, not the cross-platform subset — asymmetric with setup's tests.)
  5. [P3 — minor / pre-existing] Partial uninstall exits 0 on accumulated errors (Codex + reliability; mirrors setup — recommend process.exitCode=1); no timeout on codex mcp remove (can hang; mirrors setup); an empty mcpServers/mcp container is left behind after leaf removal (cosmetic, not exact reversal); GITNEXUS_TEST_SKILLS_ROOT honored in production code (pre-existing from setup.ts:836 — track as a follow-up, not this PR).

Refuted / reconciled (validation is a feature)

  • "Trailing-comma / BOM judged corrupt → silent incomplete uninstall" (adversarial) — refuted as a setup-divergence by both correctness and Codex: parseTree/parse share the same default ParseOptions, and setup gates on the same parse, so any file setup wrote is parse-clean for uninstall. It survives only as a robustness edge if a user later hand-edits the file — and in that case it amplifies #2. Not posted as a standalone bug.
  • Needle cross-match (gitnexus-hook vs gitnexus-antigravity-hook) — checked, they don't collide (different substrings, different files).

Recommendation

Not production-ready as-is — the core reversal works, but #1#3 are data-loss / correctness landmines on a destructive command and should be fixed first (all have small, local fixes). #5#7 are worth folding in while here. None are deep architectural problems. Merge state: mergeable, CI checks pending at review time (so not yet mergeable on checks).

CI

lint / format / typecheck / typecheck-web pass; full test + platform jobs were pending at review time; the Vercel "fail" is an auth-required deploy gate, unrelated to the code.

Coverage

Full diff reviewed (656 lines / 8 files; all source read). The happy-path reversal is correct; the findings are edge-case data-loss landmines, robustness gaps, and thin tests on a destructive command — none block the common path, but #1#3 are worth addressing before this ships.

Automated multi-tool digest (8 Claude lanes + Codex) — verify before acting.

Comment thread gitnexus/src/cli/uninstall.ts Outdated
Comment thread gitnexus/src/cli/uninstall.ts Outdated
Comment thread gitnexus/src/cli/uninstall.ts Outdated
@18502009725

Copy link
Copy Markdown

is support uninstall now?

magyargergo and others added 4 commits June 8, 2026 08:51
…yanpatwari#2062)

Address review findings on the uninstall command:

- Empty derived skill name no longer wipes the whole skills dir: a bare
  '.md' source file would make basename() return '', resolving to the
  skills dir itself. Skip empty names in derivation and reject
  empty/'.'/'..'/separator names in removeSkillsFrom.
- Corrupt settings.json no longer orphans the hook: gate the hook-script
  dir removal on status !== 'corrupt' so we don't delete a script while a
  still-registered entry points at it (Claude + Antigravity blocks).
- Hook removal is now element-granular: delete only the gitnexus command
  inside an entry's hooks[], removing the whole entry only when it becomes
  empty. Preserves a user command co-located in the same entry.
- Fallback TOML stripper: also remove descendant sub-tables
  ([mcp_servers.gitnexus.env]), track multiline strings so a bracketed
  line inside a value isn't treated as a header, and stop reflowing
  unrelated blank lines.
- Set process.exitCode=1 on partial failure; add a 10s timeout to
  'codex mcp remove'.

Tests expanded 7 -> 17: empty-skill guard, corrupt-settings hook
preservation, shared-entry hook removal, OpenCode MCP keyPath,
Antigravity MCP + AfterTool hooks, codex-remove success path, TOML
sub-table + multiline-string cases, dry-run for hooks/skills, and the
directory-layout skill branch.
@NilotpalK

NilotpalK commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Tri-review: PR #2062gitnexus uninstall

Reviewed three ways: 8 Claude reviewer lanes (gitnexus risk / test-CI / security-boundary; CE correctness / adversarial / reliability / maintainability / testing) + Codex (live, independent engine). 8 of 9 lanes are Claude under different persona prompts (correlated priors); Codex is the only independent engine, so Codex+Claude agreement is weighted strong and Claude-only agreement as "consistent across personas."

Credit — the core is sound

  • Reversal is correct. The setup→uninstall target map was checked target-by-target (correctness lane, independently confirmed by Codex): every MCP path & keyPath (['mcpServers','gitnexus'], OpenCode's distinct ['mcp','gitnexus'], Codex's [mcp_servers.gitnexus]), both hook needles/events, both hook-script dirs, and all 5 skill dirs match setup.ts exactly. No missed target.
  • Dry-run is fully side-effect-free across every branch (adversarial verified). Symlinks are safefs.rm removes the link, not its target. Command-injection clean — all execFile('codex',…) args are constant. No bidi/whitespace/secret hygiene issues.

Inline findings

  1. [P2 — latent, P1 blast radius] Empty skill name wipes the entire editor skills dir. path.basename('.md','.md') === ''path.join(targetDir,'') === targetDirfs.rm(targetDir,{recursive,force}) deletes the WHOLE skills dir (×5 editors), taking user skills with it. Unguarded. LATENT today (the shipped gitnexus/skills/ has no bare .md); reachable via a future stray file or the GITNEXUS_TEST_SKILLS_ROOT override. One-line fix: skip empty derived names. Lanes: adversarial (proven e2e) + security (P1) + my own repro. [reproduced]
  2. [P2] Corrupt settings.json → dangling hook. uninstall.ts:371 (& :388): removeDir(hookScriptDir) runs unconditionally, even when removeHookEntries returned 'corrupt'. The hook ENTRY is correctly left in place, but its script dir is deleted → the editor then runs a registered hook pointing at a missing script (ENOENT) on every matched tool call. Fix: gate the removeDir on status !== 'corrupt'. Lanes: reliability (conf 95) + my code-read. [code-read]
  3. [P2 — cross-engine] Whole-entry hook deletion destroys a co-located user hook. uninstall.ts:152 removes the entire matcher entry when ANY command in its hooks[] matches the needle, whereas setup INSERTS at element granularity. A user who adds their own command into gitnexus's entry's hooks[] loses it silently — contradicting this file's own docstring ("other hooks … preserved", :8–11). Fix: filter only the gitnexus command from entry.hooks; delete the entry only if hooks[] becomes empty. Lanes: Codex + adversarial (proven e2e) + correctness — the one cross-engine-corroborated inline finding. [reproduced]

Body findings

  1. [P2 — cross-engine] Deletion is provenance-blind. Nothing distinguishes a gitnexus-installed artifact from a user-owned one with the same name. Codex (independent) flagged this as its top issue: a user dir/skill named like a bundled skill, user files inside a gitnexus skill dir, or a user's own MCP server literally named gitnexus are all deleted purely by name match (Codex rated the skill-dir case P0; realistic trigger is narrower → P2). Adversarial + security noted the same absence of an ownership check. Suggested direction: have setup drop an ownership marker (e.g. .gitnexus-managed) that uninstall verifies before deleting. This and Add support for Ollama as a local inference backend #1 share the fix surface (removeDir).
  2. [P2/P3 — cross-engine, fallback-path only] Hand-rolled TOML stripper is fragile (runs only when the codex binary is absent). Codex + adversarial + correctness agree: (a) a [mcp_servers.gitnexus.env] child sub-table survives the strip as leftover dead/stale config when the parent [mcp_servers.gitnexus] is removed (still valid TOML, but a stale reference to a now-unregistered server); (b) the whole-file blank-line reflow (\n{3,}\n\n, leading-newline strip) touches unrelated formatting, contradicting the "preserve formatting" claim; adversarial additionally showed a [mcp_servers.gitnexus] literal inside a multiline string truncates user data. Fix: a real TOML parser, or scope the section boundary to non-descendant headers + track multiline-string state.
  3. [P2 — maintainability] setup.ts ↔ uninstall.ts duplicate the target map with no shared source of truth — ~14 file paths, 2 keyPaths, 3 event names, 2 needles, and detectIndentation (copy-pasted). The next editor/path change to setup will silently stop being reversed, with no compile/test signal. Recommend extracting a shared editor-targets.ts both import. Not a bug today; a real drift hazard for a command whose whole job is to mirror setup.
  4. [P2 — testing] Coverage is Claude-path-only. Untested: OpenCode MCP (the distinct ['mcp','gitnexus'] keyPath), Antigravity MCP, Antigravity hooks (AfterTool / gitnexus-antigravity-hook), the codex mcp remove SUCCESS path (the mock forces failure), 4 of 5 skill targets, the directory-layout skill branch, multi-match-in-one-event hook removal, and dry-run for hooks/skills. Only Claude skills prove a user skill survives — the data-loss preservation guarantee is untested for the other 4 targets. For a destructive command, strongly worth filling before merge. (uninstall.test.ts runs only in the ubuntu/coverage job, not the cross-platform subset — asymmetric with setup's tests.)
  5. [P3 — minor / pre-existing] Partial uninstall exits 0 on accumulated errors (Codex + reliability; mirrors setup — recommend process.exitCode=1); no timeout on codex mcp remove (can hang; mirrors setup); an empty mcpServers/mcp container is left behind after leaf removal (cosmetic, not exact reversal); GITNEXUS_TEST_SKILLS_ROOT honored in production code (pre-existing from setup.ts:836 — track as a follow-up, not this PR).

Refuted / reconciled (validation is a feature)

  • "Trailing-comma / BOM judged corrupt → silent incomplete uninstall" (adversarial) — refuted as a setup-divergence by both correctness and Codex: parseTree/parse share the same default ParseOptions, and setup gates on the same parse, so any file setup wrote is parse-clean for uninstall. It survives only as a robustness edge if a user later hand-edits the file — and in that case it amplifies Welcome to GitNexus Discussions! #2. Not posted as a standalone bug.
  • Needle cross-match (gitnexus-hook vs gitnexus-antigravity-hook) — checked, they don't collide (different substrings, different files).

Recommendation

Not production-ready as-is — the core reversal works, but #1#3 are data-loss / correctness landmines on a destructive command and should be fixed first (all have small, local fixes). #5#7 are worth folding in while here. None are deep architectural problems. Merge state: mergeable, CI checks pending at review time (so not yet mergeable on checks).

CI

lint / format / typecheck / typecheck-web pass; full test + platform jobs were pending at review time; the Vercel "fail" is an auth-required deploy gate, unrelated to the code.

Coverage

Full diff reviewed (656 lines / 8 files; all source read). The happy-path reversal is correct; the findings are edge-case data-loss landmines, robustness gaps, and thin tests on a destructive command — none block the common path, but #1#3 are worth addressing before this ships.

Automated multi-tool digest (8 Claude lanes + Codex) — verify before acting.

Tri-review: PR #2062gitnexus uninstall

Reviewed three ways: 8 Claude reviewer lanes (gitnexus risk / test-CI / security-boundary; CE correctness / adversarial / reliability / maintainability / testing) + Codex (live, independent engine). 8 of 9 lanes are Claude under different persona prompts (correlated priors); Codex is the only independent engine, so Codex+Claude agreement is weighted strong and Claude-only agreement as "consistent across personas."

Credit — the core is sound

  • Reversal is correct. The setup→uninstall target map was checked target-by-target (correctness lane, independently confirmed by Codex): every MCP path & keyPath (['mcpServers','gitnexus'], OpenCode's distinct ['mcp','gitnexus'], Codex's [mcp_servers.gitnexus]), both hook needles/events, both hook-script dirs, and all 5 skill dirs match setup.ts exactly. No missed target.
  • Dry-run is fully side-effect-free across every branch (adversarial verified). Symlinks are safefs.rm removes the link, not its target. Command-injection clean — all execFile('codex',…) args are constant. No bidi/whitespace/secret hygiene issues.

Inline findings

  1. [P2 — latent, P1 blast radius] Empty skill name wipes the entire editor skills dir. path.basename('.md','.md') === ''path.join(targetDir,'') === targetDirfs.rm(targetDir,{recursive,force}) deletes the WHOLE skills dir (×5 editors), taking user skills with it. Unguarded. LATENT today (the shipped gitnexus/skills/ has no bare .md); reachable via a future stray file or the GITNEXUS_TEST_SKILLS_ROOT override. One-line fix: skip empty derived names. Lanes: adversarial (proven e2e) + security (P1) + my own repro. [reproduced]
  2. [P2] Corrupt settings.json → dangling hook. uninstall.ts:371 (& :388): removeDir(hookScriptDir) runs unconditionally, even when removeHookEntries returned 'corrupt'. The hook ENTRY is correctly left in place, but its script dir is deleted → the editor then runs a registered hook pointing at a missing script (ENOENT) on every matched tool call. Fix: gate the removeDir on status !== 'corrupt'. Lanes: reliability (conf 95) + my code-read. [code-read]
  3. [P2 — cross-engine] Whole-entry hook deletion destroys a co-located user hook. uninstall.ts:152 removes the entire matcher entry when ANY command in its hooks[] matches the needle, whereas setup INSERTS at element granularity. A user who adds their own command into gitnexus's entry's hooks[] loses it silently — contradicting this file's own docstring ("other hooks … preserved", :8–11). Fix: filter only the gitnexus command from entry.hooks; delete the entry only if hooks[] becomes empty. Lanes: Codex + adversarial (proven e2e) + correctness — the one cross-engine-corroborated inline finding. [reproduced]

Body findings

  1. [P2 — cross-engine] Deletion is provenance-blind. Nothing distinguishes a gitnexus-installed artifact from a user-owned one with the same name. Codex (independent) flagged this as its top issue: a user dir/skill named like a bundled skill, user files inside a gitnexus skill dir, or a user's own MCP server literally named gitnexus are all deleted purely by name match (Codex rated the skill-dir case P0; realistic trigger is narrower → P2). Adversarial + security noted the same absence of an ownership check. Suggested direction: have setup drop an ownership marker (e.g. .gitnexus-managed) that uninstall verifies before deleting. This and Add support for Ollama as a local inference backend #1 share the fix surface (removeDir).
  2. [P2/P3 — cross-engine, fallback-path only] Hand-rolled TOML stripper is fragile (runs only when the codex binary is absent). Codex + adversarial + correctness agree: (a) a [mcp_servers.gitnexus.env] child sub-table survives the strip as leftover dead/stale config when the parent [mcp_servers.gitnexus] is removed (still valid TOML, but a stale reference to a now-unregistered server); (b) the whole-file blank-line reflow (\n{3,}\n\n, leading-newline strip) touches unrelated formatting, contradicting the "preserve formatting" claim; adversarial additionally showed a [mcp_servers.gitnexus] literal inside a multiline string truncates user data. Fix: a real TOML parser, or scope the section boundary to non-descendant headers + track multiline-string state.
  3. [P2 — maintainability] setup.ts ↔ uninstall.ts duplicate the target map with no shared source of truth — ~14 file paths, 2 keyPaths, 3 event names, 2 needles, and detectIndentation (copy-pasted). The next editor/path change to setup will silently stop being reversed, with no compile/test signal. Recommend extracting a shared editor-targets.ts both import. Not a bug today; a real drift hazard for a command whose whole job is to mirror setup.
  4. [P2 — testing] Coverage is Claude-path-only. Untested: OpenCode MCP (the distinct ['mcp','gitnexus'] keyPath), Antigravity MCP, Antigravity hooks (AfterTool / gitnexus-antigravity-hook), the codex mcp remove SUCCESS path (the mock forces failure), 4 of 5 skill targets, the directory-layout skill branch, multi-match-in-one-event hook removal, and dry-run for hooks/skills. Only Claude skills prove a user skill survives — the data-loss preservation guarantee is untested for the other 4 targets. For a destructive command, strongly worth filling before merge. (uninstall.test.ts runs only in the ubuntu/coverage job, not the cross-platform subset — asymmetric with setup's tests.)
  5. [P3 — minor / pre-existing] Partial uninstall exits 0 on accumulated errors (Codex + reliability; mirrors setup — recommend process.exitCode=1); no timeout on codex mcp remove (can hang; mirrors setup); an empty mcpServers/mcp container is left behind after leaf removal (cosmetic, not exact reversal); GITNEXUS_TEST_SKILLS_ROOT honored in production code (pre-existing from setup.ts:836 — track as a follow-up, not this PR).

Refuted / reconciled (validation is a feature)

  • "Trailing-comma / BOM judged corrupt → silent incomplete uninstall" (adversarial) — refuted as a setup-divergence by both correctness and Codex: parseTree/parse share the same default ParseOptions, and setup gates on the same parse, so any file setup wrote is parse-clean for uninstall. It survives only as a robustness edge if a user later hand-edits the file — and in that case it amplifies Welcome to GitNexus Discussions! #2. Not posted as a standalone bug.
  • Needle cross-match (gitnexus-hook vs gitnexus-antigravity-hook) — checked, they don't collide (different substrings, different files).

Recommendation

Not production-ready as-is — the core reversal works, but #1#3 are data-loss / correctness landmines on a destructive command and should be fixed first (all have small, local fixes). #5#7 are worth folding in while here. None are deep architectural problems. Merge state: mergeable, CI checks pending at review time (so not yet mergeable on checks).

CI

lint / format / typecheck / typecheck-web pass; full test + platform jobs were pending at review time; the Vercel "fail" is an auth-required deploy gate, unrelated to the code.

Coverage

Full diff reviewed (656 lines / 8 files; all source read). The happy-path reversal is correct; the findings are edge-case data-loss landmines, robustness gaps, and thin tests on a destructive command — none block the common path, but #1#3 are worth addressing before this ships.

Automated multi-tool digest (8 Claude lanes + Codex) — verify before acting.

Following up on two points from the review body (no inline thread on these, so replying here).


@magyargergo

Deletion is provenance-blind. … Suggested direction: have setup drop an ownership marker (e.g. .gitnexus-managed) that uninstall verifies before deleting.

Before adding the marker, one design concern I'd like your call on.

Migration hole: a marker only lands on installs done after it ships, so uninstall would refuse to clean every existing install (no marker) — exactly the population #168 is about. Rescuing that needs a "no marker → fall back
to name-match" path, which re-introduces the heuristic for legacy installs. The marker also doesn't cover the realistic case — a user who edited files inside an installed skill dir — since we'd still recursive-delete the dir; only
per-file manifests would, which feels disproportionate here.

The risk also isn't uniform: MCP writes mcpServers.gitnexus by clobbering (ours by construction at uninstall time); hooks are matched by our script-path needle and now removed element-granularly; the only real residual is a
user's same-named skill dir, already gated by deriving the exact bundled-skill set + dry-run-by-default (nothing deleted without --force after the preview lists it).

Inclination for this PR: keep dry-run + name-derivation, document the boundary, and treat true provenance (marker + legacy fallback) as a follow-up with the migration story worked out. Happy to do the marker here instead — just
confirming direction given the backward-compat hole. WDYT?


setup.ts ↔ uninstall.ts duplicate the target map … Recommend extracting a shared editor-targets.ts both import.

Agreed this is the real drift hazard. One scoping note: setup.ts has no clean target map today — the per-editor write logic genuinely differs (JSONC merge vs TOML upsert vs OpenCode's flat command array vs Gemini's ms-vs-s hook
timeouts). So a shared editor-targets.ts can cleanly own the locations/identities (paths, keyPaths, events, needles, script dirs) + detectIndentation, but not the per-format read/write mechanics.

Two options:

  1. Full extraction — add editor-targets.ts and rewire both files (touches the install path, but the existing setup suite covers it).
  2. Test-first, minimal setup churn — add editor-targets.ts consumed by uninstall.ts now, plus a setup → uninstall round-trip integration test (temp HOME: setup writes everything → uninstall removes everything) as a drift
    tripwire that fails CI in both directions, with the setup rewire as a fast follow-up.

@magyargergo

Copy link
Copy Markdown
Collaborator

Thanks for laying this out. The migration concern is valid.

On provenance, I agree that adding a marker in isolation would create a false sense of safety. It would only cover future installs, would still require a heuristic fallback for existing installs, and would not protect user modifications inside a previously installed directory. Proper provenance probably needs a versioned ownership manifest and a defined legacy migration policy rather than just a boolean marker.

For this PR, I am comfortable keeping the current name-derived removal with dry-run by default, provided that:

  • the README and command output clearly state that skill directories are identified by bundled skill name;
  • the preview prints the exact paths that would be removed;
  • provenance tracking is opened as a follow-up covering both managed installs and the legacy fallback behaviour.

On the target map, I would prefer option 1, but with the extraction limited to the declarative metadata you described: locations, key paths, events, needles, script directories, and shared formatting helpers. The format-specific mutation logic should remain in setup.ts and uninstall.ts.

A target module consumed only by uninstall.ts would not yet remove the drift risk; it would mainly relocate one side of the duplication. Since the existing setup suite covers the install path, the focused metadata extraction seems like reasonable scope here.

Please also add the setup --> uninstall round-trip integration test. The shared metadata prevents structural drift, while the round-trip test verifies that the two implementations remain behaviourally symmetrical.

So my preference is:

  1. Defer provenance to a tracked follow-up with the migration strategy explicitly included.
  2. Extract the shared target identities into editor-targets.ts and consume them from both setup and uninstall.
  3. Add the round-trip integration test as the regression tripwire.

magyargergo and others added 2 commits June 9, 2026 09:07
…k (review abhigyanpatwari#2062)

Maintainer review follow-ups:

- Extract editor target identities into editor-targets.ts (MCP paths/keyPaths,
  Codex TOML section, skill dirs, hook settings/events/needles/script dirs,
  shared detectIndentation). Both setup.ts and uninstall.ts consume it, so a
  target change updates both sides — killing the silent drift hazard.
- Add a setup -> uninstall round-trip integration test that iterates
  getEditorTargets(): setup writes every target, uninstall removes all of them,
  and a co-located user MCP server + user hook survive. Drift tripwire in both
  directions.
- Preview now prints the exact paths it would remove; command output + README
  state skills are matched by bundled gitnexus skill name. (Provenance marker
  deferred to a tracked follow-up.)

Hardening of the hand-rolled Codex TOML fallback (found in code review):
- Strip a section header that has a trailing inline comment (was matched as a
  header but failed the exact classify check -> section left behind while
  reported removed).
- Preserve CRLF line endings instead of rewriting the whole file to LF.
- Fix multiline-string scan: a line with an odd count of BOTH """ and '''
  no longer mis-picks the delimiter and desyncs the scanner (left->right scan).
- removeSkillsFrom guard also rejects absolute names.

Regression tests added for each. Full setup/uninstall suite green.
@magyargergo magyargergo merged commit 1716bf7 into abhigyanpatwari:main Jun 9, 2026
27 of 28 checks passed
@magyargergo

Copy link
Copy Markdown
Collaborator

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants