@evomap/evolver 1.90.0 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +27 -564
  2. package/bin/evolver-llm-proxy.js +3 -0
  3. package/bin/evolver-mcp.js +2 -0
  4. package/bin/evolver-proxy.js +3 -0
  5. package/bin/evolver.js +4 -0
  6. package/index.js +1 -3670
  7. package/package.json +32 -62
  8. package/CONTRIBUTING.md +0 -19
  9. package/LICENSE +0 -641
  10. package/README.ja-JP.md +0 -521
  11. package/README.ko-KR.md +0 -520
  12. package/README.zh-CN.md +0 -531
  13. package/SKILL.md +0 -365
  14. package/assets/cover.png +0 -0
  15. package/assets/gep/genes.seed.json +0 -496
  16. package/conformance/savings-core/constants.json +0 -30
  17. package/conformance/savings-core/golden-vectors.json +0 -333
  18. package/scripts/a2a_export.js +0 -63
  19. package/scripts/a2a_ingest.js +0 -79
  20. package/scripts/a2a_promote.js +0 -118
  21. package/scripts/analyze_by_skill.js +0 -121
  22. package/scripts/build_binaries.js +0 -479
  23. package/scripts/check-changelog.js +0 -166
  24. package/scripts/extract_log.js +0 -85
  25. package/scripts/generate_history.js +0 -75
  26. package/scripts/gep_append_event.js +0 -96
  27. package/scripts/gep_personality_report.js +0 -234
  28. package/scripts/human_report.js +0 -147
  29. package/scripts/recall-verify-report.js +0 -234
  30. package/scripts/recover_loop.js +0 -61
  31. package/scripts/refresh_stars_badge.js +0 -168
  32. package/scripts/seed-merchants.js +0 -91
  33. package/scripts/skill2recipes.js +0 -118
  34. package/scripts/suggest_version.js +0 -89
  35. package/scripts/validate-modules.js +0 -38
  36. package/scripts/validate-suite.js +0 -78
  37. package/src/adapters/claudeCode.js +0 -194
  38. package/src/adapters/codex.js +0 -216
  39. package/src/adapters/cursor.js +0 -91
  40. package/src/adapters/hookAdapter.js +0 -469
  41. package/src/adapters/kiro.js +0 -195
  42. package/src/adapters/opencode.js +0 -326
  43. package/src/adapters/scripts/_lockPaths.js +0 -74
  44. package/src/adapters/scripts/_memoryFiltering.js +0 -35
  45. package/src/adapters/scripts/_runtimePaths.js +0 -440
  46. package/src/adapters/scripts/evolver-session-end.js +0 -321
  47. package/src/adapters/scripts/evolver-session-start.js +0 -587
  48. package/src/adapters/scripts/evolver-signal-detect.js +0 -98
  49. package/src/adapters/scripts/evolver-task-recall.js +0 -173
  50. package/src/atp/atpExecute.js +0 -283
  51. package/src/atp/atpTaskPickup.js +0 -233
  52. package/src/atp/autoBuyer.js +0 -382
  53. package/src/atp/autoDeliver.js +0 -215
  54. package/src/atp/cli.js +0 -354
  55. package/src/atp/cliAutobuyPrompt.js +0 -154
  56. package/src/atp/consumerAgent.js +0 -157
  57. package/src/atp/defaultHandler.js +0 -69
  58. package/src/atp/heartbeatSignalsHandler.js +0 -254
  59. package/src/atp/hubClient.js +0 -317
  60. package/src/atp/index.js +0 -38
  61. package/src/atp/merchantAgent.js +0 -118
  62. package/src/atp/protocol.js +0 -41
  63. package/src/atp/questionComposer.js +0 -133
  64. package/src/atp/serviceHelper.js +0 -92
  65. package/src/canary.js +0 -13
  66. package/src/config.js +0 -351
  67. package/src/evolve/guards.js +0 -1
  68. package/src/evolve/pipeline/collect.js +0 -1
  69. package/src/evolve/pipeline/dispatch.js +0 -1
  70. package/src/evolve/pipeline/enrich.js +0 -1
  71. package/src/evolve/pipeline/hub.js +0 -1
  72. package/src/evolve/pipeline/select.js +0 -1
  73. package/src/evolve/pipeline/signals.js +0 -1
  74. package/src/evolve/utils.js +0 -1
  75. package/src/evolve.js +0 -1
  76. package/src/experiment/agentRunner.js +0 -229
  77. package/src/experiment/cli.js +0 -159
  78. package/src/experiment/comparison.js +0 -233
  79. package/src/experiment/metrics.js +0 -75
  80. package/src/forceUpdate.js +0 -991
  81. package/src/gep/a2a.js +0 -173
  82. package/src/gep/a2aProtocol.js +0 -1
  83. package/src/gep/analyzer.js +0 -35
  84. package/src/gep/antiAbuseTelemetry.js +0 -1
  85. package/src/gep/assetCallLog.js +0 -197
  86. package/src/gep/assetStore.js +0 -723
  87. package/src/gep/assets.js +0 -36
  88. package/src/gep/autoDistillConv.js +0 -1
  89. package/src/gep/autoDistillLlm.js +0 -1
  90. package/src/gep/bridge.js +0 -138
  91. package/src/gep/candidateEval.js +0 -1
  92. package/src/gep/candidates.js +0 -1
  93. package/src/gep/claimNudge.js +0 -121
  94. package/src/gep/cliContracts.js +0 -1190
  95. package/src/gep/contentHash.js +0 -1
  96. package/src/gep/conversationDistiller.js +0 -1
  97. package/src/gep/conversationSniffer.js +0 -1
  98. package/src/gep/crypto.js +0 -1
  99. package/src/gep/curriculum.js +0 -1
  100. package/src/gep/deviceId.js +0 -1
  101. package/src/gep/directoryClient.js +0 -115
  102. package/src/gep/envFingerprint.js +0 -1
  103. package/src/gep/epigenetics.js +0 -1
  104. package/src/gep/execBridge.js +0 -1
  105. package/src/gep/executionTrace.js +0 -291
  106. package/src/gep/explore.js +0 -1
  107. package/src/gep/featureFlags.js +0 -121
  108. package/src/gep/gitOps.js +0 -265
  109. package/src/gep/hash.js +0 -1
  110. package/src/gep/hostErrorClassifier.js +0 -34
  111. package/src/gep/hubFetch.js +0 -1
  112. package/src/gep/hubReview.js +0 -1
  113. package/src/gep/hubSearch.js +0 -1
  114. package/src/gep/hubVerify.js +0 -1
  115. package/src/gep/idleScheduler.js +0 -400
  116. package/src/gep/issueReporter.js +0 -416
  117. package/src/gep/learningSignals.js +0 -1
  118. package/src/gep/llmReview.js +0 -92
  119. package/src/gep/localStateAwareness.js +0 -243
  120. package/src/gep/mailboxTransport.js +0 -119
  121. package/src/gep/memoryGraph.js +0 -1
  122. package/src/gep/memoryGraphAdapter.js +0 -1
  123. package/src/gep/mutation.js +0 -1
  124. package/src/gep/narrativeMemory.js +0 -1
  125. package/src/gep/oauthLogin.js +0 -181
  126. package/src/gep/openPRRegistry.js +0 -1
  127. package/src/gep/paths.js +0 -522
  128. package/src/gep/personality.js +0 -1
  129. package/src/gep/policyCheck.js +0 -1
  130. package/src/gep/portable.js +0 -103
  131. package/src/gep/privacyClient.js +0 -235
  132. package/src/gep/prompt.js +0 -1
  133. package/src/gep/questionGenerator.js +0 -518
  134. package/src/gep/recallInject.js +0 -1
  135. package/src/gep/recallVerifier.js +0 -1
  136. package/src/gep/reflection.js +0 -1
  137. package/src/gep/sanitize.js +0 -264
  138. package/src/gep/savingsCore.js +0 -1
  139. package/src/gep/schemas/capsule.js +0 -170
  140. package/src/gep/schemas/gene.js +0 -154
  141. package/src/gep/schemas/index.js +0 -8
  142. package/src/gep/schemas/protocol.js +0 -51
  143. package/src/gep/schemas/task.js +0 -74
  144. package/src/gep/selector.js +0 -1
  145. package/src/gep/selfPR.js +0 -469
  146. package/src/gep/signals.js +0 -776
  147. package/src/gep/skill2gep.js +0 -1056
  148. package/src/gep/skill2gepAudit.js +0 -303
  149. package/src/gep/skill2recipes.js +0 -511
  150. package/src/gep/skillDistiller.js +0 -1
  151. package/src/gep/skillPublisher.js +0 -358
  152. package/src/gep/solidify.js +0 -1
  153. package/src/gep/strategy.js +0 -1
  154. package/src/gep/taskReceiver.js +0 -575
  155. package/src/gep/tokenSavings.js +0 -1
  156. package/src/gep/trajectoryExport.js +0 -1
  157. package/src/gep/validationReport.js +0 -55
  158. package/src/gep/validator/index.js +0 -411
  159. package/src/gep/validator/reporter.js +0 -210
  160. package/src/gep/validator/sandboxExecutor.js +0 -480
  161. package/src/gep/validator/stakeBootstrap.js +0 -357
  162. package/src/gep/workspaceKeychain.js +0 -1
  163. package/src/ops/cleanup.js +0 -80
  164. package/src/ops/commentary.js +0 -60
  165. package/src/ops/health_check.js +0 -104
  166. package/src/ops/index.js +0 -11
  167. package/src/ops/innovation.js +0 -67
  168. package/src/ops/lifecycle.js +0 -798
  169. package/src/ops/self_repair.js +0 -76
  170. package/src/ops/skills_monitor.js +0 -147
  171. package/src/ops/trigger.js +0 -33
  172. package/src/proxy/clientSettings.js +0 -405
  173. package/src/proxy/envelope.js +0 -59
  174. package/src/proxy/extensions/dmHandler.js +0 -45
  175. package/src/proxy/extensions/sessionHandler.js +0 -141
  176. package/src/proxy/extensions/skillUpdater.js +0 -64
  177. package/src/proxy/extensions/traceControl.js +0 -1
  178. package/src/proxy/index.js +0 -1395
  179. package/src/proxy/inject.js +0 -1
  180. package/src/proxy/lifecycle/manager.js +0 -1568
  181. package/src/proxy/mailbox/state.js +0 -207
  182. package/src/proxy/mailbox/store.js +0 -602
  183. package/src/proxy/router/cache_passthrough.js +0 -26
  184. package/src/proxy/router/features.js +0 -84
  185. package/src/proxy/router/gemini_route.js +0 -154
  186. package/src/proxy/router/messages_route.js +0 -535
  187. package/src/proxy/router/model_router.js +0 -113
  188. package/src/proxy/router/models_route.js +0 -52
  189. package/src/proxy/router/ollama_route.js +0 -103
  190. package/src/proxy/router/responses_route.js +0 -170
  191. package/src/proxy/router/vertex_route.js +0 -110
  192. package/src/proxy/server/http.js +0 -363
  193. package/src/proxy/server/routes.js +0 -558
  194. package/src/proxy/server/settings.js +0 -115
  195. package/src/proxy/sync/engine.js +0 -179
  196. package/src/proxy/sync/inbound.js +0 -211
  197. package/src/proxy/sync/outbound.js +0 -361
  198. package/src/proxy/task/monitor.js +0 -131
  199. package/src/proxy/trace/extractor.js +0 -1
  200. package/src/proxy/trace/usage.js +0 -1
  201. package/src/solo/breaker.js +0 -25
  202. package/src/solo/gitGuard.js +0 -65
  203. package/src/webui/client/clientJs/assets.js +0 -111
  204. package/src/webui/client/clientJs/bootstrap.js +0 -92
  205. package/src/webui/client/clientJs/common.js +0 -77
  206. package/src/webui/client/clientJs/i18n.js +0 -366
  207. package/src/webui/client/clientJs/index.js +0 -35
  208. package/src/webui/client/clientJs/interactions.js +0 -351
  209. package/src/webui/client/clientJs/overview.js +0 -152
  210. package/src/webui/client/clientJs/personality.js +0 -285
  211. package/src/webui/client/clientJs/pipelines.js +0 -330
  212. package/src/webui/client/indexHtml.js +0 -221
  213. package/src/webui/client/static.js +0 -23
  214. package/src/webui/client/stylesCss.js +0 -639
  215. package/src/webui/client/vendor/README.md +0 -15
  216. package/src/webui/client/vendor/echarts.min.js +0 -45
  217. package/src/webui/index.js +0 -14
  218. package/src/webui/observer/assets.js +0 -146
  219. package/src/webui/observer/index.js +0 -37
  220. package/src/webui/observer/interactions.js +0 -127
  221. package/src/webui/observer/jsonl.js +0 -75
  222. package/src/webui/observer/paths.js +0 -46
  223. package/src/webui/observer/personality.js +0 -43
  224. package/src/webui/observer/pipelineEvents.js +0 -58
  225. package/src/webui/observer/redact.js +0 -63
  226. package/src/webui/observer/runs.js +0 -356
  227. package/src/webui/observer/safety.js +0 -57
  228. package/src/webui/observer/skills.js +0 -70
  229. package/src/webui/observer/status.js +0 -71
  230. package/src/webui/server/http.js +0 -138
  231. package/src/webui/server/routes.js +0 -41
