@evomap/evolver 1.89.20 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +27 -564
  2. package/bin/evolver-llm-proxy.js +3 -0
  3. package/bin/evolver-mcp.js +2 -0
  4. package/bin/evolver-proxy.js +3 -0
  5. package/bin/evolver.js +4 -0
  6. package/index.js +1 -3586
  7. package/package.json +32 -62
  8. package/CONTRIBUTING.md +0 -19
  9. package/LICENSE +0 -641
  10. package/README.ja-JP.md +0 -521
  11. package/README.ko-KR.md +0 -520
  12. package/README.zh-CN.md +0 -531
  13. package/SKILL.md +0 -365
  14. package/assets/cover.png +0 -0
  15. package/assets/gep/genes.seed.json +0 -496
  16. package/conformance/savings-core/constants.json +0 -30
  17. package/conformance/savings-core/golden-vectors.json +0 -333
  18. package/scripts/a2a_export.js +0 -63
  19. package/scripts/a2a_ingest.js +0 -79
  20. package/scripts/a2a_promote.js +0 -118
  21. package/scripts/analyze_by_skill.js +0 -121
  22. package/scripts/build_binaries.js +0 -479
  23. package/scripts/check-changelog.js +0 -166
  24. package/scripts/extract_log.js +0 -85
  25. package/scripts/generate_history.js +0 -75
  26. package/scripts/gep_append_event.js +0 -96
  27. package/scripts/gep_personality_report.js +0 -234
  28. package/scripts/human_report.js +0 -147
  29. package/scripts/recall-verify-report.js +0 -234
  30. package/scripts/recover_loop.js +0 -61
  31. package/scripts/refresh_stars_badge.js +0 -168
  32. package/scripts/seed-merchants.js +0 -91
  33. package/scripts/skill2recipes.js +0 -118
  34. package/scripts/suggest_version.js +0 -89
  35. package/scripts/validate-modules.js +0 -38
  36. package/scripts/validate-suite.js +0 -78
  37. package/skills/_meta/SKILL.md +0 -41
  38. package/skills/index.json +0 -14
  39. package/src/adapters/claudeCode.js +0 -194
  40. package/src/adapters/codex.js +0 -216
  41. package/src/adapters/cursor.js +0 -91
  42. package/src/adapters/hookAdapter.js +0 -469
  43. package/src/adapters/kiro.js +0 -195
  44. package/src/adapters/opencode.js +0 -326
  45. package/src/adapters/scripts/_lockPaths.js +0 -74
  46. package/src/adapters/scripts/_memoryFiltering.js +0 -35
  47. package/src/adapters/scripts/_runtimePaths.js +0 -440
  48. package/src/adapters/scripts/evolver-session-end.js +0 -321
  49. package/src/adapters/scripts/evolver-session-start.js +0 -587
  50. package/src/adapters/scripts/evolver-signal-detect.js +0 -98
  51. package/src/adapters/scripts/evolver-task-recall.js +0 -173
  52. package/src/atp/atpExecute.js +0 -283
  53. package/src/atp/atpTaskPickup.js +0 -233
  54. package/src/atp/autoBuyer.js +0 -382
  55. package/src/atp/autoDeliver.js +0 -215
  56. package/src/atp/cli.js +0 -354
  57. package/src/atp/cliAutobuyPrompt.js +0 -154
  58. package/src/atp/consumerAgent.js +0 -157
  59. package/src/atp/defaultHandler.js +0 -69
  60. package/src/atp/heartbeatSignalsHandler.js +0 -254
  61. package/src/atp/hubClient.js +0 -317
  62. package/src/atp/index.js +0 -38
  63. package/src/atp/merchantAgent.js +0 -118
  64. package/src/atp/protocol.js +0 -41
  65. package/src/atp/questionComposer.js +0 -133
  66. package/src/atp/serviceHelper.js +0 -92
  67. package/src/canary.js +0 -13
  68. package/src/config.js +0 -351
  69. package/src/evolve/guards.js +0 -1
  70. package/src/evolve/pipeline/collect.js +0 -1
  71. package/src/evolve/pipeline/dispatch.js +0 -1
  72. package/src/evolve/pipeline/enrich.js +0 -1
  73. package/src/evolve/pipeline/hub.js +0 -1
  74. package/src/evolve/pipeline/select.js +0 -1
  75. package/src/evolve/pipeline/signals.js +0 -1
  76. package/src/evolve/utils.js +0 -1
  77. package/src/evolve.js +0 -1
  78. package/src/experiment/agentRunner.js +0 -229
  79. package/src/experiment/cli.js +0 -159
  80. package/src/experiment/comparison.js +0 -233
  81. package/src/experiment/metrics.js +0 -75
  82. package/src/forceUpdate.js +0 -991
  83. package/src/gep/a2a.js +0 -173
  84. package/src/gep/a2aProtocol.js +0 -1
  85. package/src/gep/analyzer.js +0 -35
  86. package/src/gep/antiAbuseTelemetry.js +0 -1
  87. package/src/gep/assetCallLog.js +0 -197
  88. package/src/gep/assetStore.js +0 -723
  89. package/src/gep/assets.js +0 -36
  90. package/src/gep/autoDistillConv.js +0 -1
  91. package/src/gep/autoDistillLlm.js +0 -1
  92. package/src/gep/bridge.js +0 -138
  93. package/src/gep/candidateEval.js +0 -1
  94. package/src/gep/candidates.js +0 -1
  95. package/src/gep/claimNudge.js +0 -121
  96. package/src/gep/cliContracts.js +0 -1190
  97. package/src/gep/contentHash.js +0 -1
  98. package/src/gep/conversationDistiller.js +0 -1
  99. package/src/gep/conversationSniffer.js +0 -1
  100. package/src/gep/crypto.js +0 -1
  101. package/src/gep/curriculum.js +0 -1
  102. package/src/gep/deviceId.js +0 -1
  103. package/src/gep/directoryClient.js +0 -115
  104. package/src/gep/envFingerprint.js +0 -1
  105. package/src/gep/epigenetics.js +0 -1
  106. package/src/gep/execBridge.js +0 -1
  107. package/src/gep/executionTrace.js +0 -291
  108. package/src/gep/explore.js +0 -1
  109. package/src/gep/featureFlags.js +0 -121
  110. package/src/gep/gitOps.js +0 -265
  111. package/src/gep/hash.js +0 -1
  112. package/src/gep/hostErrorClassifier.js +0 -34
  113. package/src/gep/hubFetch.js +0 -1
  114. package/src/gep/hubReview.js +0 -1
  115. package/src/gep/hubSearch.js +0 -1
  116. package/src/gep/hubVerify.js +0 -1
  117. package/src/gep/idleScheduler.js +0 -400
  118. package/src/gep/issueReporter.js +0 -416
  119. package/src/gep/learningSignals.js +0 -1
  120. package/src/gep/llmReview.js +0 -92
  121. package/src/gep/localStateAwareness.js +0 -243
  122. package/src/gep/mailboxTransport.js +0 -119
  123. package/src/gep/memoryGraph.js +0 -1
  124. package/src/gep/memoryGraphAdapter.js +0 -1
  125. package/src/gep/mutation.js +0 -1
  126. package/src/gep/narrativeMemory.js +0 -1
  127. package/src/gep/oauthLogin.js +0 -181
  128. package/src/gep/openPRRegistry.js +0 -1
  129. package/src/gep/paths.js +0 -522
  130. package/src/gep/personality.js +0 -1
  131. package/src/gep/policyCheck.js +0 -1
  132. package/src/gep/portable.js +0 -103
  133. package/src/gep/privacyClient.js +0 -235
  134. package/src/gep/prompt.js +0 -1
  135. package/src/gep/questionGenerator.js +0 -518
  136. package/src/gep/recallInject.js +0 -1
  137. package/src/gep/recallVerifier.js +0 -1
  138. package/src/gep/reflection.js +0 -1
  139. package/src/gep/sanitize.js +0 -264
  140. package/src/gep/savingsCore.js +0 -1
  141. package/src/gep/schemas/capsule.js +0 -170
  142. package/src/gep/schemas/gene.js +0 -154
  143. package/src/gep/schemas/index.js +0 -8
  144. package/src/gep/schemas/protocol.js +0 -51
  145. package/src/gep/schemas/task.js +0 -74
  146. package/src/gep/selector.js +0 -1
  147. package/src/gep/selfPR.js +0 -469
  148. package/src/gep/signals.js +0 -776
  149. package/src/gep/skill2gep.js +0 -1056
  150. package/src/gep/skill2gepAudit.js +0 -303
  151. package/src/gep/skill2recipes.js +0 -511
  152. package/src/gep/skillDistiller.js +0 -1
  153. package/src/gep/skillPublisher.js +0 -358
  154. package/src/gep/solidify.js +0 -1
  155. package/src/gep/strategy.js +0 -1
  156. package/src/gep/taskReceiver.js +0 -575
  157. package/src/gep/tokenSavings.js +0 -1
  158. package/src/gep/trajectoryExport.js +0 -1
  159. package/src/gep/validationReport.js +0 -55
  160. package/src/gep/validator/index.js +0 -411
  161. package/src/gep/validator/reporter.js +0 -210
  162. package/src/gep/validator/sandboxExecutor.js +0 -480
  163. package/src/gep/validator/stakeBootstrap.js +0 -357
  164. package/src/gep/workspaceKeychain.js +0 -1
  165. package/src/ops/cleanup.js +0 -80
  166. package/src/ops/commentary.js +0 -60
  167. package/src/ops/health_check.js +0 -104
  168. package/src/ops/index.js +0 -11
  169. package/src/ops/innovation.js +0 -67
  170. package/src/ops/lifecycle.js +0 -798
  171. package/src/ops/self_repair.js +0 -76
  172. package/src/ops/skills_monitor.js +0 -147
  173. package/src/ops/trigger.js +0 -33
  174. package/src/proxy/clientSettings.js +0 -405
  175. package/src/proxy/envelope.js +0 -59
  176. package/src/proxy/extensions/dmHandler.js +0 -45
  177. package/src/proxy/extensions/sessionHandler.js +0 -141
  178. package/src/proxy/extensions/skillUpdater.js +0 -64
  179. package/src/proxy/extensions/traceControl.js +0 -1
  180. package/src/proxy/index.js +0 -1395
  181. package/src/proxy/inject.js +0 -1
  182. package/src/proxy/lifecycle/manager.js +0 -1568
  183. package/src/proxy/mailbox/state.js +0 -207
  184. package/src/proxy/mailbox/store.js +0 -590
  185. package/src/proxy/router/cache_passthrough.js +0 -26
  186. package/src/proxy/router/features.js +0 -84
  187. package/src/proxy/router/gemini_route.js +0 -154
  188. package/src/proxy/router/messages_route.js +0 -535
  189. package/src/proxy/router/model_router.js +0 -113
  190. package/src/proxy/router/models_route.js +0 -52
  191. package/src/proxy/router/ollama_route.js +0 -103
  192. package/src/proxy/router/responses_route.js +0 -170
  193. package/src/proxy/router/vertex_route.js +0 -110
  194. package/src/proxy/server/http.js +0 -363
  195. package/src/proxy/server/routes.js +0 -558
  196. package/src/proxy/server/settings.js +0 -115
  197. package/src/proxy/sync/engine.js +0 -179
  198. package/src/proxy/sync/inbound.js +0 -211
  199. package/src/proxy/sync/outbound.js +0 -320
  200. package/src/proxy/task/monitor.js +0 -131
  201. package/src/proxy/trace/extractor.js +0 -1
  202. package/src/proxy/trace/usage.js +0 -1
  203. package/src/webui/client/clientJs/assets.js +0 -111
  204. package/src/webui/client/clientJs/bootstrap.js +0 -92
  205. package/src/webui/client/clientJs/common.js +0 -77
  206. package/src/webui/client/clientJs/i18n.js +0 -366
  207. package/src/webui/client/clientJs/index.js +0 -35
  208. package/src/webui/client/clientJs/interactions.js +0 -351
  209. package/src/webui/client/clientJs/overview.js +0 -152
  210. package/src/webui/client/clientJs/personality.js +0 -285
  211. package/src/webui/client/clientJs/pipelines.js +0 -330
  212. package/src/webui/client/indexHtml.js +0 -221
  213. package/src/webui/client/static.js +0 -23
  214. package/src/webui/client/stylesCss.js +0 -639
  215. package/src/webui/client/vendor/README.md +0 -15
  216. package/src/webui/client/vendor/echarts.min.js +0 -45
  217. package/src/webui/index.js +0 -14
  218. package/src/webui/observer/assets.js +0 -146
  219. package/src/webui/observer/index.js +0 -37
  220. package/src/webui/observer/interactions.js +0 -127
  221. package/src/webui/observer/jsonl.js +0 -75
  222. package/src/webui/observer/paths.js +0 -46
  223. package/src/webui/observer/personality.js +0 -43
  224. package/src/webui/observer/pipelineEvents.js +0 -58
  225. package/src/webui/observer/redact.js +0 -63
  226. package/src/webui/observer/runs.js +0 -356
  227. package/src/webui/observer/safety.js +0 -57
  228. package/src/webui/observer/skills.js +0 -70
  229. package/src/webui/observer/status.js +0 -71
  230. package/src/webui/server/http.js +0 -138
  231. package/src/webui/server/routes.js +0 -41
