Issue #3492: scope migration aborted with generic verification errors in
two real states. When the destination scope is installed but inventory
reports enabled:false because the user explicitly set
enabledPlugins["ecc@ecc"]: false, setup now throws a targeted
DESTINATION_EXPLICITLY_DISABLED error naming the setting and file and
offering the native claude plugin enable recovery, leaving the user's
choice untouched. The generic not-enabled error also gains the enable
command in its recovery list.
uninstallSource now accepts a destination-only state when the source
scope is gone from the fresh inventory even if the uninstall command
errored, and SOURCE_UNINSTALL_FAILED recovery no longer suggests
uninstalling a source scope the inventory no longer lists; it prepends
claude plugin list --json so the stuck state can be inspected.
Tests: node tests/lib/claude-scope-migration.test.js — five new cases
cover explicitly disabled resume and single-scope destinations across
user/project/local settings files, the generic fallback keeping the
enable path, an uninstall error with the source already absent, and
non-circular recovery. 16 -> 21 passing; red against unpatched scripts
is 15 passed / 6 failed. Full-suite failure set is byte-identical to
unpatched HEAD in a zero-dependency environment.
Also sync the pinned SOURCE_UNINSTALL_FAILED recovery in
tests/scripts/setup.test.js ('migration JSON failures retain phase,
scopes, and exact recovery') with the new claude plugin list --json
diagnostic: 29 passed / 1 failed on the patched tree before the sync,
30 passed / 0 failed after, identical to clean 2d515e4f.
On a Windows machine with Claude Code installed natively, Git for Windows,
and the ECC plugin installed, `node tests/run-all.js` on main fails 44 tests
in 7 files. Linux CI never sees them. Three of the four causes are test
isolation gaps, fixed here (43 tests):
- claude-plugin-setup, claude-scope-migration, setup (38): fixtures put a
fake claude.cmd first on PATH, but on Windows spawning a bare `claude`
resolves only .com/.exe, so a native ~/.local/bin/claude.exe ran instead
of the fake. Fixture PATHs now drop entries holding claude.exe/claude.com.
- ecc-universal-bin, eval-harness-package (4): tar received an absolute
C:\ archive path, which Git Bash's GNU tar reads as a host:path remote
spec ("Cannot connect to C: resolve failed"). Extract from inside the
destination with a relative, forward-slash archive name, which both GNU
tar and bsdtar accept.
- harness-audit (1): the cache-layout test overrode HOME only, while
findPluginInstall also reads USERPROFILE and os.homedir(), so the real
installed plugin won over the fixture. Override USERPROFILE too.
The two helpers live in tests/lib/helpers/windows-test-env.js with a
platform-independent test, so Linux CI holds both invariants too.
The 44th failure, plugin-hook-bootstrap's no-bash test, is a production
bug rather than a test leak: System32\bash.exe launches an installed WSL
distro, which passes the bash probe. It gets a separate fix.