@evomap/evolver 1.89.0 → 1.89.2

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 (98) hide show
  1. package/CONTRIBUTING.md +19 -0
  2. package/README.ja-JP.md +9 -32
  3. package/README.ko-KR.md +9 -32
  4. package/README.md +530 -86
  5. package/README.zh-CN.md +4 -31
  6. package/SKILL.md +1 -1
  7. package/assets/cover.png +0 -0
  8. package/index.js +14 -1
  9. package/package.json +17 -6
  10. package/scripts/a2a_export.js +63 -0
  11. package/scripts/a2a_ingest.js +79 -0
  12. package/scripts/a2a_promote.js +118 -0
  13. package/scripts/analyze_by_skill.js +121 -0
  14. package/scripts/build_binaries.js +479 -0
  15. package/scripts/check-changelog.js +166 -0
  16. package/scripts/extract_log.js +85 -0
  17. package/scripts/generate_history.js +75 -0
  18. package/scripts/gep_append_event.js +96 -0
  19. package/scripts/gep_personality_report.js +234 -0
  20. package/scripts/human_report.js +147 -0
  21. package/scripts/recall-verify-report.js +234 -0
  22. package/scripts/recover_loop.js +61 -0
  23. package/scripts/seed-merchants.js +91 -0
  24. package/scripts/suggest_version.js +89 -0
  25. package/scripts/validate-modules.js +38 -0
  26. package/scripts/validate-suite.js +78 -0
  27. package/skills/index.json +14 -0
  28. package/src/evolve/guards.js +1 -721
  29. package/src/evolve/pipeline/collect.js +1 -1283
  30. package/src/evolve/pipeline/dispatch.js +1 -421
  31. package/src/evolve/pipeline/enrich.js +1 -434
  32. package/src/evolve/pipeline/hub.js +1 -319
  33. package/src/evolve/pipeline/select.js +1 -274
  34. package/src/evolve/pipeline/signals.js +1 -206
  35. package/src/evolve/utils.js +1 -264
  36. package/src/evolve.js +1 -350
  37. package/src/forceUpdate.js +105 -20
  38. package/src/gep/a2aProtocol.js +1 -4395
  39. package/src/gep/autoDistillConv.js +1 -205
  40. package/src/gep/autoDistillLlm.js +1 -315
  41. package/src/gep/candidateEval.js +1 -92
  42. package/src/gep/candidates.js +1 -198
  43. package/src/gep/contentHash.js +1 -30
  44. package/src/gep/conversationSniffer.js +1 -266
  45. package/src/gep/crypto.js +1 -89
  46. package/src/gep/curriculum.js +1 -163
  47. package/src/gep/deviceId.js +1 -218
  48. package/src/gep/envFingerprint.js +1 -118
  49. package/src/gep/epigenetics.js +1 -31
  50. package/src/gep/execBridge.js +1 -711
  51. package/src/gep/explore.js +1 -289
  52. package/src/gep/hash.js +1 -15
  53. package/src/gep/hubFetch.js +1 -359
  54. package/src/gep/hubReview.js +1 -207
  55. package/src/gep/hubSearch.js +1 -526
  56. package/src/gep/hubVerify.js +1 -306
  57. package/src/gep/learningSignals.js +1 -89
  58. package/src/gep/memoryGraph.js +1 -1374
  59. package/src/gep/memoryGraphAdapter.js +1 -203
  60. package/src/gep/mutation.js +1 -203
  61. package/src/gep/narrativeMemory.js +1 -108
  62. package/src/gep/oauthLogin.js +34 -0
  63. package/src/gep/openPRRegistry.js +1 -205
  64. package/src/gep/personality.js +1 -423
  65. package/src/gep/policyCheck.js +1 -599
  66. package/src/gep/prompt.js +1 -836
  67. package/src/gep/recallInject.js +1 -409
  68. package/src/gep/recallVerifier.js +1 -318
  69. package/src/gep/reflection.js +1 -177
  70. package/src/gep/selector.js +1 -602
  71. package/src/gep/skillDistiller.js +1 -1294
  72. package/src/gep/skillPublisher.js +1 -1
  73. package/src/gep/solidify.js +1 -1699
  74. package/src/gep/strategy.js +1 -136
  75. package/src/gep/tokenSavings.js +1 -88
  76. package/src/gep/workspaceKeychain.js +1 -174
  77. package/src/proxy/extensions/traceControl.js +1 -99
  78. package/src/proxy/inject.js +1 -52
  79. package/src/proxy/lifecycle/manager.js +2 -0
  80. package/src/proxy/trace/extractor.js +1 -534
  81. package/src/proxy/trace/usage.js +1 -105
  82. package/.cursor/BUGBOT.md +0 -182
  83. package/.env.example +0 -68
  84. package/.git-commit-guard-token +0 -1
  85. package/.github/CODEOWNERS +0 -63
  86. package/.github/ISSUE_TEMPLATE/good_first_issue.md +0 -23
  87. package/.github/pull_request_template.md +0 -45
  88. package/.github/workflows/test.yml +0 -75
  89. package/CHANGELOG.md +0 -1123
  90. package/README.public.md +0 -594
  91. package/SECURITY.md +0 -108
  92. package/assets/gep/events.jsonl +0 -3
  93. package/examples/atp-consumer-quickstart.md +0 -100
  94. package/examples/hello-world.md +0 -38
  95. package/proxy-package.json +0 -39
  96. package/public.manifest.json +0 -141
  97. /package/assets/gep/{genes.json → genes.seed.json} +0 -0
  98. /package/{bundled-skills → skills}/_meta/SKILL.md +0 -0