@@ -1,723 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { getGepAssetsDir, getBundledGepAssetsDir, getRepoRoot, getSessionScope } = require('./paths');
4
- const { computeAssetId, SCHEMA_VERSION } = require('./contentHash');
5
- const { validateGene } = require('./schemas/gene');
6
- const { validateCapsule } = require('./schemas/capsule');
7
-
8
- // Run validateGene/validateCapsule before persisting. Warn-only -- never throw
9
- // because losing a write hurts more than persisting a slightly-malformed
10
- // record. The hub has its own validation gate when the asset is published.
11
- // See issue #30 (H1) for context.
12
- function _validateAssetWarn(label, validatorFn, obj) {
13
- try {
14
- validatorFn(obj);
15
- } catch (e) {
16
- console.warn('[AssetStore] ' + label + ' schema validation warning: ' + (e && e.message || e));
17
- }
18
- }
19
-
20
- function ensureDir(dir) {
21
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
22
- }
23
-
24
- // ---------------------------------------------------------------------------
25
- // File-level advisory locking for JSON read-modify-write operations.
26
- //
27
- // Problem: multiple processes (daemon + CLI script + cron) can each call
28
- // loadGenes() -> mutate -> writeJsonAtomic(), which is safe for a single
29
- // writer but loses updates when two processes interleave their read/write
30
- // windows. writeJsonAtomic is atomic w.r.t. partial writes, not w.r.t. the
31
- // enclosing read-modify-write transaction.
32
- //
33
- // Solution: O_EXCL-based lock file next to the target. Each writer acquires
34
- // the lock, runs its read/update/write, then releases. Stale locks (owner
35
- // PID no longer alive) are detected and reclaimed to avoid deadlock after
36
- // a crash.
37
- //
38
- // Synchronous by design -- all callers (upsertGene, appendCapsule, etc.) are
39
- // synchronous and run on the main loop. We keep the retry loop cheap using a
40
- // short busy-wait bounded by LOCK_TIMEOUT_MS, which is acceptable given lock
41
- // contention is rare in practice (one daemon per machine).
42
- // ---------------------------------------------------------------------------
43
- const LOCK_TIMEOUT_MS = 5000;
44
- const LOCK_RETRY_INTERVAL_MS = 20;
45
-
46
- // Synchronous sleep that parks the main thread without burning CPU.
47
- // Note: this still blocks the event loop (Atomics.wait is sync-blocking
48
- // on the main thread) — heartbeat/ATP/proxy callbacks remain stalled
49
- // during lock contention. The win here is CPU usage, not responsiveness.
50
- // Pattern mirrors policyCheck.sleepSync (src/gep/policyCheck.js:435-443).
51
- function _busyWait(ms) {
52
- const t = Math.max(0, ms);
53
- try {
54
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, t);
55
- } catch (_) {
56
- const end = Date.now() + t;
57
- while (Date.now() < end) { /* busy wait fallback */ }
58
- }
59
- }
60
-
61
- function _acquireLock(targetPath) {
62
- const lockPath = targetPath + '.lock';
63
- const deadline = Date.now() + LOCK_TIMEOUT_MS;
64
- while (Date.now() < deadline) {
65
- try {
66
- fs.writeFileSync(lockPath, String(process.pid), { flag: 'wx', encoding: 'utf8' });
67
- return lockPath;
68
- } catch (e) {
69
- if (e && e.code !== 'EEXIST') throw e;
70
- try {
71
- const pidStr = fs.readFileSync(lockPath, 'utf8').trim();
72
- const ownerPid = parseInt(pidStr, 10);
73
- if (Number.isFinite(ownerPid) && ownerPid > 0 && ownerPid !== process.pid) {
74
- try {
75
- process.kill(ownerPid, 0);
76
- } catch (_ownerErr) {
77
- try { fs.unlinkSync(lockPath); } catch (_e2) {}
78
- continue;
79
- }
80
- }
81
- } catch (_readErr) {}
82
- _busyWait(LOCK_RETRY_INTERVAL_MS);
83
- }
84
- }
85
- throw new Error('[AssetStore] Lock timeout (' + LOCK_TIMEOUT_MS + 'ms) for: ' + targetPath);
86
- }
87
-
88
- function _releaseLock(lockPath) {
89
- if (!lockPath) return;
90
- try { fs.unlinkSync(lockPath); } catch (_) {}
91
- }
92
-
93
- function withFileLock(targetPath, fn) {
94
- const lockPath = _acquireLock(targetPath);
95
- try {
96
- return fn();
97
- } finally {
98
- _releaseLock(lockPath);
99
- }
100
- }
101
-
102
- function readJsonIfExists(filePath, fallback) {
103
- try {
104
- if (!fs.existsSync(filePath)) return fallback;
105
- const raw = fs.readFileSync(filePath, 'utf8');
106
- if (!raw.trim()) return fallback;
107
- return JSON.parse(raw);
108
- } catch (e) {
109
- console.warn('[AssetStore] Failed to read ' + filePath + ':', e && e.message || e);
110
- return fallback;
111
- }
112
- }
113
-
114
- function writeJsonAtomic(filePath, obj) {
115
- const dir = path.dirname(filePath);
116
- ensureDir(dir);
117
- const tmp = `${filePath}.tmp`;
118
- fs.writeFileSync(tmp, JSON.stringify(obj, null, 2) + '\n', 'utf8');
119
- fs.renameSync(tmp, filePath);
120
- }
121
-
122
- // Build a validation command using repo-root-relative paths.
123
- // runValidations() executes with cwd=repoRoot, so require('./src/...')
124
- // resolves correctly without embedding machine-specific absolute paths.
125
- function buildValidationCmd(relModules) {
126
- const paths = relModules.map(m => `./${m}`);
127
- return `node scripts/validate-modules.js ${paths.join(' ')}`;
128
- }
129
-
130
- function getDefaultGenes() {
131
- return {
132
- version: 1,
133
- genes: [
134
- {
135
- type: 'Gene', id: 'gene_gep_repair_from_errors', category: 'repair',
136
- signals_match: [
137
- 'error|错误|异常|エラー|오류',
138
- 'exception|异常|例外|예외',
139
- 'failed|失败|失敗|실패|fail',
140
- 'unstable|不稳定|不安定|불안정',
141
- 'log_error',
142
- 'test_failure',
143
- ],
144
- preconditions: ['signals contains error-related indicators'],
145
- strategy: [
146
- 'Extract structured signals from logs and user instructions',
147
- 'Select an existing Gene by signals match (no improvisation)',
148
- 'Estimate blast radius (files, lines) before editing',
149
- 'Apply smallest reversible patch',
150
- 'Validate using declared validation steps; rollback on failure',
151
- 'Solidify knowledge: append EvolutionEvent, update Gene/Capsule store',
152
- ],
153
- constraints: { max_files: 12, forbidden_paths: ['.git', 'node_modules'] },
154
- validation: [
155
- buildValidationCmd(['src/evolve', 'src/gep/solidify', 'src/gep/policyCheck', 'src/gep/selector', 'src/gep/memoryGraph', 'src/gep/assetStore']),
156
- 'node scripts/validate-suite.js',
157
- ],
158
- },
159
- {
160
- type: 'Gene', id: 'gene_gep_optimize_prompt_and_assets', category: 'optimize',
161
- signals_match: [
162
- 'protocol|协议|プロトコル|프로토콜',
163
- 'gep',
164
- 'prompt|提示词|提示|プロンプト|프롬프트',
165
- 'audit|审计|監査|감사',
166
- 'reusable|可复用|再利用|재사용',
167
- ],
168
- preconditions: ['need stricter, auditable evolution protocol outputs'],
169
- strategy: [
170
- 'Extract signals and determine selection rationale via Selector JSON',
171
- 'Prefer reusing existing Gene/Capsule; only create if no match exists',
172
- 'Refactor prompt assembly to embed assets (genes, capsules, parent event)',
173
- 'Reduce noise and ambiguity; enforce strict output schema',
174
- 'Validate by running node index.js run and ensuring no runtime errors',
175
- 'Solidify: record EvolutionEvent, update Gene definitions, create Capsule on success',
176
- ],
177
- constraints: { max_files: 20, forbidden_paths: ['.git', 'node_modules'] },
178
- validation: [
179
- buildValidationCmd(['src/evolve', 'src/gep/prompt', 'src/gep/contentHash', 'src/gep/skillDistiller']),
180
- 'node scripts/validate-suite.js',
181
- ],
182
- },
183
- {
184
- type: 'Gene', id: 'gene_tool_integrity', category: 'repair',
185
- signals_match: ['tool_bypass|工具绕过|ツール迂回|도구우회'],
186
- preconditions: ['agent used shell/exec to perform an action that a registered tool can handle'],
187
- strategy: [
188
- 'Always prefer registered tools over ad-hoc scripts or shell workarounds',
189
- 'If a registered tool fails, report the actual error honestly and attempt to fix the root cause',
190
- 'Never fabricate explanations -- describe actual actions transparently',
191
- 'Do not create temporary scripts in extension or project directories',
192
- ],
193
- constraints: { max_files: 4, forbidden_paths: ['.git', 'node_modules'] },
194
- validation: [
195
- 'node scripts/validate-suite.js',
196
- ],
197
- anti_patterns: ['tool_bypass'],
198
- routing_hint: { tier: 'cheap', reasoning_level: 'low' },
199
- },
200
- ],
201
- };
202
- }
203
-
204
- function getDefaultCapsules() { return { version: 1, capsules: [] }; }
205
- function genesPath() { return path.join(getGepAssetsDir(), 'genes.json'); }
206
- function genesSeedPath() { return path.join(getBundledGepAssetsDir(), 'genes.seed.json'); }
207
- function bundledGenesPath() { return path.join(getBundledGepAssetsDir(), 'genes.json'); }
208
- function capsulesPath() { return path.join(getGepAssetsDir(), 'capsules.json'); }
209
- function capsulesJsonlPath() { return path.join(getGepAssetsDir(), 'capsules.jsonl'); }
210
- function eventsPath() { return path.join(getGepAssetsDir(), 'events.jsonl'); }
211
- function candidatesPath() { return path.join(getGepAssetsDir(), 'candidates.jsonl'); }
212
- function externalCandidatesPath() { return path.join(getGepAssetsDir(), 'external_candidates.jsonl'); }
213
- function failedCapsulesPath() { return path.join(getGepAssetsDir(), 'failed_capsules.json'); }
214
- function pendingSignalsPath() { return path.join(getGepAssetsDir(), 'pending_signals.json'); }
215
-
216
- // Explicit signal injection channel. A user (or an external tool) can declare
217
- // arbitrary signals out-of-band by writing
218
- // { "signals": ["publish_markdown_to_feishu", ...], "note": "..." }
219
- // to <gep-assets-dir>/pending_signals.json (getGepAssetsDir(), default
220
- // .evolver/gep -- see #166). The signal-extraction stage reads these
221
- // once and injects them into the cycle's signal set, bypassing the closed
222
- // OPPORTUNITY_SIGNALS vocabulary that regex/keyword/LLM extraction is limited
223
- // to. This is how a human-verified, deterministic capability (whose intent no
224
- // extractor would ever map to a first-class signal) gets a chance to select
225
- // its dedicated Gene.
226
- //
227
- // Read-once semantics: the file is consumed (emptied to {signals:[]}) under a
228
- // file lock so the same explicit intent does not re-fire on every subsequent
229
- // cycle. Returns the array of signal strings that were pending (possibly []).
230
- function consumePendingSignals() {
231
- const p = pendingSignalsPath();
232
- if (!fs.existsSync(p)) return [];
233
- return withFileLock(p, () => {
234
- const data = readJsonIfExists(p, { signals: [] });
235
- const raw = data && Array.isArray(data.signals) ? data.signals : [];
236
- const signals = raw
237
- .map(s => (typeof s === 'string' ? s.trim() : ''))
238
- .filter(s => s.length > 0 && s.length <= 200);
239
- // Consume whenever the file held any entries -- even if every entry was
240
- // blank / over-length / non-string and none survived filtering. Otherwise
241
- // a file like {signals:[" "]} is re-read (under lock) on every later
242
- // cycle and read-once never completes. raw.length===0 means nothing to
243
- // consume (no pending signals, an already-emptied file, or a malformed
244
- // file that readJsonIfExists already warned about); skip the write to
245
- // avoid needless churn and to preserve a corrupt file for the user to fix.
246
- if (raw.length > 0) {
247
- // Consume: leave an empty, well-formed file so the next cycle is a no-op.
248
- writeJsonAtomic(p, { signals: [], note: '' });
249
- }
250
- return signals;
251
- });
252
- }
253
-
254
- const LEGACY_RUNTIME_FILENAMES = [
255
- 'genes.json',
256
- 'capsules.json',
257
- 'genes.jsonl',
258
- 'capsules.jsonl',
259
- 'events.jsonl',
260
- 'candidates.jsonl',
261
- 'external_candidates.jsonl',
262
- 'failed_capsules.json',
263
- ];
264
-
265
- function legacyGepAssetsDir() {
266
- const baseDir = path.join(getRepoRoot(), 'assets', 'gep');
267
- const scope = getSessionScope();
268
- if (scope) {
269
- return path.join(baseDir, 'scopes', scope);
270
- }
271
- return baseDir;
272
- }
273
-
274
- function migrateLegacyRuntimeAssets() {
275
- if (process.env.GEP_ASSETS_DIR) return;
276
- const targetDir = getGepAssetsDir();
277
- const legacyDir = legacyGepAssetsDir();
278
- if (path.resolve(targetDir) === path.resolve(legacyDir)) return;
279
- if (!fs.existsSync(legacyDir)) return;
280
-
281
- let copied = 0;
282
- for (const name of LEGACY_RUNTIME_FILENAMES) {
283
- const from = path.join(legacyDir, name);
284
- const to = path.join(targetDir, name);
285
- try {
286
- if (!fs.existsSync(from) || fs.existsSync(to)) continue;
287
- ensureDir(path.dirname(to));
288
- fs.copyFileSync(from, to);
289
- copied++;
290
- } catch (e) {
291
- console.warn('[AssetStore] Failed to migrate legacy GEP asset ' + from + ':', e && e.message || e);
292
- }
293
- }
294
- if (copied > 0) {
295
- console.log('[AssetStore] Migrated ' + copied + ' GEP asset file(s) from ' + legacyDir + ' to ' + targetDir);
296
- }
297
- }
298
-
299
- // First-run seeding: if the user has no local genes.json yet, copy the
300
- // shipped genes.seed.json into place so they start with the curated
301
- // starter genes. Once genes.json exists, it is owned by the user and the
302
- // seed is never re-applied -- this is what keeps `npm i -g @evomap/evolver`
303
- // upgrades from wiping the user's accumulated asset store. See the
304
- // 2026-05-03 regression report from Ruan Chengtao.
305
- function ensureGenesSeeded() {
306
- migrateLegacyRuntimeAssets();
307
- const target = genesPath();
308
- if (fs.existsSync(target)) return;
309
- const seed = fs.existsSync(genesSeedPath()) ? genesSeedPath() : bundledGenesPath();
310
- if (!fs.existsSync(seed)) return;
311
- try {
312
- ensureDir(path.dirname(target));
313
- fs.copyFileSync(seed, target);
314
- console.log('[AssetStore] Seeded ' + target + ' from ' + path.basename(seed));
315
- } catch (e) {
316
- console.warn('[AssetStore] Failed to seed genes.json from seed:', e && e.message || e);
317
- }
318
- }
319
-
320
- function loadGenes() {
321
- ensureGenesSeeded();
322
- const jsonGenes = readJsonIfExists(genesPath(), getDefaultGenes()).genes || [];
323
- const jsonlGenes = [];
324
- try {
325
- const p = path.join(getGepAssetsDir(), 'genes.jsonl');
326
- if (fs.existsSync(p)) {
327
- const raw = fs.readFileSync(p, 'utf8');
328
- raw.split('\n').forEach(line => {
329
- if (line.trim()) {
330
- try {
331
- const parsed = JSON.parse(line);
332
- if (parsed && parsed.type === 'Gene') jsonlGenes.push(parsed);
333
- } catch(e) {}
334
- }
335
- });
336
- }
337
- } catch(e) {
338
- console.warn('[AssetStore] Failed to read genes.jsonl:', e && e.message || e);
339
- }
340
-
341
- // Combine and deduplicate by ID (JSONL takes precedence). Do NOT pass loaded
342
- // genes through createGene() — that would synthesize default fields
343
- // (epigenetic_marks, learning_history, anti_patterns, summary,
344
- // schema_version) on legacy genes that pre-date those fields, which would
345
- // change their content hash and invalidate any previously-computed
346
- // asset_id. Read paths must preserve on-disk gene shapes byte-for-byte;
347
- // callers that need normalized fields should call createGene() explicitly
348
- // (and write back via upsertGene which recomputes asset_id).
349
- const combined = [...jsonGenes, ...jsonlGenes];
350
- const unique = new Map();
351
- combined.forEach(g => {
352
- if (g && g.id) unique.set(String(g.id), g);
353
- });
354
- return Array.from(unique.values());
355
- }
356
-
357
- function loadCapsules() {
358
- const legacy = readJsonIfExists(capsulesPath(), getDefaultCapsules()).capsules || [];
359
- const jsonlCapsules = [];
360
- try {
361
- const p = capsulesJsonlPath();
362
- if (fs.existsSync(p)) {
363
- const raw = fs.readFileSync(p, 'utf8');
364
- raw.split('\n').forEach(line => {
365
- if (line.trim()) {
366
- try { jsonlCapsules.push(JSON.parse(line)); } catch(e) {}
367
- }
368
- });
369
- }
370
- } catch(e) {
371
- console.warn('[AssetStore] Failed to read capsules.jsonl:', e && e.message || e);
372
- }
373
-
374
- // Combine and deduplicate by ID
375
- const combined = [...legacy, ...jsonlCapsules];
376
- const unique = new Map();
377
- combined.forEach(c => {
378
- if (c && c.id) unique.set(String(c.id), c);
379
- });
380
- return Array.from(unique.values());
381
- }
382
-
383
- // Grow the tail chunk until it strictly contains the final newline-terminated
384
- // line, then JSON.parse it. A single event embeds the full ValidationReport
385
- // (up to ~4000 chars stdout + ~4000 chars stderr per command, plus blast
386
- // radius / metadata), so individual lines routinely exceed 4 KB and can reach
387
- // tens of KB. A fixed small chunk would either capture only the truncated
388
- // tail of that JSON line (parse error -> null -> broken parent/child event
389
- // chain) or still straddle the line boundary. Bugbot caught this on PR #34.
390
- const LAST_EVENT_INITIAL_CHUNK = 64 * 1024;
391
- const LAST_EVENT_MAX_CHUNK = 4 * 1024 * 1024;
392
-
393
- function getLastEventId() {
394
- try {
395
- const p = eventsPath();
396
- if (!fs.existsSync(p)) return null;
397
- const stat = fs.statSync(p);
398
- if (stat.size === 0) return null;
399
-
400
- const cap = Math.min(stat.size, LAST_EVENT_MAX_CHUNK);
401
- let chunkSize = Math.min(stat.size, LAST_EVENT_INITIAL_CHUNK);
402
-
403
- const fd = fs.openSync(p, 'r');
404
- try {
405
- while (true) {
406
- const buf = Buffer.alloc(chunkSize);
407
- const readPos = stat.size - chunkSize;
408
- fs.readSync(fd, buf, 0, chunkSize, readPos);
409
-
410
- // Drop the trailing newline(s) the writer appends so lastIndexOf('\n')
411
- // points to the boundary BEFORE the final line, not at end-of-file.
412
- const trimmedTail = buf.toString('utf8').replace(/\n+$/, '');
413
- const lastNl = trimmedTail.lastIndexOf('\n');
414
-
415
- // The chunk fully contains the last line when either we read from the
416
- // start of the file, or we found a newline that bounds the line on
417
- // the left. Otherwise the final line is bigger than the current chunk
418
- // and we must grow.
419
- if (readPos > 0 && lastNl < 0) {
420
- if (chunkSize < cap) {
421
- chunkSize = Math.min(cap, chunkSize * 2);
422
- continue;
423
- }
424
- return null;
425
- }
426
-
427
- const lastLine = (lastNl >= 0 ? trimmedTail.slice(lastNl + 1) : trimmedTail).trim();
428
- if (!lastLine) return null;
429
-
430
- let last;
431
- try {
432
- last = JSON.parse(lastLine);
433
- } catch (parseErr) {
434
- if (chunkSize < cap) {
435
- chunkSize = Math.min(cap, chunkSize * 2);
436
- continue;
437
- }
438
- throw parseErr;
439
- }
440
- return last && typeof last.id === 'string' ? last.id : null;
441
- }
442
- } finally {
443
- fs.closeSync(fd);
444
- }
445
- } catch (e) {
446
- console.warn('[AssetStore] Failed to read last event ID:', e && e.message || e);
447
- return null;
448
- }
449
- }
450
-
451
- // Soft cap on how much of events.jsonl we materialize into memory in one read.
452
- // On long-running daemons the file accumulates thousands of large JSON objects
453
- // (validation reports, blast radius, etc) and the previous full-read could
454
- // allocate dozens of MB per call -- and computeCapsuleSuccessStreak invokes
455
- // this on every successful solidify. Above the threshold we tail-read a chunk
456
- // from EOF and discard the partial leading line, mirroring readRecentCandidates.
457
- // All current callers only look at the recent window
458
- // (signals.js -> slice(-80), guards.js -> slice(-threshold),
459
- // a2a.computeCapsuleSuccessStreak -> backwards scan), so dropping older
460
- // records is acceptable for correctness. Tunable via EVOLVER_EVENTS_FULL_READ_MAX_BYTES.
461
- const EVENTS_FULL_READ_MAX_BYTES_DEFAULT = 2 * 1024 * 1024;
462
- const EVENTS_TAIL_READ_BYTES_DEFAULT = 2 * 1024 * 1024;
463
-
464
- function _eventsFullReadMaxBytes() {
465
- const v = parseInt(String(process.env.EVOLVER_EVENTS_FULL_READ_MAX_BYTES || ''), 10);
466
- return Number.isFinite(v) && v > 0 ? v : EVENTS_FULL_READ_MAX_BYTES_DEFAULT;
467
- }
468
-
469
- function _eventsTailReadBytes() {
470
- const v = parseInt(String(process.env.EVOLVER_EVENTS_TAIL_READ_BYTES || ''), 10);
471
- return Number.isFinite(v) && v > 0 ? v : EVENTS_TAIL_READ_BYTES_DEFAULT;
472
- }
473
-
474
- function readAllEvents() {
475
- try {
476
- const p = eventsPath();
477
- if (!fs.existsSync(p)) return [];
478
- const stat = fs.statSync(p);
479
- const fullReadCap = _eventsFullReadMaxBytes();
480
- if (stat.size <= fullReadCap) {
481
- const raw = fs.readFileSync(p, 'utf8');
482
- return raw.split('\n').map(l => l.trim()).filter(Boolean).map(l => {
483
- try { return JSON.parse(l); } catch { return null; }
484
- }).filter(Boolean);
485
- }
486
- // Large file: tail-read to avoid OOM. Drop the first line ONLY when the
487
- // chunk does not cover the whole file (readPos > 0), because in that case
488
- // it can be cut mid-JSON. When chunkSize === stat.size the read starts at
489
- // 0 and the first line is the actual start-of-file -- discarding it would
490
- // silently lose a complete event. Bugbot caught this on PR #31.
491
- const chunkSize = Math.min(stat.size, _eventsTailReadBytes());
492
- const readPos = stat.size - chunkSize;
493
- const fd = fs.openSync(p, 'r');
494
- try {
495
- const buf = Buffer.alloc(chunkSize);
496
- fs.readSync(fd, buf, 0, chunkSize, readPos);
497
- const lines = buf.toString('utf8').split('\n').map(l => l.trim()).filter(Boolean);
498
- const intact = readPos > 0 && lines.length > 1 ? lines.slice(1) : lines;
499
- return intact.map(l => {
500
- try { return JSON.parse(l); } catch { return null; }
501
- }).filter(Boolean);
502
- } finally {
503
- fs.closeSync(fd);
504
- }
505
- } catch (e) {
506
- console.warn('[AssetStore] Failed to read events.jsonl:', e && e.message || e);
507
- return [];
508
- }
509
- }
510
-
511
- function appendEventJsonl(eventObj) {
512
- const dir = getGepAssetsDir(); ensureDir(dir);
513
- fs.appendFileSync(eventsPath(), JSON.stringify(eventObj) + '\n', 'utf8');
514
- }
515
-
516
- function appendCandidateJsonl(candidateObj) {
517
- const dir = getGepAssetsDir(); ensureDir(dir);
518
- fs.appendFileSync(candidatesPath(), JSON.stringify(candidateObj) + '\n', 'utf8');
519
- }
520
-
521
- function appendExternalCandidateJsonl(obj) {
522
- const dir = getGepAssetsDir(); ensureDir(dir);
523
- fs.appendFileSync(externalCandidatesPath(), JSON.stringify(obj) + '\n', 'utf8');
524
- }
525
-
526
- function readRecentCandidates(limit = 20) {
527
- try {
528
- const p = candidatesPath();
529
- if (!fs.existsSync(p)) return [];
530
- const stat = fs.statSync(p);
531
- if (stat.size < 1024 * 1024) {
532
- const raw = fs.readFileSync(p, 'utf8');
533
- const lines = raw.split('\n').map(l => l.trim()).filter(Boolean);
534
- return lines.slice(-limit).map(l => {
535
- try { return JSON.parse(l); } catch { return null; }
536
- }).filter(Boolean);
537
- }
538
- // Large file (>1MB): only read the tail to avoid OOM.
539
- const fd = fs.openSync(p, 'r');
540
- try {
541
- const chunkSize = Math.min(stat.size, limit * 4096);
542
- const buf = Buffer.alloc(chunkSize);
543
- fs.readSync(fd, buf, 0, chunkSize, stat.size - chunkSize);
544
- const lines = buf.toString('utf8').split('\n').map(l => l.trim()).filter(Boolean);
545
- return lines.slice(-limit).map(l => {
546
- try { return JSON.parse(l); } catch { return null; }
547
- }).filter(Boolean);
548
- } finally {
549
- fs.closeSync(fd);
550
- }
551
- } catch (e) {
552
- console.warn('[AssetStore] Failed to read candidates.jsonl:', e && e.message || e);
553
- return [];
554
- }
555
- }
556
-
557
- function readRecentExternalCandidates(limit = 50) {
558
- try {
559
- const p = externalCandidatesPath();
560
- if (!fs.existsSync(p)) return [];
561
- const stat = fs.statSync(p);
562
- if (stat.size < 1024 * 1024) {
563
- const raw = fs.readFileSync(p, 'utf8');
564
- const lines = raw.split('\n').map(l => l.trim()).filter(Boolean);
565
- return lines.slice(-limit).map(l => {
566
- try { return JSON.parse(l); } catch { return null; }
567
- }).filter(Boolean);
568
- }
569
- const fd = fs.openSync(p, 'r');
570
- try {
571
- const chunkSize = Math.min(stat.size, limit * 4096);
572
- const buf = Buffer.alloc(chunkSize);
573
- fs.readSync(fd, buf, 0, chunkSize, stat.size - chunkSize);
574
- const lines = buf.toString('utf8').split('\n').map(l => l.trim()).filter(Boolean);
575
- return lines.slice(-limit).map(l => {
576
- try { return JSON.parse(l); } catch { return null; }
577
- }).filter(Boolean);
578
- } finally {
579
- fs.closeSync(fd);
580
- }
581
- } catch (e) {
582
- console.warn('[AssetStore] Failed to read external_candidates.jsonl:', e && e.message || e);
583
- return [];
584
- }
585
- }
586
-
587
- // Safety net: ensure schema_version and asset_id are present before writing.
588
- function ensureSchemaFields(obj) {
589
- if (!obj || typeof obj !== 'object') return obj;
590
- if (!obj.schema_version) obj.schema_version = SCHEMA_VERSION;
591
- if (!obj.asset_id) {
592
- try { obj.asset_id = computeAssetId(obj); } catch (e) {
593
- console.warn('[AssetStore] Failed to compute asset ID:', e && e.message || e);
594
- }
595
- }
596
- return obj;
597
- }
598
-
599
- // Recompute asset_id from current content. Called on the Gene write path
600
- // because solidify mutates epigenetic_marks / learning_history in place
601
- // (see src/gep/solidify.js applyEpigeneticMarks + adaptGeneFromLearning),
602
- // which would otherwise leave a stale content hash on disk -- breaking
603
- // content addressing, dedup, and any tamper check that re-verifies
604
- // asset_id against the persisted Gene. See issue #103.
605
- function recomputeAssetId(obj) {
606
- if (!obj || typeof obj !== 'object') return obj;
607
- try {
608
- obj.asset_id = computeAssetId(obj);
609
- } catch (e) {
610
- console.warn('[AssetStore] Failed to recompute asset ID:', e && e.message || e);
611
- }
612
- return obj;
613
- }
614
-
615
- function upsertGene(geneObj) {
616
- _validateAssetWarn('Gene', validateGene, geneObj);
617
- ensureSchemaFields(geneObj);
618
- recomputeAssetId(geneObj);
619
- ensureGenesSeeded();
620
- return withFileLock(genesPath(), () => {
621
- const current = readJsonIfExists(genesPath(), getDefaultGenes());
622
- const genes = Array.isArray(current.genes) ? current.genes : [];
623
- const idx = genes.findIndex(g => g && g.id === geneObj.id);
624
- if (idx >= 0) genes[idx] = geneObj; else genes.push(geneObj);
625
- writeJsonAtomic(genesPath(), { version: current.version || 1, genes });
626
- });
627
- }
628
-
629
- function appendCapsule(capsuleObj) {
630
- _validateAssetWarn('Capsule', validateCapsule, capsuleObj);
631
- ensureSchemaFields(capsuleObj);
632
- return withFileLock(capsulesPath(), () => {
633
- const current = readJsonIfExists(capsulesPath(), getDefaultCapsules());
634
- const capsules = Array.isArray(current.capsules) ? current.capsules : [];
635
- capsules.push(capsuleObj);
636
- writeJsonAtomic(capsulesPath(), { version: current.version || 1, capsules });
637
- });
638
- }
639
-
640
- function upsertCapsule(capsuleObj) {
641
- if (!capsuleObj || capsuleObj.type !== 'Capsule' || !capsuleObj.id) return;
642
- _validateAssetWarn('Capsule', validateCapsule, capsuleObj);
643
- ensureSchemaFields(capsuleObj);
644
- return withFileLock(capsulesPath(), () => {
645
- const current = readJsonIfExists(capsulesPath(), getDefaultCapsules());
646
- const capsules = Array.isArray(current.capsules) ? current.capsules : [];
647
- const idx = capsules.findIndex(c => c && c.type === 'Capsule' && String(c.id) === String(capsuleObj.id));
648
- if (idx >= 0) capsules[idx] = capsuleObj; else capsules.push(capsuleObj);
649
- writeJsonAtomic(capsulesPath(), { version: current.version || 1, capsules });
650
- });
651
- }
652
-
653
- const FAILED_CAPSULES_MAX = 200;
654
- const FAILED_CAPSULES_TRIM_TO = 100;
655
-
656
- function getDefaultFailedCapsules() { return { version: 1, failed_capsules: [] }; }
657
-
658
- function appendFailedCapsule(capsuleObj) {
659
- if (!capsuleObj || typeof capsuleObj !== 'object') return;
660
- ensureSchemaFields(capsuleObj);
661
- return withFileLock(failedCapsulesPath(), () => {
662
- const current = readJsonIfExists(failedCapsulesPath(), getDefaultFailedCapsules());
663
- let list = Array.isArray(current.failed_capsules) ? current.failed_capsules : [];
664
- list.push(capsuleObj);
665
- if (list.length > FAILED_CAPSULES_MAX) {
666
- list = list.slice(list.length - FAILED_CAPSULES_TRIM_TO);
667
- }
668
- writeJsonAtomic(failedCapsulesPath(), { version: current.version || 1, failed_capsules: list });
669
- });
670
- }
671
-
672
- function readRecentFailedCapsules(limit) {
673
- const n = Number.isFinite(Number(limit)) && Number(limit) > 0 ? Number(limit) : 50;
674
- try {
675
- const current = readJsonIfExists(failedCapsulesPath(), getDefaultFailedCapsules());
676
- const list = Array.isArray(current.failed_capsules) ? current.failed_capsules : [];
677
- return list.slice(Math.max(0, list.length - n));
678
- } catch (e) {
679
- console.warn('[AssetStore] Failed to read failed_capsules.json:', e && e.message || e);
680
- return [];
681
- }
682
- }
683
-
684
- // Ensure all expected asset files exist on startup.
685
- // Creates empty files for optional append-only stores so that
686
- // external grep/read commands never fail with "No such file or directory".
687
- function ensureAssetFiles() {
688
- const dir = getGepAssetsDir();
689
- ensureDir(dir);
690
- ensureGenesSeeded();
691
- const files = [
692
- { path: genesPath(), defaultContent: JSON.stringify(getDefaultGenes(), null, 2) + '\n' },
693
- { path: capsulesPath(), defaultContent: JSON.stringify(getDefaultCapsules(), null, 2) + '\n' },
694
- { path: path.join(dir, 'genes.jsonl'), defaultContent: '' },
695
- { path: eventsPath(), defaultContent: '' },
696
- { path: candidatesPath(), defaultContent: '' },
697
- { path: failedCapsulesPath(), defaultContent: JSON.stringify(getDefaultFailedCapsules(), null, 2) + '\n' },
698
- ];
699
- for (const f of files) {
700
- if (!fs.existsSync(f.path)) {
701
- try {
702
- fs.writeFileSync(f.path, f.defaultContent, 'utf8');
703
- } catch (e) {
704
- // Non-fatal: log but continue
705
- console.error(`[AssetStore] Failed to create ${f.path}: ${e.message}`);
706
- }
707
- }
708
- }
709
- }
710
-
711
- module.exports = {
712
- loadGenes, loadCapsules, readAllEvents, getLastEventId,
713
- appendEventJsonl, appendCandidateJsonl, appendExternalCandidateJsonl,
714
- readRecentCandidates, readRecentExternalCandidates,
715
- upsertGene, appendCapsule, upsertCapsule,
716
- appendFailedCapsule, readRecentFailedCapsules,
717
- genesPath, capsulesPath, eventsPath, candidatesPath, externalCandidatesPath, failedCapsulesPath,
718
- pendingSignalsPath, consumePendingSignals,
719
- genesSeedPath, bundledGenesPath, ensureGenesSeeded, migrateLegacyRuntimeAssets,
720
- ensureAssetFiles, buildValidationCmd,
721
- withFileLock,
722
- readJsonIfExists,
723
- };