@evomap/evolver 1.89.3 → 1.89.4

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