()
| 63 | // prompt-injection reason as the original allowlist (see comment in |
| 64 | // findEvolverRoot below). |
| 65 | function _buildInstallSearchPaths() { |
| 66 | const home = os.homedir(); |
| 67 | // Env-derived bases must be ABSOLUTE. A relative override (e.g. NVM_DIR='.nvm') |
| 68 | // or empty value would resolve against process.cwd() and let a hostile |
| 69 | // workspace plant a fake @evomap/evolver in the require.resolve allowlist — |
| 70 | // the prompt-injection surface PR #94 closed. isAbsolute is platform-matched |
| 71 | // (path.win32 recognises C:\ ...) so the guard holds on Windows too; a |
| 72 | // non-absolute override falls through to the trusted home/system default. |
| 73 | const _pathFlavor = process.platform === 'win32' ? path.win32 : path.posix; |
| 74 | const absEnv = (v) => (v && _pathFlavor.isAbsolute(v)) ? v : null; |
| 75 | const paths = [ |
| 76 | // npm global with `npm config set prefix` overrides |
| 77 | path.join(home, '.npm-global', 'lib', 'node_modules'), |
| 78 | path.join(home, '.local', 'lib', 'node_modules'), |
| 79 | // System-wide (apt/yum nodejs, Intel Mac Homebrew) |
| 80 | '/usr/lib/node_modules', |
| 81 | '/usr/local/lib/node_modules', |
| 82 | // Apple Silicon Homebrew (default since macOS Big Sur on M1/M2/M3/M4 — |
| 83 | // the majority of Mac dev hardware sold since 2021). Without this |
| 84 | // entry, `npm install -g @evomap/evolver` on an Apple Silicon Mac |
| 85 | // lands at /opt/homebrew/lib/node_modules/@evomap/evolver and the |
| 86 | // hook scripts cannot find the package -> additionalContext is empty |
| 87 | // -> evolution memory never reaches the LLM. |
| 88 | '/opt/homebrew/lib/node_modules', |
| 89 | // Linuxbrew (Homebrew on Linux — niche but real). |
| 90 | '/home/linuxbrew/.linuxbrew/lib/node_modules', |
| 91 | ]; |
| 92 | // Per-user Node version managers. Each manager has its own on-disk layout |
| 93 | // and its own base-dir env override; the version subdirectory is dynamic |
| 94 | // (e.g. `~/.nvm/versions/node/v22.15.0`) so we scan and append each |
| 95 | // version's node_modules. These were missing from the original hard-coded |
| 96 | // list even though NVM in particular is extremely common across all OSes. |
| 97 | |
| 98 | // NVM. Globals are per-version under `<NVM_DIR>/versions/node/<ver>/lib`. |
| 99 | // NVM_DIR defaults to ~/.nvm but is frequently relocated. |
| 100 | const nvmDir = absEnv(process.env.NVM_DIR) || path.join(home, '.nvm'); |
| 101 | _scanVersionedNodeModules(path.join(nvmDir, 'versions', 'node'), 'lib', paths); |
| 102 | |
| 103 | // fnm. Each version lives under `<base>/node-versions/<ver>/installation/`, |
| 104 | // and fnm does NOT override the npm prefix, so globals are at |
| 105 | // `installation/lib/node_modules`. The base dir is XDG-first |
| 106 | // (`$XDG_DATA_HOME/fnm`, i.e. ~/.local/share/fnm on Linux and |
| 107 | // ~/Library/Application Support/fnm on macOS); `~/.fnm` is only the legacy |
| 108 | // fallback. `$FNM_DIR` overrides everything. Scan all candidate bases; |
| 109 | // _scanVersionedNodeModules silently skips the ones that don't exist. |
| 110 | const fnmSub = path.join('installation', 'lib'); |
| 111 | const fnmBases = []; |
| 112 | if (absEnv(process.env.FNM_DIR)) fnmBases.push(process.env.FNM_DIR); |
| 113 | if (absEnv(process.env.XDG_DATA_HOME)) fnmBases.push(path.join(process.env.XDG_DATA_HOME, 'fnm')); |
| 114 | fnmBases.push(path.join(home, '.local', 'share', 'fnm')); // Linux XDG default |
| 115 | fnmBases.push(path.join(home, 'Library', 'Application Support', 'fnm')); // macOS default |
| 116 | fnmBases.push(path.join(home, '.fnm')); // legacy |
| 117 | for (const base of fnmBases) { |
| 118 | _scanVersionedNodeModules(path.join(base, 'node-versions'), fnmSub, paths); |
| 119 | } |
| 120 | |
| 121 | // Volta does NOT store global packages alongside the Node image. It |
| 122 | // sandboxes each `npm install -g`'d package under |
no test coverage detected