@@ -1,440 +0,0 @@
1
- // _runtimePaths.js
2
- // Shared path resolution for evolver hook scripts.
3
- //
4
- // Two responsibilities:
5
- // 1. Locate the evolver package root, supporting:
6
- // - $EVOLVER_ROOT explicit override
7
- // - The "scripts colocated with src" layout used during dev (../../..)
8
- // - The npm-global install layout, where the hook script lives under
9
- // `<prefix>/lib/node_modules/<host>/.../hooks/` and `..` walks lead
10
- // somewhere outside the evolver package. We resolve via
11
- // `require.resolve('@evomap/evolver/package.json')` instead.
12
- // - The `~/skills/evolver` fallback (some users symlink there).
13
- //
14
- // 2. Locate (or pick a writable default for) the evolution memory graph,
15
- // so that hook scripts in environments without an evolver-managed
16
- // project directory still record outcomes somewhere instead of
17
- // reporting "nowhere (no Hub or local path)" (#536).
18
-
19
- const fs = require('fs');
20
- const path = require('path');
21
- const os = require('os');
22
- const { spawnSync } = require('child_process');
23
-
24
- function isEvolverPackageJson(filePath) {
25
- try {
26
- const raw = fs.readFileSync(filePath, 'utf8');
27
- const pkg = JSON.parse(raw);
28
- return pkg && (pkg.name === '@evomap/evolver' || pkg.name === 'evolver');
29
- } catch {
30
- return false;
31
- }
32
- }
33
-
34
- // Scan a "versions dir" used by Node version managers (NVM, fnm, Volta, asdf)
35
- // and append each `<versions-dir>/<version>/<subdir>/node_modules` to `out`.
36
- // Skips silently when the versions dir does not exist (typical case — most
37
- // users have at most one version manager). Most-recent-mtime first so the
38
- // active version is preferred when a user has multiple Node versions
39
- // installed.
40
- function _scanVersionedNodeModules(versionsDir, subdir, out) {
41
- let entries;
42
- try {
43
- entries = fs.readdirSync(versionsDir, { withFileTypes: true });
44
- } catch {
45
- return;
46
- }
47
- const dirs = entries
48
- .filter((e) => e.isDirectory && e.isDirectory())
49
- .map((e) => {
50
- const full = path.join(versionsDir, e.name);
51
- let mtime = 0;
52
- try { mtime = fs.statSync(full).mtimeMs; } catch {}
53
- return { full, mtime };
54
- })
55
- .sort((a, b) => b.mtime - a.mtime);
56
- for (const d of dirs) {
57
- out.push(path.join(d.full, subdir, 'node_modules'));
58
- }
59
- }
60
-
61
- // Build the require.resolve paths array. All entries are user/system-scoped
62
- // install roots — process.cwd() is intentionally excluded for the same
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
123
- // `<VOLTA_HOME>/tools/image/packages/<name>/lib/node_modules` (the scope
124
- // becomes a real nested directory). Because we know the package name, this
125
- // is a single fixed path rather than a version scan. VOLTA_HOME defaults to
126
- // ~/.volta on macOS/Linux but to %LOCALAPPDATA%\Volta on Windows (where the
127
- // globals actually live, and hook processes often don't inherit VOLTA_HOME).
128
- // Verified against volta-cli/volta `volta-layout` v4 (`package_image_dir`) +
129
- // `package/manager.rs` (`source_dir` = lib/node_modules).
130
- const voltaHome = absEnv(process.env.VOLTA_HOME)
131
- || (process.platform === 'win32'
132
- ? path.join(absEnv(process.env.LOCALAPPDATA) || path.join(home, 'AppData', 'Local'), 'Volta')
133
- : path.join(home, '.volta'));
134
- paths.push(path.join(voltaHome, 'tools', 'image', 'packages', '@evomap', 'evolver', 'lib', 'node_modules'));
135
-
136
- // asdf. Globals are per-version under `<data>/installs/nodejs/<ver>/`.
137
- // asdf-nodejs dropped the `.npm` prefix override in PR #228 (Sept 2022),
138
- // so modern installs use plain `lib/node_modules`; older installs (never
139
- // re-created) still use `.npm/lib/node_modules`. Scan both. `$ASDF_DATA_DIR`
140
- // overrides the ~/.asdf default (asdf 0.16+ Go rewrite).
141
- const asdfData = absEnv(process.env.ASDF_DATA_DIR) || path.join(home, '.asdf');
142
- const asdfVersions = path.join(asdfData, 'installs', 'nodejs');
143
- _scanVersionedNodeModules(asdfVersions, 'lib', paths); // modern (post-#228)
144
- _scanVersionedNodeModules(asdfVersions, path.join('.npm', 'lib'), paths); // legacy (pre-#228)
145
-
146
- // Windows: `npm install -g` puts packages under %APPDATA%\npm\node_modules
147
- // (most common; same convention as `npm config get prefix` default on win32),
148
- // %ProgramFiles%\nodejs\node_modules (system-wide installer), or
149
- // %ProgramFiles(x86)%\nodejs\node_modules (32-bit Node on a 64-bit host).
150
- // Conditional expansion keeps the POSIX base list untouched on Linux/macOS.
151
- if (process.platform === 'win32') {
152
- const appdata = absEnv(process.env.APPDATA) || path.join(home, 'AppData', 'Roaming');
153
- paths.push(path.join(appdata, 'npm', 'node_modules'));
154
- if (absEnv(process.env.ProgramFiles)) {
155
- paths.push(path.join(process.env.ProgramFiles, 'nodejs', 'node_modules'));
156
- }
157
- if (absEnv(process.env['ProgramFiles(x86)'])) {
158
- paths.push(path.join(process.env['ProgramFiles(x86)'], 'nodejs', 'node_modules'));
159
- }
160
- }
161
-
162
- return paths;
163
- }
164
-
165
- function findEvolverRoot() {
166
- if (process.env.EVOLVER_ROOT) {
167
- const explicit = process.env.EVOLVER_ROOT;
168
- if (fs.existsSync(path.join(explicit, 'package.json')) &&
169
- isEvolverPackageJson(path.join(explicit, 'package.json'))) {
170
- return explicit;
171
- }
172
- }
173
-
174
- // Dev/repo layout: this file lives at src/adapters/scripts/_runtimePaths.js,
175
- // so `../../..` is the package root.
176
- const repoRoot = path.resolve(__dirname, '..', '..', '..');
177
- if (fs.existsSync(path.join(repoRoot, 'package.json')) &&
178
- isEvolverPackageJson(path.join(repoRoot, 'package.json'))) {
179
- return repoRoot;
180
- }
181
-
182
- // npm-global / npm-local install layout. The hook script may have been
183
- // copied out of the package into `.claude/hooks/` etc., breaking relative
184
- // walks. Use require.resolve to find the installed package authoritatively.
185
- //
186
- // SECURITY: do NOT include `process.cwd()` here. A hostile workspace can
187
- // place its own `node_modules/@evomap/evolver/package.json`, which would
188
- // be selected here and control `findMemoryGraph()` -> the memory graph
189
- // contents become attacker-controlled prompt-injection material in
190
- // `evolver-session-start.js`'s `additionalContext`. Restrict to trusted,
191
- // user/system-scoped install roots (built in `_buildInstallSearchPaths`).
192
- try {
193
- // Allowlist of trusted user/system-scoped install roots. Built by
194
- // _buildInstallSearchPaths() above so the list is one source of truth
195
- // (Apple Silicon Homebrew, Linuxbrew, NVM / fnm / Volta / asdf,
196
- // and Windows %APPDATA%\npm + %ProgramFiles%\nodejs install layouts).
197
- // process.cwd() is intentionally excluded: a hostile workspace can plant
198
- // its own node_modules/@evomap/evolver/package.json which would then
199
- // control findMemoryGraph() and feed attacker-controlled content into
200
- // evolver-session-start.js's additionalContext.
201
- const pkgJson = require.resolve('@evomap/evolver/package.json', {
202
- paths: _buildInstallSearchPaths(),
203
- });
204
- if (pkgJson && isEvolverPackageJson(pkgJson)) {
205
- return path.dirname(pkgJson);
206
- }
207
- } catch { /* not installed via npm */ }
208
-
209
- const homeSkills = path.join(os.homedir(), 'skills', 'evolver');
210
- if (fs.existsSync(path.join(homeSkills, 'package.json')) &&
211
- isEvolverPackageJson(path.join(homeSkills, 'package.json'))) {
212
- return homeSkills;
213
- }
214
-
215
- return null;
216
- }
217
-
218
- // Resolve the user's PROJECT directory — the workspace the agent is actually
219
- // working in — for git-diff collection and workspace tagging.
220
- //
221
- // Why this exists: hook scripts must NOT assume `process.cwd()` is the project
222
- // root. Cursor invokes some hook events (e.g. afterFileEdit) with the working
223
- // directory set to the *plugin* install dir (`~/.cursor/plugins/local/<name>`),
224
- // not the opened workspace. A hook that runs `git diff` in cwd would then look
225
- // for changes in the plugin directory and find none — silently recording
226
- // nothing for every task. Hosts expose the real workspace root via an env var:
227
- // - Cursor sets CURSOR_PROJECT_DIR (and a CLAUDE_PROJECT_DIR compat alias)
228
- // - Claude Code sets CLAUDE_PROJECT_DIR
229
- // Codex / opencode / Kiro and direct CLI usage leave both unset, in which case
230
- // `process.cwd()` is already the project root and remains the fallback — so
231
- // this change is a no-op on those platforms.
232
- //
233
- // SECURITY: only honor an env value that points at an existing directory. A
234
- // stale or empty value must not redirect git collection to a bogus path; we
235
- // fall through to cwd instead. We intentionally do NOT recurse into evolver
236
- // package discovery here — this is purely "where is the user's code".
237
- function resolveProjectDir() {
238
- for (const key of ['CURSOR_PROJECT_DIR', 'CLAUDE_PROJECT_DIR']) {
239
- const v = process.env[key];
240
- if (typeof v === 'string' && v.trim()) {
241
- try {
242
- if (fs.statSync(v).isDirectory()) return v;
243
- } catch { /* not a usable dir — try next / fall back to cwd */ }
244
- }
245
- }
246
- return process.cwd();
247
- }
248
-
249
- // Determine the workspace ROOT for a project, mirroring src/gep/paths.js
250
- // getWorkspaceRoot() step-for-step so the FS-only fallback lands its secret at
251
- // the SAME path paths.js would (what lets an installed @evomap/evolver read the
252
- // very same id):
253
- // 1. OPENCLAW_WORKSPACE override.
254
- // 2. else the git repo root at/above projectDir, BUT if that repo root has a
255
- // `workspace/` subdirectory, paths.js returns <repoRoot>/workspace — so we
256
- // must too, or the two land on different .evolver/workspace-id files (the
257
- // "read back identically" guarantee would break for such projects).
258
- // 3. else projectDir.
259
- function _fsWorkspaceRoot(projectDir) {
260
- if (process.env.OPENCLAW_WORKSPACE) return process.env.OPENCLAW_WORKSPACE;
261
- // Walk up from projectDir looking for a .git entry (file or dir) = repo root.
262
- let repoRoot = null;
263
- let dir = projectDir;
264
- while (dir) {
265
- if (fs.existsSync(path.join(dir, '.git'))) { repoRoot = dir; break; }
266
- const parent = path.dirname(dir);
267
- if (parent === dir) break;
268
- dir = parent;
269
- }
270
- if (!repoRoot) return projectDir;
271
- // Mirror getWorkspaceRoot()'s workspace/ subdir step.
272
- const workspaceDir = path.join(repoRoot, 'workspace');
273
- if (fs.existsSync(workspaceDir)) return workspaceDir;
274
- return repoRoot;
275
- }
276
-
277
- // FS-only re-implementation of src/gep/paths.js getWorkspaceId() for the case
278
- // where the evolver package is not installed (plugin-only installs). It reads
279
- // — and lazily, atomically creates — the per-workspace secret at
280
- // <workspaceRoot>/.evolver/workspace-id. The format (16-byte hex), the path,
281
- // the 0600 mode, the O_EXCL|O_NOFOLLOW atomic create, and the symlink
282
- // rejection all match paths.js exactly, so a workspace seeded by this fallback
283
- // is transparently picked up by paths.getWorkspaceId() once the package is
284
- // present, and vice-versa. Returns null on any read/write error (caller then
285
- // falls back to legacy cwd-tag matching — no regression).
286
- // Read <dir>/workspace-id with the same symlink guards paths.js'
287
- // _readWorkspaceIdFromFs uses: reject a symlinked .evolver dir, reject a
288
- // symlinked / non-regular id file, and require hex format. Returns the id, or
289
- // null on any error / missing file. Used for BOTH the initial read and the
290
- // EEXIST race re-read so a symlink swapped in between our lstat and openSync
291
- // can never be followed (Bugbot PR #557).
292
- function _readWsIdGuarded(dir, file) {
293
- try {
294
- const dirStat = fs.lstatSync(dir, { throwIfNoEntry: false });
295
- if (dirStat && dirStat.isSymbolicLink()) return null;
296
- const fileStat = fs.lstatSync(file, { throwIfNoEntry: false });
297
- if (!fileStat) return null;
298
- if (fileStat.isSymbolicLink() || !fileStat.isFile()) return null;
299
- const raw = fs.readFileSync(file, 'utf8').trim();
300
- return raw && /^[a-f0-9]{32,}$/i.test(raw) ? raw : null;
301
- } catch { return null; }
302
- }
303
-
304
- function _fsWorkspaceId(projectDir) {
305
- // Whole body is wrapped: the documented contract is "returns null on ANY
306
- // read/write error" so the session-start/-end hooks degrade gracefully
307
- // rather than crash. throwIfNoEntry:false only suppresses ENOENT; EACCES/EIO
308
- // and friends still throw, so a bare lstat/mkdir here must not escape
309
- // (Bugbot PR #557 round-2 — an unguarded lstat could crash the hook).
310
- try {
311
- const dir = path.join(_fsWorkspaceRoot(projectDir), '.evolver');
312
- const file = path.join(dir, 'workspace-id');
313
- // Read first, with symlink guards.
314
- const existing = _readWsIdGuarded(dir, file);
315
- if (existing) return existing;
316
- // If the file exists but the guards rejected it (symlink / bad format),
317
- // refuse rather than create over it.
318
- if (fs.lstatSync(file, { throwIfNoEntry: false })) return null;
319
- // Missing — create atomically. Refuse a symlinked .evolver dir (O_NOFOLLOW
320
- // only guards the final component, not intermediate dirs).
321
- const dirStat = fs.lstatSync(dir, { throwIfNoEntry: false });
322
- if (dirStat && dirStat.isSymbolicLink()) return null;
323
- fs.mkdirSync(dir, { recursive: true });
324
- const payload = require('crypto').randomBytes(16).toString('hex');
325
- const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL |
326
- (fs.constants.O_NOFOLLOW || 0);
327
- let fd;
328
- try {
329
- fd = fs.openSync(file, flags, 0o600);
330
- } catch (e) {
331
- // Lost a race — re-read WITH the same symlink guards (paths.js does the
332
- // same). A bare readFileSync here would follow a symlink swapped in
333
- // after our dir lstat (Bugbot PR #557).
334
- if (e && e.code === 'EEXIST') return _readWsIdGuarded(dir, file);
335
- return null; // ELOOP/EMLINK from O_NOFOLLOW hitting a symlink — refuse.
336
- }
337
- try { fs.writeSync(fd, payload + '\n', 0, 'utf8'); } finally { fs.closeSync(fd); }
338
- try { fs.chmodSync(file, 0o600); } catch { /* best-effort */ }
339
- return payload;
340
- } catch { return null; }
341
- }
342
-
343
- // Resolve the current workspace id — the forge-resistant tag the session-end
344
- // writer stamps on every memory-graph entry (`workspace_id`). This is the
345
- // SINGLE source of that resolution: the session-end writer stamps it and the
346
- // session-start reader scopes by it, so both call this one function. Keeping
347
- // it here (rather than a copy per hook) is what guarantees reader and writer
348
- // can never drift apart — if they resolved different ids, no entry would ever
349
- // match the reader's filter and workspace scoping would silently break.
350
- // Resolution order:
351
- // 1. EVOLVER_WORKSPACE_ID env override
352
- // 2. paths.getWorkspaceId() loaded from the resolved evolver root (this is
353
- // the richer path — it can additionally back the secret with the OS
354
- // keychain when @napi-rs/keyring is installed).
355
- // 3. FS-only fallback for plugin-only installs where the evolver package is
356
- // not reachable. Without this, plugin users got workspace_id=null and the
357
- // forge-resistant scoping silently degraded to cwd-tag matching (found
358
- // via real-Cursor end-to-end testing). The fallback writes the same
359
- // secret file paths.js uses, so installing the package later is seamless.
360
- // Still returns null if even the FS write fails — callers must then NOT filter
361
- // (show everything), preserving prior behavior rather than hiding all memory.
362
- function resolveWorkspaceId(evolverRoot, projectDir) {
363
- if (process.env.EVOLVER_WORKSPACE_ID) return String(process.env.EVOLVER_WORKSPACE_ID);
364
- const root = evolverRoot || findEvolverRoot();
365
- if (root) {
366
- try {
367
- const paths = require(path.join(root, 'src', 'gep', 'paths.js'));
368
- if (typeof paths.getWorkspaceId === 'function') return paths.getWorkspaceId();
369
- } catch { /* paths.js unreachable — fall through to FS-only */ }
370
- }
371
- return _fsWorkspaceId(projectDir || resolveProjectDir());
372
- }
373
-
374
- // Returns a path to the evolution memory graph, or a fallback location that
375
- // is guaranteed to be writable. Never returns null — when no evolver root is
376
- // available, we fall back to `~/.evolver/memory/evolution/memory_graph.jsonl`
377
- // so npm-global installs without a project-local evolver still capture
378
- // outcomes (#536). Callers that need a "does the file already exist" check
379
- // should use `fs.existsSync()` separately.
380
- function findMemoryGraph(evolverRoot) {
381
- if (process.env.MEMORY_GRAPH_PATH) {
382
- return process.env.MEMORY_GRAPH_PATH;
383
- }
384
- if (evolverRoot) {
385
- const lower = path.join(evolverRoot, 'memory', 'evolution', 'memory_graph.jsonl');
386
- if (fs.existsSync(lower)) return lower;
387
- const upper = path.join(evolverRoot, 'MEMORY', 'evolution', 'memory_graph.jsonl');
388
- if (fs.existsSync(upper)) return upper;
389
- // Neither exists yet — prefer lowercase under the evolver root if the
390
- // root itself is writable (dev/local install case).
391
- try {
392
- fs.accessSync(evolverRoot, fs.constants.W_OK);
393
- const dir = path.dirname(lower);
394
- try { fs.mkdirSync(dir, { recursive: true }); } catch { /* fall through */ }
395
- return lower;
396
- } catch { /* not writable, fall through to user-level */ }
397
- }
398
-
399
- // User-level fallback. Always writable, consistent across platforms.
400
- const userDir = path.join(os.homedir(), '.evolver', 'memory', 'evolution');
401
- try { fs.mkdirSync(userDir, { recursive: true }); } catch { /* best-effort */ }
402
- return path.join(userDir, 'memory_graph.jsonl');
403
- }
404
-
405
- // Is `dir` inside a git work tree? Cheap, no-shell `git rev-parse`. Returns
406
- // false on any error (git missing, not a repo, timeout) and never throws.
407
- // The session-start hook uses this only to decide whether to surface a
408
- // one-line "evolver needs a git workspace" notice, so a false negative just
409
- // suppresses the notice rather than breaking anything.
410
- function isGitWorkspace(dir) {
411
- try {
412
- const res = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
413
- cwd: dir,
414
- encoding: 'utf8',
415
- timeout: 5000,
416
- stdio: ['ignore', 'pipe', 'pipe'],
417
- shell: false,
418
- windowsHide: true,
419
- });
420
- return res.status === 0 && typeof res.stdout === 'string' && res.stdout.trim() === 'true';
421
- } catch {
422
- return false;
423
- }
424
- }
425
-
426
- module.exports = {
427
- findEvolverRoot,
428
- findMemoryGraph,
429
- resolveProjectDir,
430
- resolveWorkspaceId,
431
- isGitWorkspace,
432
- // Test-only: exposes the install-path builder so the test suite can
433
- // verify Apple Silicon Homebrew + version-manager (NVM/fnm/Volta/asdf)
434
- // + Windows %APPDATA%\npm paths are included without going through the
435
- // full require.resolve chain (which depends on a real filesystem layout).
436
- __internals: {
437
- buildInstallSearchPaths: _buildInstallSearchPaths,
438
- scanVersionedNodeModules: _scanVersionedNodeModules,
439
- },
440
- };