@evomap/evolver 1.90.0 → 2.0.0-beta.1
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.
- package/README.md +27 -564
- package/bin/evolver-llm-proxy.js +3 -0
- package/bin/evolver-mcp.js +2 -0
- package/bin/evolver-proxy.js +3 -0
- package/bin/evolver.js +4 -0
- package/index.js +1 -3670
- package/package.json +32 -62
- package/CONTRIBUTING.md +0 -19
- package/LICENSE +0 -641
- package/README.ja-JP.md +0 -521
- package/README.ko-KR.md +0 -520
- package/README.zh-CN.md +0 -531
- package/SKILL.md +0 -365
- package/assets/cover.png +0 -0
- package/assets/gep/genes.seed.json +0 -496
- package/conformance/savings-core/constants.json +0 -30
- package/conformance/savings-core/golden-vectors.json +0 -333
- package/scripts/a2a_export.js +0 -63
- package/scripts/a2a_ingest.js +0 -79
- package/scripts/a2a_promote.js +0 -118
- package/scripts/analyze_by_skill.js +0 -121
- package/scripts/build_binaries.js +0 -479
- package/scripts/check-changelog.js +0 -166
- package/scripts/extract_log.js +0 -85
- package/scripts/generate_history.js +0 -75
- package/scripts/gep_append_event.js +0 -96
- package/scripts/gep_personality_report.js +0 -234
- package/scripts/human_report.js +0 -147
- package/scripts/recall-verify-report.js +0 -234
- package/scripts/recover_loop.js +0 -61
- package/scripts/refresh_stars_badge.js +0 -168
- package/scripts/seed-merchants.js +0 -91
- package/scripts/skill2recipes.js +0 -118
- package/scripts/suggest_version.js +0 -89
- package/scripts/validate-modules.js +0 -38
- package/scripts/validate-suite.js +0 -78
- package/src/adapters/claudeCode.js +0 -194
- package/src/adapters/codex.js +0 -216
- package/src/adapters/cursor.js +0 -91
- package/src/adapters/hookAdapter.js +0 -469
- package/src/adapters/kiro.js +0 -195
- package/src/adapters/opencode.js +0 -326
- package/src/adapters/scripts/_lockPaths.js +0 -74
- package/src/adapters/scripts/_memoryFiltering.js +0 -35
- package/src/adapters/scripts/_runtimePaths.js +0 -440
- package/src/adapters/scripts/evolver-session-end.js +0 -321
- package/src/adapters/scripts/evolver-session-start.js +0 -587
- package/src/adapters/scripts/evolver-signal-detect.js +0 -98
- package/src/adapters/scripts/evolver-task-recall.js +0 -173
- package/src/atp/atpExecute.js +0 -283
- package/src/atp/atpTaskPickup.js +0 -233
- package/src/atp/autoBuyer.js +0 -382
- package/src/atp/autoDeliver.js +0 -215
- package/src/atp/cli.js +0 -354
- package/src/atp/cliAutobuyPrompt.js +0 -154
- package/src/atp/consumerAgent.js +0 -157
- package/src/atp/defaultHandler.js +0 -69
- package/src/atp/heartbeatSignalsHandler.js +0 -254
- package/src/atp/hubClient.js +0 -317
- package/src/atp/index.js +0 -38
- package/src/atp/merchantAgent.js +0 -118
- package/src/atp/protocol.js +0 -41
- package/src/atp/questionComposer.js +0 -133
- package/src/atp/serviceHelper.js +0 -92
- package/src/canary.js +0 -13
- package/src/config.js +0 -351
- package/src/evolve/guards.js +0 -1
- package/src/evolve/pipeline/collect.js +0 -1
- package/src/evolve/pipeline/dispatch.js +0 -1
- package/src/evolve/pipeline/enrich.js +0 -1
- package/src/evolve/pipeline/hub.js +0 -1
- package/src/evolve/pipeline/select.js +0 -1
- package/src/evolve/pipeline/signals.js +0 -1
- package/src/evolve/utils.js +0 -1
- package/src/evolve.js +0 -1
- package/src/experiment/agentRunner.js +0 -229
- package/src/experiment/cli.js +0 -159
- package/src/experiment/comparison.js +0 -233
- package/src/experiment/metrics.js +0 -75
- package/src/forceUpdate.js +0 -991
- package/src/gep/a2a.js +0 -173
- package/src/gep/a2aProtocol.js +0 -1
- package/src/gep/analyzer.js +0 -35
- package/src/gep/antiAbuseTelemetry.js +0 -1
- package/src/gep/assetCallLog.js +0 -197
- package/src/gep/assetStore.js +0 -723
- package/src/gep/assets.js +0 -36
- package/src/gep/autoDistillConv.js +0 -1
- package/src/gep/autoDistillLlm.js +0 -1
- package/src/gep/bridge.js +0 -138
- package/src/gep/candidateEval.js +0 -1
- package/src/gep/candidates.js +0 -1
- package/src/gep/claimNudge.js +0 -121
- package/src/gep/cliContracts.js +0 -1190
- package/src/gep/contentHash.js +0 -1
- package/src/gep/conversationDistiller.js +0 -1
- package/src/gep/conversationSniffer.js +0 -1
- package/src/gep/crypto.js +0 -1
- package/src/gep/curriculum.js +0 -1
- package/src/gep/deviceId.js +0 -1
- package/src/gep/directoryClient.js +0 -115
- package/src/gep/envFingerprint.js +0 -1
- package/src/gep/epigenetics.js +0 -1
- package/src/gep/execBridge.js +0 -1
- package/src/gep/executionTrace.js +0 -291
- package/src/gep/explore.js +0 -1
- package/src/gep/featureFlags.js +0 -121
- package/src/gep/gitOps.js +0 -265
- package/src/gep/hash.js +0 -1
- package/src/gep/hostErrorClassifier.js +0 -34
- package/src/gep/hubFetch.js +0 -1
- package/src/gep/hubReview.js +0 -1
- package/src/gep/hubSearch.js +0 -1
- package/src/gep/hubVerify.js +0 -1
- package/src/gep/idleScheduler.js +0 -400
- package/src/gep/issueReporter.js +0 -416
- package/src/gep/learningSignals.js +0 -1
- package/src/gep/llmReview.js +0 -92
- package/src/gep/localStateAwareness.js +0 -243
- package/src/gep/mailboxTransport.js +0 -119
- package/src/gep/memoryGraph.js +0 -1
- package/src/gep/memoryGraphAdapter.js +0 -1
- package/src/gep/mutation.js +0 -1
- package/src/gep/narrativeMemory.js +0 -1
- package/src/gep/oauthLogin.js +0 -181
- package/src/gep/openPRRegistry.js +0 -1
- package/src/gep/paths.js +0 -522
- package/src/gep/personality.js +0 -1
- package/src/gep/policyCheck.js +0 -1
- package/src/gep/portable.js +0 -103
- package/src/gep/privacyClient.js +0 -235
- package/src/gep/prompt.js +0 -1
- package/src/gep/questionGenerator.js +0 -518
- package/src/gep/recallInject.js +0 -1
- package/src/gep/recallVerifier.js +0 -1
- package/src/gep/reflection.js +0 -1
- package/src/gep/sanitize.js +0 -264
- package/src/gep/savingsCore.js +0 -1
- package/src/gep/schemas/capsule.js +0 -170
- package/src/gep/schemas/gene.js +0 -154
- package/src/gep/schemas/index.js +0 -8
- package/src/gep/schemas/protocol.js +0 -51
- package/src/gep/schemas/task.js +0 -74
- package/src/gep/selector.js +0 -1
- package/src/gep/selfPR.js +0 -469
- package/src/gep/signals.js +0 -776
- package/src/gep/skill2gep.js +0 -1056
- package/src/gep/skill2gepAudit.js +0 -303
- package/src/gep/skill2recipes.js +0 -511
- package/src/gep/skillDistiller.js +0 -1
- package/src/gep/skillPublisher.js +0 -358
- package/src/gep/solidify.js +0 -1
- package/src/gep/strategy.js +0 -1
- package/src/gep/taskReceiver.js +0 -575
- package/src/gep/tokenSavings.js +0 -1
- package/src/gep/trajectoryExport.js +0 -1
- package/src/gep/validationReport.js +0 -55
- package/src/gep/validator/index.js +0 -411
- package/src/gep/validator/reporter.js +0 -210
- package/src/gep/validator/sandboxExecutor.js +0 -480
- package/src/gep/validator/stakeBootstrap.js +0 -357
- package/src/gep/workspaceKeychain.js +0 -1
- package/src/ops/cleanup.js +0 -80
- package/src/ops/commentary.js +0 -60
- package/src/ops/health_check.js +0 -104
- package/src/ops/index.js +0 -11
- package/src/ops/innovation.js +0 -67
- package/src/ops/lifecycle.js +0 -798
- package/src/ops/self_repair.js +0 -76
- package/src/ops/skills_monitor.js +0 -147
- package/src/ops/trigger.js +0 -33
- package/src/proxy/clientSettings.js +0 -405
- package/src/proxy/envelope.js +0 -59
- package/src/proxy/extensions/dmHandler.js +0 -45
- package/src/proxy/extensions/sessionHandler.js +0 -141
- package/src/proxy/extensions/skillUpdater.js +0 -64
- package/src/proxy/extensions/traceControl.js +0 -1
- package/src/proxy/index.js +0 -1395
- package/src/proxy/inject.js +0 -1
- package/src/proxy/lifecycle/manager.js +0 -1568
- package/src/proxy/mailbox/state.js +0 -207
- package/src/proxy/mailbox/store.js +0 -602
- package/src/proxy/router/cache_passthrough.js +0 -26
- package/src/proxy/router/features.js +0 -84
- package/src/proxy/router/gemini_route.js +0 -154
- package/src/proxy/router/messages_route.js +0 -535
- package/src/proxy/router/model_router.js +0 -113
- package/src/proxy/router/models_route.js +0 -52
- package/src/proxy/router/ollama_route.js +0 -103
- package/src/proxy/router/responses_route.js +0 -170
- package/src/proxy/router/vertex_route.js +0 -110
- package/src/proxy/server/http.js +0 -363
- package/src/proxy/server/routes.js +0 -558
- package/src/proxy/server/settings.js +0 -115
- package/src/proxy/sync/engine.js +0 -179
- package/src/proxy/sync/inbound.js +0 -211
- package/src/proxy/sync/outbound.js +0 -361
- package/src/proxy/task/monitor.js +0 -131
- package/src/proxy/trace/extractor.js +0 -1
- package/src/proxy/trace/usage.js +0 -1
- package/src/solo/breaker.js +0 -25
- package/src/solo/gitGuard.js +0 -65
- package/src/webui/client/clientJs/assets.js +0 -111
- package/src/webui/client/clientJs/bootstrap.js +0 -92
- package/src/webui/client/clientJs/common.js +0 -77
- package/src/webui/client/clientJs/i18n.js +0 -366
- package/src/webui/client/clientJs/index.js +0 -35
- package/src/webui/client/clientJs/interactions.js +0 -351
- package/src/webui/client/clientJs/overview.js +0 -152
- package/src/webui/client/clientJs/personality.js +0 -285
- package/src/webui/client/clientJs/pipelines.js +0 -330
- package/src/webui/client/indexHtml.js +0 -221
- package/src/webui/client/static.js +0 -23
- package/src/webui/client/stylesCss.js +0 -639
- package/src/webui/client/vendor/README.md +0 -15
- package/src/webui/client/vendor/echarts.min.js +0 -45
- package/src/webui/index.js +0 -14
- package/src/webui/observer/assets.js +0 -146
- package/src/webui/observer/index.js +0 -37
- package/src/webui/observer/interactions.js +0 -127
- package/src/webui/observer/jsonl.js +0 -75
- package/src/webui/observer/paths.js +0 -46
- package/src/webui/observer/personality.js +0 -43
- package/src/webui/observer/pipelineEvents.js +0 -58
- package/src/webui/observer/redact.js +0 -63
- package/src/webui/observer/runs.js +0 -356
- package/src/webui/observer/safety.js +0 -57
- package/src/webui/observer/skills.js +0 -70
- package/src/webui/observer/status.js +0 -71
- package/src/webui/server/http.js +0 -138
- 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
|
-
};
|