package/CHANGELOG.md DELETED
@@ -1,1123 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to `@evomap/evolver` are tracked here.
4
-
5
- ## [Unreleased]
6
-
7
- ## [1.88.3] - 2026-06-06
8
-
9
- ### Fixed
10
-
11
- - **Gene selection no longer self-reinforces a do-nothing gene (#562).** A `stable_no_error`
12
- outcome (no error before/after, no parseable EvolutionEvent — i.e. the cycle did nothing
13
- measurable) was tallied as a Bayesian "success" (score 0.6). Combined with the selector's
14
- drift only diversifying when >1 gene matches a signal, a sole-matching auto-gene was
15
- re-selected every `--loop` cycle, climbed edge confidence p → ~1.0, and was never banned
16
- (the failure-streak ban never trips on "successes") — dominating selection ~99.7% of the
17
- time while producing zero artifacts (reproduced against the published 1.88.2 binary).
18
- `aggregateEdges` now tallies these zero-work outcomes as `inert`, apart from real
19
- successes, so they build no confidence and do not count toward attempts; `getMemoryAdvice`
20
- bans a gene after `GENE_INERT_BAN_STREAK` (default 8, env `EVOLVER_GENE_INERT_BAN_STREAK`)
21
- consecutive inert outcomes with no real success on a signal, so the selector falls through
22
- to mutation (null → fresh gene) and diversity is restored. Consecutive-trailing (reset by
23
- any real success/failure), so a gene that ever does real work is never punished for idle
24
- cycles.
25
- - **Self-evolution validation commands no longer fail a cycle in user projects (#562).** Seed
26
- genes (e.g. `gene_gep_repair_from_errors`) ship validation commands that target evolver's
27
- own tree (`node scripts/validate-modules.js ./src/gep/...`). When evolver runs in a user's
28
- project, `repoRoot` is that project and the script is absent, so the command could only fail
29
- with "Cannot find module" — wrongly tanking the gene's `validation_pass_rate` on an
30
- environment mismatch. `runValidationsOnce` now skips a `node <script>` validation command
31
- whose (relative) script does not exist in `repoRoot`, excluding it from the pass/fail tally
32
- instead of failing the cycle; a clear `[Solidify] Skipping validation command (script not in
33
- repoRoot)` line replaces the confusing module-not-found error. Commands whose script is
34
- present run unchanged.
35
-
36
- ## [1.88.2] - 2026-06-05
37
-
38
- ### Added
39
-
40
- - **Runtime asset injection — general agents auto-use GEP assets (P4-c, #183).** Recall now injects relevant genes/capsules into the runtime context so a general agent picks them up without an explicit `gep_recall` call.
41
- - **Cross-harness auto-exec recipes for Codex + OpenCode (P4-b, #184).** The Brain→Hand exec bridge gains Codex and OpenCode recipes, bringing cross-harness parity to the Claude Code bridge shipped in 1.88.1.
42
- - **Reuse attribution reporting (P4-a Slice A, #186, default off).** Evolver can report which prior assets a successful cycle reused. Opt-in; no behavior change at default.
43
- - **Heartbeat reports `force_update` outcome via the `last_update` field (#188).** The heartbeat loop now surfaces the result of a force-update so the Hub can observe update propagation.
44
- - **Evolver recipe CLI — build/reuse (P4-c, #173).** New `recipe` subcommand for building and reusing recipes.
45
-
46
- ### Changed
47
-
48
- - **ATP now consumes `@evomap/atp-sdk` for protocol enum constants (#185).** Protocol enums come from the shared SDK singleton instead of local literals, removing a drift source.
49
-
50
- ### Fixed
51
-
52
- - **Release gate regressions (#192, #193).** `pre_publish_check.js` now runs the project's real test runner (`npm test` → `node --test`) instead of the no-longer-installed `npx vitest run`, and fails closed on a non-zero exit (the old `0-failures-but-nonzero-exit ⇒ pass` escape hatch is gone). The `npm test` script also no longer hard-codes the stable `--test-isolation=process` flag (Node >=22.23 only); process isolation is Node's default, so `npm test` runs on Node 22.12+ again. Plus: `normalizeWorkspaceIdentity` canonicalizes transcript paths via `realpath` (macOS `/var` vs `/private/var`) before provenance comparison, and session-start Kiro dedup vs. non-git-notice throttling now use separate state files.
53
- - **ATP order/task association persisted + delivered bundle made GEP-valid (#187).**
54
- - **Proxy forwards asset search as `GET /a2a/assets/search` (#189).**
55
-
56
- ## [1.88.1] - 2026-06-03
57
-
58
- ### Added
59
-
60
- - **Conversation capability -> distilled gene (P2, `EVOLVER_CONV_DISTILL_ENABLED`).** The conversation sniffer's capability candidates (slug + transcript snippet) are now enqueued and, when enabled, run through a conversation-brief distillation (reusing the P3 claude-distill recipe + quality gate) to synthesize a candidate gene — closing discover->distill. **Shadow-only v1**: it logs a candidate gene for human review and NEVER upserts (conversation-snippet material is too thin to auto-upsert; run-green proves the validation command exits 0, not that the strategy is correct). New module `src/gep/autoDistillConv.js`; P2-owned `conv_distill.{by_hash,by_slug}` state (never the shared P3 scalars). New env: `EVOLVER_CONV_DISTILL_ENABLED` (off default), `CONV_SLUG_COOLDOWN_MS`, `EVOLVER_CONV_DISTILL_DUP_JACCARD`.
61
-
62
- ### Added
63
-
64
- - **Autonomous LLM gene distillation (P3, `EVOLVER_AUTO_DISTILL_LLM`).** evolver can now
65
- run the distillation prompt through a light read-only headless `claude` (reusing the P1
66
- exec bridge) and turn a recurring success pattern into an LLM-quality Gene with no human in
67
- the loop. New module `src/gep/autoDistillLlm.js`. Shadow-first: `off` (default) | `shadow`
68
- (log candidate, no upsert) | `enforce` (upsert after a REAL quality gate). The gate:
69
- structural validation + canonicalization, a Jaccard near-duplicate check, validation
70
- normalization (strips policy-blocked + heavy test-suite commands, injects `node --version`),
71
- and a run-green check (the gene's own validation must exit 0) BEFORE upsert. Never
72
- auto-publishes (opt-in `EVOLVER_AUTO_DISTILL_LLM_PUBLISH=true`). New env:
73
- `EVOLVE_DISTILL_MODEL`, `EVOLVE_DISTILL_MAX_TURNS`, `EVOLVE_DISTILL_TIMEOUT_MS`,
74
- `EVOLVE_DISTILL_VALIDATION_TIMEOUT_MS`, `EVOLVER_AUTO_DISTILL_LLM_DUP_JACCARD`. Wired into
75
- the daemon idle (OMLS) distill window next to the failure distiller; no behavior change at `off`.
76
-
77
- ### Added
78
-
79
- - **Claude Code auto-exec bridge (`exec` subcommand).** `node index.js exec
80
- --harness=claude-code [--once] [--max-cycles N]` closes the Brain→Hand loop on
81
- Claude Code: it runs the Brain (`node index.js run`, bridge mode), scrapes the
82
- emitted `sessions_spawn(...)`, and spawns a headless `claude -p` Hand to apply
83
- the patch and run `solidify`, gated by a status file. Opt-in and shadow-first:
84
- refuses unless `EVOLVE_EXEC_BRIDGE=true`. New module `src/gep/execBridge.js`
85
- with a per-Hand hard timeout + process-group kill (a hung/permission-broken
86
- Hand is killed and counts as a retry, never loops). The `exec` command exits
87
- non-zero on any non-success outcome. New env: `EVOLVE_EXEC_BRIDGE`,
88
- `EVOLVE_HAND_TIMEOUT_MS`, `EVOLVE_BRAIN_TIMEOUT_MS`, `EVOLVE_HAND_KILL_GRACE_MS`,
89
- `EVOLVE_IDLE_SLEEP_MS`, `EVOLVE_HAND_MAX_BUF_BYTES`, `EVOLVE_HAND_MODEL`,
90
- `EVOLVE_HAND_MAX_TURNS`, `EVOLVE_HAND_DANGEROUS`, `EVOLVE_EXEC_FALLBACKS`,
91
- `CLAUDE_BIN`.
92
-
93
- ## [1.88.0] - 2026-06-03
94
-
95
- ### Added
96
-
97
- - **Three-platform daemon autostart templates (#163).** The repo now ships
98
- install helpers so the evolver daemon auto-starts on login with
99
- restart-on-failure on every supported OS, matching the existing Linux
100
- `scripts/evolver.service`: a macOS launchd agent
101
- (`scripts/com.evomap.evolver.plist`, `KeepAlive.SuccessfulExit=false` +
102
- `ThrottleInterval=5` mirroring `Restart=on-failure`/`RestartSec=5`) and a
103
- Windows Task Scheduler installer (`scripts/install-evolver-windows.ps1`,
104
- `-Install`/`-Uninstall`). The Windows shape launches via a generated
105
- `%LOCALAPPDATA%\EvoMap\evolver-task-launcher.vbs` with the node/index paths
106
- baked into VBScript literals and a single quoted task argument — `wscript`
107
- is a Windows-subsystem host so no console window appears on logon, and
108
- `WScript.Quit rc` propagates the exit code so restart-on-failure still
109
- observes failures. All three platforms verified live through a real reboot.
110
-
111
- - **Explicit signal injection channel (#172).** A new fifth injection point in
112
- the signal-extraction stage reads user-declared signals from
113
- `<gep-assets-dir>/pending_signals.json` (default `.evolver/gep`) and merges
114
- them into a cycle's signal set, **bypassing the closed 20-entry
115
- `OPPORTUNITY_SIGNALS` vocabulary**. This lets a human-verified, deterministic
116
- capability whose intent no extractor can name still surface as a first-class
117
- signal (previously it collapsed to a generic `capability_gap` and the
118
- dedicated gene could never win selection). File-locked read-once semantics
119
- (`consumePendingSignals()` empties the file after consuming), entries trimmed
120
- and length-capped (≤200), non-fatal on error.
121
-
122
- ### Fixed
123
-
124
- - **Heartbeat / daemon-survival resilience overhaul (#163).** A nine-round
125
- hardening pass on the keep-alive path, addressing failure modes that left the
126
- daemon silently dead after sleep/wake, idle windows, or concurrent startup:
127
- - **Singleton-lock takeover is now atomic** — `linkSync` + `EEXIST` guard
128
- (with a `renameSync` fallback for link-less filesystems), replacing a
129
- non-atomic `unlinkSync`-before-write that could let two daemons both
130
- "own" the lock and rotate `node_secret` against each other into mutual
131
- 401 backoff. Lock carries a `lease: true` marker + mtime refreshed every
132
- `LOCK_REFRESH_MS` (60s Win / 120s POSIX); takeover honors both dead-PID
133
- (`kill -0` ESRCH) and lease-expiry (mtime older than `STALE_LOCK_TTL_MS`,
134
- 3min Win / 5min POSIX).
135
- - **sd_notify watchdog actually fires under systemd** — shells out to
136
- `systemd-notify(1)` (with `NotifyAccess=all`) instead of the broken
137
- `dgram` `unix_dgram` socket, which threw synchronously on node ≥22 and
138
- swallowed `READY=1`/`WATCHDOG=1`.
139
- - **Sleep/wake recovery** — a wall-clock drift detector (samples `Date.now()`
140
- every 30s, treats a >90s jump as a wake; `cpuUsage`-vs-wall-clock delta
141
- distinguishes CPU throttling from true sleep), a stuck-tick watchdog, and a
142
- wake ritual that drains dead undici connections + restarts SSE.
143
- - **Event-loop anchor + process survival** — a ref'd 10-min keepalive tick
144
- keeps the loop alive and leaves a disk heartbeat for `checkHealth`; EPIPE is
145
- swallowed without killing the process while non-EPIPE stream errors are
146
- still surfaced; `unhandledRejection` consults live heartbeat stats rather
147
- than killing a process mid-recovery.
148
- - **Hostile-hub clamps** — 429 backoff capped at 30min so a malicious/buggy
149
- `retry_after_ms: 86_400_000` can't silence the heartbeat for a day;
150
- `unknown_node` anti-cache-poisoning backoff with an absolute deadline.
151
- - Removed a dead wake hook in `evolver-signal-detect.js` whose relative
152
- `require` was `MODULE_NOT_FOUND` in the deployed `.claude/hooks/` layout
153
- (no in-proc consumer exists, so it was a no-op that cost a 3000-line
154
- `require` on every IDE edit).
155
-
156
- - **Standalone-binary obfuscation retries (#171).** Default `OBF_MAX_ATTEMPTS`
157
- raised 4 → 12 in `scripts/build_binaries.js`. The obfuscation step
158
- occasionally mangles `new.target` into an illegal `#target` token,
159
- non-deterministically across node processes; the seed-perturbation retry is
160
- the right mechanism but 4 attempts wasn't enough (the v1.87.4 deploy hit 4/4
161
- and aborted before npm publish + binary upload). No behavior change on
162
- first-attempt success; env override unchanged.
163
-
164
- ## [1.87.4] - 2026-06-02
165
-
166
- ### Fixed
167
-
168
- - **Cursor hook compatibility: project-dir resolution, workspace-scoped
169
- recall, FS workspace-id, and a non-git notice.** The session hooks assumed
170
- `process.cwd()` was the user's project, but Cursor invokes some hook events
171
- with cwd set to the plugin install dir — so `git diff` collection found
172
- nothing and the session-end hook silently recorded every task as empty.
173
- Hooks now resolve the workspace from `CURSOR_PROJECT_DIR` /
174
- `CLAUDE_PROJECT_DIR` (falling back to cwd, a no-op on Codex/opencode/Kiro/CLI).
175
- Session-start recall is now scoped to the current workspace *before* the
176
- most-recent-N window (a shared user-level memory graph no longer leaks one
177
- project's outcomes into another), with a bounded scan instead of parsing the
178
- whole graph. `workspace_id` now resolves via an FS-only fallback when the
179
- evolver package isn't reachable (plugin-only installs), writing the same
180
- `<workspaceRoot>/.evolver/workspace-id` secret `paths.getWorkspaceId()` uses
181
- so installing the package later is seamless. And a non-git folder now gets a
182
- throttled one-line notice that evolution memory is inactive, instead of
183
- failing silently. (Ported from public PRs #554/#555/#557/#558, each
184
- Bugbot-reviewed; private-dev #169/#170.)
185
-
186
- ## [1.87.3] - 2026-06-01
187
-
188
- ### Changed
189
-
190
- - **Router surfaces degenerate tier config + README defaults corrected
191
- (#152).** The shipped `DEFAULT_TIER_MODELS` pins all three tiers to
192
- `opus-4-7` on purpose (operators run tier-uniform while tuning tier
193
- mapping, so the no-downgrade guard stays inert and 5xx retries replay one
194
- model — PR #135). The trap is a user who flips `EVOMAP_ROUTER_ENABLED=1`
195
- expecting the cost savings the README advertised but leaves the per-tier
196
- `EVOMAP_MODEL_*` overrides unset: every turn resolves to the same model,
197
- so routing is a silent no-op (and a cost *increase* for anyone previously
198
- on a cheaper model). The proxy now emits a one-time `router_degenerate_tiers`
199
- WARN at startup when the router is enabled and all three tiers resolve to a
200
- single model. The README tier table previously documented `haiku-4-5` /
201
- `sonnet-4-6` / `opus-4-7` as the defaults — that was the pre-#135 mapping
202
- and never matched the shipped binary; it now states the real all-`opus-4-7`
203
- defaults and how to set the overrides to get tier-based saving.
204
-
205
- ### Fixed
206
-
207
- - **Heartbeat loop resilience (#544).** The heartbeat loop in
208
- `LifecycleManager` had two coupled defects: (1) `heartbeat()` called
209
- `store.countPending`, `getTaskMeta`, `_getEnvFingerprint`, and
210
- `hello()` *before* its `try` block, so a single throw from any
211
- helper (e.g. corrupt store, sandboxed `os.userInfo()`) escaped as a
212
- rejected promise; (2) `tick()` had no try/catch around
213
- `await this.heartbeat()`, so that rejection cancelled the next
214
- `setTimeout` and silently killed the loop until the process
215
- restarted. Reporter saw "first heartbeat fine, then evolver dead
216
- after machine sleep — restart required". Fix wraps the entire
217
- `heartbeat()` body in try/catch, defensively wraps the awaited call
218
- in `_heartbeatTick()`, lowers the consecutive-failure backoff cap
219
- from 30min → 15min (must stay above the 6min default interval or
220
- exponential backoff inverts), and adds a generation counter so
221
- `pokeHeartbeatLoop()` can't fork the loop when called while a tick
222
- is mid-await. New `pokeHeartbeatLoop()` resets the schedule on
223
- demand for callers that want wake-on-event semantics.
224
-
225
- ## [1.87.2] - 2026-05-27
226
-
227
- ### Added
228
-
229
- - **ATP autoBuyer explicit consent surface (#141).** New
230
- `evolver atp <enable|disable|status>` subcommands, an ack file at
231
- `<memory>/atp-autobuy-ack.json`, and an env override
232
- `EVOLVER_ATP_AUTOBUY=on|off`. Resolution order at runtime:
233
- env > ack > default. `setConsent` writes are atomic (tmp + rename);
234
- ack reads strictly validate `enabled: boolean` so a corrupted ack
235
- cannot park a user in a "prompt suppressed + autoBuyer off" dead
236
- zone. CLI enable/disable surfaces a loud WARN when an
237
- `EVOLVER_ATP_AUTOBUY` env value would silently override the ack at
238
- runtime. The cold-start `no_consent` daemon WARN sanitizes the hub
239
- URL via `URL.origin` so basic-auth credentials in
240
- `A2A_HUB_URL`/`EVOMAP_HUB_URL` are never written to logs.
241
-
242
- ### Changed
243
-
244
- - **ATP autoBuyer default ON for new installs (#145, follow-up to
245
- #141).** With no env override and no ack, `getConsent()` returns
246
- `{enabled: true, source: 'default'}` and autoBuyer starts under the
247
- daily/per-order caps (50/day, 10/order; cold-start half-cap for the
248
- first 5 min). The first-run TTY prompt and `evolver atp disable` are
249
- the explicit opt-out paths; ack='disable' continues to win over the
250
- default. Daemon/hook/CI cold-start emits a one-time WARN naming the
251
- hub origin, the active caps, and the `evolver atp disable` command,
252
- so the policy is never silent.
253
-
254
- - **`recallVerifier` default off (#136).** Client-side publish→recall
255
- round-trip verification is no longer enabled by default. The Hub
256
- `/a2a/fetch` contract is strict (unknown asset_id → empty results,
257
- 0 cost; verified against current Hub via random-hash smoke). The
258
- round-trip check was redundant as a default safety net. Operators
259
- who want end-to-end SLA observability can opt in with
260
- `EVOLVE_RECALL_VERIFY=1`. No code paths removed; only the default
261
- changed. (Already shipped in 1.87.1; promoted here for the
262
- main-line changelog.)
263
-
264
- ### Fixed
265
-
266
- - **Proxy router: canonicalize Bedrock model IDs + skip alias-only
267
- 5xx retry (#135) and defense-in-depth canonicalize at proxy
268
- boundary (#139).** Resolves model alias drift between Anthropic-
269
- shaped requests and Bedrock model IDs at both the router selection
270
- layer and the inbound proxy boundary so a 5xx that originated from
271
- an unresolvable alias is not retried as a different alias of the
272
- same family.
273
-
274
- - **`forceUpdate` preserves `.env`, `.env.local`, `USER.md`, and
275
- `.evolver/` (#142).** Force-update no longer wipes operator
276
- configuration or workspace identity files when overlaying a new
277
- release on top of an existing install; the preserve-list is now
278
- explicit and tested.
279
-
280
- - **`evolver setup-hooks` ships `_memoryFiltering.js` alongside
281
- generated session hooks (#547, #140, #143).** A fresh
282
- `setup-hooks --platform=codex` against `@evomap/evolver@1.87.0`
283
- left `.codex/hooks/` without `_memoryFiltering.js`, so the
284
- generated `evolver-session-start.js` crashed immediately with
285
- `Error: Cannot find module './_memoryFiltering'`.
286
- `copyHookScripts`/`removeHookScripts` now include the helper, and
287
- a regression suite scans every `require('./_*')` in the source
288
- adapter scripts to assert install + uninstall cover them.
289
- Reproduced by @rendigua on Windows; community PR
290
- EvoMap/evolver#550 from @mvanhorn arrived independently with the
291
- same diagnosis on the same day. (Already shipped in 1.87.1;
292
- promoted here for the main-line changelog.)
293
-
294
- ### Docs
295
-
296
- - **Backfill 1.87.0 proxy track + publish-gate entries
297
- (#122/#123/#127/#128/#131/#132) into the changelog (#138).**
298
-
299
- ## [1.87.1] - 2026-05-27
300
-
301
- Released from `release/1.87.1`; superseded by 1.87.2. Contains
302
- #136 and #547/#140 (see above).
303
-
304
- ## [1.87.0] - 2026-05-26
305
-
306
- ### Added
307
-
308
- - **AWS Bedrock as a second upstream for `/v1/messages` (#122).** Selected
309
- per-request via `EVOMAP_UPSTREAM=bedrock` (default `anthropic`,
310
- byte-for-byte unchanged). The Bedrock path pulls `body.model` →
311
- `modelId`, injects `anthropic_version: bedrock-2023-05-31`, strips
312
- top-level `model` / `stream` (Bedrock 400s on unknown fields, infers
313
- stream from the command type), and re-emits Bedrock chunks as standard
314
- SSE `data: <json>\n\n` so SDK callers don't need to know which upstream
315
- served the request; the SDK owns SigV4 + AWS event-stream decoding.
316
- `messages_route.js` softens the Anthropic-shaped credential check under
317
- `EVOMAP_UPSTREAM=bedrock` (proxy's real gate is still the Bearer
318
- `proxy_token` enforced in `ProxyHttpServer`; with SigV4 + AWS_* env in
319
- place, inbound `x-api-key` is meaningless on the Bedrock path). This
320
- unblocks Phase C cheap%-verification for deployments that don't have an
321
- Anthropic-account upstream available — see PR #127 for the CC v2.1.150
322
- body-shape compatibility patch that lands on top.
323
-
324
- - **Optional OS-keychain backing for `workspace-id` (issue #111 Phase 1).**
325
- `getWorkspaceId()` can now persist the per-workspace secret in the OS
326
- keychain (`@napi-rs/keyring` optional dep — macOS Keychain Services /
327
- libsecret on Linux / Windows Credential Manager) instead of leaving it
328
- readable to any same-uid process via `<workspace>/.evolver/workspace-id`.
329
- Mode is controlled by `EVOLVER_WORKSPACE_KEYCHAIN`:
330
- - `auto` (default) — try keychain, fall back to FS on any failure.
331
- Behaviour stays IDENTICAL to v1.85.x for any deployment that
332
- doesn't install the optional addon.
333
- - `force` — keychain only; throws if `@napi-rs/keyring` isn't loaded.
334
- Use in CI to assert the addon is present.
335
- - `off` — skip keychain; FS only.
336
- When the addon is available, an existing FS secret is migrated into
337
- the keychain on first call and the FS file is INTENTIONALLY retained
338
- so bun-compiled binaries (which don't sideload the `.node` addon yet
339
- — Phase 2) keep agreeing with node-CLI sessions on the same id.
340
- `EVOLVER_WORKSPACE_ID` env override is unchanged and still wins.
341
-
342
- ### Fixed
343
-
344
- - **Codex / Claude transcript directories are auto-discovered without
345
- the `EVOLVER_CURSOR_TRANSCRIPTS_DIR` env (issue #543, PR #130 + #133).**
346
- Before this release, `readCursorTranscripts()` returned empty unless
347
- the operator had manually exported `EVOLVER_CURSOR_TRANSCRIPTS_DIR`,
348
- so a fresh Codex install reported `[NO SESSION LOGS FOUND]` on every
349
- cycle even though Codex was writing rollouts to `~/.codex/sessions/`.
350
- `resolveTranscriptDirs()` now picks up `~/.codex/sessions/` and
351
- `~/.claude/projects/` by default. Cross-project transcripts are
352
- filtered against the current workspace so a session recorded in a
353
- different repo can't leak into this review (Bugbot PR #130 Security
354
- MEDIUM, fail-closed when `session_meta.cwd` is missing). PR #133
355
- follow-up accepts multiple workspace identities (`WORKSPACE_ROOT`,
356
- `process.cwd()`, `EVOLVER_REPO_ROOT`) so the auto-discovery still
357
- works in a workspace whose cwd has no `.git` and `getRepoRoot()`
358
- would otherwise fall back to the evolver install dir. Together with
359
- the entry below, this closes the second half of issue #540 (#543's
360
- half).
361
-
362
- - **`user_missing` and `session_logs_missing` advisories now quiet down
363
- on a Codex install whose `memory_graph.jsonl` is accumulating but has
364
- no `outcome` records yet (issue #540 follow-up).** The original #540
365
- fix landed a three-tier fallback for the memory snippet
366
- (`MEMORY.md` → AGENTS.md marker section → memory_graph outcome tail),
367
- but `readUserSnippet` and `_sessionLogFallback` still required
368
- `_isOutcomeEntry`-shaped records to suppress their advisories. On a
369
- fresh Codex install the first few cycles only write `signal` /
370
- `hypothesis` / `attempt` / `epoch_boundary` entries — no
371
- `outcome.status` — so both advisories kept firing on every cycle
372
- even though the graph was visibly accumulating. Reported by
373
- @rendigua on issue #540 with a 1.86.0 fresh-install reproduction.
374
- The fix adds `readMemoryGraphAnyTail()` as a tier-2 fallback under
375
- the existing outcome-tail tier, so any parseable graph entry is
376
- enough to displace the legacy `[USER.md MISSING]` /
377
- `[NO SESSION LOGS FOUND]` placeholders that `gep/signals.js`
378
- (line 348-351) keys on. `readMemorySnippet`'s contract is unchanged
379
- (AGENTS.md marker still wins over the graph for the memory tier).
380
- PR #108 round-3 hardening (cross-workspace entries from the
381
- user-level shared graph must pass the `workspace_id` / `cwd` filter)
382
- applies to the new tier too.
383
-
384
- - **Lifecycle `hello` no longer mints a fresh `node_id` when MailboxStore
385
- is empty but `~/.evomap/node_id` still exists.** The legacy GEP path
386
- (`src/gep/a2aProtocol.js`) writes the node_id as a bare hex file at
387
- `~/.evomap/node_id`. Lifecycle's hello previously consulted only
388
- `MailboxStore.getState('node_id')` and fell straight through to
389
- `crypto.randomBytes(6)` when the store was unprimed. Any install whose
390
- `state.json` was created (or wiped) after the legacy file existed —
391
- e.g. an upgrade from a pre-lifecycle version, or a partial recovery
392
- flow — therefore registered a brand-new A2ANode under the same owner
393
- on the very next daemon boot, abandoning the original record (with
394
- its stake / reputation / aliases) as an orphan in the web UI.
395
- Reading `~/.evomap/node_id` between the store lookup and the random
396
- fallback keeps both paths agreeing on a single identity.
397
-
398
- - **Router refuses silent intra-family generational downgrades, and the
399
- default tier IDs are now Bedrock-resolvable (#132).** Two related
400
- router fixes that closed the same misconfiguration class. First, the
401
- `/v1/messages` rewrite path now refuses moves where the chosen tier
402
- model is strictly older than the caller's original within the same
403
- Claude family (opus / sonnet / haiku) — cross-family rewrites (the
404
- router's core cheap-tier behaviour, opus → haiku) stay allowed.
405
- Triggered by the 2026-05-25 `/compact` stall on Aurora, where
406
- `EVOMAP_MODEL_EXPENSIVE` was still pinned to `opus-4-1` while live
407
- traffic had moved to `opus-4-7`: every planning turn silently
408
- rewrote 4-7 → 4-1, hit Bedrock 5xx on the older endpoint, and
409
- absorbed the cost via the upstream-5xx retry path. The guard makes
410
- this **loud** (`router_fallback` with `reason: downgrade_blocked`)
411
- instead of silent. Second, `DEFAULT_TIER_MODELS` previously used
412
- short aliases that Bedrock rejects with `ValidationException: invalid
413
- model identifier`. Defaults are now:
414
- - `cheap = global.anthropic.claude-haiku-4-5-20251001-v1:0`
415
- - `mid = global.anthropic.claude-sonnet-4-6` (only `global.*`
416
- sonnet on Bedrock today)
417
- - `expensive = global.anthropic.claude-opus-4-7`
418
- The `mid = sonnet-4-6` choice is exactly the case the no-downgrade
419
- guard protects: a caller pinned to `sonnet-4-7` does not silently
420
- drop to 4-6 — the request stays on 4-7 and `downgrade_blocked` shows
421
- up in telemetry, so when Bedrock adds a `global.*` `sonnet-4-7`
422
- alias the regression becomes visible instead of invisible.
423
-
424
- - **Claude Code v2.1.150+ body shapes accepted by the Bedrock upstream
425
- (#127).** Three top-level fields CC v2.1.150 sends are accepted by
426
- Anthropic's public API but rejected by Bedrock InvokeModel with HTTP
427
- 400 — `thinking: { type: "adaptive" }` (Bedrock only takes
428
- `"enabled"` / `"disabled"`), `output_config: { effort }`, and
429
- `context_management: { ... }` (both rejected as extra inputs). Without
430
- this fix every CC v2.1.150 request through the proxy in Bedrock mode
431
- 400s, so the Phase C `EVOMAP_UPSTREAM=bedrock` data window introduced
432
- by #122 could not actually log completed `router_decision` events.
433
- `_proxyBedrock` now folds `thinking.type: "adaptive"` → `"enabled"`
434
- (defaulting `budget_tokens` to `max_tokens / 2` with a 1024 floor when
435
- CC omits it — adaptive mode lets the model pick, but Bedrock's
436
- `"enabled"` requires the field) and strips `output_config` and
437
- `context_management` before forwarding. The strip site sits next to
438
- the existing `body.stream` strip and carries an explicit comment that
439
- future CC schema additions will surface the same way (whole-request
440
- 400) and need to be added to the same list.
441
-
442
- - **`proxy.token` reuses across daemon restarts instead of rotating on
443
- every boot (#128).** `ProxyHttpServer.start()` previously called
444
- `crypto.randomBytes(32)` unconditionally, so any long-lived shell that
445
- had sourced `client-env.sh` once and was still exporting
446
- `ANTHROPIC_AUTH_TOKEN` 401'd against the proxy as soon as the daemon
447
- restarted. Aurora's typical mix (6+ `claude` processes across 4
448
- Cursor terminal panes spawned hours before the most recent restart)
449
- hit this on every daemon respawn. `start()` now reads `settings.json`
450
- *before* `clearIfStale()` wipes the prior `proxy` block: if a token is
451
- on disk, it's reused; a new one is minted only on first launch or
452
- after a clean `stop()` (which still calls `clearSettings`). The trust
453
- boundary is unchanged — the file remains mode 0600, the local
454
- `127.0.0.1` socket is still reachable by any same-uid process, and
455
- compromise recovery is `evox proxy stop` + `rm
456
- ~/.evolver/settings.json` + restart.
457
-
458
- - **Grace-token list survives daemon restart (#131).** `_handleRequest`
459
- now reads `proxy.previous_tokens` directly from `settings.json`
460
- (constant-time compare against the primary plus the extras) and
461
- `start()` preserves `previous_tokens` across restart instead of having
462
- the shallow `writeSettings({proxy:{...}})` merge silently drop the
463
- list. This replaces an earlier env-shim design
464
- (`EVOMAP_PROXY_EXTRA_TOKENS` populated by a python3 block in
465
- `evolver-daemon-start.sh`) that had three places to keep in sync.
466
- Recovery path: when `~/.evolver/settings.json` is wiped externally
467
- (logout / manual `rm`) while long-lived CC sessions still hold the
468
- pre-wipe bearer in their fork-time env, the operator pastes the lost
469
- token into `previous_tokens` and the proxy keeps accepting it until
470
- those sessions die naturally. Clean `stop()` still calls
471
- `clearSettings`, which intentionally drops the whole proxy block
472
- including `previous_tokens` — clean shutdown wipes grace state by
473
- design.
474
-
475
- ### Refactored
476
-
477
- - **Lifecycle's legacy node_id reader now routes through
478
- `paths.getEvomapPath()`** so `EVOLVER_HOME` honours both reader and
479
- writer in lockstep (the writer in `src/gep/a2aProtocol.js` was
480
- consolidated onto the same helper in #114). No behaviour change
481
- beyond unifying the override semantics.
482
-
483
- - **Seed genes opt three task classes into EvoX's
484
- `GeneRoutingHint`.** PR #384 on the EvoX side wired
485
- `Gene.routing_hint` through to the multi-tier router and reasoning
486
- selector, but the bundled defaults shipped with `routing_hint: null`
487
- for every gene — so `RouterDecision.reason="gene_hint"` never fired
488
- in production telemetry (0 of 131 turns in the first reduction logged
489
- in `evox/docs/concepts/router-lessons-learned-2026-05-18.md` §3).
490
- This release bumps three seed genes off the default per the guidance
491
- baked into the LLM prompt template (`src/gep/prompt.js:179-194`):
492
- `gene_tool_integrity` and `gene_distilled_s2g-env-vars` go to
493
- `cheap` + `low` (deterministic, mostly string/JSON manipulation), and
494
- `gene_gep_optimize_tool_usage` goes to `mid` + `medium` (light
495
- reasoning, no deep multi-step planning). The broader `repair` /
496
- `innovate` genes are intentionally left null so the router's
497
- classifier picks per-turn — matches the prompt's "Omit when the gene
498
- applies broadly across complexities" rule. Fully additive; installs
499
- whose local `genes.json` already overrides any of these IDs are not
500
- touched (the asset_id-respecting load path in `loadGenes()` keeps
501
- user customisations intact).
502
-
503
- ### Internal
504
-
505
- - **End-to-end test for the issue #540 advisory-signal fix and a new
506
- CHANGELOG release-section integrity guard (#113 / #115).** PR #105
507
- added function-level tests for `readMemorySnippet` /
508
- `readUserSnippet` / `readRealSessionLog`, but the user-visible bug
509
- was the three advisory signals firing on every Codex review cycle
510
- end-to-end. New tests run `collect.js` outputs through
511
- `gep/signals.js` to lock in that pipeline so a future refactor
512
- breaking the chain at either end fails loudly. The new
513
- `scripts/check-changelog.js` + `pre_publish_check.js` integration
514
- catches the #540 / PR #107 misattribution pattern (an entry filed
515
- under `## [X.Y.Z]` after vX.Y.Z was already published). `extractSection`
516
- is line-anchored so fenced code samples don't trigger false drift,
517
- and `EVOLVER_CHANGELOG_GUARD_SOFT=1` is available as an interim
518
- escape hatch while private-dev still relies on public-mirror tags.
519
-
520
- - **Publish-time enforcement for the #542 duplicate-import regression
521
- class (#123).** The regression test shipped with PR #118 (#542 hotfix)
522
- was claimed to keep duplicate `const path = require('path')` from
523
- reaching npm again. Auditing the gate showed the claim was incomplete:
524
- `pre_publish_check.js` Check #1 (`npx vitest run`) is non-functional on
525
- this project — every test file uses `require('node:test')`, so vitest
526
- exits non-zero with `No test suite found` regardless of whether the
527
- in-file tests pass, and publishers therefore set `SKIP_TESTS=1`. The
528
- duplicate-import class would silently slip through that path. This
529
- release adds `checkEntryPointScriptsParse`, a new pre-publish step
530
- that runs `node --check` directly on every `.js` in
531
- `src/adapters/scripts/` and on `index.js` (parse-only, ~50 ms per
532
- file, independent of the test-suite check, names the offending file
533
- and line in the failure message). It also collapses
534
- `test/adaptersSyntax.test.js`'s dynamic per-file `it()` loop into one
535
- static `it()` that iterates targets internally and reports every
536
- failing file in a single assertion message — now statically
537
- discoverable by both `node --test` and vitest's node:test compat
538
- layer. Scoped narrowly to making the #542 class unpublishable; the
539
- broader vitest-vs-node:test runner question is intentionally left
540
- alone.
541
-
542
- ## [1.85.3] - 2026-05-23
543
-
544
- ### Fixed
545
-
546
- - **Codex `session-end` hook no longer crashes with `SyntaxError: Identifier 'path' has already been declared` on fresh install.** A previous
547
- change added a top-of-file import block (`fs` / `path` / `os`) without
548
- noticing that an existing `const path = require('path')` further down
549
- the file was still in place. Both v1.85.1 AND v1.85.2 shipped to npm
550
- with this regression — every fresh `npm install -g @evomap/evolver`
551
- followed by `evolver setup-hooks --platform=codex` produced a
552
- parse-broken hook, blocking Codex `session-end` entirely with no
553
- user-side workaround (issue #542). The duplicate is removed and a
554
- `node --check` regression guard now runs against every file in
555
- `src/adapters/scripts/` and the CLI entry as part of the test suite,
556
- so this class of regression (parse-time failure in entry-point scripts
557
- that the existing vitest suite never loads) cannot reach npm again.
558
-
559
- - **`getRepoRoot()` no longer escapes `node_modules` to pick up an
560
- unrelated outer `.git` (#541).** When `@evomap/evolver` is installed
561
- globally (e.g. `npm install -g @evomap/evolver` on macOS Homebrew), the
562
- package lives at `/opt/homebrew/lib/node_modules/@evomap/evolver` and
563
- Homebrew itself is a git repository, so `/opt/homebrew/.git` exists.
564
- Whenever the user ran `evolver` from a directory that did *not* have a
565
- `.git` in any ancestor (a fresh project before `git init`, a generic
566
- `~/` shell, an OpenClaw workspace, etc.), the upward walk from the
567
- install location escaped the package's own `node_modules` and resolved
568
- `repoRoot` to `/opt/homebrew` — silently sending `workspaceRoot` /
569
- `memoryDir` / `evolutionDir` to a directory that doesn't belong to the
570
- user, scanning the wrong session logs, and producing evolution
571
- proposals for the wrong codebase. No crash; the only signal was that
572
- the output looked subtly wrong. The walk now stops at the parent of
573
- the nearest `node_modules` ancestor: for a local install
574
- (`<project>/node_modules/@evomap/evolver`) the boundary still includes
575
- `<project>` so the user's `.git` is found correctly; for a global
576
- install the boundary is `/opt/homebrew/lib`, which has no `.git`, so
577
- the walk falls back to the install dir instead of escaping. Dev
578
- clones (not inside any `node_modules`) keep the original unbounded
579
- walk. `EVOLVER_REPO_ROOT` remains the explicit override and is
580
- unaffected.
581
-
582
- ## [1.85.2] - 2026-05-22
583
-
584
- ### Fixed
585
-
586
- - **Daemon restart no longer overwrites the hub-rotated `node_secret` with a
587
- stale shell `A2A_NODE_SECRET`.** When a previous run rotated the secret
588
- via `/a2a/hello`, the hub-recognised value was persisted in
589
- `~/.evomap/mailbox/state.json`. On the next daemon boot, the parent shell
590
- still exported the *old* value of `A2A_NODE_SECRET` (a child process
591
- cannot mutate its parent's env). The `_resolveNodeSecret` env-vs-store
592
- reconciliation treated this as a conflict and let env win, silently
593
- overwriting the rotated secret. Subsequent heartbeats 403'd, rotation
594
- was correctly refused by the hub (issue #320 requires proof of current
595
- ownership), and the daemon entered a 30-minute backoff that could not
596
- be self-recovered. The store now carries a `node_secret_source` tag
597
- (`'hub_rotate'` for hub-issued values, `'env_seed'` otherwise);
598
- hub_rotate values win over a differing env, while #529's original
599
- case (env-fresh, store-stale, no source tag) still falls back to env.
600
-
601
- - **Re-auth backoff escalates exponentially.** Consecutive `reAuthenticate`
602
- failures now back off 30min → 60min → 2h, capped at ~4h, instead of
603
- resetting to a flat 30 minutes every time. A daemon stuck on a bad
604
- secret no longer gets re-poked twice an hour by inbound auth errors,
605
- drowning the log without surfacing the manual-recovery requirement.
606
-
607
- ### Added
608
-
609
- - **`evolver reset-local-secret` CLI helper.** Wipes the three local
610
- `node_secret` stores (MailboxStore, legacy `~/.evomap/node_secret`,
611
- and prints an unset hint for the shell `A2A_NODE_SECRET`) so a daemon
612
- whose hub side has been web-reset (`https://evomap.ai/account` →
613
- Reset Secret) can boot clean. New `docs/secret-recovery.md` documents
614
- the env-vs-store decision tree and the recovery flow.
615
-
616
- ## [1.85.1] - 2026-05-22
617
-
618
- ### Fixed
619
-
620
- - **Stop hook no longer re-injects the evolution receipt as a user prompt.**
621
- `evolver-session-end.js` previously emitted `followup_message`,
622
- `stopMessage`, and `additionalContext` on every session end. The
623
- `followup_message` field tells Claude Code to feed the receipt back into
624
- the next inference round, which caused agents to "respond" to their own
625
- evolution log line — visible to users as an unexplained extra reasoning
626
- turn after every task. The hook now emits only `systemMessage`, a
627
- UI-only notification that does not influence the next turn.
628
-
629
- - **Cursor compatibility for the Stop hook.** Cursor's Claude Code-compatible
630
- runtime currently treats `systemMessage` as a user prompt as well. The
631
- hook now detects Cursor (via `TERM_PROGRAM=cursor`, `CURSOR_TRACE_ID`,
632
- `CURSOR_SESSION_ID`, or the manual override `EVOLVER_HOOK_HOST=cursor`)
633
- and omits `systemMessage` there. The receipt is always appended to
634
- `~/.evolver/logs/evolution.log` (override path with
635
- `EVOLVER_HOOK_LOG_DIR`) so it is never silently lost. Set
636
- `EVOLVER_HOOK_VERBOSE=1` to force the inline notification on under
637
- Cursor for debugging.
638
-
639
- - **Stop hook process now exits promptly.** The hook held the event loop
640
- open for ~7 s on every session end because its watchdog `setTimeout`
641
- was never cleared after stdin closed. The watchdog is now cancelled and
642
- the process exits explicitly when the response is written.
643
-
644
- - **`evolver --review` no longer raises `memory_missing` / `user_missing` /
645
- `session_logs_missing` on every cycle when running on Codex (#540).**
646
- Codex doesn't generate a workspace-root `MEMORY.md` / `USER.md` and
647
- doesn't expose readable session-transcript files, so the review
648
- pipeline used to keep flagging those three advisory signals on every
649
- cycle. `src/evolve/pipeline/collect.js` now falls back, in order:
650
- `MEMORY.md` / `USER.md` → the `<!-- evolver-evolution-memory -->`
651
- section that `setup-hooks` injects into the workspace's `AGENTS.md` /
652
- `CLAUDE.md` → the tail of `memory_graph.jsonl` (last 5 outcomes).
653
- `readRealSessionLog()` reuses the same memory-graph tail when no
654
- platform session source has any data. Real markdown still wins, the
655
- marked-section fallback is gated on the evolver marker (a
656
- user-authored `AGENTS.md` without it is ignored), and the legacy
657
- `[MEMORY.md MISSING]` / `[USER.md MISSING]` / `[NO SESSION LOGS
658
- FOUND]` placeholders are still returned when nothing is available —
659
- which transitively suppresses the three advisory signals that
660
- `gep/signals.js` triggers on those literal strings. README adds a
661
- "Codex caveats" subsection documenting the platform limitation.
662
-
663
- ### Security
664
-
665
- - **Per-workspace random secret replaces plain-text `cwd` self-tag in
666
- `memory_graph.jsonl` (#109).** PR #108 introduced a `cwd` field on
667
- every memory-graph entry and used it to scope reads at the user-level
668
- fallback path (`~/.evolver/memory/evolution/memory_graph.jsonl`),
669
- closing the cross-workspace leak. The Cursor Bugbot round-3 advisory
670
- pointed out that `cwd` is a self-report — any process under the same
671
- uid could write entries claiming a different workspace's `cwd` and
672
- poison its reads. This release adds `paths.getWorkspaceId()`, which
673
- lazily creates `<workspace>/.evolver/workspace-id` (mode 0600,
674
- 32-hex random) and stamps every new entry with `workspace_id`. The
675
- reader uses a three-tier policy: prefer `workspace_id` matching when
676
- the current workspace has a secret, fall back to legacy `cwd`-only
677
- matching when it doesn't (clean upgrade for pre-existing entries),
678
- drop entries that have neither. The writer lazy-loads the canonical
679
- resolver from the project-local evolver install so writer + reader
680
- resolve from the same path. The secret file is created with
681
- `O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW` after an `lstat`
682
- pre-check, so a pre-placed symlink can't redirect the write outside
683
- the workspace. `EVOLVER_WORKSPACE_ID` env var remains as an explicit
684
- override / escape hatch. Note: in the co-uid threat model, any
685
- process under the same uid can still *read* the FS secret —
686
- migrating to an OS keychain is tracked in #111.
687
-
688
- ## [1.85.0] - 2026-05-22
689
-
690
- ### Fixed
691
-
692
- - **Hook scripts now record outcomes to a writable location even when no
693
- evolver-managed project directory is present (#536).** Previously,
694
- npm-global installs of evolver had no `memory/evolution/` directory under
695
- the install root, so `evolver-session-end.js` reported "nowhere (no Hub
696
- or local path)" when neither `EVOMAP_HUB_URL` nor a project-local
697
- evolver was configured. The shared `_runtimePaths.js` helper now falls
698
- back to `~/.evolver/memory/evolution/memory_graph.jsonl`, creating the
699
- directory on first write. The same helper teaches both
700
- `evolver-session-start.js` and `evolver-session-end.js` to resolve the
701
- package root via `require.resolve('@evomap/evolver/package.json')`,
702
- which works for npm-global installs where the relative `../../..` walk
703
- used to escape the package.
704
-
705
- - **`evolver-session-end.js` no longer prints "system cannot find the
706
- path" noise on Windows (#537).** The script now invokes git via
707
- `spawnSync('git', [...], { shell: false })` with an argv array, which
708
- avoids the POSIX `2>/dev/null` redirection that cmd.exe rejected. Failed
709
- invocations (e.g. `HEAD~1` in a fresh repo) are detected by reading
710
- `res.status` explicitly instead of relying on stderr suppression.
711
-
712
- - **`setup-hooks --uninstall` now cleans up everything it installed
713
- (#538).** Three regressions fixed:
714
- - The Codex adapter now removes the `codex_hooks = true` line from
715
- `~/.codex/config.toml` and drops the surrounding `[features]` block
716
- when it becomes empty. Other unrelated entries under `[features]` are
717
- preserved.
718
- - All four markdown-injecting adapters (Claude Code, Codex, Kiro,
719
- opencode) share a new `removeMarkedSection()` helper that correctly
720
- skips the evolver-owned `## Evolution Memory` heading before searching
721
- for the next H2. The previous inline implementations matched evolver's
722
- own heading and left the entire injected section in CLAUDE.md /
723
- AGENTS.md.
724
- - `removeHookScripts()` now `console.warn`s when an `unlink` fails so
725
- Windows file-locking and permission failures stop being silent.
726
- - The Claude Code and Codex `uninstall` paths filter evolver-owned
727
- entries by command match even when the `_evolver_managed` marker is
728
- missing (older installs, hand-edited files), preventing stale
729
- `evolver-session-*` entries from being stranded.
730
-
731
- - **`setup-hooks` no longer overwrites existing user hooks under the same
732
- event (#539).** The new `mergeWithHooksUnion()` keeps the user's own
733
- Stop / SessionStart / PostToolUse entries while adding (or refreshing)
734
- evolver-owned entries, identified by the command containing
735
- `evolver-session` or `evolver-signal`. Reinstalls refresh the evolver
736
- entry instead of duplicating it. This applies to both the matcher shape
737
- used by Claude Code (`{matcher, hooks: [{type, command}]}`) and the
738
- flat shape used by Codex (`{type, command}`).
739
-
740
- ### Removed
741
-
742
- - **`FEISHU_EVOLVER_INTERVAL` env fallback for `pendingSleepMs` is no longer
743
- read.** Part of removing all Feishu-specific naming from evolver core (#65).
744
- Users who relied on this variable to override the pending-sleep interval
745
- must switch to `EVOLVE_PENDING_SLEEP_MS` (or its older alias
746
- `EVOLVE_MIN_INTERVAL`); both are still honored and have been the
747
- documented names for this knob. Without an override, `pendingSleepMs`
748
- now falls back to its existing 120000 ms default.
749
-
750
- ### Security
751
-
752
- - **All Hub-facing HTTP calls now go through `hubFetch()`, a single
753
- chokepoint that enforces both `https://` schema and TLS certificate
754
- verification (C1 PR-2 Step 2).** `src/gep/hubFetch.js` rejects any URL
755
- that does not parse as `https://` and passes an explicit
756
- `undici.Agent({ connect: { rejectUnauthorized: true } })` as the request
757
- dispatcher, making certificate verification immune to
758
- `NODE_TLS_REJECT_UNAUTHORIZED=0`. Schema validation lives in `hubFetch`
759
- itself (not only in `resolveHubUrl()`) because several hot paths —
760
- `httpTransportSend`, `getHubUrl()`, per-call `opts.hubUrl`, proxy
761
- `this.hubUrl` — bypass `resolveHubUrl()` and read env vars directly;
762
- putting the check in the fetch wrapper means it cannot be bypassed.
763
- Affected modules: `a2aProtocol`, `hubVerify`, `directoryClient`,
764
- `hubReview`, `hubSearch`, `taskReceiver`, `skillPublisher`,
765
- `validator/index`, `validator/reporter`, `validator/stakeBootstrap`,
766
- `memoryGraph`, `proxy/lifecycle/manager`, `proxy/sync/inbound`,
767
- `proxy/sync/outbound` (14 modules, 22 call sites). New runtime
768
- dependency: `undici` (Node bundles it internally but its `Agent` is
769
- not interface-compatible with the global `fetch` — both must come from
770
- the same package, hence the explicit dep). Set
771
- `EVOMAP_HUB_ALLOW_INSECURE=1` to bypass BOTH protections (local dev /
772
- mock hub on http:// or self-signed cert; same gate as Step 1).
773
- Integration test in `test/hubFetch.test.js` spins up a real HTTPS
774
- server with a self-signed cert and asserts `hubFetch` rejects even
775
- when `NODE_TLS_REJECT_UNAUTHORIZED=0` is set globally — proving the
776
- documented attack is blocked end-to-end.
777
-
778
- - **`resolveHubUrl()` now rejects non-`https://` Hub URLs (C1 PR-2 Step 1).**
779
- Passing an `http://`, `ws://`, or unparseable string as `A2A_HUB_URL` /
780
- `EVOMAP_HUB_URL` / `EVOLVER_DEFAULT_HUB_URL` now throws at call time rather
781
- than silently returning a plaintext URL. This prevents a misconfigured or
782
- attacker-controlled env var from downgrading heartbeat + capsule-publish
783
- traffic to cleartext (MITM → arbitrary code injection via hub directives).
784
- Local dev and mock-hub integration tests that need a non-TLS endpoint must
785
- set `EVOMAP_HUB_ALLOW_INSECURE=1`; any value other than exactly `"1"` is
786
- treated as absent. Do **not** use `NODE_ENV` for this gate — it is not stable
787
- in `npm`-global installs.
788
-
789
- ### Changed
790
-
791
- - **`--loop` daemon now defaults `EVOLVE_BRIDGE` to `'true'` (#96).**
792
- The previous default of `'false'` made the daemon observe-only by
793
- default: every cycle hit `rejectPendingRun(reason=loop_bridge_disabled_autoreject_no_rollback)`
794
- and produced no `EvolutionEvent`. On Aurora this manifested as 33 days
795
- of empty cycling. With the new default, the daemon actually applies
796
- pending runs to the working tree. Failed cycles still recover safely
797
- via `rollbackTracked` (mode=stash by default since v1.81.0): the
798
- partial diff gets pushed to a stash entry the user can recover with
799
- `git stash list | grep evolver-rollback` followed by `git stash pop`.
800
- The daemon prints a safety banner on start documenting both the new
801
- default and the recovery breadcrumb. Set `EVOLVE_BRIDGE=false`
802
- explicitly to opt back into observe-only mode (the banner reflects
803
- this choice as well). Operators upgrading should be aware that
804
- `--loop` is no longer side-effect-free by default.
805
- - **Daemon-mode `unhandledRejection` handler uses a sliding window (5
806
- rejections within 5 minutes) instead of a cumulative process-lifetime
807
- counter (#70).** Previously, four harmless rejections spread over days
808
- plus a fifth unrelated one would terminate a long-running daemon for
809
- noise; a cluster within a short window is the actual failure-cascade
810
- signal. The stderr format also changed: each rejection log now reads
811
- `Unhandled promise rejection (N in window):` and the exit line reads
812
- `N unhandled rejections within 300s. Exiting...` — operators parsing
813
- these lines should update their match patterns.
814
- - **First-run `getNodeId()` fallback now generates a random ID instead of
815
- hashing the device fingerprint (#71).** Previously, two installs cloned
816
- from the same VM image before either had run evolver would compute
817
- identical node IDs (same hostname / MAC / agent name / cwd → same
818
- SHA-256). The new fallback emits 12 random hex characters; format and
819
- persistence behaviour are unchanged. Existing installs already have a
820
- persisted node ID from a prior run and never re-enter this path, so this
821
- is not a migration. The startup warning text changed accordingly — any
822
- log scraper matching `"Computing node ID from device fingerprint"`
823
- should switch to `"Generating a fresh node ID"`.
824
- - **`engines.node` tightened from `>=22` to `>=22.12`.** This release switches
825
- `src/gep/contentHash.js` from an inlined SHA-256/canonicalize implementation
826
- to a thin facade that `require()`s `@evomap/gep-sdk` (an ESM-only package).
827
- `require()` of synchronous ESM was only unflagged in Node 22.12.0 (Dec
828
- 2024); on 22.0–22.11 the call throws `ERR_REQUIRE_ESM` without
829
- `--experimental-require-module`. Users on those versions must upgrade Node
830
- before installing `@evomap/evolver` 1.84.0+.
831
- - **New runtime dependency: `@evomap/gep-sdk@^1.2.0`.** This package now owns
832
- the GEP protocol surface (schemas, spec, `SCHEMA_VERSION`, `canonicalize`,
833
- `computeAssetId`, `verifyAssetId`) for every implementation in the
834
- ecosystem. Replaces four hand-maintained copies that drove the v1.80.8
835
- `"explore"` enum drift incident. The 13 internal callsites that
836
- `require('./contentHash')` keep working unchanged via a re-export facade.
837
- - **`EVOLVER_ROLLBACK_MODE` default is now `stash` (was `hard`).** Previously a
838
- failed solidify cycle ran `git reset --hard`, silently discarding uncommitted
839
- work in the host repo. The `stash` branch always existed and was the safer
840
- choice; only the default needed flipping. To restore the prior destructive
841
- behaviour explicitly, set `EVOLVER_ROLLBACK_MODE=hard`.
842
-
843
- ### Fixed
844
-
845
- - **`buildAutoGene` no longer emits evolver-internal validation commands when
846
- running in non-evolver host repos.** Auto-generated genes used to embed
847
- `node scripts/validate-modules.js …` and `node scripts/validate-suite.js`,
848
- paths that only exist inside the evolver repo itself. In any third-party host
849
- those scripts are missing, so validation always failed → soft-failure →
850
- rollback discarded the user's working tree. New helper
851
- `isInsideEvolverRepo(repoRoot)` (detected via `package.json` name
852
- `@evomap/evolver`) gates this behaviour; in foreign repos validation falls
853
- back to the portable `git diff --check`.
854
- - **`rollbackNewUntrackedFiles` now skips files whose mtime predates the cycle
855
- start.** The previous baseline-only filter could delete files the user
856
- created in a parallel editor between baseline capture and rollback.
857
- `cycleStartedAt` (sourced from `last_run.created_at`) is now passed through
858
- from the solidify call site as an mtime guard. Behaviour is unchanged when
859
- the parameter is omitted (legacy callers, unit tests).
860
-
861
- ## [1.80.7] - 2026-05-15
862
-
863
- ### Added
864
-
865
- - **Formally declare Node.js >=22 engine requirement (`package.json`).** The
866
- test suite uses `--test-isolation=process` (Node 22+). This field makes the
867
- constraint explicit to package managers and CI environments so they surface
868
- a clear error instead of a cryptic flag-not-found failure.
869
-
870
- ### Fixed
871
-
872
- - **A2A inbound integrity filter: restore symmetric `asset_id` verification.**
873
- `httpTransportReceive` was excluding the `a2a` field when recomputing the
874
- `asset_id` of received assets, creating an asymmetry with the publish path
875
- where `solidify` sets `capsule.a2a` *before* `buildPublishBundle` hashes the
876
- asset. This caused every legitimately published capsule to fail the integrity
877
- check and be silently discarded. The filter now calls `computeAssetId(asset)`
878
- with only `asset_id` excluded (matching the publish-side default), and the
879
- comment clarifies that `lowerConfidence` annotations are applied client-side
880
- *after* verification, not by the Hub on stored assets.
881
-
882
- - **GEP test isolation: scope `--test-isolation=process` to
883
- `solidifyIntegration.test.js` only.** Previously the flag ran across all
884
- test files, which doubled wall-clock time on CI for no benefit. Other test
885
- files run in the same process and share setup cleanly.
886
-
887
- - **HUB_DRY_RUN single source of truth.** Extracted `_isDryRun()` helper in
888
- `a2aProtocol.js`; `publishSkillChannel` in `skill2gep.js` now delegates to
889
- `a2a._isDryRun()` instead of duplicating the env-var check inline.
890
-
891
- ## [1.80.1] - 2026-05-07
892
-
893
- ### Fixed
894
-
895
- - **Validator: stop flooding the Hub with `env_fail` reports when the local
896
- toolchain cannot run `node <script>` (issues #11, #15).** The validator
897
- daemon now runs a one-shot preflight self-test on startup
898
- (`runPreflight()` in `sandboxExecutor.js`) that writes a trivial script to
899
- the sandbox and confirms `spawn('node', [...])` exits with code 0. If the
900
- preflight fails (no `node` on PATH for headless invocations, missing exec
901
- perm, unwritable TMPDIR, ...) the validator role is **skipped** instead of
902
- posting `commands_passed=0, duration_ms=1` reports for every Hub-issued
903
- task. A user-visible warning is printed once with the diagnostic stderr
904
- tail so the operator can fix the host. Restart `evolver` after fixing PATH
905
- to re-enable the validator role. Validator preflight result is exposed via
906
- `getValidatorDaemonStats().preflight`.
907
-
908
- ### Added
909
-
910
- - **Validator report diagnostics: per-command summaries + `failure_class`
911
- on every `ValidationReport` (issues #11, #15).** Each report now carries
912
- a top-level `failure_class` (one of `ok`, `parse_failed`,
913
- `executable_not_allowed`, `sandbox_block_node_flag`, `spawn_failed`,
914
- `timeout`, `exit_nonzero`, `unknown`) and a bounded `commands` array
915
- (capped at 8 entries) where each entry has `cmd`, `ok`, `exit_code`,
916
- `duration_ms`, `timed_out`, `failure_class`, and a 240-char `stderr_tail`.
917
- This lets the Hub-side classifier distinguish "Gene shipped legacy
918
- `node -e \"...\"` and the hardened sandbox rejected it" (a Hub/Gene
919
- incompatibility, route via `sandbox_block_node_flag`) from "validator
920
- host has no `node` binary" (genuine `env_fail`) instead of lumping both
921
- together. Older Hubs that ignore unknown payload fields keep working
922
- unchanged.
923
-
924
- ## [1.80.0] - 2026-05-07
925
-
926
- ### Added
927
-
928
- - **First-party opencode adapter (issue #523).** `evolver setup-hooks
929
- --platform=opencode` now installs a managed plugin at
930
- `.opencode/plugins/evolver.js` plus the three evolver hook scripts in
931
- `.opencode/hooks/`. The plugin wires opencode's `session.created`,
932
- `session.idle`, and `tool.execute.after` events to the existing
933
- `evolver-session-start.js` / `evolver-session-end.js` /
934
- `evolver-signal-detect.js` filters, so the runtime behavior matches
935
- Cursor / Claude Code / Codex / Kiro.
936
-
937
- Works with both project-level (`./.opencode/`) and user-level
938
- (`~/.opencode/`) installs. The plugin file is marked
939
- `_evolver_managed: true` on the first line so `--uninstall` only
940
- removes evolver-managed files and leaves user-authored plugins in
941
- `.opencode/plugins/` alone. Restart opencode after install to load
942
- the plugin.
943
-
944
- See README for the platform table.
945
-
946
- ## [1.79.1] - 2026-05-06
947
-
948
- ### Fixed
949
-
950
- - **Windows cmd-popup loop on suicide-respawn (issue #528).** On Windows,
951
- `child_process.spawn(detached: true, windowsHide: true)` allocates a new
952
- conhost (cmd) window every time -- `windowsHide` is silently ignored in
953
- detached mode, see Node.js child_process docs. So every time the daemon
954
- hit `EVOLVER_MAX_CYCLES` (default 100) or `EVOLVER_MAX_RSS_MB` (default
955
- 500) and ran the suicide-respawn, Windows users saw a new cmd popup.
956
- v1.79.0 made this worse by adding a third spawn site for the cycle
957
- hard-timeout, copying the same buggy options.
958
-
959
- The two in-process spawn sites are now consolidated into one helper
960
- `spawnReplacementProcess()` that, on Windows, defaults to *not*
961
- spawning. Instead the daemon `process.exit(1)`s and lets an external
962
- supervisor restart it. Compatible supervisors:
963
-
964
- - NSSM (Non-Sucking Service Manager).
965
- - pm2-windows-startup.
966
- - Windows Task Scheduler with "On failure: restart" rule.
967
-
968
- Users who explicitly want the in-process respawn (and accept the cmd
969
- popups) can opt back in with `EVOLVER_SUICIDE_WINDOWS=true`.
970
-
971
- No behavior change on macOS/Linux.
972
-
973
- ### Notes on the original report
974
-
975
- The reporter's auto-generated diagnosis attributed a separate symptom
976
- ("`evolver buy/orders/publish` subcommands break the daemon") to lock
977
- contention on `evolver.pid`. That hypothesis is incorrect: subcommands
978
- do not call `acquireLock()` -- only `--loop` does. What the reporter
979
- observed was the same suicide-respawn cycle ending exactly when a
980
- subcommand happened to run, and the new conhost popup made it look like
981
- the subcommand caused it. Fixing the popup also fixes the perceived
982
- "daemon got killed" symptom.
983
-
984
- ## [1.79.0] - 2026-05-06
985
-
986
- ### Fixed
987
-
988
- - **Cycle hard-timeout watchdog (issue #19).** The daemon main loop in
989
- `index.js` previously called `await evolve.run()` with no upper bound,
990
- so a single hung cycle (unclosed socket, stalled LLM call, etc.) could
991
- freeze the process indefinitely. One reporter observed cycle #5372
992
- stuck for 22 days at 0% CPU.
993
-
994
- `evolve.run()` is now wrapped in `Promise.race(evolvePromise,
995
- cycleTimeoutPromise)`. When the timeout fires, the daemon emits a
996
- `[Daemon] Cycle hard-timeout exceeded after Nms (cycle=N, phase=Y)`
997
- diagnostic, force-spawns a replacement process, and exits with code 1
998
- so a host wrapper observes the dead pid.
999
-
1000
- New environment variables (both opt-out, default-on):
1001
-
1002
- - `EVOLVER_CYCLE_TIMEOUT_ENABLED` (default `true`).
1003
- - `EVOLVER_CYCLE_TIMEOUT_MS` (default `2700000`, i.e. 45 minutes).
1004
- - `EVOLVER_PROGRESS_UPDATE_MS` (default `60000`).
1005
-
1006
- ### Added
1007
-
1008
- - **`memory/evolution/cycle_progress.json` heartbeat.** The daemon now
1009
- atomically writes a small JSON heartbeat at cycle start, every 60s
1010
- while `evolve.run()` is in flight, and again at sleep entry. External
1011
- watchdogs can poll `updated_at` to detect a true freeze (the file
1012
- stays stale for >30 minutes only when the inner event loop is
1013
- genuinely hung; normal long LLM cycles still refresh it via the
1014
- 60-second ticker).
1015
-
1016
- Schema:
1017
-
1018
- ```json
1019
- { "pid": 12345, "outer_cycle": 5372, "inner_cycle": 17,
1020
- "started_at": 1746543112000, "phase": "evolve.run|sleep|cycle_timeout_respawn",
1021
- "updated_at": 1746543210000 }
1022
- ```
1023
-
1024
- Regression tests: `test/cycleHardTimeout.test.js` (11 cases) and
1025
- `test/cycleProgressFile.test.js` (4 cases).
1026
-
1027
-
1028
- ## [1.78.9] - 2026-05-04
1029
-
1030
- ### Fixed
1031
-
1032
- - **`AGENT_SESSIONS_DIR` override silently ignored (issue #527).**
1033
- `src/evolve.js` resolved the OpenClaw sessions directory at module
1034
- load time with a hard-coded `os.homedir()/.openclaw/agents/<name>/sessions`
1035
- path, bypassing `process.env.AGENT_SESSIONS_DIR` and
1036
- `EVOLVER_SESSION_SCOPE`. On Windows and any non-standard OpenClaw
1037
- layout (including `D:\openclaw\data\.openclaw\agents\...`), session
1038
- logs were never read and every cycle surfaced
1039
- `[NO SESSION LOGS FOUND]`, forcing the LLM to fall back to its own
1040
- memory and appear to "cycle emptily".
1041
-
1042
- The module-level `AGENT_SESSIONS_DIR` now delegates to
1043
- `getAgentSessionsDir()` from `src/gep/paths.js`, which was already the
1044
- intended single source of truth. `diagnoseSessionSourceEmpty()` reuses
1045
- the same resolution when the caller does not inject a custom
1046
- `homedir` / `agentName` pair, so diagnostic output now matches what
1047
- the runtime actually reads.
1048
-
1049
- `getAgentSessionsDir()` precedence, unchanged:
1050
- 1. `process.env.AGENT_SESSIONS_DIR` (explicit override)
1051
- 2. `workspace-<agent>` prefix in `EVOLVER_SESSION_SCOPE`
1052
- 3. `AGENT_NAME` (defaults to `main`)
1053
-
1054
- Regression test: `test/evolveSessionsDir.test.js` (8 cases).
1055
-
1056
-
1057
- ## [1.78.8] - 2026-05-04
1058
-
1059
- ### Added
1060
-
1061
- - **Built-in memory_graph.jsonl rotation (issue #519).** Long-running
1062
- nodes previously accumulated multi-GB `memory/evolution/memory_graph.jsonl`
1063
- files; one reporter observed 1.8 GB / 378k lines after 48h. Evolver now
1064
- rotates the active file when it crosses a size threshold:
1065
- - `EVOLVER_MEMORY_GRAPH_AUTO_ROTATE` (default `true`) enables rotation.
1066
- - `EVOLVER_MEMORY_GRAPH_MAX_SIZE_MB` (default `100`) triggers rotation.
1067
- - `EVOLVER_MEMORY_GRAPH_RETENTION_COUNT` (default `7`) caps how many
1068
- rotated `memory_graph.jsonl.<ts>.gz` archives are kept on disk.
1069
- - A startup pass rotates an already-oversized file on the next evolver
1070
- start, so pre-existing giant files are handled automatically.
1071
- - Rotation checks are throttled (once every ~30s or every 100 writes)
1072
- so the write path stays cheap. Pruning and gzip compression run
1073
- best-effort and never block the write path.
1074
- - Regression test at `test/memoryGraphRotation.test.js`.
1075
-
1076
-
1077
- ## [1.78.7] - 2026-05-03
1078
-
1079
- ### Fixed
1080
-
1081
- - **`EVOLVER_REPO_ROOT` set in `.env` is now honored (chicken-and-egg fix).**
1082
- `index.js` previously called `getRepoRoot()` to locate the `.env` file
1083
- before `dotenv` had a chance to populate `EVOLVER_REPO_ROOT` from that
1084
- very file, and `getRepoRoot()` cached the `.git`-walk result on first
1085
- call -- so any `EVOLVER_REPO_ROOT` declared in `.env` was silently
1086
- ignored and evolver ran against the wrong repository (often
1087
- `node_modules/@evomap/evolver` itself on local npm installs). Fix:
1088
- - Bootstrap now loads `.env` from `process.cwd()` first (independent
1089
- of any cache) so an `EVOLVER_REPO_ROOT` declared there takes effect
1090
- immediately.
1091
- - `getRepoRoot()` re-reads `process.env.EVOLVER_REPO_ROOT` on every
1092
- call and overrides the cache when the env var is set, so any
1093
- override populated by a later `.env` load propagates cleanly.
1094
- - Regression test at `test/paths.test.js` covers the "env set AFTER
1095
- first getRepoRoot call" path. Reported in #526.
1096
-
1097
-
1098
- ## [1.78.1] - 2026-05-02
1099
-
1100
- ### Fixed
1101
-
1102
- - **Capsule publish payloads now attach a hub-shape `execution_trace` array**
1103
- (`src/gep/solidify.js`). Previously the trace object was only written onto
1104
- the sibling `EvolutionEvent`; the `Capsule` itself went out with no trace,
1105
- so every Capsule on the hub was flagged `trace_empty` and triggered the
1106
- `capsule-trace-enforce] would-reject` alert stream (~530 rejects / 15 min
1107
- in prod). The array form is synthesized from the existing
1108
- `validation.results`, `blast` and `canary` data -- no new data capture --
1109
- with each step carrying `stage` + `cmd` + `exit` to satisfy
1110
- `capsuleTraceQualityService`'s shape check. Anti-pattern publishes
1111
- (`EVOLVER_PUBLISH_ANTI_PATTERNS=true`) get the same treatment.
1112
-
1113
- ### Internal
1114
-
1115
- - New exported helper `buildCapsuleTraceSteps(...)` in `src/gep/solidify.js`
1116
- with a regression test at `test/capsuleExecutionTrace.test.js` that
1117
- replicates the hub's shape gates, so a drift on either end breaks CI before
1118
- it inflates production alerts.
1119
-
1120
- ## [1.78.0] - earlier
1121
-
1122
- - `evolver sync --scope` and `--export .gepx` for full-account asset
1123
- downloads. See `618e451`.